aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml41
-rw-r--r--BUILD.md20
-rw-r--r--CMakeLists.txt171
-rw-r--r--COPYING.md13
-rw-r--r--README.md8
-rw-r--r--api/gui/CMakeLists.txt7
-rw-r--r--api/gui/SkinUtils.cpp2
-rw-r--r--api/gui/SkinUtils.h2
-rw-r--r--api/gui/icons/IconList.cpp25
-rw-r--r--api/gui/icons/IconList.h4
-rw-r--r--api/gui/icons/MMCIcon.cpp2
-rw-r--r--api/gui/icons/MMCIcon.h2
-rw-r--r--api/logic/BaseInstaller.cpp10
-rw-r--r--api/logic/BaseInstaller.h12
-rw-r--r--api/logic/BaseInstance.cpp5
-rw-r--r--api/logic/BaseInstance.h45
-rw-r--r--api/logic/BaseInstanceProvider.h2
-rw-r--r--api/logic/BaseVersion.h2
-rw-r--r--api/logic/BaseVersionList.cpp2
-rw-r--r--api/logic/BaseVersionList.h2
-rw-r--r--api/logic/CMakeLists.txt91
-rw-r--r--api/logic/Commandline.cpp2
-rw-r--r--api/logic/Commandline.h2
-rw-r--r--api/logic/Env.cpp32
-rw-r--r--api/logic/Env.h11
-rw-r--r--api/logic/FileSystem.cpp64
-rw-r--r--api/logic/FileSystem.h5
-rw-r--r--api/logic/FolderInstanceProvider.cpp207
-rw-r--r--api/logic/FolderInstanceProvider.h9
-rw-r--r--api/logic/InstanceCopyTask.cpp12
-rw-r--r--api/logic/InstanceCopyTask.h3
-rw-r--r--api/logic/InstanceCreationTask.cpp39
-rw-r--r--api/logic/InstanceCreationTask.h6
-rw-r--r--api/logic/InstanceImportTask.cpp206
-rw-r--r--api/logic/InstanceImportTask.h17
-rw-r--r--api/logic/InstanceList.cpp17
-rw-r--r--api/logic/InstanceList.h9
-rw-r--r--api/logic/LoggedProcess.h2
-rw-r--r--api/logic/MMCZip.cpp6
-rw-r--r--api/logic/MMCZip.h4
-rw-r--r--api/logic/NullInstance.h42
-rw-r--r--api/logic/ProblemProvider.h34
-rw-r--r--api/logic/Version_test.cpp2
-rw-r--r--api/logic/java/JavaChecker.cpp2
-rw-r--r--api/logic/java/JavaCheckerJob.cpp7
-rw-r--r--api/logic/java/JavaCheckerJob.h36
-rw-r--r--api/logic/java/JavaInstallList.cpp7
-rw-r--r--api/logic/java/JavaInstallList.h4
-rw-r--r--api/logic/java/JavaUtils.cpp95
-rw-r--r--api/logic/java/JavaUtils.h5
-rw-r--r--api/logic/java/JavaVersion.cpp13
-rw-r--r--api/logic/java/launch/CheckJava.cpp2
-rw-r--r--api/logic/java/launch/CheckJava.h2
-rw-r--r--api/logic/launch/LaunchStep.cpp2
-rw-r--r--api/logic/launch/LaunchStep.h2
-rw-r--r--api/logic/launch/LaunchTask.cpp4
-rw-r--r--api/logic/launch/LaunchTask.h2
-rw-r--r--api/logic/launch/LogModel.cpp18
-rw-r--r--api/logic/launch/LogModel.h6
-rw-r--r--api/logic/launch/steps/PostLaunchCommand.cpp2
-rw-r--r--api/logic/launch/steps/PostLaunchCommand.h2
-rw-r--r--api/logic/launch/steps/PreLaunchCommand.cpp2
-rw-r--r--api/logic/launch/steps/PreLaunchCommand.h2
-rw-r--r--api/logic/launch/steps/TextPrint.h2
-rw-r--r--api/logic/launch/steps/Update.cpp6
-rw-r--r--api/logic/launch/steps/Update.h6
-rw-r--r--api/logic/meta/BaseEntity.cpp10
-rw-r--r--api/logic/meta/BaseEntity.h6
-rw-r--r--api/logic/meta/Index.cpp6
-rw-r--r--api/logic/meta/Index.h4
-rw-r--r--api/logic/meta/JsonFormat.cpp150
-rw-r--r--api/logic/meta/JsonFormat.h45
-rw-r--r--api/logic/meta/Version.cpp55
-rw-r--r--api/logic/meta/Version.h26
-rw-r--r--api/logic/meta/VersionList.cpp71
-rw-r--r--api/logic/meta/VersionList.h11
-rw-r--r--api/logic/minecraft/AssetsUtils.cpp2
-rw-r--r--api/logic/minecraft/AssetsUtils.h2
-rw-r--r--api/logic/minecraft/Component.cpp439
-rw-r--r--api/logic/minecraft/Component.h111
-rw-r--r--api/logic/minecraft/ComponentList.cpp1204
-rw-r--r--api/logic/minecraft/ComponentList.h146
-rw-r--r--api/logic/minecraft/ComponentList_p.h42
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.cpp691
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.h37
-rw-r--r--api/logic/minecraft/ComponentUpdateTask_p.h32
-rw-r--r--api/logic/minecraft/LaunchProfile.cpp297
-rw-r--r--api/logic/minecraft/LaunchProfile.h99
-rw-r--r--api/logic/minecraft/Library.cpp74
-rw-r--r--api/logic/minecraft/Library_test.cpp8
-rw-r--r--api/logic/minecraft/MinecraftInstance.cpp620
-rw-r--r--api/logic/minecraft/MinecraftInstance.h120
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.cpp45
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.h47
-rw-r--r--api/logic/minecraft/MinecraftProfile.cpp681
-rw-r--r--api/logic/minecraft/MinecraftProfile.h211
-rw-r--r--api/logic/minecraft/MinecraftUpdate.cpp (renamed from api/logic/minecraft/onesix/OneSixUpdate.cpp)83
-rw-r--r--api/logic/minecraft/MinecraftUpdate.h (renamed from api/logic/minecraft/onesix/OneSixUpdate.h)10
-rw-r--r--api/logic/minecraft/Mod.cpp2
-rw-r--r--api/logic/minecraft/Mod.h5
-rw-r--r--api/logic/minecraft/ModList.cpp2
-rw-r--r--api/logic/minecraft/ModList.h2
-rw-r--r--api/logic/minecraft/MojangVersionFormat.cpp2
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.cpp (renamed from api/logic/minecraft/onesix/OneSixVersionFormat.cpp)56
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.h (renamed from api/logic/minecraft/onesix/OneSixVersionFormat.h)4
-rw-r--r--api/logic/minecraft/OpSys.cpp2
-rw-r--r--api/logic/minecraft/OpSys.h2
-rw-r--r--api/logic/minecraft/ProfilePatch.cpp188
-rw-r--r--api/logic/minecraft/ProfilePatch.h70
-rw-r--r--api/logic/minecraft/ProfileStrategy.h36
-rw-r--r--api/logic/minecraft/ProfileUtils.cpp53
-rw-r--r--api/logic/minecraft/ProfileUtils.h3
-rw-r--r--api/logic/minecraft/Rule.cpp2
-rw-r--r--api/logic/minecraft/Rule.h2
-rw-r--r--api/logic/minecraft/SkinUpload.cpp4
-rw-r--r--api/logic/minecraft/SkinUpload.h4
-rw-r--r--api/logic/minecraft/VersionFile.cpp4
-rw-r--r--api/logic/minecraft/VersionFile.h28
-rw-r--r--api/logic/minecraft/World.cpp4
-rw-r--r--api/logic/minecraft/World.h2
-rw-r--r--api/logic/minecraft/WorldList.cpp2
-rw-r--r--api/logic/minecraft/WorldList.h2
-rw-r--r--api/logic/minecraft/auth/MojangAccount.cpp29
-rw-r--r--api/logic/minecraft/auth/MojangAccount.h2
-rw-r--r--api/logic/minecraft/auth/MojangAccountList.cpp75
-rw-r--r--api/logic/minecraft/auth/MojangAccountList.h2
-rw-r--r--api/logic/minecraft/auth/YggdrasilTask.cpp3
-rw-r--r--api/logic/minecraft/auth/YggdrasilTask.h2
-rw-r--r--api/logic/minecraft/auth/flows/AuthenticateTask.cpp2
-rw-r--r--api/logic/minecraft/auth/flows/AuthenticateTask.h2
-rw-r--r--api/logic/minecraft/auth/flows/RefreshTask.cpp2
-rw-r--r--api/logic/minecraft/auth/flows/RefreshTask.h2
-rw-r--r--api/logic/minecraft/auth/flows/ValidateTask.cpp2
-rw-r--r--api/logic/minecraft/auth/flows/ValidateTask.h2
-rw-r--r--api/logic/minecraft/flame/FileResolvingTask.cpp46
-rw-r--r--api/logic/minecraft/flame/PackManifest.cpp1
-rw-r--r--api/logic/minecraft/flame/PackManifest.h15
-rw-r--r--api/logic/minecraft/forge/ForgeXzDownload.cpp2
-rw-r--r--api/logic/minecraft/forge/ForgeXzDownload.h2
-rw-r--r--api/logic/minecraft/ftb/FTBInstanceProvider.cpp262
-rw-r--r--api/logic/minecraft/ftb/FTBInstanceProvider.h45
-rw-r--r--api/logic/minecraft/ftb/FTBPlugin.cpp115
-rw-r--r--api/logic/minecraft/ftb/FTBPlugin.h12
-rw-r--r--api/logic/minecraft/ftb/FTBProfileStrategy.cpp129
-rw-r--r--api/logic/minecraft/ftb/FTBProfileStrategy.h21
-rw-r--r--api/logic/minecraft/ftb/LegacyFTBInstance.cpp24
-rw-r--r--api/logic/minecraft/ftb/LegacyFTBInstance.h17
-rw-r--r--api/logic/minecraft/ftb/OneSixFTBInstance.cpp135
-rw-r--r--api/logic/minecraft/ftb/OneSixFTBInstance.h30
-rw-r--r--api/logic/minecraft/launch/ClaimAccount.h2
-rw-r--r--api/logic/minecraft/launch/CreateServerResourcePacksFolder.h2
-rw-r--r--api/logic/minecraft/launch/DirectJavaLaunch.cpp2
-rw-r--r--api/logic/minecraft/launch/DirectJavaLaunch.h2
-rw-r--r--api/logic/minecraft/launch/ExtractNatives.cpp2
-rw-r--r--api/logic/minecraft/launch/ExtractNatives.h2
-rw-r--r--api/logic/minecraft/launch/LauncherPartLaunch.cpp67
-rw-r--r--api/logic/minecraft/launch/LauncherPartLaunch.h2
-rw-r--r--api/logic/minecraft/launch/ModMinecraftJar.cpp70
-rw-r--r--api/logic/minecraft/launch/ModMinecraftJar.h9
-rw-r--r--api/logic/minecraft/launch/PrintInstanceInfo.cpp70
-rw-r--r--api/logic/minecraft/launch/PrintInstanceInfo.h2
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.cpp359
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.h115
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.cpp533
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.h112
-rw-r--r--api/logic/minecraft/legacy/LegacyUpdate.cpp399
-rw-r--r--api/logic/minecraft/legacy/LegacyUpdate.h70
-rw-r--r--api/logic/minecraft/legacy/LegacyUpgradeTask.cpp142
-rw-r--r--api/logic/minecraft/legacy/LegacyUpgradeTask.h38
-rw-r--r--api/logic/minecraft/legacy/LwjglVersionList.cpp169
-rw-r--r--api/logic/minecraft/legacy/LwjglVersionList.h116
-rw-r--r--api/logic/minecraft/onesix/OneSixInstance.cpp698
-rw-r--r--api/logic/minecraft/onesix/OneSixInstance.h127
-rw-r--r--api/logic/minecraft/onesix/OneSixProfileStrategy.cpp407
-rw-r--r--api/logic/minecraft/onesix/OneSixProfileStrategy.h26
-rw-r--r--api/logic/minecraft/update/AssetUpdateTask.cpp (renamed from api/logic/minecraft/onesix/update/AssetUpdateTask.cpp)11
-rw-r--r--api/logic/minecraft/update/AssetUpdateTask.h (renamed from api/logic/minecraft/onesix/update/AssetUpdateTask.h)7
-rw-r--r--api/logic/minecraft/update/FMLLibrariesTask.cpp (renamed from api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp)21
-rw-r--r--api/logic/minecraft/update/FMLLibrariesTask.h (renamed from api/logic/minecraft/onesix/update/FMLLibrariesTask.h)7
-rw-r--r--api/logic/minecraft/update/FoldersTask.cpp (renamed from api/logic/minecraft/onesix/update/FoldersTask.cpp)5
-rw-r--r--api/logic/minecraft/update/FoldersTask.h (renamed from api/logic/minecraft/onesix/update/FoldersTask.h)7
-rw-r--r--api/logic/minecraft/update/LibrariesTask.cpp (renamed from api/logic/minecraft/onesix/update/LibrariesTask.cpp)16
-rw-r--r--api/logic/minecraft/update/LibrariesTask.h (renamed from api/logic/minecraft/onesix/update/LibrariesTask.h)7
-rw-r--r--api/logic/modplatform/FtbPackDownloader.cpp106
-rw-r--r--api/logic/modplatform/FtbPackDownloader.h63
-rw-r--r--api/logic/modplatform/FtbPackFetchTask.cpp75
-rw-r--r--api/logic/modplatform/FtbPackFetchTask.h34
-rw-r--r--api/logic/modplatform/FtbPackInstallTask.cpp65
-rw-r--r--api/logic/modplatform/FtbPackInstallTask.h45
-rw-r--r--api/logic/modplatform/PackHelpers.h21
-rw-r--r--api/logic/net/Download.cpp74
-rw-r--r--api/logic/net/Download.h3
-rw-r--r--api/logic/net/HttpMetaCache.cpp2
-rw-r--r--api/logic/net/HttpMetaCache.h2
-rw-r--r--api/logic/net/Mode.h10
-rw-r--r--api/logic/net/NetAction.h73
-rw-r--r--api/logic/net/NetJob.cpp84
-rw-r--r--api/logic/net/NetJob.h41
-rw-r--r--api/logic/net/PasteUpload.cpp43
-rw-r--r--api/logic/net/PasteUpload.h4
-rw-r--r--api/logic/net/URLConstants.h2
-rw-r--r--api/logic/news/NewsChecker.cpp2
-rw-r--r--api/logic/news/NewsChecker.h2
-rw-r--r--api/logic/news/NewsEntry.cpp2
-rw-r--r--api/logic/news/NewsEntry.h2
-rw-r--r--api/logic/settings/INIFile.cpp2
-rw-r--r--api/logic/settings/INIFile.h2
-rw-r--r--api/logic/settings/INISettingsObject.cpp2
-rw-r--r--api/logic/settings/INISettingsObject.h2
-rw-r--r--api/logic/settings/OverrideSetting.cpp2
-rw-r--r--api/logic/settings/OverrideSetting.h2
-rw-r--r--api/logic/settings/PassthroughSetting.cpp2
-rw-r--r--api/logic/settings/PassthroughSetting.h2
-rw-r--r--api/logic/settings/Setting.cpp2
-rw-r--r--api/logic/settings/Setting.h2
-rw-r--r--api/logic/settings/SettingsObject.cpp2
-rw-r--r--api/logic/settings/SettingsObject.h2
-rw-r--r--api/logic/status/StatusChecker.cpp2
-rw-r--r--api/logic/status/StatusChecker.h2
-rw-r--r--api/logic/tasks/Task.cpp68
-rw-r--r--api/logic/tasks/Task.h32
-rw-r--r--api/logic/tasks/ThreadTask.cpp41
-rw-r--r--api/logic/tasks/ThreadTask.h26
-rw-r--r--api/logic/tools/JVisualVM.cpp3
-rw-r--r--api/logic/translations/TranslationsModel.h2
-rw-r--r--api/logic/updater/DownloadTask.cpp6
-rw-r--r--api/logic/updater/DownloadTask.h2
-rw-r--r--api/logic/updater/UpdateChecker.cpp2
-rw-r--r--api/logic/updater/UpdateChecker.h2
-rw-r--r--application/CMakeLists.txt226
-rw-r--r--application/ColumnResizer.cpp2
-rw-r--r--application/GuiUtil.cpp23
-rw-r--r--application/GuiUtil.h1
-rw-r--r--application/HoeDown.h2
-rw-r--r--application/InstancePageProvider.h16
-rw-r--r--application/InstanceWindow.cpp30
-rw-r--r--application/InstanceWindow.h7
-rw-r--r--application/LaunchController.cpp16
-rw-r--r--application/MainWindow.cpp787
-rw-r--r--application/MainWindow.h21
-rw-r--r--application/MultiMC.cpp879
-rw-r--r--application/MultiMC.h28
-rw-r--r--application/VersionProxyModel.cpp18
-rw-r--r--application/VersionProxyModel.h1
-rw-r--r--application/dialogs/AboutDialog.cpp4
-rw-r--r--application/dialogs/AboutDialog.h2
-rw-r--r--application/dialogs/AboutDialog.ui4
-rw-r--r--application/dialogs/ChooseFtbPackDialog.cpp64
-rw-r--r--application/dialogs/ChooseFtbPackDialog.h32
-rw-r--r--application/dialogs/ChooseFtbPackDialog.ui163
-rw-r--r--application/dialogs/CopyInstanceDialog.cpp2
-rw-r--r--application/dialogs/CopyInstanceDialog.h2
-rw-r--r--application/dialogs/CustomMessageBox.cpp2
-rw-r--r--application/dialogs/CustomMessageBox.h2
-rw-r--r--application/dialogs/EditAccountDialog.cpp2
-rw-r--r--application/dialogs/EditAccountDialog.h2
-rw-r--r--application/dialogs/ExportInstanceDialog.cpp6
-rw-r--r--application/dialogs/ExportInstanceDialog.h2
-rw-r--r--application/dialogs/IconPickerDialog.cpp22
-rw-r--r--application/dialogs/IconPickerDialog.h3
-rw-r--r--application/dialogs/LoginDialog.cpp2
-rw-r--r--application/dialogs/LoginDialog.h2
-rw-r--r--application/dialogs/NewComponentDialog.cpp106
-rw-r--r--application/dialogs/NewComponentDialog.h48
-rw-r--r--application/dialogs/NewComponentDialog.ui101
-rw-r--r--application/dialogs/NewInstanceDialog.cpp65
-rw-r--r--application/dialogs/NewInstanceDialog.h16
-rw-r--r--application/dialogs/NewInstanceDialog.ui129
-rw-r--r--application/dialogs/NotificationDialog.cpp1
-rw-r--r--application/dialogs/ProfileSelectDialog.cpp2
-rw-r--r--application/dialogs/ProfileSelectDialog.h2
-rw-r--r--application/dialogs/ProgressDialog.cpp4
-rw-r--r--application/dialogs/ProgressDialog.h2
-rw-r--r--application/dialogs/SkinUploadDialog.cpp61
-rw-r--r--application/dialogs/UpdateDialog.cpp6
-rw-r--r--application/dialogs/UpdateDialog.h2
-rw-r--r--application/dialogs/VersionSelectDialog.cpp8
-rw-r--r--application/dialogs/VersionSelectDialog.h5
-rw-r--r--application/groupview/GroupView.cpp67
-rw-r--r--application/groupview/GroupView.h4
-rw-r--r--application/groupview/GroupedProxyModel.cpp2
-rw-r--r--application/groupview/GroupedProxyModel.h2
-rw-r--r--application/groupview/InstanceDelegate.cpp2
-rw-r--r--application/groupview/InstanceDelegate.h2
-rw-r--r--application/groupview/VisualGroup.cpp2
-rw-r--r--application/groupview/VisualGroup.h2
-rw-r--r--application/install_prereqs.cmake.in2
-rw-r--r--application/main.cpp1
-rwxr-xr-xapplication/package/linux/multimc.desktop3
-rw-r--r--application/pagedialog/PageDialog.cpp2
-rw-r--r--application/pagedialog/PageDialog.h2
-rw-r--r--application/pages/BasePage.h10
-rw-r--r--application/pages/BasePageContainer.h1
-rw-r--r--application/pages/BasePageProvider.h2
-rw-r--r--application/pages/InstanceSettingsPage.cpp57
-rw-r--r--application/pages/InstanceSettingsPage.h2
-rw-r--r--application/pages/InstanceSettingsPage.ui109
-rw-r--r--application/pages/LegacyJarModPage.cpp162
-rw-r--r--application/pages/LegacyJarModPage.h76
-rw-r--r--application/pages/LegacyJarModPage.ui162
-rw-r--r--application/pages/LegacyUpgradePage.cpp24
-rw-r--r--application/pages/LegacyUpgradePage.h14
-rw-r--r--application/pages/LegacyUpgradePage.ui12
-rw-r--r--application/pages/LogPage.cpp65
-rw-r--r--application/pages/LogPage.h11
-rw-r--r--application/pages/ModFolderPage.cpp13
-rw-r--r--application/pages/ModFolderPage.h4
-rw-r--r--application/pages/NotesPage.h2
-rw-r--r--application/pages/OtherLogsPage.cpp68
-rw-r--r--application/pages/OtherLogsPage.h7
-rw-r--r--application/pages/OtherLogsPage.ui76
-rw-r--r--application/pages/ScreenshotsPage.cpp2
-rw-r--r--application/pages/ScreenshotsPage.h2
-rw-r--r--application/pages/ScreenshotsPage.ui2
-rw-r--r--application/pages/VersionPage.cpp203
-rw-r--r--application/pages/VersionPage.h24
-rw-r--r--application/pages/VersionPage.ui34
-rw-r--r--application/pages/WorldListPage.cpp2
-rw-r--r--application/pages/WorldListPage.h4
-rw-r--r--application/pages/global/AccountListPage.cpp2
-rw-r--r--application/pages/global/AccountListPage.h2
-rw-r--r--application/pages/global/CustomCommandsPage.cpp50
-rw-r--r--application/pages/global/CustomCommandsPage.h55
-rw-r--r--application/pages/global/ExternalToolsPage.cpp6
-rw-r--r--application/pages/global/ExternalToolsPage.h2
-rw-r--r--application/pages/global/ExternalToolsPage.ui6
-rw-r--r--application/pages/global/JavaPage.cpp52
-rw-r--r--application/pages/global/JavaPage.h2
-rw-r--r--application/pages/global/JavaPage.ui79
-rw-r--r--application/pages/global/MinecraftPage.cpp2
-rw-r--r--application/pages/global/MinecraftPage.h2
-rw-r--r--application/pages/global/MultiMCPage.cpp86
-rw-r--r--application/pages/global/MultiMCPage.h6
-rw-r--r--application/pages/global/MultiMCPage.ui109
-rw-r--r--application/pages/global/PackagesPage.cpp25
-rw-r--r--application/pages/global/PackagesPage.h2
-rw-r--r--application/pages/global/PasteEEPage.cpp10
-rw-r--r--application/pages/global/PasteEEPage.h2
-rw-r--r--application/pages/global/PasteEEPage.ui16
-rw-r--r--application/pages/global/ProxyPage.cpp2
-rw-r--r--application/pages/global/ProxyPage.h2
-rw-r--r--application/resources/MultiMC.icnsbin437319 -> 782703 bytes
-rw-r--r--application/resources/MultiMC.icobin76126 -> 85182 bytes
-rw-r--r--application/resources/OSX/OSX.qrc2
-rw-r--r--application/resources/OSX/scalable/help.svg51
-rw-r--r--application/resources/OSX/scalable/worlds.svg58
-rw-r--r--application/resources/flat/flat.qrc44
-rw-r--r--application/resources/flat/index.theme11
-rw-r--r--application/resources/flat/scalable/about.svg3
-rw-r--r--application/resources/flat/scalable/accounts.svg3
-rw-r--r--application/resources/flat/scalable/bug.svg3
-rw-r--r--application/resources/flat/scalable/cat.svg3
-rw-r--r--application/resources/flat/scalable/centralmods.svg3
-rw-r--r--application/resources/flat/scalable/checkupdate.svg3
-rw-r--r--application/resources/flat/scalable/copy.svg3
-rw-r--r--application/resources/flat/scalable/coremods.svg3
-rw-r--r--application/resources/flat/scalable/discord.svg4
-rw-r--r--application/resources/flat/scalable/externaltools.svg3
-rw-r--r--application/resources/flat/scalable/help.svg17
-rw-r--r--application/resources/flat/scalable/instance-settings.svg3
-rw-r--r--application/resources/flat/scalable/jarmods.svg3
-rw-r--r--application/resources/flat/scalable/java.svg3
-rw-r--r--application/resources/flat/scalable/loadermods.svg3
-rw-r--r--application/resources/flat/scalable/log.svg3
-rw-r--r--application/resources/flat/scalable/minecraft.svg3
-rw-r--r--application/resources/flat/scalable/multimc.svg3
-rw-r--r--application/resources/flat/scalable/new.svg3
-rw-r--r--application/resources/flat/scalable/news.svg3
-rw-r--r--application/resources/flat/scalable/notes.svg3
-rw-r--r--application/resources/flat/scalable/packages.svg3
-rw-r--r--application/resources/flat/scalable/patreon.svg3
-rw-r--r--application/resources/flat/scalable/proxy.svg3
-rw-r--r--application/resources/flat/scalable/quickmods.svg3
-rw-r--r--application/resources/flat/scalable/reddit-alien.svg3
-rw-r--r--application/resources/flat/scalable/refresh.svg3
-rw-r--r--application/resources/flat/scalable/resourcepacks.svg3
-rw-r--r--application/resources/flat/scalable/screenshot-placeholder.svg3
-rw-r--r--application/resources/flat/scalable/screenshots.svg3
-rw-r--r--application/resources/flat/scalable/settings.svg3
-rw-r--r--application/resources/flat/scalable/star.svg3
-rw-r--r--application/resources/flat/scalable/status-bad.svg3
-rw-r--r--application/resources/flat/scalable/status-good.svg3
-rw-r--r--application/resources/flat/scalable/status-running.svg3
-rw-r--r--application/resources/flat/scalable/status-yellow.svg3
-rw-r--r--application/resources/flat/scalable/viewfolder.svg3
-rw-r--r--application/resources/flat/scalable/worlds.svg3
-rw-r--r--application/resources/iOS/iOS.qrc2
-rw-r--r--application/resources/iOS/scalable/help.svg38
-rw-r--r--application/resources/iOS/scalable/worlds.svg44
-rw-r--r--application/resources/multimc/128x128/instances/infinity.pngbin9237 -> 15151 bytes
-rw-r--r--application/resources/multimc/multimc.qrc8
-rw-r--r--application/resources/multimc/scalable/custom-commands.svg338
-rw-r--r--application/resources/multimc/scalable/logo.svg353
-rw-r--r--application/resources/multimc/scalable/multimc.svg2036
-rw-r--r--application/resources/pe_blue/pe_blue.qrc2
-rw-r--r--application/resources/pe_blue/scalable/help.svg40
-rw-r--r--application/resources/pe_blue/scalable/worlds.svg63
-rw-r--r--application/resources/pe_colored/pe_colored.qrc2
-rw-r--r--application/resources/pe_colored/scalable/help.svg46
-rw-r--r--application/resources/pe_colored/scalable/worlds.svg50
-rw-r--r--application/resources/pe_dark/pe_dark.qrc2
-rw-r--r--application/resources/pe_dark/scalable/help.svg34
-rw-r--r--application/resources/pe_dark/scalable/worlds.svg63
-rw-r--r--application/resources/pe_light/pe_light.qrc2
-rw-r--r--application/resources/pe_light/scalable/help.svg36
-rw-r--r--application/resources/pe_light/scalable/worlds.svg64
-rw-r--r--application/resources/sources/multimc-discord.svg265
-rw-r--r--application/setupwizard/JavaWizardPage.cpp393
-rw-r--r--application/setupwizard/JavaWizardPage.h64
-rw-r--r--application/setupwizard/SetupWizard.h2
-rw-r--r--application/themes/SystemTheme.cpp4
-rw-r--r--application/widgets/CustomCommands.cpp48
-rw-r--r--application/widgets/CustomCommands.h43
-rw-r--r--application/widgets/CustomCommands.ui98
-rw-r--r--application/widgets/FtbModpackListItem.cpp8
-rw-r--r--application/widgets/FtbModpackListItem.h15
-rw-r--r--application/widgets/JavaSettingsWidget.cpp428
-rw-r--r--application/widgets/JavaSettingsWidget.h102
-rw-r--r--application/widgets/LabeledToolButton.cpp33
-rw-r--r--application/widgets/LabeledToolButton.h5
-rw-r--r--application/widgets/MCModInfoFrame.cpp2
-rw-r--r--application/widgets/MCModInfoFrame.h2
-rw-r--r--application/widgets/ModListView.cpp2
-rw-r--r--application/widgets/ModListView.h2
-rw-r--r--application/widgets/PageContainer.cpp2
-rw-r--r--application/widgets/PageContainer.h17
-rw-r--r--application/widgets/PageContainer_p.h2
-rw-r--r--application/widgets/ProgressWidget.cpp4
-rw-r--r--application/widgets/ServerStatus.cpp10
-rw-r--r--application/widgets/VersionListView.cpp2
-rw-r--r--application/widgets/VersionListView.h2
-rw-r--r--application/widgets/VersionSelectWidget.cpp25
-rw-r--r--application/widgets/VersionSelectWidget.h6
-rw-r--r--changelog.md229
-rw-r--r--cmake/BundleUtilities.cmake2
-rw-r--r--cmake/GetPrerequisites.cmake7
-rw-r--r--libraries/classparser/CMakeLists.txt11
-rw-r--r--libraries/classparser/include/classparser.h (renamed from libraries/classparser/include/javautils.h)6
-rw-r--r--libraries/classparser/include/classparser_config.h2
-rw-r--r--libraries/classparser/src/annotations.h1
-rw-r--r--libraries/classparser/src/classparser.cpp (renamed from libraries/classparser/src/javautils.cpp)10
-rw-r--r--libraries/classparser/src/constants.h16
-rw-r--r--libraries/iconfix/CMakeLists.txt7
-rw-r--r--libraries/javacheck/CMakeLists.txt1
-rw-r--r--libraries/launcher/CMakeLists.txt24
-rw-r--r--libraries/launcher/net/minecraft/Launcher.java2
-rw-r--r--libraries/launcher/org/multimc/EntryPoint.java21
-rw-r--r--libraries/launcher/org/multimc/IconLoader.java132
-rw-r--r--libraries/launcher/org/multimc/Launcher.java2
-rw-r--r--libraries/launcher/org/multimc/LegacyFrame.java58
-rw-r--r--libraries/launcher/org/multimc/NotFoundException.java2
-rw-r--r--libraries/launcher/org/multimc/ParamBucket.java2
-rw-r--r--libraries/launcher/org/multimc/ParseException.java2
-rw-r--r--libraries/launcher/org/multimc/Utils.java73
-rw-r--r--libraries/launcher/org/multimc/onesix/OneSixLauncher.java136
-rw-r--r--libraries/launcher/org/simplericity/macify/eawt/Application.java176
-rw-r--r--libraries/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java48
-rw-r--r--libraries/launcher/org/simplericity/macify/eawt/ApplicationEvent.java25
-rw-r--r--libraries/launcher/org/simplericity/macify/eawt/ApplicationListener.java27
-rw-r--r--libraries/launcher/org/simplericity/macify/eawt/DefaultApplication.java418
m---------libraries/libnbtplusplus0
-rw-r--r--libraries/pack200/CMakeLists.txt7
m---------libraries/quazip0
-rw-r--r--libraries/rainbow/CMakeLists.txt7
-rw-r--r--libraries/rainbow/include/rainbow_config.h2
-rw-r--r--libraries/systeminfo/include/sys.h1
-rw-r--r--travis/prepare.sh14
467 files changed, 12426 insertions, 11711 deletions
diff --git a/.travis.yml b/.travis.yml
index e2245a8f..19f39c2f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,29 +2,28 @@
language: cpp
cache: apt
-# Build matrix set up
-compiler:
- - gcc
-# - clang
-os:
- - linux
-# - osx
-env:
- - QT_VERSION=5.4.2
- - QT_VERSION=5.5.1
- # - QT_VERSION=5.6.2
matrix:
- exclude:
- # only use clang on OS X
- - os: osx
+ include:
+ - os: linux
+ dist: precise
+ sudo: required
compiler: gcc
- # only use the qt available from homebrew
- - os: osx
- env: QT_VERSION=5.4.2
- - os: osx
- env: QT_VERSION=5.5.1
- # - os: osx
- # env: QT_VERSION=5.6
+ env: TRAVIS_DIST=precise QT_VERSION=5.4.2
+ - os: linux
+ dist: precise
+ sudo: required
+ compiler: gcc
+ env: TRAVIS_DIST=precise QT_VERSION=5.6.2
+ - os: linux
+ dist: trusty
+ sudo: required
+ compiler: gcc
+ env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
+ - os: linux
+ dist: trusty
+ sudo: required
+ compiler: gcc
+ env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
# Install dependencies
install:
diff --git a/BUILD.md b/BUILD.md
index 6c6f7ab0..d347ba14 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -32,11 +32,11 @@ git submodule update
Getting the project to build and run on Linux is easy if you use any modern and up-to-date linux distribution.
## Build dependencies
-* Ideally a compiler capable of building C++14 code (for example, GCC 5.2 and above).
-* Qt 5.4.1+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution.
+* A C++ compiler capable of building C++11 code.
+* Qt 5.6+ Development tools (http://qt-project.org/downloads) ("Qt Online Installer for Linux (64 bit)") or the equivalent from your package manager. It is always better to use the Qt from your distribution, as long as it has a new enough version.
* cmake 3.1 or newer
* zlib (for example, `zlib1g-dev`)
-* java (for example, `openjdk-8-jdk`)
+* Java JDK 8 (for example, `openjdk-8-jdk`)
* GL headers (for example, `libgl1-mesa-dev`)
### Building from command line
@@ -64,7 +64,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
1. Run the Qt installer.
2. Choose a place to install Qt.
3. Choose the components you want to install.
- - You need Qt 5.4.1/gcc 64-bit ticked.
+ - You need Qt 5.6.x 64-bit ticked.
- You need Tools/Qt Creator ticked.
- Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements.
@@ -77,7 +77,7 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
3. Navigate to the MultiMC5 source folder you cloned and choose CMakeLists.txt.
4. Read the instructions that just popped up about a build location and choose one.
5. You should see "Run CMake" in the window.
- - Make sure that Generator is set to "Unix Generator (Desktop Qt 5.4.1 GCC 64bit)".
+ - Make sure that Generator is set to "Unix Generator (Desktop Qt 5.6.x GCC 64bit)".
- Hit the "Run CMake" button.
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully.
@@ -91,13 +91,13 @@ You can use IDEs like KDevelop or QtCreator to open the CMake project if you wan
Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt Creator. The project will simply not compile using Microsoft build tools, because that's not something we do. If it does compile, it is by chance only.
## Dependencies
-* [Qt 5.4.1+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
+* [Qt 5.6+ Development tools](http://qt-project.org/downloads) -- Qt Online Installer for Windows
* [OpenSSL](http://slproweb.com/products/Win32OpenSSL.html) -- Newest Win32 OpenSSL Light
- Microsoft Visual C++ 2008 Redist is required for this, there's a link on the OpenSSL download page above next to the main download.
- We use a custom build of OpenSSL that doesn't have this dependency. For normal development, the custom build is not necessary though.
-* [zlib 1.2.8+](http://zlib.net/zlib128-dll.zip)
+* [zlib 1.2+](http://gnuwin32.sourceforge.net/packages/zlib.htm) - the Setup is fine
+* [Java JDK 8](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)
* [CMake](http://www.cmake.org/cmake/resources/software.html) -- Windows (Win32 Installer)
-* [patch.exe from the GnuWin project](http://gnuwin32.sourceforge.net/packages/patch.htm).
Put it somewhere on the `PATH`, so that it is accessible from the console.
@@ -107,7 +107,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
1. Run the Qt installer
2. Choose a place to install Qt (C:\Qt is the default),
3. Choose the components you want to install
- - You need Qt 5.4.1/MinGW 4.9 (32 bit) ticked,
+ - You need Qt 5.6 (32 bit) ticked,
- You need Tools/Qt Creator ticked,
- Other components are selected by default, you can untick them if you don't need them.
4. Accept the license agreements,
@@ -132,7 +132,7 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
5. If you chose not to add CMake to the system PATH, tell Qt Creator where you installed it,
- Otherwise you can skip this step.
6. You should see "Run CMake" in the window,
- - Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.4.1 MinGW 32bit)",
+ - Make sure that Generator is set to "MinGW Generator (Desktop Qt 5.6.x MinGW 32bit)",
- Hit the "Run CMake" button,
- You'll see warnings and it might not be clear that it succeeded until you scroll to the bottom of the window.
- Hit "Finish" if CMake ran successfully.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f0f4de23..8b7d18b8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,7 +13,7 @@ endif()
project(MultiMC)
enable_testing()
-######## Set CMake options ########
+##################################### Set CMake options #####################################
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -38,6 +38,45 @@ if(UNIX AND APPLE)
endif()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
+##################################### Set Application options #####################################
+
+######## Set URLs ########
+set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
+
+######## Set version numbers ########
+set(MultiMC_VERSION_MAJOR 0)
+set(MultiMC_VERSION_MINOR 6)
+set(MultiMC_VERSION_HOTFIX 1)
+
+# Build number
+set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
+
+# Build platform.
+set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
+
+# Channel list URL
+set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
+
+# Notification URL
+set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
+
+# paste.ee API key
+set(MultiMC_PASTE_EE_API_KEY "" CACHE STRING "API key you can get from paste.ee when you register an account")
+
+# Google analytics ID
+set(MultiMC_ANALYTICS_ID "" CACHE STRING "ID you can get from Google analytics")
+
+#### Check the current Git commit and branch
+include(GetGitRevisionDescription)
+get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
+
+message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
+message(STATUS "Git refspec: ${MultiMC_GIT_REFSPEC}")
+
+set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}")
+
+#### Custom target to just print the version.
+add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
################################ 3rd Party Libs ################################
@@ -62,6 +101,133 @@ if (Qt5_POSITION_INDEPENDENT_CODE)
SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif()
+####################################### Install layout #######################################
+
+# How to install the build results
+set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (auto, win-bundle, lin-bundle, lin-nodeps, lin-system, mac-bundle)")
+set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle)
+
+if(MultiMC_LAYOUT STREQUAL "auto")
+ if(UNIX AND APPLE)
+ set(MultiMC_LAYOUT_REAL "mac-bundle")
+ elseif(UNIX)
+ set(MultiMC_LAYOUT_REAL "lin-nodeps")
+ elseif(WIN32)
+ set(MultiMC_LAYOUT_REAL "win-bundle")
+ else()
+ message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
+ endif()
+else()
+ set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT})
+endif()
+
+if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
+ set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS")
+ set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS")
+ set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS")
+ set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources")
+ set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS/jars")
+
+ set(BUNDLE_DEST_DIR ".")
+
+ # Apps to bundle
+ set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
+
+ # Mac bundle settings
+ set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
+ set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
+ set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
+ set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
+ set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
+ set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
+ set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
+ set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2018 MultiMC Contributors")
+
+ # directories to look for dependencies
+ set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+ # install as bundle
+ set(INSTALL_BUNDLE "full")
+
+ # Add the icon
+ install(FILES application/resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
+
+elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
+ set(BINARY_DEST_DIR "bin")
+ set(LIBRARY_DEST_DIR "bin")
+ set(PLUGIN_DEST_DIR "plugins")
+ set(BUNDLE_DEST_DIR ".")
+ set(RESOURCES_DEST_DIR ".")
+ set(JARS_DEST_DIR "bin/jars")
+
+ # Apps to bundle
+ set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
+
+ # directories to look for dependencies
+ set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+ # install as bundle
+ set(INSTALL_BUNDLE "full")
+
+ # Set RPATH
+ SET(MultiMC_BINARY_RPATH "$ORIGIN/")
+
+ # Install basic runner script
+ install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
+
+elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
+ set(BINARY_DEST_DIR "bin")
+ set(LIBRARY_DEST_DIR "bin")
+ set(PLUGIN_DEST_DIR "plugins")
+ set(BUNDLE_DEST_DIR ".")
+ set(RESOURCES_DEST_DIR ".")
+ set(JARS_DEST_DIR "bin/jars")
+
+ # install as bundle with no dependencies included
+ set(INSTALL_BUNDLE "nodeps")
+
+ # Set RPATH
+ SET(MultiMC_BINARY_RPATH "$ORIGIN/")
+
+ # Install basic runner script
+ install(PROGRAMS application/package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
+
+elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
+ set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary")
+ set(MultiMC_BINARY_DEST_DIR "bin" CACHE STRING "Path to the binary directory")
+ set(MultiMC_LIBRARY_DEST_DIR "lib${LIB_SUFFIX}" CACHE STRING "Path to the library directory")
+ set(MultiMC_SHARE_DEST_DIR "share/multimc" CACHE STRING "Path to the shared data directory")
+ set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}/jars")
+
+ set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR})
+ set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR})
+
+ MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR")
+ SET(MultiMC_APP_BINARY_DEFS "-DMULTIMC_JARS_LOCATION=${CMAKE_INSTALL_PREFIX}/${JARS_DEST_DIR}" "-DMULTIMC_LINUX_DATADIR")
+
+ # install as bundle with no dependencies included
+ set(INSTALL_BUNDLE "nodeps")
+
+elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
+ set(BINARY_DEST_DIR ".")
+ set(LIBRARY_DEST_DIR ".")
+ set(PLUGIN_DEST_DIR ".")
+ set(BUNDLE_DEST_DIR ".")
+ set(RESOURCES_DEST_DIR ".")
+ set(JARS_DEST_DIR "jars")
+
+ # Apps to bundle
+ set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
+
+ # directories to look for dependencies
+ set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+
+ # install as bundle
+ set(INSTALL_BUNDLE "full")
+else()
+ message(FATAL_ERROR "No sensible install layout set.")
+endif()
+
################################ Included Libs ################################
include(ExternalProject)
@@ -71,6 +237,7 @@ option(NBT_BUILD_SHARED "Build NBT shared library" ON)
option(NBT_USE_ZLIB "Build NBT library with zlib support" OFF)
option(NBT_BUILD_TESTS "Build NBT library tests" OFF) #FIXME: fix unit tests.
set(NBT_NAME MultiMC_nbt++)
+set(NBT_DEST_DIR ${LIBRARY_DEST_DIR})
add_subdirectory(libraries/libnbtplusplus)
add_subdirectory(libraries/ganalytics) # google analytics library
@@ -84,10 +251,12 @@ add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
+add_subdirectory(libraries/classparser) # google analytics library
############################### Built Artifacts ###############################
add_subdirectory(api/logic)
add_subdirectory(api/gui)
+# NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order.
add_subdirectory(application)
diff --git a/COPYING.md b/COPYING.md
index 1603a4b1..88e74cbb 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -1,6 +1,6 @@
# MultiMC
- Copyright 2012-2017 MultiMC Contributors
+ Copyright 2012-2018 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
@@ -117,6 +117,17 @@
PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS,
EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+# Material Design Icons
+
+ Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/),
+ with Reserved Font Name Material Design Icons.
+ Copyright (c) 2014, Google (http://www.google.com/design/)
+ uses the license at https://github.com/google/material-design-icons/blob/master/LICENSE
+
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
+ This license is copied below, and is also available with a FAQ at:
+ http://scripts.sil.org/OFL
+
# Pack200
The GNU General Public License (GPL)
diff --git a/README.md b/README.md
index 425c7748..58b6b040 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-![MultiMC](http://i.imgur.com/QJXbz.png)
+<p align="center">
+ <img src="http://i.imgur.com/IOcTf8M.png" alt="MultiMC logo"/>
+</p>
MultiMC 5
=========
@@ -11,7 +13,7 @@ The project uses C++ and Qt5 as the language and base framework. This might seem
We can do more, with less, on worse hardware and leave more resources for the game while keeping the launcher running and providing extra features.
-If you want to contribute, either talk to us on [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC), or pick up one of the issues that are ready for development: [![Stories in Ready](https://badge.waffle.io/MultiMC/MultiMC5.svg?label=ready&title=Ready)](http://waffle.io/MultiMC/MultiMC5)
+If you want to contribute, either talk to us on [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
### Building
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
@@ -38,7 +40,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
## License
-Copyright &copy; 2013-2017 MultiMC Contributors
+Copyright &copy; 2013-2018 MultiMC Contributors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this program except in compliance with the License. You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0).
diff --git a/api/gui/CMakeLists.txt b/api/gui/CMakeLists.txt
index 911a0ab8..deae735f 100644
--- a/api/gui/CMakeLists.txt
+++ b/api/gui/CMakeLists.txt
@@ -26,3 +26,10 @@ qt5_use_modules(MultiMC_gui Gui)
# Mark and export headers
target_include_directories(MultiMC_gui PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}")
+
+# Install it
+install(
+ TARGETS MultiMC_gui
+ RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
+) \ No newline at end of file
diff --git a/api/gui/SkinUtils.cpp b/api/gui/SkinUtils.cpp
index 66708236..3950cbc0 100644
--- a/api/gui/SkinUtils.cpp
+++ b/api/gui/SkinUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/gui/SkinUtils.h b/api/gui/SkinUtils.h
index eeaa6c12..f042b908 100644
--- a/api/gui/SkinUtils.h
+++ b/api/gui/SkinUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/gui/icons/IconList.cpp b/api/gui/icons/IconList.cpp
index 3da0a8ea..5c2c1386 100644
--- a/api/gui/icons/IconList.cpp
+++ b/api/gui/icons/IconList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -261,7 +261,7 @@ void IconList::installIcons(const QStringList &iconFiles)
QString target = FS::PathCombine(m_dir.dirName(), fileinfo.fileName());
QString suffix = fileinfo.suffix();
- if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico")
+ if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg")
continue;
if (!QFile::copy(file, target))
@@ -331,7 +331,7 @@ bool IconList::addIcon(const QString &key, const QString &name, const QString &p
{
// replace the icon even? is the input valid?
QIcon icon(path);
- if (!icon.availableSizes().size())
+ if (icon.isNull())
return false;
auto iter = name_index.find(key);
if (iter != name_index.end())
@@ -392,20 +392,6 @@ QIcon IconList::getIcon(const QString &key) const
return QIcon();
}
-QIcon IconList::getBigIcon(const QString &key) const
-{
- int icon_index = getIconIndex(key);
-
- // Fallback for icons that don't exist.
- icon_index = getIconIndex(icon_index == -1 ? "infinity" : key);
-
- if (icon_index == -1)
- return QIcon();
-
- QPixmap bigone = icons[icon_index].icon().pixmap(256,256).scaled(256,256);
- return QIcon(bigone);
-}
-
int IconList::getIconIndex(const QString &key) const
{
auto iter = name_index.find(key == "default" ? "infinity" : key);
@@ -415,4 +401,9 @@ int IconList::getIconIndex(const QString &key) const
return -1;
}
+QString IconList::getDirectory() const
+{
+ return m_dir.absolutePath();
+}
+
//#include "IconList.moc"
diff --git a/api/gui/icons/IconList.h b/api/gui/icons/IconList.h
index 6a6e8b5c..b8599103 100644
--- a/api/gui/icons/IconList.h
+++ b/api/gui/icons/IconList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,8 +38,8 @@ public:
virtual ~IconList() {};
QIcon getIcon(const QString &key) const;
- QIcon getBigIcon(const QString &key) const;
int getIconIndex(const QString &key) const;
+ QString getDirectory() const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
diff --git a/api/gui/icons/MMCIcon.cpp b/api/gui/icons/MMCIcon.cpp
index 8fe4cbfc..92518e01 100644
--- a/api/gui/icons/MMCIcon.cpp
+++ b/api/gui/icons/MMCIcon.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/gui/icons/MMCIcon.h b/api/gui/icons/MMCIcon.h
index dd65eebd..8c6752ea 100644
--- a/api/gui/icons/MMCIcon.h
+++ b/api/gui/icons/MMCIcon.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/BaseInstaller.cpp b/api/logic/BaseInstaller.cpp
index a2a575dc..51f66293 100644
--- a/api/logic/BaseInstaller.cpp
+++ b/api/logic/BaseInstaller.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +16,19 @@
#include <QFile>
#include "BaseInstaller.h"
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
BaseInstaller::BaseInstaller()
{
}
-bool BaseInstaller::isApplied(OneSixInstance *on)
+bool BaseInstaller::isApplied(MinecraftInstance *on)
{
return QFile::exists(filename(on->instanceRoot()));
}
-bool BaseInstaller::add(OneSixInstance *to)
+bool BaseInstaller::add(MinecraftInstance *to)
{
if (!patchesDir(to->instanceRoot()).exists())
{
@@ -46,7 +46,7 @@ bool BaseInstaller::add(OneSixInstance *to)
return true;
}
-bool BaseInstaller::remove(OneSixInstance *from)
+bool BaseInstaller::remove(MinecraftInstance *from)
{
return QFile::remove(filename(from->instanceRoot()));
}
diff --git a/api/logic/BaseInstaller.h b/api/logic/BaseInstaller.h
index 65a2436f..afe11d55 100644
--- a/api/logic/BaseInstaller.h
+++ b/api/logic/BaseInstaller.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
#include "multimc_logic_export.h"
-class OneSixInstance;
+class MinecraftInstance;
class QDir;
class QString;
class QObject;
@@ -32,12 +32,12 @@ class MULTIMC_LOGIC_EXPORT BaseInstaller
public:
BaseInstaller();
virtual ~BaseInstaller(){};
- bool isApplied(OneSixInstance *on);
+ bool isApplied(MinecraftInstance *on);
- virtual bool add(OneSixInstance *to);
- virtual bool remove(OneSixInstance *from);
+ virtual bool add(MinecraftInstance *to);
+ virtual bool remove(MinecraftInstance *from);
- virtual Task *createInstallTask(OneSixInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
+ virtual Task *createInstallTask(MinecraftInstance *instance, BaseVersionPtr version, QObject *parent) = 0;
protected:
virtual QString id() const = 0;
diff --git a/api/logic/BaseInstance.cpp b/api/logic/BaseInstance.cpp
index ee9e919d..7e652e0d 100644
--- a/api/logic/BaseInstance.cpp
+++ b/api/logic/BaseInstance.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -197,7 +197,7 @@ bool BaseInstance::canLaunch() const
return (!hasVersionBroken() && !isRunning());
}
-bool BaseInstance::reload()
+bool BaseInstance::reloadSettings()
{
return m_settings->reload();
}
@@ -279,6 +279,7 @@ QString BaseInstance::windowTitle() const
return "MultiMC: " + name();
}
+// FIXME: why is this here? move it to MinecraftInstance!!!
QStringList BaseInstance::extraArguments() const
{
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h
index 27b167a6..282bfb70 100644
--- a/api/logic/BaseInstance.h
+++ b/api/logic/BaseInstance.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,8 @@
#include "MessageLevel.h"
#include "pathmatcher/IPathMatcher.h"
+#include "net/Mode.h"
+
#include "multimc_logic_export.h"
class QDir;
@@ -67,9 +69,8 @@ public:
/// virtual destructor to make sure the destruction is COMPLETE
virtual ~BaseInstance() {};
- virtual void copy(SettingsObjectPtr newSettings, const QDir &newDir) {}
-
virtual void init() = 0;
+ virtual void saveNow() = 0;
/// nuke thoroughly - deletes the instance contents, notifies the list/model which is
/// responsible of cleaning up the husk
@@ -129,25 +130,8 @@ public:
virtual QStringList extraArguments() const;
- virtual QString intendedVersionId() const = 0;
- virtual bool setIntendedVersionId(QString version) = 0;
-
- /*!
- * The instance's current version.
- * This value represents the instance's current version. If this value is
- * different from the intendedVersion, the instance should be updated.
- * \warning Don't change this value unless you know what you're doing.
- */
- virtual QString currentVersionId() const = 0;
-
- /*!
- * Whether or not 'the game' should be downloaded when the instance is launched.
- */
- virtual bool shouldUpdate() const = 0;
- virtual void setShouldUpdate(bool val) = 0;
-
/// Traits. Normally inside the version, depends on instance implementation.
- virtual QSet <QString> traits() = 0;
+ virtual QSet <QString> traits() const = 0;
/**
* Gets the time that the instance was last launched.
@@ -160,12 +144,6 @@ public:
InstancePtr getSharedPtr();
/*!
- * \brief Gets a pointer to this instance's version list.
- * \return A pointer to the available version list for this instance.
- */
- virtual std::shared_ptr<BaseVersionList> versionList() const = 0;
-
- /*!
* \brief Gets this instance's settings object.
* This settings object stores instance-specific settings.
* \return A pointer to this instance's settings object.
@@ -173,7 +151,7 @@ public:
virtual SettingsObjectPtr settings() const;
/// returns a valid update task
- virtual shared_qobject_ptr<Task> createUpdateTask() = 0;
+ virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
@@ -182,12 +160,6 @@ public:
std::shared_ptr<LaunchTask> getLaunchTask();
/*!
- * Returns a task that should be done right before launch
- * This task should do any extra preparations needed
- */
- virtual std::shared_ptr<Task> createJarModdingTask() = 0;
-
- /*!
* Create envrironment variables for running the instance
*/
virtual QProcessEnvironment createEnvironment() = 0;
@@ -251,10 +223,11 @@ public:
}
}
- bool canLaunch() const;
+ virtual bool canLaunch() const;
+ virtual bool canEdit() const = 0;
virtual bool canExport() const = 0;
- virtual bool reload();
+ bool reloadSettings();
/**
* 'print' a verbose desription of the instance into a QStringList
diff --git a/api/logic/BaseInstanceProvider.h b/api/logic/BaseInstanceProvider.h
index f6833650..34489c5d 100644
--- a/api/logic/BaseInstanceProvider.h
+++ b/api/logic/BaseInstanceProvider.h
@@ -37,7 +37,7 @@ public:
{
return QString();
}
- virtual bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName)
+ virtual bool commitStagedInstance(const QString & path, const QString& instanceName, const QString & groupName)
{
return false;
}
diff --git a/api/logic/BaseVersion.h b/api/logic/BaseVersion.h
index 0f99b1a3..e49d6277 100644
--- a/api/logic/BaseVersion.h
+++ b/api/logic/BaseVersion.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/BaseVersionList.cpp b/api/logic/BaseVersionList.cpp
index 8b424c11..31a635d7 100644
--- a/api/logic/BaseVersionList.cpp
+++ b/api/logic/BaseVersionList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/BaseVersionList.h b/api/logic/BaseVersionList.h
index fa1e0861..b609e039 100644
--- a/api/logic/BaseVersionList.h
+++ b/api/logic/BaseVersionList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/CMakeLists.txt b/api/logic/CMakeLists.txt
index 2eda34fe..404044d8 100644
--- a/api/logic/CMakeLists.txt
+++ b/api/logic/CMakeLists.txt
@@ -206,22 +206,14 @@ set(MINECRAFT_SOURCES
minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/ValidateTask.h
minecraft/auth/flows/ValidateTask.cpp
- minecraft/onesix/OneSixUpdate.h
- minecraft/onesix/OneSixUpdate.cpp
- minecraft/onesix/OneSixInstance.h
- minecraft/onesix/OneSixInstance.cpp
- minecraft/onesix/OneSixProfileStrategy.cpp
- minecraft/onesix/OneSixProfileStrategy.h
- minecraft/onesix/OneSixVersionFormat.cpp
- minecraft/onesix/OneSixVersionFormat.h
- minecraft/onesix/update/AssetUpdateTask.h
- minecraft/onesix/update/AssetUpdateTask.cpp
- minecraft/onesix/update/FMLLibrariesTask.cpp
- minecraft/onesix/update/FMLLibrariesTask.h
- minecraft/onesix/update/FoldersTask.cpp
- minecraft/onesix/update/FoldersTask.h
- minecraft/onesix/update/LibrariesTask.cpp
- minecraft/onesix/update/LibrariesTask.h
+ minecraft/update/AssetUpdateTask.h
+ minecraft/update/AssetUpdateTask.cpp
+ minecraft/update/FMLLibrariesTask.cpp
+ minecraft/update/FMLLibrariesTask.h
+ minecraft/update/FoldersTask.cpp
+ minecraft/update/FoldersTask.h
+ minecraft/update/LibrariesTask.cpp
+ minecraft/update/LibrariesTask.h
minecraft/launch/ClaimAccount.cpp
minecraft/launch/ClaimAccount.h
minecraft/launch/CreateServerResourcePacksFolder.cpp
@@ -238,35 +230,42 @@ set(MINECRAFT_SOURCES
minecraft/launch/PrintInstanceInfo.h
minecraft/legacy/LegacyModList.h
minecraft/legacy/LegacyModList.cpp
- minecraft/legacy/LegacyUpdate.h
- minecraft/legacy/LegacyUpdate.cpp
minecraft/legacy/LegacyInstance.h
minecraft/legacy/LegacyInstance.cpp
- minecraft/legacy/LwjglVersionList.h
- minecraft/legacy/LwjglVersionList.cpp
+ minecraft/legacy/LegacyUpgradeTask.h
+ minecraft/legacy/LegacyUpgradeTask.cpp
minecraft/GradleSpecifier.h
- minecraft/MinecraftProfile.cpp
- minecraft/MinecraftProfile.h
- minecraft/MojangVersionFormat.cpp
- minecraft/MojangVersionFormat.h
minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.h
+ minecraft/LaunchProfile.cpp
+ minecraft/LaunchProfile.h
+ minecraft/Component.cpp
+ minecraft/Component.h
+ minecraft/ComponentList.cpp
+ minecraft/ComponentList.h
+ minecraft/ComponentUpdateTask.cpp
+ minecraft/ComponentUpdateTask.h
+ minecraft/MinecraftLoadAndCheck.h
+ minecraft/MinecraftLoadAndCheck.cpp
+ minecraft/MinecraftUpdate.h
+ minecraft/MinecraftUpdate.cpp
+ minecraft/MojangVersionFormat.cpp
+ minecraft/MojangVersionFormat.h
minecraft/Rule.cpp
minecraft/Rule.h
+ minecraft/OneSixVersionFormat.cpp
+ minecraft/OneSixVersionFormat.h
minecraft/OpSys.cpp
minecraft/OpSys.h
minecraft/ParseUtils.cpp
minecraft/ParseUtils.h
minecraft/ProfileUtils.cpp
minecraft/ProfileUtils.h
- minecraft/ProfileStrategy.h
minecraft/Library.cpp
minecraft/Library.h
minecraft/MojangDownloadInfo.h
minecraft/VersionFile.cpp
minecraft/VersionFile.h
- minecraft/ProfilePatch.cpp
- minecraft/ProfilePatch.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
minecraft/Mod.h
@@ -278,18 +277,6 @@ set(MINECRAFT_SOURCES
minecraft/WorldList.h
minecraft/WorldList.cpp
- # FTB
- minecraft/ftb/OneSixFTBInstance.h
- minecraft/ftb/OneSixFTBInstance.cpp
- minecraft/ftb/LegacyFTBInstance.h
- minecraft/ftb/LegacyFTBInstance.cpp
- minecraft/ftb/FTBProfileStrategy.h
- minecraft/ftb/FTBProfileStrategy.cpp
- minecraft/ftb/FTBInstanceProvider.cpp
- minecraft/ftb/FTBInstanceProvider.h
- minecraft/ftb/FTBPlugin.h
- minecraft/ftb/FTBPlugin.cpp
-
# Flame
minecraft/flame/PackManifest.h
minecraft/flame/PackManifest.cpp
@@ -350,8 +337,6 @@ set(TASKS_SOURCES
# Tasks
tasks/Task.h
tasks/Task.cpp
- tasks/ThreadTask.h
- tasks/ThreadTask.cpp
tasks/SequentialTask.h
tasks/SequentialTask.cpp
)
@@ -433,6 +418,20 @@ set(META_SOURCES
meta/Index.h
)
+set(MODPLATFORM_SOURCES
+ # Modplatform sources
+ modplatform/FtbPackDownloader.h
+ modplatform/FtbPackDownloader.cpp
+
+ modplatform/FtbPackFetchTask.h
+ modplatform/FtbPackFetchTask.cpp
+ modplatform/FtbPackInstallTask.h
+ modplatform/FtbPackInstallTask.cpp
+
+ modplatform/PackHelpers.h
+
+)
+
add_unit_test(Index
SOURCES meta/Index_test.cpp
LIBS MultiMC_logic
@@ -461,6 +460,7 @@ set(LOGIC_SOURCES
${TOOLS_SOURCES}
${META_SOURCES}
${ICONS_SOURCES}
+ ${MODPLATFORM_SOURCES}
)
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
@@ -469,8 +469,15 @@ set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISI
generate_export_header(MultiMC_logic)
# Link
-target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip ${NBT_NAME} ${ZLIB_LIBRARIES})
+target_link_libraries(MultiMC_logic xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES})
qt5_use_modules(MultiMC_logic Core Xml Network Concurrent)
# Mark and export headers
target_include_directories(MultiMC_logic PUBLIC "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}" PRIVATE "${ZLIB_INCLUDE_DIRS}")
+
+# Install it
+install(
+ TARGETS MultiMC_logic
+ RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
+)
diff --git a/api/logic/Commandline.cpp b/api/logic/Commandline.cpp
index 751182af..eac9db09 100644
--- a/api/logic/Commandline.cpp
+++ b/api/logic/Commandline.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/Commandline.h b/api/logic/Commandline.h
index 6c473015..c8c8be29 100644
--- a/api/logic/Commandline.h
+++ b/api/logic/Commandline.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp
index 59d4c4a8..cf321af2 100644
--- a/api/logic/Env.cpp
+++ b/api/logic/Env.cpp
@@ -13,13 +13,11 @@
#include <QDebug>
-class Env::Private
+struct Env::Private
{
-public:
QNetworkAccessManager m_qnam;
shared_qobject_ptr<HttpMetaCache> m_metacache;
std::shared_ptr<IIconList> m_iconlist;
- QMap<QString, std::shared_ptr<BaseVersionList>> m_versionLists;
shared_qobject_ptr<Meta::Index> m_metadataIndex;
QString m_jarsPath;
};
@@ -75,32 +73,6 @@ void Env::registerIconList(std::shared_ptr<IIconList> iconlist)
d->m_iconlist = iconlist;
}
-BaseVersionPtr Env::getVersion(QString component, QString version)
-{
- auto list = getVersionList(component);
- if(!list)
- {
- return nullptr;
- }
- return list->findVersion(version);
-}
-
-std::shared_ptr< BaseVersionList > Env::getVersionList(QString component)
-{
- auto iter = d->m_versionLists.find(component);
- if(iter != d->m_versionLists.end())
- {
- return *iter;
- }
- //return std::make_shared<NullVersionList>();
- return nullptr;
-}
-
-void Env::registerVersionList(QString name, std::shared_ptr< BaseVersionList > vlist)
-{
- d->m_versionLists[name] = vlist;
-}
-
shared_qobject_ptr<Meta::Index> Env::metadataIndex()
{
if (!d->m_metadataIndex)
@@ -206,5 +178,3 @@ void Env::setJarsPath(const QString& path)
{
d->m_jarsPath = path;
}
-
-#include "Env.moc"
diff --git a/api/logic/Env.h b/api/logic/Env.h
index 08b1dd0d..276d762d 100644
--- a/api/logic/Env.h
+++ b/api/logic/Env.h
@@ -24,11 +24,12 @@ class Index;
#endif
#define ENV (Env::getInstance())
+
class MULTIMC_LOGIC_EXPORT Env
{
friend class MultiMC;
private:
- class Private;
+ struct Private;
Env();
~Env();
static void dispose();
@@ -47,14 +48,6 @@ public:
/// Updates the application proxy settings from the settings object.
void updateProxySettings(QString proxyTypeStr, QString addr, int port, QString user, QString password);
- /// get a version list by name
- std::shared_ptr<BaseVersionList> getVersionList(QString component);
-
- /// get a version by list name and version name
- std::shared_ptr<BaseVersion> getVersion(QString component, QString version);
-
- void registerVersionList(QString name, std::shared_ptr<BaseVersionList> vlist);
-
void registerIconList(std::shared_ptr<IIconList> iconlist);
shared_qobject_ptr<Meta::Index> metadataIndex();
diff --git a/api/logic/FileSystem.cpp b/api/logic/FileSystem.cpp
index b3115988..4b47f415 100644
--- a/api/logic/FileSystem.cpp
+++ b/api/logic/FileSystem.cpp
@@ -3,11 +3,27 @@
#include "FileSystem.h"
#include <QDir>
+#include <QFile>
#include <QSaveFile>
#include <QFileInfo>
#include <QDebug>
#include <QUrl>
#include <QStandardPaths>
+#include <QTextStream>
+
+#if defined Q_OS_WIN32
+ #include <windows.h>
+ #include <string>
+ #include <sys/utime.h>
+ #include <winnls.h>
+ #include <shobjidl.h>
+ #include <objbase.h>
+ #include <objidl.h>
+ #include <shlguid.h>
+ #include <shlobj.h>
+#else
+ #include <utime.h>
+#endif
namespace FS {
@@ -62,21 +78,13 @@ QByteArray read(const QString &filename)
bool updateTimestamp(const QString& filename)
{
- QFile file(filename);
- if (!file.exists())
- {
- return false;
- }
- if (!file.open(QIODevice::ReadWrite))
- {
- return false;
- }
- const quint64 size = file.size();
- file.seek(size);
- file.write( QByteArray(1, '0') );
- file.resize(size);
- return true;
-
+#ifdef Q_OS_WIN32
+ std::wstring filename_utf_16 = filename.toStdWString();
+ return (_wutime64(filename_utf_16.c_str(), nullptr) == 0);
+#else
+ QByteArray filenameBA = QFile::encodeName(filename);
+ return (utime(filenameBA.data(), nullptr) == 0);
+#endif
}
bool ensureFilePathExists(QString filenamepath)
@@ -163,11 +171,6 @@ bool copy::operator()(const QString &offset)
return true;
}
-
-#if defined Q_OS_WIN32
-#include <windows.h>
-#include <string>
-#endif
bool deletePath(QString path)
{
bool OK = true;
@@ -225,7 +228,7 @@ bool deletePath(QString path)
}
-QString PathCombine(QString path1, QString path2)
+QString PathCombine(const QString & path1, const QString & path2)
{
if(!path1.size())
return path2;
@@ -234,11 +237,16 @@ QString PathCombine(QString path1, QString path2)
return QDir::cleanPath(path1 + QDir::separator() + path2);
}
-QString PathCombine(QString path1, QString path2, QString path3)
+QString PathCombine(const QString & path1, const QString & path2, const QString & path3)
{
return PathCombine(PathCombine(path1, path2), path3);
}
+QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4)
+{
+ return PathCombine(PathCombine(path1, path2, path3), path4);
+}
+
QString AbsolutePath(QString path)
{
return QFileInfo(path).absolutePath();
@@ -332,21 +340,9 @@ bool checkProblemticPathJava(QDir folder)
return pathfoldername.contains("!", Qt::CaseInsensitive);
}
-#include <QStandardPaths>
-#include <QFile>
-#include <QTextStream>
-
// Win32 crap
#if defined Q_OS_WIN
-#include <windows.h>
-#include <winnls.h>
-#include <shobjidl.h>
-#include <objbase.h>
-#include <objidl.h>
-#include <shlguid.h>
-#include <shlobj.h>
-
bool called_coinit = false;
HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args)
diff --git a/api/logic/FileSystem.h b/api/logic/FileSystem.h
index a09ee557..de8774ff 100644
--- a/api/logic/FileSystem.h
+++ b/api/logic/FileSystem.h
@@ -83,8 +83,9 @@ private:
*/
MULTIMC_LOGIC_EXPORT bool deletePath(QString path);
-MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2);
-MULTIMC_LOGIC_EXPORT QString PathCombine(QString path1, QString path2, QString path3);
+MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2);
+MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3);
+MULTIMC_LOGIC_EXPORT QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4);
MULTIMC_LOGIC_EXPORT QString AbsolutePath(QString path);
diff --git a/api/logic/FolderInstanceProvider.cpp b/api/logic/FolderInstanceProvider.cpp
index ea0d4ef0..a6d3bdc8 100644
--- a/api/logic/FolderInstanceProvider.cpp
+++ b/api/logic/FolderInstanceProvider.cpp
@@ -1,7 +1,7 @@
#include "FolderInstanceProvider.h"
#include "settings/INISettingsObject.h"
#include "FileSystem.h"
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include "NullInstance.h"
@@ -12,6 +12,7 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QUuid>
+#include <QTimer>
const static int GROUP_FILE_FORMAT_VERSION = 1;
@@ -33,11 +34,13 @@ struct WatchLock
FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const QString& instDir)
: BaseInstanceProvider(settings)
{
- m_instDir = instDir;
- if (!QDir::current().exists(m_instDir))
+ // Create aand normalize path
+ if (!QDir::current().exists(instDir))
{
- QDir::current().mkpath(m_instDir);
+ QDir::current().mkpath(instDir);
}
+ // NOTE: canonicalPath requires the path to exist. Do not move this above the creation block!
+ m_instDir = QDir(instDir).canonicalPath();
m_watcher = new QFileSystemWatcher(this);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &FolderInstanceProvider::instanceDirContentsChanged);
m_watcher->addPath(m_instDir);
@@ -46,7 +49,7 @@ FolderInstanceProvider::FolderInstanceProvider(SettingsObjectPtr settings, const
QList< InstanceId > FolderInstanceProvider::discoverInstances()
{
QList<InstanceId> out;
- QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable, QDirIterator::FollowSymlinks);
+ QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks);
while (iter.hasNext())
{
QString subDir = iter.next();
@@ -88,7 +91,7 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
if (inst_type == "OneSix" || inst_type == "Nostalgia")
{
- inst.reset(new OneSixInstance(m_globalSettings, instanceSettings, instanceRoot));
+ inst.reset(new MinecraftInstance(m_globalSettings, instanceSettings, instanceRoot));
}
else if (inst_type == "Legacy")
{
@@ -110,24 +113,6 @@ InstancePtr FolderInstanceProvider::loadInstance(const InstanceId& id)
return inst;
}
-#include "InstanceImportTask.h"
-Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon)
-{
- return new InstanceImportTask(m_globalSettings, sourceUrl, this, instName, instIcon, instGroup);
-}
-
-#include "InstanceCreationTask.h"
-Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon)
-{
- return new InstanceCreationTask(m_globalSettings, this, version, instName, instIcon, instGroup);
-}
-
-#include "InstanceCopyTask.h"
-Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves)
-{
- return new InstanceCopyTask(m_globalSettings, this, oldInstance, instName, instIcon, instGroup, copySaves);
-}
-
void FolderInstanceProvider::saveGroupList()
{
WatchLock foo(m_watcher, m_instDir);
@@ -298,7 +283,7 @@ void FolderInstanceProvider::instanceDirContentsChanged(const QString& path)
void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVariant value)
{
- QString newInstDir = value.toString();
+ QString newInstDir = QDir(value.toString()).canonicalPath();
if(newInstDir != m_instDir)
{
if(m_groupsLoaded)
@@ -311,6 +296,164 @@ void FolderInstanceProvider::on_InstFolderChanged(const Setting &setting, QVaria
}
}
+template <typename T>
+static void clamp(T& current, T min, T max)
+{
+ if (current < min)
+ {
+ current = min;
+ }
+ else if(current > max)
+ {
+ current = max;
+ }
+}
+
+// List of numbers from min to max. Next is exponent times bigger than previous.
+class ExponentialSeries
+{
+public:
+ ExponentialSeries(unsigned min, unsigned max, unsigned exponent = 2)
+ {
+ m_current = m_min = min;
+ m_max = max;
+ m_exponent = exponent;
+ }
+ void reset()
+ {
+ m_current = m_min;
+ }
+ unsigned operator()()
+ {
+ unsigned retval = m_current;
+ m_current *= m_exponent;
+ clamp(m_current, m_min, m_max);
+ return retval;
+ }
+ unsigned m_current;
+ unsigned m_min;
+ unsigned m_max;
+ unsigned m_exponent;
+};
+
+/*
+ * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows.
+ * Basically, it starts messing things up while MultiMC is extracting/creating instances
+ * and causes that horrible failure that is NTFS to lock files in place because they are open.
+ */
+class FolderInstanceStaging : public Task
+{
+Q_OBJECT
+ const unsigned minBackoff = 1;
+ const unsigned maxBackoff = 16;
+public:
+ FolderInstanceStaging (
+ FolderInstanceProvider * parent,
+ Task * child,
+ const QString & stagingPath,
+ const QString& instanceName,
+ const QString& groupName )
+ : backoff(minBackoff, maxBackoff)
+ {
+ m_parent = parent;
+ m_child.reset(child);
+ connect(child, &Task::succeeded, this, &FolderInstanceStaging::childSucceded);
+ connect(child, &Task::failed, this, &FolderInstanceStaging::childFailed);
+ connect(child, &Task::status, this, &FolderInstanceStaging::setStatus);
+ connect(child, &Task::progress, this, &FolderInstanceStaging::setProgress);
+ m_instanceName = instanceName;
+ m_groupName = groupName;
+ m_stagingPath = stagingPath;
+ m_backoffTimer.setSingleShot(true);
+ connect(&m_backoffTimer, &QTimer::timeout, this, &FolderInstanceStaging::childSucceded);
+ }
+
+protected:
+ virtual void executeTask() override
+ {
+ m_child->start();
+ }
+ QStringList warnings() const override
+ {
+ return m_child->warnings();
+ }
+
+private slots:
+ void childSucceded()
+ {
+ unsigned sleepTime = backoff();
+ if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName))
+ {
+ emitSucceeded();
+ return;
+ }
+ // we actually failed, retry?
+ if(sleepTime == maxBackoff)
+ {
+ emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something."));
+ return;
+ }
+ qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime;
+ m_backoffTimer.start(sleepTime * 500);
+ }
+ void childFailed(const QString & reason)
+ {
+ m_parent->destroyStagingPath(m_stagingPath);
+ emitFailed(reason);
+ }
+
+private:
+ ExponentialSeries backoff;
+ QString m_stagingPath;
+ FolderInstanceProvider * m_parent;
+ unique_qobject_ptr<Task> m_child;
+ QString m_instanceName;
+ QString m_groupName;
+ QTimer m_backoffTimer;
+};
+
+#include "InstanceImportTask.h"
+Task * FolderInstanceProvider::zipImportTask(const QUrl sourceUrl, const QString& instName, const QString& instGroup, const QString& instIcon)
+{
+ auto stagingPath = getStagedInstancePath();
+ auto task = new InstanceImportTask(m_globalSettings, sourceUrl, stagingPath, instName, instIcon, instGroup);
+ return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup);
+}
+
+#include "InstanceCreationTask.h"
+Task * FolderInstanceProvider::creationTask(BaseVersionPtr version, const QString& instName, const QString& instGroup, const QString& instIcon)
+{
+ auto stagingPath = getStagedInstancePath();
+ auto task = new InstanceCreationTask(m_globalSettings, stagingPath, version, instName, instIcon, instGroup);
+ return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup);
+}
+
+#include <modplatform/FtbPackInstallTask.h>
+Task * FolderInstanceProvider::ftbCreationTask(FtbPackDownloader *downloader, const QString& instName, const QString& instGroup, const QString& instIcon)
+{
+ auto stagingPath = getStagedInstancePath();
+ auto task = new FtbPackInstallTask(downloader, m_globalSettings, stagingPath, instName, instIcon, instGroup);
+ return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup);
+}
+
+#include "InstanceCopyTask.h"
+Task * FolderInstanceProvider::copyTask(const InstancePtr& oldInstance, const QString& instName, const QString& instGroup, const QString& instIcon, bool copySaves)
+{
+ auto stagingPath = getStagedInstancePath();
+ auto task = new InstanceCopyTask(m_globalSettings, stagingPath, oldInstance, instName, instIcon, instGroup, copySaves);
+ return new FolderInstanceStaging(this, task, stagingPath, instName, instGroup);
+}
+
+// FIXME: find a better place for this
+#include "minecraft/legacy/LegacyUpgradeTask.h"
+Task * FolderInstanceProvider::legacyUpgradeTask(const InstancePtr& oldInstance)
+{
+ auto stagingPath = getStagedInstancePath();
+ QString newName = tr("%1 (Migrated)").arg(oldInstance->name());
+ auto task = new LegacyUpgradeTask(m_globalSettings, stagingPath, oldInstance, newName);
+ return new FolderInstanceStaging(this, task, stagingPath, newName, oldInstance->group());
+}
+
QString FolderInstanceProvider::getStagedInstancePath()
{
QString key = QUuid::createUuid().toString();
@@ -324,21 +467,16 @@ QString FolderInstanceProvider::getStagedInstancePath()
return path;
}
-bool FolderInstanceProvider::commitStagedInstance(const QString& keyPath, const QString& path, const QString& instanceName,
- const QString& groupName)
+bool FolderInstanceProvider::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName)
{
- if(!path.contains(keyPath))
- {
- qWarning() << "It is not possible to commit" << path << "because it is not in" << keyPath;
- return false;
- }
QDir dir;
QString instID = FS::DirNameFromString(instanceName, m_instDir);
{
WatchLock lock(m_watcher, m_instDir);
- if(!dir.rename(path, FS::PathCombine(m_instDir, instID)))
+ QString destination = FS::PathCombine(m_instDir, instID);
+ if(!dir.rename(path, destination))
{
- destroyStagingPath(keyPath);
+ qWarning() << "Failed to move" << path << "to" << destination;
return false;
}
groupMap[instID] = groupName;
@@ -354,3 +492,4 @@ bool FolderInstanceProvider::destroyStagingPath(const QString& keyPath)
return FS::deletePath(keyPath);
}
+#include "FolderInstanceProvider.moc"
diff --git a/api/logic/FolderInstanceProvider.h b/api/logic/FolderInstanceProvider.h
index f350a96d..5117affc 100644
--- a/api/logic/FolderInstanceProvider.h
+++ b/api/logic/FolderInstanceProvider.h
@@ -2,6 +2,7 @@
#include "BaseInstanceProvider.h"
#include <QMap>
+#include <modplatform/FtbPackDownloader.h>
class QFileSystemWatcher;
@@ -28,6 +29,12 @@ public:
// import zipped instance into this provider
Task * zipImportTask(const QUrl sourceUrl, const QString &instName, const QString &instGroup, const QString &instIcon);
+ //create FtbInstance
+ Task * ftbCreationTask(FtbPackDownloader *downloader, const QString &instName, const QString &instGroup, const QString &instIcon);
+
+ // migrate an instance to the current format
+ Task * legacyUpgradeTask(const InstancePtr& oldInstance);
+
/**
* Create a new empty staging area for instance creation and @return a path/key top commit it later.
* Used by instance manipulation tasks.
@@ -37,7 +44,7 @@ public:
* Commit the staging area given by @keyPath to the provider - used when creation succeeds.
* Used by instance manipulation tasks.
*/
- bool commitStagedInstance(const QString & keyPath, const QString & path, const QString& instanceName, const QString & groupName) override;
+ bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName) override;
/**
* Destroy a previously created staging area given by @keyPath - used when creation fails.
* Used by instance manipulation tasks.
diff --git a/api/logic/InstanceCopyTask.cpp b/api/logic/InstanceCopyTask.cpp
index b1bd39ef..9ede65f5 100644
--- a/api/logic/InstanceCopyTask.cpp
+++ b/api/logic/InstanceCopyTask.cpp
@@ -6,10 +6,10 @@
#include "pathmatcher/RegexpMatcher.h"
#include <QtConcurrentRun>
-InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider* target, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves)
+InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString& instName, const QString& instIcon, const QString& instGroup, bool copySaves)
{
m_globalSettings = settings;
- m_target = target;
+ m_stagingPath = stagingPath;
m_origInstance = origInstance;
m_instName = instName;
m_instIcon = instIcon;
@@ -27,7 +27,7 @@ InstanceCopyTask::InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvi
void InstanceCopyTask::executeTask()
{
setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
- m_stagingPath = m_target->getStagedInstancePath();
+
FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
folderCopy.followSymlinks(false).blacklist(m_matcher.get());
@@ -42,7 +42,6 @@ void InstanceCopyTask::copyFinished()
auto successful = m_copyFuture.result();
if(!successful)
{
- m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance folder copy failed."));
return;
}
@@ -50,19 +49,14 @@ void InstanceCopyTask::copyFinished()
auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
instanceSettings->registerSetting("InstanceType", "Legacy");
- // FIXME: and this too? errors???
- m_origInstance->copy(instanceSettings, m_stagingPath);
-
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
- m_target->commitStagedInstance(m_stagingPath, m_stagingPath, m_instName, m_instGroup);
emitSucceeded();
}
void InstanceCopyTask::copyAborted()
{
- m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance folder copy has been aborted."));
return;
}
diff --git a/api/logic/InstanceCopyTask.h b/api/logic/InstanceCopyTask.h
index 28fd3f40..dc46bfec 100644
--- a/api/logic/InstanceCopyTask.h
+++ b/api/logic/InstanceCopyTask.h
@@ -17,7 +17,7 @@ class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public Task
{
Q_OBJECT
public:
- explicit InstanceCopyTask(SettingsObjectPtr settings, BaseInstanceProvider * target, InstancePtr origInstance, const QString &instName,
+ explicit InstanceCopyTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString &instName,
const QString &instIcon, const QString &instGroup, bool copySaves);
protected:
@@ -28,7 +28,6 @@ protected:
private: /* data */
SettingsObjectPtr m_globalSettings;
- BaseInstanceProvider * m_target = nullptr;
InstancePtr m_origInstance;
QString m_instName;
QString m_instIcon;
diff --git a/api/logic/InstanceCreationTask.cpp b/api/logic/InstanceCreationTask.cpp
index e7b0de7c..8a68815a 100644
--- a/api/logic/InstanceCreationTask.cpp
+++ b/api/logic/InstanceCreationTask.cpp
@@ -4,13 +4,14 @@
#include "FileSystem.h"
//FIXME: remove this
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
-InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider* target, BaseVersionPtr version,
+InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, BaseVersionPtr version,
const QString& instName, const QString& instIcon, const QString& instGroup)
{
m_globalSettings = settings;
- m_target = target;
+ m_stagingPath = stagingPath;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
@@ -20,27 +21,19 @@ InstanceCreationTask::InstanceCreationTask(SettingsObjectPtr settings, BaseInsta
void InstanceCreationTask::executeTask()
{
setStatus(tr("Creating instance from version %1").arg(m_version->name()));
- /*
- auto minecraftVersion = std::dynamic_pointer_cast<MinecraftVersion>(m_version);
- if(!minecraftVersion)
{
- emitFailed(tr("The supplied version is not a Minecraft version."));
- return ;
+ auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
+ instanceSettings->suspendSave();
+ instanceSettings->registerSetting("InstanceType", "Legacy");
+ instanceSettings->set("InstanceType", "OneSix");
+ MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
+ auto components = inst.getComponentList();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
+ inst.setName(m_instName);
+ inst.setIconKey(m_instIcon);
+ inst.init();
+ instanceSettings->resumeSave();
}
- */
-
- QString stagingPath = m_target->getStagedInstancePath();
- QDir rootDir(stagingPath);
-
- auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(stagingPath, "instance.cfg"));
- instanceSettings->registerSetting("InstanceType", "Legacy");
-
- instanceSettings->set("InstanceType", "OneSix");
- InstancePtr inst(new OneSixInstance(m_globalSettings, instanceSettings, stagingPath));
- inst->setIntendedVersionId(m_version->descriptor());
- inst->setName(m_instName);
- inst->setIconKey(m_instIcon);
- inst->init();
- m_target->commitStagedInstance(stagingPath, stagingPath, m_instName, m_instGroup);
emitSucceeded();
}
diff --git a/api/logic/InstanceCreationTask.h b/api/logic/InstanceCreationTask.h
index b4ade320..49fd4615 100644
--- a/api/logic/InstanceCreationTask.h
+++ b/api/logic/InstanceCreationTask.h
@@ -7,13 +7,11 @@
#include "settings/SettingsObject.h"
#include "BaseVersion.h"
-class BaseInstanceProvider;
-
class MULTIMC_LOGIC_EXPORT InstanceCreationTask : public Task
{
Q_OBJECT
public:
- explicit InstanceCreationTask(SettingsObjectPtr settings, BaseInstanceProvider * target, BaseVersionPtr version, const QString &instName,
+ explicit InstanceCreationTask(SettingsObjectPtr settings, const QString & stagingPath, BaseVersionPtr version, const QString &instName,
const QString &instIcon, const QString &instGroup);
protected:
@@ -22,7 +20,7 @@ protected:
private: /* data */
SettingsObjectPtr m_globalSettings;
- BaseInstanceProvider * m_target;
+ QString m_stagingPath;
BaseVersionPtr m_version;
QString m_instName;
QString m_instIcon;
diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp
index f1b3d5aa..2b481300 100644
--- a/api/logic/InstanceImportTask.cpp
+++ b/api/logic/InstanceImportTask.cpp
@@ -1,5 +1,3 @@
-#include "minecraft/onesix/OneSixInstance.h"
-
#include "InstanceImportTask.h"
#include "BaseInstance.h"
#include "BaseInstanceProvider.h"
@@ -12,16 +10,18 @@
#include <QtConcurrentRun>
// FIXME: this does not belong here, it's Minecraft/Flame specific
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
#include "minecraft/flame/FileResolvingTask.h"
#include "minecraft/flame/PackManifest.h"
#include "Json.h"
-InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target,
+InstanceImportTask::InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, const QString & stagingPath,
const QString &instName, const QString &instIcon, const QString &instGroup)
{
m_globalSettings = settings;
m_sourceUrl = sourceUrl;
- m_target = target;
+ m_stagingPath = stagingPath;
m_instName = instName;
m_instIcon = instIcon;
m_instGroup = instGroup;
@@ -34,7 +34,7 @@ void InstanceImportTask::executeTask()
if (m_sourceUrl.isLocalFile())
{
m_archivePath = m_sourceUrl.toLocalFile();
- extractAndTweak();
+ processZipPack();
}
else
{
@@ -57,7 +57,7 @@ void InstanceImportTask::executeTask()
void InstanceImportTask::downloadSucceeded()
{
- extractAndTweak();
+ processZipPack();
m_filesNetJob.reset();
}
@@ -72,34 +72,47 @@ void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total)
setProgress(current / 2, total);
}
-static QFileInfo findRecursive(const QString &dir, const QString &name)
-{
- for (const auto info : QDir(dir).entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files, QDir::DirsLast))
- {
- if (info.isFile() && info.fileName() == name)
- {
- return info;
- }
- else if (info.isDir())
- {
- const QFileInfo res = findRecursive(info.absoluteFilePath(), name);
- if (res.isFile() && res.exists())
- {
- return res;
- }
- }
- }
- return QFileInfo();
-}
-
-void InstanceImportTask::extractAndTweak()
+void InstanceImportTask::processZipPack()
{
setStatus(tr("Extracting modpack"));
- m_stagingPath = m_target->getStagedInstancePath();
QDir extractDir(m_stagingPath);
qDebug() << "Attempting to create instance from" << m_archivePath;
- m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, m_archivePath, extractDir.absolutePath());
+ // open the zip and find relevant files in it
+ m_packZip.reset(new QuaZip(m_archivePath));
+ if (!m_packZip->open(QuaZip::mdUnzip))
+ {
+ emitFailed(tr("Unable to open supplied modpack zip file."));
+ return;
+ }
+
+ QStringList blacklist = {"instance.cfg", "manifest.json"};
+ QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
+ QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
+ QString root;
+ if(!mmcFound.isNull())
+ {
+ // process as MultiMC instance/pack
+ qDebug() << "MultiMC:" << mmcFound;
+ root = mmcFound;
+ m_modpackType = ModpackType::MultiMC;
+ }
+ else if(!flameFound.isNull())
+ {
+ // process as Flame pack
+ qDebug() << "Flame:" << flameFound;
+ root = flameFound;
+ m_modpackType = ModpackType::Flame;
+ }
+
+ if(m_modpackType == ModpackType::Unknown)
+ {
+ emitFailed(tr("Archive does not contain a recognized modpack type."));
+ return;
+ }
+
+ // make sure we extract just the pack
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), root, extractDir.absolutePath());
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &InstanceImportTask::extractFinished);
connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &InstanceImportTask::extractAborted);
m_extractFutureWatcher.setFuture(m_extractFuture);
@@ -107,9 +120,9 @@ void InstanceImportTask::extractAndTweak()
void InstanceImportTask::extractFinished()
{
+ m_packZip.reset();
if (m_extractFuture.result().isEmpty())
{
- m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Failed to extract modpack"));
return;
}
@@ -137,7 +150,7 @@ void InstanceImportTask::extractFinished()
{
if(!QFile::setPermissions(filepath, permissions))
{
- qWarning() << "Could not fix" << filepath;
+ logWarning(tr("Could not fix permissions for %1").arg(filepath));
}
else
{
@@ -146,34 +159,27 @@ void InstanceImportTask::extractFinished()
}
}
- const QFileInfo instanceCfgFile = findRecursive(extractDir.absolutePath(), "instance.cfg");
- const QFileInfo flameJson = findRecursive(extractDir.absolutePath(), "manifest.json");
- if (instanceCfgFile.isFile())
- {
- qDebug() << "Pack appears to be exported from MultiMC.";
- processMultiMC(instanceCfgFile);
- }
- else if (flameJson.isFile())
+ switch(m_modpackType)
{
- qDebug() << "Pack appears to be from 'Flame'.";
- processFlame(flameJson);
- }
- else
- {
- qCritical() << "Archive does not contain a recognized modpack type.";
- m_target->destroyStagingPath(m_stagingPath);
- emitFailed(tr("Archive does not contain a recognized modpack type."));
+ case ModpackType::Flame:
+ processFlame();
+ return;
+ case ModpackType::MultiMC:
+ processMultiMC();
+ return;
+ case ModpackType::Unknown:
+ emitFailed(tr("Archive does not contain a recognized modpack type."));
+ return;
}
}
void InstanceImportTask::extractAborted()
{
- m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Instance import has been aborted."));
return;
}
-void InstanceImportTask::processFlame(const QFileInfo & manifest)
+void InstanceImportTask::processFlame()
{
const static QMap<QString,QString> forgemap = {
{"1.2.5", "3.4.9.171"},
@@ -184,24 +190,30 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
Flame::Manifest pack;
try
{
- Flame::loadManifest(pack, manifest.absoluteFilePath());
+ QString configPath = FS::PathCombine(m_stagingPath, "manifest.json");
+ Flame::loadManifest(pack, configPath);
+ QFile::remove(configPath);
}
catch (JSONValidationError & e)
{
- m_target->destroyStagingPath(m_stagingPath);
emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
return;
}
- m_packRoot = manifest.absolutePath();
if(!pack.overrides.isEmpty())
{
- QString overridePath = FS::PathCombine(m_packRoot, pack.overrides);
- QString mcPath = FS::PathCombine(m_packRoot, "minecraft");
- if (!QFile::rename(overridePath, mcPath))
+ QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides);
+ if (QFile::exists(overridePath))
{
- m_target->destroyStagingPath(m_stagingPath);
- emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides);
- return;
+ QString mcPath = FS::PathCombine(m_stagingPath, "minecraft");
+ if (!QFile::rename(overridePath, mcPath))
+ {
+ emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides);
+ return;
+ }
+ }
+ else
+ {
+ logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides));
}
}
@@ -215,22 +227,24 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
forgeVersion = id;
continue;
}
- qWarning() << "Unknown mod loader in manifest:" << id;
+ logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
}
- QString configPath = FS::PathCombine(m_packRoot, "instance.cfg");
+ QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
- OneSixInstance instance(m_globalSettings, instanceSettings, m_packRoot);
+ MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
auto mcVersion = pack.minecraft.version;
// Hack to correct some 'special sauce'...
if(mcVersion.endsWith('.'))
{
mcVersion.remove(QRegExp("[.]+$"));
- qWarning() << "Mysterious trailing dots removed from Minecraft version while importing pack.";
+ logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
}
- instance.setComponentVersion("net.minecraft", mcVersion);
+ auto components = instance.getComponentList();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", mcVersion, true);
if(!forgeVersion.isEmpty())
{
// FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata.
@@ -242,10 +256,10 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
}
else
{
- qWarning() << "Could not map recommended forge version for" << mcVersion;
+ logWarning(tr("Could not map recommended forge version for Minecraft %1").arg(mcVersion));
}
}
- instance.setComponentVersion("net.minecraftforge", forgeVersion);
+ components->setComponentVersion("net.minecraftforge", forgeVersion);
}
if (m_instIcon != "default")
{
@@ -268,7 +282,7 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
}
}
instance.init();
- QString jarmodsPath = FS::PathCombine(m_packRoot, "minecraft", "jarmods");
+ QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods");
QFileInfo jarmodsInfo(jarmodsPath);
if(jarmodsInfo.isDir())
{
@@ -281,7 +295,7 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
qDebug() << info.fileName();
jarMods.push_back(info.absoluteFilePath());
}
- auto profile = instance.getMinecraftProfile();
+ auto profile = instance.getComponentList();
profile->installJarMods(jarMods);
// nuke the original files
FS::deletePath(jarmodsPath);
@@ -294,26 +308,49 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
m_filesNetJob.reset(new NetJob(tr("Mod download")));
for(auto result: results.files)
{
- auto path = FS::PathCombine(m_packRoot, "minecraft/mods", result.fileName);
- auto dl = Net::Download::makeFile(result.url,path);
- m_filesNetJob->addNetAction(dl);
+ QString filename = result.fileName;
+ if(!result.required)
+ {
+ filename += ".disabled";
+ }
+
+ auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename);
+ auto path = FS::PathCombine(m_stagingPath , relpath);
+
+ switch(result.type)
+ {
+ case Flame::File::Type::Folder:
+ {
+ logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath));
+ // fall-through intentional, we treat these as plain old mods and dump them wherever.
+ }
+ case Flame::File::Type::SingleFile:
+ case Flame::File::Type::Mod:
+ {
+ qDebug() << "Will download" << result.url << "to" << path;
+ auto dl = Net::Download::makeFile(result.url, path);
+ m_filesNetJob->addNetAction(dl);
+ break;
+ }
+ case Flame::File::Type::Modpack:
+ logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath));
+ break;
+ case Flame::File::Type::Cmod2:
+ case Flame::File::Type::Ctoc:
+ case Flame::File::Type::Unknown:
+ logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath));
+ break;
+ }
}
m_modIdResolver.reset();
connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]()
{
m_filesNetJob.reset();
- if (!m_target->commitStagedInstance(m_stagingPath, m_packRoot, m_instName, m_instGroup))
- {
- m_target->destroyStagingPath(m_stagingPath);
- emitFailed(tr("Unable to commit instance"));
- return;
- }
emitSucceeded();
}
);
connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason)
{
- m_target->destroyStagingPath(m_stagingPath);
m_filesNetJob.reset();
emitFailed(reason);
});
@@ -327,7 +364,6 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
);
connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason)
{
- m_target->destroyStagingPath(m_stagingPath);
m_modIdResolver.reset();
emitFailed(tr("Unable to resolve mod IDs:\n") + reason);
});
@@ -342,14 +378,14 @@ void InstanceImportTask::processFlame(const QFileInfo & manifest)
m_modIdResolver->start();
}
-void InstanceImportTask::processMultiMC(const QFileInfo & config)
+void InstanceImportTask::processMultiMC()
{
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
- auto instanceSettings = std::make_shared<INISettingsObject>(config.absoluteFilePath());
+ QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg");
+ auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
instanceSettings->registerSetting("InstanceType", "Legacy");
- QString actualDir = config.absolutePath();
- NullInstance instance(m_globalSettings, instanceSettings, actualDir);
+ NullInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
// reset time played on import... because packs.
instance.resetTimePlayed();
@@ -377,11 +413,5 @@ void InstanceImportTask::processMultiMC(const QFileInfo & config)
iconList->installIcons({importIconPath});
}
}
- if (!m_target->commitStagedInstance(m_stagingPath, actualDir, m_instName, m_instGroup))
- {
- m_target->destroyStagingPath(m_stagingPath);
- emitFailed(tr("Unable to commit instance"));
- return;
- }
emitSucceeded();
}
diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h
index d5192299..99397009 100644
--- a/api/logic/InstanceImportTask.h
+++ b/api/logic/InstanceImportTask.h
@@ -9,6 +9,7 @@
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
+class QuaZip;
class BaseInstanceProvider;
namespace Flame
{
@@ -19,7 +20,7 @@ class MULTIMC_LOGIC_EXPORT InstanceImportTask : public Task
{
Q_OBJECT
public:
- explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, BaseInstanceProvider * target, const QString &instName,
+ explicit InstanceImportTask(SettingsObjectPtr settings, const QUrl sourceUrl, const QString & stagingPath, const QString &instName,
const QString &instIcon, const QString &instGroup);
protected:
@@ -27,9 +28,9 @@ protected:
virtual void executeTask() override;
private:
- void extractAndTweak();
- void processMultiMC(const QFileInfo &config);
- void processFlame(const QFileInfo &manifest);
+ void processZipPack();
+ void processMultiMC();
+ void processFlame();
private slots:
void downloadSucceeded();
@@ -43,14 +44,18 @@ private: /* data */
NetJobPtr m_filesNetJob;
shared_qobject_ptr<Flame::FileResolvingTask> m_modIdResolver;
QUrl m_sourceUrl;
- BaseInstanceProvider * m_target;
QString m_archivePath;
bool m_downloadRequired = false;
- QString m_packRoot;
QString m_instName;
QString m_instIcon;
QString m_instGroup;
QString m_stagingPath;
+ std::unique_ptr<QuaZip> m_packZip;
QFuture<QStringList> m_extractFuture;
QFutureWatcher<QStringList> m_extractFutureWatcher;
+ enum class ModpackType{
+ Unknown,
+ MultiMC,
+ Flame
+ } m_modpackType = ModpackType::Unknown;
};
diff --git a/api/logic/InstanceList.cpp b/api/logic/InstanceList.cpp
index e929293f..75b523e4 100644
--- a/api/logic/InstanceList.cpp
+++ b/api/logic/InstanceList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,10 +26,9 @@
#include "FolderInstanceProvider.h"
-InstanceList::InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent)
- : QAbstractListModel(parent), m_instDir(instDir)
+InstanceList::InstanceList(QObject *parent)
+ : QAbstractListModel(parent)
{
- m_globalSettings = globalSettings;
resumeWatch();
}
@@ -241,6 +240,14 @@ InstanceList::InstListError InstanceList::loadList(bool complete)
return NoError;
}
+void InstanceList::saveNow()
+{
+ for(auto & item: m_instances)
+ {
+ item->saveNow();
+ }
+}
+
void InstanceList::add(const QList<InstancePtr> &t)
{
beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1);
@@ -338,5 +345,3 @@ void InstanceList::propertiesChanged(BaseInstance *inst)
emit dataChanged(index(i), index(i));
}
}
-
-#include "InstanceList.moc"
diff --git a/api/logic/InstanceList.h b/api/logic/InstanceList.h
index ea4717ff..bb879c83 100644
--- a/api/logic/InstanceList.h
+++ b/api/logic/InstanceList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -27,16 +27,14 @@
#include "QObjectPtr.h"
-class QFileSystemWatcher;
class BaseInstance;
-class QDir;
class MULTIMC_LOGIC_EXPORT InstanceList : public QAbstractListModel
{
Q_OBJECT
public:
- explicit InstanceList(SettingsObjectPtr globalSettings, const QString &instDir, QObject *parent = 0);
+ explicit InstanceList(QObject *parent = 0);
virtual ~InstanceList();
public:
@@ -73,6 +71,7 @@ public:
}
InstListError loadList(bool complete = false);
+ void saveNow();
/// Add an instance provider. Takes ownership of it. Should only be done before the first load.
void addInstanceProvider(BaseInstanceProvider * provider);
@@ -100,9 +99,7 @@ private:
protected:
int m_watchLevel = 0;
QSet<BaseInstanceProvider *> m_updatedProviders;
- QString m_instDir;
QList<InstancePtr> m_instances;
QSet<QString> m_groups;
- SettingsObjectPtr m_globalSettings;
QVector<shared_qobject_ptr<BaseInstanceProvider>> m_providers;
};
diff --git a/api/logic/LoggedProcess.h b/api/logic/LoggedProcess.h
index bf3dc60e..6a80365f 100644
--- a/api/logic/LoggedProcess.h
+++ b/api/logic/LoggedProcess.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/MMCZip.cpp b/api/logic/MMCZip.cpp
index 3badbbd1..e0c64877 100644
--- a/api/logic/MMCZip.cpp
+++ b/api/logic/MMCZip.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -168,7 +168,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const
}
// ours
-QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString &root)
+QString MMCZip::findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root)
{
QuaZipDir rootDir(zip, root);
for(auto fileName: rootDir.entryList(QDir::Files))
@@ -178,7 +178,7 @@ QString MMCZip::findFileInZip(QuaZip * zip, const QString & what, const QString
}
for(auto fileName: rootDir.entryList(QDir::Dirs))
{
- QString result = findFileInZip(zip, what, root + fileName);
+ QString result = findFolderOfFileInZip(zip, what, root + fileName);
if(!result.isEmpty())
{
return result;
diff --git a/api/logic/MMCZip.h b/api/logic/MMCZip.h
index eac8d741..68094b2c 100644
--- a/api/logic/MMCZip.h
+++ b/api/logic/MMCZip.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ namespace MMCZip
*
* \return the path prefix where the file is
*/
- QString MULTIMC_LOGIC_EXPORT findFileInZip(QuaZip * zip, const QString & what, const QString &root = QString());
+ QString MULTIMC_LOGIC_EXPORT findFolderOfFileInZip(QuaZip * zip, const QString & what, const QString &root = QString(""));
/**
* Find a multiple files of the same name in archive by file name
diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h
index b530acd3..64965277 100644
--- a/api/logic/NullInstance.h
+++ b/api/logic/NullInstance.h
@@ -10,30 +10,17 @@ public:
setVersionBroken(true);
}
virtual ~NullInstance() {};
- virtual bool setIntendedVersionId(QString) override
+ virtual void init() override
{
- return false;
}
- virtual QString currentVersionId() const override
- {
- return "Null";
- };
- virtual QString intendedVersionId() const override
- {
- return "Null";
- };
- virtual void init() override
+ virtual void saveNow() override
{
- };
+ }
virtual QString getStatusbarDescription() override
{
return tr("Unknown instance type");
};
- virtual bool shouldUpdate() const override
- {
- return false;
- };
- virtual QSet< QString > traits() override
+ virtual QSet< QString > traits() const override
{
return {};
};
@@ -45,21 +32,10 @@ public:
{
return nullptr;
}
- virtual shared_qobject_ptr< Task > createUpdateTask() override
+ virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
{
return nullptr;
}
- virtual std::shared_ptr<Task> createJarModdingTask() override
- {
- return nullptr;
- }
- virtual void setShouldUpdate(bool) override
- {
- };
- virtual std::shared_ptr< BaseVersionList > versionList() const override
- {
- return nullptr;
- };
virtual QProcessEnvironment createEnvironment() override
{
return QProcessEnvironment();
@@ -84,6 +60,14 @@ public:
{
return false;
}
+ bool canEdit() const override
+ {
+ return false;
+ }
+ bool canLaunch() const override
+ {
+ return false;
+ }
QStringList verboseDescription(AuthSessionPtr session) override
{
QStringList out;
diff --git a/api/logic/ProblemProvider.h b/api/logic/ProblemProvider.h
index b30e1776..978710f0 100644
--- a/api/logic/ProblemProvider.h
+++ b/api/logic/ProblemProvider.h
@@ -1,5 +1,7 @@
#pragma once
+#include "multimc_logic_export.h"
+
enum class ProblemSeverity
{
None,
@@ -7,42 +9,28 @@ enum class ProblemSeverity
Error
};
-class PatchProblem
+struct PatchProblem
{
-public:
- PatchProblem(ProblemSeverity severity, const QString & description)
- {
- m_severity = severity;
- m_description = description;
- }
- const QString & getDescription() const
- {
- return m_description;
- }
- const ProblemSeverity getSeverity() const
- {
- return m_severity;
- }
-private:
ProblemSeverity m_severity;
QString m_description;
};
-class ProblemProvider
+class MULTIMC_LOGIC_EXPORT ProblemProvider
{
public:
- virtual const QList<PatchProblem> getProblems() = 0;
- virtual ProblemSeverity getProblemSeverity() = 0;
+ virtual ~ProblemProvider() {};
+ virtual const QList<PatchProblem> getProblems() const = 0;
+ virtual ProblemSeverity getProblemSeverity() const = 0;
};
-class ProblemContainer : public ProblemProvider
+class MULTIMC_LOGIC_EXPORT ProblemContainer : public ProblemProvider
{
public:
- const QList<PatchProblem> getProblems() override
+ const QList<PatchProblem> getProblems() const override
{
return m_problems;
}
- ProblemSeverity getProblemSeverity() override
+ ProblemSeverity getProblemSeverity() const override
{
return m_problemSeverity;
}
@@ -52,7 +40,7 @@ public:
{
m_problemSeverity = severity;
}
- m_problems.append(PatchProblem(severity, description));
+ m_problems.append({severity, description});
}
private:
diff --git a/api/logic/Version_test.cpp b/api/logic/Version_test.cpp
index 1e7920ad..b8e05768 100644
--- a/api/logic/Version_test.cpp
+++ b/api/logic/Version_test.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/java/JavaChecker.cpp b/api/logic/java/JavaChecker.cpp
index ebbd80be..f0b71e48 100644
--- a/api/logic/java/JavaChecker.cpp
+++ b/api/logic/java/JavaChecker.cpp
@@ -1,4 +1,5 @@
#include "JavaChecker.h"
+#include "JavaUtils.h"
#include <FileSystem.h>
#include <Commandline.h>
#include <QFile>
@@ -42,6 +43,7 @@ void JavaChecker::performCheck()
process->setArguments(args);
process->setProgram(m_path);
process->setProcessChannelMode(QProcess::SeparateChannels);
+ process->setProcessEnvironment(CleanEnviroment());
qDebug() << "Running java checker: " + m_path + args.join(" ");;
connect(process.get(), SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(finished(int, QProcess::ExitStatus)));
diff --git a/api/logic/java/JavaCheckerJob.cpp b/api/logic/java/JavaCheckerJob.cpp
index 01b5b28d..fabb5aaa 100644
--- a/api/logic/java/JavaCheckerJob.cpp
+++ b/api/logic/java/JavaCheckerJob.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,20 +22,19 @@ void JavaCheckerJob::partFinished(JavaCheckResult result)
num_finished++;
qDebug() << m_job_name.toLocal8Bit() << "progress:" << num_finished << "/"
<< javacheckers.size();
- emit progress(num_finished, javacheckers.size());
+ setProgress(num_finished, javacheckers.size());
javaresults.replace(result.id, result);
if (num_finished == javacheckers.size())
{
- emit finished(javaresults);
+ emitSucceeded();
}
}
void JavaCheckerJob::executeTask()
{
qDebug() << m_job_name.toLocal8Bit() << " started.";
- m_running = true;
for (auto iter : javacheckers)
{
javaresults.append(JavaCheckResult());
diff --git a/api/logic/java/JavaCheckerJob.h b/api/logic/java/JavaCheckerJob.h
index 58a98190..cac2b638 100644
--- a/api/logic/java/JavaCheckerJob.h
+++ b/api/logic/java/JavaCheckerJob.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
class JavaCheckerJob;
typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
+// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task
{
Q_OBJECT
@@ -31,41 +32,19 @@ public:
bool addJavaCheckerAction(JavaCheckerPtr base)
{
javacheckers.append(base);
- total_progress++;
// if this is already running, the action needs to be started right away!
if (isRunning())
{
- setProgress(current_progress, total_progress);
- connect(base.get(), SIGNAL(checkFinished(JavaCheckResult)), SLOT(partFinished(JavaCheckResult)));
-
+ setProgress(num_finished, javacheckers.size());
+ connect(base.get(), &JavaChecker::checkFinished, this, &JavaCheckerJob::partFinished);
base->performCheck();
}
return true;
}
-
- JavaCheckerPtr operator[](int index)
+ QList<JavaCheckResult> getResults()
{
- return javacheckers[index];
+ return javaresults;
}
- ;
- JavaCheckerPtr first()
- {
- if (javacheckers.size())
- return javacheckers[0];
- return JavaCheckerPtr();
- }
- int size() const
- {
- return javacheckers.size();
- }
- virtual bool isRunning() const override
- {
- return m_running;
- }
-
-signals:
- void started();
- void finished(QList<JavaCheckResult>);
private slots:
void partFinished(JavaCheckResult result);
@@ -77,8 +56,5 @@ private:
QString m_job_name;
QList<JavaCheckerPtr> javacheckers;
QList<JavaCheckResult> javaresults;
- qint64 current_progress = 0;
- qint64 total_progress = 0;
int num_finished = 0;
- bool m_running = false;
};
diff --git a/api/logic/java/JavaInstallList.cpp b/api/logic/java/JavaInstallList.cpp
index 44ac861d..9d2e2f8b 100644
--- a/api/logic/java/JavaInstallList.cpp
+++ b/api/logic/java/JavaInstallList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -150,7 +150,7 @@ void JavaListLoadTask::executeTask()
QList<QString> candidate_paths = ju.FindJavaPaths();
m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
- connect(m_job.get(), SIGNAL(finished(QList<JavaCheckResult>)), this, SLOT(javaCheckerFinished(QList<JavaCheckResult>)));
+ connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
qDebug() << "Probing the following Java paths: ";
@@ -170,9 +170,10 @@ void JavaListLoadTask::executeTask()
m_job->start();
}
-void JavaListLoadTask::javaCheckerFinished(QList<JavaCheckResult> results)
+void JavaListLoadTask::javaCheckerFinished()
{
QList<JavaInstallPtr> candidates;
+ auto results = m_job->getResults();
qDebug() << "Found the following valid Java installations:";
for(JavaCheckResult result : results)
diff --git a/api/logic/java/JavaInstallList.h b/api/logic/java/JavaInstallList.h
index 934e588b..39f37b80 100644
--- a/api/logic/java/JavaInstallList.h
+++ b/api/logic/java/JavaInstallList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ public:
void executeTask() override;
public slots:
- void javaCheckerFinished(QList<JavaCheckResult> results);
+ void javaCheckerFinished();
protected:
std::shared_ptr<JavaCheckerJob> m_job;
diff --git a/api/logic/java/JavaUtils.cpp b/api/logic/java/JavaUtils.cpp
index 0c2e72d7..4a77bc7e 100644
--- a/api/logic/java/JavaUtils.cpp
+++ b/api/logic/java/JavaUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,14 +22,105 @@
#include <QDebug>
#include "java/JavaUtils.h"
-#include "java/JavaCheckerJob.h"
#include "java/JavaInstallList.h"
#include "FileSystem.h"
+#define IBUS "@im=ibus"
+
JavaUtils::JavaUtils()
{
}
+static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
+{
+ QDir mmcBin(QCoreApplication::applicationDirPath());
+ auto items = LD_LIBRARY_PATH.split(':');
+ QStringList final;
+ for(auto & item: items)
+ {
+ QDir test(item);
+ if(test == mmcBin)
+ {
+ qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
+ continue;
+ }
+ final.append(item);
+ }
+ return final.join(':');
+}
+
+QProcessEnvironment CleanEnviroment()
+{
+ // prepare the process environment
+ QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
+ QProcessEnvironment env;
+
+ QStringList ignored =
+ {
+ "JAVA_ARGS",
+ "CLASSPATH",
+ "CONFIGPATH",
+ "JAVA_HOME",
+ "JRE_HOME",
+ "_JAVA_OPTIONS",
+ "JAVA_OPTIONS",
+ "JAVA_TOOL_OPTIONS"
+ };
+ for(auto key: rawenv.keys())
+ {
+ auto value = rawenv.value(key);
+ // filter out dangerous java crap
+ if(ignored.contains(key))
+ {
+ qDebug() << "Env: ignoring" << key << value;
+ continue;
+ }
+ // filter MultiMC-related things
+ if(key.startsWith("QT_"))
+ {
+ qDebug() << "Env: ignoring" << key << value;
+ continue;
+ }
+#ifdef Q_OS_LINUX
+ // Do not pass LD_* variables to java. They were intended for MultiMC
+ if(key.startsWith("LD_"))
+ {
+ qDebug() << "Env: ignoring" << key << value;
+ continue;
+ }
+ // Strip IBus
+ // IBus is a Linux IME framework. For some reason, it breaks MC?
+ if (key == "XMODIFIERS" && value.contains(IBUS))
+ {
+ QString save = value;
+ value.replace(IBUS, "");
+ qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
+ }
+ if(key == "GAME_PRELOAD")
+ {
+ env.insert("LD_PRELOAD", value);
+ continue;
+ }
+ if(key == "GAME_LIBRARY_PATH")
+ {
+ env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
+ continue;
+ }
+#endif
+ // qDebug() << "Env: " << key << value;
+ env.insert(key, value);
+ }
+#ifdef Q_OS_LINUX
+ // HACK: Workaround for QTBUG42500
+ if(!env.contains("LD_LIBRARY_PATH"))
+ {
+ env.insert("LD_LIBRARY_PATH", "");
+ }
+#endif
+
+ return env;
+}
+
JavaInstallPtr JavaUtils::MakeJavaPtr(QString path, QString id, QString arch)
{
JavaInstallPtr javaVersion(new JavaInstall());
diff --git a/api/logic/java/JavaUtils.h b/api/logic/java/JavaUtils.h
index 4418ac26..b43e93cf 100644
--- a/api/logic/java/JavaUtils.h
+++ b/api/logic/java/JavaUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
#include <QStringList>
-#include "JavaCheckerJob.h"
#include "JavaChecker.h"
#include "JavaInstallList.h"
@@ -27,6 +26,8 @@
#include "multimc_logic_export.h"
+QProcessEnvironment CleanEnviroment();
+
class MULTIMC_LOGIC_EXPORT JavaUtils : public QObject
{
Q_OBJECT
diff --git a/api/logic/java/JavaVersion.cpp b/api/logic/java/JavaVersion.cpp
index 8c1bb430..27050da3 100644
--- a/api/logic/java/JavaVersion.cpp
+++ b/api/logic/java/JavaVersion.cpp
@@ -60,9 +60,18 @@ bool JavaVersion::operator<(const JavaVersion &rhs)
{
if(m_parseable && rhs.m_parseable)
{
- if(m_major < rhs.m_major)
+ auto major = m_major;
+ auto rmajor = rhs.m_major;
+
+ // HACK: discourage using java 9
+ if(major > 8)
+ major = -major;
+ if(rmajor > 8)
+ rmajor = -rmajor;
+
+ if(major < rmajor)
return true;
- if(m_major > rhs.m_major)
+ if(major > rmajor)
return false;
if(m_minor < rhs.m_minor)
return true;
diff --git a/api/logic/java/launch/CheckJava.cpp b/api/logic/java/launch/CheckJava.cpp
index f78e1cff..24f26682 100644
--- a/api/logic/java/launch/CheckJava.cpp
+++ b/api/logic/java/launch/CheckJava.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/java/launch/CheckJava.h b/api/logic/java/launch/CheckJava.h
index 3c812277..82508cd4 100644
--- a/api/logic/java/launch/CheckJava.h
+++ b/api/logic/java/launch/CheckJava.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/LaunchStep.cpp b/api/logic/launch/LaunchStep.cpp
index 7b6ffc23..01f72a0a 100644
--- a/api/logic/launch/LaunchStep.cpp
+++ b/api/logic/launch/LaunchStep.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/LaunchStep.h b/api/logic/launch/LaunchStep.h
index d645bd6c..80778e34 100644
--- a/api/logic/launch/LaunchStep.h
+++ b/api/logic/launch/LaunchStep.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/LaunchTask.cpp b/api/logic/launch/LaunchTask.cpp
index 23c28f50..99c16721 100644
--- a/api/logic/launch/LaunchTask.cpp
+++ b/api/logic/launch/LaunchTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -83,7 +83,7 @@ void LaunchTask::onStepFinished()
}
auto step = m_steps[currentStep];
- if(step->successful())
+ if(step->wasSuccessful())
{
// end?
if(currentStep == m_steps.size() - 1)
diff --git a/api/logic/launch/LaunchTask.h b/api/logic/launch/LaunchTask.h
index 2d95f98f..746d6d19 100644
--- a/api/logic/launch/LaunchTask.h
+++ b/api/logic/launch/LaunchTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/launch/LogModel.cpp b/api/logic/launch/LogModel.cpp
index 042feeab..72b076e9 100644
--- a/api/logic/launch/LogModel.cpp
+++ b/api/logic/launch/LogModel.cpp
@@ -69,6 +69,11 @@ void LogModel::suspend(bool suspend)
m_suspended = suspend;
}
+bool LogModel::suspended()
+{
+ return m_suspended;
+}
+
void LogModel::clear()
{
beginResetModel();
@@ -147,3 +152,16 @@ void LogModel::setOverflowMessage(const QString& overflowMessage)
{
m_overflowMessage = overflowMessage;
}
+
+void LogModel::setLineWrap(bool state)
+{
+ if(m_lineWrap != state)
+ {
+ m_lineWrap = state;
+ }
+}
+
+bool LogModel::wrapLines() const
+{
+ return m_lineWrap;
+}
diff --git a/api/logic/launch/LogModel.h b/api/logic/launch/LogModel.h
index 57cd23b0..e6deac89 100644
--- a/api/logic/launch/LogModel.h
+++ b/api/logic/launch/LogModel.h
@@ -17,7 +17,9 @@ public:
void append(MessageLevel::Enum, QString line);
void clear();
+
void suspend(bool suspend);
+ bool suspended();
QString toPlainText();
@@ -26,6 +28,9 @@ public:
void setStopOnOverflow(bool stop);
void setOverflowMessage(const QString & overflowMessage);
+ void setLineWrap(bool state);
+ bool wrapLines() const;
+
enum Roles
{
LevelRole = Qt::UserRole
@@ -48,6 +53,7 @@ private: /* data */
bool m_stopOnOverflow = false;
QString m_overflowMessage = "OVERFLOW";
bool m_suspended = false;
+ bool m_lineWrap = true;
private:
Q_DISABLE_COPY(LogModel)
diff --git a/api/logic/launch/steps/PostLaunchCommand.cpp b/api/logic/launch/steps/PostLaunchCommand.cpp
index 6c7c963c..bc3b3ffe 100644
--- a/api/logic/launch/steps/PostLaunchCommand.cpp
+++ b/api/logic/launch/steps/PostLaunchCommand.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/steps/PostLaunchCommand.h b/api/logic/launch/steps/PostLaunchCommand.h
index 432867eb..36b3b96f 100644
--- a/api/logic/launch/steps/PostLaunchCommand.h
+++ b/api/logic/launch/steps/PostLaunchCommand.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/steps/PreLaunchCommand.cpp b/api/logic/launch/steps/PreLaunchCommand.cpp
index 222800a7..7c37d5cb 100644
--- a/api/logic/launch/steps/PreLaunchCommand.cpp
+++ b/api/logic/launch/steps/PreLaunchCommand.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/steps/PreLaunchCommand.h b/api/logic/launch/steps/PreLaunchCommand.h
index f57d2e2f..880cc076 100644
--- a/api/logic/launch/steps/PreLaunchCommand.h
+++ b/api/logic/launch/steps/PreLaunchCommand.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/steps/TextPrint.h b/api/logic/launch/steps/TextPrint.h
index 63d01412..3e3e06cb 100644
--- a/api/logic/launch/steps/TextPrint.h
+++ b/api/logic/launch/steps/TextPrint.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/launch/steps/Update.cpp b/api/logic/launch/steps/Update.cpp
index 956230f4..d057febb 100644
--- a/api/logic/launch/steps/Update.cpp
+++ b/api/logic/launch/steps/Update.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,7 @@ void Update::executeTask()
emitFailed(tr("Task aborted."));
return;
}
- m_updateTask.reset(m_parent->instance()->createUpdateTask());
+ m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode));
if(m_updateTask)
{
connect(m_updateTask.get(), SIGNAL(finished()), this, SLOT(updateFinished()));
@@ -42,7 +42,7 @@ void Update::proceed()
void Update::updateFinished()
{
- if(m_updateTask->successful())
+ if(m_updateTask->wasSuccessful())
{
m_updateTask.reset();
emitSucceeded();
diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h
index d855a1db..7a14011a 100644
--- a/api/logic/launch/steps/Update.h
+++ b/api/logic/launch/steps/Update.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,13 +19,14 @@
#include <QObjectPtr.h>
#include <LoggedProcess.h>
#include <java/JavaChecker.h>
+#include <net/Mode.h>
// FIXME: stupid. should be defined by the instance type? or even completely abstracted away...
class Update: public LaunchStep
{
Q_OBJECT
public:
- explicit Update(LaunchTask *parent):LaunchStep(parent) {};
+ explicit Update(LaunchTask *parent, Net::Mode mode):LaunchStep(parent), m_mode(mode) {};
virtual ~Update() {};
void executeTask() override;
@@ -40,4 +41,5 @@ private slots:
private:
shared_qobject_ptr<Task> m_updateTask;
bool m_aborted = false;
+ Net::Mode m_mode = Net::Mode::Offline;
};
diff --git a/api/logic/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp
index 439256b5..5c2339cb 100644
--- a/api/logic/meta/BaseEntity.cpp
+++ b/api/logic/meta/BaseEntity.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -74,7 +74,7 @@ Meta::BaseEntity::~BaseEntity()
QUrl Meta::BaseEntity::url() const
{
- return QUrl("https://meta.multimc.org").resolved(localFilename());
+ return QUrl("https://v1.meta.multimc.org").resolved(localFilename());
}
bool Meta::BaseEntity::loadLocalFile()
@@ -99,7 +99,7 @@ bool Meta::BaseEntity::loadLocalFile()
}
}
-void Meta::BaseEntity::load()
+void Meta::BaseEntity::load(Net::Mode loadType)
{
// load local file if nothing is loaded yet
if(!isLoaded())
@@ -110,7 +110,7 @@ void Meta::BaseEntity::load()
}
}
// if we need remote update, run the update task
- if(!shouldStartRemoteUpdate())
+ if(loadType == Net::Mode::Offline || !shouldStartRemoteUpdate())
{
return;
}
@@ -160,5 +160,3 @@ shared_qobject_ptr<Task> Meta::BaseEntity::getCurrentTask()
}
return nullptr;
}
-
-#include "BaseEntity.moc"
diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h
index 4483beab..418c979f 100644
--- a/api/logic/meta/BaseEntity.h
+++ b/api/logic/meta/BaseEntity.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
#include "QObjectPtr.h"
#include "multimc_logic_export.h"
+#include "net/Mode.h"
class Task;
namespace Meta
@@ -45,7 +46,6 @@ public: /* types */
public:
virtual ~BaseEntity();
- virtual void merge(const std::shared_ptr<BaseEntity> &other) = 0;
virtual void parse(const QJsonObject &obj) = 0;
virtual QString localFilename() const = 0;
@@ -54,7 +54,7 @@ public:
bool isLoaded() const;
bool shouldStartRemoteUpdate() const;
- void load();
+ void load(Net::Mode loadType);
shared_qobject_ptr<Task> getCurrentTask();
protected: /* methods */
diff --git a/api/logic/meta/Index.cpp b/api/logic/meta/Index.cpp
index 0749651a..6e1e34cd 100644
--- a/api/logic/meta/Index.cpp
+++ b/api/logic/meta/Index.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -103,7 +103,7 @@ void Index::parse(const QJsonObject& obj)
parseIndex(obj, this);
}
-void Index::merge(const Ptr &other)
+void Index::merge(const std::shared_ptr<Index> &other)
{
const QVector<VersionListPtr> lists = std::dynamic_pointer_cast<Index>(other)->m_lists;
// initial load, no need to merge
@@ -124,7 +124,7 @@ void Index::merge(const Ptr &other)
{
if (m_uids.contains(list->uid()))
{
- m_uids[list->uid()]->merge(list);
+ m_uids[list->uid()]->mergeFromIndex(list);
}
else
{
diff --git a/api/logic/meta/Index.h b/api/logic/meta/Index.h
index 9811e152..0ec43f96 100644
--- a/api/logic/meta/Index.h
+++ b/api/logic/meta/Index.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,7 +58,7 @@ public:
QVector<VersionListPtr> lists() const { return m_lists; }
public: // for usage by parsers only
- void merge(const BaseEntity::Ptr &other) override;
+ void merge(const std::shared_ptr<Index> &other);
void parse(const QJsonObject &obj) override;
private:
diff --git a/api/logic/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp
index fb78941f..2183e579 100644
--- a/api/logic/meta/JsonFormat.cpp
+++ b/api/logic/meta/JsonFormat.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,7 +16,7 @@
#include "JsonFormat.h"
// FIXME: remove this from here... somehow
-#include "minecraft/onesix/OneSixVersionFormat.h"
+#include "minecraft/OneSixVersionFormat.h"
#include "Json.h"
#include "Index.h"
@@ -28,8 +28,13 @@ using namespace Json;
namespace Meta
{
+MetadataVersion currentFormatVersion()
+{
+ return MetadataVersion::InitialRelease;
+}
+
// Index
-static BaseEntity::Ptr parseIndexInternal(const QJsonObject &obj)
+static std::shared_ptr<Index> parseIndexInternal(const QJsonObject &obj)
{
const QVector<QJsonObject> objects = requireIsArrayOf<QJsonObject>(obj, "packages");
QVector<VersionListPtr> lists;
@@ -49,24 +54,16 @@ static VersionPtr parseCommonVersion(const QString &uid, const QJsonObject &obj)
VersionPtr version = std::make_shared<Version>(uid, requireString(obj, "version"));
version->setTime(QDateTime::fromString(requireString(obj, "releaseTime"), Qt::ISODate).toMSecsSinceEpoch() / 1000);
version->setType(ensureString(obj, "type", QString()));
- version->setParentUid(ensureString(obj, "parentUid", QString()));
version->setRecommended(ensureBoolean(obj, QString("recommended"), false));
- if(obj.contains("requires"))
- {
- QHash<QString, QString> requires;
- auto reqobj = requireObject(obj, "requires");
- auto iter = reqobj.begin();
- while(iter != reqobj.end())
- {
- requires[iter.key()] = requireString(iter.value());
- iter++;
- }
- version->setRequires(requires);
- }
+ version->setVolatile(ensureBoolean(obj, QString("volatile"), false));
+ RequireSet requires, conflicts;
+ parseRequires(obj, &requires, "requires");
+ parseRequires(obj, &conflicts, "conflicts");
+ version->setRequires(requires, conflicts);
return version;
}
-static BaseEntity::Ptr parseVersionInternal(const QJsonObject &obj)
+static std::shared_ptr<Version> parseVersionInternal(const QJsonObject &obj)
{
VersionPtr version = parseCommonVersion(requireString(obj, "uid"), obj);
@@ -77,7 +74,7 @@ static BaseEntity::Ptr parseVersionInternal(const QJsonObject &obj)
}
// Version list / package
-static BaseEntity::Ptr parseVersionListInternal(const QJsonObject &obj)
+static std::shared_ptr<VersionList> parseVersionListInternal(const QJsonObject &obj)
{
const QString uid = requireString(obj, "uid");
@@ -93,56 +90,129 @@ static BaseEntity::Ptr parseVersionListInternal(const QJsonObject &obj)
VersionListPtr list = std::make_shared<VersionList>(uid);
list->setName(ensureString(obj, "name", QString()));
- list->setParentUid(ensureString(obj, "parentUid", QString()));
list->setVersions(versions);
return list;
}
-static int formatVersion(const QJsonObject &obj)
+MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required)
{
- if (!obj.contains("formatVersion")) {
- throw ParseException(QObject::tr("Missing required field: 'formatVersion'"));
+ if (!obj.contains("formatVersion"))
+ {
+ if(required)
+ {
+ return MetadataVersion::Invalid;
+ }
+ return MetadataVersion::InitialRelease;
}
- if (!obj.value("formatVersion").isDouble()) {
- throw ParseException(QObject::tr("Required field has invalid type: 'formatVersion'"));
+ if (!obj.value("formatVersion").isDouble())
+ {
+ return MetadataVersion::Invalid;
+ }
+ switch(obj.value("formatVersion").toInt())
+ {
+ case 0:
+ case 1:
+ return MetadataVersion::InitialRelease;
+ default:
+ return MetadataVersion::Invalid;
+ }
+}
+
+void serializeFormatVersion(QJsonObject& obj, Meta::MetadataVersion version)
+{
+ if(version == MetadataVersion::Invalid)
+ {
+ return;
}
- return obj.value("formatVersion").toInt();
+ obj.insert("formatVersion", int(version));
}
void parseIndex(const QJsonObject &obj, Index *ptr)
{
- const int version = formatVersion(obj);
- switch (version) {
- case 0:
+ const MetadataVersion version = parseFormatVersion(obj);
+ switch (version)
+ {
+ case MetadataVersion::InitialRelease:
ptr->merge(parseIndexInternal(obj));
break;
- default:
- throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
+ case MetadataVersion::Invalid:
+ throw ParseException(QObject::tr("Unknown format version!"));
}
}
void parseVersionList(const QJsonObject &obj, VersionList *ptr)
{
- const int version = formatVersion(obj);
- switch (version) {
- case 0:
+ const MetadataVersion version = parseFormatVersion(obj);
+ switch (version)
+ {
+ case MetadataVersion::InitialRelease:
ptr->merge(parseVersionListInternal(obj));
break;
- default:
- throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
+ case MetadataVersion::Invalid:
+ throw ParseException(QObject::tr("Unknown format version!"));
}
}
void parseVersion(const QJsonObject &obj, Version *ptr)
{
- const int version = formatVersion(obj);
- switch (version) {
- case 0:
+ const MetadataVersion version = parseFormatVersion(obj);
+ switch (version)
+ {
+ case MetadataVersion::InitialRelease:
ptr->merge(parseVersionInternal(obj));
break;
- default:
- throw ParseException(QObject::tr("Unknown formatVersion: %1").arg(version));
+ case MetadataVersion::Invalid:
+ throw ParseException(QObject::tr("Unknown format version!"));
}
}
+
+/*
+[
+{"uid":"foo", "equals":"version"}
+]
+*/
+void parseRequires(const QJsonObject& obj, RequireSet* ptr, const char * keyName)
+{
+ if(obj.contains(keyName))
+ {
+ QSet<QString> requires;
+ auto reqArray = requireArray(obj, keyName);
+ auto iter = reqArray.begin();
+ while(iter != reqArray.end())
+ {
+ auto reqObject = requireObject(*iter);
+ auto uid = requireString(reqObject, "uid");
+ auto equals = ensureString(reqObject, "equals", QString());
+ auto suggests = ensureString(reqObject, "suggests", QString());
+ ptr->insert({uid, equals, suggests});
+ iter++;
+ }
+ }
}
+void serializeRequires(QJsonObject& obj, RequireSet* ptr, const char * keyName)
+{
+ if(!ptr || ptr->empty())
+ {
+ return;
+ }
+ QJsonArray arrOut;
+ for(auto &iter: *ptr)
+ {
+ QJsonObject reqOut;
+ reqOut.insert("uid", iter.uid);
+ if(!iter.equalsVersion.isEmpty())
+ {
+ reqOut.insert("equals", iter.equalsVersion);
+ }
+ if(!iter.suggests.isEmpty())
+ {
+ reqOut.insert("suggests", iter.suggests);
+ }
+ arrOut.append(reqOut);
+ }
+ obj.insert(keyName, arrOut);
+}
+
+}
+
diff --git a/api/logic/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h
index aaed07fc..762a36f6 100644
--- a/api/logic/meta/JsonFormat.h
+++ b/api/logic/meta/JsonFormat.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
#include "Exception.h"
#include "meta/BaseEntity.h"
+#include <set>
namespace Meta
{
@@ -27,14 +28,56 @@ class Index;
class Version;
class VersionList;
+enum class MetadataVersion
+{
+ Invalid = -1,
+ InitialRelease = 1
+};
+
class ParseException : public Exception
{
public:
using Exception::Exception;
};
+struct Require
+{
+ bool operator==(const Require & rhs) const
+ {
+ return uid == rhs.uid;
+ }
+ bool operator<(const Require & rhs) const
+ {
+ return uid < rhs.uid;
+ }
+ bool deepEquals(const Require & rhs) const
+ {
+ return uid == rhs.uid
+ && equalsVersion == rhs.equalsVersion
+ && suggests == rhs.suggests;
+ }
+ QString uid;
+ QString equalsVersion;
+ QString suggests;
+};
+
+inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
+{
+ return qHash(key.uid, seed);
+}
+
+using RequireSet = std::set<Require>;
void parseIndex(const QJsonObject &obj, Index *ptr);
void parseVersion(const QJsonObject &obj, Version *ptr);
void parseVersionList(const QJsonObject &obj, VersionList *ptr);
+MetadataVersion parseFormatVersion(const QJsonObject &obj, bool required = true);
+void serializeFormatVersion(QJsonObject &obj, MetadataVersion version);
+
+// FIXME: this has a different shape than the others...FIX IT!?
+void parseRequires(const QJsonObject &obj, RequireSet * ptr, const char * keyName = "requires");
+void serializeRequires(QJsonObject & objOut, RequireSet* ptr, const char * keyName = "requires");
+MetadataVersion currentFormatVersion();
}
+
+Q_DECLARE_METATYPE(std::set<Meta::Require>); \ No newline at end of file
diff --git a/api/logic/meta/Version.cpp b/api/logic/meta/Version.cpp
index b00a29e7..edc70f33 100644
--- a/api/logic/meta/Version.cpp
+++ b/api/logic/meta/Version.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,7 @@
#include <QDateTime>
#include "JsonFormat.h"
-#include "minecraft/MinecraftProfile.h"
+#include "minecraft/ComponentList.h"
Meta::Version::Version(const QString &uid, const QString &version)
: BaseVersion(), m_uid(uid), m_version(version)
@@ -54,47 +54,49 @@ void Meta::Version::parse(const QJsonObject& obj)
parseVersion(obj, this);
}
-void Meta::Version::merge(const std::shared_ptr<BaseEntity> &other)
+void Meta::Version::mergeFromList(const Meta::VersionPtr& other)
{
- VersionPtr version = std::dynamic_pointer_cast<Version>(other);
- if(version->m_providesRecommendations)
+ if(other->m_providesRecommendations)
{
- if(m_recommended != version->m_recommended)
+ if(m_recommended != other->m_recommended)
{
- setRecommended(version->m_recommended);
+ setRecommended(other->m_recommended);
}
}
- if (m_type != version->m_type)
+ if (m_type != other->m_type)
{
- setType(version->m_type);
+ setType(other->m_type);
}
- if (m_time != version->m_time)
+ if (m_time != other->m_time)
{
- setTime(version->m_time);
+ setTime(other->m_time);
}
- if (m_requires != version->m_requires)
+ if (m_requires != other->m_requires)
{
- setRequires(version->m_requires);
+ m_requires = other->m_requires;
}
- if (m_parentUid != version->m_parentUid)
+ if (m_conflicts != other->m_conflicts)
{
- setParentUid(version->m_parentUid);
+ m_conflicts = other->m_conflicts;
}
- if(version->m_data)
+ if(m_volatile != other->m_volatile)
{
- setData(version->m_data);
+ setVolatile(other->m_volatile);
}
}
-QString Meta::Version::localFilename() const
+void Meta::Version::merge(const VersionPtr &other)
{
- return m_uid + '/' + m_version + ".json";
+ mergeFromList(other);
+ if(other->m_data)
+ {
+ setData(other->m_data);
+ }
}
-void Meta::Version::setParentUid(const QString& parentUid)
+QString Meta::Version::localFilename() const
{
- m_parentUid = parentUid;
- emit requiresChanged();
+ return m_uid + '/' + m_version + ".json";
}
void Meta::Version::setType(const QString &type)
@@ -109,12 +111,19 @@ void Meta::Version::setTime(const qint64 time)
emit timeChanged();
}
-void Meta::Version::setRequires(const QHash<QString, QString> &requires)
+void Meta::Version::setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts)
{
m_requires = requires;
+ m_conflicts = conflicts;
emit requiresChanged();
}
+void Meta::Version::setVolatile(bool volatile_)
+{
+ m_volatile = volatile_;
+}
+
+
void Meta::Version::setData(const VersionFilePtr &data)
{
m_data = data;
diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h
index 2f92ee9f..33bd5b35 100644
--- a/api/logic/meta/Version.h
+++ b/api/logic/meta/Version.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@
#include "multimc_logic_export.h"
+#include "JsonFormat.h"
+
namespace Meta
{
using VersionPtr = std::shared_ptr<class Version>;
@@ -48,10 +50,6 @@ public: /* con/des */
{
return m_uid;
}
- QString parentUid() const
- {
- return m_parentUid;
- }
QString version() const
{
return m_version;
@@ -65,7 +63,7 @@ public: /* con/des */
{
return m_time;
}
- const QHash<QString, QString> &requires() const
+ const Meta::RequireSet &requires() const
{
return m_requires;
}
@@ -77,17 +75,22 @@ public: /* con/des */
{
return m_recommended;
}
+ bool isLoaded() const
+ {
+ return m_data != nullptr;
+ }
- void merge(const std::shared_ptr<BaseEntity> &other) override;
+ void merge(const VersionPtr &other);
+ void mergeFromList(const VersionPtr &other);
void parse(const QJsonObject &obj) override;
QString localFilename() const override;
public: // for usage by format parsers only
- void setParentUid(const QString &parentUid);
void setType(const QString &type);
void setTime(const qint64 time);
- void setRequires(const QHash<QString, QString> &requires);
+ void setRequires(const Meta::RequireSet &requires, const Meta::RequireSet &conflicts);
+ void setVolatile(bool volatile_);
void setRecommended(bool recommended);
void setProvidesRecommendations();
void setData(const VersionFilePtr &data);
@@ -102,11 +105,12 @@ private:
bool m_recommended = false;
QString m_name;
QString m_uid;
- QString m_parentUid;
QString m_version;
QString m_type;
qint64 m_time = 0;
- QHash<QString, QString> m_requires;
+ Meta::RequireSet m_requires;
+ Meta::RequireSet m_conflicts;
+ bool m_volatile = false;
VersionFilePtr m_data;
};
}
diff --git a/api/logic/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp
index 44687d3c..9ae02301 100644
--- a/api/logic/meta/VersionList.cpp
+++ b/api/logic/meta/VersionList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
#include "Version.h"
#include "JsonFormat.h"
+#include "Version.h"
namespace Meta
{
@@ -30,7 +31,7 @@ VersionList::VersionList(const QString &uid, QObject *parent)
shared_qobject_ptr<Task> VersionList::getLoadTask()
{
- load();
+ load(Net::Mode::Online);
return getCurrentTask();
}
@@ -75,17 +76,17 @@ QVariant VersionList::data(const QModelIndex &index, int role) const
return version->version();
case ParentVersionRole:
{
- auto parentUid = this->parentUid();
- if(parentUid.isEmpty())
- {
- return QVariant();
- }
+ // FIXME: HACK: this should be generic and be replaced by something else. Anything that is a hard 'equals' dep is a 'parent uid'.
auto & reqs = version->requires();
- auto iter = reqs.find(parentUid);
+ auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Require & req)
+ {
+ return req.uid == "net.minecraft";
+ });
if (iter != reqs.end())
{
- return iter.value();
+ return (*iter).equalsVersion;
}
+ return QVariant();
}
case TypeRole: return version->type();
@@ -159,6 +160,7 @@ void VersionList::setVersions(const QVector<VersionPtr> &versions)
setupAddedVersion(i, m_versions.at(i));
}
+ // FIXME: this is dumb, we have 'recommended' as part of the metadata already...
auto recommendedIt = std::find_if(m_versions.constBegin(), m_versions.constEnd(), [](const VersionPtr &ptr) { return ptr->type() == "release"; });
m_recommended = recommendedIt == m_versions.constEnd() ? nullptr : *recommendedIt;
endResetModel();
@@ -169,28 +171,50 @@ void VersionList::parse(const QJsonObject& obj)
parseVersionList(obj, this);
}
-void VersionList::merge(const BaseEntity::Ptr &other)
+// FIXME: this is dumb, we have 'recommended' as part of the metadata already...
+static const Meta::VersionPtr &getBetterVersion(const Meta::VersionPtr &a, const Meta::VersionPtr &b)
{
- const VersionListPtr list = std::dynamic_pointer_cast<VersionList>(other);
- if (m_name != list->m_name)
+ if(!a)
+ return b;
+ if(!b)
+ return a;
+ if(a->type() == b->type())
{
- setName(list->m_name);
+ // newer of same type wins
+ return (a->rawTime() > b->rawTime() ? a : b);
}
+ // 'release' type wins
+ return (a->type() == "release" ? a : b);
+}
- if(m_parentUid != list->m_parentUid)
+void VersionList::mergeFromIndex(const VersionListPtr &other)
+{
+ if (m_name != other->m_name)
{
- setParentUid(list->m_parentUid);
+ setName(other->m_name);
+ }
+}
+
+void VersionList::merge(const VersionListPtr &other)
+{
+ if (m_name != other->m_name)
+ {
+ setName(other->m_name);
}
// TODO: do not reset the whole model. maybe?
beginResetModel();
m_versions.clear();
- for (const VersionPtr &version : list->m_versions)
+ if(other->m_versions.isEmpty())
+ {
+ qWarning() << "Empty list loaded ...";
+ }
+ for (const VersionPtr &version : other->m_versions)
{
// we already have the version. merge the contents
if (m_lookup.contains(version->version()))
{
- m_lookup.value(version->version())->merge(version);
+ m_lookup.value(version->version())->mergeFromList(version);
}
else
{
@@ -199,10 +223,7 @@ void VersionList::merge(const BaseEntity::Ptr &other)
// connect it.
setupAddedVersion(m_versions.size(), version);
m_versions.append(version);
- if (!m_recommended || (version->type() == "release" && version->rawTime() > m_recommended->rawTime()))
- {
- m_recommended = version;
- }
+ m_recommended = getBetterVersion(m_recommended, version);
}
endResetModel();
}
@@ -222,11 +243,3 @@ BaseVersionPtr VersionList::getRecommended() const
}
}
-
-void Meta::VersionList::setParentUid(const QString& parentUid)
-{
- m_parentUid = parentUid;
-}
-
-
-#include "VersionList.moc"
diff --git a/api/logic/meta/VersionList.h b/api/logic/meta/VersionList.h
index e8016314..ad8733cc 100644
--- a/api/logic/meta/VersionList.h
+++ b/api/logic/meta/VersionList.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -55,10 +55,6 @@ public:
QString localFilename() const override;
- QString parentUid() const
- {
- return m_parentUid;
- }
QString uid() const
{
return m_uid;
@@ -78,9 +74,9 @@ public:
public: // for usage only by parsers
void setName(const QString &name);
- void setParentUid(const QString &parentUid);
void setVersions(const QVector<VersionPtr> &versions);
- void merge(const BaseEntity::Ptr &other) override;
+ void merge(const VersionListPtr &other);
+ void mergeFromIndex(const VersionListPtr &other);
void parse(const QJsonObject &obj) override;
signals:
@@ -95,7 +91,6 @@ private:
QVector<VersionPtr> m_versions;
QHash<QString, VersionPtr> m_lookup;
QString m_uid;
- QString m_parentUid;
QString m_name;
VersionPtr m_recommended;
diff --git a/api/logic/minecraft/AssetsUtils.cpp b/api/logic/minecraft/AssetsUtils.cpp
index 5191e5bd..5b25bede 100644
--- a/api/logic/minecraft/AssetsUtils.cpp
+++ b/api/logic/minecraft/AssetsUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h
index 34b49e7f..b7ea9cc1 100644
--- a/api/logic/minecraft/AssetsUtils.h
+++ b/api/logic/minecraft/AssetsUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp
new file mode 100644
index 00000000..50a2ae16
--- /dev/null
+++ b/api/logic/minecraft/Component.cpp
@@ -0,0 +1,439 @@
+#include <meta/VersionList.h>
+#include <meta/Index.h>
+#include <Env.h>
+#include "Component.h"
+
+#include "meta/Version.h"
+#include "VersionFile.h"
+#include "minecraft/ComponentList.h"
+#include <FileSystem.h>
+#include <QSaveFile>
+#include "OneSixVersionFormat.h"
+#include <assert.h>
+
+Component::Component(ComponentList * parent, const QString& uid)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_uid = uid;
+}
+
+Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_metaVersion = version;
+ m_uid = version->uid();
+ m_version = m_cachedVersion = version->version();
+ m_cachedName = version->name();
+ m_loaded = version->isLoaded();
+}
+
+Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file)
+{
+ assert(parent);
+ m_parent = parent;
+
+ m_file = file;
+ m_uid = uid;
+ m_cachedVersion = m_file->version;
+ m_cachedName = m_file->name;
+ m_loaded = true;
+}
+
+std::shared_ptr<Meta::Version> Component::getMeta()
+{
+ return m_metaVersion;
+}
+
+void Component::applyTo(LaunchProfile* profile)
+{
+ // do not apply disabled components
+ if(!isEnabled())
+ {
+ return;
+ }
+ auto vfile = getVersionFile();
+ if(vfile)
+ {
+ vfile->applyTo(profile);
+ }
+ else
+ {
+ profile->applyProblemSeverity(getProblemSeverity());
+ }
+}
+
+std::shared_ptr<class VersionFile> Component::getVersionFile() const
+{
+ if(m_metaVersion)
+ {
+ if(!m_metaVersion->isLoaded())
+ {
+ m_metaVersion->load(Net::Mode::Online);
+ }
+ return m_metaVersion->data();
+ }
+ else
+ {
+ return m_file;
+ }
+}
+
+std::shared_ptr<class Meta::VersionList> Component::getVersionList() const
+{
+ // FIXME: what if the metadata index isn't loaded yet?
+ if(ENV.metadataIndex()->hasUid(m_uid))
+ {
+ return ENV.metadataIndex()->get(m_uid);
+ }
+ return nullptr;
+}
+
+int Component::getOrder()
+{
+ if(m_orderOverride)
+ return m_order;
+
+ auto vfile = getVersionFile();
+ if(vfile)
+ {
+ return vfile->order;
+ }
+ return 0;
+}
+void Component::setOrder(int order)
+{
+ m_orderOverride = true;
+ m_order = order;
+}
+QString Component::getID()
+{
+ return m_uid;
+}
+QString Component::getName()
+{
+ if (!m_cachedName.isEmpty())
+ return m_cachedName;
+ return m_uid;
+}
+QString Component::getVersion()
+{
+ return m_cachedVersion;
+}
+QString Component::getFilename()
+{
+ return m_parent->patchFilePathForUid(m_uid);
+}
+QDateTime Component::getReleaseDateTime()
+{
+ if(m_metaVersion)
+ {
+ return m_metaVersion->time();
+ }
+ auto vfile = getVersionFile();
+ if(vfile)
+ {
+ return vfile->releaseTime;
+ }
+ // FIXME: fake
+ return QDateTime::currentDateTime();
+}
+
+bool Component::isEnabled()
+{
+ return !canBeDisabled() || !m_disabled;
+};
+
+bool Component::canBeDisabled()
+{
+ return isRemovable() && !m_dependencyOnly;
+}
+
+bool Component::setEnabled(bool state)
+{
+ bool intendedDisabled = !state;
+ if (!canBeDisabled())
+ {
+ intendedDisabled = false;
+ }
+ if(intendedDisabled != m_disabled)
+ {
+ m_disabled = intendedDisabled;
+ emit dataChanged();
+ return true;
+ }
+ return false;
+}
+
+bool Component::isCustom()
+{
+ return m_file != nullptr;
+};
+
+bool Component::isCustomizable()
+{
+ if(m_metaVersion)
+ {
+ if(getVersionFile())
+ {
+ return true;
+ }
+ }
+ return false;
+}
+bool Component::isRemovable()
+{
+ return !m_important;
+}
+bool Component::isRevertible()
+{
+ if (isCustom())
+ {
+ if(ENV.metadataIndex()->hasUid(m_uid))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+bool Component::isMoveable()
+{
+ // HACK, FIXME: this was too dumb and wouldn't follow dependency constraints anyway. For now hardcoded to 'true'.
+ return true;
+}
+bool Component::isVersionChangeable()
+{
+ auto list = getVersionList();
+ if(list)
+ {
+ if(!list->isLoaded())
+ {
+ list->load(Net::Mode::Online);
+ }
+ return list->count() != 0;
+ }
+ return false;
+}
+
+void Component::setImportant(bool state)
+{
+ if(m_important != state)
+ {
+ m_important = state;
+ emit dataChanged();
+ }
+}
+
+ProblemSeverity Component::getProblemSeverity() const
+{
+ auto file = getVersionFile();
+ if(file)
+ {
+ return file->getProblemSeverity();
+ }
+ return ProblemSeverity::Error;
+}
+
+const QList<PatchProblem> Component::getProblems() const
+{
+ auto file = getVersionFile();
+ if(file)
+ {
+ return file->getProblems();
+ }
+ return {{ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.")}};
+}
+
+void Component::setVersion(const QString& version)
+{
+ if(version == m_version)
+ {
+ return;
+ }
+ m_version = version;
+ if(m_loaded)
+ {
+ // we are loaded and potentially have state to invalidate
+ if(m_file)
+ {
+ // we have a file... explicit version has been changed and there is nothing else to do.
+ }
+ else
+ {
+ // we don't have a file, therefore we are loaded with metadata
+ m_cachedVersion = version;
+ // see if the meta version is loaded
+ auto metaVersion = ENV.metadataIndex()->get(m_uid, version);
+ if(metaVersion->isLoaded())
+ {
+ // if yes, we can continue with that.
+ m_metaVersion = metaVersion;
+ }
+ else
+ {
+ // if not, we need loading
+ m_metaVersion.reset();
+ m_loaded = false;
+ }
+ updateCachedData();
+ }
+ }
+ else
+ {
+ // not loaded... assume it will be sorted out later by the update task
+ }
+ emit dataChanged();
+}
+
+bool Component::customize()
+{
+ if(isCustom())
+ {
+ return false;
+ }
+
+ auto filename = getFilename();
+ if(!FS::ensureFilePathExists(filename))
+ {
+ return false;
+ }
+ // FIXME: get rid of this try-catch.
+ try
+ {
+ QSaveFile jsonFile(filename);
+ if(!jsonFile.open(QIODevice::WriteOnly))
+ {
+ return false;
+ }
+ auto vfile = getVersionFile();
+ if(!vfile)
+ {
+ return false;
+ }
+ auto document = OneSixVersionFormat::versionFileToJson(vfile);
+ jsonFile.write(document.toJson());
+ if(!jsonFile.commit())
+ {
+ return false;
+ }
+ m_file = vfile;
+ m_metaVersion.reset();
+ emit dataChanged();
+ }
+ catch (Exception &error)
+ {
+ qWarning() << "Version could not be loaded:" << error.cause();
+ }
+ return true;
+}
+
+bool Component::revert()
+{
+ if(!isCustom())
+ {
+ // already not custom
+ return true;
+ }
+ auto filename = getFilename();
+ bool result = true;
+ // just kill the file and reload
+ if(QFile::exists(filename))
+ {
+ result = QFile::remove(filename);
+ }
+ if(result)
+ {
+ // file gone...
+ m_file.reset();
+
+ // check local cache for metadata...
+ auto version = ENV.metadataIndex()->get(m_uid, m_version);
+ if(version->isLoaded())
+ {
+ m_metaVersion = version;
+ }
+ else
+ {
+ m_metaVersion.reset();
+ m_loaded = false;
+ }
+ emit dataChanged();
+ }
+ return result;
+}
+
+/**
+ * deep inspecting compare for requirement sets
+ * By default, only uids are compared for set operations.
+ * This compares all fields of the Require structs in the sets.
+ */
+static bool deepCompare(const std::set<Meta::Require> & a, const std::set<Meta::Require> & b)
+{
+ // NOTE: this needs to be rewritten if the type of Meta::RequireSet changes
+ if(a.size() != b.size())
+ {
+ return false;
+ }
+ for(const auto & reqA :a)
+ {
+ const auto &iter2 = b.find(reqA);
+ if(iter2 == b.cend())
+ {
+ return false;
+ }
+ const auto & reqB = *iter2;
+ if(!reqA.deepEquals(reqB))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+void Component::updateCachedData()
+{
+ auto file = getVersionFile();
+ if(file)
+ {
+ bool changed = false;
+ if(m_cachedName != file->name)
+ {
+ m_cachedName = file->name;
+ changed = true;
+ }
+ if(m_cachedVersion != file->version)
+ {
+ m_cachedVersion = file->version;
+ changed = true;
+ }
+ if(m_cachedVolatile != file->m_volatile)
+ {
+ m_cachedVolatile = file->m_volatile;
+ changed = true;
+ }
+ if(!deepCompare(m_cachedRequires, file->requires))
+ {
+ m_cachedRequires = file->requires;
+ changed = true;
+ }
+ if(!deepCompare(m_cachedConflicts, file->conflicts))
+ {
+ m_cachedConflicts = file->conflicts;
+ changed = true;
+ }
+ if(changed)
+ {
+ emit dataChanged();
+ }
+ }
+ else
+ {
+ // in case we removed all the metadata
+ m_cachedRequires.clear();
+ m_cachedConflicts.clear();
+ emit dataChanged();
+ }
+}
diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h
new file mode 100644
index 00000000..778fbb18
--- /dev/null
+++ b/api/logic/minecraft/Component.h
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <memory>
+#include <QList>
+#include <QJsonDocument>
+#include <QDateTime>
+#include "meta/JsonFormat.h"
+#include "ProblemProvider.h"
+#include "QObjectPtr.h"
+#include "multimc_logic_export.h"
+
+class ComponentList;
+class LaunchProfile;
+namespace Meta
+{
+ class Version;
+ class VersionList;
+}
+class VersionFile;
+
+class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider
+{
+Q_OBJECT
+public:
+ Component(ComponentList * parent, const QString &uid);
+
+ // DEPRECATED: remove these constructors?
+ Component(ComponentList * parent, std::shared_ptr<Meta::Version> version);
+ Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file);
+
+ virtual ~Component(){};
+ void applyTo(LaunchProfile *profile);
+
+ bool isEnabled();
+ bool setEnabled (bool state);
+ bool canBeDisabled();
+
+ bool isMoveable();
+ bool isCustomizable();
+ bool isRevertible();
+ bool isRemovable();
+ bool isCustom();
+ bool isVersionChangeable();
+
+ // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
+ void setOrder(int order);
+ int getOrder();
+
+ QString getID();
+ QString getName();
+ QString getVersion();
+ std::shared_ptr<Meta::Version> getMeta();
+ QDateTime getReleaseDateTime();
+
+ QString getFilename();
+
+ std::shared_ptr<class VersionFile> getVersionFile() const;
+ std::shared_ptr<class Meta::VersionList> getVersionList() const;
+
+ void setImportant (bool state);
+
+
+ const QList<PatchProblem> getProblems() const override;
+ ProblemSeverity getProblemSeverity() const override;
+
+ void setVersion(const QString & version);
+ bool customize();
+ bool revert();
+
+ void updateCachedData();
+
+signals:
+ void dataChanged();
+
+public: /* data */
+ ComponentList * m_parent;
+
+ // BEGIN: persistent component list properties
+ /// ID of the component
+ QString m_uid;
+ /// version of the component - when there's a custom json override, this is also the version the component reverts to
+ QString m_version;
+ /// if true, this has been added automatically to satisfy dependencies and may be automatically removed
+ bool m_dependencyOnly = false;
+ /// if true, the component is either the main component of the instance, or otherwise important and cannot be removed.
+ bool m_important = false;
+ /// if true, the component is disabled
+ bool m_disabled = false;
+
+ /// cached name for display purposes, taken from the version file (meta or local override)
+ QString m_cachedName;
+ /// cached version for display AND other purposes, taken from the version file (meta or local override)
+ QString m_cachedVersion;
+ /// cached set of requirements, taken from the version file (meta or local override)
+ Meta::RequireSet m_cachedRequires;
+ Meta::RequireSet m_cachedConflicts;
+ /// if true, the component is volatile and may be automatically removed when no longer needed
+ bool m_cachedVolatile = false;
+ // END: persistent component list properties
+
+ // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code
+ bool m_orderOverride = false;
+ int m_order = 0;
+
+ // load state
+ std::shared_ptr<Meta::Version> m_metaVersion;
+ std::shared_ptr<VersionFile> m_file;
+ bool m_loaded = false;
+};
+
+typedef shared_qobject_ptr<Component> ComponentPtr;
diff --git a/api/logic/minecraft/ComponentList.cpp b/api/logic/minecraft/ComponentList.cpp
new file mode 100644
index 00000000..a207e987
--- /dev/null
+++ b/api/logic/minecraft/ComponentList.cpp
@@ -0,0 +1,1204 @@
+/* Copyright 2013-2018 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 <QCryptographicHash>
+#include <Version.h>
+#include <QDir>
+#include <QJsonDocument>
+#include <QJsonArray>
+#include <QDebug>
+
+#include "Exception.h"
+#include <minecraft/OneSixVersionFormat.h>
+#include <FileSystem.h>
+#include <QSaveFile>
+#include <Env.h>
+#include <meta/Index.h>
+#include <minecraft/MinecraftInstance.h>
+#include <QUuid>
+#include <QTimer>
+#include <Json.h>
+
+#include "ComponentList.h"
+#include "ComponentList_p.h"
+#include "ComponentUpdateTask.h"
+
+ComponentList::ComponentList(MinecraftInstance * instance)
+ : QAbstractListModel()
+{
+ d.reset(new ComponentListData);
+ d->m_instance = instance;
+ d->m_saveTimer.setSingleShot(true);
+ d->m_saveTimer.setInterval(5000);
+ connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal);
+}
+
+ComponentList::~ComponentList()
+{
+ saveNow();
+}
+
+// BEGIN: component file format
+
+static const int currentComponentsFileVersion = 1;
+
+static QJsonObject componentToJsonV1(ComponentPtr component)
+{
+ QJsonObject obj;
+ // critical
+ obj.insert("uid", component->m_uid);
+ if(!component->m_version.isEmpty())
+ {
+ obj.insert("version", component->m_version);
+ }
+ if(component->m_dependencyOnly)
+ {
+ obj.insert("dependencyOnly", true);
+ }
+ if(component->m_important)
+ {
+ obj.insert("important", true);
+ }
+ if(component->m_disabled)
+ {
+ obj.insert("disabled", true);
+ }
+
+ // cached
+ if(!component->m_cachedVersion.isEmpty())
+ {
+ obj.insert("cachedVersion", component->m_cachedVersion);
+ }
+ if(!component->m_cachedName.isEmpty())
+ {
+ obj.insert("cachedName", component->m_cachedName);
+ }
+ Meta::serializeRequires(obj, &component->m_cachedRequires, "cachedRequires");
+ Meta::serializeRequires(obj, &component->m_cachedConflicts, "cachedConflicts");
+ if(component->m_cachedVolatile)
+ {
+ obj.insert("cachedVolatile", true);
+ }
+ return obj;
+}
+
+static ComponentPtr componentFromJsonV1(ComponentList * parent, const QString & componentJsonPattern, const QJsonObject &obj)
+{
+ // critical
+ auto uid = Json::requireString(obj.value("uid"));
+ auto filePath = componentJsonPattern.arg(uid);
+ auto component = new Component(parent, uid);
+ component->m_version = Json::ensureString(obj.value("version"));
+ component->m_dependencyOnly = Json::ensureBoolean(obj.value("dependencyOnly"), false);
+ component->m_important = Json::ensureBoolean(obj.value("important"), false);
+
+ // cached
+ // TODO @RESILIENCE: ignore invalid values/structure here?
+ component->m_cachedVersion = Json::ensureString(obj.value("cachedVersion"));
+ component->m_cachedName = Json::ensureString(obj.value("cachedName"));
+ Meta::parseRequires(obj, &component->m_cachedRequires, "cachedRequires");
+ Meta::parseRequires(obj, &component->m_cachedConflicts, "cachedConflicts");
+ component->m_cachedVolatile = Json::ensureBoolean(obj.value("volatile"), false);
+ bool disabled = Json::ensureBoolean(obj.value("disabled"), false);
+ component->setEnabled(!disabled);
+ return component;
+}
+
+// Save the given component container data to a file
+static bool saveComponentList(const QString & filename, const ComponentContainer & container)
+{
+ QJsonObject obj;
+ obj.insert("formatVersion", currentComponentsFileVersion);
+ QJsonArray orderArray;
+ for(auto component: container)
+ {
+ orderArray.append(componentToJsonV1(component));
+ }
+ obj.insert("components", orderArray);
+ QSaveFile outFile(filename);
+ if (!outFile.open(QFile::WriteOnly))
+ {
+ qCritical() << "Couldn't open" << outFile.fileName()
+ << "for writing:" << outFile.errorString();
+ return false;
+ }
+ auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
+ if(outFile.write(data) != data.size())
+ {
+ qCritical() << "Couldn't write all the data into" << outFile.fileName()
+ << "because:" << outFile.errorString();
+ return false;
+ }
+ if(!outFile.commit())
+ {
+ qCritical() << "Couldn't save" << outFile.fileName()
+ << "because:" << outFile.errorString();
+ }
+ return true;
+}
+
+// Read the given file into component containers
+static bool loadComponentList(ComponentList * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container)
+{
+ QFile componentsFile(filename);
+ if (!componentsFile.exists())
+ {
+ qWarning() << "Components file doesn't exist. This should never happen.";
+ return false;
+ }
+ if (!componentsFile.open(QFile::ReadOnly))
+ {
+ qCritical() << "Couldn't open" << componentsFile.fileName()
+ << " for reading:" << componentsFile.errorString();
+ qWarning() << "Ignoring overriden order";
+ return false;
+ }
+
+ // and it's valid JSON
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error);
+ if (error.error != QJsonParseError::NoError)
+ {
+ qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString();
+ qWarning() << "Ignoring overriden order";
+ return false;
+ }
+
+ // and then read it and process it if all above is true.
+ try
+ {
+ auto obj = Json::requireObject(doc);
+ // check order file version.
+ auto version = Json::requireInteger(obj.value("formatVersion"));
+ if (version != currentComponentsFileVersion)
+ {
+ throw JSONValidationError(QObject::tr("Invalid component file version, expected %1")
+ .arg(currentComponentsFileVersion));
+ }
+ auto orderArray = Json::requireArray(obj.value("components"));
+ for(auto item: orderArray)
+ {
+ auto obj = Json::requireObject(item, "Component must be an object.");
+ container.append(componentFromJsonV1(parent, componentJsonPattern, obj));
+ }
+ }
+ catch (JSONValidationError &err)
+ {
+ qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format";
+ container.clear();
+ return false;
+ }
+ return true;
+}
+
+// END: component file format
+
+// BEGIN: save/load logic
+
+void ComponentList::saveNow()
+{
+ if(saveIsScheduled())
+ {
+ d->m_saveTimer.stop();
+ save_internal();
+ }
+}
+
+bool ComponentList::saveIsScheduled() const
+{
+ return d->dirty;
+}
+
+void ComponentList::buildingFromScratch()
+{
+ d->loaded = true;
+ d->dirty = true;
+}
+
+void ComponentList::scheduleSave()
+{
+ if(!d->loaded)
+ {
+ qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name();
+ return;
+ }
+ if(!d->dirty)
+ {
+ d->dirty = true;
+ qDebug() << "Component list save is scheduled for" << d->m_instance->name();
+ }
+ d->m_saveTimer.start();
+}
+
+QString ComponentList::componentsFilePath() const
+{
+ return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
+}
+
+QString ComponentList::patchesPattern() const
+{
+ return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json");
+}
+
+QString ComponentList::patchFilePathForUid(const QString& uid) const
+{
+ return patchesPattern().arg(uid);
+}
+
+void ComponentList::save_internal()
+{
+ qDebug() << "Component list save performed now for" << d->m_instance->name();
+ auto filename = componentsFilePath();
+ saveComponentList(filename, d->components);
+ d->dirty = false;
+}
+
+bool ComponentList::load()
+{
+ auto filename = componentsFilePath();
+ QFile componentsFile(filename);
+
+ // migrate old config to new one, if needed
+ if(!componentsFile.exists())
+ {
+ if(!migratePreComponentConfig())
+ {
+ // FIXME: the user should be notified...
+ qCritical() << "Failed to convert old pre-component config for instance" << d->m_instance->name();
+ return false;
+ }
+ }
+
+ // load the new component list and swap it with the current one...
+ ComponentContainer newComponents;
+ if(!loadComponentList(this, filename, patchesPattern(), newComponents))
+ {
+ qCritical() << "Failed to load the component config for instance" << d->m_instance->name();
+ return false;
+ }
+ else
+ {
+ // FIXME: actually use fine-grained updates, not this...
+ beginResetModel();
+ // disconnect all the old components
+ for(auto component: d->components)
+ {
+ disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ }
+ d->components.clear();
+ d->componentIndex.clear();
+ for(auto component: newComponents)
+ {
+ if(d->componentIndex.contains(component->m_uid))
+ {
+ qWarning() << "Ignoring duplicate component entry" << component->m_uid;
+ continue;
+ }
+ connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ d->components.append(component);
+ d->componentIndex[component->m_uid] = component;
+ }
+ endResetModel();
+ d->loaded = true;
+ return true;
+ }
+}
+
+void ComponentList::reload(Net::Mode netmode)
+{
+ // Do not reload when the update/resolve task is running. It is in control.
+ if(d->m_updateTask)
+ {
+ return;
+ }
+
+ // flush any scheduled saves to not lose state
+ saveNow();
+
+ // FIXME: differentiate when a reapply is required by propagating state from components
+ invalidateLaunchProfile();
+
+ if(load())
+ {
+ resolve(netmode);
+ }
+}
+
+shared_qobject_ptr<Task> ComponentList::getCurrentTask()
+{
+ return d->m_updateTask;
+}
+
+void ComponentList::resolve(Net::Mode netmode)
+{
+ auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this);
+ d->m_updateTask.reset(updateTask);
+ connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded);
+ connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed);
+ d->m_updateTask->start();
+}
+
+
+void ComponentList::updateSucceeded()
+{
+ qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name();
+ d->m_updateTask.reset();
+ invalidateLaunchProfile();
+}
+
+void ComponentList::updateFailed(const QString& error)
+{
+ qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error;
+ d->m_updateTask.reset();
+ invalidateLaunchProfile();
+}
+
+// NOTE this is really old stuff, and only needs to be used when loading the old hardcoded component-unaware format (loadPreComponentConfig).
+static void upgradeDeprecatedFiles(QString root, QString instanceName)
+{
+ auto versionJsonPath = FS::PathCombine(root, "version.json");
+ auto customJsonPath = FS::PathCombine(root, "custom.json");
+ auto mcJson = FS::PathCombine(root, "patches" , "net.minecraft.json");
+
+ QString sourceFile;
+ QString renameFile;
+
+ // convert old crap.
+ if(QFile::exists(customJsonPath))
+ {
+ sourceFile = customJsonPath;
+ renameFile = versionJsonPath;
+ }
+ else if(QFile::exists(versionJsonPath))
+ {
+ sourceFile = versionJsonPath;
+ }
+ if(!sourceFile.isEmpty() && !QFile::exists(mcJson))
+ {
+ if(!FS::ensureFilePathExists(mcJson))
+ {
+ qWarning() << "Couldn't create patches folder for" << instanceName;
+ return;
+ }
+ if(!renameFile.isEmpty() && QFile::exists(renameFile))
+ {
+ if(!QFile::rename(renameFile, renameFile + ".old"))
+ {
+ qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << instanceName;
+ return;
+ }
+ }
+ auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false);
+ ProfileUtils::removeLwjglFromPatch(file);
+ file->uid = "net.minecraft";
+ file->version = file->minecraftVersion;
+ file->name = "Minecraft";
+
+ Meta::Require needsLwjgl;
+ needsLwjgl.uid = "org.lwjgl";
+ file->requires.insert(needsLwjgl);
+
+ if(!ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), mcJson))
+ {
+ return;
+ }
+ if(!QFile::rename(sourceFile, sourceFile + ".old"))
+ {
+ qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << instanceName;
+ return;
+ }
+ }
+}
+
+/*
+ * Migrate old layout to the component based one...
+ * - Part of the version information is taken from `instance.cfg` (fed to this class from outside).
+ * - Part is taken from the old order.json file.
+ * - Part is loaded from loose json files in the instance's `patches` directory.
+ */
+bool ComponentList::migratePreComponentConfig()
+{
+ // upgrade the very old files from the beginnings of MultiMC 5
+ upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name());
+
+ QList<ComponentPtr> components;
+ QSet<QString> loaded;
+
+ auto addBuiltinPatch = [&](const QString &uid, bool asDependency, const QString & emptyVersion, const Meta::Require & req, const Meta::Require & conflict)
+ {
+ auto jsonFilePath = FS::PathCombine(d->m_instance->instanceRoot(), "patches" , uid + ".json");
+ auto intendedVersion = d->getOldConfigVersion(uid);
+ // load up the base minecraft patch
+ ComponentPtr component;
+ if(QFile::exists(jsonFilePath))
+ {
+ if(intendedVersion.isEmpty())
+ {
+ intendedVersion = emptyVersion;
+ }
+ auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false);
+ // fix uid
+ file->uid = uid;
+ // if version is missing, add it from the outside.
+ if(file->version.isEmpty())
+ {
+ file->version = intendedVersion;
+ }
+ // if this is a dependency (LWJGL), mark it also as volatile
+ if(asDependency)
+ {
+ file->m_volatile = true;
+ }
+ // insert requirements if needed
+ if(!req.uid.isEmpty())
+ {
+ file->requires.insert(req);
+ }
+ // insert conflicts if needed
+ if(!conflict.uid.isEmpty())
+ {
+ file->conflicts.insert(conflict);
+ }
+ // FIXME: @QUALITY do not ignore return value
+ ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), jsonFilePath);
+ component = new Component(this, uid, file);
+ component->m_version = intendedVersion;
+ }
+ else if(!intendedVersion.isEmpty())
+ {
+ auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion);
+ component = new Component(this, metaVersion);
+ }
+ else
+ {
+ return;
+ }
+ component->m_dependencyOnly = asDependency;
+ component->m_important = !asDependency;
+ components.append(component);
+ };
+ // TODO: insert depends and conflicts here if these are customized files...
+ Meta::Require reqLwjgl;
+ reqLwjgl.uid = "org.lwjgl";
+ reqLwjgl.suggests = "2.9.1";
+ Meta::Require conflictLwjgl3;
+ conflictLwjgl3.uid = "org.lwjgl3";
+ Meta::Require nullReq;
+ addBuiltinPatch("org.lwjgl", true, "2.9.1", nullReq, conflictLwjgl3);
+ addBuiltinPatch("net.minecraft", false, QString(), reqLwjgl, nullReq);
+
+ // first, collect all other file-based patches and load them
+ QMap<QString, ComponentPtr> loadedComponents;
+ QDir patchesDir(FS::PathCombine(d->m_instance->instanceRoot(),"patches"));
+ for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files))
+ {
+ // parse the file
+ qDebug() << "Reading" << info.fileName();
+ auto file = ProfileUtils::parseJsonFile(info, true);
+
+ // correct missing or wrong uid based on the file name
+ QString uid = info.completeBaseName();
+
+ // ignore builtins, they've been handled already
+ if (uid == "net.minecraft")
+ continue;
+ if (uid == "org.lwjgl")
+ continue;
+
+ // handle horrible corner cases
+ if(uid.isEmpty())
+ {
+ // if you have a file named '.json', make it just go away.
+ // FIXME: @QUALITY do not ignore return value
+ QFile::remove(info.absoluteFilePath());
+ continue;
+ }
+ file->uid = uid;
+ // FIXME: @QUALITY do not ignore return value
+ ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), info.absoluteFilePath());
+
+ auto component = new Component(this, file->uid, file);
+ auto version = d->getOldConfigVersion(file->uid);
+ if(!version.isEmpty())
+ {
+ component->m_version = version;
+ }
+ loadedComponents[file->uid] = component;
+ }
+ // try to load the other 'hardcoded' patches (forge, liteloader), if they weren't loaded from files
+ auto loadSpecial = [&](const QString & uid, int order)
+ {
+ auto patchVersion = d->getOldConfigVersion(uid);
+ if(!patchVersion.isEmpty() && !loadedComponents.contains(uid))
+ {
+ auto patch = new Component(this, ENV.metadataIndex()->get(uid, patchVersion));
+ patch->setOrder(order);
+ loadedComponents[uid] = patch;
+ }
+ };
+ loadSpecial("net.minecraftforge", 5);
+ loadSpecial("com.mumfrey.liteloader", 10);
+
+ // load the old order.json file, if present
+ ProfileUtils::PatchOrder userOrder;
+ ProfileUtils::readOverrideOrders(FS::PathCombine(d->m_instance->instanceRoot(), "order.json"), userOrder);
+
+ // now add all the patches by user sort order
+ for (auto uid : userOrder)
+ {
+ // ignore builtins
+ if (uid == "net.minecraft")
+ continue;
+ if (uid == "org.lwjgl")
+ continue;
+ // ordering has a patch that is gone?
+ if(!loadedComponents.contains(uid))
+ {
+ continue;
+ }
+ components.append(loadedComponents.take(uid));
+ }
+
+ // is there anything left to sort? - this is used when there are leftover components that aren't part of the order.json
+ if(!loadedComponents.isEmpty())
+ {
+ // inserting into multimap by order number as key sorts the patches and detects duplicates
+ QMultiMap<int, ComponentPtr> files;
+ auto iter = loadedComponents.begin();
+ while(iter != loadedComponents.end())
+ {
+ files.insert((*iter)->getOrder(), *iter);
+ iter++;
+ }
+
+ // then just extract the patches and put them in the list
+ for (auto order : files.keys())
+ {
+ const auto &values = files.values(order);
+ for(auto &value: values)
+ {
+ // TODO: put back the insertion of problem messages here, so the user knows about the id duplication
+ components.append(value);
+ }
+ }
+ }
+ // new we have a complete list of components...
+ return saveComponentList(componentsFilePath(), components);
+}
+
+// END: save/load
+
+void ComponentList::appendComponent(ComponentPtr component)
+{
+ insertComponent(d->components.size(), component);
+}
+
+void ComponentList::insertComponent(size_t index, ComponentPtr component)
+{
+ auto id = component->getID();
+ if(id.isEmpty())
+ {
+ qWarning() << "Attempt to add a component with empty ID!";
+ return;
+ }
+ if(d->componentIndex.contains(id))
+ {
+ qWarning() << "Attempt to add a component that is already present!";
+ return;
+ }
+ beginInsertRows(QModelIndex(), index, index);
+ d->components.insert(index, component);
+ d->componentIndex[id] = component;
+ endInsertRows();
+ connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ scheduleSave();
+}
+
+void ComponentList::componentDataChanged()
+{
+ auto objPtr = qobject_cast<Component *>(sender());
+ if(!objPtr)
+ {
+ qWarning() << "ComponentList got dataChenged signal from a non-Component!";
+ return;
+ }
+ // figure out which one is it... in a seriously dumb way.
+ int index = 0;
+ for (auto component: d->components)
+ {
+ if(component.get() == objPtr)
+ {
+ emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
+ scheduleSave();
+ return;
+ }
+ index++;
+ }
+ qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!";
+}
+
+bool ComponentList::remove(const int index)
+{
+ auto patch = getComponent(index);
+ if (!patch->isRemovable())
+ {
+ qWarning() << "Patch" << patch->getID() << "is non-removable";
+ return false;
+ }
+
+ if(!removeComponent_internal(patch))
+ {
+ qCritical() << "Patch" << patch->getID() << "could not be removed";
+ return false;
+ }
+
+ beginRemoveRows(QModelIndex(), index, index);
+ d->components.removeAt(index);
+ d->componentIndex.remove(patch->getID());
+ endRemoveRows();
+ invalidateLaunchProfile();
+ scheduleSave();
+ return true;
+}
+
+bool ComponentList::remove(const QString id)
+{
+ int i = 0;
+ for (auto patch : d->components)
+ {
+ if (patch->getID() == id)
+ {
+ return remove(i);
+ }
+ i++;
+ }
+ return false;
+}
+
+bool ComponentList::customize(int index)
+{
+ auto patch = getComponent(index);
+ if (!patch->isCustomizable())
+ {
+ qDebug() << "Patch" << patch->getID() << "is not customizable";
+ return false;
+ }
+ if(!patch->customize())
+ {
+ qCritical() << "Patch" << patch->getID() << "could not be customized";
+ return false;
+ }
+ invalidateLaunchProfile();
+ scheduleSave();
+ return true;
+}
+
+bool ComponentList::revertToBase(int index)
+{
+ auto patch = getComponent(index);
+ if (!patch->isRevertible())
+ {
+ qDebug() << "Patch" << patch->getID() << "is not revertible";
+ return false;
+ }
+ if(!patch->revert())
+ {
+ qCritical() << "Patch" << patch->getID() << "could not be reverted";
+ return false;
+ }
+ invalidateLaunchProfile();
+ scheduleSave();
+ return true;
+}
+
+Component * ComponentList::getComponent(const QString &id)
+{
+ auto iter = d->componentIndex.find(id);
+ if (iter == d->componentIndex.end())
+ {
+ return nullptr;
+ }
+ return (*iter).get();
+}
+
+Component * ComponentList::getComponent(int index)
+{
+ if(index < 0 || index >= d->components.size())
+ {
+ return nullptr;
+ }
+ return d->components[index].get();
+}
+
+QVariant ComponentList::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ int row = index.row();
+ int column = index.column();
+
+ if (row < 0 || row >= d->components.size())
+ return QVariant();
+
+ auto patch = d->components.at(row);
+
+ switch (role)
+ {
+ case Qt::CheckStateRole:
+ {
+ switch (column)
+ {
+ case NameColumn:
+ return d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked;
+ default:
+ return QVariant();
+ }
+ }
+ case Qt::DisplayRole:
+ {
+ switch (column)
+ {
+ case NameColumn:
+ return d->components.at(row)->getName();
+ case VersionColumn:
+ {
+ if(patch->isCustom())
+ {
+ return QString("%1 (Custom)").arg(patch->getVersion());
+ }
+ else
+ {
+ return patch->getVersion();
+ }
+ }
+ default:
+ return QVariant();
+ }
+ }
+ case Qt::DecorationRole:
+ {
+ switch(column)
+ {
+ case NameColumn:
+ {
+ auto severity = patch->getProblemSeverity();
+ switch (severity)
+ {
+ case ProblemSeverity::Warning:
+ return "warning";
+ case ProblemSeverity::Error:
+ return "error";
+ default:
+ return QVariant();
+ }
+ }
+ default:
+ {
+ return QVariant();
+ }
+ }
+ }
+ }
+ return QVariant();
+}
+
+bool ComponentList::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
+ {
+ return false;
+ }
+
+ if (role == Qt::CheckStateRole)
+ {
+ auto component = d->components[index.row()];
+ if (component->setEnabled(!component->isEnabled()))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+QVariant ComponentList::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal)
+ {
+ if (role == Qt::DisplayRole)
+ {
+ switch (section)
+ {
+ case NameColumn:
+ return tr("Name");
+ case VersionColumn:
+ return tr("Version");
+ default:
+ return QVariant();
+ }
+ }
+ }
+ return QVariant();
+}
+Qt::ItemFlags ComponentList::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::NoItemFlags;
+
+ Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+
+ int row = index.row();
+
+ if (row < 0 || row >= d->components.size())
+ return Qt::NoItemFlags;
+
+ auto patch = d->components.at(row);
+ // TODO: this will need fine-tuning later...
+ if(patch->canBeDisabled())
+ {
+ outFlags |= Qt::ItemIsUserCheckable;
+ }
+ return outFlags;
+}
+
+int ComponentList::rowCount(const QModelIndex &parent) const
+{
+ return d->components.size();
+}
+
+int ComponentList::columnCount(const QModelIndex &parent) const
+{
+ return NUM_COLUMNS;
+}
+
+void ComponentList::move(const int index, const MoveDirection direction)
+{
+ int theirIndex;
+ if (direction == MoveUp)
+ {
+ theirIndex = index - 1;
+ }
+ else
+ {
+ theirIndex = index + 1;
+ }
+
+ if (index < 0 || index >= d->components.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 = getComponent(index);
+ auto to = getComponent(theirIndex);
+
+ if (!from || !to || !to->isMoveable() || !from->isMoveable())
+ {
+ return;
+ }
+ beginMoveRows(QModelIndex(), index, index, QModelIndex(), togap);
+ d->components.swap(index, theirIndex);
+ endMoveRows();
+ invalidateLaunchProfile();
+ scheduleSave();
+}
+
+void ComponentList::invalidateLaunchProfile()
+{
+ d->m_profile.reset();
+}
+
+void ComponentList::installJarMods(QStringList selectedFiles)
+{
+ installJarMods_internal(selectedFiles);
+}
+
+void ComponentList::installCustomJar(QString selectedFile)
+{
+ installCustomJar_internal(selectedFile);
+}
+
+bool ComponentList::installEmpty(const QString& uid, const QString& name)
+{
+ QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
+ if(!FS::ensureFolderPathExists(patchDir))
+ {
+ return false;
+ }
+ auto f = std::make_shared<VersionFile>();
+ f->name = name;
+ f->uid = uid;
+ f->version = "1";
+ QString patchFileName = FS::PathCombine(patchDir, uid + ".json");
+ QFile file(patchFileName);
+ if (!file.open(QFile::WriteOnly))
+ {
+ qCritical() << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
+ return false;
+ }
+ file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
+ file.close();
+
+ appendComponent(new Component(this, f->uid, f));
+ scheduleSave();
+ invalidateLaunchProfile();
+ return true;
+}
+
+bool ComponentList::removeComponent_internal(ComponentPtr patch)
+{
+ bool ok = true;
+ // first, remove the patch file. this ensures it's not used anymore
+ auto fileName = patch->getFilename();
+ if(fileName.size())
+ {
+ QFile patchFile(fileName);
+ if(patchFile.exists() && !patchFile.remove())
+ {
+ qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString();
+ return false;
+ }
+ }
+
+ // FIXME: we need a generic way of removing local resources, not just jar mods...
+ auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool
+ {
+ if (!jarMod->isLocal())
+ {
+ return true;
+ }
+ QStringList jar, temp1, temp2, temp3;
+ jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath());
+ QFileInfo finfo (jar[0]);
+ if(finfo.exists())
+ {
+ QFile jarModFile(jar[0]);
+ if(!jarModFile.remove())
+ {
+ qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString();
+ return false;
+ }
+ return true;
+ }
+ return true;
+ };
+
+ auto vFile = patch->getVersionFile();
+ if(vFile)
+ {
+ auto &jarMods = vFile->jarMods;
+ for(auto &jarmod: jarMods)
+ {
+ ok &= preRemoveJarMod(jarmod);
+ }
+ }
+ return ok;
+}
+
+bool ComponentList::installJarMods_internal(QStringList filepaths)
+{
+ QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
+ if(!FS::ensureFolderPathExists(patchDir))
+ {
+ return false;
+ }
+
+ if (!FS::ensureFolderPathExists(d->m_instance->jarModsDir()))
+ {
+ return false;
+ }
+
+ for(auto filepath:filepaths)
+ {
+ 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 = FS::PathCombine(d->m_instance->jarModsDir(), target_filename);
+
+ QFileInfo targetInfo(finalPath);
+ if(targetInfo.exists())
+ {
+ return false;
+ }
+
+ if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath()))
+ {
+ return false;
+ }
+
+ auto f = std::make_shared<VersionFile>();
+ auto jarMod = std::make_shared<Library>();
+ jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1"));
+ jarMod->setFilename(target_filename);
+ jarMod->setDisplayName(sourceInfo.completeBaseName());
+ jarMod->setHint("local");
+ f->jarMods.append(jarMod);
+ f->name = target_name;
+ f->uid = target_id;
+ QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
+
+ QFile file(patchFileName);
+ if (!file.open(QFile::WriteOnly))
+ {
+ qCritical() << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
+ return false;
+ }
+ file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
+ file.close();
+
+ appendComponent(new Component(this, f->uid, f));
+ }
+ scheduleSave();
+ invalidateLaunchProfile();
+ return true;
+}
+
+bool ComponentList::installCustomJar_internal(QString filepath)
+{
+ QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
+ if(!FS::ensureFolderPathExists(patchDir))
+ {
+ return false;
+ }
+
+ QString libDir = d->m_instance->getLocalLibraryPath();
+ if (!FS::ensureFolderPathExists(libDir))
+ {
+ return false;
+ }
+
+ auto specifier = GradleSpecifier("org.multimc:customjar:1");
+ QFileInfo sourceInfo(filepath);
+ QString target_filename = specifier.getFileName();
+ QString target_id = specifier.artifactId();
+ QString target_name = sourceInfo.completeBaseName() + " (custom jar)";
+ QString finalPath = FS::PathCombine(libDir, target_filename);
+
+ QFileInfo jarInfo(finalPath);
+ if (jarInfo.exists())
+ {
+ if(!QFile::remove(finalPath))
+ {
+ return false;
+ }
+ }
+ if (!QFile::copy(filepath, finalPath))
+ {
+ return false;
+ }
+
+ auto f = std::make_shared<VersionFile>();
+ auto jarMod = std::make_shared<Library>();
+ jarMod->setRawName(specifier);
+ jarMod->setDisplayName(sourceInfo.completeBaseName());
+ jarMod->setHint("local");
+ f->mainJar = jarMod;
+ f->name = target_name;
+ f->uid = target_id;
+ QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
+
+ QFile file(patchFileName);
+ if (!file.open(QFile::WriteOnly))
+ {
+ qCritical() << "Error opening" << file.fileName()
+ << "for reading:" << file.errorString();
+ return false;
+ }
+ file.write(OneSixVersionFormat::versionFileToJson(f).toJson());
+ file.close();
+
+ appendComponent(new Component(this, f->uid, f));
+
+ scheduleSave();
+ invalidateLaunchProfile();
+ return true;
+}
+
+std::shared_ptr<LaunchProfile> ComponentList::getProfile() const
+{
+ if(!d->m_profile)
+ {
+ try
+ {
+ auto profile = std::make_shared<LaunchProfile>();
+ for(auto file: d->components)
+ {
+ qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
+ file->applyTo(profile.get());
+ }
+ d->m_profile = profile;
+ }
+ catch (Exception & error)
+ {
+ qWarning() << "Couldn't apply profile patches because: " << error.cause();
+ }
+ }
+ return d->m_profile;
+}
+
+void ComponentList::setOldConfigVersion(const QString& uid, const QString& version)
+{
+ if(version.isEmpty())
+ {
+ return;
+ }
+ d->m_oldConfigVersions[uid] = version;
+}
+
+bool ComponentList::setComponentVersion(const QString& uid, const QString& version, bool important)
+{
+ auto iter = d->componentIndex.find(uid);
+ if(iter != d->componentIndex.end())
+ {
+ ComponentPtr component = *iter;
+ // set existing
+ if(component->revert())
+ {
+ component->setVersion(version);
+ component->setImportant(important);
+ return true;
+ }
+ return false;
+ }
+ else
+ {
+ // add new
+ auto component = new Component(this, uid);
+ component->m_version = version;
+ component->m_important = important;
+ appendComponent(component);
+ return true;
+ }
+}
+
+QString ComponentList::getComponentVersion(const QString& uid) const
+{
+ const auto iter = d->componentIndex.find(uid);
+ if (iter != d->componentIndex.end())
+ {
+ return (*iter)->getVersion();
+ }
+ return QString();
+}
diff --git a/api/logic/minecraft/ComponentList.h b/api/logic/minecraft/ComponentList.h
new file mode 100644
index 00000000..cf4d9975
--- /dev/null
+++ b/api/logic/minecraft/ComponentList.h
@@ -0,0 +1,146 @@
+/* Copyright 2013-2018 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 <QAbstractListModel>
+
+#include <QString>
+#include <QList>
+#include <memory>
+
+#include "Library.h"
+#include "LaunchProfile.h"
+#include "Component.h"
+#include "ProfileUtils.h"
+#include "BaseVersion.h"
+#include "MojangDownloadInfo.h"
+#include "multimc_logic_export.h"
+#include "net/Mode.h"
+
+class MinecraftInstance;
+struct ComponentListData;
+class ComponentUpdateTask;
+
+class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel
+{
+ Q_OBJECT
+ friend ComponentUpdateTask;
+public:
+ enum Columns
+ {
+ NameColumn = 0,
+ VersionColumn,
+ NUM_COLUMNS
+ };
+
+ explicit ComponentList(MinecraftInstance * instance);
+ virtual ~ComponentList();
+
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ virtual int columnCount(const QModelIndex &parent) const override;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ /// call this to explicitly mark the component list as loaded - this is used to build a new component list from scratch.
+ void buildingFromScratch();
+
+ /// install more jar mods
+ void installJarMods(QStringList selectedFiles);
+
+ /// install a jar/zip as a replacement for the main jar
+ void installCustomJar(QString selectedFile);
+
+ enum MoveDirection { MoveUp, MoveDown };
+ /// move component file # up or down the list
+ void move(const int index, const MoveDirection direction);
+
+ /// remove component file # - including files/records
+ bool remove(const int index);
+
+ /// remove component file by id - including files/records
+ bool remove(const QString id);
+
+ bool customize(int index);
+
+ bool revertToBase(int index);
+
+ /// reload the list, reload all components, resolve dependencies
+ void reload(Net::Mode netmode);
+
+ // reload all components, resolve dependencies
+ void resolve(Net::Mode netmode);
+
+ /// get current running task...
+ shared_qobject_ptr<Task> getCurrentTask();
+
+ std::shared_ptr<LaunchProfile> getProfile() const;
+
+ // NOTE: used ONLY by MinecraftInstance to provide legacy version mappings from instance config
+ void setOldConfigVersion(const QString &uid, const QString &version);
+
+ QString getComponentVersion(const QString &uid) const;
+
+ bool setComponentVersion(const QString &uid, const QString &version, bool important = false);
+
+ bool installEmpty(const QString &uid, const QString &name);
+
+ QString patchFilePathForUid(const QString &uid) const;
+
+ /// if there is a save scheduled, do it now.
+ void saveNow();
+
+public:
+ /// get the profile component by id
+ Component * getComponent(const QString &id);
+
+ /// get the profile component by index
+ Component * getComponent(int index);
+
+private:
+ void scheduleSave();
+ bool saveIsScheduled() const;
+
+ /// apply the component patches. Catches all the errors and returns true/false for success/failure
+ void invalidateLaunchProfile();
+
+ /// Add the component to the internal list of patches
+ void appendComponent(ComponentPtr component);
+ /// insert component so that its index is ideally the specified one (returns real index)
+ void insertComponent(size_t index, ComponentPtr component);
+
+ QString componentsFilePath() const;
+ QString patchesPattern() const;
+
+private slots:
+ void save_internal();
+ void updateSucceeded();
+ void updateFailed(const QString & error);
+ void componentDataChanged();
+
+private:
+ bool load();
+ bool installJarMods_internal(QStringList filepaths);
+ bool installCustomJar_internal(QString filepath);
+ bool removeComponent_internal(ComponentPtr patch);
+
+ bool migratePreComponentConfig();
+
+private: /* data */
+
+ std::unique_ptr<ComponentListData> d;
+};
diff --git a/api/logic/minecraft/ComponentList_p.h b/api/logic/minecraft/ComponentList_p.h
new file mode 100644
index 00000000..26ca5049
--- /dev/null
+++ b/api/logic/minecraft/ComponentList_p.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "Component.h"
+#include <map>
+#include <QTimer>
+#include <QList>
+#include <QMap>
+
+class MinecraftInstance;
+using ComponentContainer = QList<ComponentPtr>;
+using ComponentIndex = QMap<QString, ComponentPtr>;
+using ConnectionList = QList<QMetaObject::Connection>;
+
+struct ComponentListData
+{
+ // the instance this belongs to
+ MinecraftInstance *m_instance;
+
+ // the launch profile (volatile, temporary thing created on demand)
+ std::shared_ptr<LaunchProfile> m_profile;
+
+ // version information migrated from instance.cfg file. Single use on migration!
+ std::map<QString, QString> m_oldConfigVersions;
+ QString getOldConfigVersion(const QString& uid) const
+ {
+ const auto iter = m_oldConfigVersions.find(uid);
+ if(iter != m_oldConfigVersions.cend())
+ {
+ return (*iter).second;
+ }
+ return QString();
+ }
+
+ // persistent list of components and related machinery
+ ComponentContainer components;
+ ComponentIndex componentIndex;
+ bool dirty = false;
+ QTimer m_saveTimer;
+ shared_qobject_ptr<Task> m_updateTask;
+ bool loaded = false;
+};
+
diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp
new file mode 100644
index 00000000..2d6ceb91
--- /dev/null
+++ b/api/logic/minecraft/ComponentUpdateTask.cpp
@@ -0,0 +1,691 @@
+#include "ComponentUpdateTask.h"
+
+#include "ComponentList_p.h"
+#include "ComponentList.h"
+#include "Component.h"
+#include <Env.h>
+#include <meta/Index.h>
+#include <meta/VersionList.h>
+#include <meta/Version.h>
+#include "ComponentUpdateTask_p.h"
+#include <cassert>
+#include <Version.h>
+#include "net/Mode.h"
+#include "OneSixVersionFormat.h"
+
+/*
+ * This is responsible for loading the components of a component list AND resolving dependency issues between them
+ */
+
+/*
+ * FIXME: the 'one shot async task' nature of this does not fit the intended usage
+ * Really, it should be a reactor/state machine that receives input from the application
+ * and dynamically adapts to changing requirements...
+ *
+ * The reactor should be the only entry into manipulating the ComponentList.
+ * See: https://en.wikipedia.org/wiki/Reactor_pattern
+ */
+
+/*
+ * Or make this operate on a snapshot of the ComponentList state, then merge results in as long as the snapshot and ComponentList didn't change?
+ * If the component list changes, start over.
+ */
+
+ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent)
+ : Task(parent)
+{
+ d.reset(new ComponentUpdateTaskData);
+ d->m_list = list;
+ d->mode = mode;
+ d->netmode = netmode;
+}
+
+ComponentUpdateTask::~ComponentUpdateTask()
+{
+}
+
+void ComponentUpdateTask::executeTask()
+{
+ qDebug() << "Loading components";
+ loadComponents();
+}
+
+namespace
+{
+enum class LoadResult
+{
+ LoadedLocal,
+ RequiresRemote,
+ Failed
+};
+
+LoadResult composeLoadResult(LoadResult a, LoadResult b)
+{
+ if (a < b)
+ {
+ return b;
+ }
+ return a;
+}
+
+static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
+{
+ if(component->m_loaded)
+ {
+ qDebug() << component->getName() << "is already loaded";
+ return LoadResult::LoadedLocal;
+ }
+
+ LoadResult result = LoadResult::Failed;
+ auto customPatchFilename = component->getFilename();
+ if(QFile::exists(customPatchFilename))
+ {
+ // if local file exists...
+
+ // check for uid problems inside...
+ bool fileChanged = false;
+ auto file = ProfileUtils::parseJsonFile(QFileInfo(customPatchFilename), false);
+ if(file->uid != component->m_uid)
+ {
+ file->uid = component->m_uid;
+ fileChanged = true;
+ }
+ if(fileChanged)
+ {
+ // FIXME: @QUALITY do not ignore return value
+ ProfileUtils::saveJsonFile(OneSixVersionFormat::versionFileToJson(file), customPatchFilename);
+ }
+
+ component->m_file = file;
+ component->m_loaded = true;
+ result = LoadResult::LoadedLocal;
+ }
+ else
+ {
+ auto metaVersion = ENV.metadataIndex()->get(component->m_uid, component->m_version);
+ component->m_metaVersion = metaVersion;
+ if(metaVersion->isLoaded())
+ {
+ component->m_loaded = true;
+ result = LoadResult::LoadedLocal;
+ }
+ else
+ {
+ metaVersion->load(netmode);
+ loadTask = metaVersion->getCurrentTask();
+ if(loadTask)
+ result = LoadResult::RequiresRemote;
+ else if (metaVersion->isLoaded())
+ result = LoadResult::LoadedLocal;
+ else
+ result = LoadResult::Failed;
+ }
+ }
+ return result;
+}
+
+// FIXME: dead code. determine if this can still be useful?
+/*
+static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
+{
+ if(component->m_loaded)
+ {
+ qDebug() << component->getName() << "is already loaded";
+ return LoadResult::LoadedLocal;
+ }
+
+ LoadResult result = LoadResult::Failed;
+ auto metaList = ENV.metadataIndex()->get(component->m_uid);
+ if(metaList->isLoaded())
+ {
+ component->m_loaded = true;
+ result = LoadResult::LoadedLocal;
+ }
+ else
+ {
+ metaList->load(netmode);
+ loadTask = metaList->getCurrentTask();
+ result = LoadResult::RequiresRemote;
+ }
+ return result;
+}
+*/
+
+static LoadResult loadIndex(shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
+{
+ // FIXME: DECIDE. do we want to run the update task anyway?
+ if(ENV.metadataIndex()->isLoaded())
+ {
+ qDebug() << "Index is already loaded";
+ return LoadResult::LoadedLocal;
+ }
+ ENV.metadataIndex()->load(netmode);
+ loadTask = ENV.metadataIndex()->getCurrentTask();
+ if(loadTask)
+ {
+ return LoadResult::RequiresRemote;
+ }
+ // FIXME: this is assuming the load succeeded... did it really?
+ return LoadResult::LoadedLocal;
+}
+}
+
+void ComponentUpdateTask::loadComponents()
+{
+ LoadResult result = LoadResult::LoadedLocal;
+ size_t taskIndex = 0;
+ size_t componentIndex = 0;
+ d->remoteLoadSuccessful = true;
+ // load the main index (it is needed to determine if components can revert)
+ {
+ // FIXME: tear out as a method? or lambda?
+ shared_qobject_ptr<Task> indexLoadTask;
+ auto singleResult = loadIndex(indexLoadTask, d->netmode);
+ result = composeLoadResult(result, singleResult);
+ if(indexLoadTask)
+ {
+ qDebug() << "Remote loading is being run for metadata index";
+ RemoteLoadStatus status;
+ status.type = RemoteLoadStatus::Type::Index;
+ d->remoteLoadStatusList.append(status);
+ connect(indexLoadTask.get(), &Task::succeeded, [=]()
+ {
+ remoteLoadSucceeded(taskIndex);
+ });
+ connect(indexLoadTask.get(), &Task::failed, [=](const QString & error)
+ {
+ remoteLoadFailed(taskIndex, error);
+ });
+ taskIndex++;
+ }
+ }
+ // load all the components OR their lists...
+ for (auto component: d->m_list->d->components)
+ {
+ shared_qobject_ptr<Task> loadTask;
+ LoadResult singleResult;
+ RemoteLoadStatus::Type loadType;
+ // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, ignore all that...
+#if 0
+ switch(d->mode)
+ {
+ case Mode::Launch:
+ {
+ singleResult = loadComponent(component, loadTask, d->netmode);
+ loadType = RemoteLoadStatus::Type::Version;
+ break;
+ }
+ case Mode::Resolution:
+ {
+ singleResult = loadComponentList(component, loadTask, d->netmode);
+ loadType = RemoteLoadStatus::Type::List;
+ break;
+ }
+ }
+#else
+ singleResult = loadComponent(component, loadTask, d->netmode);
+ loadType = RemoteLoadStatus::Type::Version;
+#endif
+ if(singleResult == LoadResult::LoadedLocal)
+ {
+ component->updateCachedData();
+ }
+ result = composeLoadResult(result, singleResult);
+ if (loadTask)
+ {
+ qDebug() << "Remote loading is being run for" << component->getName();
+ connect(loadTask.get(), &Task::succeeded, [=]()
+ {
+ remoteLoadSucceeded(taskIndex);
+ });
+ connect(loadTask.get(), &Task::failed, [=](const QString & error)
+ {
+ remoteLoadFailed(taskIndex, error);
+ });
+ RemoteLoadStatus status;
+ status.type = loadType;
+ status.componentListIndex = componentIndex;
+ d->remoteLoadStatusList.append(status);
+ taskIndex++;
+ }
+ componentIndex++;
+ }
+ d->remoteTasksInProgress = taskIndex;
+ switch(result)
+ {
+ case LoadResult::LoadedLocal:
+ {
+ // Everything got loaded. Advance to dependency resolution.
+ resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline);
+ break;
+ }
+ case LoadResult::RequiresRemote:
+ {
+ // we wait for signals.
+ break;
+ }
+ case LoadResult::Failed:
+ {
+ emitFailed(tr("Some component metadata load tasks failed."));
+ break;
+ }
+ }
+}
+
+namespace
+{
+ struct RequireEx : public Meta::Require
+ {
+ size_t indexOfFirstDependee = 0;
+ };
+ struct RequireCompositionResult
+ {
+ bool ok;
+ RequireEx outcome;
+ };
+ using RequireExSet = std::set<RequireEx>;
+}
+
+static RequireCompositionResult composeRequirement(const RequireEx & a, const RequireEx & b)
+{
+ assert(a.uid == b.uid);
+ RequireEx out;
+ out.uid = a.uid;
+ out.indexOfFirstDependee = std::min(a.indexOfFirstDependee, b.indexOfFirstDependee);
+ if(a.equalsVersion.isEmpty())
+ {
+ out.equalsVersion = b.equalsVersion;
+ }
+ else if (b.equalsVersion.isEmpty())
+ {
+ out.equalsVersion = a.equalsVersion;
+ }
+ else if (a.equalsVersion == b.equalsVersion)
+ {
+ out.equalsVersion = a.equalsVersion;
+ }
+ else
+ {
+ // FIXME: mark error as explicit version conflict
+ return {false, out};
+ }
+
+ if(a.suggests.isEmpty())
+ {
+ out.suggests = b.suggests;
+ }
+ else if (b.suggests.isEmpty())
+ {
+ out.suggests = a.suggests;
+ }
+ else
+ {
+ Version aVer(a.suggests);
+ Version bVer(b.suggests);
+ out.suggests = (aVer < bVer ? b.suggests : a.suggests);
+ }
+ return {true, out};
+}
+
+// gather the requirements from all components, finding any obvious conflicts
+static bool gatherRequirementsFromComponents(const ComponentContainer & input, RequireExSet & output)
+{
+ bool succeeded = true;
+ size_t componentNum = 0;
+ for(auto component: input)
+ {
+ auto &componentRequires = component->m_cachedRequires;
+ for(const auto & componentRequire: componentRequires)
+ {
+ auto found = std::find_if(output.cbegin(), output.cend(), [componentRequire](const Meta::Require & req){
+ return req.uid == componentRequire.uid;
+ });
+
+ RequireEx componenRequireEx;
+ componenRequireEx.uid = componentRequire.uid;
+ componenRequireEx.suggests = componentRequire.suggests;
+ componenRequireEx.equalsVersion = componentRequire.equalsVersion;
+ componenRequireEx.indexOfFirstDependee = componentNum;
+
+ if(found != output.cend())
+ {
+ // found... process it further
+ auto result = composeRequirement(componenRequireEx, *found);
+ if(result.ok)
+ {
+ output.erase(componenRequireEx);
+ output.insert(result.outcome);
+ }
+ else
+ {
+ qCritical()
+ << "Conflicting requirements:"
+ << componentRequire.uid
+ << "versions:"
+ << componentRequire.equalsVersion
+ << ";"
+ << (*found).equalsVersion;
+ }
+ succeeded &= result.ok;
+ }
+ else
+ {
+ // not found, accumulate
+ output.insert(componenRequireEx);
+ }
+ }
+ componentNum++;
+ }
+ return succeeded;
+}
+
+/// Get list of uids that can be trivially removed because nothing is depending on them anymore (and they are installed as deps)
+static void getTrivialRemovals(const ComponentContainer & components, const RequireExSet & reqs, QStringList &toRemove)
+{
+ for(const auto & component: components)
+ {
+ if(!component->m_dependencyOnly)
+ continue;
+ if(!component->m_cachedVolatile)
+ continue;
+ RequireEx reqNeedle;
+ reqNeedle.uid = component->m_uid;
+ const auto iter = reqs.find(reqNeedle);
+ if(iter == reqs.cend())
+ {
+ toRemove.append(component->m_uid);
+ }
+ }
+}
+
+/**
+ * handles:
+ * - trivial addition (there is an unmet requirement and it can be trivially met by adding something)
+ * - trivial version conflict of dependencies == explicit version required and installed is different
+ *
+ * toAdd - set of requirements than mean adding a new component
+ * toChange - set of requirements that mean changing version of an existing component
+ */
+static bool getTrivialComponentChanges(const ComponentIndex & index, const RequireExSet & input, RequireExSet & toAdd, RequireExSet & toChange)
+{
+ enum class Decision
+ {
+ Undetermined,
+ Met,
+ Missing,
+ VersionNotSame,
+ LockedVersionNotSame
+ } decision = Decision::Undetermined;
+
+ QString reqStr;
+ bool succeeded = true;
+ // list the composed requirements and say if they are met or unmet
+ for(auto & req: input)
+ {
+ do
+ {
+ if(req.equalsVersion.isEmpty())
+ {
+ reqStr = QString("Req: %1").arg(req.uid);
+ if(index.contains(req.uid))
+ {
+ decision = Decision::Met;
+ }
+ else
+ {
+ toAdd.insert(req);
+ decision = Decision::Missing;
+ }
+ break;
+ }
+ else
+ {
+ reqStr = QString("Req: %1 == %2").arg(req.uid, req.equalsVersion);
+ const auto & compIter = index.find(req.uid);
+ if(compIter == index.cend())
+ {
+ toAdd.insert(req);
+ decision = Decision::Missing;
+ break;
+ }
+ auto & comp = (*compIter);
+ if(comp->getVersion() != req.equalsVersion)
+ {
+ if(comp->m_dependencyOnly)
+ {
+ decision = Decision::VersionNotSame;
+ }
+ else
+ {
+ decision = Decision::LockedVersionNotSame;
+ }
+ break;
+ }
+ decision = Decision::Met;
+ }
+ } while(false);
+ switch(decision)
+ {
+ case Decision::Undetermined:
+ qCritical() << "No decision for" << reqStr;
+ succeeded = false;
+ break;
+ case Decision::Met:
+ qDebug() << reqStr << "Is met.";
+ break;
+ case Decision::Missing:
+ qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee;
+ toAdd.insert(req);
+ break;
+ case Decision::VersionNotSame:
+ qDebug() << reqStr << "already has different version that can be changed.";
+ toChange.insert(req);
+ break;
+ case Decision::LockedVersionNotSame:
+ qDebug() << reqStr << "already has different version that cannot be changed.";
+ succeeded = false;
+ break;
+ }
+ }
+ return succeeded;
+}
+
+// FIXME, TODO: decouple dependency resolution from loading
+// FIXME: This works directly with the ComponentList internals. It shouldn't! It needs richer data types than ComponentList uses.
+// FIXME: throw all this away and use a graph
+void ComponentUpdateTask::resolveDependencies(bool checkOnly)
+{
+ qDebug() << "Resolving dependencies";
+ /*
+ * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways:
+ * 1. There are conflicting dependencies on the same uid with different exact version numbers
+ * -> hard error
+ * 2. A dependency has non-matching exact version number
+ * -> hard error
+ * 3. A dependency is entirely missing and needs to be injected before the dependee(s)
+ * -> requirements are injected
+ *
+ * NOTE: this is a placeholder and should eventually be replaced with something 'serious'
+ */
+ auto & components = d->m_list->d->components;
+ auto & componentIndex = d->m_list->d->componentIndex;
+
+ RequireExSet allRequires;
+ QStringList toRemove;
+ do
+ {
+ allRequires.clear();
+ toRemove.clear();
+ if(!gatherRequirementsFromComponents(components, allRequires))
+ {
+ emitFailed(tr("Conflicting requirements detected during dependency checking!"));
+ return;
+ }
+ getTrivialRemovals(components, allRequires, toRemove);
+ if(!toRemove.isEmpty())
+ {
+ qDebug() << "Removing obsolete components...";
+ for(auto & remove : toRemove)
+ {
+ qDebug() << "Removing" << remove;
+ d->m_list->remove(remove);
+ }
+ }
+ } while (!toRemove.isEmpty());
+ RequireExSet toAdd;
+ RequireExSet toChange;
+ bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange);
+ if(!succeeded)
+ {
+ emitFailed(tr("Instance has conflicting dependencies."));
+ return;
+ }
+ if(checkOnly)
+ {
+ if(toAdd.size() || toChange.size())
+ {
+ emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch."));
+ }
+ else
+ {
+ emitSucceeded();
+ }
+ return;
+ }
+
+ bool recursionNeeded = false;
+ if(toAdd.size())
+ {
+ // add stuff...
+ for(auto &add: toAdd)
+ {
+ ComponentPtr component = new Component(d->m_list, add.uid);
+ if(!add.equalsVersion.isEmpty())
+ {
+ // exact version
+ qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee;
+ component->m_version = add.equalsVersion;
+ }
+ else
+ {
+ // version needs to be decided
+ qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee;
+// ############################################################################################################
+// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
+ if(!add.suggests.isEmpty())
+ {
+ component->m_version = add.suggests;
+ }
+ else
+ {
+ if(add.uid == "org.lwjgl")
+ {
+ component->m_version = "2.9.1";
+ }
+ else if (add.uid == "org.lwjgl3")
+ {
+ component->m_version = "3.1.2";
+ }
+ }
+// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
+// ############################################################################################################
+ }
+ component->m_dependencyOnly = true;
+ // FIXME: this should not work directly with the component list
+ d->m_list->insertComponent(add.indexOfFirstDependee, component);
+ componentIndex[add.uid] = component;
+ }
+ recursionNeeded = true;
+ }
+ if(toChange.size())
+ {
+ // change a version of something that exists
+ for(auto &change: toChange)
+ {
+ // FIXME: this should not work directly with the component list
+ qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion;
+ auto component = componentIndex[change.uid];
+ component->setVersion(change.equalsVersion);
+ }
+ recursionNeeded = true;
+ }
+
+ if(recursionNeeded)
+ {
+ loadComponents();
+ }
+ else
+ {
+ emitSucceeded();
+ }
+}
+
+void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
+{
+ auto &taskSlot = d->remoteLoadStatusList[taskIndex];
+ if(taskSlot.finished)
+ {
+ qWarning() << "Got multiple results from remote load task" << taskIndex;
+ return;
+ }
+ qDebug() << "Remote task" << taskIndex << "succeeded";
+ taskSlot.succeeded = false;
+ taskSlot.finished = true;
+ d->remoteTasksInProgress --;
+ // update the cached data of the component from the downloaded version file.
+ if (taskSlot.type == RemoteLoadStatus::Type::Version)
+ {
+ auto component = d->m_list->getComponent(taskSlot.componentListIndex);
+ component->m_loaded = true;
+ component->updateCachedData();
+ }
+ checkIfAllFinished();
+}
+
+
+void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg)
+{
+ auto &taskSlot = d->remoteLoadStatusList[taskIndex];
+ if(taskSlot.finished)
+ {
+ qWarning() << "Got multiple results from remote load task" << taskIndex;
+ return;
+ }
+ qDebug() << "Remote task" << taskIndex << "failed: " << msg;
+ d->remoteLoadSuccessful = false;
+ taskSlot.succeeded = false;
+ taskSlot.finished = true;
+ taskSlot.error = msg;
+ d->remoteTasksInProgress --;
+ checkIfAllFinished();
+}
+
+void ComponentUpdateTask::checkIfAllFinished()
+{
+ if(d->remoteTasksInProgress)
+ {
+ // not yet...
+ return;
+ }
+ if(d->remoteLoadSuccessful)
+ {
+ // nothing bad happened... clear the temp load status and proceed with looking at dependencies
+ d->remoteLoadStatusList.clear();
+ resolveDependencies(d->mode == Mode::Launch);
+ }
+ else
+ {
+ // remote load failed... report error and bail
+ QStringList allErrorsList;
+ for(auto & item: d->remoteLoadStatusList)
+ {
+ if(!item.succeeded)
+ {
+ allErrorsList.append(item.error);
+ }
+ }
+ auto allErrors = allErrorsList.join("\n");
+ emitFailed(tr("Component metadata update task failed while downloading from remote server:\n%1").arg(allErrors));
+ d->remoteLoadStatusList.clear();
+ }
+}
diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h
new file mode 100644
index 00000000..11d122b6
--- /dev/null
+++ b/api/logic/minecraft/ComponentUpdateTask.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "tasks/Task.h"
+#include "net/Mode.h"
+
+#include <memory>
+class ComponentList;
+struct ComponentUpdateTaskData;
+
+class ComponentUpdateTask : public Task
+{
+ Q_OBJECT
+public:
+ enum class Mode
+ {
+ Launch,
+ Resolution
+ };
+
+public:
+ explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0);
+ virtual ~ComponentUpdateTask();
+
+protected:
+ void executeTask();
+
+private:
+ void loadComponents();
+ void resolveDependencies(bool checkOnly);
+
+ void remoteLoadSucceeded(size_t index);
+ void remoteLoadFailed(size_t index, const QString &msg);
+ void checkIfAllFinished();
+
+private:
+ std::unique_ptr<ComponentUpdateTaskData> d;
+};
diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h
new file mode 100644
index 00000000..a5216506
--- /dev/null
+++ b/api/logic/minecraft/ComponentUpdateTask_p.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <cstddef>
+#include <QString>
+#include <QList>
+#include "net/Mode.h"
+
+class ComponentList;
+
+struct RemoteLoadStatus
+{
+ enum class Type
+ {
+ Index,
+ List,
+ Version
+ } type = Type::Version;
+ size_t componentListIndex = 0;
+ bool finished = false;
+ bool succeeded = false;
+ QString error;
+};
+
+struct ComponentUpdateTaskData
+{
+ ComponentList * m_list = nullptr;
+ QList<RemoteLoadStatus> remoteLoadStatusList;
+ bool remoteLoadSuccessful = true;
+ size_t remoteTasksInProgress = 0;
+ ComponentUpdateTask::Mode mode;
+ Net::Mode netmode;
+};
diff --git a/api/logic/minecraft/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp
new file mode 100644
index 00000000..436a39d9
--- /dev/null
+++ b/api/logic/minecraft/LaunchProfile.cpp
@@ -0,0 +1,297 @@
+#include "LaunchProfile.h"
+#include <Version.h>
+
+void LaunchProfile::clear()
+{
+ m_minecraftVersion.clear();
+ m_minecraftVersionType.clear();
+ m_minecraftAssets.reset();
+ m_minecraftArguments.clear();
+ m_tweakers.clear();
+ m_mainClass.clear();
+ m_appletClass.clear();
+ m_libraries.clear();
+ m_traits.clear();
+ m_jarMods.clear();
+ m_mainJar.reset();
+ m_problemSeverity = ProblemSeverity::None;
+}
+
+static void applyString(const QString & from, QString & to)
+{
+ if(from.isEmpty())
+ return;
+ to = from;
+}
+
+void LaunchProfile::applyMinecraftVersion(const QString& id)
+{
+ applyString(id, this->m_minecraftVersion);
+}
+
+void LaunchProfile::applyAppletClass(const QString& appletClass)
+{
+ applyString(appletClass, this->m_appletClass);
+}
+
+void LaunchProfile::applyMainClass(const QString& mainClass)
+{
+ applyString(mainClass, this->m_mainClass);
+}
+
+void LaunchProfile::applyMinecraftArguments(const QString& minecraftArguments)
+{
+ applyString(minecraftArguments, this->m_minecraftArguments);
+}
+
+void LaunchProfile::applyMinecraftVersionType(const QString& type)
+{
+ applyString(type, this->m_minecraftVersionType);
+}
+
+void LaunchProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets)
+{
+ if(assets)
+ {
+ m_minecraftAssets = assets;
+ }
+}
+
+void LaunchProfile::applyTraits(const QSet<QString>& traits)
+{
+ this->m_traits.unite(traits);
+}
+
+void LaunchProfile::applyTweakers(const QStringList& tweakers)
+{
+ // if the applied tweakers override an existing one, skip it. this effectively moves it later in the sequence
+ QStringList newTweakers;
+ for(auto & tweaker: m_tweakers)
+ {
+ if (tweakers.contains(tweaker))
+ {
+ continue;
+ }
+ newTweakers.append(tweaker);
+ }
+ // then just append the new tweakers (or moved original ones)
+ newTweakers += tweakers;
+ m_tweakers = newTweakers;
+}
+
+void LaunchProfile::applyJarMods(const QList<LibraryPtr>& jarMods)
+{
+ this->m_jarMods.append(jarMods);
+}
+
+static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle)
+{
+ int retval = -1;
+ for (int i = 0; i < haystack->size(); ++i)
+ {
+ if (haystack->at(i)->rawName().matchName(needle))
+ {
+ // only one is allowed.
+ if (retval != -1)
+ return -1;
+ retval = i;
+ }
+ }
+ return retval;
+}
+
+void LaunchProfile::applyMods(const QList<LibraryPtr>& mods)
+{
+ QList<LibraryPtr> * list = &m_mods;
+ for(auto & mod: mods)
+ {
+ auto modCopy = Library::limitedCopy(mod);
+
+ // find the mod by name.
+ const int index = findLibraryByName(list, mod->rawName());
+ // mod not found? just add it.
+ if (index < 0)
+ {
+ list->append(modCopy);
+ return;
+ }
+
+ auto existingLibrary = list->at(index);
+ // if we are higher it means we should update
+ if (Version(mod->version()) > Version(existingLibrary->version()))
+ {
+ list->replace(index, modCopy);
+ }
+ }
+}
+
+void LaunchProfile::applyLibrary(LibraryPtr library)
+{
+ if(!library->isActive())
+ {
+ return;
+ }
+
+ QList<LibraryPtr> * list = &m_libraries;
+ if(library->isNative())
+ {
+ list = &m_nativeLibraries;
+ }
+
+ auto libraryCopy = Library::limitedCopy(library);
+
+ // find the library by name.
+ const int index = findLibraryByName(list, library->rawName());
+ // library not found? just add it.
+ if (index < 0)
+ {
+ list->append(libraryCopy);
+ return;
+ }
+
+ auto existingLibrary = list->at(index);
+ // if we are higher it means we should update
+ if (Version(library->version()) > Version(existingLibrary->version()))
+ {
+ list->replace(index, libraryCopy);
+ }
+}
+
+const LibraryPtr LaunchProfile::getMainJar() const
+{
+ return m_mainJar;
+}
+
+void LaunchProfile::applyMainJar(LibraryPtr jar)
+{
+ if(jar)
+ {
+ m_mainJar = jar;
+ }
+}
+
+void LaunchProfile::applyProblemSeverity(ProblemSeverity severity)
+{
+ if (m_problemSeverity < severity)
+ {
+ m_problemSeverity = severity;
+ }
+}
+
+const QList<PatchProblem> LaunchProfile::getProblems() const
+{
+ // FIXME: implement something that actually makes sense here
+ return {};
+}
+
+QString LaunchProfile::getMinecraftVersion() const
+{
+ return m_minecraftVersion;
+}
+
+QString LaunchProfile::getAppletClass() const
+{
+ return m_appletClass;
+}
+
+QString LaunchProfile::getMainClass() const
+{
+ return m_mainClass;
+}
+
+const QSet<QString> &LaunchProfile::getTraits() const
+{
+ return m_traits;
+}
+
+const QStringList & LaunchProfile::getTweakers() const
+{
+ return m_tweakers;
+}
+
+bool LaunchProfile::hasTrait(const QString& trait) const
+{
+ return m_traits.contains(trait);
+}
+
+ProblemSeverity LaunchProfile::getProblemSeverity() const
+{
+ return m_problemSeverity;
+}
+
+QString LaunchProfile::getMinecraftVersionType() const
+{
+ return m_minecraftVersionType;
+}
+
+std::shared_ptr<MojangAssetIndexInfo> LaunchProfile::getMinecraftAssets() const
+{
+ if(!m_minecraftAssets)
+ {
+ return std::make_shared<MojangAssetIndexInfo>("legacy");
+ }
+ return m_minecraftAssets;
+}
+
+QString LaunchProfile::getMinecraftArguments() const
+{
+ return m_minecraftArguments;
+}
+
+const QList<LibraryPtr> & LaunchProfile::getJarMods() const
+{
+ return m_jarMods;
+}
+
+const QList<LibraryPtr> & LaunchProfile::getLibraries() const
+{
+ return m_libraries;
+}
+
+const QList<LibraryPtr> & LaunchProfile::getNativeLibraries() const
+{
+ return m_nativeLibraries;
+}
+
+void LaunchProfile::getLibraryFiles(
+ const QString& architecture,
+ QStringList& jars,
+ QStringList& nativeJars,
+ const QString& overridePath,
+ const QString& tempPath
+) const
+{
+ QStringList native32, native64;
+ jars.clear();
+ nativeJars.clear();
+ for (auto lib : getLibraries())
+ {
+ lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
+ }
+ // NOTE: order is important here, add main jar last to the lists
+ if(m_mainJar)
+ {
+ // FIXME: HACK!! jar modding is weird and unsystematic!
+ if(m_jarMods.size())
+ {
+ QDir tempDir(tempPath);
+ jars.append(tempDir.absoluteFilePath("minecraft.jar"));
+ }
+ else
+ {
+ m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
+ }
+ }
+ for (auto lib : getNativeLibraries())
+ {
+ lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
+ }
+ if(architecture == "32")
+ {
+ nativeJars.append(native32);
+ }
+ else if(architecture == "64")
+ {
+ nativeJars.append(native64);
+ }
+}
diff --git a/api/logic/minecraft/LaunchProfile.h b/api/logic/minecraft/LaunchProfile.h
new file mode 100644
index 00000000..e7f5f4af
--- /dev/null
+++ b/api/logic/minecraft/LaunchProfile.h
@@ -0,0 +1,99 @@
+#pragma once
+#include <QString>
+#include "Library.h"
+#include <ProblemProvider.h>
+
+class LaunchProfile: public ProblemProvider
+{
+public:
+ virtual ~LaunchProfile() {};
+
+public: /* application of profile variables from patches */
+ void applyMinecraftVersion(const QString& id);
+ void applyMainClass(const QString& mainClass);
+ void applyAppletClass(const QString& appletClass);
+ void applyMinecraftArguments(const QString& minecraftArguments);
+ void applyMinecraftVersionType(const QString& type);
+ void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets);
+ void applyTraits(const QSet<QString> &traits);
+ void applyTweakers(const QStringList &tweakers);
+ void applyJarMods(const QList<LibraryPtr> &jarMods);
+ void applyMods(const QList<LibraryPtr> &jarMods);
+ void applyLibrary(LibraryPtr library);
+ void applyMainJar(LibraryPtr jar);
+ void applyProblemSeverity(ProblemSeverity severity);
+ /// clear the profile
+ void clear();
+
+public: /* getters for profile variables */
+ QString getMinecraftVersion() const;
+ QString getMainClass() const;
+ QString getAppletClass() const;
+ QString getMinecraftVersionType() const;
+ MojangAssetIndexInfo::Ptr getMinecraftAssets() const;
+ QString getMinecraftArguments() const;
+ const QSet<QString> & getTraits() const;
+ const QStringList & getTweakers() const;
+ const QList<LibraryPtr> & getJarMods() const;
+ const QList<LibraryPtr> & getLibraries() const;
+ const QList<LibraryPtr> & getNativeLibraries() const;
+ const LibraryPtr getMainJar() const;
+ void getLibraryFiles(
+ const QString & architecture,
+ QStringList & jars,
+ QStringList & nativeJars,
+ const QString & overridePath,
+ const QString & tempPath
+ ) const;
+ bool hasTrait(const QString & trait) const;
+ ProblemSeverity getProblemSeverity() const override;
+ const QList<PatchProblem> getProblems() const override;
+
+private:
+ /// the version of Minecraft - jar to use
+ QString m_minecraftVersion;
+
+ /// Release type - "release" or "snapshot"
+ QString m_minecraftVersionType;
+
+ /// Assets type - "legacy" or a version ID
+ MojangAssetIndexInfo::Ptr m_minecraftAssets;
+
+ /**
+ * arguments that should be used for launching minecraft
+ *
+ * ex: "--username ${auth_player_name} --session ${auth_session}
+ * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}"
+ */
+ QString m_minecraftArguments;
+
+ /// A list of all tweaker classes
+ QStringList m_tweakers;
+
+ /// The main class to load first
+ QString m_mainClass;
+
+ /// The applet class, for some very old minecraft releases
+ QString m_appletClass;
+
+ /// the list of libraries
+ QList<LibraryPtr> m_libraries;
+
+ /// the main jar
+ LibraryPtr m_mainJar;
+
+ /// the list of libraries
+ QList<LibraryPtr> m_nativeLibraries;
+
+ /// traits, collected from all the version files (version files can only add)
+ QSet<QString> m_traits;
+
+ /// A list of jar mods. version files can add those.
+ QList<LibraryPtr> m_jarMods;
+
+ /// the list of mods
+ QList<LibraryPtr> m_mods;
+
+ ProblemSeverity m_problemSeverity = ProblemSeverity::None;
+
+};
diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp
index 22e1bd33..cd1afde4 100644
--- a/api/logic/minecraft/Library.cpp
+++ b/api/logic/minecraft/Library.cpp
@@ -104,6 +104,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
}
if (isForge)
{
+ qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url;
out.append(ForgeXzDownload::make(storage, entry));
}
else
@@ -113,11 +114,14 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
auto dl = Net::Download::makeCached(url, entry, options);
dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
+ qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url;
out.append(dl);
}
-
else
+ {
out.append(Net::Download::makeCached(url, entry, options));
+ qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url;
+ }
}
return true;
};
@@ -125,42 +129,56 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
QString raw_storage = storageSuffix(system);
if(m_mojangDownloads)
{
- if(m_mojangDownloads->artifact)
- {
- auto artifact = m_mojangDownloads->artifact;
- add_download(raw_storage, artifact->url, artifact->sha1);
- }
- if(m_nativeClassifiers.contains(system))
+ if(isNative())
{
- auto nativeClassifier = m_nativeClassifiers[system];
- if(nativeClassifier.contains("${arch}"))
+ if(m_nativeClassifiers.contains(system))
{
- auto nat32Classifier = nativeClassifier;
- nat32Classifier.replace("${arch}", "32");
- auto nat64Classifier = nativeClassifier;
- nat64Classifier.replace("${arch}", "64");
- auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier);
- if(nat32info)
+ auto nativeClassifier = m_nativeClassifiers[system];
+ if(nativeClassifier.contains("${arch}"))
{
- auto cooked_storage = raw_storage;
- cooked_storage.replace("${arch}", "32");
- add_download(cooked_storage, nat32info->url, nat32info->sha1);
+ auto nat32Classifier = nativeClassifier;
+ nat32Classifier.replace("${arch}", "32");
+ auto nat64Classifier = nativeClassifier;
+ nat64Classifier.replace("${arch}", "64");
+ auto nat32info = m_mojangDownloads->getDownloadInfo(nat32Classifier);
+ if(nat32info)
+ {
+ auto cooked_storage = raw_storage;
+ cooked_storage.replace("${arch}", "32");
+ add_download(cooked_storage, nat32info->url, nat32info->sha1);
+ }
+ auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier);
+ if(nat64info)
+ {
+ auto cooked_storage = raw_storage;
+ cooked_storage.replace("${arch}", "64");
+ add_download(cooked_storage, nat64info->url, nat64info->sha1);
+ }
}
- auto nat64info = m_mojangDownloads->getDownloadInfo(nat64Classifier);
- if(nat64info)
+ else
{
- auto cooked_storage = raw_storage;
- cooked_storage.replace("${arch}", "64");
- add_download(cooked_storage, nat64info->url, nat64info->sha1);
+ auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier);
+ if(info)
+ {
+ add_download(raw_storage, info->url, info->sha1);
+ }
}
}
else
{
- auto info = m_mojangDownloads->getDownloadInfo(nativeClassifier);
- if(info)
- {
- add_download(raw_storage, info->url, info->sha1);
- }
+ qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS";
+ }
+ }
+ else
+ {
+ if(m_mojangDownloads->artifact)
+ {
+ auto artifact = m_mojangDownloads->artifact;
+ add_download(raw_storage, artifact->url, artifact->sha1);
+ }
+ else
+ {
+ qDebug() << "Ignoring java library" << m_name << "because it has no artifact";
}
}
}
diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp
index 3f4828c9..1aac951b 100644
--- a/api/logic/minecraft/Library_test.cpp
+++ b/api/logic/minecraft/Library_test.cpp
@@ -2,7 +2,7 @@
#include "TestUtil.h"
#include "minecraft/MojangVersionFormat.h"
-#include "minecraft/onesix/OneSixVersionFormat.h"
+#include "minecraft/OneSixVersionFormat.h"
#include "minecraft/Library.h"
#include "net/HttpMetaCache.h"
#include "FileSystem.h"
@@ -75,6 +75,7 @@ slots:
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString("data"));
QCOMPARE(downloads.size(), 0);
+ qDebug() << failedFiles;
QCOMPARE(failedFiles.size(), 0);
QStringList jar, native, native32, native64;
@@ -240,10 +241,9 @@ slots:
QCOMPARE(native64, {});
QStringList failedFiles;
auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString());
- QCOMPARE(dls.size(), 2);
+ QCOMPARE(dls.size(), 1);
QCOMPARE(failedFiles, {});
- QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209.jar"));
- QCOMPARE(dls[1]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
+ QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar"));
}
void test_onenine_native_arch()
{
diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp
index cb080bfe..0fffc99f 100644
--- a/api/logic/minecraft/MinecraftInstance.cpp
+++ b/api/logic/minecraft/MinecraftInstance.cpp
@@ -17,15 +17,24 @@
#include "launch/steps/PreLaunchCommand.h"
#include "launch/steps/TextPrint.h"
#include "minecraft/launch/LauncherPartLaunch.h"
+#include "minecraft/launch/DirectJavaLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h"
#include "minecraft/launch/ClaimAccount.h"
#include "java/launch/CheckJava.h"
-#include <meta/Index.h>
-#include <meta/VersionList.h>
+#include "java/JavaUtils.h"
+#include "meta/Index.h"
+#include "meta/VersionList.h"
-#include <icons/IIconList.h>
+#include "ModList.h"
+#include "WorldList.h"
+
+#include "icons/IIconList.h"
#include <QCoreApplication>
+#include "ComponentList.h"
+#include "AssetsUtils.h"
+#include "MinecraftUpdate.h"
+#include "MinecraftLoadAndCheck.h"
#define IBUS "@im=ibus"
@@ -87,6 +96,53 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
// Minecraft launch method
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
+
+ // DEPRECATED: Read what versions the user configuration thinks should be used
+ m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
+ m_settings->registerSetting("LWJGLVersion", "");
+ m_settings->registerSetting("ForgeVersion", "");
+ m_settings->registerSetting("LiteloaderVersion", "");
+
+ m_components.reset(new ComponentList(this));
+ m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString());
+ auto setting = m_settings->getSetting("LWJGLVersion");
+ m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString());
+ m_components->setOldConfigVersion("net.minecraftforge", m_settings->get("ForgeVersion").toString());
+ m_components->setOldConfigVersion("com.mumfrey.liteloader", m_settings->get("LiteloaderVersion").toString());
+}
+
+void MinecraftInstance::init()
+{
+}
+
+void MinecraftInstance::saveNow()
+{
+ m_components->saveNow();
+}
+
+QString MinecraftInstance::typeName() const
+{
+ return "Minecraft";
+}
+
+std::shared_ptr<ComponentList> MinecraftInstance::getComponentList() const
+{
+ return m_components;
+}
+
+QSet<QString> MinecraftInstance::traits() const
+{
+ auto components = getComponentList();
+ if (!components)
+ {
+ return {"version-incomplete"};
+ }
+ auto profile = components->getProfile();
+ if (!profile)
+ {
+ return {"version-incomplete"};
+ }
+ return profile->getTraits();
}
QString MinecraftInstance::minecraftRoot() const
@@ -94,10 +150,10 @@ QString MinecraftInstance::minecraftRoot() const
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
- if (dotMCDir.exists() && !mcDir.exists())
- return dotMCDir.filePath();
- else
+ if (mcDir.exists() && !dotMCDir.exists())
return mcDir.filePath();
+ else
+ return dotMCDir.filePath();
}
QString MinecraftInstance::binRoot() const
@@ -105,9 +161,110 @@ QString MinecraftInstance::binRoot() const
return FS::PathCombine(minecraftRoot(), "bin");
}
-std::shared_ptr< BaseVersionList > MinecraftInstance::versionList() const
+QString MinecraftInstance::getNativePath() const
+{
+ QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/"));
+ return natives_dir.absolutePath();
+}
+
+QString MinecraftInstance::getLocalLibraryPath() const
+{
+ QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/"));
+ return libraries_dir.absolutePath();
+}
+
+QString MinecraftInstance::loaderModsDir() const
+{
+ return FS::PathCombine(minecraftRoot(), "mods");
+}
+
+QString MinecraftInstance::coreModsDir() const
+{
+ return FS::PathCombine(minecraftRoot(), "coremods");
+}
+
+QString MinecraftInstance::resourcePacksDir() const
+{
+ return FS::PathCombine(minecraftRoot(), "resourcepacks");
+}
+
+QString MinecraftInstance::texturePacksDir() const
+{
+ return FS::PathCombine(minecraftRoot(), "texturepacks");
+}
+
+QString MinecraftInstance::instanceConfigFolder() const
+{
+ return FS::PathCombine(minecraftRoot(), "config");
+}
+
+QString MinecraftInstance::jarModsDir() const
+{
+ return FS::PathCombine(instanceRoot(), "jarmods");
+}
+
+QString MinecraftInstance::libDir() const
+{
+ return FS::PathCombine(minecraftRoot(), "lib");
+}
+
+QString MinecraftInstance::worldDir() const
+{
+ return FS::PathCombine(minecraftRoot(), "saves");
+}
+
+QDir MinecraftInstance::librariesPath() const
+{
+ return QDir::current().absoluteFilePath("libraries");
+}
+
+QDir MinecraftInstance::jarmodsPath() const
+{
+ return QDir(jarModsDir());
+}
+
+QDir MinecraftInstance::versionsPath() const
+{
+ return QDir::current().absoluteFilePath("versions");
+}
+
+QStringList MinecraftInstance::getClassPath() const
+{
+ QStringList jars, nativeJars;
+ auto javaArchitecture = settings()->get("JavaArchitecture").toString();
+ auto profile = m_components->getProfile();
+ profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
+ return jars;
+}
+
+QString MinecraftInstance::getMainClass() const
+{
+ auto profile = m_components->getProfile();
+ return profile->getMainClass();
+}
+
+QStringList MinecraftInstance::getNativeJars() const
{
- return ENV.metadataIndex()->get("net.minecraft");
+ QStringList jars, nativeJars;
+ auto javaArchitecture = settings()->get("JavaArchitecture").toString();
+ auto profile = m_components->getProfile();
+ profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
+ return nativeJars;
+}
+
+QStringList MinecraftInstance::extraArguments() const
+{
+ auto list = BaseInstance::extraArguments();
+ auto version = getComponentList();
+ if (!version)
+ return list;
+ auto jarMods = getJarMods();
+ if (!jarMods.isEmpty())
+ {
+ list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true",
+ "-Dfml.ignorePatchDiscrepancies=true"});
+ }
+ return list;
}
QStringList MinecraftInstance::javaArguments() const
@@ -122,6 +279,15 @@ QStringList MinecraftInstance::javaArguments() const
args << "-Xdock:icon=icon.png";
args << QString("-Xdock:name=\"%1\"").arg(windowTitle());
#endif
+ auto traits_ = traits();
+ // HACK: fix issues on macOS with 1.13 snapshots
+ // NOTE: Oracle Java option. if there are alternate jvm implementations, this would be the place to customize this for them
+#ifdef Q_OS_MAC
+ if(traits_.contains("FirstThreadOnMacOS"))
+ {
+ args << QString("-XstartOnFirstThread");
+ }
+#endif
// HACK: Stupid hack for Intel drivers. See: https://mojang.atlassian.net/browse/MCL-767
#ifdef Q_OS_WIN32
@@ -129,8 +295,18 @@ QStringList MinecraftInstance::javaArguments() const
"minecraft.exe.heapdump");
#endif
- args << QString("-Xms%1m").arg(settings()->get("MinMemAlloc").toInt());
- args << QString("-Xmx%1m").arg(settings()->get("MaxMemAlloc").toInt());
+ int min = settings()->get("MinMemAlloc").toInt();
+ int max = settings()->get("MaxMemAlloc").toInt();
+ if(min < max)
+ {
+ args << QString("-Xms%1m").arg(min);
+ args << QString("-Xmx%1m").arg(max);
+ }
+ else
+ {
+ args << QString("-Xms%1m").arg(max);
+ args << QString("-Xmx%1m").arg(min);
+ }
// No PermGen in newer java.
JavaVersion javaVersion = getJavaVersion();
@@ -160,100 +336,282 @@ QMap<QString, QString> MinecraftInstance::getVariables() const
return out;
}
-static QString processLD_LIBRARY_PATH(const QString & LD_LIBRARY_PATH)
+QProcessEnvironment MinecraftInstance::createEnvironment()
+{
+ // prepare the process environment
+ QProcessEnvironment env = CleanEnviroment();
+
+ // export some infos
+ auto variables = getVariables();
+ for (auto it = variables.begin(); it != variables.end(); ++it)
+ {
+ env.insert(it.key(), it.value());
+ }
+ return env;
+}
+
+static QString replaceTokensIn(QString text, QMap<QString, QString> with)
{
- QDir mmcBin(QCoreApplication::applicationDirPath());
- auto items = LD_LIBRARY_PATH.split(':');
- QStringList final;
- for(auto & item: items)
+ QString result;
+ QRegExp token_regexp("\\$\\{(.+)\\}");
+ token_regexp.setMinimal(true);
+ QStringList list;
+ int tail = 0;
+ int head = 0;
+ while ((head = token_regexp.indexIn(text, head)) != -1)
{
- QDir test(item);
- if(test == mmcBin)
+ result.append(text.mid(tail, head - tail));
+ QString key = token_regexp.cap(1);
+ auto iter = with.find(key);
+ if (iter != with.end())
{
- qDebug() << "Env:LD_LIBRARY_PATH ignoring path" << item;
- continue;
+ result.append(*iter);
}
- final.append(item);
+ head += token_regexp.matchedLength();
+ tail = head;
}
- return final.join(':');
+ result.append(text.mid(tail));
+ return result;
}
-QProcessEnvironment MinecraftInstance::createEnvironment()
+QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const
{
- // prepare the process environment
- QProcessEnvironment rawenv = QProcessEnvironment::systemEnvironment();
- QProcessEnvironment env;
-
- QStringList ignored =
- {
- "JAVA_ARGS",
- "CLASSPATH",
- "CONFIGPATH",
- "JAVA_HOME",
- "JRE_HOME",
- "_JAVA_OPTIONS",
- "JAVA_OPTIONS",
- "JAVA_TOOL_OPTIONS"
- };
- for(auto key: rawenv.keys())
+ auto profile = m_components->getProfile();
+ QString args_pattern = profile->getMinecraftArguments();
+ for (auto tweaker : profile->getTweakers())
+ {
+ args_pattern += " --tweakClass " + tweaker;
+ }
+
+ QMap<QString, QString> token_mapping;
+ // yggdrasil!
+ if(session)
+ {
+ token_mapping["auth_username"] = session->username;
+ token_mapping["auth_session"] = session->session;
+ token_mapping["auth_access_token"] = session->access_token;
+ token_mapping["auth_player_name"] = session->player_name;
+ token_mapping["auth_uuid"] = session->uuid;
+ token_mapping["user_properties"] = session->serializeUserProperties();
+ token_mapping["user_type"] = session->user_type;
+ }
+
+ // blatant self-promotion.
+ token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5";
+
+ token_mapping["version_type"] = profile->getMinecraftVersionType();
+
+ QString absRootDir = QDir(minecraftRoot()).absolutePath();
+ token_mapping["game_directory"] = absRootDir;
+ QString absAssetsDir = QDir("assets/").absolutePath();
+ auto assets = profile->getMinecraftAssets();
+ // FIXME: this is wrong and should be run as an async task
+ token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath();
+
+ // 1.7.3+ assets tokens
+ token_mapping["assets_root"] = absAssetsDir;
+ token_mapping["assets_index_name"] = assets->id;
+
+ QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
+ for (int i = 0; i < parts.length(); i++)
{
- auto value = rawenv.value(key);
- // filter out dangerous java crap
- if(ignored.contains(key))
+ parts[i] = replaceTokensIn(parts[i], token_mapping);
+ }
+ return parts;
+}
+
+QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
+{
+ QString launchScript;
+
+ if (!m_components)
+ return QString();
+ auto profile = m_components->getProfile();
+ if(!profile)
+ return QString();
+
+ auto mainClass = getMainClass();
+ if (!mainClass.isEmpty())
+ {
+ launchScript += "mainClass " + mainClass + "\n";
+ }
+ auto appletClass = profile->getAppletClass();
+ if (!appletClass.isEmpty())
+ {
+ launchScript += "appletClass " + appletClass + "\n";
+ }
+
+ // generic minecraft params
+ for (auto param : processMinecraftArgs(session))
+ {
+ launchScript += "param " + param + "\n";
+ }
+
+ // window size, title and state, legacy
+ {
+ 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";
+ }
+
+ // legacy auth
+ if(session)
+ {
+ launchScript += "userName " + session->player_name + "\n";
+ launchScript += "sessionId " + session->session + "\n";
+ }
+
+ // libraries and class path.
+ {
+ QStringList jars, nativeJars;
+ auto javaArchitecture = settings()->get("JavaArchitecture").toString();
+ profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
+ for(auto file: jars)
{
- qDebug() << "Env: ignoring" << key << value;
- continue;
+ launchScript += "cp " + file + "\n";
}
- // filter MultiMC-related things
- if(key.startsWith("QT_"))
+ for(auto file: nativeJars)
{
- qDebug() << "Env: ignoring" << key << value;
- continue;
+ launchScript += "ext " + file + "\n";
}
-#ifdef Q_OS_LINUX
- // Do not pass LD_* variables to java. They were intended for MultiMC
- if(key.startsWith("LD_"))
+ launchScript += "natives " + getNativePath() + "\n";
+ }
+
+ for (auto trait : profile->getTraits())
+ {
+ launchScript += "traits " + trait + "\n";
+ }
+ launchScript += "launcher onesix\n";
+ // qDebug() << "Generated launch script:" << launchScript;
+ return launchScript;
+}
+
+QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
+{
+ QStringList out;
+ out << "Main Class:" << " " + getMainClass() << "";
+ out << "Native path:" << " " + getNativePath() << "";
+
+ auto profile = m_components->getProfile();
+
+ auto alltraits = traits();
+ if(alltraits.size())
+ {
+ out << "Traits:";
+ for (auto trait : alltraits)
{
- qDebug() << "Env: ignoring" << key << value;
- continue;
+ out << "traits " + trait;
}
- // Strip IBus
- // IBus is a Linux IME framework. For some reason, it breaks MC?
- if (key == "XMODIFIERS" && value.contains(IBUS))
+ out << "";
+ }
+
+ // libraries and class path.
+ {
+ out << "Libraries:";
+ QStringList jars, nativeJars;
+ auto javaArchitecture = settings()->get("JavaArchitecture").toString();
+ profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
+ auto printLibFile = [&](const QString & path)
+ {
+ QFileInfo info(path);
+ if(info.exists())
+ {
+ out << " " + path;
+ }
+ else
+ {
+ out << " " + path + " (missing)";
+ }
+ };
+ for(auto file: jars)
{
- QString save = value;
- value.replace(IBUS, "");
- qDebug() << "Env: stripped" << IBUS << "from" << save << ":" << value;
+ printLibFile(file);
}
- if(key == "GAME_PRELOAD")
+ out << "";
+ out << "Native libraries:";
+ for(auto file: nativeJars)
{
- env.insert("LD_PRELOAD", value);
- continue;
+ printLibFile(file);
}
- if(key == "GAME_LIBRARY_PATH")
+ out << "";
+ }
+
+ if(loaderModList()->size())
+ {
+ out << "Mods:";
+ for(auto & mod: loaderModList()->allMods())
{
- env.insert("LD_LIBRARY_PATH", processLD_LIBRARY_PATH(value));
- continue;
+ if(!mod.enabled())
+ continue;
+ if(mod.type() == Mod::MOD_FOLDER)
+ continue;
+ // TODO: proper implementation would need to descend into folders.
+
+ out << " " + mod.filename().completeBaseName();
}
-#endif
- qDebug() << "Env: " << key << value;
- env.insert(key, value);
+ out << "";
}
-#ifdef Q_OS_LINUX
- // HACK: Workaround for QTBUG42500
- if(!env.contains("LD_LIBRARY_PATH"))
+
+ if(coreModList()->size())
{
- env.insert("LD_LIBRARY_PATH", "");
+ out << "Core Mods:";
+ for(auto & coremod: coreModList()->allMods())
+ {
+ if(!coremod.enabled())
+ continue;
+ if(coremod.type() == Mod::MOD_FOLDER)
+ continue;
+ // TODO: proper implementation would need to descend into folders.
+
+ out << " " + coremod.filename().completeBaseName();
+ }
+ out << "";
}
-#endif
- // export some infos
- auto variables = getVariables();
- for (auto it = variables.begin(); it != variables.end(); ++it)
+ auto & jarMods = profile->getJarMods();
+ if(jarMods.size())
{
- env.insert(it.key(), it.value());
+ out << "Jar Mods:";
+ for(auto & jarmod: jarMods)
+ {
+ auto displayname = jarmod->displayName(currentSystem);
+ auto realname = jarmod->filename(currentSystem);
+ if(displayname != realname)
+ {
+ out << " " + displayname + " (" + realname + ")";
+ }
+ else
+ {
+ out << " " + realname;
+ }
+ }
+ out << "";
}
- return env;
+
+ auto params = processMinecraftArgs(nullptr);
+ out << "Params:";
+ out << " " + params.join(' ');
+ out << "";
+
+ QString windowParams;
+ if (settings()->get("LaunchMaximized").toBool())
+ {
+ out << "Window size: max (if available)";
+ }
+ else
+ {
+ auto width = settings()->get("MinecraftWinWidth").toInt();
+ auto height = settings()->get("MinecraftWinHeight").toInt();
+ out << "Window size: " + QString::number(width) + " x " + QString::number(height);
+ }
+ out << "";
+ return out;
}
QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSessionPtr session)
@@ -278,7 +636,6 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
addToFilter(sessionRef.access_token, tr("<ACCESS TOKEN>"));
addToFilter(sessionRef.client_token, tr("<CLIENT TOKEN>"));
addToFilter(sessionRef.uuid, tr("<PROFILE ID>"));
- addToFilter(sessionRef.player_name, tr("<PROFILE NAME>"));
auto i = sessionRef.u.properties.begin();
while (i != sessionRef.u.properties.end())
@@ -384,7 +741,7 @@ QString MinecraftInstance::getStatusbarDescription()
}
QString description;
- description.append(tr("Minecraft %1 (%2)").arg(intendedVersionId()).arg(typeName()));
+ description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
if(totalTimePlayed() > 0)
{
description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
@@ -396,6 +753,22 @@ QString MinecraftInstance::getStatusbarDescription()
return description;
}
+shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
+{
+ switch (mode)
+ {
+ case Net::Mode::Offline:
+ {
+ return shared_qobject_ptr<Task>(new MinecraftLoadAndCheck(this));
+ }
+ case Net::Mode::Online:
+ {
+ return shared_qobject_ptr<Task>(new OneSixUpdate(this));
+ }
+ }
+ return nullptr;
+}
+
std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
{
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
@@ -415,7 +788,7 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
}
// check launch method
- QStringList validMethods = validLaunchMethods();
+ QStringList validMethods = {"LauncherPart", "DirectJava"};
QString method = launchMethod();
if(!validMethods.contains(method))
{
@@ -435,11 +808,14 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
if(session->status != AuthSession::PlayableOffline)
{
process->appendStep(std::make_shared<ClaimAccount>(pptr, session));
- process->appendStep(std::make_shared<Update>(pptr));
+ process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online));
+ }
+ else
+ {
+ process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline));
}
// if there are any jar mods
- if(getJarMods().size())
{
auto step = std::make_shared<ModMinecraftJar>(pptr);
process->appendStep(step);
@@ -465,8 +841,21 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
{
// actually launch the game
- auto step = createMainLaunchStep(pptr, session);
- process->appendStep(step);
+ auto method = launchMethod();
+ if(method == "LauncherPart")
+ {
+ auto step = std::make_shared<LauncherPartLaunch>(pptr);
+ step->setWorkingDirectory(minecraftRoot());
+ step->setAuthSession(session);
+ process->appendStep(step);
+ }
+ else if (method == "DirectJava")
+ {
+ auto step = std::make_shared<DirectJavaLaunch>(pptr);
+ step->setWorkingDirectory(minecraftRoot());
+ step->setAuthSession(session);
+ process->appendStep(step);
+ }
}
// run post-exit command if that's needed
@@ -495,5 +884,68 @@ JavaVersion MinecraftInstance::getJavaVersion() const
return JavaVersion(settings()->get("JavaVersion").toString());
}
+std::shared_ptr<ModList> MinecraftInstance::loaderModList() const
+{
+ if (!m_loader_mod_list)
+ {
+ m_loader_mod_list.reset(new ModList(loaderModsDir()));
+ }
+ m_loader_mod_list->update();
+ return m_loader_mod_list;
+}
+
+std::shared_ptr<ModList> MinecraftInstance::coreModList() const
+{
+ if (!m_core_mod_list)
+ {
+ m_core_mod_list.reset(new ModList(coreModsDir()));
+ }
+ m_core_mod_list->update();
+ return m_core_mod_list;
+}
+
+std::shared_ptr<ModList> MinecraftInstance::resourcePackList() const
+{
+ if (!m_resource_pack_list)
+ {
+ m_resource_pack_list.reset(new ModList(resourcePacksDir()));
+ }
+ m_resource_pack_list->update();
+ return m_resource_pack_list;
+}
+
+std::shared_ptr<ModList> MinecraftInstance::texturePackList() const
+{
+ if (!m_texture_pack_list)
+ {
+ m_texture_pack_list.reset(new ModList(texturePacksDir()));
+ }
+ m_texture_pack_list->update();
+ return m_texture_pack_list;
+}
+
+std::shared_ptr<WorldList> MinecraftInstance::worldList() const
+{
+ if (!m_world_list)
+ {
+ m_world_list.reset(new WorldList(worldDir()));
+ }
+ return m_world_list;
+}
+
+QList< Mod > MinecraftInstance::getJarMods() const
+{
+ auto profile = m_components->getProfile();
+ QList<Mod> mods;
+ for (auto jarmod : profile->getJarMods())
+ {
+ QStringList jar, temp1, temp2, temp3;
+ jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
+ // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
+ mods.push_back(Mod(QFileInfo(jar[0])));
+ }
+ return mods;
+}
+
#include "MinecraftInstance.moc"
diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h
index 7f967ce0..446a39d5 100644
--- a/api/logic/minecraft/MinecraftInstance.h
+++ b/api/logic/minecraft/MinecraftInstance.h
@@ -3,88 +3,122 @@
#include <java/JavaVersion.h>
#include "minecraft/Mod.h"
#include <QProcess>
-
+#include <QDir>
#include "multimc_logic_export.h"
class ModList;
class WorldList;
class LaunchStep;
+class ComponentList;
class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
{
+ Q_OBJECT
public:
MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
virtual ~MinecraftInstance() {};
+ virtual void init() override;
+ virtual void saveNow();
- /// Path to the instance's minecraft directory.
- QString minecraftRoot() const;
-
- /// Path to the instance's minecraft/bin directory.
- QString binRoot() const;
+ // FIXME: remove
+ QString typeName() const override;
+ // FIXME: remove
+ QSet<QString> traits() const override;
- ////// Mod Lists //////
- virtual std::shared_ptr<ModList> resourcePackList() const
- {
- return nullptr;
- }
- virtual std::shared_ptr<ModList> texturePackList() const
- {
- return nullptr;
- }
- virtual std::shared_ptr<WorldList> worldList() const
+ bool canEdit() const override
{
- return nullptr;
+ return true;
}
- /// get all jar mods applicable to this instance's jar
- virtual QList<Mod> getJarMods() const
+
+ bool canExport() const override
{
- return QList<Mod>();
+ return true;
}
- virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
- virtual QString createLaunchScript(AuthSessionPtr session) = 0;
-
- //FIXME: nuke?
- virtual std::shared_ptr<BaseVersionList> versionList() const override;
+ ////// Directories and files //////
+ QString jarModsDir() const;
+ QString resourcePacksDir() const;
+ QString texturePacksDir() const;
+ QString loaderModsDir() const;
+ QString coreModsDir() const;
+ QString libDir() const;
+ QString worldDir() const;
+ QDir jarmodsPath() const;
+ QDir librariesPath() const;
+ QDir versionsPath() const;
+ QString instanceConfigFolder() const override;
+ QString minecraftRoot() const; // Path to the instance's minecraft directory.
+ QString binRoot() const; // Path to the instance's minecraft bin directory.
+ QString getNativePath() const; // where to put the natives during/before launch
+ QString getLocalLibraryPath() const; // where the instance-local libraries should be
+
+
+ ////// Profile management //////
+ std::shared_ptr<ComponentList> getComponentList() const;
+ ////// Mod Lists //////
+ std::shared_ptr<ModList> loaderModList() const;
+ std::shared_ptr<ModList> coreModList() const;
+ std::shared_ptr<ModList> resourcePackList() const;
+ std::shared_ptr<ModList> texturePackList() const;
+ std::shared_ptr<WorldList> worldList() const;
+
+
+ ////// Launch stuff //////
+ shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
+ std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
+ QStringList extraArguments() const override;
+ QStringList verboseDescription(AuthSessionPtr session) override;
+ QList<Mod> getJarMods() const;
+ QString createLaunchScript(AuthSessionPtr session);
/// get arguments passed to java
QStringList javaArguments() const;
/// get variables for launch command variable substitution/environment
- virtual QMap<QString, QString> getVariables() const override;
+ QMap<QString, QString> getVariables() const override;
/// create an environment for launching processes
- virtual QProcessEnvironment createEnvironment() override;
+ QProcessEnvironment createEnvironment() override;
/// guess log level from a line of minecraft log
- virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override;
-
- virtual IPathMatcher::Ptr getLogFileMatcher() override;
-
- virtual QString getLogFileRoot() override;
+ MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) override;
- virtual QString getStatusbarDescription() override;
+ IPathMatcher::Ptr getLogFileMatcher() override;
- virtual QStringList getClassPath() const = 0;
- virtual QStringList getNativeJars() const = 0;
+ QString getLogFileRoot() override;
- virtual QString getMainClass() const = 0;
+ QString getStatusbarDescription() override;
- virtual QString getNativePath() const = 0;
+ // FIXME: remove
+ virtual QStringList getClassPath() const;
+ // FIXME: remove
+ virtual QStringList getNativeJars() const;
+ // FIXME: remove
+ virtual QString getMainClass() const;
- virtual QString getLocalLibraryPath() const = 0;
-
- virtual QStringList processMinecraftArgs(AuthSessionPtr account) const = 0;
+ // FIXME: remove
+ virtual QStringList processMinecraftArgs(AuthSessionPtr account) const;
virtual JavaVersion getJavaVersion() const;
+signals:
+ void versionReloaded();
+
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
- virtual QStringList validLaunchMethods() = 0;
- virtual QString launchMethod();
- virtual std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) = 0;
+ QStringList validLaunchMethods();
+ QString launchMethod();
+
private:
QString prettifyTimeDuration(int64_t duration);
+
+protected: // data
+ std::shared_ptr<ComponentList> m_components;
+ mutable std::shared_ptr<ModList> m_loader_mod_list;
+ mutable std::shared_ptr<ModList> m_core_mod_list;
+ mutable std::shared_ptr<ModList> m_resource_pack_list;
+ mutable std::shared_ptr<ModList> m_texture_pack_list;
+ mutable std::shared_ptr<WorldList> m_world_list;
};
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;
diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp
new file mode 100644
index 00000000..c64bbddf
--- /dev/null
+++ b/api/logic/minecraft/MinecraftLoadAndCheck.cpp
@@ -0,0 +1,45 @@
+#include "MinecraftLoadAndCheck.h"
+#include "MinecraftInstance.h"
+#include "ComponentList.h"
+
+MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
+{
+}
+
+void MinecraftLoadAndCheck::executeTask()
+{
+ // add offline metadata load task
+ auto components = m_inst->getComponentList();
+ components->reload(Net::Mode::Offline);
+ m_task = components->getCurrentTask();
+
+ if(!m_task)
+ {
+ emitSucceeded();
+ return;
+ }
+ connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded);
+ connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed);
+ connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress);
+ connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus);
+}
+
+void MinecraftLoadAndCheck::subtaskSucceeded()
+{
+ if(isFinished())
+ {
+ qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
+ return;
+ }
+ emitSucceeded();
+}
+
+void MinecraftLoadAndCheck::subtaskFailed(QString error)
+{
+ if(isFinished())
+ {
+ qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
+ return;
+ }
+ emitFailed(error);
+}
diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h
new file mode 100644
index 00000000..00515f2d
--- /dev/null
+++ b/api/logic/minecraft/MinecraftLoadAndCheck.h
@@ -0,0 +1,47 @@
+/* Copyright 2013-2018 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 <QObject>
+#include <QList>
+#include <QUrl>
+
+#include "tasks/Task.h"
+#include <quazip.h>
+
+#include "QObjectPtr.h"
+
+class MinecraftVersion;
+class MinecraftInstance;
+
+class MinecraftLoadAndCheck : public Task
+{
+ Q_OBJECT
+public:
+ explicit MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent = 0);
+ void executeTask() override;
+
+private slots:
+ void subtaskSucceeded();
+ void subtaskFailed(QString error);
+
+private:
+ MinecraftInstance *m_inst = nullptr;
+ shared_qobject_ptr<Task> m_task;
+ QString m_preFailure;
+ QString m_fail_reason;
+};
+
diff --git a/api/logic/minecraft/MinecraftProfile.cpp b/api/logic/minecraft/MinecraftProfile.cpp
deleted file mode 100644
index 4c1ab818..00000000
--- a/api/logic/minecraft/MinecraftProfile.cpp
+++ /dev/null
@@ -1,681 +0,0 @@
-/* Copyright 2013-2017 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 <QCryptographicHash>
-#include <Version.h>
-#include <QDir>
-#include <QJsonDocument>
-#include <QJsonArray>
-#include <QDebug>
-
-#include "minecraft/MinecraftProfile.h"
-#include "ProfileUtils.h"
-#include "ProfileStrategy.h"
-#include "Exception.h"
-
-MinecraftProfile::MinecraftProfile(ProfileStrategy *strategy)
- : QAbstractListModel()
-{
- setStrategy(strategy);
- clear();
-}
-
-MinecraftProfile::~MinecraftProfile()
-{
- if(m_strategy)
- {
- delete m_strategy;
- }
-}
-
-void MinecraftProfile::setStrategy(ProfileStrategy* strategy)
-{
- Q_ASSERT(strategy != nullptr);
-
- if(m_strategy != nullptr)
- {
- delete m_strategy;
- m_strategy = nullptr;
- }
- m_strategy = strategy;
- m_strategy->profile = this;
-}
-
-ProfileStrategy* MinecraftProfile::strategy()
-{
- return m_strategy;
-}
-
-void MinecraftProfile::reload()
-{
- beginResetModel();
- m_strategy->load();
- reapplyPatches();
- endResetModel();
-}
-
-void MinecraftProfile::clear()
-{
- m_minecraftVersion.clear();
- m_minecraftVersionType.clear();
- m_minecraftAssets.reset();
- m_minecraftArguments.clear();
- m_tweakers.clear();
- m_mainClass.clear();
- m_appletClass.clear();
- m_libraries.clear();
- m_traits.clear();
- m_jarMods.clear();
- m_mainJar.reset();
- m_problemSeverity = ProblemSeverity::None;
-}
-
-void MinecraftProfile::clearPatches()
-{
- beginResetModel();
- m_patches.clear();
- endResetModel();
-}
-
-void MinecraftProfile::appendPatch(ProfilePatchPtr patch)
-{
- int index = m_patches.size();
- beginInsertRows(QModelIndex(), index, index);
- m_patches.append(patch);
- endInsertRows();
-}
-
-bool MinecraftProfile::remove(const int index)
-{
- auto patch = versionPatch(index);
- if (!patch->isRemovable())
- {
- qDebug() << "Patch" << patch->getID() << "is non-removable";
- return false;
- }
-
- if(!m_strategy->removePatch(patch))
- {
- qCritical() << "Patch" << patch->getID() << "could not be removed";
- return false;
- }
-
- beginRemoveRows(QModelIndex(), index, index);
- m_patches.removeAt(index);
- endRemoveRows();
- reapplyPatches();
- saveCurrentOrder();
- return true;
-}
-
-bool MinecraftProfile::remove(const QString id)
-{
- int i = 0;
- for (auto patch : m_patches)
- {
- if (patch->getID() == id)
- {
- return remove(i);
- }
- i++;
- }
- return false;
-}
-
-bool MinecraftProfile::customize(int index)
-{
- auto patch = versionPatch(index);
- if (!patch->isCustomizable())
- {
- qDebug() << "Patch" << patch->getID() << "is not customizable";
- return false;
- }
- if(!m_strategy->customizePatch(patch))
- {
- qCritical() << "Patch" << patch->getID() << "could not be customized";
- return false;
- }
- reapplyPatches();
- saveCurrentOrder();
- // FIXME: maybe later in unstable
- // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
- return true;
-}
-
-bool MinecraftProfile::revertToBase(int index)
-{
- auto patch = versionPatch(index);
- if (!patch->isRevertible())
- {
- qDebug() << "Patch" << patch->getID() << "is not revertible";
- return false;
- }
- if(!m_strategy->revertPatch(patch))
- {
- qCritical() << "Patch" << patch->getID() << "could not be reverted";
- return false;
- }
- reapplyPatches();
- saveCurrentOrder();
- // FIXME: maybe later in unstable
- // emit dataChanged(createIndex(index, 0), createIndex(index, columnCount(QModelIndex()) - 1));
- return true;
-}
-
-ProfilePatchPtr MinecraftProfile::versionPatch(const QString &id)
-{
- for (auto patch : m_patches)
- {
- if (patch->getID() == id)
- {
- return patch;
- }
- }
- return nullptr;
-}
-
-ProfilePatchPtr MinecraftProfile::versionPatch(int index)
-{
- if(index < 0 || index >= m_patches.size())
- return nullptr;
- return m_patches[index];
-}
-
-bool MinecraftProfile::isVanilla()
-{
- for(auto patchptr: m_patches)
- {
- if(patchptr->isCustom())
- return false;
- }
- return true;
-}
-
-bool MinecraftProfile::revertToVanilla()
-{
- // remove patches, if present
- auto VersionPatchesCopy = m_patches;
- for(auto & it: VersionPatchesCopy)
- {
- if (!it->isCustom())
- {
- continue;
- }
- if(it->isRevertible() || it->isRemovable())
- {
- if(!remove(it->getID()))
- {
- qWarning() << "Couldn't remove" << it->getID() << "from profile!";
- reapplyPatches();
- saveCurrentOrder();
- return false;
- }
- }
- }
- reapplyPatches();
- saveCurrentOrder();
- return true;
-}
-
-QVariant MinecraftProfile::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
- int column = index.column();
-
- if (row < 0 || row >= m_patches.size())
- return QVariant();
-
- auto patch = m_patches.at(row);
-
- if (role == Qt::DisplayRole)
- {
- switch (column)
- {
- case 0:
- return m_patches.at(row)->getName();
- case 1:
- {
- if(patch->isCustom())
- {
- return QString("%1 (Custom)").arg(patch->getVersion());
- }
- else
- {
- return patch->getVersion();
- }
- }
- default:
- return QVariant();
- }
- }
- if(role == Qt::DecorationRole)
- {
- switch(column)
- {
- case 0:
- {
- auto severity = patch->getProblemSeverity();
- switch (severity)
- {
- case ProblemSeverity::Warning:
- return "warning";
- case ProblemSeverity::Error:
- return "error";
- default:
- return QVariant();
- }
- }
- default:
- {
- return QVariant();
- }
- }
- }
- return QVariant();
-}
-QVariant MinecraftProfile::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 MinecraftProfile::flags(const QModelIndex &index) const
-{
- if (!index.isValid())
- return Qt::NoItemFlags;
- return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
-}
-
-int MinecraftProfile::rowCount(const QModelIndex &parent) const
-{
- return m_patches.size();
-}
-
-int MinecraftProfile::columnCount(const QModelIndex &parent) const
-{
- return 2;
-}
-
-void MinecraftProfile::saveCurrentOrder() const
-{
- ProfileUtils::PatchOrder order;
- for(auto item: m_patches)
- {
- if(!item->isMoveable())
- continue;
- order.append(item->getID());
- }
- m_strategy->saveOrder(order);
-}
-
-void MinecraftProfile::move(const int index, const MoveDirection direction)
-{
- int theirIndex;
- if (direction == MoveUp)
- {
- theirIndex = index - 1;
- }
- else
- {
- theirIndex = index + 1;
- }
-
- if (index < 0 || index >= m_patches.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);
- m_patches.swap(index, theirIndex);
- endMoveRows();
- reapplyPatches();
- saveCurrentOrder();
-}
-void MinecraftProfile::resetOrder()
-{
- m_strategy->resetOrder();
- reload();
-}
-
-bool MinecraftProfile::reapplyPatches()
-{
- try
- {
- clear();
- for(auto file: m_patches)
- {
- qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD");
- file->applyTo(this);
- }
- }
- catch (Exception & error)
- {
- clear();
- qWarning() << "Couldn't apply profile patches because: " << error.cause();
- return false;
- }
- return true;
-}
-
-static void applyString(const QString & from, QString & to)
-{
- if(from.isEmpty())
- return;
- to = from;
-}
-
-void MinecraftProfile::applyMinecraftVersion(const QString& id)
-{
- applyString(id, this->m_minecraftVersion);
-}
-
-void MinecraftProfile::applyAppletClass(const QString& appletClass)
-{
- applyString(appletClass, this->m_appletClass);
-}
-
-void MinecraftProfile::applyMainClass(const QString& mainClass)
-{
- applyString(mainClass, this->m_mainClass);
-}
-
-void MinecraftProfile::applyMinecraftArguments(const QString& minecraftArguments)
-{
- applyString(minecraftArguments, this->m_minecraftArguments);
-}
-
-void MinecraftProfile::applyMinecraftVersionType(const QString& type)
-{
- applyString(type, this->m_minecraftVersionType);
-}
-
-void MinecraftProfile::applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets)
-{
- if(assets)
- {
- m_minecraftAssets = assets;
- }
-}
-
-void MinecraftProfile::applyTraits(const QSet<QString>& traits)
-{
- this->m_traits.unite(traits);
-}
-
-void MinecraftProfile::applyTweakers(const QStringList& tweakers)
-{
- // FIXME: check for dupes?
- // FIXME: does order matter?
- for (auto tweaker : tweakers)
- {
- this->m_tweakers += tweaker;
- }
-}
-
-void MinecraftProfile::applyJarMods(const QList<LibraryPtr>& jarMods)
-{
- this->m_jarMods.append(jarMods);
-}
-
-static int findLibraryByName(QList<LibraryPtr> *haystack, const GradleSpecifier &needle)
-{
- int retval = -1;
- for (int i = 0; i < haystack->size(); ++i)
- {
- if (haystack->at(i)->rawName().matchName(needle))
- {
- // only one is allowed.
- if (retval != -1)
- return -1;
- retval = i;
- }
- }
- return retval;
-}
-
-void MinecraftProfile::applyMods(const QList<LibraryPtr>& mods)
-{
- QList<LibraryPtr> * list = &m_mods;
- for(auto & mod: mods)
- {
- auto modCopy = Library::limitedCopy(mod);
-
- // find the mod by name.
- const int index = findLibraryByName(list, mod->rawName());
- // mod not found? just add it.
- if (index < 0)
- {
- list->append(modCopy);
- return;
- }
-
- auto existingLibrary = list->at(index);
- // if we are higher it means we should update
- if (Version(mod->version()) > Version(existingLibrary->version()))
- {
- list->replace(index, modCopy);
- }
- }
-}
-
-void MinecraftProfile::applyLibrary(LibraryPtr library)
-{
- if(!library->isActive())
- {
- return;
- }
-
- QList<LibraryPtr> * list = &m_libraries;
- if(library->isNative())
- {
- list = &m_nativeLibraries;
- }
-
- auto libraryCopy = Library::limitedCopy(library);
-
- // find the library by name.
- const int index = findLibraryByName(list, library->rawName());
- // library not found? just add it.
- if (index < 0)
- {
- list->append(libraryCopy);
- return;
- }
-
- auto existingLibrary = list->at(index);
- // if we are higher it means we should update
- if (Version(library->version()) > Version(existingLibrary->version()))
- {
- list->replace(index, libraryCopy);
- }
-}
-
-const LibraryPtr MinecraftProfile::getMainJar() const
-{
- return m_mainJar;
-}
-
-void MinecraftProfile::applyMainJar(LibraryPtr jar)
-{
- if(jar)
- {
- m_mainJar = jar;
- }
-}
-
-void MinecraftProfile::applyProblemSeverity(ProblemSeverity severity)
-{
- if (m_problemSeverity < severity)
- {
- m_problemSeverity = severity;
- }
-}
-
-
-QString MinecraftProfile::getMinecraftVersion() const
-{
- return m_minecraftVersion;
-}
-
-QString MinecraftProfile::getAppletClass() const
-{
- return m_appletClass;
-}
-
-QString MinecraftProfile::getMainClass() const
-{
- return m_mainClass;
-}
-
-const QSet<QString> &MinecraftProfile::getTraits() const
-{
- return m_traits;
-}
-
-const QStringList & MinecraftProfile::getTweakers() const
-{
- return m_tweakers;
-}
-
-bool MinecraftProfile::hasTrait(const QString& trait) const
-{
- return m_traits.contains(trait);
-}
-
-ProblemSeverity MinecraftProfile::getProblemSeverity() const
-{
- return m_problemSeverity;
-}
-
-QString MinecraftProfile::getMinecraftVersionType() const
-{
- return m_minecraftVersionType;
-}
-
-std::shared_ptr<MojangAssetIndexInfo> MinecraftProfile::getMinecraftAssets() const
-{
- if(!m_minecraftAssets)
- {
- return std::make_shared<MojangAssetIndexInfo>("legacy");
- }
- return m_minecraftAssets;
-}
-
-QString MinecraftProfile::getMinecraftArguments() const
-{
- return m_minecraftArguments;
-}
-
-const QList<LibraryPtr> & MinecraftProfile::getJarMods() const
-{
- return m_jarMods;
-}
-
-const QList<LibraryPtr> & MinecraftProfile::getLibraries() const
-{
- return m_libraries;
-}
-
-const QList<LibraryPtr> & MinecraftProfile::getNativeLibraries() const
-{
- return m_nativeLibraries;
-}
-
-void MinecraftProfile::getLibraryFiles(const QString& architecture, QStringList& jars, QStringList& nativeJars, const QString& overridePath, const QString& tempPath) const
-{
- QStringList native32, native64;
- jars.clear();
- nativeJars.clear();
- for (auto lib : getLibraries())
- {
- lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
- }
- // NOTE: order is important here, add main jar last to the lists
- if(m_mainJar)
- {
- // FIXME: HACK!! jar modding is weird and unsystematic!
- if(m_jarMods.size())
- {
- QDir tempDir(tempPath);
- jars.append(tempDir.absoluteFilePath("minecraft.jar"));
- }
- else
- {
- m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
- }
- }
- for (auto lib : getNativeLibraries())
- {
- lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath);
- }
- if(architecture == "32")
- {
- nativeJars.append(native32);
- }
- else if(architecture == "64")
- {
- nativeJars.append(native64);
- }
-}
-
-void MinecraftProfile::installJarMods(QStringList selectedFiles)
-{
- m_strategy->installJarMods(selectedFiles);
-}
-
-/*
- * TODO: get rid of this. Get rid of all order numbers.
- */
-int MinecraftProfile::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: m_patches)
- {
- int order = thing->getOrder();
- if(order > largest)
- largest = order;
- }
- return largest + 1;
-}
diff --git a/api/logic/minecraft/MinecraftProfile.h b/api/logic/minecraft/MinecraftProfile.h
deleted file mode 100644
index 192c77f9..00000000
--- a/api/logic/minecraft/MinecraftProfile.h
+++ /dev/null
@@ -1,211 +0,0 @@
-/* Copyright 2013-2017 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 <QAbstractListModel>
-
-#include <QString>
-#include <QList>
-#include <memory>
-
-#include "Library.h"
-#include "ProfilePatch.h"
-#include "BaseVersion.h"
-#include "MojangDownloadInfo.h"
-
-#include "multimc_logic_export.h"
-
-class ProfileStrategy;
-class OneSixInstance;
-
-
-class MULTIMC_LOGIC_EXPORT MinecraftProfile : public QAbstractListModel
-{
- Q_OBJECT
-
-public:
- explicit MinecraftProfile(ProfileStrategy *strategy);
- virtual ~MinecraftProfile();
-
- void setStrategy(ProfileStrategy * strategy);
- ProfileStrategy *strategy();
-
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
- virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- virtual int columnCount(const QModelIndex &parent) const override;
- virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
-
- /// is this version unchanged by the user?
- bool isVanilla();
-
- /// remove any customizations on top of whatever 'vanilla' means
- bool revertToVanilla();
-
- /// install more jar mods
- void installJarMods(QStringList selectedFiles);
-
- /// DEPRECATED, remove ASAP
- int getFreeOrderNumber();
-
- enum MoveDirection { MoveUp, MoveDown };
- /// move patch file # up or down the list
- void move(const int index, const MoveDirection direction);
-
- /// remove patch file # - including files/records
- bool remove(const int index);
-
- /// remove patch file by id - including files/records
- bool remove(const QString id);
-
- bool customize(int index);
-
- bool revertToBase(int index);
-
- void resetOrder();
-
- /// reload all profile patches from storage, clear the profile and apply the patches
- void reload();
-
- /// clear the profile
- void clear();
-
- /// apply the patches. Catches all the errors and returns true/false for success/failure
- bool reapplyPatches();
-
-public: /* application of profile variables from patches */
- void applyMinecraftVersion(const QString& id);
- void applyMainClass(const QString& mainClass);
- void applyAppletClass(const QString& appletClass);
- void applyMinecraftArguments(const QString& minecraftArguments);
- void applyMinecraftVersionType(const QString& type);
- void applyMinecraftAssets(MojangAssetIndexInfo::Ptr assets);
- void applyTraits(const QSet<QString> &traits);
- void applyTweakers(const QStringList &tweakers);
- void applyJarMods(const QList<LibraryPtr> &jarMods);
- void applyMods(const QList<LibraryPtr> &jarMods);
- void applyLibrary(LibraryPtr library);
- void applyMainJar(LibraryPtr jar);
- void applyProblemSeverity(ProblemSeverity severity);
-
-public: /* getters for profile variables */
- QString getMinecraftVersion() const;
- QString getMainClass() const;
- QString getAppletClass() const;
- QString getMinecraftVersionType() const;
- MojangAssetIndexInfo::Ptr getMinecraftAssets() const;
- QString getMinecraftArguments() const;
- const QSet<QString> & getTraits() const;
- const QStringList & getTweakers() const;
- const QList<LibraryPtr> & getJarMods() const;
- const QList<LibraryPtr> & getLibraries() const;
- const QList<LibraryPtr> & getNativeLibraries() const;
- const LibraryPtr getMainJar() const;
- void getLibraryFiles(const QString & architecture, QStringList & jars, QStringList & nativeJars, const QString & overridePath,
- const QString & tempPath) const;
- bool hasTrait(const QString & trait) const;
- ProblemSeverity getProblemSeverity() const;
-
-public:
- /// get the profile patch by id
- ProfilePatchPtr versionPatch(const QString &id);
-
- /// get the profile patch by index
- ProfilePatchPtr versionPatch(int index);
-
- /// save the current patch order
- void saveCurrentOrder() const;
-
- /// Remove all the patches
- void clearPatches();
-
- /// Add the patch object to the internal list of patches
- void appendPatch(ProfilePatchPtr patch);
-
-private: /* data */
- /// the version of Minecraft - jar to use
- QString m_minecraftVersion;
-
- /// Release type - "release" or "snapshot"
- QString m_minecraftVersionType;
-
- /// Assets type - "legacy" or a version ID
- MojangAssetIndexInfo::Ptr m_minecraftAssets;
-
- /**
- * arguments that should be used for launching minecraft
- *
- * ex: "--username ${auth_player_name} --session ${auth_session}
- * --version ${version_name} --gameDir ${game_directory} --assetsDir ${game_assets}"
- */
- QString m_minecraftArguments;
-
- /// A list of all tweaker classes
- QStringList m_tweakers;
-
- /// The main class to load first
- QString m_mainClass;
-
- /// The applet class, for some very old minecraft releases
- QString m_appletClass;
-
- /// the list of libraries
- QList<LibraryPtr> m_libraries;
-
- /// the main jar
- LibraryPtr m_mainJar;
-
- /// the list of libraries
- QList<LibraryPtr> m_nativeLibraries;
-
- /// traits, collected from all the version files (version files can only add)
- QSet<QString> m_traits;
-
- /// A list of jar mods. version files can add those.
- QList<LibraryPtr> m_jarMods;
-
- /// the list of mods
- QList<LibraryPtr> m_mods;
-
- ProblemSeverity m_problemSeverity = ProblemSeverity::None;
-
- /*
- FIXME: add support for those rules here? Looks like a pile of quick hacks to me though.
-
- "rules": [
- {
- "action": "allow"
- },
- {
- "action": "disallow",
- "os": {
- "name": "osx",
- "version": "^10\\.5\\.\\d$"
- }
- }
- ],
- "incompatibilityReason": "There is a bug in LWJGL which makes it incompatible with OSX
- 10.5.8. Please go to New Profile and use 1.5.2 for now. Sorry!"
- }
- */
- // QList<Rule> rules;
-
- /// list of attached profile patches
- QList<ProfilePatchPtr> m_patches;
-
- /// strategy used for profile operations
- ProfileStrategy *m_strategy = nullptr;
-};
diff --git a/api/logic/minecraft/onesix/OneSixUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp
index e0027032..86835fa4 100644
--- a/api/logic/minecraft/onesix/OneSixUpdate.cpp
+++ b/api/logic/minecraft/MinecraftUpdate.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 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,8 +15,8 @@
#include "Env.h"
#include <minecraft/forge/ForgeXzDownload.h>
-#include "OneSixUpdate.h"
-#include "OneSixInstance.h"
+#include "MinecraftUpdate.h"
+#include "MinecraftInstance.h"
#include <QFile>
#include <QFileInfo>
@@ -24,7 +24,7 @@
#include <QDataStream>
#include "BaseInstance.h"
-#include "minecraft/MinecraftProfile.h"
+#include "minecraft/ComponentList.h"
#include "minecraft/Library.h"
#include "net/URLConstants.h"
#include <FileSystem.h>
@@ -37,42 +37,26 @@
#include <meta/Index.h>
#include <meta/Version.h>
-OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
+OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
{
+}
+
+void OneSixUpdate::executeTask()
+{
+ m_tasks.clear();
// create folders
{
m_tasks.append(std::make_shared<FoldersTask>(m_inst));
}
- // add metadata update tasks, if necessary
+ // add metadata update task if necessary
{
- /*
- * FIXME: there are some corner cases here that remain unhandled:
- * what if local load succeeds but remote fails? The version is still usable...
- * We should not rely on the remote to be there... and prefer local files if it does not respond.
- */
- qDebug() << "Updating patches...";
- auto profile = m_inst->getMinecraftProfile();
- m_inst->reloadProfile();
- for(int i = 0; i < profile->rowCount(); i++)
+ auto components = m_inst->getComponentList();
+ components->reload(Net::Mode::Online);
+ auto task = components->getCurrentTask();
+ if(task)
{
- auto patch = profile->versionPatch(i);
- auto id = patch->getID();
- auto metadata = patch->getMeta();
- if(metadata)
- {
- metadata->load();
- auto task = metadata->getCurrentTask();
- if(task)
- {
- qDebug() << "Loading remote meta patch" << id;
- m_tasks.append(task.unwrap());
- }
- }
- else
- {
- qDebug() << "Ignoring local patch" << id;
- }
+ m_tasks.append(task.unwrap());
}
}
@@ -90,10 +74,7 @@ OneSixUpdate::OneSixUpdate(OneSixInstance *inst, QObject *parent) : Task(parent)
{
m_tasks.append(std::make_shared<AssetUpdateTask>(m_inst));
}
-}
-void OneSixUpdate::executeTask()
-{
if(!m_preFailure.isEmpty())
{
emitFailed(m_preFailure);
@@ -109,6 +90,11 @@ void OneSixUpdate::next()
emitFailed(tr("Aborted by user."));
return;
}
+ if(m_failed_out_of_order)
+ {
+ emitFailed(m_fail_reason);
+ return;
+ }
m_currentTask ++;
if(m_currentTask > 0)
{
@@ -127,6 +113,7 @@ void OneSixUpdate::next()
// if the task is already finished by the time we look at it, skip it
if(task->isFinished())
{
+ qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
next();
}
connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
@@ -142,11 +129,37 @@ void OneSixUpdate::next()
void OneSixUpdate::subtaskSucceeded()
{
+ if(isFinished())
+ {
+ qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
+ return;
+ }
+ auto senderTask = QObject::sender();
+ auto currentTask = m_tasks[m_currentTask].get();
+ if(senderTask != currentTask)
+ {
+ qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order.";
+ return;
+ }
next();
}
void OneSixUpdate::subtaskFailed(QString error)
{
+ if(isFinished())
+ {
+ qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
+ return;
+ }
+ auto senderTask = QObject::sender();
+ auto currentTask = m_tasks[m_currentTask].get();
+ if(senderTask != currentTask)
+ {
+ qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order.";
+ m_failed_out_of_order = true;
+ m_fail_reason = error;
+ return;
+ }
emitFailed(error);
}
diff --git a/api/logic/minecraft/onesix/OneSixUpdate.h b/api/logic/minecraft/MinecraftUpdate.h
index 6bcfd41a..78c02049 100644
--- a/api/logic/minecraft/onesix/OneSixUpdate.h
+++ b/api/logic/minecraft/MinecraftUpdate.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,13 +25,13 @@
#include <quazip.h>
class MinecraftVersion;
-class OneSixInstance;
+class MinecraftInstance;
class OneSixUpdate : public Task
{
Q_OBJECT
public:
- explicit OneSixUpdate(OneSixInstance *inst, QObject *parent = 0);
+ explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0);
void executeTask() override;
bool canAbort() const override;
@@ -45,9 +45,11 @@ private:
void next();
private:
- OneSixInstance *m_inst = nullptr;
+ MinecraftInstance *m_inst = nullptr;
QList<std::shared_ptr<Task>> m_tasks;
QString m_preFailure;
int m_currentTask = -1;
bool m_abort = false;
+ bool m_failed_out_of_order = false;
+ QString m_fail_reason;
};
diff --git a/api/logic/minecraft/Mod.cpp b/api/logic/minecraft/Mod.cpp
index 5b4ecbb6..03e04b2b 100644
--- a/api/logic/minecraft/Mod.cpp
+++ b/api/logic/minecraft/Mod.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/Mod.h b/api/logic/minecraft/Mod.h
index 96ee8174..ccab1867 100644
--- a/api/logic/minecraft/Mod.h
+++ b/api/logic/minecraft/Mod.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,7 +58,8 @@ public:
}
QString name() const
{
- if(m_name.trimmed().isEmpty())
+ QString name = m_name.trimmed();
+ if(name.isEmpty() || name == "Example Mod")
{
return m_mmc_id;
}
diff --git a/api/logic/minecraft/ModList.cpp b/api/logic/minecraft/ModList.cpp
index 02b09eef..6ccf20e2 100644
--- a/api/logic/minecraft/ModList.cpp
+++ b/api/logic/minecraft/ModList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/ModList.h b/api/logic/minecraft/ModList.h
index 8cdd2d9a..72f50edb 100644
--- a/api/logic/minecraft/ModList.h
+++ b/api/logic/minecraft/ModList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/MojangVersionFormat.cpp b/api/logic/minecraft/MojangVersionFormat.cpp
index f73c8e49..a0aa2894 100644
--- a/api/logic/minecraft/MojangVersionFormat.cpp
+++ b/api/logic/minecraft/MojangVersionFormat.cpp
@@ -1,5 +1,5 @@
#include "MojangVersionFormat.h"
-#include "onesix/OneSixVersionFormat.h"
+#include "OneSixVersionFormat.h"
#include "MojangDownloadInfo.h"
#include "Json.h"
diff --git a/api/logic/minecraft/onesix/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp
index 7ebf514f..f7ab25b3 100644
--- a/api/logic/minecraft/onesix/OneSixVersionFormat.cpp
+++ b/api/logic/minecraft/OneSixVersionFormat.cpp
@@ -52,6 +52,15 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
QJsonObject root = doc.object();
+ Meta::MetadataVersion formatVersion = Meta::parseFormatVersion(root, false);
+ switch(formatVersion)
+ {
+ case Meta::MetadataVersion::InitialRelease:
+ break;
+ case Meta::MetadataVersion::Invalid:
+ throw JSONValidationError(filename + " does not contain a recognizable version of the metadata format.");
+ }
+
if (requireOrder)
{
if (root.contains("order"))
@@ -77,8 +86,6 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
}
out->version = root.value("version").toString();
- out->dependsOnMinecraftVersion = root.value("mcVersion").toString();
- // out->filename = filename;
MojangVersionFormat::readVersionProperties(root, out.get());
@@ -192,6 +199,30 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
out->mainJar = lib;
}
+ if (root.contains("requires"))
+ {
+ Meta::parseRequires(root, &out->requires);
+ }
+ QString dependsOnMinecraftVersion = root.value("mcVersion").toString();
+ if(!dependsOnMinecraftVersion.isEmpty())
+ {
+ Meta::Require mcReq;
+ mcReq.uid = "net.minecraft";
+ mcReq.equalsVersion = dependsOnMinecraftVersion;
+ if (out->requires.count(mcReq) == 0)
+ {
+ out->requires.insert(mcReq);
+ }
+ }
+ if (root.contains("conflicts"))
+ {
+ Meta::parseRequires(root, &out->conflicts);
+ }
+ if (root.contains("volatile"))
+ {
+ out->m_volatile = requireBoolean(root, "volatile");
+ }
+
/* removed features that shouldn't be used */
if (root.contains("tweakers"))
{
@@ -216,19 +247,16 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
return out;
}
-QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch, bool saveOrder)
+QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch)
{
QJsonObject root;
- if (saveOrder)
- {
- root.insert("order", patch->order);
- }
writeString(root, "name", patch->name);
writeString(root, "uid", patch->uid);
writeString(root, "version", patch->version);
- writeString(root, "mcVersion", patch->dependsOnMinecraftVersion);
+
+ Meta::serializeFormatVersion(root, Meta::MetadataVersion::InitialRelease);
MojangVersionFormat::writeVersionProperties(patch.get(), root);
@@ -266,6 +294,18 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
}
root.insert("mods", array);
}
+ if(!patch->requires.empty())
+ {
+ Meta::serializeRequires(root, &patch->requires, "requires");
+ }
+ if(!patch->conflicts.empty())
+ {
+ Meta::serializeRequires(root, &patch->conflicts, "conflicts");
+ }
+ if(patch->m_volatile)
+ {
+ root.insert("volatile", true);
+ }
// write the contents to a json document.
{
QJsonDocument out;
diff --git a/api/logic/minecraft/onesix/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h
index 64f18da8..8b782db0 100644
--- a/api/logic/minecraft/onesix/OneSixVersionFormat.h
+++ b/api/logic/minecraft/OneSixVersionFormat.h
@@ -1,7 +1,7 @@
#pragma once
#include <minecraft/VersionFile.h>
-#include <minecraft/MinecraftProfile.h>
+#include <minecraft/ComponentList.h>
#include <minecraft/Library.h>
#include <QJsonDocument>
@@ -10,7 +10,7 @@ class OneSixVersionFormat
public:
// version files / profile patches
static VersionFilePtr versionFileFromJson(const QJsonDocument &doc, const QString &filename, const bool requireOrder);
- static QJsonDocument versionFileToJson(const VersionFilePtr &patch, bool saveOrder);
+ static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
// libraries
static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp
index c2c28d1b..2165fa7f 100644
--- a/api/logic/minecraft/OpSys.cpp
+++ b/api/logic/minecraft/OpSys.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/OpSys.h b/api/logic/minecraft/OpSys.h
index 8f43f480..e44b1e07 100644
--- a/api/logic/minecraft/OpSys.h
+++ b/api/logic/minecraft/OpSys.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/ProfilePatch.cpp b/api/logic/minecraft/ProfilePatch.cpp
deleted file mode 100644
index a2605278..00000000
--- a/api/logic/minecraft/ProfilePatch.cpp
+++ /dev/null
@@ -1,188 +0,0 @@
-#include <meta/VersionList.h>
-#include <meta/Index.h>
-#include <Env.h>
-#include "ProfilePatch.h"
-
-#include "meta/Version.h"
-#include "VersionFile.h"
-#include "minecraft/MinecraftProfile.h"
-
-ProfilePatch::ProfilePatch(std::shared_ptr<Meta::Version> version)
- :m_metaVersion(version)
-{
-}
-
-ProfilePatch::ProfilePatch(std::shared_ptr<VersionFile> file, const QString& filename)
- :m_file(file), m_filename(filename)
-{
-}
-
-std::shared_ptr<Meta::Version> ProfilePatch::getMeta()
-{
- return m_metaVersion;
-}
-
-void ProfilePatch::applyTo(MinecraftProfile* profile)
-{
- auto vfile = getVersionFile();
- if(vfile)
- {
- vfile->applyTo(profile);
- }
- else
- {
- profile->applyProblemSeverity(getProblemSeverity());
- }
-}
-
-std::shared_ptr<class VersionFile> ProfilePatch::getVersionFile()
-{
- if(m_metaVersion)
- {
- if(!m_metaVersion->isLoaded())
- {
- m_metaVersion->load();
- }
- return m_metaVersion->data();
- }
- else
- {
- return m_file;
- }
-}
-
-std::shared_ptr<class Meta::VersionList> ProfilePatch::getVersionList()
-{
- if(m_metaVersion)
- {
- return ENV.metadataIndex()->get(m_metaVersion->uid());
- }
- return nullptr;
-}
-
-int ProfilePatch::getOrder()
-{
- if(m_orderOverride)
- return m_order;
-
- auto vfile = getVersionFile();
- if(vfile)
- {
- return vfile->order;
- }
- return 0;
-}
-void ProfilePatch::setOrder(int order)
-{
- m_orderOverride = true;
- m_order = order;
-}
-QString ProfilePatch::getID()
-{
- if(m_metaVersion)
- return m_metaVersion->uid();
- return getVersionFile()->uid;
-}
-QString ProfilePatch::getName()
-{
- if(m_metaVersion)
- return m_metaVersion->name();
- return getVersionFile()->name;
-}
-QString ProfilePatch::getVersion()
-{
- if(m_metaVersion)
- return m_metaVersion->version();
- return getVersionFile()->version;
-}
-QString ProfilePatch::getFilename()
-{
- return m_filename;
-}
-QDateTime ProfilePatch::getReleaseDateTime()
-{
- if(m_metaVersion)
- {
- return m_metaVersion->time();
- }
- return getVersionFile()->releaseTime;
-}
-
-bool ProfilePatch::isCustom()
-{
- return !m_isVanilla;
-};
-
-bool ProfilePatch::isCustomizable()
-{
- if(m_metaVersion)
- {
- if(getVersionFile())
- {
- return true;
- }
- }
- return false;
-}
-bool ProfilePatch::isRemovable()
-{
- return m_isRemovable;
-}
-bool ProfilePatch::isRevertible()
-{
- return m_isRevertible;
-}
-bool ProfilePatch::isMoveable()
-{
- return m_isMovable;
-}
-bool ProfilePatch::isVersionChangeable()
-{
- auto list = getVersionList();
- if(list)
- {
- if(!list->isLoaded())
- {
- list->load();
- }
- return list->count() != 0;
- }
- return false;
-}
-
-void ProfilePatch::setVanilla (bool state)
-{
- m_isVanilla = state;
-}
-void ProfilePatch::setRemovable (bool state)
-{
- m_isRemovable = state;
-}
-void ProfilePatch::setRevertible (bool state)
-{
- m_isRevertible = state;
-}
-void ProfilePatch::setMovable (bool state)
-{
- m_isMovable = state;
-}
-
-ProblemSeverity ProfilePatch::getProblemSeverity()
-{
- auto file = getVersionFile();
- if(file)
- {
- return file->getProblemSeverity();
- }
- return ProblemSeverity::Error;
-}
-
-const QList<PatchProblem> ProfilePatch::getProblems()
-{
- auto file = getVersionFile();
- if(file)
- {
- return file->getProblems();
- }
- return {PatchProblem(ProblemSeverity::Error, QObject::tr("Patch is not loaded yet."))};
-}
diff --git a/api/logic/minecraft/ProfilePatch.h b/api/logic/minecraft/ProfilePatch.h
deleted file mode 100644
index 59171a0a..00000000
--- a/api/logic/minecraft/ProfilePatch.h
+++ /dev/null
@@ -1,70 +0,0 @@
-#pragma once
-
-#include <memory>
-#include <QList>
-#include <QJsonDocument>
-#include <QDateTime>
-#include "ProblemProvider.h"
-
-class MinecraftProfile;
-namespace Meta
-{
- class Version;
- class VersionList;
-}
-class VersionFile;
-
-class ProfilePatch : public ProblemProvider
-{
-public:
- ProfilePatch(std::shared_ptr<Meta::Version> version);
- ProfilePatch(std::shared_ptr<VersionFile> file, const QString &filename = QString());
-
- virtual ~ProfilePatch(){};
- virtual void applyTo(MinecraftProfile *profile);
-
- virtual bool isMoveable();
- virtual bool isCustomizable();
- virtual bool isRevertible();
- virtual bool isRemovable();
- virtual bool isCustom();
- virtual bool isVersionChangeable();
-
- virtual void setOrder(int order);
- virtual int getOrder();
-
- virtual QString getID();
- virtual QString getName();
- virtual QString getVersion();
- virtual std::shared_ptr<Meta::Version> getMeta();
- virtual QDateTime getReleaseDateTime();
-
- virtual QString getFilename();
-
- virtual std::shared_ptr<class VersionFile> getVersionFile();
- virtual std::shared_ptr<class Meta::VersionList> getVersionList();
-
- void setVanilla (bool state);
- void setRemovable (bool state);
- void setRevertible (bool state);
- void setMovable (bool state);
-
- const QList<PatchProblem> getProblems() override;
- ProblemSeverity getProblemSeverity() override;
-
-protected:
- // Properties for UI and version manipulation from UI in general
- bool m_isMovable = false;
- bool m_isRevertible = false;
- bool m_isRemovable = false;
- bool m_isVanilla = false;
-
- bool m_orderOverride = false;
- int m_order = 0;
-
- std::shared_ptr<Meta::Version> m_metaVersion;
- std::shared_ptr<VersionFile> m_file;
- QString m_filename;
-};
-
-typedef std::shared_ptr<ProfilePatch> ProfilePatchPtr;
diff --git a/api/logic/minecraft/ProfileStrategy.h b/api/logic/minecraft/ProfileStrategy.h
deleted file mode 100644
index 26bdf661..00000000
--- a/api/logic/minecraft/ProfileStrategy.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#pragma once
-
-#include "ProfileUtils.h"
-#include "ProfilePatch.h"
-
-class MinecraftProfile;
-
-class ProfileStrategy
-{
- friend class MinecraftProfile;
-public:
- virtual ~ProfileStrategy(){};
-
- /// load the patch files into the profile
- virtual void load() = 0;
-
- /// reset the order of patches
- virtual bool resetOrder() = 0;
-
- /// save the order of patches, given the order
- virtual bool saveOrder(ProfileUtils::PatchOrder order) = 0;
-
- /// install a list of jar mods into the instance
- virtual bool installJarMods(QStringList filepaths) = 0;
-
- /// remove any files or records that constitute the version patch
- virtual bool removePatch(ProfilePatchPtr jarMod) = 0;
-
- /// make the patch custom, if possible
- virtual bool customizePatch(ProfilePatchPtr patch) = 0;
-
- /// revert the custom patch to 'vanilla', if possible
- virtual bool revertPatch(ProfilePatchPtr patch) = 0;
-protected:
- MinecraftProfile *profile;
-};
diff --git a/api/logic/minecraft/ProfileUtils.cpp b/api/logic/minecraft/ProfileUtils.cpp
index 8c5bc052..a6d2028d 100644
--- a/api/logic/minecraft/ProfileUtils.cpp
+++ b/api/logic/minecraft/ProfileUtils.cpp
@@ -1,6 +1,6 @@
#include "ProfileUtils.h"
#include "minecraft/VersionFilterData.h"
-#include "minecraft/onesix/OneSixVersionFormat.h"
+#include "minecraft/OneSixVersionFormat.h"
#include "Json.h"
#include <QDebug>
@@ -14,38 +14,6 @@ namespace ProfileUtils
static const int currentOrderFileVersion = 1;
-bool writeOverrideOrders(QString path, const PatchOrder &order)
-{
- QJsonObject obj;
- obj.insert("version", currentOrderFileVersion);
- QJsonArray orderArray;
- for(auto str: order)
- {
- orderArray.append(str);
- }
- obj.insert("order", orderArray);
- QSaveFile orderFile(path);
- if (!orderFile.open(QFile::WriteOnly))
- {
- qCritical() << "Couldn't open" << orderFile.fileName()
- << "for writing:" << orderFile.errorString();
- return false;
- }
- auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented);
- if(orderFile.write(data) != data.size())
- {
- qCritical() << "Couldn't write all the data into" << orderFile.fileName()
- << "because:" << orderFile.errorString();
- return false;
- }
- if(!orderFile.commit())
- {
- qCritical() << "Couldn't save" << orderFile.fileName()
- << "because:" << orderFile.errorString();
- }
- return true;
-}
-
bool readOverrideOrders(QString path, PatchOrder &order)
{
QFile orderFile(path);
@@ -154,6 +122,25 @@ VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder)
return guardedParseJson(doc, fileInfo.completeBaseName(), fileInfo.absoluteFilePath(), requireOrder);
}
+bool saveJsonFile(const QJsonDocument doc, const QString & filename)
+{
+ auto data = doc.toJson();
+ QSaveFile jsonFile(filename);
+ if(!jsonFile.open(QIODevice::WriteOnly))
+ {
+ jsonFile.cancelWriting();
+ qWarning() << "Couldn't open" << filename << "for writing";
+ return false;
+ }
+ jsonFile.write(data);
+ if(!jsonFile.commit())
+ {
+ qWarning() << "Couldn't save" << filename;
+ return false;
+ }
+ return true;
+}
+
VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo)
{
QFile file(fileInfo.absoluteFilePath());
diff --git a/api/logic/minecraft/ProfileUtils.h b/api/logic/minecraft/ProfileUtils.h
index 267fd42b..351c36cb 100644
--- a/api/logic/minecraft/ProfileUtils.h
+++ b/api/logic/minecraft/ProfileUtils.h
@@ -16,6 +16,9 @@ bool writeOverrideOrders(QString path, const PatchOrder &order);
/// Parse a version file in JSON format
VersionFilePtr parseJsonFile(const QFileInfo &fileInfo, const bool requireOrder);
+/// Save a JSON file (in any format)
+bool saveJsonFile(const QJsonDocument doc, const QString & filename);
+
/// Parse a version file in binary JSON format
VersionFilePtr parseBinaryJsonFile(const QFileInfo &fileInfo);
diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp
index 8e2838ee..43d673c2 100644
--- a/api/logic/minecraft/Rule.cpp
+++ b/api/logic/minecraft/Rule.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/Rule.h b/api/logic/minecraft/Rule.h
index d5fd2492..dc1eee0b 100644
--- a/api/logic/minecraft/Rule.h
+++ b/api/logic/minecraft/Rule.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/SkinUpload.cpp b/api/logic/minecraft/SkinUpload.cpp
index 1d1e38f3..bd246139 100644
--- a/api/logic/minecraft/SkinUpload.cpp
+++ b/api/logic/minecraft/SkinUpload.cpp
@@ -6,9 +6,9 @@
QByteArray getModelString(SkinUpload::Model model) {
switch (model) {
case SkinUpload::STEVE:
- return "steve";
+ return "";
case SkinUpload::ALEX:
- return "alex";
+ return "slim";
default:
qDebug() << "Unknown skin type!";
return "";
diff --git a/api/logic/minecraft/SkinUpload.h b/api/logic/minecraft/SkinUpload.h
index 86944b82..5b331fa9 100644
--- a/api/logic/minecraft/SkinUpload.h
+++ b/api/logic/minecraft/SkinUpload.h
@@ -9,9 +9,9 @@
typedef std::shared_ptr<class SkinUpload> SkinUploadPtr;
-class MULTIMC_LOGIC_EXPORT SkinUpload : public Task\
+class MULTIMC_LOGIC_EXPORT SkinUpload : public Task
{
-Q_OBJECT
+ Q_OBJECT
public:
enum Model
{
diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp
index 85989549..9f485c55 100644
--- a/api/logic/minecraft/VersionFile.cpp
+++ b/api/logic/minecraft/VersionFile.cpp
@@ -5,7 +5,7 @@
#include "minecraft/VersionFile.h"
#include "minecraft/Library.h"
-#include "minecraft/MinecraftProfile.h"
+#include "minecraft/ComponentList.h"
#include "ParseUtils.h"
#include <Version.h>
@@ -15,7 +15,7 @@ static bool isMinecraftVersion(const QString &uid)
return uid == "net.minecraft";
}
-void VersionFile::applyTo(MinecraftProfile *profile)
+void VersionFile::applyTo(LaunchProfile *profile)
{
// Only real Minecraft can set those. Don't let anything override them.
if (isMinecraftVersion(uid))
diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h
index e3fb46ed..5aea7a7a 100644
--- a/api/logic/minecraft/VersionFile.h
+++ b/api/logic/minecraft/VersionFile.h
@@ -10,27 +10,26 @@
#include "minecraft/Rule.h"
#include "ProblemProvider.h"
#include "Library.h"
+#include <meta/JsonFormat.h>
-class MinecraftProfile;
+class ComponentList;
class VersionFile;
+class LaunchProfile;
struct MojangDownloadInfo;
struct MojangAssetIndexInfo;
-typedef std::shared_ptr<VersionFile> VersionFilePtr;
+using VersionFilePtr = std::shared_ptr<VersionFile>;
class VersionFile : public ProblemContainer
{
friend class MojangVersionFormat;
friend class OneSixVersionFormat;
public: /* methods */
- void applyTo(MinecraftProfile *profile);
+ void applyTo(LaunchProfile* profile);
public: /* data */
/// MultiMC: order hint for this version file if no explicit order is set
int order = 0;
- /// MultiMC: filename of the file this was loaded from
- // QString filename;
-
/// MultiMC: human readable name of this package
QString name;
@@ -76,7 +75,7 @@ public: /* data */
/// Mojang: list of libraries to add to the version
QList<LibraryPtr> libraries;
- // The main jar (Minecraft version library, normally)
+ /// The main jar (Minecraft version library, normally)
LibraryPtr mainJar;
/// MultiMC: list of attached traits of this version file - used to enable features
@@ -88,6 +87,21 @@ public: /* data */
/// MultiMC: list of mods added to this version
QList<LibraryPtr> mods;
+ /**
+ * MultiMC: set of packages this depends on
+ * NOTE: this is shared with the meta format!!!
+ */
+ Meta::RequireSet requires;
+
+ /**
+ * MultiMC: set of packages this conflicts with
+ * NOTE: this is shared with the meta format!!!
+ */
+ Meta::RequireSet conflicts;
+
+ /// is volatile -- may be removed as soon as it is no longer needed by something else
+ bool m_volatile = false;
+
public:
// Mojang: DEPRECATED list of 'downloads' - client jar, server jar, windows server exe, maybe more.
QMap <QString, std::shared_ptr<MojangDownloadInfo>> mojangDownloads;
diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp
index 81f52ff5..68c0a5cc 100644
--- a/api/logic/minecraft/World.cpp
+++ b/api/logic/minecraft/World.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -146,7 +146,7 @@ void World::readFromZip(const QFileInfo &file)
{
return;
}
- auto location = MMCZip::findFileInZip(&zip, "level.dat");
+ auto location = MMCZip::findFolderOfFileInZip(&zip, "level.dat");
is_valid = !location.isEmpty();
if (!is_valid)
{
diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h
index a87cb8ec..7087bf48 100644
--- a/api/logic/minecraft/World.h
+++ b/api/logic/minecraft/World.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp
index 47ce2379..4278c2f7 100644
--- a/api/logic/minecraft/WorldList.cpp
+++ b/api/logic/minecraft/WorldList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h
index 700fe2fd..811393cd 100644
--- a/api/logic/minecraft/WorldList.h
+++ b/api/logic/minecraft/WorldList.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp
index f4c628a6..edad4344 100644
--- a/api/logic/minecraft/auth/MojangAccount.cpp
+++ b/api/logic/minecraft/auth/MojangAccount.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -170,8 +170,7 @@ AccountStatus MojangAccount::accountStatus() const
return Verified;
}
-std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session,
- QString password)
+std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session, QString password)
{
Q_ASSERT(m_currentTask.get() == nullptr);
@@ -186,18 +185,28 @@ std::shared_ptr<YggdrasilTask> MojangAccount::login(AuthSessionPtr session,
return nullptr;
}
- if (password.isEmpty())
+ if(accountStatus() == Verified && !session->wants_online)
{
- m_currentTask.reset(new RefreshTask(this));
+ session->status = AuthSession::PlayableOffline;
+ session->auth_server_online = false;
+ fillSession(session);
+ return nullptr;
}
else
{
- m_currentTask.reset(new AuthenticateTask(this, password));
- }
- m_currentTask->assignSession(session);
+ if (password.isEmpty())
+ {
+ m_currentTask.reset(new RefreshTask(this));
+ }
+ else
+ {
+ m_currentTask.reset(new AuthenticateTask(this, password));
+ }
+ m_currentTask->assignSession(session);
- connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
- connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ connect(m_currentTask.get(), SIGNAL(succeeded()), SLOT(authSucceeded()));
+ connect(m_currentTask.get(), SIGNAL(failed(QString)), SLOT(authFailed(QString)));
+ }
return m_currentTask;
}
diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h
index c7f15723..b2bbc357 100644
--- a/api/logic/minecraft/auth/MojangAccount.h
+++ b/api/logic/minecraft/auth/MojangAccount.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/MojangAccountList.cpp b/api/logic/minecraft/auth/MojangAccountList.cpp
index bcda1703..21ae188a 100644
--- a/api/logic/minecraft/auth/MojangAccountList.cpp
+++ b/api/logic/minecraft/auth/MojangAccountList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -53,34 +53,47 @@ const MojangAccountPtr MojangAccountList::at(int i) const
void MojangAccountList::addAccount(const MojangAccountPtr account)
{
- beginResetModel();
+ int row = m_accounts.count();
+ beginInsertRows(QModelIndex(), row, row);
connect(account.get(), SIGNAL(changed()), SLOT(accountChanged()));
m_accounts.append(account);
- endResetModel();
+ endInsertRows();
onListChanged();
}
void MojangAccountList::removeAccount(const QString &username)
{
- beginResetModel();
+ int idx = 0;
for (auto account : m_accounts)
{
if (account->username() == username)
{
+ beginRemoveRows(QModelIndex(), idx, idx);
m_accounts.removeOne(account);
+ endRemoveRows();
return;
}
+ idx++;
}
- endResetModel();
onListChanged();
}
void MojangAccountList::removeAccount(QModelIndex index)
{
- beginResetModel();
- m_accounts.removeAt(index.row());
- endResetModel();
- onListChanged();
+ int row = index.row();
+ if(index.isValid() && row >= 0 && row < m_accounts.size())
+ {
+ auto & account = m_accounts[row];
+ if(account == m_activeAccount)
+ {
+ m_activeAccount = nullptr;
+ onActiveChanged();
+ }
+ beginRemoveRows(QModelIndex(), row, row);
+ m_accounts.removeAt(index.row());
+ endRemoveRows();
+ onListChanged();
+ }
}
MojangAccountPtr MojangAccountList::activeAccount() const
@@ -90,21 +103,49 @@ MojangAccountPtr MojangAccountList::activeAccount() const
void MojangAccountList::setActiveAccount(const QString &username)
{
- beginResetModel();
- if (username.isEmpty())
+ if (username.isEmpty() && m_activeAccount)
{
+ int idx = 0;
+ auto prevActiveAcc = m_activeAccount;
m_activeAccount = nullptr;
+ for (MojangAccountPtr account : m_accounts)
+ {
+ if (account == prevActiveAcc)
+ {
+ emit dataChanged(index(idx), index(idx));
+ }
+ idx ++;
+ }
+ onActiveChanged();
}
else
{
+ auto currentActiveAccount = m_activeAccount;
+ int currentActiveAccountIdx = -1;
+ auto newActiveAccount = m_activeAccount;
+ int newActiveAccountIdx = -1;
+ int idx = 0;
for (MojangAccountPtr account : m_accounts)
{
if (account->username() == username)
- m_activeAccount = account;
+ {
+ newActiveAccount = account;
+ newActiveAccountIdx = idx;
+ }
+ if(currentActiveAccount == account)
+ {
+ currentActiveAccountIdx = idx;
+ }
+ idx++;
+ }
+ if(currentActiveAccount != newActiveAccount)
+ {
+ emit dataChanged(index(currentActiveAccountIdx), index(currentActiveAccountIdx));
+ emit dataChanged(index(newActiveAccountIdx), index(newActiveAccountIdx));
+ m_activeAccount = newActiveAccount;
+ onActiveChanged();
}
}
- endResetModel();
- onActiveChanged();
}
void MojangAccountList::accountChanged()
@@ -167,7 +208,7 @@ QVariant MojangAccountList::data(const QModelIndex &index, int role) const
switch (index.column())
{
case ActiveColumn:
- return account == m_activeAccount;
+ return account == m_activeAccount ? Qt::Checked : Qt::Unchecked;
}
default:
@@ -207,13 +248,13 @@ QVariant MojangAccountList::headerData(int section, Qt::Orientation orientation,
}
}
-int MojangAccountList::rowCount(const QModelIndex &parent) const
+int MojangAccountList::rowCount(const QModelIndex &) const
{
// Return count
return count();
}
-int MojangAccountList::columnCount(const QModelIndex &parent) const
+int MojangAccountList::columnCount(const QModelIndex &) const
{
return 2;
}
diff --git a/api/logic/minecraft/auth/MojangAccountList.h b/api/logic/minecraft/auth/MojangAccountList.h
index 0a7a10d9..dd6c07e8 100644
--- a/api/logic/minecraft/auth/MojangAccountList.h
+++ b/api/logic/minecraft/auth/MojangAccountList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/YggdrasilTask.cpp b/api/logic/minecraft/auth/YggdrasilTask.cpp
index 425da76a..f17d803f 100644
--- a/api/logic/minecraft/auth/YggdrasilTask.cpp
+++ b/api/logic/minecraft/auth/YggdrasilTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -130,6 +130,7 @@ void YggdrasilTask::processReply()
"</ul>"));
return;
// used for invalid credentials and similar errors. Fall through.
+ case QNetworkReply::ContentAccessDenied:
case QNetworkReply::ContentOperationNotPermittedError:
break;
default:
diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h
index f21c2733..b165d3e9 100644
--- a/api/logic/minecraft/auth/YggdrasilTask.h
+++ b/api/logic/minecraft/auth/YggdrasilTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
index ad892073..ff4e7d47 100644
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
+++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
@@ -1,5 +1,5 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/flows/AuthenticateTask.h b/api/logic/minecraft/auth/flows/AuthenticateTask.h
index a15c8a9e..ab7a6e64 100644
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.h
+++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/flows/RefreshTask.cpp b/api/logic/minecraft/auth/flows/RefreshTask.cpp
index 76738900..73050907 100644
--- a/api/logic/minecraft/auth/flows/RefreshTask.cpp
+++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/flows/RefreshTask.h b/api/logic/minecraft/auth/flows/RefreshTask.h
index 3c99101e..a97b54e6 100644
--- a/api/logic/minecraft/auth/flows/RefreshTask.h
+++ b/api/logic/minecraft/auth/flows/RefreshTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/flows/ValidateTask.cpp b/api/logic/minecraft/auth/flows/ValidateTask.cpp
index f82e6542..b6d6c823 100644
--- a/api/logic/minecraft/auth/flows/ValidateTask.cpp
+++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp
@@ -1,5 +1,5 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/auth/flows/ValidateTask.h b/api/logic/minecraft/auth/flows/ValidateTask.h
index a93bc76e..77de24c7 100644
--- a/api/logic/minecraft/auth/flows/ValidateTask.h
+++ b/api/logic/minecraft/auth/flows/ValidateTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/flame/FileResolvingTask.cpp b/api/logic/minecraft/flame/FileResolvingTask.cpp
index d55beb63..efc73621 100644
--- a/api/logic/minecraft/flame/FileResolvingTask.cpp
+++ b/api/logic/minecraft/flame/FileResolvingTask.cpp
@@ -48,7 +48,51 @@ void Flame::FileResolvingTask::netJobFinished()
continue;
}
out.fileName = Json::requireString(obj, "FileNameOnDisk");
- out.url = Json::requireString(obj, "DownloadURL");
+ QString rawUrl = Json::requireString(obj, "DownloadURL");
+ out.url = QUrl(rawUrl, QUrl::TolerantMode);
+ if(!out.url.isValid())
+ {
+ throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
+ }
+ // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
+ // It is also optional
+ QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
+ if(!projObj.isEmpty())
+ {
+ QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
+ if(strType == "singlefile")
+ {
+ out.type = File::Type::SingleFile;
+ }
+ else if(strType == "ctoc")
+ {
+ out.type = File::Type::Ctoc;
+ }
+ else if(strType == "cmod2")
+ {
+ out.type = File::Type::Cmod2;
+ }
+ else if(strType == "mod")
+ {
+ out.type = File::Type::Mod;
+ }
+ else if(strType == "folder")
+ {
+ out.type = File::Type::Folder;
+ }
+ else if(strType == "modpack")
+ {
+ out.type = File::Type::Modpack;
+ }
+ else
+ {
+ qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType;
+ out.type = File::Type::Unknown;
+ failed = true;
+ continue;
+ }
+ out.targetFolder = Json::ensureString(projObj, "Path", "mods");
+ }
out.resolved = true;
}
catch(JSONValidationError & e)
diff --git a/api/logic/minecraft/flame/PackManifest.cpp b/api/logic/minecraft/flame/PackManifest.cpp
index 62921493..6a9324fe 100644
--- a/api/logic/minecraft/flame/PackManifest.cpp
+++ b/api/logic/minecraft/flame/PackManifest.cpp
@@ -5,7 +5,6 @@ static void loadFileV1(Flame::File & f, QJsonObject & file)
{
f.projectId = Json::requireInteger(file, "projectID");
f.fileId = Json::requireInteger(file, "fileID");
- // FIXME: what does this mean?
f.required = Json::ensureBoolean(file, QString("required"), true);
}
diff --git a/api/logic/minecraft/flame/PackManifest.h b/api/logic/minecraft/flame/PackManifest.h
index ae91bffb..1a5254a8 100644
--- a/api/logic/minecraft/flame/PackManifest.h
+++ b/api/logic/minecraft/flame/PackManifest.h
@@ -2,6 +2,7 @@
#include <QString>
#include <QVector>
+#include <QUrl>
namespace Flame
{
@@ -9,12 +10,24 @@ struct File
{
int projectId = 0;
int fileId = 0;
+ // NOTE: the opposite to 'optional'. This is at the time of writing unused.
bool required = true;
// our
bool resolved = false;
QString fileName;
- QString url;
+ QUrl url;
+ QString targetFolder = QLatin1Literal("mods");
+ enum class Type
+ {
+ Unknown,
+ Folder,
+ Ctoc,
+ SingleFile,
+ Cmod2,
+ Modpack,
+ Mod
+ } type = Type::Mod;
};
struct Modloader
diff --git a/api/logic/minecraft/forge/ForgeXzDownload.cpp b/api/logic/minecraft/forge/ForgeXzDownload.cpp
index 593aa24f..a05d0f8d 100644
--- a/api/logic/minecraft/forge/ForgeXzDownload.cpp
+++ b/api/logic/minecraft/forge/ForgeXzDownload.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/forge/ForgeXzDownload.h b/api/logic/minecraft/forge/ForgeXzDownload.h
index ef23809b..cee402ef 100644
--- a/api/logic/minecraft/forge/ForgeXzDownload.h
+++ b/api/logic/minecraft/forge/ForgeXzDownload.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/ftb/FTBInstanceProvider.cpp b/api/logic/minecraft/ftb/FTBInstanceProvider.cpp
deleted file mode 100644
index fe23a84e..00000000
--- a/api/logic/minecraft/ftb/FTBInstanceProvider.cpp
+++ /dev/null
@@ -1,262 +0,0 @@
-#include "FTBInstanceProvider.h"
-
-#include <QDir>
-#include <QDebug>
-#include <QXmlStreamReader>
-#include <QRegularExpression>
-
-#include <settings/INISettingsObject.h>
-#include <FileSystem.h>
-
-#include "Env.h"
-
-#include "LegacyFTBInstance.h"
-#include "OneSixFTBInstance.h"
-
-inline uint qHash(FTBRecord record)
-{
- return qHash(record.instanceDir);
-}
-
-FTBInstanceProvider::FTBInstanceProvider(SettingsObjectPtr settings)
- : BaseInstanceProvider(settings)
-{
- // nil
-}
-
-QList<InstanceId> FTBInstanceProvider::discoverInstances()
-{
- // nothing to load when we don't have
- if (m_globalSettings->get("TrackFTBInstances").toBool() != true)
- {
- return {};
- }
- m_records.clear();
- discoverFTBEntries();
- return m_records.keys();
-}
-
-InstancePtr FTBInstanceProvider::loadInstance(const InstanceId& id)
-{
- // process the records we acquired.
- auto iter = m_records.find(id);
- if(iter == m_records.end())
- {
- qWarning() << "Cannot load instance" << id << "without a record";
- return nullptr;
- }
- auto & record = m_records[id];
- qDebug() << "Loading FTB instance from " << record.instanceDir;
- QString iconKey = record.iconKey;
- auto icons = ENV.icons();
- if(icons)
- {
- icons->addIcon(iconKey, iconKey, FS::PathCombine(record.templateDir, record.logo), IconType::Transient);
- }
- auto settingsFilePath = FS::PathCombine(record.instanceDir, "instance.cfg");
- qDebug() << "ICON get!";
-
- if (QFileInfo(settingsFilePath).exists())
- {
- auto instPtr = loadInstance(record);
- if (!instPtr)
- {
- qWarning() << "Couldn't load instance config:" << settingsFilePath;
- if(!QFile::remove(settingsFilePath))
- {
- qWarning() << "Couldn't remove broken instance config!";
- return nullptr;
- }
- // failed to load, but removed the poisonous file
- }
- else
- {
- return InstancePtr(instPtr);
- }
- }
- auto instPtr = createInstance(record);
- if (!instPtr)
- {
- qWarning() << "Couldn't create FTB instance!";
- return nullptr;
- }
- return InstancePtr(instPtr);
-}
-
-void FTBInstanceProvider::discoverFTBEntries()
-{
- QDir dir = QDir(m_globalSettings->get("FTBLauncherLocal").toString());
- QDir dataDir = QDir(m_globalSettings->get("FTBRoot").toString());
- if (!dataDir.exists())
- {
- qDebug() << "The FTB directory specified does not exist. Please check your settings";
- return;
- }
- else if (!dir.exists())
- {
- qDebug() << "The FTB launcher data directory specified does not exist. Please check "
- "your settings";
- return;
- }
- dir.cd("ModPacks");
- auto allFiles = dir.entryList(QDir::Readable | QDir::Files, QDir::Name);
- for (auto filename : allFiles)
- {
- if (!filename.endsWith(".xml"))
- continue;
- auto fpath = dir.absoluteFilePath(filename);
- QFile f(fpath);
- qDebug() << "Discovering FTB instances -- " << fpath;
- if (!f.open(QFile::ReadOnly))
- continue;
-
- // read the FTB packs XML.
- QXmlStreamReader reader(&f);
- while (!reader.atEnd())
- {
- switch (reader.readNext())
- {
- case QXmlStreamReader::StartElement:
- {
- if (reader.name() == "modpack")
- {
- QXmlStreamAttributes attrs = reader.attributes();
- FTBRecord record;
- record.dirName = attrs.value("dir").toString();
- record.instanceDir = dataDir.absoluteFilePath(record.dirName);
- record.templateDir = dir.absoluteFilePath(record.dirName);
- QDir test(record.instanceDir);
- qDebug() << dataDir.absolutePath() << record.instanceDir << record.dirName;
- if (!test.exists())
- continue;
- record.name = attrs.value("name").toString();
- record.logo = attrs.value("logo").toString();
- QString logo = record.logo;
- record.iconKey = logo.remove(QRegularExpression("\\..*"));
- auto customVersions = attrs.value("customMCVersions");
- if (!customVersions.isNull())
- {
- QMap<QString, QString> versionMatcher;
- QString customVersionsStr = customVersions.toString();
- QStringList list = customVersionsStr.split(';');
- for (auto item : list)
- {
- auto segment = item.split('^');
- if (segment.size() != 2)
- {
- qCritical() << "FTB: Segment of size < 2 in "
- << customVersionsStr;
- continue;
- }
- versionMatcher[segment[0]] = segment[1];
- }
- auto actualVersion = attrs.value("version").toString();
- if (versionMatcher.contains(actualVersion))
- {
- record.mcVersion = versionMatcher[actualVersion];
- }
- else
- {
- record.mcVersion = attrs.value("mcVersion").toString();
- }
- }
- else
- {
- record.mcVersion = attrs.value("mcVersion").toString();
- }
- record.description = attrs.value("description").toString();
- auto id = "FTB/" + record.dirName;
- m_records[id] = record;
- }
- break;
- }
- case QXmlStreamReader::EndElement:
- break;
- case QXmlStreamReader::Characters:
- break;
- default:
- break;
- }
- }
- f.close();
- }
-}
-
-InstancePtr FTBInstanceProvider::loadInstance(const FTBRecord & record) const
-{
- InstancePtr inst;
-
- auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg"));
- m_settings->registerSetting("InstanceType", "Legacy");
-
- qDebug() << "Loading existing " << record.name;
-
- QString inst_type = m_settings->get("InstanceType").toString();
- if (inst_type == "LegacyFTB")
- {
- inst.reset(new LegacyFTBInstance(m_globalSettings, m_settings, record.instanceDir));
- }
- else if (inst_type == "OneSixFTB")
- {
- inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir));
- }
- else
- {
- return nullptr;
- }
- qDebug() << "Construction " << record.instanceDir;
-
- SettingsObject::Lock lock(inst->settings());
- inst->init();
- qDebug() << "Init " << record.instanceDir;
- inst->setGroupInitial("FTB");
- /**
- * FIXME: this does not respect the user's preferences. BUT, it would work nicely with the planned pack support
- * -> instead of changing the user values, change pack values (defaults you can look at and revert to)
- */
- /*
- inst->setName(record.name);
- inst->setIconKey(record.iconKey);
- inst->setNotes(record.description);
- */
- if (inst->intendedVersionId() != record.mcVersion)
- {
- inst->setIntendedVersionId(record.mcVersion);
- }
- qDebug() << "Loaded instance " << inst->name() << " from " << inst->instanceRoot();
- return inst;
-}
-
-InstancePtr FTBInstanceProvider::createInstance(const FTBRecord & record) const
-{
- QDir rootDir(record.instanceDir);
-
- InstancePtr inst;
-
- qDebug() << "Converting " << record.name << " as new.";
-
- if (!rootDir.exists() && !rootDir.mkpath("."))
- {
- qCritical() << "Can't create instance folder" << record.instanceDir;
- return nullptr;
- }
-
- auto m_settings = std::make_shared<INISettingsObject>(FS::PathCombine(record.instanceDir, "instance.cfg"));
- m_settings->registerSetting("InstanceType", "Legacy");
-
- // all legacy versions are built in. therefore we can do this even if we don't have ALL the versions Mojang has on their servers.
- m_settings->set("InstanceType", "OneSixFTB");
- inst.reset(new OneSixFTBInstance(m_globalSettings, m_settings, record.instanceDir));
-
- // initialize
- {
- SettingsObject::Lock lock(inst->settings());
- inst->setIntendedVersionId(record.mcVersion);
- inst->init();
- inst->setGroupInitial("FTB");
- inst->setName(record.name);
- inst->setIconKey(record.iconKey);
- inst->setNotes(record.description);
- }
- return inst;
-}
diff --git a/api/logic/minecraft/ftb/FTBInstanceProvider.h b/api/logic/minecraft/ftb/FTBInstanceProvider.h
deleted file mode 100644
index fb3ecb6c..00000000
--- a/api/logic/minecraft/ftb/FTBInstanceProvider.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include "BaseInstanceProvider.h"
-#include <QMap>
-
-class QFileSystemWatcher;
-
-struct MULTIMC_LOGIC_EXPORT FTBRecord
-{
- QString dirName;
- QString name;
- QString logo;
- QString iconKey;
- QString mcVersion;
- QString description;
- QString instanceDir;
- QString templateDir;
- bool operator==(const FTBRecord other) const
- {
- return instanceDir == other.instanceDir;
- }
-};
-
-class MULTIMC_LOGIC_EXPORT FTBInstanceProvider : public BaseInstanceProvider
-{
- Q_OBJECT
-
-public:
- FTBInstanceProvider (SettingsObjectPtr settings);
-
-public:
- QList<InstanceId> discoverInstances() override;
- InstancePtr loadInstance(const InstanceId& id) override;
- void loadGroupList() override {};
- void saveGroupList() override {};
-
-private: /* methods */
- void discoverFTBEntries();
- InstancePtr createInstance(const FTBRecord & record) const;
- InstancePtr loadInstance(const FTBRecord & record) const;
-
-
-private:
- QMap<InstanceId, FTBRecord> m_records;
-};
diff --git a/api/logic/minecraft/ftb/FTBPlugin.cpp b/api/logic/minecraft/ftb/FTBPlugin.cpp
deleted file mode 100644
index 541879a1..00000000
--- a/api/logic/minecraft/ftb/FTBPlugin.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-#include "FTBPlugin.h"
-#include <Env.h>
-#include "LegacyFTBInstance.h"
-#include "OneSixFTBInstance.h"
-#include <BaseInstance.h>
-#include <InstanceList.h>
-#include <settings/INISettingsObject.h>
-#include <FileSystem.h>
-
-#include <QDebug>
-#include <QRegularExpression>
-
-#ifdef Q_OS_WIN32
-#include <windows.h>
-static const int APPDATA_BUFFER_SIZE = 1024;
-#endif
-
-static QString getLocalCacheStorageLocation()
-{
- QString ftbDefault;
-#ifdef Q_OS_WIN32
- wchar_t buf[APPDATA_BUFFER_SIZE];
- if (GetEnvironmentVariableW(L"LOCALAPPDATA", buf, APPDATA_BUFFER_SIZE)) // local
- {
- ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher");
- }
- else if (GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE)) // roaming
- {
- ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher");
- }
- else
- {
- qCritical() << "Your LOCALAPPDATA and APPDATA folders are missing!"
- " If you are on windows, this means your system is broken.";
- }
-#elif defined(Q_OS_MAC)
- ftbDefault = FS::PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher");
-#else
- ftbDefault = QDir::home().absoluteFilePath(".ftblauncher");
-#endif
- return ftbDefault;
-}
-
-
-static QString getRoamingStorageLocation()
-{
- QString ftbDefault;
-#ifdef Q_OS_WIN32
- wchar_t buf[APPDATA_BUFFER_SIZE];
- QString cacheStorage;
- if (GetEnvironmentVariableW(L"APPDATA", buf, APPDATA_BUFFER_SIZE))
- {
- ftbDefault = QDir(QString::fromWCharArray(buf)).absoluteFilePath("ftblauncher");
- }
- else
- {
- qCritical() << "Your APPDATA folder is missing! If you are on windows, this means your system is broken.";
- }
-#elif defined(Q_OS_MAC)
- ftbDefault = FS::PathCombine(QDir::homePath(), "Library/Application Support/ftblauncher");
-#else
- ftbDefault = QDir::home().absoluteFilePath(".ftblauncher");
-#endif
- return ftbDefault;
-}
-
-void FTBPlugin::initialize(SettingsObjectPtr globalSettings)
-{
- // FTB
- globalSettings->registerSetting("TrackFTBInstances", false);
- QString ftbRoaming = getRoamingStorageLocation();
- QString ftbLocal = getLocalCacheStorageLocation();
-
- globalSettings->registerSetting("FTBLauncherRoaming", ftbRoaming);
- globalSettings->registerSetting("FTBLauncherLocal", ftbLocal);
- qDebug() << "FTB Launcher paths:" << globalSettings->get("FTBLauncherRoaming").toString()
- << "and" << globalSettings->get("FTBLauncherLocal").toString();
-
- globalSettings->registerSetting("FTBRoot");
- if (globalSettings->get("FTBRoot").isNull())
- {
- QString ftbRoot;
- QFile f(QDir(globalSettings->get("FTBLauncherRoaming").toString()).absoluteFilePath("ftblaunch.cfg"));
- qDebug() << "Attempting to read" << f.fileName();
- if (f.open(QFile::ReadOnly))
- {
- const QString data = QString::fromLatin1(f.readAll());
- QRegularExpression exp("installPath=(.*)");
- ftbRoot = QDir::cleanPath(exp.match(data).captured(1));
-#ifdef Q_OS_WIN32
- if (!ftbRoot.isEmpty())
- {
- if (ftbRoot.at(0).isLetter() && ftbRoot.size() > 1 && ftbRoot.at(1) == '/')
- {
- ftbRoot.remove(1, 1);
- }
- }
-#endif
- if (ftbRoot.isEmpty())
- {
- qDebug() << "Failed to get FTB root path";
- }
- else
- {
- qDebug() << "FTB is installed at" << ftbRoot;
- globalSettings->set("FTBRoot", ftbRoot);
- }
- }
- else
- {
- qWarning() << "Couldn't open" << f.fileName() << ":" << f.errorString();
- qWarning() << "This is perfectly normal if you don't have FTB installed";
- }
- }
-}
diff --git a/api/logic/minecraft/ftb/FTBPlugin.h b/api/logic/minecraft/ftb/FTBPlugin.h
deleted file mode 100644
index e1b56545..00000000
--- a/api/logic/minecraft/ftb/FTBPlugin.h
+++ /dev/null
@@ -1,12 +0,0 @@
-#pragma once
-
-#include <BaseInstance.h>
-
-#include "multimc_logic_export.h"
-
-// Pseudo-plugin for FTB related things. Super derpy!
-class MULTIMC_LOGIC_EXPORT FTBPlugin
-{
-public:
- static void initialize(SettingsObjectPtr globalSettings);
-};
diff --git a/api/logic/minecraft/ftb/FTBProfileStrategy.cpp b/api/logic/minecraft/ftb/FTBProfileStrategy.cpp
deleted file mode 100644
index 9fa4e6a1..00000000
--- a/api/logic/minecraft/ftb/FTBProfileStrategy.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-#include "FTBProfileStrategy.h"
-#include "OneSixFTBInstance.h"
-
-#include <FileSystem.h>
-
-#include <QDir>
-#include <QUuid>
-#include <QJsonDocument>
-#include <QJsonArray>
-
-FTBProfileStrategy::FTBProfileStrategy(OneSixFTBInstance* instance) : OneSixProfileStrategy(instance)
-{
-}
-
-void FTBProfileStrategy::loadDefaultBuiltinPatches()
-{
- // FIXME: this should be here, but it needs us to be able to deal with multiple libraries paths
- // OneSixProfileStrategy::loadDefaultBuiltinPatches();
- auto mcVersion = m_instance->intendedVersionId();
- auto nativeInstance = dynamic_cast<OneSixFTBInstance *>(m_instance);
-
- ProfilePatchPtr minecraftPatch;
- {
- std::shared_ptr< VersionFile > file;
- auto mcJson = m_instance->versionsPath().absoluteFilePath(mcVersion + "/" + mcVersion + ".json");
- // load up the base minecraft patch
- if(QFile::exists(mcJson))
- {
- file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false);
- for(auto addLib: file->libraries)
- {
- addLib->setHint("local");
- addLib->setStoragePrefix(nativeInstance->librariesPath().absolutePath());
- }
- }
- else
- {
- file = std::make_shared<VersionFile>();
- file->addProblem(ProblemSeverity::Error, QObject::tr("Minecraft version is missing in the FTB data."));
- }
- file->uid = "net.minecraft";
- file->name = QObject::tr("Minecraft (tracked)");
- if(file->version.isEmpty())
- {
- file->version = mcVersion;
- }
- minecraftPatch = std::make_shared<ProfilePatch>(file);
- minecraftPatch->setVanilla(true);
- minecraftPatch->setOrder(-2);
- }
- profile->appendPatch(minecraftPatch);
-
- ProfilePatchPtr packPatch;
- {
- std::shared_ptr< VersionFile > file;
- auto mcJson = m_instance->minecraftRoot() + "/pack.json";
- // load up the base minecraft patch, if it's there...
- if(QFile::exists(mcJson))
- {
- file = ProfileUtils::parseJsonFile(QFileInfo(mcJson), false);
- // adapt the loaded file - the FTB patch file format is different than ours.
- file->minecraftVersion.clear();
- file->mainJar = nullptr;
- for(auto addLib: file->libraries)
- {
- addLib->setHint("local");
- addLib->setStoragePrefix(nativeInstance->librariesPath().absolutePath());
- }
- }
- else
- {
- file = std::make_shared<VersionFile>();
- file->addProblem(ProblemSeverity::Error, QObject::tr("Modpack version file is missing."));
- }
- file->uid = "org.multimc.ftb.pack";
- file->name = QObject::tr("%1 (FTB pack)").arg(m_instance->name());
- if(file->version.isEmpty())
- {
- file->version = QObject::tr("Unknown");
- QFile versionFile (FS::PathCombine(m_instance->instanceRoot(), "version"));
- if(versionFile.exists())
- {
- if(versionFile.open(QIODevice::ReadOnly))
- {
- // FIXME: just guessing the encoding/charset here.
- auto version = QString::fromUtf8(versionFile.readAll());
- file->version = version;
- }
- }
- }
- packPatch = std::make_shared<ProfilePatch>(file);
- packPatch->setVanilla(true);
- packPatch->setOrder(1);
- }
- profile->appendPatch(packPatch);
-}
-
-void FTBProfileStrategy::load()
-{
- profile->clearPatches();
-
- loadDefaultBuiltinPatches();
- loadUserPatches();
-}
-
-bool FTBProfileStrategy::saveOrder(ProfileUtils::PatchOrder order)
-{
- return false;
-}
-
-bool FTBProfileStrategy::resetOrder()
-{
- return false;
-}
-
-bool FTBProfileStrategy::installJarMods(QStringList filepaths)
-{
- return false;
-}
-
-bool FTBProfileStrategy::customizePatch(ProfilePatchPtr patch)
-{
- return false;
-}
-
-bool FTBProfileStrategy::revertPatch(ProfilePatchPtr patch)
-{
- return false;
-}
diff --git a/api/logic/minecraft/ftb/FTBProfileStrategy.h b/api/logic/minecraft/ftb/FTBProfileStrategy.h
deleted file mode 100644
index 522af098..00000000
--- a/api/logic/minecraft/ftb/FTBProfileStrategy.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#pragma once
-#include "minecraft/ProfileStrategy.h"
-#include "minecraft/onesix/OneSixProfileStrategy.h"
-
-class OneSixFTBInstance;
-
-class FTBProfileStrategy : public OneSixProfileStrategy
-{
-public:
- FTBProfileStrategy(OneSixFTBInstance * instance);
- virtual ~FTBProfileStrategy() {};
- virtual void load() override;
- virtual bool resetOrder() override;
- virtual bool saveOrder(ProfileUtils::PatchOrder order) override;
- virtual bool installJarMods(QStringList filepaths) override;
- virtual bool customizePatch (ProfilePatchPtr patch) override;
- virtual bool revertPatch (ProfilePatchPtr patch) override;
-
-protected:
- virtual void loadDefaultBuiltinPatches() override;
-};
diff --git a/api/logic/minecraft/ftb/LegacyFTBInstance.cpp b/api/logic/minecraft/ftb/LegacyFTBInstance.cpp
deleted file mode 100644
index 63412a33..00000000
--- a/api/logic/minecraft/ftb/LegacyFTBInstance.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-#include "LegacyFTBInstance.h"
-#include <settings/INISettingsObject.h>
-#include <QDir>
-
-LegacyFTBInstance::LegacyFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) :
- LegacyInstance(globalSettings, settings, rootDir)
-{
-}
-
-QString LegacyFTBInstance::id() const
-{
- return "FTB/" + BaseInstance::id();
-}
-
-void LegacyFTBInstance::copy(SettingsObjectPtr newSettings, const QDir& newDir)
-{
- // set the target instance to be plain Legacy
- newSettings->set("InstanceType", "Legacy");
-}
-
-QString LegacyFTBInstance::typeName() const
-{
- return tr("Legacy FTB");
-}
diff --git a/api/logic/minecraft/ftb/LegacyFTBInstance.h b/api/logic/minecraft/ftb/LegacyFTBInstance.h
deleted file mode 100644
index 2f2e34a2..00000000
--- a/api/logic/minecraft/ftb/LegacyFTBInstance.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include "minecraft/legacy/LegacyInstance.h"
-
-class LegacyFTBInstance : public LegacyInstance
-{
- Q_OBJECT
-public:
- explicit LegacyFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
- QString id() const override;
- void copy(SettingsObjectPtr newSettings, const QDir &newDir) override;
- QString typeName() const override;
- bool canExport() const override
- {
- return false;
- }
-};
diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp b/api/logic/minecraft/ftb/OneSixFTBInstance.cpp
deleted file mode 100644
index edf31eb7..00000000
--- a/api/logic/minecraft/ftb/OneSixFTBInstance.cpp
+++ /dev/null
@@ -1,135 +0,0 @@
-#include "OneSixFTBInstance.h"
-#include "FTBProfileStrategy.h"
-
-#include "minecraft/MinecraftProfile.h"
-#include "minecraft/GradleSpecifier.h"
-#include "tasks/SequentialTask.h"
-#include <settings/INISettingsObject.h>
-#include <FileSystem.h>
-
-#include <QJsonArray>
-
-OneSixFTBInstance::OneSixFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) :
- OneSixInstance(globalSettings, settings, rootDir)
-{
- m_globalSettings = globalSettings;
-}
-
-void OneSixFTBInstance::copy(SettingsObjectPtr newSettings, const QDir &newDir)
-{
- QStringList libraryNames;
- // create patch file
- {
- qDebug()<< "Creating patch file for FTB instance...";
- QFile f(minecraftRoot() + "/pack.json");
- if (!f.open(QFile::ReadOnly))
- {
- qCritical() << "Couldn't open" << f.fileName() << ":" << f.errorString();
- return;
- }
- QJsonObject root = QJsonDocument::fromJson(f.readAll()).object();
- QJsonArray libs = root.value("libraries").toArray();
- QJsonArray outLibs;
- for (auto lib : libs)
- {
- QJsonObject libObj = lib.toObject();
- libObj.insert("MMC-hint", QString("local"));
- libObj.insert("insert", QString("prepend"));
- libraryNames.append(libObj.value("name").toString());
- outLibs.append(libObj);
- }
- root.remove("libraries");
- root.remove("id");
-
- // HACK HACK HACK HACK
- // A workaround for a problem in MultiMC, triggered by a historical problem in FTB,
- // triggered by Mojang getting their library versions wrong in 1.7.10
- if(intendedVersionId() == "1.7.10")
- {
- auto insert = [&outLibs, &libraryNames](QString name)
- {
- QJsonObject libObj;
- libObj.insert("insert", QString("replace"));
- libObj.insert("name", name);
- libraryNames.push_back(name);
- outLibs.prepend(libObj);
- };
- insert("com.google.guava:guava:16.0");
- insert("org.apache.commons:commons-lang3:3.2.1");
- }
- root.insert("+libraries", outLibs);
- root.insert("order", 1);
- root.insert("fileId", QString("org.multimc.ftb.pack.json"));
- root.insert("name", name());
- root.insert("mcVersion", intendedVersionId());
- root.insert("version", intendedVersionId());
- FS::ensureFilePathExists(newDir.absoluteFilePath("patches/ftb.json"));
- QFile out(newDir.absoluteFilePath("patches/ftb.json"));
- if (!out.open(QFile::WriteOnly | QFile::Truncate))
- {
- qCritical() << "Couldn't open" << out.fileName() << ":" << out.errorString();
- return;
- }
- out.write(QJsonDocument(root).toJson());
- }
- // copy libraries
- {
- qDebug() << "Copying FTB libraries";
- for (auto library : libraryNames)
- {
- GradleSpecifier lib(library);
- const QString out = QDir::current().absoluteFilePath("libraries/" + lib.toPath());
- if (QFile::exists(out))
- {
- continue;
- }
- if (!FS::ensureFilePathExists(out))
- {
- qCritical() << "Couldn't create folder structure for" << out;
- }
- if (!QFile::copy(librariesPath().absoluteFilePath(lib.toPath()), out))
- {
- qCritical() << "Couldn't copy" << QString(lib);
- }
- }
- }
- // now set the target instance to be plain OneSix
- newSettings->set("InstanceType", "OneSix");
-}
-
-QString OneSixFTBInstance::id() const
-{
- return "FTB/" + BaseInstance::id();
-}
-
-QDir OneSixFTBInstance::librariesPath() const
-{
- return QDir(m_globalSettings->get("FTBRoot").toString() + "/libraries");
-}
-
-QDir OneSixFTBInstance::versionsPath() const
-{
- return QDir(m_globalSettings->get("FTBRoot").toString() + "/versions");
-}
-
-bool OneSixFTBInstance::providesVersionFile() const
-{
- return true;
-}
-
-void OneSixFTBInstance::createProfile()
-{
- m_profile.reset(new MinecraftProfile(new FTBProfileStrategy(this)));
-}
-
-shared_qobject_ptr<Task> OneSixFTBInstance::createUpdateTask()
-{
- return OneSixInstance::createUpdateTask();
-}
-
-QString OneSixFTBInstance::typeName() const
-{
- return tr("OneSix FTB");
-}
-
-#include "OneSixFTBInstance.moc"
diff --git a/api/logic/minecraft/ftb/OneSixFTBInstance.h b/api/logic/minecraft/ftb/OneSixFTBInstance.h
deleted file mode 100644
index 640f609c..00000000
--- a/api/logic/minecraft/ftb/OneSixFTBInstance.h
+++ /dev/null
@@ -1,30 +0,0 @@
-#pragma once
-
-#include "minecraft/onesix/OneSixInstance.h"
-
-class OneSixFTBInstance : public OneSixInstance
-{
- Q_OBJECT
-public:
- explicit OneSixFTBInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
- virtual ~OneSixFTBInstance(){};
-
- void copy(SettingsObjectPtr newSettings, const QDir &newDir) override;
-
- virtual void createProfile() override;
-
- virtual shared_qobject_ptr<Task> createUpdateTask() override;
-
- virtual QString id() const override;
-
- QDir librariesPath() const override;
- QDir versionsPath() const override;
- bool providesVersionFile() const override;
- virtual QString typeName() const override;
- bool canExport() const override
- {
- return false;
- }
-private:
- SettingsObjectPtr m_globalSettings;
-};
diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h
index 53f3cee9..de9007d1 100644
--- a/api/logic/minecraft/launch/ClaimAccount.h
+++ b/api/logic/minecraft/launch/ClaimAccount.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h
index 24daa81e..92026ecb 100644
--- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h
+++ b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp
index 07dbb86c..4ccc5c3c 100644
--- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp
+++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h
index 585b5c74..19087b50 100644
--- a/api/logic/minecraft/launch/DirectJavaLaunch.h
+++ b/api/logic/minecraft/launch/DirectJavaLaunch.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp
index f30dea89..7ddde374 100644
--- a/api/logic/minecraft/launch/ExtractNatives.cpp
+++ b/api/logic/minecraft/launch/ExtractNatives.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h
index 0ec021a9..6e1e7cd4 100644
--- a/api/logic/minecraft/launch/ExtractNatives.h
+++ b/api/logic/minecraft/launch/ExtractNatives.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.cpp b/api/logic/minecraft/launch/LauncherPartLaunch.cpp
index b641968c..1fe9c323 100644
--- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp
+++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,32 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent)
connect(&m_process, &LoggedProcess::stateChanged, this, &LauncherPartLaunch::on_state);
}
+#ifdef Q_OS_WIN
+// returns 8.3 file format from long path
+#include <windows.h>
+QString shortPathName(const QString & file)
+{
+ auto input = file.toStdWString();
+ std::wstring output;
+ long length = GetShortPathNameW(input.c_str(), NULL, 0);
+ // NOTE: this resizing might seem weird...
+ // when GetShortPathNameW fails, it returns length including null character
+ // when it succeeds, it returns length excluding null character
+ // See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa364989(v=vs.85).aspx
+ output.resize(length);
+ GetShortPathNameW(input.c_str(),(LPWSTR)output.c_str(),length);
+ output.resize(length-1);
+ QString ret = QString::fromStdWString(output);
+ return ret;
+}
+#endif
+
+// if the string survives roundtrip through local 8bit encoding...
+bool fitsInLocal8bit(const QString & string)
+{
+ return string == QString::fromLocal8Bit(string.toLocal8Bit());
+}
+
void LauncherPartLaunch::executeTask()
{
auto instance = m_parent->instance();
@@ -45,7 +71,44 @@ void LauncherPartLaunch::executeTask()
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
- args << "-jar" << FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar");
+ auto classPath = minecraftInstance->getClassPath();
+ classPath.prepend(FS::PathCombine(ENV.getJarsPath(), "NewLaunch.jar"));
+
+ auto natPath = minecraftInstance->getNativePath();
+#ifdef Q_OS_WIN
+ if (!fitsInLocal8bit(natPath))
+ {
+ args << "-Djava.library.path=" + shortPathName(natPath);
+ }
+ else
+ {
+ args << "-Djava.library.path=" + natPath;
+ }
+#else
+ args << "-Djava.library.path=" + natPath;
+#endif
+
+ args << "-cp";
+#ifdef Q_OS_WIN
+ QStringList processed;
+ for(auto & item: classPath)
+ {
+ if (!fitsInLocal8bit(item))
+ {
+ processed << shortPathName(item);
+ }
+ else
+ {
+ processed << item;
+ }
+ }
+ args << processed.join(';');
+#else
+ args << classPath.join(':');
+#endif
+ args << "org.multimc.EntryPoint";
+
+ qDebug() << args.join(' ');
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
if(!wrapperCommandStr.isEmpty())
diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h
index 01c73895..d384c2d1 100644
--- a/api/logic/minecraft/launch/LauncherPartLaunch.h
+++ b/api/logic/minecraft/launch/LauncherPartLaunch.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp
index dad41a98..34825b45 100644
--- a/api/logic/minecraft/launch/ModMinecraftJar.cpp
+++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 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,31 +14,69 @@
*/
#include "ModMinecraftJar.h"
-#include <launch/LaunchTask.h>
-#include <QStandardPaths>
+#include "launch/LaunchTask.h"
+#include "MMCZip.h"
+#include "minecraft/OpSys.h"
+#include "FileSystem.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
void ModMinecraftJar::executeTask()
{
- m_jarModTask = m_parent->instance()->createJarModdingTask();
- if(m_jarModTask)
+ auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+
+ if(!m_inst->getJarMods().size())
{
- connect(m_jarModTask.get(), SIGNAL(finished()), this, SLOT(jarModdingFinished()));
- m_jarModTask->start();
+ emitSucceeded();
return;
}
+ // nuke obsolete stripped jar(s) if needed
+ if(!FS::ensureFolderPathExists(m_inst->binRoot()))
+ {
+ emitFailed(tr("Couldn't create the bin folder for Minecraft.jar"));
+ }
+
+ auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
+ if(!removeJar())
+ {
+ emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath));
+ }
+
+ // create temporary modded jar, if needed
+ auto components = m_inst->getComponentList();
+ auto profile = components->getProfile();
+ auto jarMods = m_inst->getJarMods();
+ if(jarMods.size())
+ {
+ auto mainJar = profile->getMainJar();
+ QStringList jars, temp1, temp2, temp3, temp4;
+ mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath());
+ auto sourceJarPath = jars[0];
+ if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods))
+ {
+ emitFailed(tr("Failed to create the custom Minecraft jar file."));
+ return;
+ }
+ }
emitSucceeded();
}
-void ModMinecraftJar::jarModdingFinished()
+void ModMinecraftJar::finalize()
{
- if(m_jarModTask->successful())
- {
- emitSucceeded();
- }
- else
+ removeJar();
+}
+
+bool ModMinecraftJar::removeJar()
+{
+ auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+ auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
+ QFile finalJar(finalJarPath);
+ if(finalJar.exists())
{
- QString reason = tr("jar modding failed because: %1.\n\n").arg(m_jarModTask->failReason());
- emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
+ if(!finalJar.remove())
+ {
+ return false;
+ }
}
+ return true;
}
diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h
index 1196c487..b9a2d35b 100644
--- a/api/logic/minecraft/launch/ModMinecraftJar.h
+++ b/api/logic/minecraft/launch/ModMinecraftJar.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
#include <launch/LaunchStep.h>
#include <memory>
-// FIXME: temporary wrapper for existing task.
class ModMinecraftJar: public LaunchStep
{
Q_OBJECT
@@ -31,9 +30,7 @@ public:
{
return false;
}
-private slots:
- void jarModdingFinished();
-
+ void finalize() override;
private:
- std::shared_ptr<Task> m_jarModTask;
+ bool removeJar();
};
diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp
index a9a87955..83bf584f 100644
--- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp
+++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,13 +13,73 @@
* limitations under the License.
*/
+#include <fstream>
+#include <string>
+
#include "PrintInstanceInfo.h"
#include <launch/LaunchTask.h>
void PrintInstanceInfo::executeTask()
{
- auto instance = m_parent->instance();
- auto lines = instance->verboseDescription(m_session);
- logLines(lines, MessageLevel::MultiMC);
- emitSucceeded();
+ auto instance = m_parent->instance();
+ auto lines = instance->verboseDescription(m_session);
+
+#ifdef Q_OS_LINUX
+ std::ifstream cpuin("/proc/cpuinfo");
+ for (std::string line; std::getline(cpuin, line);)
+ {
+ if (strncmp(line.c_str(), "model name", 10) == 0)
+ {
+ QStringList clines = (QStringList() << QString::fromStdString(line.substr(13, std::string::npos)));
+ logLines(clines, MessageLevel::MultiMC);
+ break;
+ }
+ }
+
+ char buff[512];
+ int gpuline = -1;
+ int cline = 0;
+ FILE *fp = popen("lspci -k", "r");
+ if (fp != NULL)
+ {
+ while (fgets(buff, 512, fp) != NULL)
+ {
+ std::string str(buff);
+ if (str.length() < 9)
+ continue;
+ if (str.substr(8, 3) == "VGA")
+ {
+ gpuline = cline;
+ QStringList glines = (QStringList() << QString::fromStdString(str.substr(35, std::string::npos)));
+ logLines(glines, MessageLevel::MultiMC);
+ }
+ if (gpuline > -1 && gpuline != cline)
+ {
+ if (cline - gpuline < 3)
+ {
+ QStringList alines = (QStringList() << QString::fromStdString(str.substr(1, std::string::npos)));
+ logLines(alines, MessageLevel::MultiMC);
+ }
+ }
+ cline++;
+ }
+ }
+
+ FILE *fp2 = popen("glxinfo", "r");
+ if (fp2 != NULL)
+ {
+ while (fgets(buff, 512, fp2) != NULL)
+ {
+ if (strncmp(buff, "OpenGL version string:", 22) == 0)
+ {
+ QStringList drlines = (QStringList() << QString::fromUtf8(buff));
+ logLines(drlines, MessageLevel::MultiMC);
+ break;
+ }
+ }
+ }
+#endif
+
+ logLines(lines, MessageLevel::MultiMC);
+ emitSucceeded();
}
diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h
index 6aed0865..61615ba1 100644
--- a/api/logic/minecraft/launch/PrintInstanceInfo.h
+++ b/api/logic/minecraft/launch/PrintInstanceInfo.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp
index 0987d56f..6e318458 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.cpp
+++ b/api/logic/minecraft/legacy/LegacyInstance.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
#include "LegacyInstance.h"
-#include "minecraft/legacy/LegacyUpdate.h"
#include "minecraft/legacy/LegacyModList.h"
#include "minecraft/ModList.h"
#include "minecraft/WorldList.h"
@@ -28,14 +27,12 @@
#include <FileSystem.h>
LegacyInstance::LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
- : MinecraftInstance(globalSettings, settings, rootDir)
+ : BaseInstance(globalSettings, settings, rootDir)
{
- m_lwjglFolderSetting = globalSettings->getSetting("LWJGLDir");
settings->registerSetting("NeedsRebuild", true);
settings->registerSetting("ShouldUpdate", false);
- settings->registerSetting("JarVersion", "Unknown");
- settings->registerSetting("LwjglVersion", "2.9.0");
- settings->registerSetting("IntendedJarVersion", "");
+ settings->registerSetting("JarVersion", QString());
+ settings->registerSetting("IntendedJarVersion", QString());
/*
* custom base jar has no default. it is determined in code... see the accessor methods for
*it
@@ -68,178 +65,102 @@ QString LegacyInstance::customBaseJar() const
return value;
}
-void LegacyInstance::setCustomBaseJar(QString val)
-{
- if (val.isNull() || val.isEmpty() || val == defaultCustomBaseJar())
- m_settings->reset("CustomBaseJar");
- else
- m_settings->set("CustomBaseJar", val);
-}
-
-void LegacyInstance::setShouldUseCustomBaseJar(bool val)
-{
- m_settings->set("UseCustomBaseJar", val);
-}
-
bool LegacyInstance::shouldUseCustomBaseJar() const
{
return m_settings->get("UseCustomBaseJar").toBool();
}
-shared_qobject_ptr<Task> LegacyInstance::createUpdateTask()
+shared_qobject_ptr<Task> LegacyInstance::createUpdateTask(Net::Mode)
{
- // make sure the jar mods list is initialized by asking for it.
- auto list = jarModList();
- // create an update task
- return shared_qobject_ptr<Task>(new LegacyUpdate(this, this));
+ return nullptr;
}
-std::shared_ptr<Task> LegacyInstance::createJarModdingTask()
+/*
+class LegacyJarModTask : public Task
{
- class JarModTask : public Task
+ //Q_OBJECT
+public:
+ explicit LegacyJarModTask(std::shared_ptr<LegacyInstance> inst) : Task(nullptr), m_inst(inst)
+ {
+ }
+ virtual void executeTask()
{
- public:
- explicit JarModTask(std::shared_ptr<LegacyInstance> inst) : Task(nullptr), m_inst(inst)
+ if (!m_inst->shouldRebuild())
{
+ emitSucceeded();
+ return;
}
- virtual void executeTask()
- {
- if (!m_inst->shouldRebuild())
- {
- emitSucceeded();
- return;
- }
- // Get the mod list
- auto modList = m_inst->getJarMods();
+ // Get the mod list
+ auto modList = m_inst->getJarMods();
- QFileInfo runnableJar(m_inst->runnableJar());
- QFileInfo baseJar(m_inst->baseJar());
- bool base_is_custom = m_inst->shouldUseCustomBaseJar();
+ QFileInfo runnableJar(m_inst->runnableJar());
+ QFileInfo baseJar(m_inst->baseJar());
+ bool base_is_custom = m_inst->shouldUseCustomBaseJar();
- // Nothing to do if there are no jar mods to install, no backup and just the mc jar
- if (base_is_custom)
- {
- // yes, this can happen if the instance only has the runnable jar and not the base jar
- // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe
- // because that's not something mmc4 guarantees
- if (runnableJar.isFile() && !baseJar.exists() && modList.empty())
- {
- m_inst->setShouldRebuild(false);
- emitSucceeded();
- return;
- }
-
- setStatus(tr("Installing mods: Backing up minecraft.jar ..."));
- if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath()))
- {
- emitFailed("It seems both the active and base jar are gone. A fresh base jar will "
- "be used on next run.");
- m_inst->setShouldRebuild(true);
- m_inst->setShouldUpdate(true);
- m_inst->setShouldUseCustomBaseJar(false);
- return;
- }
- }
-
- if (!baseJar.exists())
+ // Nothing to do if there are no jar mods to install, no backup and just the mc jar
+ if (base_is_custom)
+ {
+ // yes, this can happen if the instance only has the runnable jar and not the base jar
+ // it *could* be assumed that such an instance is vanilla, but that wouldn't be safe
+ // because that's not something mmc4 guarantees
+ if (runnableJar.isFile() && !baseJar.exists() && modList.empty())
{
- emitFailed("The base jar " + baseJar.filePath() + " does not exist");
+ m_inst->setShouldRebuild(false);
+ emitSucceeded();
return;
}
- if (runnableJar.exists() && !QFile::remove(runnableJar.filePath()))
+ setStatus(tr("Installing mods: Backing up minecraft.jar ..."));
+ if (!baseJar.exists() && !QFile::copy(runnableJar.filePath(), baseJar.filePath()))
{
- emitFailed("Failed to delete old minecraft.jar");
+ emitFailed("It seems both the active and base jar are gone. A fresh base jar will "
+ "be used on next run.");
+ m_inst->setShouldRebuild(true);
+ m_inst->setShouldUpdate(true);
+ m_inst->setShouldUseCustomBaseJar(false);
return;
}
+ }
- setStatus(tr("Installing mods: Opening minecraft.jar ..."));
-
- QString outputJarPath = runnableJar.filePath();
- QString inputJarPath = baseJar.filePath();
-
- if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList))
- {
- emitFailed(tr("Failed to create the custom Minecraft jar file."));
- return;
- }
- m_inst->setShouldRebuild(false);
- // inst->UpdateVersion(true);
- emitSucceeded();
+ if (!baseJar.exists())
+ {
+ emitFailed("The base jar " + baseJar.filePath() + " does not exist");
return;
-
}
- std::shared_ptr<LegacyInstance> m_inst;
- };
- return std::make_shared<JarModTask>(std::dynamic_pointer_cast<LegacyInstance>(shared_from_this()));
-}
-QString LegacyInstance::createLaunchScript(AuthSessionPtr session)
-{
- QString launchScript;
-
- // window size
- QString windowParams;
- if (settings()->get("LaunchMaximized").toBool())
- {
- windowParams = "max";
- }
- else
- {
- windowParams = QString("%1x%2").arg(settings()->get("MinecraftWinWidth").toInt()).arg(settings()->get("MinecraftWinHeight").toInt());
- }
-
- QString lwjgl = QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath();
- launchScript += "userName " + session->player_name + "\n";
- launchScript += "sessionId " + session->session + "\n";
- launchScript += "windowTitle " + windowTitle() + "\n";
- launchScript += "windowParams " + windowParams + "\n";
- launchScript += "cp bin/minecraft.jar\n";
- launchScript += "cp " + lwjgl + "/lwjgl.jar\n";
- launchScript += "cp " + lwjgl + "/lwjgl_util.jar\n";
- launchScript += "cp " + lwjgl + "/jinput.jar\n";
- launchScript += "natives " + lwjgl + "/natives\n";
- launchScript += "traits legacyLaunch\n";
- launchScript += "launcher onesix\n";
- return launchScript;
-}
+ if (runnableJar.exists() && !QFile::remove(runnableJar.filePath()))
+ {
+ emitFailed("Failed to delete old minecraft.jar");
+ return;
+ }
-std::shared_ptr<LaunchStep> LegacyInstance::createMainLaunchStep(LaunchTask * parent, AuthSessionPtr session)
-{
- auto step = std::make_shared<LauncherPartLaunch>(parent);
- step->setWorkingDirectory(minecraftRoot());
- step->setAuthSession(session);
- return step;
-}
+ setStatus(tr("Installing mods: Opening minecraft.jar ..."));
-QString LegacyInstance::launchMethod()
-{
- return "Legacy";
-}
+ QString outputJarPath = runnableJar.filePath();
+ QString inputJarPath = baseJar.filePath();
-QStringList LegacyInstance::validLaunchMethods()
-{
- return {"Legacy"};
-}
+ if(!MMCZip::createModdedJar(inputJarPath, outputJarPath, modList))
+ {
+ emitFailed(tr("Failed to create the custom Minecraft jar file."));
+ return;
+ }
+ m_inst->setShouldRebuild(false);
+ // inst->UpdateVersion(true);
+ emitSucceeded();
+ return;
-std::shared_ptr<ModList> LegacyInstance::coreModList() const
-{
- if (!core_mod_list)
- {
- core_mod_list.reset(new ModList(coreModsDir()));
}
- core_mod_list->update();
- return core_mod_list;
-}
+ std::shared_ptr<LegacyInstance> m_inst;
+};
+*/
std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const
{
if (!jar_mod_list)
{
auto list = new LegacyModList(jarModsDir(), modListFile());
- connect(list, SIGNAL(changed()), SLOT(jarModsChanged()));
jar_mod_list.reset(list);
}
jar_mod_list->update();
@@ -251,39 +172,20 @@ QList<Mod> LegacyInstance::getJarMods() const
return jarModList()->allMods();
}
-void LegacyInstance::jarModsChanged()
+QString LegacyInstance::minecraftRoot() const
{
- qDebug() << "Jar mods of instance " << name() << " have changed. Jar will be rebuilt.";
- setShouldRebuild(true);
-}
+ QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
+ QFileInfo dotMCDir(FS::PathCombine(instanceRoot(), ".minecraft"));
-std::shared_ptr<ModList> LegacyInstance::loaderModList() const
-{
- if (!loader_mod_list)
- {
- loader_mod_list.reset(new ModList(loaderModsDir()));
- }
- loader_mod_list->update();
- return loader_mod_list;
+ if (mcDir.exists() && !dotMCDir.exists())
+ return mcDir.filePath();
+ else
+ return dotMCDir.filePath();
}
-std::shared_ptr<ModList> LegacyInstance::texturePackList() const
+QString LegacyInstance::binRoot() const
{
- if (!texture_pack_list)
- {
- texture_pack_list.reset(new ModList(texturePacksDir()));
- }
- texture_pack_list->update();
- return texture_pack_list;
-}
-
-std::shared_ptr<WorldList> LegacyInstance::worldList() const
-{
- if (!m_world_list)
- {
- m_world_list.reset(new WorldList(savesDir()));
- }
- return m_world_list;
+ return FS::PathCombine(minecraftRoot(), "bin");
}
QString LegacyInstance::jarModsDir() const
@@ -340,38 +242,16 @@ bool LegacyInstance::shouldRebuild() const
return m_settings->get("NeedsRebuild").toBool();
}
-void LegacyInstance::setShouldRebuild(bool val)
-{
- m_settings->set("NeedsRebuild", val);
-}
-
QString LegacyInstance::currentVersionId() const
{
return m_settings->get("JarVersion").toString();
}
-QString LegacyInstance::lwjglVersion() const
-{
- return m_settings->get("LwjglVersion").toString();
-}
-
-void LegacyInstance::setLWJGLVersion(QString val)
-{
- m_settings->set("LwjglVersion", val);
-}
-
QString LegacyInstance::intendedVersionId() const
{
return m_settings->get("IntendedJarVersion").toString();
}
-bool LegacyInstance::setIntendedVersionId(QString version)
-{
- settings()->set("IntendedJarVersion", version);
- setShouldUpdate(true);
- return true;
-}
-
bool LegacyInstance::shouldUpdate() const
{
QVariant var = settings()->get("ShouldUpdate");
@@ -382,11 +262,6 @@ bool LegacyInstance::shouldUpdate() const
return true;
}
-void LegacyInstance::setShouldUpdate(bool val)
-{
- settings()->set("ShouldUpdate", val);
-}
-
QString LegacyInstance::defaultBaseJar() const
{
return "versions/" + intendedVersionId() + "/" + intendedVersionId() + ".jar";
@@ -397,9 +272,13 @@ QString LegacyInstance::defaultCustomBaseJar() const
return FS::PathCombine(binRoot(), "mcbackup.jar");
}
-QString LegacyInstance::lwjglFolder() const
+std::shared_ptr<WorldList> LegacyInstance::worldList() const
{
- return m_lwjglFolderSetting->get().toString();
+ if (!m_world_list)
+ {
+ m_world_list.reset(new WorldList(savesDir()));
+ }
+ return m_world_list;
}
QString LegacyInstance::typeName() const
@@ -407,6 +286,11 @@ QString LegacyInstance::typeName() const
return tr("Legacy");
}
+QString LegacyInstance::getStatusbarDescription()
+{
+ return tr("Instance from previous versions.");
+}
+
QStringList LegacyInstance::verboseDescription(AuthSessionPtr session)
{
QStringList out;
@@ -422,48 +306,6 @@ QStringList LegacyInstance::verboseDescription(AuthSessionPtr session)
out << "";
}
- if(loaderModList()->size())
- {
- out << "Mods:";
- for(auto & mod: loaderModList()->allMods())
- {
- if(!mod.enabled())
- continue;
- if(mod.type() == Mod::MOD_FOLDER)
- continue;
- // TODO: proper implementation would need to descend into folders.
-
- out << " " + mod.filename().completeBaseName();
- }
- out << "";
- }
-
- if(coreModList()->size())
- {
- out << "Core Mods:";
- for(auto & coremod: coreModList()->allMods())
- {
- if(!coremod.enabled())
- continue;
- if(coremod.type() == Mod::MOD_FOLDER)
- continue;
- // TODO: proper implementation would need to descend into folders.
-
- out << " " + coremod.filename().completeBaseName();
- }
- out << "";
- }
-
- if(jarModList()->size())
- {
- out << "Jar Mods:";
- for(auto & jarmod: jarModList()->allMods())
- {
- out << " " + jarmod.name() + " (" + jarmod.filename().filePath() + ")";
- }
- out << "";
- }
-
QString windowParams;
if (settings()->get("LaunchMaximized").toBool())
{
@@ -478,40 +320,3 @@ QStringList LegacyInstance::verboseDescription(AuthSessionPtr session)
out << "";
return out;
}
-
-QStringList LegacyInstance::getClassPath() const
-{
- QString launchScript;
- QString lwjgl = getNativePath();
- QStringList out =
- {
- "bin/minecraft.jar",
- lwjgl + "/lwjgl.jar",
- lwjgl + "/lwjgl_util.jar",
- lwjgl + "/jinput.jar"
- };
- return out;
-}
-
-QString LegacyInstance::getMainClass() const
-{
- return "net.minecraft.client.Minecraft";
-}
-
-QString LegacyInstance::getNativePath() const
-{
- return QDir(m_lwjglFolderSetting->get().toString() + "/" + lwjglVersion()).absolutePath();
-}
-
-QStringList LegacyInstance::getNativeJars() const
-{
- return {};
-}
-
-QStringList LegacyInstance::processMinecraftArgs(AuthSessionPtr account) const
-{
- QStringList out;
- out.append(account->player_name);
- out.append(account->session);
- return out;
-}
diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h
index 15d1383f..4a8bc436 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.h
+++ b/api/logic/minecraft/legacy/LegacyInstance.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 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,22 +15,27 @@
#pragma once
-#include "minecraft/MinecraftInstance.h"
+#include "BaseInstance.h"
+#include "minecraft/Mod.h"
#include "multimc_logic_export.h"
class ModList;
class LegacyModList;
+class WorldList;
class Task;
-
-class MULTIMC_LOGIC_EXPORT LegacyInstance : public MinecraftInstance
+/*
+ * WHY: Legacy instances - from MultiMC 3 and 4 - are here only to provide a way to upgrade them to the current format.
+ */
+class MULTIMC_LOGIC_EXPORT LegacyInstance : public BaseInstance
{
Q_OBJECT
public:
explicit LegacyInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
- virtual void init() override {};
+ virtual void init() override {}
+ virtual void saveNow() override {}
/// Path to the instance's minecraft.jar
QString runnableJar() const;
@@ -38,20 +43,6 @@ 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<LegacyModList> jarModList() const ;
- virtual QList< Mod > getJarMods() const override;
- std::shared_ptr<ModList> coreModList() const;
- std::shared_ptr<ModList> loaderModList() const;
- std::shared_ptr<ModList> texturePackList() const override;
- std::shared_ptr<WorldList> worldList() const override;
-
////// Directories //////
QString libDir() const;
QString savesDir() const;
@@ -61,8 +52,10 @@ public:
QString coreModsDir() const;
QString resourceDir() const;
virtual QString instanceConfigFolder() const override;
+ QString minecraftRoot() const; // Path to the instance's minecraft directory.
+ QString binRoot() const; // Path to the instance's minecraft bin directory.
- /// Get the curent base jar of this instance. By default, it's the
+ /// Get the curent base jar of this instance. By default, it's the
/// versions/$version/$version.jar
QString baseJar() const;
@@ -75,13 +68,15 @@ public:
* Whether or not custom base jar is used
*/
bool shouldUseCustomBaseJar() const;
- void setShouldUseCustomBaseJar(bool val);
/*!
* The value of the custom base jar
*/
QString customBaseJar() const;
- void setCustomBaseJar(QString val);
+
+ std::shared_ptr<LegacyModList> jarModList() const;
+ QList<Mod> getJarMods() const;
+ std::shared_ptr<WorldList> worldList() const;
/*!
* Whether or not the instance's minecraft.jar needs to be rebuilt.
@@ -89,67 +84,57 @@ public:
* re-added to a fresh minecraft.jar file.
*/
bool shouldRebuild() const;
- void setShouldRebuild(bool val);
-
- virtual QString currentVersionId() const override;
-
- //! The version of LWJGL that this instance uses.
- QString lwjglVersion() const;
- //! Where the lwjgl versions foor this instance can be found... HACK HACK HACK
- QString lwjglFolder() const;
+ QString currentVersionId() const;
+ QString intendedVersionId() const;
- /// st the version of LWJGL libs this instance will use
- void setLWJGLVersion(QString val);
-
- virtual QString intendedVersionId() const override;
- virtual bool setIntendedVersionId(QString version) override;
-
- virtual QSet<QString> traits() override
+ QSet<QString> traits() const override
{
return {"legacy-instance", "texturepacks"};
};
- virtual bool shouldUpdate() const override;
- virtual void setShouldUpdate(bool val) override;
- virtual shared_qobject_ptr<Task> createUpdateTask() override;
- virtual std::shared_ptr<Task> createJarModdingTask() override;
- virtual QString createLaunchScript(AuthSessionPtr session) override;
+ virtual bool shouldUpdate() const;
+ virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
virtual QString typeName() const override;
- bool canExport() const override
+ bool canLaunch() const override
+ {
+ return false;
+ }
+ bool canEdit() const override
{
return true;
}
-
- QStringList getClassPath() const override;
- QString getMainClass() const override;
-
- QStringList getNativeJars() const override;
- QString getNativePath() const override;
-
- QString getLocalLibraryPath() const override
+ bool canExport() const override
+ {
+ return false;
+ }
+ std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
+ {
+ return nullptr;
+ }
+ IPathMatcher::Ptr getLogFileMatcher() override
+ {
+ return nullptr;
+ }
+ QString getLogFileRoot() override
{
- return QString();
+ return minecraftRoot();
}
- QStringList processMinecraftArgs(AuthSessionPtr account) const override;
+ QString getStatusbarDescription() override;
QStringList verboseDescription(AuthSessionPtr session) override;
-protected:
- std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) override;
- QStringList validLaunchMethods() override;
- QString launchMethod() override;
-
+ QProcessEnvironment createEnvironment() override
+ {
+ return QProcessEnvironment();
+ }
+ QMap<QString, QString> getVariables() const override
+ {
+ return {};
+ }
protected:
mutable std::shared_ptr<LegacyModList> jar_mod_list;
- mutable std::shared_ptr<ModList> core_mod_list;
- mutable std::shared_ptr<ModList> loader_mod_list;
- mutable std::shared_ptr<ModList> texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list;
- std::shared_ptr<Setting> m_lwjglFolderSetting;
-protected
-slots:
- virtual void jarModsChanged();
};
diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp
index 052f75fd..638b2e21 100644
--- a/api/logic/minecraft/legacy/LegacyModList.cpp
+++ b/api/logic/minecraft/legacy/LegacyModList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 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,55 +15,26 @@
#include "LegacyModList.h"
#include <FileSystem.h>
-#include <QMimeData>
-#include <QUrl>
-#include <QUuid>
#include <QString>
-#include <QFileSystemWatcher>
#include <QDebug>
LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
- : QAbstractListModel(), m_dir(dir), m_list_file(list_file)
+ : m_dir(dir), m_list_file(list_file)
{
FS::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);
- m_list_id = QUuid::createUuid().toString();
- m_watcher = new QFileSystemWatcher(this);
- is_watching = false;
- connect(m_watcher, SIGNAL(directoryChanged(QString)), this,
- SLOT(directoryChanged(QString)));
}
-void LegacyModList::startWatching()
-{
- update();
- is_watching = m_watcher->addPath(m_dir.absolutePath());
- if (is_watching)
- {
- qDebug() << "Started watching " << m_dir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to start watching " << m_dir.absolutePath();
- }
-}
-
-void LegacyModList::stopWatching()
-{
- is_watching = !m_watcher->removePath(m_dir.absolutePath());
- if (!is_watching)
+ struct OrderItem
{
- qDebug() << "Stopped watching " << m_dir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to stop watching " << m_dir.absolutePath();
- }
-}
+ QString id;
+ bool enabled = false;
+ };
+ typedef QList<OrderItem> OrderList;
-void LegacyModList::internalSort(QList<Mod> &what)
+static void internalSort(QList<Mod> &what)
{
auto predicate = [](const Mod &left, const Mod &right)
{
@@ -76,9 +47,43 @@ void LegacyModList::internalSort(QList<Mod> &what)
std::sort(what.begin(), what.end(), predicate);
}
+static OrderList readListFile(const QString &m_list_file)
+{
+ OrderList itemList;
+ if (m_list_file.isNull() || m_list_file.isEmpty())
+ return itemList;
+
+ QFile textFile(m_list_file);
+ if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
+ return OrderList();
+
+ QTextStream textStream;
+ textStream.setAutoDetectUnicode(true);
+ textStream.setDevice(&textFile);
+ while (true)
+ {
+ QString line = textStream.readLine();
+ if (line.isNull() || line.isEmpty())
+ break;
+ else
+ {
+ OrderItem it;
+ it.enabled = !line.endsWith(".disabled");
+ if (!it.enabled)
+ {
+ line.chop(9);
+ }
+ it.id = line;
+ itemList.append(it);
+ }
+ }
+ textFile.close();
+ return itemList;
+}
+
bool LegacyModList::update()
{
- if (!isValid())
+ if (!m_dir.exists() || !m_dir.isReadable())
return false;
QList<Mod> orderedMods;
@@ -88,7 +93,7 @@ bool LegacyModList::update()
bool orderOrStateChanged = false;
// first, process the ordered items (if any)
- OrderList listOrder = readListFile();
+ OrderList listOrder = readListFile(m_list_file);
for (auto item : listOrder)
{
QFileInfo infoEnabled(m_dir.filePath(item.id));
@@ -157,460 +162,10 @@ bool LegacyModList::update()
}
}
}
- beginResetModel();
mods.swap(orderedMods);
- endResetModel();
if (orderOrStateChanged && !m_list_file.isEmpty())
{
qDebug() << "Mod list " << m_list_file << " changed!";
- saveListFile();
- emit changed();
- }
- return true;
-}
-
-void LegacyModList::directoryChanged(QString path)
-{
- update();
-}
-
-LegacyModList::OrderList LegacyModList::readListFile()
-{
- OrderList itemList;
- if (m_list_file.isNull() || m_list_file.isEmpty())
- return itemList;
-
- QFile textFile(m_list_file);
- if (!textFile.open(QIODevice::ReadOnly | QIODevice::Text))
- return OrderList();
-
- QTextStream textStream;
- textStream.setAutoDetectUnicode(true);
- textStream.setDevice(&textFile);
- while (true)
- {
- QString line = textStream.readLine();
- if (line.isNull() || line.isEmpty())
- break;
- else
- {
- OrderItem it;
- it.enabled = !line.endsWith(".disabled");
- if (!it.enabled)
- {
- line.chop(9);
- }
- it.id = line;
- itemList.append(it);
- }
- }
- textFile.close();
- return itemList;
-}
-
-bool LegacyModList::saveListFile()
-{
- if (m_list_file.isNull() || m_list_file.isEmpty())
- return false;
- QFile textFile(m_list_file);
- if (!textFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
- return false;
- QTextStream textStream;
- textStream.setGenerateByteOrderMark(true);
- textStream.setCodec("UTF-8");
- textStream.setDevice(&textFile);
- for (auto mod : mods)
- {
- textStream << mod.mmc_id();
- if (!mod.enabled())
- textStream << ".disabled";
- textStream << endl;
- }
- textFile.close();
- return false;
-}
-
-bool LegacyModList::isValid()
-{
- return m_dir.exists() && m_dir.isReadable();
-}
-
-bool LegacyModList::installMod(const QString &filename, int index)
-{
- // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
- QFileInfo fileinfo(FS::NormalizePath(filename));
-
- qDebug() << "installing: " << fileinfo.absoluteFilePath();
-
- if (!fileinfo.exists() || !fileinfo.isReadable() || index < 0)
- {
- return false;
- }
- Mod m(fileinfo);
- if (!m.valid())
- return false;
-
- // if it's already there, replace the original mod (in place)
- int idx = mods.indexOf(m);
- if (idx != -1)
- {
- int idx2 = mods.indexOf(m, idx + 1);
- if (idx2 != -1)
- return false;
- if (mods[idx].replace(m))
- {
-
- auto left = this->index(index);
- auto right = this->index(index, columnCount(QModelIndex()) - 1);
- emit dataChanged(left, right);
- saveListFile();
- update();
- return true;
- }
- return false;
- }
-
- auto type = m.type();
- if (type == Mod::MOD_UNKNOWN)
- return false;
- if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
- {
- QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName());
- if (!QFile::copy(fileinfo.filePath(), newpath))
- return false;
- m.repath(newpath);
- beginInsertRows(QModelIndex(), index, index);
- mods.insert(index, m);
- endInsertRows();
- saveListFile();
- update();
- return true;
- }
- else if (type == Mod::MOD_FOLDER)
- {
-
- QString from = fileinfo.filePath();
- QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName());
- if (!FS::copy(from, to)())
- return false;
- m.repath(to);
- beginInsertRows(QModelIndex(), index, index);
- mods.insert(index, m);
- endInsertRows();
- saveListFile();
- update();
- return true;
}
- return false;
-}
-
-bool LegacyModList::deleteMod(int index)
-{
- if (index >= mods.size() || index < 0)
- return false;
- Mod &m = mods[index];
- if (m.destroy())
- {
- beginRemoveRows(QModelIndex(), index, index);
- mods.removeAt(index);
- endRemoveRows();
- saveListFile();
- emit changed();
- return true;
- }
- return false;
-}
-
-bool LegacyModList::deleteMods(int first, int last)
-{
- for (int i = first; i <= last; i++)
- {
- Mod &m = mods[i];
- m.destroy();
- }
- beginRemoveRows(QModelIndex(), first, last);
- mods.erase(mods.begin() + first, mods.begin() + last + 1);
- endRemoveRows();
- saveListFile();
- emit changed();
- return true;
-}
-
-bool LegacyModList::moveModTo(int from, int to)
-{
- if (from < 0 || from >= mods.size())
- return false;
- if (to >= rowCount())
- to = rowCount() - 1;
- if (to == -1)
- to = rowCount() - 1;
- if (from == to)
- return false;
- int togap = to > from ? to + 1 : to;
- beginMoveRows(QModelIndex(), from, from, QModelIndex(), togap);
- mods.move(from, to);
- endMoveRows();
- saveListFile();
- emit changed();
- return true;
-}
-
-bool LegacyModList::moveModUp(int from)
-{
- if (from > 0)
- return moveModTo(from, from - 1);
- return false;
-}
-
-bool LegacyModList::moveModsUp(int first, int last)
-{
- if (first == 0)
- return false;
-
- beginMoveRows(QModelIndex(), first, last, QModelIndex(), first - 1);
- mods.move(first - 1, last);
- endMoveRows();
- saveListFile();
- emit changed();
return true;
}
-
-bool LegacyModList::moveModDown(int from)
-{
- if (from < 0)
- return false;
- if (from < mods.size() - 1)
- return moveModTo(from, from + 1);
- return false;
-}
-
-bool LegacyModList::moveModsDown(int first, int last)
-{
- if (last == mods.size() - 1)
- return false;
-
- beginMoveRows(QModelIndex(), first, last, QModelIndex(), last + 2);
- mods.move(last + 1, first);
- endMoveRows();
- saveListFile();
- emit changed();
- return true;
-}
-
-int LegacyModList::columnCount(const QModelIndex &parent) const
-{
- return 3;
-}
-
-QVariant LegacyModList::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
- int column = index.column();
-
- if (row < 0 || row >= mods.size())
- return QVariant();
-
- switch (role)
- {
- case Qt::DisplayRole:
- switch (column)
- {
- case NameColumn:
- return mods[row].name();
- case VersionColumn:
- return mods[row].version();
-
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- return mods[row].mmc_id();
-
- case Qt::CheckStateRole:
- switch (column)
- {
- case ActiveColumn:
- return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
-}
-
-bool LegacyModList::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
- {
- return false;
- }
-
- if (role == Qt::CheckStateRole)
- {
- auto &mod = mods[index.row()];
- if (mod.enable(!mod.enabled()))
- {
- emit dataChanged(index, index);
- return true;
- }
- }
- return false;
-}
-
-QVariant LegacyModList::headerData(int section, Qt::Orientation orientation, int role) const
-{
- switch (role)
- {
- case Qt::DisplayRole:
- switch (section)
- {
- case ActiveColumn:
- return QString();
- case NameColumn:
- return tr("Name");
- case VersionColumn:
- return tr("Version");
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- switch (section)
- {
- case ActiveColumn:
- return tr("Is the mod enabled?");
- case NameColumn:
- return tr("The name of the mod.");
- case VersionColumn:
- return tr("The version of the mod.");
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
- return QVariant();
-}
-
-Qt::ItemFlags LegacyModList::flags(const QModelIndex &index) const
-{
- Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- if (index.isValid())
- return Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled |
- defaultFlags;
- else
- return Qt::ItemIsDropEnabled | defaultFlags;
-}
-
-QStringList LegacyModList::mimeTypes() const
-{
- QStringList types;
- types << "text/uri-list";
- types << "text/plain";
- return types;
-}
-
-Qt::DropActions LegacyModList::supportedDropActions() const
-{
- // copy from outside, move from within and other mod lists
- return Qt::CopyAction | Qt::MoveAction;
-}
-
-Qt::DropActions LegacyModList::supportedDragActions() const
-{
- // move to other mod lists or VOID
- return Qt::MoveAction;
-}
-
-QMimeData *LegacyModList::mimeData(const QModelIndexList &indexes) const
-{
- QMimeData *data = new QMimeData();
-
- if (indexes.size() == 0)
- return data;
-
- auto idx = indexes[0];
- int row = idx.row();
- if (row < 0 || row >= mods.size())
- return data;
-
- QStringList params;
- params << m_list_id << QString::number(row);
- data->setText(params.join('|'));
- return data;
-}
-
-bool LegacyModList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
- const QModelIndex &parent)
-{
- if (action == Qt::IgnoreAction)
- return true;
- // check if the action is supported
- if (!data || !(action & supportedDropActions()))
- return false;
- if (parent.isValid())
- {
- row = parent.row();
- column = parent.column();
- }
-
- if (row > rowCount())
- row = rowCount();
- if (row == -1)
- row = rowCount();
- if (column == -1)
- column = 0;
- qDebug() << "Drop row: " << row << " column: " << column;
-
- // files dropped from outside?
- if (data->hasUrls())
- {
- bool was_watching = is_watching;
- if (was_watching)
- stopWatching();
- auto urls = data->urls();
- for (auto url : urls)
- {
- // only local files may be dropped...
- if (!url.isLocalFile())
- continue;
- QString filename = url.toLocalFile();
- installMod(filename, row);
- // if there is no ordering, re-sort the list
- if (m_list_file.isEmpty())
- {
- beginResetModel();
- internalSort(mods);
- endResetModel();
- }
- }
- if (was_watching)
- startWatching();
- return true;
- }
- else if (data->hasText())
- {
- QString sourcestr = data->text();
- auto list = sourcestr.split('|');
- if (list.size() != 2)
- return false;
- QString remoteId = list[0];
- int remoteIndex = list[1].toInt();
- qDebug() << "move: " << sourcestr;
- // no moving of things between two lists
- if (remoteId != m_list_id)
- return false;
- // no point moving to the same place...
- if (row == remoteIndex)
- return false;
- // otherwise, move the mod :D
- moveModTo(remoteIndex, row);
- return true;
- }
- return false;
-}
diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h
index d1bd0e8b..19b191a7 100644
--- a/api/logic/minecraft/legacy/LegacyModList.h
+++ b/api/logic/minecraft/legacy/LegacyModList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,7 +18,6 @@
#include <QList>
#include <QString>
#include <QDir>
-#include <QAbstractListModel>
#include "minecraft/Mod.h"
@@ -26,102 +25,19 @@
class LegacyInstance;
class BaseInstance;
-class QFileSystemWatcher;
/**
* A legacy mod list.
* Backed by a folder.
*/
-class MULTIMC_LOGIC_EXPORT LegacyModList : public QAbstractListModel
+class MULTIMC_LOGIC_EXPORT LegacyModList
{
- Q_OBJECT
public:
- enum Columns
- {
- ActiveColumn = 0,
- NameColumn,
- VersionColumn
- };
- LegacyModList(const QString &dir, const QString &list_file = QString());
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
- virtual bool setData(const QModelIndex &index, const QVariant &value,
- int role = Qt::EditRole);
-
- virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
- {
- return size();
- }
- ;
- virtual QVariant headerData(int section, Qt::Orientation orientation,
- int role = Qt::DisplayRole) const;
- virtual int columnCount(const QModelIndex &parent) const;
-
- size_t size() const
- {
- return mods.size();
- }
- ;
- bool empty() const
- {
- return size() == 0;
- }
- Mod &operator[](size_t index)
- {
- return mods[index];
- }
+ LegacyModList(const QString &dir, const QString &list_file = QString());
/// Reloads the mod list and returns true if the list changed.
- virtual bool update();
-
- /**
- * Adds the given mod to the list at the given index - if the list supports custom ordering
- */
- virtual bool installMod(const QString & filename, int index = 0);
-
- /// Deletes the mod at the given index.
- virtual bool deleteMod(int index);
-
- /// Deletes all the selected mods
- virtual bool deleteMods(int first, int last);
-
- /**
- * move the mod at index to the position N
- * 0 is the beginning of the list, length() is the end of the list.
- */
- virtual bool moveModTo(int from, int to);
-
- /**
- * move the mod at index one position upwards
- */
- virtual bool moveModUp(int from);
- virtual bool moveModsUp(int first, int last);
-
- /**
- * move the mod at index one position downwards
- */
- virtual bool moveModDown(int from);
- virtual bool moveModsDown(int first, int last);
-
- /// flags, mostly to support drag&drop
- virtual Qt::ItemFlags flags(const QModelIndex &index) const;
- /// get data for drag action
- virtual QMimeData *mimeData(const QModelIndexList &indexes) const;
- /// get the supported mime types
- virtual QStringList mimeTypes() const;
- /// process data from drop action
- virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
- const QModelIndex &parent);
- /// what drag actions do we support?
- virtual Qt::DropActions supportedDragActions() const;
-
- /// what drop actions do we support?
- virtual Qt::DropActions supportedDropActions() const;
-
- void startWatching();
- void stopWatching();
-
- virtual bool isValid();
+ bool update();
QDir dir()
{
@@ -133,28 +49,8 @@ public:
return mods;
}
-private:
- void internalSort(QList<Mod> & what);
- struct OrderItem
- {
- QString id;
- bool enabled = false;
- };
- typedef QList<OrderItem> OrderList;
- OrderList readListFile();
- bool saveListFile();
-private
-slots:
- void directoryChanged(QString path);
-
-signals:
- void changed();
-
protected:
- QFileSystemWatcher *m_watcher;
- bool is_watching;
QDir m_dir;
QString m_list_file;
- QString m_list_id;
QList<Mod> mods;
};
diff --git a/api/logic/minecraft/legacy/LegacyUpdate.cpp b/api/logic/minecraft/legacy/LegacyUpdate.cpp
deleted file mode 100644
index e263d0de..00000000
--- a/api/logic/minecraft/legacy/LegacyUpdate.cpp
+++ /dev/null
@@ -1,399 +0,0 @@
-/* Copyright 2013-2017 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 <QStringList>
-#include <quazip.h>
-#include <quazipfile.h>
-#include <QDebug>
-
-#include "Env.h"
-#include "BaseInstance.h"
-#include "net/URLConstants.h"
-#include "MMCZip.h"
-
-#include "LegacyUpdate.h"
-#include "LegacyModList.h"
-
-#include "LwjglVersionList.h"
-#include "LegacyInstance.h"
-#include <FileSystem.h>
-
-LegacyUpdate::LegacyUpdate(BaseInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
-{
-}
-
-void LegacyUpdate::executeTask()
-{
- fmllibsStart();
-}
-
-void LegacyUpdate::fmllibsStart()
-{
- // Get the mod list
- LegacyInstance *inst = (LegacyInstance *)m_inst;
- auto modList = inst->jarModList();
-
- bool forge_present = false;
-
- QString version = inst->intendedVersionId();
- auto & fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
- if (!fmlLibsMapping.contains(version))
- {
- lwjglStart();
- return;
- }
-
- auto &libList = fmlLibsMapping[version];
-
- // determine if we need some libs for FML or forge
- setStatus(tr("Checking for FML libraries..."));
- for (unsigned i = 0; i < modList->size(); i++)
- {
- auto &mod = modList->operator[](i);
-
- // do not use disabled mods.
- if (!mod.enabled())
- continue;
-
- if (mod.type() != Mod::MOD_ZIPFILE)
- continue;
-
- if (mod.mmc_id().contains("forge", Qt::CaseInsensitive))
- {
- forge_present = true;
- break;
- }
- if (mod.mmc_id().contains("fml", Qt::CaseInsensitive))
- {
- forge_present = true;
- break;
- }
- }
- // we don't...
- if (!forge_present)
- {
- lwjglStart();
- return;
- }
-
- // now check the lib folder inside the instance for files.
- for (auto &lib : libList)
- {
- QFileInfo libInfo(FS::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())
- {
- lwjglStart();
- return;
- }
-
- // download missing libs to our place
- setStatus(tr("Dowloading FML libraries..."));
- auto dljob = new NetJob("FML libraries");
- auto metacache = ENV.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(Net::Download::makeCached(QUrl(urlString), entry));
- }
-
- connect(dljob, &NetJob::succeeded, this, &LegacyUpdate::fmllibsFinished);
- connect(dljob, &NetJob::failed, this, &LegacyUpdate::fmllibsFailed);
- connect(dljob, &NetJob::progress, this, &LegacyUpdate::progress);
- legacyDownloadJob.reset(dljob);
- legacyDownloadJob->start();
-}
-
-void LegacyUpdate::fmllibsFinished()
-{
- legacyDownloadJob.reset();
- if(!fmlLibsToProcess.isEmpty())
- {
- setStatus(tr("Copying FML libraries into the instance..."));
- LegacyInstance *inst = (LegacyInstance *)m_inst;
- auto metacache = ENV.metacache();
- int index = 0;
- for (auto &lib : fmlLibsToProcess)
- {
- progress(index, fmlLibsToProcess.size());
- auto entry = metacache->resolveEntry("fmllibs", lib.filename);
- auto path = FS::PathCombine(inst->libDir(), lib.filename);
- if(!FS::ensureFilePathExists(path))
- {
- emitFailed(tr("Failed creating FML library folder inside the instance."));
- return;
- }
- if (!QFile::copy(entry->getFullPath(), FS::PathCombine(inst->libDir(), lib.filename)))
- {
- emitFailed(tr("Failed copying Forge/FML library: %1.").arg(lib.filename));
- return;
- }
- index++;
- }
- progress(index, fmlLibsToProcess.size());
- }
- lwjglStart();
-}
-
-void LegacyUpdate::fmllibsFailed(QString reason)
-{
- emitFailed(tr("Game update failed: it was impossible to fetch the required FML libraries. Reason: %1").arg(reason));
- return;
-}
-
-void LegacyUpdate::lwjglStart()
-{
- LegacyInstance *inst = (LegacyInstance *)m_inst;
-
- auto list = std::dynamic_pointer_cast<LWJGLVersionList>(ENV.getVersionList("org.lwjgl.legacy"));
- if (!list->isLoaded())
- {
- setStatus(tr("Checking the LWJGL version list..."));
- list->loadList();
- auto task = list->getLoadTask();
- connect(task.get(), &Task::succeeded, this, &LegacyUpdate::lwjglStart);
- connect(task.get(), &Task::failed, this, [&](const QString & error)
- {
- emitFailed(tr("Failed to refresh LWJGL list: %1.").arg(error));
- });
- return;
- }
-
- lwjglVersion = inst->lwjglVersion();
- lwjglTargetPath = FS::PathCombine(inst->lwjglFolder(), lwjglVersion);
- lwjglNativesPath = FS::PathCombine(lwjglTargetPath, "natives");
-
- // if the 'done' file exists, we don't have to download this again
- QFileInfo doneFile(FS::PathCombine(lwjglTargetPath, "done"));
- if (doneFile.exists())
- {
- jarStart();
- return;
- }
-
- setStatus(tr("Downloading new LWJGL..."));
- auto version = std::dynamic_pointer_cast<LWJGLVersion>(list->findVersion(lwjglVersion));
- if (!version)
- {
- emitFailed(QString("Game update failed: the selected LWJGL version is invalid: %1").arg(lwjglVersion));
- return;
- }
-
- QString url = version->url();
- QUrl realUrl(url);
- QString hostname = realUrl.host();
- auto worker = &ENV.qnam();
- QNetworkRequest req(realUrl);
- req.setRawHeader("Host", hostname.toLatin1());
- req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
- QNetworkReply *rep = worker->get(req);
-
- m_reply = std::shared_ptr<QNetworkReply>(rep);
- connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress);
- connect(worker, &QNetworkAccessManager::finished, this, &LegacyUpdate::lwjglFinished);
-}
-
-void LegacyUpdate::lwjglFinished(QNetworkReply *reply)
-{
- if (m_reply.get() != reply)
- {
- return;
- }
- if (reply->error() != QNetworkReply::NoError)
- {
- emitFailed("Failed to download: " + reply->errorString() +
- "\nSometimes you have to wait a bit if you download many LWJGL versions in "
- "a row. YMMV");
- return;
- }
- auto worker = &ENV.qnam();
- // Here i check if there is a cookie for me in the reply and extract it
- QList<QNetworkCookie> cookies =
- qvariant_cast<QList<QNetworkCookie>>(reply->header(QNetworkRequest::SetCookieHeader));
- if (cookies.count() != 0)
- {
- // you must tell which cookie goes with which url
- worker->cookieJar()->setCookiesFromUrl(cookies, QUrl("sourceforge.net"));
- }
-
- // here you can check for the 302 or whatever other header i need
- QVariant newLoc = reply->header(QNetworkRequest::LocationHeader);
- if (newLoc.isValid())
- {
- QString redirectedTo = reply->header(QNetworkRequest::LocationHeader).toString();
- QUrl realUrl(redirectedTo);
- QString hostname = realUrl.host();
- QNetworkRequest req(redirectedTo);
- req.setRawHeader("Host", hostname.toLatin1());
- req.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
- QNetworkReply *rep = worker->get(req);
- connect(rep, &QNetworkReply::downloadProgress, this, &LegacyUpdate::progress);
- m_reply = std::shared_ptr<QNetworkReply>(rep);
- return;
- }
- QFile saveMe("lwjgl.zip");
- saveMe.open(QIODevice::WriteOnly);
- saveMe.write(m_reply->readAll());
- saveMe.close();
- setStatus(tr("Installing new LWJGL..."));
- extractLwjgl();
- jarStart();
-}
-void LegacyUpdate::extractLwjgl()
-{
- // make sure the directories are there
-
- bool success = FS::ensureFolderPathExists(lwjglNativesPath);
-
- if (!success)
- {
- emitFailed("Failed to extract the lwjgl libs - error when creating required folders.");
- return;
- }
-
- QuaZip zip("lwjgl.zip");
- if (!zip.open(QuaZip::mdUnzip))
- {
- emitFailed("Failed to extract the lwjgl libs - not a valid archive.");
- return;
- }
-
- // and now we are going to access files inside it
- QuaZipFile file(&zip);
- const QString jarNames[] = {"jinput.jar", "lwjgl_util.jar", "lwjgl.jar"};
- for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile())
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- emitFailed("Failed to extract the lwjgl libs - error while reading archive.");
- return;
- }
- QuaZipFileInfo info;
- QString name = file.getActualFileName();
- if (name.endsWith('/'))
- {
- file.close();
- continue;
- }
- QString destFileName;
- // Look for the jars
- for (int i = 0; i < 3; i++)
- {
- if (name.endsWith(jarNames[i]))
- {
- destFileName = FS::PathCombine(lwjglTargetPath, jarNames[i]);
- }
- }
- // Not found? look for the natives
- if (destFileName.isEmpty())
- {
-#ifdef Q_OS_WIN32
- QString nativesDir = "windows";
-#else
-#ifdef Q_OS_MAC
- QString nativesDir = "macosx";
-#else
- QString nativesDir = "linux";
-#endif
-#endif
- if (name.contains(nativesDir))
- {
- int lastSlash = name.lastIndexOf('/');
- int lastBackSlash = name.lastIndexOf('\\');
- if (lastSlash != -1)
- name = name.mid(lastSlash + 1);
- else if (lastBackSlash != -1)
- name = name.mid(lastBackSlash + 1);
- destFileName = FS::PathCombine(lwjglNativesPath, name);
- }
- }
- // Now if destFileName is still empty, go to the next file.
- if (!destFileName.isEmpty())
- {
- setStatus(tr("Installing new LWJGL - extracting ") + name + "...");
- QFile output(destFileName);
- output.open(QIODevice::WriteOnly);
- output.write(file.readAll());
- output.close();
- }
- file.close(); // do not forget to close!
- }
- zip.close();
- m_reply.reset();
- QFile doneFile(FS::PathCombine(lwjglTargetPath, "done"));
- doneFile.open(QIODevice::WriteOnly);
- doneFile.write("done.");
- doneFile.close();
-}
-
-void LegacyUpdate::lwjglFailed(QString reason)
-{
- emitFailed(tr("Bad stuff happened while trying to get the lwjgl libs: %1").arg(reason));
-}
-
-void LegacyUpdate::jarStart()
-{
- LegacyInstance *inst = (LegacyInstance *)m_inst;
- if (!inst->shouldUpdate() || inst->shouldUseCustomBaseJar())
- {
- emitSucceeded();
- return;
- }
-
- setStatus(tr("Checking for jar updates..."));
- // Make directories
- QDir binDir(inst->binRoot());
- if (!binDir.exists() && !binDir.mkpath("."))
- {
- emitFailed("Failed to create bin folder.");
- return;
- }
-
- // Build a list of URLs that will need to be downloaded.
- setStatus(tr("Downloading new minecraft.jar ..."));
-
- QString version_id = inst->intendedVersionId();
-
- auto dljob = new NetJob("Minecraft.jar for version " + version_id);
-
- auto metacache = ENV.metacache();
- auto entry = metacache->resolveEntry("versions", URLConstants::getJarPath(version_id));
- dljob->addNetAction(Net::Download::makeCached(QUrl(URLConstants::getLegacyJarUrl(version_id)), entry));
- connect(dljob, SIGNAL(succeeded()), SLOT(jarFinished()));
- connect(dljob, SIGNAL(failed(QString)), SLOT(jarFailed(QString)));
- connect(dljob, SIGNAL(progress(qint64, qint64)), SIGNAL(progress(qint64, qint64)));
- legacyDownloadJob.reset(dljob);
- legacyDownloadJob->start();
-}
-
-void LegacyUpdate::jarFinished()
-{
- // process the jar
- emitSucceeded();
-}
-
-void LegacyUpdate::jarFailed(QString reason)
-{
- // bad, bad
- emitFailed(tr("Failed to download the minecraft jar: %1.").arg(reason));
-}
diff --git a/api/logic/minecraft/legacy/LegacyUpdate.h b/api/logic/minecraft/legacy/LegacyUpdate.h
deleted file mode 100644
index caab978e..00000000
--- a/api/logic/minecraft/legacy/LegacyUpdate.h
+++ /dev/null
@@ -1,70 +0,0 @@
-/* Copyright 2013-2017 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 <QObject>
-#include <QList>
-#include <QUrl>
-
-#include "net/NetJob.h"
-#include "tasks/Task.h"
-#include "minecraft/VersionFilterData.h"
-
-class MinecraftVersion;
-class BaseInstance;
-class QuaZip;
-class Mod;
-
-class LegacyUpdate : public Task
-{
- Q_OBJECT
-public:
- explicit LegacyUpdate(BaseInstance *inst, QObject *parent = 0);
- virtual void executeTask();
-
-private
-slots:
- void lwjglStart();
- void lwjglFinished(QNetworkReply *);
- void lwjglFailed(QString reason);
-
- void jarStart();
- void jarFinished();
- void jarFailed(QString reason);
-
- void fmllibsStart();
- void fmllibsFinished();
- void fmllibsFailed(QString reason);
-
- void extractLwjgl();
-
-private:
-
- std::shared_ptr<QNetworkReply> m_reply;
-
- // target version, determined during this task
- // MinecraftVersion *targetVersion;
- QString lwjglURL;
- QString lwjglVersion;
-
- QString lwjglTargetPath;
- QString lwjglNativesPath;
-
-private:
- NetJobPtr legacyDownloadJob;
- BaseInstance *m_inst = nullptr;
- QList<FMLlib> fmlLibsToProcess;
-};
diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
new file mode 100644
index 00000000..6cda3e4d
--- /dev/null
+++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
@@ -0,0 +1,142 @@
+#include "LegacyUpgradeTask.h"
+#include "BaseInstanceProvider.h"
+#include "settings/INISettingsObject.h"
+#include "FileSystem.h"
+#include "NullInstance.h"
+#include "pathmatcher/RegexpMatcher.h"
+#include <QtConcurrentRun>
+#include "LegacyInstance.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
+#include "classparser.h"
+
+LegacyUpgradeTask::LegacyUpgradeTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString & newName)
+{
+ m_globalSettings = settings;
+ m_stagingPath = stagingPath;
+ m_origInstance = origInstance;
+ m_newName = newName;
+}
+
+void LegacyUpgradeTask::executeTask()
+{
+ setStatus(tr("Copying instance %1").arg(m_origInstance->name()));
+
+ FS::copy folderCopy(m_origInstance->instanceRoot(), m_stagingPath);
+ folderCopy.followSymlinks(true);
+
+ m_copyFuture = QtConcurrent::run(QThreadPool::globalInstance(), folderCopy);
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::finished, this, &LegacyUpgradeTask::copyFinished);
+ connect(&m_copyFutureWatcher, &QFutureWatcher<bool>::canceled, this, &LegacyUpgradeTask::copyAborted);
+ m_copyFutureWatcher.setFuture(m_copyFuture);
+}
+
+static QString decideVersion(const QString& currentVersion, const QString& intendedVersion)
+{
+ if(intendedVersion != currentVersion)
+ {
+ if(!intendedVersion.isEmpty())
+ {
+ return intendedVersion;
+ }
+ else if(!currentVersion.isEmpty())
+ {
+ return currentVersion;
+ }
+ }
+ else
+ {
+ if(!intendedVersion.isEmpty())
+ {
+ return intendedVersion;
+ }
+ }
+ return QString();
+}
+
+void LegacyUpgradeTask::copyFinished()
+{
+ auto successful = m_copyFuture.result();
+ if(!successful)
+ {
+ emitFailed(tr("Instance folder copy failed."));
+ return;
+ }
+ auto legacyInst = std::dynamic_pointer_cast<LegacyInstance>(m_origInstance);
+
+ auto instanceSettings = std::make_shared<INISettingsObject>(FS::PathCombine(m_stagingPath, "instance.cfg"));
+ instanceSettings->registerSetting("InstanceType", "Legacy");
+ instanceSettings->set("InstanceType", "OneSix");
+ // NOTE: this scope ensures the instance is fully saved before we emitSucceeded
+ {
+ MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
+ inst.setName(m_newName);
+ inst.init();
+
+ QString preferredVersionNumber = decideVersion(legacyInst->currentVersionId(), legacyInst->intendedVersionId());
+ if(preferredVersionNumber.isNull())
+ {
+ // try to decide version based on the jar(s?)
+ preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->baseJar());
+ if(preferredVersionNumber.isNull())
+ {
+ preferredVersionNumber = classparser::GetMinecraftJarVersion(legacyInst->runnableJar());
+ if(preferredVersionNumber.isNull())
+ {
+ emitFailed(tr("Could not decide Minecraft version."));
+ return;
+ }
+ }
+ }
+ auto components = inst.getComponentList();
+ components->buildingFromScratch();
+ components->setComponentVersion("net.minecraft", preferredVersionNumber, true);
+
+ if(legacyInst->shouldUseCustomBaseJar())
+ {
+ QString jarPath = legacyInst->customBaseJar();
+ qDebug() << "Base jar is custom! : " << jarPath;
+ // FIXME: handle case when the jar is unreadable?
+ // TODO: check the hash, if it's the same as the upstream jar, do not do this
+ components->installCustomJar(jarPath);
+ }
+
+ auto jarMods = legacyInst->getJarMods();
+ for(auto & jarMod: jarMods)
+ {
+ QString modPath = jarMod.filename().absoluteFilePath();
+ qDebug() << "jarMod: " << modPath;
+ components->installJarMods({modPath});
+ }
+
+ // remove all the extra garbage we no longer need
+ auto removeAll = [&](const QString &root, const QStringList &things)
+ {
+ for(auto &thing : things)
+ {
+ auto removePath = FS::PathCombine(root, thing);
+ QFileInfo stat(removePath);
+ if(stat.isDir())
+ {
+ FS::deletePath(removePath);
+ }
+ else
+ {
+ QFile::remove(removePath);
+ }
+ }
+ };
+ QStringList rootRemovables = {"modlist", "version", "instMods"};
+ QStringList mcRemovables = {"bin", "MultiMCLauncher.jar", "icon.png"};
+ removeAll(inst.instanceRoot(), rootRemovables);
+ removeAll(inst.minecraftRoot(), mcRemovables);
+ }
+ emitSucceeded();
+}
+
+void LegacyUpgradeTask::copyAborted()
+{
+ emitFailed(tr("Instance folder copy has been aborted."));
+ return;
+}
+
diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.h b/api/logic/minecraft/legacy/LegacyUpgradeTask.h
new file mode 100644
index 00000000..56896385
--- /dev/null
+++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "tasks/Task.h"
+#include "multimc_logic_export.h"
+#include "net/NetJob.h"
+#include <QUrl>
+#include <QFuture>
+#include <QFutureWatcher>
+#include "settings/SettingsObject.h"
+#include "BaseVersion.h"
+#include "BaseInstance.h"
+
+
+class BaseInstanceProvider;
+
+class MULTIMC_LOGIC_EXPORT LegacyUpgradeTask : public Task
+{
+ Q_OBJECT
+public:
+ explicit LegacyUpgradeTask(SettingsObjectPtr settings, const QString & stagingPath, InstancePtr origInstance, const QString & newName);
+
+protected:
+ //! Entry point for tasks.
+ virtual void executeTask() override;
+ void copyFinished();
+ void copyAborted();
+
+private: /* data */
+ SettingsObjectPtr m_globalSettings;
+ InstancePtr m_origInstance;
+ QString m_stagingPath;
+ QString m_newName;
+ QFuture<bool> m_copyFuture;
+ QFutureWatcher<bool> m_copyFutureWatcher;
+};
+
+
+
diff --git a/api/logic/minecraft/legacy/LwjglVersionList.cpp b/api/logic/minecraft/legacy/LwjglVersionList.cpp
deleted file mode 100644
index 3d7ad2d4..00000000
--- a/api/logic/minecraft/legacy/LwjglVersionList.cpp
+++ /dev/null
@@ -1,169 +0,0 @@
-/* Copyright 2013-2017 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 "LwjglVersionList.h"
-#include "Env.h"
-
-#include <QtNetwork>
-#include <QtXml>
-#include <QRegExp>
-
-#include <QDebug>
-
-#define RSS_URL "https://sourceforge.net/projects/java-game-lib/rss"
-
-LWJGLVersionList::LWJGLVersionList(QObject *parent) : BaseVersionList(parent)
-{
-}
-
-QVariant LWJGLVersionList::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- if (index.row() > count())
- return QVariant();
-
- const PtrLWJGLVersion version = m_vlist.at(index.row());
-
- switch (role)
- {
- case Qt::DisplayRole:
- return version->name();
-
- case Qt::ToolTipRole:
- return version->url();
-
- default:
- return QVariant();
- }
-}
-
-QVariant LWJGLVersionList::headerData(int section, Qt::Orientation orientation, int role) const
-{
- switch (role)
- {
- case Qt::DisplayRole:
- return tr("Version");
-
- case Qt::ToolTipRole:
- return tr("LWJGL version name.");
-
- default:
- return QVariant();
- }
-}
-
-int LWJGLVersionList::columnCount(const QModelIndex &parent) const
-{
- return 1;
-}
-
-void LWJGLVersionList::loadList()
-{
- if(m_loading)
- {
- return;
- }
- m_loading = true;
-
- qDebug() << "Downloading LWJGL RSS...";
- m_rssDLJob.reset(new NetJob("LWJGL RSS"));
- m_rssDL = Net::Download::makeByteArray(QUrl(RSS_URL), &m_rssData);
- m_rssDLJob->addNetAction(m_rssDL);
- connect(m_rssDLJob.get(), &NetJob::failed, this, &LWJGLVersionList::rssFailed);
- connect(m_rssDLJob.get(), &NetJob::succeeded, this, &LWJGLVersionList::rssSucceeded);
- m_rssDLJob->start();
-}
-
-inline QDomElement getDomElementByTagName(QDomElement parent, QString tagname)
-{
- QDomNodeList elementList = parent.elementsByTagName(tagname);
- if (elementList.count())
- return elementList.at(0).toElement();
- else
- return QDomElement();
-}
-
-void LWJGLVersionList::rssFailed(const QString& reason)
-{
- m_rssDLJob.reset();
- m_loading = false;
- qWarning() << "Failed to load LWJGL list. Network error: " + reason;
-}
-
-void LWJGLVersionList::rssSucceeded()
-{
- QRegExp lwjglRegex("lwjgl-(([0-9]\\.?)+)\\.zip");
- Q_ASSERT_X(lwjglRegex.isValid(), "load LWJGL list", "LWJGL regex is invalid");
-
- QDomDocument doc;
-
- QString xmlErrorMsg;
- int errorLine;
-
- if (!doc.setContent(m_rssData, false, &xmlErrorMsg, &errorLine))
- {
- qWarning() << "Failed to load LWJGL list. XML error: " + xmlErrorMsg + " at line " + QString::number(errorLine);
- m_rssDLJob.reset();
- m_rssData.clear();
- m_loading = false;
- return;
- }
- m_rssData.clear();
-
- QDomNodeList items = doc.elementsByTagName("item");
-
- QList<PtrLWJGLVersion> tempList;
-
- for (int i = 0; i < items.length(); i++)
- {
- Q_ASSERT_X(items.at(i).isElement(), "load LWJGL list", "XML element isn't an element... wat?");
-
- QDomElement linkElement = getDomElementByTagName(items.at(i).toElement(), "link");
- if (linkElement.isNull())
- {
- qDebug() << "Link element" << i << "in RSS feed doesn't exist! Skipping.";
- continue;
- }
-
- QString link = linkElement.text();
-
- // Make sure it's a download link.
- if (link.endsWith("/download") && link.contains(lwjglRegex))
- {
- QString name = link.mid(lwjglRegex.indexIn(link) + 6);
- // Subtract 4 here to remove the .zip file extension.
- name = name.left(lwjglRegex.matchedLength() - 10);
-
- QUrl url(link);
- if (!url.isValid())
- {
- qWarning() << "LWJGL version URL isn't valid:" << link << "Skipping.";
- continue;
- }
- qDebug() << "Discovered LWGL version" << name << "at" << link;
- tempList.append(std::make_shared<LWJGLVersion>(name, link));
- }
- }
-
- beginResetModel();
- m_vlist.swap(tempList);
- endResetModel();
-
- qDebug() << "Loaded LWJGL list.";
- m_rssDLJob.reset();
- m_loading = false;
-}
diff --git a/api/logic/minecraft/legacy/LwjglVersionList.h b/api/logic/minecraft/legacy/LwjglVersionList.h
deleted file mode 100644
index f5312e2c..00000000
--- a/api/logic/minecraft/legacy/LwjglVersionList.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/* Copyright 2013-2017 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 <QObject>
-#include <QAbstractListModel>
-#include <QUrl>
-#include <QNetworkReply>
-#include <memory>
-
-#include "BaseVersion.h"
-#include "BaseVersionList.h"
-
-#include "multimc_logic_export.h"
-#include <net/NetJob.h>
-
-class LWJGLVersion;
-typedef std::shared_ptr<LWJGLVersion> PtrLWJGLVersion;
-
-class MULTIMC_LOGIC_EXPORT LWJGLVersion : public BaseVersion
-{
-public:
- LWJGLVersion(const QString &name, const QString &url)
- : m_name(name), m_url(url)
- {
- }
-
- virtual QString descriptor()
- {
- return m_name;
- }
-
- virtual QString name()
- {
- return m_name;
- }
-
- virtual QString typeString() const
- {
- return QObject::tr("Upstream");
- }
-
- QString url() const
- {
- return m_url;
- }
-
-protected:
- QString m_name;
- QString m_url;
-};
-
-class MULTIMC_LOGIC_EXPORT LWJGLVersionList : public BaseVersionList
-{
- Q_OBJECT
-public:
- explicit LWJGLVersionList(QObject *parent = 0);
-
- bool isLoaded() override
- {
- return m_vlist.length() > 0;
- }
- virtual const BaseVersionPtr at(int i) const override
- {
- return m_vlist[i];
- }
-
- virtual shared_qobject_ptr<Task> getLoadTask() override
- {
- return m_rssDLJob;
- }
-
- virtual void sortVersions() override {};
-
- virtual void updateListData(QList< BaseVersionPtr > versions) override {};
-
- int count() const override
- {
- return m_vlist.length();
- }
-
- virtual QVariant data(const QModelIndex &index, int role) const override;
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
- virtual int rowCount(const QModelIndex &parent) const override
- {
- return count();
- }
- virtual int columnCount(const QModelIndex &parent) const override;
-
-public slots:
- virtual void loadList();
-
-private slots:
- void rssFailed(const QString & reason);
- void rssSucceeded();
-
-private:
- QList<PtrLWJGLVersion> m_vlist;
- Net::Download::Ptr m_rssDL;
- NetJobPtr m_rssDLJob;
- QByteArray m_rssData;
- bool m_loading = false;
-};
diff --git a/api/logic/minecraft/onesix/OneSixInstance.cpp b/api/logic/minecraft/onesix/OneSixInstance.cpp
deleted file mode 100644
index ecfd0647..00000000
--- a/api/logic/minecraft/onesix/OneSixInstance.cpp
+++ /dev/null
@@ -1,698 +0,0 @@
-/* Copyright 2013-2017 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <QDebug>
-#include <minecraft/launch/DirectJavaLaunch.h>
-#include <minecraft/launch/LauncherPartLaunch.h>
-#include <Env.h>
-
-#include "OneSixInstance.h"
-#include "OneSixUpdate.h"
-#include "OneSixProfileStrategy.h"
-
-#include "minecraft/MinecraftProfile.h"
-#include "minecraft/launch/ModMinecraftJar.h"
-#include "MMCZip.h"
-
-#include "minecraft/AssetsUtils.h"
-#include "minecraft/WorldList.h"
-#include <FileSystem.h>
-
-OneSixInstance::OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir)
- : MinecraftInstance(globalSettings, settings, rootDir)
-{
- // set explicitly during instance creation
- m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
-
- // defaults to the version we've been using for years (2.9.1)
- m_settings->registerSetting("LWJGLVersion", "2.9.1");
-
- // optionals
- m_settings->registerSetting("ForgeVersion", "");
- m_settings->registerSetting("LiteloaderVersion", "");
-}
-
-void OneSixInstance::init()
-{
- createProfile();
-}
-
-void OneSixInstance::createProfile()
-{
- m_profile.reset(new MinecraftProfile(new OneSixProfileStrategy(this)));
-}
-
-QSet<QString> OneSixInstance::traits()
-{
- auto version = getMinecraftProfile();
- if (!version)
- {
- return {"version-incomplete"};
- }
- else
- {
- return version->getTraits();
- }
-}
-
-shared_qobject_ptr<Task> OneSixInstance::createUpdateTask()
-{
- return shared_qobject_ptr<Task>(new OneSixUpdate(this));
-}
-
-QString replaceTokensIn(QString text, QMap<QString, QString> with)
-{
- QString result;
- QRegExp token_regexp("\\$\\{(.+)\\}");
- token_regexp.setMinimal(true);
- QStringList list;
- int tail = 0;
- int head = 0;
- while ((head = token_regexp.indexIn(text, head)) != -1)
- {
- result.append(text.mid(tail, head - tail));
- QString key = token_regexp.cap(1);
- auto iter = with.find(key);
- if (iter != with.end())
- {
- result.append(*iter);
- }
- head += token_regexp.matchedLength();
- tail = head;
- }
- result.append(text.mid(tail));
- return result;
-}
-
-QStringList OneSixInstance::processMinecraftArgs(AuthSessionPtr session) const
-{
- QString args_pattern = m_profile->getMinecraftArguments();
- for (auto tweaker : m_profile->getTweakers())
- {
- args_pattern += " --tweakClass " + tweaker;
- }
-
- QMap<QString, QString> token_mapping;
- // yggdrasil!
- if(session)
- {
- token_mapping["auth_username"] = session->username;
- token_mapping["auth_session"] = session->session;
- token_mapping["auth_access_token"] = session->access_token;
- token_mapping["auth_player_name"] = session->player_name;
- token_mapping["auth_uuid"] = session->uuid;
- token_mapping["user_properties"] = session->serializeUserProperties();
- token_mapping["user_type"] = session->user_type;
- }
-
- // blatant self-promotion.
- token_mapping["profile_name"] = token_mapping["version_name"] = "MultiMC5";
- if(m_profile->isVanilla())
- {
- token_mapping["version_type"] = m_profile->getMinecraftVersionType();
- }
- else
- {
- token_mapping["version_type"] = "custom";
- }
-
- QString absRootDir = QDir(minecraftRoot()).absolutePath();
- token_mapping["game_directory"] = absRootDir;
- QString absAssetsDir = QDir("assets/").absolutePath();
- auto assets = m_profile->getMinecraftAssets();
- token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath();
-
- // 1.7.3+ assets tokens
- token_mapping["assets_root"] = absAssetsDir;
- token_mapping["assets_index_name"] = assets->id;
-
- QStringList parts = args_pattern.split(' ', QString::SkipEmptyParts);
- for (int i = 0; i < parts.length(); i++)
- {
- parts[i] = replaceTokensIn(parts[i], token_mapping);
- }
- return parts;
-}
-
-QString OneSixInstance::getNativePath() const
-{
- QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/"));
- return natives_dir.absolutePath();
-}
-
-QString OneSixInstance::getLocalLibraryPath() const
-{
- QDir libraries_dir(FS::PathCombine(instanceRoot(), "libraries/"));
- return libraries_dir.absolutePath();
-}
-
-QString OneSixInstance::createLaunchScript(AuthSessionPtr session)
-{
- QString launchScript;
-
- if (!m_profile)
- return nullptr;
-
- auto mainClass = getMainClass();
- if (!mainClass.isEmpty())
- {
- launchScript += "mainClass " + mainClass + "\n";
- }
- auto appletClass = m_profile->getAppletClass();
- if (!appletClass.isEmpty())
- {
- launchScript += "appletClass " + appletClass + "\n";
- }
-
- // generic minecraft params
- for (auto param : processMinecraftArgs(session))
- {
- launchScript += "param " + param + "\n";
- }
-
- // window size, title and state, legacy
- {
- 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";
- }
-
- // legacy auth
- if(session)
- {
- launchScript += "userName " + session->player_name + "\n";
- launchScript += "sessionId " + session->session + "\n";
- }
-
- // libraries and class path.
- {
- QStringList jars, nativeJars;
- auto javaArchitecture = settings()->get("JavaArchitecture").toString();
- m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
- for(auto file: jars)
- {
- launchScript += "cp " + file + "\n";
- }
- for(auto file: nativeJars)
- {
- launchScript += "ext " + file + "\n";
- }
- launchScript += "natives " + getNativePath() + "\n";
- }
-
- for (auto trait : m_profile->getTraits())
- {
- launchScript += "traits " + trait + "\n";
- }
- launchScript += "launcher onesix\n";
- return launchScript;
-}
-
-QStringList OneSixInstance::verboseDescription(AuthSessionPtr session)
-{
- QStringList out;
- out << "Main Class:" << " " + getMainClass() << "";
- out << "Native path:" << " " + getNativePath() << "";
-
-
- auto alltraits = traits();
- if(alltraits.size())
- {
- out << "Traits:";
- for (auto trait : alltraits)
- {
- out << "traits " + trait;
- }
- out << "";
- }
-
- // libraries and class path.
- {
- out << "Libraries:";
- QStringList jars, nativeJars;
- auto javaArchitecture = settings()->get("JavaArchitecture").toString();
- m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
- auto printLibFile = [&](const QString & path)
- {
- QFileInfo info(path);
- if(info.exists())
- {
- out << " " + path;
- }
- else
- {
- out << " " + path + " (missing)";
- }
- };
- for(auto file: jars)
- {
- printLibFile(file);
- }
- out << "";
- out << "Native libraries:";
- for(auto file: nativeJars)
- {
- printLibFile(file);
- }
- out << "";
- }
-
- if(loaderModList()->size())
- {
- out << "Mods:";
- for(auto & mod: loaderModList()->allMods())
- {
- if(!mod.enabled())
- continue;
- if(mod.type() == Mod::MOD_FOLDER)
- continue;
- // TODO: proper implementation would need to descend into folders.
-
- out << " " + mod.filename().completeBaseName();
- }
- out << "";
- }
-
- if(coreModList()->size())
- {
- out << "Core Mods:";
- for(auto & coremod: coreModList()->allMods())
- {
- if(!coremod.enabled())
- continue;
- if(coremod.type() == Mod::MOD_FOLDER)
- continue;
- // TODO: proper implementation would need to descend into folders.
-
- out << " " + coremod.filename().completeBaseName();
- }
- out << "";
- }
-
- auto & jarMods = m_profile->getJarMods();
- if(jarMods.size())
- {
- out << "Jar Mods:";
- for(auto & jarmod: jarMods)
- {
- auto displayname = jarmod->displayName(currentSystem);
- auto realname = jarmod->filename(currentSystem);
- if(displayname != realname)
- {
- out << " " + displayname + " (" + realname + ")";
- }
- else
- {
- out << " " + realname;
- }
- }
- out << "";
- }
-
- auto params = processMinecraftArgs(nullptr);
- out << "Params:";
- out << " " + params.join(' ');
- out << "";
-
- QString windowParams;
- if (settings()->get("LaunchMaximized").toBool())
- {
- out << "Window size: max (if available)";
- }
- else
- {
- auto width = settings()->get("MinecraftWinWidth").toInt();
- auto height = settings()->get("MinecraftWinHeight").toInt();
- out << "Window size: " + QString::number(width) + " x " + QString::number(height);
- }
- out << "";
- return out;
-}
-
-
-std::shared_ptr<LaunchStep> OneSixInstance::createMainLaunchStep(LaunchTask * parent, AuthSessionPtr session)
-{
- auto method = launchMethod();
- if(method == "LauncherPart")
- {
- auto step = std::make_shared<LauncherPartLaunch>(parent);
- step->setAuthSession(session);
- step->setWorkingDirectory(minecraftRoot());
- return step;
- }
- else if (method == "DirectJava")
- {
- auto step = std::make_shared<DirectJavaLaunch>(parent);
- step->setWorkingDirectory(minecraftRoot());
- step->setAuthSession(session);
- return step;
- }
- return nullptr;
-}
-
-
-std::shared_ptr<Task> OneSixInstance::createJarModdingTask()
-{
- class JarModTask : public Task
- {
- public:
- explicit JarModTask(std::shared_ptr<OneSixInstance> inst) : Task(nullptr), m_inst(inst)
- {
- }
- virtual void executeTask()
- {
- auto profile = m_inst->getMinecraftProfile();
- // nuke obsolete stripped jar(s) if needed
- QString version_id = profile->getMinecraftVersion();
- if(!FS::ensureFolderPathExists(m_inst->binRoot()))
- {
- emitFailed(tr("Couldn't create the bin folder for Minecraft.jar"));
- }
- auto finalJarPath = QDir(m_inst->binRoot()).absoluteFilePath("minecraft.jar");
- QFile finalJar(finalJarPath);
- if(finalJar.exists())
- {
- if(!finalJar.remove())
- {
- emitFailed(tr("Couldn't remove stale jar file: %1").arg(finalJarPath));
- return;
- }
- }
-
- // create temporary modded jar, if needed
- auto jarMods = m_inst->getJarMods();
- if(jarMods.size())
- {
- auto mainJar = profile->getMainJar();
- QStringList jars, temp1, temp2, temp3, temp4;
- mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath());
- auto sourceJarPath = jars[0];
- if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods))
- {
- emitFailed(tr("Failed to create the custom Minecraft jar file."));
- return;
- }
- }
- emitSucceeded();
- }
- std::shared_ptr<OneSixInstance> m_inst;
- };
- return std::make_shared<JarModTask>(std::dynamic_pointer_cast<OneSixInstance>(shared_from_this()));
-}
-
-std::shared_ptr<ModList> OneSixInstance::loaderModList() const
-{
- if (!m_loader_mod_list)
- {
- m_loader_mod_list.reset(new ModList(loaderModsDir()));
- }
- m_loader_mod_list->update();
- return m_loader_mod_list;
-}
-
-std::shared_ptr<ModList> OneSixInstance::coreModList() const
-{
- if (!m_core_mod_list)
- {
- m_core_mod_list.reset(new ModList(coreModsDir()));
- }
- m_core_mod_list->update();
- return m_core_mod_list;
-}
-
-std::shared_ptr<ModList> OneSixInstance::resourcePackList() const
-{
- if (!m_resource_pack_list)
- {
- m_resource_pack_list.reset(new ModList(resourcePacksDir()));
- }
- m_resource_pack_list->update();
- return m_resource_pack_list;
-}
-
-std::shared_ptr<ModList> OneSixInstance::texturePackList() const
-{
- if (!m_texture_pack_list)
- {
- m_texture_pack_list.reset(new ModList(texturePacksDir()));
- }
- m_texture_pack_list->update();
- return m_texture_pack_list;
-}
-
-std::shared_ptr<WorldList> OneSixInstance::worldList() const
-{
- if (!m_world_list)
- {
- m_world_list.reset(new WorldList(worldDir()));
- }
- return m_world_list;
-}
-
-bool OneSixInstance::setIntendedVersionId(QString version)
-{
- return setComponentVersion("net.minecraft", version);
-}
-
-QString OneSixInstance::intendedVersionId() const
-{
- return getComponentVersion("net.minecraft");
-}
-
-bool OneSixInstance::setComponentVersion(const QString& uid, const QString& version)
-{
- if(uid == "net.minecraft")
- {
- settings()->set("IntendedVersion", version);
- }
- else if (uid == "org.lwjgl")
- {
- settings()->set("LWJGLVersion", version);
- }
- else if (uid == "net.minecraftforge")
- {
- settings()->set("ForgeVersion", version);
- }
- else if (uid == "com.mumfrey.liteloader")
- {
- settings()->set("LiteloaderVersion", version);
- }
- if(getMinecraftProfile())
- {
- clearProfile();
- }
- emit propertiesChanged(this);
- return true;
-}
-
-QString OneSixInstance::getComponentVersion(const QString& uid) const
-{
- if(uid == "net.minecraft")
- {
- return settings()->get("IntendedVersion").toString();
- }
- else if(uid == "org.lwjgl")
- {
- return settings()->get("LWJGLVersion").toString();
- }
- else if(uid == "net.minecraftforge")
- {
- return settings()->get("ForgeVersion").toString();
- }
- else if(uid == "com.mumfrey.liteloader")
- {
- return settings()->get("LiteloaderVersion").toString();
- }
- return QString();
-}
-
-QList< Mod > OneSixInstance::getJarMods() const
-{
- QList<Mod> mods;
- for (auto jarmod : m_profile->getJarMods())
- {
- QStringList jar, temp1, temp2, temp3;
- jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath());
- // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem));
- mods.push_back(Mod(QFileInfo(jar[0])));
- }
- return mods;
-}
-
-void OneSixInstance::setShouldUpdate(bool)
-{
-}
-
-bool OneSixInstance::shouldUpdate() const
-{
- return true;
-}
-
-QString OneSixInstance::currentVersionId() const
-{
- return intendedVersionId();
-}
-
-void OneSixInstance::reloadProfile()
-{
- m_profile->reload();
- setVersionBroken(m_profile->getProblemSeverity() == ProblemSeverity::Error);
- emit versionReloaded();
-}
-
-void OneSixInstance::clearProfile()
-{
- m_profile->clear();
- emit versionReloaded();
-}
-
-std::shared_ptr<MinecraftProfile> OneSixInstance::getMinecraftProfile() const
-{
- return m_profile;
-}
-
-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");
-}
-
-bool OneSixInstance::providesVersionFile() const
-{
- return false;
-}
-
-bool OneSixInstance::reload()
-{
- if (BaseInstance::reload())
- {
- try
- {
- reloadProfile();
- return true;
- }
- catch (...)
- {
- return false;
- }
- }
- return false;
-}
-
-QString OneSixInstance::loaderModsDir() const
-{
- return FS::PathCombine(minecraftRoot(), "mods");
-}
-
-QString OneSixInstance::coreModsDir() const
-{
- return FS::PathCombine(minecraftRoot(), "coremods");
-}
-
-QString OneSixInstance::resourcePacksDir() const
-{
- return FS::PathCombine(minecraftRoot(), "resourcepacks");
-}
-
-QString OneSixInstance::texturePacksDir() const
-{
- return FS::PathCombine(minecraftRoot(), "texturepacks");
-}
-
-QString OneSixInstance::instanceConfigFolder() const
-{
- return FS::PathCombine(minecraftRoot(), "config");
-}
-
-QString OneSixInstance::jarModsDir() const
-{
- return FS::PathCombine(instanceRoot(), "jarmods");
-}
-
-QString OneSixInstance::libDir() const
-{
- return FS::PathCombine(minecraftRoot(), "lib");
-}
-
-QString OneSixInstance::worldDir() const
-{
- return FS::PathCombine(minecraftRoot(), "saves");
-}
-
-QStringList OneSixInstance::extraArguments() const
-{
- auto list = BaseInstance::extraArguments();
- auto version = getMinecraftProfile();
- if (!version)
- return list;
- auto jarMods = getJarMods();
- if (!jarMods.isEmpty())
- {
- list.append({"-Dfml.ignoreInvalidMinecraftCertificates=true",
- "-Dfml.ignorePatchDiscrepancies=true"});
- }
- return list;
-}
-
-std::shared_ptr<OneSixInstance> OneSixInstance::getSharedPtr()
-{
- return std::dynamic_pointer_cast<OneSixInstance>(BaseInstance::getSharedPtr());
-}
-
-QString OneSixInstance::typeName() const
-{
- return tr("OneSix");
-}
-
-QStringList OneSixInstance::validLaunchMethods()
-{
- return {"LauncherPart", "DirectJava"};
-}
-
-QStringList OneSixInstance::getClassPath() const
-{
- QStringList jars, nativeJars;
- auto javaArchitecture = settings()->get("JavaArchitecture").toString();
- m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
- return jars;
-}
-
-QString OneSixInstance::getMainClass() const
-{
- return m_profile->getMainClass();
-}
-
-QStringList OneSixInstance::getNativeJars() const
-{
- QStringList jars, nativeJars;
- auto javaArchitecture = settings()->get("JavaArchitecture").toString();
- m_profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
- return nativeJars;
-}
diff --git a/api/logic/minecraft/onesix/OneSixInstance.h b/api/logic/minecraft/onesix/OneSixInstance.h
deleted file mode 100644
index bf12160e..00000000
--- a/api/logic/minecraft/onesix/OneSixInstance.h
+++ /dev/null
@@ -1,127 +0,0 @@
-/* Copyright 2013-2017 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 "minecraft/MinecraftInstance.h"
-
-#include "minecraft/MinecraftProfile.h"
-#include "minecraft/ModList.h"
-
-#include "multimc_logic_export.h"
-
-class MULTIMC_LOGIC_EXPORT OneSixInstance : public MinecraftInstance
-{
- Q_OBJECT
-public:
- explicit OneSixInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir);
- virtual ~OneSixInstance(){};
-
- virtual void init() override;
-
- ////// Mod Lists //////
- std::shared_ptr<ModList> loaderModList() const;
- std::shared_ptr<ModList> coreModList() const;
- std::shared_ptr<ModList> resourcePackList() const override;
- std::shared_ptr<ModList> texturePackList() const override;
- std::shared_ptr<WorldList> worldList() const override;
- virtual QList<Mod> getJarMods() const override;
- virtual void createProfile();
-
- virtual QSet<QString> traits() override;
-
- ////// Directories and files //////
- QString jarModsDir() const;
- QString resourcePacksDir() const;
- QString texturePacksDir() const;
- QString loaderModsDir() const;
- QString coreModsDir() const;
- QString libDir() const;
- QString worldDir() const;
- virtual QString instanceConfigFolder() const override;
-
- virtual shared_qobject_ptr<Task> createUpdateTask() override;
- virtual std::shared_ptr<Task> createJarModdingTask() override;
- virtual QString createLaunchScript(AuthSessionPtr session) override;
- QStringList verboseDescription(AuthSessionPtr session) override;
-
- virtual QString intendedVersionId() const override;
- virtual bool setIntendedVersionId(QString version) override;
- virtual QString currentVersionId() const override;
-
- QString getComponentVersion(const QString &uid) const;
- bool setComponentVersion(const QString &uid, const QString &version);
-
- virtual bool shouldUpdate() const override;
- virtual void setShouldUpdate(bool val) override;
-
- /**
- * reload the profile, including version json files.
- *
- * throws various exceptions :3
- */
- void reloadProfile();
-
- /// clears all version information in preparation for an update
- void clearProfile();
-
- /// get the current full version info
- std::shared_ptr<MinecraftProfile> getMinecraftProfile() const;
-
- virtual QDir jarmodsPath() const;
- virtual QDir librariesPath() const;
- virtual QDir versionsPath() const;
- virtual bool providesVersionFile() const;
-
- bool reload() override;
-
- virtual QStringList extraArguments() const override;
-
- std::shared_ptr<OneSixInstance> getSharedPtr();
-
- virtual QString typeName() const override;
-
- bool canExport() const override
- {
- return true;
- }
-
- QStringList getClassPath() const override;
- QString getMainClass() const override;
-
- QStringList getNativeJars() const override;
- QString getNativePath() const override;
-
- QString getLocalLibraryPath() const override;
-
- QStringList processMinecraftArgs(AuthSessionPtr account) const override;
-
-protected:
- std::shared_ptr<LaunchStep> createMainLaunchStep(LaunchTask *parent, AuthSessionPtr session) override;
- QStringList validLaunchMethods() override;
-
-signals:
- void versionReloaded();
-
-protected:
- std::shared_ptr<MinecraftProfile> m_profile;
- mutable std::shared_ptr<ModList> m_loader_mod_list;
- mutable std::shared_ptr<ModList> m_core_mod_list;
- mutable std::shared_ptr<ModList> m_resource_pack_list;
- mutable std::shared_ptr<ModList> m_texture_pack_list;
- mutable std::shared_ptr<WorldList> m_world_list;
-};
-
-Q_DECLARE_METATYPE(std::shared_ptr<OneSixInstance>)
diff --git a/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp b/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp
deleted file mode 100644
index ef2a7294..00000000
--- a/api/logic/minecraft/onesix/OneSixProfileStrategy.cpp
+++ /dev/null
@@ -1,407 +0,0 @@
-#include "OneSixProfileStrategy.h"
-#include "OneSixInstance.h"
-#include "OneSixVersionFormat.h"
-
-#include "Env.h"
-#include <FileSystem.h>
-
-#include <QDir>
-#include <QUuid>
-#include <QJsonDocument>
-#include <QJsonArray>
-#include <QSaveFile>
-#include <QResource>
-#include <meta/Index.h>
-#include <meta/Version.h>
-
-#include <tuple>
-
-OneSixProfileStrategy::OneSixProfileStrategy(OneSixInstance* instance)
-{
- m_instance = instance;
-}
-
-void OneSixProfileStrategy::upgradeDeprecatedFiles()
-{
- auto versionJsonPath = FS::PathCombine(m_instance->instanceRoot(), "version.json");
- auto customJsonPath = FS::PathCombine(m_instance->instanceRoot(), "custom.json");
- auto mcJson = FS::PathCombine(m_instance->instanceRoot(), "patches" , "net.minecraft.json");
-
- QString sourceFile;
- QString renameFile;
-
- // convert old crap.
- if(QFile::exists(customJsonPath))
- {
- sourceFile = customJsonPath;
- renameFile = versionJsonPath;
- }
- else if(QFile::exists(versionJsonPath))
- {
- sourceFile = versionJsonPath;
- }
- if(!sourceFile.isEmpty() && !QFile::exists(mcJson))
- {
- if(!FS::ensureFilePathExists(mcJson))
- {
- qWarning() << "Couldn't create patches folder for" << m_instance->name();
- return;
- }
- if(!renameFile.isEmpty() && QFile::exists(renameFile))
- {
- if(!QFile::rename(renameFile, renameFile + ".old"))
- {
- qWarning() << "Couldn't rename" << renameFile << "to" << renameFile + ".old" << "in" << m_instance->name();
- return;
- }
- }
- auto file = ProfileUtils::parseJsonFile(QFileInfo(sourceFile), false);
- ProfileUtils::removeLwjglFromPatch(file);
- file->uid = "net.minecraft";
- file->version = file->minecraftVersion;
- file->name = "Minecraft";
- auto data = OneSixVersionFormat::versionFileToJson(file, false).toJson();
- QSaveFile newPatchFile(mcJson);
- if(!newPatchFile.open(QIODevice::WriteOnly))
- {
- newPatchFile.cancelWriting();
- qWarning() << "Couldn't open main patch for writing in" << m_instance->name();
- return;
- }
- newPatchFile.write(data);
- if(!newPatchFile.commit())
- {
- qWarning() << "Couldn't save main patch in" << m_instance->name();
- return;
- }
- if(!QFile::rename(sourceFile, sourceFile + ".old"))
- {
- qWarning() << "Couldn't rename" << sourceFile << "to" << sourceFile + ".old" << "in" << m_instance->name();
- return;
- }
- }
-}
-
-void OneSixProfileStrategy::loadDefaultBuiltinPatches()
-{
- auto addBuiltinPatch = [&](const QString &uid, const QString intendedVersion, int order)
- {
- auto jsonFilePath = FS::PathCombine(m_instance->instanceRoot(), "patches" , uid + ".json");
- // load up the base minecraft patch
- ProfilePatchPtr profilePatch;
- if(QFile::exists(jsonFilePath))
- {
- auto file = ProfileUtils::parseJsonFile(QFileInfo(jsonFilePath), false);
- if(file->version.isEmpty())
- {
- file->version = intendedVersion;
- }
- profilePatch = std::make_shared<ProfilePatch>(file, jsonFilePath);
- profilePatch->setVanilla(false);
- profilePatch->setRevertible(true);
- }
- else
- {
- auto metaVersion = ENV.metadataIndex()->get(uid, intendedVersion);
- profilePatch = std::make_shared<ProfilePatch>(metaVersion);
- profilePatch->setVanilla(true);
- }
- profilePatch->setOrder(order);
- profile->appendPatch(profilePatch);
- };
- addBuiltinPatch("net.minecraft", m_instance->getComponentVersion("net.minecraft"), -2);
- addBuiltinPatch("org.lwjgl", m_instance->getComponentVersion("org.lwjgl"), -1);
-}
-
-void OneSixProfileStrategy::loadUserPatches()
-{
- // first, collect all patches (that are not builtins of OneSix) and load them
- QMap<QString, ProfilePatchPtr> loadedPatches;
- QDir patchesDir(FS::PathCombine(m_instance->instanceRoot(),"patches"));
- for (auto info : patchesDir.entryInfoList(QStringList() << "*.json", QDir::Files))
- {
- // parse the file
- qDebug() << "Reading" << info.fileName();
- auto file = ProfileUtils::parseJsonFile(info, true);
- // ignore builtins
- if (file->uid == "net.minecraft")
- continue;
- if (file->uid == "org.lwjgl")
- continue;
- auto patch = std::make_shared<ProfilePatch>(file, info.filePath());
- patch->setRemovable(true);
- patch->setMovable(true);
- if(ENV.metadataIndex()->hasUid(file->uid))
- {
- // FIXME: requesting a uid/list creates it in the index... this allows reverting to possibly invalid versions...
- patch->setRevertible(true);
- }
- loadedPatches[file->uid] = patch;
- }
- // these are 'special'... if not already loaded from instance files, grab them from the metadata repo.
- auto loadSpecial = [&](const QString & uid, int order)
- {
- auto patchVersion = m_instance->getComponentVersion(uid);
- if(!patchVersion.isEmpty() && !loadedPatches.contains(uid))
- {
- auto patch = std::make_shared<ProfilePatch>(ENV.metadataIndex()->get(uid, patchVersion));
- patch->setOrder(order);
- patch->setVanilla(true);
- patch->setRemovable(true);
- patch->setMovable(true);
- loadedPatches[uid] = patch;
- }
- };
- loadSpecial("net.minecraftforge", 5);
- loadSpecial("com.mumfrey.liteloader", 10);
-
- // now add all the patches by user sort order
- ProfileUtils::PatchOrder userOrder;
- ProfileUtils::readOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), userOrder);
- for (auto uid : userOrder)
- {
- // ignore builtins
- if (uid == "net.minecraft")
- continue;
- if (uid == "org.lwjgl")
- continue;
- // ordering has a patch that is gone?
- if(!loadedPatches.contains(uid))
- {
- continue;
- }
- profile->appendPatch(loadedPatches.take(uid));
- }
-
- // is there anything left to sort?
- if(loadedPatches.isEmpty())
- {
- // TODO: save the order here?
- return;
- }
-
- // inserting into multimap by order number as key sorts the patches and detects duplicates
- QMultiMap<int, ProfilePatchPtr> files;
- auto iter = loadedPatches.begin();
- while(iter != loadedPatches.end())
- {
- files.insert((*iter)->getOrder(), *iter);
- iter++;
- }
-
- // then just extract the patches and put them in the list
- for (auto order : files.keys())
- {
- const auto &values = files.values(order);
- for(auto &value: values)
- {
- // TODO: put back the insertion of problem messages here, so the user knows about the id duplication
- profile->appendPatch(value);
- }
- }
- // TODO: save the order here?
-}
-
-
-void OneSixProfileStrategy::load()
-{
- profile->clearPatches();
-
- upgradeDeprecatedFiles();
- loadDefaultBuiltinPatches();
- loadUserPatches();
-}
-
-bool OneSixProfileStrategy::saveOrder(ProfileUtils::PatchOrder order)
-{
- return ProfileUtils::writeOverrideOrders(FS::PathCombine(m_instance->instanceRoot(), "order.json"), order);
-}
-
-bool OneSixProfileStrategy::resetOrder()
-{
- return QDir(m_instance->instanceRoot()).remove("order.json");
-}
-
-bool OneSixProfileStrategy::removePatch(ProfilePatchPtr patch)
-{
- bool ok = true;
- // first, remove the patch file. this ensures it's not used anymore
- auto fileName = patch->getFilename();
- if(fileName.size())
- {
- QFile patchFile(fileName);
- if(patchFile.exists() && !patchFile.remove())
- {
- qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString();
- return false;
- }
- }
- if(!m_instance->getComponentVersion(patch->getID()).isEmpty())
- {
- m_instance->setComponentVersion(patch->getID(), QString());
- }
-
- // FIXME: we need a generic way of removing local resources, not just jar mods...
- auto preRemoveJarMod = [&](LibraryPtr jarMod) -> bool
- {
- if (!jarMod->isLocal())
- {
- return true;
- }
- QStringList jar, temp1, temp2, temp3;
- jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, m_instance->jarmodsPath().absolutePath());
- QFileInfo finfo (jar[0]);
- if(finfo.exists())
- {
- QFile jarModFile(jar[0]);
- if(!jarModFile.remove())
- {
- qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString();
- return false;
- }
- return true;
- }
- return true;
- };
-
- auto &jarMods = patch->getVersionFile()->jarMods;
- for(auto &jarmod: jarMods)
- {
- ok &= preRemoveJarMod(jarmod);
- }
- return ok;
-}
-
-bool OneSixProfileStrategy::customizePatch(ProfilePatchPtr patch)
-{
- if(patch->isCustom())
- {
- return false;
- }
-
- auto filename = FS::PathCombine(m_instance->instanceRoot(), "patches" , patch->getID() + ".json");
- if(!FS::ensureFilePathExists(filename))
- {
- return false;
- }
- // FIXME: get rid of this try-catch.
- try
- {
- QSaveFile jsonFile(filename);
- if(!jsonFile.open(QIODevice::WriteOnly))
- {
- return false;
- }
- auto vfile = patch->getVersionFile();
- if(!vfile)
- {
- return false;
- }
- auto document = OneSixVersionFormat::versionFileToJson(vfile, true);
- jsonFile.write(document.toJson());
- if(!jsonFile.commit())
- {
- return false;
- }
- load();
- }
- catch (Exception &error)
- {
- qWarning() << "Version could not be loaded:" << error.cause();
- }
- return true;
-}
-
-bool OneSixProfileStrategy::revertPatch(ProfilePatchPtr patch)
-{
- if(!patch->isCustom())
- {
- // already not custom
- return true;
- }
- auto filename = patch->getFilename();
- if(!QFile::exists(filename))
- {
- // already gone / not custom
- return true;
- }
- // just kill the file and reload
- bool result = QFile::remove(filename);
- // FIXME: get rid of this try-catch.
- try
- {
- load();
- }
- catch (Exception &error)
- {
- qWarning() << "Version could not be loaded:" << error.cause();
- }
- return result;
-}
-
-bool OneSixProfileStrategy::installJarMods(QStringList filepaths)
-{
- QString patchDir = FS::PathCombine(m_instance->instanceRoot(), "patches");
- if(!FS::ensureFolderPathExists(patchDir))
- {
- return false;
- }
-
- if (!FS::ensureFolderPathExists(m_instance->jarModsDir()))
- {
- return false;
- }
-
- for(auto filepath:filepaths)
- {
- 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 = FS::PathCombine(m_instance->jarModsDir(), target_filename);
-
- QFileInfo targetInfo(finalPath);
- if(targetInfo.exists())
- {
- return false;
- }
-
- if (!QFile::copy(sourceInfo.absoluteFilePath(),QFileInfo(finalPath).absoluteFilePath()))
- {
- return false;
- }
-
- auto f = std::make_shared<VersionFile>();
- auto jarMod = std::make_shared<Library>();
- jarMod->setRawName(GradleSpecifier("org.multimc.jarmods:" + id + ":1"));
- jarMod->setFilename(target_filename);
- jarMod->setDisplayName(sourceInfo.completeBaseName());
- jarMod->setHint("local");
- f->jarMods.append(jarMod);
- f->name = target_name;
- f->uid = target_id;
- f->order = profile->getFreeOrderNumber();
- QString patchFileName = FS::PathCombine(patchDir, target_id + ".json");
-
- QFile file(patchFileName);
- if (!file.open(QFile::WriteOnly))
- {
- qCritical() << "Error opening" << file.fileName()
- << "for reading:" << file.errorString();
- return false;
- }
- file.write(OneSixVersionFormat::versionFileToJson(f, true).toJson());
- file.close();
-
- auto patch = std::make_shared<ProfilePatch>(f, patchFileName);
- patch->setMovable(true);
- patch->setRemovable(true);
- profile->appendPatch(patch);
- }
- profile->saveCurrentOrder();
- profile->reapplyPatches();
- return true;
-}
-
diff --git a/api/logic/minecraft/onesix/OneSixProfileStrategy.h b/api/logic/minecraft/onesix/OneSixProfileStrategy.h
deleted file mode 100644
index 96c1ba7b..00000000
--- a/api/logic/minecraft/onesix/OneSixProfileStrategy.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-#include "minecraft/ProfileStrategy.h"
-
-class OneSixInstance;
-
-class OneSixProfileStrategy : public ProfileStrategy
-{
-public:
- OneSixProfileStrategy(OneSixInstance * instance);
- virtual ~OneSixProfileStrategy() {};
- virtual void load() override;
- virtual bool resetOrder() override;
- virtual bool saveOrder(ProfileUtils::PatchOrder order) override;
- virtual bool installJarMods(QStringList filepaths) override;
- virtual bool removePatch(ProfilePatchPtr patch) override;
- virtual bool customizePatch(ProfilePatchPtr patch) override;
- virtual bool revertPatch(ProfilePatchPtr patch) override;
-
-protected:
- virtual void loadDefaultBuiltinPatches();
- virtual void loadUserPatches();
- void upgradeDeprecatedFiles();
-
-protected:
- OneSixInstance *m_instance;
-};
diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp
index 21600ff0..2ad2b5b2 100644
--- a/api/logic/minecraft/onesix/update/AssetUpdateTask.cpp
+++ b/api/logic/minecraft/update/AssetUpdateTask.cpp
@@ -1,17 +1,19 @@
#include "Env.h"
#include "AssetUpdateTask.h"
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
#include "net/ChecksumValidator.h"
#include "minecraft/AssetsUtils.h"
-AssetUpdateTask::AssetUpdateTask(OneSixInstance * inst)
+AssetUpdateTask::AssetUpdateTask(MinecraftInstance * inst)
{
m_inst = inst;
}
void AssetUpdateTask::executeTask()
{
setStatus(tr("Updating assets index..."));
- auto profile = m_inst->getMinecraftProfile();
+ auto components = m_inst->getComponentList();
+ auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
QUrl indexUrl = assets->url;
QString localPath = assets->id + ".json";
@@ -47,7 +49,8 @@ void AssetUpdateTask::assetIndexFinished()
AssetsIndex index;
qDebug() << m_inst->name() << ": Finished asset index download";
- auto profile = m_inst->getMinecraftProfile();
+ auto components = m_inst->getComponentList();
+ auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
QString asset_fname = "assets/indexes/" + assets->id + ".json";
diff --git a/api/logic/minecraft/onesix/update/AssetUpdateTask.h b/api/logic/minecraft/update/AssetUpdateTask.h
index dff72571..c666faa6 100644
--- a/api/logic/minecraft/onesix/update/AssetUpdateTask.h
+++ b/api/logic/minecraft/update/AssetUpdateTask.h
@@ -1,12 +1,13 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
-class OneSixInstance;
+class MinecraftInstance;
class AssetUpdateTask : public Task
{
+ Q_OBJECT
public:
- AssetUpdateTask(OneSixInstance * inst);
+ AssetUpdateTask(MinecraftInstance * inst);
void executeTask() override;
bool canAbort() const override;
@@ -20,6 +21,6 @@ public slots:
bool abort() override;
private:
- OneSixInstance *m_inst;
+ MinecraftInstance *m_inst;
NetJobPtr downloadJob;
};
diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp
index 1cbee95e..1bd339e4 100644
--- a/api/logic/minecraft/onesix/update/FMLLibrariesTask.cpp
+++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp
@@ -2,26 +2,27 @@
#include <FileSystem.h>
#include <minecraft/VersionFilterData.h>
#include "FMLLibrariesTask.h"
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
-
-FMLLibrariesTask::FMLLibrariesTask(OneSixInstance * inst)
+FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
{
m_inst = inst;
}
void FMLLibrariesTask::executeTask()
{
// Get the mod list
- OneSixInstance *inst = (OneSixInstance *)m_inst;
- std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile();
- bool forge_present = false;
+ MinecraftInstance *inst = (MinecraftInstance *)m_inst;
+ auto components = inst->getComponentList();
+ auto profile = components->getProfile();
if (!profile->hasTrait("legacyFML"))
{
emitSucceeded();
+ return;
}
- QString version = inst->intendedVersionId();
+ QString version = components->getComponentVersion("net.minecraft");
auto &fmlLibsMapping = g_VersionFilterData.fmlLibsMapping;
if (!fmlLibsMapping.contains(version))
{
@@ -33,9 +34,7 @@ void FMLLibrariesTask::executeTask()
// determine if we need some libs for FML or forge
setStatus(tr("Checking for FML libraries..."));
- forge_present = (profile->versionPatch("net.minecraftforge") != nullptr);
- // we don't...
- if (!forge_present)
+ if(!components->getComponent("net.minecraftforge"))
{
emitSucceeded();
return;
@@ -87,7 +86,7 @@ void FMLLibrariesTask::fmllibsFinished()
if (!fmlLibsToProcess.isEmpty())
{
setStatus(tr("Copying FML libraries into the instance..."));
- OneSixInstance *inst = (OneSixInstance *)m_inst;
+ MinecraftInstance *inst = (MinecraftInstance *)m_inst;
auto metacache = ENV.metacache();
int index = 0;
for (auto &lib : fmlLibsToProcess)
diff --git a/api/logic/minecraft/onesix/update/FMLLibrariesTask.h b/api/logic/minecraft/update/FMLLibrariesTask.h
index d1c250e4..10f48f5b 100644
--- a/api/logic/minecraft/onesix/update/FMLLibrariesTask.h
+++ b/api/logic/minecraft/update/FMLLibrariesTask.h
@@ -1,12 +1,13 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
-class OneSixInstance;
+class MinecraftInstance;
class FMLLibrariesTask : public Task
{
+ Q_OBJECT
public:
- FMLLibrariesTask(OneSixInstance * inst);
+ FMLLibrariesTask(MinecraftInstance * inst);
void executeTask() override;
@@ -20,7 +21,7 @@ public slots:
bool abort() override;
private:
- OneSixInstance *m_inst;
+ MinecraftInstance *m_inst;
NetJobPtr downloadJob;
QList<FMLlib> fmlLibsToProcess;
};
diff --git a/api/logic/minecraft/onesix/update/FoldersTask.cpp b/api/logic/minecraft/update/FoldersTask.cpp
index 239a2675..34e2292a 100644
--- a/api/logic/minecraft/onesix/update/FoldersTask.cpp
+++ b/api/logic/minecraft/update/FoldersTask.cpp
@@ -1,8 +1,9 @@
#include "FoldersTask.h"
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
#include <QDir>
-FoldersTask::FoldersTask(OneSixInstance * inst)
+FoldersTask::FoldersTask(MinecraftInstance * inst)
+ :Task()
{
m_inst = inst;
}
diff --git a/api/logic/minecraft/onesix/update/FoldersTask.h b/api/logic/minecraft/update/FoldersTask.h
index 552d3098..6e669b1e 100644
--- a/api/logic/minecraft/onesix/update/FoldersTask.h
+++ b/api/logic/minecraft/update/FoldersTask.h
@@ -2,13 +2,14 @@
#include "tasks/Task.h"
-class OneSixInstance;
+class MinecraftInstance;
class FoldersTask : public Task
{
+ Q_OBJECT
public:
- FoldersTask(OneSixInstance * inst);
+ FoldersTask(MinecraftInstance * inst);
void executeTask() override;
private:
- OneSixInstance *m_inst;
+ MinecraftInstance *m_inst;
};
diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp
index 2cd41ded..0bec61c1 100644
--- a/api/logic/minecraft/onesix/update/LibrariesTask.cpp
+++ b/api/logic/minecraft/update/LibrariesTask.cpp
@@ -1,8 +1,9 @@
#include "Env.h"
#include "LibrariesTask.h"
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
-LibrariesTask::LibrariesTask(OneSixInstance * inst)
+LibrariesTask::LibrariesTask(MinecraftInstance * inst)
{
m_inst = inst;
}
@@ -11,16 +12,11 @@ void LibrariesTask::executeTask()
{
setStatus(tr("Getting the library files from Mojang..."));
qDebug() << m_inst->name() << ": downloading libraries";
- OneSixInstance *inst = (OneSixInstance *)m_inst;
- inst->reloadProfile();
- if(inst->hasVersionBroken())
- {
- emitFailed(tr("Failed to load the version description files - check the instance for errors."));
- return;
- }
+ MinecraftInstance *inst = (MinecraftInstance *)m_inst;
// Build a list of URLs that will need to be downloaded.
- std::shared_ptr<MinecraftProfile> profile = inst->getMinecraftProfile();
+ auto components = inst->getComponentList();
+ auto profile = components->getProfile();
auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()));
downloadJob.reset(job);
diff --git a/api/logic/minecraft/onesix/update/LibrariesTask.h b/api/logic/minecraft/update/LibrariesTask.h
index 80cf0d2a..d06a5037 100644
--- a/api/logic/minecraft/onesix/update/LibrariesTask.h
+++ b/api/logic/minecraft/update/LibrariesTask.h
@@ -1,12 +1,13 @@
#pragma once
#include "tasks/Task.h"
#include "net/NetJob.h"
-class OneSixInstance;
+class MinecraftInstance;
class LibrariesTask : public Task
{
+ Q_OBJECT
public:
- LibrariesTask(OneSixInstance * inst);
+ LibrariesTask(MinecraftInstance * inst);
void executeTask() override;
@@ -19,6 +20,6 @@ public slots:
bool abort() override;
private:
- OneSixInstance *m_inst;
+ MinecraftInstance *m_inst;
NetJobPtr downloadJob;
};
diff --git a/api/logic/modplatform/FtbPackDownloader.cpp b/api/logic/modplatform/FtbPackDownloader.cpp
new file mode 100644
index 00000000..a3951bfd
--- /dev/null
+++ b/api/logic/modplatform/FtbPackDownloader.cpp
@@ -0,0 +1,106 @@
+#include "FtbPackDownloader.h"
+#include "PackHelpers.h"
+#include "FtbPackFetchTask.h"
+#include "Env.h"
+
+FtbPackDownloader::FtbPackDownloader() {
+ done = false;
+ fetching = false;
+}
+
+FtbPackDownloader::~FtbPackDownloader(){
+ delete netJobContainer.get();
+ netJobContainer.reset(nullptr);
+}
+
+bool FtbPackDownloader::isValidPackSelected(){
+ FtbModpack dummy;
+ dummy.name = "__INVALID__";
+
+ FtbModpack other = fetchedPacks.value(selected.name, dummy);
+ if(other.name == "__INVALID__") {
+ return false;
+ }
+
+ return other.oldVersions.contains(selectedVersion);
+}
+
+QString FtbPackDownloader::getSuggestedInstanceName() {
+ return selected.name;
+}
+
+FtbModpackList FtbPackDownloader::getModpacks() {
+ return static_cast<FtbModpackList>(fetchedPacks.values());
+}
+
+void FtbPackDownloader::fetchModpacks(bool force = false){
+ if(fetching || (!force && done)) {
+ qDebug() << "Skipping modpack refetch because done or already fetching [done =>" << done << "| fetching =>" << fetching << "]";
+ return;
+ }
+
+ fetching = true;
+
+ fetchTask = new FtbPackFetchTask();
+ connect(fetchTask, &FtbPackFetchTask::finished, this, &FtbPackDownloader::fetchSuccess);
+ connect(fetchTask, &FtbPackFetchTask::failed, this, &FtbPackDownloader::fetchFailed);
+ fetchTask->fetch();
+}
+
+
+void FtbPackDownloader::fetchSuccess(FtbModpackList modpacks) {
+ for(int i = 0; i < modpacks.size(); i++) {
+ fetchedPacks.insert(modpacks.at(i).name, modpacks.at(i));
+ }
+
+ fetching = false;
+ done = true;
+ emit ready();
+ fetchTask->deleteLater();
+}
+
+void FtbPackDownloader::fetchFailed(QString reason) {
+ qWarning() << "Failed to fetch FtbData" << reason;
+ fetching = false;
+ emit packFetchFailed();
+ fetchTask->deleteLater();
+}
+
+void FtbPackDownloader::selectPack(FtbModpack modpack, QString version) {
+ selected = modpack;
+ selectedVersion = version;
+}
+
+FtbModpack FtbPackDownloader::getSelectedPack() {
+ return selected;
+}
+
+void FtbPackDownloader::downloadSelected(MetaEntryPtr cache) {
+ NetJob *job = new NetJob("Downlad FTB Pack");
+
+ cache->setStale(true);
+ QString url = QString("http://ftb.cursecdn.com/FTB2/modpacks/%1/%2/%3").arg(selected.dir, selectedVersion.replace(".", "_"), selected.file);
+ job->addNetAction(Net::Download::makeCached(url, cache));
+ downloadPath = cache->getFullPath();
+
+ netJobContainer.reset(job);
+
+ connect(job, &NetJob::succeeded, this, &FtbPackDownloader::_downloadSucceeded);
+ connect(job, &NetJob::failed, this, &FtbPackDownloader::_downloadFailed);
+ connect(job, &NetJob::progress, this, &FtbPackDownloader::_downloadProgress);
+ job->start();
+}
+
+void FtbPackDownloader::_downloadSucceeded() {
+ netJobContainer.reset();
+ emit downloadSucceded(downloadPath);
+}
+
+void FtbPackDownloader::_downloadProgress(qint64 current, qint64 total) {
+ emit downloadProgress(current, total);
+}
+
+void FtbPackDownloader::_downloadFailed(QString reason) {
+ netJobContainer.reset();
+ emit downloadFailed(reason);
+}
diff --git a/api/logic/modplatform/FtbPackDownloader.h b/api/logic/modplatform/FtbPackDownloader.h
new file mode 100644
index 00000000..45490afc
--- /dev/null
+++ b/api/logic/modplatform/FtbPackDownloader.h
@@ -0,0 +1,63 @@
+#include <QString>
+#include <QUrl>
+#include <QList>
+#include <QObject>
+#include "FtbPackFetchTask.h"
+#include "tasks/Task.h"
+#include "net/NetJob.h"
+
+#include "PackHelpers.h"
+#include "Env.h"
+
+#pragma once
+
+class FtbPackDownloader;
+class MULTIMC_LOGIC_EXPORT FtbPackDownloader : public QObject {
+
+ Q_OBJECT
+
+private:
+ QMap<QString, FtbModpack> fetchedPacks;
+ bool fetching;
+ bool done;
+
+ FtbModpack selected;
+ QString selectedVersion;
+ QString downloadPath;
+
+ FtbPackFetchTask *fetchTask = 0;
+ NetJobPtr netJobContainer;
+
+ void _downloadSucceeded();
+ void _downloadFailed(QString reason);
+ void _downloadProgress(qint64 current, qint64 total);
+
+private slots:
+ void fetchSuccess(FtbModpackList modlist);
+ void fetchFailed(QString reason);
+
+public:
+ FtbPackDownloader();
+ ~FtbPackDownloader();
+
+ bool isValidPackSelected();
+ void selectPack(FtbModpack modpack, QString version);
+
+ FtbModpack getSelectedPack();
+
+ void fetchModpacks(bool force);
+ void downloadSelected(MetaEntryPtr cache);
+
+ QString getSuggestedInstanceName();
+
+ FtbModpackList getModpacks();
+
+signals:
+ void ready();
+ void packFetchFailed();
+
+ void downloadSucceded(QString archivePath);
+ void downloadFailed(QString reason);
+ void downloadProgress(qint64 current, qint64 total);
+
+};
diff --git a/api/logic/modplatform/FtbPackFetchTask.cpp b/api/logic/modplatform/FtbPackFetchTask.cpp
new file mode 100644
index 00000000..6f578e04
--- /dev/null
+++ b/api/logic/modplatform/FtbPackFetchTask.cpp
@@ -0,0 +1,75 @@
+#include "FtbPackFetchTask.h"
+#include <QDomDocument>
+
+FtbPackFetchTask::FtbPackFetchTask() {
+
+}
+
+FtbPackFetchTask::~FtbPackFetchTask() {
+
+}
+
+void FtbPackFetchTask::fetch() {
+ NetJob *netJob = new NetJob("FtbModpackFetch");
+
+ QUrl url = QUrl("https://ftb.cursecdn.com/FTB2/static/modpacks.xml");
+ qDebug() << "Downloading version info from " << url.toString();
+
+ netJob->addNetAction(downloadPtr = Net::Download::makeByteArray(url, &modpacksXmlFileData));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed);
+
+ jobPtr.reset(netJob);
+ netJob->start();
+}
+
+void FtbPackFetchTask::fileDownloadFinished(){
+
+ jobPtr.reset();
+
+ QDomDocument doc;
+
+ QString errorMsg = "Unknown error.";
+ int errorLine = -1;
+ int errorCol = -1;
+
+ if(!doc.setContent(modpacksXmlFileData, false, &errorMsg, &errorLine, &errorCol)){
+ auto fullErrMsg = QString("Failed to fetch modpack data: %s %d:%d!").arg(errorMsg, errorLine, errorCol);
+ qWarning() << fullErrMsg;
+ emit failed(fullErrMsg);
+ modpacksXmlFileData.clear();
+ return;
+ }
+
+ modpacksXmlFileData.clear();
+
+ FtbModpackList modpackList;
+
+ QDomNodeList nodes = doc.elementsByTagName("modpack");
+ for(int i = 0; i < nodes.length(); i++) {
+ QDomElement element = nodes.at(i).toElement();
+
+ FtbModpack modpack;
+ modpack.name = element.attribute("name");
+ modpack.currentVersion = element.attribute("version");
+ modpack.mcVersion = element.attribute("mcVersion");
+ modpack.description = element.attribute("description");
+ modpack.mods = element.attribute("mods");
+ modpack.image = element.attribute("image");
+ modpack.oldVersions = element.attribute("oldVersions").split(";");
+ modpack.author = element.attribute("author");
+
+ modpack.dir = element.attribute("dir");
+ modpack.file = element.attribute("url");
+
+ modpackList.append(modpack);
+ }
+
+ emit finished(modpackList);
+}
+
+void FtbPackFetchTask::fileDownloadFailed(QString reason){
+ qWarning() << "Fetching FtbPacks failed: " << reason;
+ emit failed(reason);
+}
diff --git a/api/logic/modplatform/FtbPackFetchTask.h b/api/logic/modplatform/FtbPackFetchTask.h
new file mode 100644
index 00000000..df5a96e6
--- /dev/null
+++ b/api/logic/modplatform/FtbPackFetchTask.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include "multimc_logic_export.h"
+#include "net/NetJob.h"
+#include <QTemporaryDir>
+#include <QByteArray>
+#include <QObject>
+#include "PackHelpers.h"
+
+class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject {
+
+ Q_OBJECT
+
+public:
+ FtbPackFetchTask();
+ ~FtbPackFetchTask();
+
+ void fetch();
+
+private:
+ NetJobPtr jobPtr;
+ Net::Download::Ptr downloadPtr;
+
+ QByteArray modpacksXmlFileData;
+
+protected slots:
+ void fileDownloadFinished();
+ void fileDownloadFailed(QString reason);
+
+signals:
+ void finished(FtbModpackList list);
+ void failed(QString reason);
+
+};
diff --git a/api/logic/modplatform/FtbPackInstallTask.cpp b/api/logic/modplatform/FtbPackInstallTask.cpp
new file mode 100644
index 00000000..bedf3942
--- /dev/null
+++ b/api/logic/modplatform/FtbPackInstallTask.cpp
@@ -0,0 +1,65 @@
+#include "FtbPackInstallTask.h"
+#include "Env.h"
+#include "MMCZip.h"
+#include "QtConcurrent"
+
+FtbPackInstallTask::FtbPackInstallTask(FtbPackDownloader *downloader, SettingsObjectPtr settings,
+ const QString &stagingPath, const QString &instName, const QString &instIcon, const QString &instGroup) :
+ m_globalSettings(settings), m_stagingPath(stagingPath), m_instName(instName), m_instIcon(instIcon), m_instGroup(instGroup)
+{
+ m_downloader = downloader;
+}
+
+void FtbPackInstallTask::executeTask() {
+ downloadPack();
+}
+
+void FtbPackInstallTask::downloadPack(){
+ FtbModpack toInstall = m_downloader->getSelectedPack();
+ setStatus(tr("Installing new FTB Pack %1").arg(toInstall.name));
+
+ auto entry = ENV.metacache()->resolveEntry("general", "FTBPack/" + toInstall.name);
+ m_downloader->downloadSelected(entry);
+
+ connect(m_downloader, &FtbPackDownloader::downloadSucceded, this, &FtbPackInstallTask::onDownloadSucceeded);
+ connect(m_downloader, &FtbPackDownloader::downloadProgress, this, &FtbPackInstallTask::onDownloadProgress);
+ connect(m_downloader, &FtbPackDownloader::downloadFailed, this,&FtbPackInstallTask::onDownloadFailed);
+}
+
+void FtbPackInstallTask::onDownloadSucceeded(QString archivePath){
+ qDebug() << "Download succeeded!";
+ unzip(archivePath);
+}
+
+void FtbPackInstallTask::onDownloadFailed(QString reason) {
+ emitFailed(reason);
+}
+
+void FtbPackInstallTask::onDownloadProgress(qint64 current, qint64 total){
+ progress(current, total);
+}
+
+void FtbPackInstallTask::unzip(QString archivePath) {
+ setStatus(QString("Extracting modpack from %1").arg(archivePath));
+ QDir extractDir(m_stagingPath);
+
+ m_packZip.reset(new QuaZip(archivePath));
+ if(!m_packZip->open(QuaZip::mdUnzip)) {
+ emitFailed(tr("Failed to open modpack file %1!").arg(archivePath));
+ return;
+ }
+
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString("/"), extractDir.absolutePath());
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &FtbPackInstallTask::onUnzipFinished);
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &FtbPackInstallTask::onUnzipCanceled);
+ m_extractFutureWatcher.setFuture(m_extractFuture);
+}
+
+void FtbPackInstallTask::onUnzipFinished() {
+ qDebug() << "Unzipped:" << m_stagingPath;
+ emitSucceeded();
+}
+
+void FtbPackInstallTask::onUnzipCanceled() {
+ emitAborted();
+}
diff --git a/api/logic/modplatform/FtbPackInstallTask.h b/api/logic/modplatform/FtbPackInstallTask.h
new file mode 100644
index 00000000..23ef0811
--- /dev/null
+++ b/api/logic/modplatform/FtbPackInstallTask.h
@@ -0,0 +1,45 @@
+#pragma once
+#include "tasks/Task.h"
+#include "modplatform/FtbPackDownloader.h"
+#include "BaseInstanceProvider.h"
+#include "net/NetJob.h"
+#include "quazip.h"
+#include "quazipdir.h"
+
+class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public Task {
+
+ Q_OBJECT
+
+public:
+ explicit FtbPackInstallTask(FtbPackDownloader *downloader, SettingsObjectPtr settings, const QString & stagingPath, const QString &instName,
+ const QString &instIcon, const QString &instGroup);
+
+protected:
+ //! Entry point for tasks.
+ virtual void executeTask() override;
+
+private: /* data */
+ SettingsObjectPtr m_globalSettings;
+ QString m_stagingPath;
+ QString m_instName;
+ QString m_instIcon;
+ QString m_instGroup;
+ NetJobPtr m_netJobPtr;
+ FtbPackDownloader *m_downloader = nullptr;
+
+ std::unique_ptr<QuaZip> m_packZip;
+ QFuture<QStringList> m_extractFuture;
+ QFutureWatcher<QStringList> m_extractFutureWatcher;
+
+ void downloadPack();
+ void unzip(QString archivePath);
+ void install();
+
+private slots:
+ void onDownloadSucceeded(QString archivePath);
+ void onDownloadFailed(QString reason);
+ void onDownloadProgress(qint64 current, qint64 total);
+
+ void onUnzipFinished();
+ void onUnzipCanceled();
+};
diff --git a/api/logic/modplatform/PackHelpers.h b/api/logic/modplatform/PackHelpers.h
new file mode 100644
index 00000000..ba0e5cb0
--- /dev/null
+++ b/api/logic/modplatform/PackHelpers.h
@@ -0,0 +1,21 @@
+#pragma once
+#include <QList>
+
+//Header for structs etc...
+
+struct FtbModpack {
+ QString name;
+ QString description;
+ QString author;
+ QStringList oldVersions;
+ QString currentVersion;
+ QString mcVersion;
+ QString mods;
+ QString image;
+
+ //Technical data
+ QString dir;
+ QString file; //<- Url in the xml, but doesn't make much sense
+};
+
+typedef QList<FtbModpack> FtbModpackList;
diff --git a/api/logic/net/Download.cpp b/api/logic/net/Download.cpp
index 12c1b201..631d3076 100644
--- a/api/logic/net/Download.cpp
+++ b/api/logic/net/Download.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -102,7 +102,8 @@ void Download::start()
connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
- connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead()));
+ connect(rep, &QNetworkReply::sslErrors, this, &Download::sslErrors);
+ connect(rep, &QNetworkReply::readyRead, this, &Download::downloadReadyRead);
}
void Download::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
@@ -135,31 +136,68 @@ void Download::downloadError(QNetworkReply::NetworkError error)
}
}
-bool Download::handleRedirect()
+void Download::sslErrors(const QList<QSslError> & errors)
{
- QVariant redirect = m_reply->header(QNetworkRequest::LocationHeader);
- QString redirectURL;
- if(redirect.isValid())
+ int i = 1;
+ for (auto error : errors)
{
- redirectURL = redirect.toString();
+ qCritical() << "Download" << m_url.toString() << "SSL Error #" << i << " : " << error.errorString();
+ auto cert = error.certificate();
+ qCritical() << "Certificate in question:\n" << cert.toText();
+ i++;
}
- // FIXME: This is a hack for https://bugreports.qt-project.org/browse/QTBUG-41061
- else if(m_reply->hasRawHeader("Location"))
+}
+
+bool Download::handleRedirect()
+{
+ QUrl redirect = m_reply->header(QNetworkRequest::LocationHeader).toUrl();
+ if(!redirect.isValid())
{
- auto data = m_reply->rawHeader("Location");
- if(data.size() > 2 && data[0] == '/' && data[1] == '/')
+ if(!m_reply->hasRawHeader("Location"))
+ {
+ // no redirect -> it's fine to continue
+ return false;
+ }
+ // there is a Location header, but it's not correct. we need to apply some workarounds...
+ QByteArray redirectBA = m_reply->rawHeader("Location");
+ if(redirectBA.size() == 0)
+ {
+ // empty, yet present redirect header? WTF?
+ return false;
+ }
+ QString redirectStr = QString::fromUtf8(redirectBA);
+
+ /*
+ * IF the URL begins with //, we need to insert the URL scheme.
+ * See: https://bugreports.qt-project.org/browse/QTBUG-41061
+ */
+ if(redirectStr.startsWith("//"))
+ {
+ redirectStr = m_reply->url().scheme() + ":" + redirectStr;
+ }
+
+ /*
+ * Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
+ * FIXME: report Qt bug for this
+ */
+ redirect = QUrl(redirectStr, QUrl::TolerantMode);
+ if(!redirect.isValid())
{
- redirectURL = m_reply->url().scheme() + ":" + data;
+ qWarning() << "Failed to parse redirect URL:" << redirectStr;
+ downloadError(QNetworkReply::ProtocolFailure);
+ return false;
}
+ qDebug() << "Fixed location header:" << redirect;
}
- if (!redirectURL.isEmpty())
+ else
{
- m_url = QUrl(redirect.toString());
- qDebug() << "Following redirect to " << m_url.toString();
- start();
- return true;
+ qDebug() << "Location header:" << redirect;
}
- return false;
+
+ m_url = QUrl(redirect.toString());
+ qDebug() << "Following redirect to " << m_url.toString();
+ start();
+ return true;
}
diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h
index 3347dc96..00bf108c 100644
--- a/api/logic/net/Download.h
+++ b/api/logic/net/Download.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -58,6 +58,7 @@ private: /* methods */
protected slots:
void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
void downloadError(QNetworkReply::NetworkError error) override;
+ void sslErrors(const QList<QSslError> & errors);
void downloadFinished() override;
void downloadReadyRead() override;
diff --git a/api/logic/net/HttpMetaCache.cpp b/api/logic/net/HttpMetaCache.cpp
index 3eec185b..ebcb0a27 100644
--- a/api/logic/net/HttpMetaCache.cpp
+++ b/api/logic/net/HttpMetaCache.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/net/HttpMetaCache.h b/api/logic/net/HttpMetaCache.h
index 21c9b6c8..7ee5f735 100644
--- a/api/logic/net/HttpMetaCache.h
+++ b/api/logic/net/HttpMetaCache.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/net/Mode.h b/api/logic/net/Mode.h
new file mode 100644
index 00000000..62e26d92
--- /dev/null
+++ b/api/logic/net/Mode.h
@@ -0,0 +1,10 @@
+#pragma once
+
+namespace Net
+{
+enum class Mode
+{
+ Offline,
+ Online
+};
+}
diff --git a/api/logic/net/NetAction.h b/api/logic/net/NetAction.h
index a533c317..08e40a29 100644
--- a/api/logic/net/NetAction.h
+++ b/api/logic/net/NetAction.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,6 +30,10 @@ enum JobStatus
Job_Finished,
Job_Failed,
Job_Aborted,
+ /*
+ * FIXME: @NUKE this confuses the task failing with us having a fallback in the form of local data. Clear up the confusion.
+ * Same could be true for aborted task - the presence of pre-existing result is a separate concern
+ */
Job_Failed_Proceed
};
@@ -43,18 +47,26 @@ protected:
public:
virtual ~NetAction() {};
-public:
- virtual qint64 totalProgress() const
+ bool isRunning() const
{
- return m_total_progress;
+ return m_status == Job_InProgress;
}
- virtual qint64 currentProgress() const
+ bool isFinished() const
{
- return m_progress;
+ return m_status >= Job_Finished;
}
- virtual qint64 numberOfFailures() const
+ bool wasSuccessful() const
{
- return m_failures;
+ return m_status == Job_Finished || m_status == Job_Failed_Proceed;
+ }
+
+ qint64 totalProgress() const
+ {
+ return m_total_progress;
+ }
+ qint64 currentProgress() const
+ {
+ return m_progress;
}
virtual bool abort()
{
@@ -64,25 +76,10 @@ public:
{
return false;
}
-
-public:
- /// the network reply
- unique_qobject_ptr<QNetworkReply> m_reply;
-
- /// source URL
- QUrl m_url;
-
- /// The file's status
- JobStatus m_status = Job_NotStarted;
-
- /// index within the parent job
- int m_index_within_job = 0;
-
- qint64 m_progress = 0;
- qint64 m_total_progress = 1;
-
- /// number of failures up to this point
- int m_failures = 0;
+ QUrl url()
+ {
+ return m_url;
+ }
signals:
void started(int index);
@@ -91,14 +88,28 @@ signals:
void failed(int index);
void aborted(int index);
-protected
-slots:
+protected slots:
virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0;
virtual void downloadError(QNetworkReply::NetworkError error) = 0;
virtual void downloadFinished() = 0;
virtual void downloadReadyRead() = 0;
-public
-slots:
+public slots:
virtual void start() = 0;
+
+public:
+ /// index within the parent job, FIXME: nuke
+ int m_index_within_job = 0;
+
+ /// the network reply
+ unique_qobject_ptr<QNetworkReply> m_reply;
+
+ /// source URL
+ QUrl m_url;
+
+ qint64 m_progress = 0;
+ qint64 m_total_progress = 1;
+
+protected:
+ JobStatus m_status = Job_NotStarted;
};
diff --git a/api/logic/net/NetJob.cpp b/api/logic/net/NetJob.cpp
index 275da749..304b5820 100644
--- a/api/logic/net/NetJob.cpp
+++ b/api/logic/net/NetJob.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -59,55 +59,74 @@ void NetJob::partAborted(int index)
void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal)
{
auto &slot = parts_progress[index];
-
- current_progress -= slot.current_progress;
slot.current_progress = bytesReceived;
- current_progress += slot.current_progress;
-
- total_progress -= slot.total_progress;
slot.total_progress = bytesTotal;
- total_progress += slot.total_progress;
- setProgress(current_progress, total_progress);
+
+ int done = m_done.size();
+ int doing = m_doing.size();
+ int all = parts_progress.size();
+
+ qint64 bytesAll = 0;
+ qint64 bytesTotalAll = 0;
+ for(auto & partIdx: m_doing)
+ {
+ auto part = parts_progress[partIdx];
+ // do not count parts with unknown/nonsensical total size
+ if(part.total_progress <= 0)
+ {
+ continue;
+ }
+ bytesAll += part.current_progress;
+ bytesTotalAll += part.total_progress;
+ }
+
+ qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll;
+ auto current = done * 1000 + doing * inprogress;
+ auto current_total = all * 1000;
+ // HACK: make sure it never jumps backwards.
+ if(m_current_progress > current)
+ {
+ current = m_current_progress;
+ }
+ m_current_progress = current;
+ setProgress(current, current_total);
}
void NetJob::executeTask()
{
- qDebug() << m_job_name.toLocal8Bit() << " started.";
- m_running = true;
- for (int i = 0; i < downloads.size(); i++)
- {
- m_todo.enqueue(i);
- }
// hack that delays early failures so they can be caught easier
QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection);
}
void NetJob::startMoreParts()
{
- // check for final conditions if there's nothing in the queue
+ if(!isRunning())
+ {
+ // this actually makes sense. You can put running downloads into a NetJob and then not start it until much later.
+ return;
+ }
+ // OK. We are actively processing tasks, proceed.
+ // Check for final conditions if there's nothing in the queue.
if(!m_todo.size())
{
if(!m_doing.size())
{
if(!m_failed.size())
{
- qDebug() << m_job_name << "succeeded.";
emitSucceeded();
}
else if(m_aborted)
{
- qDebug() << m_job_name << "aborted.";
- emitFailed(tr("Job '%1' aborted.").arg(m_job_name));
+ emitAborted();
}
else
{
- qCritical() << m_job_name << "failed.";
- emitFailed(tr("Job '%1' failed to process:\n%2").arg(m_job_name).arg(getFailedFiles().join("\n")));
+ emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n")));
}
}
return;
}
- // otherwise try to start more parts
+ // There's work to do, try to start more parts.
while (m_doing.size() < 6)
{
if(!m_todo.size())
@@ -131,7 +150,7 @@ QStringList NetJob::getFailedFiles()
QStringList failed;
for (auto index: m_failed)
{
- failed.push_back(downloads[index]->m_url.toString());
+ failed.push_back(downloads[index]->url().toString());
}
failed.sort();
return failed;
@@ -170,3 +189,24 @@ bool NetJob::abort()
}
return fullyAborted;
}
+
+bool NetJob::addNetAction(NetActionPtr action)
+{
+ action->m_index_within_job = downloads.size();
+ downloads.append(action);
+ part_info pi;
+ parts_progress.append(pi);
+ partProgress(parts_progress.count() - 1, action->currentProgress(), action->totalProgress());
+
+ if(action->isRunning())
+ {
+ connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
+ connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
+ connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)), SLOT(partProgress(int, qint64, qint64)));
+ }
+ else
+ {
+ m_todo.append(parts_progress.size() - 1);
+ }
+ return true;
+}
diff --git a/api/logic/net/NetJob.h b/api/logic/net/NetJob.h
index ca4f5df1..be58c61a 100644
--- a/api/logic/net/NetJob.h
+++ b/api/logic/net/NetJob.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -30,32 +30,13 @@ class MULTIMC_LOGIC_EXPORT NetJob : public Task
{
Q_OBJECT
public:
- explicit NetJob(QString job_name) : Task(), m_job_name(job_name) {}
- virtual ~NetJob() {}
- bool addNetAction(NetActionPtr action)
+ explicit NetJob(QString job_name) : Task()
{
- action->m_index_within_job = downloads.size();
- downloads.append(action);
- part_info pi;
- {
- pi.current_progress = action->currentProgress();
- pi.total_progress = action->totalProgress();
- pi.failures = action->numberOfFailures();
- }
- parts_progress.append(pi);
- total_progress += pi.total_progress;
- // if this is already running, the action needs to be started right away!
- if (isRunning())
- {
- setProgress(current_progress, total_progress);
- connect(action.get(), SIGNAL(succeeded(int)), SLOT(partSucceeded(int)));
- connect(action.get(), SIGNAL(failed(int)), SLOT(partFailed(int)));
- connect(action.get(), SIGNAL(netActionProgress(int, qint64, qint64)),
- SLOT(partProgress(int, qint64, qint64)));
- action->start();
- }
- return true;
+ setObjectName(job_name);
}
+ virtual ~NetJob() {}
+
+ bool addNetAction(NetActionPtr action);
NetActionPtr operator[](int index)
{
@@ -75,10 +56,6 @@ public:
{
return downloads.size();
}
- virtual bool isRunning() const override
- {
- return m_running;
- }
QStringList getFailedFiles();
bool canAbort() const override;
@@ -102,17 +79,13 @@ private:
qint64 current_progress = 0;
qint64 total_progress = 1;
int failures = 0;
- bool connected = false;
};
- QString m_job_name;
QList<NetActionPtr> downloads;
QList<part_info> parts_progress;
QQueue<int> m_todo;
QSet<int> m_doing;
QSet<int> m_done;
QSet<int> m_failed;
- qint64 current_progress = 0;
- qint64 total_progress = 0;
- bool m_running = false;
+ qint64 m_current_progress = 0;
bool m_aborted = false;
};
diff --git a/api/logic/net/PasteUpload.cpp b/api/logic/net/PasteUpload.cpp
index 59779b2c..d1ddf39d 100644
--- a/api/logic/net/PasteUpload.cpp
+++ b/api/logic/net/PasteUpload.cpp
@@ -2,41 +2,45 @@
#include "Env.h"
#include <QDebug>
#include <QJsonObject>
+#include <QJsonArray>
#include <QJsonDocument>
+#include <QFile>
PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window)
{
m_key = key;
QByteArray temp;
- temp = text.toUtf8();
- temp.replace('\n', "\r\n");
- m_textSize = temp.size();
- m_text = "key=" + m_key.toLatin1() + "&description=MultiMC5+Log+File&language=plain&format=json&expire=2592000&paste=" + temp.toPercentEncoding();
- buf = new QBuffer(&m_text);
+ QJsonObject topLevelObj;
+ QJsonObject sectionObject;
+ sectionObject.insert("contents", text);
+ QJsonArray sectionArray;
+ sectionArray.append(sectionObject);
+ topLevelObj.insert("description", "MultiMC Log Upload");
+ topLevelObj.insert("sections", sectionArray);
+ QJsonDocument docOut;
+ docOut.setObject(topLevelObj);
+ m_jsonContent = docOut.toJson();
}
PasteUpload::~PasteUpload()
{
- if(buf)
- {
- delete buf;
- }
}
bool PasteUpload::validateText()
{
- return m_textSize <= maxSize();
+ return m_jsonContent.size() <= maxSize();
}
void PasteUpload::executeTask()
{
- QNetworkRequest request(QUrl("https://paste.ee/api"));
+ QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes"));
request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
- request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
- request.setRawHeader("Content-Length", QByteArray::number(m_text.size()));
+ request.setRawHeader("Content-Type", "application/json");
+ request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size()));
+ request.setRawHeader("X-Auth-Token", m_key.toStdString().c_str());
- QNetworkReply *rep = ENV.qnam().post(request, buf);
+ QNetworkReply *rep = ENV.qnam().post(request, m_jsonContent);
m_reply = std::shared_ptr<QNetworkReply>(rep);
setStatus(tr("Uploading to paste.ee"));
@@ -54,10 +58,10 @@ void PasteUpload::downloadError(QNetworkReply::NetworkError error)
void PasteUpload::downloadFinished()
{
+ QByteArray data = m_reply->readAll();
// if the download succeeded
if (m_reply->error() == QNetworkReply::NetworkError::NoError)
{
- QByteArray data = m_reply->readAll();
m_reply.reset();
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(data, &jsonError);
@@ -85,14 +89,15 @@ void PasteUpload::downloadFinished()
bool PasteUpload::parseResult(QJsonDocument doc)
{
auto object = doc.object();
- auto status = object.value("status").toString("error");
- if (status == "error")
+ auto status = object.value("success").toBool();
+ if (!status)
{
qCritical() << "paste.ee reported error:" << QString(object.value("error").toString());
return false;
}
- m_pasteLink = object.value("paste").toObject().value("link").toString();
- m_pasteID = object.value("paste").toObject().value("id").toString();
+ m_pasteLink = object.value("link").toString();
+ m_pasteID = object.value("id").toString();
+ qDebug() << m_pasteLink;
return true;
}
diff --git a/api/logic/net/PasteUpload.h b/api/logic/net/PasteUpload.h
index 78d1da8e..62d2e766 100644
--- a/api/logic/net/PasteUpload.h
+++ b/api/logic/net/PasteUpload.h
@@ -34,14 +34,12 @@ protected:
private:
bool parseResult(QJsonDocument doc);
- QByteArray m_text;
QString m_error;
QWidget *m_window;
QString m_pasteID;
QString m_pasteLink;
QString m_key;
- int m_textSize = 0;
- QBuffer * buf = nullptr;
+ QByteArray m_jsonContent;
std::shared_ptr<QNetworkReply> m_reply;
public
slots:
diff --git a/api/logic/net/URLConstants.h b/api/logic/net/URLConstants.h
index dedb1424..22d128b2 100644
--- a/api/logic/net/URLConstants.h
+++ b/api/logic/net/URLConstants.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/news/NewsChecker.cpp b/api/logic/news/NewsChecker.cpp
index 47cdfc05..0ee3f9da 100644
--- a/api/logic/news/NewsChecker.cpp
+++ b/api/logic/news/NewsChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/news/NewsChecker.h b/api/logic/news/NewsChecker.h
index ba701a9d..44f2534a 100644
--- a/api/logic/news/NewsChecker.h
+++ b/api/logic/news/NewsChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/news/NewsEntry.cpp b/api/logic/news/NewsEntry.cpp
index da9f00a7..4377f766 100644
--- a/api/logic/news/NewsEntry.cpp
+++ b/api/logic/news/NewsEntry.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/news/NewsEntry.h b/api/logic/news/NewsEntry.h
index 8a91399a..16a17f9c 100644
--- a/api/logic/news/NewsEntry.h
+++ b/api/logic/news/NewsEntry.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/INIFile.cpp b/api/logic/settings/INIFile.cpp
index e2162931..9e97f861 100644
--- a/api/logic/settings/INIFile.cpp
+++ b/api/logic/settings/INIFile.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/INIFile.h b/api/logic/settings/INIFile.h
index 3295209e..f0c63d3c 100644
--- a/api/logic/settings/INIFile.h
+++ b/api/logic/settings/INIFile.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/INISettingsObject.cpp b/api/logic/settings/INISettingsObject.cpp
index 0a1628d2..ff2cee31 100644
--- a/api/logic/settings/INISettingsObject.cpp
+++ b/api/logic/settings/INISettingsObject.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/INISettingsObject.h b/api/logic/settings/INISettingsObject.h
index b776db59..111215e6 100644
--- a/api/logic/settings/INISettingsObject.h
+++ b/api/logic/settings/INISettingsObject.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/OverrideSetting.cpp b/api/logic/settings/OverrideSetting.cpp
index 1d7b56b5..a3d48e03 100644
--- a/api/logic/settings/OverrideSetting.cpp
+++ b/api/logic/settings/OverrideSetting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/OverrideSetting.h b/api/logic/settings/OverrideSetting.h
index 70c0b817..f2cbc5dc 100644
--- a/api/logic/settings/OverrideSetting.h
+++ b/api/logic/settings/OverrideSetting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/PassthroughSetting.cpp b/api/logic/settings/PassthroughSetting.cpp
index 2a407260..5da5d11c 100644
--- a/api/logic/settings/PassthroughSetting.cpp
+++ b/api/logic/settings/PassthroughSetting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/PassthroughSetting.h b/api/logic/settings/PassthroughSetting.h
index 999efbec..ee844da4 100644
--- a/api/logic/settings/PassthroughSetting.h
+++ b/api/logic/settings/PassthroughSetting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/Setting.cpp b/api/logic/settings/Setting.cpp
index c4167436..fa0041e0 100644
--- a/api/logic/settings/Setting.cpp
+++ b/api/logic/settings/Setting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/Setting.h b/api/logic/settings/Setting.h
index 79c6f26c..3edea7be 100644
--- a/api/logic/settings/Setting.h
+++ b/api/logic/settings/Setting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/SettingsObject.cpp b/api/logic/settings/SettingsObject.cpp
index 545a57d4..87a8c2a8 100644
--- a/api/logic/settings/SettingsObject.cpp
+++ b/api/logic/settings/SettingsObject.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/settings/SettingsObject.h b/api/logic/settings/SettingsObject.h
index d0b6bea1..8582d8ad 100644
--- a/api/logic/settings/SettingsObject.h
+++ b/api/logic/settings/SettingsObject.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/status/StatusChecker.cpp b/api/logic/status/StatusChecker.cpp
index 4b9418ee..bff9fda9 100644
--- a/api/logic/status/StatusChecker.cpp
+++ b/api/logic/status/StatusChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/status/StatusChecker.h b/api/logic/status/StatusChecker.h
index 004381a5..f19aba9a 100644
--- a/api/logic/status/StatusChecker.h
+++ b/api/logic/status/StatusChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/tasks/Task.cpp b/api/logic/tasks/Task.cpp
index 23ee08e4..2523aeb2 100644
--- a/api/logic/tasks/Task.cpp
+++ b/api/logic/tasks/Task.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,31 +41,79 @@ void Task::start()
{
m_running = true;
emit started();
+ qDebug() << "Task" << describe() << "started";
executeTask();
}
void Task::emitFailed(QString reason)
{
+ // Don't fail twice.
+ if (!m_running)
+ {
+ qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason;
+ return;
+ }
m_running = false;
m_finished = true;
m_succeeded = false;
m_failReason = reason;
- qCritical() << "Task failed: " << reason;
+ qCritical() << "Task" << describe() << "failed: " << reason;
emit failed(reason);
emit finished();
}
+void Task::emitAborted()
+{
+ // Don't abort twice.
+ if (!m_running)
+ {
+ qCritical() << "Task" << describe() << "aborted while not running!!!!";
+ return;
+ }
+ m_running = false;
+ m_finished = true;
+ m_succeeded = false;
+ m_failReason = "Aborted.";
+ qDebug() << "Task" << describe() << "aborted.";
+ emit failed(m_failReason);
+ emit finished();
+}
+
void Task::emitSucceeded()
{
- if (!m_running) { return; } // Don't succeed twice.
+ // Don't succeed twice.
+ if (!m_running)
+ {
+ qCritical() << "Task" << describe() << "succeeded while not running!!!!";
+ return;
+ }
m_running = false;
m_finished = true;
m_succeeded = true;
- qDebug() << "Task succeeded";
+ qDebug() << "Task" << describe() << "succeeded";
emit succeeded();
emit finished();
}
+QString Task::describe()
+{
+ QString outStr;
+ QTextStream out(&outStr);
+ out << metaObject()->className() << QChar('(');
+ auto name = objectName();
+ if(name.isEmpty())
+ {
+ out << QString("0x%1").arg((quintptr)this, 0, 16);
+ }
+ else
+ {
+ out << name;
+ }
+ out << QChar(')');
+ out.flush();
+ return outStr;
+}
+
bool Task::isRunning() const
{
return m_running;
@@ -76,7 +124,7 @@ bool Task::isFinished() const
return m_finished;
}
-bool Task::successful() const
+bool Task::wasSuccessful() const
{
return m_succeeded;
}
@@ -86,3 +134,13 @@ QString Task::failReason() const
return m_failReason;
}
+void Task::logWarning(const QString& line)
+{
+ qWarning() << line;
+ m_Warnings.append(line);
+}
+
+QStringList Task::warnings() const
+{
+ return m_Warnings;
+}
diff --git a/api/logic/tasks/Task.h b/api/logic/tasks/Task.h
index 47c4a13e..643f8510 100644
--- a/api/logic/tasks/Task.h
+++ b/api/logic/tasks/Task.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
#include <QObject>
#include <QString>
+#include <QStringList>
#include "multimc_logic_export.h"
@@ -27,21 +28,17 @@ public:
explicit Task(QObject *parent = 0);
virtual ~Task() {};
- virtual bool isRunning() const;
-
- virtual bool isFinished() const;
-
- /*!
- * True if this task was successful.
- * If the task failed or is still running, returns false.
- */
- virtual bool successful() const;
+ bool isRunning() const;
+ bool isFinished() const;
+ bool wasSuccessful() const;
/*!
* Returns the string that was passed to emitFailed as the error message when the task failed.
* If the task hasn't failed, returns an empty string.
*/
- virtual QString failReason() const;
+ QString failReason() const;
+
+ virtual QStringList warnings() const;
virtual bool canAbort() const { return false; }
@@ -60,6 +57,12 @@ public:
return m_progressTotal;
}
+protected:
+ void logWarning(const QString & line);
+
+private:
+ QString describe();
+
signals:
void started();
void progress(qint64 current, qint64 total);
@@ -68,8 +71,7 @@ signals:
void failed(QString reason);
void status(QString status);
-public
-slots:
+public slots:
virtual void start();
virtual bool abort() { return false; };
@@ -78,16 +80,18 @@ protected:
protected slots:
virtual void emitSucceeded();
+ virtual void emitAborted();
virtual void emitFailed(QString reason);
public slots:
void setStatus(const QString &status);
void setProgress(qint64 current, qint64 total);
-protected:
+private:
bool m_running = false;
bool m_finished = false;
bool m_succeeded = false;
+ QStringList m_Warnings;
QString m_failReason = "";
QString m_status;
int m_progress = 0;
diff --git a/api/logic/tasks/ThreadTask.cpp b/api/logic/tasks/ThreadTask.cpp
deleted file mode 100644
index ddd1dee5..00000000
--- a/api/logic/tasks/ThreadTask.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "ThreadTask.h"
-#include <QtConcurrentRun>
-ThreadTask::ThreadTask(Task * internal, QObject *parent) : Task(parent), m_internal(internal)
-{
-}
-
-void ThreadTask::start()
-{
- connect(m_internal, SIGNAL(failed(QString)), SLOT(iternal_failed(QString)));
- connect(m_internal, SIGNAL(progress(qint64,qint64)), SLOT(iternal_progress(qint64,qint64)));
- connect(m_internal, SIGNAL(started()), SLOT(iternal_started()));
- connect(m_internal, SIGNAL(status(QString)), SLOT(iternal_status(QString)));
- connect(m_internal, SIGNAL(succeeded()), SLOT(iternal_succeeded()));
- m_running = true;
- QtConcurrent::run(m_internal, &Task::start);
-}
-
-void ThreadTask::iternal_failed(QString reason)
-{
- emitFailed(reason);
-}
-
-void ThreadTask::iternal_progress(qint64 current, qint64 total)
-{
- progress(current, total);
-}
-
-void ThreadTask::iternal_started()
-{
- emit started();
-}
-
-void ThreadTask::iternal_status(QString status)
-{
- setStatus(status);
-}
-
-void ThreadTask::iternal_succeeded()
-{
- emitSucceeded();
-}
diff --git a/api/logic/tasks/ThreadTask.h b/api/logic/tasks/ThreadTask.h
deleted file mode 100644
index 46ce3a36..00000000
--- a/api/logic/tasks/ThreadTask.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include "Task.h"
-#include "multimc_logic_export.h"
-
-class MULTIMC_LOGIC_EXPORT ThreadTask : public Task
-{
- Q_OBJECT
-public:
- explicit ThreadTask(Task * internal, QObject * parent = nullptr);
-
-protected:
- void executeTask() {};
-
-public slots:
- virtual void start();
-
-private slots:
- void iternal_started();
- void iternal_progress(qint64 current, qint64 total);
- void iternal_succeeded();
- void iternal_failed(QString reason);
- void iternal_status(QString status);
-private:
- Task * m_internal;
-}; \ No newline at end of file
diff --git a/api/logic/tools/JVisualVM.cpp b/api/logic/tools/JVisualVM.cpp
index 169967d9..8fdb594f 100644
--- a/api/logic/tools/JVisualVM.cpp
+++ b/api/logic/tools/JVisualVM.cpp
@@ -92,7 +92,8 @@ bool JVisualVMFactory::check(const QString &path, QString *error)
*error = QObject::tr("Empty path");
return false;
}
- if (!QDir::isAbsolutePath(path) || !QFileInfo(path).isExecutable() || !path.contains("visualvm"))
+ QFileInfo finfo(path);
+ if (!finfo.isExecutable() || !finfo.fileName().contains("visualvm"))
{
*error = QObject::tr("Invalid path to JVisualVM");
return false;
diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h
index 092c775e..bd481134 100644
--- a/api/logic/translations/TranslationsModel.h
+++ b/api/logic/translations/TranslationsModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/updater/DownloadTask.cpp b/api/logic/updater/DownloadTask.cpp
index 0d40f78a..e0adf593 100644
--- a/api/logic/updater/DownloadTask.cpp
+++ b/api/logic/updater/DownloadTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -70,7 +70,7 @@ void DownloadTask::vinfoDownloadFailed()
{
// Something failed. We really need the second download (current version info), so parse
// downloads anyways as long as the first one succeeded.
- if (m_newVersionFileListDownload->m_status != Job_Failed)
+ if (m_newVersionFileListDownload->wasSuccessful())
{
processDownloadedVersionInfo();
return;
@@ -97,7 +97,7 @@ void DownloadTask::processDownloadedVersionInfo()
}
// if we have the current version info, use it.
- if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->m_status != Job_Failed)
+ if (m_currentVersionFileListDownload && m_currentVersionFileListDownload->wasSuccessful())
{
setStatus(tr("Reading file list for current version..."));
qDebug() << "Reading file list for current version...";
diff --git a/api/logic/updater/DownloadTask.h b/api/logic/updater/DownloadTask.h
index bcbe9736..186826e2 100644
--- a/api/logic/updater/DownloadTask.h
+++ b/api/logic/updater/DownloadTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/updater/UpdateChecker.cpp b/api/logic/updater/UpdateChecker.cpp
index a02068d8..d8be2c1a 100644
--- a/api/logic/updater/UpdateChecker.cpp
+++ b/api/logic/updater/UpdateChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/api/logic/updater/UpdateChecker.h b/api/logic/updater/UpdateChecker.h
index cbf85fd6..4996da26 100644
--- a/api/logic/updater/UpdateChecker.h
+++ b/api/logic/updater/UpdateChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt
index 5d3a964d..da9a42c6 100644
--- a/application/CMakeLists.txt
+++ b/application/CMakeLists.txt
@@ -1,44 +1,6 @@
project(application)
-######## Set URLs ########
-set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
-
-######## Set version numbers ########
-set(MultiMC_VERSION_MAJOR 0)
-set(MultiMC_VERSION_MINOR 6)
-set(MultiMC_VERSION_HOTFIX 0)
-
-# Build number
-set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
-
-# Build platform.
-set(MultiMC_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used by the notification system and to display in the about dialog.")
-
-# Channel list URL
-set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
-
-# Notification URL
-set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
-
-# paste.ee API key
-set(MultiMC_PASTE_EE_API_KEY "" CACHE STRING "API key you can get from paste.ee when you register an account")
-
-# Google analytics ID
-set(MultiMC_ANALYTICS_ID "" CACHE STRING "ID you can get from Google analytics")
-
-#### Check the current Git commit and branch
-include(GetGitRevisionDescription)
-get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
-
-message(STATUS "Git commit: ${MultiMC_GIT_COMMIT}")
-message(STATUS "Git refspec: ${MultiMC_GIT_REFSPEC}")
-
-set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}")
-
-#### Custom target to just print the version.
-add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
-
-######## Configure header ########
+######## Configure the file with build properties ########
configure_file("${PROJECT_SOURCE_DIR}/BuildConfig.cpp.in" "${PROJECT_BINARY_DIR}/BuildConfig.cpp")
################################ FILES ################################
@@ -131,8 +93,6 @@ SET(MULTIMC_SOURCES
pages/ScreenshotsPage.h
pages/OtherLogsPage.cpp
pages/OtherLogsPage.h
- pages/LegacyJarModPage.cpp
- pages/LegacyJarModPage.h
pages/LegacyUpgradePage.cpp
pages/LegacyUpgradePage.h
pages/WorldListPage.cpp
@@ -141,6 +101,8 @@ SET(MULTIMC_SOURCES
# GUI - global settings pages
pages/global/AccountListPage.cpp
pages/global/AccountListPage.h
+ pages/global/CustomCommandsPage.cpp
+ pages/global/CustomCommandsPage.h
pages/global/ExternalToolsPage.cpp
pages/global/ExternalToolsPage.h
pages/global/JavaPage.cpp
@@ -175,6 +137,8 @@ SET(MULTIMC_SOURCES
dialogs/LoginDialog.h
dialogs/ModEditDialogCommon.cpp
dialogs/ModEditDialogCommon.h
+ dialogs/NewComponentDialog.cpp
+ dialogs/NewComponentDialog.h
dialogs/NewInstanceDialog.cpp
dialogs/NewInstanceDialog.h
dialogs/NotificationDialog.cpp
@@ -189,14 +153,21 @@ SET(MULTIMC_SOURCES
dialogs/VersionSelectDialog.h
dialogs/SkinUploadDialog.cpp
dialogs/SkinUploadDialog.h
+ dialogs/ChooseFtbPackDialog.cpp
+ dialogs/ChooseFtbPackDialog.h
+
# GUI - widgets
widgets/Common.cpp
widgets/Common.h
+ widgets/CustomCommands.cpp
+ widgets/CustomCommands.h
widgets/FocusLineEdit.cpp
widgets/FocusLineEdit.h
widgets/IconLabel.cpp
widgets/IconLabel.h
+ widgets/JavaSettingsWidget.cpp
+ widgets/JavaSettingsWidget.h
widgets/LabeledToolButton.cpp
widgets/LabeledToolButton.h
widgets/LineSeparator.cpp
@@ -218,7 +189,8 @@ SET(MULTIMC_SOURCES
widgets/VersionSelectWidget.h
widgets/ProgressWidget.h
widgets/ProgressWidget.cpp
-
+ widgets/FtbModpackListItem.h
+ widgets/FtbModpackListItem.cpp
# GUI - instance group view
groupview/GroupedProxyModel.cpp
@@ -233,7 +205,7 @@ SET(MULTIMC_SOURCES
######## UIs ########
SET(MULTIMC_UIS
- # Option pages
+ # Instance pages
pages/VersionPage.ui
pages/ModFolderPage.ui
pages/LogPage.ui
@@ -241,7 +213,6 @@ SET(MULTIMC_UIS
pages/NotesPage.ui
pages/ScreenshotsPage.ui
pages/OtherLogsPage.ui
- pages/LegacyJarModPage.ui
pages/LegacyUpgradePage.ui
pages/WorldListPage.ui
@@ -257,6 +228,7 @@ SET(MULTIMC_UIS
# Dialogs
dialogs/CopyInstanceDialog.ui
+ dialogs/NewComponentDialog.ui
dialogs/NewInstanceDialog.ui
dialogs/AboutDialog.ui
dialogs/ProgressDialog.ui
@@ -268,8 +240,10 @@ SET(MULTIMC_UIS
dialogs/UpdateDialog.ui
dialogs/NotificationDialog.ui
dialogs/SkinUploadDialog.ui
+ dialogs/ChooseFtbPackDialog.ui
# Widgets/other
+ widgets/CustomCommands.ui
widgets/MCModInfoFrame.ui
)
@@ -282,6 +256,7 @@ set(MULTIMC_QRCS
resources/pe_blue/pe_blue.qrc
resources/OSX/OSX.qrc
resources/iOS/iOS.qrc
+ resources/flat/flat.qrc
resources/documents/documents.qrc
)
@@ -297,167 +272,26 @@ qt5_add_resources(MULTIMC_RESOURCES ${MULTIMC_QRCS})
# Add executable
add_executable(MultiMC MACOSX_BUNDLE WIN32 ${MULTIMC_SOURCES} ${MULTIMC_UI} ${MULTIMC_RESOURCES} ${MULTIMC_RCS})
target_link_libraries(MultiMC MultiMC_gui ${QUAZIP_LIBRARIES} hoedown MultiMC_rainbow LocalPeer ganalytics)
-
-################################ INSTALLATION AND PACKAGING ################################
-
-######## Packaging/install paths setup ########
-
-# How to install the build results
-set(MultiMC_LAYOUT "auto" CACHE STRING "The layout for MultiMC installation (auto, win-bundle, lin-bundle, lin-nodeps, lin-system, mac-bundle)")
-set_property(CACHE MultiMC_LAYOUT PROPERTY STRINGS auto win-bundle lin-bundle lin-nodeps lin-system mac-bundle)
-
-if(MultiMC_LAYOUT STREQUAL "auto")
- if(UNIX AND APPLE)
- set(MultiMC_LAYOUT_REAL "mac-bundle")
- elseif(UNIX)
- set(MultiMC_LAYOUT_REAL "lin-bundle")
- elseif(WIN32)
- set(MultiMC_LAYOUT_REAL "win-bundle")
- else()
- message(FATAL_ERROR "Cannot choose a sensible install layout for your platform.")
- endif()
-else()
- set(MultiMC_LAYOUT_REAL ${MultiMC_LAYOUT})
+if(DEFINED MultiMC_APP_BINARY_NAME)
+ set_target_properties(MultiMC PROPERTIES OUTPUT_NAME "${MultiMC_APP_BINARY_NAME}")
endif()
-
-if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
- set(BINARY_DEST_DIR "MultiMC.app/Contents/MacOS")
- set(LIBRARY_DEST_DIR "MultiMC.app/Contents/MacOS")
- set(PLUGIN_DEST_DIR "MultiMC.app/Contents/MacOS")
- set(RESOURCES_DEST_DIR "MultiMC.app/Contents/Resources")
- set(JARS_DEST_DIR "MultiMC.app/Contents/MacOS")
-
- set(BUNDLE_DEST_DIR ".")
-
- # Apps to bundle
- set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.app")
-
- # Mac bundle settings
- set(MACOSX_BUNDLE_BUNDLE_NAME "MultiMC")
- set(MACOSX_BUNDLE_INFO_STRING "MultiMC Minecraft launcher and management utility.")
- set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.multimc.MultiMC5")
- set(MACOSX_BUNDLE_BUNDLE_VERSION "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
- set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
- set(MACOSX_BUNDLE_LONG_VERSION_STRING "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MINOR}.${MultiMC_VERSION_HOTFIX}.${MultiMC_VERSION_BUILD}")
- set(MACOSX_BUNDLE_ICON_FILE MultiMC.icns)
- set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2017 MultiMC Contributors")
-
- # directories to look for dependencies
- set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-
- # install as bundle
- set(INSTALL_BUNDLE "full")
-
- # Add the icon
- install(FILES resources/MultiMC.icns DESTINATION ${RESOURCES_DEST_DIR})
-
-elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-bundle")
- set(BINARY_DEST_DIR "bin")
- set(LIBRARY_DEST_DIR "bin")
- set(PLUGIN_DEST_DIR "plugins")
- set(BUNDLE_DEST_DIR ".")
- set(RESOURCES_DEST_DIR ".")
- set(JARS_DEST_DIR "bin")
-
- # Apps to bundle
- set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/MultiMC")
-
- # directories to look for dependencies
- set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-
- # install as bundle
- set(INSTALL_BUNDLE "full")
-
- # Set RPATH
- SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "$ORIGIN/")
-
- # Install basic runner script
- install(PROGRAMS package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
-
-elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-nodeps")
- set(BINARY_DEST_DIR "bin")
- set(LIBRARY_DEST_DIR "bin")
- set(PLUGIN_DEST_DIR "plugins")
- set(BUNDLE_DEST_DIR ".")
- set(RESOURCES_DEST_DIR ".")
- set(JARS_DEST_DIR "bin")
-
- # install as bundle with no dependencies included
- set(INSTALL_BUNDLE "nodeps")
-
- # Set RPATH
- SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "$ORIGIN/")
-
- # Install basic runner script
- install(PROGRAMS package/linux/MultiMC DESTINATION ${BUNDLE_DEST_DIR})
-
-elseif(MultiMC_LAYOUT_REAL STREQUAL "lin-system")
- set(MultiMC_BINARY_DEST_DIR "usr/bin" CACHE STRING "Relative path from packaging root to the binary directory")
- set(MultiMC_LIBRARY_DEST_DIR "usr/lib" CACHE STRING "Relative path from packaging root to the library directory")
- set(MultiMC_SHARE_DEST_DIR "usr/share/multimc" CACHE STRING "Relative path from packaging root to the shared data directory")
- set(MultiMC_APP_BINARY_NAME "multimc" CACHE STRING "Name of the MultiMC binary for the purposes of linux packaging")
- set(JARS_DEST_DIR "${MultiMC_SHARE_DEST_DIR}")
-
- set(BINARY_DEST_DIR ${MultiMC_BINARY_DEST_DIR})
- set(LIBRARY_DEST_DIR ${MultiMC_LIBRARY_DEST_DIR})
-
- MESSAGE(STATUS "Compiling for linux system with ${MultiMC_SHARE_DEST_DIR} and MULTIMC_LINUX_DATADIR")
- set_target_properties(MultiMC PROPERTIES OUTPUT_NAME ${MultiMC_APP_BINARY_NAME})
- target_compile_definitions(MultiMC PRIVATE "-DMULTIMC_JARS_LOCATION=/${MultiMC_SHARE_DEST_DIR}/jars" "-DMULTIMC_LINUX_DATADIR"
- )
-
- # install as bundle with no dependencies included
- set(INSTALL_BUNDLE "nodeps")
-
-elseif(MultiMC_LAYOUT_REAL STREQUAL "win-bundle")
- set(BINARY_DEST_DIR ".")
- set(LIBRARY_DEST_DIR ".")
- set(PLUGIN_DEST_DIR ".")
- set(BUNDLE_DEST_DIR ".")
- set(RESOURCES_DEST_DIR ".")
- set(JARS_DEST_DIR ".")
-
- # Apps to bundle
- set(APPS "\${CMAKE_INSTALL_PREFIX}/MultiMC.exe")
-
- # directories to look for dependencies
- set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
-
- # install as bundle
- set(INSTALL_BUNDLE "full")
-else()
- message(FATAL_ERROR "No sensible install layout set.")
+if(DEFINED MultiMC_BINARY_RPATH)
+ SET_TARGET_PROPERTIES(MultiMC PROPERTIES INSTALL_RPATH "${MultiMC_BINARY_RPATH}")
+endif()
+if(DEFINED MultiMC_APP_BINARY_DEFS)
+ target_compile_definitions(MultiMC PRIVATE ${MultiMC_APP_BINARY_DEFS})
endif()
-######## Install files ########
-
-#### Executable ####
install(TARGETS MultiMC
BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime
LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
)
-install_jar(JavaCheck "${JARS_DEST_DIR}/jars")
-install_jar(NewLaunch "${JARS_DEST_DIR}/jars")
-
-#### Dependency installations ####
-if(INSTALL_BUNDLE STREQUAL "nodeps")
- # Just our own stuff
- # FIXME: this does not remove RPATH.
- install(
- FILES
- $<TARGET_FILE:MultiMC_gui>
- $<TARGET_FILE:MultiMC_logic>
- $<TARGET_FILE:MultiMC_rainbow>
- $<TARGET_FILE:MultiMC_quazip>
- $<TARGET_FILE:MultiMC_iconfix>
- $<TARGET_FILE:MultiMC_unpack200>
- $<TARGET_FILE:MultiMC_nbt++>
- DESTINATION
- ${LIBRARY_DEST_DIR}
- )
-elseif(INSTALL_BUNDLE STREQUAL "full")
+#### The MultiMC bundle mess! ####
+# Bundle utilities are used to complete the portable packages - they add all the libraries that would otherwise be missing on the target system.
+# NOTE: it seems that this absolutely has to be here, and nowhere else.
+if(INSTALL_BUNDLE STREQUAL "full")
# Add qt.conf - this makes Qt stop looking for things outside the bundle
install(
CODE "file(WRITE \"\${CMAKE_INSTALL_PREFIX}/${RESOURCES_DEST_DIR}/qt.conf\" \" \")"
diff --git a/application/ColumnResizer.cpp b/application/ColumnResizer.cpp
index 49440830..ee99bf40 100644
--- a/application/ColumnResizer.cpp
+++ b/application/ColumnResizer.cpp
@@ -197,5 +197,3 @@ void ColumnResizer::addWidgetsFromFormLayout(QFormLayout* layout, QFormLayout::I
d->m_wrWidgetItemList << newItem;
}
}
-
-#include <ColumnResizer.moc> \ No newline at end of file
diff --git a/application/GuiUtil.cpp b/application/GuiUtil.cpp
index 054fc0be..b05fc57c 100644
--- a/application/GuiUtil.cpp
+++ b/application/GuiUtil.cpp
@@ -33,7 +33,7 @@ QString GuiUtil::uploadPaste(const QString &text, QWidget *parentWidget)
}
dialog.execWithTask(paste.get());
- if (!paste->successful())
+ if (!paste->wasSuccessful())
{
CustomMessageBox::selectable(parentWidget, QObject::tr("Upload failed"),
paste->failReason(), QMessageBox::Critical)->exec();
@@ -56,8 +56,7 @@ void GuiUtil::setClipboardText(const QString &text)
QApplication::clipboard()->setText(text);
}
-
-QStringList GuiUtil::BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget)
+static QStringList BrowseForFileInternal(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget, bool single)
{
static QMap<QString, QString> savedPaths;
@@ -82,7 +81,7 @@ QStringList GuiUtil::BrowseForFiles(QString context, QString caption, QString fi
}
urls.append(QUrl::fromLocalFile(defaultPath));
- w.setFileMode(QFileDialog::ExistingFiles);
+ w.setFileMode(single ? QFileDialog::ExistingFile : QFileDialog::ExistingFiles);
w.setAcceptMode(QFileDialog::AcceptOpen);
w.setNameFilter(filter);
@@ -114,3 +113,19 @@ QStringList GuiUtil::BrowseForFiles(QString context, QString caption, QString fi
savedPaths[context] = w.directory().absolutePath();
return {};
}
+
+QString GuiUtil::BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget)
+{
+ auto resultList = BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, true);
+ if(resultList.size())
+ {
+ return resultList[0];
+ }
+ return QString();
+}
+
+
+QStringList GuiUtil::BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget)
+{
+ return BrowseForFileInternal(context, caption, filter, defaultPath, parentWidget, false);
+}
diff --git a/application/GuiUtil.h b/application/GuiUtil.h
index 52520c56..5e109383 100644
--- a/application/GuiUtil.h
+++ b/application/GuiUtil.h
@@ -7,4 +7,5 @@ namespace GuiUtil
QString uploadPaste(const QString &text, QWidget *parentWidget);
void setClipboardText(const QString &text);
QStringList BrowseForFiles(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget);
+QString BrowseForFile(QString context, QString caption, QString filter, QString defaultPath, QWidget *parentWidget);
}
diff --git a/application/HoeDown.h b/application/HoeDown.h
index 70a9e5cd..18c315c6 100644
--- a/application/HoeDown.h
+++ b/application/HoeDown.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/InstancePageProvider.h b/application/InstancePageProvider.h
index 1d6cc5d7..b13ce93d 100644
--- a/application/InstancePageProvider.h
+++ b/application/InstancePageProvider.h
@@ -1,5 +1,5 @@
#pragma once
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
#include "minecraft/legacy/LegacyInstance.h"
#include <FileSystem.h>
#include "pages/BasePage.h"
@@ -13,7 +13,7 @@
#include "pages/InstanceSettingsPage.h"
#include "pages/OtherLogsPage.h"
#include "pages/BasePageProvider.h"
-#include "pages/LegacyJarModPage.h"
+#include "pages/LegacyUpgradePage.h"
#include "pages/WorldListPage.h"
@@ -31,7 +31,7 @@ public:
{
QList<BasePage *> values;
values.append(new LogPage(inst));
- std::shared_ptr<OneSixInstance> onesix = std::dynamic_pointer_cast<OneSixInstance>(inst);
+ std::shared_ptr<MinecraftInstance> onesix = std::dynamic_pointer_cast<MinecraftInstance>(inst);
if(onesix)
{
values.append(new VersionPage(onesix.get()));
@@ -49,18 +49,10 @@ public:
std::shared_ptr<LegacyInstance> legacy = std::dynamic_pointer_cast<LegacyInstance>(inst);
if(legacy)
{
- // FIXME: actually implement the legacy instance upgrade, then enable this.
- //values.append(new LegacyUpgradePage(this));
- values.append(new LegacyJarModPage(legacy.get()));
- auto modsPage = new ModFolderPage(legacy.get(), legacy->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods");
- modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
- values.append(modsPage);
- values.append(new ModFolderPage(legacy.get(), legacy->coreModList(), "coremods", "coremods", tr("Core mods"), "Loader-mods"));
- values.append(new TexturePackPage(legacy.get()));
+ values.append(new LegacyUpgradePage(legacy));
values.append(new NotesPage(legacy.get()));
values.append(new WorldListPage(legacy.get(), legacy->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds"));
values.append(new ScreenshotsPage(FS::PathCombine(legacy->minecraftRoot(), "screenshots")));
- values.append(new InstanceSettingsPage(legacy.get()));
}
auto logMatcher = inst->getLogFileMatcher();
if(logMatcher)
diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp
index dd643f47..2e876fea 100644
--- a/application/InstanceWindow.cpp
+++ b/application/InstanceWindow.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent)
{
auto provider = std::make_shared<InstancePageProvider>(m_instance);
m_container = new PageContainer(provider, "console", this);
+ m_container->setParentContainer(this);
setCentralWidget(m_container);
}
@@ -72,7 +73,7 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent)
m_launchOfflineButton = new QPushButton();
horizontalLayout->addWidget(m_launchOfflineButton);
m_launchOfflineButton->setText(tr("Launch Offline"));
- setKillButton(m_instance->isRunning());
+ updateLaunchButtons();
connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked()));
m_closeButton = new QPushButton();
@@ -115,14 +116,21 @@ void InstanceWindow::on_instanceStatusChanged(BaseInstance::Status, BaseInstance
}
}
-void InstanceWindow::setKillButton(bool kill)
+void InstanceWindow::updateLaunchButtons()
{
- if(kill)
+ if(m_instance->isRunning())
{
m_launchOfflineButton->setEnabled(false);
m_killButton->setText(tr("Kill"));
m_killButton->setToolTip(tr("Kill the running instance"));
}
+ else if(!m_instance->canLaunch())
+ {
+ m_launchOfflineButton->setEnabled(false);
+ m_killButton->setText(tr("Launch"));
+ m_killButton->setToolTip(tr("Launch the instance"));
+ m_killButton->setEnabled(false);
+ }
else
{
m_launchOfflineButton->setEnabled(true);
@@ -141,9 +149,9 @@ void InstanceWindow::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> p
m_proc = proc;
}
-void InstanceWindow::on_RunningState_changed(bool running)
+void InstanceWindow::on_RunningState_changed(bool)
{
- setKillButton(running);
+ updateLaunchButtons();
m_container->refreshContainer();
}
@@ -206,3 +214,13 @@ void InstanceWindow::refreshContainer()
InstanceWindow::~InstanceWindow()
{
}
+
+bool InstanceWindow::requestClose()
+{
+ if(m_container->prepareToClose())
+ {
+ close();
+ return true;
+ }
+ return false;
+}
diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h
index 8e272e09..2b08644e 100644
--- a/application/InstanceWindow.h
+++ b/application/InstanceWindow.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,6 +40,9 @@ public:
// save all settings and changes (prepare for launch)
bool saveAll();
+ // request closing the window (from a page)
+ bool requestClose() override;
+
signals:
void isClosing();
@@ -57,7 +60,7 @@ protected:
void closeEvent(QCloseEvent *) override;
private:
- void setKillButton(bool kill);
+ void updateLaunchButtons();
private:
std::shared_ptr<LaunchTask> m_proc;
diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp
index e8f369be..2e711933 100644
--- a/application/LaunchController.cpp
+++ b/application/LaunchController.cpp
@@ -23,18 +23,18 @@ LaunchController::LaunchController(QObject *parent) : Task(parent)
void LaunchController::executeTask()
{
- login();
-}
-
-// FIXME: minecraft specific
-void LaunchController::login()
-{
if (!m_instance)
{
emitFailed(tr("No instance specified"));
return;
}
+ login();
+}
+
+// FIXME: minecraft specific
+void LaunchController::login()
+{
JavaCommon::checkJVMArgs(m_instance->settings()->get("JvmArgs").toString(), m_parentWidget);
// Find an account to use.
@@ -103,7 +103,7 @@ void LaunchController::login()
progDialog.setSkipButton(true, tr("Play Offline"));
}
progDialog.execWithTask(task.get());
- if (!task->successful())
+ if (!task->wasSuccessful())
{
auto failReasonNew = task->failReason();
if(failReasonNew == "Invalid token.")
@@ -192,7 +192,7 @@ void LaunchController::launchInstance()
Q_ASSERT_X(m_instance != NULL, "launchInstance", "instance is NULL");
Q_ASSERT_X(m_session.get() != nullptr, "launchInstance", "session is NULL");
- if(!m_instance->reload())
+ if(!m_instance->reloadSettings())
{
QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile."));
emitFailed(tr("Couldn't load the instance profile."));
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp
index 9eba9c44..1d36ef8c 100644
--- a/application/MainWindow.cpp
+++ b/application/MainWindow.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Andrew Okin
* Peterix
@@ -54,7 +54,6 @@
#include <java/JavaUtils.h>
#include <java/JavaInstallList.h>
#include <launch/LaunchTask.h>
-#include <minecraft/legacy/LwjglVersionList.h>
#include <minecraft/auth/MojangAccountList.h>
#include <SkinUtils.h>
#include <net/URLConstants.h>
@@ -91,58 +90,150 @@
#include <InstanceImportTask.h>
#include "UpdateController.h"
+// WHY: to hold the pre-translation strings together with the T pointer, so it can be retranslated without a lot of ugly code
+template <typename T>
+class Translated
+{
+public:
+ Translated(){}
+ Translated(QWidget *parent)
+ {
+ m_contained = new T(parent);
+ }
+ void setTooltipId(const char * tooltip)
+ {
+ m_tooltip = tooltip;
+ }
+ void setTextId(const char * text)
+ {
+ m_text = text;
+ }
+ operator T*()
+ {
+ return m_contained;
+ }
+ T * operator->()
+ {
+ return m_contained;
+ }
+ void retranslate()
+ {
+ if(m_text)
+ {
+ m_contained->setText(QApplication::translate("MainWindow", m_text));
+ }
+ if(m_tooltip)
+ {
+ m_contained->setToolTip(QApplication::translate("MainWindow", m_tooltip));
+ }
+ }
+private:
+ T * m_contained = nullptr;
+ const char * m_text = nullptr;
+ const char * m_tooltip = nullptr;
+};
+using TranslatedAction = Translated<QAction>;
+using TranslatedToolButton = Translated<QToolButton>;
+
+class TranslatedToolbar
+{
+public:
+ TranslatedToolbar(){}
+ TranslatedToolbar(QWidget *parent)
+ {
+ m_contained = new QToolBar(parent);
+ }
+ void setWindowTitleId(const char * title)
+ {
+ m_title = title;
+ }
+ operator QToolBar*()
+ {
+ return m_contained;
+ }
+ QToolBar * operator->()
+ {
+ return m_contained;
+ }
+ void retranslate()
+ {
+ if(m_title)
+ {
+ m_contained->setWindowTitle(QApplication::translate("MainWindow", m_title));
+ }
+ }
+private:
+ QToolBar * m_contained = nullptr;
+ const char * m_title = nullptr;
+};
+
class MainWindow::Ui
{
public:
- QAction *actionAddInstance;
- QAction *actionViewInstanceFolder;
- QAction *actionRefresh;
- QAction *actionViewCentralModsFolder;
- QAction *actionCheckUpdate;
- QAction *actionSettings;
- QAction *actionReportBug;
- QAction *actionPatreon;
- QAction *actionMoreNews;
- QAction *actionAbout;
- QAction *actionLaunchInstance;
- QAction *actionRenameInstance;
- QAction *actionChangeInstGroup;
- QAction *actionChangeInstIcon;
- QAction *actionEditInstNotes;
- QAction *actionEditInstance;
- QAction *actionWorlds;
- QAction *actionViewSelectedInstFolder;
- QAction *actionDeleteInstance;
- QAction *actionConfig_Folder;
- QAction *actionCAT;
- QAction *actionREDDIT;
- QAction *actionDISCORD;
- QAction *actionCopyInstance;
- QAction *actionManageAccounts;
- QAction *actionLaunchInstanceOffline;
- QAction *actionScreenshots;
- QAction *actionInstanceSettings;
- QAction *actionExportInstance;
- QWidget *centralWidget;
- QHBoxLayout *horizontalLayout;
- QToolBar *mainToolBar;
- QStatusBar *statusBar;
- QToolBar *instanceToolBar;
- QToolBar *newsToolBar;
+ TranslatedAction actionAddInstance;
+ //TranslatedAction actionRefresh;
+ TranslatedAction actionCheckUpdate;
+ TranslatedAction actionSettings;
+ TranslatedAction actionPatreon;
+ TranslatedAction actionMoreNews;
+ TranslatedAction actionManageAccounts;
+ TranslatedAction actionLaunchInstance;
+ TranslatedAction actionRenameInstance;
+ TranslatedAction actionChangeInstGroup;
+ TranslatedAction actionChangeInstIcon;
+ TranslatedAction actionEditInstNotes;
+ TranslatedAction actionEditInstance;
+ TranslatedAction actionWorlds;
+ TranslatedAction actionViewSelectedInstFolder;
+ TranslatedAction actionDeleteInstance;
+ TranslatedAction actionConfig_Folder;
+ TranslatedAction actionCAT;
+ TranslatedAction actionCopyInstance;
+ TranslatedAction actionLaunchInstanceOffline;
+ TranslatedAction actionScreenshots;
+ TranslatedAction actionExportInstance;
+ QVector<TranslatedAction *> all_actions;
+
+ LabeledToolButton *renameButton = nullptr;
+ LabeledToolButton *changeIconButton = nullptr;
+
+ QMenu * foldersMenu = nullptr;
+ TranslatedToolButton foldersMenuButton;
+ TranslatedAction actionViewInstanceFolder;
+ TranslatedAction actionViewCentralModsFolder;
+
+ QMenu * helpMenu = nullptr;
+ TranslatedToolButton helpMenuButton;
+ TranslatedAction actionReportBug;
+ TranslatedAction actionDISCORD;
+ TranslatedAction actionREDDIT;
+ TranslatedAction actionAbout;
+
+ QVector<TranslatedToolButton *> all_toolbuttons;
+
+ QWidget *centralWidget = nullptr;
+ QHBoxLayout *horizontalLayout = nullptr;
+ QStatusBar *statusBar = nullptr;
+
+ TranslatedToolbar mainToolBar;
+ TranslatedToolbar instanceToolBar;
+ TranslatedToolbar newsToolBar;
+ QVector<TranslatedToolbar *> all_toolbars;
bool m_kill = false;
void updateLaunchAction()
{
if(m_kill)
{
- actionLaunchInstance->setText(tr("Kill"));
- actionLaunchInstance->setToolTip(tr("Kill the running instance"));
+ actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Kill"));
+ actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance"));
}
else
{
- actionLaunchInstance->setText(tr("Launch"));
- actionLaunchInstance->setToolTip(tr("Launch the selected instance."));
+ actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch"));
+ actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance."));
}
+ actionLaunchInstance.retranslate();
}
void setLaunchAction(bool kill)
{
@@ -150,168 +241,349 @@ public:
updateLaunchAction();
}
- void setupUi(QMainWindow *MainWindow)
+ void createMainToolbar(QMainWindow *MainWindow)
{
- if (MainWindow->objectName().isEmpty())
- {
- MainWindow->setObjectName(QStringLiteral("MainWindow"));
- }
- MainWindow->resize(694, 563);
- MainWindow->setWindowIcon(MMC->getThemedIcon("multimc"));
- actionAddInstance = new QAction(MainWindow);
+ mainToolBar = TranslatedToolbar(MainWindow);
+ mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
+ mainToolBar->setMovable(false);
+ mainToolBar->setAllowedAreas(Qt::TopToolBarArea);
+ mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ mainToolBar->setFloatable(false);
+ mainToolBar.setWindowTitleId(QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar"));
+
+ actionAddInstance = TranslatedAction(MainWindow);
actionAddInstance->setObjectName(QStringLiteral("actionAddInstance"));
actionAddInstance->setIcon(MMC->getThemedIcon("new"));
- actionViewInstanceFolder = new QAction(MainWindow);
+ actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instance"));
+ actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance."));
+ all_actions.append(&actionAddInstance);
+ mainToolBar->addAction(actionAddInstance);
+
+ mainToolBar->addSeparator();
+
+ foldersMenu = new QMenu(MainWindow);
+ foldersMenu->setToolTipsVisible(true);
+
+ actionViewInstanceFolder = TranslatedAction(MainWindow);
actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder"));
actionViewInstanceFolder->setIcon(MMC->getThemedIcon("viewfolder"));
- actionRefresh = new QAction(MainWindow);
- actionRefresh->setObjectName(QStringLiteral("actionRefresh"));
- actionRefresh->setIcon(MMC->getThemedIcon("refresh"));
- actionViewCentralModsFolder = new QAction(MainWindow);
+ actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder"));
+ actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance folder in a file browser."));
+ all_actions.append(&actionViewInstanceFolder);
+ foldersMenu->addAction(actionViewInstanceFolder);
+
+ actionViewCentralModsFolder = TranslatedAction(MainWindow);
actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder"));
actionViewCentralModsFolder->setIcon(MMC->getThemedIcon("centralmods"));
- if(BuildConfig.UPDATER_ENABLED)
- {
- actionCheckUpdate = new QAction(MainWindow);
- actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate"));
- actionCheckUpdate->setIcon(MMC->getThemedIcon("checkupdate"));
- }
- actionSettings = new QAction(MainWindow);
+ actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder"));
+ actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the central mods folder in a file browser."));
+ all_actions.append(&actionViewCentralModsFolder);
+ foldersMenu->addAction(actionViewCentralModsFolder);
+
+ foldersMenuButton = TranslatedToolButton(MainWindow);
+ foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Folders"));
+ foldersMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open one of the folders shared between instances."));
+ foldersMenuButton->setMenu(foldersMenu);
+ foldersMenuButton->setPopupMode(QToolButton::InstantPopup);
+ foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder"));
+ all_toolbuttons.append(&foldersMenuButton);
+ QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow);
+ foldersButtonAction->setDefaultWidget(foldersMenuButton);
+ mainToolBar->addAction(foldersButtonAction);
+
+ actionSettings = TranslatedAction(MainWindow);
actionSettings->setObjectName(QStringLiteral("actionSettings"));
actionSettings->setIcon(MMC->getThemedIcon("settings"));
actionSettings->setMenuRole(QAction::PreferencesRole);
- actionReportBug = new QAction(MainWindow);
+ actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings"));
+ actionSettings.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change settings."));
+ all_actions.append(&actionSettings);
+ mainToolBar->addAction(actionSettings);
+
+ helpMenu = new QMenu(MainWindow);
+ helpMenu->setToolTipsVisible(true);
+
+ actionReportBug = TranslatedAction(MainWindow);
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
actionReportBug->setIcon(MMC->getThemedIcon("bug"));
- actionPatreon = new QAction(MainWindow);
- actionPatreon->setObjectName(QStringLiteral("actionPatreon"));
- actionPatreon->setIcon(MMC->getThemedIcon("patreon"));
- actionMoreNews = new QAction(MainWindow);
- actionMoreNews->setObjectName(QStringLiteral("actionMoreNews"));
- actionMoreNews->setIcon(MMC->getThemedIcon("news"));
- actionAbout = new QAction(MainWindow);
+ actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug"));
+ actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with MultiMC."));
+ all_actions.append(&actionReportBug);
+ helpMenu->addAction(actionReportBug);
+
+ actionDISCORD = TranslatedAction(MainWindow);
+ actionDISCORD->setObjectName(QStringLiteral("actionDISCORD"));
+ actionDISCORD->setIcon(MMC->getThemedIcon("discord"));
+ actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord"));
+ actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC discord voice chat."));
+ all_actions.append(&actionDISCORD);
+ helpMenu->addAction(actionDISCORD);
+
+ actionREDDIT = TranslatedAction(MainWindow);
+ actionREDDIT->setObjectName(QStringLiteral("actionREDDIT"));
+ actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien"));
+ actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Reddit"));
+ actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open MultiMC subreddit."));
+ all_actions.append(&actionREDDIT);
+ helpMenu->addAction(actionREDDIT);
+
+ actionAbout = TranslatedAction(MainWindow);
actionAbout->setObjectName(QStringLiteral("actionAbout"));
actionAbout->setIcon(MMC->getThemedIcon("about"));
actionAbout->setMenuRole(QAction::AboutRole);
- actionLaunchInstance = new QAction(MainWindow);
- actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance"));
- actionRenameInstance = new QAction(MainWindow);
- actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance"));
- actionChangeInstGroup = new QAction(MainWindow);
- actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup"));
- actionChangeInstIcon = new QAction(MainWindow);
- actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon"));
- actionChangeInstIcon->setEnabled(true);
- actionChangeInstIcon->setIcon(QIcon(":/icons/instances/infinity"));
- actionChangeInstIcon->setIconVisibleInMenu(true);
- actionEditInstNotes = new QAction(MainWindow);
- actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes"));
- actionEditInstance = new QAction(MainWindow);
- actionEditInstance->setObjectName(QStringLiteral("actionEditInstance"));
- actionWorlds = new QAction(MainWindow);
- actionWorlds->setObjectName(QStringLiteral("actionWorlds"));
- actionViewSelectedInstFolder = new QAction(MainWindow);
- actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder"));
- actionDeleteInstance = new QAction(MainWindow);
- actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
- actionConfig_Folder = new QAction(MainWindow);
- actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder"));
- actionCAT = new QAction(MainWindow);
+ actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "About MultiMC"));
+ actionAbout.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View information about MultiMC."));
+ all_actions.append(&actionAbout);
+ helpMenu->addAction(actionAbout);
+
+ helpMenuButton = TranslatedToolButton(MainWindow);
+ helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help"));
+ helpMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Get help with MultiMC or Minecraft."));
+ helpMenuButton->setMenu(helpMenu);
+ helpMenuButton->setPopupMode(QToolButton::InstantPopup);
+ helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ helpMenuButton->setIcon(MMC->getThemedIcon("help"));
+ all_toolbuttons.append(&helpMenuButton);
+ QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow);
+ helpButtonAction->setDefaultWidget(helpMenuButton);
+ mainToolBar->addAction(helpButtonAction);
+
+ if(BuildConfig.UPDATER_ENABLED)
+ {
+ actionCheckUpdate = TranslatedAction(MainWindow);
+ actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate"));
+ actionCheckUpdate->setIcon(MMC->getThemedIcon("checkupdate"));
+ actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Update"));
+ actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Check for new updates for MultiMC."));
+ all_actions.append(&actionCheckUpdate);
+ mainToolBar->addAction(actionCheckUpdate);
+ }
+
+ mainToolBar->addSeparator();
+
+ actionPatreon = TranslatedAction(MainWindow);
+ actionPatreon->setObjectName(QStringLiteral("actionPatreon"));
+ actionPatreon->setIcon(MMC->getThemedIcon("patreon"));
+ actionPatreon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Support MultiMC"));
+ actionPatreon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC Patreon page."));
+ all_actions.append(&actionPatreon);
+ mainToolBar->addAction(actionPatreon);
+
+ actionCAT = TranslatedAction(MainWindow);
actionCAT->setObjectName(QStringLiteral("actionCAT"));
actionCAT->setCheckable(true);
actionCAT->setIcon(MMC->getThemedIcon("cat"));
- actionREDDIT = new QAction(MainWindow);
- actionREDDIT->setObjectName(QStringLiteral("actionREDDIT"));
- actionREDDIT->setIcon(MMC->getThemedIcon("reddit-alien"));
- actionDISCORD = new QAction(MainWindow);
- actionDISCORD->setObjectName(QStringLiteral("actionDISCORD"));
- actionDISCORD->setIcon(MMC->getThemedIcon("discord"));
- actionCopyInstance = new QAction(MainWindow);
- actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance"));
- actionCopyInstance->setIcon(MMC->getThemedIcon("copy"));
- actionManageAccounts = new QAction(MainWindow);
+ actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Meow"));
+ actionCAT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "It's a fluffy kitty :3"));
+ actionCAT->setPriority(QAction::LowPriority);
+ all_actions.append(&actionCAT);
+ mainToolBar->addAction(actionCAT);
+
+ // profile menu and its actions
+ actionManageAccounts = TranslatedAction(MainWindow);
actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts"));
- actionLaunchInstanceOffline = new QAction(MainWindow);
- actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline"));
- actionScreenshots = new QAction(MainWindow);
- actionScreenshots->setObjectName(QStringLiteral("actionScreenshots"));
- actionInstanceSettings = new QAction(MainWindow);
- actionInstanceSettings->setObjectName(QStringLiteral("actionInstanceSettings"));
- actionExportInstance = new QAction(MainWindow);
- actionExportInstance->setObjectName(QStringLiteral("actionExportInstance"));
- centralWidget = new QWidget(MainWindow);
- centralWidget->setObjectName(QStringLiteral("centralWidget"));
- horizontalLayout = new QHBoxLayout(centralWidget);
- horizontalLayout->setSpacing(0);
- horizontalLayout->setContentsMargins(11, 11, 11, 11);
- horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
- horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
- horizontalLayout->setContentsMargins(0, 0, 0, 0);
- MainWindow->setCentralWidget(centralWidget);
- mainToolBar = new QToolBar(MainWindow);
- mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
- mainToolBar->setMovable(false);
- mainToolBar->setAllowedAreas(Qt::TopToolBarArea);
- mainToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
- mainToolBar->setFloatable(false);
+ actionManageAccounts.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Accounts"));
+ // FIXME: no tooltip!
+ actionManageAccounts->setCheckable(false);
+ actionManageAccounts->setIcon(MMC->getThemedIcon("accounts"));
+ all_actions.append(&actionManageAccounts);
+
+ all_toolbars.append(&mainToolBar);
MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar);
+ }
+
+ void createStatusBar(QMainWindow *MainWindow)
+ {
statusBar = new QStatusBar(MainWindow);
statusBar->setObjectName(QStringLiteral("statusBar"));
MainWindow->setStatusBar(statusBar);
- instanceToolBar = new QToolBar(MainWindow);
- instanceToolBar->setObjectName(QStringLiteral("instanceToolBar"));
- instanceToolBar->setEnabled(true);
- instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
- instanceToolBar->setIconSize(QSize(80, 80));
- instanceToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
- instanceToolBar->setFloatable(false);
- MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar);
- newsToolBar = new QToolBar(MainWindow);
+ }
+
+ void createNewsToolbar(QMainWindow *MainWindow)
+ {
+ newsToolBar = TranslatedToolbar(MainWindow);
newsToolBar->setObjectName(QStringLiteral("newsToolBar"));
newsToolBar->setMovable(false);
newsToolBar->setAllowedAreas(Qt::BottomToolBarArea);
newsToolBar->setIconSize(QSize(16, 16));
newsToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
newsToolBar->setFloatable(false);
+ newsToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "News Toolbar"));
+
+ actionMoreNews = TranslatedAction(MainWindow);
+ actionMoreNews->setObjectName(QStringLiteral("actionMoreNews"));
+ actionMoreNews->setIcon(MMC->getThemedIcon("news"));
+ actionMoreNews.setTextId(QT_TRANSLATE_NOOP("MainWindow", "More news..."));
+ actionMoreNews.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the MultiMC development blog to read more news about MultiMC."));
+ all_actions.append(&actionMoreNews);
+ newsToolBar->addAction(actionMoreNews);
+
+ all_toolbars.append(&newsToolBar);
MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar);
+ }
- mainToolBar->addAction(actionAddInstance);
- mainToolBar->addAction(actionCopyInstance);
- mainToolBar->addSeparator();
- mainToolBar->addAction(actionViewInstanceFolder);
- mainToolBar->addAction(actionViewCentralModsFolder);
- mainToolBar->addAction(actionRefresh);
- mainToolBar->addSeparator();
- if(BuildConfig.UPDATER_ENABLED)
- {
- mainToolBar->addAction(actionCheckUpdate);
- }
- mainToolBar->addAction(actionSettings);
- mainToolBar->addSeparator();
- mainToolBar->addAction(actionReportBug);
- mainToolBar->addAction(actionAbout);
- mainToolBar->addSeparator();
- mainToolBar->addAction(actionPatreon);
- mainToolBar->addAction(actionREDDIT);
- mainToolBar->addAction(actionDISCORD);
- mainToolBar->addAction(actionCAT);
- instanceToolBar->addAction(actionChangeInstIcon);
+ void createInstanceToolbar(QMainWindow *MainWindow)
+ {
+ instanceToolBar = TranslatedToolbar(MainWindow);
+ instanceToolBar->setObjectName(QStringLiteral("instanceToolBar"));
+ // disabled until we have an instance selected
+ instanceToolBar->setEnabled(false);
+ instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea);
+ instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
+ instanceToolBar->setFloatable(false);
+ instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar"));
+
+ // NOTE: not added to toolbar, but used for instance context menu (right click)
+ actionChangeInstIcon = TranslatedAction(MainWindow);
+ actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon"));
+ actionChangeInstIcon->setIcon(QIcon(":/icons/instances/infinity"));
+ actionChangeInstIcon->setIconVisibleInMenu(true);
+ actionChangeInstIcon.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Icon"));
+ actionChangeInstIcon.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's icon."));
+ all_actions.append(&actionChangeInstIcon);
+
+ changeIconButton = new LabeledToolButton(MainWindow);
+ changeIconButton->setObjectName(QStringLiteral("changeIconButton"));
+ changeIconButton->setIcon(MMC->getThemedIcon("news"));
+ changeIconButton->setToolTip(actionChangeInstIcon->toolTip());
+ changeIconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ instanceToolBar->addWidget(changeIconButton);
+
+ // NOTE: not added to toolbar, but used for instance context menu (right click)
+ actionRenameInstance = TranslatedAction(MainWindow);
+ actionRenameInstance->setObjectName(QStringLiteral("actionRenameInstance"));
+ actionRenameInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Rename"));
+ actionRenameInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Rename the selected instance."));
+ all_actions.append(&actionRenameInstance);
+
+ // the rename label is inside the rename tool button
+ renameButton = new LabeledToolButton(MainWindow);
+ renameButton->setObjectName(QStringLiteral("renameButton"));
+ renameButton->setToolTip(actionRenameInstance->toolTip());
+ renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ instanceToolBar->addWidget(renameButton);
+
+ instanceToolBar->addSeparator();
+
+ actionLaunchInstance = TranslatedAction(MainWindow);
+ actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance"));
+ all_actions.append(&actionLaunchInstance);
instanceToolBar->addAction(actionLaunchInstance);
+
+ actionLaunchInstanceOffline = TranslatedAction(MainWindow);
+ actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline"));
+ actionLaunchInstanceOffline.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch Offline"));
+ actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode."));
+ all_actions.append(&actionLaunchInstanceOffline);
instanceToolBar->addAction(actionLaunchInstanceOffline);
+
instanceToolBar->addSeparator();
+
+ actionEditInstance = TranslatedAction(MainWindow);
+ actionEditInstance->setObjectName(QStringLiteral("actionEditInstance"));
+ actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Instance"));
+ actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions."));
+ all_actions.append(&actionEditInstance);
instanceToolBar->addAction(actionEditInstance);
- instanceToolBar->addAction(actionInstanceSettings);
+
+ actionEditInstNotes = TranslatedAction(MainWindow);
+ actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes"));
+ actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Notes"));
+ actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Edit the notes for the selected instance."));
+ all_actions.append(&actionEditInstNotes);
instanceToolBar->addAction(actionEditInstNotes);
+
+ actionWorlds = TranslatedAction(MainWindow);
+ actionWorlds->setObjectName(QStringLiteral("actionWorlds"));
+ actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Worlds"));
+ actionWorlds.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the worlds of this instance."));
+ all_actions.append(&actionWorlds);
instanceToolBar->addAction(actionWorlds);
+
+ actionScreenshots = TranslatedAction(MainWindow);
+ actionScreenshots->setObjectName(QStringLiteral("actionScreenshots"));
+ actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Screenshots"));
+ actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View and upload screenshots for this instance."));
+ all_actions.append(&actionScreenshots);
instanceToolBar->addAction(actionScreenshots);
+
+ actionChangeInstGroup = TranslatedAction(MainWindow);
+ actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup"));
+ actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Group"));
+ actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's group."));
+ all_actions.append(&actionChangeInstGroup);
instanceToolBar->addAction(actionChangeInstGroup);
+
instanceToolBar->addSeparator();
+
+ actionViewSelectedInstFolder = TranslatedAction(MainWindow);
+ actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder"));
+ actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Instance Folder"));
+ actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser."));
+ all_actions.append(&actionViewSelectedInstFolder);
instanceToolBar->addAction(actionViewSelectedInstFolder);
+
+ actionConfig_Folder = TranslatedAction(MainWindow);
+ actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder"));
+ actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Config Folder"));
+ actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance's config folder."));
+ all_actions.append(&actionConfig_Folder);
instanceToolBar->addAction(actionConfig_Folder);
+
instanceToolBar->addSeparator();
+
+ actionExportInstance = TranslatedAction(MainWindow);
+ actionExportInstance->setObjectName(QStringLiteral("actionExportInstance"));
+ actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Export Instance"));
+ // FIXME: missing tooltip
+ all_actions.append(&actionExportInstance);
instanceToolBar->addAction(actionExportInstance);
+
+ actionDeleteInstance = TranslatedAction(MainWindow);
+ actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance"));
+ actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Delete"));
+ actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance."));
+ all_actions.append(&actionDeleteInstance);
instanceToolBar->addAction(actionDeleteInstance);
- newsToolBar->addAction(actionMoreNews);
+
+ actionCopyInstance = TranslatedAction(MainWindow);
+ actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance"));
+ actionCopyInstance->setIcon(MMC->getThemedIcon("copy"));
+ actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Copy Instance"));
+ actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance."));
+ all_actions.append(&actionCopyInstance);
+ instanceToolBar->addAction(actionCopyInstance);
+
+ all_toolbars.append(&instanceToolBar);
+ MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar);
+ }
+
+ void setupUi(QMainWindow *MainWindow)
+ {
+ if (MainWindow->objectName().isEmpty())
+ {
+ MainWindow->setObjectName(QStringLiteral("MainWindow"));
+ }
+ MainWindow->resize(694, 563);
+ MainWindow->setWindowIcon(MMC->getThemedIcon("logo"));
+ MainWindow->setWindowTitle("MultiMC 5");
+
+ createMainToolbar(MainWindow);
+
+ centralWidget = new QWidget(MainWindow);
+ centralWidget->setObjectName(QStringLiteral("centralWidget"));
+ horizontalLayout = new QHBoxLayout(centralWidget);
+ horizontalLayout->setSpacing(0);
+ horizontalLayout->setContentsMargins(11, 11, 11, 11);
+ horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
+ horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
+ horizontalLayout->setContentsMargins(0, 0, 0, 0);
+ MainWindow->setCentralWidget(centralWidget);
+
+ createStatusBar(MainWindow);
+ createNewsToolbar(MainWindow);
+ createInstanceToolbar(MainWindow);
retranslateUi(MainWindow);
@@ -320,69 +592,28 @@ public:
void retranslateUi(QMainWindow *MainWindow)
{
- MainWindow->setWindowTitle("MultiMC 5");
- actionAddInstance->setText(tr("Add Instance"));
- actionAddInstance->setToolTip(tr("Add a new instance."));
- actionViewInstanceFolder->setText(tr("View Instance Folder"));
- actionViewInstanceFolder->setToolTip(tr("Open the instance folder in a file browser."));
- actionRefresh->setText(tr("Refresh"));
- actionRefresh->setToolTip(tr("Reload the instance list."));
- actionViewCentralModsFolder->setText(tr("View Central Mods Folder"));
- actionViewCentralModsFolder->setToolTip(tr("Open the central mods folder in a file browser."));
- if(BuildConfig.UPDATER_ENABLED)
+ QString winTitle = tr("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString());
+ if (!BuildConfig.BUILD_PLATFORM.isEmpty())
{
- actionCheckUpdate->setText(tr("Check for Updates"));
- actionCheckUpdate->setToolTip(tr("Check for new updates for MultiMC."));
+ winTitle += tr(" on %1", "on platform, as in operating system").arg(BuildConfig.BUILD_PLATFORM);
}
- actionSettings->setText(tr("Settings"));
- actionSettings->setToolTip(tr("Change settings."));
- actionReportBug->setText(tr("Report a Bug"));
- actionReportBug->setToolTip(tr("Open the bug tracker to report a bug with MultiMC."));
- actionPatreon->setText(tr("Support us on Patreon!"));
- actionPatreon->setToolTip(tr("Open the MultiMC Patreon page."));
- actionMoreNews->setText(tr("More news..."));
- actionMoreNews->setToolTip(tr("Open the MultiMC development blog to read more news about MultiMC."));
- actionAbout->setText(tr("About MultiMC"));
- actionAbout->setToolTip(tr("View information about MultiMC."));
- updateLaunchAction();
- actionRenameInstance->setText(tr("Instance Name"));
- actionRenameInstance->setToolTip(tr("Rename the selected instance."));
- actionChangeInstGroup->setText(tr("Change Group"));
- actionChangeInstGroup->setToolTip(tr("Change the selected instance's group."));
- actionChangeInstIcon->setText(tr("Change Icon"));
- actionChangeInstIcon->setToolTip(tr("Change the selected instance's icon."));
- actionEditInstNotes->setText(tr("Edit Notes"));
- actionEditInstNotes->setToolTip(tr("Edit the notes for the selected instance."));
- actionWorlds->setText(tr("View Worlds"));
- actionWorlds->setToolTip(tr("View the worlds of this instance."));
- actionEditInstance->setText(tr("Edit Instance"));
- actionEditInstance->setToolTip(tr("Change the instance settings, mods and versions."));
- actionViewSelectedInstFolder->setText(tr("Instance Folder"));
- actionViewSelectedInstFolder->setToolTip(tr("Open the selected instance's root folder in a file browser."));
- actionDeleteInstance->setText(tr("Delete"));
- actionDeleteInstance->setToolTip(tr("Delete the selected instance."));
- actionConfig_Folder->setText(tr("Config Folder"));
- actionConfig_Folder->setToolTip(tr("Open the instance's config folder."));
- actionCAT->setText(tr("Meow"));
- actionCAT->setToolTip(tr("It's a fluffy kitty :3"));
- actionREDDIT->setText(tr("Reddit"));
- actionREDDIT->setToolTip(tr("Open MultiMC subreddit."));
- actionDISCORD->setText(tr("Discord"));
- actionDISCORD->setToolTip(tr("Open MultiMC discord voice chat."));
- actionCopyInstance->setText(tr("Copy Instance"));
- actionCopyInstance->setToolTip(tr("Copy the selected instance."));
- actionManageAccounts->setText(tr("Manage Accounts"));
- actionManageAccounts->setToolTip(tr("Manage your Mojang or Minecraft accounts."));
- actionLaunchInstanceOffline->setText(tr("Launch Offline"));
- actionLaunchInstanceOffline->setToolTip(tr("Launch the selected instance in offline mode."));
- actionScreenshots->setText(tr("Manage Screenshots"));
- actionScreenshots->setToolTip(tr("View and upload screenshots for this instance."));
- actionInstanceSettings->setText(tr("Instance Settings"));
- actionInstanceSettings->setToolTip(tr("Change the settings specific to the instance."));
- actionExportInstance->setText(tr("Export Instance"));
- mainToolBar->setWindowTitle(tr("Main Toolbar"));
- instanceToolBar->setWindowTitle(tr("Instance Toolbar"));
- newsToolBar->setWindowTitle(tr("News Toolbar"));
+ MainWindow->setWindowTitle(winTitle);
+ // all the actions
+ for(auto * item: all_actions)
+ {
+ item->retranslate();
+ }
+ for(auto * item: all_toolbars)
+ {
+ item->retranslate();
+ }
+ for(auto * item: all_toolbuttons)
+ {
+ item->retranslate();
+ }
+ // submenu buttons
+ foldersMenuButton->setText(tr("Folders"));
+ helpMenuButton->setText(tr("Help"));
} // retranslateUi
};
@@ -390,13 +621,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
{
ui->setupUi(this);
- QString winTitle = tr("MultiMC 5 - Version %1").arg(BuildConfig.printableVersionString());
- if (!BuildConfig.BUILD_PLATFORM.isEmpty())
- {
- winTitle += tr(" on %1", "on platform, as in operating system").arg(BuildConfig.BUILD_PLATFORM);
- }
- setWindowTitle(winTitle);
-
// OSX magic.
setUnifiedTitleAndToolBarOnMac(true);
@@ -407,21 +631,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
connect(q, SIGNAL(activated()), qApp, SLOT(quit()));
}
- // The instance action toolbar customizations
- {
- // disabled until we have an instance selected
- ui->instanceToolBar->setEnabled(false);
-
- // the rename label is inside the rename tool button
- renameButton = new LabeledToolButton();
- renameButton->setText("Instance Name");
- renameButton->setToolTip(ui->actionRenameInstance->toolTip());
- connect(renameButton, SIGNAL(clicked(bool)), SLOT(on_actionRenameInstance_triggered()));
- ui->instanceToolBar->insertWidget(ui->actionLaunchInstance, renameButton);
- ui->instanceToolBar->insertSeparator(ui->actionLaunchInstance);
- renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
- }
-
// Add the news label to the news toolbar.
{
m_newsChecker.reset(new NewsChecker(BuildConfig.NEWS_RSS_URL));
@@ -489,10 +698,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
ui->mainToolBar->addWidget(spacer);
accountMenu = new QMenu(this);
- manageAccountsAction = new QAction(tr("Manage Accounts"), this);
- manageAccountsAction->setCheckable(false);
- manageAccountsAction->setIcon(MMC->getThemedIcon("accounts"));
- connect(manageAccountsAction, SIGNAL(triggered(bool)), this, SLOT(on_actionManageAccounts_triggered()));
repopulateAccountsMenu();
@@ -566,6 +771,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
{
bool updatesAllowed = MMC->updatesAreAllowed();
updatesAllowedChanged(updatesAllowed);
+
+ connect(ui->actionCheckUpdate, &QAction::triggered, this, &MainWindow::checkForUpdates);
+
// set up the updater object.
auto updater = MMC->updateChecker();
connect(updater.get(), &UpdateChecker::updateAvailable, this, &MainWindow::updateAvailable);
@@ -616,22 +824,17 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
{
actions = ui->instanceToolBar->actions();
- QAction *actionVoid = new QAction(m_selectedInstance->name(), this);
- actionVoid->setEnabled(false);
-
- QAction *actionRename = new QAction(tr("Rename"), this);
- actionRename->setToolTip(ui->actionRenameInstance->toolTip());
+ // replace the change icon widget with an actual action
+ actions.replace(0, ui->actionChangeInstIcon);
- QAction *actionCopyInstance = new QAction(tr("Copy instance"), this);
- actionCopyInstance->setToolTip(ui->actionCopyInstance->toolTip());
+ // replace the rename widget with an actual action
+ actions.replace(1, ui->actionRenameInstance);
- connect(actionRename, SIGNAL(triggered(bool)), SLOT(on_actionRenameInstance_triggered()));
- connect(actionCopyInstance, SIGNAL(triggered(bool)), SLOT(on_actionCopyInstance_triggered()));
-
- actions.replace(1, actionRename);
+ // add header
actions.prepend(actionSep);
+ QAction *actionVoid = new QAction(m_selectedInstance->name(), this);
+ actionVoid->setEnabled(false);
actions.prepend(actionVoid);
- actions.append(actionCopyInstance);
}
else
{
@@ -660,14 +863,16 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
QVariantMap data;
data["group"] = group;
actionDeleteGroup->setData(data);
- connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(on_actionDeleteGroup_triggered()));
+ connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup()));
actions.append(actionDeleteGroup);
}
}
QMenu myMenu;
myMenu.addActions(actions);
+ /*
if (onInstance)
myMenu.setEnabled(m_selectedInstance->canLaunch());
+ */
myMenu.exec(view->mapToGlobal(pos));
}
@@ -795,7 +1000,7 @@ void MainWindow::repopulateAccountsMenu()
connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount()));
accountMenu->addSeparator();
- accountMenu->addAction(manageAccountsAction);
+ accountMenu->addAction(ui->actionManageAccounts);
}
void MainWindow::updatesAllowedChanged(bool allowed)
@@ -870,7 +1075,7 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
on_actionDeleteInstance_triggered();
return true;
case Qt::Key_F5:
- on_actionRefresh_triggered();
+ refreshInstances();
return true;
case Qt::Key_F2:
on_actionRenameInstance_triggered();
@@ -1034,7 +1239,15 @@ void MainWindow::runModalTask(Task *task)
{
connect(task, &Task::failed, [this](QString reason)
{
- CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show();
+ CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show();
+ });
+ connect(task, &Task::succeeded, [this, task]()
+ {
+ QStringList warnings = task->warnings();
+ if(warnings.count())
+ {
+ CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show();
+ }
});
ProgressDialog loadDialog(this);
loadDialog.setSkipButton(true, tr("Abort"));
@@ -1059,6 +1272,14 @@ void MainWindow::instanceFromVersion(QString instName, QString instGroup, QStrin
// finalizeInstance(newInstance);
}
+void MainWindow::instanceFromFtbPack(FtbPackDownloader *downloader, QString instName, QString instGroup, QString instIcon) {
+ std::unique_ptr<Task> task(MMC->folderProvider()->ftbCreationTask(downloader, instName, instGroup, instIcon));
+ runModalTask(task.get());
+
+ // FIXME: handle instance selection after creation
+ // finalizeInstance(newInstance);
+}
+
void MainWindow::on_actionCopyInstance_triggered()
{
if (!m_selectedInstance)
@@ -1083,7 +1304,7 @@ void MainWindow::finalizeInstance(InstancePtr inst)
if (MMC->accounts()->anyAccountIsValid())
{
ProgressDialog loadDialog(this);
- auto update = inst->createUpdateTask();
+ auto update = inst->createUpdateTask(Net::Mode::Online);
connect(update.get(), &Task::failed, [this](QString reason)
{
QString error = QString("Instance load failed: %1").arg(reason);
@@ -1134,7 +1355,10 @@ void MainWindow::addInstance(QString url)
const QUrl modpackUrl = newInstDlg.modpackUrl();
- if (modpackUrl.isValid())
+ if(newInstDlg.isFtbModpackRequested()) {
+ instanceFromFtbPack(newInstDlg.getFtbPackDownloader(), newInstDlg.instName(), newInstDlg.instGroup(), newInstDlg.iconKey());
+ }
+ else if (modpackUrl.isValid())
{
instanceFromZipPack(newInstDlg.instName(), newInstDlg.instGroup(), newInstDlg.iconKey(), modpackUrl);
}
@@ -1186,8 +1410,9 @@ void MainWindow::on_actionChangeInstIcon_triggered()
if (dlg.result() == QDialog::Accepted)
{
m_selectedInstance->setIconKey(dlg.selectedIconKey);
- auto ico = MMC->icons()->getBigIcon(dlg.selectedIconKey);
- ui->actionChangeInstIcon->setIcon(ico);
+ auto icon = MMC->icons()->getIcon(dlg.selectedIconKey);
+ ui->actionChangeInstIcon->setIcon(icon);
+ ui->changeIconButton->setIcon(icon);
}
}
@@ -1195,14 +1420,18 @@ void MainWindow::iconUpdated(QString icon)
{
if (icon == m_currentInstIcon)
{
- ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
+ auto icon = MMC->icons()->getIcon(m_currentInstIcon);
+ ui->actionChangeInstIcon->setIcon(icon);
+ ui->changeIconButton->setIcon(icon);
}
}
void MainWindow::updateInstanceToolIcon(QString new_icon)
{
m_currentInstIcon = new_icon;
- ui->actionChangeInstIcon->setIcon(MMC->icons()->getBigIcon(m_currentInstIcon));
+ auto icon = MMC->icons()->getIcon(m_currentInstIcon);
+ ui->actionChangeInstIcon->setIcon(icon);
+ ui->changeIconButton->setIcon(icon);
}
void MainWindow::setSelectedInstanceById(const QString &id)
@@ -1235,7 +1464,7 @@ void MainWindow::on_actionChangeInstGroup_triggered()
m_selectedInstance->setGroupPost(name);
}
-void MainWindow::on_actionDeleteGroup_triggered()
+void MainWindow::deleteGroup()
{
QObject* obj = sender();
if(!obj)
@@ -1259,7 +1488,7 @@ void MainWindow::on_actionViewInstanceFolder_triggered()
DesktopServices::openDirectory(str);
}
-void MainWindow::on_actionRefresh_triggered()
+void MainWindow::refreshInstances()
{
MMC->instances()->loadList(true);
}
@@ -1278,7 +1507,7 @@ void MainWindow::on_actionConfig_Folder_triggered()
}
}
-void MainWindow::on_actionCheckUpdate_triggered()
+void MainWindow::checkForUpdates()
{
if(BuildConfig.UPDATER_ENABLED)
{
@@ -1379,11 +1608,13 @@ void MainWindow::on_actionDeleteInstance_triggered()
{
return;
}
- auto response = CustomMessageBox::selectable(this, tr("CAREFUL!"), tr("About to delete: %1\nThis is permanent and will completely erase "
- "all data, even for tracked instances!\nAre you sure?")
- .arg(m_selectedInstance->name()),
- QMessageBox::Warning, QMessageBox::Yes | QMessageBox::No)
- ->exec();
+ auto response = CustomMessageBox::selectable(
+ this,
+ tr("CAREFUL!"),
+ tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()),
+ QMessageBox::Warning,
+ QMessageBox::Yes | QMessageBox::No
+ )->exec();
if (response == QMessageBox::Yes)
{
m_selectedInstance->nuke();
@@ -1413,7 +1644,7 @@ void MainWindow::on_actionRenameInstance_triggered()
if (ok && name.length())
{
m_selectedInstance->setName(name);
- renameButton->setText(name);
+ ui->renameButton->setText(name);
}
}
}
@@ -1528,7 +1759,7 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
}
ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch());
ui->actionExportInstance->setEnabled(m_selectedInstance->canExport());
- renameButton->setText(m_selectedInstance->name());
+ ui->renameButton->setText(m_selectedInstance->name());
m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
updateInstanceToolIcon(m_selectedInstance->iconKey());
@@ -1562,7 +1793,7 @@ void MainWindow::selectionBad()
statusBar()->clearMessage();
ui->instanceToolBar->setEnabled(false);
- renameButton->setText(tr("Rename Instance"));
+ ui->renameButton->setText(tr("Rename Instance"));
updateInstanceToolIcon("infinity");
// ...and then see if we can enable the previously selected instance
diff --git a/application/MainWindow.h b/application/MainWindow.h
index 2a70f17c..e4c281dc 100644
--- a/application/MainWindow.h
+++ b/application/MainWindow.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
#include "minecraft/auth/MojangAccount.h"
#include "net/NetJob.h"
#include "updater/GoUpdate.h"
+#include <modplatform/FtbPackDownloader.h>
class LaunchController;
class NewsChecker;
@@ -74,6 +75,10 @@ private slots:
void on_actionChangeInstGroup_triggered();
void on_actionChangeInstIcon_triggered();
+ void on_changeIconButton_clicked(bool)
+ {
+ on_actionChangeInstIcon_triggered();
+ }
void on_actionViewInstanceFolder_triggered();
@@ -81,11 +86,11 @@ private slots:
void on_actionViewSelectedInstFolder_triggered();
- void on_actionRefresh_triggered();
+ void refreshInstances();
void on_actionViewCentralModsFolder_triggered();
- void on_actionCheckUpdate_triggered();
+ void checkForUpdates();
void on_actionSettings_triggered();
@@ -109,11 +114,15 @@ private slots:
void on_actionDeleteInstance_triggered();
- void on_actionDeleteGroup_triggered();
+ void deleteGroup();
void on_actionExportInstance_triggered();
void on_actionRenameInstance_triggered();
+ void on_renameButton_clicked(bool)
+ {
+ on_actionRenameInstance_triggered();
+ }
void on_actionEditInstance_triggered();
@@ -177,6 +186,7 @@ private:
void runModalTask(Task *task);
void instanceFromVersion(QString instName, QString instGroup, QString instIcon, BaseVersionPtr version);
void instanceFromZipPack(QString instName, QString instGroup, QString instIcon, QUrl url);
+ void instanceFromFtbPack(FtbPackDownloader *downloader, QString instName, QString instGroup, QString instIcon);
void finalizeInstance(InstancePtr inst);
private:
@@ -185,14 +195,11 @@ private:
// these are managed by Qt's memory management model!
GroupView *view = nullptr;
InstanceProxyModel *proxymodel = nullptr;
- LabeledToolButton *renameButton = nullptr;
- QToolButton *changeIconButton = nullptr;
QToolButton *newsLabel = nullptr;
QLabel *m_statusLeft = nullptr;
ServerStatus *m_statusRight = nullptr;
QMenu *accountMenu = nullptr;
QToolButton *accountMenuButton = nullptr;
- QAction *manageAccountsAction = nullptr;
unique_qobject_ptr<NetJob> skin_download_job;
unique_qobject_ptr<NewsChecker> m_newsChecker;
diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp
index c78a536e..f928d11b 100644
--- a/application/MultiMC.cpp
+++ b/application/MultiMC.cpp
@@ -11,6 +11,7 @@
#include "pages/global/AccountListPage.h"
#include "pages/global/PasteEEPage.h"
#include "pages/global/PackagesPage.h"
+#include "pages/global/CustomCommandsPage.h"
#include "themes/ITheme.h"
#include "themes/SystemTheme.h"
@@ -36,13 +37,9 @@
#include "dialogs/CustomMessageBox.h"
#include "InstanceList.h"
#include "FolderInstanceProvider.h"
-#include "minecraft/ftb/FTBInstanceProvider.h"
#include <minecraft/auth/MojangAccountList.h>
#include "icons/IconList.h"
-//FIXME: get rid of this
-#include "minecraft/legacy/LwjglVersionList.h"
-
#include "net/HttpMetaCache.h"
#include "net/URLConstants.h"
#include "Env.h"
@@ -61,8 +58,6 @@
#include "translations/TranslationsModel.h"
-#include "minecraft/ftb/FTBPlugin.h"
-
#include <Commandline.h>
#include <FileSystem.h>
#include <DesktopServices.h>
@@ -71,6 +66,7 @@
#include <ganalytics.h>
#include <sys.h>
+
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
@@ -90,6 +86,26 @@ using namespace Commandline;
"This usually fixes the problem and you can move the application elsewhere afterwards.\n"\
"\n"
+static void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ const char *levels = "DWCFIS";
+ const QString format("%1 %2 %3\n");
+
+ qint64 msecstotal = MMC->timeSinceStart();
+ qint64 seconds = msecstotal / 1000;
+ qint64 msecs = msecstotal % 1000;
+ QString foo;
+ char buf[1025] = {0};
+ ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs);
+
+ QString out = format.arg(buf).arg(levels[type]).arg(msg);
+
+ MMC->logFile->write(out.toUtf8());
+ MMC->logFile->flush();
+ QTextStream(stderr) << out.toLocal8Bit();
+ fflush(stderr);
+}
+
MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
{
#if defined Q_OS_WIN32
@@ -207,10 +223,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
if (xdgDataHome.isEmpty())
xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
dataPath = xdgDataHome + "/multimc";
- printf("BLAH %s", xdgDataHome.toStdString().c_str());
-
adjustedBy += "XDG standard " + dataPath;
-
#else
dataPath = applicationDirPath();
adjustedBy += "Fallback to binary path " + dataPath;
@@ -247,23 +260,70 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
);
return;
}
+
+ /*
+ * Establish the mechanism for communication with an already running MultiMC that uses the same data path.
+ * If there is one, tell it what the user actually wanted to do and exit.
+ * We want to initialize this before logging to avoid messing with the log of a potential already running copy.
+ */
auto appID = ApplicationId::fromPathAndVersion(QDir::currentPath(), BuildConfig.printableVersionString());
- m_peerInstance = new LocalPeer(this, appID);
- connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived);
- if(m_peerInstance->isClient())
{
- if(m_instanceIdToLaunch.isEmpty())
+ // FIXME: you can run the same binaries with multiple data dirs and they won't clash. This could cause issues for updates.
+ m_peerInstance = new LocalPeer(this, appID);
+ connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived);
+ if(m_peerInstance->isClient())
{
- m_peerInstance->sendMessage("activate", 2000);
+ if(m_instanceIdToLaunch.isEmpty())
+ {
+ m_peerInstance->sendMessage("activate", 2000);
+ }
+ else
+ {
+ m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000);
+ }
+ m_status = MultiMC::Succeeded;
+ return;
}
- else
+ }
+
+ // init the logger
+ {
+ static const QString logBase = "MultiMC-%0.log";
+ auto moveFile = [](const QString &oldName, const QString &newName)
+ {
+ QFile::remove(newName);
+ QFile::copy(oldName, newName);
+ QFile::remove(oldName);
+ };
+
+ moveFile(logBase.arg(3), logBase.arg(4));
+ moveFile(logBase.arg(2), logBase.arg(3));
+ moveFile(logBase.arg(1), logBase.arg(2));
+ moveFile(logBase.arg(0), logBase.arg(1));
+
+ logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0)));
+ if(!logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
{
- m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000);
+ showFatalErrorMessage(
+ "MultiMC data folder is not writable!",
+ "MultiMC couldn't create a log file - the MultiMC data folder is not writable.\n"
+ "\n"
+ #if defined(Q_OS_MAC)
+ MACOS_HINT
+ #endif
+ "Make sure you have write permissions to the MultiMC data folder.\n"
+ "\n"
+ "MultiMC cannot continue until you fix this problem."
+ );
+ return;
}
- m_status = MultiMC::Succeeded;
- return;
+ qInstallMessageHandler(appDebugOutput);
+ qDebug() << "<> Log initialized.";
}
+ // Set up paths
+ {
+ // Root path is used for updates.
#ifdef Q_OS_LINUX
QDir foo(FS::PathCombine(binPath, ".."));
m_rootPath = foo.absolutePath();
@@ -272,48 +332,36 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
#elif defined(Q_OS_MAC)
QDir foo(FS::PathCombine(binPath, "../.."));
m_rootPath = foo.absolutePath();
+ // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues)
+ FS::updateTimestamp(m_rootPath);
#endif
- // init the logger
- if(!initLogger())
- {
- showFatalErrorMessage(
- "MultiMC data folder is not writable!",
- "MultiMC couldn't create a log file - the MultiMC data folder is not writable.\n"
- "\n"
-#if defined(Q_OS_MAC)
- MACOS_HINT
+#ifdef MULTIMC_JARS_LOCATION
+ ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) );
#endif
- "Make sure you have write permissions to the MultiMC data folder.\n"
- "\n"
- "MultiMC cannot continue until you fix this problem."
- );
- return;
- }
- qDebug() << "MultiMC 5, (c) 2013-2017 MultiMC Contributors";
- qDebug() << "Version : " << BuildConfig.printableVersionString();
- qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
- qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
- if (adjustedBy.size())
- {
- qDebug() << "Work dir before adjustment : " << origcwdPath;
- qDebug() << "Work dir after adjustment : " << QDir::currentPath();
- qDebug() << "Adjusted by : " << adjustedBy;
- }
- else
- {
- qDebug() << "Work dir : " << QDir::currentPath();
- }
- qDebug() << "Binary path : " << binPath;
- qDebug() << "Application root path : " << m_rootPath;
- if(!m_instanceIdToLaunch.isEmpty())
- {
- qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
+ qDebug() << "MultiMC 5, (c) 2013-2018 MultiMC Contributors";
+ qDebug() << "Version : " << BuildConfig.printableVersionString();
+ qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
+ qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
+ if (adjustedBy.size())
+ {
+ qDebug() << "Work dir before adjustment : " << origcwdPath;
+ qDebug() << "Work dir after adjustment : " << QDir::currentPath();
+ qDebug() << "Adjusted by : " << adjustedBy;
+ }
+ else
+ {
+ qDebug() << "Work dir : " << QDir::currentPath();
+ }
+ qDebug() << "Binary path : " << binPath;
+ qDebug() << "Application root path : " << m_rootPath;
+ if(!m_instanceIdToLaunch.isEmpty())
+ {
+ qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
+ }
+ qDebug() << "<> Paths set.";
}
-#ifdef MULTIMC_JARS_LOCATION
- ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) );
-#endif
do // once
{
@@ -336,24 +384,251 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
} while(false);
- // load settings
- initGlobalSettings();
+ // Initialize application settings
+ {
+ m_settings.reset(new INISettingsObject("multimc.cfg", this));
+ // Updates
+ m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
+ m_settings->registerSetting("AutoUpdate", true);
+
+ // Theming
+ m_settings->registerSetting("IconTheme", QString("multimc"));
+ m_settings->registerSetting("ApplicationTheme", QString("system"));
+
+ // Notifications
+ m_settings->registerSetting("ShownNotifications", QString());
+
+ // Remembered state
+ m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
+
+ QString defaultMonospace;
+ int defaultSize = 11;
+#ifdef Q_OS_WIN32
+ defaultMonospace = "Courier";
+ defaultSize = 10;
+#elif defined(Q_OS_MAC)
+ defaultMonospace = "Menlo";
+#else
+ defaultMonospace = "Monospace";
+#endif
+
+ // resolve the font so the default actually matches
+ QFont consoleFont;
+ consoleFont.setFamily(defaultMonospace);
+ consoleFont.setStyleHint(QFont::Monospace);
+ consoleFont.setFixedPitch(true);
+ QFontInfo consoleFontInfo(consoleFont);
+ QString resolvedDefaultMonospace = consoleFontInfo.family();
+ QFont resolvedFont(resolvedDefaultMonospace);
+ qDebug() << "Detected default console font:" << resolvedDefaultMonospace
+ << ", substitutions:" << resolvedFont.substitutions().join(',');
+
+ m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace);
+ m_settings->registerSetting("ConsoleFontSize", defaultSize);
+ m_settings->registerSetting("ConsoleMaxLines", 100000);
+ m_settings->registerSetting("ConsoleOverflowStop", true);
+
+ // Folders
+ m_settings->registerSetting("InstanceDir", "instances");
+ m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods");
+ m_settings->registerSetting("IconsDir", "icons");
+
+ // Editors
+ m_settings->registerSetting("JsonEditor", QString());
+
+ // Language
+ m_settings->registerSetting("Language", QString());
+
+ // Console
+ m_settings->registerSetting("ShowConsole", false);
+ m_settings->registerSetting("AutoCloseConsole", false);
+ m_settings->registerSetting("ShowConsoleOnError", true);
+ m_settings->registerSetting("LogPrePostOutput", true);
+
+ // Window Size
+ m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false);
+ m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
+ m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480);
+
+ // Proxy Settings
+ m_settings->registerSetting("ProxyType", "None");
+ m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1");
+ m_settings->registerSetting("ProxyPort", 8080);
+ m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, "");
+ m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, "");
+
+ // Memory
+ m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
+ m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024);
+ m_settings->registerSetting("PermGen", 128);
+
+ // Java Settings
+ m_settings->registerSetting("JavaPath", "");
+ m_settings->registerSetting("JavaTimestamp", 0);
+ m_settings->registerSetting("JavaArchitecture", "");
+ m_settings->registerSetting("JavaVersion", "");
+ m_settings->registerSetting("LastHostname", "");
+ m_settings->registerSetting("JvmArgs", "");
+
+ // Minecraft launch method
+ m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
+
+ // Wrapper command for launch
+ m_settings->registerSetting("WrapperCommand", "");
+
+ // Custom Commands
+ m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, "");
+ m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, "");
+
+ // The cat
+ m_settings->registerSetting("TheCat", false);
+
+ m_settings->registerSetting("InstSortMode", "Name");
+ m_settings->registerSetting("SelectedInstance", QString());
+
+ // Window state and geometry
+ m_settings->registerSetting("MainWindowState", "");
+ m_settings->registerSetting("MainWindowGeometry", "");
+
+ m_settings->registerSetting("ConsoleWindowState", "");
+ m_settings->registerSetting("ConsoleWindowGeometry", "");
+
+ m_settings->registerSetting("SettingsGeometry", "");
+
+ m_settings->registerSetting("PagedGeometry", "");
+
+ m_settings->registerSetting("UpdateDialogGeometry", "");
+
+ // paste.ee API key
+ m_settings->registerSetting("PasteEEAPIKey", "multimc");
+
+ if(!BuildConfig.ANALYTICS_ID.isEmpty())
+ {
+ // Analytics
+ m_settings->registerSetting("Analytics", true);
+ m_settings->registerSetting("AnalyticsSeen", 0);
+ m_settings->registerSetting("AnalyticsClientID", QString());
+ }
+
+ // Init page provider
+ {
+ m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
+ m_globalSettingsProvider->addPage<MultiMCPage>();
+ m_globalSettingsProvider->addPage<MinecraftPage>();
+ m_globalSettingsProvider->addPage<JavaPage>();
+ m_globalSettingsProvider->addPage<CustomCommandsPage>();
+ m_globalSettingsProvider->addPage<ProxyPage>();
+ // m_globalSettingsProvider->addPage<PackagesPage>();
+ m_globalSettingsProvider->addPage<ExternalToolsPage>();
+ m_globalSettingsProvider->addPage<AccountListPage>();
+ m_globalSettingsProvider->addPage<PasteEEPage>();
+ }
+ qDebug() << "<> Settings loaded.";
+ }
// load translations
- initTranslations();
+ {
+ m_translations.reset(new TranslationsModel("translations"));
+ auto bcp47Name = m_settings->get("Language").toString();
+ m_translations->selectLanguage(bcp47Name);
+ qDebug() << "Your language is" << bcp47Name;
+ qDebug() << "<> Translations loaded.";
+ }
// initialize the updater
if(BuildConfig.UPDATER_ENABLED)
{
m_updateChecker.reset(new UpdateChecker(BuildConfig.CHANLIST_URL, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD));
+ qDebug() << "<> Updater started.";
}
- initIcons();
- initThemes();
- initInstances();
- initAccounts();
- initNetwork();
- initLegacyLwjgl();
+ // Instance icons
+ {
+ auto setting = MMC->settings()->getSetting("IconsDir");
+ QStringList instFolders =
+ {
+ ":/icons/multimc/32x32/instances/",
+ ":/icons/multimc/50x50/instances/",
+ ":/icons/multimc/128x128/instances/"
+ };
+ m_icons.reset(new IconList(instFolders, setting->get().toString()));
+ connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value)
+ {
+ m_icons->directoryChanged(value.toString());
+ });
+ ENV.registerIconList(m_icons);
+ qDebug() << "<> Instance icons intialized.";
+ }
+
+ // Icon themes
+ {
+ // TODO: icon themes and instance icons do not mesh well together. Rearrange and fix discrepancies!
+ // set icon theme search path!
+ auto searchPaths = QIcon::themeSearchPaths();
+ searchPaths.append("iconthemes");
+ QIcon::setThemeSearchPaths(searchPaths);
+ qDebug() << "<> Icon themes initialized.";
+ }
+
+ // Initialize widget themes
+ {
+ auto insertTheme = [this](ITheme * theme)
+ {
+ m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme)));
+ };
+ auto darkTheme = new DarkTheme();
+ insertTheme(new SystemTheme());
+ insertTheme(darkTheme);
+ insertTheme(new BrightTheme());
+ insertTheme(new CustomTheme(darkTheme, "custom"));
+ qDebug() << "<> Widget themes initialized.";
+ }
+
+ // initialize and load all instances
+ {
+ auto InstDirSetting = m_settings->getSetting("InstanceDir");
+ // instance path: check for problems with '!' in instance path and warn the user in the log
+ // and rememer that we have to show him a dialog when the gui starts (if it does so)
+ QString instDir = InstDirSetting->get().toString();
+ qDebug() << "Instance path : " << instDir;
+ if (FS::checkProblemticPathJava(QDir(instDir)))
+ {
+ qWarning() << "Your instance path contains \'!\' and this is known to cause java problems";
+ }
+ m_instances.reset(new InstanceList(this));
+ m_instanceFolder = new FolderInstanceProvider(m_settings, instDir);
+ connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged);
+ m_instances->addInstanceProvider(m_instanceFolder);
+ qDebug() << "Loading Instances...";
+ m_instances->loadList(true);
+ qDebug() << "<> Instances loaded.";
+ }
+
+ // and accounts
+ {
+ m_accounts.reset(new MojangAccountList(this));
+ qDebug() << "Loading accounts...";
+ m_accounts->setListFilePath("accounts.json", true);
+ m_accounts->loadList();
+ qDebug() << "<> Accounts loaded.";
+ }
+
+ // init the http meta cache
+ {
+ ENV.initHttpMetaCache();
+ qDebug() << "<> Cache initialized.";
+ }
+
+ // init proxy settings
+ {
+ QString proxyTypeStr = settings()->get("ProxyType").toString();
+ QString addr = settings()->get("ProxyAddr").toString();
+ int port = settings()->get("ProxyPort").value<qint16>();
+ QString user = settings()->get("ProxyUser").toString();
+ QString pass = settings()->get("ProxyPass").toString();
+ ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass);
+ qDebug() << "<> Proxy settings done.";
+ }
// now we have network, download translation updates
m_translations->downloadIndex();
@@ -366,14 +641,69 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
profiler->registerSettings(m_settings);
}
- initMCEdit();
+ // Create the MCEdit thing... why is this here?
+ {
+ m_mcedit.reset(new MCEditTool(m_settings));
+ }
+
+ connect(this, &MultiMC::aboutToQuit, [this](){
+ if(m_instances)
+ {
+ // save any remaining instance state
+ m_instances->saveNow();
+ }
+ if(logFile)
+ {
+ logFile->flush();
+ logFile->close();
+ }
+ });
+
+ {
+ setIconTheme(settings()->get("IconTheme").toString());
+ qDebug() << "<> Icon theme set.";
+ setApplicationTheme(settings()->get("ApplicationTheme").toString(), true);
+ qDebug() << "<> Application theme set.";
+ }
+
+ // Initialize analytics
+ [this]()
+ {
+ const int analyticsVersion = 2;
+ if(BuildConfig.ANALYTICS_ID.isEmpty())
+ {
+ return;
+ }
- connect(this, SIGNAL(aboutToQuit()), SLOT(onExit()));
+ auto analyticsSetting = m_settings->getSetting("Analytics");
+ connect(analyticsSetting.get(), &Setting::SettingChanged, this, &MultiMC::analyticsSettingChanged);
+ QString clientID = m_settings->get("AnalyticsClientID").toString();
+ if(clientID.isEmpty())
+ {
+ clientID = QUuid::createUuid().toString();
+ clientID.remove(QLatin1Char('{'));
+ clientID.remove(QLatin1Char('}'));
+ m_settings->set("AnalyticsClientID", clientID);
+ }
+ m_analytics = new GAnalytics(BuildConfig.ANALYTICS_ID, clientID, analyticsVersion, this);
+ m_analytics->setLogLevel(GAnalytics::Debug);
+ m_analytics->setAnonymizeIPs(true);
+ m_analytics->setNetworkAccessManager(&ENV.qnam());
- setIconTheme(settings()->get("IconTheme").toString());
- setApplicationTheme(settings()->get("ApplicationTheme").toString(), true);
+ if(m_settings->get("AnalyticsSeen").toInt() < m_analytics->version())
+ {
+ qDebug() << "Analytics info not seen by user yet (or old version).";
+ return;
+ }
+ if(!m_settings->get("Analytics").toBool())
+ {
+ qDebug() << "Analytics disabled by user.";
+ return;
+ }
- initAnalytics();
+ m_analytics->enable();
+ qDebug() << "<> Initialized analytics with tid" << BuildConfig.ANALYTICS_ID;
+ }();
if(createSetupWizard())
{
@@ -461,6 +791,7 @@ void MultiMC::performMainStartupAction()
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if(inst)
{
+ qDebug() << "<> Instance launching:" << m_instanceIdToLaunch;
launch(inst, true, nullptr);
return;
}
@@ -469,6 +800,7 @@ void MultiMC::performMainStartupAction()
{
// normal main window
showMainWindow(false);
+ qDebug() << "<> Main window shown.";
}
}
@@ -481,17 +813,22 @@ void MultiMC::showFatalErrorMessage(const QString& title, const QString& content
MultiMC::~MultiMC()
{
+ // kill the other globals.
+ Env::dispose();
+
+ // Shut down logger by setting the logger function to nothing
+ qInstallMessageHandler(nullptr);
+
#if defined Q_OS_WIN32
+ // Detach from Windows console
if(consoleAttached)
{
- const char * endline = "\n";
- auto out = GetStdHandle (STD_OUTPUT_HANDLE);
- DWORD written;
- WriteConsole(out, endline, strlen(endline), &written, NULL);
+ fclose(stdout);
+ fclose(stdin);
+ fclose(stderr);
+ FreeConsole();
}
#endif
- shutdownLogger();
- Env::dispose();
}
void MultiMC::messageReceived(const QString& message)
@@ -515,149 +852,6 @@ void MultiMC::messageReceived(const QString& message)
}
}
-void MultiMC::initNetwork()
-{
- // init the http meta cache
- ENV.initHttpMetaCache();
-
- // init proxy settings
- {
- QString proxyTypeStr = settings()->get("ProxyType").toString();
- QString addr = settings()->get("ProxyAddr").toString();
- int port = settings()->get("ProxyPort").value<qint16>();
- QString user = settings()->get("ProxyUser").toString();
- QString pass = settings()->get("ProxyPass").toString();
- ENV.updateProxySettings(proxyTypeStr, addr, port, user, pass);
- }
-}
-
-void MultiMC::initTranslations()
-{
- m_translations.reset(new TranslationsModel("translations"));
- auto bcp47Name = m_settings->get("Language").toString();
- m_translations->selectLanguage(bcp47Name);
- qDebug() << "Your language is" << bcp47Name;
-}
-
-void MultiMC::initIcons()
-{
- auto setting = MMC->settings()->getSetting("IconsDir");
- QStringList instFolders =
- {
- ":/icons/multimc/32x32/instances/",
- ":/icons/multimc/50x50/instances/",
- ":/icons/multimc/128x128/instances/"
- };
- m_icons.reset(new IconList(instFolders, setting->get().toString()));
- connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value)
- {
- m_icons->directoryChanged(value.toString());
- });
- ENV.registerIconList(m_icons);
-
- // set icon theme search path!
- auto searchPaths = QIcon::themeSearchPaths();
- searchPaths.append("iconthemes");
- QIcon::setThemeSearchPaths(searchPaths);
-}
-
-void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
-{
- const char *levels = "DWCFIS";
- const QString format("%1 %2 %3\n");
-
- qint64 msecstotal = MMC->timeSinceStart();
- qint64 seconds = msecstotal / 1000;
- qint64 msecs = msecstotal % 1000;
- QString foo;
- char buf[1025] = {0};
- ::snprintf(buf, 1024, "%5lld.%03lld", seconds, msecs);
-
- QString out = format.arg(buf).arg(levels[type]).arg(msg);
-
- MMC->logFile->write(out.toUtf8());
- MMC->logFile->flush();
- QTextStream(stderr) << out.toLocal8Bit();
- fflush(stderr);
-}
-
-static void moveFile(const QString &oldName, const QString &newName)
-{
- QFile::remove(newName);
- QFile::copy(oldName, newName);
- QFile::remove(oldName);
-}
-
-bool MultiMC::initLogger()
-{
- static const QString logBase = "MultiMC-%0.log";
-
- moveFile(logBase.arg(3), logBase.arg(4));
- moveFile(logBase.arg(2), logBase.arg(3));
- moveFile(logBase.arg(1), logBase.arg(2));
- moveFile(logBase.arg(0), logBase.arg(1));
-
- logFile = std::unique_ptr<QFile>(new QFile(logBase.arg(0)));
- auto succeeded = logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate);
- if(!succeeded)
- {
- return false;
- }
- qInstallMessageHandler(appDebugOutput);
- return true;
-}
-
-void MultiMC::shutdownLogger()
-{
- qInstallMessageHandler(nullptr);
-}
-
-void MultiMC::initAnalytics()
-{
- const int analyticsVersion = 2;
- if(BuildConfig.ANALYTICS_ID.isEmpty())
- {
- return;
- }
-
- auto analyticsSetting = m_settings->getSetting("Analytics");
- connect(analyticsSetting.get(), &Setting::SettingChanged, this, &MultiMC::analyticsSettingChanged);
- QString clientID = m_settings->get("AnalyticsClientID").toString();
- if(clientID.isEmpty())
- {
- clientID = QUuid::createUuid().toString();
- clientID.remove(QLatin1Char('{'));
- clientID.remove(QLatin1Char('}'));
- m_settings->set("AnalyticsClientID", clientID);
- }
- m_analytics = new GAnalytics(BuildConfig.ANALYTICS_ID, clientID, analyticsVersion, this);
- m_analytics->setLogLevel(GAnalytics::Debug);
- m_analytics->setAnonymizeIPs(true);
- m_analytics->setNetworkAccessManager(&ENV.qnam());
-
- if(m_settings->get("AnalyticsSeen").toInt() < m_analytics->version())
- {
- qDebug() << "Analytics info not seen by user yet (or old version).";
- return;
- }
- if(!m_settings->get("Analytics").toBool())
- {
- qDebug() << "Analytics disabled by user.";
- return;
- }
-
- m_analytics->enable();
- qDebug() << "Initialized analytics with tid" << BuildConfig.ANALYTICS_ID;
-}
-
-void MultiMC::shutdownAnalytics()
-{
- if(m_analytics)
- {
- // TODO: persist unsent messages? send them now?
- }
-}
-
void MultiMC::analyticsSettingChanged(const Setting&, QVariant value)
{
if(!m_analytics)
@@ -674,217 +868,16 @@ void MultiMC::analyticsSettingChanged(const Setting&, QVariant value)
m_analytics->enable(enabled);
}
-void MultiMC::initInstances()
-{
- auto InstDirSetting = m_settings->getSetting("InstanceDir");
- // instance path: check for problems with '!' in instance path and warn the user in the log
- // and rememer that we have to show him a dialog when the gui starts (if it does so)
- QString instDir = m_settings->get("InstanceDir").toString();
- qDebug() << "Instance path : " << instDir;
- if (FS::checkProblemticPathJava(QDir(instDir)))
- {
- qWarning() << "Your instance path contains \'!\' and this is known to cause java problems";
- }
- m_instances.reset(new InstanceList(m_settings, InstDirSetting->get().toString(), this));
- m_instanceFolder = new FolderInstanceProvider(m_settings, instDir);
- connect(InstDirSetting.get(), &Setting::SettingChanged, m_instanceFolder, &FolderInstanceProvider::on_InstFolderChanged);
- m_instances->addInstanceProvider(m_instanceFolder);
- m_instances->addInstanceProvider(new FTBInstanceProvider(m_settings));
- qDebug() << "Loading Instances...";
- m_instances->loadList(true);
-}
-
-void MultiMC::initAccounts()
-{
- // and accounts
- m_accounts.reset(new MojangAccountList(this));
- qDebug() << "Loading accounts...";
- m_accounts->setListFilePath("accounts.json", true);
- m_accounts->loadList();
-}
-
-void MultiMC::initGlobalSettings()
-{
- m_settings.reset(new INISettingsObject("multimc.cfg", this));
- // Updates
- m_settings->registerSetting("UpdateChannel", BuildConfig.VERSION_CHANNEL);
- m_settings->registerSetting("AutoUpdate", true);
-
- // Theming
- m_settings->registerSetting("IconTheme", QString("multimc"));
- m_settings->registerSetting("ApplicationTheme", QString("system"));
-
- // Notifications
- m_settings->registerSetting("ShownNotifications", QString());
-
- // Remembered state
- m_settings->registerSetting("LastUsedGroupForNewInstance", QString());
-
- QString defaultMonospace;
- int defaultSize = 11;
-#ifdef Q_OS_WIN32
- defaultMonospace = "Courier";
- defaultSize = 10;
-#elif defined(Q_OS_MAC)
- defaultMonospace = "Menlo";
-#else
- defaultMonospace = "Monospace";
-#endif
-
- // resolve the font so the default actually matches
- QFont consoleFont;
- consoleFont.setFamily(defaultMonospace);
- consoleFont.setStyleHint(QFont::Monospace);
- consoleFont.setFixedPitch(true);
- QFontInfo consoleFontInfo(consoleFont);
- QString resolvedDefaultMonospace = consoleFontInfo.family();
- QFont resolvedFont(resolvedDefaultMonospace);
- qDebug() << "Detected default console font:" << resolvedDefaultMonospace
- << ", substitutions:" << resolvedFont.substitutions().join(',');
-
- m_settings->registerSetting("ConsoleFont", resolvedDefaultMonospace);
- m_settings->registerSetting("ConsoleFontSize", defaultSize);
- m_settings->registerSetting("ConsoleMaxLines", 100000);
- m_settings->registerSetting("ConsoleOverflowStop", true);
-
- FTBPlugin::initialize(m_settings);
-
- // Folders
- m_settings->registerSetting("InstanceDir", "instances");
- m_settings->registerSetting({"CentralModsDir", "ModsDir"}, "mods");
- m_settings->registerSetting({"LWJGLDir", "LwjglDir"}, "lwjgl");
- m_settings->registerSetting("IconsDir", "icons");
-
- // Editors
- m_settings->registerSetting("JsonEditor", QString());
-
- // Language
- m_settings->registerSetting("Language", QString());
-
- // Console
- m_settings->registerSetting("ShowConsole", false);
- m_settings->registerSetting("AutoCloseConsole", false);
- m_settings->registerSetting("ShowConsoleOnError", true);
- m_settings->registerSetting("LogPrePostOutput", true);
-
- // Console Colors
- // m_settings->registerSetting("SysMessageColor", QColor(Qt::blue));
- // m_settings->registerSetting("StdOutColor", QColor(Qt::black));
- // m_settings->registerSetting("StdErrColor", QColor(Qt::red));
-
- // Window Size
- m_settings->registerSetting({"LaunchMaximized", "MCWindowMaximize"}, false);
- m_settings->registerSetting({"MinecraftWinWidth", "MCWindowWidth"}, 854);
- m_settings->registerSetting({"MinecraftWinHeight", "MCWindowHeight"}, 480);
-
- // Proxy Settings
- m_settings->registerSetting("ProxyType", "None");
- m_settings->registerSetting({"ProxyAddr", "ProxyHostName"}, "127.0.0.1");
- m_settings->registerSetting("ProxyPort", 8080);
- m_settings->registerSetting({"ProxyUser", "ProxyUsername"}, "");
- m_settings->registerSetting({"ProxyPass", "ProxyPassword"}, "");
-
- // Memory
- m_settings->registerSetting({"MinMemAlloc", "MinMemoryAlloc"}, 512);
- m_settings->registerSetting({"MaxMemAlloc", "MaxMemoryAlloc"}, 1024);
- m_settings->registerSetting("PermGen", 128);
-
- // Java Settings
- m_settings->registerSetting("JavaPath", "");
- m_settings->registerSetting("JavaTimestamp", 0);
- m_settings->registerSetting("JavaArchitecture", "");
- m_settings->registerSetting("JavaVersion", "");
- m_settings->registerSetting("LastHostname", "");
- m_settings->registerSetting("JvmArgs", "");
-
- // Minecraft launch method
- m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
-
- // Wrapper command for launch
- m_settings->registerSetting("WrapperCommand", "");
-
- // Custom Commands
- m_settings->registerSetting({"PreLaunchCommand", "PreLaunchCmd"}, "");
- m_settings->registerSetting({"PostExitCommand", "PostExitCmd"}, "");
-
- // The cat
- m_settings->registerSetting("TheCat", false);
-
- m_settings->registerSetting("InstSortMode", "Name");
- m_settings->registerSetting("SelectedInstance", QString());
-
- // Window state and geometry
- m_settings->registerSetting("MainWindowState", "");
- m_settings->registerSetting("MainWindowGeometry", "");
-
- m_settings->registerSetting("ConsoleWindowState", "");
- m_settings->registerSetting("ConsoleWindowGeometry", "");
-
- m_settings->registerSetting("SettingsGeometry", "");
-
- m_settings->registerSetting("PagedGeometry", "");
-
- m_settings->registerSetting("UpdateDialogGeometry", "");
-
- // Jar mod nag dialog in version page
- m_settings->registerSetting("JarModNagSeen", false);
-
- // paste.ee API key
- m_settings->registerSetting("PasteEEAPIKey", "multimc");
-
- if(!BuildConfig.ANALYTICS_ID.isEmpty())
- {
- // Analytics
- m_settings->registerSetting("Analytics", true);
- m_settings->registerSetting("AnalyticsSeen", 0);
- m_settings->registerSetting("AnalyticsClientID", QString());
- }
-
- // Init page provider
- {
- m_globalSettingsProvider = std::make_shared<GenericPageProvider>(tr("Settings"));
- m_globalSettingsProvider->addPage<MultiMCPage>();
- m_globalSettingsProvider->addPage<MinecraftPage>();
- m_globalSettingsProvider->addPage<JavaPage>();
- m_globalSettingsProvider->addPage<ProxyPage>();
- m_globalSettingsProvider->addPage<PackagesPage>();
- m_globalSettingsProvider->addPage<ExternalToolsPage>();
- m_globalSettingsProvider->addPage<AccountListPage>();
- m_globalSettingsProvider->addPage<PasteEEPage>();
- }
-}
-
-void MultiMC::initMCEdit()
-{
- m_mcedit.reset(new MCEditTool(m_settings));
-}
-
-void MultiMC::initLegacyLwjgl()
-{
- auto list = lwjgllist();
-}
-
std::shared_ptr<TranslationsModel> MultiMC::translations()
{
return m_translations;
}
-std::shared_ptr<LWJGLVersionList> MultiMC::lwjgllist()
-{
- if (!m_lwjgllist)
- {
- m_lwjgllist.reset(new LWJGLVersionList());
- ENV.registerVersionList("org.lwjgl.legacy", m_lwjgllist);
- }
- return m_lwjgllist;
-}
-
std::shared_ptr<JavaInstallList> MultiMC::javalist()
{
if (!m_javalist)
{
m_javalist.reset(new JavaInstallList());
- ENV.registerVersionList("com.java", m_javalist);
}
return m_javalist;
}
@@ -901,19 +894,6 @@ std::vector<ITheme *> MultiMC::getValidApplicationThemes()
return ret;
}
-void MultiMC::initThemes()
-{
- auto insertTheme = [this](ITheme * theme)
- {
- m_themes.insert(std::make_pair(theme->id(), std::unique_ptr<ITheme>(theme)));
- };
- auto darkTheme = new DarkTheme();
- insertTheme(new SystemTheme());
- insertTheme(darkTheme);
- insertTheme(new BrightTheme());
- insertTheme(new CustomTheme(darkTheme, "custom"));
-}
-
void MultiMC::setApplicationTheme(const QString& name, bool initial)
{
auto systemPalette = qApp->palette();
@@ -939,19 +919,6 @@ QIcon MultiMC::getThemedIcon(const QString& name)
return XdgIcon::fromTheme(name);
}
-void MultiMC::onExit()
-{
- if(m_instances)
- {
- // m_instances->saveGroupList();
- }
- if(logFile)
- {
- logFile->flush();
- logFile->close();
- }
-}
-
bool MultiMC::openJsonEditor(const QString &filename)
{
const QString file = QDir::current().absoluteFilePath(filename);
@@ -1007,6 +974,11 @@ bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *pro
showInstanceWindow(instance, "console");
return true;
}
+ else if (instance->canEdit())
+ {
+ showInstanceWindow(instance);
+ return true;
+ }
return false;
}
@@ -1159,8 +1131,19 @@ MainWindow* MultiMC::showMainWindow(bool minimized)
* cd4 = CPU architecture
*/
QVariantMap customValues;
- customValues["cm1"] = m_settings->get("MinMemAlloc");
- customValues["cm2"] = m_settings->get("MaxMemAlloc");
+ int min = m_settings->get("MinMemAlloc").toInt();
+ int max = m_settings->get("MaxMemAlloc").toInt();
+ if(min < max)
+ {
+ customValues["cm1"] = min;
+ customValues["cm2"] = max;
+ }
+ else
+ {
+ customValues["cm1"] = max;
+ customValues["cm2"] = min;
+ }
+
constexpr uint64_t Mega = 1024ull * 1024ull;
int ramSize = int(Sys::getSystemRam() / Mega);
qDebug() << "RAM size is" << ramSize << "MB";
@@ -1232,5 +1215,3 @@ void MultiMC::on_windowClose()
exit(0);
}
}
-
-#include "MultiMC.moc"
diff --git a/application/MultiMC.h b/application/MultiMC.h
index c9bba2f8..e93d8a69 100644
--- a/application/MultiMC.h
+++ b/application/MultiMC.h
@@ -18,7 +18,6 @@ class SetupWizard;
class FolderInstanceProvider;
class GenericPageProvider;
class QFile;
-class LWJGLVersionList;
class HttpMetaCache;
class SettingsObject;
class InstanceList;
@@ -91,7 +90,7 @@ public:
}
std::shared_ptr<TranslationsModel> translations();
- std::shared_ptr<LWJGLVersionList> lwjgllist();
+
std::shared_ptr<JavaInstallList> javalist();
std::shared_ptr<InstanceList> instances() const
@@ -144,11 +143,6 @@ public:
InstanceWindow *showInstanceWindow(InstancePtr instance, QString page = QString());
MainWindow *showMainWindow(bool minimized = false);
- size_t numRunningInstances()
- {
- return m_runningInstances;
- }
-
void updateIsRunning(bool running);
bool updatesAreAllowed();
@@ -160,10 +154,6 @@ public slots:
bool kill(InstancePtr instance);
private slots:
- /**
- * Do all the things that should be done before we exit
- */
- void onExit();
void on_windowClose();
void messageReceived(const QString & message);
void controllerSucceeded();
@@ -172,19 +162,6 @@ private slots:
void setupWizardFinished(int status);
private:
- bool initLogger();
- void shutdownLogger();
- void initIcons();
- void initThemes();
- void initGlobalSettings();
- void initTranslations();
- void initNetwork();
- void initInstances();
- void initAccounts();
- void initMCEdit();
- void initAnalytics();
- void initLegacyLwjgl();
- void shutdownAnalytics();
bool createSetupWizard();
void performMainStartupAction();
@@ -205,7 +182,6 @@ private:
std::shared_ptr<IconList> m_icons;
std::shared_ptr<UpdateChecker> m_updateChecker;
std::shared_ptr<MojangAccountList> m_accounts;
- std::shared_ptr<LWJGLVersionList> m_lwjgllist;
std::shared_ptr<JavaInstallList> m_javalist;
std::shared_ptr<TranslationsModel> m_translations;
std::shared_ptr<GenericPageProvider> m_globalSettingsProvider;
@@ -217,8 +193,10 @@ private:
QString m_rootPath;
Status m_status = MultiMC::StartingUp;
+#if defined Q_OS_WIN32
// used on Windows to attach the standard IO streams
bool consoleAttached = false;
+#endif
// FIXME: attach to instances instead.
struct InstanceXtras
diff --git a/application/VersionProxyModel.cpp b/application/VersionProxyModel.cpp
index 96bdc9ab..0dbc4ef6 100644
--- a/application/VersionProxyModel.cpp
+++ b/application/VersionProxyModel.cpp
@@ -369,6 +369,24 @@ QModelIndex VersionProxyModel::getRecommended() const
return index(recommended, 0);
}
+QModelIndex VersionProxyModel::getVersion(const QString& version) const
+{
+ int found = -1;
+ for (int i = 0; i < rowCount(); i++)
+ {
+ auto value = sourceModel()->data(mapToSource(index(i, 0)), BaseVersionList::VersionRole);
+ if (value.toString() == version)
+ {
+ found = i;
+ }
+ }
+ if(found == -1)
+ {
+ return QModelIndex();
+ }
+ return index(found, 0);
+}
+
void VersionProxyModel::clearFilters()
{
m_filters.clear();
diff --git a/application/VersionProxyModel.h b/application/VersionProxyModel.h
index 19e89271..4441ea6b 100644
--- a/application/VersionProxyModel.h
+++ b/application/VersionProxyModel.h
@@ -42,6 +42,7 @@ public:
void setFilter(const BaseVersionList::ModelRoles column, const QString &filter, const bool exact);
void clearFilters();
QModelIndex getRecommended() const;
+ QModelIndex getVersion(const QString & version) const;
private slots:
void sourceDataChanged(const QModelIndex &source_top_left,const QModelIndex &source_bottom_right);
diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp
index 3d16f5c3..8f2e72c2 100644
--- a/application/dialogs/AboutDialog.cpp
+++ b/application/dialogs/AboutDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -93,7 +93,7 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
ui->urlLabel->setOpenExternalLinks(true);
- ui->icon->setPixmap(MMC->getThemedIcon("multimc").pixmap(64));
+ ui->icon->setPixmap(MMC->getThemedIcon("logo").pixmap(64));
ui->title->setText("MultiMC 5");
ui->versionLabel->setText(tr("Version") +": " + BuildConfig.printableVersionString());
diff --git a/application/dialogs/AboutDialog.h b/application/dialogs/AboutDialog.h
index 65c3628d..9768b866 100644
--- a/application/dialogs/AboutDialog.h
+++ b/application/dialogs/AboutDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/AboutDialog.ui b/application/dialogs/AboutDialog.ui
index d1cafcb2..5e8e3e68 100644
--- a/application/dialogs/AboutDialog.ui
+++ b/application/dialogs/AboutDialog.ui
@@ -165,7 +165,7 @@
</font>
</property>
<property name="text">
- <string>© 2012-2017 MultiMC Contributors</string>
+ <string>© 2012-2018 MultiMC Contributors</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -180,7 +180,7 @@
</font>
</property>
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://github.com/MultiMC/MultiMC5&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://github.com/MultiMC/MultiMC5&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://github.com/MultiMC/MultiMC5&quot;&gt;http://github.com/MultiMC/MultiMC5&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/application/dialogs/ChooseFtbPackDialog.cpp b/application/dialogs/ChooseFtbPackDialog.cpp
new file mode 100644
index 00000000..ae7c72e1
--- /dev/null
+++ b/application/dialogs/ChooseFtbPackDialog.cpp
@@ -0,0 +1,64 @@
+#include "ChooseFtbPackDialog.h"
+#include "widgets/FtbModpackListItem.h"
+
+ChooseFtbPackDialog::ChooseFtbPackDialog(FtbModpackList modpacks) : ui(new Ui::ChooseFtbPackDialog) {
+ ui->setupUi(this);
+
+ for(int i = 0; i < modpacks.size(); i++) {
+ FtbModpackListItem *item = new FtbModpackListItem(ui->packList, modpacks.at(i));
+
+ item->setText(modpacks.at(i).name);
+ }
+
+ //TODO: Use a model/view instead of a widget
+ connect(ui->packList, &QListWidget::itemClicked, this, &ChooseFtbPackDialog::onListItemClicked);
+ connect(ui->packVersionSelection, &QComboBox::currentTextChanged, this, &ChooseFtbPackDialog::onVersionSelectionItemChanged);
+
+ ui->modpackInfo->setOpenExternalLinks(true);
+
+}
+
+ChooseFtbPackDialog::~ChooseFtbPackDialog(){
+ delete ui;
+}
+
+void ChooseFtbPackDialog::onListItemClicked(QListWidgetItem *item){
+ ui->packVersionSelection->clear();
+ FtbModpack selectedPack = static_cast<FtbModpackListItem*>(item)->getModpack();
+
+ ui->modpackInfo->setHtml("Pack by <b>" + selectedPack.author + "</b>" + "<br>Minecraft " + selectedPack.mcVersion + "<br>"
+ "<br>" + selectedPack.description + "<ul><li>" + selectedPack.mods.replace(";", "</li><li>") + "</li></ul>");
+
+ bool currentAdded = false;
+
+ for(int i = 0; i < selectedPack.oldVersions.size(); i++) {
+ if(selectedPack.currentVersion == selectedPack.oldVersions.at(i)) {
+ currentAdded = true;
+ }
+ ui->packVersionSelection->addItem(selectedPack.oldVersions.at(i));
+ }
+
+ if(!currentAdded) {
+ ui->packVersionSelection->addItem(selectedPack.currentVersion);
+ }
+
+ selected = selectedPack;
+
+}
+
+void ChooseFtbPackDialog::onVersionSelectionItemChanged(QString data) {
+ if(data.isNull() || data.isEmpty()) {
+ selectedVersion = "";
+ return;
+ }
+
+ selectedVersion = data;
+}
+
+FtbModpack ChooseFtbPackDialog::getSelectedModpack() {
+ return selected;
+}
+
+QString ChooseFtbPackDialog::getSelectedVersion() {
+ return selectedVersion;
+}
diff --git a/application/dialogs/ChooseFtbPackDialog.h b/application/dialogs/ChooseFtbPackDialog.h
new file mode 100644
index 00000000..212aa27b
--- /dev/null
+++ b/application/dialogs/ChooseFtbPackDialog.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <QDialog>
+#include <net/NetJob.h>
+#include <modplatform/PackHelpers.h>
+#include "ui_ChooseFtbPackDialog.h"
+#include <modplatform/PackHelpers.h>
+
+namespace Ui {
+ class ChooseFtbPackDialog;
+}
+
+class ChooseFtbPackDialog : public QDialog {
+
+ Q_OBJECT
+
+private:
+ Ui::ChooseFtbPackDialog *ui;
+ FtbModpack selected;
+ QString selectedVersion;
+
+private slots:
+ void onListItemClicked(QListWidgetItem *item);
+ void onVersionSelectionItemChanged(QString data);
+
+public:
+ ChooseFtbPackDialog(FtbModpackList packs);
+ ~ChooseFtbPackDialog();
+
+ FtbModpack getSelectedModpack();
+ QString getSelectedVersion();
+};
diff --git a/application/dialogs/ChooseFtbPackDialog.ui b/application/dialogs/ChooseFtbPackDialog.ui
new file mode 100644
index 00000000..fdf845a9
--- /dev/null
+++ b/application/dialogs/ChooseFtbPackDialog.ui
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ChooseFtbPackDialog</class>
+ <widget class="QDialog" name="ChooseFtbPackDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>730</width>
+ <height>437</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="sizeGripEnabled">
+ <bool>false</bool>
+ </property>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="geometry">
+ <rect>
+ <x>540</x>
+ <y>400</y>
+ <width>176</width>
+ <height>25</height>
+ </rect>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="geometry">
+ <rect>
+ <x>10</x>
+ <y>10</y>
+ <width>261</width>
+ <height>381</height>
+ </rect>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>259</width>
+ <height>379</height>
+ </rect>
+ </property>
+ <widget class="QListWidget" name="packList">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>261</width>
+ <height>381</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </widget>
+ <widget class="QScrollArea" name="scrollArea_2">
+ <property name="geometry">
+ <rect>
+ <x>280</x>
+ <y>10</y>
+ <width>441</width>
+ <height>381</height>
+ </rect>
+ </property>
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents_2">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>439</width>
+ <height>379</height>
+ </rect>
+ </property>
+ <widget class="QTextBrowser" name="modpackInfo">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>441</width>
+ <height>381</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </widget>
+ <widget class="QComboBox" name="packVersionSelection">
+ <property name="geometry">
+ <rect>
+ <x>450</x>
+ <y>400</y>
+ <width>72</width>
+ <height>25</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QLabel" name="selectedVersionLabel">
+ <property name="geometry">
+ <rect>
+ <x>340</x>
+ <y>400</y>
+ <width>101</width>
+ <height>21</height>
+ </rect>
+ </property>
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
+ </property>
+ </widget>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ChooseFtbPackDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>666</x>
+ <y>422</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>889</x>
+ <y>501</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ChooseFtbPackDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>680</x>
+ <y>411</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>524</x>
+ <y>458</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp
index 1cf13144..72ef00fa 100644
--- a/application/dialogs/CopyInstanceDialog.cpp
+++ b/application/dialogs/CopyInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/CopyInstanceDialog.h b/application/dialogs/CopyInstanceDialog.h
index 5a0f41f1..809552eb 100644
--- a/application/dialogs/CopyInstanceDialog.h
+++ b/application/dialogs/CopyInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/CustomMessageBox.cpp b/application/dialogs/CustomMessageBox.cpp
index 007c941a..a7d75263 100644
--- a/application/dialogs/CustomMessageBox.cpp
+++ b/application/dialogs/CustomMessageBox.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/CustomMessageBox.h b/application/dialogs/CustomMessageBox.h
index 85059153..f9c0ad4e 100644
--- a/application/dialogs/CustomMessageBox.h
+++ b/application/dialogs/CustomMessageBox.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/EditAccountDialog.cpp b/application/dialogs/EditAccountDialog.cpp
index 4f12c70c..e43be1d8 100644
--- a/application/dialogs/EditAccountDialog.cpp
+++ b/application/dialogs/EditAccountDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/EditAccountDialog.h b/application/dialogs/EditAccountDialog.h
index 3365dd1a..f121a111 100644
--- a/application/dialogs/EditAccountDialog.h
+++ b/application/dialogs/EditAccountDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/ExportInstanceDialog.cpp b/application/dialogs/ExportInstanceDialog.cpp
index be859994..0e19b758 100644
--- a/application/dialogs/ExportInstanceDialog.cpp
+++ b/application/dialogs/ExportInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -321,9 +321,11 @@ ExportInstanceDialog::ExportInstanceDialog(InstancePtr instance, QWidget *parent
auto root = instance->instanceRoot();
ui->treeView->setModel(proxyModel);
ui->treeView->setRootIndex(proxyModel->mapFromSource(model->index(root)));
+ ui->treeView->sortByColumn(0, Qt::AscendingOrder);
connect(proxyModel, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int)));
+ model->setFilter(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Hidden);
model->setRootPath(root);
auto headerView = ui->treeView->header();
headerView->setSectionResizeMode(QHeaderView::ResizeToContents);
@@ -387,7 +389,7 @@ bool ExportInstanceDialog::doExport()
const QString output = QFileDialog::getSaveFileName(
this, tr("Export %1").arg(m_instance->name()),
FS::PathCombine(QDir::homePath(), name + ".zip"), "Zip (*.zip)", nullptr, QFileDialog::DontConfirmOverwrite);
- if (output.isNull())
+ if (output.isEmpty())
{
return false;
}
diff --git a/application/dialogs/ExportInstanceDialog.h b/application/dialogs/ExportInstanceDialog.h
index 0e136ad8..7b9c6726 100644
--- a/application/dialogs/ExportInstanceDialog.h
+++ b/application/dialogs/ExportInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/IconPickerDialog.cpp b/application/dialogs/IconPickerDialog.cpp
index 70649b72..4ffd12bc 100644
--- a/application/dialogs/IconPickerDialog.cpp
+++ b/application/dialogs/IconPickerDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
#include "groupview/InstanceDelegate.h"
#include "icons/IconList.h"
+#include <DesktopServices.h>
IconPickerDialog::IconPickerDialog(QWidget *parent)
: QDialog(parent), ui(new Ui::IconPickerDialog)
@@ -59,19 +60,21 @@ IconPickerDialog::IconPickerDialog(QWidget *parent)
contentsWidget->setModel(MMC->icons().get());
+ // NOTE: ResetRole forces the button to be on the left, while the OK/Cancel ones are on the right. We win.
auto buttonAdd = ui->buttonBox->addButton(tr("Add Icon"), QDialogButtonBox::ResetRole);
- auto buttonRemove =
- ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole);
+ auto buttonRemove = ui->buttonBox->addButton(tr("Remove Icon"), QDialogButtonBox::ResetRole);
connect(buttonAdd, SIGNAL(clicked(bool)), SLOT(addNewIcon()));
connect(buttonRemove, SIGNAL(clicked(bool)), SLOT(removeSelectedIcon()));
connect(contentsWidget, SIGNAL(doubleClicked(QModelIndex)), SLOT(activated(QModelIndex)));
- connect(contentsWidget->selectionModel(),
- SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
- SLOT(selectionChanged(QItemSelection, QItemSelection)));
+ connect(contentsWidget->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), SLOT(selectionChanged(QItemSelection, QItemSelection)));
+
+ auto buttonFolder = ui->buttonBox->addButton(tr("Open Folder"), QDialogButtonBox::ResetRole);
+ connect(buttonFolder, &QPushButton::clicked, this, &IconPickerDialog::openFolder);
}
+
bool IconPickerDialog::eventFilter(QObject *obj, QEvent *evt)
{
if (obj != ui->iconView)
@@ -101,7 +104,7 @@ void IconPickerDialog::addNewIcon()
QString selectIcons = tr("Select Icons");
//: The type of icon files
QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(),
- tr("Icons") + "(*.png *.jpg *.jpeg *.ico)");
+ tr("Icons") + "(*.png *.jpg *.jpeg *.ico *.svg)");
MMC->icons()->installIcons(fileNames);
}
@@ -152,3 +155,8 @@ IconPickerDialog::~IconPickerDialog()
{
delete ui;
}
+
+void IconPickerDialog::openFolder()
+{
+ DesktopServices::openDirectory(MMC->icons()->getDirectory(), true);
+}
diff --git a/application/dialogs/IconPickerDialog.h b/application/dialogs/IconPickerDialog.h
index a2a657ed..9053ec61 100644
--- a/application/dialogs/IconPickerDialog.h
+++ b/application/dialogs/IconPickerDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,4 +45,5 @@ slots:
void delayed_scroll(QModelIndex);
void addNewIcon();
void removeSelectedIcon();
+ void openFolder();
};
diff --git a/application/dialogs/LoginDialog.cpp b/application/dialogs/LoginDialog.cpp
index 37e26045..b2020372 100644
--- a/application/dialogs/LoginDialog.cpp
+++ b/application/dialogs/LoginDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/LoginDialog.h b/application/dialogs/LoginDialog.h
index 571da778..27b97cb0 100644
--- a/application/dialogs/LoginDialog.h
+++ b/application/dialogs/LoginDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/NewComponentDialog.cpp b/application/dialogs/NewComponentDialog.cpp
new file mode 100644
index 00000000..514aa938
--- /dev/null
+++ b/application/dialogs/NewComponentDialog.cpp
@@ -0,0 +1,106 @@
+/* Copyright 2013-2018 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 "NewComponentDialog.h"
+#include "ui_NewComponentDialog.h"
+
+#include <BaseVersion.h>
+#include <icons/IconList.h>
+#include <tasks/Task.h>
+#include <InstanceList.h>
+
+#include "VersionSelectDialog.h"
+#include "ProgressDialog.h"
+#include "IconPickerDialog.h"
+
+#include <QLayout>
+#include <QPushButton>
+#include <QFileDialog>
+#include <QValidator>
+
+#include <meta/Index.h>
+#include <meta/VersionList.h>
+
+NewComponentDialog::NewComponentDialog(const QString & initialName, const QString & initialUid, QWidget *parent)
+ : QDialog(parent), ui(new Ui::NewComponentDialog)
+{
+ ui->setupUi(this);
+ resize(minimumSizeHint());
+
+ ui->nameTextBox->setText(initialName);
+ ui->uidTextBox->setText(initialUid);
+
+ connect(ui->nameTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState);
+ connect(ui->uidTextBox, &QLineEdit::textChanged, this, &NewComponentDialog::updateDialogState);
+
+ auto groups = MMC->instances()->getGroups().toSet();
+ ui->nameTextBox->setFocus();
+
+ originalPlaceholderText = ui->uidTextBox->placeholderText();
+ updateDialogState();
+}
+
+NewComponentDialog::~NewComponentDialog()
+{
+ delete ui;
+}
+
+void NewComponentDialog::updateDialogState()
+{
+ auto protoUid = ui->nameTextBox->text().toLower();
+ protoUid.remove(QRegularExpression("[^a-z]"));
+ if(protoUid.isEmpty())
+ {
+ ui->uidTextBox->setPlaceholderText(originalPlaceholderText);
+ }
+ else
+ {
+ QString suggestedUid = "org.multimc.custom." + protoUid;
+ ui->uidTextBox->setPlaceholderText(suggestedUid);
+ }
+ bool allowOK = !name().isEmpty() && !uid().isEmpty() && !uidBlacklist.contains(uid());
+ ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowOK);
+}
+
+QString NewComponentDialog::name() const
+{
+ auto result = ui->nameTextBox->text();
+ if(result.size())
+ {
+ return result.trimmed();
+ }
+ return QString();
+}
+
+QString NewComponentDialog::uid() const
+{
+ auto result = ui->uidTextBox->text();
+ if(result.size())
+ {
+ return result.trimmed();
+ }
+ result = ui->uidTextBox->placeholderText();
+ if(result.size() && result != originalPlaceholderText)
+ {
+ return result.trimmed();
+ }
+ return QString();
+}
+
+void NewComponentDialog::setBlacklist(QStringList badUids)
+{
+ uidBlacklist = badUids;
+}
diff --git a/application/dialogs/NewComponentDialog.h b/application/dialogs/NewComponentDialog.h
new file mode 100644
index 00000000..70caec0f
--- /dev/null
+++ b/application/dialogs/NewComponentDialog.h
@@ -0,0 +1,48 @@
+/* Copyright 2013-2018 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 <QString>
+#include <QStringList>
+
+namespace Ui
+{
+class NewComponentDialog;
+}
+
+class NewComponentDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit NewComponentDialog(const QString & initialName = QString(), const QString & initialUid = QString(), QWidget *parent = 0);
+ virtual ~NewComponentDialog();
+ void setBlacklist(QStringList badUids);
+
+ QString name() const;
+ QString uid() const;
+
+private slots:
+ void updateDialogState();
+
+private:
+ Ui::NewComponentDialog *ui;
+
+ QString originalPlaceholderText;
+ QStringList uidBlacklist;
+};
diff --git a/application/dialogs/NewComponentDialog.ui b/application/dialogs/NewComponentDialog.ui
new file mode 100644
index 00000000..b30c2431
--- /dev/null
+++ b/application/dialogs/NewComponentDialog.ui
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>NewComponentDialog</class>
+ <widget class="QDialog" name="NewComponentDialog">
+ <property name="windowModality">
+ <enum>Qt::ApplicationModal</enum>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>345</width>
+ <height>146</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Copy Instance</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff>:/icons/toolbar/copy</normaloff>:/icons/toolbar/copy</iconset>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLineEdit" name="nameTextBox">
+ <property name="placeholderText">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="uidTextBox">
+ <property name="placeholderText">
+ <string>uid</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </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>
+ <tabstops>
+ <tabstop>nameTextBox</tabstop>
+ <tabstop>uidTextBox</tabstop>
+ </tabstops>
+ <resources>
+ <include location="../../graphics.qrc"/>
+ </resources>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>NewComponentDialog</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>NewComponentDialog</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/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp
index d1a2bbfa..5faf57ac 100644
--- a/application/dialogs/NewInstanceDialog.cpp
+++ b/application/dialogs/NewInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
#include "VersionSelectDialog.h"
#include "ProgressDialog.h"
#include "IconPickerDialog.h"
+#include "ChooseFtbPackDialog.h"
#include <QLayout>
#include <QPushButton>
@@ -71,7 +72,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
}
else
{
- vlist->load();
+ vlist->load(Net::Mode::Online);
auto task = vlist->getLoadTask();
if(vlist->isLoaded())
{
@@ -92,9 +93,12 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
connect(ui->modpackEdit, &QLineEdit::textChanged, this, &NewInstanceDialog::updateDialogState);
connect(ui->modpackBox, &QRadioButton::clicked, this, &NewInstanceDialog::updateDialogState);
+
connect(ui->versionBox, &QRadioButton::clicked, this, &NewInstanceDialog::updateDialogState);
connect(ui->versionTextBox, &QLineEdit::textChanged, this, &NewInstanceDialog::updateDialogState);
+ connect(ui->ftbBox, &QRadioButton::clicked, this, &NewInstanceDialog::updateDialogState);
+
auto groups = MMC->instances()->getGroups().toSet();
auto groupList = QStringList(groups.toList());
groupList.sort(Qt::CaseInsensitive);
@@ -117,6 +121,14 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
ui->modpackBox->setChecked(true);
ui->modpackEdit->setText(url);
}
+
+ ftbPackDownloader = new FtbPackDownloader();
+
+ connect(ftbPackDownloader, &FtbPackDownloader::ready, this, &NewInstanceDialog::ftbPackDataDownloadSuccessfully);
+ connect(ftbPackDownloader, &FtbPackDownloader::packFetchFailed, this, &NewInstanceDialog::ftbPackDataDownloadFailed);
+
+ ftbPackDownloader->fetchModpacks(false);
+
updateDialogState();
}
@@ -147,6 +159,17 @@ void NewInstanceDialog::updateDialogState()
QFileInfo fi(url.fileName());
suggestedName = fi.completeBaseName();
}
+ else if (ui->ftbBox->isChecked())
+ {
+ if(ftbPackDownloader->isValidPackSelected()) {
+ suggestedName = ftbPackDownloader->getSuggestedInstanceName();
+ ui->labelFtbPack->setText(selectedPack.name);
+ }
+
+ }
+
+ ftbModpackRequested = ui->ftbBox->isChecked();
+
if(suggestedName.isEmpty())
{
ui->instNameTextBox->setPlaceholderText(originalPlaceholderText);
@@ -156,9 +179,10 @@ void NewInstanceDialog::updateDialogState()
ui->instNameTextBox->setPlaceholderText(suggestedName);
}
bool allowOK = !instName().isEmpty() && (
- (ui->versionBox->isChecked() && m_selectedVersion) ||
- (ui->modpackBox->isChecked() && ui->modpackEdit->hasAcceptableInput())
- );
+ (ui->versionBox->isChecked() && m_selectedVersion) ||
+ (ui->modpackBox->isChecked() && ui->modpackEdit->hasAcceptableInput()) ||
+ (ui->ftbBox->isChecked() && ftbPackDownloader && ftbPackDownloader->isValidPackSelected() )
+ );
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowOK);
}
@@ -271,3 +295,34 @@ void NewInstanceDialog::on_modpackBtn_clicked()
}
}
}
+
+bool NewInstanceDialog::isFtbModpackRequested() {
+ return ftbModpackRequested;
+}
+
+FtbPackDownloader *NewInstanceDialog::getFtbPackDownloader() {
+ return ftbPackDownloader;
+}
+
+void NewInstanceDialog::on_btnChooseFtbPack_clicked() {
+ ChooseFtbPackDialog dl(ftbPackDownloader->getModpacks());
+ dl.exec();
+ if(dl.result() == QDialog::Accepted) {
+ selectedPack = dl.getSelectedModpack();
+ ftbPackDownloader->selectPack(selectedPack, dl.getSelectedVersion());
+ }
+ updateDialogState();
+}
+
+void NewInstanceDialog::ftbPackDataDownloadSuccessfully() {
+ ui->packDataDownloadStatus->setText(tr("(Pack data download complete)"));
+ // ui->labelFtbPack->setText(tr("Disabled for now... not completed!"));
+
+ // Disable for PR
+ ui->ftbBox->setEnabled(true);
+}
+
+void NewInstanceDialog::ftbPackDataDownloadFailed() {
+ ui->packDataDownloadStatus->setText(tr("(Pack data download failed)"));
+}
+
diff --git a/application/dialogs/NewInstanceDialog.h b/application/dialogs/NewInstanceDialog.h
index 000b6a06..9b0f7f53 100644
--- a/application/dialogs/NewInstanceDialog.h
+++ b/application/dialogs/NewInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
#include <QDialog>
#include "BaseVersion.h"
+#include "modplatform/FtbPackDownloader.h"
+#include "modplatform/PackHelpers.h"
namespace Ui
{
@@ -42,19 +44,31 @@ public:
QUrl modpackUrl() const;
BaseVersionPtr selectedVersion() const;
+ bool isFtbModpackRequested();
+ FtbPackDownloader* getFtbPackDownloader();
+
private
slots:
void on_btnChangeVersion_clicked();
void on_iconButton_clicked();
void on_modpackBtn_clicked();
+ void on_btnChooseFtbPack_clicked();
void on_instNameTextBox_textChanged(const QString &arg1);
void versionListUpdated();
+ void ftbPackDataDownloadSuccessfully();
+ void ftbPackDataDownloadFailed();
+
private:
Ui::NewInstanceDialog *ui;
bool m_versionSetByUser = false;
+ bool ftbModpackRequested = false;
+
BaseVersionPtr m_selectedVersion;
QString InstIconKey;
QString originalPlaceholderText;
+
+ FtbPackDownloader* ftbPackDownloader;
+ FtbModpack selectedPack;
};
diff --git a/application/dialogs/NewInstanceDialog.ui b/application/dialogs/NewInstanceDialog.ui
index 6b875ff4..428b9c57 100644
--- a/application/dialogs/NewInstanceDialog.ui
+++ b/application/dialogs/NewInstanceDialog.ui
@@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>281</width>
- <height>404</height>
+ <height>407</height>
</rect>
</property>
<property name="windowTitle">
@@ -107,37 +107,50 @@
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
- <item row="4" column="2">
- <widget class="QToolButton" name="modpackBtn">
+ <item row="5" column="0">
+ <widget class="QRadioButton" name="ftbBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
+ <string>Install FTB Pack</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <widget class="QToolButton" name="btnChangeVersion">
+ <property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
- <item row="4" column="0" colspan="2">
- <widget class="FocusLineEdit" name="modpackEdit">
+ <item row="7" column="2">
+ <widget class="QToolButton" name="btnChooseFtbPack">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
- <string notr="true">http://</string>
+ <string>...</string>
</property>
</widget>
</item>
- <item row="2" column="0" colspan="2">
- <widget class="QLineEdit" name="versionTextBox">
- <property name="readOnly">
- <bool>true</bool>
+ <item row="4" column="2">
+ <widget class="QToolButton" name="modpackBtn">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string notr="true">...</string>
</property>
</widget>
</item>
- <item row="2" column="2">
- <widget class="QToolButton" name="btnChangeVersion">
+ <item row="7" column="0" colspan="2">
+ <widget class="QLabel" name="labelFtbPack">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
<property name="text">
- <string notr="true">...</string>
+ <string> No Pack choosen</string>
</property>
</widget>
</item>
@@ -158,6 +171,30 @@
</property>
</widget>
</item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QLineEdit" name="versionTextBox">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" colspan="2">
+ <widget class="FocusLineEdit" name="modpackEdit">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string notr="true">http://</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1" colspan="2">
+ <widget class="QLabel" name="packDataDownloadStatus">
+ <property name="text">
+ <string>(Loading Pack data...)</string>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
@@ -219,8 +256,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
- <x>257</x>
- <y>333</y>
+ <x>266</x>
+ <y>378</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@@ -235,11 +272,11 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
- <x>325</x>
- <y>333</y>
+ <x>271</x>
+ <y>378</y>
</hint>
<hint type="destinationlabel">
- <x>286</x>
+ <x>280</x>
<y>274</y>
</hint>
</hints>
@@ -251,12 +288,12 @@
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>81</x>
- <y>229</y>
+ <x>91</x>
+ <y>251</y>
</hint>
<hint type="destinationlabel">
- <x>236</x>
- <y>221</y>
+ <x>240</x>
+ <y>278</y>
</hint>
</hints>
</connection>
@@ -267,12 +304,12 @@
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
- <x>129</x>
- <y>225</y>
+ <x>139</x>
+ <y>251</y>
</hint>
<hint type="destinationlabel">
- <x>328</x>
- <y>229</y>
+ <x>270</x>
+ <y>278</y>
</hint>
</hints>
</connection>
@@ -287,8 +324,8 @@
<y>195</y>
</hint>
<hint type="destinationlabel">
- <x>213</x>
- <y>191</y>
+ <x>223</x>
+ <y>224</y>
</hint>
</hints>
</connection>
@@ -303,8 +340,40 @@
<y>198</y>
</hint>
<hint type="destinationlabel">
- <x>322</x>
- <y>192</y>
+ <x>270</x>
+ <y>224</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>ftbBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>btnChooseFtbPack</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>67</x>
+ <y>301</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>254</x>
+ <y>327</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>ftbBox</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>labelFtbPack</receiver>
+ <slot>setEnabled(bool)</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>81</x>
+ <y>310</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>73</x>
+ <y>334</y>
</hint>
</hints>
</connection>
diff --git a/application/dialogs/NotificationDialog.cpp b/application/dialogs/NotificationDialog.cpp
index 8f920371..d61bf6bd 100644
--- a/application/dialogs/NotificationDialog.cpp
+++ b/application/dialogs/NotificationDialog.cpp
@@ -18,6 +18,7 @@ NotificationDialog::NotificationDialog(const NotificationChecker::NotificationEn
case NotificationChecker::NotificationEntry::Warning:
icon = QStyle::SP_MessageBoxWarning;
break;
+ default:
case NotificationChecker::NotificationEntry::Information:
icon = QStyle::SP_MessageBoxInformation;
break;
diff --git a/application/dialogs/ProfileSelectDialog.cpp b/application/dialogs/ProfileSelectDialog.cpp
index 16ffe51f..f1b335f9 100644
--- a/application/dialogs/ProfileSelectDialog.cpp
+++ b/application/dialogs/ProfileSelectDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/ProfileSelectDialog.h b/application/dialogs/ProfileSelectDialog.h
index c8ef5959..b1268743 100644
--- a/application/dialogs/ProfileSelectDialog.h
+++ b/application/dialogs/ProfileSelectDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/ProgressDialog.cpp b/application/dialogs/ProgressDialog.cpp
index db973c5a..9bf54a52 100644
--- a/application/dialogs/ProgressDialog.cpp
+++ b/application/dialogs/ProgressDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -116,7 +116,7 @@ bool ProgressDialog::handleImmediateResult(QDialog::DialogCode &result)
{
if(task->isFinished())
{
- if(task->successful())
+ if(task->wasSuccessful())
{
result = QDialog::Accepted;
}
diff --git a/application/dialogs/ProgressDialog.h b/application/dialogs/ProgressDialog.h
index 28de8247..f27b71e1 100644
--- a/application/dialogs/ProgressDialog.h
+++ b/application/dialogs/ProgressDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/SkinUploadDialog.cpp b/application/dialogs/SkinUploadDialog.cpp
index ebbab785..93414c6e 100644
--- a/application/dialogs/SkinUploadDialog.cpp
+++ b/application/dialogs/SkinUploadDialog.cpp
@@ -20,14 +20,59 @@ void SkinUploadDialog::on_buttonBox_accepted()
if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted)
{
//FIXME: recover with password prompt
- CustomMessageBox::selectable(this, tr("Failed to login!"), tr("Unknown error"), QMessageBox::Warning)->exec();
+ CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to login!"), QMessageBox::Warning)->exec();
close();
return;
}
- QString fileName = ui->skinPathTextBox->text();
- if (!QFile::exists(fileName))
+ QString fileName;
+ QString input = ui->skinPathTextBox->text();
+ QRegExp urlPrefixMatcher("^([a-z]+)://.+$");
+ bool isLocalFile = false;
+ // it has an URL prefix -> it is an URL
+ if(urlPrefixMatcher.exactMatch(input))
{
- CustomMessageBox::selectable(this, tr("Skin file does not exist!"), tr("Unknown error"), QMessageBox::Warning)->exec();
+ QUrl fileURL = input;
+ if(fileURL.isValid())
+ {
+ // local?
+ if(fileURL.isLocalFile())
+ {
+ isLocalFile = true;
+ fileName = fileURL.toLocalFile();
+ }
+ else
+ {
+ CustomMessageBox::selectable(
+ this,
+ tr("Skin Upload"),
+ tr("Using remote URLs for setting skins is not implemented yet."),
+ QMessageBox::Warning
+ )->exec();
+ close();
+ return;
+ }
+ }
+ else
+ {
+ CustomMessageBox::selectable(
+ this,
+ tr("Skin Upload"),
+ tr("You cannot use an invalid URL for uploading skins."),
+ QMessageBox::Warning
+ )->exec();
+ close();
+ return;
+ }
+ }
+ else
+ {
+ // just assume it's a path then
+ isLocalFile = true;
+ fileName = ui->skinPathTextBox->text();
+ }
+ if (isLocalFile && !QFile::exists(fileName))
+ {
+ CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Skin file does not exist!"), QMessageBox::Warning)->exec();
close();
return;
}
@@ -43,22 +88,22 @@ void SkinUploadDialog::on_buttonBox_accepted()
SkinUploadPtr upload = std::make_shared<SkinUpload>(this, session, FS::read(fileName), model);
if (prog.execWithTask((Task*)upload.get()) != QDialog::Accepted)
{
- CustomMessageBox::selectable(this, tr("Failed to upload skin!"), tr("Unknown error"), QMessageBox::Warning)->exec();
+ CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Failed to upload skin!"), QMessageBox::Warning)->exec();
close();
return;
}
- CustomMessageBox::selectable(this, tr("Skin uploaded!"), tr("Success"), QMessageBox::Information)->exec();
+ CustomMessageBox::selectable(this, tr("Skin Upload"), tr("Success"), QMessageBox::Information)->exec();
close();
}
void SkinUploadDialog::on_skinBrowseBtn_clicked()
{
QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png");
- QString cooked_path = FS::NormalizePath(raw_path);
- if (cooked_path.isEmpty() || !QFileInfo::exists(cooked_path))
+ if (raw_path.isEmpty() || !QFileInfo::exists(raw_path))
{
return;
}
+ QString cooked_path = FS::NormalizePath(raw_path);
ui->skinPathTextBox->setText(cooked_path);
}
diff --git a/application/dialogs/UpdateDialog.cpp b/application/dialogs/UpdateDialog.cpp
index c935ec3b..30c7173d 100644
--- a/application/dialogs/UpdateDialog.cpp
+++ b/application/dialogs/UpdateDialog.cpp
@@ -81,6 +81,12 @@ QString reprocessCommits(QByteArray json)
{
const auto & commitval = commitarray[i];
auto commitobj = Json::requireObject(commitval);
+ auto parents_info = Json::ensureArray(commitobj, "parents");
+ // NOTE: this ignores merge commits, because they have more than one parent
+ if(parents_info.size() > 1)
+ {
+ continue;
+ }
auto commit_url = Json::requireString(commitobj, "html_url");
auto commit_info = Json::requireObject(commitobj, "commit");
auto commit_message = Json::requireString(commit_info, "message");
diff --git a/application/dialogs/UpdateDialog.h b/application/dialogs/UpdateDialog.h
index 8099464b..78960c99 100644
--- a/application/dialogs/UpdateDialog.h
+++ b/application/dialogs/UpdateDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/dialogs/VersionSelectDialog.cpp b/application/dialogs/VersionSelectDialog.cpp
index de525d89..a44572cc 100644
--- a/application/dialogs/VersionSelectDialog.cpp
+++ b/application/dialogs/VersionSelectDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -83,6 +83,12 @@ void VersionSelectDialog::retranslate()
m_refreshButton->setText(tr("&Refresh"));
}
+void VersionSelectDialog::setCurrentVersion(const QString& version)
+{
+ m_currentVersion = version;
+ m_versionWidget->setCurrentVersion(version);
+}
+
void VersionSelectDialog::setEmptyString(QString emptyString)
{
m_versionWidget->setEmptyString(emptyString);
diff --git a/application/dialogs/VersionSelectDialog.h b/application/dialogs/VersionSelectDialog.h
index 4b13a57b..c8b00c65 100644
--- a/application/dialogs/VersionSelectDialog.h
+++ b/application/dialogs/VersionSelectDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -46,12 +46,12 @@ public:
BaseVersionPtr selectedVersion() const;
+ void setCurrentVersion(const QString & version);
void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter);
void setExactFilter(BaseVersionList::ModelRoles role, QString filter);
void setEmptyString(QString emptyString);
void setEmptyErrorString(QString emptyErrorString);
void setResizeOn(int column);
- void setUseLatest(const bool useLatest);
private slots:
void on_refreshButton_clicked();
@@ -61,6 +61,7 @@ private:
void selectRecommended();
private:
+ QString m_currentVersion;
VersionSelectWidget *m_versionWidget = nullptr;
QVBoxLayout *m_verticalLayout = nullptr;
QHBoxLayout *m_horizontalLayout = nullptr;
diff --git a/application/groupview/GroupView.cpp b/application/groupview/GroupView.cpp
index 6178db0b..0d6aa49e 100644
--- a/application/groupview/GroupView.cpp
+++ b/application/groupview/GroupView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -104,37 +104,9 @@ inline bool operator<(const LocaleString &lhs, const LocaleString &rhs)
return (QString::localeAwareCompare(lhs, rhs) < 0);
}
-void GroupView::updateGeometries()
+void GroupView::updateScrollbar()
{
- geometryCache.clear();
int previousScroll = verticalScrollBar()->value();
-
- QMap<LocaleString, VisualGroup *> cats;
-
- for (int i = 0; i < model()->rowCount(); ++i)
- {
- const QString groupName = model()->index(i, 0).data(GroupViewRoles::GroupRole).toString();
- if (!cats.contains(groupName))
- {
- VisualGroup *old = this->category(groupName);
- if (old)
- {
- auto cat = new VisualGroup(old);
- cats.insert(groupName, cat);
- cat->update();
- }
- else
- {
- auto cat = new VisualGroup(groupName, this);
- cats.insert(groupName, cat);
- cat->update();
- }
- }
- }
-
- qDeleteAll(m_groups);
- m_groups = cats.values();
-
if (m_groups.isEmpty())
{
verticalScrollBar()->setRange(0, 0);
@@ -167,7 +139,38 @@ void GroupView::updateGeometries()
}
verticalScrollBar()->setValue(qMin(previousScroll, verticalScrollBar()->maximum()));
+}
+
+void GroupView::updateGeometries()
+{
+ geometryCache.clear();
+
+ QMap<LocaleString, VisualGroup *> cats;
+ for (int i = 0; i < model()->rowCount(); ++i)
+ {
+ const QString groupName = model()->index(i, 0).data(GroupViewRoles::GroupRole).toString();
+ if (!cats.contains(groupName))
+ {
+ VisualGroup *old = this->category(groupName);
+ if (old)
+ {
+ auto cat = new VisualGroup(old);
+ cats.insert(groupName, cat);
+ cat->update();
+ }
+ else
+ {
+ auto cat = new VisualGroup(groupName, this);
+ cats.insert(groupName, cat);
+ cat->update();
+ }
+ }
+ }
+
+ qDeleteAll(m_groups);
+ m_groups = cats.values();
+ updateScrollbar();
viewport()->update();
}
@@ -511,6 +514,10 @@ void GroupView::resizeEvent(QResizeEvent *event)
m_currentItemsPerRow = newItemsPerRow;
updateGeometries();
}
+ else
+ {
+ updateScrollbar();
+ }
}
void GroupView::dragEnterEvent(QDragEnterEvent *event)
diff --git a/application/groupview/GroupView.h b/application/groupview/GroupView.h
index a3f8a400..07c65bb5 100644
--- a/application/groupview/GroupView.h
+++ b/application/groupview/GroupView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -96,6 +96,8 @@ protected:
void startDrag(Qt::DropActions supportedActions) override;
+ void updateScrollbar();
+
private:
friend struct VisualGroup;
QList<VisualGroup *> m_groups;
diff --git a/application/groupview/GroupedProxyModel.cpp b/application/groupview/GroupedProxyModel.cpp
index e7d4670e..c13f2411 100644
--- a/application/groupview/GroupedProxyModel.cpp
+++ b/application/groupview/GroupedProxyModel.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/groupview/GroupedProxyModel.h b/application/groupview/GroupedProxyModel.h
index dc39346f..babeb308 100644
--- a/application/groupview/GroupedProxyModel.h
+++ b/application/groupview/GroupedProxyModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/groupview/InstanceDelegate.cpp b/application/groupview/InstanceDelegate.cpp
index 10e31178..0855c04a 100644
--- a/application/groupview/InstanceDelegate.cpp
+++ b/application/groupview/InstanceDelegate.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/groupview/InstanceDelegate.h b/application/groupview/InstanceDelegate.h
index 49496b5b..c0148570 100644
--- a/application/groupview/InstanceDelegate.h
+++ b/application/groupview/InstanceDelegate.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/groupview/VisualGroup.cpp b/application/groupview/VisualGroup.cpp
index e6f23af0..940c7a8b 100644
--- a/application/groupview/VisualGroup.cpp
+++ b/application/groupview/VisualGroup.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/groupview/VisualGroup.h b/application/groupview/VisualGroup.h
index 940acd7a..2caac49f 100644
--- a/application/groupview/VisualGroup.h
+++ b/application/groupview/VisualGroup.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/install_prereqs.cmake.in b/application/install_prereqs.cmake.in
index 7f283c89..2906a4ec 100644
--- a/application/install_prereqs.cmake.in
+++ b/application/install_prereqs.cmake.in
@@ -10,6 +10,8 @@ function(gp_resolved_file_type_override resolved_file type_var)
set(${type_var} other PARENT_SCOPE)
elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libpng")
set(${type_var} other PARENT_SCOPE)
+ elseif(resolved_file MATCHES "^/(usr/)?lib(.+)?/libproxy")
+ set(${type_var} other PARENT_SCOPE)
elseif((resolved_file MATCHES "^/(usr/)?lib(.+)?/libstdc\\+\\+") AND (UNIX AND NOT APPLE))
set(${type_var} other PARENT_SCOPE)
endif()
diff --git a/application/main.cpp b/application/main.cpp
index 48983dd6..ade05faf 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -49,6 +49,7 @@ int main(int argc, char *argv[])
Q_INIT_RESOURCE(pe_colored);
Q_INIT_RESOURCE(OSX);
Q_INIT_RESOURCE(iOS);
+ Q_INIT_RESOURCE(flat);
return app.exec();
}
case MultiMC::Failed:
diff --git a/application/package/linux/multimc.desktop b/application/package/linux/multimc.desktop
index 7fce5c7a..514b330f 100755
--- a/application/package/linux/multimc.desktop
+++ b/application/package/linux/multimc.desktop
@@ -1,6 +1,5 @@
[Desktop Entry]
Version=1.0
-Encoding=UTF-8
Name=MultiMC
GenericName=MultiMC
Comment=Free, open source launcher and instance manager for Minecraft.
@@ -8,4 +7,4 @@ Type=Application
Terminal=false
Exec=multimc
Icon=multimc
-Categories=Application;Game
+Categories=Game
diff --git a/application/pagedialog/PageDialog.cpp b/application/pagedialog/PageDialog.cpp
index b175b55f..b201de85 100644
--- a/application/pagedialog/PageDialog.cpp
+++ b/application/pagedialog/PageDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pagedialog/PageDialog.h b/application/pagedialog/PageDialog.h
index 2717df93..67cd290e 100644
--- a/application/pagedialog/PageDialog.h
+++ b/application/pagedialog/PageDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/BasePage.h b/application/pages/BasePage.h
index 1d6e4bd0..63a26239 100644
--- a/application/pages/BasePage.h
+++ b/application/pages/BasePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -33,11 +33,15 @@ public:
virtual QString helpPage() const { return QString(); }
virtual void opened() {}
virtual void closed() {}
- virtual void setParentContainer(BasePageContainer *) {};
-
+ virtual void setParentContainer(BasePageContainer * container)
+ {
+ m_container = container;
+ };
public:
int stackIndex = -1;
int listIndex = -1;
+protected:
+ BasePageContainer * m_container = nullptr;
};
typedef std::shared_ptr<BasePage> BasePagePtr;
diff --git a/application/pages/BasePageContainer.h b/application/pages/BasePageContainer.h
index 660685d3..ff7315c2 100644
--- a/application/pages/BasePageContainer.h
+++ b/application/pages/BasePageContainer.h
@@ -6,4 +6,5 @@ public:
virtual ~BasePageContainer(){};
virtual bool selectPage(QString pageId) = 0;
virtual void refreshContainer() = 0;
+ virtual bool requestClose() = 0;
};
diff --git a/application/pages/BasePageProvider.h b/application/pages/BasePageProvider.h
index b976bc16..0ebcff7a 100644
--- a/application/pages/BasePageProvider.h
+++ b/application/pages/BasePageProvider.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/InstanceSettingsPage.cpp b/application/pages/InstanceSettingsPage.cpp
index 82438583..71e90a32 100644
--- a/application/pages/InstanceSettingsPage.cpp
+++ b/application/pages/InstanceSettingsPage.cpp
@@ -11,12 +11,16 @@
#include <java/JavaInstallList.h>
#include <FileSystem.h>
+#include <sys.h>
+#include <widgets/CustomCommands.h>
InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent)
: QWidget(parent), ui(new Ui::InstanceSettingsPage), m_instance(inst)
{
m_settings = inst->settings();
ui->setupUi(this);
+ auto sysMB = Sys::getSystemRam() / Sys::megabyte;
+ ui->maxMemSpinBox->setMaximum(sysMB);
loadSettings();
}
@@ -77,8 +81,18 @@ void InstanceSettingsPage::applySettings()
m_settings->set("OverrideMemory", memory);
if (memory)
{
- m_settings->set("MinMemAlloc", ui->minMemSpinBox->value());
- m_settings->set("MaxMemAlloc", ui->maxMemSpinBox->value());
+ int min = ui->minMemSpinBox->value();
+ int max = ui->maxMemSpinBox->value();
+ if(min < max)
+ {
+ m_settings->set("MinMemAlloc", min);
+ m_settings->set("MaxMemAlloc", max);
+ }
+ else
+ {
+ m_settings->set("MinMemAlloc", max);
+ m_settings->set("MaxMemAlloc", min);
+ }
m_settings->set("PermGen", ui->permGenSpinBox->value());
}
else
@@ -117,13 +131,13 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("OverrideJava");
// Custom Commands
- bool custcmd = ui->customCommandsGroupBox->isChecked();
+ bool custcmd = ui->customCommands->checked();
m_settings->set("OverrideCommands", custcmd);
if (custcmd)
{
- m_settings->set("PreLaunchCommand", ui->preLaunchCmdTextBox->text());
- m_settings->set("WrapperCommand", ui->wrapperCmdTextBox->text());
- m_settings->set("PostExitCommand", ui->postExitCmdTextBox->text());
+ m_settings->set("PreLaunchCommand", ui->customCommands->prelaunchCommand());
+ m_settings->set("WrapperCommand", ui->customCommands->wrapperCommand());
+ m_settings->set("PostExitCommand", ui->customCommands->postexitCommand());
}
else
{
@@ -149,8 +163,18 @@ void InstanceSettingsPage::loadSettings()
// 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());
+ int min = m_settings->get("MinMemAlloc").toInt();
+ int max = m_settings->get("MaxMemAlloc").toInt();
+ if(min < max)
+ {
+ ui->minMemSpinBox->setValue(min);
+ ui->maxMemSpinBox->setValue(max);
+ }
+ else
+ {
+ ui->minMemSpinBox->setValue(max);
+ ui->maxMemSpinBox->setValue(min);
+ }
ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt());
// Java Settings
@@ -164,11 +188,14 @@ void InstanceSettingsPage::loadSettings()
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->wrapperCmdTextBox->setText(m_settings->get("WrapperCommand").toString());
- ui->postExitCmdTextBox->setText(m_settings->get("PostExitCommand").toString());
+ // Custom commands
+ ui->customCommands->initialize(
+ true,
+ m_settings->get("OverrideCommands").toBool(),
+ m_settings->get("PreLaunchCommand").toString(),
+ m_settings->get("WrapperCommand").toString(),
+ m_settings->get("PostExitCommand").toString()
+ );
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()
@@ -189,13 +216,13 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked()
void InstanceSettingsPage::on_javaBrowseBtn_clicked()
{
QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"));
- QString cooked_path = FS::NormalizePath(raw_path);
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if(cooked_path.isEmpty())
+ if(raw_path.isEmpty())
{
return;
}
+ QString cooked_path = FS::NormalizePath(raw_path);
QFileInfo javaInfo(cooked_path);;
if(!javaInfo.exists() || !javaInfo.isExecutable())
diff --git a/application/pages/InstanceSettingsPage.h b/application/pages/InstanceSettingsPage.h
index 5930a2fd..4959bdbe 100644
--- a/application/pages/InstanceSettingsPage.h
+++ b/application/pages/InstanceSettingsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/InstanceSettingsPage.ui b/application/pages/InstanceSettingsPage.ui
index 6163297f..0c180df3 100644
--- a/application/pages/InstanceSettingsPage.ui
+++ b/application/pages/InstanceSettingsPage.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>458</width>
- <height>508</height>
+ <width>553</width>
+ <height>522</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -42,7 +42,7 @@
<bool>true</bool>
</property>
<property name="title">
- <string>Java ins&amp;tallation</string>
+ <string>Java insta&amp;llation</string>
</property>
<property name="checkable">
<bool>true</bool>
@@ -196,7 +196,7 @@
<bool>true</bool>
</property>
<property name="title">
- <string>Java arguments</string>
+ <string>Java argumen&amp;ts</string>
</property>
<property name="checkable">
<bool>true</bool>
@@ -211,19 +211,6 @@
</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">
@@ -363,81 +350,7 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
- <widget class="QGroupBox" name="customCommandsGroupBox">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="title">
- <string>Cus&amp;tom Commands</string>
- </property>
- <property name="checkable">
- <bool>true</bool>
- </property>
- <property name="checked">
- <bool>false</bool>
- </property>
- <layout class="QGridLayout" name="gridLayout_4">
- <item row="2" column="0">
- <widget class="QLabel" name="labelPostExitCmd">
- <property name="text">
- <string>Post-exit command:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="preLaunchCmdTextBox"/>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="labelPreLaunchCmd">
- <property name="text">
- <string>Pre-launch command:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="postExitCmdTextBox"/>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="labelWrapperCmd">
- <property name="text">
- <string>Wrapper command:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="wrapperCmdTextBox"/>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="labelCustomCmdsDescription">
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pre-launch command runs before the instance launches and post-exit command runs after it exits.&lt;/p&gt;&lt;p&gt;Both will be run in MultiMC's working folder with extra environment variables:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;INST_NAME - Name of the instance&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;INST_ID - ID of the instance&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;INST_DIR - absolute path of the instance&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;INST_MC_DIR - absolute path of minecraft&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;INST_JAVA - java binary used for launch&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;INST_JAVA_ARGS - command-line parameters used for launch&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
- </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>
+ <widget class="CustomCommands" name="customCommands" native="true"/>
</item>
</layout>
</widget>
@@ -445,6 +358,14 @@
</item>
</layout>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>CustomCommands</class>
+ <extends>QWidget</extends>
+ <header>widgets/CustomCommands.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
<tabstops>
<tabstop>settingsTabs</tabstop>
<tabstop>javaSettingsGroupBox</tabstop>
@@ -466,10 +387,6 @@
<tabstop>showConsoleCheck</tabstop>
<tabstop>autoCloseConsoleCheck</tabstop>
<tabstop>showConsoleErrorCheck</tabstop>
- <tabstop>customCommandsGroupBox</tabstop>
- <tabstop>preLaunchCmdTextBox</tabstop>
- <tabstop>wrapperCmdTextBox</tabstop>
- <tabstop>postExitCmdTextBox</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/application/pages/LegacyJarModPage.cpp b/application/pages/LegacyJarModPage.cpp
deleted file mode 100644
index c13bce8c..00000000
--- a/application/pages/LegacyJarModPage.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/* Copyright 2013-2017 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 <QKeyEvent>
-#include <QKeyEvent>
-
-#include "dialogs/VersionSelectDialog.h"
-#include "dialogs/ProgressDialog.h"
-#include "dialogs/ModEditDialogCommon.h"
-#include "minecraft/legacy/LegacyModList.h"
-#include "minecraft/legacy/LegacyInstance.h"
-#include "Env.h"
-#include <DesktopServices.h>
-#include "MultiMC.h"
-#include <GuiUtil.h>
-
-LegacyJarModPage::LegacyJarModPage(LegacyInstance *inst, QWidget *parent)
- : QWidget(parent), ui(new Ui::LegacyJarModPage), m_inst(inst)
-{
- ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
-
- 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;
-}
-
-bool LegacyJarModPage::shouldDisplay() const
-{
- return !m_inst->isRunning();
-}
-
-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_addJarBtn_clicked()
-{
- auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget());
- if(!list.empty())
- {
- m_jarmods->stopWatching();
- for (auto filename : list)
- {
- m_jarmods->installMod(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()
-{
- DesktopServices::openDirectory(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/application/pages/LegacyJarModPage.h b/application/pages/LegacyJarModPage.h
deleted file mode 100644
index 2a5ac75f..00000000
--- a/application/pages/LegacyJarModPage.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/* Copyright 2013-2017 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 "net/NetJob.h"
-#include "BasePage.h"
-#include <MultiMC.h>
-
-class LegacyModList;
-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() const override
- {
- return tr("Jar Mods");
- }
- virtual QIcon icon() const override
- {
- return MMC->getThemedIcon("jarmods");
- }
- virtual QString id() const override
- {
- return "jarmods";
- }
- virtual QString helpPage() const override
- {
- return "Legacy-jar-mods";
- }
- virtual bool shouldDisplay() const override;
-
-private
-slots:
-
- void on_addJarBtn_clicked();
- void on_rmJarBtn_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<LegacyModList> m_jarmods;
- LegacyInstance *m_inst;
- NetJobPtr forgeJob;
-};
diff --git a/application/pages/LegacyJarModPage.ui b/application/pages/LegacyJarModPage.ui
deleted file mode 100644
index 137a4ae5..00000000
--- a/application/pages/LegacyJarModPage.ui
+++ /dev/null
@@ -1,162 +0,0 @@
-<?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>659</width>
- <height>593</height>
- </rect>
- </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>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout">
- <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>
- <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>
- </layout>
- </widget>
- </widget>
- </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>widgets/ModListView.h</header>
- </customwidget>
- <customwidget>
- <class>MCModInfoFrame</class>
- <extends>QFrame</extends>
- <header>widgets/MCModInfoFrame.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
- <class>LineSeparator</class>
- <extends>QWidget</extends>
- <header>widgets/LineSeparator.h</header>
- <container>1</container>
- </customwidget>
- </customwidgets>
- <resources/>
- <connections/>
-</ui>
diff --git a/application/pages/LegacyUpgradePage.cpp b/application/pages/LegacyUpgradePage.cpp
index 14cb916d..a8f4a08c 100644
--- a/application/pages/LegacyUpgradePage.cpp
+++ b/application/pages/LegacyUpgradePage.cpp
@@ -2,8 +2,13 @@
#include "ui_LegacyUpgradePage.h"
#include "minecraft/legacy/LegacyInstance.h"
+#include "minecraft/legacy/LegacyUpgradeTask.h"
+#include "MultiMC.h"
+#include "FolderInstanceProvider.h"
+#include "dialogs/CustomMessageBox.h"
+#include "dialogs/ProgressDialog.h"
-LegacyUpgradePage::LegacyUpgradePage(LegacyInstance *inst, QWidget *parent)
+LegacyUpgradePage::LegacyUpgradePage(InstancePtr inst, QWidget *parent)
: QWidget(parent), ui(new Ui::LegacyUpgradePage), m_inst(inst)
{
ui->setupUi(this);
@@ -14,9 +19,24 @@ LegacyUpgradePage::~LegacyUpgradePage()
delete ui;
}
+void LegacyUpgradePage::runModalTask(Task *task)
+{
+ connect(task, &Task::failed, [this](QString reason)
+ {
+ CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Warning)->show();
+ });
+ ProgressDialog loadDialog(this);
+ loadDialog.setSkipButton(true, tr("Abort"));
+ if(loadDialog.execWithTask(task) == QDialog::Accepted)
+ {
+ m_container->requestClose();
+ }
+}
+
void LegacyUpgradePage::on_upgradeButton_clicked()
{
- // now what?
+ std::unique_ptr<Task> task(MMC->folderProvider()->legacyUpgradeTask(m_inst));
+ runModalTask(task.get());
}
bool LegacyUpgradePage::shouldDisplay() const
diff --git a/application/pages/LegacyUpgradePage.h b/application/pages/LegacyUpgradePage.h
index 4731bb82..3e1abe93 100644
--- a/application/pages/LegacyUpgradePage.h
+++ b/application/pages/LegacyUpgradePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
#include "minecraft/legacy/LegacyInstance.h"
#include "pages/BasePage.h"
#include <MultiMC.h>
+#include "tasks/Task.h"
namespace Ui
{
@@ -31,7 +32,7 @@ class LegacyUpgradePage : public QWidget, public BasePage
Q_OBJECT
public:
- explicit LegacyUpgradePage(LegacyInstance *inst, QWidget *parent = 0);
+ explicit LegacyUpgradePage(InstancePtr inst, QWidget *parent = 0);
virtual ~LegacyUpgradePage();
virtual QString displayName() const override
{
@@ -50,11 +51,14 @@ public:
return "Legacy-upgrade";
}
virtual bool shouldDisplay() const override;
-private
-slots:
+
+private slots:
void on_upgradeButton_clicked();
private:
+ void runModalTask(Task *task);
+
+private:
Ui::LegacyUpgradePage *ui;
- LegacyInstance *m_inst;
+ InstancePtr m_inst;
};
diff --git a/application/pages/LegacyUpgradePage.ui b/application/pages/LegacyUpgradePage.ui
index 5e8c74eb..a94ee039 100644
--- a/application/pages/LegacyUpgradePage.ui
+++ b/application/pages/LegacyUpgradePage.ui
@@ -26,15 +26,7 @@
<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>
+ <string>&lt;html&gt;&lt;body&gt;&lt;h1&gt;Upgrade is required&lt;/h1&gt;&lt;p&gt;MultiMC now supports old Minecraft versions and all the required features in the new (OneSix) instance format. As a consequence, the old (Legacy) format has been entirely disabled and old instances need to be upgraded.&lt;/p&gt;&lt;p&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&gt;Please report any issues on our &lt;a href=&quot;https://github.com/MultiMC/MultiMC5/issues&quot;&gt;github issues page&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;There is also a &lt;a href=&quot;https://discord.gg/GtPmv93&quot;&gt;discord channel for testing here&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
@@ -44,7 +36,7 @@ p, li { white-space: pre-wrap; }
<item>
<widget class="QCommandLinkButton" name="upgradeButton">
<property name="text">
- <string>Start the upgrade! (Not Yet Implemented, Coming Soonâ„¢)</string>
+ <string>Upgrade the instance</string>
</property>
</widget>
</item>
diff --git a/application/pages/LogPage.cpp b/application/pages/LogPage.cpp
index 75e1df7d..0fa1ee67 100644
--- a/application/pages/LogPage.cpp
+++ b/application/pages/LogPage.cpp
@@ -141,13 +141,11 @@ LogPage::LogPage(InstancePtr instance, QWidget *parent)
auto launchTask = m_instance->getLaunchTask();
if(launchTask)
{
- on_InstanceLaunchTask_changed(launchTask);
+ setInstanceLaunchTaskChanged(launchTask, true);
}
- connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::on_InstanceLaunchTask_changed);
+ connect(m_instance.get(), &BaseInstance::launchTaskChanged, this, &LogPage::onInstanceLaunchTaskChanged);
}
- ui->text->setWordWrap(true);
-
auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this);
connect(findShortcut, SIGNAL(activated()), SLOT(findActivated()));
auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this);
@@ -162,13 +160,53 @@ LogPage::~LogPage()
delete ui;
}
-void LogPage::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc)
+void LogPage::modelStateToUI()
+{
+ if(m_model->wrapLines())
+ {
+ ui->text->setWordWrap(true);
+ ui->wrapCheckbox->setCheckState(Qt::Checked);
+ }
+ else
+ {
+ ui->text->setWordWrap(false);
+ ui->wrapCheckbox->setCheckState(Qt::Unchecked);
+ }
+ if(m_model->suspended())
+ {
+ ui->trackLogCheckbox->setCheckState(Qt::Unchecked);
+ }
+ else
+ {
+ ui->trackLogCheckbox->setCheckState(Qt::Checked);
+ }
+}
+
+void LogPage::UIToModelState()
+{
+ if(!m_model)
+ {
+ return;
+ }
+ m_model->setLineWrap(ui->wrapCheckbox->checkState() == Qt::Checked);
+ m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked);
+}
+
+void LogPage::setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial)
{
m_process = proc;
if(m_process)
{
m_model = proc->getLogModel();
m_proxy->setSourceModel(m_model.get());
+ if(initial)
+ {
+ modelStateToUI();
+ }
+ else
+ {
+ UIToModelState();
+ }
}
else
{
@@ -177,6 +215,11 @@ void LogPage::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc)
}
}
+void LogPage::onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc)
+{
+ setInstanceLaunchTaskChanged(proc, false);
+}
+
bool LogPage::apply()
{
return true;
@@ -218,7 +261,7 @@ void LogPage::on_btnClear_clicked()
if(!m_model)
return;
m_model->clear();
- m_parentContainer->refreshContainer();
+ m_container->refreshContainer();
}
void LogPage::on_btnBottom_clicked()
@@ -228,12 +271,17 @@ void LogPage::on_btnBottom_clicked()
void LogPage::on_trackLogCheckbox_clicked(bool checked)
{
+ if(!m_model)
+ return;
m_model->suspend(!checked);
}
void LogPage::on_wrapCheckbox_clicked(bool checked)
{
ui->text->setWordWrap(checked);
+ if(!m_model)
+ return;
+ m_model->setLineWrap(checked);
}
void LogPage::on_findButton_clicked()
@@ -262,8 +310,3 @@ void LogPage::findActivated()
ui->searchBar->selectAll();
}
}
-
-void LogPage::setParentContainer(BasePageContainer * container)
-{
- m_parentContainer = container;
-}
diff --git a/application/pages/LogPage.h b/application/pages/LogPage.h
index 8dceb94f..b830118e 100644
--- a/application/pages/LogPage.h
+++ b/application/pages/LogPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -54,7 +54,6 @@ public:
return "Minecraft-Logs";
}
virtual bool shouldDisplay() const override;
- virtual void setParentContainer(BasePageContainer *) override;
private slots:
void on_btnPaste_clicked();
@@ -70,14 +69,18 @@ private slots:
void findNextActivated();
void findPreviousActivated();
- void on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc);
+ void onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc);
+
+private:
+ void modelStateToUI();
+ void UIToModelState();
+ void setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial);
private:
Ui::LogPage *ui;
InstancePtr m_instance;
std::shared_ptr<LaunchTask> m_process;
- BasePageContainer * m_parentContainer;
LogFormatProxyModel * m_proxy;
shared_qobject_ptr <LogModel> m_model;
};
diff --git a/application/pages/ModFolderPage.cpp b/application/pages/ModFolderPage.cpp
index 5c60cc7f..2b3f4416 100644
--- a/application/pages/ModFolderPage.cpp
+++ b/application/pages/ModFolderPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,7 @@
#include "minecraft/ModList.h"
#include "minecraft/Mod.h"
#include "minecraft/VersionFilterData.h"
+#include "minecraft/ComponentList.h"
#include <DesktopServices.h>
ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr<ModList> mods, QString id,
@@ -99,21 +100,21 @@ bool CoreModFolderPage::shouldDisplay() const
{
if (ModFolderPage::shouldDisplay())
{
- auto inst = dynamic_cast<OneSixInstance *>(m_inst);
+ auto inst = dynamic_cast<MinecraftInstance *>(m_inst);
if (!inst)
return true;
- auto version = inst->getMinecraftProfile();
+ auto version = inst->getComponentList();
if (!version)
return true;
- if(!version->versionPatch("net.minecraftforge"))
+ if(!version->getComponent("net.minecraftforge"))
{
return false;
}
- if(!version->versionPatch("net.minecraft"))
+ if(!version->getComponent("net.minecraft"))
{
return false;
}
- if(version->versionPatch("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
+ if(version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate)
{
return true;
}
diff --git a/application/pages/ModFolderPage.h b/application/pages/ModFolderPage.h
index 191fa9dc..02282c41 100644
--- a/application/pages/ModFolderPage.h
+++ b/application/pages/ModFolderPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
#include <QWidget>
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
#include "BasePage.h"
#include <MultiMC.h>
diff --git a/application/pages/NotesPage.h b/application/pages/NotesPage.h
index a119142f..eab446ad 100644
--- a/application/pages/NotesPage.h
+++ b/application/pages/NotesPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/OtherLogsPage.cpp b/application/pages/OtherLogsPage.cpp
index 3988e939..2141e0cc 100644
--- a/application/pages/OtherLogsPage.cpp
+++ b/application/pages/OtherLogsPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
#include "RecursiveFileSystemWatcher.h"
#include <GZip.h>
#include <FileSystem.h>
+#include <QShortcut>
OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget *parent)
: QWidget(parent), ui(new Ui::OtherLogsPage), m_path(path), m_fileFilter(fileFilter),
@@ -35,6 +36,17 @@ OtherLogsPage::OtherLogsPage(QString path, IPathMatcher::Ptr fileFilter, QWidget
connect(m_watcher, &RecursiveFileSystemWatcher::filesChanged, this, &OtherLogsPage::populateSelectLogBox);
populateSelectLogBox();
+
+ auto findShortcut = new QShortcut(QKeySequence(QKeySequence::Find), this);
+ connect(findShortcut, &QShortcut::activated, this, &OtherLogsPage::findActivated);
+
+ auto findNextShortcut = new QShortcut(QKeySequence(QKeySequence::FindNext), this);
+ connect(findNextShortcut, &QShortcut::activated, this, &OtherLogsPage::findNextActivated);
+
+ auto findPreviousShortcut = new QShortcut(QKeySequence(QKeySequence::FindPrevious), this);
+ connect(findPreviousShortcut, &QShortcut::activated, this, &OtherLogsPage::findPreviousActivated);
+
+ connect(ui->searchBar, &QLineEdit::returnPressed, this, &OtherLogsPage::on_findButton_clicked);
}
OtherLogsPage::~OtherLogsPage()
@@ -115,9 +127,22 @@ void OtherLogsPage::on_btnReload_clicked()
}
else
{
+ auto setPlainText = [&](const QString & text)
+ {
+ QString fontFamily = MMC->settings()->get("ConsoleFont").toString();
+ bool conversionOk = false;
+ int fontSize = MMC->settings()->get("ConsoleFontSize").toInt(&conversionOk);
+ if(!conversionOk)
+ {
+ fontSize = 11;
+ }
+ QTextDocument *doc = ui->text->document();
+ doc->setDefaultFont(QFont(fontFamily, fontSize));
+ ui->text->setPlainText(text);
+ };
auto showTooBig = [&]()
{
- ui->text->setPlainText(
+ setPlainText(
tr("The file (%1) is too big. You may want to open it in a viewer optimized "
"for large files.").arg(file.fileName()));
};
@@ -132,7 +157,7 @@ void OtherLogsPage::on_btnReload_clicked()
QByteArray temp;
if(!GZip::unzip(file.readAll(), temp))
{
- ui->text->setPlainText(
+ setPlainText(
tr("The file (%1) is not readable.").arg(file.fileName()));
return;
}
@@ -147,7 +172,7 @@ void OtherLogsPage::on_btnReload_clicked()
showTooBig();
return;
}
- ui->text->setPlainText(content);
+ setPlainText(content);
}
}
@@ -200,7 +225,7 @@ void OtherLogsPage::on_btnClean_clicked()
}
else
{
- messageBox->setText(tr("Do you really want to these files?\n%1").arg(toDelete.join('\n')));
+ messageBox->setText(tr("Do you really want to delete these files?\n%1").arg(toDelete.join('\n')));
}
messageBox->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
messageBox->setDefaultButton(QMessageBox::Ok);
@@ -253,3 +278,36 @@ void OtherLogsPage::setControlsEnabled(const bool enabled)
ui->text->setEnabled(enabled);
ui->btnClean->setEnabled(enabled);
}
+
+// FIXME: HACK, use LogView instead?
+static void findNext(QPlainTextEdit * _this, const QString& what, bool reverse)
+{
+ _this->find(what, reverse ? QTextDocument::FindFlag::FindBackward : QTextDocument::FindFlag(0));
+}
+
+void OtherLogsPage::on_findButton_clicked()
+{
+ auto modifiers = QApplication::keyboardModifiers();
+ bool reverse = modifiers & Qt::ShiftModifier;
+ findNext(ui->text, ui->searchBar->text(), reverse);
+}
+
+void OtherLogsPage::findNextActivated()
+{
+ findNext(ui->text, ui->searchBar->text(), false);
+}
+
+void OtherLogsPage::findPreviousActivated()
+{
+ findNext(ui->text, ui->searchBar->text(), true);
+}
+
+void OtherLogsPage::findActivated()
+{
+ // focus the search bar if it doesn't have focus
+ if (!ui->searchBar->hasFocus())
+ {
+ ui->searchBar->setFocus();
+ ui->searchBar->selectAll();
+ }
+}
diff --git a/application/pages/OtherLogsPage.h b/application/pages/OtherLogsPage.h
index 9ccf964f..157f5e9d 100644
--- a/application/pages/OtherLogsPage.h
+++ b/application/pages/OtherLogsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,6 +64,11 @@ private slots:
void on_btnDelete_clicked();
void on_btnClean_clicked();
+ void on_findButton_clicked();
+ void findActivated();
+ void findNextActivated();
+ void findPreviousActivated();
+
private:
void setControlsEnabled(const bool enabled);
diff --git a/application/pages/OtherLogsPage.ui b/application/pages/OtherLogsPage.ui
index 43d6a35b..56ff3b62 100644
--- a/application/pages/OtherLogsPage.ui
+++ b/application/pages/OtherLogsPage.ui
@@ -32,8 +32,34 @@
<attribute name="title">
<string notr="true">Tab 1</string>
</attribute>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="searchBar"/>
+ </item>
+ <item row="2" column="2">
+ <widget class="QPushButton" name="findButton">
+ <property name="text">
+ <string>Find</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="4">
+ <widget class="QPlainTextEdit" name="text">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="4">
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="1">
<widget class="QPushButton" name="btnCopy">
@@ -65,6 +91,16 @@
</property>
</widget>
</item>
+ <item row="3" column="4">
+ <widget class="QPushButton" name="btnClean">
+ <property name="toolTip">
+ <string>Clear the log</string>
+ </property>
+ <property name="text">
+ <string>Clean</string>
+ </property>
+ </widget>
+ </item>
<item row="3" column="0">
<widget class="QPushButton" name="btnReload">
<property name="text">
@@ -82,31 +118,12 @@
</property>
</widget>
</item>
- <item row="3" column="4">
- <widget class="QPushButton" name="btnClean">
- <property name="toolTip">
- <string>Clear the log</string>
- </property>
- <property name="text">
- <string>Clean</string>
- </property>
- </widget>
- </item>
</layout>
</item>
- <item>
- <widget class="QPlainTextEdit" name="text">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOn</enum>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Search:</string>
</property>
</widget>
</item>
@@ -117,7 +134,16 @@
</layout>
</widget>
<tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>selectLogBox</tabstop>
+ <tabstop>btnReload</tabstop>
+ <tabstop>btnCopy</tabstop>
+ <tabstop>btnPaste</tabstop>
+ <tabstop>btnDelete</tabstop>
+ <tabstop>btnClean</tabstop>
<tabstop>text</tabstop>
+ <tabstop>searchBar</tabstop>
+ <tabstop>findButton</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/application/pages/ScreenshotsPage.cpp b/application/pages/ScreenshotsPage.cpp
index a4ee6f9d..7d32576a 100644
--- a/application/pages/ScreenshotsPage.cpp
+++ b/application/pages/ScreenshotsPage.cpp
@@ -280,7 +280,7 @@ void ScreenshotsPage::on_viewFolderBtn_clicked()
void ScreenshotsPage::on_uploadBtn_clicked()
{
- auto selection = ui->listView->selectionModel()->selectedIndexes();
+ auto selection = ui->listView->selectionModel()->selectedRows();
if (selection.isEmpty())
return;
diff --git a/application/pages/ScreenshotsPage.h b/application/pages/ScreenshotsPage.h
index 4aa16d18..c3ccbdee 100644
--- a/application/pages/ScreenshotsPage.h
+++ b/application/pages/ScreenshotsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/ScreenshotsPage.ui b/application/pages/ScreenshotsPage.ui
index 7b70c07e..d05c4384 100644
--- a/application/pages/ScreenshotsPage.ui
+++ b/application/pages/ScreenshotsPage.ui
@@ -39,7 +39,7 @@
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
- <enum>QAbstractItemView::SelectItems</enum>
+ <enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
diff --git a/application/pages/VersionPage.cpp b/application/pages/VersionPage.cpp
index 8c14818f..00ae0a7e 100644
--- a/application/pages/VersionPage.cpp
+++ b/application/pages/VersionPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
#include "dialogs/CustomMessageBox.h"
#include "dialogs/VersionSelectDialog.h"
+#include "dialogs/NewComponentDialog.h"
#include "dialogs/ModEditDialogCommon.h"
#include "dialogs/ProgressDialog.h"
@@ -35,7 +36,7 @@
#include <QString>
#include <QUrl>
-#include "minecraft/MinecraftProfile.h"
+#include "minecraft/ComponentList.h"
#include "minecraft/auth/MojangAccountList.h"
#include "minecraft/Mod.h"
#include "icons/IconList.h"
@@ -50,13 +51,13 @@ class IconProxy : public QIdentityProxyModel
{
Q_OBJECT
public:
-
+
IconProxy(QWidget *parentWidget) : QIdentityProxyModel(parentWidget)
{
connect(parentWidget, &QObject::destroyed, this, &IconProxy::widgetGone);
m_parentWidget = parentWidget;
}
-
+
virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override
{
QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role);
@@ -98,20 +99,15 @@ bool VersionPage::shouldDisplay() const
return !m_inst->isRunning();
}
-void VersionPage::setParentContainer(BasePageContainer * container)
-{
- m_container = container;
-}
-
-VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent)
+VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
: QWidget(parent), ui(new Ui::VersionPage), m_inst(inst)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
+ m_profile = m_inst->getComponentList();
- reloadMinecraftProfile();
+ reloadComponentList();
- m_profile = m_inst->getMinecraftProfile();
if (m_profile)
{
auto proxy = new IconProxy(ui->packageView);
@@ -119,10 +115,9 @@ VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent)
ui->packageView->setModel(proxy);
ui->packageView->installEventFilter(this);
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
- connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged,
- this, &VersionPage::versionCurrent);
+ connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
auto smodel = ui->packageView->selectionModel();
- connect(smodel, SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(packageCurrent(QModelIndex, QModelIndex)));
+ connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
updateVersionControls();
// select first item.
preselect(0);
@@ -131,7 +126,7 @@ VersionPage::VersionPage(OneSixInstance *inst, QWidget *parent)
{
disableVersionControls();
}
- connect(m_inst, &OneSixInstance::versionReloaded, this,
+ connect(m_inst, &MinecraftInstance::versionReloaded, this,
&VersionPage::updateVersionControls);
}
@@ -148,7 +143,7 @@ void VersionPage::packageCurrent(const QModelIndex &current, const QModelIndex &
return;
}
int row = current.row();
- auto patch = m_profile->versionPatch(row);
+ auto patch = m_profile->getComponent(row);
auto severity = patch->getProblemSeverity();
switch(severity)
{
@@ -168,15 +163,15 @@ void VersionPage::packageCurrent(const QModelIndex &current, const QModelIndex &
QString problemOut;
for (auto &problem: problems)
{
- if(problem.getSeverity() == ProblemSeverity::Error)
+ if(problem.m_severity == ProblemSeverity::Error)
{
problemOut += tr("Error: ");
}
- else if(problem.getSeverity() == ProblemSeverity::Warning)
+ else if(problem.m_severity == ProblemSeverity::Warning)
{
problemOut += tr("Warning: ");
}
- problemOut += problem.getDescription();
+ problemOut += problem.m_description;
problemOut += "\n";
}
ui->frame->setModDescription(problemOut);
@@ -198,11 +193,11 @@ void VersionPage::disableVersionControls()
updateButtons();
}
-bool VersionPage::reloadMinecraftProfile()
+bool VersionPage::reloadComponentList()
{
try
{
- m_inst->reloadProfile();
+ m_profile->reload(Net::Mode::Online);
return true;
}
catch (Exception &e)
@@ -221,7 +216,7 @@ bool VersionPage::reloadMinecraftProfile()
void VersionPage::on_reloadBtn_clicked()
{
- reloadMinecraftProfile();
+ reloadComponentList();
m_container->refreshContainer();
}
@@ -236,7 +231,7 @@ void VersionPage::on_removeBtn_clicked()
}
}
updateButtons();
- reloadMinecraftProfile();
+ reloadComponentList();
m_container->refreshContainer();
}
@@ -250,47 +245,20 @@ void VersionPage::on_modBtn_clicked()
void VersionPage::on_jarmodBtn_clicked()
{
- bool nagShown = false;
- if (!m_profile->hasTrait("legacyLaunch") && !m_profile->hasTrait("alphaLaunch"))
- {
- // not legacy launch... nag
- auto seenNag = MMC->settings()->get("JarModNagSeen").toBool();
- if(!seenNag)
- {
- auto result = QMessageBox::question(this,
- tr("Are you sure?"),
- tr("This will add mods directly to the Minecraft jar.\n"
- "Unless you KNOW that this is what NEEDS to be done, you should just use the mods folder (Loader mods).\n"
- "\n"
- "Do you want to continue?"),
- tr("I understand, continue."), tr("Cancel"), QString(), 1, 1
- );
- if(result != 0)
- return;
- nagShown = true;
- }
- }
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget());
if(!list.empty())
{
m_profile->installJarMods(list);
- if(nagShown)
- {
- MMC->settings()->set("JarModNagSeen", QVariant(true));
- }
}
updateButtons();
}
-void VersionPage::on_resetOrderBtn_clicked()
+void VersionPage::on_jarBtn_clicked()
{
- try
- {
- m_profile->resetOrder();
- }
- catch (Exception &e)
+ auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget());
+ if(!jarPath.isEmpty())
{
- QMessageBox::critical(this, tr("Error"), e.cause());
+ m_profile->installCustomJar(jarPath);
}
updateButtons();
}
@@ -299,7 +267,7 @@ void VersionPage::on_moveUpBtn_clicked()
{
try
{
- m_profile->move(currentRow(), MinecraftProfile::MoveUp);
+ m_profile->move(currentRow(), ComponentList::MoveUp);
}
catch (Exception &e)
{
@@ -312,7 +280,7 @@ void VersionPage::on_moveDownBtn_clicked()
{
try
{
- m_profile->move(currentRow(), MinecraftProfile::MoveDown);
+ m_profile->move(currentRow(), ComponentList::MoveDown);
}
catch (Exception &e)
{
@@ -328,7 +296,7 @@ void VersionPage::on_changeVersionBtn_clicked()
{
return;
}
- auto patch = m_profile->versionPatch(versionRow);
+ auto patch = m_profile->getComponent(versionRow);
auto name = patch->getName();
auto list = patch->getVersionList();
if(!list)
@@ -336,10 +304,39 @@ void VersionPage::on_changeVersionBtn_clicked()
return;
}
auto uid = list->uid();
+ // FIXME: this is a horrible HACK. Get version filtering information from the actual metadata...
+ if(uid == "net.minecraftforge")
+ {
+ on_forgeBtn_clicked();
+ return;
+ }
+ else if (uid == "com.mumfrey.liteloader")
+ {
+ on_liteloaderBtn_clicked();
+ return;
+ }
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
+ auto currentVersion = patch->getVersion();
+ if(!currentVersion.isEmpty())
+ {
+ vselect.setCurrentVersion(currentVersion);
+ }
if (!vselect.exec() || !vselect.selectedVersion())
return;
+ qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor();
+ bool important = false;
+ if(uid == "net.minecraft")
+ {
+ important = true;
+ }
+ m_profile->setComponentVersion(uid, vselect.selectedVersion()->descriptor(), important);
+ m_profile->resolve(Net::Mode::Online);
+ m_container->refreshContainer();
+}
+
+void VersionPage::on_downloadBtn_clicked()
+{
if (!MMC->accounts()->anyAccountIsValid())
{
CustomMessageBox::selectable(
@@ -350,42 +347,17 @@ void VersionPage::on_changeVersionBtn_clicked()
return;
}
- qDebug() << "Change" << uid << "to" << vselect.selectedVersion()->descriptor();
- if(uid == "net.minecraft")
- {
- if (!m_profile->isVanilla())
- {
- 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_profile->revertToVanilla();
- reloadMinecraftProfile();
- }
- }
- m_inst->setComponentVersion(uid, vselect.selectedVersion()->descriptor());
- doUpdate();
- m_container->refreshContainer();
-}
-
-int VersionPage::doUpdate()
-{
- auto updateTask = m_inst->createUpdateTask();
+ auto updateTask = m_inst->createUpdateTask(Net::Mode::Online);
if (!updateTask)
{
- return 1;
+ return;
}
ProgressDialog tDialog(this);
connect(updateTask.get(), SIGNAL(failed(QString)), SLOT(onGameUpdateError(QString)));
- int ret = tDialog.execWithTask(updateTask.get());
+ // FIXME: unused return value
+ tDialog.execWithTask(updateTask.get());
updateButtons();
m_container->refreshContainer();
- return ret;
}
void VersionPage::on_forgeBtn_clicked()
@@ -396,20 +368,45 @@ void VersionPage::on_forgeBtn_clicked()
return;
}
VersionSelectDialog vselect(vlist.get(), tr("Select Forge version"), this);
- vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_inst->currentVersionId());
- vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_inst->currentVersionId());
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
+ vselect.setEmptyString(tr("No Forge versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyErrorString(tr("Couldn't load or download the Forge version lists!"));
+
+ auto currentVersion = m_profile->getComponentVersion("net.minecraftforge");
+ if(!currentVersion.isEmpty())
+ {
+ vselect.setCurrentVersion(currentVersion);
+ }
+
if (vselect.exec() && vselect.selectedVersion())
{
auto vsn = vselect.selectedVersion();
- m_inst->setComponentVersion("net.minecraftforge", vsn->descriptor());
- m_profile->reload();
+ m_profile->setComponentVersion("net.minecraftforge", vsn->descriptor());
+ m_profile->resolve(Net::Mode::Online);
// m_profile->installVersion();
preselect(m_profile->rowCount(QModelIndex())-1);
m_container->refreshContainer();
}
}
+void VersionPage::on_addEmptyBtn_clicked()
+{
+ NewComponentDialog compdialog(QString(), QString(), this);
+ QStringList blacklist;
+ for(int i = 0; i < m_profile->rowCount(); i++)
+ {
+ auto comp = m_profile->getComponent(i);
+ blacklist.push_back(comp->getID());
+ }
+ compdialog.setBlacklist(blacklist);
+ if (compdialog.exec())
+ {
+ qDebug() << "name:" << compdialog.name();
+ qDebug() << "uid:" << compdialog.uid();
+ m_profile->installEmpty(compdialog.uid(), compdialog.name());
+ }
+}
+
void VersionPage::on_liteloaderBtn_clicked()
{
auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader");
@@ -418,14 +415,21 @@ void VersionPage::on_liteloaderBtn_clicked()
return;
}
VersionSelectDialog vselect(vlist.get(), tr("Select LiteLoader version"), this);
- vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_inst->currentVersionId());
- vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_inst->currentVersionId());
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
+ vselect.setEmptyString(tr("No LiteLoader versions are currently available for Minecraft ") + m_profile->getComponentVersion("net.minecraft"));
vselect.setEmptyErrorString(tr("Couldn't load or download the LiteLoader version lists!"));
+
+ auto currentVersion = m_profile->getComponentVersion("com.mumfrey.liteloader");
+ if(!currentVersion.isEmpty())
+ {
+ vselect.setCurrentVersion(currentVersion);
+ }
+
if (vselect.exec() && vselect.selectedVersion())
{
auto vsn = vselect.selectedVersion();
- m_inst->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
- m_profile->reload();
+ m_profile->setComponentVersion("com.mumfrey.liteloader", vsn->descriptor());
+ m_profile->resolve(Net::Mode::Online);
// m_profile->installVersion(vselect.selectedVersion());
preselect(m_profile->rowCount(QModelIndex())-1);
m_container->refreshContainer();
@@ -461,7 +465,7 @@ void VersionPage::updateButtons(int row)
{
if(row == -1)
row = currentRow();
- auto patch = m_profile->versionPatch(row);
+ auto patch = m_profile->getComponent(row);
if (!patch)
{
ui->removeBtn->setDisabled(true);
@@ -490,14 +494,14 @@ void VersionPage::onGameUpdateError(QString error)
QMessageBox::Warning)->show();
}
-ProfilePatchPtr VersionPage::current()
+Component * VersionPage::current()
{
auto row = currentRow();
if(row < 0)
{
return nullptr;
}
- return m_profile->versionPatch(row);
+ return m_profile->getComponent(row);
}
int VersionPage::currentRow()
@@ -516,7 +520,7 @@ void VersionPage::on_customizeBtn_clicked()
{
return;
}
- auto patch = m_profile->versionPatch(version);
+ auto patch = m_profile->getComponent(version);
if(!patch->getVersionFile())
{
// TODO: wait for the update task to finish here...
@@ -563,3 +567,4 @@ void VersionPage::on_revertBtn_clicked()
}
#include "VersionPage.moc"
+
diff --git a/application/pages/VersionPage.h b/application/pages/VersionPage.h
index cc6c0b7b..49620c56 100644
--- a/application/pages/VersionPage.h
+++ b/application/pages/VersionPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,8 @@
#include <QWidget>
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/ComponentList.h"
#include "BasePage.h"
namespace Ui
@@ -30,7 +31,7 @@ class VersionPage : public QWidget, public BasePage
Q_OBJECT
public:
- explicit VersionPage(OneSixInstance *inst, QWidget *parent = 0);
+ explicit VersionPage(MinecraftInstance *inst, QWidget *parent = 0);
virtual ~VersionPage();
virtual QString displayName() const override
{
@@ -43,32 +44,32 @@ public:
}
virtual QString helpPage() const override
{
- return "Instance-Versions";
+ return "Instance-Version";
}
virtual bool shouldDisplay() const override;
- virtual void setParentContainer(BasePageContainer *) override;
-
private slots:
void on_forgeBtn_clicked();
+ void on_addEmptyBtn_clicked();
void on_liteloaderBtn_clicked();
void on_reloadBtn_clicked();
void on_removeBtn_clicked();
- void on_resetOrderBtn_clicked();
void on_moveUpBtn_clicked();
void on_moveDownBtn_clicked();
void on_jarmodBtn_clicked();
+ void on_jarBtn_clicked();
void on_revertBtn_clicked();
void on_editBtn_clicked();
void on_modBtn_clicked();
void on_customizeBtn_clicked();
+ void on_downloadBtn_clicked();
void updateVersionControls();
void disableVersionControls();
void on_changeVersionBtn_clicked();
private:
- ProfilePatchPtr current();
+ Component * current();
int currentRow();
void updateButtons(int row = -1);
void preselect(int row = 0);
@@ -76,14 +77,13 @@ private:
protected:
/// FIXME: this shouldn't be necessary!
- bool reloadMinecraftProfile();
+ bool reloadComponentList();
private:
Ui::VersionPage *ui;
- std::shared_ptr<MinecraftProfile> m_profile;
- OneSixInstance *m_inst;
+ std::shared_ptr<ComponentList> m_profile;
+ MinecraftInstance *m_inst;
int currentIdx = 0;
- BasePageContainer * m_container = nullptr;
public slots:
void versionCurrent(const QModelIndex &current, const QModelIndex &previous);
diff --git a/application/pages/VersionPage.ui b/application/pages/VersionPage.ui
index c16208db..d54dd840 100644
--- a/application/pages/VersionPage.ui
+++ b/application/pages/VersionPage.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>693</width>
- <height>750</height>
+ <width>870</width>
+ <height>1008</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -206,17 +206,21 @@
<string>Add a mod into the Minecraft jar file.</string>
</property>
<property name="text">
- <string>Add jar mod</string>
+ <string>Add to Minecraft.jar</string>
</property>
</widget>
</item>
<item>
- <widget class="QPushButton" name="resetOrderBtn">
- <property name="toolTip">
- <string>Reset apply order of packages.</string>
+ <widget class="QPushButton" name="jarBtn">
+ <property name="text">
+ <string>Replace Minecraft.jar</string>
</property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="addEmptyBtn">
<property name="text">
- <string>Reset order</string>
+ <string>Add Empty</string>
</property>
</widget>
</item>
@@ -231,6 +235,16 @@
</widget>
</item>
<item>
+ <widget class="QPushButton" name="downloadBtn">
+ <property name="toolTip">
+ <string>Download the files needed to launch the instance now.</string>
+ </property>
+ <property name="text">
+ <string>Download All</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -281,7 +295,6 @@
</customwidget>
</customwidgets>
<tabstops>
- <tabstop>tabWidget</tabstop>
<tabstop>packageView</tabstop>
<tabstop>changeVersionBtn</tabstop>
<tabstop>moveUpBtn</tabstop>
@@ -294,8 +307,11 @@
<tabstop>liteloaderBtn</tabstop>
<tabstop>modBtn</tabstop>
<tabstop>jarmodBtn</tabstop>
- <tabstop>resetOrderBtn</tabstop>
+ <tabstop>jarBtn</tabstop>
+ <tabstop>addEmptyBtn</tabstop>
<tabstop>reloadBtn</tabstop>
+ <tabstop>downloadBtn</tabstop>
+ <tabstop>tabWidget</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/application/pages/WorldListPage.cpp b/application/pages/WorldListPage.cpp
index b6195bb3..56a7e791 100644
--- a/application/pages/WorldListPage.cpp
+++ b/application/pages/WorldListPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/WorldListPage.h b/application/pages/WorldListPage.h
index 89d86158..d0aa6150 100644
--- a/application/pages/WorldListPage.h
+++ b/application/pages/WorldListPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,7 @@
#include <QWidget>
-#include "minecraft/onesix/OneSixInstance.h"
+#include "minecraft/MinecraftInstance.h"
#include "BasePage.h"
#include <MultiMC.h>
#include <LoggedProcess.h>
diff --git a/application/pages/global/AccountListPage.cpp b/application/pages/global/AccountListPage.cpp
index 8edcad98..63943174 100644
--- a/application/pages/global/AccountListPage.cpp
+++ b/application/pages/global/AccountListPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/AccountListPage.h b/application/pages/global/AccountListPage.h
index 52022b6c..fa5561fe 100644
--- a/application/pages/global/AccountListPage.h
+++ b/application/pages/global/AccountListPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/CustomCommandsPage.cpp b/application/pages/global/CustomCommandsPage.cpp
new file mode 100644
index 00000000..1352b6be
--- /dev/null
+++ b/application/pages/global/CustomCommandsPage.cpp
@@ -0,0 +1,50 @@
+#include "CustomCommandsPage.h"
+#include <QVBoxLayout>
+#include <QTabWidget>
+#include <QTabBar>
+
+CustomCommandsPage::CustomCommandsPage(QWidget* parent): QWidget(parent)
+{
+
+ auto verticalLayout = new QVBoxLayout(this);
+ verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
+ verticalLayout->setContentsMargins(0, 0, 0, 0);
+
+ auto tabWidget = new QTabWidget(this);
+ tabWidget->setObjectName(QStringLiteral("tabWidget"));
+ commands = new CustomCommands(this);
+ tabWidget->addTab(commands, "Foo");
+ tabWidget->tabBar()->hide();
+ verticalLayout->addWidget(tabWidget);
+ loadSettings();
+}
+
+CustomCommandsPage::~CustomCommandsPage()
+{
+}
+
+bool CustomCommandsPage::apply()
+{
+ applySettings();
+ return true;
+}
+
+void CustomCommandsPage::applySettings()
+{
+ auto s = MMC->settings();
+ s->set("PreLaunchCommand", commands->prelaunchCommand());
+ s->set("WrapperCommand", commands->wrapperCommand());
+ s->set("PostExitCommand", commands->postexitCommand());
+}
+
+void CustomCommandsPage::loadSettings()
+{
+ auto s = MMC->settings();
+ commands->initialize(
+ false,
+ true,
+ s->get("PreLaunchCommand").toString(),
+ s->get("WrapperCommand").toString(),
+ s->get("PostExitCommand").toString()
+ );
+}
diff --git a/application/pages/global/CustomCommandsPage.h b/application/pages/global/CustomCommandsPage.h
new file mode 100644
index 00000000..52256ed3
--- /dev/null
+++ b/application/pages/global/CustomCommandsPage.h
@@ -0,0 +1,55 @@
+/* Copyright 2018-2018 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 <memory>
+#include <QDialog>
+
+#include "pages/BasePage.h"
+#include <MultiMC.h>
+#include "widgets/CustomCommands.h"
+
+class CustomCommandsPage : public QWidget, public BasePage
+{
+ Q_OBJECT
+
+public:
+ explicit CustomCommandsPage(QWidget *parent = 0);
+ ~CustomCommandsPage();
+
+ QString displayName() const override
+ {
+ return tr("Custom Commands");
+ }
+ QIcon icon() const override
+ {
+ return MMC->getThemedIcon("custom-commands");
+ }
+ QString id() const override
+ {
+ return "custom-commands";
+ }
+ QString helpPage() const override
+ {
+ return "Custom-commands";
+ }
+ bool apply() override;
+
+private:
+ void applySettings();
+ void loadSettings();
+ CustomCommands * commands;
+};
diff --git a/application/pages/global/ExternalToolsPage.cpp b/application/pages/global/ExternalToolsPage.cpp
index b446fc06..ff63ecbb 100644
--- a/application/pages/global/ExternalToolsPage.cpp
+++ b/application/pages/global/ExternalToolsPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -206,12 +206,12 @@ void ExternalToolsPage::on_jsonEditorBrowseBtn_clicked()
? QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).first()
#endif
: ui->jsonEditorTextBox->text());
- QString cooked_file = FS::NormalizePath(raw_file);
- if (cooked_file.isEmpty())
+ if (raw_file.isEmpty())
{
return;
}
+ QString cooked_file = FS::NormalizePath(raw_file);
// it has to exist and be an executable
if (QFileInfo(cooked_file).exists() && QFileInfo(cooked_file).isExecutable())
diff --git a/application/pages/global/ExternalToolsPage.h b/application/pages/global/ExternalToolsPage.h
index b222db5e..de46d8a6 100644
--- a/application/pages/global/ExternalToolsPage.h
+++ b/application/pages/global/ExternalToolsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/ExternalToolsPage.ui b/application/pages/global/ExternalToolsPage.ui
index cdb33b35..5f19898b 100644
--- a/application/pages/global/ExternalToolsPage.ui
+++ b/application/pages/global/ExternalToolsPage.ui
@@ -63,7 +63,7 @@
<item>
<widget class="QLabel" name="jprofilerLink">
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.ej-technologies.com/products/jprofiler/overview.html&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.ej-technologies.com/products/jprofiler/overview.html&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.ej-technologies.com/products/jprofiler/overview.html&quot;&gt;http://www.ej-technologies.com/products/jprofiler/overview.html&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@@ -100,7 +100,7 @@
<item>
<widget class="QLabel" name="jvisualvmLink">
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://visualvm.java.net/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://visualvm.java.net/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://visualvm.github.io/&quot;&gt;https://visualvm.github.io/&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@@ -137,7 +137,7 @@
<item>
<widget class="QLabel" name="mceditLink">
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.mcedit.net/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.mcedit.net/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.mcedit.net/&quot;&gt;http://www.mcedit.net/&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
diff --git a/application/pages/global/JavaPage.cpp b/application/pages/global/JavaPage.cpp
index 543cc11f..57250c79 100644
--- a/application/pages/global/JavaPage.cpp
+++ b/application/pages/global/JavaPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@
#include <QDir>
#include "dialogs/VersionSelectDialog.h"
-#include <ColumnResizer.h>
#include "java/JavaUtils.h"
#include "java/JavaInstallList.h"
@@ -30,16 +29,15 @@
#include "settings/SettingsObject.h"
#include <FileSystem.h>
#include "MultiMC.h"
+#include <sys.h>
JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage)
{
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
- auto resizer = new ColumnResizer(this);
- resizer->addWidgetsFromLayout(ui->javaSettingsGroupBox->layout(), 0);
- resizer->addWidgetsFromLayout(ui->customCommandsGroupBox->layout(), 0);
-
+ auto sysMB = Sys::getSystemRam() / Sys::megabyte;
+ ui->maxMemSpinBox->setMaximum(sysMB);
loadSettings();
}
@@ -59,36 +57,46 @@ void JavaPage::applySettings()
auto s = MMC->settings();
// Memory
- s->set("MinMemAlloc", ui->minMemSpinBox->value());
- s->set("MaxMemAlloc", ui->maxMemSpinBox->value());
+ int min = ui->minMemSpinBox->value();
+ int max = ui->maxMemSpinBox->value();
+ if(min < max)
+ {
+ s->set("MinMemAlloc", min);
+ s->set("MaxMemAlloc", max);
+ }
+ else
+ {
+ s->set("MinMemAlloc", max);
+ s->set("MaxMemAlloc", min);
+ }
s->set("PermGen", ui->permGenSpinBox->value());
// Java Settings
s->set("JavaPath", ui->javaPathTextBox->text());
s->set("JvmArgs", ui->jvmArgsTextBox->text());
JavaCommon::checkJVMArgs(s->get("JvmArgs").toString(), this->parentWidget());
-
- // Custom Commands
- s->set("PreLaunchCommand", ui->preLaunchCmdTextBox->text());
- s->set("WrapperCommand", ui->wrapperCmdTextBox->text());
- s->set("PostExitCommand", ui->postExitCmdTextBox->text());
}
void JavaPage::loadSettings()
{
auto s = MMC->settings();
// Memory
- ui->minMemSpinBox->setValue(s->get("MinMemAlloc").toInt());
- ui->maxMemSpinBox->setValue(s->get("MaxMemAlloc").toInt());
+ int min = s->get("MinMemAlloc").toInt();
+ int max = s->get("MaxMemAlloc").toInt();
+ if(min < max)
+ {
+ ui->minMemSpinBox->setValue(min);
+ ui->maxMemSpinBox->setValue(max);
+ }
+ else
+ {
+ ui->minMemSpinBox->setValue(max);
+ ui->maxMemSpinBox->setValue(min);
+ }
ui->permGenSpinBox->setValue(s->get("PermGen").toInt());
// Java Settings
ui->javaPathTextBox->setText(s->get("JavaPath").toString());
ui->jvmArgsTextBox->setText(s->get("JvmArgs").toString());
-
- // Custom Commands
- ui->preLaunchCmdTextBox->setText(s->get("PreLaunchCommand").toString());
- ui->wrapperCmdTextBox->setText(s->get("WrapperCommand").toString());
- ui->postExitCmdTextBox->setText(s->get("PostExitCommand").toString());
}
void JavaPage::on_javaDetectBtn_clicked()
@@ -108,14 +116,14 @@ void JavaPage::on_javaDetectBtn_clicked()
void JavaPage::on_javaBrowseBtn_clicked()
{
QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"));
- QString cooked_path = FS::NormalizePath(raw_path);
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if(cooked_path.isEmpty())
+ if(raw_path.isEmpty())
{
return;
}
+ QString cooked_path = FS::NormalizePath(raw_path);
QFileInfo javaInfo(cooked_path);;
if(!javaInfo.exists() || !javaInfo.isExecutable())
{
diff --git a/application/pages/global/JavaPage.h b/application/pages/global/JavaPage.h
index 4feec427..e3a9f37f 100644
--- a/application/pages/global/JavaPage.h
+++ b/application/pages/global/JavaPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/JavaPage.ui b/application/pages/global/JavaPage.ui
index 9023b932..201b310c 100644
--- a/application/pages/global/JavaPage.ui
+++ b/application/pages/global/JavaPage.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>545</width>
- <height>760</height>
+ <height>580</height>
</rect>
</property>
<property name="sizePolicy">
@@ -17,7 +17,16 @@
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
- <property name="margin">
+ <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>
@@ -217,62 +226,17 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="customCommandsGroupBox">
- <property name="title">
- <string>Custom Commands</string>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
</property>
- <layout class="QGridLayout" name="gridLayout_4">
- <item row="3" column="0">
- <widget class="QLabel" name="labelPostExitCmd">
- <property name="text">
- <string>Post-exit command:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0">
- <widget class="QLabel" name="labelPreLaunchCmd">
- <property name="text">
- <string>Pre-launch command:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="preLaunchCmdTextBox"/>
- </item>
- <item row="3" column="1">
- <widget class="QLineEdit" name="postExitCmdTextBox"/>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="labelWrapperCmd">
- <property name="text">
- <string>Wrapper command:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="wrapperCmdTextBox"/>
- </item>
- </layout>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="labelCustomCmdsDescription">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
</property>
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pre-launch command runs before the instance launches and post-exit command runs after it exits. Both will be run in MultiMC's working folder with INST_ID, INST_DIR, and INST_NAME as environment variables.&lt;/p&gt;&lt;p&gt;Wrapper command allows running java using an extra wrapper program (like 'optirun' on Linux)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
+ </spacer>
</item>
</layout>
</widget>
@@ -289,9 +253,6 @@
<tabstop>jvmArgsTextBox</tabstop>
<tabstop>javaDetectBtn</tabstop>
<tabstop>javaTestBtn</tabstop>
- <tabstop>preLaunchCmdTextBox</tabstop>
- <tabstop>wrapperCmdTextBox</tabstop>
- <tabstop>postExitCmdTextBox</tabstop>
<tabstop>tabWidget</tabstop>
</tabstops>
<resources/>
diff --git a/application/pages/global/MinecraftPage.cpp b/application/pages/global/MinecraftPage.cpp
index b3a56327..aecd8d57 100644
--- a/application/pages/global/MinecraftPage.cpp
+++ b/application/pages/global/MinecraftPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/MinecraftPage.h b/application/pages/global/MinecraftPage.h
index 311b0967..d1abd6fe 100644
--- a/application/pages/global/MinecraftPage.h
+++ b/application/pages/global/MinecraftPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/MultiMCPage.cpp b/application/pages/global/MultiMCPage.cpp
index 4073f6b7..620fc3a3 100644
--- a/application/pages/global/MultiMCPage.cpp
+++ b/application/pages/global/MultiMCPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
#include <QDir>
#include <QTextCharFormat>
-#include <ColumnResizer.h>
#include "updater/UpdateChecker.h"
#include "settings/SettingsObject.h"
@@ -49,10 +48,6 @@ MultiMCPage::MultiMCPage(QWidget *parent) : QWidget(parent), ui(new Ui::MultiMCP
ui->sortingModeGroup->setId(ui->sortByNameBtn, Sort_Name);
ui->sortingModeGroup->setId(ui->sortLastLaunchedBtn, Sort_LastLaunch);
- auto resizer = new ColumnResizer(this);
- resizer->addWidgetsFromLayout(ui->groupBox->layout(), 1);
- resizer->addWidgetsFromLayout(ui->foldersBox->layout(), 1);
-
defaultFormat = new QTextCharFormat(ui->fontPreview->currentCharFormat());
m_languageModel = MMC->translations();
@@ -97,40 +92,14 @@ bool MultiMCPage::apply()
return true;
}
-void MultiMCPage::on_ftbLauncherBrowseBtn_clicked()
-{
- QString raw_dir = QFileDialog::getExistingDirectory(this, tr("FTB Launcher Folder"),
- ui->ftbLauncherBox->text());
- QString cooked_dir = FS::NormalizePath(raw_dir);
-
- // do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
- {
- ui->ftbLauncherBox->setText(cooked_dir);
- }
-}
-void MultiMCPage::on_ftbBrowseBtn_clicked()
-{
- QString raw_dir =
- QFileDialog::getExistingDirectory(this, tr("FTB Folder"), ui->ftbBox->text());
- QString cooked_dir = FS::NormalizePath(raw_dir);
-
- // do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
- {
- ui->ftbBox->setText(cooked_dir);
- }
-}
-
void MultiMCPage::on_instDirBrowseBtn_clicked()
{
- QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"),
- ui->instDirTextBox->text());
- QString cooked_dir = FS::NormalizePath(raw_dir);
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Instance Folder"), ui->instDirTextBox->text());
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ if (!raw_dir.isEmpty() && QDir(raw_dir).exists())
{
+ QString cooked_dir = FS::NormalizePath(raw_dir);
if (FS::checkProblemticPathJava(QDir(cooked_dir)))
{
QMessageBox warning;
@@ -157,40 +126,26 @@ void MultiMCPage::on_instDirBrowseBtn_clicked()
void MultiMCPage::on_iconsDirBrowseBtn_clicked()
{
- QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"),
- ui->iconsDirTextBox->text());
- QString cooked_dir = FS::NormalizePath(raw_dir);
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Icons Folder"), ui->iconsDirTextBox->text());
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ if (!raw_dir.isEmpty() && QDir(raw_dir).exists())
{
+ QString cooked_dir = FS::NormalizePath(raw_dir);
ui->iconsDirTextBox->setText(cooked_dir);
}
}
void MultiMCPage::on_modsDirBrowseBtn_clicked()
{
- QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"),
- ui->modsDirTextBox->text());
- QString cooked_dir = FS::NormalizePath(raw_dir);
+ QString raw_dir = QFileDialog::getExistingDirectory(this, tr("Mods Folder"), ui->modsDirTextBox->text());
// do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
+ if (!raw_dir.isEmpty() && QDir(raw_dir).exists())
{
+ QString cooked_dir = FS::NormalizePath(raw_dir);
ui->modsDirTextBox->setText(cooked_dir);
}
}
-void MultiMCPage::on_lwjglDirBrowseBtn_clicked()
-{
- QString raw_dir = QFileDialog::getExistingDirectory(this, tr("LWJGL Folder"),
- ui->lwjglDirTextBox->text());
- QString cooked_dir = FS::NormalizePath(raw_dir);
-
- // do not allow current dir - it's dirty. Do not allow dirs that don't exist
- if (!cooked_dir.isEmpty() && QDir(cooked_dir).exists())
- {
- ui->lwjglDirTextBox->setText(cooked_dir);
- }
-}
void MultiMCPage::languageIndexChanged(int index)
{
@@ -315,6 +270,9 @@ void MultiMCPage::applySettings()
s->set("IconTheme", "iOS");
break;
case 7:
+ s->set("IconTheme", "flat");
+ break;
+ case 8:
s->set("IconTheme", "custom");
break;
case 0:
@@ -346,16 +304,10 @@ void MultiMCPage::applySettings()
s->set("ConsoleMaxLines", ui->lineLimitSpinBox->value());
s->set("ConsoleOverflowStop", ui->checkStopLogging->checkState() != Qt::Unchecked);
- // FTB
- s->set("TrackFTBInstances", ui->trackFtbBox->isChecked());
- s->set("FTBLauncherLocal", FS::NormalizePath(ui->ftbLauncherBox->text()));
- s->set("FTBRoot", FS::NormalizePath(ui->ftbBox->text()));
-
// Folders
// TODO: Offer to move instances to new instance folder.
s->set("InstanceDir", ui->instDirTextBox->text());
s->set("CentralModsDir", ui->modsDirTextBox->text());
- s->set("LWJGLDir", ui->lwjglDirTextBox->text());
s->set("IconsDir", ui->iconsDirTextBox->text());
auto sortMode = (InstSortMode)ui->sortingModeGroup->checkedId();
@@ -414,10 +366,14 @@ void MultiMCPage::loadSettings()
{
ui->themeComboBox->setCurrentIndex(6);
}
- else if (theme == "custom")
+ else if (theme == "flat")
{
ui->themeComboBox->setCurrentIndex(7);
}
+ else if (theme == "custom")
+ {
+ ui->themeComboBox->setCurrentIndex(8);
+ }
else
{
ui->themeComboBox->setCurrentIndex(0);
@@ -457,15 +413,9 @@ void MultiMCPage::loadSettings()
ui->lineLimitSpinBox->setValue(s->get("ConsoleMaxLines").toInt());
ui->checkStopLogging->setChecked(s->get("ConsoleOverflowStop").toBool());
- // FTB
- ui->trackFtbBox->setChecked(s->get("TrackFTBInstances").toBool());
- ui->ftbLauncherBox->setText(s->get("FTBLauncherLocal").toString());
- ui->ftbBox->setText(s->get("FTBRoot").toString());
-
// Folders
ui->instDirTextBox->setText(s->get("InstanceDir").toString());
ui->modsDirTextBox->setText(s->get("CentralModsDir").toString());
- ui->lwjglDirTextBox->setText(s->get("LWJGLDir").toString());
ui->iconsDirTextBox->setText(s->get("IconsDir").toString());
QString sortMode = s->get("InstSortMode").toString();
diff --git a/application/pages/global/MultiMCPage.h b/application/pages/global/MultiMCPage.h
index 98eb353d..d5194c0e 100644
--- a/application/pages/global/MultiMCPage.h
+++ b/application/pages/global/MultiMCPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -64,12 +64,8 @@ private:
private
slots:
- void on_ftbLauncherBrowseBtn_clicked();
- void on_ftbBrowseBtn_clicked();
-
void on_instDirBrowseBtn_clicked();
void on_modsDirBrowseBtn_clicked();
- void on_lwjglDirBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked();
void languageIndexChanged(int index);
diff --git a/application/pages/global/MultiMCPage.ui b/application/pages/global/MultiMCPage.ui
index 0b966876..124401c3 100644
--- a/application/pages/global/MultiMCPage.ui
+++ b/application/pages/global/MultiMCPage.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>467</width>
- <height>614</height>
+ <height>629</height>
</rect>
</property>
<property name="sizePolicy">
@@ -89,75 +89,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox">
- <property name="title">
- <string notr="true">FTB</string>
- </property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="2" column="0">
- <widget class="QLabel" name="label">
- <property name="text">
- <string>&amp;Launcher:</string>
- </property>
- <property name="buddy">
- <cstring>ftbLauncherBox</cstring>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="ftbLauncherBox">
- <property name="enabled">
- <bool>false</bool>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLineEdit" name="ftbBox"/>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Files:</string>
- </property>
- <property name="buddy">
- <cstring>ftbBox</cstring>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QToolButton" name="ftbBrowseBtn">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="text">
- <string notr="true">...</string>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QToolButton" name="ftbLauncherBrowseBtn">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="focusPolicy">
- <enum>Qt::TabFocus</enum>
- </property>
- <property name="text">
- <string notr="true">...</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="3">
- <widget class="QCheckBox" name="trackFtbBox">
- <property name="text">
- <string>Track FTB instances</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="foldersBox">
<property name="title">
<string>Folders</string>
@@ -196,9 +127,6 @@
<item row="1" column="1">
<widget class="QLineEdit" name="modsDirTextBox"/>
</item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="lwjglDirTextBox"/>
- </item>
<item row="1" column="2">
<widget class="QToolButton" name="modsDirBrowseBtn">
<property name="text">
@@ -206,27 +134,10 @@
</property>
</widget>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="labelLWJGLDir">
- <property name="text">
- <string notr="true">LW&amp;JGL:</string>
- </property>
- <property name="buddy">
- <cstring>lwjglDirTextBox</cstring>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QToolButton" name="lwjglDirBrowseBtn">
- <property name="text">
- <string notr="true">...</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
+ <item row="2" column="1">
<widget class="QLineEdit" name="iconsDirTextBox"/>
</item>
- <item row="3" column="0">
+ <item row="2" column="0">
<widget class="QLabel" name="labelIconsDir">
<property name="text">
<string>&amp;Icons:</string>
@@ -236,7 +147,7 @@
</property>
</widget>
</item>
- <item row="3" column="2">
+ <item row="2" column="2">
<widget class="QToolButton" name="iconsDirBrowseBtn">
<property name="text">
<string notr="true">...</string>
@@ -393,6 +304,11 @@
</item>
<item>
<property name="text">
+ <string notr="true">Flat</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string>Custom</string>
</property>
</item>
@@ -645,17 +561,10 @@
<tabstop>tabWidget</tabstop>
<tabstop>autoUpdateCheckBox</tabstop>
<tabstop>updateChannelComboBox</tabstop>
- <tabstop>trackFtbBox</tabstop>
- <tabstop>ftbLauncherBox</tabstop>
- <tabstop>ftbLauncherBrowseBtn</tabstop>
- <tabstop>ftbBox</tabstop>
- <tabstop>ftbBrowseBtn</tabstop>
<tabstop>instDirTextBox</tabstop>
<tabstop>instDirBrowseBtn</tabstop>
<tabstop>modsDirTextBox</tabstop>
<tabstop>modsDirBrowseBtn</tabstop>
- <tabstop>lwjglDirTextBox</tabstop>
- <tabstop>lwjglDirBrowseBtn</tabstop>
<tabstop>iconsDirTextBox</tabstop>
<tabstop>iconsDirBrowseBtn</tabstop>
<tabstop>resetNotificationsBtn</tabstop>
diff --git a/application/pages/global/PackagesPage.cpp b/application/pages/global/PackagesPage.cpp
index e15ddbab..5fd4934c 100644
--- a/application/pages/global/PackagesPage.cpp
+++ b/application/pages/global/PackagesPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -38,10 +38,17 @@ static QString formatRequires(const VersionPtr &version)
auto iter = reqs.begin();
while (iter != reqs.end())
{
- auto &uid = iter.key();
- auto &version = iter.value();
+ auto &uid = iter->uid;
+ auto &version = iter->equalsVersion;
const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid;
- lines.append(QString("%1 (%2)").arg(readable, version));
+ if(!version.isEmpty())
+ {
+ lines.append(QString("%1 (%2)").arg(readable, version));
+ }
+ else
+ {
+ lines.append(QString("%1").arg(readable));
+ }
iter++;
}
return lines.join('\n');
@@ -95,7 +102,7 @@ QIcon PackagesPage::icon() const
void PackagesPage::on_refreshIndexBtn_clicked()
{
- ENV.metadataIndex()->load();
+ ENV.metadataIndex()->load(Net::Mode::Online);
}
void PackagesPage::on_refreshFileBtn_clicked()
{
@@ -104,7 +111,7 @@ void PackagesPage::on_refreshFileBtn_clicked()
{
return;
}
- list->load();
+ list->load(Net::Mode::Online);
}
void PackagesPage::on_refreshVersionBtn_clicked()
{
@@ -113,7 +120,7 @@ void PackagesPage::on_refreshVersionBtn_clicked()
{
return;
}
- version->load();
+ version->load(Net::Mode::Online);
}
void PackagesPage::on_fileSearchEdit_textChanged(const QString &search)
@@ -156,7 +163,7 @@ void PackagesPage::updateCurrentVersionList(const QModelIndex &index)
ui->fileName->setText(list->name());
m_versionProxy->setSourceModel(list.get());
ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable()));
- list->load();
+ list->load(Net::Mode::Offline);
}
else
{
@@ -213,5 +220,5 @@ void PackagesPage::updateVersion()
void PackagesPage::opened()
{
- ENV.metadataIndex()->load();
+ ENV.metadataIndex()->load(Net::Mode::Offline);
}
diff --git a/application/pages/global/PackagesPage.h b/application/pages/global/PackagesPage.h
index 80c2886d..2afbcf8e 100644
--- a/application/pages/global/PackagesPage.h
+++ b/application/pages/global/PackagesPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2017 MultiMC Contributors
+/* Copyright 2015-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/PasteEEPage.cpp b/application/pages/global/PasteEEPage.cpp
index 2ceb725f..3d4e3a88 100644
--- a/application/pages/global/PasteEEPage.cpp
+++ b/application/pages/global/PasteEEPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -43,11 +43,7 @@ void PasteEEPage::loadSettings()
{
auto s = MMC->settings();
QString keyToUse = s->get("PasteEEAPIKey").toString();
- if(keyToUse == "public")
- {
- ui->publicButton->setChecked(true);
- }
- else if(keyToUse == "multimc")
+ if(keyToUse == "multimc")
{
ui->multimcButton->setChecked(true);
}
@@ -65,8 +61,6 @@ void PasteEEPage::applySettings()
QString pasteKeyToUse;
if (ui->customButton->isChecked())
pasteKeyToUse = ui->customAPIkeyEdit->text();
- else if (ui->publicButton->isChecked())
- pasteKeyToUse = "public";
else
{
pasteKeyToUse = "multimc";
diff --git a/application/pages/global/PasteEEPage.h b/application/pages/global/PasteEEPage.h
index 2bd5f2f2..1b152577 100644
--- a/application/pages/global/PasteEEPage.h
+++ b/application/pages/global/PasteEEPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/PasteEEPage.ui b/application/pages/global/PasteEEPage.ui
index 689c421f..10883781 100644
--- a/application/pages/global/PasteEEPage.ui
+++ b/application/pages/global/PasteEEPage.ui
@@ -40,16 +40,6 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_10">
<item>
- <widget class="QRadioButton" name="publicButton">
- <property name="text">
- <string>No key - &amp;2MB upload limit</string>
- </property>
- <attribute name="buttonGroup">
- <string notr="true">pasteButtonGroup</string>
- </attribute>
- </widget>
- </item>
- <item>
<widget class="QRadioButton" name="multimcButton">
<property name="text">
<string>MultiMC key - 12MB &amp;upload limit</string>
@@ -89,7 +79,7 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://paste.ee&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#2980b9;&quot;&gt;paste.ee&lt;/span&gt;&lt;/a&gt; is used by MultiMC for log uploads. If you have a &lt;a href=&quot;https://paste.ee&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#2980b9;&quot;&gt;paste.ee&lt;/span&gt;&lt;/a&gt; account, you can add your API key here and have your uploaded logs paired with your account.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://paste.ee&quot;&gt;paste.ee&lt;/a&gt; is used by MultiMC for log uploads. If you have a &lt;a href=&quot;https://paste.ee&quot;&gt;paste.ee&lt;/a&gt; account, you can add your API key here and have your uploaded logs paired with your account.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@@ -97,6 +87,9 @@
<property name="wordWrap">
<bool>true</bool>
</property>
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
@@ -123,7 +116,6 @@
</widget>
<tabstops>
<tabstop>tabWidget</tabstop>
- <tabstop>publicButton</tabstop>
<tabstop>multimcButton</tabstop>
<tabstop>customButton</tabstop>
<tabstop>customAPIkeyEdit</tabstop>
diff --git a/application/pages/global/ProxyPage.cpp b/application/pages/global/ProxyPage.cpp
index a68963ca..1e75bab1 100644
--- a/application/pages/global/ProxyPage.cpp
+++ b/application/pages/global/ProxyPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/pages/global/ProxyPage.h b/application/pages/global/ProxyPage.h
index 1ed53808..565c2857 100644
--- a/application/pages/global/ProxyPage.h
+++ b/application/pages/global/ProxyPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/resources/MultiMC.icns b/application/resources/MultiMC.icns
index 05ce9207..c4eb59d5 100644
--- a/application/resources/MultiMC.icns
+++ b/application/resources/MultiMC.icns
Binary files differ
diff --git a/application/resources/MultiMC.ico b/application/resources/MultiMC.ico
index 734af0fb..1846964e 100644
--- a/application/resources/MultiMC.ico
+++ b/application/resources/MultiMC.ico
Binary files differ
diff --git a/application/resources/OSX/OSX.qrc b/application/resources/OSX/OSX.qrc
index 20c71eb8..a7d7be17 100644
--- a/application/resources/OSX/OSX.qrc
+++ b/application/resources/OSX/OSX.qrc
@@ -10,6 +10,7 @@
<file>scalable/copy.svg</file>
<file>scalable/coremods.svg</file>
<file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
@@ -31,5 +32,6 @@
<file>scalable/status-good.svg</file>
<file>scalable/status-yellow.svg</file>
<file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
</qresource>
</RCC>
diff --git a/application/resources/OSX/scalable/help.svg b/application/resources/OSX/scalable/help.svg
new file mode 100644
index 00000000..9d1b367c
--- /dev/null
+++ b/application/resources/OSX/scalable/help.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata24"><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><defs
+ id="defs22" /><rect
+ id="rect2"
+ height="32"
+ width="32"
+ fill="none"
+ clip-rule="evenodd"
+ fill-rule="evenodd" /><path
+ id="path4"
+ d="M30,28H2c-1.1,0-2-0.9-2-2V8c0-1.1,0.9-2,2-2h28c1.1,0,2,0.9,2,2v18C32,27.1,31.1,28,30,28z"
+ fill="#B6B5B6" /><path
+ id="path6"
+ d="M30,27H2c-1.1,0-2-0.9-2-2V8c0-1.1,0.9-2,2-2h28c1.1,0,2,0.9,2,2v17C32,26.1,31.1,27,30,27z"
+ fill="#FBFBFB" /><g
+ id="g854"><g
+ id="g861"
+ transform="matrix(0.51850737,0,0,0.51850737,29.410453,7.662064)"
+ style="fill:#585858;fill-opacity:1"><g
+ id="text832"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.34714317px;line-height:85.45981598px;font-family:'Nimbus Sans L';-inkscape-font-specification:'Nimbus Sans L';letter-spacing:0px;word-spacing:0px;fill:#585858;fill-opacity:1;stroke:none;stroke-width:3.4183929px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ aria-label="?"><path
+ id="path855"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Monofonto;-inkscape-font-specification:Monofonto;fill:#585858;fill-opacity:1;stroke-width:3.4183929px"
+ d="m -20.968435,13.920532 c 0,0.6381 -0.07293,1.239737 -0.218777,1.804911 -0.145852,0.565174 -0.401092,1.066538 -0.76572,1.504093 -0.401092,0.492249 -0.774836,0.902456 -1.121233,1.230621 -0.346397,0.328166 -0.656332,0.6381 -0.929803,0.929803 -0.25524,0.273471 -0.464902,0.556059 -0.628985,0.847762 -0.145851,0.291703 -0.218777,0.656331 -0.218777,1.093885 v 0.38286 h -3.144921 V 20.72997 c 0,-0.601637 0.136736,-1.221505 0.410207,-1.859605 0.273471,-0.656332 0.756604,-1.285316 1.449399,-1.886953 0.346397,-0.309935 0.638099,-0.583406 0.875108,-0.820415 0.237009,-0.25524 0.428439,-0.492248 0.57429,-0.711025 0.145851,-0.237009 0.246124,-0.483133 0.300819,-0.738373 0.05469,-0.273471 0.08204,-0.57429 0.08204,-0.902456 0,-0.528711 -0.12762,-0.984497 -0.38286,-1.367357 -0.237009,-0.401091 -0.656331,-0.601637 -1.257968,-0.601637 -1.057423,0 -1.640829,0.692794 -1.750218,2.078383 h -3.06288 c 0.01823,-0.747489 0.145853,-1.431167 0.38286,-2.051036 0.237009,-0.6381 0.565175,-1.185043 0.984498,-1.640829 0.419323,-0.474017 0.920687,-0.8386455 1.504092,-1.0938855 0.601637,-0.25524 1.2762,-0.38286 2.023689,-0.38286 0.838646,0 1.567903,0.1367357 2.187771,0.4102072 0.619869,0.25524 1.130349,0.6198684 1.53144,1.0938853 0.401092,0.455786 0.692795,1.002729 0.875109,1.640829 0.200546,0.619869 0.300819,1.294432 0.30082,2.023689 z" /></g><circle
+ id="circle6-6"
+ r="1.8"
+ cy="25.466606"
+ cx="-26.38899"
+ style="fill:#585858;fill-opacity:1" /></g><g
+ id="g17"><g
+ id="_x38__12_"><g
+ id="g12"><path
+ d="M16,9c-4.4,0-8,3.6-8,8c0,4.4,3.6,8,8,8s8-3.6,8-8C24,12.6,20.4,9,16,9z M16,24c-3.9,0-7-3.1-7-7 c0-3.9,3.1-7,7-7s7,3.1,7,7C23,20.9,19.9,24,16,24z"
+ id="path10"
+ fill="#585858" /></g></g></g></g></svg> \ No newline at end of file
diff --git a/application/resources/OSX/scalable/worlds.svg b/application/resources/OSX/scalable/worlds.svg
new file mode 100644
index 00000000..b1491272
--- /dev/null
+++ b/application/resources/OSX/scalable/worlds.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 24 24"
+ viewBox="0 0 24 24"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata22"><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><defs
+ id="defs20" /><path
+ id="path2"
+ d="M21,20H9c-1.1,0-2-0.9-2-2V6c0-1.1,0.9-2,2-2h12c1.1,0,2,0.9,2,2 v12C23,19.1,22.1,20,21,20z"
+ fill="#E6E6E6"
+ clip-rule="evenodd"
+ fill-rule="evenodd" /><rect
+ id="rect4"
+ height="24"
+ width="24"
+ fill="none" /><g
+ id="_x36__8_"><g
+ id="g8"><path
+ style="fill:#585858"
+ id="path6"
+ d="M 21,4 H 9 C 7.9,4 7,4.9 7,6 v 12 c 0,1.1 0.9,2 2,2 h 12 c 1.1,0 2,-0.9 2,-2 V 6 C 23,4.9 22.1,4 21,4 Z m 1,14 -1,1 H 9 L 8,18 V 6 C 8,5.4 8.4,5 9,5 h 12 c 0.6,0 1,0.4 1,1 z" /></g></g><circle
+ style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd"
+ id="circle11"
+ r="1"
+ cy="11.559504"
+ cx="-3.8492424" /><g
+ style="stroke:#585858;stroke-width:1.55746377;stroke-opacity:1"
+ transform="matrix(0.55621145,0.32075232,-0.32075232,0.55621145,11.232654,-2.0314203)"
+ id="g881"><ellipse
+ ry="8.9473686"
+ rx="8.9473696"
+ cy="16"
+ cx="16"
+ id="path845"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#585858;stroke-width:1.55746377;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><ellipse
+ ry="8.9473686"
+ rx="3.4915254"
+ cy="15.947369"
+ cx="16"
+ id="path845-3"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#585858;stroke-width:1.55746377;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#585858;stroke-width:1.55746377;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.1848294,12.430327 C 12.570183,14.790801 20.662272,13.548526 23.355,11.76263"
+ id="path870" /><path
+ id="path872"
+ d="M 8.2166335,20.492522 C 12.601987,18.132048 21.148735,17.776739 23.841463,19.562635"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#585858;stroke-width:1.55746377;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/application/resources/flat/flat.qrc b/application/resources/flat/flat.qrc
new file mode 100644
index 00000000..aee2e30c
--- /dev/null
+++ b/application/resources/flat/flat.qrc
@@ -0,0 +1,44 @@
+<!DOCTYPE RCC>
+<RCC version="1.0">
+ <qresource prefix="/icons/flat">
+ <file>index.theme</file>
+ <file>scalable/about.svg</file>
+ <file>scalable/accounts.svg</file>
+ <file>scalable/bug.svg</file>
+ <file>scalable/cat.svg</file>
+ <file>scalable/centralmods.svg</file>
+ <file>scalable/checkupdate.svg</file>
+ <file>scalable/copy.svg</file>
+ <file>scalable/coremods.svg</file>
+ <file>scalable/discord.svg</file>
+ <file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
+ <file>scalable/instance-settings.svg</file>
+ <file>scalable/jarmods.svg</file>
+ <file>scalable/java.svg</file>
+ <file>scalable/loadermods.svg</file>
+ <file>scalable/log.svg</file>
+ <file>scalable/minecraft.svg</file>
+ <file>scalable/multimc.svg</file>
+ <file>scalable/new.svg</file>
+ <file>scalable/news.svg</file>
+ <file>scalable/notes.svg</file>
+ <file>scalable/packages.svg</file>
+ <file>scalable/patreon.svg</file>
+ <file>scalable/proxy.svg</file>
+ <file>scalable/quickmods.svg</file>
+ <file>scalable/reddit-alien.svg</file>
+ <file>scalable/refresh.svg</file>
+ <file>scalable/resourcepacks.svg</file>
+ <file>scalable/screenshot-placeholder.svg</file>
+ <file>scalable/screenshots.svg</file>
+ <file>scalable/settings.svg</file>
+ <file>scalable/star.svg</file>
+ <file>scalable/status-bad.svg</file>
+ <file>scalable/status-good.svg</file>
+ <file>scalable/status-running.svg</file>
+ <file>scalable/status-yellow.svg</file>
+ <file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
+ </qresource>
+</RCC>
diff --git a/application/resources/flat/index.theme b/application/resources/flat/index.theme
new file mode 100644
index 00000000..34e27aa0
--- /dev/null
+++ b/application/resources/flat/index.theme
@@ -0,0 +1,11 @@
+[Icon Theme]
+Name=Flat
+Comment=Flat icons
+Inherits=multimc
+Directories=scalable
+
+[scalable]
+Size=48
+Type=Scalable
+MinSize=16
+MaxSize=256
diff --git a/application/resources/flat/scalable/about.svg b/application/resources/flat/scalable/about.svg
new file mode 100644
index 00000000..4f85045d
--- /dev/null
+++ b/application/resources/flat/scalable/about.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/accounts.svg b/application/resources/flat/scalable/accounts.svg
new file mode 100644
index 00000000..e6a1328d
--- /dev/null
+++ b/application/resources/flat/scalable/accounts.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M16,13C15.71,13 15.38,13 15.03,13.05C16.19,13.89 17,15 17,16.5V19H23V16.5C23,14.17 18.33,13 16,13M8,13C5.67,13 1,14.17 1,16.5V19H15V16.5C15,14.17 10.33,13 8,13M8,11A3,3 0 0,0 11,8A3,3 0 0,0 8,5A3,3 0 0,0 5,8A3,3 0 0,0 8,11M16,11A3,3 0 0,0 19,8A3,3 0 0,0 16,5A3,3 0 0,0 13,8A3,3 0 0,0 16,11Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/bug.svg b/application/resources/flat/scalable/bug.svg
new file mode 100644
index 00000000..ea370faa
--- /dev/null
+++ b/application/resources/flat/scalable/bug.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M20 8h-2.81c-.45-.78-1.07-1.45-1.82-1.96L17 4.41 15.59 3l-2.17 2.17C12.96 5.06 12.49 5 12 5c-.49 0-.96.06-1.41.17L8.41 3 7 4.41l1.62 1.63C7.88 6.55 7.26 7.22 6.81 8H4v2h2.09c-.05.33-.09.66-.09 1v1H4v2h2v1c0 .34.04.67.09 1H4v2h2.81c1.04 1.79 2.97 3 5.19 3s4.15-1.21 5.19-3H20v-2h-2.09c.05-.33.09-.66.09-1v-1h2v-2h-2v-1c0-.34-.04-.67-.09-1H20V8zm-6 8h-4v-2h4v2zm0-4h-4v-2h4v2z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/cat.svg b/application/resources/flat/scalable/cat.svg
new file mode 100644
index 00000000..e90763b5
--- /dev/null
+++ b/application/resources/flat/scalable/cat.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,8L10.67,8.09C9.81,7.07 7.4,4.5 5,4.5C5,4.5 3.03,7.46 4.96,11.41C4.41,12.24 4.07,12.67 4,13.66L2.07,13.95L2.28,14.93L4.04,14.67L4.18,15.38L2.61,16.32L3.08,17.21L4.53,16.32C5.68,18.76 8.59,20 12,20C15.41,20 18.32,18.76 19.47,16.32L20.92,17.21L21.39,16.32L19.82,15.38L19.96,14.67L21.72,14.93L21.93,13.95L20,13.66C19.93,12.67 19.59,12.24 19.04,11.41C20.97,7.46 19,4.5 19,4.5C16.6,4.5 14.19,7.07 13.33,8.09L12,8M9,11A1,1 0 0,1 10,12A1,1 0 0,1 9,13A1,1 0 0,1 8,12A1,1 0 0,1 9,11M15,11A1,1 0 0,1 16,12A1,1 0 0,1 15,13A1,1 0 0,1 14,12A1,1 0 0,1 15,11M11,14H13L12.3,15.39C12.5,16.03 13.06,16.5 13.75,16.5A1.5,1.5 0 0,0 15.25,15H15.75A2,2 0 0,1 13.75,17C13,17 12.35,16.59 12,16V16H12C11.65,16.59 11,17 10.25,17A2,2 0 0,1 8.25,15H8.75A1.5,1.5 0 0,0 10.25,16.5C10.94,16.5 11.5,16.03 11.7,15.39L11,14Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/centralmods.svg b/application/resources/flat/scalable/centralmods.svg
new file mode 100644
index 00000000..c694662a
--- /dev/null
+++ b/application/resources/flat/scalable/centralmods.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm-2.06 11L15 15.28 12.06 17l.78-3.33-2.59-2.24 3.41-.29L15 8l1.34 3.14 3.41.29-2.59 2.24.78 3.33z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/checkupdate.svg b/application/resources/flat/scalable/checkupdate.svg
new file mode 100644
index 00000000..e6525a08
--- /dev/null
+++ b/application/resources/flat/scalable/checkupdate.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M21 10.12h-6.78l2.74-2.82c-2.73-2.7-7.15-2.8-9.88-.1-2.73 2.71-2.73 7.08 0 9.79 2.73 2.71 7.15 2.71 9.88 0C18.32 15.65 19 14.08 19 12.1h2c0 1.98-.88 4.55-2.64 6.29-3.51 3.48-9.21 3.48-12.72 0-3.5-3.47-3.53-9.11-.02-12.58 3.51-3.47 9.14-3.47 12.65 0L21 3v7.12zM12.5 8v4.25l3.5 2.08-.72 1.21L11 13V8h1.5z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/copy.svg b/application/resources/flat/scalable/copy.svg
new file mode 100644
index 00000000..36986e0d
--- /dev/null
+++ b/application/resources/flat/scalable/copy.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6zm16-4H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-1 9h-4v4h-2v-4H9V9h4V5h2v4h4v2z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/coremods.svg b/application/resources/flat/scalable/coremods.svg
new file mode 100644
index 00000000..21a3450e
--- /dev/null
+++ b/application/resources/flat/scalable/coremods.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M22,12A10,10,0,1,1,12,2,10,10,0,0,1,22,12ZM12,4a8,8,0,1,0,8,8A8,8,0,0,0,12,4Zm4.23,14-1.12-4.82,3.73-3.23-4.92-.42L12,5,10.08,9.54,5.16,10l3.73,3.23L7.77,18,12,15.45,16.23,18"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/discord.svg b/application/resources/flat/scalable/discord.svg
new file mode 100644
index 00000000..ad63180f
--- /dev/null
+++ b/application/resources/flat/scalable/discord.svg
@@ -0,0 +1,4 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M10.14,11.63a1.15,1.15,0,1,0,1,1.14A1.1,1.1,0,0,0,10.14,11.63Zm3.75,0a1.15,1.15,0,1,0,1,1.14A1.1,1.1,0,0,0,13.89,11.63Z"/>
+ <path d="M18.89,3H5.11A2.11,2.11,0,0,0,3,5.12V19a2.11,2.11,0,0,0,2.11,2.12H16.77l-.55-1.9,1.32,1.22,1.24,1.15,2.21,2V5.12A2.11,2.11,0,0,0,18.89,3Zm-4,13.43s-.37-.44-.68-.83a3.25,3.25,0,0,0,1.86-1.22,5.89,5.89,0,0,1-1.18.61,6.77,6.77,0,0,1-1.49.44,7.21,7.21,0,0,1-2.66,0A8.63,8.63,0,0,1,9.25,15a6,6,0,0,1-.75-.35l-.09-.05,0,0-.29-.17a3.2,3.2,0,0,0,1.8,1.21l-.69.85a3.73,3.73,0,0,1-3.14-1.56,13.77,13.77,0,0,1,1.48-6,5.09,5.09,0,0,1,2.89-1.08l.1.12A6.94,6.94,0,0,0,7.82,9.26s.23-.12.61-.3a7.72,7.72,0,0,1,2.33-.65l.17,0a8.7,8.7,0,0,1,2.08,0,8.38,8.38,0,0,1,3.1,1A6.85,6.85,0,0,0,13.55,8l.14-.16a5.09,5.09,0,0,1,2.89,1.08,13.77,13.77,0,0,1,1.48,6A3.76,3.76,0,0,1,14.92,16.43Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/externaltools.svg b/application/resources/flat/scalable/externaltools.svg
new file mode 100644
index 00000000..55820dfc
--- /dev/null
+++ b/application/resources/flat/scalable/externaltools.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M22.7,19L13.6,9.9C14.5,7.6 14,4.9 12.1,3C10.1,1 7.1,0.6 4.7,1.7L9,6L6,9L1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1C4.8,14 7.5,14.5 9.8,13.6L18.9,22.7C19.3,23.1 19.9,23.1 20.3,22.7L22.6,20.4C23.1,20 23.1,19.3 22.7,19Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/help.svg b/application/resources/flat/scalable/help.svg
new file mode 100644
index 00000000..26d5d7f4
--- /dev/null
+++ b/application/resources/flat/scalable/help.svg
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ id="svg4"
+ version="1.1"
+ width="24"
+ viewBox="0 0 24 24"
+ height="24"
+ fill="#757575">
+ <path
+ d="m 15.07,11.25 -0.9,0.92 C 13.45,12.89 13,13.5 13,15 h -2 v -0.5 c 0,-1.11 0.45,-2.11 1.17,-2.83 l 1.24,-1.26 C 13.78,10.049999 14,9.549999 14,9 14,7.89 13.100001,7 12,7 A 2,2 0 0 0 10,9 H 7.9999995 A 4,4 0 0 1 12,5 4,4 0 0 1 16,9 c 0,0.879999 -0.36,1.67 -0.93,2.25 M 13,19 h -2 v -2 h 2 M 12,2 A 10,10 0 0 0 1.9999995,12 10,10 0 0 0 12,22 10,10 0 0 0 22,12 C 22,6.47 17.5,2 12,2 Z"
+ id="path817" />
+</svg>
diff --git a/application/resources/flat/scalable/instance-settings.svg b/application/resources/flat/scalable/instance-settings.svg
new file mode 100644
index 00000000..dd9d86ed
--- /dev/null
+++ b/application/resources/flat/scalable/instance-settings.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/jarmods.svg b/application/resources/flat/scalable/jarmods.svg
new file mode 100644
index 00000000..db90fa34
--- /dev/null
+++ b/application/resources/flat/scalable/jarmods.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M2,21H20V19H2M20,8H18V5h2m0-2H4V13a4,4,0,0,0,4,4h6a4,4,0,0,0,4-4V10h2a2,2,0,0,0,2-2V5A2,2,0,0,0,20,3ZM11,4.43l1.62,3.29,3.63.53-2.63,2.56.62,3.62L11,12.72,7.75,14.43l.62-3.62L5.74,8.25l3.63-.53Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/java.svg b/application/resources/flat/scalable/java.svg
new file mode 100644
index 00000000..dc19ee23
--- /dev/null
+++ b/application/resources/flat/scalable/java.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M2,21H20V19H2M20,8H18V5H20M20,3H4V13A4,4 0 0,0 8,17H14A4,4 0 0,0 18,13V10H20A2,2 0 0,0 22,8V5C22,3.89 21.1,3 20,3Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/loadermods.svg b/application/resources/flat/scalable/loadermods.svg
new file mode 100644
index 00000000..8a2fd12c
--- /dev/null
+++ b/application/resources/flat/scalable/loadermods.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M16.23,18l-1.12-4.82,3.73-3.23-4.92-.42L12,5,10.08,9.54,5.16,10l3.73,3.23L7.77,18,12,15.45,16.23,18M20,4H4V20H20Zm0,18H4a2,2,0,0,1-2-2V4A2,2,0,0,1,4,2H20a2,2,0,0,1,2,2V20A2,2,0,0,1,20,22Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/log.svg b/application/resources/flat/scalable/log.svg
new file mode 100644
index 00000000..e8caa08a
--- /dev/null
+++ b/application/resources/flat/scalable/log.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/minecraft.svg b/application/resources/flat/scalable/minecraft.svg
new file mode 100644
index 00000000..c17c44cd
--- /dev/null
+++ b/application/resources/flat/scalable/minecraft.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L6.04,7.5L12,10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V9.21L13,12.58V19.29L19,15.91Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/multimc.svg b/application/resources/flat/scalable/multimc.svg
new file mode 100644
index 00000000..1c1f2359
--- /dev/null
+++ b/application/resources/flat/scalable/multimc.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M0,2A2,2,0,0,1,2,0H22a2,2,0,0,1,2,2V8L22,8V6H20V8H18v2H16V8H14V6H12V8H10V6H8v4H6V8H4V6H2V8H0ZM0,22a2,2,0,0,0,2,2H22a2,2,0,0,0,2-2V10H22V22H2V10H0Zm18.71-3.29a3.83,3.83,0,0,0-5.41-5.41L12,14.59l-1.29-1.29a3.83,3.83,0,1,0,0,5.41L12,17.41l1.29,1.29a3.83,3.83,0,0,0,5.41,0Zm-4-4a1.83,1.83,0,1,1,0,2.59L13.41,16Zm-5.41,0L10.59,16,9.29,17.29a1.83,1.83,0,1,1,0-2.59Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/new.svg b/application/resources/flat/scalable/new.svg
new file mode 100644
index 00000000..01f19d7c
--- /dev/null
+++ b/application/resources/flat/scalable/new.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/news.svg b/application/resources/flat/scalable/news.svg
new file mode 100644
index 00000000..8868414e
--- /dev/null
+++ b/application/resources/flat/scalable/news.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M20,11H4V8H20M20,15H13V13H20M20,19H13V17H20M11,19H4V13H11M20.33,4.67L18.67,3L17,4.67L15.33,3L13.67,4.67L12,3L10.33,4.67L8.67,3L7,4.67L5.33,3L3.67,4.67L2,3V19A2,2 0 0,0 4,21H20A2,2 0 0,0 22,19V3L20.33,4.67Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/notes.svg b/application/resources/flat/scalable/notes.svg
new file mode 100644
index 00000000..ebe0cb5a
--- /dev/null
+++ b/application/resources/flat/scalable/notes.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/packages.svg b/application/resources/flat/scalable/packages.svg
new file mode 100644
index 00000000..fe576a43
--- /dev/null
+++ b/application/resources/flat/scalable/packages.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M5.12,5l.81-1h12l.94,1m1.67.23L19.15,3.55A1.45,1.45,0,0,0,18,3H6a1.49,1.49,0,0,0-1.16.55L3.46,5.23A1.92,1.92,0,0,0,3,6.5V19a2,2,0,0,0,2,2H19a2,2,0,0,0,2-2V6.5A1.92,1.92,0,0,0,20.54,5.23Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/patreon.svg b/application/resources/flat/scalable/patreon.svg
new file mode 100644
index 00000000..ad561f57
--- /dev/null
+++ b/application/resources/flat/scalable/patreon.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,3h0a9,9,0,0,0-9,9v9H5.09V12a6.91,6.91,0,1,1,7.23,6.9,5.9,5.9,0,0,1-2.59-.47v-3A4.13,4.13,0,1,0,7.85,12v9H12A9,9,0,1,0,12,3Zm0,15.91h0Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/proxy.svg b/application/resources/flat/scalable/proxy.svg
new file mode 100644
index 00000000..4956fec8
--- /dev/null
+++ b/application/resources/flat/scalable/proxy.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M22 4v-.5C22 2.12 20.88 1 19.5 1S17 2.12 17 3.5V4c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1h5c.55 0 1-.45 1-1V5c0-.55-.45-1-1-1zm-.8 0h-3.4v-.5c0-.94.76-1.7 1.7-1.7s1.7.76 1.7 1.7V4zm-2.28 8c.04.33.08.66.08 1 0 2.08-.8 3.97-2.1 5.39-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H7v-2h2c.55 0 1-.45 1-1V8h2c1.1 0 2-.9 2-2V3.46c-.95-.3-1.95-.46-3-.46C5.48 3 1 7.48 1 13s4.48 10 10 10 10-4.48 10-10c0-.34-.02-.67-.05-1h-2.03zM10 20.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L8 16v1c0 1.1.9 2 2 2v1.93z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/quickmods.svg b/application/resources/flat/scalable/quickmods.svg
new file mode 100644
index 00000000..952d1e0e
--- /dev/null
+++ b/application/resources/flat/scalable/quickmods.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M8.75,5.5v7.15H10.7V18.5l4.55-7.8h-2.6l2.6-5.2ZM20,4H4V20H20Zm0,18H4a2,2,0,0,1-2-2V4A2,2,0,0,1,4,2H20a2,2,0,0,1,2,2V20A2,2,0,0,1,20,22Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/reddit-alien.svg b/application/resources/flat/scalable/reddit-alien.svg
new file mode 100644
index 00000000..9bcfbedc
--- /dev/null
+++ b/application/resources/flat/scalable/reddit-alien.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M20,11.86a1.76,1.76,0,0,0-1.75-1.75,1.73,1.73,0,0,0-1.22.51,9,9,0,0,0-4.67-1.38l1-3.16L16,6.71s0,0,0,0a1.46,1.46,0,1,0,.1-.53l-2.89-.68a.25.25,0,0,0-.29.17L11.83,9.23a9.16,9.16,0,0,0-4.88,1.36,1.75,1.75,0,1,0-2.07,2.79,3,3,0,0,0-.06.58C4.82,16.58,8,18.7,12,18.7s7.14-2.13,7.14-4.74a2.94,2.94,0,0,0-.05-.55A1.74,1.74,0,0,0,20,11.86ZM8.51,13.08a1.06,1.06,0,1,1,1.06,1.06A1.06,1.06,0,0,1,8.51,13.08Zm6.06,3.14A3.48,3.48,0,0,1,12,17h0a3.48,3.48,0,0,1-2.56-.79.25.25,0,0,1,.35-.35,3,3,0,0,0,2.2.65h0a3,3,0,0,0,2.2-.65.25.25,0,1,1,.35.35Zm-.13-2.08a1.06,1.06,0,1,1,1.06-1.06A1.06,1.06,0,0,1,14.44,14.14Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/refresh.svg b/application/resources/flat/scalable/refresh.svg
new file mode 100644
index 00000000..94be1e27
--- /dev/null
+++ b/application/resources/flat/scalable/refresh.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/resourcepacks.svg b/application/resources/flat/scalable/resourcepacks.svg
new file mode 100644
index 00000000..b6054baf
--- /dev/null
+++ b/application/resources/flat/scalable/resourcepacks.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M19.51 3.08L3.08 19.51c.09.34.27.65.51.9.25.24.56.42.9.51L20.93 4.49c-.19-.69-.73-1.23-1.42-1.41zM11.88 3L3 11.88v2.83L14.71 3h-2.83zM5 3c-1.1 0-2 .9-2 2v2l4-4H5zm14 18c.55 0 1.05-.22 1.41-.59.37-.36.59-.86.59-1.41v-2l-4 4h2zm-9.71 0h2.83L21 12.12V9.29L9.29 21z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/screenshot-placeholder.svg b/application/resources/flat/scalable/screenshot-placeholder.svg
new file mode 100644
index 00000000..99e0c17a
--- /dev/null
+++ b/application/resources/flat/scalable/screenshot-placeholder.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M21 15h2v2h-2v-2zm0-4h2v2h-2v-2zm2 8h-2v2c1 0 2-1 2-2zM13 3h2v2h-2V3zm8 4h2v2h-2V7zm0-4v2h2c0-1-1-2-2-2zM1 7h2v2H1V7zm16-4h2v2h-2V3zm0 16h2v2h-2v-2zM3 3C2 3 1 4 1 5h2V3zm6 0h2v2H9V3zM5 3h2v2H5V3zm-4 8v8c0 1.1.9 2 2 2h12V11H1zm2 8l2.5-3.21 1.79 2.15 2.5-3.22L13 19H3z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/screenshots.svg b/application/resources/flat/scalable/screenshots.svg
new file mode 100644
index 00000000..208bb104
--- /dev/null
+++ b/application/resources/flat/scalable/screenshots.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M21 3H3C2 3 1 4 1 5v14c0 1.1.9 2 2 2h18c1 0 2-1 2-2V5c0-1-1-2-2-2zM5 17l3.5-4.5 2.5 3.01L14.5 11l4.5 6H5z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/settings.svg b/application/resources/flat/scalable/settings.svg
new file mode 100644
index 00000000..dd9d86ed
--- /dev/null
+++ b/application/resources/flat/scalable/settings.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/star.svg b/application/resources/flat/scalable/star.svg
new file mode 100644
index 00000000..878bdca8
--- /dev/null
+++ b/application/resources/flat/scalable/star.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,17.27L18.18,21L16.54,13.97L22,9.24L14.81,8.62L12,2L9.19,8.62L2,9.24L7.45,13.97L5.82,21L12,17.27Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/status-bad.svg b/application/resources/flat/scalable/status-bad.svg
new file mode 100644
index 00000000..3f8e0116
--- /dev/null
+++ b/application/resources/flat/scalable/status-bad.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12,2A10,10,0,1,0,22,12,10,10,0,0,0,12,2Zm4.24,7.17L13.41,12l2.83,2.83-1.41,1.41L12,13.41,9.17,16.24,7.76,14.83,10.59,12,7.76,9.17,9.17,7.76,12,10.59l2.83-2.83Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/status-good.svg b/application/resources/flat/scalable/status-good.svg
new file mode 100644
index 00000000..3503d6ba
--- /dev/null
+++ b/application/resources/flat/scalable/status-good.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/status-running.svg b/application/resources/flat/scalable/status-running.svg
new file mode 100644
index 00000000..7c750319
--- /dev/null
+++ b/application/resources/flat/scalable/status-running.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/status-yellow.svg b/application/resources/flat/scalable/status-yellow.svg
new file mode 100644
index 00000000..ac2d2349
--- /dev/null
+++ b/application/resources/flat/scalable/status-yellow.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/viewfolder.svg b/application/resources/flat/scalable/viewfolder.svg
new file mode 100644
index 00000000..2f5e29c9
--- /dev/null
+++ b/application/resources/flat/scalable/viewfolder.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/flat/scalable/worlds.svg b/application/resources/flat/scalable/worlds.svg
new file mode 100644
index 00000000..95a59bd4
--- /dev/null
+++ b/application/resources/flat/scalable/worlds.svg
@@ -0,0 +1,3 @@
+<svg fill="#757575" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
+ <path d="M18.2,13a3.18,3.18,0,0,1-.84,2.16.8.8,0,0,0-.76-.56h-.4V13.4a.4.4,0,0,0-.4-.4H13.4v-.8h.8a.4.4,0,0,0,.4-.4V11h.8a.8.8,0,0,0,.8-.8V10A3.19,3.19,0,0,1,18.2,13Zm-4.4,1.6v-.4l-1.92-1.92a3.18,3.18,0,0,0,2.72,3.89V15.4A.8.8,0,0,1,13.8,14.6ZM22,8V18a2,2,0,0,1-2,2H4a2,2,0,0,1-2-2V6A2,2,0,0,1,4,4h6l2,2h8A2,2,0,0,1,22,8Zm-3,5a4,4,0,1,0-4,4A4,4,0,0,0,19,13Z"/>
+</svg> \ No newline at end of file
diff --git a/application/resources/iOS/iOS.qrc b/application/resources/iOS/iOS.qrc
index eb625d0b..0cb642f4 100644
--- a/application/resources/iOS/iOS.qrc
+++ b/application/resources/iOS/iOS.qrc
@@ -10,6 +10,7 @@
<file>scalable/copy.svg</file>
<file>scalable/coremods.svg</file>
<file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
@@ -31,5 +32,6 @@
<file>scalable/status-good.svg</file>
<file>scalable/status-yellow.svg</file>
<file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
</qresource>
</RCC>
diff --git a/application/resources/iOS/scalable/help.svg b/application/resources/iOS/scalable/help.svg
new file mode 100644
index 00000000..9c2d2e93
--- /dev/null
+++ b/application/resources/iOS/scalable/help.svg
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata18"><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><defs
+ id="defs16" /><g
+ id="g11"><g
+ id="_x38__8_"><g
+ id="g6"><path
+ id="path4"
+ d="M16,0C7.2,0,0,7.2,0,16c0,8.8,7.2,16,16,16c8.8,0,16-7.2,16-16C32,7.2,24.8,0,16,0z M16,30 C8.3,30,2,23.7,2,16C2,8.3,8.3,2,16,2c7.7,0,14,6.3,14,14C30,23.7,23.7,30,16,30z"
+ fill="#3366CC" /></g></g></g><g
+ style="fill:#3366cc;fill-opacity:1"
+ transform="matrix(1.0878826,0,0,1.0878826,44.136533,-3.5919647)"
+ id="g861"><g
+ aria-label="?"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.34714317px;line-height:85.45981598px;font-family:'Nimbus Sans L';-inkscape-font-specification:'Nimbus Sans L';letter-spacing:0px;word-spacing:0px;fill:#3366cc;fill-opacity:1;stroke:none;stroke-width:3.4183929px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text832"><path
+ d="m -20.968435,13.920532 c 0,0.6381 -0.07293,1.239737 -0.218777,1.804911 -0.145852,0.565174 -0.401092,1.066538 -0.76572,1.504093 -0.401092,0.492249 -0.774836,0.902456 -1.121233,1.230621 -0.346397,0.328166 -0.656332,0.6381 -0.929803,0.929803 -0.25524,0.273471 -0.464902,0.556059 -0.628985,0.847762 -0.145851,0.291703 -0.218777,0.656331 -0.218777,1.093885 v 0.38286 h -3.144921 V 20.72997 c 0,-0.601637 0.136736,-1.221505 0.410207,-1.859605 0.273471,-0.656332 0.756604,-1.285316 1.449399,-1.886953 0.346397,-0.309935 0.638099,-0.583406 0.875108,-0.820415 0.237009,-0.25524 0.428439,-0.492248 0.57429,-0.711025 0.145851,-0.237009 0.246124,-0.483133 0.300819,-0.738373 0.05469,-0.273471 0.08204,-0.57429 0.08204,-0.902456 0,-0.528711 -0.12762,-0.984497 -0.38286,-1.367357 -0.237009,-0.401091 -0.656331,-0.601637 -1.257968,-0.601637 -1.057423,0 -1.640829,0.692794 -1.750218,2.078383 h -3.06288 c 0.01823,-0.747489 0.145853,-1.431167 0.38286,-2.051036 0.237009,-0.6381 0.565175,-1.185043 0.984498,-1.640829 0.419323,-0.474017 0.920687,-0.8386455 1.504092,-1.0938855 0.601637,-0.25524 1.2762,-0.38286 2.023689,-0.38286 0.838646,0 1.567903,0.1367357 2.187771,0.4102072 0.619869,0.25524 1.130349,0.6198684 1.53144,1.0938853 0.401092,0.455786 0.692795,1.002729 0.875109,1.640829 0.200546,0.619869 0.300819,1.294432 0.30082,2.023689 z"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Monofonto;-inkscape-font-specification:Monofonto;fill:#3366cc;fill-opacity:1;stroke-width:3.4183929px"
+ id="path855" /></g><circle
+ style="fill:#3366cc;fill-opacity:1"
+ cx="-26.38899"
+ cy="25.466606"
+ r="1.8"
+ id="circle6-6" /></g></svg> \ No newline at end of file
diff --git a/application/resources/iOS/scalable/worlds.svg b/application/resources/iOS/scalable/worlds.svg
new file mode 100644
index 00000000..1596fd76
--- /dev/null
+++ b/application/resources/iOS/scalable/worlds.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata12"><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><defs
+ id="defs10" /><g
+ id="_x36__21_"><g
+ id="g4"><path
+ style="fill:#3366cc;fill-opacity:1"
+ id="path2"
+ d="M 4,0 C 1.8,0 0,1.8 0,4 v 24 c 0,2.2 1.8,4 4,4 h 24 c 2.2,0 4,-1.8 4,-4 V 4 C 32,1.8 30.2,0 28,0 Z m 0,2 h 24 c 0.1375,0 0.272461,0.014258 0.402344,0.041016 6.24e-4,1.264e-4 0.0013,-1.27e-4 0.002,0 0.782578,0.1593282 1.395359,0.7721094 1.554687,1.5546875 C 29.98599,3.7261594 30,3.8618476 30,4 v 24 c 0,0.1375 -0.01426,0.272461 -0.04102,0.402344 -1.26e-4,6.24e-4 1.27e-4,0.0013 0,0.002 -0.159328,0.782578 -0.772109,1.395359 -1.554687,1.554687 C 28.273841,29.98599 28.138152,30 28,30 H 4 c -0.1378377,0 -0.2724931,-0.01471 -0.4023438,-0.04102 -6.162e-4,-1.27e-4 -0.00134,1.28e-4 -0.00195,0 -0.7825781,-0.159328 -1.3953593,-0.772109 -1.5546875,-1.554687 -1.27e-4,-6.24e-4 1.264e-4,-0.0013 0,-0.002 C 2.0142578,28.272461 2,28.1375 2,28 V 4 c 0,-0.1378377 0.014708,-0.2724931 0.041016,-0.4023438 1.27e-4,-6.162e-4 -1.275e-4,-0.00134 0,-0.00195 C 2.2003438,2.813125 2.813125,2.2003438 3.5957031,2.0410156 3.7261328,2.0144609 3.8615,2 4,2 Z" /></g></g><g
+ style="stroke:#3366cc;stroke-width:1.63695813;stroke-opacity:1"
+ transform="matrix(1.0584012,0.61035173,-0.61035173,1.0584012,8.831209,-10.700046)"
+ id="g881"><ellipse
+ ry="8.9473686"
+ rx="8.9473696"
+ cy="16"
+ cx="16"
+ id="path845"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#3366cc;stroke-width:1.63695813;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><ellipse
+ ry="8.9473686"
+ rx="3.4915254"
+ cy="15.947369"
+ cx="16"
+ id="path845-3"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#3366cc;stroke-width:1.63695813;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#3366cc;stroke-width:1.63695813;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.1848294,12.430327 C 12.570183,14.790801 20.662272,13.548526 23.355,11.76263"
+ id="path870" /><path
+ id="path872"
+ d="M 8.2166335,20.492522 C 12.601987,18.132048 21.148735,17.776739 23.841463,19.562635"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#3366cc;stroke-width:1.63695813;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/application/resources/multimc/128x128/instances/infinity.png b/application/resources/multimc/128x128/instances/infinity.png
index 226847fb..cc7ca7bc 100644
--- a/application/resources/multimc/128x128/instances/infinity.png
+++ b/application/resources/multimc/128x128/instances/infinity.png
Binary files differ
diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc
index b4bc5c16..780e458d 100644
--- a/application/resources/multimc/multimc.qrc
+++ b/application/resources/multimc/multimc.qrc
@@ -2,7 +2,10 @@
<RCC version="1.0">
<qresource prefix="/icons/multimc">
<file>index.theme</file>
- <!-- Logo. Our own. -->
+ <!-- Logo. Our own. For use in branding. -->
+ <file>scalable/logo.svg</file>
+
+ <!-- Logo. Our own. For use within the application (Settings pages and similar). -->
<file>scalable/multimc.svg</file>
<!-- REDDIT logo icon, needs reddit license! -->
@@ -59,6 +62,9 @@
<file>64x64/screenshots.png</file>
<file>scalable/screenshots.svg</file>
+ <!-- Custom commands. -->
+ <file>scalable/custom-commands.svg</file>
+
<!-- Patron logo. (C) 2014 Patreon, Inc., http://www.patreon.com/toolbox?ftyp=media -->
<file>16x16/patreon.png</file>
<file>22x22/patreon.png</file>
diff --git a/application/resources/multimc/scalable/custom-commands.svg b/application/resources/multimc/scalable/custom-commands.svg
new file mode 100644
index 00000000..b7f1a149
--- /dev/null
+++ b/application/resources/multimc/scalable/custom-commands.svg
@@ -0,0 +1,338 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ id="svg2"
+ height="64"
+ width="64"
+ version="1.1"
+ sodipodi:docname="custom-commands.svg"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06">
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="3840"
+ inkscape:window-height="2123"
+ id="namedview52"
+ showgrid="false"
+ inkscape:zoom="20.85965"
+ inkscape:cx="28.409224"
+ inkscape:cy="33.675543"
+ inkscape:window-x="1200"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg2"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="true"
+ inkscape:snap-bbox-edge-midpoints="true"
+ inkscape:snap-bbox-midpoints="true"
+ inkscape:bbox-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid858" />
+ </sodipodi:namedview>
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient3931">
+ <stop
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:0"
+ id="stop3933" />
+ <stop
+ offset="0.69999987"
+ style="stop-color:#ffffff;stop-opacity:0.10396039"
+ id="stop3939" />
+ <stop
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0.14356436"
+ id="stop3935" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3900">
+ <stop
+ offset="0"
+ style="stop-color:#f6f6f6;stop-opacity:1"
+ id="stop3902" />
+ <stop
+ offset="0.75714284"
+ style="stop-color:#494949;stop-opacity:1"
+ id="stop3904" />
+ <stop
+ offset="1"
+ style="stop-color:#2c2c2c;stop-opacity:1"
+ id="stop3906" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3808">
+ <stop
+ offset="0"
+ style="stop-color:#333333;stop-opacity:1"
+ id="stop3810" />
+ <stop
+ offset="1"
+ style="stop-color:#c8c8c8;stop-opacity:1"
+ id="stop3812" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3030">
+ <stop
+ offset="0"
+ style="stop-color:#000000;stop-opacity:1"
+ id="stop3032" />
+ <stop
+ offset="0.75714284"
+ style="stop-color:#333333;stop-opacity:1"
+ id="stop3038" />
+ <stop
+ offset="1"
+ style="stop-color:#4d4d4d;stop-opacity:1"
+ id="stop3034" />
+ </linearGradient>
+ <radialGradient
+ gradientTransform="matrix(1.3519242,1.8838281,-1.5359217,1.1022493,15.935733,948.08165)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3030"
+ id="radialGradient3036"
+ fy="14.242621"
+ fx="29.381905"
+ r="16.375"
+ cy="14.242621"
+ cx="29.381905" />
+ <linearGradient
+ gradientTransform="matrix(1.5,0,0,1,-16,4)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3808"
+ id="linearGradient3824"
+ y2="1033.8622"
+ x2="34"
+ y1="1033.8622"
+ x1="30" />
+ <linearGradient
+ gradientTransform="matrix(0.82142857,0,0,1.500001,6.7142857,-522.68214)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3808"
+ id="linearGradient3834"
+ y2="1039.3622"
+ x2="32"
+ y1="1043.3622"
+ x1="32" />
+ <radialGradient
+ gradientTransform="matrix(6.479993,1.9525666,-10.415476,2.1794781,10657.845,-1282.8793)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3900"
+ id="radialGradient3844"
+ fy="1039.813"
+ fx="30.724609"
+ r="3"
+ cy="1039.813"
+ cx="30.724609" />
+ <radialGradient
+ gradientTransform="matrix(2.5191507,2.9862959,-4.0491019,3.333339,4186.8847,-2518.44)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3900"
+ id="radialGradient3852"
+ fy="1039.813"
+ fx="30.724609"
+ r="3"
+ cy="1039.813"
+ cx="30.724609" />
+ <radialGradient
+ gradientTransform="matrix(-2.5191507,2.9863064,4.0491022,3.3333507,-4122.8849,-2518.4524)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3900"
+ id="radialGradient3857"
+ fy="1039.813"
+ fx="30.724609"
+ r="3"
+ cy="1039.813"
+ cx="30.724609" />
+ <radialGradient
+ gradientTransform="matrix(-0.69414478,2.3073251,-1.6952184,-0.67174747,96.941544,960.82172)"
+ gradientUnits="userSpaceOnUse"
+ xlink:href="#linearGradient3900"
+ id="radialGradient3937"
+ fy="21.976955"
+ fx="31.946348"
+ r="19.25"
+ cy="21.976955"
+ cx="31.946348" />
+ </defs>
+ <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 />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ transform="translate(0,-988.36218)">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#radialGradient3036);fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.25954175;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect2997"
+ y="992.99194"
+ x="5.6297708"
+ ry="0.70710492"
+ rx="0.70710504"
+ height="53.740437"
+ width="53.740459" />
+ </g>
+ <g
+ id="g1021"
+ transform="translate(1.7703716e-7,-0.42472956)">
+ <g
+ style="fill:#008000"
+ transform="matrix(0.08572572,0,0,0.08572572,9.999999,10.424713)"
+ id="g899">
+ <g
+ style="fill:#008000"
+ id="g867">
+ <g
+ style="fill:#008000"
+ id="g865">
+ <path
+ style="fill:#008000"
+ d="m 226.434,249.503 c 0,-6.995 -2.705,-13.403 -7.846,-18.556 L 61.8,74.165 c -5.128,-5.141 -11.554,-7.852 -18.568,-7.852 -7.026,0 -13.452,2.717 -18.556,7.846 l -16.83,16.83 c -5.129,5.135 -7.84,11.549 -7.84,18.538 0,7.026 2.717,13.452 7.846,18.556 L 129.267,249.503 7.84,370.936 C 2.711,376.071 0,382.491 0,389.486 c 0,7.02 2.717,13.439 7.846,18.544 l 16.775,16.774 c 5.116,5.165 11.555,7.895 18.611,7.895 7.044,0 13.47,-2.723 18.556,-7.846 l 156.813,-156.8 c 5.128,-5.14 7.833,-11.549 7.833,-18.55 z"
+ id="path860"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:#008000"
+ d="m 498.866,384.951 h -323.02 c -7.203,0 -13.611,2.583 -18.581,7.528 -4.896,4.92 -7.484,11.327 -7.484,18.531 v 21.536 c 0,7.252 2.607,13.672 7.491,18.543 4.915,4.927 11.34,7.528 18.574,7.528 h 323.02 c 7.239,0 13.659,-2.607 18.531,-7.497 4.927,-4.908 7.533,-11.334 7.533,-18.58 v -21.537 c 0,-7.209 -2.589,-13.616 -7.54,-18.592 -4.913,-4.877 -11.321,-7.46 -18.524,-7.46 z"
+ id="path862"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+ <g
+ style="fill:#008000"
+ id="g869" />
+ <g
+ style="fill:#008000"
+ id="g871" />
+ <g
+ style="fill:#008000"
+ id="g873" />
+ <g
+ style="fill:#008000"
+ id="g875" />
+ <g
+ style="fill:#008000"
+ id="g877" />
+ <g
+ style="fill:#008000"
+ id="g879" />
+ <g
+ style="fill:#008000"
+ id="g881" />
+ <g
+ style="fill:#008000"
+ id="g883" />
+ <g
+ style="fill:#008000"
+ id="g885" />
+ <g
+ style="fill:#008000"
+ id="g887" />
+ <g
+ style="fill:#008000"
+ id="g889" />
+ <g
+ style="fill:#008000"
+ id="g891" />
+ <g
+ style="fill:#008000"
+ id="g893" />
+ <g
+ style="fill:#008000"
+ id="g895" />
+ <g
+ style="fill:#008000"
+ id="g897" />
+ </g>
+ <g
+ id="g914"
+ transform="matrix(0.08572572,0,0,0.08572572,9.9999994,8.4247072)"
+ style="fill:#00ff00">
+ <g
+ id="g856"
+ style="fill:#00ff00">
+ <g
+ id="g854"
+ style="fill:#00ff00">
+ <path
+ inkscape:connector-curvature="0"
+ id="path850"
+ d="m 226.434,249.503 c 0,-6.995 -2.705,-13.403 -7.846,-18.556 L 61.8,74.165 c -5.128,-5.141 -11.554,-7.852 -18.568,-7.852 -7.026,0 -13.452,2.717 -18.556,7.846 l -16.83,16.83 c -5.129,5.135 -7.84,11.549 -7.84,18.538 0,7.026 2.717,13.452 7.846,18.556 L 129.267,249.503 7.84,370.936 C 2.711,376.071 0,382.491 0,389.486 c 0,7.02 2.717,13.439 7.846,18.544 l 16.775,16.774 c 5.116,5.165 11.555,7.895 18.611,7.895 7.044,0 13.47,-2.723 18.556,-7.846 l 156.813,-156.8 c 5.128,-5.14 7.833,-11.549 7.833,-18.55 z"
+ style="fill:#00ff00" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path852"
+ d="m 498.866,384.951 h -323.02 c -7.203,0 -13.611,2.583 -18.581,7.528 -4.896,4.92 -7.484,11.327 -7.484,18.531 v 21.536 c 0,7.252 2.607,13.672 7.491,18.543 4.915,4.927 11.34,7.528 18.574,7.528 h 323.02 c 7.239,0 13.659,-2.607 18.531,-7.497 4.927,-4.908 7.533,-11.334 7.533,-18.58 v -21.537 c 0,-7.209 -2.589,-13.616 -7.54,-18.592 -4.913,-4.877 -11.321,-7.46 -18.524,-7.46 z"
+ style="fill:#00ff00" />
+ </g>
+ </g>
+ <g
+ id="g858"
+ style="fill:#00ff00" />
+ <g
+ id="g860"
+ style="fill:#00ff00" />
+ <g
+ id="g862"
+ style="fill:#00ff00" />
+ <g
+ id="g864"
+ style="fill:#00ff00" />
+ <g
+ id="g866"
+ style="fill:#00ff00" />
+ <g
+ id="g868"
+ style="fill:#00ff00" />
+ <g
+ id="g870"
+ style="fill:#00ff00" />
+ <g
+ id="g872"
+ style="fill:#00ff00" />
+ <g
+ id="g874"
+ style="fill:#00ff00" />
+ <g
+ id="g876"
+ style="fill:#00ff00" />
+ <g
+ id="g878"
+ style="fill:#00ff00" />
+ <g
+ id="g880"
+ style="fill:#00ff00" />
+ <g
+ id="g882"
+ style="fill:#00ff00" />
+ <g
+ id="g884"
+ style="fill:#00ff00" />
+ <g
+ id="g886"
+ style="fill:#00ff00" />
+ </g>
+ </g>
+</svg>
diff --git a/application/resources/multimc/scalable/logo.svg b/application/resources/multimc/scalable/logo.svg
new file mode 100644
index 00000000..8bb0e289
--- /dev/null
+++ b/application/resources/multimc/scalable/logo.svg
@@ -0,0 +1,353 @@
+<?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="68.26667"
+ height="68.26667"
+ id="svg4427"
+ version="1.1"
+ inkscape:version="0.92.1 r"
+ sodipodi:docname="multimc-smooth-biginfinity.svg"
+ inkscape:export-filename="/home/peterix/playground/MultiMC-icons/multimc-smooth-biginfinity.png"
+ inkscape:export-xdpi="180"
+ inkscape:export-ydpi="180">
+ <defs
+ id="defs4429">
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4809">
+ <stop
+ style="stop-color:#98c867;stop-opacity:1"
+ offset="0"
+ id="stop4805" />
+ <stop
+ style="stop-color:#5c9a33;stop-opacity:1"
+ offset="1"
+ id="stop4807" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5668"
+ inkscape:collect="always">
+ <stop
+ id="stop5670"
+ offset="0"
+ style="stop-color:#75b54b;stop-opacity:1;" />
+ <stop
+ id="stop5672"
+ offset="1"
+ style="stop-color:#75b54b;stop-opacity:0.6" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5084"
+ inkscape:collect="always">
+ <stop
+ id="stop5086"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:0.8" />
+ <stop
+ id="stop5088"
+ offset="1"
+ style="stop-color:#000000;stop-opacity:0.35" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5668"
+ id="linearGradient5072"
+ x1="6.7342591"
+ y1="28.510933"
+ x2="50.506943"
+ y2="61.773685"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-0.01532073,-0.00938002)" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5084"
+ id="linearGradient5082"
+ x1="14.312115"
+ y1="9.7948904"
+ x2="44.097023"
+ y2="82.973114"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5668"
+ id="linearGradient3281"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-0.01532073,-0.00938002)"
+ x1="6.7342591"
+ y1="28.510933"
+ x2="50.506943"
+ y2="61.773685" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5668"
+ id="linearGradient3283"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-0.01532073,-0.00938002)"
+ x1="6.7342591"
+ y1="28.510933"
+ x2="50.506943"
+ y2="61.773685" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5668"
+ id="linearGradient3286"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.01941371,-0.00842234)"
+ x1="6.7342591"
+ y1="28.510933"
+ x2="50.506943"
+ y2="61.773685" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5084"
+ id="linearGradient3288"
+ gradientUnits="userSpaceOnUse"
+ x1="14.312115"
+ y1="9.7948904"
+ x2="44.097023"
+ y2="82.973114" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5084"
+ id="linearGradient3290"
+ gradientUnits="userSpaceOnUse"
+ x1="14.312115"
+ y1="9.7948904"
+ x2="44.097023"
+ y2="82.973114" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5084"
+ id="linearGradient3293"
+ gradientUnits="userSpaceOnUse"
+ x1="14.312115"
+ y1="9.7948904"
+ x2="44.097023"
+ y2="82.973114"
+ gradientTransform="scale(1.2671525,0.89790119)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5580">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.0627451"
+ offset="0"
+ id="stop5576" />
+ <stop
+ style="stop-color:#322217;stop-opacity:0.58823532"
+ offset="1"
+ id="stop5578" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3999"
+ inkscape:collect="always">
+ <stop
+ id="stop3995"
+ offset="0"
+ style="stop-color:#a3704b;stop-opacity:1" />
+ <stop
+ id="stop3997"
+ offset="1"
+ style="stop-color:#6a4a33;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2727"
+ inkscape:collect="always">
+ <stop
+ id="stop2723"
+ offset="0"
+ style="stop-color:#966c4a;stop-opacity:1" />
+ <stop
+ id="stop2725"
+ offset="1"
+ style="stop-color:#593d29;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2727"
+ id="linearGradient2050"
+ gradientUnits="userSpaceOnUse"
+ x1="36.546478"
+ y1="33.80484"
+ x2="86.415741"
+ y2="97.065842" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3999"
+ id="radialGradient2052"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-9.105292e-4,-0.00104444)"
+ cx="34.133331"
+ cy="34.133335"
+ fx="34.133331"
+ fy="34.133335"
+ r="29.866665" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5580"
+ id="linearGradient2140"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-0.0010513,-9.083059e-4)"
+ x1="29.866674"
+ y1="29.867579"
+ x2="38.400005"
+ y2="38.400913" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5084"
+ id="linearGradient4790"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.82864077,-1.0012743)"
+ x1="14.312115"
+ y1="9.7948904"
+ x2="44.097023"
+ y2="82.973114" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4809"
+ id="radialGradient4803"
+ cx="-42.66758"
+ cy="-34.134373"
+ fx="-42.66758"
+ fy="-34.134373"
+ r="34.132812"
+ gradientTransform="matrix(1.7500268,0.1250019,-0.01781176,0.24936465,95.393964,18.110151)"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.6203867"
+ inkscape:cx="52.171166"
+ inkscape:cy="11.292073"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:document-units="px"
+ inkscape:grid-bbox="true"
+ inkscape:window-width="1368"
+ inkscape:window-height="905"
+ inkscape:window-x="2452"
+ inkscape:window-y="723"
+ inkscape:window-maximized="0"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="false"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-paths="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-center="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid4446"
+ empspacing="16"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true"
+ spacingx="4.2666667"
+ spacingy="4.2666667"
+ originx="0"
+ originy="0" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata4432">
+ <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">
+ <g
+ id="g2048"
+ transform="translate(9.113e-4,0.00104183)">
+ <rect
+ rx="8.5333338"
+ ry="8.5333338"
+ style="fill:url(#linearGradient2050);fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ id="rect2026"
+ width="68.26667"
+ height="68.26667"
+ x="-1.3322676e-15"
+ y="3.0270508e-06" />
+ <rect
+ rx="4.2666626"
+ y="4.2656283"
+ x="4.2657552"
+ height="59.733334"
+ width="59.73333"
+ id="rect2028"
+ style="fill:url(#radialGradient2052);fill-opacity:1;stroke:none;stroke-width:14.93333435"
+ ry="4.2666669" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4811"
+ d="m 4.2669272,4.2645856 -9.11e-4,8.5333334 h 4.267577 v 4.267579 h 8.5332038 v 4.265625 h 4.265625 V 8.5322946 H 25.6 v 8.5332034 h 4.265625 v -4.267579 h 4.267578 v 8.533204 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 v -4.267579 h 4.267579 v 4.267579 h 8.533203 l -1.3e-4,-12.8009124 z"
+ style="opacity:0.6;fill:#593d29;fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ sodipodi:nodetypes="ccccccccccccccccccccccccccc" />
+ <path
+ style="fill:url(#radialGradient4803);fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ d="m 8.5329442,-0.0018207 c -4.7274675,0 -8.5332035,3.805736 -8.5332035,8.533203 v 4.2675787 h 4.265625 V 8.5313823 c 0,-0.521698 0.105433,-1.01339 0.27539,-1.47461 -0.169616,0.460814 -0.27539,0.953462 -0.27539,1.47461 h 4.2675785 v 4.2675787 h 4.2656248 4.267578 v 4.265625 h 4.265625 V 12.798961 8.5313823 4.2657573 h 4.267578 v 4.265625 4.2675787 h 4.265625 V 8.5313823 h 4.267578 v 4.2675787 4.265625 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 V 8.5313823 h 4.267579 v 4.2675787 h 4.265625 4.267578 V 8.5313823 h 4.265625 c 0,-4.727467 -3.805737,-8.533203 -8.533203,-8.533203 z m -3.019531,5.513671 c -0.318089,0.317888 -0.570428,0.695824 -0.7753915,1.101563 0.2048795,-0.405231 0.4576385,-0.784012 0.7753915,-1.101563 z"
+ id="path4794"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:url(#linearGradient2140);fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ d="m 8.5322887,-9.083059e-4 c -4.72747,0 -8.5332,3.8057359059 -8.5332,8.5332029059 V 59.731515 c 0,4.727467 3.80573,8.535156 8.5332,8.535156 H 59.731502 c 4.72747,0 8.5332,-3.807689 8.5332,-8.535156 V 8.5322946 c 0,-4.727467 -3.80573,-8.5332029059 -8.5332,-8.5332029059 z m 0,4.2675779059 H 59.731502 c 2.36373,0 4.26758,1.901892 4.26758,4.265625 V 59.731515 c 0,2.363733 -1.90385,4.267578 -4.26758,4.267578 H 8.5322887 c -2.36373,0 -4.26758,-1.903845 -4.26758,-4.267578 V 8.5322946 c 0,-2.363733 1.90385,-4.265625 4.26758,-4.265625 z"
+ id="path2046"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g1092">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4786"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4790);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
+ d="m 38.886673,44.940882 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.5594458,-3.37376 C 6.7526311,43.114834 5.275567,39.986037 5.2755773,36.088937 5.275567,32.347763 6.7526311,29.207831 9.7067742,26.669132 12.346618,24.419991 15.897857,23.295407 20.360501,23.295373 c 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 19.747676,44.473233 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 50.483209,27.77145 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 -1.759968,-1.558805 -4.132699,-2.338222 -7.118198,-2.338251" />
+ <path
+ d="m 39.715314,45.942156 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.559446,-3.37376 -2.9541431,-2.360505 -4.4312072,-5.489302 -4.4311969,-9.386402 -1.03e-5,-3.741174 1.4770538,-6.881106 4.4311969,-9.419805 2.639844,-2.249141 6.191083,-3.373725 10.653727,-3.373759 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 20.576317,45.474507 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 51.31185,28.772724 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 C 56.67008,29.55217 54.297349,28.772753 51.31185,28.772724"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3293);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
+ id="path3279"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 37.904564,42.951873 c -0.974278,-0.801672 -2.231352,-2.137814 -3.771231,-4.008428 -2.105642,2.672298 -4.085537,4.598568 -5.939688,5.778817 -2.325625,1.425227 -5.295466,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124577 -10.5594464,-3.37376 -2.9541428,-2.360505 -4.4312068,-5.489302 -4.4311963,-9.386401 -1.05e-5,-3.741175 1.4770535,-6.881107 4.4311963,-9.419805 2.6398444,-2.249142 6.1910824,-3.373727 10.6537284,-3.37376 2.294137,3.3e-5 4.289745,0.334068 5.986829,1.002107 1.979863,0.734909 3.645487,1.737016 4.99688,3.00632 1.257039,1.13575 2.514116,2.471891 3.771231,4.008428 2.105562,-2.672257 4.085456,-4.598528 5.939689,-5.778817 2.325544,-1.425185 5.295387,-2.137795 8.909534,-2.137828 4.242576,3.3e-5 7.762387,1.12462 10.559446,3.373761 2.954062,2.360545 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477135,6.881148 -4.431197,9.419805 -2.639924,2.249182 -6.191164,3.373767 -10.653728,3.37376 -2.294217,7e-6 -4.289826,-0.334028 -5.986828,-1.002107 -1.697101,-0.601254 -3.362727,-1.603361 -4.996882,-3.006321 m -19.138997,-0.46765 c 5.185412,1.3e-5 9.333762,-2.67227 12.445062,-8.016856 -3.991252,-5.834462 -8.139602,-8.751704 -12.445062,-8.751733 -3.142714,2.9e-5 -5.515444,0.801714 -7.118198,2.405056 -1.7284972,1.714743 -2.5927368,3.707819 -2.5927216,5.979239 -1.52e-5,2.49415 0.8642244,4.509496 2.5927216,6.046045 1.759888,1.558845 4.132618,2.338262 7.118198,2.338249 M 49.5011,25.782442 c -4.682663,2.8e-5 -8.831014,2.672311 -12.445063,8.016855 3.959745,5.834504 8.108096,8.751745 12.445063,8.751733 3.142634,1.2e-5 5.515365,-0.801673 7.118198,-2.405056 1.728417,-1.7147 2.592657,-3.707778 2.592721,-5.979238 -6.4e-5,-2.49411 -0.864304,-4.509456 -2.592721,-6.046046 C 54.85933,26.561886 52.486599,25.78247 49.5011,25.782442"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3286);fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ id="path3272"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="ccscsccccccccccccccccccccscscccccsccscccccc"
+ inkscape:connector-curvature="0"
+ id="text5100"
+ d="m 19.4,21.166667 c -4.462644,3.3e-5 -8.026822,1.150858 -10.6666667,3.4 -2.9541428,2.538698 -4.4333436,5.658825 -4.4333333,9.4 -1.03e-5,3.897098 1.4791905,7.039495 4.4333333,9.4 -1.622701,-2.044271 -2.433341,-4.51168 -2.4333333,-7.4 -1.03e-5,-3.741175 1.4791905,-6.861302 4.433333,-9.4 2.639845,-2.249142 6.204023,-3.399967 10.666667,-3.4 2.294138,3.3e-5 4.302916,0.365295 6,1.033333 1.979862,0.73491 3.615274,1.730695 4.966667,3 0.06836,0.06177 0.131637,0.137049 0.2,0.2 -0.731813,-0.797005 -1.468213,-1.538822 -2.2,-2.2 -1.351393,-1.269305 -2.986805,-2.26509 -4.966667,-3 -1.697084,-0.668038 -3.705862,-1.0333 -6,-1.033333 z m 29.6,0.1 c -3.614148,3.3e-5 -6.574457,0.74148 -8.9,2.166666 -1.818222,1.157367 -3.923451,3.291388 -5.983333,5.883334 0.618278,0.658774 1.248369,1.377605 1.866666,2.133333 2.105562,-2.672257 4.262434,-4.836378 6.116667,-6.016667 2.325543,-1.425186 5.285852,-2.166633 8.9,-2.166666 4.242576,3.3e-5 7.769607,1.150858 10.566667,3.4 -0.570388,-0.722129 -1.227721,-1.382884 -2,-2 C 56.769607,22.417525 53.242576,21.2667 49,21.266667 Z m 8.866667,8.1 c 0.9092,1.305235 1.366619,2.857751 1.366666,4.666666 -6.5e-5,2.271461 -0.871584,4.285301 -2.6,6 -1.602834,1.603384 -3.957366,2.400012 -7.1,2.4 -2.653707,8e-6 -5.320858,-1.032242 -7.833333,-3.216666 3.136636,3.509305 6.469807,5.216676 9.833333,5.216666 3.142634,1.2e-5 5.497166,-0.796616 7.1,-2.4 1.728416,-1.714699 2.599935,-3.728539 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496744 -2.6,-6.033333 -0.24943,-0.220921 -0.49262,-0.443723 -0.766666,-0.633333 z m -26.633334,4.966666 c -3.1113,5.344585 -7.247921,8.033345 -12.433333,8.033334 -2.58055,1e-5 -4.543473,-0.352086 -6.208333,-1.516667 0.348871,0.50642 0.590094,0.752276 1.075,1.183333 1.759888,1.558846 4.147753,2.333345 7.133333,2.333334 5.185412,1.1e-5 9.322033,-2.688749 12.433333,-8.033334 z m 4.933334,6.5 c -0.04103,0.05207 -0.09239,0.08182 -0.133334,0.133334 0.687326,0.744419 1.306949,1.359747 1.833334,1.8 -0.529404,-0.580895 -1.078447,-1.178283 -1.7,-1.933334 z"
+ style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.3;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
+ <path
+ sodipodi:nodetypes="ccsccscccccccccccccccccccscscccccsccsccccccc"
+ id="text5058-0"
+ d="m 19.730474,21.54714 c -4.462645,3.3e-5 -8.026823,1.150859 -10.6666669,3.4 -2.9541429,2.538699 -4.433344,5.658826 -4.4333333,9.4 -1.07e-5,3.897099 1.4791904,7.039495 4.4333333,9.4 0.042837,0.03444 0.090155,0.06608 0.1333334,0.1 -2.2392086,-2.228193 -3.3666752,-5.040417 -3.3666667,-8.433333 -1.07e-5,-3.741174 1.4791904,-6.861301 4.4333332,-9.4 2.639844,-2.249141 6.204022,-3.399967 10.666667,-3.4 2.294137,3.3e-5 4.302916,0.365295 6,1.033333 1.870874,0.694455 3.42364,1.628367 4.733333,2.8 -0.314265,-0.308986 -0.652406,-0.582729 -0.966667,-0.866666 -1.351393,-1.269305 -2.986804,-2.265091 -4.966666,-3 -1.697084,-0.668039 -3.705863,-1.033301 -6,-1.033334 z m 29.6,0.1 c -3.614149,3.3e-5 -6.574457,0.741481 -8.9,2.166667 -1.813279,1.154221 -3.963039,3.235656 -6.016667,5.816667 0.355649,0.402628 0.711011,0.798625 1.066667,1.233333 2.105561,-2.672257 4.295767,-4.803044 6.15,-5.983333 2.325543,-1.425187 5.285851,-2.166634 8.9,-2.166667 4.22442,3.3e-5 7.742084,1.136734 10.533333,3.366667 -0.36096,-0.367566 -0.745726,-0.696967 -1.166667,-1.033334 -2.797059,-2.249141 -6.32409,-3.399967 -10.566666,-3.4 z m 8.233333,7.333334 c 1.323326,1.449243 1.999942,3.250987 2,5.433333 -6.5e-5,2.27146 -0.871584,4.2853 -2.6,6 -1.602834,1.603383 -3.957366,2.400012 -7.1,2.4 -2.406328,6e-6 -4.776468,-0.90386 -7.066667,-2.7 2.669147,2.483838 5.436929,3.766674 8.266667,3.766667 3.142634,1.1e-5 5.497166,-0.796617 7.1,-2.4 1.728416,-1.7147 2.599935,-3.72854 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496745 -2.6,-6.033334 -0.185641,-0.164422 -0.400724,-0.319587 -0.6,-0.466666 z m -26,5.733333 c -3.1113,5.344584 -7.247921,8.033345 -12.433333,8.033333 -2.612382,1.1e-5 -4.759372,-0.60651 -6.433334,-1.8 0.166027,0.176488 0.313947,0.367942 0.5,0.533334 1.759888,1.558845 4.147754,2.333345 7.133334,2.333333 5.185412,1.2e-5 9.322033,-2.688749 12.433333,-8.033333 z m 4.133333,5.566667 c -0.04657,0.05909 -0.08689,0.108298 -0.133333,0.166666 1.038571,1.18897 1.9748,2.169945 2.7,2.766667 0.06249,0.05364 0.137426,0.08086 0.2,0.133333 -0.792178,-0.781249 -1.706288,-1.778539 -2.766667,-3.066666 z"
+ style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.6;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ inkscape:connector-curvature="0" />
+ </g>
+ </g>
+</svg>
diff --git a/application/resources/multimc/scalable/multimc.svg b/application/resources/multimc/scalable/multimc.svg
index 178509ac..8bb0e289 100644
--- a/application/resources/multimc/scalable/multimc.svg
+++ b/application/resources/multimc/scalable/multimc.svg
@@ -10,18 +10,30 @@
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="64px"
- height="64px"
+ width="68.26667"
+ height="68.26667"
id="svg4427"
version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="multimc.svg"
- inkscape:export-filename="/home/peterix/projects/MultiMC4/src/resources/insticons/infinity128.png"
+ inkscape:version="0.92.1 r"
+ sodipodi:docname="multimc-smooth-biginfinity.svg"
+ inkscape:export-filename="/home/peterix/playground/MultiMC-icons/multimc-smooth-biginfinity.png"
inkscape:export-xdpi="180"
inkscape:export-ydpi="180">
<defs
id="defs4429">
<linearGradient
+ inkscape:collect="always"
+ id="linearGradient4809">
+ <stop
+ style="stop-color:#98c867;stop-opacity:1"
+ offset="0"
+ id="stop4805" />
+ <stop
+ style="stop-color:#5c9a33;stop-opacity:1"
+ offset="1"
+ id="stop4807" />
+ </linearGradient>
+ <linearGradient
id="linearGradient5668"
inkscape:collect="always">
<stop
@@ -89,7 +101,7 @@
xlink:href="#linearGradient5668"
id="linearGradient3286"
gradientUnits="userSpaceOnUse"
- gradientTransform="matrix(1.1879555,0,0,0.84178237,-0.01820035,-0.00789594)"
+ gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.01941371,-0.00842234)"
x1="6.7342591"
y1="28.510933"
x2="50.506943"
@@ -121,7 +133,94 @@
y1="9.7948904"
x2="44.097023"
y2="82.973114"
- gradientTransform="scale(1.1879555,0.84178237)" />
+ gradientTransform="scale(1.2671525,0.89790119)" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient5580">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.0627451"
+ offset="0"
+ id="stop5576" />
+ <stop
+ style="stop-color:#322217;stop-opacity:0.58823532"
+ offset="1"
+ id="stop5578" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3999"
+ inkscape:collect="always">
+ <stop
+ id="stop3995"
+ offset="0"
+ style="stop-color:#a3704b;stop-opacity:1" />
+ <stop
+ id="stop3997"
+ offset="1"
+ style="stop-color:#6a4a33;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2727"
+ inkscape:collect="always">
+ <stop
+ id="stop2723"
+ offset="0"
+ style="stop-color:#966c4a;stop-opacity:1" />
+ <stop
+ id="stop2725"
+ offset="1"
+ style="stop-color:#593d29;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2727"
+ id="linearGradient2050"
+ gradientUnits="userSpaceOnUse"
+ x1="36.546478"
+ y1="33.80484"
+ x2="86.415741"
+ y2="97.065842" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3999"
+ id="radialGradient2052"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-9.105292e-4,-0.00104444)"
+ cx="34.133331"
+ cy="34.133335"
+ fx="34.133331"
+ fy="34.133335"
+ r="29.866665" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5580"
+ id="linearGradient2140"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="translate(-0.0010513,-9.083059e-4)"
+ x1="29.866674"
+ y1="29.867579"
+ x2="38.400005"
+ y2="38.400913" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient5084"
+ id="linearGradient4790"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.82864077,-1.0012743)"
+ x1="14.312115"
+ y1="9.7948904"
+ x2="44.097023"
+ y2="82.973114" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4809"
+ id="radialGradient4803"
+ cx="-42.66758"
+ cy="-34.134373"
+ fx="-42.66758"
+ fy="-34.134373"
+ r="34.132812"
+ gradientTransform="matrix(1.7500268,0.1250019,-0.01781176,0.24936465,95.393964,18.110151)"
+ gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
@@ -130,18 +229,30 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="6"
- inkscape:cx="10.09561"
- inkscape:cy="35.232628"
+ inkscape:zoom="3.6203867"
+ inkscape:cx="52.171166"
+ inkscape:cy="11.292073"
inkscape:current-layer="layer1"
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">
+ inkscape:window-width="1368"
+ inkscape:window-height="905"
+ inkscape:window-x="2452"
+ inkscape:window-y="723"
+ inkscape:window-maximized="0"
+ inkscape:snap-bbox="true"
+ inkscape:bbox-paths="false"
+ inkscape:snap-bbox-edge-midpoints="false"
+ inkscape:bbox-nodes="true"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:snap-smooth-nodes="true"
+ inkscape:snap-midpoints="false"
+ inkscape:snap-intersection-paths="true"
+ inkscape:object-paths="true"
+ inkscape:snap-object-midpoints="true"
+ inkscape:snap-text-baseline="true"
+ inkscape:snap-center="true">
<inkscape:grid
type="xygrid"
id="grid4446"
@@ -149,8 +260,10 @@
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
- spacingx="4px"
- spacingy="4px" />
+ spacingx="4.2666667"
+ spacingy="4.2666667"
+ originx="0"
+ originy="0" />
</sodipodi:namedview>
<metadata
id="metadata4432">
@@ -168,1826 +281,73 @@
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
- <rect
- style="opacity:0.34999999999999998;fill:#552200;fill-opacity:1;stroke:none"
- id="rect5674"
- width="64.125"
- height="64"
- x="-0.125"
- y="0.1249999" />
- <rect
- style="fill:#74b44a;fill-opacity:1;stroke:none"
- id="rect4448"
- width="4"
- height="4"
- x="0"
- y="0" />
- <rect
- y="0"
- x="4"
- height="4"
- width="4"
- id="rect4450"
- style="fill:#76b64c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#73b349;fill-opacity:1;stroke:none"
- id="rect4452"
- width="4"
- height="4"
- x="8"
- y="0" />
- <rect
- y="0"
- x="12"
- height="4"
- width="4"
- id="rect4454"
- style="fill:#66a63c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#66a63c;fill-opacity:1;stroke:none"
- id="rect4456"
- width="4"
- height="4"
- x="16"
- y="0" />
- <rect
- y="0"
- x="20"
- height="4"
- width="4"
- id="rect4458"
- style="fill:#6faf45;fill-opacity:1;stroke:none" />
- <rect
- y="4"
- x="0"
- height="4"
- width="4"
- id="rect4460"
- style="fill:#75b54b;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#6cac42;fill-opacity:1;stroke:none"
- id="rect4462"
- width="4"
- height="4"
- x="4"
- y="4" />
- <rect
- y="4"
- x="8"
- height="4"
- width="4"
- id="rect4464"
- style="fill:#8ab95a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#81b051;fill-opacity:1;stroke:none"
- id="rect4466"
- width="4"
- height="4"
- x="12"
- y="4" />
- <rect
- y="4"
- x="16"
- height="4"
- width="4"
- id="rect4468"
- style="fill:#83b253;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4470"
- width="4"
- height="4"
- x="20"
- y="4" />
- <rect
- y="0"
- x="24"
- height="4"
- width="4"
- id="rect4472"
- style="fill:#5f9f35;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#6cac42;fill-opacity:1;stroke:none"
- id="rect4474"
- width="4"
- height="4"
- x="28"
- y="0" />
- <rect
- y="0"
- x="32"
- height="4"
- width="4"
- id="rect4476"
- style="fill:#7ebe54;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#76b64c;fill-opacity:1;stroke:none"
- id="rect4478"
- width="4"
- height="4"
- x="36"
- y="0" />
- <rect
- y="0"
- x="40"
- height="4"
- width="4"
- id="rect4480"
- style="fill:#6aaa40;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#67a73d;fill-opacity:1;stroke:none"
- id="rect4482"
- width="4"
- height="4"
- x="44"
- y="0" />
- <rect
- style="fill:#68a83e;fill-opacity:1;stroke:none"
- id="rect4484"
- width="4"
- height="4"
- x="24"
- y="4" />
- <rect
- y="4"
- x="28"
- height="4"
- width="4"
- id="rect4486"
- style="fill:#62a238;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#5f9f35;fill-opacity:1;stroke:none"
- id="rect4488"
- width="4"
- height="4"
- x="32"
- y="4" />
- <rect
- y="4"
- x="36"
- height="4"
- width="4"
- id="rect4490"
- style="fill:#93c263;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#90bf60;fill-opacity:1;stroke:none"
- id="rect4492"
- width="4"
- height="4"
- x="40"
- y="4" />
- <rect
- y="4"
- x="44"
- height="4"
- width="4"
- id="rect4494"
- style="fill:#73b349;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#69a93f;fill-opacity:1;stroke:none"
- id="rect4496"
- width="4"
- height="4"
- x="48"
- y="0" />
- <rect
- y="0"
- x="52"
- height="4"
- width="4"
- id="rect4498"
- style="fill:#61a137;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#509026;fill-opacity:1;stroke:none"
- id="rect4500"
- width="4"
- height="4"
- x="56"
- y="0" />
- <rect
- y="0"
- x="60"
- height="4"
- width="4"
- id="rect4502"
- style="fill:#6dad43;fill-opacity:1;stroke:none" />
- <rect
- y="4"
- x="48"
- height="4"
- width="4"
- id="rect4508"
- style="fill:#61a137;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#6cac42;fill-opacity:1;stroke:none"
- id="rect4510"
- width="4"
- height="4"
- x="52"
- y="4" />
- <rect
- y="4"
- x="56"
- height="4"
- width="4"
- id="rect4512"
- style="fill:#67a73d;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#6bab41;fill-opacity:1;stroke:none"
- id="rect4514"
- width="4"
- height="4"
- x="60"
- y="4" />
- <rect
- y="8"
- x="0"
- height="4"
- width="4"
- id="rect4520"
- style="fill:#8dbc5d;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4522"
- width="4"
- height="4"
- x="4"
- y="8" />
- <rect
- y="8"
- x="8"
- height="4"
- width="4"
- id="rect4524"
- style="fill:#9ccb6c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#64a43a;fill-opacity:1;stroke:none"
- id="rect4526"
- width="4"
- height="4"
- x="12"
- y="8" />
- <rect
- y="8"
- x="16"
- height="4"
- width="4"
- id="rect4528"
- style="fill:#69a93f;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4530"
- width="4"
- height="4"
- x="20"
- y="8" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4532"
- width="4"
- height="4"
- x="0"
- y="12" />
- <rect
- y="12"
- x="4"
- height="4"
- width="4"
- id="rect4534"
- style="fill:#6c6c6c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4536"
- width="4"
- height="4"
- x="8"
- y="12" />
- <rect
- y="12"
- x="12"
- height="4"
- width="4"
- id="rect4538"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#71b147;fill-opacity:1;stroke:none"
- id="rect4540"
- width="4"
- height="4"
- x="16"
- y="12" />
- <rect
- y="12"
- x="20"
- height="4"
- width="4"
- id="rect4542"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#70b046;fill-opacity:1;stroke:none"
- id="rect4544"
- width="4"
- height="4"
- x="24"
- y="8" />
- <rect
- y="8"
- x="28"
- height="4"
- width="4"
- id="rect4546"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#74b44a;fill-opacity:1;stroke:none"
- id="rect4548"
- width="4"
- height="4"
- x="32"
- y="8" />
- <rect
- y="8"
- x="36"
- height="4"
- width="4"
- id="rect4550"
- style="fill:#7fbf55;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#92c162;fill-opacity:1;stroke:none"
- id="rect4552"
- width="4"
- height="4"
- x="40"
- y="8" />
- <rect
- y="8"
- x="44"
- height="4"
- width="4"
- id="rect4554"
- style="fill:#97c667;fill-opacity:1;stroke:none" />
- <rect
- y="12"
- x="24"
- height="4"
- width="4"
- id="rect4556"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4558"
- width="4"
- height="4"
- x="28"
- y="12" />
- <rect
- y="12"
- x="32"
- height="4"
- width="4"
- id="rect4560"
- style="fill:#5f9f35;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4562"
- width="4"
- height="4"
- x="36"
- y="12" />
- <rect
- y="12"
- x="40"
- height="4"
- width="4"
- id="rect4564"
- style="fill:#6dad43;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4566"
- width="4"
- height="4"
- x="44"
- y="12" />
- <rect
- y="8"
- x="48"
- height="4"
- width="4"
- id="rect4568"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#57972d;fill-opacity:1;stroke:none"
- id="rect4570"
- width="4"
- height="4"
- x="52"
- y="8" />
- <rect
- y="8"
- x="56"
- height="4"
- width="4"
- id="rect4572"
- style="fill:#60a036;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4574"
- width="4"
- height="4"
- x="60"
- y="8" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4576"
- width="4"
- height="4"
- x="48"
- y="12" />
- <rect
- y="12"
- x="52"
- height="4"
- width="4"
- id="rect4578"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4580"
- width="4"
- height="4"
- x="56"
- y="12" />
- <rect
- y="12"
- x="60"
- height="4"
- width="4"
- id="rect4582"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- y="16"
- x="0"
- height="4"
- width="4"
- id="rect4584"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4586"
- width="4"
- height="4"
- x="4"
- y="16" />
- <rect
- y="16"
- x="8"
- height="4"
- width="4"
- id="rect4588"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4590"
- width="4"
- height="4"
- x="12"
- y="16" />
- <rect
- y="16"
- x="16"
- height="4"
- width="4"
- id="rect4592"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4594"
- width="4"
- height="4"
- x="20"
- y="16" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4596"
- width="4"
- height="4"
- x="0"
- y="20" />
- <rect
- y="20"
- x="4"
- height="4"
- width="4"
- id="rect4598"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4600"
- width="4"
- height="4"
- x="8"
- y="20" />
- <rect
- y="20"
- x="12"
- height="4"
- width="4"
- id="rect4602"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4604"
- width="4"
- height="4"
- x="16"
- y="20" />
- <rect
- y="20"
- x="20"
- height="4"
- width="4"
- id="rect4606"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4608"
- width="4"
- height="4"
- x="24"
- y="16" />
- <rect
- y="16"
- x="28"
- height="4"
- width="4"
- id="rect4610"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4612"
- width="4"
- height="4"
- x="32"
- y="16" />
- <rect
- y="16"
- x="36"
- height="4"
- width="4"
- id="rect4614"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4616"
- width="4"
- height="4"
- x="40"
- y="16" />
- <rect
- y="16"
- x="44"
- height="4"
- width="4"
- id="rect4618"
- style="fill:#6c6c6c;fill-opacity:1;stroke:none" />
- <rect
- y="20"
- x="24"
- height="4"
- width="4"
- id="rect4620"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4622"
- width="4"
- height="4"
- x="28"
- y="20" />
- <rect
- y="20"
- x="32"
- height="4"
- width="4"
- id="rect4624"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4626"
- width="4"
- height="4"
- x="36"
- y="20" />
- <rect
- y="20"
- x="40"
- height="4"
- width="4"
- id="rect4628"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4630"
- width="4"
- height="4"
- x="44"
- y="20" />
- <rect
- y="16"
- x="48"
- height="4"
- width="4"
- id="rect4632"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4634"
- width="4"
- height="4"
- x="52"
- y="16" />
- <rect
- y="16"
- x="56"
- height="4"
- width="4"
- id="rect4636"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4638"
- width="4"
- height="4"
- x="60"
- y="16" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4640"
- width="4"
- height="4"
- x="48"
- y="20" />
- <rect
- y="20"
- x="52"
- height="4"
- width="4"
- id="rect4642"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4644"
- width="4"
- height="4"
- x="56"
- y="20" />
- <rect
- y="20"
- x="60"
- height="4"
- width="4"
- id="rect4646"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4648"
- width="4"
- height="4"
- x="0"
- y="24" />
- <rect
- y="24"
- x="4"
- height="4"
- width="4"
- id="rect4650"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4652"
- width="4"
- height="4"
- x="8"
- y="24" />
- <rect
- y="24"
- x="12"
- height="4"
- width="4"
- id="rect4654"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#878787;fill-opacity:1;stroke:none"
- id="rect4656"
- width="4"
- height="4"
- x="16"
- y="24" />
- <rect
- y="24"
- x="20"
- height="4"
- width="4"
- id="rect4658"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- y="28"
- x="0"
- height="4"
- width="4"
- id="rect4660"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4662"
- width="4"
- height="4"
- x="4"
- y="28" />
- <rect
- y="28"
- x="8"
- height="4"
- width="4"
- id="rect4664"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4666"
- width="4"
- height="4"
- x="12"
- y="28" />
- <rect
- y="28"
- x="16"
- height="4"
- width="4"
- id="rect4668"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4670"
- width="4"
- height="4"
- x="20"
- y="28" />
- <rect
- y="24"
- x="24"
- height="4"
- width="4"
- id="rect4672"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4674"
- width="4"
- height="4"
- x="28"
- y="24" />
- <rect
- y="24"
- x="32"
- height="4"
- width="4"
- id="rect4676"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4678"
- width="4"
- height="4"
- x="36"
- y="24" />
- <rect
- y="24"
- x="40"
- height="4"
- width="4"
- id="rect4680"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4682"
- width="4"
- height="4"
- x="44"
- y="24" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4684"
- width="4"
- height="4"
- x="24"
- y="28" />
- <rect
- y="28"
- x="28"
- height="4"
- width="4"
- id="rect4686"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4688"
- width="4"
- height="4"
- x="32"
- y="28" />
- <rect
- y="28"
- x="36"
- height="4"
- width="4"
- id="rect4690"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4692"
- width="4"
- height="4"
- x="40"
- y="28" />
- <rect
- y="28"
- x="44"
- height="4"
- width="4"
- id="rect4694"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4696"
- width="4"
- height="4"
- x="48"
- y="24" />
- <rect
- y="24"
- x="52"
- height="4"
- width="4"
- id="rect4698"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4700"
- width="4"
- height="4"
- x="56"
- y="24" />
- <rect
- y="24"
- x="60"
- height="4"
- width="4"
- id="rect4702"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- y="28"
- x="48"
- height="4"
- width="4"
- id="rect4704"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4706"
- width="4"
- height="4"
- x="52"
- y="28" />
- <rect
- y="28"
- x="56"
- height="4"
- width="4"
- id="rect4708"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4710"
- width="4"
- height="4"
- x="60"
- y="28" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4448-5"
- width="4"
- height="4"
- x="0"
- y="32" />
- <rect
- y="32"
- x="4"
- height="4"
- width="4"
- id="rect4450-2"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4452-3"
- width="4"
- height="4"
- x="8"
- y="32" />
- <rect
- y="32"
- x="12"
- height="4"
- width="4"
- id="rect4454-7"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4456-2"
- width="4"
- height="4"
- x="16"
- y="32" />
- <rect
- y="32"
- x="20"
- height="4"
- width="4"
- id="rect4458-4"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- y="36"
- x="0"
- height="4"
- width="4"
- id="rect4460-9"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4462-7"
- width="4"
- height="4"
- x="4"
- y="36" />
- <rect
- y="36"
- x="8"
- height="4"
- width="4"
- id="rect4464-3"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4466-7"
- width="4"
- height="4"
- x="12"
- y="36" />
- <rect
- y="36"
- x="16"
- height="4"
- width="4"
- id="rect4468-8"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4470-9"
- width="4"
- height="4"
- x="20"
- y="36" />
- <rect
- y="32"
- x="24"
- height="4"
- width="4"
- id="rect4472-9"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4474-6"
- width="4"
- height="4"
- x="28"
- y="32" />
- <rect
- y="32"
- x="32"
- height="4"
- width="4"
- id="rect4476-7"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4478-8"
- width="4"
- height="4"
- x="36"
- y="32" />
- <rect
- y="32"
- x="40"
- height="4"
- width="4"
- id="rect4480-1"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4482-6"
- width="4"
- height="4"
- x="44"
- y="32" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4484-7"
- width="4"
- height="4"
- x="24"
- y="36" />
- <rect
- y="36"
- x="28"
- height="4"
- width="4"
- id="rect4486-1"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4488-9"
- width="4"
- height="4"
- x="32"
- y="36" />
- <rect
- y="36"
- x="36"
- height="4"
- width="4"
- id="rect4490-9"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4492-3"
- width="4"
- height="4"
- x="40"
- y="36" />
- <rect
- y="36"
- x="44"
- height="4"
- width="4"
- id="rect4494-9"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4496-4"
- width="4"
- height="4"
- x="48"
- y="32" />
- <rect
- y="32"
- x="52"
- height="4"
- width="4"
- id="rect4498-2"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4500-1"
- width="4"
- height="4"
- x="56"
- y="32" />
- <rect
- y="32"
- x="60"
- height="4"
- width="4"
- id="rect4502-7"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- y="36"
- x="48"
- height="4"
- width="4"
- id="rect4508-4"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4510-6"
- width="4"
- height="4"
- x="52"
- y="36" />
- <rect
- y="36"
- x="56"
- height="4"
- width="4"
- id="rect4512-8"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4514-1"
- width="4"
- height="4"
- x="60"
- y="36" />
- <rect
- y="40"
- x="0"
- height="4"
- width="4"
- id="rect4520-0"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4522-3"
- width="4"
- height="4"
- x="4"
- y="40" />
- <rect
- y="40"
- x="8"
- height="4"
- width="4"
- id="rect4524-1"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4526-3"
- width="4"
- height="4"
- x="12"
- y="40" />
- <rect
- y="40"
- x="16"
- height="4"
- width="4"
- id="rect4528-7"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4530-0"
- width="4"
- height="4"
- x="20"
- y="40" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4532-2"
- width="4"
- height="4"
- x="0"
- y="44" />
- <rect
- y="44"
- x="4"
- height="4"
- width="4"
- id="rect4534-0"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4536-9"
- width="4"
- height="4"
- x="8"
- y="44" />
- <rect
- y="44"
- x="12"
- height="4"
- width="4"
- id="rect4538-0"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4540-2"
- width="4"
- height="4"
- x="16"
- y="44" />
- <rect
- y="44"
- x="20"
- height="4"
- width="4"
- id="rect4542-9"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4544-6"
- width="4"
- height="4"
- x="24"
- y="40" />
- <rect
- y="40"
- x="28"
- height="4"
- width="4"
- id="rect4546-9"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4548-9"
- width="4"
- height="4"
- x="32"
- y="40" />
- <rect
- y="40"
- x="36"
- height="4"
- width="4"
- id="rect4550-8"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4552-7"
- width="4"
- height="4"
- x="40"
- y="40" />
- <rect
- y="40"
- x="44"
- height="4"
- width="4"
- id="rect4554-6"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- y="44"
- x="24"
- height="4"
- width="4"
- id="rect4556-1"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4558-9"
- width="4"
- height="4"
- x="28"
- y="44" />
- <rect
- y="44"
- x="32"
- height="4"
- width="4"
- id="rect4560-7"
- style="fill:#6c6c6c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4562-5"
- width="4"
- height="4"
- x="36"
- y="44" />
- <rect
- y="44"
- x="40"
- height="4"
- width="4"
- id="rect4564-8"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4566-2"
- width="4"
- height="4"
- x="44"
- y="44" />
- <rect
- y="40"
- x="48"
- height="4"
- width="4"
- id="rect4568-9"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4570-1"
- width="4"
- height="4"
- x="52"
- y="40" />
- <rect
- y="40"
- x="56"
- height="4"
- width="4"
- id="rect4572-9"
- style="fill:#878787;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4574-5"
- width="4"
- height="4"
- x="60"
- y="40" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4576-8"
- width="4"
- height="4"
- x="48"
- y="44" />
- <rect
- y="44"
- x="52"
- height="4"
- width="4"
- id="rect4578-7"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4580-6"
- width="4"
- height="4"
- x="56"
- y="44" />
- <rect
- y="44"
- x="60"
- height="4"
- width="4"
- id="rect4582-0"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- y="48"
- x="0"
- height="4"
- width="4"
- id="rect4930"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4932"
- width="4"
- height="4"
- x="4"
- y="48" />
- <rect
- y="48"
- x="8"
- height="4"
- width="4"
- id="rect4934"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4936"
- width="4"
- height="4"
- x="12"
- y="48" />
- <rect
- y="48"
- x="16"
- height="4"
- width="4"
- id="rect4938"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4940"
- width="4"
- height="4"
- x="20"
- y="48" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4942"
- width="4"
- height="4"
- x="0"
- y="52" />
- <rect
- y="52"
- x="4"
- height="4"
- width="4"
- id="rect4944"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4946"
- width="4"
- height="4"
- x="8"
- y="52" />
- <rect
- y="52"
- x="12"
- height="4"
- width="4"
- id="rect4948"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4950"
- width="4"
- height="4"
- x="16"
- y="52" />
- <rect
- y="52"
- x="20"
- height="4"
- width="4"
- id="rect4952"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4954"
- width="4"
- height="4"
- x="24"
- y="48" />
- <rect
- y="48"
- x="28"
- height="4"
- width="4"
- id="rect4956"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4958"
- width="4"
- height="4"
- x="32"
- y="48" />
- <rect
- y="48"
- x="36"
- height="4"
- width="4"
- id="rect4960"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4962"
- width="4"
- height="4"
- x="40"
- y="48" />
- <rect
- y="48"
- x="44"
- height="4"
- width="4"
- id="rect4964"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- y="52"
- x="24"
- height="4"
- width="4"
- id="rect4966"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4968"
- width="4"
- height="4"
- x="28"
- y="52" />
- <rect
- y="52"
- x="32"
- height="4"
- width="4"
- id="rect4970"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4972"
- width="4"
- height="4"
- x="36"
- y="52" />
- <rect
- y="52"
- x="40"
- height="4"
- width="4"
- id="rect4974"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4976"
- width="4"
- height="4"
- x="44"
- y="52" />
- <rect
- y="48"
- x="48"
- height="4"
- width="4"
- id="rect4978"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4980"
- width="4"
- height="4"
- x="52"
- y="48" />
- <rect
- y="48"
- x="56"
- height="4"
- width="4"
- id="rect4982"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#b9855c;fill-opacity:1;stroke:none"
- id="rect4984"
- width="4"
- height="4"
- x="60"
- y="48" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect4986"
- width="4"
- height="4"
- x="48"
- y="52" />
- <rect
- y="52"
- x="52"
- height="4"
- width="4"
- id="rect4988"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4990"
- width="4"
- height="4"
- x="56"
- y="52" />
- <rect
- y="52"
- x="60"
- height="4"
- width="4"
- id="rect4992"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect4994"
- width="4"
- height="4"
- x="0"
- y="56" />
- <rect
- y="56"
- x="4"
- height="4"
- width="4"
- id="rect4996"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect4998"
- width="4"
- height="4"
- x="8"
- y="56" />
- <rect
- y="56"
- x="12"
- height="4"
- width="4"
- id="rect5000"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect5002"
- width="4"
- height="4"
- x="16"
- y="56" />
- <rect
- y="56"
- x="20"
- height="4"
- width="4"
- id="rect5004"
- style="fill:#593d29;fill-opacity:1;stroke:none" />
- <rect
- y="60"
- x="0"
- height="4"
- width="4"
- id="rect5006"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect5008"
- width="4"
- height="4"
- x="4"
- y="60" />
- <rect
- y="60"
- x="8"
- height="4"
- width="4"
- id="rect5010"
- style="fill:#b9855c;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect5012"
- width="4"
- height="4"
- x="12"
- y="60" />
- <rect
- y="60"
- x="16"
- height="4"
- width="4"
- id="rect5014"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect5016"
- width="4"
- height="4"
- x="20"
- y="60" />
- <rect
- y="56"
- x="24"
- height="4"
- width="4"
- id="rect5018"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect5020"
- width="4"
- height="4"
- x="28"
- y="56" />
- <rect
- y="56"
- x="32"
- height="4"
- width="4"
- id="rect5022"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect5024"
- width="4"
- height="4"
- x="36"
- y="56" />
- <rect
- y="56"
- x="40"
- height="4"
- width="4"
- id="rect5026"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect5028"
- width="4"
- height="4"
- x="44"
- y="56" />
- <rect
- style="fill:#878787;fill-opacity:1;stroke:none"
- id="rect5030"
- width="4"
- height="4"
- x="24"
- y="60" />
- <rect
- y="60"
- x="28"
- height="4"
- width="4"
- id="rect5032"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect5034"
- width="4"
- height="4"
- x="32"
- y="60" />
- <rect
- y="60"
- x="36"
- height="4"
- width="4"
- id="rect5036"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect5038"
- width="4"
- height="4"
- x="40"
- y="60" />
- <rect
- y="60"
- x="44"
- height="4"
- width="4"
- id="rect5040"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#79553a;fill-opacity:1;stroke:none"
- id="rect5042"
- width="4"
- height="4"
- x="48"
- y="56" />
- <rect
- y="56"
- x="52"
- height="4"
- width="4"
- id="rect5044"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect5046"
- width="4"
- height="4"
- x="56"
- y="56" />
- <rect
- y="56"
- x="60"
- height="4"
- width="4"
- id="rect5048"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- y="60"
- x="48"
- height="4"
- width="4"
- id="rect5050"
- style="fill:#966c4a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#966c4a;fill-opacity:1;stroke:none"
- id="rect5052"
- width="4"
- height="4"
- x="52"
- y="60" />
- <rect
- y="60"
- x="56"
- height="4"
- width="4"
- id="rect5054"
- style="fill:#79553a;fill-opacity:1;stroke:none" />
- <rect
- style="fill:#593d29;fill-opacity:1;stroke:none"
- id="rect5056"
- width="4"
- height="4"
- x="60"
- y="60" />
- <path
- inkscape:connector-curvature="0"
- id="path3279"
- style="font-size:76.18933868px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3293);fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
- d="m 37.233107,43.070771 c -0.913385,-0.751568 -2.091894,-2.0042 -3.535529,-3.7579 -1.974039,2.505279 -3.83019,4.311158 -5.568458,5.41764 -2.180273,1.33615 -4.9645,2.004221 -8.352688,2.004214 -3.97749,7e-6 -7.277313,-1.054292 -9.8994808,-3.1629 -2.7695088,-2.212974 -4.1542564,-5.146221 -4.1542467,-8.799752 -9.7e-6,-3.507351 1.3847379,-6.451037 4.1542467,-8.831067 2.4748538,-2.10857 5.8041408,-3.162868 9.9878698,-3.162899 2.150754,3.1e-5 4.021637,0.313189 5.612652,0.939475 1.856121,0.688978 3.417645,1.628452 4.684576,2.818425 1.178474,1.064766 2.356983,2.317398 3.535529,3.757901 1.973965,-2.505241 3.830116,-4.311119 5.568458,-5.41764 2.180198,-1.336112 4.964424,-2.004182 8.352687,-2.004214 3.977416,3.2e-5 7.277239,1.05433 9.899482,3.1629 2.769434,2.213012 4.154182,5.146259 4.154247,8.799751 -6.5e-5,3.50739 -1.384813,6.451076 -4.154247,8.831068 -2.47493,2.108607 -5.804215,3.162907 -9.987869,3.162899 -2.15083,8e-6 -4.021712,-0.31315 -5.612653,-0.939475 -1.591032,-0.563676 -3.152556,-1.503151 -4.684576,-2.818426 M 19.290297,42.63235 c 4.861324,1.1e-5 8.750403,-2.505254 11.667246,-7.515802 -3.7418,-5.46981 -7.630877,-8.204724 -11.667246,-8.20475 -2.946295,2.6e-5 -5.17073,0.751606 -6.67331,2.254741 -1.620467,1.607569 -2.430691,3.476079 -2.430677,5.605534 -1.4e-5,2.338267 0.81021,4.227655 2.430677,5.668168 1.649894,1.461418 3.874329,2.19212 6.67331,2.192109 M 48.104859,26.974429 c -4.389996,2.7e-5 -8.279074,2.505292 -11.667245,7.515802 3.712261,5.469847 7.601339,8.204762 11.667245,8.20475 2.946219,1.2e-5 5.170654,-0.751567 6.673311,-2.25474 1.62039,-1.607532 2.430615,-3.476042 2.430677,-5.605536 -6.2e-5,-2.338228 -0.810287,-4.227615 -2.430677,-5.668166 -1.64997,-1.46138 -3.874405,-2.192083 -6.673311,-2.19211" />
- <path
- inkscape:connector-curvature="0"
- id="path3272"
- style="font-size:76.18933868px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3286);fill-opacity:1;stroke:none;font-family:DejaVu Sans;-inkscape-font-specification:DejaVu Sans"
- d="M 35.535529,40.267381 C 34.622143,39.515813 33.443636,38.26318 32,36.50948 c -1.97404,2.505279 -3.830191,4.311157 -5.568458,5.417641 -2.180273,1.33615 -4.964499,2.004221 -8.352688,2.004213 -3.97749,8e-6 -7.277313,-1.054291 -9.8994809,-3.1629 -2.7695089,-2.212973 -4.1542564,-5.14622 -4.1542466,-8.799751 -9.8e-6,-3.507351 1.3847377,-6.451037 4.1542466,-8.831067 2.4748539,-2.10857 5.8041399,-3.162869 9.9878699,-3.1629 2.150754,3.1e-5 4.021636,0.313189 5.612653,0.939476 1.856121,0.688977 3.417644,1.628452 4.684575,2.818425 1.178474,1.064765 2.356983,2.317397 3.535529,3.757901 1.973964,-2.505241 3.830115,-4.31112 5.568458,-5.417641 2.180198,-1.336111 4.964425,-2.004183 8.352688,-2.004214 3.977415,3.1e-5 7.277238,1.054331 9.899481,3.162901 2.769433,2.213011 4.154181,5.146259 4.154247,8.799751 -6.6e-5,3.50739 -1.384814,6.451076 -4.154247,8.831067 -2.474929,2.108608 -5.804216,3.162907 -9.98787,3.1629 -2.150829,7e-6 -4.021712,-0.313151 -5.612651,-0.939475 -1.591033,-0.563676 -3.152557,-1.503151 -4.684577,-2.818426 m -17.94281,-0.438422 c 4.861324,1.2e-5 8.750402,-2.505253 11.667246,-7.515802 -3.741799,-5.469809 -7.630877,-8.204723 -11.667246,-8.20475 -2.946294,2.7e-5 -5.170729,0.751607 -6.673311,2.25474 -1.6204657,1.607571 -2.4306903,3.47608 -2.4306761,5.605536 -1.42e-5,2.338266 0.8102104,4.227653 2.4306761,5.668168 1.649895,1.461417 3.87433,2.19212 6.673311,2.192108 m 28.814562,-15.65792 c -4.389996,2.7e-5 -8.279075,2.505292 -11.667246,7.515802 3.712261,5.469847 7.60134,8.204761 11.667246,8.20475 2.94622,1.1e-5 5.170655,-0.751569 6.673311,-2.25474 1.620391,-1.607532 2.430616,-3.476042 2.430676,-5.605536 -6e-5,-2.338228 -0.810285,-4.227615 -2.430676,-5.668168 -1.64997,-1.461379 -3.874405,-2.192081 -6.673311,-2.192108" />
- <path
- style="font-size:76.18933868px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;opacity:0.3;fill:#ccff00;fill-opacity:1;stroke:none;font-family:Sans"
- d="m 18.1875,19.84375 c -4.183729,3.1e-5 -7.525146,1.07893 -10,3.1875 -2.7695089,2.38003 -4.1562597,5.305149 -4.15625,8.8125 -9.7e-6,3.65353 1.3867411,6.599527 4.15625,8.8125 -1.5212822,-1.916504 -2.2812572,-4.2297 -2.28125,-6.9375 -9.7e-6,-3.507351 1.3867411,-6.43247 4.15625,-8.8125 2.474854,-2.10857 5.816271,-3.187469 10,-3.1875 2.150754,3.1e-5 4.033984,0.342464 5.625,0.96875 1.856121,0.688978 3.389319,1.622527 4.65625,2.8125 0.06409,0.05791 0.12341,0.128483 0.1875,0.1875 -0.686074,-0.747192 -1.376449,-1.442646 -2.0625,-2.0625 -1.266931,-1.189973 -2.800129,-2.123522 -4.65625,-2.8125 -1.591016,-0.626286 -3.474246,-0.968719 -5.625,-0.96875 z m 27.75,0.09375 c -3.388264,3.1e-5 -6.163553,0.695138 -8.34375,2.03125 -1.704583,1.085031 -3.678235,3.085676 -5.609375,5.515625 0.579636,0.617601 1.170346,1.291505 1.75,2 1.973964,-2.505241 3.996032,-4.534104 5.734375,-5.640625 2.180197,-1.336112 4.955486,-2.031219 8.34375,-2.03125 3.977415,3.1e-5 7.284007,1.07893 9.90625,3.1875 -0.534738,-0.676996 -1.150988,-1.296453 -1.875,-1.875 -2.622243,-2.10857 -5.928835,-3.187469 -9.90625,-3.1875 z m 8.3125,7.59375 c 0.852375,1.223658 1.281206,2.679142 1.28125,4.375 -6.1e-5,2.129494 -0.81711,4.017469 -2.4375,5.625 -1.502657,1.503172 -3.710031,2.250011 -6.65625,2.25 -2.487851,7e-6 -4.988305,-0.967727 -7.34375,-3.015625 2.940596,3.289974 6.065444,4.890634 9.21875,4.890625 2.946219,1.1e-5 5.153593,-0.746828 6.65625,-2.25 1.62039,-1.607531 2.437439,-3.495506 2.4375,-5.625 -6.1e-5,-2.338228 -0.81711,-4.215698 -2.4375,-5.65625 C 54.734909,27.917887 54.506918,27.70901 54.25,27.53125 z M 29.28125,32.1875 c -2.916844,5.010548 -6.794926,7.531261 -11.65625,7.53125 -2.419266,1e-5 -4.259506,-0.33008 -5.820312,-1.421875 0.327066,0.474769 0.553213,0.705259 1.007812,1.109375 1.649895,1.461418 3.888519,2.187511 6.6875,2.1875 4.861324,1.1e-5 8.739406,-2.520702 11.65625,-7.53125 z m 4.625,6.09375 c -0.03847,0.04882 -0.08662,0.07671 -0.125,0.125 0.644368,0.697893 1.225264,1.274763 1.71875,1.6875 -0.496316,-0.544589 -1.011044,-1.10464 -1.59375,-1.8125 z"
- id="text5100"
- inkscape:connector-curvature="0"
- sodipodi:nodetypes="ccscsccccccccccccccccccccscscccccsccscccccc" />
- <path
- inkscape:connector-curvature="0"
- style="font-size:76.18933868px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;opacity:0.6;fill:#ccff00;fill-opacity:1;stroke:none;font-family:Sans"
- d="m 18.497319,20.200444 c -4.183729,3.1e-5 -7.525146,1.07893 -9.9999998,3.1875 -2.769509,2.38003 -4.15626,5.305149 -4.15625,8.8125 -10e-6,3.65353 1.386741,6.599526 4.15625,8.8125 0.04016,0.03229 0.08452,0.06195 0.125,0.09375 -2.099258,-2.088931 -3.156258,-4.725391 -3.15625,-7.90625 -10e-6,-3.507351 1.386741,-6.43247 4.15625,-8.8125 2.4748538,-2.10857 5.8162708,-3.187469 9.9999998,-3.1875 2.150754,3.1e-5 4.033984,0.342464 5.625,0.96875 1.753945,0.651051 3.209663,1.526594 4.4375,2.625 -0.294623,-0.289675 -0.611631,-0.546309 -0.90625,-0.8125 -1.266931,-1.189973 -2.800129,-2.123522 -4.65625,-2.8125 -1.591016,-0.626286 -3.474246,-0.968719 -5.625,-0.96875 z m 27.75,0.09375 c -3.388264,3.1e-5 -6.163553,0.695138 -8.34375,2.03125 -1.699949,1.082082 -3.715349,3.033428 -5.640625,5.453125 0.333421,0.377464 0.666573,0.748711 1,1.15625 1.973964,-2.505241 4.027282,-4.502854 5.765625,-5.609375 2.180197,-1.336112 4.955486,-2.031219 8.34375,-2.03125 3.960394,3.1e-5 7.258204,1.065688 9.875,3.15625 -0.3384,-0.344593 -0.699118,-0.653406 -1.09375,-0.96875 -2.622243,-2.10857 -5.928835,-3.187469 -9.90625,-3.1875 z m 7.71875,6.875 c 1.240618,1.358666 1.874946,3.047801 1.875,5.09375 -6.1e-5,2.129494 -0.81711,4.017469 -2.4375,5.625 -1.502657,1.503172 -3.710031,2.250011 -6.65625,2.25 -2.255932,6e-6 -4.477939,-0.847369 -6.625,-2.53125 2.502325,2.328598 5.097121,3.531257 7.75,3.53125 2.946219,1.1e-5 5.153593,-0.746828 6.65625,-2.25 1.62039,-1.607531 2.437439,-3.495506 2.4375,-5.625 -6.1e-5,-2.338228 -0.81711,-4.215698 -2.4375,-5.65625 -0.174038,-0.154146 -0.375679,-0.299613 -0.5625,-0.4375 z m -24.375,5.375 c -2.916844,5.010548 -6.794926,7.531261 -11.65625,7.53125 -2.449108,1e-5 -4.461911,-0.568603 -6.03125,-1.6875 0.15565,0.165457 0.294325,0.344945 0.46875,0.5 1.649895,1.461418 3.888519,2.187511 6.6875,2.1875 4.861324,1.1e-5 8.739406,-2.520702 11.65625,-7.53125 z m 3.875,5.21875 c -0.04366,0.0554 -0.08146,0.10153 -0.125,0.15625 0.97366,1.114659 1.851375,2.034323 2.53125,2.59375 0.05858,0.05029 0.128837,0.07581 0.1875,0.125 -0.742667,-0.732421 -1.599645,-1.667381 -2.59375,-2.875 z"
- id="text5058-0"
- sodipodi:nodetypes="ccsccscccccccccccccccccccscscccccsccsccccccc" />
+ <g
+ id="g2048"
+ transform="translate(9.113e-4,0.00104183)">
+ <rect
+ rx="8.5333338"
+ ry="8.5333338"
+ style="fill:url(#linearGradient2050);fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ id="rect2026"
+ width="68.26667"
+ height="68.26667"
+ x="-1.3322676e-15"
+ y="3.0270508e-06" />
+ <rect
+ rx="4.2666626"
+ y="4.2656283"
+ x="4.2657552"
+ height="59.733334"
+ width="59.73333"
+ id="rect2028"
+ style="fill:url(#radialGradient2052);fill-opacity:1;stroke:none;stroke-width:14.93333435"
+ ry="4.2666669" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4811"
+ d="m 4.2669272,4.2645856 -9.11e-4,8.5333334 h 4.267577 v 4.267579 h 8.5332038 v 4.265625 h 4.265625 V 8.5322946 H 25.6 v 8.5332034 h 4.265625 v -4.267579 h 4.267578 v 8.533204 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 v -4.267579 h 4.267579 v 4.267579 h 8.533203 l -1.3e-4,-12.8009124 z"
+ style="opacity:0.6;fill:#593d29;fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ sodipodi:nodetypes="ccccccccccccccccccccccccccc" />
+ <path
+ style="fill:url(#radialGradient4803);fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ d="m 8.5329442,-0.0018207 c -4.7274675,0 -8.5332035,3.805736 -8.5332035,8.533203 v 4.2675787 h 4.265625 V 8.5313823 c 0,-0.521698 0.105433,-1.01339 0.27539,-1.47461 -0.169616,0.460814 -0.27539,0.953462 -0.27539,1.47461 h 4.2675785 v 4.2675787 h 4.2656248 4.267578 v 4.265625 h 4.265625 V 12.798961 8.5313823 4.2657573 h 4.267578 v 4.265625 4.2675787 h 4.265625 V 8.5313823 h 4.267578 v 4.2675787 4.265625 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 V 8.5313823 h 4.267579 v 4.2675787 h 4.265625 4.267578 V 8.5313823 h 4.265625 c 0,-4.727467 -3.805737,-8.533203 -8.533203,-8.533203 z m -3.019531,5.513671 c -0.318089,0.317888 -0.570428,0.695824 -0.7753915,1.101563 0.2048795,-0.405231 0.4576385,-0.784012 0.7753915,-1.101563 z"
+ id="path4794"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:url(#linearGradient2140);fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ d="m 8.5322887,-9.083059e-4 c -4.72747,0 -8.5332,3.8057359059 -8.5332,8.5332029059 V 59.731515 c 0,4.727467 3.80573,8.535156 8.5332,8.535156 H 59.731502 c 4.72747,0 8.5332,-3.807689 8.5332,-8.535156 V 8.5322946 c 0,-4.727467 -3.80573,-8.5332029059 -8.5332,-8.5332029059 z m 0,4.2675779059 H 59.731502 c 2.36373,0 4.26758,1.901892 4.26758,4.265625 V 59.731515 c 0,2.363733 -1.90385,4.267578 -4.26758,4.267578 H 8.5322887 c -2.36373,0 -4.26758,-1.903845 -4.26758,-4.267578 V 8.5322946 c 0,-2.363733 1.90385,-4.265625 4.26758,-4.265625 z"
+ id="path2046"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ id="g1092">
+ <path
+ inkscape:connector-curvature="0"
+ id="path4786"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4790);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
+ d="m 38.886673,44.940882 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.5594458,-3.37376 C 6.7526311,43.114834 5.275567,39.986037 5.2755773,36.088937 5.275567,32.347763 6.7526311,29.207831 9.7067742,26.669132 12.346618,24.419991 15.897857,23.295407 20.360501,23.295373 c 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 19.747676,44.473233 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 50.483209,27.77145 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 -1.759968,-1.558805 -4.132699,-2.338222 -7.118198,-2.338251" />
+ <path
+ d="m 39.715314,45.942156 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.559446,-3.37376 -2.9541431,-2.360505 -4.4312072,-5.489302 -4.4311969,-9.386402 -1.03e-5,-3.741174 1.4770538,-6.881106 4.4311969,-9.419805 2.639844,-2.249141 6.191083,-3.373725 10.653727,-3.373759 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 20.576317,45.474507 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 51.31185,28.772724 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 C 56.67008,29.55217 54.297349,28.772753 51.31185,28.772724"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3293);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
+ id="path3279"
+ inkscape:connector-curvature="0" />
+ <path
+ d="m 37.904564,42.951873 c -0.974278,-0.801672 -2.231352,-2.137814 -3.771231,-4.008428 -2.105642,2.672298 -4.085537,4.598568 -5.939688,5.778817 -2.325625,1.425227 -5.295466,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124577 -10.5594464,-3.37376 -2.9541428,-2.360505 -4.4312068,-5.489302 -4.4311963,-9.386401 -1.05e-5,-3.741175 1.4770535,-6.881107 4.4311963,-9.419805 2.6398444,-2.249142 6.1910824,-3.373727 10.6537284,-3.37376 2.294137,3.3e-5 4.289745,0.334068 5.986829,1.002107 1.979863,0.734909 3.645487,1.737016 4.99688,3.00632 1.257039,1.13575 2.514116,2.471891 3.771231,4.008428 2.105562,-2.672257 4.085456,-4.598528 5.939689,-5.778817 2.325544,-1.425185 5.295387,-2.137795 8.909534,-2.137828 4.242576,3.3e-5 7.762387,1.12462 10.559446,3.373761 2.954062,2.360545 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477135,6.881148 -4.431197,9.419805 -2.639924,2.249182 -6.191164,3.373767 -10.653728,3.37376 -2.294217,7e-6 -4.289826,-0.334028 -5.986828,-1.002107 -1.697101,-0.601254 -3.362727,-1.603361 -4.996882,-3.006321 m -19.138997,-0.46765 c 5.185412,1.3e-5 9.333762,-2.67227 12.445062,-8.016856 -3.991252,-5.834462 -8.139602,-8.751704 -12.445062,-8.751733 -3.142714,2.9e-5 -5.515444,0.801714 -7.118198,2.405056 -1.7284972,1.714743 -2.5927368,3.707819 -2.5927216,5.979239 -1.52e-5,2.49415 0.8642244,4.509496 2.5927216,6.046045 1.759888,1.558845 4.132618,2.338262 7.118198,2.338249 M 49.5011,25.782442 c -4.682663,2.8e-5 -8.831014,2.672311 -12.445063,8.016855 3.959745,5.834504 8.108096,8.751745 12.445063,8.751733 3.142634,1.2e-5 5.515365,-0.801673 7.118198,-2.405056 1.728417,-1.7147 2.592657,-3.707778 2.592721,-5.979238 -6.4e-5,-2.49411 -0.864304,-4.509456 -2.592721,-6.046046 C 54.85933,26.561886 52.486599,25.78247 49.5011,25.782442"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3286);fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ id="path3272"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="ccscsccccccccccccccccccccscscccccsccscccccc"
+ inkscape:connector-curvature="0"
+ id="text5100"
+ d="m 19.4,21.166667 c -4.462644,3.3e-5 -8.026822,1.150858 -10.6666667,3.4 -2.9541428,2.538698 -4.4333436,5.658825 -4.4333333,9.4 -1.03e-5,3.897098 1.4791905,7.039495 4.4333333,9.4 -1.622701,-2.044271 -2.433341,-4.51168 -2.4333333,-7.4 -1.03e-5,-3.741175 1.4791905,-6.861302 4.433333,-9.4 2.639845,-2.249142 6.204023,-3.399967 10.666667,-3.4 2.294138,3.3e-5 4.302916,0.365295 6,1.033333 1.979862,0.73491 3.615274,1.730695 4.966667,3 0.06836,0.06177 0.131637,0.137049 0.2,0.2 -0.731813,-0.797005 -1.468213,-1.538822 -2.2,-2.2 -1.351393,-1.269305 -2.986805,-2.26509 -4.966667,-3 -1.697084,-0.668038 -3.705862,-1.0333 -6,-1.033333 z m 29.6,0.1 c -3.614148,3.3e-5 -6.574457,0.74148 -8.9,2.166666 -1.818222,1.157367 -3.923451,3.291388 -5.983333,5.883334 0.618278,0.658774 1.248369,1.377605 1.866666,2.133333 2.105562,-2.672257 4.262434,-4.836378 6.116667,-6.016667 2.325543,-1.425186 5.285852,-2.166633 8.9,-2.166666 4.242576,3.3e-5 7.769607,1.150858 10.566667,3.4 -0.570388,-0.722129 -1.227721,-1.382884 -2,-2 C 56.769607,22.417525 53.242576,21.2667 49,21.266667 Z m 8.866667,8.1 c 0.9092,1.305235 1.366619,2.857751 1.366666,4.666666 -6.5e-5,2.271461 -0.871584,4.285301 -2.6,6 -1.602834,1.603384 -3.957366,2.400012 -7.1,2.4 -2.653707,8e-6 -5.320858,-1.032242 -7.833333,-3.216666 3.136636,3.509305 6.469807,5.216676 9.833333,5.216666 3.142634,1.2e-5 5.497166,-0.796616 7.1,-2.4 1.728416,-1.714699 2.599935,-3.728539 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496744 -2.6,-6.033333 -0.24943,-0.220921 -0.49262,-0.443723 -0.766666,-0.633333 z m -26.633334,4.966666 c -3.1113,5.344585 -7.247921,8.033345 -12.433333,8.033334 -2.58055,1e-5 -4.543473,-0.352086 -6.208333,-1.516667 0.348871,0.50642 0.590094,0.752276 1.075,1.183333 1.759888,1.558846 4.147753,2.333345 7.133333,2.333334 5.185412,1.1e-5 9.322033,-2.688749 12.433333,-8.033334 z m 4.933334,6.5 c -0.04103,0.05207 -0.09239,0.08182 -0.133334,0.133334 0.687326,0.744419 1.306949,1.359747 1.833334,1.8 -0.529404,-0.580895 -1.078447,-1.178283 -1.7,-1.933334 z"
+ style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.3;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672" />
+ <path
+ sodipodi:nodetypes="ccsccscccccccccccccccccccscscccccsccsccccccc"
+ id="text5058-0"
+ d="m 19.730474,21.54714 c -4.462645,3.3e-5 -8.026823,1.150859 -10.6666669,3.4 -2.9541429,2.538699 -4.433344,5.658826 -4.4333333,9.4 -1.07e-5,3.897099 1.4791904,7.039495 4.4333333,9.4 0.042837,0.03444 0.090155,0.06608 0.1333334,0.1 -2.2392086,-2.228193 -3.3666752,-5.040417 -3.3666667,-8.433333 -1.07e-5,-3.741174 1.4791904,-6.861301 4.4333332,-9.4 2.639844,-2.249141 6.204022,-3.399967 10.666667,-3.4 2.294137,3.3e-5 4.302916,0.365295 6,1.033333 1.870874,0.694455 3.42364,1.628367 4.733333,2.8 -0.314265,-0.308986 -0.652406,-0.582729 -0.966667,-0.866666 -1.351393,-1.269305 -2.986804,-2.265091 -4.966666,-3 -1.697084,-0.668039 -3.705863,-1.033301 -6,-1.033334 z m 29.6,0.1 c -3.614149,3.3e-5 -6.574457,0.741481 -8.9,2.166667 -1.813279,1.154221 -3.963039,3.235656 -6.016667,5.816667 0.355649,0.402628 0.711011,0.798625 1.066667,1.233333 2.105561,-2.672257 4.295767,-4.803044 6.15,-5.983333 2.325543,-1.425187 5.285851,-2.166634 8.9,-2.166667 4.22442,3.3e-5 7.742084,1.136734 10.533333,3.366667 -0.36096,-0.367566 -0.745726,-0.696967 -1.166667,-1.033334 -2.797059,-2.249141 -6.32409,-3.399967 -10.566666,-3.4 z m 8.233333,7.333334 c 1.323326,1.449243 1.999942,3.250987 2,5.433333 -6.5e-5,2.27146 -0.871584,4.2853 -2.6,6 -1.602834,1.603383 -3.957366,2.400012 -7.1,2.4 -2.406328,6e-6 -4.776468,-0.90386 -7.066667,-2.7 2.669147,2.483838 5.436929,3.766674 8.266667,3.766667 3.142634,1.1e-5 5.497166,-0.796617 7.1,-2.4 1.728416,-1.7147 2.599935,-3.72854 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496745 -2.6,-6.033334 -0.185641,-0.164422 -0.400724,-0.319587 -0.6,-0.466666 z m -26,5.733333 c -3.1113,5.344584 -7.247921,8.033345 -12.433333,8.033333 -2.612382,1.1e-5 -4.759372,-0.60651 -6.433334,-1.8 0.166027,0.176488 0.313947,0.367942 0.5,0.533334 1.759888,1.558845 4.147754,2.333345 7.133334,2.333333 5.185412,1.2e-5 9.322033,-2.688749 12.433333,-8.033333 z m 4.133333,5.566667 c -0.04657,0.05909 -0.08689,0.108298 -0.133333,0.166666 1.038571,1.18897 1.9748,2.169945 2.7,2.766667 0.06249,0.05364 0.137426,0.08086 0.2,0.133333 -0.792178,-0.781249 -1.706288,-1.778539 -2.766667,-3.066666 z"
+ style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.6;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ inkscape:connector-curvature="0" />
+ </g>
</g>
</svg>
diff --git a/application/resources/pe_blue/pe_blue.qrc b/application/resources/pe_blue/pe_blue.qrc
index a4525d2e..1706371a 100644
--- a/application/resources/pe_blue/pe_blue.qrc
+++ b/application/resources/pe_blue/pe_blue.qrc
@@ -10,6 +10,7 @@
<file>scalable/copy.svg</file>
<file>scalable/coremods.svg</file>
<file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
@@ -31,5 +32,6 @@
<file>scalable/status-good.svg</file>
<file>scalable/status-yellow.svg</file>
<file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
</qresource>
</RCC>
diff --git a/application/resources/pe_blue/scalable/help.svg b/application/resources/pe_blue/scalable/help.svg
new file mode 100644
index 00000000..e98540cb
--- /dev/null
+++ b/application/resources/pe_blue/scalable/help.svg
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata19"><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><defs
+ id="defs17" /><g
+ id="g12"><circle
+ id="circle2"
+ r="12"
+ cy="16"
+ cx="16"
+ fill="#DAEEFF" /><path
+ id="path10"
+ d="M16,32C7.2,32,0,24.8,0,16C0,7.2,7.2,0,16,0c8.8,0,16,7.2,16,16C32,24.8,24.8,32,16,32L16,32z M16,4 C9.4,4,4,9.4,4,16s5.4,12,12,12s12-5.4,12-12S22.6,4,16,4z"
+ fill="#3366CC" /></g><g
+ transform="translate(41.863574,-2.0092638)"
+ id="g861"><g
+ aria-label="?"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.34714317px;line-height:85.45981598px;font-family:'Nimbus Sans L';-inkscape-font-specification:'Nimbus Sans L';letter-spacing:0px;word-spacing:0px;fill:#666666;fill-opacity:1;stroke:none;stroke-width:3.4183929px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text832"><path
+ d="m -20.968435,13.920532 c 0,0.6381 -0.07293,1.239737 -0.218777,1.804911 -0.145852,0.565174 -0.401092,1.066538 -0.76572,1.504093 -0.401092,0.492249 -0.774836,0.902456 -1.121233,1.230621 -0.346397,0.328166 -0.656332,0.6381 -0.929803,0.929803 -0.25524,0.273471 -0.464902,0.556059 -0.628985,0.847762 -0.145851,0.291703 -0.218777,0.656331 -0.218777,1.093885 v 0.38286 h -3.144921 V 20.72997 c 0,-0.601637 0.136736,-1.221505 0.410207,-1.859605 0.273471,-0.656332 0.756604,-1.285316 1.449399,-1.886953 0.346397,-0.309935 0.638099,-0.583406 0.875108,-0.820415 0.237009,-0.25524 0.428439,-0.492248 0.57429,-0.711025 0.145851,-0.237009 0.246124,-0.483133 0.300819,-0.738373 0.05469,-0.273471 0.08204,-0.57429 0.08204,-0.902456 0,-0.528711 -0.12762,-0.984497 -0.38286,-1.367357 -0.237009,-0.401091 -0.656331,-0.601637 -1.257968,-0.601637 -1.057423,0 -1.640829,0.692794 -1.750218,2.078383 h -3.06288 c 0.01823,-0.747489 0.145853,-1.431167 0.38286,-2.051036 0.237009,-0.6381 0.565175,-1.185043 0.984498,-1.640829 0.419323,-0.474017 0.920687,-0.8386455 1.504092,-1.0938855 0.601637,-0.25524 1.2762,-0.38286 2.023689,-0.38286 0.838646,0 1.567903,0.1367357 2.187771,0.4102072 0.619869,0.25524 1.130349,0.6198684 1.53144,1.0938853 0.401092,0.455786 0.692795,1.002729 0.875109,1.640829 0.200546,0.619869 0.300819,1.294432 0.30082,2.023689 z"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Monofonto;-inkscape-font-specification:Monofonto;fill:#666666;fill-opacity:1;stroke-width:3.4183929px"
+ id="path855" /></g><circle
+ style="fill:#666666"
+ cx="-26.38899"
+ cy="25.466606"
+ r="1.8"
+ id="circle6-6" /></g></svg> \ No newline at end of file
diff --git a/application/resources/pe_blue/scalable/worlds.svg b/application/resources/pe_blue/scalable/worlds.svg
new file mode 100644
index 00000000..1670c035
--- /dev/null
+++ b/application/resources/pe_blue/scalable/worlds.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata45"><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><defs
+ id="defs43" /><path
+ id="path2"
+ d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z"
+ fill="#3366CC"
+ clip-rule="evenodd"
+ fill-rule="evenodd" /><path
+ fill="#DAEEFF"
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ id="path4"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" /><g
+ id="g10" /><g
+ id="g12" /><g
+ id="g14" /><g
+ id="g16" /><g
+ id="g18" /><g
+ id="g20" /><g
+ id="g22" /><g
+ id="g24" /><g
+ id="g26" /><g
+ id="g28" /><g
+ id="g30" /><g
+ id="g32" /><g
+ id="g34" /><g
+ id="g36" /><g
+ id="g38" /><g
+ transform="rotate(29.970903,16,16)"
+ id="g881"><ellipse
+ ry="8.9473686"
+ rx="8.9473696"
+ cy="16"
+ cx="16"
+ id="path845"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><ellipse
+ ry="8.9473686"
+ rx="3.4915254"
+ cy="15.947369"
+ cx="16"
+ id="path845-3"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.1848294,12.430327 C 12.570183,14.790801 20.250652,13.785896 22.94338,12"
+ id="path870" /><path
+ id="path872"
+ d="M 8.2166335,20.492522 C 12.601987,18.132048 21.148735,17.776739 23.841463,19.562635"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/application/resources/pe_colored/pe_colored.qrc b/application/resources/pe_colored/pe_colored.qrc
index 7de8d5ab..588e3ac0 100644
--- a/application/resources/pe_colored/pe_colored.qrc
+++ b/application/resources/pe_colored/pe_colored.qrc
@@ -10,6 +10,7 @@
<file>scalable/copy.svg</file>
<file>scalable/coremods.svg</file>
<file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
@@ -31,5 +32,6 @@
<file>scalable/status-good.svg</file>
<file>scalable/status-yellow.svg</file>
<file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
</qresource>
</RCC>
diff --git a/application/resources/pe_colored/scalable/help.svg b/application/resources/pe_colored/scalable/help.svg
new file mode 100644
index 00000000..c1ee5258
--- /dev/null
+++ b/application/resources/pe_colored/scalable/help.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata23"><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><defs
+ id="defs21" /><circle
+ id="circle2"
+ r="12"
+ cy="16"
+ cx="16"
+ fill="#F2F2F2" /><g
+ id="g16"><path
+ id="path10"
+ d="M16,4c-4,0-7.6,2-9.7,5C4.8,11,4,13.4,4,16c0,6.6,5.4,12,12,12c6.6,0,12-5.4,12-12c0-2.6-0.8-5-2.3-7 C23.6,6,20,4,16,4z"
+ fill="none" /><path
+ id="path12"
+ d="M16,4c4,0,7.6,2,9.7,5h4.6c-2.6-5.3-8-9-14.4-9S4.2,3.7,1.6,9h4.6C8.4,6,12,4,16,4z"
+ fill="#39B54A" /><path
+ id="path14"
+ d="M32,16L32,16c0-2.6-0.6-4.9-1.6-7h-4.6c1.4,2,2.3,4.4,2.3,7c0,6.6-5.4,12-12,12C9.4,28,4,22.6,4,16 c0-2.6,0.8-5,2.3-7H1.6c-1,2.1-1.6,4.5-1.6,7c0,8.8,7.2,16,16,16h0C24.8,32,32,24.9,32,16z"
+ fill="#8C6239" /></g><g
+ transform="translate(41.863574,-2.0092638)"
+ id="g861"><g
+ aria-label="?"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.34714317px;line-height:85.45981598px;font-family:'Nimbus Sans L';-inkscape-font-specification:'Nimbus Sans L';letter-spacing:0px;word-spacing:0px;fill:#666666;fill-opacity:1;stroke:none;stroke-width:3.4183929px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text832"><path
+ d="m -20.968435,13.920532 c 0,0.6381 -0.07293,1.239737 -0.218777,1.804911 -0.145852,0.565174 -0.401092,1.066538 -0.76572,1.504093 -0.401092,0.492249 -0.774836,0.902456 -1.121233,1.230621 -0.346397,0.328166 -0.656332,0.6381 -0.929803,0.929803 -0.25524,0.273471 -0.464902,0.556059 -0.628985,0.847762 -0.145851,0.291703 -0.218777,0.656331 -0.218777,1.093885 v 0.38286 h -3.144921 V 20.72997 c 0,-0.601637 0.136736,-1.221505 0.410207,-1.859605 0.273471,-0.656332 0.756604,-1.285316 1.449399,-1.886953 0.346397,-0.309935 0.638099,-0.583406 0.875108,-0.820415 0.237009,-0.25524 0.428439,-0.492248 0.57429,-0.711025 0.145851,-0.237009 0.246124,-0.483133 0.300819,-0.738373 0.05469,-0.273471 0.08204,-0.57429 0.08204,-0.902456 0,-0.528711 -0.12762,-0.984497 -0.38286,-1.367357 -0.237009,-0.401091 -0.656331,-0.601637 -1.257968,-0.601637 -1.057423,0 -1.640829,0.692794 -1.750218,2.078383 h -3.06288 c 0.01823,-0.747489 0.145853,-1.431167 0.38286,-2.051036 0.237009,-0.6381 0.565175,-1.185043 0.984498,-1.640829 0.419323,-0.474017 0.920687,-0.8386455 1.504092,-1.0938855 0.601637,-0.25524 1.2762,-0.38286 2.023689,-0.38286 0.838646,0 1.567903,0.1367357 2.187771,0.4102072 0.619869,0.25524 1.130349,0.6198684 1.53144,1.0938853 0.401092,0.455786 0.692795,1.002729 0.875109,1.640829 0.200546,0.619869 0.300819,1.294432 0.30082,2.023689 z"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Monofonto;-inkscape-font-specification:Monofonto;fill:#666666;fill-opacity:1;stroke-width:3.4183929px"
+ id="path855" /></g><circle
+ style="fill:#666666"
+ cx="-26.38899"
+ cy="25.466606"
+ r="1.8"
+ id="circle6-6" /></g></svg> \ No newline at end of file
diff --git a/application/resources/pe_colored/scalable/worlds.svg b/application/resources/pe_colored/scalable/worlds.svg
new file mode 100644
index 00000000..087ba7c9
--- /dev/null
+++ b/application/resources/pe_colored/scalable/worlds.svg
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata19"><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><defs
+ id="defs17" /><g
+ id="g6"><path
+ id="path2"
+ d="M26,0H6C2.7,0,0,2.7,0,6v3h32V6C32,2.7,29.3,0,26,0z"
+ fill="#39B54A" /><path
+ id="path4"
+ d="M0,26c0,3.3,2.7,6,6,6h20c3.3,0,6-2.7,6-6V9H0V26z"
+ fill="#8C6239" /></g><path
+ fill="#F2F2F2"
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ id="path8"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z" /><g
+ transform="rotate(29.970903,16,15.973684)"
+ id="g881"><ellipse
+ ry="8.9473686"
+ rx="8.9473696"
+ cy="16"
+ cx="16"
+ id="path845"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><ellipse
+ ry="8.9473686"
+ rx="3.4915254"
+ cy="15.947369"
+ cx="16"
+ id="path845-3"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.1848294,12.430327 C 12.570183,14.790801 20.250652,13.785896 22.94338,12"
+ id="path870" /><path
+ id="path872"
+ d="M 8.2166335,20.492522 C 12.601987,18.132048 21.148735,17.776739 23.841463,19.562635"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/application/resources/pe_dark/pe_dark.qrc b/application/resources/pe_dark/pe_dark.qrc
index cafc2559..3543f590 100644
--- a/application/resources/pe_dark/pe_dark.qrc
+++ b/application/resources/pe_dark/pe_dark.qrc
@@ -10,6 +10,7 @@
<file>scalable/copy.svg</file>
<file>scalable/coremods.svg</file>
<file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
@@ -31,5 +32,6 @@
<file>scalable/status-good.svg</file>
<file>scalable/status-yellow.svg</file>
<file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
</qresource>
</RCC>
diff --git a/application/resources/pe_dark/scalable/help.svg b/application/resources/pe_dark/scalable/help.svg
new file mode 100644
index 00000000..2a1518ae
--- /dev/null
+++ b/application/resources/pe_dark/scalable/help.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata17"><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><defs
+ id="defs15" /><g
+ id="g10"><path
+ id="path8"
+ d="M16,32C7.2,32,0,24.8,0,16C0,7.2,7.2,0,16,0c8.8,0,16,7.2,16,16C32,24.8,24.8,32,16,32L16,32z M16,4C9.4,4,4,9.4,4,16 s5.4,12,12,12s12-5.4,12-12S22.6,4,16,4z" /><g
+ transform="translate(41.863574,-2.0092638)"
+ id="g861"><g
+ aria-label="?"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.34714317px;line-height:85.45981598px;font-family:'Nimbus Sans L';-inkscape-font-specification:'Nimbus Sans L';letter-spacing:0px;word-spacing:0px;fill:#666666;fill-opacity:1;stroke:none;stroke-width:3.4183929px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text832"><path
+ d="m -20.968435,13.920532 c 0,0.6381 -0.07293,1.239737 -0.218777,1.804911 -0.145852,0.565174 -0.401092,1.066538 -0.76572,1.504093 -0.401092,0.492249 -0.774836,0.902456 -1.121233,1.230621 -0.346397,0.328166 -0.656332,0.6381 -0.929803,0.929803 -0.25524,0.273471 -0.464902,0.556059 -0.628985,0.847762 -0.145851,0.291703 -0.218777,0.656331 -0.218777,1.093885 v 0.38286 h -3.144921 V 20.72997 c 0,-0.601637 0.136736,-1.221505 0.410207,-1.859605 0.273471,-0.656332 0.756604,-1.285316 1.449399,-1.886953 0.346397,-0.309935 0.638099,-0.583406 0.875108,-0.820415 0.237009,-0.25524 0.428439,-0.492248 0.57429,-0.711025 0.145851,-0.237009 0.246124,-0.483133 0.300819,-0.738373 0.05469,-0.273471 0.08204,-0.57429 0.08204,-0.902456 0,-0.528711 -0.12762,-0.984497 -0.38286,-1.367357 -0.237009,-0.401091 -0.656331,-0.601637 -1.257968,-0.601637 -1.057423,0 -1.640829,0.692794 -1.750218,2.078383 h -3.06288 c 0.01823,-0.747489 0.145853,-1.431167 0.38286,-2.051036 0.237009,-0.6381 0.565175,-1.185043 0.984498,-1.640829 0.419323,-0.474017 0.920687,-0.8386455 1.504092,-1.0938855 0.601637,-0.25524 1.2762,-0.38286 2.023689,-0.38286 0.838646,0 1.567903,0.1367357 2.187771,0.4102072 0.619869,0.25524 1.130349,0.6198684 1.53144,1.0938853 0.401092,0.455786 0.692795,1.002729 0.875109,1.640829 0.200546,0.619869 0.300819,1.294432 0.30082,2.023689 z"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Monofonto;-inkscape-font-specification:Monofonto;fill:#666666;fill-opacity:1;stroke-width:3.4183929px"
+ id="path855" /></g><circle
+ style="fill:#666666"
+ cx="-26.38899"
+ cy="25.466606"
+ r="1.8"
+ id="circle6-6" /></g></g></svg> \ No newline at end of file
diff --git a/application/resources/pe_dark/scalable/worlds.svg b/application/resources/pe_dark/scalable/worlds.svg
new file mode 100644
index 00000000..2b01070f
--- /dev/null
+++ b/application/resources/pe_dark/scalable/worlds.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata45"><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><defs
+ id="defs43" /><path
+ id="path2"
+ d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6v20 C32,29.3,29.3,32,26,32z"
+ clip-rule="evenodd"
+ fill-rule="evenodd" /><path
+ id="path4"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
+ fill="#F2F2F2"
+ clip-rule="evenodd"
+ fill-rule="evenodd" /><g
+ id="g10" /><g
+ id="g12" /><g
+ id="g14" /><g
+ id="g16" /><g
+ id="g18" /><g
+ id="g20" /><g
+ id="g22" /><g
+ id="g24" /><g
+ id="g26" /><g
+ id="g28" /><g
+ id="g30" /><g
+ id="g32" /><g
+ id="g34" /><g
+ id="g36" /><g
+ id="g38" /><g
+ style="stroke:#666666;stroke-opacity:1"
+ transform="rotate(29.970903,16,16)"
+ id="g881"><ellipse
+ ry="8.9473686"
+ rx="8.9473696"
+ cy="16"
+ cx="16"
+ id="path845"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><ellipse
+ ry="8.9473686"
+ rx="3.4915254"
+ cy="15.947369"
+ cx="16"
+ id="path845-3"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.1848294,12.430327 C 12.570183,14.790801 20.250652,13.785896 22.94338,12"
+ id="path870" /><path
+ id="path872"
+ d="M 8.2166335,20.492522 C 12.601987,18.132048 21.148735,17.776739 23.841463,19.562635"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#666666;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/application/resources/pe_light/pe_light.qrc b/application/resources/pe_light/pe_light.qrc
index 53d0890b..f83d6eba 100644
--- a/application/resources/pe_light/pe_light.qrc
+++ b/application/resources/pe_light/pe_light.qrc
@@ -10,6 +10,7 @@
<file>scalable/copy.svg</file>
<file>scalable/coremods.svg</file>
<file>scalable/externaltools.svg</file>
+ <file>scalable/help.svg</file>
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
@@ -31,6 +32,7 @@
<file>scalable/status-good.svg</file>
<file>scalable/status-yellow.svg</file>
<file>scalable/viewfolder.svg</file>
+ <file>scalable/worlds.svg</file>
</qresource>
</RCC>
diff --git a/application/resources/pe_light/scalable/help.svg b/application/resources/pe_light/scalable/help.svg
new file mode 100644
index 00000000..f820c679
--- /dev/null
+++ b/application/resources/pe_light/scalable/help.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata17"><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><defs
+ id="defs15" /><g
+ id="g10"><path
+ id="path8"
+ d="M16,32C7.2,32,0,24.8,0,16C0,7.2,7.2,0,16,0c8.8,0,16,7.2,16,16C32,24.8,24.8,32,16,32L16,32z M16,4 C9.4,4,4,9.4,4,16s5.4,12,12,12s12-5.4,12-12S22.6,4,16,4z"
+ fill="#F2F2F2" /></g><g
+ style="fill:#f2f2f2;fill-opacity:1"
+ transform="translate(41.863574,-2.0092638)"
+ id="g861"><g
+ aria-label="?"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.34714317px;line-height:85.45981598px;font-family:'Nimbus Sans L';-inkscape-font-specification:'Nimbus Sans L';letter-spacing:0px;word-spacing:0px;fill:#f2f2f2;fill-opacity:1;stroke:none;stroke-width:3.4183929px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text832"><path
+ d="m -20.968435,13.920532 c 0,0.6381 -0.07293,1.239737 -0.218777,1.804911 -0.145852,0.565174 -0.401092,1.066538 -0.76572,1.504093 -0.401092,0.492249 -0.774836,0.902456 -1.121233,1.230621 -0.346397,0.328166 -0.656332,0.6381 -0.929803,0.929803 -0.25524,0.273471 -0.464902,0.556059 -0.628985,0.847762 -0.145851,0.291703 -0.218777,0.656331 -0.218777,1.093885 v 0.38286 h -3.144921 V 20.72997 c 0,-0.601637 0.136736,-1.221505 0.410207,-1.859605 0.273471,-0.656332 0.756604,-1.285316 1.449399,-1.886953 0.346397,-0.309935 0.638099,-0.583406 0.875108,-0.820415 0.237009,-0.25524 0.428439,-0.492248 0.57429,-0.711025 0.145851,-0.237009 0.246124,-0.483133 0.300819,-0.738373 0.05469,-0.273471 0.08204,-0.57429 0.08204,-0.902456 0,-0.528711 -0.12762,-0.984497 -0.38286,-1.367357 -0.237009,-0.401091 -0.656331,-0.601637 -1.257968,-0.601637 -1.057423,0 -1.640829,0.692794 -1.750218,2.078383 h -3.06288 c 0.01823,-0.747489 0.145853,-1.431167 0.38286,-2.051036 0.237009,-0.6381 0.565175,-1.185043 0.984498,-1.640829 0.419323,-0.474017 0.920687,-0.8386455 1.504092,-1.0938855 0.601637,-0.25524 1.2762,-0.38286 2.023689,-0.38286 0.838646,0 1.567903,0.1367357 2.187771,0.4102072 0.619869,0.25524 1.130349,0.6198684 1.53144,1.0938853 0.401092,0.455786 0.692795,1.002729 0.875109,1.640829 0.200546,0.619869 0.300819,1.294432 0.30082,2.023689 z"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:Monofonto;-inkscape-font-specification:Monofonto;fill:#f2f2f2;fill-opacity:1;stroke-width:3.4183929px"
+ id="path855" /></g><circle
+ style="fill:#f2f2f2;fill-opacity:1"
+ cx="-26.38899"
+ cy="25.466606"
+ r="1.8"
+ id="circle6-6" /></g></svg> \ No newline at end of file
diff --git a/application/resources/pe_light/scalable/worlds.svg b/application/resources/pe_light/scalable/worlds.svg
new file mode 100644
index 00000000..bf4c21a3
--- /dev/null
+++ b/application/resources/pe_light/scalable/worlds.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata45"><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><defs
+ id="defs43" /><path
+ id="path2"
+ d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z"
+ fill="#F2F2F2"
+ clip-rule="evenodd"
+ fill-rule="evenodd" /><path
+ id="path4"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
+ fill="#4D4D4D"
+ clip-rule="evenodd"
+ fill-rule="evenodd" /><g
+ id="g10" /><g
+ id="g12" /><g
+ id="g14" /><g
+ id="g16" /><g
+ id="g18" /><g
+ id="g20" /><g
+ id="g22" /><g
+ id="g24" /><g
+ id="g26" /><g
+ id="g28" /><g
+ id="g30" /><g
+ id="g32" /><g
+ id="g34" /><g
+ id="g36" /><g
+ id="g38" /><g
+ style="stroke:#ffffff;stroke-opacity:1"
+ transform="rotate(29.970903,16,16)"
+ id="g881"><ellipse
+ ry="8.9473686"
+ rx="8.9473696"
+ cy="16"
+ cx="16"
+ id="path845"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><ellipse
+ ry="8.9473686"
+ rx="3.4915254"
+ cy="15.947369"
+ cx="16"
+ id="path845-3"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" /><path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 8.1848294,12.430327 C 12.570183,14.790801 20.250652,13.785896 22.94338,12"
+ id="path870" /><path
+ id="path872"
+ d="M 8.2166335,20.492522 C 12.601987,18.132048 21.148735,17.776739 23.841463,19.562635"
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /></g></svg> \ No newline at end of file
diff --git a/application/resources/sources/multimc-discord.svg b/application/resources/sources/multimc-discord.svg
new file mode 100644
index 00000000..c3c73044
--- /dev/null
+++ b/application/resources/sources/multimc-discord.svg
@@ -0,0 +1,265 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<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"
+ version="1.1"
+ id="svg4427"
+ height="68.26667"
+ width="68.26667">
+ <defs
+ id="defs4429">
+ <linearGradient
+ id="linearGradient4809">
+ <stop
+ id="stop4805"
+ offset="0"
+ style="stop-color:#98c867;stop-opacity:1" />
+ <stop
+ id="stop4807"
+ offset="1"
+ style="stop-color:#5c9a33;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5668">
+ <stop
+ style="stop-color:#75b54b;stop-opacity:1;"
+ offset="0"
+ id="stop5670" />
+ <stop
+ style="stop-color:#75b54b;stop-opacity:0.6"
+ offset="1"
+ id="stop5672" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient5084">
+ <stop
+ style="stop-color:#000000;stop-opacity:0.8"
+ offset="0"
+ id="stop5086" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0.35"
+ offset="1"
+ id="stop5088" />
+ </linearGradient>
+ <linearGradient
+ gradientTransform="translate(-0.01532073,-0.00938002)"
+ gradientUnits="userSpaceOnUse"
+ y2="61.773685"
+ x2="50.506943"
+ y1="28.510933"
+ x1="6.7342591"
+ id="linearGradient5072"
+ xlink:href="#linearGradient5668" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="82.973114"
+ x2="44.097023"
+ y1="9.7948904"
+ x1="14.312115"
+ id="linearGradient5082"
+ xlink:href="#linearGradient5084" />
+ <linearGradient
+ y2="61.773685"
+ x2="50.506943"
+ y1="28.510933"
+ x1="6.7342591"
+ gradientTransform="translate(-0.01532073,-0.00938002)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3281"
+ xlink:href="#linearGradient5668" />
+ <linearGradient
+ y2="61.773685"
+ x2="50.506943"
+ y1="28.510933"
+ x1="6.7342591"
+ gradientTransform="translate(-0.01532073,-0.00938002)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3283"
+ xlink:href="#linearGradient5668" />
+ <linearGradient
+ y2="61.773685"
+ x2="50.506943"
+ y1="28.510933"
+ x1="6.7342591"
+ gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.01941371,-0.00842234)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3286"
+ xlink:href="#linearGradient5668" />
+ <linearGradient
+ y2="82.973114"
+ x2="44.097023"
+ y1="9.7948904"
+ x1="14.312115"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3288"
+ xlink:href="#linearGradient5084" />
+ <linearGradient
+ y2="82.973114"
+ x2="44.097023"
+ y1="9.7948904"
+ x1="14.312115"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3290"
+ xlink:href="#linearGradient5084" />
+ <linearGradient
+ gradientTransform="scale(1.2671525,0.89790119)"
+ y2="82.973114"
+ x2="44.097023"
+ y1="9.7948904"
+ x1="14.312115"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient3293"
+ xlink:href="#linearGradient5084" />
+ <linearGradient
+ id="linearGradient5580">
+ <stop
+ id="stop5576"
+ offset="0"
+ style="stop-color:#000000;stop-opacity:0.0627451" />
+ <stop
+ id="stop5578"
+ offset="1"
+ style="stop-color:#322217;stop-opacity:0.58823532" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3999">
+ <stop
+ style="stop-color:#a3704b;stop-opacity:1"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#6a4a33;stop-opacity:1"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient2727">
+ <stop
+ style="stop-color:#966c4a;stop-opacity:1"
+ offset="0"
+ id="stop2723" />
+ <stop
+ style="stop-color:#593d29;stop-opacity:1"
+ offset="1"
+ id="stop2725" />
+ </linearGradient>
+ <linearGradient
+ y2="97.065842"
+ x2="86.415741"
+ y1="33.80484"
+ x1="36.546478"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2050"
+ xlink:href="#linearGradient2727" />
+ <radialGradient
+ r="29.866665"
+ fy="34.133335"
+ fx="34.133331"
+ cy="34.133335"
+ cx="34.133331"
+ gradientTransform="matrix(1.1428572,0,0,1.1428572,-4.8771039,-4.8772393)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient2052"
+ xlink:href="#linearGradient3999" />
+ <linearGradient
+ y2="38.400913"
+ x2="38.400005"
+ y1="29.867579"
+ x1="29.866674"
+ gradientTransform="translate(77.635668,-7.276299)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient2140"
+ xlink:href="#linearGradient5580" />
+ <linearGradient
+ y2="82.973114"
+ x2="44.097023"
+ y1="9.7948904"
+ x1="14.312115"
+ gradientTransform="matrix(1.2671525,0,0,0.89790119,-0.82864077,-1.0012743)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient4790"
+ xlink:href="#linearGradient5084" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.7500268,0.1250019,-0.01781176,0.24936465,95.393964,18.110151)"
+ r="34.132812"
+ fy="-34.134373"
+ fx="-42.66758"
+ cy="-34.134373"
+ cx="-42.66758"
+ id="radialGradient4803"
+ xlink:href="#linearGradient4809" />
+ </defs>
+ <metadata
+ id="metadata4432">
+ <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">
+ <g
+ transform="translate(9.113e-4,0.00104183)"
+ id="g2048">
+ <rect
+ y="3.0270508e-06"
+ x="-1.3322676e-15"
+ height="68.26667"
+ width="68.26667"
+ id="rect2026"
+ style="fill:url(#linearGradient2050);fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ ry="8.5333338"
+ rx="8.5333338" />
+ <rect
+ ry="0"
+ style="fill:url(#radialGradient2052);fill-opacity:1;stroke:none;stroke-width:17.06666946"
+ id="rect2028"
+ width="68.26667"
+ height="68.26667"
+ x="-0.00091432704"
+ y="-0.0010418301"
+ rx="0" />
+ <path
+ style="opacity:0.6;fill:#593d29;fill-opacity:1;stroke:none;stroke-width:17.06666756"
+ d="m 4.2669272,4.2645856 -9.11e-4,8.5333334 h 4.267577 v 4.267579 h 8.5332038 v 4.265625 h 4.265625 V 8.5322946 H 25.6 v 8.5332034 h 4.265625 v -4.267579 h 4.267578 v 8.533204 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 v -4.267579 h 4.267579 v 4.267579 h 8.533203 l -1.3e-4,-12.8009124 z"
+ id="path4811" />
+ <path
+ id="path4794"
+ d="m -9.113e-4,-0.0010388 6.52e-4,8.5324211 v 4.2675787 h 4.265625 V 8.5313823 c 0,-0.521698 0.105433,-1.01339 0.27539,-1.47461 -0.169616,0.460814 -0.27539,0.953462 -0.27539,1.47461 h 4.2675785 v 4.2675787 h 4.2656248 4.267578 v 4.265625 h 4.265625 V 12.798961 8.5313823 4.2657573 h 4.267578 v 4.265625 4.2675787 h 4.265625 V 8.5313823 h 4.267578 v 4.2675787 4.265625 h 4.265625 v -4.265625 h 4.267578 v 4.265625 h 4.267579 v -4.265625 h 4.265624 V 8.5313823 h 4.267579 v 4.2675787 h 4.265625 4.267578 V 8.5313823 h 4.265625 l 3.9e-4,-8.5324211 z m 5.5143245,5.5128891 c -0.318089,0.317888 -0.570428,0.695824 -0.7753915,1.101563 0.2048795,-0.405231 0.4576385,-0.784012 0.7753915,-1.101563 z"
+ style="fill:url(#radialGradient4803);fill-opacity:1;stroke:none;stroke-width:17.06666756" />
+ </g>
+ <g
+ id="g1092">
+ <path
+ d="m 38.886673,44.940882 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.5594458,-3.37376 C 6.7526311,43.114834 5.275567,39.986037 5.2755773,36.088937 5.275567,32.347763 6.7526311,29.207831 9.7067742,26.669132 12.346618,24.419991 15.897857,23.295407 20.360501,23.295373 c 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 19.747676,44.473233 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 50.483209,27.77145 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 -1.759968,-1.558805 -4.132699,-2.338222 -7.118198,-2.338251"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient4790);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
+ id="path4786" />
+ <path
+ id="path3279"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3293);fill-opacity:1;stroke:none;stroke-width:1.06666672;opacity:0.5"
+ d="m 39.715314,45.942156 c -0.974277,-0.801673 -2.231353,-2.137814 -3.771231,-4.008427 -2.105641,2.672298 -4.085536,4.598569 -5.939688,5.778816 -2.325625,1.425227 -5.295467,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124578 -10.559446,-3.37376 -2.9541431,-2.360505 -4.4312072,-5.489302 -4.4311969,-9.386402 -1.03e-5,-3.741174 1.4770538,-6.881106 4.4311969,-9.419805 2.639844,-2.249141 6.191083,-3.373725 10.653727,-3.373759 2.294138,3.4e-5 4.289747,0.334069 5.986829,1.002107 1.979863,0.73491 3.645488,1.737016 4.996881,3.00632 1.257039,1.135751 2.514115,2.471891 3.771231,4.008428 2.105563,-2.672257 4.085457,-4.598527 5.939689,-5.778816 2.325544,-1.425186 5.295385,-2.137794 8.909533,-2.137828 4.242577,3.4e-5 7.762388,1.124618 10.559447,3.37376 2.954063,2.360546 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477134,6.881147 -4.431197,9.419806 -2.639925,2.24918 -6.191163,3.373767 -10.653727,3.373758 -2.294219,9e-6 -4.289826,-0.334026 -5.98683,-1.002106 -1.697101,-0.601255 -3.362726,-1.603361 -4.996881,-3.006321 M 20.576317,45.474507 c 5.185412,1.1e-5 9.333763,-2.672271 12.445062,-8.016856 -3.991253,-5.834464 -8.139602,-8.751705 -12.445062,-8.751733 -3.142715,2.8e-5 -5.515446,0.801713 -7.118198,2.405057 -1.728498,1.71474 -2.592737,3.707818 -2.592722,5.979236 -1.5e-5,2.494152 0.864224,4.509499 2.592722,6.046046 1.759887,1.558846 4.132618,2.338261 7.118198,2.33825 M 51.31185,28.772724 c -4.682663,2.9e-5 -8.831013,2.672312 -12.445062,8.016856 3.959745,5.834503 8.108095,8.751746 12.445062,8.751733 3.142633,1.3e-5 5.515364,-0.801671 7.118198,-2.405056 1.728416,-1.714701 2.592656,-3.707778 2.592722,-5.979238 -6.6e-5,-2.49411 -0.864306,-4.509456 -2.592722,-6.046044 C 56.67008,29.55217 54.297349,28.772753 51.31185,28.772724" />
+ <path
+ id="path3272"
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.18933868px;line-height:125%;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans';letter-spacing:0px;word-spacing:0px;fill:url(#linearGradient3286);fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ d="m 37.904564,42.951873 c -0.974278,-0.801672 -2.231352,-2.137814 -3.771231,-4.008428 -2.105642,2.672298 -4.085537,4.598568 -5.939688,5.778817 -2.325625,1.425227 -5.295466,2.137836 -8.909534,2.137828 -4.242656,8e-6 -7.762467,-1.124577 -10.5594464,-3.37376 -2.9541428,-2.360505 -4.4312068,-5.489302 -4.4311963,-9.386401 -1.05e-5,-3.741175 1.4770535,-6.881107 4.4311963,-9.419805 2.6398444,-2.249142 6.1910824,-3.373727 10.6537284,-3.37376 2.294137,3.3e-5 4.289745,0.334068 5.986829,1.002107 1.979863,0.734909 3.645487,1.737016 4.99688,3.00632 1.257039,1.13575 2.514116,2.471891 3.771231,4.008428 2.105562,-2.672257 4.085456,-4.598528 5.939689,-5.778817 2.325544,-1.425185 5.295387,-2.137795 8.909534,-2.137828 4.242576,3.3e-5 7.762387,1.12462 10.559446,3.373761 2.954062,2.360545 4.431127,5.489343 4.431197,9.386401 -7e-5,3.741216 -1.477135,6.881148 -4.431197,9.419805 -2.639924,2.249182 -6.191164,3.373767 -10.653728,3.37376 -2.294217,7e-6 -4.289826,-0.334028 -5.986828,-1.002107 -1.697101,-0.601254 -3.362727,-1.603361 -4.996882,-3.006321 m -19.138997,-0.46765 c 5.185412,1.3e-5 9.333762,-2.67227 12.445062,-8.016856 -3.991252,-5.834462 -8.139602,-8.751704 -12.445062,-8.751733 -3.142714,2.9e-5 -5.515444,0.801714 -7.118198,2.405056 -1.7284972,1.714743 -2.5927368,3.707819 -2.5927216,5.979239 -1.52e-5,2.49415 0.8642244,4.509496 2.5927216,6.046045 1.759888,1.558845 4.132618,2.338262 7.118198,2.338249 M 49.5011,25.782442 c -4.682663,2.8e-5 -8.831014,2.672311 -12.445063,8.016855 3.959745,5.834504 8.108096,8.751745 12.445063,8.751733 3.142634,1.2e-5 5.515365,-0.801673 7.118198,-2.405056 1.728417,-1.7147 2.592657,-3.707778 2.592721,-5.979238 -6.4e-5,-2.49411 -0.864304,-4.509456 -2.592721,-6.046046 C 54.85933,26.561886 52.486599,25.78247 49.5011,25.782442" />
+ <path
+ style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.3;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ d="m 19.4,21.166667 c -4.462644,3.3e-5 -8.026822,1.150858 -10.6666667,3.4 -2.9541428,2.538698 -4.4333436,5.658825 -4.4333333,9.4 -1.03e-5,3.897098 1.4791905,7.039495 4.4333333,9.4 -1.622701,-2.044271 -2.433341,-4.51168 -2.4333333,-7.4 -1.03e-5,-3.741175 1.4791905,-6.861302 4.433333,-9.4 2.639845,-2.249142 6.204023,-3.399967 10.666667,-3.4 2.294138,3.3e-5 4.302916,0.365295 6,1.033333 1.979862,0.73491 3.615274,1.730695 4.966667,3 0.06836,0.06177 0.131637,0.137049 0.2,0.2 -0.731813,-0.797005 -1.468213,-1.538822 -2.2,-2.2 -1.351393,-1.269305 -2.986805,-2.26509 -4.966667,-3 -1.697084,-0.668038 -3.705862,-1.0333 -6,-1.033333 z m 29.6,0.1 c -3.614148,3.3e-5 -6.574457,0.74148 -8.9,2.166666 -1.818222,1.157367 -3.923451,3.291388 -5.983333,5.883334 0.618278,0.658774 1.248369,1.377605 1.866666,2.133333 2.105562,-2.672257 4.262434,-4.836378 6.116667,-6.016667 2.325543,-1.425186 5.285852,-2.166633 8.9,-2.166666 4.242576,3.3e-5 7.769607,1.150858 10.566667,3.4 -0.570388,-0.722129 -1.227721,-1.382884 -2,-2 C 56.769607,22.417525 53.242576,21.2667 49,21.266667 Z m 8.866667,8.1 c 0.9092,1.305235 1.366619,2.857751 1.366666,4.666666 -6.5e-5,2.271461 -0.871584,4.285301 -2.6,6 -1.602834,1.603384 -3.957366,2.400012 -7.1,2.4 -2.653707,8e-6 -5.320858,-1.032242 -7.833333,-3.216666 3.136636,3.509305 6.469807,5.216676 9.833333,5.216666 3.142634,1.2e-5 5.497166,-0.796616 7.1,-2.4 1.728416,-1.714699 2.599935,-3.728539 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496744 -2.6,-6.033333 -0.24943,-0.220921 -0.49262,-0.443723 -0.766666,-0.633333 z m -26.633334,4.966666 c -3.1113,5.344585 -7.247921,8.033345 -12.433333,8.033334 -2.58055,1e-5 -4.543473,-0.352086 -6.208333,-1.516667 0.348871,0.50642 0.590094,0.752276 1.075,1.183333 1.759888,1.558846 4.147753,2.333345 7.133333,2.333334 5.185412,1.1e-5 9.322033,-2.688749 12.433333,-8.033334 z m 4.933334,6.5 c -0.04103,0.05207 -0.09239,0.08182 -0.133334,0.133334 0.687326,0.744419 1.306949,1.359747 1.833334,1.8 -0.529404,-0.580895 -1.078447,-1.178283 -1.7,-1.933334 z"
+ id="text5100" />
+ <path
+ style="font-style:normal;font-weight:normal;font-size:76.18933868px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.6;fill:#ccff00;fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ d="m 19.730474,21.54714 c -4.462645,3.3e-5 -8.026823,1.150859 -10.6666669,3.4 -2.9541429,2.538699 -4.433344,5.658826 -4.4333333,9.4 -1.07e-5,3.897099 1.4791904,7.039495 4.4333333,9.4 0.042837,0.03444 0.090155,0.06608 0.1333334,0.1 -2.2392086,-2.228193 -3.3666752,-5.040417 -3.3666667,-8.433333 -1.07e-5,-3.741174 1.4791904,-6.861301 4.4333332,-9.4 2.639844,-2.249141 6.204022,-3.399967 10.666667,-3.4 2.294137,3.3e-5 4.302916,0.365295 6,1.033333 1.870874,0.694455 3.42364,1.628367 4.733333,2.8 -0.314265,-0.308986 -0.652406,-0.582729 -0.966667,-0.866666 -1.351393,-1.269305 -2.986804,-2.265091 -4.966666,-3 -1.697084,-0.668039 -3.705863,-1.033301 -6,-1.033334 z m 29.6,0.1 c -3.614149,3.3e-5 -6.574457,0.741481 -8.9,2.166667 -1.813279,1.154221 -3.963039,3.235656 -6.016667,5.816667 0.355649,0.402628 0.711011,0.798625 1.066667,1.233333 2.105561,-2.672257 4.295767,-4.803044 6.15,-5.983333 2.325543,-1.425187 5.285851,-2.166634 8.9,-2.166667 4.22442,3.3e-5 7.742084,1.136734 10.533333,3.366667 -0.36096,-0.367566 -0.745726,-0.696967 -1.166667,-1.033334 -2.797059,-2.249141 -6.32409,-3.399967 -10.566666,-3.4 z m 8.233333,7.333334 c 1.323326,1.449243 1.999942,3.250987 2,5.433333 -6.5e-5,2.27146 -0.871584,4.2853 -2.6,6 -1.602834,1.603383 -3.957366,2.400012 -7.1,2.4 -2.406328,6e-6 -4.776468,-0.90386 -7.066667,-2.7 2.669147,2.483838 5.436929,3.766674 8.266667,3.766667 3.142634,1.1e-5 5.497166,-0.796617 7.1,-2.4 1.728416,-1.7147 2.599935,-3.72854 2.6,-6 -6.5e-5,-2.49411 -0.871584,-4.496745 -2.6,-6.033334 -0.185641,-0.164422 -0.400724,-0.319587 -0.6,-0.466666 z m -26,5.733333 c -3.1113,5.344584 -7.247921,8.033345 -12.433333,8.033333 -2.612382,1.1e-5 -4.759372,-0.60651 -6.433334,-1.8 0.166027,0.176488 0.313947,0.367942 0.5,0.533334 1.759888,1.558845 4.147754,2.333345 7.133334,2.333333 5.185412,1.2e-5 9.322033,-2.688749 12.433333,-8.033333 z m 4.133333,5.566667 c -0.04657,0.05909 -0.08689,0.108298 -0.133333,0.166666 1.038571,1.18897 1.9748,2.169945 2.7,2.766667 0.06249,0.05364 0.137426,0.08086 0.2,0.133333 -0.792178,-0.781249 -1.706288,-1.778539 -2.766667,-3.066666 z"
+ id="text5058-0" />
+ </g>
+ </g>
+</svg>
diff --git a/application/setupwizard/JavaWizardPage.cpp b/application/setupwizard/JavaWizardPage.cpp
index 46b6ee3f..cd2bca46 100644
--- a/application/setupwizard/JavaWizardPage.cpp
+++ b/application/setupwizard/JavaWizardPage.cpp
@@ -17,184 +17,35 @@
#include <sys.h>
#include <QFileDialog>
#include <JavaCommon.h>
+#include "widgets/JavaSettingsWidget.h"
JavaWizardPage::JavaWizardPage(QWidget *parent)
:BaseWizardPage(parent)
{
- m_availableMemory = Sys::getSystemRam() / (1024ull * 1024ull);
-
- goodIcon = MMC->getThemedIcon("status-good");
- yellowIcon = MMC->getThemedIcon("status-yellow");
- badIcon = MMC->getThemedIcon("status-bad");
setupUi();
-
- connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
- connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
- connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
- connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaWizardPage::javaVersionSelected);
- connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaWizardPage::on_javaBrowseBtn_clicked);
- connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaWizardPage::javaPathEdited);
- connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaWizardPage::on_javaStatusBtn_clicked);
}
void JavaWizardPage::setupUi()
{
setObjectName(QStringLiteral("javaPage"));
- m_verticalLayout = new QVBoxLayout(this);
- m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
-
- m_versionWidget = new VersionSelectWidget(MMC->javalist().get(), this);
- m_versionWidget->setResizeOn(2);
- m_verticalLayout->addWidget(m_versionWidget);
-
- m_horizontalLayout = new QHBoxLayout();
- m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
- m_javaPathTextBox = new QLineEdit(this);
- m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox"));
-
- m_horizontalLayout->addWidget(m_javaPathTextBox);
-
- m_javaBrowseBtn = new QPushButton(this);
- m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn"));
- /*
- QSizePolicy sizePolicy2(QSizePolicy::Fixed, QSizePolicy::Fixed);
- sizePolicy2.setHorizontalStretch(0);
- sizePolicy2.setVerticalStretch(0);
- sizePolicy2.setHeightForWidth(m_javaBrowseBtn->sizePolicy().hasHeightForWidth());
- m_javaBrowseBtn->setSizePolicy(sizePolicy2);
- m_javaBrowseBtn->setMaximumSize(QSize(28, 16777215));
- */
- m_horizontalLayout->addWidget(m_javaBrowseBtn);
-
- m_javaStatusBtn = new QToolButton(this);
- m_javaStatusBtn->setIcon(yellowIcon);
- m_horizontalLayout->addWidget(m_javaStatusBtn);
-
- m_verticalLayout->addLayout(m_horizontalLayout);
-
- m_memoryGroupBox = new QGroupBox(this);
- m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox"));
- m_gridLayout_2 = new QGridLayout(m_memoryGroupBox);
- m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2"));
-
- m_labelMinMem = new QLabel(m_memoryGroupBox);
- m_labelMinMem->setObjectName(QStringLiteral("labelMinMem"));
- m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1);
-
- m_minMemSpinBox = new QSpinBox(m_memoryGroupBox);
- m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox"));
- m_minMemSpinBox->setSuffix(QStringLiteral(" MB"));
- m_minMemSpinBox->setMinimum(128);
- m_minMemSpinBox->setMaximum(m_availableMemory);
- m_minMemSpinBox->setSingleStep(128);
- m_labelMinMem->setBuddy(m_minMemSpinBox);
- m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1);
-
- m_labelMaxMem = new QLabel(m_memoryGroupBox);
- m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem"));
- m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1);
-
- m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox);
- m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox"));
- m_maxMemSpinBox->setSuffix(QStringLiteral(" MB"));
- m_maxMemSpinBox->setMinimum(128);
- m_maxMemSpinBox->setMaximum(m_availableMemory);
- m_maxMemSpinBox->setSingleStep(128);
- m_labelMaxMem->setBuddy(m_maxMemSpinBox);
- m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1);
+ QVBoxLayout * layout = new QVBoxLayout(this);
- m_labelPermGen = new QLabel(m_memoryGroupBox);
- m_labelPermGen->setObjectName(QStringLiteral("labelPermGen"));
- m_labelPermGen->setText(QStringLiteral("PermGen:"));
- m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1);
- m_labelPermGen->setVisible(false);
-
- m_permGenSpinBox = new QSpinBox(m_memoryGroupBox);
- m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox"));
- m_permGenSpinBox->setSuffix(QStringLiteral(" MB"));
- m_permGenSpinBox->setMinimum(64);
- m_permGenSpinBox->setMaximum(m_availableMemory);
- m_permGenSpinBox->setSingleStep(8);
- m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1);
- m_permGenSpinBox->setVisible(false);
-
- m_verticalLayout->addWidget(m_memoryGroupBox);
+ m_java_widget = new JavaSettingsWidget(this);
+ layout->addWidget(m_java_widget);
+ setLayout(layout);
retranslate();
}
void JavaWizardPage::refresh()
{
- m_versionWidget->loadList();
+ m_java_widget->refresh();
}
void JavaWizardPage::initializePage()
{
- m_versionWidget->initialize();
- auto s = MMC->settings();
- // Memory
- observedMinMemory = s->get("MinMemAlloc").toInt();
- observedMaxMemory = s->get("MaxMemAlloc").toInt();
- observedPermGenMemory = s->get("PermGen").toInt();
- m_minMemSpinBox->setValue(observedMinMemory);
- m_maxMemSpinBox->setValue(observedMaxMemory);
- m_permGenSpinBox->setValue(observedPermGenMemory);
-}
-
-bool JavaWizardPage::validatePage()
-{
- auto settings = MMC->settings();
- auto path = m_javaPathTextBox->text();
- switch(javaStatus)
- {
- case JavaStatus::NotSet:
- case JavaStatus::DoesNotExist:
- case JavaStatus::DoesNotStart:
- case JavaStatus::ReturnedInvalidData:
- {
- int button = CustomMessageBox::selectable(
- this,
- tr("No Java version selected"),
- tr("You didn't select a Java version or selected something that doesn't work.\n"
- "MultiMC will not be able to start Minecraft.\n"
- "Do you wish to proceed without any Java?"
- "\n\n"
- "You can change the Java version in the settings later.\n"
- ),
- QMessageBox::Warning,
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::NoButton
- )->exec();
- if(button == QMessageBox::No)
- {
- return false;
- }
- }
- break;
- case JavaStatus::Pending:
- {
- return false;
- }
- case JavaStatus::Good:
- {
- settings->set("JavaPath", path);
- }
- }
-
- // Memory
- auto s = MMC->settings();
- s->set("MinMemAlloc", m_minMemSpinBox->value());
- s->set("MaxMemAlloc", m_maxMemSpinBox->value());
- if (m_permGenSpinBox->isVisible())
- {
- s->set("PermGen", m_permGenSpinBox->value());
- }
- else
- {
- s->reset("PermGen");
- }
- return true;
+ m_java_widget->initialize();
}
bool JavaWizardPage::wantsRefreshButton()
@@ -202,221 +53,37 @@ bool JavaWizardPage::wantsRefreshButton()
return true;
}
-void JavaWizardPage::memoryValueChanged(int)
+bool JavaWizardPage::validatePage()
{
- bool actuallyChanged = false;
- int min = m_minMemSpinBox->value();
- int max = m_maxMemSpinBox->value();
- int permgen = m_permGenSpinBox->value();
- QObject *obj = sender();
- if (obj == m_minMemSpinBox && min != observedMinMemory)
+ auto settings = MMC->settings();
+ auto result = m_java_widget->validate();
+ switch(result)
{
- observedMinMemory = min;
- actuallyChanged = true;
- if (min > max)
+ default:
+ case JavaSettingsWidget::ValidationStatus::Bad:
{
- observedMaxMemory = min;
- m_maxMemSpinBox->setValue(min);
+ return false;
}
- }
- else if (obj == m_maxMemSpinBox && max != observedMaxMemory)
- {
- observedMaxMemory = max;
- actuallyChanged = true;
- if (min > max)
+ case JavaSettingsWidget::ValidationStatus::AllOK:
{
- observedMinMemory = max;
- m_minMemSpinBox->setValue(max);
+ settings->set("JavaPath", m_java_widget->javaPath());
}
- }
- else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory)
- {
- observedPermGenMemory = permgen;
- actuallyChanged = true;
- }
- if(actuallyChanged)
- {
- checkJavaPathOnEdit(m_javaPathTextBox->text());
- }
-}
-
-void JavaWizardPage::javaVersionSelected(BaseVersionPtr version)
-{
- auto java = std::dynamic_pointer_cast<JavaInstall>(version);
- if(!java)
- {
- return;
- }
- auto visible = java->id.requiresPermGen();
- m_labelPermGen->setVisible(visible);
- m_permGenSpinBox->setVisible(visible);
- m_javaPathTextBox->setText(java->path);
- checkJavaPath(java->path);
-}
-
-void JavaWizardPage::on_javaBrowseBtn_clicked()
-{
- QString filter;
-#if defined Q_OS_WIN32
- filter = "Java (javaw.exe)";
-#else
- filter = "Java (java)";
-#endif
- QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter);
- if(raw_path.isNull())
- {
- return;
- }
- QString cooked_path = FS::NormalizePath(raw_path);
- m_javaPathTextBox->setText(cooked_path);
- checkJavaPath(cooked_path);
-}
-
-void JavaWizardPage::on_javaStatusBtn_clicked()
-{
- QString text;
- bool failed = false;
- switch(javaStatus)
- {
- case JavaStatus::NotSet:
- checkJavaPath(m_javaPathTextBox->text());
- return;
- case JavaStatus::DoesNotExist:
- text += QObject::tr("The specified file either doesn't exist or is not a proper executable.");
- failed = true;
- break;
- case JavaStatus::DoesNotStart:
+ case JavaSettingsWidget::ValidationStatus::JavaBad:
{
- text += QObject::tr("The specified java binary didn't start properly.<br />");
- auto htmlError = m_result.errorLog;
- if(!htmlError.isEmpty())
+ // Memory
+ auto s = MMC->settings();
+ s->set("MinMemAlloc", m_java_widget->minHeapSize());
+ s->set("MaxMemAlloc", m_java_widget->maxHeapSize());
+ if (m_java_widget->permGenEnabled())
{
- htmlError.replace('\n', "<br />");
- text += QString("<font color=\"red\">%1</font>").arg(htmlError);
+ s->set("PermGen", m_java_widget->permGenSize());
}
- failed = true;
- break;
- }
- case JavaStatus::ReturnedInvalidData:
- {
- text += QObject::tr("The specified java binary returned unexpected results:<br />");
- auto htmlOut = m_result.outLog;
- if(!htmlOut.isEmpty())
+ else
{
- htmlOut.replace('\n', "<br />");
- text += QString("<font color=\"red\">%1</font>").arg(htmlOut);
+ s->reset("PermGen");
}
- failed = true;
- break;
- }
- case JavaStatus::Good:
- text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version "
- "reported: %2<br />").arg(m_result.realPlatform, m_result.javaVersion.toString());
- break;
- case JavaStatus::Pending:
- // TODO: abort here?
- return;
- }
- CustomMessageBox::selectable(
- this,
- failed ? QObject::tr("Java test success") : QObject::tr("Java test failure"),
- text,
- failed ? QMessageBox::Critical : QMessageBox::Information
- )->show();
-}
-
-void JavaWizardPage::setJavaStatus(JavaWizardPage::JavaStatus status)
-{
- javaStatus = status;
- switch(javaStatus)
- {
- case JavaStatus::Good:
- m_javaStatusBtn->setIcon(goodIcon);
- break;
- case JavaStatus::NotSet:
- case JavaStatus::Pending:
- m_javaStatusBtn->setIcon(yellowIcon);
- break;
- default:
- m_javaStatusBtn->setIcon(badIcon);
- break;
- }
-}
-
-void JavaWizardPage::javaPathEdited(const QString& path)
-{
- checkJavaPathOnEdit(path);
-}
-
-void JavaWizardPage::checkJavaPathOnEdit(const QString& path)
-{
- auto realPath = FS::ResolveExecutable(path);
- QFileInfo pathInfo(realPath);
- if (pathInfo.baseName().toLower().contains("java"))
- {
- checkJavaPath(path);
- }
- else
- {
- if(!m_checker)
- {
- setJavaStatus(JavaStatus::NotSet);
- }
- }
-}
-
-void JavaWizardPage::checkJavaPath(const QString &path)
-{
- if(m_checker)
- {
- queuedCheck = path;
- return;
- }
- auto realPath = FS::ResolveExecutable(path);
- if(realPath.isNull())
- {
- setJavaStatus(JavaStatus::DoesNotExist);
- return;
- }
- setJavaStatus(JavaStatus::Pending);
- m_checker.reset(new JavaChecker());
- m_checker->m_path = path;
- m_checker->m_minMem = m_minMemSpinBox->value();
- m_checker->m_maxMem = m_maxMemSpinBox->value();
- if(m_permGenSpinBox->isVisible())
- {
- m_checker->m_permGen = m_permGenSpinBox->value();
- }
- connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaWizardPage::checkFinished);
- m_checker->performCheck();
-}
-
-void JavaWizardPage::checkFinished(JavaCheckResult result)
-{
- m_result = result;
- switch(result.validity)
- {
- case JavaCheckResult::Validity::Valid:
- {
- setJavaStatus(JavaStatus::Good);
- break;
+ return true;
}
- case JavaCheckResult::Validity::ReturnedInvalidData:
- {
- setJavaStatus(JavaStatus::ReturnedInvalidData);
- break;
- }
- case JavaCheckResult::Validity::Errored:
- {
- setJavaStatus(JavaStatus::DoesNotStart);
- break;
- }
- }
- m_checker.reset();
- if(!queuedCheck.isNull())
- {
- checkJavaPath(queuedCheck);
- queuedCheck.clear();
}
}
@@ -425,11 +92,5 @@ void JavaWizardPage::retranslate()
setTitle(tr("Java"));
setSubTitle(tr("You do not have a working Java set up yet or it went missing.\n"
"Please select one of the following or browse for a java executable."));
- m_memoryGroupBox->setTitle(tr("Memory"));
- m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use."));
- m_labelMinMem->setText(tr("Minimum memory allocation:"));
- m_labelMaxMem->setText(tr("Maximum memory allocation:"));
- m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with."));
- m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes."));
- m_javaBrowseBtn->setText(tr("Browse"));
+ m_java_widget->retranslate();
}
diff --git a/application/setupwizard/JavaWizardPage.h b/application/setupwizard/JavaWizardPage.h
index f240217e..4ea31d24 100644
--- a/application/setupwizard/JavaWizardPage.h
+++ b/application/setupwizard/JavaWizardPage.h
@@ -1,21 +1,8 @@
#pragma once
#include "BaseWizardPage.h"
-#include <BaseVersion.h>
-#include <QObjectPtr.h>
-#include <java/JavaChecker.h>
-#include <QIcon>
-class QLineEdit;
-class VersionSelectWidget;
-class QSpinBox;
-class QPushButton;
-class QVBoxLayout;
-class QHBoxLayout;
-class QGroupBox;
-class QGridLayout;
-class QLabel;
-class QToolButton;
+class JavaSettingsWidget;
class JavaWizardPage : public BaseWizardPage
{
@@ -32,58 +19,11 @@ public:
void initializePage() override;
bool validatePage() override;
- enum class JavaStatus
- {
- NotSet,
- Pending,
- Good,
- DoesNotExist,
- DoesNotStart,
- ReturnedInvalidData
- } javaStatus = JavaStatus::NotSet;
-
-protected slots:
- void memoryValueChanged(int);
- void javaPathEdited(const QString &path);
- void javaVersionSelected(BaseVersionPtr version);
- void on_javaBrowseBtn_clicked();
- void on_javaStatusBtn_clicked();
- void checkFinished(JavaCheckResult result);
-
protected: /* methods */
- void checkJavaPathOnEdit(const QString &path);
- void checkJavaPath(const QString &path);
- void setJavaStatus(JavaStatus status);
void setupUi();
void retranslate() override;
private: /* data */
- VersionSelectWidget *m_versionWidget = nullptr;
- QVBoxLayout *m_verticalLayout = nullptr;
-
- QLineEdit * m_javaPathTextBox = nullptr;
- QPushButton * m_javaBrowseBtn = nullptr;
- QToolButton * m_javaStatusBtn = nullptr;
- QHBoxLayout *m_horizontalLayout = nullptr;
-
- QGroupBox *m_memoryGroupBox = nullptr;
- QGridLayout *m_gridLayout_2 = nullptr;
- QSpinBox *m_maxMemSpinBox = nullptr;
- QLabel *m_labelMinMem = nullptr;
- QLabel *m_labelMaxMem = nullptr;
- QSpinBox *m_minMemSpinBox = nullptr;
- QLabel *m_labelPermGen = nullptr;
- QSpinBox *m_permGenSpinBox = nullptr;
- QIcon goodIcon;
- QIcon yellowIcon;
- QIcon badIcon;
-
- int observedMinMemory = 0;
- int observedMaxMemory = 0;
- int observedPermGenMemory = 0;
- QString queuedCheck;
- uint64_t m_availableMemory = 0ull;
- shared_qobject_ptr<JavaChecker> m_checker;
- JavaCheckResult m_result;
+ JavaSettingsWidget *m_java_widget = nullptr;
};
diff --git a/application/setupwizard/SetupWizard.h b/application/setupwizard/SetupWizard.h
index a336f575..e3292997 100644
--- a/application/setupwizard/SetupWizard.h
+++ b/application/setupwizard/SetupWizard.h
@@ -1,4 +1,4 @@
-/* Copyright 2017 MultiMC Contributors
+/* Copyright 2017-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/themes/SystemTheme.cpp b/application/themes/SystemTheme.cpp
index 069b0b1b..a9ee853a 100644
--- a/application/themes/SystemTheme.cpp
+++ b/application/themes/SystemTheme.cpp
@@ -9,7 +9,7 @@ SystemTheme::SystemTheme()
const auto & style = QApplication::style();
systemPalette = style->standardPalette();
QString lowerThemeName = style->objectName();
- qWarning() << systemTheme;
+ qDebug() << systemTheme;
QStringList styles = QStyleFactory::keys();
for(auto &st: styles)
{
@@ -21,7 +21,7 @@ SystemTheme::SystemTheme()
}
// fall back to fusion if we can't find the current theme.
systemTheme = "Fusion";
- qWarning() << "System theme not found, defaulted to Fusion";
+ qDebug() << "System theme not found, defaulted to Fusion";
}
void SystemTheme::apply(bool initial)
diff --git a/application/widgets/CustomCommands.cpp b/application/widgets/CustomCommands.cpp
new file mode 100644
index 00000000..9f11e344
--- /dev/null
+++ b/application/widgets/CustomCommands.cpp
@@ -0,0 +1,48 @@
+#include "CustomCommands.h"
+#include "ui_CustomCommands.h"
+
+CustomCommands::~CustomCommands()
+{
+}
+
+CustomCommands::CustomCommands(QWidget* parent):
+ QWidget(parent),
+ ui(new Ui::CustomCommands)
+{
+ ui->setupUi(this);
+}
+
+void CustomCommands::initialize(bool checkable, bool checked, const QString& prelaunch, const QString& wrapper, const QString& postexit)
+{
+ ui->customCommandsGroupBox->setCheckable(checkable);
+ if(checkable)
+ {
+ ui->customCommandsGroupBox->setChecked(checked);
+ }
+ ui->preLaunchCmdTextBox->setText(prelaunch);
+ ui->wrapperCmdTextBox->setText(wrapper);
+ ui->postExitCmdTextBox->setText(postexit);
+}
+
+
+bool CustomCommands::checked() const
+{
+ if(!ui->customCommandsGroupBox->isCheckable())
+ return true;
+ return ui->customCommandsGroupBox->isChecked();
+}
+
+QString CustomCommands::prelaunchCommand() const
+{
+ return ui->preLaunchCmdTextBox->text();
+}
+
+QString CustomCommands::wrapperCommand() const
+{
+ return ui->wrapperCmdTextBox->text();
+}
+
+QString CustomCommands::postexitCommand() const
+{
+ return ui->postExitCmdTextBox->text();
+}
diff --git a/application/widgets/CustomCommands.h b/application/widgets/CustomCommands.h
new file mode 100644
index 00000000..2bc7cb1a
--- /dev/null
+++ b/application/widgets/CustomCommands.h
@@ -0,0 +1,43 @@
+/* Copyright 2018-2018 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>
+
+namespace Ui
+{
+class CustomCommands;
+}
+
+class CustomCommands : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit CustomCommands(QWidget *parent = 0);
+ ~CustomCommands();
+ void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit);
+
+ bool checked() const;
+ QString prelaunchCommand() const;
+ QString wrapperCommand() const;
+ QString postexitCommand() const;
+
+private:
+ Ui::CustomCommands *ui;
+};
+
+
diff --git a/application/widgets/CustomCommands.ui b/application/widgets/CustomCommands.ui
new file mode 100644
index 00000000..44104460
--- /dev/null
+++ b/application/widgets/CustomCommands.ui
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CustomCommands</class>
+ <widget class="QWidget" name="CustomCommands">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>518</width>
+ <height>646</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="customCommandsGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Cus&amp;tom Commands</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item row="2" column="0">
+ <widget class="QLabel" name="labelPostExitCmd">
+ <property name="text">
+ <string>Post-exit command:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="preLaunchCmdTextBox"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="labelPreLaunchCmd">
+ <property name="text">
+ <string>Pre-launch command:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="postExitCmdTextBox"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="labelWrapperCmd">
+ <property name="text">
+ <string>Wrapper command:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="wrapperCmdTextBox"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="labelCustomCmdsDescription">
+ <property name="text">
+ <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pre-launch command runs before the instance launches and post-exit command runs after it exits.&lt;/p&gt;&lt;p&gt;Both will be run in MultiMC's working folder with extra environment variables:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;INST_NAME - Name of the instance&lt;/li&gt;&lt;li&gt;INST_ID - ID of the instance&lt;/li&gt;&lt;li&gt;INST_DIR - absolute path of the instance&lt;/li&gt;&lt;li&gt;INST_MC_DIR - absolute path of minecraft&lt;/li&gt;&lt;li&gt;INST_JAVA - java binary used for launch&lt;/li&gt;&lt;li&gt;INST_JAVA_ARGS - command-line parameters used for launch&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Wrapper command allows launching using an extra wrapper program (like 'optirun' on Linux)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+ </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>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/application/widgets/FtbModpackListItem.cpp b/application/widgets/FtbModpackListItem.cpp
new file mode 100644
index 00000000..874e0eac
--- /dev/null
+++ b/application/widgets/FtbModpackListItem.cpp
@@ -0,0 +1,8 @@
+#include "FtbModpackListItem.h"
+
+FtbModpackListItem::FtbModpackListItem(QListWidget *list, FtbModpack modpack) : QListWidgetItem(list), modpack(modpack) {
+}
+
+FtbModpack FtbModpackListItem::getModpack(){
+ return modpack;
+}
diff --git a/application/widgets/FtbModpackListItem.h b/application/widgets/FtbModpackListItem.h
new file mode 100644
index 00000000..977cad2d
--- /dev/null
+++ b/application/widgets/FtbModpackListItem.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "QListWidget"
+#include <modplatform/PackHelpers.h>
+
+class FtbModpackListItem : public QListWidgetItem {
+
+private:
+ FtbModpack modpack;
+
+public:
+ FtbModpackListItem(QListWidget *list, FtbModpack modpack);
+ FtbModpack getModpack();
+
+};
diff --git a/application/widgets/JavaSettingsWidget.cpp b/application/widgets/JavaSettingsWidget.cpp
new file mode 100644
index 00000000..13cd27e7
--- /dev/null
+++ b/application/widgets/JavaSettingsWidget.cpp
@@ -0,0 +1,428 @@
+#include "JavaSettingsWidget.h"
+#include <MultiMC.h>
+
+#include <java/JavaInstall.h>
+#include <dialogs/CustomMessageBox.h>
+#include <java/JavaUtils.h>
+#include <sys.h>
+
+#include <QVBoxLayout>
+#include <QGroupBox>
+#include <QSpinBox>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QToolButton>
+#include <widgets/VersionSelectWidget.h>
+#include <FileSystem.h>
+#include <QFileDialog>
+
+JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent)
+{
+ m_availableMemory = Sys::getSystemRam() / Sys::megabyte;
+
+ goodIcon = MMC->getThemedIcon("status-good");
+ yellowIcon = MMC->getThemedIcon("status-yellow");
+ badIcon = MMC->getThemedIcon("status-bad");
+ setupUi();
+
+ connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
+ connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
+ connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int)));
+ connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected);
+ connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked);
+ connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited);
+ connect(m_javaStatusBtn, &QToolButton::clicked, this, &JavaSettingsWidget::on_javaStatusBtn_clicked);
+}
+
+void JavaSettingsWidget::setupUi()
+{
+ setObjectName(QStringLiteral("javaSettingsWidget"));
+ m_verticalLayout = new QVBoxLayout(this);
+ m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
+
+ m_versionWidget = new VersionSelectWidget(MMC->javalist().get(), this);
+ m_versionWidget->setResizeOn(2);
+ m_verticalLayout->addWidget(m_versionWidget);
+
+ m_horizontalLayout = new QHBoxLayout();
+ m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
+ m_javaPathTextBox = new QLineEdit(this);
+ m_javaPathTextBox->setObjectName(QStringLiteral("javaPathTextBox"));
+
+ m_horizontalLayout->addWidget(m_javaPathTextBox);
+
+ m_javaBrowseBtn = new QPushButton(this);
+ m_javaBrowseBtn->setObjectName(QStringLiteral("javaBrowseBtn"));
+
+ m_horizontalLayout->addWidget(m_javaBrowseBtn);
+
+ m_javaStatusBtn = new QToolButton(this);
+ m_javaStatusBtn->setIcon(yellowIcon);
+ m_horizontalLayout->addWidget(m_javaStatusBtn);
+
+ m_verticalLayout->addLayout(m_horizontalLayout);
+
+ m_memoryGroupBox = new QGroupBox(this);
+ m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox"));
+ m_gridLayout_2 = new QGridLayout(m_memoryGroupBox);
+ m_gridLayout_2->setObjectName(QStringLiteral("gridLayout_2"));
+
+ m_labelMinMem = new QLabel(m_memoryGroupBox);
+ m_labelMinMem->setObjectName(QStringLiteral("labelMinMem"));
+ m_gridLayout_2->addWidget(m_labelMinMem, 0, 0, 1, 1);
+
+ m_minMemSpinBox = new QSpinBox(m_memoryGroupBox);
+ m_minMemSpinBox->setObjectName(QStringLiteral("minMemSpinBox"));
+ m_minMemSpinBox->setSuffix(QStringLiteral(" MB"));
+ m_minMemSpinBox->setMinimum(128);
+ m_minMemSpinBox->setMaximum(m_availableMemory);
+ m_minMemSpinBox->setSingleStep(128);
+ m_labelMinMem->setBuddy(m_minMemSpinBox);
+ m_gridLayout_2->addWidget(m_minMemSpinBox, 0, 1, 1, 1);
+
+ m_labelMaxMem = new QLabel(m_memoryGroupBox);
+ m_labelMaxMem->setObjectName(QStringLiteral("labelMaxMem"));
+ m_gridLayout_2->addWidget(m_labelMaxMem, 1, 0, 1, 1);
+
+ m_maxMemSpinBox = new QSpinBox(m_memoryGroupBox);
+ m_maxMemSpinBox->setObjectName(QStringLiteral("maxMemSpinBox"));
+ m_maxMemSpinBox->setSuffix(QStringLiteral(" MB"));
+ m_maxMemSpinBox->setMinimum(128);
+ m_maxMemSpinBox->setMaximum(m_availableMemory);
+ m_maxMemSpinBox->setSingleStep(128);
+ m_labelMaxMem->setBuddy(m_maxMemSpinBox);
+ m_gridLayout_2->addWidget(m_maxMemSpinBox, 1, 1, 1, 1);
+
+ m_labelPermGen = new QLabel(m_memoryGroupBox);
+ m_labelPermGen->setObjectName(QStringLiteral("labelPermGen"));
+ m_labelPermGen->setText(QStringLiteral("PermGen:"));
+ m_gridLayout_2->addWidget(m_labelPermGen, 2, 0, 1, 1);
+ m_labelPermGen->setVisible(false);
+
+ m_permGenSpinBox = new QSpinBox(m_memoryGroupBox);
+ m_permGenSpinBox->setObjectName(QStringLiteral("permGenSpinBox"));
+ m_permGenSpinBox->setSuffix(QStringLiteral(" MB"));
+ m_permGenSpinBox->setMinimum(64);
+ m_permGenSpinBox->setMaximum(m_availableMemory);
+ m_permGenSpinBox->setSingleStep(8);
+ m_gridLayout_2->addWidget(m_permGenSpinBox, 2, 1, 1, 1);
+ m_permGenSpinBox->setVisible(false);
+
+ m_verticalLayout->addWidget(m_memoryGroupBox);
+
+ retranslate();
+}
+
+void JavaSettingsWidget::initialize()
+{
+ m_versionWidget->initialize();
+ auto s = MMC->settings();
+ // Memory
+ observedMinMemory = s->get("MinMemAlloc").toInt();
+ observedMaxMemory = s->get("MaxMemAlloc").toInt();
+ observedPermGenMemory = s->get("PermGen").toInt();
+ m_minMemSpinBox->setValue(observedMinMemory);
+ m_maxMemSpinBox->setValue(observedMaxMemory);
+ m_permGenSpinBox->setValue(observedPermGenMemory);
+}
+
+void JavaSettingsWidget::refresh()
+{
+ m_versionWidget->loadList();
+}
+
+JavaSettingsWidget::ValidationStatus JavaSettingsWidget::validate()
+{
+ switch(javaStatus)
+ {
+ default:
+ case JavaStatus::NotSet:
+ case JavaStatus::DoesNotExist:
+ case JavaStatus::DoesNotStart:
+ case JavaStatus::ReturnedInvalidData:
+ {
+ int button = CustomMessageBox::selectable(
+ this,
+ tr("No Java version selected"),
+ tr("You didn't select a Java version or selected something that doesn't work.\n"
+ "MultiMC will not be able to start Minecraft.\n"
+ "Do you wish to proceed without any Java?"
+ "\n\n"
+ "You can change the Java version in the settings later.\n"
+ ),
+ QMessageBox::Warning,
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::NoButton
+ )->exec();
+ if(button == QMessageBox::No)
+ {
+ return ValidationStatus::Bad;
+ }
+ return ValidationStatus::JavaBad;
+ }
+ break;
+ case JavaStatus::Pending:
+ {
+ return ValidationStatus::Bad;
+ }
+ case JavaStatus::Good:
+ {
+ return ValidationStatus::AllOK;
+ }
+ }
+}
+
+QString JavaSettingsWidget::javaPath() const
+{
+ return m_javaPathTextBox->text();
+}
+
+int JavaSettingsWidget::maxHeapSize() const
+{
+ return m_maxMemSpinBox->value();
+}
+
+int JavaSettingsWidget::minHeapSize() const
+{
+ return m_minMemSpinBox->value();
+}
+
+bool JavaSettingsWidget::permGenEnabled() const
+{
+ return m_permGenSpinBox->isVisible();
+}
+
+int JavaSettingsWidget::permGenSize() const
+{
+ return m_permGenSpinBox->value();
+}
+
+void JavaSettingsWidget::memoryValueChanged(int)
+{
+ bool actuallyChanged = false;
+ int min = m_minMemSpinBox->value();
+ int max = m_maxMemSpinBox->value();
+ int permgen = m_permGenSpinBox->value();
+ QObject *obj = sender();
+ if (obj == m_minMemSpinBox && min != observedMinMemory)
+ {
+ observedMinMemory = min;
+ actuallyChanged = true;
+ if (min > max)
+ {
+ observedMaxMemory = min;
+ m_maxMemSpinBox->setValue(min);
+ }
+ }
+ else if (obj == m_maxMemSpinBox && max != observedMaxMemory)
+ {
+ observedMaxMemory = max;
+ actuallyChanged = true;
+ if (min > max)
+ {
+ observedMinMemory = max;
+ m_minMemSpinBox->setValue(max);
+ }
+ }
+ else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory)
+ {
+ observedPermGenMemory = permgen;
+ actuallyChanged = true;
+ }
+ if(actuallyChanged)
+ {
+ checkJavaPathOnEdit(m_javaPathTextBox->text());
+ }
+}
+
+void JavaSettingsWidget::javaVersionSelected(BaseVersionPtr version)
+{
+ auto java = std::dynamic_pointer_cast<JavaInstall>(version);
+ if(!java)
+ {
+ return;
+ }
+ auto visible = java->id.requiresPermGen();
+ m_labelPermGen->setVisible(visible);
+ m_permGenSpinBox->setVisible(visible);
+ m_javaPathTextBox->setText(java->path);
+ checkJavaPath(java->path);
+}
+
+void JavaSettingsWidget::on_javaBrowseBtn_clicked()
+{
+ QString filter;
+#if defined Q_OS_WIN32
+ filter = "Java (javaw.exe)";
+#else
+ filter = "Java (java)";
+#endif
+ QString raw_path = QFileDialog::getOpenFileName(this, tr("Find Java executable"), QString(), filter);
+ if(raw_path.isEmpty())
+ {
+ return;
+ }
+ QString cooked_path = FS::NormalizePath(raw_path);
+ m_javaPathTextBox->setText(cooked_path);
+ checkJavaPath(cooked_path);
+}
+
+void JavaSettingsWidget::on_javaStatusBtn_clicked()
+{
+ QString text;
+ bool failed = false;
+ switch(javaStatus)
+ {
+ case JavaStatus::NotSet:
+ checkJavaPath(m_javaPathTextBox->text());
+ return;
+ case JavaStatus::DoesNotExist:
+ text += QObject::tr("The specified file either doesn't exist or is not a proper executable.");
+ failed = true;
+ break;
+ case JavaStatus::DoesNotStart:
+ {
+ text += QObject::tr("The specified java binary didn't start properly.<br />");
+ auto htmlError = m_result.errorLog;
+ if(!htmlError.isEmpty())
+ {
+ htmlError.replace('\n', "<br />");
+ text += QString("<font color=\"red\">%1</font>").arg(htmlError);
+ }
+ failed = true;
+ break;
+ }
+ case JavaStatus::ReturnedInvalidData:
+ {
+ text += QObject::tr("The specified java binary returned unexpected results:<br />");
+ auto htmlOut = m_result.outLog;
+ if(!htmlOut.isEmpty())
+ {
+ htmlOut.replace('\n', "<br />");
+ text += QString("<font color=\"red\">%1</font>").arg(htmlOut);
+ }
+ failed = true;
+ break;
+ }
+ case JavaStatus::Good:
+ text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version "
+ "reported: %2<br />").arg(m_result.realPlatform, m_result.javaVersion.toString());
+ break;
+ case JavaStatus::Pending:
+ // TODO: abort here?
+ return;
+ }
+ CustomMessageBox::selectable(
+ this,
+ failed ? QObject::tr("Java test success") : QObject::tr("Java test failure"),
+ text,
+ failed ? QMessageBox::Critical : QMessageBox::Information
+ )->show();
+}
+
+void JavaSettingsWidget::setJavaStatus(JavaSettingsWidget::JavaStatus status)
+{
+ javaStatus = status;
+ switch(javaStatus)
+ {
+ case JavaStatus::Good:
+ m_javaStatusBtn->setIcon(goodIcon);
+ break;
+ case JavaStatus::NotSet:
+ case JavaStatus::Pending:
+ m_javaStatusBtn->setIcon(yellowIcon);
+ break;
+ default:
+ m_javaStatusBtn->setIcon(badIcon);
+ break;
+ }
+}
+
+void JavaSettingsWidget::javaPathEdited(const QString& path)
+{
+ checkJavaPathOnEdit(path);
+}
+
+void JavaSettingsWidget::checkJavaPathOnEdit(const QString& path)
+{
+ auto realPath = FS::ResolveExecutable(path);
+ QFileInfo pathInfo(realPath);
+ if (pathInfo.baseName().toLower().contains("java"))
+ {
+ checkJavaPath(path);
+ }
+ else
+ {
+ if(!m_checker)
+ {
+ setJavaStatus(JavaStatus::NotSet);
+ }
+ }
+}
+
+void JavaSettingsWidget::checkJavaPath(const QString &path)
+{
+ if(m_checker)
+ {
+ queuedCheck = path;
+ return;
+ }
+ auto realPath = FS::ResolveExecutable(path);
+ if(realPath.isNull())
+ {
+ setJavaStatus(JavaStatus::DoesNotExist);
+ return;
+ }
+ setJavaStatus(JavaStatus::Pending);
+ m_checker.reset(new JavaChecker());
+ m_checker->m_path = path;
+ m_checker->m_minMem = m_minMemSpinBox->value();
+ m_checker->m_maxMem = m_maxMemSpinBox->value();
+ if(m_permGenSpinBox->isVisible())
+ {
+ m_checker->m_permGen = m_permGenSpinBox->value();
+ }
+ connect(m_checker.get(), &JavaChecker::checkFinished, this, &JavaSettingsWidget::checkFinished);
+ m_checker->performCheck();
+}
+
+void JavaSettingsWidget::checkFinished(JavaCheckResult result)
+{
+ m_result = result;
+ switch(result.validity)
+ {
+ case JavaCheckResult::Validity::Valid:
+ {
+ setJavaStatus(JavaStatus::Good);
+ break;
+ }
+ case JavaCheckResult::Validity::ReturnedInvalidData:
+ {
+ setJavaStatus(JavaStatus::ReturnedInvalidData);
+ break;
+ }
+ case JavaCheckResult::Validity::Errored:
+ {
+ setJavaStatus(JavaStatus::DoesNotStart);
+ break;
+ }
+ }
+ m_checker.reset();
+ if(!queuedCheck.isNull())
+ {
+ checkJavaPath(queuedCheck);
+ queuedCheck.clear();
+ }
+}
+
+void JavaSettingsWidget::retranslate()
+{
+ m_memoryGroupBox->setTitle(tr("Memory"));
+ m_maxMemSpinBox->setToolTip(tr("The maximum amount of memory Minecraft is allowed to use."));
+ m_labelMinMem->setText(tr("Minimum memory allocation:"));
+ m_labelMaxMem->setText(tr("Maximum memory allocation:"));
+ m_minMemSpinBox->setToolTip(tr("The amount of memory Minecraft is started with."));
+ m_permGenSpinBox->setToolTip(tr("The amount of memory available to store loaded Java classes."));
+ m_javaBrowseBtn->setText(tr("Browse"));
+}
diff --git a/application/widgets/JavaSettingsWidget.h b/application/widgets/JavaSettingsWidget.h
new file mode 100644
index 00000000..3a94f851
--- /dev/null
+++ b/application/widgets/JavaSettingsWidget.h
@@ -0,0 +1,102 @@
+#pragma once
+#include <QWidget>
+
+#include <java/JavaChecker.h>
+#include <BaseVersion.h>
+#include <QObjectPtr.h>
+#include <QIcon>
+
+class QLineEdit;
+class VersionSelectWidget;
+class QSpinBox;
+class QPushButton;
+class QVBoxLayout;
+class QHBoxLayout;
+class QGroupBox;
+class QGridLayout;
+class QLabel;
+class QToolButton;
+
+/**
+ * This is a widget for all the Java settings dialogs and pages.
+ */
+class JavaSettingsWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit JavaSettingsWidget(QWidget *parent);
+ virtual ~JavaSettingsWidget() {};
+
+ enum class JavaStatus
+ {
+ NotSet,
+ Pending,
+ Good,
+ DoesNotExist,
+ DoesNotStart,
+ ReturnedInvalidData
+ } javaStatus = JavaStatus::NotSet;
+
+ enum class ValidationStatus
+ {
+ Bad,
+ JavaBad,
+ AllOK
+ };
+
+ void refresh();
+ void initialize();
+ ValidationStatus validate();
+ void retranslate();
+
+ bool permGenEnabled() const;
+ int permGenSize() const;
+ int minHeapSize() const;
+ int maxHeapSize() const;
+ QString javaPath() const;
+
+
+protected slots:
+ void memoryValueChanged(int);
+ void javaPathEdited(const QString &path);
+ void javaVersionSelected(BaseVersionPtr version);
+ void on_javaBrowseBtn_clicked();
+ void on_javaStatusBtn_clicked();
+ void checkFinished(JavaCheckResult result);
+
+protected: /* methods */
+ void checkJavaPathOnEdit(const QString &path);
+ void checkJavaPath(const QString &path);
+ void setJavaStatus(JavaStatus status);
+ void setupUi();
+
+private: /* data */
+ VersionSelectWidget *m_versionWidget = nullptr;
+ QVBoxLayout *m_verticalLayout = nullptr;
+
+ QLineEdit * m_javaPathTextBox = nullptr;
+ QPushButton * m_javaBrowseBtn = nullptr;
+ QToolButton * m_javaStatusBtn = nullptr;
+ QHBoxLayout *m_horizontalLayout = nullptr;
+
+ QGroupBox *m_memoryGroupBox = nullptr;
+ QGridLayout *m_gridLayout_2 = nullptr;
+ QSpinBox *m_maxMemSpinBox = nullptr;
+ QLabel *m_labelMinMem = nullptr;
+ QLabel *m_labelMaxMem = nullptr;
+ QSpinBox *m_minMemSpinBox = nullptr;
+ QLabel *m_labelPermGen = nullptr;
+ QSpinBox *m_permGenSpinBox = nullptr;
+ QIcon goodIcon;
+ QIcon yellowIcon;
+ QIcon badIcon;
+
+ int observedMinMemory = 0;
+ int observedMaxMemory = 0;
+ int observedPermGenMemory = 0;
+ QString queuedCheck;
+ uint64_t m_availableMemory = 0ull;
+ shared_qobject_ptr<JavaChecker> m_checker;
+ JavaCheckResult m_result;
+};
diff --git a/application/widgets/LabeledToolButton.cpp b/application/widgets/LabeledToolButton.cpp
index 827fdf2d..744d2e00 100644
--- a/application/widgets/LabeledToolButton.cpp
+++ b/application/widgets/LabeledToolButton.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
#include <QStyleOption>
#include "LabeledToolButton.h"
#include <QApplication>
+#include <QDebug>
/*
*
@@ -36,7 +37,7 @@ LabeledToolButton::LabeledToolButton(QWidget * parent)
m_label->setAlignment(Qt::AlignCenter);
m_label->setTextInteractionFlags(Qt::NoTextInteraction);
// somehow, this makes word wrap work in the QLabel. yay.
- m_label->setMinimumWidth(100);
+ //m_label->setMinimumWidth(100);
}
QString LabeledToolButton::text() const
@@ -49,6 +50,13 @@ void LabeledToolButton::setText(const QString & text)
m_label->setText(text);
}
+void LabeledToolButton::setIcon(QIcon icon)
+{
+ m_icon = icon;
+ resetIcon();
+}
+
+
/*!
\reimp
*/
@@ -82,5 +90,26 @@ QSize LabeledToolButton::sizeHint() const
void LabeledToolButton::resizeEvent(QResizeEvent * event)
{
m_label->setGeometry(QRect(4, 4, width()-8, height()-8));
+ if(!m_icon.isNull())
+ {
+ resetIcon();
+ }
QWidget::resizeEvent(event);
}
+
+void LabeledToolButton::resetIcon()
+{
+ auto iconSz = m_icon.actualSize(QSize(160, 80));
+ float w = iconSz.width();
+ float h = iconSz.height();
+ float ar = w/h;
+ // FIXME: hardcoded max size of 160x80
+ int newW = 80 * ar;
+ if(newW > 160)
+ newW = 160;
+ QSize newSz (newW, 80);
+ auto pixmap = m_icon.pixmap(newSz);
+ m_label->setPixmap(pixmap);
+ m_label->setMinimumHeight(80);
+ m_label->setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Preferred );
+}
diff --git a/application/widgets/LabeledToolButton.h b/application/widgets/LabeledToolButton.h
index 999650fc..151a5c2c 100644
--- a/application/widgets/LabeledToolButton.h
+++ b/application/widgets/LabeledToolButton.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,13 +25,16 @@ class LabeledToolButton : public QToolButton
Q_OBJECT
QLabel * m_label;
+ QIcon m_icon;
public:
LabeledToolButton(QWidget * parent = 0);
QString text() const;
void setText(const QString & text);
+ void setIcon(QIcon icon);
virtual QSize sizeHint() const;
protected:
void resizeEvent(QResizeEvent * event);
+ void resetIcon();
};
diff --git a/application/widgets/MCModInfoFrame.cpp b/application/widgets/MCModInfoFrame.cpp
index 9214ef8b..629c17e7 100644
--- a/application/widgets/MCModInfoFrame.cpp
+++ b/application/widgets/MCModInfoFrame.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/MCModInfoFrame.h b/application/widgets/MCModInfoFrame.h
index da8f6183..6bcd345c 100644
--- a/application/widgets/MCModInfoFrame.h
+++ b/application/widgets/MCModInfoFrame.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/ModListView.cpp b/application/widgets/ModListView.cpp
index 05b85c60..96e8d91b 100644
--- a/application/widgets/ModListView.cpp
+++ b/application/widgets/ModListView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/ModListView.h b/application/widgets/ModListView.h
index 7f3a4a71..baca23f4 100644
--- a/application/widgets/ModListView.h
+++ b/application/widgets/ModListView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/PageContainer.cpp b/application/widgets/PageContainer.cpp
index dacd6acd..0f78329a 100644
--- a/application/widgets/PageContainer.cpp
+++ b/application/widgets/PageContainer.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/PageContainer.h b/application/widgets/PageContainer.h
index c3d77486..96eac80a 100644
--- a/application/widgets/PageContainer.h
+++ b/application/widgets/PageContainer.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -47,9 +47,23 @@ public:
*/
bool prepareToClose();
+ /* request close - used by individual pages */
+ bool requestClose() override
+ {
+ if(m_container)
+ {
+ return m_container->requestClose();
+ }
+ return false;
+ }
+
virtual bool selectPage(QString pageId) override;
void refreshContainer() override;
+ virtual void setParentContainer(BasePageContainer * container)
+ {
+ m_container = container;
+ };
private:
void createUI();
@@ -60,6 +74,7 @@ slots:
void help();
private:
+ BasePageContainer * m_container = nullptr;
BasePage * m_currentPage = 0;
QSortFilterProxyModel *m_proxyModel;
PageModel *m_model;
diff --git a/application/widgets/PageContainer_p.h b/application/widgets/PageContainer_p.h
index 7e49294f..ed8171f1 100644
--- a/application/widgets/PageContainer_p.h
+++ b/application/widgets/PageContainer_p.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/ProgressWidget.cpp b/application/widgets/ProgressWidget.cpp
index bd5967a3..fab099a9 100644
--- a/application/widgets/ProgressWidget.cpp
+++ b/application/widgets/ProgressWidget.cpp
@@ -48,12 +48,12 @@ bool ProgressWidget::exec(std::shared_ptr<Task> task)
{
loop.exec();
}
- return task->successful();
+ return task->wasSuccessful();
}
void ProgressWidget::handleTaskFinish()
{
- if (!m_task->successful())
+ if (!m_task->wasSuccessful())
{
m_label->setText(m_task->failReason());
}
diff --git a/application/widgets/ServerStatus.cpp b/application/widgets/ServerStatus.cpp
index 4602dde5..f1963b68 100644
--- a/application/widgets/ServerStatus.cpp
+++ b/application/widgets/ServerStatus.cpp
@@ -63,15 +63,13 @@ ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent,
yellowIcon = MMC->getThemedIcon("status-yellow");
badIcon = MMC->getThemedIcon("status-bad");
- addStatus("minecraft.net", tr("Web"));
- addLine();
- addStatus("account.mojang.com", tr("Account"));
- addLine();
- addStatus("skins.minecraft.net", tr("Skins"));
- addLine();
addStatus("authserver.mojang.com", tr("Auth"));
addLine();
addStatus("sessionserver.mojang.com", tr("Session"));
+ addLine();
+ addStatus("textures.minecraft.net", tr("Skins"));
+ addLine();
+ addStatus("api.mojang.com", tr("API"));
m_statusRefresh = new QToolButton(this);
m_statusRefresh->setCheckable(true);
diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp
index 4b60c5e6..8c80ecf3 100644
--- a/application/widgets/VersionListView.cpp
+++ b/application/widgets/VersionListView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/VersionListView.h b/application/widgets/VersionListView.h
index 83a20c1f..b7a881e9 100644
--- a/application/widgets/VersionListView.h
+++ b/application/widgets/VersionListView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/application/widgets/VersionSelectWidget.cpp b/application/widgets/VersionSelectWidget.cpp
index 1696d9d1..2a7cbfb7 100644
--- a/application/widgets/VersionSelectWidget.cpp
+++ b/application/widgets/VersionSelectWidget.cpp
@@ -41,6 +41,11 @@ VersionSelectWidget::VersionSelectWidget(BaseVersionList* vlist, QWidget* parent
QMetaObject::connectSlotsByName(this);
}
+void VersionSelectWidget::setCurrentVersion(const QString& version)
+{
+ m_currentVersion = version;
+}
+
void VersionSelectWidget::setEmptyString(QString emptyString)
{
listView->setEmptyString(emptyString);
@@ -134,15 +139,33 @@ void VersionSelectWidget::preselect()
{
if(preselectedAlready)
return;
- preselectedAlready = true;
+ selectCurrent();
+ if(preselectedAlready)
+ return;
selectRecommended();
}
+void VersionSelectWidget::selectCurrent()
+{
+ if(m_currentVersion.isEmpty())
+ {
+ return;
+ }
+ auto idx = m_proxyModel->getVersion(m_currentVersion);
+ if(idx.isValid())
+ {
+ preselectedAlready = true;
+ listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
+ listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
+ }
+}
+
void VersionSelectWidget::selectRecommended()
{
auto idx = m_proxyModel->getRecommended();
if(idx.isValid())
{
+ preselectedAlready = true;
listView->selectionModel()->setCurrentIndex(idx,QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
listView->scrollTo(idx, QAbstractItemView::PositionAtCenter);
}
diff --git a/application/widgets/VersionSelectWidget.h b/application/widgets/VersionSelectWidget.h
index 66e512ac..3ea0b4f5 100644
--- a/application/widgets/VersionSelectWidget.h
+++ b/application/widgets/VersionSelectWidget.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -40,13 +40,14 @@ public:
bool hasVersions() const;
BaseVersionPtr selectedVersion() const;
void selectRecommended();
+ void selectCurrent();
+ void setCurrentVersion(const QString & version);
void setFuzzyFilter(BaseVersionList::ModelRoles role, QString filter);
void setExactFilter(BaseVersionList::ModelRoles role, QString filter);
void setEmptyString(QString emptyString);
void setEmptyErrorString(QString emptyErrorString);
void setResizeOn(int column);
- void setUseLatest(const bool useLatest);
signals:
void selectedVersionChanged(BaseVersionPtr version);
@@ -64,6 +65,7 @@ private:
void preselect();
private:
+ QString m_currentVersion;
BaseVersionList *m_vlist = nullptr;
VersionProxyModel *m_proxyModel = nullptr;
int resizeOnColumn = 0;
diff --git a/changelog.md b/changelog.md
index 6d366c49..b9ea1631 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,227 @@
-# MultiMC 0.5.1
+# MultiMC 0.6.1
-## Improvements
+## New or changed features
+
+- GH-2087: The version page now has a button that will download all the necessary files without launching the game.
+
+## Bugfixes
+
+- Several issues related to bad URLs returned by the Curse servers have been fixed.
+
+ The Curse platform does not use valid URLs according to [RFC 3986, section 2](https://tools.ietf.org/html/rfc3986#section-2) by including spaces and UTF-8 characters without percent encoding them.
+ MultiMC has been improved to handle these invalid URLs and report errors in case other invalid URLs are encountered.
+ This affected pretty much all modpack imports. You may want to reimport them if you were affected by this.
+
+- GH-1780, GH-2102, GH-2103: Multiple issues with the build system and packaging on linux have been fixed.
+
+ - Installed libraries now no longer have `RPATH` set and have the correct file permissions when using the `lin-system` layout.
+ - Installation using the `lin-bundle` layout has been fixed on platforms that use position independent code.
+ - `CMAKE_INSTALL_PREFIX` and `DESTDIR` now behave as expected on linux platforms.
+
+- MultiMC no longer logs the process environment and launch scripts to its log files.
+
+- GH-2089: Mention of instance tracking has been removed from the deletion confirmation dialog.
+
+- GH-2087: The obsolete 'revert to vanilla' logic that was previously applied to versions has been removed.
+
+ This should remove some confusing situations that could happen while changing and manipulation instance versions.
+
+- The temporary `Minecraft.jar` is now removed from the instance after it stops running.
+
+- GH-2119: The main instance view scrollbar now correctly updates when the window is resized without changing the number of icons that can fit into it horizontally.
+
+# Previous releases
+
+## MultiMC 0.6.0
+
+### New or changed features
+
+- Contact with Mojang, Forge and LiteLoader servers is no longer handled by MultiMC, but a metadata server. Instead of generating and storing the files at the point of installation, they are updated hourly on the server and can be fixed when something goes wrong.
+
+ This goes along with some changes to the instance format and to the metadata format.
+
+ Instead of including the metadata JSON files directly in the instances, the instances now contain a new `mmc-pack.json` file that specifies versions to be used.
+
+ The metadata can be found at [v1.meta.multimc.org](https://v1.meta.multimc.org), the [meta.multimc.org](https://meta.multimc.org) endpoint that was used during development will be replaced by documentation.
+
+ This should be a much more reliable solution going forward, because it allows fixing issues without releasing new versions of MultiMC or reinstalling Forge/LiteLoader/others.
+
+- Tracking of FTB launcher instances has been replaced with direct import of Curse modpacks.
+
+ You can import the modpack zip files from CurseForge and FTB:
+ - Get the zip, for example from [here](https://www.feed-the-beast.com/projects/ftb-retro-ssp/files/2219693).
+ - Drag & Drop it on top of the main window, or select it in the new instance dialog.
+ - Let the magic happen.
+
+ If you need help moving over your old instances or worlds from the FTB launcher, stop by in the MultiMC discord server.
+
+ The Curse import functionality is there thanks to the work [@Dries007](https://twitter.com/driesk007) and [@NikkiAI](https://twitter.com/NikkyAI) have done on [CurseMeta](https://cursemeta.dries007.net/).
+
+- GH-1314: MultiMC now allows replacing the main jar in an instance without having to mod the Mojang jars.
+
+ This goes along with changing the wording of the jar mod button to make it clear that it adds files to the main Minecraft jar instead of installing mod files with the `.jar` extension.
+
+- Because the current instance format can now handle replacing the main jar, Legacy format instances are no longer directly supported.
+
+ Instead of launching, you will be prompted to convert them to the current instance format.
+ If the automated process fails, stop by in the MultiMC discord server and ask for help.
+
+- Main window UI has been changed for increased clarity.
+
+ Many people had issues finding the settings and instead ended up using the per-instance overrides. The main toolbar now has labels and the per-instance overrides have been deemphasized by removing the direct path to them from the main window. In general, it should be easier to find the right settings menu without getting things completely wrong on the first try.
+
+- GH-1997: MultiMC now supports Java 9.
+
+ This does not mean that the current mod loaders and mods do, but you can run Vanilla Minecraft with Java 9 now.
+
+ However, Java 9 will come up last in the lists when multiple versions are installed and its use is strongly discouraged.
+
+- GH-2026: You can launch Minecraft 1.13 snapshots - and hopefully also 1.13 once it is released.
+
+ The bare minimum of changes needed for 1.13 to launch has been done.
+
+ This does not mean support for modded 1.13!
+
+ It is not yet clear what it will even look like and what exactly will be needed for Forge to be able to install properly.
+
+- Bundled Qt libraries have been updated to version 5.6.3 on macOS and Windows
+
+ This means less issues with SSL encryption on macOS and better support for HiDPI/retina displays, along with many bug fixes.
+ The workarounds for SSL problems on macOS have been removed thanks to this.
+
+- Linux builds were moved to a newer version of Ubuntu (14.04)
+
+ This means better support on newer distribution releases, and dropping support for older distributions.
+
+- Bundled OpenSSL library on Windows no longer requires Visual Studio runtime libraries.
+
+ This should avoid issues with missing runtime libraries.
+
+- GH-1855: The instance window now has an offline launch button.
+
+- GH-1886: UI now clarifies that MultiMC proxy settings do not apply to the game.
+
+- It is now possible to package MultiMC on linux without hacks.
+
+ The build system has a concept of 'install layouts'. Example Arch linux package that uses this (multimc-git) is [available in the AUR](https://aur.archlinux.org/packages/multimc-git).
+
+- Wrapper commands now support arguments.
+
+ Previously, they would be treated as a single command -- spaces and all.
+
+- UI elements that set maximum JVM memory are now limited to the amount of system memory present.
+
+ Before, they were hardcoded.
+
+
+
+ This is to accommodate the needs of some new mods for ancient Minecraft versions that do not work well with the applet wrapper.
+
+- On instance launch, the used GPU and graphics driver are reported - but only on linux.
+
+ Other platforms will hopefully get this in the future.
+
+- There are some under the hood improvements for ancient Minecraft versions and versions not provided by Mojang.
+
+ - The `haspaid` parameter is set for the applet wrapper.
+ - MultiMC will prefer to use `.minecraft` instead of `minecraft` folder inside the instances now.
+ - There is some preliminary support for classic multiplayer - see [this workflowy list](https://workflowy.com/s/2EyDMcp7CU#/1cbfc198cf28) for details.
+ - A new `noapplet` trait has been added to allow running legacy Minecraft versions without the applet wrapper.
+
+- Mods without changed metadata (Example Mod) are now listed under their filename instead.
+
+- Tweaker list in metadata now overrides the order of already present tweakers.
+
+ This allows running [Vivecraft](http://www.vivecraft.org/). Official support will hopefully follow.
+
+- Instance icons can now be in the SVG format. Also, aspect ratio of SVG icons is now preserved in the instance toolbar.
+
+- GH-1082: It is now possible to disable and enable version components (packages) similarly to mods.
+
+- A new material design / flat icon theme has been added.
+
+- When changing instance component versions, the present version is selected first.
+
+### Bugfixes
+
+- paste.ee upload now works again.
+
+ MultiMC now uses its new API. If you used a custom API key before, you will need to generate a new one.
+
+- GH-1873, GH-1873, GH-1875 : The main window can now be closed regardless of running instances and running instances directly will not create a main window.
+
+- GH-1854: MultiMC should no longer crash when the instance is closed while the kill confirmation dialog is open.
+
+- GH-1956: Launch will abort sooner when important files are missing.
+
+- GH-1874: Instance launching and updating MultiMC are now mutually exclusive.
+
+ It was possible to do both at the same time, with undefined results.
+
+- GH-1864: imgur album creation now works again.
+
+- GH-1876: Various included libraries have been changed to satisfy their license terms.
+
+ Namely:
+ - pack200 (GPL with classpath exception, now a shared library)
+ - iconfix (LGPL, now a shared library)
+ - quazip (LGPL, now a shared library)
+ - ColumnResizer (replaced with a BSD-3 version).
+
+- GH-1882: Update dialog will now save its location and size.
+
+- GH-1885: MultiMC will now correctly download zero-byte files.
+
+ No content does not equal no file and a presence of a file can mean the difference between something working or not.
+
+- When importing modpacks, file permissions from the pack archive will no longer be preserved.
+
+ The archives are sometimes broken and have invalid permissions, especially when coming from sources other than MultiMC.
+
+- Instance export filter has been fixed.
+
+ The filtering logic was picking and ignoring incorrect files under some conditions. Also, hidden files were ignored.
+
+- Download progress bars are now less jumpy.
+
+ Instead of tracking the total size of all downloads, each download gets a fixed share of the progress bar.
+ In many cases, the size of files is unknown before a download starts. The change means that the total progress bar size cannot increase as new downloads start and file sizes are discovered.
+
+- GH-1927: fix crash bugs related to FML library downloads succeeding multiple times.
+
+- Rare problems with error 201 during Mojang authentication have been fixed.
+
+- GH-1971: MultiMC will now no longer check path prefixes when importing instances.
+
+ This has caused more issues than it solved. Now it will simply try to move the files instead of giving up early.
+
+- Instance import and creation have been overhauled in general for increased reliability.
+
+- Hardcoded link colors in various dialogs and dialog pages have been fixed and now should follow theme palettes.
+
+- GH-1993: Minimum and maximum JVM memory settings will now get swapped if set the wrong way.
+
+ The values self-correct on both settings save and load now.
+
+- GH-2050: Fixed behavior of cancel buttons when browsing for paths.
+
+ This affected various settings dialogs and pages, setting the paths to an invalid value when the dialogs were closed with the `Cancel` button.
+
+- The checkboxes in the accounts settings page now have the correct appearance.
+
+- MultiMC responds to account manipulation better.
+
+ - Setting and resetting default account will update the account list properly.
+ - Removing the active account will now also reset it (previously, it would 'stay around').
+ - The accounts model is no longer reset by every action.
+
+- When closing and reopening the instance window, the log settings are preserved.
+
+- In the instance export dialog, the sorting order has been changed to go from `a` to `z`, not backwards.
+
+## MultiMC 0.5.1
+
+### Improvements
- Log uploads now use HTTPS because the [paste.ee](https://paste.ee) site is switching to HTTPS only.
@@ -43,8 +264,6 @@
The instance type of the copy was not set, causing it to not be usable.
-# Previous releases
-
## MultiMC 0.5.0
### New or changed features
@@ -709,4 +928,4 @@ Long time coming, this release brought a lot of incremental improvements and fix
- Added additional information to the about dialog.
## MultiMC 0.0
-- Initial release.
+- Initial release. \ No newline at end of file
diff --git a/cmake/BundleUtilities.cmake b/cmake/BundleUtilities.cmake
index 0f6cd059..e3f50b94 100644
--- a/cmake/BundleUtilities.cmake
+++ b/cmake/BundleUtilities.cmake
@@ -301,7 +301,7 @@ function(get_bundle_and_executable app bundle_var executable_var valid_var)
endif()
endif()
else()
- message(STATUS "warning: *NOT* handled - directory/file does not exist...")
+ message(STATUS "warning: *NOT* handled - directory/file ${app} does not exist...")
endif()
if(NOT valid)
diff --git a/cmake/GetPrerequisites.cmake b/cmake/GetPrerequisites.cmake
index d782ced9..39c2cc63 100644
--- a/cmake/GetPrerequisites.cmake
+++ b/cmake/GetPrerequisites.cmake
@@ -205,6 +205,13 @@ function(is_file_executable file result_var)
return()
endif()
+ # "file" version 5.22 does not print "(used shared libraries)"
+ # but uses "interpreter"
+ if("${file_ov}" MATCHES "shared object.*interpreter")
+ set(${result_var} 1 PARENT_SCOPE)
+ return()
+ endif()
+
else()
message(STATUS "warning: No 'file' command, skipping execute_process...")
endif()
diff --git a/libraries/classparser/CMakeLists.txt b/libraries/classparser/CMakeLists.txt
index a6c3fa14..db266f53 100644
--- a/libraries/classparser/CMakeLists.txt
+++ b/libraries/classparser/CMakeLists.txt
@@ -18,7 +18,7 @@ include_directories(${Qt5Base_INCLUDE_DIRS})
set(CLASSPARSER_HEADERS
# Public headers
include/classparser_config.h
-include/javautils.h
+include/classparser.h
# Private headers
src/annotations.h
@@ -30,12 +30,13 @@ src/membuffer.h
)
set(CLASSPARSER_SOURCES
-src/javautils.cpp
+src/classparser.cpp
src/annotations.cpp
)
add_definitions(-DCLASSPARSER_LIBRARY)
-add_library(classparser SHARED ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS})
-target_include_directories(classparser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
-qt5_use_modules(classparser Core)
+add_library(MultiMC_classparser STATIC ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS})
+target_include_directories(MultiMC_classparser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
+target_link_libraries(MultiMC_classparser MultiMC_quazip)
+qt5_use_modules(MultiMC_classparser Core)
diff --git a/libraries/classparser/include/javautils.h b/libraries/classparser/include/classparser.h
index aad7b504..23a65589 100644
--- a/libraries/classparser/include/javautils.h
+++ b/libraries/classparser/include/classparser.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -18,9 +18,7 @@
#include <QString>
#include "classparser_config.h"
-#define MCVer_Unknown "Unknown"
-
-namespace javautils
+namespace classparser
{
/**
* @brief Get the version from a minecraft.jar by parsing its class files. Expensive!
diff --git a/libraries/classparser/include/classparser_config.h b/libraries/classparser/include/classparser_config.h
index cc903210..db8f40a3 100644
--- a/libraries/classparser/include/classparser_config.h
+++ b/libraries/classparser/include/classparser_config.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/classparser/src/annotations.h b/libraries/classparser/src/annotations.h
index aa25d241..dd603af3 100644
--- a/libraries/classparser/src/annotations.h
+++ b/libraries/classparser/src/annotations.h
@@ -42,6 +42,7 @@ protected:
public:
element_value(element_value_type type, constant_pool &pool) : type(type), pool(pool) {};
+ virtual ~element_value() {}
element_value_type getElementValueType()
{
diff --git a/libraries/classparser/src/javautils.cpp b/libraries/classparser/src/classparser.cpp
index 719032af..8837781f 100644
--- a/libraries/classparser/src/javautils.cpp
+++ b/libraries/classparser/src/classparser.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -15,17 +15,18 @@
* limitations under the License.
*/
#include "classfile.h"
-#include "javautils.h"
+#include "classparser.h"
#include <QFile>
#include <quazipfile.h>
+#include <QDebug>
-namespace javautils
+namespace classparser
{
QString GetMinecraftJarVersion(QString jarName)
{
- QString version = MCVer_Unknown;
+ QString version;
// check if minecraft.jar exists
QFile jar(jarName);
@@ -61,6 +62,7 @@ QString GetMinecraftJarVersion(QString jarName)
if (constant.type != java::constant::j_string_data)
continue;
const std::string &str = constant.str_data;
+ qDebug() << QString::fromStdString(str);
if (str.compare(0, 20, "Minecraft Minecraft ") == 0)
{
version = str.substr(20).data();
diff --git a/libraries/classparser/src/constants.h b/libraries/classparser/src/constants.h
index 242b943e..9c74ab20 100644
--- a/libraries/classparser/src/constants.h
+++ b/libraries/classparser/src/constants.h
@@ -21,14 +21,12 @@ public:
j_methodref = 10,
j_interface_methodref = 11,
j_nameandtype = 12
+ // FIXME: missing some constant types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
} type;
constant(util::membuffer &buf)
{
buf.read(type);
- // invalid constant type!
- if (type > j_nameandtype || type == (type_t)0 || type == (type_t)2)
- throw new classfile_exception();
// load data depending on type
switch (type)
@@ -65,10 +63,13 @@ public:
buf.read_be(name_and_type.name_index);
buf.read_be(name_and_type.descriptor_index);
break;
+ default:
+ // invalid constant type!
+ throw new classfile_exception();
}
}
- constant(int fake)
+ constant(int)
{
type = j_hole;
}
@@ -115,6 +116,9 @@ public:
ss << "NameAndType: " << name_and_type.name_index << " "
<< name_and_type.descriptor_index;
break;
+ default:
+ ss << "Invalid entry (" << int(type) << ")";
+ break;
}
return ss.str();
}
@@ -166,10 +170,10 @@ public:
*/
void load(util::membuffer &buf)
{
+ // FIXME: @SANITY this should check for the end of buffer.
uint16_t length = 0;
buf.read_be(length);
length--;
- uint16_t index = 1;
const constant *last_constant = nullptr;
while (length)
{
@@ -182,12 +186,10 @@ public:
// push in a fake constant to preserve indexing
constants.push_back(constant(0));
length -= 2;
- index += 2;
}
else
{
length--;
- index++;
}
}
}
diff --git a/libraries/iconfix/CMakeLists.txt b/libraries/iconfix/CMakeLists.txt
index 6a99effe..93bfdd06 100644
--- a/libraries/iconfix/CMakeLists.txt
+++ b/libraries/iconfix/CMakeLists.txt
@@ -19,3 +19,10 @@ qt5_use_modules(MultiMC_iconfix Core Widgets)
set_target_properties(MultiMC_iconfix PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_iconfix)
+
+# Install it
+install(
+ TARGETS MultiMC_iconfix
+ RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
+) \ No newline at end of file
diff --git a/libraries/javacheck/CMakeLists.txt b/libraries/javacheck/CMakeLists.txt
index 9768650e..381efe08 100644
--- a/libraries/javacheck/CMakeLists.txt
+++ b/libraries/javacheck/CMakeLists.txt
@@ -11,3 +11,4 @@ set(SRC
)
add_jar(JavaCheck ${SRC})
+install_jar(JavaCheck "${JARS_DEST_DIR}")
diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt
index 9c0e128b..a4f52edb 100644
--- a/libraries/launcher/CMakeLists.txt
+++ b/libraries/launcher/CMakeLists.txt
@@ -7,27 +7,15 @@ set(CMAKE_JAVA_JAR_ENTRY_POINT org.multimc.EntryPoint)
set(CMAKE_JAVA_COMPILE_FLAGS -target 1.6 -source 1.6 -Xlint:deprecation -Xlint:unchecked)
set(SRC
- # OSX things
- org/simplericity/macify/eawt/Application.java
- org/simplericity/macify/eawt/ApplicationAdapter.java
- org/simplericity/macify/eawt/ApplicationEvent.java
- org/simplericity/macify/eawt/ApplicationListener.java
- org/simplericity/macify/eawt/DefaultApplication.java
-
- # legacy applet wrapper thing.
- # The launcher has to be there for silly FML/Forge relauncher.
- net/minecraft/Launcher.java
- org/multimc/LegacyFrame.java
-
- # onesix launcher
- org/multimc/onesix/OneSixLauncher.java
-
- # generic launcher
org/multimc/EntryPoint.java
org/multimc/Launcher.java
+ org/multimc/LegacyFrame.java
+ org/multimc/NotFoundException.java
+ org/multimc/ParamBucket.java
org/multimc/ParseException.java
org/multimc/Utils.java
- org/multimc/IconLoader.java
+ org/multimc/onesix/OneSixLauncher.java
+ net/minecraft/Launcher.java
)
add_jar(NewLaunch ${SRC})
-
+install_jar(NewLaunch "${JARS_DEST_DIR}")
diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java
index 5aec2654..0c991cf5 100644
--- a/libraries/launcher/net/minecraft/Launcher.java
+++ b/libraries/launcher/net/minecraft/Launcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java
index e4d335b7..8c9b8074 100644
--- a/libraries/launcher/org/multimc/EntryPoint.java
+++ b/libraries/launcher/org/multimc/EntryPoint.java
@@ -1,5 +1,5 @@
package org.multimc;/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 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,11 +15,7 @@ package org.multimc;/*
*/
import org.multimc.onesix.OneSixLauncher;
-import org.simplericity.macify.eawt.Application;
-import org.simplericity.macify.eawt.DefaultApplication;
-import javax.imageio.ImageIO;
-import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.Charset;
@@ -34,21 +30,6 @@ public class EntryPoint
public static void main(String[] args)
{
- // Set the OSX application icon first, if we are on OSX.
- Application application = new DefaultApplication();
- if(application.isMac())
- {
- try
- {
- BufferedImage image = ImageIO.read(new File("icon.png"));
- application.setApplicationIconImage(image);
- }
- catch (IOException e)
- {
- e.printStackTrace();
- }
- }
-
EntryPoint listener = new EntryPoint();
int retCode = listener.listen();
if (retCode != 0)
diff --git a/libraries/launcher/org/multimc/IconLoader.java b/libraries/launcher/org/multimc/IconLoader.java
deleted file mode 100644
index f1638f3a..00000000
--- a/libraries/launcher/org/multimc/IconLoader.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package org.multimc;
-
-import javax.imageio.ImageIO;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-
-/*****************************************************************************
- * A convenience class for loading icons from images.
- *
- * Icons loaded from this class are formatted to fit within the required
- * dimension (16x16, 32x32, or 128x128). If the source image is larger than the
- * target dimension, it is shrunk down to the minimum size that will fit. If it
- * is smaller, then it is only scaled up if the new scale can be a per-pixel
- * linear scale (i.e., x2, x3, x4, etc). In both cases, the image's width/height
- * ratio is kept the same as the source image.
- *
- * @author Chris Molini
- *****************************************************************************/
-public class IconLoader
-{
- /*************************************************************************
- * Loads an icon in ByteBuffer form.
- *
- * @param filepath
- * The location of the Image to use as an icon.
- *
- * @return An array of ByteBuffers containing the pixel data for the icon in
- * various sizes (as recommended by the OS).
- *************************************************************************/
- public static ByteBuffer[] load(String filepath)
- {
- BufferedImage image;
- try {
- image = ImageIO.read ( new File( filepath ) );
- } catch ( IOException e ) {
- e.printStackTrace();
- return new ByteBuffer[0];
- }
- ByteBuffer[] buffers;
- buffers = new ByteBuffer[1];
- buffers[0] = loadInstance(image, 128);
- return buffers;
- }
-
- /*************************************************************************
- * Copies the supplied image into a square icon at the indicated size.
- *
- * @param image
- * The image to place onto the icon.
- * @param dimension
- * The desired size of the icon.
- *
- * @return A ByteBuffer of pixel data at the indicated size.
- *************************************************************************/
- private static ByteBuffer loadInstance(BufferedImage image, int dimension)
- {
- BufferedImage scaledIcon = new BufferedImage(dimension, dimension,
- BufferedImage.TYPE_INT_ARGB_PRE);
- Graphics2D g = scaledIcon.createGraphics();
- double ratio = getIconRatio(image, scaledIcon);
- double width = image.getWidth() * ratio;
- double height = image.getHeight() * ratio;
- g.drawImage(image, (int) ((scaledIcon.getWidth() - width) / 2),
- (int) ((scaledIcon.getHeight() - height) / 2), (int) (width),
- (int) (height), null);
- g.dispose();
-
- return convertToByteBuffer(scaledIcon);
- }
-
- /*************************************************************************
- * Gets the width/height ratio of the icon. This is meant to simplify
- * scaling the icon to a new dimension.
- *
- * @param src
- * The base image that will be placed onto the icon.
- * @param icon
- * The icon that will have the image placed on it.
- *
- * @return The amount to scale the source image to fit it onto the icon
- * appropriately.
- *************************************************************************/
- private static double getIconRatio(BufferedImage src, BufferedImage icon)
- {
- double ratio = 1;
- if (src.getWidth() > icon.getWidth())
- ratio = (double) (icon.getWidth()) / src.getWidth();
- else
- ratio = (int) (icon.getWidth() / src.getWidth());
- if (src.getHeight() > icon.getHeight())
- {
- double r2 = (double) (icon.getHeight()) / src.getHeight();
- if (r2 < ratio)
- ratio = r2;
- }
- else
- {
- double r2 = (int) (icon.getHeight() / src.getHeight());
- if (r2 < ratio)
- ratio = r2;
- }
- return ratio;
- }
-
- /*************************************************************************
- * Converts a BufferedImage into a ByteBuffer of pixel data.
- *
- * @param image
- * The image to convert.
- *
- * @return A ByteBuffer that contains the pixel data of the supplied image.
- *************************************************************************/
- public static ByteBuffer convertToByteBuffer(BufferedImage image)
- {
- byte[] buffer = new byte[image.getWidth() * image.getHeight() * 4];
- int counter = 0;
- for (int i = 0; i < image.getHeight(); i++)
- for (int j = 0; j < image.getWidth(); j++)
- {
- int colorSpace = image.getRGB(j, i);
- buffer[counter + 0] = (byte) ((colorSpace << 8) >> 24);
- buffer[counter + 1] = (byte) ((colorSpace << 16) >> 24);
- buffer[counter + 2] = (byte) ((colorSpace << 24) >> 24);
- buffer[counter + 3] = (byte) (colorSpace >> 24);
- counter += 4;
- }
- return ByteBuffer.wrap(buffer);
- }
-} \ No newline at end of file
diff --git a/libraries/launcher/org/multimc/Launcher.java b/libraries/launcher/org/multimc/Launcher.java
index fd7af8b8..2e851d18 100644
--- a/libraries/launcher/org/multimc/Launcher.java
+++ b/libraries/launcher/org/multimc/Launcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/launcher/org/multimc/LegacyFrame.java b/libraries/launcher/org/multimc/LegacyFrame.java
index 8f7a364c..9842eb0e 100644
--- a/libraries/launcher/org/multimc/LegacyFrame.java
+++ b/libraries/launcher/org/multimc/LegacyFrame.java
@@ -1,5 +1,5 @@
package org.multimc;/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,9 +23,12 @@ import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.awt.image.BufferedImage;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Scanner;
public class LegacyFrame extends Frame implements WindowListener
{
@@ -43,19 +46,64 @@ public class LegacyFrame extends Frame implements WindowListener
this.addWindowListener ( this );
}
- public void start ( Applet mcApplet, String user, String session, Dimension winSize, boolean maximize )
+ public void start ( Applet mcApplet, String user, String session, int winSizeW, int winSizeH, boolean maximize )
{
try {
appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) );
} catch ( MalformedURLException ignored ) {}
+
+ // Implements support for launching in to multiplayer on classic servers using a mpticket
+ // file generated by an external program and stored in the instance's root folder.
+ File mpticketFile = null;
+ Scanner fileReader = null;
+ try {
+ mpticketFile = new File(System.getProperty("user.dir") + "/../mpticket").getCanonicalFile();
+ fileReader = new Scanner(new FileInputStream(mpticketFile), "ascii");
+ String[] mpticketParams = new String[3];
+
+ for(int i=0;i<3;i++) {
+ if(fileReader.hasNextLine()) {
+ mpticketParams[i] = fileReader.nextLine();
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ // Assumes parameters are valid and in the correct order
+ appletWrap.setParameter("server", mpticketParams[0]);
+ appletWrap.setParameter("port", mpticketParams[1]);
+ appletWrap.setParameter("mppass", mpticketParams[2]);
+
+ fileReader.close();
+ mpticketFile.delete();
+ }
+ catch (FileNotFoundException e) {}
+ catch (IllegalArgumentException e) {
+
+ fileReader.close();
+ File mpticketFileCorrupt = new File(System.getProperty("user.dir") + "/../mpticket.corrupt");
+ if(mpticketFileCorrupt.exists()) {
+ mpticketFileCorrupt.delete();
+ }
+ mpticketFile.renameTo(mpticketFileCorrupt);
+
+ System.err.println("Malformed mpticket file, missing argument.");
+ e.printStackTrace(System.err);
+ System.exit(-1);
+ }
+ catch (Exception e) {
+ e.printStackTrace(System.err);
+ System.exit(-1);
+ }
appletWrap.setParameter ( "username", user );
appletWrap.setParameter ( "sessionid", session );
appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button.
- appletWrap.setParameter ( "demo", "false" );
- appletWrap.setParameter("fullscreen", "false");
+ appletWrap.setParameter ( "haspaid", "true" ); // Some old versions need this for world saves to work.
+ appletWrap.setParameter ( "demo", "false" );
+ appletWrap.setParameter ( "fullscreen", "false" );
mcApplet.setStub(appletWrap);
this.add ( appletWrap );
- appletWrap.setPreferredSize ( winSize );
+ appletWrap.setPreferredSize ( new Dimension (winSizeW, winSizeH) );
this.pack();
this.setLocationRelativeTo ( null );
this.setResizable ( true );
diff --git a/libraries/launcher/org/multimc/NotFoundException.java b/libraries/launcher/org/multimc/NotFoundException.java
index aa27c173..2c5da6de 100644
--- a/libraries/launcher/org/multimc/NotFoundException.java
+++ b/libraries/launcher/org/multimc/NotFoundException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/launcher/org/multimc/ParamBucket.java b/libraries/launcher/org/multimc/ParamBucket.java
index d3880dd7..5e9c3ff6 100644
--- a/libraries/launcher/org/multimc/ParamBucket.java
+++ b/libraries/launcher/org/multimc/ParamBucket.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/launcher/org/multimc/ParseException.java b/libraries/launcher/org/multimc/ParseException.java
index b2e5ff65..9a8fe521 100644
--- a/libraries/launcher/org/multimc/ParseException.java
+++ b/libraries/launcher/org/multimc/ParseException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/launcher/org/multimc/Utils.java b/libraries/launcher/org/multimc/Utils.java
index 860c0864..c5292eaf 100644
--- a/libraries/launcher/org/multimc/Utils.java
+++ b/libraries/launcher/org/multimc/Utils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2017 MultiMC Contributors
+ * Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -68,77 +68,6 @@ public class Utils
}
/**
- * Adds the specified library to the classpath
- *
- * @param s the path to add
- * @throws Exception
- */
- public static void addToClassPath(String s) throws Exception
- {
- File f = new File(s);
- URL u = f.toURI().toURL();
- URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
- Class urlClass = URLClassLoader.class;
- Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
- method.setAccessible(true);
- method.invoke(urlClassLoader, new Object[]{u});
- }
-
- /**
- * Adds many libraries to the classpath
- *
- * @param jars the paths to add
- */
- public static boolean addToClassPath(List<String> jars)
- {
- boolean pure = true;
- // initialize the class path
- for (String jar : jars)
- {
- try
- {
- Utils.addToClassPath(jar);
- } catch (Exception e)
- {
- System.err.println("Unable to load: " + jar);
- e.printStackTrace(System.err);
- pure = false;
- }
- }
- return pure;
- }
-
- /**
- * Adds the specified path to the java library path
- *
- * @param pathToAdd the path to add
- * @throws Exception
- */
- @Deprecated
- public static void addLibraryPath(String pathToAdd) throws Exception
- {
- final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
- usrPathsField.setAccessible(true);
-
- //get array of paths
- final String[] paths = (String[]) usrPathsField.get(null);
-
- //check if the path to add is already present
- for (String path : paths)
- {
- if (path.equals(pathToAdd))
- {
- return;
- }
- }
-
- //add the new path
- final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
- newPaths[newPaths.length - 1] = pathToAdd;
- usrPathsField.set(null, newPaths);
- }
-
- /**
* Finds a field that looks like a Minecraft base folder in a supplied class
*
* @param mc the class to scan
diff --git a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
index 61a30ede..9667297d 100644
--- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
+++ b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
@@ -1,4 +1,4 @@
-/* Copyright 2012-2017 MultiMC Contributors
+/* Copyright 2012-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ 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.Method;
@@ -43,7 +42,8 @@ public class OneSixLauncher implements Launcher
private String windowParams;
// secondary parameters
- private Dimension winSize;
+ private int winSizeW;
+ private int winSizeH;
private boolean maximize;
private String cwd;
@@ -65,7 +65,9 @@ public class OneSixLauncher implements Launcher
windowParams = params.firstSafe("windowParams", "854x480");
cwd = System.getProperty("user.dir");
- winSize = new Dimension(854, 480);
+
+ winSizeW = 854;
+ winSizeH = 480;
maximize = false;
String[] dimStrings = windowParams.split("x");
@@ -78,7 +80,8 @@ public class OneSixLauncher implements Launcher
{
try
{
- winSize = new Dimension(Integer.parseInt(dimStrings[0]), Integer.parseInt(dimStrings[1]));
+ winSizeW = Integer.parseInt(dimStrings[0]);
+ winSizeH = Integer.parseInt(dimStrings[1]);
} catch (NumberFormatException ignored) {}
}
}
@@ -111,34 +114,37 @@ public class OneSixLauncher implements Launcher
System.setProperty("minecraft.applet.TargetDirectory", cwd);
- String[] mcArgs = new String[2];
- mcArgs[0] = userName;
- mcArgs[1] = sessionId;
+ if(!traits.contains("noapplet"))
+ {
+ 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, winSizeW, winSizeH, maximize);
+ return 0;
+ } catch (Exception e)
+ {
+ Utils.log("Applet wrapper failed:", "Error");
+ e.printStackTrace(System.err);
+ Utils.log();
+ Utils.log("Falling back to using main class.");
+ }
+ }
- Utils.log("Launching with applet wrapper...");
+ // init params for the main method to chomp on.
+ String[] paramsArray = mcparams.toArray(new String[mcparams.size()]);
try
{
- Class<?> MCAppletClass = cl.loadClass(appletClass);
- Applet mcappl = (Applet) MCAppletClass.newInstance();
- LegacyFrame mcWindow = new LegacyFrame(windowTitle);
- mcWindow.start(mcappl, userName, sessionId, winSize, maximize);
+ mc.getMethod("main", String[].class).invoke(null, (Object) paramsArray);
+ return 0;
} catch (Exception e)
{
- Utils.log("Applet wrapper failed:", "Error");
+ Utils.log("Failed to invoke the Minecraft main class:", "Fatal");
e.printStackTrace(System.err);
- Utils.log();
- Utils.log("Falling back to compatibility mode.");
- try
- {
- mc.getMethod("main", String[].class).invoke(null, (Object) mcArgs);
- } catch (Exception e1)
- {
- Utils.log("Failed to invoke the Minecraft main class:", "Fatal");
- e1.printStackTrace(System.err);
- return -1;
- }
+ return -1;
}
- return 0;
}
int launchWithMainClass()
@@ -153,9 +159,9 @@ public class OneSixLauncher implements Launcher
else
{
mcparams.add("--width");
- mcparams.add(Integer.toString(winSize.width));
+ mcparams.add(Integer.toString(winSizeW));
mcparams.add("--height");
- mcparams.add(Integer.toString(winSize.height));
+ mcparams.add(Integer.toString(winSizeH));
}
// Get the Minecraft Class.
@@ -181,53 +187,7 @@ public class OneSixLauncher implements Launcher
e.printStackTrace(System.err);
return -1;
}
- /*
- final java.nio.ByteBuffer[] icons = IconLoader.load("icon.png");
- new Thread() {
- public void run() {
- ClassLoader cl = ClassLoader.getSystemClassLoader();
- try
- {
- Class<?> Display;
- Method isCreated;
- Method setTitle;
- Method setIcon;
- Field fieldWindowCreated;
- Boolean created = false;
- Display = cl.loadClass("org.lwjgl.opengl.Display");
- 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(!created)
- {
- try
- {
- Thread.sleep(150);
- created = (Boolean) fieldWindowCreated.get( null );
- } catch (InterruptedException ignored) {}
- }
- // Give it a bit more time ;)
- Thread.sleep(150);
- // set the title
- setTitle.invoke(null,windowTitle);
- // only set icon when there's actually something to set...
- if(icons.length > 0)
- {
- setIcon.invoke(null,(Object)icons);
- }
- }
- catch (Exception e)
- {
- System.err.println("Couldn't set window icon or title.");
- e.printStackTrace(System.err);
- }
- }
- }
- .start();
- */
+
// init params for the main method to chomp on.
String[] paramsArray = mcparams.toArray(new String[mcparams.size()]);
try
@@ -257,32 +217,6 @@ public class OneSixLauncher implements Launcher
return -1;
}
- // add libraries to classpath
- if(!Utils.addToClassPath(libraries))
- {
- System.err.println("Halting launch due to previous errors.");
- return -1;
- }
-
- // set the native libs path... the brute force way
- try
- {
- System.setProperty("java.library.path", nativePath);
- System.setProperty("org.lwjgl.librarypath", nativePath);
- System.setProperty("net.java.games.input.librarypath", nativePath);
- // 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);
- System.err.println("Minecraft might fail to launch...");
- }
-
// grab the system classloader and ...
cl = ClassLoader.getSystemClassLoader();
diff --git a/libraries/launcher/org/simplericity/macify/eawt/Application.java b/libraries/launcher/org/simplericity/macify/eawt/Application.java
deleted file mode 100644
index 153bb9ee..00000000
--- a/libraries/launcher/org/simplericity/macify/eawt/Application.java
+++ /dev/null
@@ -1,176 +0,0 @@
-package org.simplericity.macify.eawt;
-
-/*
- * Copyright 2007 Eirik Bjorsnos.
- *
- * 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.
- */
-
-import java.awt.*;
-import java.awt.image.BufferedImage;
-
-/**
- * The Macify Library API interface provides integration with the OS X platform for Java Applications.
- * The API includes a facade to the
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/index.html">
- * Apple Java Extensions API
- * </a>.
- * Additionally, it provides access to several useful methods in the Cocoa NSApplication API.
- *
- * The default implementation of this interface is {@link org.simplericity.macify.eawt.DefaultApplication}.
- */
-public interface Application {
-
- static int REQUEST_USER_ATTENTION_TYPE_CRITICAL = 1 ;
- static int REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL = 2 ;
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addAboutMenuItem()">
- * Apple's API
- * </a>.
- */
- void addAboutMenuItem();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addApplicationListener(com.apple.eawt.ApplicationListener)">
- * Apple's API
- * </a>.
- */
- void addApplicationListener(ApplicationListener applicationListener);
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#addPreferencesMenuItem()">
- * Apple's API
- * </a>.
- */
- void addPreferencesMenuItem();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledAboutMenu()">
- * Apple's API
- * </a>.
- */
- boolean getEnabledAboutMenu();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledPreferencesMenu()">
- * Apple's API
- * </a>.
- */
- boolean getEnabledPreferencesMenu();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#isAboutMenuItemPresent()">
- * Apple's API
- * </a>.
- */
- boolean isAboutMenuItemPresent();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#isPreferencesMenuItemPresent()">
- * Apple's API
- * </a>.
- */
- boolean isPreferencesMenuItemPresent();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removeAboutMenuItem()">
- * Apple's API
- * </a>.
- */
- void removeAboutMenuItem();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removeApplicationListener(com.apple.eawt.ApplicationListener)">
- * Apple's API
- * </a>.
- */
- void removeApplicationListener(ApplicationListener applicationListener);
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#removePreferencesMenuItem()">
- * Apple's API
- * </a>.
- */
- void removePreferencesMenuItem();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledAboutMenu()">
- * Apple's API
- * </a>.
- */
- void setEnabledAboutMenu(boolean enabled);
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getEnabledPreferencesMenu()">
- * Apple's API
- * </a>.
- */
- void setEnabledPreferencesMenu(boolean enabled);
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Java/Reference/1.5.0/appledoc/api/com/apple/eawt/Application.html#getMouseLocationOnScreen()">
- * Apple's API
- * </a>.
- */
- Point getMouseLocationOnScreen();
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/doc/uid/TP40004004">
- * Apple's NSApplication Class Reference
- * </a>.
- * @param type on of {@link #REQUEST_USER_ATTENTION_TYPE_CRITICAL} or {@link #REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL}.
- */
- int requestUserAttention(int type);
-
- /**
- * See
- * <a href="http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/Classes/NSApplication_Class/index.html#//apple_ref/doc/uid/TP40004004">
- * Apple's NSApplication Class Reference
- * </a>
- */
- void cancelUserAttentionRequest(int request);
-
- /**
- * Update the application's icon image
- * @param image
- */
- void setApplicationIconImage(BufferedImage image);
-
- /**
- * Get the application's icon image.
- */
- BufferedImage getApplicationIconImage();
-
- /**
- * Determines whether the application is running on a Mac AND the Apple Extensions API classes are available.
- * @return
- */
- boolean isMac();
-
-
-}
diff --git a/libraries/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java b/libraries/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java
deleted file mode 100644
index e9c3db7d..00000000
--- a/libraries/launcher/org/simplericity/macify/eawt/ApplicationAdapter.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.simplericity.macify.eawt;
-
-/*
- * Copyright 2007 Eirik Bjorsnos.
- *
- * 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.
- */
-
-public class ApplicationAdapter implements ApplicationListener {
-
- public void handleQuit(ApplicationEvent event) {
-
- }
-
- public void handleAbout(ApplicationEvent event) {
-
- }
-
- public void handleOpenApplication(ApplicationEvent event) {
-
- }
-
- public void handleOpenFile(ApplicationEvent event) {
-
- }
-
- public void handlePreferences(ApplicationEvent event) {
-
- }
-
- public void handlePrintFile(ApplicationEvent event) {
-
- }
-
- public void handleReOpenApplication(ApplicationEvent event) {
-
- }
-}
diff --git a/libraries/launcher/org/simplericity/macify/eawt/ApplicationEvent.java b/libraries/launcher/org/simplericity/macify/eawt/ApplicationEvent.java
deleted file mode 100644
index 78420355..00000000
--- a/libraries/launcher/org/simplericity/macify/eawt/ApplicationEvent.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.simplericity.macify.eawt;
-
-/*
- * Copyright 2007 Eirik Bjorsnos.
- *
- * 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.
- */
-
-public interface ApplicationEvent {
- String getFilename();
- boolean isHandled();
- void setHandled(boolean handled);
- Object getSource();
- String toString();
-}
diff --git a/libraries/launcher/org/simplericity/macify/eawt/ApplicationListener.java b/libraries/launcher/org/simplericity/macify/eawt/ApplicationListener.java
deleted file mode 100644
index a291bee4..00000000
--- a/libraries/launcher/org/simplericity/macify/eawt/ApplicationListener.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package org.simplericity.macify.eawt;
-
-/*
- * Copyright 2007 Eirik Bjorsnos.
- *
- * 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.
- */
-
-public interface ApplicationListener {
- void handleAbout(ApplicationEvent event);
- void handleOpenApplication(ApplicationEvent event);
- void handleOpenFile(ApplicationEvent event);
- void handlePreferences(ApplicationEvent event);
- void handlePrintFile(ApplicationEvent event);
- void handleQuit(ApplicationEvent event);
- void handleReOpenApplication(ApplicationEvent event);
-}
diff --git a/libraries/launcher/org/simplericity/macify/eawt/DefaultApplication.java b/libraries/launcher/org/simplericity/macify/eawt/DefaultApplication.java
deleted file mode 100644
index 5752a350..00000000
--- a/libraries/launcher/org/simplericity/macify/eawt/DefaultApplication.java
+++ /dev/null
@@ -1,418 +0,0 @@
-package org.simplericity.macify.eawt;
-
-/*
- * Copyright 2007 Eirik Bjorsnos.
- *
- * 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.
- */
-
-
-import javax.imageio.ImageIO;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.io.*;
-import java.lang.reflect.*;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.net.MalformedURLException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-
-/**
- * Implements Application by calling the Mac OS X API through reflection.
- * If this class is used on a non-OS X platform the operations will have no effect or they will simulate
- * what the Apple API would do for those who manipulate state. ({@link #setEnabledAboutMenu(boolean)} etc.)
- */
-@SuppressWarnings("unchecked")
-public class DefaultApplication implements Application {
-
- private Object application;
- private Class applicationListenerClass;
-
- Map listenerMap = Collections.synchronizedMap(new HashMap<Object, Object>());
- private boolean enabledAboutMenu = true;
- private boolean enabledPreferencesMenu;
- private boolean aboutMenuItemPresent = true;
- private boolean preferencesMenuItemPresent;
- private ClassLoader classLoader;
-
- public DefaultApplication() {
- try {
- final File file = new File("/System/Library/Java");
- if (file.exists()) {
- ClassLoader scl = ClassLoader.getSystemClassLoader();
- Class clc = scl.getClass();
- if (URLClassLoader.class.isAssignableFrom(clc)) {
- Method addUrl = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
- addUrl.setAccessible(true);
- addUrl.invoke(scl, new Object[]{file.toURI().toURL()});
- }
- }
-
- Class appClass = Class.forName("com.apple.eawt.Application");
- application = appClass.getMethod("getApplication", new Class[0]).invoke(null, new Object[0]);
- applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener");
- } catch (ClassNotFoundException e) {
- application = null;
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (MalformedURLException e) {
- throw new RuntimeException(e);
- }
-
- }
-
- public boolean isMac() {
- return application != null;
- }
-
- public void addAboutMenuItem() {
- if (isMac()) {
- callMethod(application, "addAboutMenuItem");
- } else {
- this.aboutMenuItemPresent = true;
- }
- }
-
- public void addApplicationListener(ApplicationListener applicationListener) {
-
- if (!Modifier.isPublic(applicationListener.getClass().getModifiers())) {
- throw new IllegalArgumentException("ApplicationListener must be a public class");
- }
- if (isMac()) {
- Object listener = Proxy.newProxyInstance(getClass().getClassLoader(),
- new Class[]{applicationListenerClass},
- new ApplicationListenerInvocationHandler(applicationListener));
-
- callMethod(application, "addApplicationListener", new Class[]{applicationListenerClass}, new Object[]{listener});
- listenerMap.put(applicationListener, listener);
- } else {
- listenerMap.put(applicationListener, applicationListener);
- }
- }
-
- public void addPreferencesMenuItem() {
- if (isMac()) {
- callMethod("addPreferencesMenuItem");
- } else {
- this.preferencesMenuItemPresent = true;
- }
- }
-
- public boolean getEnabledAboutMenu() {
- if (isMac()) {
- return callMethod("getEnabledAboutMenu").equals(Boolean.TRUE);
- } else {
- return enabledAboutMenu;
- }
- }
-
- public boolean getEnabledPreferencesMenu() {
- if (isMac()) {
- Object result = callMethod("getEnabledPreferencesMenu");
- return result.equals(Boolean.TRUE);
- } else {
- return enabledPreferencesMenu;
- }
- }
-
- public Point getMouseLocationOnScreen() {
- if (isMac()) {
- try {
- Method method = application.getClass().getMethod("getMouseLocationOnScreen", new Class[0]);
- return (Point) method.invoke(null, new Object[0]);
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- }
- } else {
- return new Point(0, 0);
- }
- }
-
- public boolean isAboutMenuItemPresent() {
- if (isMac()) {
- return callMethod("isAboutMenuItemPresent").equals(Boolean.TRUE);
- } else {
- return aboutMenuItemPresent;
- }
- }
-
- public boolean isPreferencesMenuItemPresent() {
- if (isMac()) {
- return callMethod("isPreferencesMenuItemPresent").equals(Boolean.TRUE);
- } else {
- return this.preferencesMenuItemPresent;
- }
- }
-
- public void removeAboutMenuItem() {
- if (isMac()) {
- callMethod("removeAboutMenuItem");
- } else {
- this.aboutMenuItemPresent = false;
- }
- }
-
- public synchronized void removeApplicationListener(ApplicationListener applicationListener) {
- if (isMac()) {
- Object listener = listenerMap.get(applicationListener);
- callMethod(application, "removeApplicationListener", new Class[]{applicationListenerClass}, new Object[]{listener});
-
- }
- listenerMap.remove(applicationListener);
- }
-
- public void removePreferencesMenuItem() {
- if (isMac()) {
- callMethod("removeAboutMenuItem");
- } else {
- this.preferencesMenuItemPresent = false;
- }
- }
-
- public void setEnabledAboutMenu(boolean enabled) {
- if (isMac()) {
- callMethod(application, "setEnabledAboutMenu", new Class[]{Boolean.TYPE}, new Object[]{Boolean.valueOf(enabled)});
- } else {
- this.enabledAboutMenu = enabled;
- }
- }
-
- public void setEnabledPreferencesMenu(boolean enabled) {
- if (isMac()) {
- callMethod(application, "setEnabledPreferencesMenu", new Class[]{Boolean.TYPE}, new Object[]{Boolean.valueOf(enabled)});
- } else {
- this.enabledPreferencesMenu = enabled;
- }
-
- }
-
- public int requestUserAttention(int type) {
- if (type != REQUEST_USER_ATTENTION_TYPE_CRITICAL && type != REQUEST_USER_ATTENTION_TYPE_INFORMATIONAL) {
- throw new IllegalArgumentException("Requested user attention type is not allowed: " + type);
- }
- try {
- Object application = getNSApplication();
- Field critical = application.getClass().getField("UserAttentionRequestCritical");
- Field informational = application.getClass().getField("UserAttentionRequestInformational");
- Field actual = type == REQUEST_USER_ATTENTION_TYPE_CRITICAL ? critical : informational;
-
- return ((Integer) application.getClass().getMethod("requestUserAttention", new Class[]{Integer.TYPE}).invoke(application, new Object[]{actual.get(null)})).intValue();
-
- } catch (ClassNotFoundException e) {
- return -1;
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (NoSuchFieldException e) {
- throw new RuntimeException(e);
- }
- }
-
- public void cancelUserAttentionRequest(int request) {
- try {
- Object application = getNSApplication();
- application.getClass().getMethod("cancelUserAttentionRequest", new Class[]{Integer.TYPE}).invoke(application, new Object[]{new Integer(request)});
- } catch (ClassNotFoundException e) {
- // Nada
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- }
- }
-
- private Object getNSApplication() throws ClassNotFoundException {
- try {
- Class applicationClass = Class.forName("com.apple.cocoa.application.NSApplication");
- return applicationClass.getMethod("sharedApplication", new Class[0]).invoke(null, new Object[0]);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- }
- }
-
- public void setApplicationIconImage(BufferedImage image) {
- if (isMac()) {
- try {
- Method setDockIconImage = application.getClass().getMethod("setDockIconImage", Image.class);
-
- try {
- setDockIconImage.invoke(application, image);
- } catch (IllegalAccessException e) {
-
- } catch (InvocationTargetException e) {
-
- }
- } catch (NoSuchMethodException mnfe) {
-
-
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- try {
- ImageIO.write(image, "png", stream);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
-
- try {
- Class nsDataClass = Class.forName("com.apple.cocoa.foundation.NSData");
- Constructor constructor = nsDataClass.getConstructor(new Class[]{new byte[0].getClass()});
-
- Object nsData = constructor.newInstance(new Object[]{stream.toByteArray()});
-
- Class nsImageClass = Class.forName("com.apple.cocoa.application.NSImage");
- Object nsImage = nsImageClass.getConstructor(new Class[]{nsDataClass}).newInstance(new Object[]{nsData});
-
- Object application = getNSApplication();
-
- application.getClass().getMethod("setApplicationIconImage", new Class[]{nsImageClass}).invoke(application, new Object[]{nsImage});
-
- } catch (ClassNotFoundException e) {
-
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (InstantiationException e) {
- throw new RuntimeException(e);
- }
-
- }
-
- }
- }
-
- public BufferedImage getApplicationIconImage() {
- if (isMac()) {
-
- try {
- Method getDockIconImage = application.getClass().getMethod("getDockIconImage");
- try {
- return (BufferedImage) getDockIconImage.invoke(application);
- } catch (IllegalAccessException e) {
-
- } catch (InvocationTargetException e) {
-
- }
- } catch (NoSuchMethodException nsme) {
-
- try {
- Class nsDataClass = Class.forName("com.apple.cocoa.foundation.NSData");
- Class nsImageClass = Class.forName("com.apple.cocoa.application.NSImage");
- Object application = getNSApplication();
- Object nsImage = application.getClass().getMethod("applicationIconImage", new Class[0]).invoke(application, new Object[0]);
-
- Object nsData = nsImageClass.getMethod("TIFFRepresentation", new Class[0]).invoke(nsImage, new Object[0]);
-
- Integer length = (Integer) nsDataClass.getMethod("length", new Class[0]).invoke(nsData, new Object[0]);
- byte[] bytes = (byte[]) nsDataClass.getMethod("bytes", new Class[]{Integer.TYPE, Integer.TYPE}).invoke(nsData, new Object[]{Integer.valueOf(0), length});
-
- BufferedImage image = ImageIO.read(new ByteArrayInputStream(bytes));
- return image;
-
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- }
-
- return null;
- }
-
- private Object callMethod(String methodname) {
- return callMethod(application, methodname, new Class[0], new Object[0]);
- }
-
- private Object callMethod(Object object, String methodname) {
- return callMethod(object, methodname, new Class[0], new Object[0]);
- }
-
- private Object callMethod(Object object, String methodname, Class[] classes, Object[] arguments) {
- try {
- if (classes == null) {
- classes = new Class[arguments.length];
- for (int i = 0; i < classes.length; i++) {
- classes[i] = arguments[i].getClass();
-
- }
- }
- Method addListnerMethod = object.getClass().getMethod(methodname, classes);
- return addListnerMethod.invoke(object, arguments);
- } catch (NoSuchMethodException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (InvocationTargetException e) {
- throw new RuntimeException(e);
- }
- }
-
- class ApplicationListenerInvocationHandler implements InvocationHandler {
- private ApplicationListener applicationListener;
-
- ApplicationListenerInvocationHandler(ApplicationListener applicationListener) {
- this.applicationListener = applicationListener;
- }
-
- public Object invoke(Object object, Method appleMethod, Object[] objects) throws Throwable {
-
- ApplicationEvent event = createApplicationEvent(objects[0]);
- try {
- Method method = applicationListener.getClass().getMethod(appleMethod.getName(), new Class[]{ApplicationEvent.class});
- return method.invoke(applicationListener, new Object[]{event});
- } catch (NoSuchMethodException e) {
- if (appleMethod.getName().equals("equals") && objects.length == 1) {
- return Boolean.valueOf(object == objects[0]);
- }
- return null;
- }
- }
- }
-
- private ApplicationEvent createApplicationEvent(final Object appleApplicationEvent) {
- return (ApplicationEvent) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{ApplicationEvent.class}, new InvocationHandler() {
- public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
- return appleApplicationEvent.getClass().getMethod(method.getName(), method.getParameterTypes()).invoke(appleApplicationEvent, objects);
- }
- });
- }
-}
diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus
-Subproject 4b305bbd2ac0e7a26987baf7949a484a87b474d
+Subproject 92f8d57227feb94643378ecf595626c60c0f59b
diff --git a/libraries/pack200/CMakeLists.txt b/libraries/pack200/CMakeLists.txt
index 359445d2..b568e506 100644
--- a/libraries/pack200/CMakeLists.txt
+++ b/libraries/pack200/CMakeLists.txt
@@ -37,6 +37,13 @@ target_link_libraries(MultiMC_unpack200 ${ZLIB_LIBRARIES})
set_target_properties(MultiMC_unpack200 PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
generate_export_header(MultiMC_unpack200)
+# Install it
+install(
+ TARGETS MultiMC_unpack200
+ RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
+)
+
if(PACK200_BUILD_BINARY)
add_executable(anti200 anti200.cpp)
target_link_libraries(anti200 MultiMC_unpack200)
diff --git a/libraries/quazip b/libraries/quazip
-Subproject 164acc35fd5f77d353161dcf1c4e121bc2ce756
+Subproject 683e2ec8ada758d6e48d31ec606840802e6941b
diff --git a/libraries/rainbow/CMakeLists.txt b/libraries/rainbow/CMakeLists.txt
index 15019a71..bc561800 100644
--- a/libraries/rainbow/CMakeLists.txt
+++ b/libraries/rainbow/CMakeLists.txt
@@ -13,3 +13,10 @@ add_library(MultiMC_rainbow SHARED ${RAINBOW_SOURCES})
target_include_directories(MultiMC_rainbow PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
qt5_use_modules(MultiMC_rainbow Core Gui)
+
+# Install it
+install(
+ TARGETS MultiMC_rainbow
+ RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
+)
diff --git a/libraries/rainbow/include/rainbow_config.h b/libraries/rainbow/include/rainbow_config.h
index cac53837..9290dc8a 100644
--- a/libraries/rainbow/include/rainbow_config.h
+++ b/libraries/rainbow/include/rainbow_config.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2017 MultiMC Contributors
+/* Copyright 2013-2018 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
diff --git a/libraries/systeminfo/include/sys.h b/libraries/systeminfo/include/sys.h
index 36f7d9cd..e40d9a92 100644
--- a/libraries/systeminfo/include/sys.h
+++ b/libraries/systeminfo/include/sys.h
@@ -3,6 +3,7 @@
namespace Sys
{
+const uint64_t megabyte = 1024ull * 1024ull;
struct KernelInfo
{
QString kernelName;
diff --git a/travis/prepare.sh b/travis/prepare.sh
index dc63f0bd..edf9df73 100644
--- a/travis/prepare.sh
+++ b/travis/prepare.sh
@@ -9,16 +9,20 @@ then
echo $QT_WITHOUT_DOTS
echo $QT_PKG_PREFIX
echo $QT_PKG_INSTALL
- sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS}
+ if [ "$TRAVIS_DIST" = "precise" ]; then
+ sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS}
+ else
+ sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS}-$TRAVIS_DIST
+ fi
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test # for a recent GCC
- sudo add-apt-repository "deb http://llvm.org/apt/precise/ llvm-toolchain-precise-3.5 main"
+ sudo add-apt-repository "deb http://llvm.org/apt/${TRAVIS_DIST}/ llvm-toolchain-${TRAVIS_DIST}-3.5 main"
sudo apt-get update -qq
- sudo apt-get install ${QT_PKG_PREFIX}base ${QT_PKG_PREFIX}svg ${QT_PKG_PREFIX}tools ${QT_PKG_PREFIX}webkit
+ sudo apt-get install ${QT_PKG_PREFIX}base ${QT_PKG_PREFIX}svg ${QT_PKG_PREFIX}tools
sudo mkdir -p /opt/cmake-3/
- wget --no-check-certificate http://www.cmake.org/files/v3.2/cmake-3.2.2-Linux-x86_64.sh
- sudo sh cmake-3.2.2-Linux-x86_64.sh --skip-license --prefix=/opt/cmake-3/
+ wget --no-check-certificate http://www.cmake.org/files/v3.9/cmake-3.9.3-Linux-x86_64.sh
+ sudo sh cmake-3.9.3-Linux-x86_64.sh --skip-license --prefix=/opt/cmake-3/
export CMAKE_PREFIX_PATH=/opt/$QT_PKG_INSTALL/lib/cmake
export PATH=/opt/cmake-3/bin:/opt/$QT_PKG_INSTALL/bin:$PATH