aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE.md51
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.yml51
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml5
-rw-r--r--.github/ISSUE_TEMPLATE/suggestion.yml40
-rw-r--r--BUILD.md74
-rw-r--r--CMakeLists.txt16
-rw-r--r--COPYING.md55
-rw-r--r--README.md4
-rw-r--r--api/gui/SkinUtils.cpp2
-rw-r--r--api/gui/SkinUtils.h2
-rw-r--r--api/gui/icons/IconList.cpp2
-rw-r--r--api/gui/icons/IconList.h2
-rw-r--r--api/gui/icons/MMCIcon.cpp2
-rw-r--r--api/gui/icons/MMCIcon.h2
-rw-r--r--api/logic/BaseInstaller.cpp2
-rw-r--r--api/logic/BaseInstaller.h2
-rw-r--r--api/logic/BaseInstance.cpp25
-rw-r--r--api/logic/BaseInstance.h12
-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.txt73
-rw-r--r--api/logic/Commandline.cpp2
-rw-r--r--api/logic/Commandline.h2
-rw-r--r--api/logic/Env.cpp11
-rw-r--r--api/logic/InstanceImportTask.cpp53
-rw-r--r--api/logic/InstanceImportTask.h25
-rw-r--r--api/logic/InstanceList.cpp2
-rw-r--r--api/logic/InstanceList.h2
-rw-r--r--api/logic/LoggedProcess.h2
-rw-r--r--api/logic/MMCZip.cpp71
-rw-r--r--api/logic/MMCZip.h29
-rw-r--r--api/logic/NullInstance.h4
-rw-r--r--api/logic/RWStorage.h3
-rw-r--r--api/logic/Version_test.cpp2
-rw-r--r--api/logic/java/JavaChecker.cpp4
-rw-r--r--api/logic/java/JavaChecker.h1
-rw-r--r--api/logic/java/JavaCheckerJob.cpp2
-rw-r--r--api/logic/java/JavaCheckerJob.h2
-rw-r--r--api/logic/java/JavaInstallList.cpp2
-rw-r--r--api/logic/java/JavaInstallList.h2
-rw-r--r--api/logic/java/JavaUtils.cpp78
-rw-r--r--api/logic/java/JavaUtils.h4
-rw-r--r--api/logic/java/launch/CheckJava.cpp33
-rw-r--r--api/logic/java/launch/CheckJava.h4
-rw-r--r--api/logic/launch/LaunchStep.cpp2
-rw-r--r--api/logic/launch/LaunchStep.h2
-rw-r--r--api/logic/launch/LaunchTask.cpp2
-rw-r--r--api/logic/launch/LaunchTask.h2
-rw-r--r--api/logic/launch/steps/LookupServerAddress.cpp95
-rw-r--r--api/logic/launch/steps/LookupServerAddress.h49
-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.cpp2
-rw-r--r--api/logic/launch/steps/Update.h2
-rw-r--r--api/logic/meta/BaseEntity.cpp2
-rw-r--r--api/logic/meta/BaseEntity.h2
-rw-r--r--api/logic/meta/Index.cpp2
-rw-r--r--api/logic/meta/Index.h2
-rw-r--r--api/logic/meta/JsonFormat.cpp2
-rw-r--r--api/logic/meta/JsonFormat.h2
-rw-r--r--api/logic/meta/Version.cpp2
-rw-r--r--api/logic/meta/Version.h2
-rw-r--r--api/logic/meta/VersionList.cpp2
-rw-r--r--api/logic/meta/VersionList.h2
-rw-r--r--api/logic/minecraft/AssetsUtils.cpp2
-rw-r--r--api/logic/minecraft/AssetsUtils.h2
-rw-r--r--api/logic/minecraft/GradleSpecifier.h42
-rw-r--r--api/logic/minecraft/GradleSpecifier_test.cpp5
-rw-r--r--api/logic/minecraft/Library.cpp8
-rw-r--r--api/logic/minecraft/Library_test.cpp3
-rw-r--r--api/logic/minecraft/MinecraftInstance.cpp119
-rw-r--r--api/logic/minecraft/MinecraftInstance.h9
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.h2
-rw-r--r--api/logic/minecraft/MinecraftUpdate.cpp2
-rw-r--r--api/logic/minecraft/MinecraftUpdate.h2
-rw-r--r--api/logic/minecraft/MojangVersionFormat.cpp12
-rw-r--r--api/logic/minecraft/MojangVersionFormat.h3
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.cpp34
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.h9
-rw-r--r--api/logic/minecraft/OpSys.cpp2
-rw-r--r--api/logic/minecraft/OpSys.h2
-rw-r--r--api/logic/minecraft/PackProfile.cpp2
-rw-r--r--api/logic/minecraft/PackProfile.h8
-rw-r--r--api/logic/minecraft/Rule.cpp2
-rw-r--r--api/logic/minecraft/Rule.h2
-rw-r--r--api/logic/minecraft/VersionFilterData.cpp53
-rw-r--r--api/logic/minecraft/VersionFilterData.h5
-rw-r--r--api/logic/minecraft/World.cpp213
-rw-r--r--api/logic/minecraft/World.h35
-rw-r--r--api/logic/minecraft/WorldList.cpp21
-rw-r--r--api/logic/minecraft/WorldList.h8
-rw-r--r--api/logic/minecraft/auth/MojangAccount.cpp2
-rw-r--r--api/logic/minecraft/auth/MojangAccount.h2
-rw-r--r--api/logic/minecraft/auth/MojangAccountList.cpp2
-rw-r--r--api/logic/minecraft/auth/MojangAccountList.h2
-rw-r--r--api/logic/minecraft/auth/YggdrasilTask.cpp2
-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/launch/ClaimAccount.h2
-rw-r--r--api/logic/minecraft/launch/CreateGameFolders.cpp28
-rw-r--r--api/logic/minecraft/launch/CreateGameFolders.h (renamed from api/logic/minecraft/launch/CreateServerResourcePacksFolder.h)10
-rw-r--r--api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp19
-rw-r--r--api/logic/minecraft/launch/DirectJavaLaunch.cpp23
-rw-r--r--api/logic/minecraft/launch/DirectJavaLaunch.h11
-rw-r--r--api/logic/minecraft/launch/ExtractNatives.cpp23
-rw-r--r--api/logic/minecraft/launch/ExtractNatives.h2
-rw-r--r--api/logic/minecraft/launch/LauncherPartLaunch.cpp21
-rw-r--r--api/logic/minecraft/launch/LauncherPartLaunch.h11
-rw-r--r--api/logic/minecraft/launch/MinecraftServerTarget.cpp66
-rw-r--r--api/logic/minecraft/launch/MinecraftServerTarget.h30
-rw-r--r--api/logic/minecraft/launch/ModMinecraftJar.cpp2
-rw-r--r--api/logic/minecraft/launch/ModMinecraftJar.h2
-rw-r--r--api/logic/minecraft/launch/PrintInstanceInfo.cpp4
-rw-r--r--api/logic/minecraft/launch/PrintInstanceInfo.h7
-rw-r--r--api/logic/minecraft/launch/ReconstructAssets.cpp2
-rw-r--r--api/logic/minecraft/launch/ReconstructAssets.h2
-rw-r--r--api/logic/minecraft/launch/ScanModFolders.cpp11
-rw-r--r--api/logic/minecraft/launch/ScanModFolders.h2
-rw-r--r--api/logic/minecraft/launch/VerifyJavaInstall.cpp34
-rw-r--r--api/logic/minecraft/launch/VerifyJavaInstall.h17
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.cpp4
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.h7
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.cpp2
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.h2
-rw-r--r--api/logic/minecraft/mod/LocalModParseTask.cpp171
-rw-r--r--api/logic/minecraft/mod/Mod.cpp2
-rw-r--r--api/logic/minecraft/mod/Mod.h2
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.cpp2
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.h2
-rw-r--r--api/logic/minecraft/mod/ResourcePackFolderModel.cpp23
-rw-r--r--api/logic/minecraft/mod/ResourcePackFolderModel.h13
-rw-r--r--api/logic/minecraft/mod/TexturePackFolderModel.cpp23
-rw-r--r--api/logic/minecraft/mod/TexturePackFolderModel.h13
-rw-r--r--api/logic/minecraft/services/SkinDelete.cpp42
-rw-r--r--api/logic/minecraft/services/SkinDelete.h30
-rw-r--r--api/logic/minecraft/services/SkinUpload.cpp (renamed from api/logic/minecraft/SkinUpload.cpp)31
-rw-r--r--api/logic/minecraft/services/SkinUpload.h (renamed from api/logic/minecraft/SkinUpload.h)0
-rw-r--r--api/logic/minecraft/update/FMLLibrariesTask.cpp2
-rw-r--r--api/logic/modplatform/atlauncher/ATLPackIndex.cpp33
-rw-r--r--api/logic/modplatform/atlauncher/ATLPackIndex.h36
-rw-r--r--api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp764
-rw-r--r--api/logic/modplatform/atlauncher/ATLPackInstallTask.h102
-rw-r--r--api/logic/modplatform/atlauncher/ATLPackManifest.cpp218
-rw-r--r--api/logic/modplatform/atlauncher/ATLPackManifest.h126
-rw-r--r--api/logic/modplatform/flame/FlamePackIndex.cpp92
-rw-r--r--api/logic/modplatform/flame/FlamePackIndex.h43
-rw-r--r--api/logic/modplatform/flame/PackManifest.cpp4
-rw-r--r--api/logic/modplatform/legacy_ftb/PackInstallTask.cpp1
-rw-r--r--api/logic/modplatform/legacy_ftb/PackInstallTask.h7
-rw-r--r--api/logic/modplatform/modpacksch/FTBPackInstallTask.cpp209
-rw-r--r--api/logic/modplatform/modpacksch/FTBPackInstallTask.h47
-rw-r--r--api/logic/modplatform/modpacksch/FTBPackManifest.cpp156
-rw-r--r--api/logic/modplatform/modpacksch/FTBPackManifest.h127
-rw-r--r--api/logic/modplatform/technic/SingleZipPackInstallTask.cpp141
-rw-r--r--api/logic/modplatform/technic/SingleZipPackInstallTask.h65
-rw-r--r--api/logic/modplatform/technic/SolderPackInstallTask.cpp207
-rw-r--r--api/logic/modplatform/technic/SolderPackInstallTask.h60
-rw-r--r--api/logic/modplatform/technic/TechnicPackProcessor.cpp208
-rw-r--r--api/logic/modplatform/technic/TechnicPackProcessor.h35
-rw-r--r--api/logic/mojang/PackageManifest.cpp427
-rw-r--r--api/logic/mojang/PackageManifest.h173
-rw-r--r--api/logic/mojang/PackageManifest_test.cpp344
-rw-r--r--api/logic/mojang/testdata/1.8.0_202-x64.json1
-rwxr-xr-xapi/logic/mojang/testdata/inspect/a/b.txt0
l---------api/logic/mojang/testdata/inspect/a/b/b.txt1
-rw-r--r--api/logic/mojang/testdata/inspect_win/a/b.txt0
-rw-r--r--api/logic/mojang/testdata/inspect_win/a/b/b.txt0
-rw-r--r--api/logic/net/Download.cpp5
-rw-r--r--api/logic/net/Download.h2
-rw-r--r--api/logic/net/HttpMetaCache.cpp2
-rw-r--r--api/logic/net/HttpMetaCache.h2
-rw-r--r--api/logic/net/NetAction.h2
-rw-r--r--api/logic/net/NetJob.cpp4
-rw-r--r--api/logic/net/NetJob.h4
-rw-r--r--api/logic/net/PasteUpload.cpp3
-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/screenshots/ImgurAlbumCreation.cpp4
-rw-r--r--api/logic/screenshots/ImgurUpload.cpp4
-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.cpp2
-rw-r--r--api/logic/tasks/Task.h2
-rw-r--r--api/logic/translations/TranslationsModel.cpp23
-rw-r--r--api/logic/translations/TranslationsModel.h2
-rw-r--r--api/logic/updater/DownloadTask.cpp2
-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.txt41
-rw-r--r--application/HoeDown.h2
-rw-r--r--application/InstancePageProvider.h2
-rw-r--r--application/InstanceWindow.cpp8
-rw-r--r--application/InstanceWindow.h2
-rw-r--r--application/JavaCommon.cpp3
-rw-r--r--application/KonamiCode.cpp2
-rw-r--r--application/LaunchController.cpp44
-rw-r--r--application/LaunchController.h7
-rw-r--r--application/MainWindow.cpp78
-rw-r--r--application/MainWindow.h4
-rw-r--r--application/MultiMC.cpp92
-rw-r--r--application/MultiMC.h10
-rw-r--r--application/dialogs/AboutDialog.cpp2
-rw-r--r--application/dialogs/AboutDialog.h2
-rw-r--r--application/dialogs/AboutDialog.ui2
-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.cpp2
-rw-r--r--application/dialogs/ExportInstanceDialog.h2
-rw-r--r--application/dialogs/IconPickerDialog.cpp2
-rw-r--r--application/dialogs/IconPickerDialog.h2
-rw-r--r--application/dialogs/LoginDialog.cpp2
-rw-r--r--application/dialogs/LoginDialog.h2
-rw-r--r--application/dialogs/LoginDialog.ui14
-rw-r--r--application/dialogs/NewComponentDialog.cpp2
-rw-r--r--application/dialogs/NewComponentDialog.h2
-rw-r--r--application/dialogs/NewInstanceDialog.cpp24
-rw-r--r--application/dialogs/NewInstanceDialog.h7
-rw-r--r--application/dialogs/ProfileSelectDialog.cpp2
-rw-r--r--application/dialogs/ProfileSelectDialog.h2
-rw-r--r--application/dialogs/ProgressDialog.cpp2
-rw-r--r--application/dialogs/ProgressDialog.h2
-rw-r--r--application/dialogs/SkinUploadDialog.cpp2
-rw-r--r--application/dialogs/UpdateDialog.h2
-rw-r--r--application/dialogs/VersionSelectDialog.cpp2
-rw-r--r--application/dialogs/VersionSelectDialog.h2
-rw-r--r--application/groupview/GroupView.cpp2
-rw-r--r--application/groupview/GroupView.h2
-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/package/rpm/MultiMC5.spec47
-rw-r--r--application/package/rpm/README.md12
-rw-r--r--application/package/ubuntu/README.md14
-rw-r--r--application/package/ubuntu/multimc/DEBIAN/control4
-rw-r--r--application/package/ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml54
-rw-r--r--application/package/ubuntu/readme.md12
-rw-r--r--application/pagedialog/PageDialog.cpp2
-rw-r--r--application/pagedialog/PageDialog.h2
-rw-r--r--application/pages/BasePage.h2
-rw-r--r--application/pages/BasePageProvider.h2
-rw-r--r--application/pages/global/AccountListPage.cpp26
-rw-r--r--application/pages/global/AccountListPage.h23
-rw-r--r--application/pages/global/AccountListPage.ui10
-rw-r--r--application/pages/global/CustomCommandsPage.h2
-rw-r--r--application/pages/global/ExternalToolsPage.cpp2
-rw-r--r--application/pages/global/ExternalToolsPage.h2
-rw-r--r--application/pages/global/JavaPage.cpp6
-rw-r--r--application/pages/global/JavaPage.h2
-rw-r--r--application/pages/global/JavaPage.ui6
-rw-r--r--application/pages/global/LanguagePage.h2
-rw-r--r--application/pages/global/MinecraftPage.cpp16
-rw-r--r--application/pages/global/MinecraftPage.h2
-rw-r--r--application/pages/global/MinecraftPage.ui52
-rw-r--r--application/pages/global/MultiMCPage.cpp2
-rw-r--r--application/pages/global/MultiMCPage.h2
-rw-r--r--application/pages/global/PasteEEPage.cpp2
-rw-r--r--application/pages/global/PasteEEPage.h2
-rw-r--r--application/pages/global/ProxyPage.cpp2
-rw-r--r--application/pages/global/ProxyPage.h2
-rw-r--r--application/pages/instance/GameOptionsPage.cpp3
-rw-r--r--application/pages/instance/GameOptionsPage.h2
-rw-r--r--application/pages/instance/InstanceSettingsPage.cpp55
-rw-r--r--application/pages/instance/InstanceSettingsPage.h2
-rw-r--r--application/pages/instance/InstanceSettingsPage.ui154
-rw-r--r--application/pages/instance/LegacyUpgradePage.h2
-rw-r--r--application/pages/instance/LogPage.cpp6
-rw-r--r--application/pages/instance/LogPage.h2
-rw-r--r--application/pages/instance/ModFolderPage.cpp4
-rw-r--r--application/pages/instance/ModFolderPage.h2
-rw-r--r--application/pages/instance/NotesPage.h2
-rw-r--r--application/pages/instance/OtherLogsPage.cpp2
-rw-r--r--application/pages/instance/OtherLogsPage.h2
-rw-r--r--application/pages/instance/ResourcePackPage.h3
-rw-r--r--application/pages/instance/ScreenshotsPage.h2
-rw-r--r--application/pages/instance/ServersPage.cpp11
-rw-r--r--application/pages/instance/ServersPage.h7
-rw-r--r--application/pages/instance/ServersPage.ui6
-rw-r--r--application/pages/instance/TexturePackPage.h2
-rw-r--r--application/pages/instance/VersionPage.cpp32
-rw-r--r--application/pages/instance/VersionPage.h6
-rw-r--r--application/pages/instance/VersionPage.ui18
-rw-r--r--application/pages/instance/WorldListPage.cpp68
-rw-r--r--application/pages/instance/WorldListPage.h4
-rw-r--r--application/pages/instance/WorldListPage.ui24
-rw-r--r--application/pages/modplatform/ImportPage.cpp2
-rw-r--r--application/pages/modplatform/ImportPage.h2
-rw-r--r--application/pages/modplatform/VanillaPage.cpp13
-rw-r--r--application/pages/modplatform/VanillaPage.h2
-rw-r--r--application/pages/modplatform/atlauncher/AtlFilterModel.cpp81
-rw-r--r--application/pages/modplatform/atlauncher/AtlFilterModel.h34
-rw-r--r--application/pages/modplatform/atlauncher/AtlListModel.cpp194
-rw-r--r--application/pages/modplatform/atlauncher/AtlListModel.h52
-rw-r--r--application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp209
-rw-r--r--application/pages/modplatform/atlauncher/AtlOptionalModDialog.h66
-rw-r--r--application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui65
-rw-r--r--application/pages/modplatform/atlauncher/AtlPage.cpp175
-rw-r--r--application/pages/modplatform/atlauncher/AtlPage.h87
-rw-r--r--application/pages/modplatform/atlauncher/AtlPage.ui97
-rw-r--r--application/pages/modplatform/flame/FlameModel.cpp (renamed from application/pages/modplatform/twitch/TwitchModel.cpp)115
-rw-r--r--application/pages/modplatform/flame/FlameModel.h (renamed from application/pages/modplatform/twitch/TwitchModel.h)10
-rw-r--r--application/pages/modplatform/flame/FlamePage.cpp185
-rw-r--r--application/pages/modplatform/flame/FlamePage.h (renamed from application/pages/modplatform/twitch/TwitchPage.h)31
-rw-r--r--application/pages/modplatform/flame/FlamePage.ui90
-rw-r--r--application/pages/modplatform/ftb/FtbFilterModel.cpp64
-rw-r--r--application/pages/modplatform/ftb/FtbFilterModel.h33
-rw-r--r--application/pages/modplatform/ftb/FtbListModel.cpp304
-rw-r--r--application/pages/modplatform/ftb/FtbListModel.h69
-rw-r--r--application/pages/modplatform/ftb/FtbPage.cpp145
-rw-r--r--application/pages/modplatform/ftb/FtbPage.h80
-rw-r--r--application/pages/modplatform/ftb/FtbPage.ui84
-rw-r--r--application/pages/modplatform/legacy_ftb/Page.cpp79
-rw-r--r--application/pages/modplatform/legacy_ftb/Page.h2
-rw-r--r--application/pages/modplatform/legacy_ftb/Page.ui9
-rw-r--r--application/pages/modplatform/technic/TechnicData.h42
-rw-r--r--application/pages/modplatform/technic/TechnicModel.cpp238
-rw-r--r--application/pages/modplatform/technic/TechnicModel.h70
-rw-r--r--application/pages/modplatform/technic/TechnicPage.cpp198
-rw-r--r--application/pages/modplatform/technic/TechnicPage.h78
-rw-r--r--application/pages/modplatform/technic/TechnicPage.ui (renamed from application/pages/modplatform/twitch/TwitchPage.ui)78
-rw-r--r--application/pages/modplatform/twitch/TwitchData.h38
-rw-r--r--application/pages/modplatform/twitch/TwitchPage.cpp111
-rw-r--r--application/resources/MultiMC.icobin85182 -> 55224 bytes
-rw-r--r--application/resources/assets/underconstruction.pngbin0 -> 14490 bytes
-rw-r--r--application/resources/multimc/16x16/patreon.pngbin682 -> 840 bytes
-rw-r--r--application/resources/multimc/22x22/patreon.pngbin976 -> 939 bytes
-rw-r--r--application/resources/multimc/24x24/patreon.pngbin1034 -> 977 bytes
-rw-r--r--application/resources/multimc/32x32/patreon.pngbin1450 -> 1086 bytes
-rw-r--r--application/resources/multimc/48x48/patreon.pngbin2317 -> 1390 bytes
-rw-r--r--application/resources/multimc/64x64/patreon.pngbin3212 -> 1667 bytes
-rw-r--r--application/resources/multimc/multimc.qrc9
-rw-r--r--application/resources/multimc/scalable/atlauncher-placeholder.pngbin0 -> 13542 bytes
-rw-r--r--application/resources/multimc/scalable/atlauncher.svg15
-rw-r--r--application/resources/multimc/scalable/technic.svg14
-rw-r--r--application/resources/multimc/scalable/twitch.svg63
-rw-r--r--application/setupwizard/SetupWizard.h2
-rw-r--r--application/themes/SystemTheme.cpp5
-rw-r--r--application/widgets/CustomCommands.h2
-rw-r--r--application/widgets/JavaSettingsWidget.cpp2
-rw-r--r--application/widgets/LabeledToolButton.cpp2
-rw-r--r--application/widgets/LabeledToolButton.h2
-rw-r--r--application/widgets/LanguageSelectionWidget.h2
-rw-r--r--application/widgets/MCModInfoFrame.cpp3
-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.h2
-rw-r--r--application/widgets/PageContainer_p.h2
-rw-r--r--application/widgets/ServerStatus.cpp2
-rw-r--r--application/widgets/VersionListView.cpp2
-rw-r--r--application/widgets/VersionListView.h2
-rw-r--r--application/widgets/VersionSelectWidget.h2
-rw-r--r--buildconfig/BuildConfig.cpp.in5
-rw-r--r--buildconfig/BuildConfig.h21
-rw-r--r--changelog.md68
-rw-r--r--libraries/README.md7
-rw-r--r--libraries/classparser/include/classparser.h2
-rw-r--r--libraries/classparser/include/classparser_config.h2
-rw-r--r--libraries/classparser/src/classparser.cpp2
-rw-r--r--libraries/classparser/src/javaendian.h33
-rw-r--r--libraries/ganalytics/src/ganalytics_worker.cpp2
-rw-r--r--libraries/javacheck/JavaCheck.java2
-rw-r--r--libraries/launcher/net/minecraft/Launcher.java8
-rw-r--r--libraries/launcher/org/multimc/EntryPoint.java2
-rw-r--r--libraries/launcher/org/multimc/Launcher.java2
-rw-r--r--libraries/launcher/org/multimc/LegacyFrame.java20
-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.java2
-rw-r--r--libraries/launcher/org/multimc/onesix/OneSixLauncher.java18
-rw-r--r--libraries/optional-bare/CMakeLists.txt5
-rw-r--r--libraries/optional-bare/LICENSE.txt23
-rw-r--r--libraries/optional-bare/README.md5
-rw-r--r--libraries/optional-bare/include/nonstd/optional508
-rw-r--r--libraries/rainbow/include/rainbow_config.h2
-rw-r--r--libraries/systeminfo/include/sys.h2
-rw-r--r--libraries/systeminfo/src/sys_unix.cpp1
-rw-r--r--libraries/tomlc99/CMakeLists.txt10
-rw-r--r--libraries/tomlc99/LICENSE22
-rw-r--r--libraries/tomlc99/README.md194
-rw-r--r--libraries/tomlc99/include/toml.h175
-rw-r--r--libraries/tomlc99/src/toml.c2300
414 files changed, 12786 insertions, 1194 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
deleted file mode 100644
index 58596fa0..00000000
--- a/.github/ISSUE_TEMPLATE.md
+++ /dev/null
@@ -1,51 +0,0 @@
-<!--
-Before submitting this issue, please make sure you have:
-
- 1. Filled out this form completely, the only optional field is "additional info".
- - Use as many details as possible and state the problem clearly.
- 2. Proof-read your ENTIRE issue report.
- - Grammar and spelling mistakes make issue reports harder to understand.
- 3. Made sure your problem is not caused by an issue in your own modpack.
- - We provide support for MultiMC, not your modpack. Problems with your modpack will be ignored.
- 4. Given the issue a descriptive title.
- - A good title includes a brief summary of the issue and avoids things such as "Help" and "What?!".
- Use of UPPERCASE is discouraged, as it reads like someone is screaming.
- 5. Place all information below the ---- of lines.
- - It makes the issue look pretty
-
-If you believe your issue to be a bug, please make sure you check the wiki page: https://github.com/MultiMC/MultiMC5/wiki/Report-a-Bug
--->
-
-System Information
------------------------------
-MultiMC version:
-
-Operating System:
-
-Summary of the issue or suggestion:
-----------------------------------------------
-
-
-What should happen:
-------------------------------
-
-
-Steps to reproduce the issue (Add more if needed):
--------------------------------------------------------------
-1.
-
-2.
-
-3.
-
-Suspected cause:
----------------------------
-
-
-Logs/Screenshots:
-----------------------------
-[//]: # (Please refer to https://github.com/MultiMC/MultiMC5/wiki/Log-Upload for instructions on how to attach your logs.)
-
-
-Additional Info:
----------------------------
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 00000000..5b8d858e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,51 @@
+name: Bug Report
+description: File a bug report
+labels: [bug, needs-triage]
+body:
+- type: markdown
+ attributes:
+ value: |
+ If you need help with running Minecraft, please visit us [on our Discord](https://discord.gg/multimc) before making a bug report.
+
+ Before submitting a bug report, please make sure you have read this *entire* form, and that:
+ * You have read the [FAQ](https://github.com/MultiMC/MultiMC5/wiki/FAQ) and it has not answered your question
+ * Your bug is not caused by Minecraft or any mods you have installed.
+ * Your issue has not been reported before, [make sure to use the search function!](https://github.com/MultiMC/MultiMC5/issues)
+
+ **Do not forget to give your issue a descriptive title.** "Bug in the instance screen" makes it hard to distinguish issues at a glance.
+- type: dropdown
+ attributes:
+ label: Operating System
+ description: If you know this bug occurs on multiple operating systems, select all you have tested.
+ multiple: true
+ options:
+ - Windows
+ - macOS
+ - Linux
+ - Other
+- type: textarea
+ attributes:
+ label: Description of bug
+ description: What did you expect to happen, what happened, and why is it incorrect?
+ placeholder: The cat button should show a cat, but it showed a dog instead!
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Steps to reproduce
+ description: A bulleted list, or an exported instance if relevant.
+ placeholder: "* Press the cat button"
+ validations:
+ required: true
+- type: textarea
+ attributes:
+ label: Suspected cause
+ description: If you know what could be causing this bug, describe it here.
+ validations:
+ required: false
+- type: checkboxes
+ attributes:
+ label: This issue is unique
+ options:
+ - label: I have searched the issue tracker and did not find an issue describing my bug.
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 00000000..089f1eb5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+ - name: MultiMC Discord
+ url: https://discord.gg/multimc
+ about: Please ask for support here before opening an issue.
diff --git a/.github/ISSUE_TEMPLATE/suggestion.yml b/.github/ISSUE_TEMPLATE/suggestion.yml
new file mode 100644
index 00000000..4c6a9eb9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/suggestion.yml
@@ -0,0 +1,40 @@
+name: Suggestion
+description: Make a suggestion
+labels: [idea, needs-triage]
+body:
+- type: markdown
+ attributes:
+ value: |
+ ### Use this form to suggest a feature for MultiMC.
+- type: input
+ attributes:
+ label: Role
+ description: In what way do you use MultiMC that needs this feature?
+ placeholder: I play modded Minecraft.
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: Suggestion
+ description: What do you want MultiMC to do?
+ placeholder: I want the cat button to meow.
+ validations:
+ required: true
+- type: input
+ attributes:
+ label: Benefit
+ description: Why do you need MultiMC to do this?
+ placeholder: so that I can always hear a cat when I need to.
+ validations:
+ required: true
+- type: checkboxes
+ attributes:
+ label: This suggestion is unique
+ options:
+ - label: I have searched the issue tracker and did not find an issue describing my suggestion, especially not one that has been rejected.
+ required: true
+- type: markdown
+ attributes:
+ value: |
+ ### You may use the editor below to elaborate further.
+# The issue_body: true up there makes the standard WYSIWYG editor for issues show up down here.
diff --git a/BUILD.md b/BUILD.md
index 9f0061a0..f6b66e70 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -7,12 +7,12 @@ Build Instructions
* [Getting the source](#source)
* [Linux](#linux)
* [Windows](#windows)
-* [OS X](#os-x)
+* [macOS](#macos)
# Note
MultiMC is a portable application and is not supposed to be installed into any system folders.
-That would be anything outside your home folder. Before runing `make install`, make sure
+That would be anything outside your home folder. Before running `make install`, make sure
you set the install path to something you have write access to. Never build this under
an administrator/root level account. Don't use `sudo`. It won't work and it's not supposed to work.
@@ -22,7 +22,7 @@ an administrator/root level account. Don't use `sudo`. It won't work and it's no
Clone the source code using git and grab all the submodules:
```
-git clone git@github.com:MultiMC/MultiMC5.git
+git clone https://github.com/MultiMC/MultiMC5.git
git submodule init
git submodule update
```
@@ -92,14 +92,19 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
## Dependencies
* [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
+ - http://download.qt.io/new_archive/qt/5.6/5.6.0/qt-opensource-windows-x86-mingw492-5.6.0.exe
+ - Download the MinGW version (MSVC version does not work).
+* [OpenSSL](https://github.com/IndySockets/OpenSSL-Binaries/tree/master/Archive/) -- Win32 OpenSSL, version 1.0.2g (from 2016)
+ - https://github.com/IndySockets/OpenSSL-Binaries/raw/master/Archive/openssl-1.0.2g-i386-win32.zip
+ - the usual OpenSSL for Windows (http://slproweb.com/products/Win32OpenSSL.html) only provides the newest version of OpenSSL, and we need the 1.0.2g version
+ - **Download the 32-bit version, not 64-bit.**
- 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+](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)
- Put it somewhere on the `PATH`, so that it is accessible from the console.
+Ensure that OpenSSL, zlib, Java and CMake are on `PATH`.
## Getting set up
@@ -115,9 +120,8 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
- Installation can take a very long time, go grab a cup of tea or something and let it work.
### Installing OpenSSL
-1. Run the OpenSSL installer,
-2. It's best to choose the option to copy OpenSSL DLLs to the `/bin` directory
- - If you do this you'll need to add that directory (the default being `C:\OpenSSL-Win32\bin`) to your PATH system variable (Google how to do this, or use this guide for Java: http://www.java.com/en/download/help/path.xml).
+1. Download .zip file from the link above.
+2. Unzip and add the directory to PATH, so CMake can find it.
### Installing CMake
1. Run the CMake installer,
@@ -140,6 +144,24 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
- If the project builds successfully it will run and the MultiMC5 window will pop up,
- Test OpenSSL by making an instance and trying to log in. If Qt Creator couldn't find OpenSSL during the CMake stage, login will fail and you'll get an error.
+The following .dlls are needed for the app to run (copy them to build directory if you want to be able to move the build to another pc):
+```
+platforms/qwindows.dll
+libeay32.dll
+libgcc_s_dw2-1.dll
+libssp-0.dll
+libstdc++-6.dll
+libwinpthread-1.dll
+Qt5Core.dll
+Qt5Gui.dll
+Qt5Network.dll
+Qt5Svg.dll
+Qt5Widgets.dll
+Qt5Xml.dll
+ssleay32.dll
+zlib1.dll
+```
+
**These build instructions worked for me (Drayshak) on a fresh Windows 8 x64 Professional install. If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
### Compile from command line on Windows
1. If you installed Qt with the web installer, there should be a shortcut called `Qt 5.4 for Desktop (MinGW 4.9 32-bit)` in the Start menu on Windows 7 and 10. Best way to find it is to search for it. Do note you cannot just use cmd.exe, you have to use the shortcut, otherwise the proper MinGW software will not be on the PATH.
@@ -150,36 +172,32 @@ Getting the project to build and run on Windows is easy if you use Qt's IDE, Qt
5. Run the command `mingw32-make install`, and it should install MultiMC, to whatever the `-DCMAKE_INSTALL_PREFIX` was.
6. In most cases, whenever compiling, the OpenSSL dll's aren't put into the directory to where MultiMC installs, meaning you cannot log in. The best way to fix this is just to do `copy C:\OpenSSL-Win32\*.dll C:\Where\you\installed\MultiMC\to`. This should copy the required OpenSSL dll's to log in.
-# OS X
+# macOS
### Install prerequisites:
-* install homebrew
-* then:
-
-```
-brew install qt5
-brew tap homebrew/versions
-brew install gcc48
-brew install cmake
-```
+- Install XCode and set it up to the point where you can build things from a terminal
+- Install the official build of CMake (https://cmake.org/download/)
+- Install JDK 8 (https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html)
+- Get Qt 5.6 and install it (https://download.qt.io/new_archive/qt/5.6/5.6.3/)
### Build
Pick an installation path - this is where the final `.app` will be constructed when you run `make install`. Supply it as the `CMAKE_INSTALL_PREFIX` argument during CMake configuration.
```
-git clone https://github.com/MultiMC/MultiMC5.git
+git clone --recursive https://github.com/MultiMC/MultiMC5.git
cd MultiMC5
-git submodule init
-git submodule update
mkdir build
cd build
-export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
-export CC=/usr/local/bin/gcc-4.8
-export CXX=/usr/local/bin/g++-4.8
-cmake .. -DCMAKE_INSTALL_PREFIX:PATH=/Users/YOU/some/path/that/makes/sense/
-make
+cmake \
+ -DCMAKE_C_COMPILER=/usr/bin/clang \
+ -DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX:PATH="../dist/" \
+ -DCMAKE_PREFIX_PATH="/path/to/Qt5.6/" \
+ -DQt5_DIR="/path/to/Qt5.6/" \
+ -DMultiMC_LAYOUT=mac-bundle \
+ -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 \
+ ..
make install
```
-
-**These build instructions were taken and adapted from https://gist.github.com/number5/7250865 If they don't work for you, let us know on IRC ([Esper/#MultiMC](http://webchat.esper.net/?nick=&channels=MultiMC))!**
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b178462e..5e3d6cea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -75,9 +75,21 @@ set(MultiMC_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch M
# paste.ee API key
set(MultiMC_PASTE_EE_API_KEY "utLvciUouSURFzfjPxLBf5W4ISsUX4pwBDF7N1AfZ" CACHE STRING "API key you can get from paste.ee when you register an account")
+# Imgur API Client ID
+set(MultiMC_IMGUR_CLIENT_ID "5b97b0713fba4a3" CACHE STRING "Client ID you can get from Imgur when you register an application")
+
# Google analytics ID
set(MultiMC_ANALYTICS_ID "UA-87731965-2" CACHE STRING "ID you can get from Google analytics")
+# Bug tracker URL
+set(MultiMC_BUG_TRACKER_URL "" CACHE STRING "URL for the bug tracker.")
+
+# Discord URL
+set(MultiMC_DISCORD_URL "" CACHE STRING "URL for the Discord guild.")
+
+# Subreddit URL
+set(MultiMC_SUBREDDIT_URL "" CACHE STRING "URL for the subreddit.")
+
#### Check the current Git commit and branch
include(GetGitRevisionDescription)
get_git_head_revision(MultiMC_GIT_REFSPEC MultiMC_GIT_COMMIT)
@@ -154,7 +166,7 @@ if(MultiMC_LAYOUT_REAL STREQUAL "mac-bundle")
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-2019 MultiMC Contributors")
+ set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2015-2021 MultiMC Contributors")
# directories to look for dependencies
set(DIRS ${QT_LIBS_DIR} ${QT_LIBEXECS_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
@@ -264,6 +276,8 @@ 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
+add_subdirectory(libraries/optional-bare)
+add_subdirectory(libraries/tomlc99) # toml parser
############################### Built Artifacts ###############################
diff --git a/COPYING.md b/COPYING.md
index 70ff7ce2..caa4bed5 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -1,6 +1,6 @@
# MultiMC
- Copyright 2012-2019 MultiMC Contributors
+ Copyright 2012-2021 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
@@ -223,3 +223,56 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+
+# optional-bare
+
+ Code from https://github.com/martinmoene/optional-bare/
+
+ Boost Software License - Version 1.0 - August 17th, 2003
+
+ Permission is hereby granted, free of charge, to any person or organization
+ obtaining a copy of the software and accompanying documentation covered by
+ this license (the "Software") to use, reproduce, display, distribute,
+ execute, and transmit the Software, and to prepare derivative works of the
+ Software, and to permit third-parties to whom the Software is furnished to
+ do so, all subject to the following:
+
+ The copyright notices in the Software and this entire statement, including
+ the above license grant, this restriction and the following disclaimer,
+ must be included in all copies of the Software, in whole or in part, and
+ all derivative works of the Software, unless such copies or derivative
+ works are solely in the form of machine-executable object code generated by
+ a source language processor.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+ FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ DEALINGS IN THE SOFTWARE.
+
+# tomlc99
+
+ MIT License
+
+ Copyright (c) 2017 CK Tan
+ https://github.com/cktan/tomlc99
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/README.md b/README.md
index a66c21d3..ede8f88f 100644
--- a/README.md
+++ b/README.md
@@ -13,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 [Discord](https://discord.gg/0k2zsXGNHs0fE4Wm), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from the github issues [workflowy](https://github.com/MultiMC/MultiMC5/issues) - there is always plenty of ideas around.
+If you want to contribute, either talk to us on [Discord](https://discord.gg/multimc), [IRC](http://webchat.esper.net/?nick=&channels=MultiMC)(esper.net/#MultiMC) or pick up some item from the github issues [workflowy](https://github.com/MultiMC/MultiMC5/issues) - there is always plenty of ideas around.
### Building
If you want to build MultiMC yourself, check [BUILD.md](BUILD.md) for build instructions.
@@ -39,7 +39,7 @@ Apache covers reasonable use for the name - a mention of the project's origins i
## License
-Copyright &copy; 2013-2019 MultiMC Contributors
+Copyright &copy; 2013-2021 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/SkinUtils.cpp b/api/gui/SkinUtils.cpp
index e9618bd5..ec969889 100644
--- a/api/gui/SkinUtils.cpp
+++ b/api/gui/SkinUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 a7f680e6..b44f4228 100644
--- a/api/gui/SkinUtils.h
+++ b/api/gui/SkinUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 72edb46f..70350534 100644
--- a/api/gui/icons/IconList.cpp
+++ b/api/gui/icons/IconList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/gui/icons/IconList.h
index 274a9f02..f07415fa 100644
--- a/api/gui/icons/IconList.h
+++ b/api/gui/icons/IconList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/api/gui/icons/MMCIcon.cpp
index 5058717a..f0b82ec9 100644
--- a/api/gui/icons/MMCIcon.cpp
+++ b/api/gui/icons/MMCIcon.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 9fd1b9a2..fd14b5b7 100644
--- a/api/gui/icons/MMCIcon.h
+++ b/api/gui/icons/MMCIcon.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 3819a4e4..d61c3fe9 100644
--- a/api/logic/BaseInstaller.cpp
+++ b/api/logic/BaseInstaller.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/logic/BaseInstaller.h
index 59e00e06..3e40b355 100644
--- a/api/logic/BaseInstaller.h
+++ b/api/logic/BaseInstaller.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/BaseInstance.cpp b/api/logic/BaseInstance.cpp
index 7a95e255..46b45827 100644
--- a/api/logic/BaseInstance.cpp
+++ b/api/logic/BaseInstance.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s
m_settings->registerSetting("notes", "");
m_settings->registerSetting("lastLaunchTime", 0);
m_settings->registerSetting("totalTimePlayed", 0);
+ m_settings->registerSetting("lastTimePlayed", 0);
// Custom Commands
auto commandSetting = m_settings->registerSetting({"OverrideCommands","OverrideLaunchCmd"}, false);
@@ -134,15 +135,24 @@ void BaseInstance::setRunning(bool running)
m_isRunning = running;
+ if(!m_settings->get("RecordGameTime").toBool())
+ {
+ emit runningStatusChanged(running);
+ return;
+ }
+
if(running)
{
m_timeStarted = QDateTime::currentDateTime();
}
else
{
- qint64 current = settings()->get("totalTimePlayed").toLongLong();
QDateTime timeEnded = QDateTime::currentDateTime();
+
+ qint64 current = settings()->get("totalTimePlayed").toLongLong();
settings()->set("totalTimePlayed", current + m_timeStarted.secsTo(timeEnded));
+ settings()->set("lastTimePlayed", m_timeStarted.secsTo(timeEnded));
+
emit propertiesChanged(this);
}
@@ -160,9 +170,20 @@ int64_t BaseInstance::totalTimePlayed() const
return current;
}
+int64_t BaseInstance::lastTimePlayed() const
+{
+ if(m_isRunning)
+ {
+ QDateTime timeNow = QDateTime::currentDateTime();
+ return m_timeStarted.secsTo(timeNow);
+ }
+ return settings()->get("lastTimePlayed").toLongLong();
+}
+
void BaseInstance::resetTimePlayed()
{
settings()->reset("totalTimePlayed");
+ settings()->reset("lastTimePlayed");
}
QString BaseInstance::instanceType() const
diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h
index 3c342cb3..d250e03e 100644
--- a/api/logic/BaseInstance.h
+++ b/api/logic/BaseInstance.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,6 +34,8 @@
#include "multimc_logic_export.h"
+#include "minecraft/launch/MinecraftServerTarget.h"
+
class QDir;
class Task;
class LaunchTask;
@@ -85,6 +87,7 @@ public:
void setRunning(bool running);
bool isRunning() const;
int64_t totalTimePlayed() const;
+ int64_t lastTimePlayed() const;
void resetTimePlayed();
/// get the type of this instance
@@ -145,7 +148,8 @@ public:
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
- virtual shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
+ virtual shared_qobject_ptr<LaunchTask> createLaunchTask(
+ AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
/// returns the current launch task (if any)
shared_qobject_ptr<LaunchTask> getLaunchTask();
@@ -221,9 +225,9 @@ public:
bool reloadSettings();
/**
- * 'print' a verbose desription of the instance into a QStringList
+ * 'print' a verbose description of the instance into a QStringList
*/
- virtual QStringList verboseDescription(AuthSessionPtr session) = 0;
+ virtual QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) = 0;
Status currentStatus() const;
diff --git a/api/logic/BaseVersion.h b/api/logic/BaseVersion.h
index 15e64bc0..b88105fb 100644
--- a/api/logic/BaseVersion.h
+++ b/api/logic/BaseVersion.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 b3f44463..aa9cb6cf 100644
--- a/api/logic/BaseVersionList.cpp
+++ b/api/logic/BaseVersionList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 64a33d3e..29e21bdb 100644
--- a/api/logic/BaseVersionList.h
+++ b/api/logic/BaseVersionList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 50eff4ba..6d269714 100644
--- a/api/logic/CMakeLists.txt
+++ b/api/logic/CMakeLists.txt
@@ -124,6 +124,8 @@ set(NET_SOURCES
# Game launch logic
set(LAUNCH_SOURCES
+ launch/steps/LookupServerAddress.cpp
+ launch/steps/LookupServerAddress.h
launch/steps/PostLaunchCommand.cpp
launch/steps/PostLaunchCommand.h
launch/steps/PreLaunchCommand.cpp
@@ -226,8 +228,8 @@ set(MINECRAFT_SOURCES
minecraft/launch/ClaimAccount.cpp
minecraft/launch/ClaimAccount.h
- minecraft/launch/CreateServerResourcePacksFolder.cpp
- minecraft/launch/CreateServerResourcePacksFolder.h
+ minecraft/launch/CreateGameFolders.cpp
+ minecraft/launch/CreateGameFolders.h
minecraft/launch/ModMinecraftJar.cpp
minecraft/launch/ModMinecraftJar.h
minecraft/launch/DirectJavaLaunch.cpp
@@ -236,12 +238,16 @@ set(MINECRAFT_SOURCES
minecraft/launch/ExtractNatives.h
minecraft/launch/LauncherPartLaunch.cpp
minecraft/launch/LauncherPartLaunch.h
+ minecraft/launch/MinecraftServerTarget.cpp
+ minecraft/launch/MinecraftServerTarget.h
minecraft/launch/PrintInstanceInfo.cpp
minecraft/launch/PrintInstanceInfo.h
minecraft/launch/ReconstructAssets.cpp
minecraft/launch/ReconstructAssets.h
minecraft/launch/ScanModFolders.cpp
minecraft/launch/ScanModFolders.h
+ minecraft/launch/VerifyJavaInstall.cpp
+ minecraft/launch/VerifyJavaInstall.h
minecraft/legacy/LegacyModList.h
minecraft/legacy/LegacyModList.cpp
@@ -298,14 +304,23 @@ set(MINECRAFT_SOURCES
minecraft/mod/ModFolderLoadTask.cpp
minecraft/mod/LocalModParseTask.h
minecraft/mod/LocalModParseTask.cpp
+ minecraft/mod/ResourcePackFolderModel.h
+ minecraft/mod/ResourcePackFolderModel.cpp
+ minecraft/mod/TexturePackFolderModel.h
+ minecraft/mod/TexturePackFolderModel.cpp
# Assets
minecraft/AssetsUtils.h
minecraft/AssetsUtils.cpp
- # Skin upload utilities
- minecraft/SkinUpload.cpp
- minecraft/SkinUpload.h
+ # Minecraft services
+ minecraft/services/SkinUpload.cpp
+ minecraft/services/SkinUpload.h
+ minecraft/services/SkinDelete.cpp
+ minecraft/services/SkinDelete.h
+
+ mojang/PackageManifest.h
+ mojang/PackageManifest.cpp
)
add_unit_test(GradleSpecifier
@@ -313,6 +328,22 @@ add_unit_test(GradleSpecifier
LIBS MultiMC_logic
)
+add_executable(PackageManifest
+ mojang/PackageManifest_test.cpp
+)
+target_link_libraries(PackageManifest
+ MultiMC_logic
+ Qt5::Test
+)
+target_include_directories(PackageManifest
+ PRIVATE ../../cmake/UnitTest/
+)
+add_test(
+ NAME PackageManifest
+ COMMAND PackageManifest
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
add_unit_test(MojangVersionFormat
SOURCES minecraft/MojangVersionFormat_test.cpp
LIBS MultiMC_logic
@@ -445,12 +476,39 @@ set(FTB_SOURCES
set(FLAME_SOURCES
# Flame
+ modplatform/flame/FlamePackIndex.cpp
+ modplatform/flame/FlamePackIndex.h
modplatform/flame/PackManifest.h
modplatform/flame/PackManifest.cpp
modplatform/flame/FileResolvingTask.h
modplatform/flame/FileResolvingTask.cpp
)
+set(MODPACKSCH_SOURCES
+ modplatform/modpacksch/FTBPackInstallTask.h
+ modplatform/modpacksch/FTBPackInstallTask.cpp
+ modplatform/modpacksch/FTBPackManifest.h
+ modplatform/modpacksch/FTBPackManifest.cpp
+)
+
+set(TECHNIC_SOURCES
+ modplatform/technic/SingleZipPackInstallTask.h
+ modplatform/technic/SingleZipPackInstallTask.cpp
+ modplatform/technic/SolderPackInstallTask.h
+ modplatform/technic/SolderPackInstallTask.cpp
+ modplatform/technic/TechnicPackProcessor.h
+ modplatform/technic/TechnicPackProcessor.cpp
+)
+
+set(ATLAUNCHER_SOURCES
+ modplatform/atlauncher/ATLPackIndex.cpp
+ modplatform/atlauncher/ATLPackIndex.h
+ modplatform/atlauncher/ATLPackInstallTask.cpp
+ modplatform/atlauncher/ATLPackInstallTask.h
+ modplatform/atlauncher/ATLPackManifest.cpp
+ modplatform/atlauncher/ATLPackManifest.h
+)
+
add_unit_test(Index
SOURCES meta/Index_test.cpp
LIBS MultiMC_logic
@@ -481,6 +539,9 @@ set(LOGIC_SOURCES
${ICONS_SOURCES}
${FTB_SOURCES}
${FLAME_SOURCES}
+ ${MODPACKSCH_SOURCES}
+ ${TECHNIC_SOURCES}
+ ${ATLAUNCHER_SOURCES}
)
add_library(MultiMC_logic SHARED ${LOGIC_SOURCES})
@@ -489,7 +550,7 @@ set_target_properties(MultiMC_logic PROPERTIES CXX_VISIBILITY_PRESET hidden VISI
generate_export_header(MultiMC_logic)
# Link
-target_link_libraries(MultiMC_logic systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES} BuildConfig)
+target_link_libraries(MultiMC_logic systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES} optional-bare tomlc99 BuildConfig)
target_link_libraries(MultiMC_logic Qt5::Core Qt5::Xml Qt5::Network Qt5::Concurrent)
# Mark and export headers
diff --git a/api/logic/Commandline.cpp b/api/logic/Commandline.cpp
index 59f1666b..2c0fde64 100644
--- a/api/logic/Commandline.cpp
+++ b/api/logic/Commandline.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/Commandline.h b/api/logic/Commandline.h
index 1adfb79d..09c1707e 100644
--- a/api/logic/Commandline.h
+++ b/api/logic/Commandline.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp
index 0d496d4e..71b49d95 100644
--- a/api/logic/Env.cpp
+++ b/api/logic/Env.cpp
@@ -96,8 +96,11 @@ void Env::initHttpMetaCache()
m_metacache->addBase("fmllibs", QDir("mods/minecraftforge/libs").absolutePath());
m_metacache->addBase("liteloader", QDir("mods/liteloader").absolutePath());
m_metacache->addBase("general", QDir("cache").absolutePath());
+ m_metacache->addBase("ATLauncherPacks", QDir("cache/ATLauncherPacks").absolutePath());
m_metacache->addBase("FTBPacks", QDir("cache/FTBPacks").absolutePath());
- m_metacache->addBase("TwitchPacks", QDir("cache/TwitchPacks").absolutePath());
+ m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath());
+ m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath());
+ m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath());
m_metacache->addBase("skins", QDir("accounts/skins").absolutePath());
m_metacache->addBase("root", QDir::currentPath());
m_metacache->addBase("translations", QDir("translations").absolutePath());
@@ -160,11 +163,9 @@ void Env::updateProxySettings(QString proxyTypeStr, QString addr, int port, QStr
proxyDesc = "DERP proxy: ";
break;
}
- proxyDesc += QString("%3@%1:%2 pass %4")
+ proxyDesc += QString("%1:%2")
.arg(proxy.hostName())
- .arg(proxy.port())
- .arg(proxy.user())
- .arg(proxy.password());
+ .arg(proxy.port());
qDebug() << proxyDesc;
}
diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp
index e2187416..3eac4d57 100644
--- a/api/logic/InstanceImportTask.cpp
+++ b/api/logic/InstanceImportTask.cpp
@@ -1,3 +1,18 @@
+/* Copyright 2013-2021 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 "InstanceImportTask.h"
#include "BaseInstance.h"
#include "FileSystem.h"
@@ -15,6 +30,8 @@
#include "modplatform/flame/FileResolvingTask.h"
#include "modplatform/flame/PackManifest.h"
#include "Json.h"
+#include <quazipdir.h>
+#include "modplatform/technic/TechnicPackProcessor.h"
InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
{
@@ -23,8 +40,6 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
void InstanceImportTask::executeTask()
{
- InstancePtr newInstance;
-
if (m_sourceUrl.isLocalFile())
{
m_archivePath = m_sourceUrl.toLocalFile();
@@ -82,6 +97,7 @@ void InstanceImportTask::processZipPack()
QStringList blacklist = {"instance.cfg", "manifest.json"};
QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg");
+ bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json");
QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json");
QString root;
if(!mmcFound.isNull())
@@ -91,6 +107,14 @@ void InstanceImportTask::processZipPack()
root = mmcFound;
m_modpackType = ModpackType::MultiMC;
}
+ else if (technicFound)
+ {
+ // process as Technic pack
+ qDebug() << "Technic:" << technicFound;
+ extractDir.mkpath(".minecraft");
+ extractDir.cd(".minecraft");
+ m_modpackType = ModpackType::Technic;
+ }
else if(!flameFound.isNull())
{
// process as Flame pack
@@ -98,7 +122,6 @@ void InstanceImportTask::processZipPack()
root = flameFound;
m_modpackType = ModpackType::Flame;
}
-
if(m_modpackType == ModpackType::Unknown)
{
emitFailed(tr("Archive does not contain a recognized modpack type."));
@@ -115,7 +138,7 @@ void InstanceImportTask::processZipPack()
void InstanceImportTask::extractFinished()
{
m_packZip.reset();
- if (m_extractFuture.result().isEmpty())
+ if (!m_extractFuture.result())
{
emitFailed(tr("Failed to extract modpack"));
return;
@@ -161,6 +184,9 @@ void InstanceImportTask::extractFinished()
case ModpackType::MultiMC:
processMultiMC();
return;
+ case ModpackType::Technic:
+ processTechnic();
+ return;
case ModpackType::Unknown:
emitFailed(tr("Archive does not contain a recognized modpack type."));
return;
@@ -212,6 +238,7 @@ void InstanceImportTask::processFlame()
}
QString forgeVersion;
+ QString fabricVersion;
for(auto &loader: pack.minecraft.modLoaders)
{
auto id = loader.id;
@@ -221,6 +248,12 @@ void InstanceImportTask::processFlame()
forgeVersion = id;
continue;
}
+ if(id.startsWith("fabric-"))
+ {
+ id.remove("fabric-");
+ fabricVersion = id;
+ continue;
+ }
logWarning(tr("Unknown mod loader in manifest: %1").arg(id));
}
@@ -255,6 +288,10 @@ void InstanceImportTask::processFlame()
}
components->setComponentVersion("net.minecraftforge", forgeVersion);
}
+ if(!fabricVersion.isEmpty())
+ {
+ components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion);
+ }
if (m_instIcon != "default")
{
instance.setIconKey(m_instIcon);
@@ -371,6 +408,14 @@ void InstanceImportTask::processFlame()
m_modIdResolver->start();
}
+void InstanceImportTask::processTechnic()
+{
+ shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
+ connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded);
+ connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed);
+ packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath);
+}
+
void InstanceImportTask::processMultiMC()
{
// FIXME: copy from FolderInstanceProvider!!! FIX IT!!!
diff --git a/api/logic/InstanceImportTask.h b/api/logic/InstanceImportTask.h
index d326391b..7291324d 100644
--- a/api/logic/InstanceImportTask.h
+++ b/api/logic/InstanceImportTask.h
@@ -1,3 +1,18 @@
+/* Copyright 2013-2021 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 "InstanceTask.h"
@@ -9,6 +24,8 @@
#include "settings/SettingsObject.h"
#include "QObjectPtr.h"
+#include <nonstd/optional>
+
class QuaZip;
namespace Flame
{
@@ -29,6 +46,7 @@ private:
void processZipPack();
void processMultiMC();
void processFlame();
+ void processTechnic();
private slots:
void downloadSucceeded();
@@ -44,11 +62,12 @@ private: /* data */
QString m_archivePath;
bool m_downloadRequired = false;
std::unique_ptr<QuaZip> m_packZip;
- QFuture<QStringList> m_extractFuture;
- QFutureWatcher<QStringList> m_extractFutureWatcher;
+ QFuture<nonstd::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
enum class ModpackType{
Unknown,
MultiMC,
- Flame
+ Flame,
+ Technic
} m_modpackType = ModpackType::Unknown;
};
diff --git a/api/logic/InstanceList.cpp b/api/logic/InstanceList.cpp
index a6dd734b..02fae6ac 100644
--- a/api/logic/InstanceList.cpp
+++ b/api/logic/InstanceList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/InstanceList.h b/api/logic/InstanceList.h
index 005a24cd..8215cb66 100644
--- a/api/logic/InstanceList.h
+++ b/api/logic/InstanceList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/LoggedProcess.h b/api/logic/LoggedProcess.h
index 256c0c45..327cdc6a 100644
--- a/api/logic/LoggedProcess.h
+++ b/api/logic/LoggedProcess.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 3afdbf5e..b25c61e7 100644
--- a/api/logic/MMCZip.cpp
+++ b/api/logic/MMCZip.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -208,16 +208,27 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re
// ours
-QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
+nonstd::optional<QStringList> MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target)
{
QDir directory(target);
QStringList extracted;
+
qDebug() << "Extracting subdir" << subdir << "from" << zip->getZipName() << "to" << target;
- if (!zip->goToFirstFile())
+ auto numEntries = zip->getEntriesCount();
+ if(numEntries < 0) {
+ qWarning() << "Failed to enumerate files in archive";
+ return nonstd::nullopt;
+ }
+ else if(numEntries == 0) {
+ qDebug() << "Extracting empty archives seems odd...";
+ return extracted;
+ }
+ else if (!zip->goToFirstFile())
{
qWarning() << "Failed to seek to first file in zip";
- return QStringList();
+ return nonstd::nullopt;
}
+
do
{
QString name = zip->getCurrentFileName();
@@ -235,7 +246,7 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
{
qWarning() << "Failed to extract file" << name << "to" << absFilePath;
JlCompress::removeFile(extracted);
- return QStringList();
+ return nonstd::nullopt;
}
extracted.append(absFilePath);
qDebug() << "Extracted file" << name;
@@ -244,12 +255,58 @@ QStringList MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QSt
}
// ours
-QStringList MMCZip::extractDir(QString fileCompressed, QString dir)
+bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &target)
+{
+ return JlCompress::extractFile(zip, file, target);
+}
+
+// ours
+nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString dir)
{
QuaZip zip(fileCompressed);
if (!zip.open(QuaZip::mdUnzip))
{
- return {};
+ // check if this is a minimum size empty zip file...
+ QFileInfo fileInfo(fileCompressed);
+ if(fileInfo.size() == 22) {
+ return QStringList();
+ }
+ qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
+ return nonstd::nullopt;
}
return MMCZip::extractSubDir(&zip, "", dir);
}
+
+// ours
+nonstd::optional<QStringList> MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir)
+{
+ QuaZip zip(fileCompressed);
+ if (!zip.open(QuaZip::mdUnzip))
+ {
+ // check if this is a minimum size empty zip file...
+ QFileInfo fileInfo(fileCompressed);
+ if(fileInfo.size() == 22) {
+ return QStringList();
+ }
+ qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();;
+ return nonstd::nullopt;
+ }
+ return MMCZip::extractSubDir(&zip, subdir, dir);
+}
+
+// ours
+bool MMCZip::extractFile(QString fileCompressed, QString file, QString target)
+{
+ QuaZip zip(fileCompressed);
+ if (!zip.open(QuaZip::mdUnzip))
+ {
+ // check if this is a minimum size empty zip file...
+ QFileInfo fileInfo(fileCompressed);
+ if(fileInfo.size() == 22) {
+ return true;
+ }
+ qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();
+ return false;
+ }
+ return MMCZip::extractRelFile(&zip, file, target);
+}
diff --git a/api/logic/MMCZip.h b/api/logic/MMCZip.h
index 85ac7802..98d9cd5b 100644
--- a/api/logic/MMCZip.h
+++ b/api/logic/MMCZip.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 "multimc_logic_export.h"
#include <JlCompress.h>
+#include <nonstd/optional>
namespace MMCZip
{
@@ -57,7 +58,9 @@ namespace MMCZip
/**
* Extract a subdirectory from an archive
*/
- QStringList MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
+ nonstd::optional<QStringList> MULTIMC_LOGIC_EXPORT extractSubDir(QuaZip *zip, const QString & subdir, const QString &target);
+
+ bool MULTIMC_LOGIC_EXPORT extractRelFile(QuaZip *zip, const QString & file, const QString &target);
/**
* Extract a whole archive.
@@ -66,6 +69,26 @@ namespace MMCZip
* \param dir The directory to extract to, the current directory if left empty.
* \return The list of the full paths of the files extracted, empty on failure.
*/
- QStringList MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
+ nonstd::optional<QStringList> MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString dir);
+
+ /**
+ * Extract a subdirectory from an archive
+ *
+ * \param fileCompressed The name of the archive.
+ * \param subdir The directory within the archive to extract
+ * \param dir The directory to extract to, the current directory if left empty.
+ * \return The list of the full paths of the files extracted, empty on failure.
+ */
+ nonstd::optional<QStringList> MULTIMC_LOGIC_EXPORT extractDir(QString fileCompressed, QString subdir, QString dir);
+
+ /**
+ * Extract a single file from an archive into a directory
+ *
+ * \param fileCompressed The name of the archive.
+ * \param file The file within the archive to extract
+ * \param dir The directory to extract to, the current directory if left empty.
+ * \return true for success or false for failure
+ */
+ bool MULTIMC_LOGIC_EXPORT extractFile(QString fileCompressed, QString file, QString dir);
}
diff --git a/api/logic/NullInstance.h b/api/logic/NullInstance.h
index e9ba1a13..94ed6c3a 100644
--- a/api/logic/NullInstance.h
+++ b/api/logic/NullInstance.h
@@ -27,7 +27,7 @@ public:
{
return instanceRoot();
};
- shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
+ shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override
{
return nullptr;
}
@@ -67,7 +67,7 @@ public:
{
return false;
}
- QStringList verboseDescription(AuthSessionPtr session) override
+ QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override
{
QStringList out;
out << "Null instance - placeholder.";
diff --git a/api/logic/RWStorage.h b/api/logic/RWStorage.h
index 5d792367..3028388e 100644
--- a/api/logic/RWStorage.h
+++ b/api/logic/RWStorage.h
@@ -1,6 +1,9 @@
#pragma once
#include <QWriteLocker>
#include <QReadLocker>
+#include <QMap>
+#include <QSet>
+
template <typename K, typename V>
class RWStorage
{
diff --git a/api/logic/Version_test.cpp b/api/logic/Version_test.cpp
index 9c222a70..b2d657a6 100644
--- a/api/logic/Version_test.cpp
+++ b/api/logic/Version_test.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 ca0f4bde..d78d6505 100644
--- a/api/logic/java/JavaChecker.cpp
+++ b/api/logic/java/JavaChecker.cpp
@@ -115,7 +115,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
}
}
- if(!results.contains("os.arch") || !results.contains("java.version") || !success)
+ if(!results.contains("os.arch") || !results.contains("java.version") || !results.contains("java.vendor") || !success)
{
result.validity = JavaCheckResult::Validity::ReturnedInvalidData;
emit checkFinished(result);
@@ -124,6 +124,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
auto os_arch = results["os.arch"];
auto java_version = results["java.version"];
+ auto java_vendor = results["java.vendor"];
bool is_64 = os_arch == "x86_64" || os_arch == "amd64";
@@ -132,6 +133,7 @@ void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
result.mojangPlatform = is_64 ? "64" : "32";
result.realPlatform = os_arch;
result.javaVersion = java_version;
+ result.javaVendor = java_vendor;
qDebug() << "Java checker succeeded.";
emit checkFinished(result);
}
diff --git a/api/logic/java/JavaChecker.h b/api/logic/java/JavaChecker.h
index af0dcb90..0a96249a 100644
--- a/api/logic/java/JavaChecker.h
+++ b/api/logic/java/JavaChecker.h
@@ -17,6 +17,7 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
QString mojangPlatform;
QString realPlatform;
JavaVersion javaVersion;
+ QString javaVendor;
QString outLog;
QString errorLog;
bool is_64bit = false;
diff --git a/api/logic/java/JavaCheckerJob.cpp b/api/logic/java/JavaCheckerJob.cpp
index 223d8f55..67d70066 100644
--- a/api/logic/java/JavaCheckerJob.cpp
+++ b/api/logic/java/JavaCheckerJob.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/JavaCheckerJob.h b/api/logic/java/JavaCheckerJob.h
index 24d0d1b8..c0986420 100644
--- a/api/logic/java/JavaCheckerJob.h
+++ b/api/logic/java/JavaCheckerJob.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/JavaInstallList.cpp b/api/logic/java/JavaInstallList.cpp
index a71a7dbe..0bded03c 100644
--- a/api/logic/java/JavaInstallList.cpp
+++ b/api/logic/java/JavaInstallList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/JavaInstallList.h b/api/logic/java/JavaInstallList.h
index b98908f3..1785a7b6 100644
--- a/api/logic/java/JavaInstallList.h
+++ b/api/logic/java/JavaInstallList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/JavaUtils.cpp b/api/logic/java/JavaUtils.cpp
index 1f9719a8..4b231e6a 100644
--- a/api/logic/java/JavaUtils.cpp
+++ b/api/logic/java/JavaUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 @@ JavaInstallPtr JavaUtils::GetDefaultJava()
}
#if defined(Q_OS_WIN32)
-QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName)
+QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix)
{
QList<JavaInstallPtr> javas;
@@ -175,8 +175,6 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
RegQueryValueExA(jreKey, "CurrentVersion", NULL, NULL, (BYTE *)value, &valueSz);
}
- QString recommended = value;
-
TCHAR subKeyName[255];
DWORD subKeyNameSize, numSubKeys, retCode;
@@ -195,7 +193,7 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
if (retCode == ERROR_SUCCESS)
{
// Now open the registry key for the version that we just got.
- QString newKeyName = keyName + "\\" + subKeyName;
+ QString newKeyName = keyName + "\\" + subKeyName + subkeySuffix;
HKEY newKey;
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, newKeyName.toStdString().c_str(), 0,
@@ -204,11 +202,11 @@ QList<JavaInstallPtr> JavaUtils::FindJavaFromRegistryKey(DWORD keyType, QString
// Read the JavaHome value to find where Java is installed.
value = new char[0];
valueSz = 0;
- if (RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
+ if (RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value,
&valueSz) == ERROR_MORE_DATA)
{
value = new char[valueSz];
- RegQueryValueEx(newKey, "JavaHome", NULL, NULL, (BYTE *)value,
+ RegQueryValueEx(newKey, keyJavaDir.toStdString().c_str(), NULL, NULL, (BYTE *)value,
&valueSz);
// Now, we construct the version object and add it to the list.
@@ -237,25 +235,78 @@ QList<QString> JavaUtils::FindJavaPaths()
{
QList<JavaInstallPtr> java_candidates;
+ // Oracle
QList<JavaInstallPtr> JRE64s = this->FindJavaFromRegistryKey(
- KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
+ KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome");
QList<JavaInstallPtr> JDK64s = this->FindJavaFromRegistryKey(
- KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
+ KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome");
QList<JavaInstallPtr> JRE32s = this->FindJavaFromRegistryKey(
- KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment");
+ KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Runtime Environment", "JavaHome");
QList<JavaInstallPtr> JDK32s = this->FindJavaFromRegistryKey(
- KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit");
-
+ KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\Java Development Kit", "JavaHome");
+
+ // Oracle for Java 9 and newer
+ QList<JavaInstallPtr> NEWJRE64s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome");
+ QList<JavaInstallPtr> NEWJDK64s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_64KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome");
+ QList<JavaInstallPtr> NEWJRE32s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JRE", "JavaHome");
+ QList<JavaInstallPtr> NEWJDK32s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_32KEY, "SOFTWARE\\JavaSoft\\JDK", "JavaHome");
+
+ // AdoptOpenJDK
+ QList<JavaInstallPtr> ADOPTOPENJRE32s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI");
+ QList<JavaInstallPtr> ADOPTOPENJRE64s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JRE", "Path", "\\hotspot\\MSI");
+ QList<JavaInstallPtr> ADOPTOPENJDK32s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_32KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI");
+ QList<JavaInstallPtr> ADOPTOPENJDK64s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_64KEY, "SOFTWARE\\AdoptOpenJDK\\JDK", "Path", "\\hotspot\\MSI");
+
+ // Microsoft
+ QList<JavaInstallPtr> MICROSOFTJDK64s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_64KEY, "SOFTWARE\\Microsoft\\JDK", "Path", "\\hotspot\\MSI");
+
+ // Azul Zulu
+ QList<JavaInstallPtr> ZULU64s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_64KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath");
+ QList<JavaInstallPtr> ZULU32s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_32KEY, "SOFTWARE\\Azul Systems\\Zulu", "InstallationPath");
+
+ // BellSoft Liberica
+ QList<JavaInstallPtr> LIBERICA64s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_64KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath");
+ QList<JavaInstallPtr> LIBERICA32s = this->FindJavaFromRegistryKey(
+ KEY_WOW64_32KEY, "SOFTWARE\\BellSoft\\Liberica", "InstallationPath");
+
+ // List x64 before x86
java_candidates.append(JRE64s);
+ java_candidates.append(NEWJRE64s);
+ java_candidates.append(ADOPTOPENJRE64s);
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK64s);
+ java_candidates.append(NEWJDK64s);
+ java_candidates.append(ADOPTOPENJDK64s);
+ java_candidates.append(MICROSOFTJDK64s);
+ java_candidates.append(ZULU64s);
+ java_candidates.append(LIBERICA64s);
+
java_candidates.append(JRE32s);
+ java_candidates.append(NEWJRE32s);
+ java_candidates.append(ADOPTOPENJRE32s);
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre8/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre7/bin/javaw.exe"));
java_candidates.append(MakeJavaPtr("C:/Program Files (x86)/Java/jre6/bin/javaw.exe"));
java_candidates.append(JDK32s);
+ java_candidates.append(NEWJDK32s);
+ java_candidates.append(ADOPTOPENJDK32s);
+ java_candidates.append(ZULU32s);
+ java_candidates.append(LIBERICA32s);
+
java_candidates.append(MakeJavaPtr(this->GetDefaultJava()->path));
QList<QString> candidates;
@@ -330,6 +381,9 @@ QList<QString> JavaUtils::FindJavaPaths()
scanJavaDir("/usr/lib32/jvm");
// javas stored in MultiMC's folder
scanJavaDir("java");
+ // manually installed JDKs in /opt
+ scanJavaDir("/opt/jdk");
+ scanJavaDir("/opt/jdks");
return javas;
}
#else
diff --git a/api/logic/java/JavaUtils.h b/api/logic/java/JavaUtils.h
index 100d942f..206acf89 100644
--- a/api/logic/java/JavaUtils.h
+++ b/api/logic/java/JavaUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,6 @@ public:
JavaInstallPtr GetDefaultJava();
#ifdef Q_OS_WIN
- QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName);
+ QList<JavaInstallPtr> FindJavaFromRegistryKey(DWORD keyType, QString keyName, QString keyJavaDir, QString subkeySuffix = "");
#endif
};
diff --git a/api/logic/java/launch/CheckJava.cpp b/api/logic/java/launch/CheckJava.cpp
index b75c6dc6..f58602f0 100644
--- a/api/logic/java/launch/CheckJava.cpp
+++ b/api/logic/java/launch/CheckJava.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,17 +33,17 @@ void CheckJava::executeTask()
if (perInstance)
{
emit logLine(
- tr("The java binary \"%1\" couldn't be found. Please fix the java path "
+ QString("The java binary \"%1\" couldn't be found. Please fix the java path "
"override in the instance's settings or disable it.").arg(m_javaPath),
MessageLevel::Warning);
}
else
{
- emit logLine(tr("The java binary \"%1\" couldn't be found. Please set up java in "
+ emit logLine(QString("The java binary \"%1\" couldn't be found. Please set up java in "
"the settings.").arg(m_javaPath),
MessageLevel::Warning);
}
- emitFailed(tr("Java path is not valid."));
+ emitFailed(QString("Java path is not valid."));
return;
}
else
@@ -56,12 +56,13 @@ void CheckJava::executeTask()
auto storedUnixTime = settings->get("JavaTimestamp").toLongLong();
auto storedArchitecture = settings->get("JavaArchitecture").toString();
auto storedVersion = settings->get("JavaVersion").toString();
+ auto storedVendor = settings->get("JavaVendor").toString();
m_javaUnixTime = javaUnixTime;
// if timestamps are not the same, or something is missing, check!
- if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0)
+ if (javaUnixTime != storedUnixTime || storedVersion.size() == 0 || storedArchitecture.size() == 0 || storedVendor.size() == 0)
{
m_JavaChecker = new JavaChecker();
- emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
+ emit logLine(QString("Checking Java version..."), MessageLevel::MultiMC);
connect(m_JavaChecker.get(), &JavaChecker::checkFinished, this, &CheckJava::checkJavaFinished);
m_JavaChecker->m_path = realJavaPath;
m_JavaChecker->performCheck();
@@ -71,7 +72,8 @@ void CheckJava::executeTask()
{
auto verString = instance->settings()->get("JavaVersion").toString();
auto archString = instance->settings()->get("JavaArchitecture").toString();
- printJavaInfo(verString, archString);
+ auto vendorString = instance->settings()->get("JavaVendor").toString();
+ printJavaInfo(verString, archString, vendorString);
}
emitSucceeded();
}
@@ -83,16 +85,16 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
case JavaCheckResult::Validity::Errored:
{
// Error message displayed if java can't start
- emit logLine(tr("Could not start java:"), MessageLevel::Error);
+ emit logLine(QString("Could not start java:"), MessageLevel::Error);
emit logLines(result.errorLog.split('\n'), MessageLevel::Error);
emit logLine("\nCheck your MultiMC Java settings.", MessageLevel::MultiMC);
printSystemInfo(false, false);
- emitFailed(tr("Could not start java!"));
+ emitFailed(QString("Could not start java!"));
return;
}
case JavaCheckResult::Validity::ReturnedInvalidData:
{
- emit logLine(tr("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
+ emit logLine(QString("Java checker returned some invalid data MultiMC doesn't understand:"), MessageLevel::Error);
emit logLines(result.outLog.split('\n'), MessageLevel::Warning);
emit logLine("\nMinecraft might not start properly.", MessageLevel::MultiMC);
printSystemInfo(false, false);
@@ -102,9 +104,10 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
case JavaCheckResult::Validity::Valid:
{
auto instance = m_parent->instance();
- printJavaInfo(result.javaVersion.toString(), result.mojangPlatform);
+ printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.javaVendor);
instance->settings()->set("JavaVersion", result.javaVersion.toString());
instance->settings()->set("JavaArchitecture", result.mojangPlatform);
+ instance->settings()->set("JavaVendor", result.javaVendor);
instance->settings()->set("JavaTimestamp", m_javaUnixTime);
emitSucceeded();
return;
@@ -112,9 +115,9 @@ void CheckJava::checkJavaFinished(JavaCheckResult result)
}
}
-void CheckJava::printJavaInfo(const QString& version, const QString& architecture)
+void CheckJava::printJavaInfo(const QString& version, const QString& architecture, const QString & vendor)
{
- emit logLine(tr("Java is version %1, using %2-bit architecture.\n\n").arg(version, architecture), MessageLevel::MultiMC);
+ emit logLine(QString("Java is version %1, using %2-bit architecture, from %3.\n\n").arg(version, architecture, vendor), MessageLevel::MultiMC);
printSystemInfo(true, architecture == "64");
}
@@ -124,13 +127,13 @@ void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit)
auto system64 = Sys::isSystem64bit();
if(cpu64 != system64)
{
- emit logLine(tr("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
+ emit logLine(QString("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error);
}
if(javaIsKnown)
{
if(javaIs64bit != system64)
{
- emit logLine(tr("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
+ emit logLine(QString("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error);
}
}
}
diff --git a/api/logic/java/launch/CheckJava.h b/api/logic/java/launch/CheckJava.h
index f0dd2308..68cd618b 100644
--- a/api/logic/java/launch/CheckJava.h
+++ b/api/logic/java/launch/CheckJava.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ private slots:
void checkJavaFinished(JavaCheckResult result);
private:
- void printJavaInfo(const QString & version, const QString & architecture);
+ void printJavaInfo(const QString & version, const QString & architecture, const QString & vendor);
void printSystemInfo(bool javaIsKnown, bool javaIs64bit);
private:
diff --git a/api/logic/launch/LaunchStep.cpp b/api/logic/launch/LaunchStep.cpp
index bcaca406..d6bb6e88 100644
--- a/api/logic/launch/LaunchStep.cpp
+++ b/api/logic/launch/LaunchStep.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 dd380e43..3939f960 100644
--- a/api/logic/launch/LaunchStep.h
+++ b/api/logic/launch/LaunchStep.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 841b8363..e6f6bbac 100644
--- a/api/logic/launch/LaunchTask.cpp
+++ b/api/logic/launch/LaunchTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/launch/LaunchTask.h b/api/logic/launch/LaunchTask.h
index ee04bd9a..2be59c83 100644
--- a/api/logic/launch/LaunchTask.h
+++ b/api/logic/launch/LaunchTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/launch/steps/LookupServerAddress.cpp b/api/logic/launch/steps/LookupServerAddress.cpp
new file mode 100644
index 00000000..de56c28a
--- /dev/null
+++ b/api/logic/launch/steps/LookupServerAddress.cpp
@@ -0,0 +1,95 @@
+/* Copyright 2013-2021 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 "LookupServerAddress.h"
+
+#include <launch/LaunchTask.h>
+
+LookupServerAddress::LookupServerAddress(LaunchTask *parent) :
+ LaunchStep(parent), m_dnsLookup(new QDnsLookup(this))
+{
+ connect(m_dnsLookup, &QDnsLookup::finished, this, &LookupServerAddress::on_dnsLookupFinished);
+
+ m_dnsLookup->setType(QDnsLookup::SRV);
+}
+
+void LookupServerAddress::setLookupAddress(const QString &lookupAddress)
+{
+ m_lookupAddress = lookupAddress;
+ m_dnsLookup->setName(QString("_minecraft._tcp.%1").arg(lookupAddress));
+}
+
+void LookupServerAddress::setOutputAddressPtr(MinecraftServerTargetPtr output)
+{
+ m_output = std::move(output);
+}
+
+bool LookupServerAddress::abort()
+{
+ m_dnsLookup->abort();
+ emitFailed("Aborted");
+ return true;
+}
+
+void LookupServerAddress::executeTask()
+{
+ m_dnsLookup->lookup();
+}
+
+void LookupServerAddress::on_dnsLookupFinished()
+{
+ if (isFinished())
+ {
+ // Aborted
+ return;
+ }
+
+ if (m_dnsLookup->error() != QDnsLookup::NoError)
+ {
+ emit logLine(QString("Failed to resolve server address (this is NOT an error!) %1: %2\n")
+ .arg(m_dnsLookup->name(), m_dnsLookup->errorString()), MessageLevel::MultiMC);
+ resolve(m_lookupAddress, 25565); // Technically the task failed, however, we don't abort the launch
+ // and leave it up to minecraft to fail (or maybe not) when connecting
+ return;
+ }
+
+ const auto records = m_dnsLookup->serviceRecords();
+ if (records.empty())
+ {
+ emit logLine(
+ QString("Failed to resolve server address %1: the DNS lookup succeeded, but no records were returned.\n")
+ .arg(m_dnsLookup->name()), MessageLevel::Warning);
+ resolve(m_lookupAddress, 25565); // Technically the task failed, however, we don't abort the launch
+ // and leave it up to minecraft to fail (or maybe not) when connecting
+ return;
+ }
+
+ const auto &firstRecord = records.at(0);
+ quint16 port = firstRecord.port();
+
+ emit logLine(QString("Resolved server address %1 to %2 with port %3\n").arg(
+ m_dnsLookup->name(), firstRecord.target(), QString::number(port)),MessageLevel::MultiMC);
+ resolve(firstRecord.target(), port);
+}
+
+void LookupServerAddress::resolve(const QString &address, quint16 port)
+{
+ m_output->address = address;
+ m_output->port = port;
+
+ emitSucceeded();
+ m_dnsLookup->deleteLater();
+}
diff --git a/api/logic/launch/steps/LookupServerAddress.h b/api/logic/launch/steps/LookupServerAddress.h
new file mode 100644
index 00000000..5a5c3de1
--- /dev/null
+++ b/api/logic/launch/steps/LookupServerAddress.h
@@ -0,0 +1,49 @@
+/* Copyright 2013-2021 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 <launch/LaunchStep.h>
+#include <QObjectPtr.h>
+#include <QDnsLookup>
+
+#include "minecraft/launch/MinecraftServerTarget.h"
+
+class LookupServerAddress: public LaunchStep {
+Q_OBJECT
+public:
+ explicit LookupServerAddress(LaunchTask *parent);
+ virtual ~LookupServerAddress() {};
+
+ virtual void executeTask();
+ virtual bool abort();
+ virtual bool canAbort() const
+ {
+ return true;
+ }
+
+ void setLookupAddress(const QString &lookupAddress);
+ void setOutputAddressPtr(MinecraftServerTargetPtr output);
+
+private slots:
+ void on_dnsLookupFinished();
+
+private:
+ void resolve(const QString &address, quint16 port);
+
+ QDnsLookup *m_dnsLookup;
+ QString m_lookupAddress;
+ MinecraftServerTargetPtr m_output;
+};
diff --git a/api/logic/launch/steps/PostLaunchCommand.cpp b/api/logic/launch/steps/PostLaunchCommand.cpp
index e0b2b6fc..d48d03d1 100644
--- a/api/logic/launch/steps/PostLaunchCommand.cpp
+++ b/api/logic/launch/steps/PostLaunchCommand.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 da200534..ab4c494f 100644
--- a/api/logic/launch/steps/PostLaunchCommand.h
+++ b/api/logic/launch/steps/PostLaunchCommand.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 6f1f280d..20e089e2 100644
--- a/api/logic/launch/steps/PreLaunchCommand.cpp
+++ b/api/logic/launch/steps/PreLaunchCommand.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 f1e9db5d..dc069f71 100644
--- a/api/logic/launch/steps/PreLaunchCommand.h
+++ b/api/logic/launch/steps/PreLaunchCommand.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 099fdc13..2937c64a 100644
--- a/api/logic/launch/steps/TextPrint.h
+++ b/api/logic/launch/steps/TextPrint.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 6eb012f7..28bd153d 100644
--- a/api/logic/launch/steps/Update.cpp
+++ b/api/logic/launch/steps/Update.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/logic/launch/steps/Update.h
index 331698bd..0c9d91e0 100644
--- a/api/logic/launch/steps/Update.h
+++ b/api/logic/launch/steps/Update.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/meta/BaseEntity.cpp b/api/logic/meta/BaseEntity.cpp
index 31d79aa3..5ff7a59a 100644
--- a/api/logic/meta/BaseEntity.cpp
+++ b/api/logic/meta/BaseEntity.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h
index 5fc5981a..04a37420 100644
--- a/api/logic/meta/BaseEntity.h
+++ b/api/logic/meta/BaseEntity.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/Index.cpp b/api/logic/meta/Index.cpp
index 4e0026b3..6802470d 100644
--- a/api/logic/meta/Index.cpp
+++ b/api/logic/meta/Index.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/Index.h b/api/logic/meta/Index.h
index 7a84ff38..e9412e70 100644
--- a/api/logic/meta/Index.h
+++ b/api/logic/meta/Index.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/JsonFormat.cpp b/api/logic/meta/JsonFormat.cpp
index 41abb8e9..796da4bb 100644
--- a/api/logic/meta/JsonFormat.cpp
+++ b/api/logic/meta/JsonFormat.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/JsonFormat.h b/api/logic/meta/JsonFormat.h
index 06732ffd..93217b7e 100644
--- a/api/logic/meta/JsonFormat.h
+++ b/api/logic/meta/JsonFormat.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/Version.cpp b/api/logic/meta/Version.cpp
index 2bbe19a7..a8dc3169 100644
--- a/api/logic/meta/Version.cpp
+++ b/api/logic/meta/Version.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/Version.h b/api/logic/meta/Version.h
index d902049a..a38d7bcd 100644
--- a/api/logic/meta/Version.h
+++ b/api/logic/meta/Version.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/VersionList.cpp b/api/logic/meta/VersionList.cpp
index cc15d0e4..607007eb 100644
--- a/api/logic/meta/VersionList.cpp
+++ b/api/logic/meta/VersionList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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/meta/VersionList.h b/api/logic/meta/VersionList.h
index 24b01d08..bba32ca3 100644
--- a/api/logic/meta/VersionList.h
+++ b/api/logic/meta/VersionList.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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.cpp b/api/logic/minecraft/AssetsUtils.cpp
index 68089080..c01733b6 100644
--- a/api/logic/minecraft/AssetsUtils.cpp
+++ b/api/logic/minecraft/AssetsUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 356b8c8a..32e57060 100644
--- a/api/logic/minecraft/AssetsUtils.h
+++ b/api/logic/minecraft/AssetsUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/GradleSpecifier.h b/api/logic/minecraft/GradleSpecifier.h
index 959325c6..60e0a726 100644
--- a/api/logic/minecraft/GradleSpecifier.h
+++ b/api/logic/minecraft/GradleSpecifier.h
@@ -18,32 +18,35 @@ struct GradleSpecifier
{
/*
org.gradle.test.classifiers : service : 1.0 : jdk15 @ jar
- DEBUG 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar"
- DEBUG 1 "org.gradle.test.classifiers"
- DEBUG 2 "service"
- DEBUG 3 "1.0"
- DEBUG 4 ":jdk15"
- DEBUG 5 "jdk15"
- DEBUG 6 "@jar"
- DEBUG 7 "jar"
+ 0 "org.gradle.test.classifiers:service:1.0:jdk15@jar"
+ 1 "org.gradle.test.classifiers"
+ 2 "service"
+ 3 "1.0"
+ 4 "jdk15"
+ 5 "jar"
*/
- QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(:([^:@]+))?" "(@([^:@]+))?");
+ QRegExp matcher("([^:@]+):([^:@]+):([^:@]+)" "(?::([^:@]+))?" "(?:@([^:@]+))?");
m_valid = matcher.exactMatch(value);
+ if(!m_valid) {
+ m_invalidValue = value;
+ return *this;
+ }
auto elements = matcher.capturedTexts();
m_groupId = elements[1];
m_artifactId = elements[2];
m_version = elements[3];
- m_classifier = elements[5];
- if(!elements[7].isEmpty())
+ m_classifier = elements[4];
+ if(!elements[5].isEmpty())
{
- m_extension = elements[7];
+ m_extension = elements[5];
}
return *this;
}
- operator QString() const
+ QString serialize() const
{
- if(!m_valid)
- return "INVALID";
+ if(!m_valid) {
+ return m_invalidValue;
+ }
QString retval = m_groupId + ":" + m_artifactId + ":" + m_version;
if(!m_classifier.isEmpty())
{
@@ -57,6 +60,9 @@ struct GradleSpecifier
}
QString getFileName() const
{
+ if(!m_valid) {
+ return QString();
+ }
QString filename = m_artifactId + '-' + m_version;
if(!m_classifier.isEmpty())
{
@@ -67,8 +73,9 @@ struct GradleSpecifier
}
QString toPath(const QString & filenameOverride = QString()) const
{
- if(!m_valid)
- return "INVALID";
+ if(!m_valid) {
+ return QString();
+ }
QString filename;
if(filenameOverride.isEmpty())
{
@@ -134,6 +141,7 @@ struct GradleSpecifier
return true;
}
private:
+ QString m_invalidValue;
QString m_groupId;
QString m_artifactId;
QString m_version;
diff --git a/api/logic/minecraft/GradleSpecifier_test.cpp b/api/logic/minecraft/GradleSpecifier_test.cpp
index f49ec718..0900c9d8 100644
--- a/api/logic/minecraft/GradleSpecifier_test.cpp
+++ b/api/logic/minecraft/GradleSpecifier_test.cpp
@@ -31,7 +31,7 @@ slots:
{
QFETCH(QString, through);
- QString converted = GradleSpecifier(through);
+ QString converted = GradleSpecifier(through).serialize();
QCOMPARE(converted, through);
}
@@ -68,7 +68,8 @@ slots:
GradleSpecifier spec(input);
QVERIFY(!spec.valid());
- QCOMPARE(spec.operator QString(), QString("INVALID"));
+ QCOMPARE(spec.serialize(), input);
+ QCOMPARE(spec.toPath(), QString());
}
};
diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp
index b3c7657c..f2293679 100644
--- a/api/logic/minecraft/Library.cpp
+++ b/api/logic/minecraft/Library.cpp
@@ -94,13 +94,13 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(
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;
+ qDebug() << "Checksummed Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
out.append(dl);
}
else
{
out.append(Net::Download::makeCached(url, entry, options));
- qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url;
+ qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
}
return true;
};
@@ -145,7 +145,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(
}
else
{
- qDebug() << "Ignoring native library" << m_name << "because it has no classifier for current OS";
+ qDebug() << "Ignoring native library" << m_name.serialize() << "because it has no classifier for current OS";
}
}
else
@@ -157,7 +157,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(
}
else
{
- qDebug() << "Ignoring java library" << m_name << "because it has no artifact";
+ qDebug() << "Ignoring java library" << m_name.serialize() << "because it has no artifact";
}
}
}
diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp
index c3d6150d..75bb4db1 100644
--- a/api/logic/minecraft/Library_test.cpp
+++ b/api/logic/minecraft/Library_test.cpp
@@ -18,7 +18,8 @@ private:
jsonFile.open(QIODevice::ReadOnly);
auto data = jsonFile.readAll();
jsonFile.close();
- return MojangVersionFormat::libraryFromJson(QJsonDocument::fromJson(data).object(), file);
+ ProblemContainer problems;
+ return MojangVersionFormat::libraryFromJson(problems, QJsonDocument::fromJson(data).object(), file);
}
// get absolute path to expected storage, assuming default cache prefix
QStringList getStorage(QString relative)
diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp
index db259395..dbf9f816 100644
--- a/api/logic/minecraft/MinecraftInstance.cpp
+++ b/api/logic/minecraft/MinecraftInstance.cpp
@@ -1,5 +1,5 @@
#include "MinecraftInstance.h"
-#include <minecraft/launch/CreateServerResourcePacksFolder.h>
+#include <minecraft/launch/CreateGameFolders.h>
#include <minecraft/launch/ExtractNatives.h>
#include <minecraft/launch/PrintInstanceInfo.h>
#include <settings/Setting.h>
@@ -12,6 +12,7 @@
#include <java/JavaVersion.h>
#include "launch/LaunchTask.h"
+#include "launch/steps/LookupServerAddress.h"
#include "launch/steps/PostLaunchCommand.h"
#include "launch/steps/Update.h"
#include "launch/steps/PreLaunchCommand.h"
@@ -22,12 +23,15 @@
#include "minecraft/launch/ClaimAccount.h"
#include "minecraft/launch/ReconstructAssets.h"
#include "minecraft/launch/ScanModFolders.h"
+#include "minecraft/launch/VerifyJavaInstall.h"
#include "java/launch/CheckJava.h"
#include "java/JavaUtils.h"
#include "meta/Index.h"
#include "meta/VersionList.h"
#include "mod/ModFolderModel.h"
+#include "mod/ResourcePackFolderModel.h"
+#include "mod/TexturePackFolderModel.h"
#include "WorldList.h"
#include "icons/IIconList.h"
@@ -38,6 +42,7 @@
#include "MinecraftUpdate.h"
#include "MinecraftLoadAndCheck.h"
#include <minecraft/gameoptions/GameOptions.h>
+#include <minecraft/update/FoldersTask.h>
#define IBUS "@im=ibus"
@@ -100,6 +105,20 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO
auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false);
m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride);
+ // Native library workarounds
+ auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false);
+ m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride);
+ m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride);
+
+ // Game time
+ auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false);
+ m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride);
+ m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride);
+
+ // Join server on launch, this does not have a global override
+ m_settings->registerSetting("JoinServerOnLaunch", false);
+ m_settings->registerSetting("JoinServerOnLaunchAddress", "");
+
// DEPRECATED: Read what versions the user configuration thinks should be used
m_settings->registerSetting({"IntendedVersion", "MinecraftVersion"}, "");
m_settings->registerSetting("LWJGLVersion", "");
@@ -384,7 +403,8 @@ static QString replaceTokensIn(QString text, QMap<QString, QString> with)
return result;
}
-QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) const
+QStringList MinecraftInstance::processMinecraftArgs(
+ AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) const
{
auto profile = m_components->getProfile();
QString args_pattern = profile->getMinecraftArguments();
@@ -393,6 +413,12 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons
args_pattern += " --tweakClass " + tweaker;
}
+ if (serverToJoin && !serverToJoin->address.isEmpty())
+ {
+ args_pattern += " --server " + serverToJoin->address;
+ args_pattern += " --port " + QString::number(serverToJoin->port);
+ }
+
QMap<QString, QString> token_mapping;
// yggdrasil!
if(session)
@@ -429,7 +455,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons
return parts;
}
-QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
+QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
QString launchScript;
@@ -450,8 +476,17 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
launchScript += "appletClass " + appletClass + "\n";
}
+ if (serverToJoin && !serverToJoin->address.isEmpty())
+ {
+ launchScript += "serverAddress " + serverToJoin->address + "\n";
+ launchScript += "serverPort " + QString::number(serverToJoin->port) + "\n";
+ }
+
// generic minecraft params
- for (auto param : processMinecraftArgs(session))
+ for (auto param : processMinecraftArgs(
+ session,
+ nullptr /* When using a launch script, the server parameters are handled by it*/
+ ))
{
launchScript += "param " + param + "\n";
}
@@ -501,7 +536,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session)
return launchScript;
}
-QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
+QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
QStringList out;
out << "Main Class:" << " " + getMainClass() << "";
@@ -520,11 +555,23 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
out << "";
}
+ auto settings = this->settings();
+ bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
+ bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
+ if (nativeOpenAL || nativeGLFW)
+ {
+ if (nativeOpenAL)
+ out << "Using system OpenAL.";
+ if (nativeGLFW)
+ out << "Using system GLFW.";
+ out << "";
+ }
+
// libraries and class path.
{
out << "Libraries:";
QStringList jars, nativeJars;
- auto javaArchitecture = settings()->get("JavaArchitecture").toString();
+ auto javaArchitecture = settings->get("JavaArchitecture").toString();
profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot());
auto printLibFile = [&](const QString & path)
{
@@ -604,20 +651,20 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
out << "";
}
- auto params = processMinecraftArgs(nullptr);
+ auto params = processMinecraftArgs(nullptr, serverToJoin);
out << "Params:";
out << " " + params.join(' ');
out << "";
QString windowParams;
- if (settings()->get("LaunchMaximized").toBool())
+ if (settings->get("LaunchMaximized").toBool())
{
out << "Window size: max (if available)";
}
else
{
- auto width = settings()->get("MinecraftWinWidth").toInt();
- auto height = settings()->get("MinecraftWinHeight").toInt();
+ auto width = settings->get("MinecraftWinWidth").toInt();
+ auto height = settings->get("MinecraftWinHeight").toInt();
out << "Window size: " + QString::number(width) + " x " + QString::number(height);
}
out << "";
@@ -751,9 +798,15 @@ QString MinecraftInstance::getStatusbarDescription()
QString description;
description.append(tr("Minecraft %1 (%2)").arg(m_components->getComponentVersion("net.minecraft")).arg(typeName()));
- if(totalTimePlayed() > 0)
+ if(m_settings->get("ShowGameTime").toBool())
{
- description.append(tr(", played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
+ if (lastTimePlayed() > 0) {
+ description.append(tr(", last played for %1").arg(prettifyTimeDuration(lastTimePlayed())));
+ }
+
+ if (totalTimePlayed() > 0) {
+ description.append(tr(", total played for %1").arg(prettifyTimeDuration(totalTimePlayed())));
+ }
}
if(hasCrashed())
{
@@ -778,7 +831,7 @@ shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
return nullptr;
}
-shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
+shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
// FIXME: get rid of shared_from_this ...
auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
@@ -805,6 +858,26 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
return process;
}
+ // create the .minecraft folder and server-resource-packs (workaround for Minecraft bug MCL-3732)
+ {
+ process->appendStep(new CreateGameFolders(pptr));
+ }
+
+ if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool())
+ {
+ QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString();
+ serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress)));
+ }
+
+ if(serverToJoin && serverToJoin->port == 25565)
+ {
+ // Resolve server address to join on launch
+ auto *step = new LookupServerAddress(pptr);
+ step->setLookupAddress(serverToJoin->address);
+ step->setOutputAddressPtr(serverToJoin);
+ process->appendStep(step);
+ }
+
// run pre-launch command if that's needed
if(getPreLaunchCommand().size())
{
@@ -829,19 +902,14 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(new ModMinecraftJar(pptr));
}
- // if there are any jar mods
+ // Scan mods folders for mods
{
process->appendStep(new ScanModFolders(pptr));
}
// print some instance info here...
{
- process->appendStep(new PrintInstanceInfo(pptr, session));
- }
-
- // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732)
- {
- process->appendStep(new CreateServerResourcePacksFolder(pptr));
+ process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin));
}
// extract native jars if needed
@@ -854,6 +922,11 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
process->appendStep(new ReconstructAssets(pptr));
}
+ // verify that minimum Java requirements are met
+ {
+ process->appendStep(new VerifyJavaInstall(pptr));
+ }
+
{
// actually launch the game
auto method = launchMethod();
@@ -862,6 +935,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
auto step = new LauncherPartLaunch(pptr);
step->setWorkingDirectory(gameRoot());
step->setAuthSession(session);
+ step->setServerToJoin(serverToJoin);
process->appendStep(step);
}
else if (method == "DirectJava")
@@ -869,6 +943,7 @@ shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPt
auto step = new DirectJavaLaunch(pptr);
step->setWorkingDirectory(gameRoot());
step->setAuthSession(session);
+ step->setServerToJoin(serverToJoin);
process->appendStep(step);
}
}
@@ -925,7 +1000,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
{
if (!m_resource_pack_list)
{
- m_resource_pack_list.reset(new ModFolderModel(resourcePacksDir()));
+ m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir()));
m_resource_pack_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction);
}
@@ -936,7 +1011,7 @@ std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
{
if (!m_texture_pack_list)
{
- m_texture_pack_list.reset(new ModFolderModel(texturePacksDir()));
+ m_texture_pack_list.reset(new TexturePackFolderModel(texturePacksDir()));
m_texture_pack_list->disableInteraction(isRunning());
connect(this, &BaseInstance::runningStatusChanged, m_texture_pack_list.get(), &ModFolderModel::disableInteraction);
}
diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h
index 985a8a76..05600797 100644
--- a/api/logic/minecraft/MinecraftInstance.h
+++ b/api/logic/minecraft/MinecraftInstance.h
@@ -5,6 +5,7 @@
#include <QProcess>
#include <QDir>
#include "multimc_logic_export.h"
+#include "minecraft/launch/MinecraftServerTarget.h"
class ModFolderModel;
class WorldList;
@@ -76,11 +77,11 @@ public:
////// Launch stuff //////
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
- shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override;
+ shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override;
QStringList extraArguments() const override;
- QStringList verboseDescription(AuthSessionPtr session) override;
+ QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
QList<Mod> getJarMods() const;
- QString createLaunchScript(AuthSessionPtr session);
+ QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin);
/// get arguments passed to java
QStringList javaArguments() const;
@@ -107,7 +108,7 @@ public:
virtual QString getMainClass() const;
// FIXME: remove
- virtual QStringList processMinecraftArgs(AuthSessionPtr account) const;
+ virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const;
virtual JavaVersion getJavaVersion() const;
diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h
index 6924ca2d..3435b52b 100644
--- a/api/logic/minecraft/MinecraftLoadAndCheck.h
+++ b/api/logic/minecraft/MinecraftLoadAndCheck.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/MinecraftUpdate.cpp b/api/logic/minecraft/MinecraftUpdate.cpp
index 68dd437d..8f1565b0 100644
--- a/api/logic/minecraft/MinecraftUpdate.cpp
+++ b/api/logic/minecraft/MinecraftUpdate.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/MinecraftUpdate.h b/api/logic/minecraft/MinecraftUpdate.h
index eb8e74db..fadebff9 100644
--- a/api/logic/minecraft/MinecraftUpdate.h
+++ b/api/logic/minecraft/MinecraftUpdate.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 33d3c54c..f9cb2228 100644
--- a/api/logic/minecraft/MojangVersionFormat.cpp
+++ b/api/logic/minecraft/MojangVersionFormat.cpp
@@ -220,7 +220,7 @@ VersionFilePtr MojangVersionFormat::versionFileFromJson(const QJsonDocument &doc
{
auto libObj = requireObject(libVal);
- auto lib = MojangVersionFormat::libraryFromJson(libObj, filename);
+ auto lib = MojangVersionFormat::libraryFromJson(*out, libObj, filename);
out->libraries.append(lib);
}
}
@@ -283,14 +283,18 @@ QJsonDocument MojangVersionFormat::versionFileToJson(const VersionFilePtr &patch
}
}
-LibraryPtr MojangVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename)
+LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename)
{
LibraryPtr out(new Library());
if (!libObj.contains("name"))
{
throw JSONValidationError(filename + "contains a library that doesn't have a 'name' field");
}
- out->m_name = libObj.value("name").toString();
+ auto rawName = libObj.value("name").toString();
+ out->m_name = rawName;
+ if(!out->m_name.valid()) {
+ problems.addProblem(ProblemSeverity::Error, QObject::tr("Library %1 name is broken and cannot be processed.").arg(rawName));
+ }
Bits::readString(libObj, "url", out->m_repositoryURL);
if (libObj.contains("extract"))
@@ -333,7 +337,7 @@ LibraryPtr MojangVersionFormat::libraryFromJson(const QJsonObject &libObj, const
QJsonObject MojangVersionFormat::libraryToJson(Library *library)
{
QJsonObject libRoot;
- libRoot.insert("name", (QString)library->m_name);
+ libRoot.insert("name", library->m_name.serialize());
if (!library->m_repositoryURL.isEmpty())
{
libRoot.insert("url", library->m_repositoryURL);
diff --git a/api/logic/minecraft/MojangVersionFormat.h b/api/logic/minecraft/MojangVersionFormat.h
index 76c529e9..2871dae4 100644
--- a/api/logic/minecraft/MojangVersionFormat.h
+++ b/api/logic/minecraft/MojangVersionFormat.h
@@ -3,6 +3,7 @@
#include <minecraft/VersionFile.h>
#include <minecraft/Library.h>
#include <QJsonDocument>
+#include <ProblemProvider.h>
#include "multimc_logic_export.h"
@@ -20,6 +21,6 @@ public:
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
// libraries
- static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
+ static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
static QJsonObject libraryToJson(Library *library);
};
diff --git a/api/logic/minecraft/OneSixVersionFormat.cpp b/api/logic/minecraft/OneSixVersionFormat.cpp
index 7ac9e2db..d6aaa790 100644
--- a/api/logic/minecraft/OneSixVersionFormat.cpp
+++ b/api/logic/minecraft/OneSixVersionFormat.cpp
@@ -13,9 +13,9 @@ static void readString(const QJsonObject &root, const QString &key, QString &var
}
}
-LibraryPtr OneSixVersionFormat::libraryFromJson(const QJsonObject &libObj, const QString &filename)
+LibraryPtr OneSixVersionFormat::libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename)
{
- LibraryPtr out = MojangVersionFormat::libraryFromJson(libObj, filename);
+ LibraryPtr out = MojangVersionFormat::libraryFromJson(problems, libObj, filename);
readString(libObj, "MMC-hint", out->m_hint);
readString(libObj, "MMC-absulute_url", out->m_absoluteURL);
readString(libObj, "MMC-absoluteUrl", out->m_absoluteURL);
@@ -115,7 +115,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
- auto lib = OneSixVersionFormat::jarModFromJson(libObj, filename);
+ auto lib = OneSixVersionFormat::jarModFromJson(*out, libObj, filename);
// and add to jar mods
out->jarMods.append(lib);
}
@@ -126,7 +126,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
- auto lib = OneSixVersionFormat::plusJarModFromJson(libObj, filename, out->name);
+ auto lib = OneSixVersionFormat::plusJarModFromJson(*out, libObj, filename, out->name);
// and add to jar mods
out->jarMods.append(lib);
}
@@ -138,20 +138,20 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
{
QJsonObject libObj = requireObject(libVal);
// parse the jarmod
- auto lib = OneSixVersionFormat::modFromJson(libObj, filename);
+ auto lib = OneSixVersionFormat::modFromJson(*out, libObj, filename);
// and add to jar mods
out->mods.append(lib);
}
}
- auto readLibs = [&](const char * which, QList<LibraryPtr> & out)
+ auto readLibs = [&](const char * which, QList<LibraryPtr> & outList)
{
for (auto libVal : requireArray(root.value(which)))
{
QJsonObject libObj = requireObject(libVal);
// parse the library
- auto lib = libraryFromJson(libObj, filename);
- out.append(lib);
+ auto lib = libraryFromJson(*out, libObj, filename);
+ outList.append(lib);
}
};
bool hasPlusLibs = root.contains("+libraries");
@@ -180,7 +180,7 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
if(root.contains("mainJar"))
{
QJsonObject libObj = requireObject(root, "mainJar");
- out->mainJar = libraryFromJson(libObj, filename);
+ out->mainJar = libraryFromJson(*out, libObj, filename);
}
// else reconstruct it from downloads and id ... if that's available
else if(!out->minecraftVersion.isEmpty())
@@ -330,8 +330,12 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
}
}
-LibraryPtr OneSixVersionFormat::plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName)
-{
+LibraryPtr OneSixVersionFormat::plusJarModFromJson(
+ ProblemContainer & problems,
+ const QJsonObject &libObj,
+ const QString &filename,
+ const QString &originalName
+) {
LibraryPtr out(new Library());
if (!libObj.contains("name"))
{
@@ -366,9 +370,9 @@ LibraryPtr OneSixVersionFormat::plusJarModFromJson(const QJsonObject &libObj, co
return out;
}
-LibraryPtr OneSixVersionFormat::jarModFromJson(const QJsonObject& libObj, const QString& filename)
+LibraryPtr OneSixVersionFormat::jarModFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename)
{
- return libraryFromJson(libObj, filename);
+ return libraryFromJson(problems, libObj, filename);
}
@@ -377,9 +381,9 @@ QJsonObject OneSixVersionFormat::jarModtoJson(Library *jarmod)
return libraryToJson(jarmod);
}
-LibraryPtr OneSixVersionFormat::modFromJson(const QJsonObject& libObj, const QString& filename)
+LibraryPtr OneSixVersionFormat::modFromJson(ProblemContainer & problems, const QJsonObject& libObj, const QString& filename)
{
- return libraryFromJson(libObj, filename);
+ return libraryFromJson(problems, libObj, filename);
}
QJsonObject OneSixVersionFormat::modtoJson(Library *jarmod)
diff --git a/api/logic/minecraft/OneSixVersionFormat.h b/api/logic/minecraft/OneSixVersionFormat.h
index 14ae385c..1a091d88 100644
--- a/api/logic/minecraft/OneSixVersionFormat.h
+++ b/api/logic/minecraft/OneSixVersionFormat.h
@@ -4,6 +4,7 @@
#include <minecraft/PackProfile.h>
#include <minecraft/Library.h>
#include <QJsonDocument>
+#include <ProblemProvider.h>
class OneSixVersionFormat
{
@@ -13,17 +14,17 @@ public:
static QJsonDocument versionFileToJson(const VersionFilePtr &patch);
// libraries
- static LibraryPtr libraryFromJson(const QJsonObject &libObj, const QString &filename);
+ static LibraryPtr libraryFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
static QJsonObject libraryToJson(Library *library);
// DEPRECATED: old 'plus' jar mods generated by the application
- static LibraryPtr plusJarModFromJson(const QJsonObject &libObj, const QString &filename, const QString &originalName);
+ static LibraryPtr plusJarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename, const QString &originalName);
// new jar mods derived from libraries
- static LibraryPtr jarModFromJson(const QJsonObject &libObj, const QString &filename);
+ static LibraryPtr jarModFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
static QJsonObject jarModtoJson(Library * jarmod);
// mods, also derived from libraries
- static LibraryPtr modFromJson(const QJsonObject &libObj, const QString &filename);
+ static LibraryPtr modFromJson(ProblemContainer & problems, const QJsonObject &libObj, const QString &filename);
static QJsonObject modtoJson(Library * jarmod);
};
diff --git a/api/logic/minecraft/OpSys.cpp b/api/logic/minecraft/OpSys.cpp
index 5e1e6c2b..f6a4ed1c 100644
--- a/api/logic/minecraft/OpSys.cpp
+++ b/api/logic/minecraft/OpSys.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 58852acb..63c750b1 100644
--- a/api/logic/minecraft/OpSys.h
+++ b/api/logic/minecraft/OpSys.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/PackProfile.cpp b/api/logic/minecraft/PackProfile.cpp
index 15f2f55d..f6918116 100644
--- a/api/logic/minecraft/PackProfile.cpp
+++ b/api/logic/minecraft/PackProfile.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/PackProfile.h b/api/logic/minecraft/PackProfile.h
index 66cd5e3e..e55e6a58 100644
--- a/api/logic/minecraft/PackProfile.h
+++ b/api/logic/minecraft/PackProfile.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -114,6 +114,10 @@ public:
/// get the profile component by index
Component * getComponent(int index);
+ /// Add the component to the internal list of patches
+ // todo(merged): is this the best approach
+ void appendComponent(ComponentPtr component);
+
private:
void scheduleSave();
bool saveIsScheduled() const;
@@ -121,8 +125,6 @@ private:
/// 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);
diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp
index cd10504f..af2861e3 100644
--- a/api/logic/minecraft/Rule.cpp
+++ b/api/logic/minecraft/Rule.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 ef10a6c3..7aa34d96 100644
--- a/api/logic/minecraft/Rule.h
+++ b/api/logic/minecraft/Rule.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/VersionFilterData.cpp b/api/logic/minecraft/VersionFilterData.cpp
index 11f7eea9..38e7b60c 100644
--- a/api/logic/minecraft/VersionFilterData.cpp
+++ b/api/logic/minecraft/VersionFilterData.cpp
@@ -7,18 +7,18 @@ VersionFilterData::VersionFilterData()
{
// 1.3.*
auto libs13 =
- QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
- {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
- {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false}};
+ QList<FMLlib>{{"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"},
+ {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"},
+ {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"}};
fmlLibsMapping["1.3.2"] = libs13;
// 1.4.*
auto libs14 = QList<FMLlib>{
- {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b", false},
- {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f", false},
- {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82", false},
- {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb", false}};
+ {"argo-2.25.jar", "bb672829fde76cb163004752b86b0484bd0a7f4b"},
+ {"guava-12.0.1.jar", "b8e78b9af7bf45900e14c6f958486b6ca682195f"},
+ {"asm-all-4.0.jar", "98308890597acb64047f7e896638e0d98753ae82"},
+ {"bcprov-jdk15on-147.jar", "b6f5d9926b0afbde9f4dbe3db88c5247be7794bb"}};
fmlLibsMapping["1.4"] = libs14;
fmlLibsMapping["1.4.1"] = libs14;
@@ -31,30 +31,30 @@ VersionFilterData::VersionFilterData()
// 1.5
fmlLibsMapping["1.5"] = QList<FMLlib>{
- {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
- {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
- {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
- {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
- {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8", false},
- {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
+ {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"},
+ {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"},
+ {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"},
+ {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"},
+ {"deobfuscation_data_1.5.zip", "5f7c142d53776f16304c0bbe10542014abad6af8"},
+ {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}};
// 1.5.1
fmlLibsMapping["1.5.1"] = QList<FMLlib>{
- {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
- {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
- {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
- {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
- {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6", false},
- {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
+ {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"},
+ {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"},
+ {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"},
+ {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"},
+ {"deobfuscation_data_1.5.1.zip", "22e221a0d89516c1f721d6cab056a7e37471d0a6"},
+ {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}};
// 1.5.2
fmlLibsMapping["1.5.2"] = QList<FMLlib>{
- {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51", false},
- {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a", false},
- {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58", false},
- {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65", true},
- {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9", false},
- {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85", true}};
+ {"argo-small-3.2.jar", "58912ea2858d168c50781f956fa5b59f0f7c6b51"},
+ {"guava-14.0-rc3.jar", "931ae21fa8014c3ce686aaa621eae565fefb1a6a"},
+ {"asm-all-4.1.jar", "054986e962b88d8660ae4566475658469595ef58"},
+ {"bcprov-jdk15on-148.jar", "960dea7c9181ba0b17e8bab0c06a43f0a5f04e65"},
+ {"deobfuscation_data_1.5.2.zip", "446e55cd986582c70fcf12cb27bc00114c5adfd9"},
+ {"scala-library.jar", "458d046151ad179c85429ed7420ffb1eaf6ddf85"}};
// don't use installers for those.
forgeInstallerBlacklist = QSet<QString>({"1.5.2"});
@@ -65,4 +65,7 @@ VersionFilterData::VersionFilterData()
QSet<QString>{"net.java.jinput:jinput", "net.java.jinput:jinput-platform",
"net.java.jutils:jutils", "org.lwjgl.lwjgl:lwjgl",
"org.lwjgl.lwjgl:lwjgl_util", "org.lwjgl.lwjgl:lwjgl-platform"};
+
+ java8BeginsDate = timeFromS3Time("2017-03-30T09:32:19+00:00");
+ java16BeginsDate = timeFromS3Time("2021-05-12T11:19:15+00:00");
}
diff --git a/api/logic/minecraft/VersionFilterData.h b/api/logic/minecraft/VersionFilterData.h
index 88e91f11..d100acc3 100644
--- a/api/logic/minecraft/VersionFilterData.h
+++ b/api/logic/minecraft/VersionFilterData.h
@@ -10,7 +10,6 @@ struct FMLlib
{
QString filename;
QString checksum;
- bool ours;
};
struct VersionFilterData
@@ -24,5 +23,9 @@ struct VersionFilterData
QDateTime legacyCutoffDate;
// Libraries that belong to LWJGL
QSet<QString> lwjglWhitelist;
+ // release date of first version to require Java 8 (17w13a)
+ QDateTime java8BeginsDate;
+ // release data of first version to require Java 16 (21w19a)
+ QDateTime java16BeginsDate;
};
extern VersionFilterData MULTIMC_LOGIC_EXPORT g_VersionFilterData;
diff --git a/api/logic/minecraft/World.cpp b/api/logic/minecraft/World.cpp
index 17dbf4ae..a2b4dac7 100644
--- a/api/logic/minecraft/World.cpp
+++ b/api/logic/minecraft/World.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,7 +32,36 @@
#include <QCoreApplication>
-QString gameTypeToString(GameType type)
+#include <nonstd/optional>
+
+using nonstd::optional;
+using nonstd::nullopt;
+
+GameType::GameType(nonstd::optional<int> original):
+ original(original)
+{
+ if(!original) {
+ return;
+ }
+ switch(*original) {
+ case 0:
+ type = GameType::Survival;
+ break;
+ case 1:
+ type = GameType::Creative;
+ break;
+ case 2:
+ type = GameType::Adventure;
+ break;
+ case 3:
+ type = GameType::Spectator;
+ break;
+ default:
+ break;
+ }
+}
+
+QString GameType::toTranslatedString() const
{
switch (type)
{
@@ -47,7 +76,31 @@ QString gameTypeToString(GameType type)
default:
break;
}
- return QObject::tr("Unknown");
+ if(original) {
+ return QCoreApplication::translate("GameType", "Unknown (%1)").arg(*original);
+ }
+ return QCoreApplication::translate("GameType", "Undefined");
+}
+
+QString GameType::toLogString() const
+{
+ switch (type)
+ {
+ case GameType::Survival:
+ return "Survival";
+ case GameType::Creative:
+ return "Creative";
+ case GameType::Adventure:
+ return "Adventure";
+ case GameType::Spectator:
+ return "Spectator";
+ default:
+ break;
+ }
+ if(original) {
+ return QString("Unknown (%1)").arg(*original);
+ }
+ return "Undefined";
}
std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data)
@@ -58,15 +111,22 @@ std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data)
return nullptr;
}
std::istringstream foo(std::string(output.constData(), output.size()));
- auto pair = nbt::io::read_compound(foo);
+ try {
+ auto pair = nbt::io::read_compound(foo);
- if(pair.first != "")
- return nullptr;
+ if(pair.first != "")
+ return nullptr;
- if(pair.second == nullptr)
- return nullptr;
+ if(pair.second == nullptr)
+ return nullptr;
- return std::move(pair.second);
+ return std::move(pair.second);
+ }
+ catch (const nbt::io::input_error &e)
+ {
+ qWarning() << "Unable to parse level.dat:" << e.what();
+ return nullptr;
+ }
}
QByteArray serializeLevelDat(nbt::tag_compound * levelInfo)
@@ -138,14 +198,31 @@ void World::repath(const QFileInfo &file)
m_folderName = file.fileName();
if(file.isFile() && file.suffix() == "zip")
{
+ m_iconFile = QString();
readFromZip(file);
}
else if(file.isDir())
{
+ QFileInfo assumedIconPath(file.absoluteFilePath() + "/icon.png");
+ if(assumedIconPath.exists()) {
+ m_iconFile = assumedIconPath.absoluteFilePath();
+ }
readFromFS(file);
}
}
+bool World::resetIcon()
+{
+ if(m_iconFile.isNull()) {
+ return false;
+ }
+ if(QFile(m_iconFile).remove()) {
+ m_iconFile = QString();
+ return true;
+ }
+ return false;
+}
+
void World::readFromFS(const QFileInfo &file)
{
auto bytes = getLevelDatDataFromFS(file);
@@ -212,7 +289,7 @@ bool World::install(const QString &to, const QString &name)
{
return false;
}
- ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath).isEmpty();
+ ok = !MMCZip::extractSubDir(&zip, m_containerOffsetPath, finalPath);
}
else if(m_containerFile.isDir())
{
@@ -271,14 +348,16 @@ bool World::rename(const QString &newName)
return true;
}
-static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString())
+namespace {
+
+optional<QString> read_string (nbt::value& parent, const char * name)
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::String)
{
- return fallback;
+ return nullopt;
}
auto & tag_str = namedValue.as<nbt::tag_string>();
return QString::fromStdString(tag_str.get());
@@ -286,25 +365,25 @@ static QString read_string (nbt::value& parent, const char * name, const QString
catch (const std::out_of_range &e)
{
// fallback for old world formats
- qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback;
- return fallback;
+ qWarning() << "String NBT tag" << name << "could not be found.";
+ return nullopt;
}
catch (const std::bad_cast &e)
{
// type mismatch
- qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback;
- return fallback;
+ qWarning() << "NBT tag" << name << "could not be converted to string.";
+ return nullopt;
}
}
-static int64_t read_long (nbt::value& parent, const char * name, const int64_t & fallback = 0)
+optional<int64_t> read_long (nbt::value& parent, const char * name)
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::Long)
{
- return fallback;
+ return nullopt;
}
auto & tag_str = namedValue.as<nbt::tag_long>();
return tag_str.get();
@@ -312,25 +391,25 @@ static int64_t read_long (nbt::value& parent, const char * name, const int64_t &
catch (const std::out_of_range &e)
{
// fallback for old world formats
- qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback;
- return fallback;
+ qWarning() << "Long NBT tag" << name << "could not be found.";
+ return nullopt;
}
catch (const std::bad_cast &e)
{
// type mismatch
- qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback;
- return fallback;
+ qWarning() << "NBT tag" << name << "could not be converted to long.";
+ return nullopt;
}
}
-static int read_int (nbt::value& parent, const char * name, const int & fallback = 0)
+optional<int> read_int (nbt::value& parent, const char * name)
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::Int)
{
- return fallback;
+ return nullopt;
}
auto & tag_str = namedValue.as<nbt::tag_int>();
return tag_str.get();
@@ -338,62 +417,72 @@ static int read_int (nbt::value& parent, const char * name, const int & fallback
catch (const std::out_of_range &e)
{
// fallback for old world formats
- qWarning() << "Int NBT tag" << name << "could not be found. Defaulting to" << fallback;
- return fallback;
+ qWarning() << "Int NBT tag" << name << "could not be found.";
+ return nullopt;
}
catch (const std::bad_cast &e)
{
// type mismatch
- qWarning() << "NBT tag" << name << "could not be converted to int. Defaulting to" << fallback;
- return fallback;
+ qWarning() << "NBT tag" << name << "could not be converted to int.";
+ return nullopt;
}
}
+GameType read_gametype(nbt::value& parent, const char * name) {
+ return GameType(read_int(parent, name));
+}
+
+}
+
void World::loadFromLevelDat(QByteArray data)
{
- try
+ auto levelData = parseLevelDat(data);
+ if(!levelData)
{
- auto levelData = parseLevelDat(data);
- if(!levelData)
- {
- is_valid = false;
- return;
- }
-
- auto &val = levelData->at("Data");
- is_valid = val.get_type() == nbt::tag_type::Compound;
- if(!is_valid)
- return;
+ is_valid = false;
+ return;
+ }
- m_actualName = read_string(val, "LevelName", m_folderName);
+ nbt::value * valPtr = nullptr;
+ try {
+ valPtr = &levelData->at("Data");
+ }
+ catch (const std::out_of_range &e) {
+ qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what();
+ is_valid = false;
+ return;
+ }
+ nbt::value &val = *valPtr;
+ is_valid = val.get_type() == nbt::tag_type::Compound;
+ if(!is_valid)
+ return;
- int64_t temp = read_long(val, "LastPlayed", 0);
- if(temp == 0)
- {
- m_lastPlayed = levelDatTime;
- }
- else
- {
- m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp);
- }
+ auto name = read_string(val, "LevelName");
+ m_actualName = name ? *name : m_folderName;
- int GameType_val = read_int(val, "GameType", 0);
- m_gameType = (GameType) GameType_val;
+ auto timestamp = read_long(val, "LastPlayed");
+ m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime;
- m_randomSeed = read_long(val, "RandomSeed", 0);
+ m_gameType = read_gametype(val, "GameType");
- qDebug() << "World Name:" << m_actualName;
- qDebug() << "Last Played:" << m_lastPlayed.toString();
- qDebug() << "Seed:" << m_randomSeed;
- qDebug() << "GameMode:" << GameType_val;
+ optional<int64_t> randomSeed;
+ try {
+ auto &WorldGen_val = val.at("WorldGenSettings");
+ randomSeed = read_long(WorldGen_val, "seed");
}
- catch (const nbt::io::input_error &e)
- {
- qWarning() << "Unable to load" << m_folderName << ":" << e.what();
- is_valid = false;
- return;
+ catch (const std::out_of_range &) {}
+ if(!randomSeed) {
+ randomSeed = read_long(val, "RandomSeed");
+ }
+ m_randomSeed = randomSeed ? *randomSeed : 0;
+
+ qDebug() << "World Name:" << m_actualName;
+ qDebug() << "Last Played:" << m_lastPlayed.toString();
+ if(randomSeed) {
+ qDebug() << "Seed:" << *randomSeed;
}
+ qDebug() << "GameType:" << m_gameType.toLogString();
}
bool World::replace(World &with)
diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h
index 818701fa..1d94d54d 100644
--- a/api/logic/minecraft/World.h
+++ b/api/logic/minecraft/World.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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,17 +16,27 @@
#pragma once
#include <QFileInfo>
#include <QDateTime>
+#include <nonstd/optional>
#include "multimc_logic_export.h"
-enum class GameType
-{
- Survival,
- Creative,
- Adventure,
- Spectator
+struct MULTIMC_LOGIC_EXPORT GameType {
+ GameType() = default;
+ GameType (nonstd::optional<int> original);
+
+ QString toTranslatedString() const;
+ QString toLogString() const;
+
+ enum
+ {
+ Unknown = -1,
+ Survival = 0,
+ Creative,
+ Adventure,
+ Spectator
+ } type = Unknown;
+ nonstd::optional<int> original;
};
-QString MULTIMC_LOGIC_EXPORT gameTypeToString(GameType type);
class MULTIMC_LOGIC_EXPORT World
{
@@ -40,6 +50,10 @@ public:
{
return m_actualName;
}
+ QString iconFile() const
+ {
+ return m_iconFile;
+ }
QDateTime lastPlayed() const
{
return m_lastPlayed;
@@ -70,6 +84,8 @@ public:
bool replace(World &with);
// change the world's filesystem path (used by world lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
+ // remove the icon file, if any
+ bool resetIcon();
bool rename(const QString &to);
bool install(const QString &to, const QString &name= QString());
@@ -88,9 +104,10 @@ protected:
QString m_containerOffsetPath;
QString m_folderName;
QString m_actualName;
+ QString m_iconFile;
QDateTime levelDatTime;
QDateTime m_lastPlayed;
int64_t m_randomSeed = 0;
- GameType m_gameType = GameType::Survival;
+ GameType m_gameType;
bool is_valid = false;
};
diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp
index b7a24434..f6309dbd 100644
--- a/api/logic/minecraft/WorldList.cpp
+++ b/api/logic/minecraft/WorldList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -136,6 +136,19 @@ bool WorldList::deleteWorlds(int first, int last)
return true;
}
+bool WorldList::resetIcon(int row)
+{
+ if (row >= worlds.size() || row < 0)
+ return false;
+ World &m = worlds[row];
+ if(m.resetIcon()) {
+ emit dataChanged(index(row), index(row), {WorldList::IconFileRole});
+ return true;
+ }
+ return false;
+}
+
+
int WorldList::columnCount(const QModelIndex &parent) const
{
return 3;
@@ -162,7 +175,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
return world.name();
case GameModeColumn:
- return gameTypeToString(world.gameType());
+ return world.gameType().toTranslatedString();
case LastPlayedColumn:
return world.lastPlayed();
@@ -195,6 +208,10 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
{
return world.lastPlayed();
}
+ case IconFileRole:
+ {
+ return world.iconFile();
+ }
default:
return QVariant();
}
diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h
index 37ad330a..740b1461 100644
--- a/api/logic/minecraft/WorldList.h
+++ b/api/logic/minecraft/WorldList.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 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,8 @@ public:
SeedRole,
NameRole,
GameModeRole,
- LastPlayedRole
+ LastPlayedRole,
+ IconFileRole
};
WorldList(const QString &dir);
@@ -81,6 +82,9 @@ public:
/// Deletes the mod at the given index.
virtual bool deleteWorld(int index);
+ /// Removes the world icon, if any
+ virtual bool resetIcon(int index);
+
/// Deletes all the selected mods
virtual bool deleteWorlds(int first, int last);
diff --git a/api/logic/minecraft/auth/MojangAccount.cpp b/api/logic/minecraft/auth/MojangAccount.cpp
index 657e0009..f5853fe3 100644
--- a/api/logic/minecraft/auth/MojangAccount.cpp
+++ b/api/logic/minecraft/auth/MojangAccount.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/api/logic/minecraft/auth/MojangAccount.h b/api/logic/minecraft/auth/MojangAccount.h
index 7006435e..30a5f2ff 100644
--- a/api/logic/minecraft/auth/MojangAccount.h
+++ b/api/logic/minecraft/auth/MojangAccount.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 b594c652..e584cb3b 100644
--- a/api/logic/minecraft/auth/MojangAccountList.cpp
+++ b/api/logic/minecraft/auth/MojangAccountList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/logic/minecraft/auth/MojangAccountList.h
index 7760b32f..cc3a61a2 100644
--- a/api/logic/minecraft/auth/MojangAccountList.h
+++ b/api/logic/minecraft/auth/MojangAccountList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 570fe1ea..0857b46b 100644
--- a/api/logic/minecraft/auth/YggdrasilTask.cpp
+++ b/api/logic/minecraft/auth/YggdrasilTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/logic/minecraft/auth/YggdrasilTask.h
index c5352386..8af2e132 100644
--- a/api/logic/minecraft/auth/YggdrasilTask.h
+++ b/api/logic/minecraft/auth/YggdrasilTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 8286c7a4..2e8dc859 100644
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
+++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
@@ -1,5 +1,5 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 e95837a9..4c14eec7 100644
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.h
+++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 7a789f42..ecba178d 100644
--- a/api/logic/minecraft/auth/flows/RefreshTask.cpp
+++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 b2dfd261..f0840dda 100644
--- a/api/logic/minecraft/auth/flows/RefreshTask.h
+++ b/api/logic/minecraft/auth/flows/RefreshTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 01f0f819..6b3f0a65 100644
--- a/api/logic/minecraft/auth/flows/ValidateTask.cpp
+++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp
@@ -1,5 +1,5 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 dcb76b2b..986c2e9f 100644
--- a/api/logic/minecraft/auth/flows/ValidateTask.h
+++ b/api/logic/minecraft/auth/flows/ValidateTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h
index d0892981..c5bd75f3 100644
--- a/api/logic/minecraft/launch/ClaimAccount.h
+++ b/api/logic/minecraft/launch/ClaimAccount.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/CreateGameFolders.cpp b/api/logic/minecraft/launch/CreateGameFolders.cpp
new file mode 100644
index 00000000..4081e72e
--- /dev/null
+++ b/api/logic/minecraft/launch/CreateGameFolders.cpp
@@ -0,0 +1,28 @@
+#include "CreateGameFolders.h"
+#include "minecraft/MinecraftInstance.h"
+#include "launch/LaunchTask.h"
+#include "FileSystem.h"
+
+CreateGameFolders::CreateGameFolders(LaunchTask* parent): LaunchStep(parent)
+{
+}
+
+void CreateGameFolders::executeTask()
+{
+ auto instance = m_parent->instance();
+ std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
+
+ if(!FS::ensureFolderPathExists(minecraftInstance->gameRoot()))
+ {
+ emit logLine("Couldn't create the main game folder", MessageLevel::Error);
+ emitFailed(tr("Couldn't create the main game folder"));
+ return;
+ }
+
+ // HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created.
+ if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs")))
+ {
+ emit logLine("Couldn't create the 'server-resource-packs' folder", MessageLevel::Error);
+ }
+ emitSucceeded();
+}
diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h b/api/logic/minecraft/launch/CreateGameFolders.h
index 00ab2f2d..9c7d3c94 100644
--- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h
+++ b/api/logic/minecraft/launch/CreateGameFolders.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,13 @@
#include <LoggedProcess.h>
#include <minecraft/auth/AuthSession.h>
-// HACK: this is a workaround for MCL-3732 - 'server-resource-packs' folder is created.
-class CreateServerResourcePacksFolder: public LaunchStep
+// Create the main .minecraft for the instance and any other necessary folders
+class CreateGameFolders: public LaunchStep
{
Q_OBJECT
public:
- explicit CreateServerResourcePacksFolder(LaunchTask *parent);
- virtual ~CreateServerResourcePacksFolder() {};
+ explicit CreateGameFolders(LaunchTask *parent);
+ virtual ~CreateGameFolders() {};
virtual void executeTask();
virtual bool canAbort() const
diff --git a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp b/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp
deleted file mode 100644
index ae426e31..00000000
--- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.cpp
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "CreateServerResourcePacksFolder.h"
-#include "minecraft/MinecraftInstance.h"
-#include "launch/LaunchTask.h"
-#include "FileSystem.h"
-
-CreateServerResourcePacksFolder::CreateServerResourcePacksFolder(LaunchTask* parent): LaunchStep(parent)
-{
-}
-
-void CreateServerResourcePacksFolder::executeTask()
-{
- auto instance = m_parent->instance();
- std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
- if(!FS::ensureFolderPathExists(FS::PathCombine(minecraftInstance->gameRoot(), "server-resource-packs")))
- {
- emit logLine(tr("Couldn't create the 'server-resource-packs' folder"), MessageLevel::Error);
- }
- emitSucceeded();
-}
diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.cpp b/api/logic/minecraft/launch/DirectJavaLaunch.cpp
index 5c2bb91e..2110384f 100644
--- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp
+++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,7 +55,7 @@ void DirectJavaLaunch::executeTask()
// make detachable - this will keep the process running even if the object is destroyed
m_process.setDetachable(true);
- auto mcArgs = minecraftInstance->processMinecraftArgs(m_session);
+ auto mcArgs = minecraftInstance->processMinecraftArgs(m_session, m_serverToJoin);
args.append(mcArgs);
QString wrapperCommandStr = instance->getWrapperCommand().trimmed();
@@ -66,9 +66,9 @@ void DirectJavaLaunch::executeTask()
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
- QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
- emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
+ const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found.");
+ emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal);
+ emitFailed(tr(reason).arg(wrapperCommand));
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
@@ -87,18 +87,17 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state)
{
case LoggedProcess::FailedToStart:
{
- //: Error message displayed if instace can't start
- QString reason = tr("Could not launch minecraft!");
+ //: Error message displayed if instance can't start
+ const char *reason = QT_TR_NOOP("Could not launch minecraft!");
emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
+ emitFailed(tr(reason));
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
-
{
m_parent->setPid(-1);
- emitFailed("Game crashed.");
+ emitFailed(tr("Game crashed."));
return;
}
case LoggedProcess::Finished:
@@ -108,7 +107,7 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state)
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
- emitFailed("Game crashed.");
+ emitFailed(tr("Game crashed."));
return;
}
//FIXME: make this work again
@@ -118,7 +117,7 @@ void DirectJavaLaunch::on_state(LoggedProcess::State state)
break;
}
case LoggedProcess::Running:
- emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
+ emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
break;
diff --git a/api/logic/minecraft/launch/DirectJavaLaunch.h b/api/logic/minecraft/launch/DirectJavaLaunch.h
index 9f57272c..58b119b8 100644
--- a/api/logic/minecraft/launch/DirectJavaLaunch.h
+++ b/api/logic/minecraft/launch/DirectJavaLaunch.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,8 @@
#include <LoggedProcess.h>
#include <minecraft/auth/AuthSession.h>
+#include "MinecraftServerTarget.h"
+
class DirectJavaLaunch: public LaunchStep
{
Q_OBJECT
@@ -38,6 +40,12 @@ public:
{
m_session = session;
}
+
+ void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
+ {
+ m_serverToJoin = std::move(serverToJoin);
+ }
+
private slots:
void on_state(LoggedProcess::State state);
@@ -45,5 +53,6 @@ private:
LoggedProcess m_process;
QString m_command;
AuthSessionPtr m_session;
+ MinecraftServerTargetPtr m_serverToJoin;
};
diff --git a/api/logic/minecraft/launch/ExtractNatives.cpp b/api/logic/minecraft/launch/ExtractNatives.cpp
index 253d13bc..d57499aa 100644
--- a/api/logic/minecraft/launch/ExtractNatives.cpp
+++ b/api/logic/minecraft/launch/ExtractNatives.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,7 +33,7 @@ static QString replaceSuffix (QString target, const QString &suffix, const QStri
return target + replacement;
}
-static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack)
+static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibHack, bool nativeOpenAL, bool nativeGLFW)
{
QuaZip zip(source);
if(!zip.open(QuaZip::mdUnzip))
@@ -48,6 +48,13 @@ static bool unzipNatives(QString source, QString targetFolder, bool applyJnilibH
do
{
QString name = zip.getCurrentFileName();
+ auto lowercase = name.toLower();
+ if (nativeGLFW && name.contains("glfw")) {
+ continue;
+ }
+ if (nativeOpenAL && name.contains("openal")) {
+ continue;
+ }
if(applyJnilibHack)
{
name = replaceSuffix(name, ".jnilib", ".dylib");
@@ -76,16 +83,20 @@ void ExtractNatives::executeTask()
emitSucceeded();
return;
}
+ auto settings = minecraftInstance->settings();
+ bool nativeOpenAL = settings->get("UseNativeOpenAL").toBool();
+ bool nativeGLFW = settings->get("UseNativeGLFW").toBool();
+
auto outputPath = minecraftInstance->getNativePath();
auto javaVersion = minecraftInstance->getJavaVersion();
bool jniHackEnabled = javaVersion.major() >= 8;
for(const auto &source: toExtract)
{
- if(!unzipNatives(source, outputPath, jniHackEnabled))
+ if(!unzipNatives(source, outputPath, jniHackEnabled, nativeOpenAL, nativeGLFW))
{
- auto reason = tr("Couldn't extract native jar '%1' to destination '%2'").arg(source, outputPath);
- emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
+ const char *reason = QT_TR_NOOP("Couldn't extract native jar '%1' to destination '%2'");
+ emit logLine(QString(reason).arg(source, outputPath), MessageLevel::Fatal);
+ emitFailed(tr(reason).arg(source, outputPath));
}
}
emitSucceeded();
diff --git a/api/logic/minecraft/launch/ExtractNatives.h b/api/logic/minecraft/launch/ExtractNatives.h
index abb3bd7a..094fcd6b 100644
--- a/api/logic/minecraft/launch/ExtractNatives.h
+++ b/api/logic/minecraft/launch/ExtractNatives.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 e854a3a6..ee469770 100644
--- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp
+++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,7 +59,7 @@ void LauncherPartLaunch::executeTask()
auto instance = m_parent->instance();
std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
- m_launchScript = minecraftInstance->createLaunchScript(m_session);
+ m_launchScript = minecraftInstance->createLaunchScript(m_session, m_serverToJoin);
QStringList args = minecraftInstance->javaArguments();
QString allArgs = args.join(", ");
emit logLine("Java Arguments:\n[" + m_parent->censorPrivateInfo(allArgs) + "]\n\n", MessageLevel::MultiMC);
@@ -118,9 +118,9 @@ void LauncherPartLaunch::executeTask()
auto realWrapperCommand = QStandardPaths::findExecutable(wrapperCommand);
if (realWrapperCommand.isEmpty())
{
- QString reason = tr("The wrapper command \"%1\" couldn't be found.").arg(wrapperCommand);
- emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
+ const char *reason = QT_TR_NOOP("The wrapper command \"%1\" couldn't be found.");
+ emit logLine(QString(reason).arg(wrapperCommand), MessageLevel::Fatal);
+ emitFailed(tr(reason).arg(wrapperCommand));
return;
}
emit logLine("Wrapper command is:\n" + wrapperCommandStr + "\n\n", MessageLevel::MultiMC);
@@ -140,17 +140,16 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
case LoggedProcess::FailedToStart:
{
//: Error message displayed if instace can't start
- QString reason = tr("Could not launch minecraft!");
+ const char *reason = QT_TR_NOOP("Could not launch minecraft!");
emit logLine(reason, MessageLevel::Fatal);
- emitFailed(reason);
+ emitFailed(tr(reason));
return;
}
case LoggedProcess::Aborted:
case LoggedProcess::Crashed:
-
{
m_parent->setPid(-1);
- emitFailed("Game crashed.");
+ emitFailed(tr("Game crashed."));
return;
}
case LoggedProcess::Finished:
@@ -160,7 +159,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
auto exitCode = m_process.exitCode();
if(exitCode != 0)
{
- emitFailed("Game crashed.");
+ emitFailed(tr("Game crashed."));
return;
}
//FIXME: make this work again
@@ -170,7 +169,7 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state)
break;
}
case LoggedProcess::Running:
- emit logLine(tr("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
+ emit logLine(QString("Minecraft process ID: %1\n\n").arg(m_process.processId()), MessageLevel::MultiMC);
m_parent->setPid(m_process.processId());
m_parent->instance()->setLastLaunch();
// send the launch script to the launcher part
diff --git a/api/logic/minecraft/launch/LauncherPartLaunch.h b/api/logic/minecraft/launch/LauncherPartLaunch.h
index 1bc4a5f8..6a7ee0e5 100644
--- a/api/logic/minecraft/launch/LauncherPartLaunch.h
+++ b/api/logic/minecraft/launch/LauncherPartLaunch.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,8 @@
#include <LoggedProcess.h>
#include <minecraft/auth/AuthSession.h>
+#include "MinecraftServerTarget.h"
+
class LauncherPartLaunch: public LaunchStep
{
Q_OBJECT
@@ -39,6 +41,11 @@ public:
m_session = session;
}
+ void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
+ {
+ m_serverToJoin = std::move(serverToJoin);
+ }
+
private slots:
void on_state(LoggedProcess::State state);
@@ -47,5 +54,7 @@ private:
QString m_command;
AuthSessionPtr m_session;
QString m_launchScript;
+ MinecraftServerTargetPtr m_serverToJoin;
+
bool mayProceed = false;
};
diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.cpp b/api/logic/minecraft/launch/MinecraftServerTarget.cpp
new file mode 100644
index 00000000..569273b6
--- /dev/null
+++ b/api/logic/minecraft/launch/MinecraftServerTarget.cpp
@@ -0,0 +1,66 @@
+/* Copyright 2013-2021 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 "MinecraftServerTarget.h"
+
+#include <QStringList>
+
+MinecraftServerTarget MinecraftServerTarget::parse(const QString &fullAddress) {
+ QStringList split = fullAddress.split(":");
+
+ // The logic below replicates the exact logic minecraft uses for parsing server addresses.
+ // While the conversion is not lossless and eats errors, it ensures the same behavior
+ // within Minecraft and MultiMC when entering server addresses.
+ if (fullAddress.startsWith("["))
+ {
+ int bracket = fullAddress.indexOf("]");
+ if (bracket > 0)
+ {
+ QString ipv6 = fullAddress.mid(1, bracket - 1);
+ QString port = fullAddress.mid(bracket + 1).trimmed();
+
+ if (port.startsWith(":") && !ipv6.isEmpty())
+ {
+ port = port.mid(1);
+ split = QStringList({ ipv6, port });
+ }
+ else
+ {
+ split = QStringList({ipv6});
+ }
+ }
+ }
+
+ if (split.size() > 2)
+ {
+ split = QStringList({fullAddress});
+ }
+
+ QString realAddress = split[0];
+
+ quint16 realPort = 25565;
+ if (split.size() > 1)
+ {
+ bool ok;
+ realPort = split[1].toUInt(&ok);
+
+ if (!ok)
+ {
+ realPort = 25565;
+ }
+ }
+
+ return MinecraftServerTarget { realAddress, realPort };
+}
diff --git a/api/logic/minecraft/launch/MinecraftServerTarget.h b/api/logic/minecraft/launch/MinecraftServerTarget.h
new file mode 100644
index 00000000..3c5786f4
--- /dev/null
+++ b/api/logic/minecraft/launch/MinecraftServerTarget.h
@@ -0,0 +1,30 @@
+/* Copyright 2013-2021 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 <QString>
+#include <multimc_logic_export.h>
+
+struct MinecraftServerTarget {
+ QString address;
+ quint16 port;
+
+ static MULTIMC_LOGIC_EXPORT MinecraftServerTarget parse(const QString &fullAddress);
+};
+
+typedef std::shared_ptr<MinecraftServerTarget> MinecraftServerTargetPtr;
diff --git a/api/logic/minecraft/launch/ModMinecraftJar.cpp b/api/logic/minecraft/launch/ModMinecraftJar.cpp
index ba43a68d..93de9d59 100644
--- a/api/logic/minecraft/launch/ModMinecraftJar.cpp
+++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/logic/minecraft/launch/ModMinecraftJar.h
index bed5079f..081c6a91 100644
--- a/api/logic/minecraft/launch/ModMinecraftJar.h
+++ b/api/logic/minecraft/launch/ModMinecraftJar.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/PrintInstanceInfo.cpp b/api/logic/minecraft/launch/PrintInstanceInfo.cpp
index 4cc180fd..0b9611ad 100644
--- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp
+++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -101,6 +101,6 @@ void PrintInstanceInfo::executeTask()
#endif
logLines(log, MessageLevel::MultiMC);
- logLines(instance->verboseDescription(m_session), MessageLevel::MultiMC);
+ logLines(instance->verboseDescription(m_session, m_serverToJoin), MessageLevel::MultiMC);
emitSucceeded();
}
diff --git a/api/logic/minecraft/launch/PrintInstanceInfo.h b/api/logic/minecraft/launch/PrintInstanceInfo.h
index 1d081f3e..fdc30f31 100644
--- a/api/logic/minecraft/launch/PrintInstanceInfo.h
+++ b/api/logic/minecraft/launch/PrintInstanceInfo.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,13 +18,15 @@
#include <launch/LaunchStep.h>
#include <memory>
#include "minecraft/auth/AuthSession.h"
+#include "minecraft/launch/MinecraftServerTarget.h"
// FIXME: temporary wrapper for existing task.
class PrintInstanceInfo: public LaunchStep
{
Q_OBJECT
public:
- explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session) : LaunchStep(parent), m_session(session) {};
+ explicit PrintInstanceInfo(LaunchTask *parent, AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) :
+ LaunchStep(parent), m_session(session), m_serverToJoin(serverToJoin) {};
virtual ~PrintInstanceInfo(){};
virtual void executeTask();
@@ -34,5 +36,6 @@ public:
}
private:
AuthSessionPtr m_session;
+ MinecraftServerTargetPtr m_serverToJoin;
};
diff --git a/api/logic/minecraft/launch/ReconstructAssets.cpp b/api/logic/minecraft/launch/ReconstructAssets.cpp
index 3ac320bd..4d206665 100644
--- a/api/logic/minecraft/launch/ReconstructAssets.cpp
+++ b/api/logic/minecraft/launch/ReconstructAssets.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h
index 228fa083..58d7febd 100644
--- a/api/logic/minecraft/launch/ReconstructAssets.h
+++ b/api/logic/minecraft/launch/ReconstructAssets.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/ScanModFolders.cpp b/api/logic/minecraft/launch/ScanModFolders.cpp
index 485c3dc4..2a0e21b3 100644
--- a/api/logic/minecraft/launch/ScanModFolders.cpp
+++ b/api/logic/minecraft/launch/ScanModFolders.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,11 +27,16 @@ void ScanModFolders::executeTask()
auto loaders = m_inst->loaderModList();
connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
- loaders->update();
+ if(!loaders->update()) {
+ m_modsDone = true;
+ }
auto cores = m_inst->coreModList();
connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone);
- cores->update();
+ if(!cores->update()) {
+ m_coreModsDone = true;
+ }
+ checkDone();
}
void ScanModFolders::modsDone()
diff --git a/api/logic/minecraft/launch/ScanModFolders.h b/api/logic/minecraft/launch/ScanModFolders.h
index 360df98d..d5989170 100644
--- a/api/logic/minecraft/launch/ScanModFolders.h
+++ b/api/logic/minecraft/launch/ScanModFolders.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/VerifyJavaInstall.cpp b/api/logic/minecraft/launch/VerifyJavaInstall.cpp
new file mode 100644
index 00000000..657669af
--- /dev/null
+++ b/api/logic/minecraft/launch/VerifyJavaInstall.cpp
@@ -0,0 +1,34 @@
+#include "VerifyJavaInstall.h"
+
+#include <launch/LaunchTask.h>
+#include <minecraft/MinecraftInstance.h>
+#include <minecraft/PackProfile.h>
+#include <minecraft/VersionFilterData.h>
+
+void VerifyJavaInstall::executeTask() {
+ auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+
+ auto javaVersion = m_inst->getJavaVersion();
+ auto minecraftComponent = m_inst->getPackProfile()->getComponent("net.minecraft");
+
+ // Java 16 requirement
+ if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java16BeginsDate) {
+ if (javaVersion.major() < 16) {
+ emit logLine("Minecraft 21w19a and above require the use of Java 16",
+ MessageLevel::Fatal);
+ emitFailed(tr("Minecraft 21w19a and above require the use of Java 16"));
+ return;
+ }
+ }
+ // Java 8 requirement
+ else if (minecraftComponent->getReleaseDateTime() >= g_VersionFilterData.java8BeginsDate) {
+ if (javaVersion.major() < 8) {
+ emit logLine("Minecraft 17w13a and above require the use of Java 8",
+ MessageLevel::Fatal);
+ emitFailed(tr("Minecraft 17w13a and above require the use of Java 8"));
+ return;
+ }
+ }
+
+ emitSucceeded();
+}
diff --git a/api/logic/minecraft/launch/VerifyJavaInstall.h b/api/logic/minecraft/launch/VerifyJavaInstall.h
new file mode 100644
index 00000000..a553106d
--- /dev/null
+++ b/api/logic/minecraft/launch/VerifyJavaInstall.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <launch/LaunchStep.h>
+
+class VerifyJavaInstall : public LaunchStep {
+ Q_OBJECT
+
+public:
+ explicit VerifyJavaInstall(LaunchTask *parent) : LaunchStep(parent) {
+ };
+ ~VerifyJavaInstall() override = default;
+
+ void executeTask() override;
+ bool canAbort() const override {
+ return false;
+ }
+};
diff --git a/api/logic/minecraft/legacy/LegacyInstance.cpp b/api/logic/minecraft/legacy/LegacyInstance.cpp
index b9d68a9c..9f9bda5a 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.cpp
+++ b/api/logic/minecraft/legacy/LegacyInstance.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -225,7 +225,7 @@ QString LegacyInstance::getStatusbarDescription()
return tr("Instance from previous versions.");
}
-QStringList LegacyInstance::verboseDescription(AuthSessionPtr session)
+QStringList LegacyInstance::verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
QStringList out;
diff --git a/api/logic/minecraft/legacy/LegacyInstance.h b/api/logic/minecraft/legacy/LegacyInstance.h
index 7c0b94e8..325bac7a 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.h
+++ b/api/logic/minecraft/legacy/LegacyInstance.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -111,7 +111,8 @@ public:
{
return false;
}
- shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
+ shared_qobject_ptr<LaunchTask> createLaunchTask(
+ AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override
{
return nullptr;
}
@@ -125,7 +126,7 @@ public:
}
QString getStatusbarDescription() override;
- QStringList verboseDescription(AuthSessionPtr session) override;
+ QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override;
QProcessEnvironment createEnvironment() override
{
diff --git a/api/logic/minecraft/legacy/LegacyModList.cpp b/api/logic/minecraft/legacy/LegacyModList.cpp
index 23b837c1..7301eb8c 100644
--- a/api/logic/minecraft/legacy/LegacyModList.cpp
+++ b/api/logic/minecraft/legacy/LegacyModList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h
index 9a7bea50..8881d471 100644
--- a/api/logic/minecraft/legacy/LegacyModList.h
+++ b/api/logic/minecraft/legacy/LegacyModList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp
index 22ebd7d4..0d6972fb 100644
--- a/api/logic/minecraft/mod/LocalModParseTask.cpp
+++ b/api/logic/minecraft/mod/LocalModParseTask.cpp
@@ -6,6 +6,7 @@
#include <QJsonValue>
#include <quazip.h>
#include <quazipfile.h>
+#include <toml.h>
#include "settings/INIFile.h"
#include "FileSystem.h"
@@ -90,6 +91,124 @@ std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
return nullptr;
}
+// https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md
+std::shared_ptr<ModDetails> ReadMCModTOML(QByteArray contents)
+{
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+
+ char errbuf[200];
+ // top-level table
+ toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf));
+
+ if(!tomlData)
+ {
+ return nullptr;
+ }
+
+ // array defined by [[mods]]
+ toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods");
+ // we only really care about the first element, since multiple mods in one file is not supported by us at the moment
+ toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0);
+
+ // mandatory properties - always in [[mods]]
+ toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId");
+ if(modIdDatum.ok)
+ {
+ details->mod_id = modIdDatum.u.s;
+ // library says this is required for strings
+ free(modIdDatum.u.s);
+ }
+ toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version");
+ if(versionDatum.ok)
+ {
+ details->version = versionDatum.u.s;
+ free(versionDatum.u.s);
+ }
+ toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName");
+ if(displayNameDatum.ok)
+ {
+ details->name = displayNameDatum.u.s;
+ free(displayNameDatum.u.s);
+ }
+ toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description");
+ if(descriptionDatum.ok)
+ {
+ details->description = descriptionDatum.u.s;
+ free(descriptionDatum.u.s);
+ }
+
+ // optional properties - can be in the root table or [[mods]]
+ toml_datum_t authorsDatum = toml_string_in(tomlData, "authors");
+ QString authors = "";
+ if(authorsDatum.ok)
+ {
+ authors = authorsDatum.u.s;
+ free(authorsDatum.u.s);
+ }
+ else
+ {
+ authorsDatum = toml_string_in(tomlModsTable0, "authors");
+ if(authorsDatum.ok)
+ {
+ authors = authorsDatum.u.s;
+ free(authorsDatum.u.s);
+ }
+ }
+ if(!authors.isEmpty())
+ {
+ // author information is stored as a string now, not a list
+ details->authors.append(authors);
+ }
+ // is credits even used anywhere? including this for completion/parity with old data version
+ toml_datum_t creditsDatum = toml_string_in(tomlData, "credits");
+ QString credits = "";
+ if(creditsDatum.ok)
+ {
+ authors = creditsDatum.u.s;
+ free(creditsDatum.u.s);
+ }
+ else
+ {
+ creditsDatum = toml_string_in(tomlModsTable0, "credits");
+ if(creditsDatum.ok)
+ {
+ credits = creditsDatum.u.s;
+ free(creditsDatum.u.s);
+ }
+ }
+ details->credits = credits;
+ toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL");
+ QString homeurl = "";
+ if(homeurlDatum.ok)
+ {
+ homeurl = homeurlDatum.u.s;
+ free(homeurlDatum.u.s);
+ }
+ else
+ {
+ homeurlDatum = toml_string_in(tomlModsTable0, "displayURL");
+ if(homeurlDatum.ok)
+ {
+ homeurl = homeurlDatum.u.s;
+ free(homeurlDatum.u.s);
+ }
+ }
+ if(!homeurl.isEmpty())
+ {
+ // fix up url.
+ if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
+ {
+ homeurl.prepend("http://");
+ }
+ }
+ details->homeurl = homeurl;
+
+ // this seems to be recursive, so it should free everything
+ toml_free(tomlData);
+
+ return details;
+}
+
// https://fabricmc.net/wiki/documentation:fabric_mod_json
std::shared_ptr<ModDetails> ReadFabricModInfo(QByteArray contents)
{
@@ -198,7 +317,57 @@ void LocalModParseTask::processAsZip()
QuaZipFile file(&zip);
- if (zip.setCurrentFile("mcmod.info"))
+ if (zip.setCurrentFile("META-INF/mods.toml"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadMCModTOML(file.readAll());
+ file.close();
+
+ // to replace ${file.jarVersion} with the actual version, as needed
+ if (m_result->details && m_result->details->version == "${file.jarVersion}")
+ {
+ if (zip.setCurrentFile("META-INF/MANIFEST.MF"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ // quick and dirty line-by-line parser
+ auto manifestLines = file.readAll().split('\n');
+ QString manifestVersion = "";
+ for (auto &line : manifestLines)
+ {
+ if (QString(line).startsWith("Implementation-Version: "))
+ {
+ manifestVersion = QString(line).remove("Implementation-Version: ");
+ break;
+ }
+ }
+
+ // some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF
+ // also keep with forge's behavior of setting the version to "NONE" if none is found
+ if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "")
+ {
+ manifestVersion = "NONE";
+ }
+
+ m_result->details->version = manifestVersion;
+
+ file.close();
+ }
+ }
+
+ zip.close();
+ return;
+ }
+ else if (zip.setCurrentFile("mcmod.info"))
{
if (!file.open(QIODevice::ReadOnly))
{
diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp
index df8b406d..b6bff29b 100644
--- a/api/logic/minecraft/mod/Mod.cpp
+++ b/api/logic/minecraft/mod/Mod.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/Mod.h b/api/logic/minecraft/mod/Mod.h
index d787fb48..f77ffd41 100644
--- a/api/logic/minecraft/mod/Mod.h
+++ b/api/logic/minecraft/mod/Mod.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp
index 59ccaaba..031eebe5 100644
--- a/api/logic/minecraft/mod/ModFolderModel.cpp
+++ b/api/logic/minecraft/mod/ModFolderModel.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/ModFolderModel.h b/api/logic/minecraft/mod/ModFolderModel.h
index 03c584a9..b0a76121 100644
--- a/api/logic/minecraft/mod/ModFolderModel.h
+++ b/api/logic/minecraft/mod/ModFolderModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/ResourcePackFolderModel.cpp b/api/logic/minecraft/mod/ResourcePackFolderModel.cpp
new file mode 100644
index 00000000..f3d7f566
--- /dev/null
+++ b/api/logic/minecraft/mod/ResourcePackFolderModel.cpp
@@ -0,0 +1,23 @@
+#include "ResourcePackFolderModel.h"
+
+ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) {
+}
+
+QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const {
+ if (role == Qt::ToolTipRole) {
+ switch (section) {
+ case ActiveColumn:
+ return tr("Is the resource pack enabled?");
+ case NameColumn:
+ return tr("The name of the resource pack.");
+ case VersionColumn:
+ return tr("The version of the resource pack.");
+ case DateColumn:
+ return tr("The date and time this resource pack was last changed (or added).");
+ default:
+ return QVariant();
+ }
+ }
+
+ return ModFolderModel::headerData(section, orientation, role);
+}
diff --git a/api/logic/minecraft/mod/ResourcePackFolderModel.h b/api/logic/minecraft/mod/ResourcePackFolderModel.h
new file mode 100644
index 00000000..47eb4bb2
--- /dev/null
+++ b/api/logic/minecraft/mod/ResourcePackFolderModel.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "ModFolderModel.h"
+
+class MULTIMC_LOGIC_EXPORT ResourcePackFolderModel : public ModFolderModel
+{
+ Q_OBJECT
+
+public:
+ explicit ResourcePackFolderModel(const QString &dir);
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+};
diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.cpp b/api/logic/minecraft/mod/TexturePackFolderModel.cpp
new file mode 100644
index 00000000..d5956da1
--- /dev/null
+++ b/api/logic/minecraft/mod/TexturePackFolderModel.cpp
@@ -0,0 +1,23 @@
+#include "TexturePackFolderModel.h"
+
+TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) {
+}
+
+QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const {
+ if (role == Qt::ToolTipRole) {
+ switch (section) {
+ case ActiveColumn:
+ return tr("Is the texture pack enabled?");
+ case NameColumn:
+ return tr("The name of the texture pack.");
+ case VersionColumn:
+ return tr("The version of the texture pack.");
+ case DateColumn:
+ return tr("The date and time this texture pack was last changed (or added).");
+ default:
+ return QVariant();
+ }
+ }
+
+ return ModFolderModel::headerData(section, orientation, role);
+}
diff --git a/api/logic/minecraft/mod/TexturePackFolderModel.h b/api/logic/minecraft/mod/TexturePackFolderModel.h
new file mode 100644
index 00000000..d773b17b
--- /dev/null
+++ b/api/logic/minecraft/mod/TexturePackFolderModel.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "ModFolderModel.h"
+
+class MULTIMC_LOGIC_EXPORT TexturePackFolderModel : public ModFolderModel
+{
+ Q_OBJECT
+
+public:
+ explicit TexturePackFolderModel(const QString &dir);
+
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+};
diff --git a/api/logic/minecraft/services/SkinDelete.cpp b/api/logic/minecraft/services/SkinDelete.cpp
new file mode 100644
index 00000000..34977257
--- /dev/null
+++ b/api/logic/minecraft/services/SkinDelete.cpp
@@ -0,0 +1,42 @@
+#include "SkinDelete.h"
+#include <QNetworkRequest>
+#include <QHttpMultiPart>
+#include <Env.h>
+
+SkinDelete::SkinDelete(QObject *parent, AuthSessionPtr session)
+ : Task(parent), m_session(session)
+{
+}
+
+void SkinDelete::executeTask()
+{
+ QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins/active"));
+ request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit());
+ QNetworkReply *rep = ENV.qnam().deleteResource(request);
+ m_reply = std::shared_ptr<QNetworkReply>(rep);
+
+ setStatus(tr("Deleting skin"));
+ connect(rep, &QNetworkReply::uploadProgress, this, &Task::setProgress);
+ connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(downloadError(QNetworkReply::NetworkError)));
+ connect(rep, SIGNAL(finished()), this, SLOT(downloadFinished()));
+}
+
+void SkinDelete::downloadError(QNetworkReply::NetworkError error)
+{
+ // error happened during download.
+ qCritical() << "Network error: " << error;
+ emitFailed(m_reply->errorString());
+}
+
+void SkinDelete::downloadFinished()
+{
+ // if the download failed
+ if (m_reply->error() != QNetworkReply::NetworkError::NoError)
+ {
+ emitFailed(QString("Network error: %1").arg(m_reply->errorString()));
+ m_reply.reset();
+ return;
+ }
+ emitSucceeded();
+}
+
diff --git a/api/logic/minecraft/services/SkinDelete.h b/api/logic/minecraft/services/SkinDelete.h
new file mode 100644
index 00000000..705ce8ef
--- /dev/null
+++ b/api/logic/minecraft/services/SkinDelete.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <QFile>
+#include <QtNetwork/QtNetwork>
+#include <memory>
+#include <minecraft/auth/AuthSession.h>
+#include "tasks/Task.h"
+#include "multimc_logic_export.h"
+
+typedef std::shared_ptr<class SkinDelete> SkinDeletePtr;
+
+class MULTIMC_LOGIC_EXPORT SkinDelete : public Task
+{
+ Q_OBJECT
+public:
+ SkinDelete(QObject *parent, AuthSessionPtr session);
+ virtual ~SkinDelete() = default;
+
+private:
+ AuthSessionPtr m_session;
+ std::shared_ptr<QNetworkReply> m_reply;
+
+protected:
+ virtual void executeTask();
+
+public slots:
+ void downloadError(QNetworkReply::NetworkError);
+ void downloadFinished();
+};
+
diff --git a/api/logic/minecraft/SkinUpload.cpp b/api/logic/minecraft/services/SkinUpload.cpp
index 83bdf592..4e5a1698 100644
--- a/api/logic/minecraft/SkinUpload.cpp
+++ b/api/logic/minecraft/services/SkinUpload.cpp
@@ -3,15 +3,14 @@
#include <QHttpMultiPart>
#include <Env.h>
-QByteArray getModelString(SkinUpload::Model model) {
+QByteArray getVariant(SkinUpload::Model model) {
switch (model) {
- case SkinUpload::STEVE:
- return "";
- case SkinUpload::ALEX:
- return "slim";
default:
qDebug() << "Unknown skin type!";
- return "";
+ case SkinUpload::STEVE:
+ return "CLASSIC";
+ case SkinUpload::ALEX:
+ return "SLIM";
}
}
@@ -22,25 +21,23 @@ SkinUpload::SkinUpload(QObject *parent, AuthSessionPtr session, QByteArray skin,
void SkinUpload::executeTask()
{
- QNetworkRequest request(QUrl(QString("https://api.mojang.com/user/profile/%1/skin").arg(m_session->uuid)));
- request.setRawHeader("Authorization", QString("Bearer: %1").arg(m_session->access_token).toLocal8Bit());
-
+ QNetworkRequest request(QUrl("https://api.minecraftservices.com/minecraft/profile/skins"));
+ request.setRawHeader("Authorization", QString("Bearer %1").arg(m_session->access_token).toLocal8Bit());
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
- QHttpPart model;
- model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"model\""));
- model.setBody(getModelString(m_model));
-
QHttpPart skin;
skin.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("image/png"));
- skin.setHeader(QNetworkRequest::ContentDispositionHeader,
- QVariant("form-data; name=\"file\"; filename=\"skin.png\""));
+ skin.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"skin.png\""));
skin.setBody(m_skin);
- multiPart->append(model);
+ QHttpPart model;
+ model.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"variant\""));
+ model.setBody(getVariant(m_model));
+
multiPart->append(skin);
+ multiPart->append(model);
- QNetworkReply *rep = ENV.qnam().put(request, multiPart);
+ QNetworkReply *rep = ENV.qnam().post(request, multiPart);
m_reply = std::shared_ptr<QNetworkReply>(rep);
setStatus(tr("Uploading skin"));
diff --git a/api/logic/minecraft/SkinUpload.h b/api/logic/minecraft/services/SkinUpload.h
index c77abb03..c77abb03 100644
--- a/api/logic/minecraft/SkinUpload.h
+++ b/api/logic/minecraft/services/SkinUpload.h
diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp
index 85993e81..a05a7c2a 100644
--- a/api/logic/minecraft/update/FMLLibrariesTask.cpp
+++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp
@@ -64,7 +64,7 @@ void FMLLibrariesTask::executeTask()
for (auto &lib : fmlLibsToProcess)
{
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
- QString urlString = (lib.ours ? BuildConfig.FMLLIBS_OUR_BASE_URL : BuildConfig.FMLLIBS_FORGE_BASE_URL) + lib.filename;
+ QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry));
}
diff --git a/api/logic/modplatform/atlauncher/ATLPackIndex.cpp b/api/logic/modplatform/atlauncher/ATLPackIndex.cpp
new file mode 100644
index 00000000..35f50b18
--- /dev/null
+++ b/api/logic/modplatform/atlauncher/ATLPackIndex.cpp
@@ -0,0 +1,33 @@
+#include "ATLPackIndex.h"
+
+#include <QRegularExpression>
+
+#include "Json.h"
+
+static void loadIndexedVersion(ATLauncher::IndexedVersion & v, QJsonObject & obj)
+{
+ v.version = Json::requireString(obj, "version");
+ v.minecraft = Json::requireString(obj, "minecraft");
+}
+
+void ATLauncher::loadIndexedPack(ATLauncher::IndexedPack & m, QJsonObject & obj)
+{
+ m.id = Json::requireInteger(obj, "id");
+ m.position = Json::requireInteger(obj, "position");
+ m.name = Json::requireString(obj, "name");
+ m.type = Json::requireString(obj, "type") == "private" ?
+ ATLauncher::PackType::Private :
+ ATLauncher::PackType::Public;
+ auto versionsArr = Json::requireArray(obj, "versions");
+ for (const auto versionRaw : versionsArr)
+ {
+ auto versionObj = Json::requireObject(versionRaw);
+ ATLauncher::IndexedVersion version;
+ loadIndexedVersion(version, versionObj);
+ m.versions.append(version);
+ }
+ m.system = Json::ensureBoolean(obj, QString("system"), false);
+ m.description = Json::ensureString(obj, "description", "");
+
+ m.safeName = Json::requireString(obj, "name").replace(QRegularExpression("[^A-Za-z0-9]"), "");
+}
diff --git a/api/logic/modplatform/atlauncher/ATLPackIndex.h b/api/logic/modplatform/atlauncher/ATLPackIndex.h
new file mode 100644
index 00000000..5e2e6487
--- /dev/null
+++ b/api/logic/modplatform/atlauncher/ATLPackIndex.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include "ATLPackManifest.h"
+
+#include <QString>
+#include <QVector>
+#include <QMetaType>
+
+#include "multimc_logic_export.h"
+
+namespace ATLauncher
+{
+
+struct IndexedVersion
+{
+ QString version;
+ QString minecraft;
+};
+
+struct IndexedPack
+{
+ int id;
+ int position;
+ QString name;
+ PackType type;
+ QVector<IndexedVersion> versions;
+ bool system;
+ QString description;
+
+ QString safeName;
+};
+
+MULTIMC_LOGIC_EXPORT void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
+}
+
+Q_DECLARE_METATYPE(ATLauncher::IndexedPack)
diff --git a/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp b/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp
new file mode 100644
index 00000000..55087a27
--- /dev/null
+++ b/api/logic/modplatform/atlauncher/ATLPackInstallTask.cpp
@@ -0,0 +1,764 @@
+#include <Env.h>
+#include <quazip.h>
+#include <QtConcurrent/QtConcurrent>
+#include <MMCZip.h>
+#include <minecraft/OneSixVersionFormat.h>
+#include <Version.h>
+#include <net/ChecksumValidator.h>
+#include "ATLPackInstallTask.h"
+
+#include "BuildConfig.h"
+#include "FileSystem.h"
+#include "Json.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "settings/INISettingsObject.h"
+#include "meta/Index.h"
+#include "meta/Version.h"
+#include "meta/VersionList.h"
+
+namespace ATLauncher {
+
+PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString pack, QString version)
+{
+ m_support = support;
+ m_pack = pack;
+ m_version_name = version;
+}
+
+bool PackInstallTask::abort()
+{
+ if(abortable)
+ {
+ return jobPtr->abort();
+ }
+ return false;
+}
+
+void PackInstallTask::executeTask()
+{
+ qDebug() << "PackInstallTask::executeTask: " << QThread::currentThreadId();
+ auto *netJob = new NetJob("ATLauncher::VersionFetch");
+ auto searchUrl = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.json")
+ .arg(m_pack).arg(m_version_name);
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+
+ QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
+ QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
+}
+
+void PackInstallTask::onDownloadSucceeded()
+{
+ qDebug() << "PackInstallTask::onDownloadSucceeded: " << QThread::currentThreadId();
+ jobPtr.reset();
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ auto obj = doc.object();
+
+ ATLauncher::PackVersion version;
+ try
+ {
+ ATLauncher::loadVersion(version, obj);
+ }
+ catch (const JSONValidationError &e)
+ {
+ emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
+ return;
+ }
+ m_version = version;
+
+ auto vlist = ENV.metadataIndex()->get("net.minecraft");
+ if(!vlist)
+ {
+ emitFailed(tr("Failed to get local metadata index for %1").arg("net.minecraft"));
+ return;
+ }
+
+ auto ver = vlist->getVersion(m_version.minecraft);
+ if (!ver) {
+ emitFailed(tr("Failed to get local metadata index for '%1' v%2").arg("net.minecraft").arg(m_version.minecraft));
+ return;
+ }
+ ver->load(Net::Mode::Online);
+ minecraftVersion = ver;
+
+ if(m_version.noConfigs) {
+ downloadMods();
+ }
+ else {
+ installConfigs();
+ }
+}
+
+void PackInstallTask::onDownloadFailed(QString reason)
+{
+ qDebug() << "PackInstallTask::onDownloadFailed: " << QThread::currentThreadId();
+ jobPtr.reset();
+ emitFailed(reason);
+}
+
+QString PackInstallTask::getDirForModType(ModType type, QString raw)
+{
+ switch (type) {
+ // Mod types that can either be ignored at this stage, or ignored
+ // completely.
+ case ModType::Root:
+ case ModType::Extract:
+ case ModType::Decomp:
+ case ModType::TexturePackExtract:
+ case ModType::ResourcePackExtract:
+ case ModType::MCPC:
+ return Q_NULLPTR;
+ case ModType::Forge:
+ // Forge detection happens later on, if it cannot be detected it will
+ // install a jarmod component.
+ case ModType::Jar:
+ return "jarmods";
+ case ModType::Mods:
+ return "mods";
+ case ModType::Flan:
+ return "Flan";
+ case ModType::Dependency:
+ return FS::PathCombine("mods", m_version.minecraft);
+ case ModType::Ic2Lib:
+ return FS::PathCombine("mods", "ic2");
+ case ModType::DenLib:
+ return FS::PathCombine("mods", "denlib");
+ case ModType::Coremods:
+ return "coremods";
+ case ModType::Plugins:
+ return "plugins";
+ case ModType::TexturePack:
+ return "texturepacks";
+ case ModType::ResourcePack:
+ return "resourcepacks";
+ case ModType::ShaderPack:
+ return "shaderpacks";
+ case ModType::Millenaire:
+ qWarning() << "Unsupported mod type: " + raw;
+ return Q_NULLPTR;
+ case ModType::Unknown:
+ emitFailed(tr("Unknown mod type: %1").arg(raw));
+ return Q_NULLPTR;
+ }
+
+ return Q_NULLPTR;
+}
+
+QString PackInstallTask::getVersionForLoader(QString uid)
+{
+ if(m_version.loader.recommended || m_version.loader.latest || m_version.loader.choose) {
+ auto vlist = ENV.metadataIndex()->get(uid);
+ if(!vlist)
+ {
+ emitFailed(tr("Failed to get local metadata index for %1").arg(uid));
+ return Q_NULLPTR;
+ }
+
+ if(!vlist->isLoaded()) {
+ vlist->load(Net::Mode::Online);
+ }
+
+ if(m_version.loader.recommended || m_version.loader.latest) {
+ for (int i = 0; i < vlist->versions().size(); i++) {
+ auto version = vlist->versions().at(i);
+ auto reqs = version->requires();
+
+ // filter by minecraft version, if the loader depends on a certain version.
+ // not all mod loaders depend on a given Minecraft version, so we won't do this
+ // filtering for those loaders.
+ if (m_version.loader.type != "fabric") {
+ auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) {
+ return req.uid == "net.minecraft";
+ });
+ if (iter == reqs.end()) continue;
+ if (iter->equalsVersion != m_version.minecraft) continue;
+ }
+
+ if (m_version.loader.recommended) {
+ // first recommended build we find, we use.
+ if (!version->isRecommended()) continue;
+ }
+
+ return version->descriptor();
+ }
+
+ emitFailed(tr("Failed to find version for %1 loader").arg(m_version.loader.type));
+ return Q_NULLPTR;
+ }
+ else if(m_version.loader.choose) {
+ // Fabric Loader doesn't depend on a given Minecraft version.
+ if (m_version.loader.type == "fabric") {
+ return m_support->chooseVersion(vlist, Q_NULLPTR);
+ }
+
+ return m_support->chooseVersion(vlist, m_version.minecraft);
+ }
+ }
+
+ if (m_version.loader.version == Q_NULLPTR || m_version.loader.version.isEmpty()) {
+ emitFailed(tr("No loader version set for modpack!"));
+ return Q_NULLPTR;
+ }
+
+ return m_version.loader.version;
+}
+
+QString PackInstallTask::detectLibrary(VersionLibrary library)
+{
+ // Try to detect what the library is
+ if (!library.server.isEmpty() && library.server.split("/").length() >= 3) {
+ auto lastSlash = library.server.lastIndexOf("/");
+ auto locationAndVersion = library.server.mid(0, lastSlash);
+ auto fileName = library.server.mid(lastSlash + 1);
+
+ lastSlash = locationAndVersion.lastIndexOf("/");
+ auto location = locationAndVersion.mid(0, lastSlash);
+ auto version = locationAndVersion.mid(lastSlash + 1);
+
+ lastSlash = location.lastIndexOf("/");
+ auto group = location.mid(0, lastSlash).replace("/", ".");
+ auto artefact = location.mid(lastSlash + 1);
+
+ return group + ":" + artefact + ":" + version;
+ }
+
+ if(library.file.contains("-")) {
+ auto lastSlash = library.file.lastIndexOf("-");
+ auto name = library.file.mid(0, lastSlash);
+ auto version = library.file.mid(lastSlash + 1).remove(".jar");
+
+ if(name == QString("guava")) {
+ return "com.google.guava:guava:" + version;
+ }
+ else if(name == QString("commons-lang3")) {
+ return "org.apache.commons:commons-lang3:" + version;
+ }
+ }
+
+ return "org.multimc.atlauncher:" + library.md5 + ":1";
+}
+
+bool PackInstallTask::createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
+{
+ if(m_version.libraries.isEmpty()) {
+ return true;
+ }
+
+ QList<GradleSpecifier> exempt;
+ for(const auto & componentUid : componentsToInstall.keys()) {
+ auto componentVersion = componentsToInstall.value(componentUid);
+
+ for(const auto & library : componentVersion->data()->libraries) {
+ GradleSpecifier lib(library->rawName());
+ exempt.append(lib);
+ }
+ }
+
+ {
+ for(const auto & library : minecraftVersion->data()->libraries) {
+ GradleSpecifier lib(library->rawName());
+ exempt.append(lib);
+ }
+ }
+
+ auto uuid = QUuid::createUuid();
+ auto id = uuid.toString().remove('{').remove('}');
+ auto target_id = "org.multimc.atlauncher." + id;
+
+ auto patchDir = FS::PathCombine(instanceRoot, "patches");
+ if(!FS::ensureFolderPathExists(patchDir))
+ {
+ return false;
+ }
+ auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
+
+ auto f = std::make_shared<VersionFile>();
+ f->name = m_pack + " " + m_version_name + " (libraries)";
+
+ for(const auto & lib : m_version.libraries) {
+ auto libName = detectLibrary(lib);
+ GradleSpecifier libSpecifier(libName);
+
+ bool libExempt = false;
+ for(const auto & existingLib : exempt) {
+ if(libSpecifier.matchName(existingLib)) {
+ // If the pack specifies a newer version of the lib, use that!
+ libExempt = Version(libSpecifier.version()) >= Version(existingLib.version());
+ }
+ }
+ if(libExempt) continue;
+
+ auto library = std::make_shared<Library>();
+ library->setRawName(libName);
+
+ switch(lib.download) {
+ case DownloadType::Server:
+ library->setAbsoluteUrl(BuildConfig.ATL_DOWNLOAD_SERVER_URL + lib.url);
+ break;
+ case DownloadType::Direct:
+ library->setAbsoluteUrl(lib.url);
+ break;
+ case DownloadType::Browser:
+ case DownloadType::Unknown:
+ emitFailed(tr("Unknown or unsupported download type: %1").arg(lib.download_raw));
+ return false;
+ }
+
+ f->libraries.append(library);
+ }
+
+ if(f->libraries.isEmpty()) {
+ return true;
+ }
+
+ 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();
+
+ profile->appendComponent(new Component(profile.get(), target_id, f));
+ return true;
+}
+
+bool PackInstallTask::createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile)
+{
+ if(m_version.mainClass == QString() && m_version.extraArguments == QString()) {
+ return true;
+ }
+
+ auto uuid = QUuid::createUuid();
+ auto id = uuid.toString().remove('{').remove('}');
+ auto target_id = "org.multimc.atlauncher." + id;
+
+ auto patchDir = FS::PathCombine(instanceRoot, "patches");
+ if(!FS::ensureFolderPathExists(patchDir))
+ {
+ return false;
+ }
+ auto patchFileName = FS::PathCombine(patchDir, target_id + ".json");
+
+ QStringList mainClasses;
+ QStringList tweakers;
+ for(const auto & componentUid : componentsToInstall.keys()) {
+ auto componentVersion = componentsToInstall.value(componentUid);
+
+ if(componentVersion->data()->mainClass != QString("")) {
+ mainClasses.append(componentVersion->data()->mainClass);
+ }
+ tweakers.append(componentVersion->data()->addTweakers);
+ }
+
+ auto f = std::make_shared<VersionFile>();
+ f->name = m_pack + " " + m_version_name;
+ if(m_version.mainClass != QString() && !mainClasses.contains(m_version.mainClass)) {
+ f->mainClass = m_version.mainClass;
+ }
+
+ // Parse out tweakers
+ auto args = m_version.extraArguments.split(" ");
+ QString previous;
+ for(auto arg : args) {
+ if(arg.startsWith("--tweakClass=") || previous == "--tweakClass") {
+ auto tweakClass = arg.remove("--tweakClass=");
+ if(tweakers.contains(tweakClass)) continue;
+
+ f->addTweakers.append(tweakClass);
+ }
+ previous = arg;
+ }
+
+ if(f->mainClass == QString() && f->addTweakers.isEmpty()) {
+ return true;
+ }
+
+ 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();
+
+ profile->appendComponent(new Component(profile.get(), target_id, f));
+ return true;
+}
+
+void PackInstallTask::installConfigs()
+{
+ qDebug() << "PackInstallTask::installConfigs: " << QThread::currentThreadId();
+ setStatus(tr("Downloading configs..."));
+ jobPtr.reset(new NetJob(tr("Config download")));
+
+ auto path = QString("Configs/%1/%2.zip").arg(m_pack).arg(m_version_name);
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "packs/%1/versions/%2/Configs.zip")
+ .arg(m_pack).arg(m_version_name);
+ auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", path);
+ entry->setStale(true);
+
+ auto dl = Net::Download::makeCached(url, entry);
+ if (!m_version.configs.sha1.isEmpty()) {
+ auto rawSha1 = QByteArray::fromHex(m_version.configs.sha1.toLatin1());
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
+ }
+ jobPtr->addNetAction(dl);
+ archivePath = entry->getFullPath();
+
+ connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
+ {
+ abortable = false;
+ jobPtr.reset();
+ extractConfigs();
+ });
+ connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
+ {
+ abortable = false;
+ jobPtr.reset();
+ emitFailed(reason);
+ });
+ connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
+ {
+ abortable = true;
+ setProgress(current, total);
+ });
+
+ jobPtr->start();
+}
+
+void PackInstallTask::extractConfigs()
+{
+ qDebug() << "PackInstallTask::extractConfigs: " << QThread::currentThreadId();
+ setStatus(tr("Extracting configs..."));
+
+ QDir extractDir(m_stagingPath);
+
+ QuaZip packZip(archivePath);
+ if(!packZip.open(QuaZip::mdUnzip))
+ {
+ emitFailed(tr("Failed to open pack configs %1!").arg(archivePath));
+ return;
+ }
+
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/minecraft");
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, [&]()
+ {
+ downloadMods();
+ });
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
+ {
+ emitAborted();
+ });
+ m_extractFutureWatcher.setFuture(m_extractFuture);
+}
+
+void PackInstallTask::downloadMods()
+{
+ qDebug() << "PackInstallTask::installMods: " << QThread::currentThreadId();
+
+ QVector<ATLauncher::VersionMod> optionalMods;
+ for (const auto& mod : m_version.mods) {
+ if (mod.optional) {
+ optionalMods.push_back(mod);
+ }
+ }
+
+ // Select optional mods, if pack contains any
+ QVector<QString> selectedMods;
+ if (!optionalMods.isEmpty()) {
+ setStatus(tr("Selecting optional mods..."));
+ selectedMods = m_support->chooseOptionalMods(optionalMods);
+ }
+
+ setStatus(tr("Downloading mods..."));
+
+ jarmods.clear();
+ jobPtr.reset(new NetJob(tr("Mod download")));
+ for(const auto& mod : m_version.mods) {
+ // skip non-client mods
+ if(!mod.client) continue;
+
+ // skip optional mods that were not selected
+ if(mod.optional && !selectedMods.contains(mod.name)) continue;
+
+ QString url;
+ switch(mod.download) {
+ case DownloadType::Server:
+ url = BuildConfig.ATL_DOWNLOAD_SERVER_URL + mod.url;
+ break;
+ case DownloadType::Browser:
+ emitFailed(tr("Unsupported download type: %1").arg(mod.download_raw));
+ return;
+ case DownloadType::Direct:
+ url = mod.url;
+ break;
+ case DownloadType::Unknown:
+ emitFailed(tr("Unknown download type: %1").arg(mod.download_raw));
+ return;
+ }
+
+ QFileInfo fileName(mod.file);
+ auto cacheName = fileName.completeBaseName() + "-" + mod.md5 + "." + fileName.suffix();
+
+ if (mod.type == ModType::Extract || mod.type == ModType::TexturePackExtract || mod.type == ModType::ResourcePackExtract) {
+ auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName);
+ entry->setStale(true);
+ modsToExtract.insert(entry->getFullPath(), mod);
+
+ auto dl = Net::Download::makeCached(url, entry);
+ if (!mod.md5.isEmpty()) {
+ auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
+ }
+ jobPtr->addNetAction(dl);
+ }
+ else if(mod.type == ModType::Decomp) {
+ auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName);
+ entry->setStale(true);
+ modsToDecomp.insert(entry->getFullPath(), mod);
+
+ auto dl = Net::Download::makeCached(url, entry);
+ if (!mod.md5.isEmpty()) {
+ auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
+ }
+ jobPtr->addNetAction(dl);
+ }
+ else {
+ auto relpath = getDirForModType(mod.type, mod.type_raw);
+ if(relpath == Q_NULLPTR) continue;
+
+ auto entry = ENV.metacache()->resolveEntry("ATLauncherPacks", cacheName);
+ entry->setStale(true);
+
+ auto dl = Net::Download::makeCached(url, entry);
+ if (!mod.md5.isEmpty()) {
+ auto rawMd5 = QByteArray::fromHex(mod.md5.toLatin1());
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Md5, rawMd5));
+ }
+ jobPtr->addNetAction(dl);
+
+ auto path = FS::PathCombine(m_stagingPath, "minecraft", relpath, mod.file);
+ qDebug() << "Will download" << url << "to" << path;
+ modsToCopy[entry->getFullPath()] = path;
+
+ if(mod.type == ModType::Forge) {
+ auto vlist = ENV.metadataIndex()->get("net.minecraftforge");
+ if(vlist)
+ {
+ auto ver = vlist->getVersion(mod.version);
+ if(ver) {
+ ver->load(Net::Mode::Online);
+ componentsToInstall.insert("net.minecraftforge", ver);
+ continue;
+ }
+ }
+
+ qDebug() << "Jarmod: " + path;
+ jarmods.push_back(path);
+ }
+
+ if(mod.type == ModType::Jar) {
+ qDebug() << "Jarmod: " + path;
+ jarmods.push_back(path);
+ }
+ }
+ }
+
+ connect(jobPtr.get(), &NetJob::succeeded, this, &PackInstallTask::onModsDownloaded);
+ connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
+ {
+ abortable = false;
+ jobPtr.reset();
+ emitFailed(reason);
+ });
+ connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
+ {
+ abortable = true;
+ setProgress(current, total);
+ });
+
+ jobPtr->start();
+}
+
+void PackInstallTask::onModsDownloaded() {
+ abortable = false;
+
+ qDebug() << "PackInstallTask::onModsDownloaded: " << QThread::currentThreadId();
+ jobPtr.reset();
+
+ if(!modsToExtract.empty() || !modsToDecomp.empty() || !modsToCopy.empty()) {
+ m_modExtractFuture = QtConcurrent::run(QThreadPool::globalInstance(), this, &PackInstallTask::extractMods, modsToExtract, modsToDecomp, modsToCopy);
+ connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onModsExtracted);
+ connect(&m_modExtractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, [&]()
+ {
+ emitAborted();
+ });
+ m_modExtractFutureWatcher.setFuture(m_modExtractFuture);
+ }
+ else {
+ install();
+ }
+}
+
+void PackInstallTask::onModsExtracted() {
+ qDebug() << "PackInstallTask::onModsExtracted: " << QThread::currentThreadId();
+ if(m_modExtractFuture.result()) {
+ install();
+ }
+ else {
+ emitFailed(tr("Failed to extract mods..."));
+ }
+}
+
+bool PackInstallTask::extractMods(
+ const QMap<QString, VersionMod> &toExtract,
+ const QMap<QString, VersionMod> &toDecomp,
+ const QMap<QString, QString> &toCopy
+) {
+ qDebug() << "PackInstallTask::extractMods: " << QThread::currentThreadId();
+
+ setStatus(tr("Extracting mods..."));
+ for (auto iter = toExtract.begin(); iter != toExtract.end(); iter++) {
+ auto &modPath = iter.key();
+ auto &mod = iter.value();
+
+ QString extractToDir;
+ if(mod.type == ModType::Extract) {
+ extractToDir = getDirForModType(mod.extractTo, mod.extractTo_raw);
+ }
+ else if(mod.type == ModType::TexturePackExtract) {
+ extractToDir = FS::PathCombine("texturepacks", "extracted");
+ }
+ else if(mod.type == ModType::ResourcePackExtract) {
+ extractToDir = FS::PathCombine("resourcepacks", "extracted");
+ }
+
+ QDir extractDir(m_stagingPath);
+ auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir);
+
+ QString folderToExtract = "";
+ if(mod.type == ModType::Extract) {
+ folderToExtract = mod.extractFolder;
+ folderToExtract.remove(QRegExp("^/"));
+ }
+
+ qDebug() << "Extracting " + mod.file + " to " + extractToDir;
+ if(!MMCZip::extractDir(modPath, folderToExtract, extractToPath)) {
+ // assume error
+ return false;
+ }
+ }
+
+ for (auto iter = toDecomp.begin(); iter != toDecomp.end(); iter++) {
+ auto &modPath = iter.key();
+ auto &mod = iter.value();
+ auto extractToDir = getDirForModType(mod.decompType, mod.decompType_raw);
+
+ QDir extractDir(m_stagingPath);
+ auto extractToPath = FS::PathCombine(extractDir.absolutePath(), "minecraft", extractToDir, mod.decompFile);
+
+ qDebug() << "Extracting " + mod.decompFile + " to " + extractToDir;
+ if(!MMCZip::extractFile(modPath, mod.decompFile, extractToPath)) {
+ qWarning() << "Failed to extract" << mod.decompFile;
+ return false;
+ }
+ }
+
+ for (auto iter = toCopy.begin(); iter != toCopy.end(); iter++) {
+ auto &from = iter.key();
+ auto &to = iter.value();
+ FS::copy fileCopyOperation(from, to);
+ if(!fileCopyOperation()) {
+ qWarning() << "Failed to copy" << from << "to" << to;
+ return false;
+ }
+ }
+ return true;
+}
+
+void PackInstallTask::install()
+{
+ qDebug() << "PackInstallTask::install: " << QThread::currentThreadId();
+ setStatus(tr("Installing modpack"));
+
+ auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
+ auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
+ instanceSettings->suspendSave();
+ instanceSettings->registerSetting("InstanceType", "Legacy");
+ instanceSettings->set("InstanceType", "OneSix");
+
+ MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
+ auto components = instance.getPackProfile();
+ components->buildingFromScratch();
+
+ // Use a component to add libraries BEFORE Minecraft
+ if(!createLibrariesComponent(instance.instanceRoot(), components)) {
+ emitFailed(tr("Failed to create libraries component"));
+ return;
+ }
+
+ // Minecraft
+ components->setComponentVersion("net.minecraft", m_version.minecraft, true);
+
+ // Loader
+ if(m_version.loader.type == QString("forge"))
+ {
+ auto version = getVersionForLoader("net.minecraftforge");
+ if(version == Q_NULLPTR) return;
+
+ components->setComponentVersion("net.minecraftforge", version, true);
+ }
+ else if(m_version.loader.type == QString("fabric"))
+ {
+ auto version = getVersionForLoader("net.fabricmc.fabric-loader");
+ if(version == Q_NULLPTR) return;
+
+ components->setComponentVersion("net.fabricmc.fabric-loader", version, true);
+ }
+ else if(m_version.loader.type != QString())
+ {
+ emitFailed(tr("Unknown loader type: ") + m_version.loader.type);
+ return;
+ }
+
+ for(const auto & componentUid : componentsToInstall.keys()) {
+ auto version = componentsToInstall.value(componentUid);
+ components->setComponentVersion(componentUid, version->version());
+ }
+
+ components->installJarMods(jarmods);
+
+ // Use a component to fill in the rest of the data
+ // todo: use more detection
+ if(!createPackComponent(instance.instanceRoot(), components)) {
+ emitFailed(tr("Failed to create pack component"));
+ return;
+ }
+
+ components->saveNow();
+
+ instance.setName(m_instName);
+ instance.setIconKey(m_instIcon);
+ instanceSettings->resumeSave();
+
+ jarmods.clear();
+ emitSucceeded();
+}
+
+}
diff --git a/api/logic/modplatform/atlauncher/ATLPackInstallTask.h b/api/logic/modplatform/atlauncher/ATLPackInstallTask.h
new file mode 100644
index 00000000..15fd9b32
--- /dev/null
+++ b/api/logic/modplatform/atlauncher/ATLPackInstallTask.h
@@ -0,0 +1,102 @@
+#pragma once
+
+#include <meta/VersionList.h>
+#include "ATLPackManifest.h"
+
+#include "InstanceTask.h"
+#include "multimc_logic_export.h"
+#include "net/NetJob.h"
+#include "settings/INISettingsObject.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "meta/Version.h"
+
+#include <nonstd/optional>
+
+namespace ATLauncher {
+
+class MULTIMC_LOGIC_EXPORT UserInteractionSupport {
+
+public:
+ /**
+ * Requests a user interaction to select which optional mods should be installed.
+ */
+ virtual QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) = 0;
+
+ /**
+ * Requests a user interaction to select a component version from a given version list
+ * and constrained to a given Minecraft version.
+ */
+ virtual QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) = 0;
+
+};
+
+class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
+{
+Q_OBJECT
+
+public:
+ explicit PackInstallTask(UserInteractionSupport *support, QString pack, QString version);
+ virtual ~PackInstallTask(){}
+
+ bool canAbort() const override { return true; }
+ bool abort() override;
+
+protected:
+ virtual void executeTask() override;
+
+private slots:
+ void onDownloadSucceeded();
+ void onDownloadFailed(QString reason);
+
+ void onModsDownloaded();
+ void onModsExtracted();
+
+private:
+ QString getDirForModType(ModType type, QString raw);
+ QString getVersionForLoader(QString uid);
+ QString detectLibrary(VersionLibrary library);
+
+ bool createLibrariesComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
+ bool createPackComponent(QString instanceRoot, std::shared_ptr<PackProfile> profile);
+
+ void installConfigs();
+ void extractConfigs();
+ void downloadMods();
+ bool extractMods(
+ const QMap<QString, VersionMod> &toExtract,
+ const QMap<QString, VersionMod> &toDecomp,
+ const QMap<QString, QString> &toCopy
+ );
+ void install();
+
+private:
+ UserInteractionSupport *m_support;
+
+ bool abortable = false;
+
+ NetJobPtr jobPtr;
+ QByteArray response;
+
+ QString m_pack;
+ QString m_version_name;
+ PackVersion m_version;
+
+ QMap<QString, VersionMod> modsToExtract;
+ QMap<QString, VersionMod> modsToDecomp;
+ QMap<QString, QString> modsToCopy;
+
+ QString archivePath;
+ QStringList jarmods;
+ Meta::VersionPtr minecraftVersion;
+ QMap<QString, Meta::VersionPtr> componentsToInstall;
+
+ QFuture<nonstd::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+
+ QFuture<bool> m_modExtractFuture;
+ QFutureWatcher<bool> m_modExtractFutureWatcher;
+
+};
+
+}
diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.cpp b/api/logic/modplatform/atlauncher/ATLPackManifest.cpp
new file mode 100644
index 00000000..e25d8346
--- /dev/null
+++ b/api/logic/modplatform/atlauncher/ATLPackManifest.cpp
@@ -0,0 +1,218 @@
+#include "ATLPackManifest.h"
+
+#include "Json.h"
+
+static ATLauncher::DownloadType parseDownloadType(QString rawType) {
+ if(rawType == QString("server")) {
+ return ATLauncher::DownloadType::Server;
+ }
+ else if(rawType == QString("browser")) {
+ return ATLauncher::DownloadType::Browser;
+ }
+ else if(rawType == QString("direct")) {
+ return ATLauncher::DownloadType::Direct;
+ }
+
+ return ATLauncher::DownloadType::Unknown;
+}
+
+static ATLauncher::ModType parseModType(QString rawType) {
+ // See https://wiki.atlauncher.com/mod_types
+ if(rawType == QString("root")) {
+ return ATLauncher::ModType::Root;
+ }
+ else if(rawType == QString("forge")) {
+ return ATLauncher::ModType::Forge;
+ }
+ else if(rawType == QString("jar")) {
+ return ATLauncher::ModType::Jar;
+ }
+ else if(rawType == QString("mods")) {
+ return ATLauncher::ModType::Mods;
+ }
+ else if(rawType == QString("flan")) {
+ return ATLauncher::ModType::Flan;
+ }
+ else if(rawType == QString("dependency") || rawType == QString("depandency")) {
+ return ATLauncher::ModType::Dependency;
+ }
+ else if(rawType == QString("ic2lib")) {
+ return ATLauncher::ModType::Ic2Lib;
+ }
+ else if(rawType == QString("denlib")) {
+ return ATLauncher::ModType::DenLib;
+ }
+ else if(rawType == QString("coremods")) {
+ return ATLauncher::ModType::Coremods;
+ }
+ else if(rawType == QString("mcpc")) {
+ return ATLauncher::ModType::MCPC;
+ }
+ else if(rawType == QString("plugins")) {
+ return ATLauncher::ModType::Plugins;
+ }
+ else if(rawType == QString("extract")) {
+ return ATLauncher::ModType::Extract;
+ }
+ else if(rawType == QString("decomp")) {
+ return ATLauncher::ModType::Decomp;
+ }
+ else if(rawType == QString("texturepack")) {
+ return ATLauncher::ModType::TexturePack;
+ }
+ else if(rawType == QString("resourcepack")) {
+ return ATLauncher::ModType::ResourcePack;
+ }
+ else if(rawType == QString("shaderpack")) {
+ return ATLauncher::ModType::ShaderPack;
+ }
+ else if(rawType == QString("texturepackextract")) {
+ return ATLauncher::ModType::TexturePackExtract;
+ }
+ else if(rawType == QString("resourcepackextract")) {
+ return ATLauncher::ModType::ResourcePackExtract;
+ }
+ else if(rawType == QString("millenaire")) {
+ return ATLauncher::ModType::Millenaire;
+ }
+
+ return ATLauncher::ModType::Unknown;
+}
+
+static void loadVersionLoader(ATLauncher::VersionLoader & p, QJsonObject & obj) {
+ p.type = Json::requireString(obj, "type");
+ p.choose = Json::ensureBoolean(obj, QString("choose"), false);
+
+ auto metadata = Json::requireObject(obj, "metadata");
+ p.latest = Json::ensureBoolean(metadata, QString("latest"), false);
+ p.recommended = Json::ensureBoolean(metadata, QString("recommended"), false);
+
+ // Minecraft Forge
+ if (p.type == "forge") {
+ p.version = Json::ensureString(metadata, "version", "");
+ }
+
+ // Fabric Loader
+ if (p.type == "fabric") {
+ p.version = Json::ensureString(metadata, "loader", "");
+ }
+}
+
+static void loadVersionLibrary(ATLauncher::VersionLibrary & p, QJsonObject & obj) {
+ p.url = Json::requireString(obj, "url");
+ p.file = Json::requireString(obj, "file");
+ p.md5 = Json::requireString(obj, "md5");
+
+ p.download_raw = Json::requireString(obj, "download");
+ p.download = parseDownloadType(p.download_raw);
+
+ p.server = Json::ensureString(obj, "server", "");
+}
+
+static void loadVersionConfigs(ATLauncher::VersionConfigs & p, QJsonObject & obj) {
+ p.filesize = Json::requireInteger(obj, "filesize");
+ p.sha1 = Json::requireString(obj, "sha1");
+}
+
+static void loadVersionMod(ATLauncher::VersionMod & p, QJsonObject & obj) {
+ p.name = Json::requireString(obj, "name");
+ p.version = Json::requireString(obj, "version");
+ p.url = Json::requireString(obj, "url");
+ p.file = Json::requireString(obj, "file");
+ p.md5 = Json::ensureString(obj, "md5", "");
+
+ p.download_raw = Json::requireString(obj, "download");
+ p.download = parseDownloadType(p.download_raw);
+
+ p.type_raw = Json::requireString(obj, "type");
+ p.type = parseModType(p.type_raw);
+
+ // This contributes to the Minecraft Forge detection, where we rely on mod.type being "Forge"
+ // when the mod represents Forge. As there is little difference between "Jar" and "Forge, some
+ // packs regretfully use "Jar". This will correct the type to "Forge" in these cases (as best
+ // it can).
+ if(p.name == QString("Minecraft Forge") && p.type == ATLauncher::ModType::Jar) {
+ p.type_raw = "forge";
+ p.type = ATLauncher::ModType::Forge;
+ }
+
+ if(obj.contains("extractTo")) {
+ p.extractTo_raw = Json::requireString(obj, "extractTo");
+ p.extractTo = parseModType(p.extractTo_raw);
+ p.extractFolder = Json::ensureString(obj, "extractFolder", "").replace("%s%", "/");
+ }
+
+ if(obj.contains("decompType")) {
+ p.decompType_raw = Json::requireString(obj, "decompType");
+ p.decompType = parseModType(p.decompType_raw);
+ p.decompFile = Json::requireString(obj, "decompFile");
+ }
+
+ p.description = Json::ensureString(obj, QString("description"), "");
+ p.optional = Json::ensureBoolean(obj, QString("optional"), false);
+ p.recommended = Json::ensureBoolean(obj, QString("recommended"), false);
+ p.selected = Json::ensureBoolean(obj, QString("selected"), false);
+ p.hidden = Json::ensureBoolean(obj, QString("hidden"), false);
+ p.library = Json::ensureBoolean(obj, QString("library"), false);
+ p.group = Json::ensureString(obj, QString("group"), "");
+ if(obj.contains("depends")) {
+ auto dependsArr = Json::requireArray(obj, "depends");
+ for (const auto depends : dependsArr) {
+ p.depends.append(Json::requireString(depends));
+ }
+ }
+
+ p.client = Json::ensureBoolean(obj, QString("client"), false);
+
+ // computed
+ p.effectively_hidden = p.hidden || p.library;
+}
+
+void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj)
+{
+ v.version = Json::requireString(obj, "version");
+ v.minecraft = Json::requireString(obj, "minecraft");
+ v.noConfigs = Json::ensureBoolean(obj, QString("noConfigs"), false);
+
+ if(obj.contains("mainClass")) {
+ auto main = Json::requireObject(obj, "mainClass");
+ v.mainClass = Json::ensureString(main, "mainClass", "");
+ }
+
+ if(obj.contains("extraArguments")) {
+ auto arguments = Json::requireObject(obj, "extraArguments");
+ v.extraArguments = Json::ensureString(arguments, "arguments", "");
+ }
+
+ if(obj.contains("loader")) {
+ auto loader = Json::requireObject(obj, "loader");
+ loadVersionLoader(v.loader, loader);
+ }
+
+ if(obj.contains("libraries")) {
+ auto libraries = Json::requireArray(obj, "libraries");
+ for (const auto libraryRaw : libraries)
+ {
+ auto libraryObj = Json::requireObject(libraryRaw);
+ ATLauncher::VersionLibrary target;
+ loadVersionLibrary(target, libraryObj);
+ v.libraries.append(target);
+ }
+ }
+
+ if(obj.contains("mods")) {
+ auto mods = Json::requireArray(obj, "mods");
+ for (const auto modRaw : mods)
+ {
+ auto modObj = Json::requireObject(modRaw);
+ ATLauncher::VersionMod mod;
+ loadVersionMod(mod, modObj);
+ v.mods.append(mod);
+ }
+ }
+
+ if(obj.contains("configs")) {
+ auto configsObj = Json::requireObject(obj, "configs");
+ loadVersionConfigs(v.configs, configsObj);
+ }
+}
diff --git a/api/logic/modplatform/atlauncher/ATLPackManifest.h b/api/logic/modplatform/atlauncher/ATLPackManifest.h
new file mode 100644
index 00000000..17821e4c
--- /dev/null
+++ b/api/logic/modplatform/atlauncher/ATLPackManifest.h
@@ -0,0 +1,126 @@
+#pragma once
+
+#include <QString>
+#include <QVector>
+#include <QJsonObject>
+#include <multimc_logic_export.h>
+
+namespace ATLauncher
+{
+
+enum class PackType
+{
+ Public,
+ Private
+};
+
+enum class ModType
+{
+ Root,
+ Forge,
+ Jar,
+ Mods,
+ Flan,
+ Dependency,
+ Ic2Lib,
+ DenLib,
+ Coremods,
+ MCPC,
+ Plugins,
+ Extract,
+ Decomp,
+ TexturePack,
+ ResourcePack,
+ ShaderPack,
+ TexturePackExtract,
+ ResourcePackExtract,
+ Millenaire,
+ Unknown
+};
+
+enum class DownloadType
+{
+ Server,
+ Browser,
+ Direct,
+ Unknown
+};
+
+struct VersionLoader
+{
+ QString type;
+ bool latest;
+ bool recommended;
+ bool choose;
+
+ QString version;
+};
+
+struct VersionLibrary
+{
+ QString url;
+ QString file;
+ QString server;
+ QString md5;
+ DownloadType download;
+ QString download_raw;
+};
+
+struct VersionMod
+{
+ QString name;
+ QString version;
+ QString url;
+ QString file;
+ QString md5;
+ DownloadType download;
+ QString download_raw;
+ ModType type;
+ QString type_raw;
+
+ ModType extractTo;
+ QString extractTo_raw;
+ QString extractFolder;
+
+ ModType decompType;
+ QString decompType_raw;
+ QString decompFile;
+
+ QString description;
+ bool optional;
+ bool recommended;
+ bool selected;
+ bool hidden;
+ bool library;
+ QString group;
+ QVector<QString> depends;
+
+ bool client;
+
+ // computed
+ bool effectively_hidden;
+};
+
+struct VersionConfigs
+{
+ int filesize;
+ QString sha1;
+};
+
+struct PackVersion
+{
+ QString version;
+ QString minecraft;
+ bool noConfigs;
+ QString mainClass;
+ QString extraArguments;
+
+ VersionLoader loader;
+ QVector<VersionLibrary> libraries;
+ QVector<VersionMod> mods;
+ VersionConfigs configs;
+};
+
+MULTIMC_LOGIC_EXPORT void loadVersion(PackVersion & v, QJsonObject & obj);
+
+}
diff --git a/api/logic/modplatform/flame/FlamePackIndex.cpp b/api/logic/modplatform/flame/FlamePackIndex.cpp
new file mode 100644
index 00000000..3d8ea22a
--- /dev/null
+++ b/api/logic/modplatform/flame/FlamePackIndex.cpp
@@ -0,0 +1,92 @@
+#include "FlamePackIndex.h"
+
+#include "Json.h"
+
+void Flame::loadIndexedPack(Flame::IndexedPack & pack, QJsonObject & obj)
+{
+ pack.addonId = Json::requireInteger(obj, "id");
+ pack.name = Json::requireString(obj, "name");
+ pack.websiteUrl = Json::ensureString(obj, "websiteUrl", "");
+ pack.description = Json::ensureString(obj, "summary", "");
+
+ bool thumbnailFound = false;
+ auto attachments = Json::requireArray(obj, "attachments");
+ for(auto attachmentRaw: attachments) {
+ auto attachmentObj = Json::requireObject(attachmentRaw);
+ bool isDefault = attachmentObj.value("isDefault").toBool(false);
+ if(isDefault) {
+ thumbnailFound = true;
+ pack.logoName = Json::requireString(attachmentObj, "title");
+ pack.logoUrl = Json::requireString(attachmentObj, "thumbnailUrl");
+ break;
+ }
+ }
+
+ if(!thumbnailFound) {
+ throw JSONValidationError(QString("Pack without an icon, skipping: %1").arg(pack.name));
+ }
+
+ auto authors = Json::requireArray(obj, "authors");
+ for(auto authorIter: authors) {
+ auto author = Json::requireObject(authorIter);
+ Flame::ModpackAuthor packAuthor;
+ packAuthor.name = Json::requireString(author, "name");
+ packAuthor.url = Json::requireString(author, "url");
+ pack.authors.append(packAuthor);
+ }
+ int defaultFileId = Json::requireInteger(obj, "defaultFileId");
+
+ bool found = false;
+ // check if there are some files before adding the pack
+ auto files = Json::requireArray(obj, "latestFiles");
+ for(auto fileIter: files) {
+ auto file = Json::requireObject(fileIter);
+ int id = Json::requireInteger(file, "id");
+
+ // NOTE: for now, ignore everything that's not the default...
+ if(id != defaultFileId) {
+ continue;
+ }
+
+ auto versionArray = Json::requireArray(file, "gameVersion");
+ if(versionArray.size() < 1) {
+ continue;
+ }
+
+ found = true;
+ break;
+ }
+ if(!found) {
+ throw JSONValidationError(QString("Pack with no good file, skipping: %1").arg(pack.name));
+ }
+}
+
+void Flame::loadIndexedPackVersions(Flame::IndexedPack & pack, QJsonArray & arr)
+{
+ QVector<Flame::IndexedVersion> unsortedVersions;
+ for(auto versionIter: arr) {
+ auto version = Json::requireObject(versionIter);
+ Flame::IndexedVersion file;
+
+ file.addonId = pack.addonId;
+ file.fileId = Json::requireInteger(version, "id");
+ auto versionArray = Json::requireArray(version, "gameVersion");
+ if(versionArray.size() < 1) {
+ continue;
+ }
+
+ // pick the latest version supported
+ file.mcVersion = versionArray[0].toString();
+ file.version = Json::requireString(version, "displayName");
+ file.downloadUrl = Json::requireString(version, "downloadUrl");
+ unsortedVersions.append(file);
+ }
+
+ auto orderSortPredicate = [](const IndexedVersion & a, const IndexedVersion & b) -> bool
+ {
+ return a.fileId > b.fileId;
+ };
+ std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate);
+ pack.versions = unsortedVersions;
+ pack.versionsLoaded = true;
+}
diff --git a/api/logic/modplatform/flame/FlamePackIndex.h b/api/logic/modplatform/flame/FlamePackIndex.h
new file mode 100644
index 00000000..cdeb2c13
--- /dev/null
+++ b/api/logic/modplatform/flame/FlamePackIndex.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include <QList>
+#include <QMetaType>
+#include <QString>
+#include <QVector>
+
+#include "multimc_logic_export.h"
+
+namespace Flame {
+
+struct ModpackAuthor {
+ QString name;
+ QString url;
+};
+
+struct IndexedVersion {
+ int addonId;
+ int fileId;
+ QString version;
+ QString mcVersion;
+ QString downloadUrl;
+};
+
+struct IndexedPack
+{
+ int addonId;
+ QString name;
+ QString description;
+ QList<ModpackAuthor> authors;
+ QString logoName;
+ QString logoUrl;
+ QString websiteUrl;
+
+ bool versionsLoaded = false;
+ QVector<IndexedVersion> versions;
+};
+
+MULTIMC_LOGIC_EXPORT void loadIndexedPack(IndexedPack & m, QJsonObject & obj);
+MULTIMC_LOGIC_EXPORT void loadIndexedPackVersions(IndexedPack & m, QJsonArray & arr);
+}
+
+Q_DECLARE_METATYPE(Flame::IndexedPack)
diff --git a/api/logic/modplatform/flame/PackManifest.cpp b/api/logic/modplatform/flame/PackManifest.cpp
index 1db0a161..b928fd16 100644
--- a/api/logic/modplatform/flame/PackManifest.cpp
+++ b/api/logic/modplatform/flame/PackManifest.cpp
@@ -21,7 +21,7 @@ static void loadMinecraftV1(Flame::Minecraft & m, QJsonObject & minecraft)
// intended use is likely hardcoded in the 'Flame' client, the manifest says nothing
m.libraries = Json::ensureString(minecraft, QString("libraries"), QString());
auto arr = Json::ensureArray(minecraft, "modLoaders", QJsonArray());
- for (const auto & item : arr)
+ for (QJsonValueRef item : arr)
{
auto obj = Json::requireObject(item);
Flame::Modloader loader;
@@ -38,7 +38,7 @@ static void loadManifestV1(Flame::Manifest & m, QJsonObject & manifest)
m.version = Json::ensureString(manifest, QString("version"), QString());
m.author = Json::ensureString(manifest, QString("author"), "Anonymous Coward");
auto arr = Json::ensureArray(manifest, "files", QJsonArray());
- for (const auto & item : arr)
+ for (QJsonValueRef item : arr)
{
auto obj = Json::requireObject(item);
Flame::File file;
diff --git a/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp b/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp
index 9bf6c76a..c77f3250 100644
--- a/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp
+++ b/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -121,6 +121,7 @@ void PackInstallTask::install()
QString instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
+ instanceSettings->suspendSave();
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
diff --git a/api/logic/modplatform/legacy_ftb/PackInstallTask.h b/api/logic/modplatform/legacy_ftb/PackInstallTask.h
index 1eec1880..f3515781 100644
--- a/api/logic/modplatform/legacy_ftb/PackInstallTask.h
+++ b/api/logic/modplatform/legacy_ftb/PackInstallTask.h
@@ -8,6 +8,8 @@
#include "meta/VersionList.h"
#include "PackHelpers.h"
+#include <nonstd/optional>
+
namespace LegacyFTB {
class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
@@ -18,6 +20,7 @@ public:
explicit PackInstallTask(Modpack pack, QString version);
virtual ~PackInstallTask(){}
+ bool canAbort() const override { return true; }
bool abort() override;
protected:
@@ -40,8 +43,8 @@ private slots:
private: /* data */
bool abortable = false;
std::unique_ptr<QuaZip> m_packZip;
- QFuture<QStringList> m_extractFuture;
- QFutureWatcher<QStringList> m_extractFutureWatcher;
+ QFuture<nonstd::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
NetJobPtr netJobContainer;
QString archivePath;
diff --git a/api/logic/modplatform/modpacksch/FTBPackInstallTask.cpp b/api/logic/modplatform/modpacksch/FTBPackInstallTask.cpp
new file mode 100644
index 00000000..f22373bc
--- /dev/null
+++ b/api/logic/modplatform/modpacksch/FTBPackInstallTask.cpp
@@ -0,0 +1,209 @@
+#include "FTBPackInstallTask.h"
+
+#include "BuildConfig.h"
+#include "Env.h"
+#include "FileSystem.h"
+#include "Json.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "net/ChecksumValidator.h"
+#include "settings/INISettingsObject.h"
+
+namespace ModpacksCH {
+
+PackInstallTask::PackInstallTask(Modpack pack, QString version)
+{
+ m_pack = pack;
+ m_version_name = version;
+}
+
+bool PackInstallTask::abort()
+{
+ if(abortable)
+ {
+ return jobPtr->abort();
+ }
+ return false;
+}
+
+void PackInstallTask::executeTask()
+{
+ // Find pack version
+ bool found = false;
+ VersionInfo version;
+
+ for(auto vInfo : m_pack.versions) {
+ if (vInfo.name == m_version_name) {
+ found = true;
+ version = vInfo;
+ break;
+ }
+ }
+
+ if(!found) {
+ emitFailed(tr("Failed to find pack version %1").arg(m_version_name));
+ return;
+ }
+
+ auto *netJob = new NetJob("ModpacksCH::VersionFetch");
+ auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2")
+ .arg(m_pack.id).arg(version.id);
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+
+ QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
+ QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
+}
+
+void PackInstallTask::onDownloadSucceeded()
+{
+ jobPtr.reset();
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ auto obj = doc.object();
+
+ ModpacksCH::Version version;
+ try
+ {
+ ModpacksCH::loadVersion(version, obj);
+ }
+ catch (const JSONValidationError &e)
+ {
+ emitFailed(tr("Could not understand pack manifest:\n") + e.cause());
+ return;
+ }
+ m_version = version;
+
+ downloadPack();
+}
+
+void PackInstallTask::onDownloadFailed(QString reason)
+{
+ jobPtr.reset();
+ emitFailed(reason);
+}
+
+void PackInstallTask::downloadPack()
+{
+ setStatus(tr("Downloading mods..."));
+
+ jobPtr.reset(new NetJob(tr("Mod download")));
+ for(auto file : m_version.files) {
+ if(file.serverOnly) continue;
+
+ QFileInfo fileName(file.name);
+ auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix();
+
+ auto entry = ENV.metacache()->resolveEntry("ModpacksCHPacks", cacheName);
+ entry->setStale(true);
+
+ auto relpath = FS::PathCombine("minecraft", file.path, file.name);
+ auto path = FS::PathCombine(m_stagingPath, relpath);
+
+ qDebug() << "Will download" << file.url << "to" << path;
+ filesToCopy[entry->getFullPath()] = path;
+
+ auto dl = Net::Download::makeCached(file.url, entry);
+ if (!file.sha1.isEmpty()) {
+ auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1());
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
+ }
+ jobPtr->addNetAction(dl);
+ }
+
+ connect(jobPtr.get(), &NetJob::succeeded, this, [&]()
+ {
+ abortable = false;
+ jobPtr.reset();
+ install();
+ });
+ connect(jobPtr.get(), &NetJob::failed, [&](QString reason)
+ {
+ abortable = false;
+ jobPtr.reset();
+ emitFailed(reason);
+ });
+ connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total)
+ {
+ abortable = true;
+ setProgress(current, total);
+ });
+
+ jobPtr->start();
+}
+
+void PackInstallTask::install()
+{
+ setStatus(tr("Copying modpack files"));
+
+ for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) {
+ auto &from = iter.key();
+ auto &to = iter.value();
+ FS::copy fileCopyOperation(from, to);
+ if(!fileCopyOperation()) {
+ qWarning() << "Failed to copy" << from << "to" << to;
+ emitFailed(tr("Failed to copy files"));
+ return;
+ }
+ }
+
+ setStatus(tr("Installing modpack"));
+
+ auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg");
+ auto instanceSettings = std::make_shared<INISettingsObject>(instanceConfigPath);
+ instanceSettings->suspendSave();
+ instanceSettings->registerSetting("InstanceType", "Legacy");
+ instanceSettings->set("InstanceType", "OneSix");
+
+ MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
+ auto components = instance.getPackProfile();
+ components->buildingFromScratch();
+
+ for(auto target : m_version.targets) {
+ if(target.type == "game" && target.name == "minecraft") {
+ components->setComponentVersion("net.minecraft", target.version, true);
+ break;
+ }
+ }
+
+ for(auto target : m_version.targets) {
+ if(target.type != "modloader") continue;
+
+ if(target.name == "forge") {
+ components->setComponentVersion("net.minecraftforge", target.version, true);
+ }
+ else if(target.name == "fabric") {
+ components->setComponentVersion("net.fabricmc.fabric-loader", target.version, true);
+ }
+ }
+
+ // install any jar mods
+ QDir jarModsDir(FS::PathCombine(m_stagingPath, "minecraft", "jarmods"));
+ if (jarModsDir.exists()) {
+ QStringList jarMods;
+
+ for (const auto& info : jarModsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) {
+ jarMods.push_back(info.absoluteFilePath());
+ }
+
+ components->installJarMods(jarMods);
+ }
+
+ components->saveNow();
+
+ instance.setName(m_instName);
+ instance.setIconKey(m_instIcon);
+ instanceSettings->resumeSave();
+
+ emitSucceeded();
+}
+
+}
diff --git a/api/logic/modplatform/modpacksch/FTBPackInstallTask.h b/api/logic/modplatform/modpacksch/FTBPackInstallTask.h
new file mode 100644
index 00000000..55db3d3c
--- /dev/null
+++ b/api/logic/modplatform/modpacksch/FTBPackInstallTask.h
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "FTBPackManifest.h"
+
+#include "InstanceTask.h"
+#include "multimc_logic_export.h"
+#include "net/NetJob.h"
+
+namespace ModpacksCH {
+
+class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
+{
+ Q_OBJECT
+
+public:
+ explicit PackInstallTask(Modpack pack, QString version);
+ virtual ~PackInstallTask(){}
+
+ bool canAbort() const override { return true; }
+ bool abort() override;
+
+protected:
+ virtual void executeTask() override;
+
+private slots:
+ void onDownloadSucceeded();
+ void onDownloadFailed(QString reason);
+
+private:
+ void downloadPack();
+ void install();
+
+private:
+ bool abortable = false;
+
+ NetJobPtr jobPtr;
+ QByteArray response;
+
+ Modpack m_pack;
+ QString m_version_name;
+ Version m_version;
+
+ QMap<QString, QString> filesToCopy;
+
+};
+
+}
diff --git a/api/logic/modplatform/modpacksch/FTBPackManifest.cpp b/api/logic/modplatform/modpacksch/FTBPackManifest.cpp
new file mode 100644
index 00000000..fd99d332
--- /dev/null
+++ b/api/logic/modplatform/modpacksch/FTBPackManifest.cpp
@@ -0,0 +1,156 @@
+#include "FTBPackManifest.h"
+
+#include "Json.h"
+
+static void loadSpecs(ModpacksCH::Specs & s, QJsonObject & obj)
+{
+ s.id = Json::requireInteger(obj, "id");
+ s.minimum = Json::requireInteger(obj, "minimum");
+ s.recommended = Json::requireInteger(obj, "recommended");
+}
+
+static void loadTag(ModpacksCH::Tag & t, QJsonObject & obj)
+{
+ t.id = Json::requireInteger(obj, "id");
+ t.name = Json::requireString(obj, "name");
+}
+
+static void loadArt(ModpacksCH::Art & a, QJsonObject & obj)
+{
+ a.id = Json::requireInteger(obj, "id");
+ a.url = Json::requireString(obj, "url");
+ a.type = Json::requireString(obj, "type");
+ a.width = Json::requireInteger(obj, "width");
+ a.height = Json::requireInteger(obj, "height");
+ a.compressed = Json::requireBoolean(obj, "compressed");
+ a.sha1 = Json::requireString(obj, "sha1");
+ a.size = Json::requireInteger(obj, "size");
+ a.updated = Json::requireInteger(obj, "updated");
+}
+
+static void loadAuthor(ModpacksCH::Author & a, QJsonObject & obj)
+{
+ a.id = Json::requireInteger(obj, "id");
+ a.name = Json::requireString(obj, "name");
+ a.type = Json::requireString(obj, "type");
+ a.website = Json::requireString(obj, "website");
+ a.updated = Json::requireInteger(obj, "updated");
+}
+
+static void loadVersionInfo(ModpacksCH::VersionInfo & v, QJsonObject & obj)
+{
+ v.id = Json::requireInteger(obj, "id");
+ v.name = Json::requireString(obj, "name");
+ v.type = Json::requireString(obj, "type");
+ v.updated = Json::requireInteger(obj, "updated");
+ auto specs = Json::requireObject(obj, "specs");
+ loadSpecs(v.specs, specs);
+}
+
+void ModpacksCH::loadModpack(ModpacksCH::Modpack & m, QJsonObject & obj)
+{
+ m.id = Json::requireInteger(obj, "id");
+ m.name = Json::requireString(obj, "name");
+ m.synopsis = Json::requireString(obj, "synopsis");
+ m.description = Json::requireString(obj, "description");
+ m.type = Json::requireString(obj, "type");
+ m.featured = Json::requireBoolean(obj, "featured");
+ m.installs = Json::requireInteger(obj, "installs");
+ m.plays = Json::requireInteger(obj, "plays");
+ m.updated = Json::requireInteger(obj, "updated");
+ m.refreshed = Json::requireInteger(obj, "refreshed");
+ auto artArr = Json::requireArray(obj, "art");
+ for (QJsonValueRef artRaw : artArr)
+ {
+ auto artObj = Json::requireObject(artRaw);
+ ModpacksCH::Art art;
+ loadArt(art, artObj);
+ m.art.append(art);
+ }
+ auto authorArr = Json::requireArray(obj, "authors");
+ for (QJsonValueRef authorRaw : authorArr)
+ {
+ auto authorObj = Json::requireObject(authorRaw);
+ ModpacksCH::Author author;
+ loadAuthor(author, authorObj);
+ m.authors.append(author);
+ }
+ auto versionArr = Json::requireArray(obj, "versions");
+ for (QJsonValueRef versionRaw : versionArr)
+ {
+ auto versionObj = Json::requireObject(versionRaw);
+ ModpacksCH::VersionInfo version;
+ loadVersionInfo(version, versionObj);
+ m.versions.append(version);
+ }
+ auto tagArr = Json::requireArray(obj, "tags");
+ for (QJsonValueRef tagRaw : tagArr)
+ {
+ auto tagObj = Json::requireObject(tagRaw);
+ ModpacksCH::Tag tag;
+ loadTag(tag, tagObj);
+ m.tags.append(tag);
+ }
+ m.updated = Json::requireInteger(obj, "updated");
+}
+
+static void loadVersionTarget(ModpacksCH::VersionTarget & a, QJsonObject & obj)
+{
+ a.id = Json::requireInteger(obj, "id");
+ a.name = Json::requireString(obj, "name");
+ a.type = Json::requireString(obj, "type");
+ a.version = Json::requireString(obj, "version");
+ a.updated = Json::requireInteger(obj, "updated");
+}
+
+static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj)
+{
+ a.id = Json::requireInteger(obj, "id");
+ a.type = Json::requireString(obj, "type");
+ a.path = Json::requireString(obj, "path");
+ a.name = Json::requireString(obj, "name");
+ a.version = Json::requireString(obj, "version");
+ a.url = Json::requireString(obj, "url");
+ a.sha1 = Json::requireString(obj, "sha1");
+ a.size = Json::requireInteger(obj, "size");
+ a.clientOnly = Json::requireBoolean(obj, "clientonly");
+ a.serverOnly = Json::requireBoolean(obj, "serveronly");
+ a.optional = Json::requireBoolean(obj, "optional");
+ a.updated = Json::requireInteger(obj, "updated");
+}
+
+void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj)
+{
+ m.id = Json::requireInteger(obj, "id");
+ m.parent = Json::requireInteger(obj, "parent");
+ m.name = Json::requireString(obj, "name");
+ m.type = Json::requireString(obj, "type");
+ m.installs = Json::requireInteger(obj, "installs");
+ m.plays = Json::requireInteger(obj, "plays");
+ m.updated = Json::requireInteger(obj, "updated");
+ m.refreshed = Json::requireInteger(obj, "refreshed");
+ auto specs = Json::requireObject(obj, "specs");
+ loadSpecs(m.specs, specs);
+ auto targetArr = Json::requireArray(obj, "targets");
+ for (QJsonValueRef targetRaw : targetArr)
+ {
+ auto versionObj = Json::requireObject(targetRaw);
+ ModpacksCH::VersionTarget target;
+ loadVersionTarget(target, versionObj);
+ m.targets.append(target);
+ }
+ auto fileArr = Json::requireArray(obj, "files");
+ for (QJsonValueRef fileRaw : fileArr)
+ {
+ auto fileObj = Json::requireObject(fileRaw);
+ ModpacksCH::VersionFile file;
+ loadVersionFile(file, fileObj);
+ m.files.append(file);
+ }
+}
+
+//static void loadVersionChangelog(ModpacksCH::VersionChangelog & m, QJsonObject & obj)
+//{
+// m.content = Json::requireString(obj, "content");
+// m.updated = Json::requireInteger(obj, "updated");
+//}
diff --git a/api/logic/modplatform/modpacksch/FTBPackManifest.h b/api/logic/modplatform/modpacksch/FTBPackManifest.h
new file mode 100644
index 00000000..518fffbf
--- /dev/null
+++ b/api/logic/modplatform/modpacksch/FTBPackManifest.h
@@ -0,0 +1,127 @@
+#pragma once
+
+#include <QString>
+#include <QVector>
+#include <QUrl>
+#include <QJsonObject>
+#include <QMetaType>
+
+#include "multimc_logic_export.h"
+
+namespace ModpacksCH
+{
+
+struct Specs
+{
+ int id;
+ int minimum;
+ int recommended;
+};
+
+struct Tag
+{
+ int id;
+ QString name;
+};
+
+struct Art
+{
+ int id;
+ QString url;
+ QString type;
+ int width;
+ int height;
+ bool compressed;
+ QString sha1;
+ int size;
+ int64_t updated;
+};
+
+struct Author
+{
+ int id;
+ QString name;
+ QString type;
+ QString website;
+ int64_t updated;
+};
+
+struct VersionInfo
+{
+ int id;
+ QString name;
+ QString type;
+ int64_t updated;
+ Specs specs;
+};
+
+struct Modpack
+{
+ int id;
+ QString name;
+ QString synopsis;
+ QString description;
+ QString type;
+ bool featured;
+ int installs;
+ int plays;
+ int64_t updated;
+ int64_t refreshed;
+ QVector<Art> art;
+ QVector<Author> authors;
+ QVector<VersionInfo> versions;
+ QVector<Tag> tags;
+};
+
+struct VersionTarget
+{
+ int id;
+ QString type;
+ QString name;
+ QString version;
+ int64_t updated;
+};
+
+struct VersionFile
+{
+ int id;
+ QString type;
+ QString path;
+ QString name;
+ QString version;
+ QString url;
+ QString sha1;
+ int size;
+ bool clientOnly;
+ bool serverOnly;
+ bool optional;
+ int64_t updated;
+};
+
+struct Version
+{
+ int id;
+ int parent;
+ QString name;
+ QString type;
+ int installs;
+ int plays;
+ int64_t updated;
+ int64_t refreshed;
+ Specs specs;
+ QVector<VersionTarget> targets;
+ QVector<VersionFile> files;
+};
+
+struct VersionChangelog
+{
+ QString content;
+ int64_t updated;
+};
+
+MULTIMC_LOGIC_EXPORT void loadModpack(Modpack & m, QJsonObject & obj);
+
+MULTIMC_LOGIC_EXPORT void loadVersion(Version & m, QJsonObject & obj);
+}
+
+Q_DECLARE_METATYPE(ModpacksCH::Modpack)
diff --git a/api/logic/modplatform/technic/SingleZipPackInstallTask.cpp b/api/logic/modplatform/technic/SingleZipPackInstallTask.cpp
new file mode 100644
index 00000000..dbce8e53
--- /dev/null
+++ b/api/logic/modplatform/technic/SingleZipPackInstallTask.cpp
@@ -0,0 +1,141 @@
+/* Copyright 2013-2021 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 "SingleZipPackInstallTask.h"
+
+#include "Env.h"
+#include "MMCZip.h"
+#include "TechnicPackProcessor.h"
+
+#include <QtConcurrent>
+#include <FileSystem.h>
+
+Technic::SingleZipPackInstallTask::SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
+{
+ m_sourceUrl = sourceUrl;
+ m_minecraftVersion = minecraftVersion;
+}
+
+bool Technic::SingleZipPackInstallTask::abort() {
+ if(m_abortable)
+ {
+ return m_filesNetJob->abort();
+ }
+ return false;
+}
+
+void Technic::SingleZipPackInstallTask::executeTask()
+{
+ setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString()));
+
+ const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path();
+ auto entry = ENV.metacache()->resolveEntry("general", path);
+ entry->setStale(true);
+ m_filesNetJob.reset(new NetJob(tr("Modpack download")));
+ m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry));
+ m_archivePath = entry->getFullPath();
+ auto job = m_filesNetJob.get();
+ connect(job, &NetJob::succeeded, this, &Technic::SingleZipPackInstallTask::downloadSucceeded);
+ connect(job, &NetJob::progress, this, &Technic::SingleZipPackInstallTask::downloadProgressChanged);
+ connect(job, &NetJob::failed, this, &Technic::SingleZipPackInstallTask::downloadFailed);
+ m_filesNetJob->start();
+}
+
+void Technic::SingleZipPackInstallTask::downloadSucceeded()
+{
+ m_abortable = false;
+
+ setStatus(tr("Extracting modpack"));
+ QDir extractDir(FS::PathCombine(m_stagingPath, ".minecraft"));
+ qDebug() << "Attempting to create instance from" << m_archivePath;
+
+ // 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;
+ }
+ m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractSubDir, m_packZip.get(), QString(""), extractDir.absolutePath());
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SingleZipPackInstallTask::extractFinished);
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SingleZipPackInstallTask::extractAborted);
+ m_extractFutureWatcher.setFuture(m_extractFuture);
+ m_filesNetJob.reset();
+}
+
+void Technic::SingleZipPackInstallTask::downloadFailed(QString reason)
+{
+ m_abortable = false;
+ emitFailed(reason);
+ m_filesNetJob.reset();
+}
+
+void Technic::SingleZipPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
+{
+ m_abortable = true;
+ setProgress(current / 2, total);
+}
+
+void Technic::SingleZipPackInstallTask::extractFinished()
+{
+ m_packZip.reset();
+ if (!m_extractFuture.result())
+ {
+ emitFailed(tr("Failed to extract modpack"));
+ return;
+ }
+ QDir extractDir(m_stagingPath);
+
+ qDebug() << "Fixing permissions for extracted pack files...";
+ QDirIterator it(extractDir, QDirIterator::Subdirectories);
+ while (it.hasNext())
+ {
+ auto filepath = it.next();
+ QFileInfo file(filepath);
+ auto permissions = QFile::permissions(filepath);
+ auto origPermissions = permissions;
+ if (file.isDir())
+ {
+ // Folder +rwx for current user
+ permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
+ }
+ else
+ {
+ // File +rw for current user
+ permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
+ }
+ if (origPermissions != permissions)
+ {
+ if (!QFile::setPermissions(filepath, permissions))
+ {
+ logWarning(tr("Could not fix permissions for %1").arg(filepath));
+ }
+ else
+ {
+ qDebug() << "Fixed" << filepath;
+ }
+ }
+ }
+
+ shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
+ connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded);
+ connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed);
+ packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion);
+}
+
+void Technic::SingleZipPackInstallTask::extractAborted()
+{
+ emitFailed(tr("Instance import has been aborted."));
+}
diff --git a/api/logic/modplatform/technic/SingleZipPackInstallTask.h b/api/logic/modplatform/technic/SingleZipPackInstallTask.h
new file mode 100644
index 00000000..ec2ff605
--- /dev/null
+++ b/api/logic/modplatform/technic/SingleZipPackInstallTask.h
@@ -0,0 +1,65 @@
+/* Copyright 2013-2021 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 "InstanceTask.h"
+#include "net/NetJob.h"
+#include "multimc_logic_export.h"
+
+#include "quazip.h"
+
+#include <QFutureWatcher>
+#include <QStringList>
+#include <QUrl>
+
+#include <nonstd/optional>
+
+namespace Technic {
+
+class MULTIMC_LOGIC_EXPORT SingleZipPackInstallTask : public InstanceTask
+{
+ Q_OBJECT
+
+public:
+ SingleZipPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
+
+ bool canAbort() const override { return true; }
+ bool abort() override;
+
+protected:
+ void executeTask() override;
+
+
+private slots:
+ void downloadSucceeded();
+ void downloadFailed(QString reason);
+ void downloadProgressChanged(qint64 current, qint64 total);
+ void extractFinished();
+ void extractAborted();
+
+private:
+ bool m_abortable = false;
+
+ QUrl m_sourceUrl;
+ QString m_minecraftVersion;
+ QString m_archivePath;
+ NetJobPtr m_filesNetJob;
+ std::unique_ptr<QuaZip> m_packZip;
+ QFuture<nonstd::optional<QStringList>> m_extractFuture;
+ QFutureWatcher<nonstd::optional<QStringList>> m_extractFutureWatcher;
+};
+
+} // namespace Technic
diff --git a/api/logic/modplatform/technic/SolderPackInstallTask.cpp b/api/logic/modplatform/technic/SolderPackInstallTask.cpp
new file mode 100644
index 00000000..1b4186d4
--- /dev/null
+++ b/api/logic/modplatform/technic/SolderPackInstallTask.cpp
@@ -0,0 +1,207 @@
+/* Copyright 2013-2021 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 "SolderPackInstallTask.h"
+
+#include <FileSystem.h>
+#include <Json.h>
+#include <QtConcurrentRun>
+#include <MMCZip.h>
+#include "TechnicPackProcessor.h"
+
+Technic::SolderPackInstallTask::SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion)
+{
+ m_sourceUrl = sourceUrl;
+ m_minecraftVersion = minecraftVersion;
+}
+
+bool Technic::SolderPackInstallTask::abort() {
+ if(m_abortable)
+ {
+ return m_filesNetJob->abort();
+ }
+ return false;
+}
+
+void Technic::SolderPackInstallTask::executeTask()
+{
+ setStatus(tr("Finding recommended version:\n%1").arg(m_sourceUrl.toString()));
+ m_filesNetJob.reset(new NetJob(tr("Finding recommended version")));
+ m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response));
+ auto job = m_filesNetJob.get();
+ connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::versionSucceeded);
+ connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
+ m_filesNetJob->start();
+}
+
+void Technic::SolderPackInstallTask::versionSucceeded()
+{
+ try
+ {
+ QJsonDocument doc = Json::requireDocument(m_response);
+ QJsonObject obj = Json::requireObject(doc);
+ QString version = Json::requireString(obj, "recommended", "__placeholder__");
+ m_sourceUrl = m_sourceUrl.toString() + '/' + version;
+ }
+ catch (const JSONValidationError &e)
+ {
+ emitFailed(e.cause());
+ m_filesNetJob.reset();
+ return;
+ }
+
+ setStatus(tr("Resolving modpack files:\n%1").arg(m_sourceUrl.toString()));
+ m_filesNetJob.reset(new NetJob(tr("Resolving modpack files")));
+ m_filesNetJob->addNetAction(Net::Download::makeByteArray(m_sourceUrl, &m_response));
+ auto job = m_filesNetJob.get();
+ connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded);
+ connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
+ m_filesNetJob->start();
+}
+
+void Technic::SolderPackInstallTask::fileListSucceeded()
+{
+ setStatus(tr("Downloading modpack:"));
+ QStringList modUrls;
+ try
+ {
+ QJsonDocument doc = Json::requireDocument(m_response);
+ QJsonObject obj = Json::requireObject(doc);
+ QString minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__");
+ if (!minecraftVersion.isEmpty())
+ m_minecraftVersion = minecraftVersion;
+ QJsonArray mods = Json::requireArray(obj, "mods", "'mods'");
+ for (auto mod: mods)
+ {
+ QJsonObject modObject = Json::requireObject(mod);
+ modUrls.append(Json::requireString(modObject, "url", "'url'"));
+ }
+ }
+ catch (const JSONValidationError &e)
+ {
+ emitFailed(e.cause());
+ m_filesNetJob.reset();
+ return;
+ }
+ m_filesNetJob.reset(new NetJob(tr("Downloading modpack")));
+ int i = 0;
+ for (auto &modUrl: modUrls)
+ {
+ auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
+ m_filesNetJob->addNetAction(Net::Download::makeFile(modUrl, path));
+ i++;
+ }
+
+ m_modCount = modUrls.size();
+
+ connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded);
+ connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged);
+ connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed);
+ m_filesNetJob->start();
+}
+
+void Technic::SolderPackInstallTask::downloadSucceeded()
+{
+ m_abortable = false;
+
+ setStatus(tr("Extracting modpack"));
+ m_filesNetJob.reset();
+ m_extractFuture = QtConcurrent::run([this]()
+ {
+ int i = 0;
+ QString extractDir = FS::PathCombine(m_stagingPath, ".minecraft");
+ FS::ensureFolderPathExists(extractDir);
+
+ while (m_modCount > i)
+ {
+ auto path = FS::PathCombine(m_outputDir.path(), QString("%1").arg(i));
+ if (!MMCZip::extractDir(path, extractDir))
+ {
+ return false;
+ }
+ i++;
+ }
+ return true;
+ });
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &Technic::SolderPackInstallTask::extractFinished);
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &Technic::SolderPackInstallTask::extractAborted);
+ m_extractFutureWatcher.setFuture(m_extractFuture);
+}
+
+void Technic::SolderPackInstallTask::downloadFailed(QString reason)
+{
+ m_abortable = false;
+ emitFailed(reason);
+ m_filesNetJob.reset();
+}
+
+void Technic::SolderPackInstallTask::downloadProgressChanged(qint64 current, qint64 total)
+{
+ m_abortable = true;
+ setProgress(current / 2, total);
+}
+
+void Technic::SolderPackInstallTask::extractFinished()
+{
+ if (!m_extractFuture.result())
+ {
+ emitFailed(tr("Failed to extract modpack"));
+ return;
+ }
+ QDir extractDir(m_stagingPath);
+
+ qDebug() << "Fixing permissions for extracted pack files...";
+ QDirIterator it(extractDir, QDirIterator::Subdirectories);
+ while (it.hasNext())
+ {
+ auto filepath = it.next();
+ QFileInfo file(filepath);
+ auto permissions = QFile::permissions(filepath);
+ auto origPermissions = permissions;
+ if(file.isDir())
+ {
+ // Folder +rwx for current user
+ permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser;
+ }
+ else
+ {
+ // File +rw for current user
+ permissions |= QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser;
+ }
+ if(origPermissions != permissions)
+ {
+ if(!QFile::setPermissions(filepath, permissions))
+ {
+ logWarning(tr("Could not fix permissions for %1").arg(filepath));
+ }
+ else
+ {
+ qDebug() << "Fixed" << filepath;
+ }
+ }
+ }
+
+ shared_qobject_ptr<Technic::TechnicPackProcessor> packProcessor = new Technic::TechnicPackProcessor();
+ connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded);
+ connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed);
+ packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion, true);
+}
+
+void Technic::SolderPackInstallTask::extractAborted()
+{
+ emitFailed(tr("Instance import has been aborted."));
+ return;
+}
+
diff --git a/api/logic/modplatform/technic/SolderPackInstallTask.h b/api/logic/modplatform/technic/SolderPackInstallTask.h
new file mode 100644
index 00000000..9f0f20a9
--- /dev/null
+++ b/api/logic/modplatform/technic/SolderPackInstallTask.h
@@ -0,0 +1,60 @@
+/* Copyright 2013-2021 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 <InstanceTask.h>
+#include <net/NetJob.h>
+#include <tasks/Task.h>
+
+#include <QUrl>
+
+namespace Technic
+{
+ class MULTIMC_LOGIC_EXPORT SolderPackInstallTask : public InstanceTask
+ {
+ Q_OBJECT
+ public:
+ explicit SolderPackInstallTask(const QUrl &sourceUrl, const QString &minecraftVersion);
+
+ bool canAbort() const override { return true; }
+ bool abort() override;
+
+ protected:
+ //! Entry point for tasks.
+ virtual void executeTask() override;
+
+ private slots:
+ void versionSucceeded();
+ void fileListSucceeded();
+ void downloadSucceeded();
+ void downloadFailed(QString reason);
+ void downloadProgressChanged(qint64 current, qint64 total);
+ void extractFinished();
+ void extractAborted();
+
+ private:
+ bool m_abortable = false;
+
+ NetJobPtr m_filesNetJob;
+ QUrl m_sourceUrl;
+ QString m_minecraftVersion;
+ QByteArray m_response;
+ QTemporaryDir m_outputDir;
+ int m_modCount;
+ QFuture<bool> m_extractFuture;
+ QFutureWatcher<bool> m_extractFutureWatcher;
+ };
+}
diff --git a/api/logic/modplatform/technic/TechnicPackProcessor.cpp b/api/logic/modplatform/technic/TechnicPackProcessor.cpp
new file mode 100644
index 00000000..52979b7c
--- /dev/null
+++ b/api/logic/modplatform/technic/TechnicPackProcessor.cpp
@@ -0,0 +1,208 @@
+/* Copyright 2020-2021 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 "TechnicPackProcessor.h"
+
+#include <FileSystem.h>
+#include <Json.h>
+#include <minecraft/MinecraftInstance.h>
+#include <minecraft/PackProfile.h>
+#include <quazip.h>
+#include <quazipdir.h>
+#include <quazipfile.h>
+#include <settings/INISettingsObject.h>
+
+#include <memory>
+
+void Technic::TechnicPackProcessor::run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion, const bool isSolder)
+{
+ QString minecraftPath = FS::PathCombine(stagingPath, ".minecraft");
+ QString configPath = FS::PathCombine(stagingPath, "instance.cfg");
+ auto instanceSettings = std::make_shared<INISettingsObject>(configPath);
+ instanceSettings->registerSetting("InstanceType", "Legacy");
+ instanceSettings->set("InstanceType", "OneSix");
+ MinecraftInstance instance(globalSettings, instanceSettings, stagingPath);
+
+ instance.setName(instName);
+
+ if (instIcon != "default")
+ {
+ instance.setIconKey(instIcon);
+ }
+
+ auto components = instance.getPackProfile();
+ components->buildingFromScratch();
+
+ QByteArray data;
+
+ QString modpackJar = FS::PathCombine(minecraftPath, "bin", "modpack.jar");
+ QString versionJson = FS::PathCombine(minecraftPath, "bin", "version.json");
+ QString fmlMinecraftVersion;
+ if (QFile::exists(modpackJar))
+ {
+ QuaZip zipFile(modpackJar);
+ if (!zipFile.open(QuaZip::mdUnzip))
+ {
+ emit failed(tr("Unable to open \"bin/modpack.jar\" file!"));
+ return;
+ }
+ QuaZipDir zipFileRoot(&zipFile, "/");
+ if (zipFileRoot.exists("/version.json"))
+ {
+ if (zipFileRoot.exists("/fmlversion.properties"))
+ {
+ zipFile.setCurrentFile("fmlversion.properties");
+ QuaZipFile file(&zipFile);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ emit failed(tr("Unable to open \"fmlversion.properties\"!"));
+ return;
+ }
+ QByteArray fmlVersionData = file.readAll();
+ file.close();
+ INIFile iniFile;
+ iniFile.loadFile(fmlVersionData);
+ // If not present, this evaluates to a null string
+ fmlMinecraftVersion = iniFile["fmlbuild.mcversion"].toString();
+ }
+ zipFile.setCurrentFile("version.json", QuaZip::csSensitive);
+ QuaZipFile file(&zipFile);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ emit failed(tr("Unable to open \"version.json\"!"));
+ return;
+ }
+ data = file.readAll();
+ file.close();
+ }
+ else
+ {
+ if (minecraftVersion.isEmpty())
+ emit failed(tr("Could not find \"version.json\" inside \"bin/modpack.jar\", but minecraft version is unknown"));
+ components->setComponentVersion("net.minecraft", minecraftVersion, true);
+ components->installJarMods({modpackJar});
+
+ // Forge for 1.4.7 and for 1.5.2 require extra libraries.
+ // Figure out the forge version and add it as a component
+ // (the code still comes from the jar mod installed above)
+ if (zipFileRoot.exists("/forgeversion.properties"))
+ {
+ zipFile.setCurrentFile("forgeversion.properties", QuaZip::csSensitive);
+ QuaZipFile file(&zipFile);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ // Really shouldn't happen, but error handling shall not be forgotten
+ emit failed(tr("Unable to open \"forgeversion.properties\""));
+ return;
+ }
+ QByteArray forgeVersionData = file.readAll();
+ file.close();
+ INIFile iniFile;
+ iniFile.loadFile(forgeVersionData);
+ QString major, minor, revision, build;
+ major = iniFile["forge.major.number"].toString();
+ minor = iniFile["forge.minor.number"].toString();
+ revision = iniFile["forge.revision.number"].toString();
+ build = iniFile["forge.build.number"].toString();
+
+ if (major.isEmpty() || minor.isEmpty() || revision.isEmpty() || build.isEmpty())
+ {
+ emit failed(tr("Invalid \"forgeversion.properties\"!"));
+ return;
+ }
+
+ components->setComponentVersion("net.minecraftforge", major + '.' + minor + '.' + revision + '.' + build);
+ }
+
+ components->saveNow();
+ emit succeeded();
+ return;
+ }
+ }
+ else if (QFile::exists(versionJson))
+ {
+ QFile file(versionJson);
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ emit failed(tr("Unable to open \"version.json\"!"));
+ return;
+ }
+ data = file.readAll();
+ file.close();
+ }
+ else
+ {
+ // This is the "Vanilla" modpack, excluded by the search code
+ emit failed(tr("Unable to find a \"version.json\"!"));
+ return;
+ }
+
+ try
+ {
+ QJsonDocument doc = Json::requireDocument(data);
+ QJsonObject root = Json::requireObject(doc, "version.json");
+ QString minecraftVersion = Json::ensureString(root, "inheritsFrom", QString(), "");
+ if (minecraftVersion.isEmpty())
+ {
+ if (fmlMinecraftVersion.isEmpty())
+ {
+ emit failed(tr("Could not understand \"version.json\":\ninheritsFrom is missing"));
+ return;
+ }
+ minecraftVersion = fmlMinecraftVersion;
+ }
+ components->setComponentVersion("net.minecraft", minecraftVersion, true);
+ for (auto library: Json::ensureArray(root, "libraries", {}))
+ {
+ if (!library.isObject())
+ {
+ continue;
+ }
+
+ auto libraryObject = Json::ensureObject(library, {}, "");
+ auto libraryName = Json::ensureString(libraryObject, "name", "", "");
+
+ if (libraryName.startsWith("net.minecraftforge:forge:") && libraryName.contains('-'))
+ {
+ QString libraryVersion = libraryName.section(':', 2);
+ if (!libraryVersion.startsWith("1.7.10-"))
+ {
+ components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1));
+ }
+ else
+ {
+ // 1.7.10 versions sometimes look like 1.7.10-10.13.4.1614-1.7.10, this filters out the 10.13.4.1614 part
+ components->setComponentVersion("net.minecraftforge", libraryName.section('-', 1, 1));
+ }
+ }
+ else if (libraryName.startsWith("net.minecraftforge:minecraftforge:"))
+ {
+ components->setComponentVersion("net.minecraftforge", libraryName.section(':', 2));
+ }
+ else if (libraryName.startsWith("net.fabricmc:fabric-loader:"))
+ {
+ components->setComponentVersion("net.fabricmc.fabric-loader", libraryName.section(':', 2));
+ }
+ }
+ }
+ catch (const JSONValidationError &e)
+ {
+ emit failed(tr("Could not understand \"version.json\":\n") + e.cause());
+ return;
+ }
+
+ components->saveNow();
+ emit succeeded();
+}
diff --git a/api/logic/modplatform/technic/TechnicPackProcessor.h b/api/logic/modplatform/technic/TechnicPackProcessor.h
new file mode 100644
index 00000000..2ad803b3
--- /dev/null
+++ b/api/logic/modplatform/technic/TechnicPackProcessor.h
@@ -0,0 +1,35 @@
+/* Copyright 2020-2021 MultiMC Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <QString>
+#include "settings/SettingsObject.h"
+
+namespace Technic
+{
+ // not exporting it, only used in SingleZipPackInstallTask, InstanceImportTask and SolderPackInstallTask
+ class TechnicPackProcessor : public QObject
+ {
+ Q_OBJECT
+
+ signals:
+ void succeeded();
+ void failed(QString reason);
+
+ public:
+ void run(SettingsObjectPtr globalSettings, const QString &instName, const QString &instIcon, const QString &stagingPath, const QString &minecraftVersion=QString(), const bool isSolder = false);
+ };
+}
diff --git a/api/logic/mojang/PackageManifest.cpp b/api/logic/mojang/PackageManifest.cpp
new file mode 100644
index 00000000..b3dfd7fc
--- /dev/null
+++ b/api/logic/mojang/PackageManifest.cpp
@@ -0,0 +1,427 @@
+#include "PackageManifest.h"
+#include <Json.h>
+#include <QDir>
+#include <QDirIterator>
+#include <QCryptographicHash>
+#include <QDebug>
+
+#ifndef Q_OS_WIN32
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+
+namespace mojang_files {
+
+const Hash hash_of_empty_string = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
+
+int Path::compare(const Path& rhs) const
+{
+ auto left_cursor = begin();
+ auto left_end = end();
+ auto right_cursor = rhs.begin();
+ auto right_end = rhs.end();
+
+ while (left_cursor != left_end && right_cursor != right_end)
+ {
+ if(*left_cursor < *right_cursor)
+ {
+ return -1;
+ }
+ else if(*left_cursor > *right_cursor)
+ {
+ return 1;
+ }
+ left_cursor++;
+ right_cursor++;
+ }
+
+ if(left_cursor == left_end)
+ {
+ if(right_cursor == right_end)
+ {
+ return 0;
+ }
+ return -1;
+ }
+ return 1;
+}
+
+void Package::addFile(const Path& path, const File& file) {
+ addFolder(path.parent_path());
+ files[path] = file;
+}
+
+void Package::addFolder(Path folder) {
+ if(!folder.has_parent_path()) {
+ return;
+ }
+ do {
+ folders.insert(folder);
+ folder = folder.parent_path();
+ } while(folder.has_parent_path());
+}
+
+void Package::addLink(const Path& path, const Path& target) {
+ addFolder(path.parent_path());
+ symlinks[path] = target;
+}
+
+void Package::addSource(const FileSource& source) {
+ sources[source.hash] = source;
+}
+
+
+namespace {
+void fromJson(QJsonDocument & doc, Package & out) {
+ std::set<Path> seen_paths;
+ if (!doc.isObject())
+ {
+ throw JSONValidationError("file manifest is not an object");
+ }
+ QJsonObject root = doc.object();
+
+ auto filesObj = Json::ensureObject(root, "files");
+ auto iter = filesObj.begin();
+ while (iter != filesObj.end())
+ {
+ Path objectPath = Path(iter.key());
+ auto value = iter.value();
+ iter++;
+ if(seen_paths.count(objectPath)) {
+ throw JSONValidationError("duplicate path inside manifest, the manifest is invalid");
+ }
+ if (!value.isObject())
+ {
+ throw JSONValidationError("file entry inside manifest is not an an object");
+ }
+ seen_paths.insert(objectPath);
+
+ auto fileObject = value.toObject();
+ auto type = Json::requireString(fileObject, "type");
+ if(type == "directory") {
+ out.addFolder(objectPath);
+ continue;
+ }
+ else if(type == "file") {
+ FileSource bestSource;
+ File file;
+ file.executable = Json::ensureBoolean(fileObject, QString("executable"), false);
+ auto downloads = Json::requireObject(fileObject, "downloads");
+ for(auto iter2 = downloads.begin(); iter2 != downloads.end(); iter2++) {
+ FileSource source;
+
+ auto downloadObject = Json::requireObject(iter2.value());
+ source.hash = Json::requireString(downloadObject, "sha1");
+ source.size = Json::requireInteger(downloadObject, "size");
+ source.url = Json::requireString(downloadObject, "url");
+
+ auto compression = iter2.key();
+ if(compression == "raw") {
+ file.hash = source.hash;
+ file.size = source.size;
+ source.compression = Compression::Raw;
+ }
+ else if (compression == "lzma") {
+ source.compression = Compression::Lzma;
+ }
+ else {
+ continue;
+ }
+ bestSource.upgrade(source);
+ }
+ if(bestSource.isBad()) {
+ throw JSONValidationError("No valid compression method for file " + iter.key());
+ }
+ out.addFile(objectPath, file);
+ out.addSource(bestSource);
+ }
+ else if(type == "link") {
+ auto target = Json::requireString(fileObject, "target");
+ out.symlinks[objectPath] = target;
+ out.addLink(objectPath, target);
+ }
+ else {
+ throw JSONValidationError("Invalid item type in manifest: " + type);
+ }
+ }
+ // make sure the containing folder exists
+ out.folders.insert(Path());
+}
+}
+
+Package Package::fromManifestContents(const QByteArray& contents)
+{
+ Package out;
+ try
+ {
+ auto doc = Json::requireDocument(contents, "Manifest");
+ fromJson(doc, out);
+ return out;
+ }
+ catch (const Exception &e)
+ {
+ qDebug() << QString("Unable to parse manifest: %1").arg(e.cause());
+ out.valid = false;
+ return out;
+ }
+}
+
+Package Package::fromManifestFile(const QString & filename) {
+ Package out;
+ try
+ {
+ auto doc = Json::requireDocument(filename, filename);
+ fromJson(doc, out);
+ return out;
+ }
+ catch (const Exception &e)
+ {
+ qDebug() << QString("Unable to parse manifest file %1: %2").arg(filename, e.cause());
+ out.valid = false;
+ return out;
+ }
+}
+
+#ifndef Q_OS_WIN32
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+namespace {
+// FIXME: Qt obscures symlink targets by making them absolute. that is useless. this is the workaround - we do it ourselves
+bool actually_read_symlink_target(const QString & filepath, Path & out)
+{
+ struct ::stat st;
+ // FIXME: here, we assume the native filesystem encoding. May the Gods have mercy upon our Souls.
+ QByteArray nativePath = filepath.toUtf8();
+ const char * filepath_cstr = nativePath.data();
+
+ if (lstat(filepath_cstr, &st) != 0)
+ {
+ return false;
+ }
+
+ auto size = st.st_size ? st.st_size + 1 : PATH_MAX;
+ std::string temp(size, '\0');
+ // because we don't realiably know how long the damn thing actually is, we loop and expand. POSIX is naff
+ do
+ {
+ auto link_length = ::readlink(filepath_cstr, &temp[0], temp.size());
+ if(link_length == -1)
+ {
+ return false;
+ }
+ if(std::string::size_type(link_length) < temp.size())
+ {
+ // buffer was long enough and we managed to read the link target. RETURN here.
+ temp.resize(link_length);
+ out = Path(QString::fromUtf8(temp.c_str()));
+ return true;
+ }
+ temp.resize(temp.size() * 2);
+ } while (true);
+}
+}
+#endif
+
+// FIXME: Qt filesystem abstraction is bad, but ... let's hope it doesn't break too much?
+// FIXME: The error handling is just DEFICIENT
+Package Package::fromInspectedFolder(const QString& folderPath)
+{
+ QDir root(folderPath);
+
+ Package out;
+ QDirIterator iterator(folderPath, QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden, QDirIterator::Subdirectories);
+ while(iterator.hasNext()) {
+ iterator.next();
+
+ auto fileInfo = iterator.fileInfo();
+ auto relPath = root.relativeFilePath(fileInfo.filePath());
+ // FIXME: this is probably completely busted on Windows anyway, so just disable it.
+ // Qt makes shit up and doesn't understand the platform details
+ // TODO: Actually use a filesystem library that isn't terrible and has decen license.
+ // I only know one, and I wrote it. Sadly, currently proprietary. PAIN.
+#ifndef Q_OS_WIN32
+ if(fileInfo.isSymLink()) {
+ Path targetPath;
+ if(!actually_read_symlink_target(fileInfo.filePath(), targetPath)) {
+ qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
+ out.valid = false;
+ }
+ out.addLink(relPath, targetPath);
+ }
+ else
+#endif
+ if(fileInfo.isDir()) {
+ out.addFolder(relPath);
+ }
+ else if(fileInfo.isFile()) {
+ File f;
+ f.executable = fileInfo.isExecutable();
+ f.size = fileInfo.size();
+ // FIXME: async / optimize the hashing
+ QFile input(fileInfo.absoluteFilePath());
+ if(!input.open(QIODevice::ReadOnly)) {
+ qCritical() << "Folder inspection: Failed to open file:" << fileInfo.absoluteFilePath();
+ out.valid = false;
+ break;
+ }
+ f.hash = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Sha1).toHex().constData();
+ out.addFile(relPath, f);
+ }
+ else {
+ // Something else... oh my
+ qCritical() << "Folder inspection: Unknown filesystem object:" << fileInfo.absoluteFilePath();
+ out.valid = false;
+ break;
+ }
+ }
+ out.folders.insert(Path("."));
+ out.valid = true;
+ return out;
+}
+
+namespace {
+struct shallow_first_sort
+{
+ bool operator()(const Path &lhs, const Path &rhs) const
+ {
+ auto lhs_depth = lhs.length();
+ auto rhs_depth = rhs.length();
+ if(lhs_depth < rhs_depth)
+ {
+ return true;
+ }
+ else if(lhs_depth == rhs_depth)
+ {
+ if(lhs < rhs)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+struct deep_first_sort
+{
+ bool operator()(const Path &lhs, const Path &rhs) const
+ {
+ auto lhs_depth = lhs.length();
+ auto rhs_depth = rhs.length();
+ if(lhs_depth > rhs_depth)
+ {
+ return true;
+ }
+ else if(lhs_depth == rhs_depth)
+ {
+ if(lhs < rhs)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+}
+
+UpdateOperations UpdateOperations::resolve(const Package& from, const Package& to)
+{
+ UpdateOperations out;
+
+ if(!from.valid || !to.valid) {
+ out.valid = false;
+ return out;
+ }
+
+ // Files
+ for(auto iter = from.files.begin(); iter != from.files.end(); iter++) {
+ const auto &current_hash = iter->second.hash;
+ const auto &current_executable = iter->second.executable;
+ const auto &path = iter->first;
+
+ auto iter2 = to.files.find(path);
+ if(iter2 == to.files.end()) {
+ // removed
+ out.deletes.push_back(path);
+ continue;
+ }
+ auto new_hash = iter2->second.hash;
+ auto new_executable = iter2->second.executable;
+ if (current_hash != new_hash) {
+ out.deletes.push_back(path);
+ out.downloads.emplace(
+ std::pair<Path, FileDownload>{
+ path,
+ FileDownload(to.sources.at(iter2->second.hash), iter2->second.executable)
+ }
+ );
+ }
+ else if (current_executable != new_executable) {
+ out.executable_fixes[path] = new_executable;
+ }
+ }
+ for(auto iter = to.files.begin(); iter != to.files.end(); iter++) {
+ auto path = iter->first;
+ if(!from.files.count(path)) {
+ out.downloads.emplace(
+ std::pair<Path, FileDownload>{
+ path,
+ FileDownload(to.sources.at(iter->second.hash), iter->second.executable)
+ }
+ );
+ }
+ }
+
+ // Folders
+ std::set<Path, deep_first_sort> remove_folders;
+ std::set<Path, shallow_first_sort> make_folders;
+ for(auto from_path: from.folders) {
+ auto iter = to.folders.find(from_path);
+ if(iter == to.folders.end()) {
+ remove_folders.insert(from_path);
+ }
+ }
+ for(auto & rmdir: remove_folders) {
+ out.rmdirs.push_back(rmdir);
+ }
+ for(auto to_path: to.folders) {
+ auto iter = from.folders.find(to_path);
+ if(iter == from.folders.end()) {
+ make_folders.insert(to_path);
+ }
+ }
+ for(auto & mkdir: make_folders) {
+ out.mkdirs.push_back(mkdir);
+ }
+
+ // Symlinks
+ for(auto iter = from.symlinks.begin(); iter != from.symlinks.end(); iter++) {
+ const auto &current_target = iter->second;
+ const auto &path = iter->first;
+
+ auto iter2 = to.symlinks.find(path);
+ if(iter2 == to.symlinks.end()) {
+ // removed
+ out.deletes.push_back(path);
+ continue;
+ }
+ const auto &new_target = iter2->second;
+ if (current_target != new_target) {
+ out.deletes.push_back(path);
+ out.mklinks[path] = iter2->second;
+ }
+ }
+ for(auto iter = to.symlinks.begin(); iter != to.symlinks.end(); iter++) {
+ auto path = iter->first;
+ if(!from.symlinks.count(path)) {
+ out.mklinks[path] = iter->second;
+ }
+ }
+ out.valid = true;
+ return out;
+}
+
+}
diff --git a/api/logic/mojang/PackageManifest.h b/api/logic/mojang/PackageManifest.h
new file mode 100644
index 00000000..d01a0554
--- /dev/null
+++ b/api/logic/mojang/PackageManifest.h
@@ -0,0 +1,173 @@
+#pragma once
+
+#include <QString>
+#include <map>
+#include <set>
+#include <QStringList>
+#include "tasks/Task.h"
+
+#include "multimc_logic_export.h"
+
+namespace mojang_files {
+
+using Hash = QString;
+extern const Hash empty_hash;
+
+// simple-ish path implementation. assumes always relative and does not allow '..' entries
+class MULTIMC_LOGIC_EXPORT Path
+{
+public:
+ using parts_type = QStringList;
+
+ Path() = default;
+ Path(QString string) {
+ auto parts_in = string.split('/');
+ for(auto & part: parts_in) {
+ if(part.isEmpty() || part == ".") {
+ continue;
+ }
+ if(part == "..") {
+ if(parts.size()) {
+ parts.pop_back();
+ }
+ continue;
+ }
+ parts.push_back(part);
+ }
+ }
+
+ bool has_parent_path() const
+ {
+ return parts.size() > 0;
+ }
+
+ Path parent_path() const
+ {
+ if (parts.empty())
+ return Path();
+ return Path(parts.begin(), std::prev(parts.end()));
+ }
+
+ bool empty() const
+ {
+ return parts.empty();
+ }
+
+ int length() const
+ {
+ return parts.length();
+ }
+
+ bool operator==(const Path & rhs) const {
+ return parts == rhs.parts;
+ }
+
+ bool operator!=(const Path & rhs) const {
+ return parts != rhs.parts;
+ }
+
+ inline bool operator<(const Path& rhs) const
+ {
+ return compare(rhs) < 0;
+ }
+
+ parts_type::const_iterator begin() const
+ {
+ return parts.begin();
+ }
+
+ parts_type::const_iterator end() const
+ {
+ return parts.end();
+ }
+
+ QString toString() const {
+ return parts.join("/");
+ }
+
+private:
+ Path(const parts_type::const_iterator & start, const parts_type::const_iterator & end) {
+ auto cursor = start;
+ while(cursor != end) {
+ parts.push_back(*cursor);
+ cursor++;
+ }
+ }
+ int compare(const Path& p) const;
+
+ parts_type parts;
+};
+
+
+enum class Compression {
+ Raw,
+ Lzma,
+ Unknown
+};
+
+
+struct MULTIMC_LOGIC_EXPORT FileSource
+{
+ Compression compression = Compression::Unknown;
+ Hash hash;
+ QString url;
+ std::size_t size = 0;
+ void upgrade(const FileSource & other) {
+ if(compression == Compression::Unknown || other.size < size) {
+ *this = other;
+ }
+ }
+ bool isBad() const {
+ return compression == Compression::Unknown;
+ }
+};
+
+struct MULTIMC_LOGIC_EXPORT File
+{
+ Hash hash;
+ bool executable;
+ std::uint64_t size = 0;
+};
+
+struct MULTIMC_LOGIC_EXPORT Package {
+ static Package fromInspectedFolder(const QString &folderPath);
+ static Package fromManifestFile(const QString &path);
+ static Package fromManifestContents(const QByteArray& contents);
+
+ explicit operator bool() const
+ {
+ return valid;
+ }
+ void addFolder(Path folder);
+ void addFile(const Path & path, const File & file);
+ void addLink(const Path & path, const Path & target);
+ void addSource(const FileSource & source);
+
+ std::map<Hash, FileSource> sources;
+ bool valid = true;
+ std::set<Path> folders;
+ std::map<Path, File> files;
+ std::map<Path, Path> symlinks;
+};
+
+struct MULTIMC_LOGIC_EXPORT FileDownload : FileSource
+{
+ FileDownload(const FileSource& source, bool executable) {
+ static_cast<FileSource &> (*this) = source;
+ this->executable = executable;
+ }
+ bool executable = false;
+};
+
+struct MULTIMC_LOGIC_EXPORT UpdateOperations {
+ static UpdateOperations resolve(const Package & from, const Package & to);
+ bool valid = false;
+ std::vector<Path> deletes;
+ std::vector<Path> rmdirs;
+ std::vector<Path> mkdirs;
+ std::map<Path, FileDownload> downloads;
+ std::map<Path, Path> mklinks;
+ std::map<Path, bool> executable_fixes;
+};
+
+}
diff --git a/api/logic/mojang/PackageManifest_test.cpp b/api/logic/mojang/PackageManifest_test.cpp
new file mode 100644
index 00000000..d4c55c5a
--- /dev/null
+++ b/api/logic/mojang/PackageManifest_test.cpp
@@ -0,0 +1,344 @@
+#include <QTest>
+#include <QDebug>
+#include "TestUtil.h"
+
+#include "mojang/PackageManifest.h"
+
+using namespace mojang_files;
+
+QDebug operator<<(QDebug debug, const Path &path)
+{
+ debug << path.toString();
+ return debug;
+}
+
+class PackageManifestTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void test_parse();
+ void test_parse_file();
+ void test_inspect();
+#ifndef Q_OS_WIN32
+ void test_inspect_symlinks();
+#endif
+ void mkdir_deep();
+ void rmdir_deep();
+
+ void identical_file();
+ void changed_file();
+ void added_file();
+ void removed_file();
+};
+
+namespace {
+QByteArray basic_manifest = R"END(
+{
+ "files": {
+ "a/b.txt": {
+ "type": "file",
+ "downloads": {
+ "raw": {
+ "url": "http://dethware.org/b.txt",
+ "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "size": 0
+ }
+ },
+ "executable": true
+ },
+ "a/b/c": {
+ "type": "directory"
+ },
+ "a/b/c.txt": {
+ "type": "link",
+ "target": "../b.txt"
+ }
+ }
+}
+)END";
+}
+
+void PackageManifestTest::test_parse()
+{
+ auto manifest = Package::fromManifestContents(basic_manifest);
+ QVERIFY(manifest.valid == true);
+ QVERIFY(manifest.files.size() == 1);
+ QVERIFY(manifest.files.count(Path("a/b.txt")));
+ auto &file = manifest.files[Path("a/b.txt")];
+ QVERIFY(file.executable == true);
+ QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+ QVERIFY(file.size == 0);
+ QVERIFY(manifest.folders.size() == 4);
+ QVERIFY(manifest.folders.count(Path(".")));
+ QVERIFY(manifest.folders.count(Path("a")));
+ QVERIFY(manifest.folders.count(Path("a/b")));
+ QVERIFY(manifest.folders.count(Path("a/b/c")));
+ QVERIFY(manifest.symlinks.size() == 1);
+ auto symlinkPath = Path("a/b/c.txt");
+ QVERIFY(manifest.symlinks.count(symlinkPath));
+ auto &symlink = manifest.symlinks[symlinkPath];
+ QVERIFY(symlink == Path("../b.txt"));
+ QVERIFY(manifest.sources.size() == 1);
+}
+
+void PackageManifestTest::test_parse_file() {
+ auto path = QFINDTESTDATA("testdata/1.8.0_202-x64.json");
+ auto manifest = Package::fromManifestFile(path);
+ QVERIFY(manifest.valid == true);
+}
+
+
+void PackageManifestTest::test_inspect() {
+ auto path = QFINDTESTDATA("testdata/inspect_win/");
+ auto manifest = Package::fromInspectedFolder(path);
+ QVERIFY(manifest.valid == true);
+ QVERIFY(manifest.files.size() == 2);
+ QVERIFY(manifest.files.count(Path("a/b.txt")));
+ auto &file1 = manifest.files[Path("a/b.txt")];
+ QVERIFY(file1.executable == false);
+ QVERIFY(file1.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+ QVERIFY(file1.size == 0);
+ QVERIFY(manifest.files.count(Path("a/b/b.txt")));
+ auto &file2 = manifest.files[Path("a/b/b.txt")];
+ QVERIFY(file2.executable == false);
+ QVERIFY(file2.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+ QVERIFY(file2.size == 0);
+ QVERIFY(manifest.folders.size() == 3);
+ QVERIFY(manifest.folders.count(Path(".")));
+ QVERIFY(manifest.folders.count(Path("a")));
+ QVERIFY(manifest.folders.count(Path("a/b")));
+ QVERIFY(manifest.symlinks.size() == 0);
+}
+
+#ifndef Q_OS_WIN32
+void PackageManifestTest::test_inspect_symlinks() {
+ auto path = QFINDTESTDATA("testdata/inspect/");
+ auto manifest = Package::fromInspectedFolder(path);
+ QVERIFY(manifest.valid == true);
+ QVERIFY(manifest.files.size() == 1);
+ QVERIFY(manifest.files.count(Path("a/b.txt")));
+ auto &file = manifest.files[Path("a/b.txt")];
+ QVERIFY(file.executable == true);
+ QVERIFY(file.hash == "da39a3ee5e6b4b0d3255bfef95601890afd80709");
+ QVERIFY(file.size == 0);
+ QVERIFY(manifest.folders.size() == 3);
+ QVERIFY(manifest.folders.count(Path(".")));
+ QVERIFY(manifest.folders.count(Path("a")));
+ QVERIFY(manifest.folders.count(Path("a/b")));
+ QVERIFY(manifest.symlinks.size() == 1);
+ QVERIFY(manifest.symlinks.count(Path("a/b/b.txt")));
+ qDebug() << manifest.symlinks[Path("a/b/b.txt")];
+ QVERIFY(manifest.symlinks[Path("a/b/b.txt")] == Path("../b.txt"));
+}
+#endif
+
+void PackageManifestTest::mkdir_deep() {
+
+ Package from;
+ auto to = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d/e": {
+ "type": "directory"
+ }
+ }
+}
+)END");
+ auto operations = UpdateOperations::resolve(from, to);
+ QVERIFY(operations.deletes.size() == 0);
+ QVERIFY(operations.rmdirs.size() == 0);
+
+ QVERIFY(operations.mkdirs.size() == 6);
+ QVERIFY(operations.mkdirs[0] == Path("."));
+ QVERIFY(operations.mkdirs[1] == Path("a"));
+ QVERIFY(operations.mkdirs[2] == Path("a/b"));
+ QVERIFY(operations.mkdirs[3] == Path("a/b/c"));
+ QVERIFY(operations.mkdirs[4] == Path("a/b/c/d"));
+ QVERIFY(operations.mkdirs[5] == Path("a/b/c/d/e"));
+
+ QVERIFY(operations.downloads.size() == 0);
+ QVERIFY(operations.mklinks.size() == 0);
+ QVERIFY(operations.executable_fixes.size() == 0);
+}
+
+void PackageManifestTest::rmdir_deep() {
+
+ Package to;
+ auto from = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d/e": {
+ "type": "directory"
+ }
+ }
+}
+)END");
+ auto operations = UpdateOperations::resolve(from, to);
+ QVERIFY(operations.deletes.size() == 0);
+
+ QVERIFY(operations.rmdirs.size() == 6);
+ QVERIFY(operations.rmdirs[0] == Path("a/b/c/d/e"));
+ QVERIFY(operations.rmdirs[1] == Path("a/b/c/d"));
+ QVERIFY(operations.rmdirs[2] == Path("a/b/c"));
+ QVERIFY(operations.rmdirs[3] == Path("a/b"));
+ QVERIFY(operations.rmdirs[4] == Path("a"));
+ QVERIFY(operations.rmdirs[5] == Path("."));
+
+ QVERIFY(operations.mkdirs.size() == 0);
+ QVERIFY(operations.downloads.size() == 0);
+ QVERIFY(operations.mklinks.size() == 0);
+ QVERIFY(operations.executable_fixes.size() == 0);
+}
+
+void PackageManifestTest::identical_file() {
+ QByteArray manifest = R"END(
+{
+ "files": {
+ "a/b/c/d/empty.txt": {
+ "type": "file",
+ "downloads": {
+ "raw": {
+ "url": "http://dethware.org/empty.txt",
+ "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "size": 0
+ }
+ },
+ "executable": false
+ }
+ }
+}
+)END";
+ auto from = Package::fromManifestContents(manifest);
+ auto to = Package::fromManifestContents(manifest);
+ auto operations = UpdateOperations::resolve(from, to);
+ QVERIFY(operations.deletes.size() == 0);
+ QVERIFY(operations.rmdirs.size() == 0);
+ QVERIFY(operations.mkdirs.size() == 0);
+ QVERIFY(operations.downloads.size() == 0);
+ QVERIFY(operations.mklinks.size() == 0);
+ QVERIFY(operations.executable_fixes.size() == 0);
+}
+
+void PackageManifestTest::changed_file() {
+ auto from = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d/file": {
+ "type": "file",
+ "downloads": {
+ "raw": {
+ "url": "http://dethware.org/empty.txt",
+ "sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
+ "size": 0
+ }
+ },
+ "executable": false
+ }
+ }
+}
+)END");
+ auto to = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d/file": {
+ "type": "file",
+ "downloads": {
+ "raw": {
+ "url": "http://dethware.org/space.txt",
+ "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46",
+ "size": 1
+ }
+ },
+ "executable": false
+ }
+ }
+}
+)END");
+ auto operations = UpdateOperations::resolve(from, to);
+ QVERIFY(operations.deletes.size() == 1);
+ QCOMPARE(operations.deletes[0], Path("a/b/c/d/file"));
+ QVERIFY(operations.rmdirs.size() == 0);
+ QVERIFY(operations.mkdirs.size() == 0);
+ QVERIFY(operations.downloads.size() == 1);
+ QVERIFY(operations.mklinks.size() == 0);
+ QVERIFY(operations.executable_fixes.size() == 0);
+}
+
+void PackageManifestTest::added_file() {
+ auto from = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d": {
+ "type": "directory"
+ }
+ }
+}
+)END");
+ auto to = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d/file": {
+ "type": "file",
+ "downloads": {
+ "raw": {
+ "url": "http://dethware.org/space.txt",
+ "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46",
+ "size": 1
+ }
+ },
+ "executable": false
+ }
+ }
+}
+)END");
+ auto operations = UpdateOperations::resolve(from, to);
+ QVERIFY(operations.deletes.size() == 0);
+ QVERIFY(operations.rmdirs.size() == 0);
+ QVERIFY(operations.mkdirs.size() == 0);
+ QVERIFY(operations.downloads.size() == 1);
+ QVERIFY(operations.mklinks.size() == 0);
+ QVERIFY(operations.executable_fixes.size() == 0);
+}
+
+void PackageManifestTest::removed_file() {
+ auto from = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d/file": {
+ "type": "file",
+ "downloads": {
+ "raw": {
+ "url": "http://dethware.org/space.txt",
+ "sha1": "dd122581c8cd44d0227f9c305581ffcb4b6f1b46",
+ "size": 1
+ }
+ },
+ "executable": false
+ }
+ }
+}
+)END");
+ auto to = Package::fromManifestContents(R"END(
+{
+ "files": {
+ "a/b/c/d": {
+ "type": "directory"
+ }
+ }
+}
+)END");
+ auto operations = UpdateOperations::resolve(from, to);
+ QVERIFY(operations.deletes.size() == 1);
+ QCOMPARE(operations.deletes[0], Path("a/b/c/d/file"));
+ QVERIFY(operations.rmdirs.size() == 0);
+ QVERIFY(operations.mkdirs.size() == 0);
+ QVERIFY(operations.downloads.size() == 0);
+ QVERIFY(operations.mklinks.size() == 0);
+ QVERIFY(operations.executable_fixes.size() == 0);
+}
+
+QTEST_GUILESS_MAIN(PackageManifestTest)
+
+#include "PackageManifest_test.moc"
+
diff --git a/api/logic/mojang/testdata/1.8.0_202-x64.json b/api/logic/mojang/testdata/1.8.0_202-x64.json
new file mode 100644
index 00000000..3d99d719
--- /dev/null
+++ b/api/logic/mojang/testdata/1.8.0_202-x64.json
@@ -0,0 +1 @@
+{"files": {"COPYRIGHT": {"downloads": {"lzma": {"sha1": "dd860e040807f7e53ae89da5f28dd73d57ac605d", "size": 1431, "url": "https://launcher.mojang.com/v1/objects/dd860e040807f7e53ae89da5f28dd73d57ac605d/COPYRIGHT"}, "raw": {"sha1": "c725183c757011e7ba96c83c1e86ee7e8b516a2b", "size": 3244, "url": "https://launcher.mojang.com/v1/objects/c725183c757011e7ba96c83c1e86ee7e8b516a2b/COPYRIGHT"}}, "executable": false, "type": "file"}, "LICENSE": {"downloads": {"raw": {"sha1": "3e86865deec0814c958bcf7fb87f790bccc0e8bd", "size": 40, "url": "https://launcher.mojang.com/v1/objects/3e86865deec0814c958bcf7fb87f790bccc0e8bd/LICENSE"}}, "executable": false, "type": "file"}, "README": {"downloads": {"raw": {"sha1": "f90331df1e5badeadc501d8dd70714c62a920204", "size": 46, "url": "https://launcher.mojang.com/v1/objects/f90331df1e5badeadc501d8dd70714c62a920204/README"}}, "executable": false, "type": "file"}, "THIRDPARTYLICENSEREADME-JAVAFX.txt": {"downloads": {"lzma": {"sha1": "4fee85109d7ff04b982d0576dabd15397f599125", "size": 15455, "url": "https://launcher.mojang.com/v1/objects/4fee85109d7ff04b982d0576dabd15397f599125/THIRDPARTYLICENSEREADME-JAVAFX.txt"}, "raw": {"sha1": "56ff42f87607b997b52ae0ef8bf315e36932e870", "size": 112724, "url": "https://launcher.mojang.com/v1/objects/56ff42f87607b997b52ae0ef8bf315e36932e870/THIRDPARTYLICENSEREADME-JAVAFX.txt"}}, "executable": false, "type": "file"}, "THIRDPARTYLICENSEREADME.txt": {"downloads": {"lzma": {"sha1": "419c1414ba46ae9dbfd38cf4e0601fff61644429", "size": 32266, "url": "https://launcher.mojang.com/v1/objects/419c1414ba46ae9dbfd38cf4e0601fff61644429/THIRDPARTYLICENSEREADME.txt"}, "raw": {"sha1": "b83c3f32261de3e48ccd20614a11e066b1ec9027", "size": 153824, "url": "https://launcher.mojang.com/v1/objects/b83c3f32261de3e48ccd20614a11e066b1ec9027/THIRDPARTYLICENSEREADME.txt"}}, "executable": false, "type": "file"}, "Welcome.html": {"downloads": {"lzma": {"sha1": "01c21a74b4aafb7cbe0388233c43cbdf77dcaaea", "size": 528, "url": "https://launcher.mojang.com/v1/objects/01c21a74b4aafb7cbe0388233c43cbdf77dcaaea/Welcome.html"}, "raw": {"sha1": "d98ae54f03dac87419abc19b97e315830c2da55f", "size": 955, "url": "https://launcher.mojang.com/v1/objects/d98ae54f03dac87419abc19b97e315830c2da55f/Welcome.html"}}, "executable": false, "type": "file"}, "bin": {"type": "directory"}, "bin/ControlPanel": {"target": "jcontrol", "type": "link"}, "bin/java": {"downloads": {"lzma": {"sha1": "3857eea1d59e1bc545c67a753ed2768254807b8a", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/3857eea1d59e1bc545c67a753ed2768254807b8a/java"}, "raw": {"sha1": "3d20560fb5d1a49cb689c2226972e92e06d27ba6", "size": 8464, "url": "https://launcher.mojang.com/v1/objects/3d20560fb5d1a49cb689c2226972e92e06d27ba6/java"}}, "executable": true, "type": "file"}, "bin/javaws": {"downloads": {"lzma": {"sha1": "a6bec5c049e76c4488294a256a2084ea23ddb440", "size": 38173, "url": "https://launcher.mojang.com/v1/objects/a6bec5c049e76c4488294a256a2084ea23ddb440/javaws"}, "raw": {"sha1": "955c0f0066e2f893b0c2b3ccd83e223722e4ab74", "size": 140296, "url": "https://launcher.mojang.com/v1/objects/955c0f0066e2f893b0c2b3ccd83e223722e4ab74/javaws"}}, "executable": true, "type": "file"}, "bin/jcontrol": {"downloads": {"lzma": {"sha1": "40c5e33748f252e1d950b579a4185ab2c23fc908", "size": 2166, "url": "https://launcher.mojang.com/v1/objects/40c5e33748f252e1d950b579a4185ab2c23fc908/jcontrol"}, "raw": {"sha1": "ed541733c8b51e34349c1f8010b277e58ad73f1e", "size": 6264, "url": "https://launcher.mojang.com/v1/objects/ed541733c8b51e34349c1f8010b277e58ad73f1e/jcontrol"}}, "executable": true, "type": "file"}, "bin/jjs": {"downloads": {"lzma": {"sha1": "d44d1ac421979f7671921986214812095a5b0e3b", "size": 2168, "url": "https://launcher.mojang.com/v1/objects/d44d1ac421979f7671921986214812095a5b0e3b/jjs"}, "raw": {"sha1": "f00f944c3dbe556793b5dc686aaeee3e5722e99b", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/f00f944c3dbe556793b5dc686aaeee3e5722e99b/jjs"}}, "executable": true, "type": "file"}, "bin/keytool": {"downloads": {"lzma": {"sha1": "93c607dce450976667c382f609a367167bdec05c", "size": 2175, "url": "https://launcher.mojang.com/v1/objects/93c607dce450976667c382f609a367167bdec05c/keytool"}, "raw": {"sha1": "7114b561546270e441e9ed1bcc24e5188c068a42", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/7114b561546270e441e9ed1bcc24e5188c068a42/keytool"}}, "executable": true, "type": "file"}, "bin/orbd": {"downloads": {"lzma": {"sha1": "b27dfded5e2b2f6f02c555971c94e46ca14ac81b", "size": 2254, "url": "https://launcher.mojang.com/v1/objects/b27dfded5e2b2f6f02c555971c94e46ca14ac81b/orbd"}, "raw": {"sha1": "7f31217fecb3dbbd89f1dd3783fca58793a66fd2", "size": 8656, "url": "https://launcher.mojang.com/v1/objects/7f31217fecb3dbbd89f1dd3783fca58793a66fd2/orbd"}}, "executable": true, "type": "file"}, "bin/pack200": {"downloads": {"lzma": {"sha1": "b52da4497b49b1508b6225a5740857ddb8f52e97", "size": 2183, "url": "https://launcher.mojang.com/v1/objects/b52da4497b49b1508b6225a5740857ddb8f52e97/pack200"}, "raw": {"sha1": "16ef3e801efb57e50bc6477a27a9d95d02d0775b", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/16ef3e801efb57e50bc6477a27a9d95d02d0775b/pack200"}}, "executable": true, "type": "file"}, "bin/policytool": {"downloads": {"lzma": {"sha1": "87da4c07da45f3d1a1a9d732af197cd39bf69d10", "size": 2182, "url": "https://launcher.mojang.com/v1/objects/87da4c07da45f3d1a1a9d732af197cd39bf69d10/policytool"}, "raw": {"sha1": "a52a29424470cb9b8db5c2fb1751d0b697a7ec8e", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/a52a29424470cb9b8db5c2fb1751d0b697a7ec8e/policytool"}}, "executable": true, "type": "file"}, "bin/rmid": {"downloads": {"lzma": {"sha1": "1494c1174fde0c0a93ea117bc7edf7eb936c0512", "size": 2172, "url": "https://launcher.mojang.com/v1/objects/1494c1174fde0c0a93ea117bc7edf7eb936c0512/rmid"}, "raw": {"sha1": "5c8710e1ab924e5b09a07bcb4c6e106293bbd1a8", "size": 8584, "url": "https://launcher.mojang.com/v1/objects/5c8710e1ab924e5b09a07bcb4c6e106293bbd1a8/rmid"}}, "executable": true, "type": "file"}, "bin/rmiregistry": {"downloads": {"lzma": {"sha1": "7070cf2ec5a5e520a880bae699431edf02083e7e", "size": 2174, "url": "https://launcher.mojang.com/v1/objects/7070cf2ec5a5e520a880bae699431edf02083e7e/rmiregistry"}, "raw": {"sha1": "5f518daa7050028d5d9d849634c73136f2b23a54", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/5f518daa7050028d5d9d849634c73136f2b23a54/rmiregistry"}}, "executable": true, "type": "file"}, "bin/servertool": {"downloads": {"lzma": {"sha1": "1db683a11cc9b7313426c84412f4d95be2fa7ccd", "size": 2185, "url": "https://launcher.mojang.com/v1/objects/1db683a11cc9b7313426c84412f4d95be2fa7ccd/servertool"}, "raw": {"sha1": "49d0ebfeb265ce5a8733e1014541ea2525674a60", "size": 8592, "url": "https://launcher.mojang.com/v1/objects/49d0ebfeb265ce5a8733e1014541ea2525674a60/servertool"}}, "executable": true, "type": "file"}, "bin/tnameserv": {"downloads": {"lzma": {"sha1": "36da9c9a2c5a8b662a3f8d52ca67339bce1c2714", "size": 2291, "url": "https://launcher.mojang.com/v1/objects/36da9c9a2c5a8b662a3f8d52ca67339bce1c2714/tnameserv"}, "raw": {"sha1": "09d998f8efcb6f55d0d87f59e08f8b89662796d9", "size": 8656, "url": "https://launcher.mojang.com/v1/objects/09d998f8efcb6f55d0d87f59e08f8b89662796d9/tnameserv"}}, "executable": true, "type": "file"}, "bin/unpack200": {"downloads": {"lzma": {"sha1": "344959e32fc7ee19eebe7b3cf5ab6d1a7d6641f2", "size": 79721, "url": "https://launcher.mojang.com/v1/objects/344959e32fc7ee19eebe7b3cf5ab6d1a7d6641f2/unpack200"}, "raw": {"sha1": "5dd933132f1b202e19e0c8e093f7113711cfdfc1", "size": 182616, "url": "https://launcher.mojang.com/v1/objects/5dd933132f1b202e19e0c8e093f7113711cfdfc1/unpack200"}}, "executable": true, "type": "file"}, "lib": {"type": "directory"}, "lib/amd64": {"type": "directory"}, "lib/amd64/jli": {"type": "directory"}, "lib/amd64/jli/libjli.so": {"downloads": {"lzma": {"sha1": "372331ee8e375888f798a2e88180a94493e141b0", "size": 48327, "url": "https://launcher.mojang.com/v1/objects/372331ee8e375888f798a2e88180a94493e141b0/libjli.so"}, "raw": {"sha1": "73b0cf8b7415686bc40c561ff77ff2740ccf7a44", "size": 108616, "url": "https://launcher.mojang.com/v1/objects/73b0cf8b7415686bc40c561ff77ff2740ccf7a44/libjli.so"}}, "executable": true, "type": "file"}, "lib/amd64/jvm.cfg": {"downloads": {"lzma": {"sha1": "86bcfebec37b38415525ffd77d3eaf70d0b1b4ca", "size": 435, "url": "https://launcher.mojang.com/v1/objects/86bcfebec37b38415525ffd77d3eaf70d0b1b4ca/jvm.cfg"}, "raw": {"sha1": "84b38bdc745de446ba0ca0232ea3aaf2efd721da", "size": 627, "url": "https://launcher.mojang.com/v1/objects/84b38bdc745de446ba0ca0232ea3aaf2efd721da/jvm.cfg"}}, "executable": false, "type": "file"}, "lib/amd64/libavplugin-53.so": {"downloads": {"lzma": {"sha1": "a332366762d9efc7b845a682b7edce62db44618c", "size": 14747, "url": "https://launcher.mojang.com/v1/objects/a332366762d9efc7b845a682b7edce62db44618c/libavplugin-53.so"}, "raw": {"sha1": "9bd1473dd8a0dc7950c7af1cc69a45548df26eb5", "size": 51720, "url": "https://launcher.mojang.com/v1/objects/9bd1473dd8a0dc7950c7af1cc69a45548df26eb5/libavplugin-53.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-54.so": {"downloads": {"lzma": {"sha1": "2c615852a0720a275163e00597c1f711f11341da", "size": 15153, "url": "https://launcher.mojang.com/v1/objects/2c615852a0720a275163e00597c1f711f11341da/libavplugin-54.so"}, "raw": {"sha1": "8808050c5949c4800b42d1b19b1f8b0d120bcacb", "size": 51768, "url": "https://launcher.mojang.com/v1/objects/8808050c5949c4800b42d1b19b1f8b0d120bcacb/libavplugin-54.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-55.so": {"downloads": {"lzma": {"sha1": "39ee8e7fe14f0010c78973962800f539c3e4c16b", "size": 15168, "url": "https://launcher.mojang.com/v1/objects/39ee8e7fe14f0010c78973962800f539c3e4c16b/libavplugin-55.so"}, "raw": {"sha1": "f10ea4ea3489e96d8d161a96790133c417ec44e1", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/f10ea4ea3489e96d8d161a96790133c417ec44e1/libavplugin-55.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-56.so": {"downloads": {"lzma": {"sha1": "abe7feced5a559f1bdc868526dc69484e0e591a0", "size": 15169, "url": "https://launcher.mojang.com/v1/objects/abe7feced5a559f1bdc868526dc69484e0e591a0/libavplugin-56.so"}, "raw": {"sha1": "e5bfcbff5a5a5a5993a3e689a05ef358c131a3ed", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/e5bfcbff5a5a5a5993a3e689a05ef358c131a3ed/libavplugin-56.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-57.so": {"downloads": {"lzma": {"sha1": "4dd26b4ef2294b6929dcb2c7546b47eac5cc78a9", "size": 15174, "url": "https://launcher.mojang.com/v1/objects/4dd26b4ef2294b6929dcb2c7546b47eac5cc78a9/libavplugin-57.so"}, "raw": {"sha1": "2949e7ff9b0ac90e8943c211cff141ab12eec3f8", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/2949e7ff9b0ac90e8943c211cff141ab12eec3f8/libavplugin-57.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-ffmpeg-56.so": {"downloads": {"lzma": {"sha1": "c688ba1cfa442bf18bee43b2fa870b4dc1ce3fb6", "size": 15231, "url": "https://launcher.mojang.com/v1/objects/c688ba1cfa442bf18bee43b2fa870b4dc1ce3fb6/libavplugin-ffmpeg-56.so"}, "raw": {"sha1": "0d36c971a9ad99fc2292092fdec3a4179b1021b9", "size": 51920, "url": "https://launcher.mojang.com/v1/objects/0d36c971a9ad99fc2292092fdec3a4179b1021b9/libavplugin-ffmpeg-56.so"}}, "executable": true, "type": "file"}, "lib/amd64/libavplugin-ffmpeg-57.so": {"downloads": {"lzma": {"sha1": "087426bdbffebcfa372a438e863785f4ffbe9a6b", "size": 15180, "url": "https://launcher.mojang.com/v1/objects/087426bdbffebcfa372a438e863785f4ffbe9a6b/libavplugin-ffmpeg-57.so"}, "raw": {"sha1": "5e9c4eb4b49eb8e57c01003ec73a1eb8d6d8c462", "size": 51784, "url": "https://launcher.mojang.com/v1/objects/5e9c4eb4b49eb8e57c01003ec73a1eb8d6d8c462/libavplugin-ffmpeg-57.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt.so": {"downloads": {"lzma": {"sha1": "018be58b205b73c842a55df811b70d0e8237216e", "size": 195720, "url": "https://launcher.mojang.com/v1/objects/018be58b205b73c842a55df811b70d0e8237216e/libawt.so"}, "raw": {"sha1": "02632cd326e3161c00a7e784599dd7b9ee053dce", "size": 759184, "url": "https://launcher.mojang.com/v1/objects/02632cd326e3161c00a7e784599dd7b9ee053dce/libawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt_headless.so": {"downloads": {"lzma": {"sha1": "7ac2517cff75d4bbb0a0412a9b5f18c74ea188fa", "size": 11211, "url": "https://launcher.mojang.com/v1/objects/7ac2517cff75d4bbb0a0412a9b5f18c74ea188fa/libawt_headless.so"}, "raw": {"sha1": "862157ec957008d0911c5daedc004b3a202623a4", "size": 39176, "url": "https://launcher.mojang.com/v1/objects/862157ec957008d0911c5daedc004b3a202623a4/libawt_headless.so"}}, "executable": true, "type": "file"}, "lib/amd64/libawt_xawt.so": {"downloads": {"lzma": {"sha1": "d536a96af27dfe35de6bb2c8759d51c488cdd8d4", "size": 149598, "url": "https://launcher.mojang.com/v1/objects/d536a96af27dfe35de6bb2c8759d51c488cdd8d4/libawt_xawt.so"}, "raw": {"sha1": "28232b3e01b6f11bfe098bfc6eafc3a513dcebf1", "size": 470232, "url": "https://launcher.mojang.com/v1/objects/28232b3e01b6f11bfe098bfc6eafc3a513dcebf1/libawt_xawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libbci.so": {"downloads": {"lzma": {"sha1": "c36fad091d11e64c815d5ca17c0ef7a55b0776b1", "size": 3509, "url": "https://launcher.mojang.com/v1/objects/c36fad091d11e64c815d5ca17c0ef7a55b0776b1/libbci.so"}, "raw": {"sha1": "33824051db1ccb6332e22c2b63231055240d0af0", "size": 12760, "url": "https://launcher.mojang.com/v1/objects/33824051db1ccb6332e22c2b63231055240d0af0/libbci.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdcpr.so": {"downloads": {"lzma": {"sha1": "70c6b0933a37f2b1124d6e7c131039241fe796ee", "size": 75969, "url": "https://launcher.mojang.com/v1/objects/70c6b0933a37f2b1124d6e7c131039241fe796ee/libdcpr.so"}, "raw": {"sha1": "fa7001bc5d80579e2716590f3eee8027da0beae7", "size": 204456, "url": "https://launcher.mojang.com/v1/objects/fa7001bc5d80579e2716590f3eee8027da0beae7/libdcpr.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdecora_sse.so": {"downloads": {"lzma": {"sha1": "514acc017dfb6cefaf8cc6d18006ce55781cc9bc", "size": 24397, "url": "https://launcher.mojang.com/v1/objects/514acc017dfb6cefaf8cc6d18006ce55781cc9bc/libdecora_sse.so"}, "raw": {"sha1": "d0c84233504c916e548e29f513e25f6a7479abfc", "size": 74912, "url": "https://launcher.mojang.com/v1/objects/d0c84233504c916e548e29f513e25f6a7479abfc/libdecora_sse.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdeploy.so": {"downloads": {"lzma": {"sha1": "6cf31fd98301c749ac0d2c7825f6d925a4409760", "size": 168999, "url": "https://launcher.mojang.com/v1/objects/6cf31fd98301c749ac0d2c7825f6d925a4409760/libdeploy.so"}, "raw": {"sha1": "b3832e97ed8ca794884b56a591b83d02a2c0c06f", "size": 642368, "url": "https://launcher.mojang.com/v1/objects/b3832e97ed8ca794884b56a591b83d02a2c0c06f/libdeploy.so"}}, "executable": true, "type": "file"}, "lib/amd64/libdt_socket.so": {"downloads": {"lzma": {"sha1": "4cc5c880dbb6fa180436d12d60f0abec8ebb59dc", "size": 7784, "url": "https://launcher.mojang.com/v1/objects/4cc5c880dbb6fa180436d12d60f0abec8ebb59dc/libdt_socket.so"}, "raw": {"sha1": "91ce96f252b8139fc12f0f224ed5b1a041767ab7", "size": 24616, "url": "https://launcher.mojang.com/v1/objects/91ce96f252b8139fc12f0f224ed5b1a041767ab7/libdt_socket.so"}}, "executable": true, "type": "file"}, "lib/amd64/libfontmanager.so": {"downloads": {"lzma": {"sha1": "f94e5e94c71c603ff4d3cd1e7e3d9e181fcc145d", "size": 146951, "url": "https://launcher.mojang.com/v1/objects/f94e5e94c71c603ff4d3cd1e7e3d9e181fcc145d/libfontmanager.so"}, "raw": {"sha1": "2428e805f2c53d1283a033dfd11a86fbb7bd7159", "size": 490672, "url": "https://launcher.mojang.com/v1/objects/2428e805f2c53d1283a033dfd11a86fbb7bd7159/libfontmanager.so"}}, "executable": true, "type": "file"}, "lib/amd64/libfxplugins.so": {"downloads": {"lzma": {"sha1": "a640143365d382a5ad743a784bc2f3706d9d6d67", "size": 50048, "url": "https://launcher.mojang.com/v1/objects/a640143365d382a5ad743a784bc2f3706d9d6d67/libfxplugins.so"}, "raw": {"sha1": "0fd4ac04a84c131f1aaee9e6b0898ff9ea69e3ee", "size": 151448, "url": "https://launcher.mojang.com/v1/objects/0fd4ac04a84c131f1aaee9e6b0898ff9ea69e3ee/libfxplugins.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglass.so": {"downloads": {"lzma": {"sha1": "f1ff517714fa5f2c861f33b32db823fe851541f1", "size": 2856, "url": "https://launcher.mojang.com/v1/objects/f1ff517714fa5f2c861f33b32db823fe851541f1/libglass.so"}, "raw": {"sha1": "e7f4fece30ac727be8148d33b8256abd3a41cef9", "size": 13072, "url": "https://launcher.mojang.com/v1/objects/e7f4fece30ac727be8148d33b8256abd3a41cef9/libglass.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglassgtk2.so": {"downloads": {"lzma": {"sha1": "15b90f7a2baacd15e80aa9785d87cf1e4258376d", "size": 220476, "url": "https://launcher.mojang.com/v1/objects/15b90f7a2baacd15e80aa9785d87cf1e4258376d/libglassgtk2.so"}, "raw": {"sha1": "e30a634c2ff2143bdee512360553d6e0304f33b2", "size": 844984, "url": "https://launcher.mojang.com/v1/objects/e30a634c2ff2143bdee512360553d6e0304f33b2/libglassgtk2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglassgtk3.so": {"downloads": {"lzma": {"sha1": "868c231165f8c9043b7f0e7de208ec023f06a6e7", "size": 220560, "url": "https://launcher.mojang.com/v1/objects/868c231165f8c9043b7f0e7de208ec023f06a6e7/libglassgtk3.so"}, "raw": {"sha1": "762a11a2b376b7b5a2a7cad780715524fdd176d5", "size": 845304, "url": "https://launcher.mojang.com/v1/objects/762a11a2b376b7b5a2a7cad780715524fdd176d5/libglassgtk3.so"}}, "executable": true, "type": "file"}, "lib/amd64/libglib-lite.so": {"downloads": {"lzma": {"sha1": "61b8871242febe1be262de167dc20ae94bf964b4", "size": 457046, "url": "https://launcher.mojang.com/v1/objects/61b8871242febe1be262de167dc20ae94bf964b4/libglib-lite.so"}, "raw": {"sha1": "63afa060fc3f120af76128e51d32603fc4336fa8", "size": 1538352, "url": "https://launcher.mojang.com/v1/objects/63afa060fc3f120af76128e51d32603fc4336fa8/libglib-lite.so"}}, "executable": true, "type": "file"}, "lib/amd64/libgstreamer-lite.so": {"downloads": {"lzma": {"sha1": "2447dc368406ba1b989a29937d41924620e01988", "size": 673056, "url": "https://launcher.mojang.com/v1/objects/2447dc368406ba1b989a29937d41924620e01988/libgstreamer-lite.so"}, "raw": {"sha1": "5505e7ca592ac64371d3db8fe53bcb602e9723d3", "size": 2263872, "url": "https://launcher.mojang.com/v1/objects/5505e7ca592ac64371d3db8fe53bcb602e9723d3/libgstreamer-lite.so"}}, "executable": true, "type": "file"}, "lib/amd64/libhprof.so": {"downloads": {"lzma": {"sha1": "94a5589c818db1fb1cf1881e24e217c309fce2e4", "size": 64471, "url": "https://launcher.mojang.com/v1/objects/94a5589c818db1fb1cf1881e24e217c309fce2e4/libhprof.so"}, "raw": {"sha1": "4bb9bdeef6133b6dd558d52d691b077c03e9b0ee", "size": 175504, "url": "https://launcher.mojang.com/v1/objects/4bb9bdeef6133b6dd558d52d691b077c03e9b0ee/libhprof.so"}}, "executable": true, "type": "file"}, "lib/amd64/libinstrument.so": {"downloads": {"lzma": {"sha1": "84ffea356caf725b42c86a8ebc9587f477ddde29", "size": 18603, "url": "https://launcher.mojang.com/v1/objects/84ffea356caf725b42c86a8ebc9587f477ddde29/libinstrument.so"}, "raw": {"sha1": "cb8009769601e3fecd7ea2b36c344f737b1a9da7", "size": 51560, "url": "https://launcher.mojang.com/v1/objects/cb8009769601e3fecd7ea2b36c344f737b1a9da7/libinstrument.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2gss.so": {"downloads": {"lzma": {"sha1": "4b2aa699506b126098b585a9617ce1c05707fa29", "size": 14132, "url": "https://launcher.mojang.com/v1/objects/4b2aa699506b126098b585a9617ce1c05707fa29/libj2gss.so"}, "raw": {"sha1": "cbce4a302b255d4d1924ef7606f038af766c5e86", "size": 47688, "url": "https://launcher.mojang.com/v1/objects/cbce4a302b255d4d1924ef7606f038af766c5e86/libj2gss.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2pcsc.so": {"downloads": {"lzma": {"sha1": "2361d3b2e3da48593c391b29b0d2b5409e4c55e5", "size": 5074, "url": "https://launcher.mojang.com/v1/objects/2361d3b2e3da48593c391b29b0d2b5409e4c55e5/libj2pcsc.so"}, "raw": {"sha1": "1274178492e7a3e997e12f67794616f7c3d8d0b9", "size": 18296, "url": "https://launcher.mojang.com/v1/objects/1274178492e7a3e997e12f67794616f7c3d8d0b9/libj2pcsc.so"}}, "executable": true, "type": "file"}, "lib/amd64/libj2pkcs11.so": {"downloads": {"lzma": {"sha1": "ef927e2790ba05931d0f0bdd63da3d275a834946", "size": 21573, "url": "https://launcher.mojang.com/v1/objects/ef927e2790ba05931d0f0bdd63da3d275a834946/libj2pkcs11.so"}, "raw": {"sha1": "bd4f2af9bfdc6168633d1920c1a1415de06bb45a", "size": 79472, "url": "https://launcher.mojang.com/v1/objects/bd4f2af9bfdc6168633d1920c1a1415de06bb45a/libj2pkcs11.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjaas_unix.so": {"downloads": {"lzma": {"sha1": "7f7e843544ee1eb1454a5826bdd4218685b79430", "size": 2404, "url": "https://launcher.mojang.com/v1/objects/7f7e843544ee1eb1454a5826bdd4218685b79430/libjaas_unix.so"}, "raw": {"sha1": "4c517925c7d464a5b719898eb0bea1b04df31f1f", "size": 8192, "url": "https://launcher.mojang.com/v1/objects/4c517925c7d464a5b719898eb0bea1b04df31f1f/libjaas_unix.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjava.so": {"downloads": {"lzma": {"sha1": "5eee7a42600a44a8bb8d6d7f510fd96a29637ac0", "size": 63113, "url": "https://launcher.mojang.com/v1/objects/5eee7a42600a44a8bb8d6d7f510fd96a29637ac0/libjava.so"}, "raw": {"sha1": "e280aeddf3fc0ec664aef7efc0e0e197a54aaf02", "size": 227672, "url": "https://launcher.mojang.com/v1/objects/e280aeddf3fc0ec664aef7efc0e0e197a54aaf02/libjava.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjava_crw_demo.so": {"downloads": {"lzma": {"sha1": "b197cf23ae3556eb0b45c663f0a8cb62408b961e", "size": 10412, "url": "https://launcher.mojang.com/v1/objects/b197cf23ae3556eb0b45c663f0a8cb62408b961e/libjava_crw_demo.so"}, "raw": {"sha1": "18f20f906977c90d0090b41dbda8dd5cfead5a4c", "size": 26144, "url": "https://launcher.mojang.com/v1/objects/18f20f906977c90d0090b41dbda8dd5cfead5a4c/libjava_crw_demo.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font.so": {"downloads": {"lzma": {"sha1": "ffbba0e5022f829412b86063d8a90f95f16709b1", "size": 5608, "url": "https://launcher.mojang.com/v1/objects/ffbba0e5022f829412b86063d8a90f95f16709b1/libjavafx_font.so"}, "raw": {"sha1": "8634a0aca612fc40420a4a7cc8af4cc46cfc6725", "size": 17104, "url": "https://launcher.mojang.com/v1/objects/8634a0aca612fc40420a4a7cc8af4cc46cfc6725/libjavafx_font.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_freetype.so": {"downloads": {"lzma": {"sha1": "310271eda8a2ac264ffc3640a9d847b49438d0bd", "size": 6942, "url": "https://launcher.mojang.com/v1/objects/310271eda8a2ac264ffc3640a9d847b49438d0bd/libjavafx_font_freetype.so"}, "raw": {"sha1": "3e7572d047c12ba2bc43acec7f98a67c20af8042", "size": 27616, "url": "https://launcher.mojang.com/v1/objects/3e7572d047c12ba2bc43acec7f98a67c20af8042/libjavafx_font_freetype.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_pango.so": {"downloads": {"lzma": {"sha1": "a7bcf0669e70b0f43099a99c81e6b6440cb40ac0", "size": 5820, "url": "https://launcher.mojang.com/v1/objects/a7bcf0669e70b0f43099a99c81e6b6440cb40ac0/libjavafx_font_pango.so"}, "raw": {"sha1": "f0b775cc9a514c7ee8b4d6fb300653ce548caf10", "size": 25560, "url": "https://launcher.mojang.com/v1/objects/f0b775cc9a514c7ee8b4d6fb300653ce548caf10/libjavafx_font_pango.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_font_t2k.so": {"downloads": {"lzma": {"sha1": "551c29dc7c7fc83223aa36a6187f7e0c5d650538", "size": 431450, "url": "https://launcher.mojang.com/v1/objects/551c29dc7c7fc83223aa36a6187f7e0c5d650538/libjavafx_font_t2k.so"}, "raw": {"sha1": "91e5813057c3b852d411540160f8ad05fb9f1ed3", "size": 1486128, "url": "https://launcher.mojang.com/v1/objects/91e5813057c3b852d411540160f8ad05fb9f1ed3/libjavafx_font_t2k.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjavafx_iio.so": {"downloads": {"lzma": {"sha1": "c832998fd5e06ed6dcd6428816194c350785420c", "size": 101479, "url": "https://launcher.mojang.com/v1/objects/c832998fd5e06ed6dcd6428816194c350785420c/libjavafx_iio.so"}, "raw": {"sha1": "dcdf68cb25677b76c1cf0bb94294e6e9880a6678", "size": 256336, "url": "https://launcher.mojang.com/v1/objects/dcdf68cb25677b76c1cf0bb94294e6e9880a6678/libjavafx_iio.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjawt.so": {"downloads": {"lzma": {"sha1": "c1ced6aad5c69ff444dc67d0fd7e333558953831", "size": 1872, "url": "https://launcher.mojang.com/v1/objects/c1ced6aad5c69ff444dc67d0fd7e333558953831/libjawt.so"}, "raw": {"sha1": "c5032f2c6fa40bea24e56605cf76b26a27e87b67", "size": 8048, "url": "https://launcher.mojang.com/v1/objects/c5032f2c6fa40bea24e56605cf76b26a27e87b67/libjawt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjdwp.so": {"downloads": {"lzma": {"sha1": "c1aabbb3f5a624b9ad10ed871a1d83510a99b646", "size": 94884, "url": "https://launcher.mojang.com/v1/objects/c1aabbb3f5a624b9ad10ed871a1d83510a99b646/libjdwp.so"}, "raw": {"sha1": "a043e97be47937f6f552e94cf79c76c1c57f9594", "size": 272248, "url": "https://launcher.mojang.com/v1/objects/a043e97be47937f6f552e94cf79c76c1c57f9594/libjdwp.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfr.so": {"downloads": {"lzma": {"sha1": "11b8e6bfffdccbacbf9dd29dea4b90b753f3c1b7", "size": 8780, "url": "https://launcher.mojang.com/v1/objects/11b8e6bfffdccbacbf9dd29dea4b90b753f3c1b7/libjfr.so"}, "raw": {"sha1": "312392dd186b11c418183e818f1928e8685a07e5", "size": 28384, "url": "https://launcher.mojang.com/v1/objects/312392dd186b11c418183e818f1928e8685a07e5/libjfr.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfxmedia.so": {"downloads": {"lzma": {"sha1": "a4e7a126eb648ce6e5e6dc151831da37d8334139", "size": 391897, "url": "https://launcher.mojang.com/v1/objects/a4e7a126eb648ce6e5e6dc151831da37d8334139/libjfxmedia.so"}, "raw": {"sha1": "5fa54944327a6012c3d34cb5c1c4432762178dc8", "size": 1636376, "url": "https://launcher.mojang.com/v1/objects/5fa54944327a6012c3d34cb5c1c4432762178dc8/libjfxmedia.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjfxwebkit.so": {"downloads": {"lzma": {"sha1": "b274debd222cdcc2ee84160ebb95144b3880bc97", "size": 20492825, "url": "https://launcher.mojang.com/v1/objects/b274debd222cdcc2ee84160ebb95144b3880bc97/libjfxwebkit.so"}, "raw": {"sha1": "ecee564c3b2f645131b35bb3004abd4caeabd291", "size": 91014584, "url": "https://launcher.mojang.com/v1/objects/ecee564c3b2f645131b35bb3004abd4caeabd291/libjfxwebkit.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjpeg.so": {"downloads": {"lzma": {"sha1": "9ad55e370c5eaaa73c3158339db3c368b1aaf0cb", "size": 113072, "url": "https://launcher.mojang.com/v1/objects/9ad55e370c5eaaa73c3158339db3c368b1aaf0cb/libjpeg.so"}, "raw": {"sha1": "651e6d53ae67db1f0efbf7f104447a9b49b7e333", "size": 292520, "url": "https://launcher.mojang.com/v1/objects/651e6d53ae67db1f0efbf7f104447a9b49b7e333/libjpeg.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsdt.so": {"downloads": {"lzma": {"sha1": "04b6d1361a34c496b5f652b2477784d69b8b6baf", "size": 3964, "url": "https://launcher.mojang.com/v1/objects/04b6d1361a34c496b5f652b2477784d69b8b6baf/libjsdt.so"}, "raw": {"sha1": "82b48a82bf6183d34cf00a0f81661b45c616f31b", "size": 12904, "url": "https://launcher.mojang.com/v1/objects/82b48a82bf6183d34cf00a0f81661b45c616f31b/libjsdt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsig.so": {"downloads": {"lzma": {"sha1": "37d3b89abde397216cc4ecb1339d8543d99b8428", "size": 3536, "url": "https://launcher.mojang.com/v1/objects/37d3b89abde397216cc4ecb1339d8543d99b8428/libjsig.so"}, "raw": {"sha1": "42e52ba1bcbe0362ab24bcf65c93797354db6fb9", "size": 13336, "url": "https://launcher.mojang.com/v1/objects/42e52ba1bcbe0362ab24bcf65c93797354db6fb9/libjsig.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsound.so": {"downloads": {"lzma": {"sha1": "7e3c565d74d8ffae716f32b05544fa4a6f108adc", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7e3c565d74d8ffae716f32b05544fa4a6f108adc/libjsound.so"}, "raw": {"sha1": "0c0fc63b92d7b83c9960fa80d45c80553ea20254", "size": 8232, "url": "https://launcher.mojang.com/v1/objects/0c0fc63b92d7b83c9960fa80d45c80553ea20254/libjsound.so"}}, "executable": true, "type": "file"}, "lib/amd64/libjsoundalsa.so": {"downloads": {"lzma": {"sha1": "b06c51858a25ff776519495f1b9b3d9f604b089f", "size": 23097, "url": "https://launcher.mojang.com/v1/objects/b06c51858a25ff776519495f1b9b3d9f604b089f/libjsoundalsa.so"}, "raw": {"sha1": "281d37f0326d4a12dc7ea316ead09c198ff7bdf7", "size": 83256, "url": "https://launcher.mojang.com/v1/objects/281d37f0326d4a12dc7ea316ead09c198ff7bdf7/libjsoundalsa.so"}}, "executable": true, "type": "file"}, "lib/amd64/liblcms.so": {"downloads": {"lzma": {"sha1": "7a239baba2086cae49114b382b74b971da02f08e", "size": 176175, "url": "https://launcher.mojang.com/v1/objects/7a239baba2086cae49114b382b74b971da02f08e/liblcms.so"}, "raw": {"sha1": "c8895cc3c3d023d9e059225969ab67954772c0a1", "size": 526872, "url": "https://launcher.mojang.com/v1/objects/c8895cc3c3d023d9e059225969ab67954772c0a1/liblcms.so"}}, "executable": true, "type": "file"}, "lib/amd64/libmanagement.so": {"downloads": {"lzma": {"sha1": "aed3fdbcefd1716abfc6a306687c8b741cbb318e", "size": 12838, "url": "https://launcher.mojang.com/v1/objects/aed3fdbcefd1716abfc6a306687c8b741cbb318e/libmanagement.so"}, "raw": {"sha1": "eba35f61e0d50e30874b7c7b335edf2d52662423", "size": 51808, "url": "https://launcher.mojang.com/v1/objects/eba35f61e0d50e30874b7c7b335edf2d52662423/libmanagement.so"}}, "executable": true, "type": "file"}, "lib/amd64/libmlib_image.so": {"downloads": {"lzma": {"sha1": "1bb181f079492d55c7a458e96488cd17fe0a7b86", "size": 310272, "url": "https://launcher.mojang.com/v1/objects/1bb181f079492d55c7a458e96488cd17fe0a7b86/libmlib_image.so"}, "raw": {"sha1": "c973c450d33873675945d4694be484e3427f58f1", "size": 1048136, "url": "https://launcher.mojang.com/v1/objects/c973c450d33873675945d4694be484e3427f58f1/libmlib_image.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnet.so": {"downloads": {"lzma": {"sha1": "9dd79703b6deb86e0321afe01c6ac508263c8312", "size": 38123, "url": "https://launcher.mojang.com/v1/objects/9dd79703b6deb86e0321afe01c6ac508263c8312/libnet.so"}, "raw": {"sha1": "b3a17b7d53fcdf1e689e1ec29ce851eee6022ead", "size": 116920, "url": "https://launcher.mojang.com/v1/objects/b3a17b7d53fcdf1e689e1ec29ce851eee6022ead/libnet.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnio.so": {"downloads": {"lzma": {"sha1": "5697c89d5d5d9b74f2e1555fcbba79dd4049e287", "size": 24445, "url": "https://launcher.mojang.com/v1/objects/5697c89d5d5d9b74f2e1555fcbba79dd4049e287/libnio.so"}, "raw": {"sha1": "573bf8f64dbcc397f8abd3e1da28f90ab0679f5b", "size": 93872, "url": "https://launcher.mojang.com/v1/objects/573bf8f64dbcc397f8abd3e1da28f90ab0679f5b/libnio.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnpjp2.so": {"downloads": {"lzma": {"sha1": "6fe53b5951ff740e7f2ef7ffe5975af26da06718", "size": 57892, "url": "https://launcher.mojang.com/v1/objects/6fe53b5951ff740e7f2ef7ffe5975af26da06718/libnpjp2.so"}, "raw": {"sha1": "2bb13c53a4280379253475e51216b97eed1d4ce3", "size": 216592, "url": "https://launcher.mojang.com/v1/objects/2bb13c53a4280379253475e51216b97eed1d4ce3/libnpjp2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libnpt.so": {"downloads": {"lzma": {"sha1": "1b170b09a32b1b8b6624fa5d1f94ec60b2bf3876", "size": 5070, "url": "https://launcher.mojang.com/v1/objects/1b170b09a32b1b8b6624fa5d1f94ec60b2bf3876/libnpt.so"}, "raw": {"sha1": "6b1ff6b9b4624f3cc7801f221c82b8046fb76364", "size": 17504, "url": "https://launcher.mojang.com/v1/objects/6b1ff6b9b4624f3cc7801f221c82b8046fb76364/libnpt.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_common.so": {"downloads": {"lzma": {"sha1": "f4aca04c90bc7505851c074a08b2c31cae1f94fa", "size": 23315, "url": "https://launcher.mojang.com/v1/objects/f4aca04c90bc7505851c074a08b2c31cae1f94fa/libprism_common.so"}, "raw": {"sha1": "b00866b6ed8646a29a334d46e297267552f27de8", "size": 59008, "url": "https://launcher.mojang.com/v1/objects/b00866b6ed8646a29a334d46e297267552f27de8/libprism_common.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_es2.so": {"downloads": {"lzma": {"sha1": "7ff4173c338c7a6f370f231670055737e032da3e", "size": 18416, "url": "https://launcher.mojang.com/v1/objects/7ff4173c338c7a6f370f231670055737e032da3e/libprism_es2.so"}, "raw": {"sha1": "1390a1dc14345e5a948148e59195d62f3a83863f", "size": 63808, "url": "https://launcher.mojang.com/v1/objects/1390a1dc14345e5a948148e59195d62f3a83863f/libprism_es2.so"}}, "executable": true, "type": "file"}, "lib/amd64/libprism_sw.so": {"downloads": {"lzma": {"sha1": "6728e8bf7b214067d715be6fe0325910d63c2468", "size": 29457, "url": "https://launcher.mojang.com/v1/objects/6728e8bf7b214067d715be6fe0325910d63c2468/libprism_sw.so"}, "raw": {"sha1": "7a6c34cb2bbcde411778d1b3f8677c39e32c3ac4", "size": 71608, "url": "https://launcher.mojang.com/v1/objects/7a6c34cb2bbcde411778d1b3f8677c39e32c3ac4/libprism_sw.so"}}, "executable": true, "type": "file"}, "lib/amd64/libresource.so": {"downloads": {"lzma": {"sha1": "1e35e63f1e74915fba620f1febf420b919d49bc5", "size": 2633, "url": "https://launcher.mojang.com/v1/objects/1e35e63f1e74915fba620f1febf420b919d49bc5/libresource.so"}, "raw": {"sha1": "57490353ad0d83ab1930355213dea269795434fe", "size": 13456, "url": "https://launcher.mojang.com/v1/objects/57490353ad0d83ab1930355213dea269795434fe/libresource.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsctp.so": {"downloads": {"lzma": {"sha1": "4340132ed250d7849a016e071be773eaedd33aa8", "size": 9332, "url": "https://launcher.mojang.com/v1/objects/4340132ed250d7849a016e071be773eaedd33aa8/libsctp.so"}, "raw": {"sha1": "4a80e743750f127c0d5a359f5cd60b97e7ee12ae", "size": 29552, "url": "https://launcher.mojang.com/v1/objects/4a80e743750f127c0d5a359f5cd60b97e7ee12ae/libsctp.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsplashscreen.so": {"downloads": {"lzma": {"sha1": "b226c8dbd73a548fc2b042ee6db6cc80e727597c", "size": 190305, "url": "https://launcher.mojang.com/v1/objects/b226c8dbd73a548fc2b042ee6db6cc80e727597c/libsplashscreen.so"}, "raw": {"sha1": "87d6a491f5ba7e6c4d972264a0c9063afea567a2", "size": 441376, "url": "https://launcher.mojang.com/v1/objects/87d6a491f5ba7e6c4d972264a0c9063afea567a2/libsplashscreen.so"}}, "executable": true, "type": "file"}, "lib/amd64/libsunec.so": {"downloads": {"lzma": {"sha1": "6ebba98fab1e3d872d1363235b76497f6f9babdc", "size": 88829, "url": "https://launcher.mojang.com/v1/objects/6ebba98fab1e3d872d1363235b76497f6f9babdc/libsunec.so"}, "raw": {"sha1": "3b262a0a530f6e4e539aed2cd27b4de1d0ed8859", "size": 283368, "url": "https://launcher.mojang.com/v1/objects/3b262a0a530f6e4e539aed2cd27b4de1d0ed8859/libsunec.so"}}, "executable": true, "type": "file"}, "lib/amd64/libt2k.so": {"downloads": {"lzma": {"sha1": "602cb812ef0b350ccf56ce209a260ddbe3743d92", "size": 190720, "url": "https://launcher.mojang.com/v1/objects/602cb812ef0b350ccf56ce209a260ddbe3743d92/libt2k.so"}, "raw": {"sha1": "b072c56df997f61e15e6b5a43b8907b0d25c2043", "size": 504840, "url": "https://launcher.mojang.com/v1/objects/b072c56df997f61e15e6b5a43b8907b0d25c2043/libt2k.so"}}, "executable": true, "type": "file"}, "lib/amd64/libunpack.so": {"downloads": {"lzma": {"sha1": "7107b615e941074f0b14c31c88fb67200aacd37f", "size": 70308, "url": "https://launcher.mojang.com/v1/objects/7107b615e941074f0b14c31c88fb67200aacd37f/libunpack.so"}, "raw": {"sha1": "b05ff862ed87928ed91e80e5604673c5ea710a53", "size": 197712, "url": "https://launcher.mojang.com/v1/objects/b05ff862ed87928ed91e80e5604673c5ea710a53/libunpack.so"}}, "executable": true, "type": "file"}, "lib/amd64/libverify.so": {"downloads": {"lzma": {"sha1": "ecd98efb8c7da441a8c3580e8f5598f3cb4165b1", "size": 21885, "url": "https://launcher.mojang.com/v1/objects/ecd98efb8c7da441a8c3580e8f5598f3cb4165b1/libverify.so"}, "raw": {"sha1": "e2c8d92531c45ab9be69ffb72c87fa12e9e59827", "size": 66112, "url": "https://launcher.mojang.com/v1/objects/e2c8d92531c45ab9be69ffb72c87fa12e9e59827/libverify.so"}}, "executable": true, "type": "file"}, "lib/amd64/libzip.so": {"downloads": {"lzma": {"sha1": "7c562342e3f7b138dc978495447e3e6a96c2cf45", "size": 54876, "url": "https://launcher.mojang.com/v1/objects/7c562342e3f7b138dc978495447e3e6a96c2cf45/libzip.so"}, "raw": {"sha1": "5f4bf35a5c3e8f8c650e891d1031589b8ab6d77f", "size": 127016, "url": "https://launcher.mojang.com/v1/objects/5f4bf35a5c3e8f8c650e891d1031589b8ab6d77f/libzip.so"}}, "executable": true, "type": "file"}, "lib/amd64/server": {"type": "directory"}, "lib/amd64/server/Xusage.txt": {"downloads": {"lzma": {"sha1": "acb2da24a4c765887df83985e4c26d6be302a0a3", "size": 629, "url": "https://launcher.mojang.com/v1/objects/acb2da24a4c765887df83985e4c26d6be302a0a3/Xusage.txt"}, "raw": {"sha1": "6983727eafe140f9dd793c78aa6f3e007438243a", "size": 1423, "url": "https://launcher.mojang.com/v1/objects/6983727eafe140f9dd793c78aa6f3e007438243a/Xusage.txt"}}, "executable": false, "type": "file"}, "lib/amd64/server/libjsig.so": {"target": "../libjsig.so", "type": "link"}, "lib/amd64/server/libjvm.so": {"downloads": {"lzma": {"sha1": "d5c6f3338aaa6712f79d680ac8c3e31beebaa886", "size": 4154311, "url": "https://launcher.mojang.com/v1/objects/d5c6f3338aaa6712f79d680ac8c3e31beebaa886/libjvm.so"}, "raw": {"sha1": "23a98e1eb505cc3fb91bc0cb2adb71ab9270e9ca", "size": 17045016, "url": "https://launcher.mojang.com/v1/objects/23a98e1eb505cc3fb91bc0cb2adb71ab9270e9ca/libjvm.so"}}, "executable": true, "type": "file"}, "lib/applet": {"type": "directory"}, "lib/calendars.properties": {"downloads": {"lzma": {"sha1": "4a757c23f2942bd802a4f80235131146d9267750", "size": 558, "url": "https://launcher.mojang.com/v1/objects/4a757c23f2942bd802a4f80235131146d9267750/calendars.properties"}, "raw": {"sha1": "42ebb0988124433b8f2a6e5d9a74ed41240bcfc6", "size": 1378, "url": "https://launcher.mojang.com/v1/objects/42ebb0988124433b8f2a6e5d9a74ed41240bcfc6/calendars.properties"}}, "executable": false, "type": "file"}, "lib/charsets.jar": {"downloads": {"lzma": {"sha1": "2bf44143b2ad9d7d55045a4de4a562330c194dc0", "size": 412367, "url": "https://launcher.mojang.com/v1/objects/2bf44143b2ad9d7d55045a4de4a562330c194dc0/charsets.jar"}, "raw": {"sha1": "d73ab9f8de255a7e112ddd13622bf7f6b18c8447", "size": 3135615, "url": "https://launcher.mojang.com/v1/objects/d73ab9f8de255a7e112ddd13622bf7f6b18c8447/charsets.jar"}}, "executable": false, "type": "file"}, "lib/classlist": {"downloads": {"lzma": {"sha1": "14e7c73d21b8513b0aff8d86e5cb34c52021fbca", "size": 15024, "url": "https://launcher.mojang.com/v1/objects/14e7c73d21b8513b0aff8d86e5cb34c52021fbca/classlist"}, "raw": {"sha1": "9c0404b63c87e2fed35e3a6cd137d6cf876c42bd", "size": 84311, "url": "https://launcher.mojang.com/v1/objects/9c0404b63c87e2fed35e3a6cd137d6cf876c42bd/classlist"}}, "executable": false, "type": "file"}, "lib/cmm": {"type": "directory"}, "lib/cmm/CIEXYZ.pf": {"downloads": {"lzma": {"sha1": "fcc5ca2fd3f45cac3434b480fa3ce00007e96529", "size": 8964, "url": "https://launcher.mojang.com/v1/objects/fcc5ca2fd3f45cac3434b480fa3ce00007e96529/CIEXYZ.pf"}, "raw": {"sha1": "b7779924c70554647b87c2a86159ca7781e929f8", "size": 51236, "url": "https://launcher.mojang.com/v1/objects/b7779924c70554647b87c2a86159ca7781e929f8/CIEXYZ.pf"}}, "executable": false, "type": "file"}, "lib/cmm/GRAY.pf": {"downloads": {"lzma": {"sha1": "5388ccfe67d3131d6d02143d8e8895003ab14ff6", "size": 299, "url": "https://launcher.mojang.com/v1/objects/5388ccfe67d3131d6d02143d8e8895003ab14ff6/GRAY.pf"}, "raw": {"sha1": "27f93961d66b8230d0cdb8b166bc8b4153d5bc2d", "size": 632, "url": "https://launcher.mojang.com/v1/objects/27f93961d66b8230d0cdb8b166bc8b4153d5bc2d/GRAY.pf"}}, "executable": false, "type": "file"}, "lib/cmm/LINEAR_RGB.pf": {"downloads": {"lzma": {"sha1": "2bd90f09c8deb64b1729d6b8173c78f9e9cab27b", "size": 678, "url": "https://launcher.mojang.com/v1/objects/2bd90f09c8deb64b1729d6b8173c78f9e9cab27b/LINEAR_RGB.pf"}, "raw": {"sha1": "7913274c2f73bafcf888f09ff60990b100214ede", "size": 1044, "url": "https://launcher.mojang.com/v1/objects/7913274c2f73bafcf888f09ff60990b100214ede/LINEAR_RGB.pf"}}, "executable": false, "type": "file"}, "lib/cmm/PYCC.pf": {"downloads": {"lzma": {"sha1": "dbb2197ecff3fcdd142e9006490c8cb5c8d19af8", "size": 171521, "url": "https://launcher.mojang.com/v1/objects/dbb2197ecff3fcdd142e9006490c8cb5c8d19af8/PYCC.pf"}, "raw": {"sha1": "4f7eed05b8f0eea7bcdc8f8f7aaeb1925ce7b144", "size": 274474, "url": "https://launcher.mojang.com/v1/objects/4f7eed05b8f0eea7bcdc8f8f7aaeb1925ce7b144/PYCC.pf"}}, "executable": false, "type": "file"}, "lib/cmm/sRGB.pf": {"downloads": {"raw": {"sha1": "9eaea0911d89d63e39e95f2e2116eaec7e0bb91e", "size": 3144, "url": "https://launcher.mojang.com/v1/objects/9eaea0911d89d63e39e95f2e2116eaec7e0bb91e/sRGB.pf"}}, "executable": false, "type": "file"}, "lib/content-types.properties": {"downloads": {"lzma": {"sha1": "43a23d9a6c637c128b14cfa3feced93cbcf85b1a", "size": 1617, "url": "https://launcher.mojang.com/v1/objects/43a23d9a6c637c128b14cfa3feced93cbcf85b1a/content-types.properties"}, "raw": {"sha1": "b21698017c4a2866b5fabe59681b7592e72c83b1", "size": 5916, "url": "https://launcher.mojang.com/v1/objects/b21698017c4a2866b5fabe59681b7592e72c83b1/content-types.properties"}}, "executable": false, "type": "file"}, "lib/currency.data": {"downloads": {"lzma": {"sha1": "451b3f166ea34ef2aefbb01606ea5adcc0d65b42", "size": 1184, "url": "https://launcher.mojang.com/v1/objects/451b3f166ea34ef2aefbb01606ea5adcc0d65b42/currency.data"}, "raw": {"sha1": "bf524381a7a9b9d5bbab48069c583d2936e367a1", "size": 4134, "url": "https://launcher.mojang.com/v1/objects/bf524381a7a9b9d5bbab48069c583d2936e367a1/currency.data"}}, "executable": false, "type": "file"}, "lib/deploy": {"type": "directory"}, "lib/deploy.jar": {"downloads": {"raw": {"sha1": "fbe1de8fcd9a3d482c59414dce9311e4194766c9", "size": 2255881, "url": "https://launcher.mojang.com/v1/objects/fbe1de8fcd9a3d482c59414dce9311e4194766c9/deploy.jar"}}, "executable": false, "type": "file"}, "lib/deploy/MixedCodeMainDialog.ui": {"downloads": {"lzma": {"sha1": "7d812964343d1e978442f5c847c709784fc18fc0", "size": 683, "url": "https://launcher.mojang.com/v1/objects/7d812964343d1e978442f5c847c709784fc18fc0/MixedCodeMainDialog.ui"}, "raw": {"sha1": "c9b1af1c229e54b2d8a3d642d4f0bb31dc15be79", "size": 4507, "url": "https://launcher.mojang.com/v1/objects/c9b1af1c229e54b2d8a3d642d4f0bb31dc15be79/MixedCodeMainDialog.ui"}}, "executable": false, "type": "file"}, "lib/deploy/MixedCodeMainDialogJs.ui": {"downloads": {"lzma": {"sha1": "54fb58dbcc59e35e0ae896d0e266ae0c5bcf85c2", "size": 792, "url": "https://launcher.mojang.com/v1/objects/54fb58dbcc59e35e0ae896d0e266ae0c5bcf85c2/MixedCodeMainDialogJs.ui"}, "raw": {"sha1": "ad6337fb6d46750e14c12b439a5856f4b6864d0d", "size": 6110, "url": "https://launcher.mojang.com/v1/objects/ad6337fb6d46750e14c12b439a5856f4b6864d0d/MixedCodeMainDialogJs.ui"}}, "executable": false, "type": "file"}, "lib/deploy/cautionshield.icns": {"downloads": {"lzma": {"sha1": "7cea751dc168605054ec38ce8bfa71812be405c1", "size": 2333, "url": "https://launcher.mojang.com/v1/objects/7cea751dc168605054ec38ce8bfa71812be405c1/cautionshield.icns"}, "raw": {"sha1": "1de7ed5d5fc75aa1bcede088c655bee3bde64c38", "size": 3588, "url": "https://launcher.mojang.com/v1/objects/1de7ed5d5fc75aa1bcede088c655bee3bde64c38/cautionshield.icns"}}, "executable": false, "type": "file"}, "lib/deploy/ffjcext.zip": {"downloads": {"lzma": {"sha1": "80bcb9b3794f69d87dba93e90230f288e651e798", "size": 1809, "url": "https://launcher.mojang.com/v1/objects/80bcb9b3794f69d87dba93e90230f288e651e798/ffjcext.zip"}, "raw": {"sha1": "76d051ca7d3666ff25ea8eb9957a05574a45287f", "size": 13454, "url": "https://launcher.mojang.com/v1/objects/76d051ca7d3666ff25ea8eb9957a05574a45287f/ffjcext.zip"}}, "executable": false, "type": "file"}, "lib/deploy/java-icon.ico": {"downloads": {"lzma": {"sha1": "2a24f0207d7ab5976a8b0d92b4b381d49e895c9d", "size": 8468, "url": "https://launcher.mojang.com/v1/objects/2a24f0207d7ab5976a8b0d92b4b381d49e895c9d/java-icon.ico"}, "raw": {"sha1": "2997ceb26ff49a7d7c5e7a2405b5fb50b62c7914", "size": 29926, "url": "https://launcher.mojang.com/v1/objects/2997ceb26ff49a7d7c5e7a2405b5fb50b62c7914/java-icon.ico"}}, "executable": false, "type": "file"}, "lib/deploy/messages.properties": {"downloads": {"lzma": {"sha1": "c1e16f80dc0b1f1a591cecf3cbab4ba5e47492f4", "size": 1225, "url": "https://launcher.mojang.com/v1/objects/c1e16f80dc0b1f1a591cecf3cbab4ba5e47492f4/messages.properties"}, "raw": {"sha1": "dc52841c708e3c1eb2a044088a43396d1291bb5e", "size": 2860, "url": "https://launcher.mojang.com/v1/objects/dc52841c708e3c1eb2a044088a43396d1291bb5e/messages.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_de.properties": {"downloads": {"lzma": {"sha1": "42b42e6e1d2cb2d781f2226bde612ce519b29bc8", "size": 1394, "url": "https://launcher.mojang.com/v1/objects/42b42e6e1d2cb2d781f2226bde612ce519b29bc8/messages_de.properties"}, "raw": {"sha1": "d989fe1b8f7904888d5102294ebefd28d932ecdb", "size": 3306, "url": "https://launcher.mojang.com/v1/objects/d989fe1b8f7904888d5102294ebefd28d932ecdb/messages_de.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_es.properties": {"downloads": {"lzma": {"sha1": "c4a653e9802ca982e892b45d88c1e259c09e8c8e", "size": 1404, "url": "https://launcher.mojang.com/v1/objects/c4a653e9802ca982e892b45d88c1e259c09e8c8e/messages_es.properties"}, "raw": {"sha1": "1b0334b79db481c3a59be6915d5118d760c97baa", "size": 3600, "url": "https://launcher.mojang.com/v1/objects/1b0334b79db481c3a59be6915d5118d760c97baa/messages_es.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_fr.properties": {"downloads": {"lzma": {"sha1": "2d8dee07e3f5aab7318a22e169810b216ac44f97", "size": 1401, "url": "https://launcher.mojang.com/v1/objects/2d8dee07e3f5aab7318a22e169810b216ac44f97/messages_fr.properties"}, "raw": {"sha1": "69bd2d03c2064f8679de5b4e430ea61b567c69c5", "size": 3409, "url": "https://launcher.mojang.com/v1/objects/69bd2d03c2064f8679de5b4e430ea61b567c69c5/messages_fr.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_it.properties": {"downloads": {"lzma": {"sha1": "7c28cdd8d9e34355ba0fc03004c4f64749cae57e", "size": 1375, "url": "https://launcher.mojang.com/v1/objects/7c28cdd8d9e34355ba0fc03004c4f64749cae57e/messages_it.properties"}, "raw": {"sha1": "dbe49949308f28540a42ae6cd2ad58afbf615592", "size": 3223, "url": "https://launcher.mojang.com/v1/objects/dbe49949308f28540a42ae6cd2ad58afbf615592/messages_it.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_ja.properties": {"downloads": {"lzma": {"sha1": "9a6a4c086e48b9e615b72b6bbebb3c724d178ff4", "size": 1680, "url": "https://launcher.mojang.com/v1/objects/9a6a4c086e48b9e615b72b6bbebb3c724d178ff4/messages_ja.properties"}, "raw": {"sha1": "751170a7cdefcb1226604ac3f8196e06a04fd7ac", "size": 6349, "url": "https://launcher.mojang.com/v1/objects/751170a7cdefcb1226604ac3f8196e06a04fd7ac/messages_ja.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_ko.properties": {"downloads": {"lzma": {"sha1": "0c57c2ebfa0830f816657a384898487fc492efac", "size": 1645, "url": "https://launcher.mojang.com/v1/objects/0c57c2ebfa0830f816657a384898487fc492efac/messages_ko.properties"}, "raw": {"sha1": "bf9e055b5ab138ad6d49769e2b7630b7938848d6", "size": 5712, "url": "https://launcher.mojang.com/v1/objects/bf9e055b5ab138ad6d49769e2b7630b7938848d6/messages_ko.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_pt_BR.properties": {"downloads": {"lzma": {"sha1": "f8364dba0aa0a7e445a1a8d0e7ad66b996f70063", "size": 1388, "url": "https://launcher.mojang.com/v1/objects/f8364dba0aa0a7e445a1a8d0e7ad66b996f70063/messages_pt_BR.properties"}, "raw": {"sha1": "24e4951743521ab9a11381c77bd0cdb1ed30f5b5", "size": 3285, "url": "https://launcher.mojang.com/v1/objects/24e4951743521ab9a11381c77bd0cdb1ed30f5b5/messages_pt_BR.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_sv.properties": {"downloads": {"lzma": {"sha1": "65e5897d552258141aacf02f087c7c9c33ad0727", "size": 1355, "url": "https://launcher.mojang.com/v1/objects/65e5897d552258141aacf02f087c7c9c33ad0727/messages_sv.properties"}, "raw": {"sha1": "bb5a4aa0ba499f6b1916a83e3c7922a4583b4adb", "size": 3384, "url": "https://launcher.mojang.com/v1/objects/bb5a4aa0ba499f6b1916a83e3c7922a4583b4adb/messages_sv.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_CN.properties": {"downloads": {"lzma": {"sha1": "de7d39a6e6748e9f47e842c9da90f515921c222c", "size": 1506, "url": "https://launcher.mojang.com/v1/objects/de7d39a6e6748e9f47e842c9da90f515921c222c/messages_zh_CN.properties"}, "raw": {"sha1": "1c2b96673dddd3596890ef4fc22017d484a1f652", "size": 4072, "url": "https://launcher.mojang.com/v1/objects/1c2b96673dddd3596890ef4fc22017d484a1f652/messages_zh_CN.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_HK.properties": {"downloads": {"lzma": {"sha1": "e8d0e3a63caa2535a4f361033941f34dcc170a7e", "size": 1529, "url": "https://launcher.mojang.com/v1/objects/e8d0e3a63caa2535a4f361033941f34dcc170a7e/messages_zh_TW.properties"}, "raw": {"sha1": "37a57aad121c14c25e149206179728fa62203bf0", "size": 3752, "url": "https://launcher.mojang.com/v1/objects/37a57aad121c14c25e149206179728fa62203bf0/messages_zh_TW.properties"}}, "executable": false, "type": "file"}, "lib/deploy/messages_zh_TW.properties": {"downloads": {"lzma": {"sha1": "e8d0e3a63caa2535a4f361033941f34dcc170a7e", "size": 1529, "url": "https://launcher.mojang.com/v1/objects/e8d0e3a63caa2535a4f361033941f34dcc170a7e/messages_zh_TW.properties"}, "raw": {"sha1": "37a57aad121c14c25e149206179728fa62203bf0", "size": 3752, "url": "https://launcher.mojang.com/v1/objects/37a57aad121c14c25e149206179728fa62203bf0/messages_zh_TW.properties"}}, "executable": false, "type": "file"}, "lib/deploy/mixcode_s.png": {"downloads": {"raw": {"sha1": "4604e9f265eec97bccd0151c3a81afa9e69499e5", "size": 3115, "url": "https://launcher.mojang.com/v1/objects/4604e9f265eec97bccd0151c3a81afa9e69499e5/mixcode_s.png"}}, "executable": false, "type": "file"}, "lib/deploy/splash.gif": {"downloads": {"raw": {"sha1": "20e7aec75f6d036d504277542e507eb7dc24aae8", "size": 8590, "url": "https://launcher.mojang.com/v1/objects/20e7aec75f6d036d504277542e507eb7dc24aae8/splash.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash@2x.gif": {"downloads": {"raw": {"sha1": "0ae4a5bda2a6d628fac51462390b503c99509fdc", "size": 15276, "url": "https://launcher.mojang.com/v1/objects/0ae4a5bda2a6d628fac51462390b503c99509fdc/splash2x.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash_11-lic.gif": {"downloads": {"raw": {"sha1": "8def364e07f40142822df84b5bb4f50846cb5e4e", "size": 7805, "url": "https://launcher.mojang.com/v1/objects/8def364e07f40142822df84b5bb4f50846cb5e4e/splash_11-lic.gif"}}, "executable": false, "type": "file"}, "lib/deploy/splash_11@2x-lic.gif": {"downloads": {"raw": {"sha1": "d2bff9bbf7920ca743b81a0ee23b0719b4d057ca", "size": 12250, "url": "https://launcher.mojang.com/v1/objects/d2bff9bbf7920ca743b81a0ee23b0719b4d057ca/splash_11%402x-lic.gif"}}, "executable": false, "type": "file"}, "lib/desktop": {"type": "directory"}, "lib/desktop/applications": {"type": "directory"}, "lib/desktop/applications/sun-java.desktop": {"downloads": {"lzma": {"sha1": "109d1cdf165f38da92da70b403ca940192a7a9a8", "size": 536, "url": "https://launcher.mojang.com/v1/objects/109d1cdf165f38da92da70b403ca940192a7a9a8/sun-java.desktop"}, "raw": {"sha1": "d346dfe90505603ce5aff5a3c6c2e4a23d5bd990", "size": 777, "url": "https://launcher.mojang.com/v1/objects/d346dfe90505603ce5aff5a3c6c2e4a23d5bd990/sun-java.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/applications/sun-javaws.desktop": {"downloads": {"lzma": {"sha1": "5e1815e7f83515881e6998584dc6bb02c5bef09a", "size": 451, "url": "https://launcher.mojang.com/v1/objects/5e1815e7f83515881e6998584dc6bb02c5bef09a/sun-javaws.desktop"}, "raw": {"sha1": "50ce8e519b836e0f53d58ce1a359d98b6cafdda6", "size": 619, "url": "https://launcher.mojang.com/v1/objects/50ce8e519b836e0f53d58ce1a359d98b6cafdda6/sun-javaws.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/applications/sun_java.desktop": {"downloads": {"lzma": {"sha1": "49ab0ccb54c3be68281d05055bc56a88b1281d3c", "size": 447, "url": "https://launcher.mojang.com/v1/objects/49ab0ccb54c3be68281d05055bc56a88b1281d3c/sun_java.desktop"}, "raw": {"sha1": "79120ee8160ad6f3c9b90c2641fb7edf3af96b5d", "size": 624, "url": "https://launcher.mojang.com/v1/objects/79120ee8160ad6f3c9b90c2641fb7edf3af96b5d/sun_java.desktop"}}, "executable": false, "type": "file"}, "lib/desktop/icons": {"type": "directory"}, "lib/desktop/icons/HighContrast": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/apps": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "366e7a48e9e4fb92eaeabbcaeb4626122a66cecb", "size": 417, "url": "https://launcher.mojang.com/v1/objects/366e7a48e9e4fb92eaeabbcaeb4626122a66cecb/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "629c48907368ecf32d2395b6459c367f79d84689", "size": 464, "url": "https://launcher.mojang.com/v1/objects/629c48907368ecf32d2395b6459c367f79d84689/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/apps": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "8373482d072684e09830dbdb97a76ea264c9f4e9", "size": 3451, "url": "https://launcher.mojang.com/v1/objects/8373482d072684e09830dbdb97a76ea264c9f4e9/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrast/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "56a4996519f8f3541eba7b7a7a69bcdcd8ed0410", "size": 2088, "url": "https://launcher.mojang.com/v1/objects/56a4996519f8f3541eba7b7a7a69bcdcd8ed0410/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/apps": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "bf0995acb94aa794e73c5b971282ff13ffe42793", "size": 402, "url": "https://launcher.mojang.com/v1/objects/bf0995acb94aa794e73c5b971282ff13ffe42793/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "1477eceda25e162fcda2e69ee3906091973d8344", "size": 473, "url": "https://launcher.mojang.com/v1/objects/1477eceda25e162fcda2e69ee3906091973d8344/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/apps": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "413da160dd9899b95f53d4cc11f5ee0550cc6585", "size": 3410, "url": "https://launcher.mojang.com/v1/objects/413da160dd9899b95f53d4cc11f5ee0550cc6585/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/HighContrastInverse/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "d66e04dfa25c196bec2e201547325b79846ab674", "size": 2085, "url": "https://launcher.mojang.com/v1/objects/d66e04dfa25c196bec2e201547325b79846ab674/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/apps": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "f93b7cf0a6d27d664a7f09dab6933b2768536f52", "size": 519, "url": "https://launcher.mojang.com/v1/objects/f93b7cf0a6d27d664a7f09dab6933b2768536f52/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "0aa1605877280b88de1f1cc3e7e4bdbeed968a73", "size": 525, "url": "https://launcher.mojang.com/v1/objects/0aa1605877280b88de1f1cc3e7e4bdbeed968a73/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/apps": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "1fcf4fd6da61873b5f21b39412da26509734b7cc", "size": 1507, "url": "https://launcher.mojang.com/v1/objects/1fcf4fd6da61873b5f21b39412da26509734b7cc/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/LowContrast/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "e36636b1c04dc283c18adf669b892d54b15d3ee6", "size": 1948, "url": "https://launcher.mojang.com/v1/objects/e36636b1c04dc283c18adf669b892d54b15d3ee6/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/apps": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/apps/sun-java.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "e91d05bfe9b889bf8a227908b597cab4630da8f2", "size": 383, "url": "https://launcher.mojang.com/v1/objects/e91d05bfe9b889bf8a227908b597cab4630da8f2/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes": {"type": "directory"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/16x16/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "d2f6abe8e498aeecb334fb43f63001d34dbf6ea5", "size": 783, "url": "https://launcher.mojang.com/v1/objects/d2f6abe8e498aeecb334fb43f63001d34dbf6ea5/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/apps": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/apps/sun-java.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/apps/sun-javaws.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/apps/sun-jcontrol.png": {"downloads": {"raw": {"sha1": "6c90a38eaada9c32a678a282be18ec5b43a84264", "size": 1439, "url": "https://launcher.mojang.com/v1/objects/6c90a38eaada9c32a678a282be18ec5b43a84264/sun-javaws.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes": {"type": "directory"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-application-x-java-archive.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-application-x-java-jnlp-file.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/icons/hicolor/48x48/mimetypes/gnome-mime-text-x-java.png": {"downloads": {"raw": {"sha1": "4d5e6e0c41d1076bc86f3ab157c88a41a5716997", "size": 3202, "url": "https://launcher.mojang.com/v1/objects/4d5e6e0c41d1076bc86f3ab157c88a41a5716997/gnome-mime-application-x-java-archive.png"}}, "executable": false, "type": "file"}, "lib/desktop/mime": {"type": "directory"}, "lib/desktop/mime/packages": {"type": "directory"}, "lib/desktop/mime/packages/x-java-archive.xml": {"downloads": {"lzma": {"sha1": "841230729f0a59de2a1071d155d96358232b2ba1", "size": 591, "url": "https://launcher.mojang.com/v1/objects/841230729f0a59de2a1071d155d96358232b2ba1/x-java-archive.xml"}, "raw": {"sha1": "b6297fd36efa799312961f95ebf0c85c920d5037", "size": 1822, "url": "https://launcher.mojang.com/v1/objects/b6297fd36efa799312961f95ebf0c85c920d5037/x-java-archive.xml"}}, "executable": false, "type": "file"}, "lib/desktop/mime/packages/x-java-jnlp-file.xml": {"downloads": {"lzma": {"sha1": "abf9acbe7c18027c4f036c4e42bb2cf1115525fa", "size": 302, "url": "https://launcher.mojang.com/v1/objects/abf9acbe7c18027c4f036c4e42bb2cf1115525fa/x-java-jnlp-file.xml"}, "raw": {"sha1": "72f03da83ddb76c9105f619fcfa4dbdad70e6b30", "size": 412, "url": "https://launcher.mojang.com/v1/objects/72f03da83ddb76c9105f619fcfa4dbdad70e6b30/x-java-jnlp-file.xml"}}, "executable": false, "type": "file"}, "lib/ext": {"type": "directory"}, "lib/ext/cldrdata.jar": {"downloads": {"raw": {"sha1": "6cacc961942d3f02a88907aa8f2eaae8e20c95a0", "size": 3860502, "url": "https://launcher.mojang.com/v1/objects/6cacc961942d3f02a88907aa8f2eaae8e20c95a0/cldrdata.jar"}}, "executable": false, "type": "file"}, "lib/ext/dnsns.jar": {"downloads": {"raw": {"sha1": "93bebdd7514e53ae31d60c5daba673878c8822ec", "size": 8286, "url": "https://launcher.mojang.com/v1/objects/93bebdd7514e53ae31d60c5daba673878c8822ec/dnsns.jar"}}, "executable": false, "type": "file"}, "lib/ext/jaccess.jar": {"downloads": {"raw": {"sha1": "2f54879df7c29ec67c40d40cfc95c0d4a968bea1", "size": 44516, "url": "https://launcher.mojang.com/v1/objects/2f54879df7c29ec67c40d40cfc95c0d4a968bea1/jaccess.jar"}}, "executable": false, "type": "file"}, "lib/ext/jfxrt.jar": {"downloads": {"lzma": {"sha1": "a6c5b6a782ba360ada6651f5322dcab88c75711c", "size": 3374270, "url": "https://launcher.mojang.com/v1/objects/a6c5b6a782ba360ada6651f5322dcab88c75711c/jfxrt.jar"}, "raw": {"sha1": "1ad7a876f045399c23ee4ab7dee380a04ca2ac08", "size": 18508094, "url": "https://launcher.mojang.com/v1/objects/1ad7a876f045399c23ee4ab7dee380a04ca2ac08/jfxrt.jar"}}, "executable": false, "type": "file"}, "lib/ext/localedata.jar": {"downloads": {"raw": {"sha1": "0cc9f550d4e410b5aa29dbfd2c1b5c99391c7f70", "size": 1178926, "url": "https://launcher.mojang.com/v1/objects/0cc9f550d4e410b5aa29dbfd2c1b5c99391c7f70/localedata.jar"}}, "executable": false, "type": "file"}, "lib/ext/meta-index": {"downloads": {"lzma": {"sha1": "1359457529f42bacf495afcb68149ae036442dd9", "size": 594, "url": "https://launcher.mojang.com/v1/objects/1359457529f42bacf495afcb68149ae036442dd9/meta-index"}, "raw": {"sha1": "334649c6e2d5d7248211f30855e97cfcb4558851", "size": 1269, "url": "https://launcher.mojang.com/v1/objects/334649c6e2d5d7248211f30855e97cfcb4558851/meta-index"}}, "executable": false, "type": "file"}, "lib/ext/nashorn.jar": {"downloads": {"raw": {"sha1": "dec5dd17a0f52ae79dfbfb38840bffb8b7a679a5", "size": 2023869, "url": "https://launcher.mojang.com/v1/objects/dec5dd17a0f52ae79dfbfb38840bffb8b7a679a5/nashorn.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunec.jar": {"downloads": {"raw": {"sha1": "bf1c817820341a246f7130fe046e8310b03d04f6", "size": 41672, "url": "https://launcher.mojang.com/v1/objects/bf1c817820341a246f7130fe046e8310b03d04f6/sunec.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunjce_provider.jar": {"downloads": {"raw": {"sha1": "bb3494e4b5cb3c3e60da767207731f18b267cb34", "size": 279326, "url": "https://launcher.mojang.com/v1/objects/bb3494e4b5cb3c3e60da767207731f18b267cb34/sunjce_provider.jar"}}, "executable": false, "type": "file"}, "lib/ext/sunpkcs11.jar": {"downloads": {"raw": {"sha1": "5bb1dedc3344cd3bb86828d4aa8ca82f4a606ed4", "size": 250218, "url": "https://launcher.mojang.com/v1/objects/5bb1dedc3344cd3bb86828d4aa8ca82f4a606ed4/sunpkcs11.jar"}}, "executable": false, "type": "file"}, "lib/ext/zipfs.jar": {"downloads": {"raw": {"sha1": "37b338f0e8e60d6396f51275130e8110816d7b30", "size": 68964, "url": "https://launcher.mojang.com/v1/objects/37b338f0e8e60d6396f51275130e8110816d7b30/zipfs.jar"}}, "executable": false, "type": "file"}, "lib/flavormap.properties": {"downloads": {"lzma": {"sha1": "2d5ef19ee77ccfc95c9413eea155cde59a48fadd", "size": 1541, "url": "https://launcher.mojang.com/v1/objects/2d5ef19ee77ccfc95c9413eea155cde59a48fadd/flavormap.properties"}, "raw": {"sha1": "4e66e8fe12d7f8b3b0c4e1a1915f329bb1fbf6d2", "size": 3901, "url": "https://launcher.mojang.com/v1/objects/4e66e8fe12d7f8b3b0c4e1a1915f329bb1fbf6d2/flavormap.properties"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.5.bfc": {"downloads": {"lzma": {"sha1": "5197f6e387f16458b7408134e38b06f20f625e4c", "size": 795, "url": "https://launcher.mojang.com/v1/objects/5197f6e387f16458b7408134e38b06f20f625e4c/fontconfig.RedHat.5.bfc"}, "raw": {"sha1": "fb806ada6e68f16a9fe2b726a39d9ef5a835c0c2", "size": 4532, "url": "https://launcher.mojang.com/v1/objects/fb806ada6e68f16a9fe2b726a39d9ef5a835c0c2/fontconfig.RedHat.5.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.5.properties.src": {"downloads": {"lzma": {"sha1": "3897ae198e96e5a687c9c9b218ff5df60c868e0d", "size": 1089, "url": "https://launcher.mojang.com/v1/objects/3897ae198e96e5a687c9c9b218ff5df60c868e0d/fontconfig.RedHat.5.properties.src"}, "raw": {"sha1": "c67d1a06cb37b66e69560c9f5e4be7cf08af0493", "size": 8841, "url": "https://launcher.mojang.com/v1/objects/c67d1a06cb37b66e69560c9f5e4be7cf08af0493/fontconfig.RedHat.5.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.6.bfc": {"downloads": {"lzma": {"sha1": "ef2f5d1f8d620be9927db45d3a28bd75777245cb", "size": 818, "url": "https://launcher.mojang.com/v1/objects/ef2f5d1f8d620be9927db45d3a28bd75777245cb/fontconfig.RedHat.6.bfc"}, "raw": {"sha1": "9ba3b3e2c621c31d0ef1b2053c80f77419a19965", "size": 4250, "url": "https://launcher.mojang.com/v1/objects/9ba3b3e2c621c31d0ef1b2053c80f77419a19965/fontconfig.RedHat.6.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.RedHat.6.properties.src": {"downloads": {"lzma": {"sha1": "74f4148f9d7ec3d67bbd724834d478a72cfdb0db", "size": 1111, "url": "https://launcher.mojang.com/v1/objects/74f4148f9d7ec3d67bbd724834d478a72cfdb0db/fontconfig.RedHat.6.properties.src"}, "raw": {"sha1": "768e58d4d314621c38daf9fde6d67119f370acd9", "size": 8735, "url": "https://launcher.mojang.com/v1/objects/768e58d4d314621c38daf9fde6d67119f370acd9/fontconfig.RedHat.6.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.10.bfc": {"downloads": {"lzma": {"sha1": "d8fe9b1d8d02368dcd452de93024c6f60670eb87", "size": 1083, "url": "https://launcher.mojang.com/v1/objects/d8fe9b1d8d02368dcd452de93024c6f60670eb87/fontconfig.SuSE.10.bfc"}, "raw": {"sha1": "ffd0dfbd1553e15b11649a73a0b3f48318138e0d", "size": 6702, "url": "https://launcher.mojang.com/v1/objects/ffd0dfbd1553e15b11649a73a0b3f48318138e0d/fontconfig.SuSE.10.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.10.properties.src": {"downloads": {"lzma": {"sha1": "2c382bd741a9e23114be3da82dee8290ebfca8a9", "size": 1555, "url": "https://launcher.mojang.com/v1/objects/2c382bd741a9e23114be3da82dee8290ebfca8a9/fontconfig.SuSE.10.properties.src"}, "raw": {"sha1": "a38dbdbbc514567b8281e1aea726865f37e97894", "size": 16772, "url": "https://launcher.mojang.com/v1/objects/a38dbdbbc514567b8281e1aea726865f37e97894/fontconfig.SuSE.10.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.11.bfc": {"downloads": {"lzma": {"sha1": "2b78cbf11289c9858951fea7180696ba3b7176d6", "size": 1092, "url": "https://launcher.mojang.com/v1/objects/2b78cbf11289c9858951fea7180696ba3b7176d6/fontconfig.SuSE.11.bfc"}, "raw": {"sha1": "a4d8500fcb47f6327460a95851b1368660da8302", "size": 7032, "url": "https://launcher.mojang.com/v1/objects/a4d8500fcb47f6327460a95851b1368660da8302/fontconfig.SuSE.11.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.SuSE.11.properties.src": {"downloads": {"lzma": {"sha1": "5c1635803906e2c59d36492dec724dd7ae49a5ab", "size": 1589, "url": "https://launcher.mojang.com/v1/objects/5c1635803906e2c59d36492dec724dd7ae49a5ab/fontconfig.SuSE.11.properties.src"}, "raw": {"sha1": "c4b69589e41a7279a71866a9134213be19cdf88d", "size": 16781, "url": "https://launcher.mojang.com/v1/objects/c4b69589e41a7279a71866a9134213be19cdf88d/fontconfig.SuSE.11.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.Turbo.bfc": {"downloads": {"lzma": {"sha1": "1c771325d9ee4af209a3db92294451d58962c7a4", "size": 822, "url": "https://launcher.mojang.com/v1/objects/1c771325d9ee4af209a3db92294451d58962c7a4/fontconfig.Turbo.bfc"}, "raw": {"sha1": "f24368deeb85cc9d0781083bc56e773518d72219", "size": 4668, "url": "https://launcher.mojang.com/v1/objects/f24368deeb85cc9d0781083bc56e773518d72219/fontconfig.Turbo.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.Turbo.properties.src": {"downloads": {"lzma": {"sha1": "7748ffa17e2c8a34754138efa963ba39bd1cbbb3", "size": 1113, "url": "https://launcher.mojang.com/v1/objects/7748ffa17e2c8a34754138efa963ba39bd1cbbb3/fontconfig.Turbo.properties.src"}, "raw": {"sha1": "2bb7258bed7ccd4f117e4e5f892c9b13424b0c82", "size": 9192, "url": "https://launcher.mojang.com/v1/objects/2bb7258bed7ccd4f117e4e5f892c9b13424b0c82/fontconfig.Turbo.properties.src"}}, "executable": false, "type": "file"}, "lib/fontconfig.bfc": {"downloads": {"lzma": {"sha1": "be6d49ee8c64f458c4f0e64254963fec48d25150", "size": 286, "url": "https://launcher.mojang.com/v1/objects/be6d49ee8c64f458c4f0e64254963fec48d25150/fontconfig.bfc"}, "raw": {"sha1": "de39b0e19637f58d92a0188122514aa7247ebb5b", "size": 1678, "url": "https://launcher.mojang.com/v1/objects/de39b0e19637f58d92a0188122514aa7247ebb5b/fontconfig.bfc"}}, "executable": false, "type": "file"}, "lib/fontconfig.properties.src": {"downloads": {"lzma": {"sha1": "9498d5e00e5401200667687e826e28c60fa60ba4", "size": 417, "url": "https://launcher.mojang.com/v1/objects/9498d5e00e5401200667687e826e28c60fa60ba4/fontconfig.properties.src"}, "raw": {"sha1": "3617ff1424fd204415242565541facf862b16eb4", "size": 1938, "url": "https://launcher.mojang.com/v1/objects/3617ff1424fd204415242565541facf862b16eb4/fontconfig.properties.src"}}, "executable": false, "type": "file"}, "lib/fonts": {"type": "directory"}, "lib/fonts/LucidaBrightDemiBold.ttf": {"downloads": {"lzma": {"sha1": "4f748750831a7719440dff5457f4d207d0f24d21", "size": 42347, "url": "https://launcher.mojang.com/v1/objects/4f748750831a7719440dff5457f4d207d0f24d21/LucidaBrightDemiBold.ttf"}, "raw": {"sha1": "b5c97f985639e19a3b712193ee48b55dda581fd1", "size": 75144, "url": "https://launcher.mojang.com/v1/objects/b5c97f985639e19a3b712193ee48b55dda581fd1/LucidaBrightDemiBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightDemiItalic.ttf": {"downloads": {"lzma": {"sha1": "f82e9a688553c100ecb98412b985807ed56dff5d", "size": 43119, "url": "https://launcher.mojang.com/v1/objects/f82e9a688553c100ecb98412b985807ed56dff5d/LucidaBrightDemiItalic.ttf"}, "raw": {"sha1": "1fd1f757febf3e5f5fbb7fbf7a56587a40d57de7", "size": 75124, "url": "https://launcher.mojang.com/v1/objects/1fd1f757febf3e5f5fbb7fbf7a56587a40d57de7/LucidaBrightDemiItalic.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightItalic.ttf": {"downloads": {"lzma": {"sha1": "6d630df719271319c3d53f90a3d425118b908266", "size": 46206, "url": "https://launcher.mojang.com/v1/objects/6d630df719271319c3d53f90a3d425118b908266/LucidaBrightItalic.ttf"}, "raw": {"sha1": "aa5c037865c563726ecd63d61ca26443589be425", "size": 80856, "url": "https://launcher.mojang.com/v1/objects/aa5c037865c563726ecd63d61ca26443589be425/LucidaBrightItalic.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaBrightRegular.ttf": {"downloads": {"lzma": {"sha1": "4b2e31aaec2238b6ecf9f845bad0a1c6d09fbbfe", "size": 181085, "url": "https://launcher.mojang.com/v1/objects/4b2e31aaec2238b6ecf9f845bad0a1c6d09fbbfe/LucidaBrightRegular.ttf"}, "raw": {"sha1": "5d7ed564791c900a8786936930ba99385653139c", "size": 344908, "url": "https://launcher.mojang.com/v1/objects/5d7ed564791c900a8786936930ba99385653139c/LucidaBrightRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaSansDemiBold.ttf": {"downloads": {"lzma": {"sha1": "079b16dc3c4918ab1f4f760b6dc5e6586c219042", "size": 173229, "url": "https://launcher.mojang.com/v1/objects/079b16dc3c4918ab1f4f760b6dc5e6586c219042/LucidaSansDemiBold.ttf"}, "raw": {"sha1": "92b79fefc35e96190250c602a8fed85276b32a95", "size": 317896, "url": "https://launcher.mojang.com/v1/objects/92b79fefc35e96190250c602a8fed85276b32a95/LucidaSansDemiBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaSansRegular.ttf": {"downloads": {"lzma": {"sha1": "64a65d7b94d7153d20957ef6d06bebb4dd0f48e4", "size": 326062, "url": "https://launcher.mojang.com/v1/objects/64a65d7b94d7153d20957ef6d06bebb4dd0f48e4/LucidaSansRegular.ttf"}, "raw": {"sha1": "39cc8bcb8d4a71d4657fc92ef0b9f4e3e9e67add", "size": 698236, "url": "https://launcher.mojang.com/v1/objects/39cc8bcb8d4a71d4657fc92ef0b9f4e3e9e67add/LucidaSansRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaTypewriterBold.ttf": {"downloads": {"lzma": {"sha1": "cdb017f7c34bea0802bc5ea5583aef721ed99c49", "size": 130412, "url": "https://launcher.mojang.com/v1/objects/cdb017f7c34bea0802bc5ea5583aef721ed99c49/LucidaTypewriterBold.ttf"}, "raw": {"sha1": "a5da2eb49448f461470387c939f0e69119310e0b", "size": 234068, "url": "https://launcher.mojang.com/v1/objects/a5da2eb49448f461470387c939f0e69119310e0b/LucidaTypewriterBold.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/LucidaTypewriterRegular.ttf": {"downloads": {"lzma": {"sha1": "aeda4a09a53783b0dc97de8e20071bea874cbfe5", "size": 135184, "url": "https://launcher.mojang.com/v1/objects/aeda4a09a53783b0dc97de8e20071bea874cbfe5/LucidaTypewriterRegular.ttf"}, "raw": {"sha1": "c144dcafe4faf2e79cfd74d8134a631f30234db1", "size": 242700, "url": "https://launcher.mojang.com/v1/objects/c144dcafe4faf2e79cfd74d8134a631f30234db1/LucidaTypewriterRegular.ttf"}}, "executable": false, "type": "file"}, "lib/fonts/fonts.dir": {"downloads": {"lzma": {"sha1": "68f2dd93b215ec8b8d9409d2b9c825632c6b907d", "size": 273, "url": "https://launcher.mojang.com/v1/objects/68f2dd93b215ec8b8d9409d2b9c825632c6b907d/fonts.dir"}, "raw": {"sha1": "97f40cca185c954adf5cc582345a7cb8e4c50578", "size": 4041, "url": "https://launcher.mojang.com/v1/objects/97f40cca185c954adf5cc582345a7cb8e4c50578/fonts.dir"}}, "executable": false, "type": "file"}, "lib/hijrah-config-umalqura.properties": {"downloads": {"lzma": {"sha1": "02e8d296e3b18a450f1ed1547cbf2b7275664c9a", "size": 1969, "url": "https://launcher.mojang.com/v1/objects/02e8d296e3b18a450f1ed1547cbf2b7275664c9a/hijrah-config-umalqura.properties"}, "raw": {"sha1": "84aa425100740722e91f4725caf849e7863d12ba", "size": 13962, "url": "https://launcher.mojang.com/v1/objects/84aa425100740722e91f4725caf849e7863d12ba/hijrah-config-umalqura.properties"}}, "executable": false, "type": "file"}, "lib/images": {"type": "directory"}, "lib/images/cursors": {"type": "directory"}, "lib/images/cursors/cursors.properties": {"downloads": {"lzma": {"sha1": "612bd0f610ee1023947c4a2a8d3fc7d6f97e7d8f", "size": 385, "url": "https://launcher.mojang.com/v1/objects/612bd0f610ee1023947c4a2a8d3fc7d6f97e7d8f/cursors.properties"}, "raw": {"sha1": "f2b9a22ddd0a77869497a64f28f07e89a7d41f48", "size": 1274, "url": "https://launcher.mojang.com/v1/objects/f2b9a22ddd0a77869497a64f28f07e89a7d41f48/cursors.properties"}}, "executable": false, "type": "file"}, "lib/images/cursors/invalid32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_CopyDrop32x32.gif": {"downloads": {"raw": {"sha1": "eb7620fae702172aa663a19d170a0b929d3b11d1", "size": 158, "url": "https://launcher.mojang.com/v1/objects/eb7620fae702172aa663a19d170a0b929d3b11d1/motif_CopyDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_CopyNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_LinkDrop32x32.gif": {"downloads": {"raw": {"sha1": "9699137f990c240e714481563181069c8f6c17bb", "size": 162, "url": "https://launcher.mojang.com/v1/objects/9699137f990c240e714481563181069c8f6c17bb/motif_LinkDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_LinkNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_MoveDrop32x32.gif": {"downloads": {"raw": {"sha1": "03c1617ce3c5ab8af03e46d30a8c8f31ab57fb1b", "size": 141, "url": "https://launcher.mojang.com/v1/objects/03c1617ce3c5ab8af03e46d30a8c8f31ab57fb1b/motif_MoveDrop32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/cursors/motif_MoveNoDrop32x32.gif": {"downloads": {"raw": {"sha1": "259edc45b4569427e8319895a444f4295d54348f", "size": 153, "url": "https://launcher.mojang.com/v1/objects/259edc45b4569427e8319895a444f4295d54348f/invalid32x32.gif"}}, "executable": false, "type": "file"}, "lib/images/icons": {"type": "directory"}, "lib/images/icons/sun-java.png": {"downloads": {"raw": {"sha1": "d101b693aa054f51097eebdfeed8b8a6ca7b55b8", "size": 4707, "url": "https://launcher.mojang.com/v1/objects/d101b693aa054f51097eebdfeed8b8a6ca7b55b8/sun-java.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_HighContrast.png": {"downloads": {"raw": {"sha1": "a6b1e418d6b5d03719b96f61f0c5236a60970151", "size": 3729, "url": "https://launcher.mojang.com/v1/objects/a6b1e418d6b5d03719b96f61f0c5236a60970151/sun-java_HighContrast.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_HighContrastInverse.png": {"downloads": {"raw": {"sha1": "2dda28b9bddc9b5b018e3e8a8b062a99d9b2f887", "size": 3777, "url": "https://launcher.mojang.com/v1/objects/2dda28b9bddc9b5b018e3e8a8b062a99d9b2f887/sun-java_HighContrastInverse.png"}}, "executable": false, "type": "file"}, "lib/images/icons/sun-java_LowContrast.png": {"downloads": {"raw": {"sha1": "7714cc4e894c3626c8da6fe742ed22b2829122d9", "size": 4012, "url": "https://launcher.mojang.com/v1/objects/7714cc4e894c3626c8da6fe742ed22b2829122d9/sun-java_LowContrast.png"}}, "executable": false, "type": "file"}, "lib/javafx.properties": {"downloads": {"raw": {"sha1": "49e6b75d109e5fd3f6cbe7cc5fa9a7980796d14d", "size": 56, "url": "https://launcher.mojang.com/v1/objects/49e6b75d109e5fd3f6cbe7cc5fa9a7980796d14d/javafx.properties"}}, "executable": false, "type": "file"}, "lib/javaws.jar": {"downloads": {"raw": {"sha1": "04fa5ae04ead65b91be5dee575497e49ffd49fe9", "size": 488118, "url": "https://launcher.mojang.com/v1/objects/04fa5ae04ead65b91be5dee575497e49ffd49fe9/javaws.jar"}}, "executable": false, "type": "file"}, "lib/jce.jar": {"downloads": {"raw": {"sha1": "5460adee09cc5fc8829c0acfc46c34670a7d70a0", "size": 115646, "url": "https://launcher.mojang.com/v1/objects/5460adee09cc5fc8829c0acfc46c34670a7d70a0/jce.jar"}}, "executable": false, "type": "file"}, "lib/jexec": {"downloads": {"lzma": {"sha1": "2d4323d4e060f8126d026ca6c03b8972aedd2fab", "size": 3311, "url": "https://launcher.mojang.com/v1/objects/2d4323d4e060f8126d026ca6c03b8972aedd2fab/jexec"}, "raw": {"sha1": "6aa01f1d8d103974164bcfaea03c04eeeefd7d41", "size": 13376, "url": "https://launcher.mojang.com/v1/objects/6aa01f1d8d103974164bcfaea03c04eeeefd7d41/jexec"}}, "executable": true, "type": "file"}, "lib/jfr": {"type": "directory"}, "lib/jfr.jar": {"downloads": {"lzma": {"sha1": "5b9d615c91c72f4fe356d9b4105946679452d1e1", "size": 137982, "url": "https://launcher.mojang.com/v1/objects/5b9d615c91c72f4fe356d9b4105946679452d1e1/jfr.jar"}, "raw": {"sha1": "0f3fd66a336703d935bdc22ad8082bc51d34e534", "size": 560713, "url": "https://launcher.mojang.com/v1/objects/0f3fd66a336703d935bdc22ad8082bc51d34e534/jfr.jar"}}, "executable": false, "type": "file"}, "lib/jfr/default.jfc": {"downloads": {"lzma": {"sha1": "373ddd878146dd8ce8991c2c5115a05a82859bdb", "size": 2207, "url": "https://launcher.mojang.com/v1/objects/373ddd878146dd8ce8991c2c5115a05a82859bdb/default.jfc"}, "raw": {"sha1": "1a64b68d0e7d43f8149faba94440be54f4f24527", "size": 20109, "url": "https://launcher.mojang.com/v1/objects/1a64b68d0e7d43f8149faba94440be54f4f24527/default.jfc"}}, "executable": false, "type": "file"}, "lib/jfr/profile.jfc": {"downloads": {"lzma": {"sha1": "3dcdc5feee3ccfb66bc8726b666944cd4bdadae3", "size": 2199, "url": "https://launcher.mojang.com/v1/objects/3dcdc5feee3ccfb66bc8726b666944cd4bdadae3/profile.jfc"}, "raw": {"sha1": "5d7d08a595f76322c51ae43ea966fbba6b69eebe", "size": 20065, "url": "https://launcher.mojang.com/v1/objects/5d7d08a595f76322c51ae43ea966fbba6b69eebe/profile.jfc"}}, "executable": false, "type": "file"}, "lib/jfxswt.jar": {"downloads": {"raw": {"sha1": "99d9a264c898d84c01e1c42565e7fe1a89dcd72d", "size": 33932, "url": "https://launcher.mojang.com/v1/objects/99d9a264c898d84c01e1c42565e7fe1a89dcd72d/jfxswt.jar"}}, "executable": false, "type": "file"}, "lib/jsse.jar": {"downloads": {"lzma": {"sha1": "94a17dfbc2e76cd12c33970a15341424f875a9ce", "size": 187549, "url": "https://launcher.mojang.com/v1/objects/94a17dfbc2e76cd12c33970a15341424f875a9ce/jsse.jar"}, "raw": {"sha1": "92c5c626e8a2d16f41272c0e404d4f992dd8310a", "size": 675599, "url": "https://launcher.mojang.com/v1/objects/92c5c626e8a2d16f41272c0e404d4f992dd8310a/jsse.jar"}}, "executable": false, "type": "file"}, "lib/jvm.hprof.txt": {"downloads": {"lzma": {"sha1": "eccdb240a815b2a83a502749339b27bb8669965b", "size": 1863, "url": "https://launcher.mojang.com/v1/objects/eccdb240a815b2a83a502749339b27bb8669965b/jvm.hprof.txt"}, "raw": {"sha1": "fbd61d52534cdd0c15df332114d469c65d001e33", "size": 4226, "url": "https://launcher.mojang.com/v1/objects/fbd61d52534cdd0c15df332114d469c65d001e33/jvm.hprof.txt"}}, "executable": false, "type": "file"}, "lib/locale": {"type": "directory"}, "lib/locale/de": {"type": "directory"}, "lib/locale/de/LC_MESSAGES": {"type": "directory"}, "lib/locale/de/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3061d922907cc557208109088fc6ab81d577ff6f", "size": 970, "url": "https://launcher.mojang.com/v1/objects/3061d922907cc557208109088fc6ab81d577ff6f/sunw_java_plugin.mo"}, "raw": {"sha1": "5b223a3d723ac1cfce63623fb109f2868d47d1b7", "size": 2483, "url": "https://launcher.mojang.com/v1/objects/5b223a3d723ac1cfce63623fb109f2868d47d1b7/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/es": {"type": "directory"}, "lib/locale/es/LC_MESSAGES": {"type": "directory"}, "lib/locale/es/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "24338049a89b323e17182b3a3006b50565d4fa0f", "size": 979, "url": "https://launcher.mojang.com/v1/objects/24338049a89b323e17182b3a3006b50565d4fa0f/sunw_java_plugin.mo"}, "raw": {"sha1": "6cc63dc97f2fdb2ed799e48b1dc98c4f37cdecc1", "size": 2477, "url": "https://launcher.mojang.com/v1/objects/6cc63dc97f2fdb2ed799e48b1dc98c4f37cdecc1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/fr": {"type": "directory"}, "lib/locale/fr/LC_MESSAGES": {"type": "directory"}, "lib/locale/fr/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "22796a48ef39f57d2d6fa70f41308e493d7f05c1", "size": 1033, "url": "https://launcher.mojang.com/v1/objects/22796a48ef39f57d2d6fa70f41308e493d7f05c1/sunw_java_plugin.mo"}, "raw": {"sha1": "d9d5b458db6e83fdf85c3526aeee3f57c4929840", "size": 2746, "url": "https://launcher.mojang.com/v1/objects/d9d5b458db6e83fdf85c3526aeee3f57c4929840/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/it": {"type": "directory"}, "lib/locale/it/LC_MESSAGES": {"type": "directory"}, "lib/locale/it/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "59a4cae38bfb8927745674d0efc2f284bc277987", "size": 958, "url": "https://launcher.mojang.com/v1/objects/59a4cae38bfb8927745674d0efc2f284bc277987/sunw_java_plugin.mo"}, "raw": {"sha1": "f6e72e3b2141ccc3dffab10ae14a754e494577ba", "size": 2434, "url": "https://launcher.mojang.com/v1/objects/f6e72e3b2141ccc3dffab10ae14a754e494577ba/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ja": {"type": "directory"}, "lib/locale/ja/LC_MESSAGES": {"type": "directory"}, "lib/locale/ja/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "7d6aeed563e1cefcf0224cf522048468088884a9", "size": 1036, "url": "https://launcher.mojang.com/v1/objects/7d6aeed563e1cefcf0224cf522048468088884a9/sunw_java_plugin.mo"}, "raw": {"sha1": "378881a8cb8dd2aebb43eacd0c68519be4f258b1", "size": 2415, "url": "https://launcher.mojang.com/v1/objects/378881a8cb8dd2aebb43eacd0c68519be4f258b1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ko": {"type": "directory"}, "lib/locale/ko.UTF-8": {"type": "directory"}, "lib/locale/ko.UTF-8/LC_MESSAGES": {"type": "directory"}, "lib/locale/ko.UTF-8/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "12ee3b21511e8497d95ea0ba9d6fe519227d0b16", "size": 1069, "url": "https://launcher.mojang.com/v1/objects/12ee3b21511e8497d95ea0ba9d6fe519227d0b16/sunw_java_plugin.mo"}, "raw": {"sha1": "cb19df01c59662dbe2f4050b1290d374b82fe1fa", "size": 2753, "url": "https://launcher.mojang.com/v1/objects/cb19df01c59662dbe2f4050b1290d374b82fe1fa/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/ko/LC_MESSAGES": {"type": "directory"}, "lib/locale/ko/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "6e2e47c64c360517fd436bc79c823b5679a1efe6", "size": 996, "url": "https://launcher.mojang.com/v1/objects/6e2e47c64c360517fd436bc79c823b5679a1efe6/sunw_java_plugin.mo"}, "raw": {"sha1": "12c8a118d150c78f719314df6dec49a967af71e9", "size": 2399, "url": "https://launcher.mojang.com/v1/objects/12c8a118d150c78f719314df6dec49a967af71e9/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/pt_BR": {"type": "directory"}, "lib/locale/pt_BR/LC_MESSAGES": {"type": "directory"}, "lib/locale/pt_BR/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "bcaa7e7916493f071f1bf64bf58c6b038e3569c9", "size": 940, "url": "https://launcher.mojang.com/v1/objects/bcaa7e7916493f071f1bf64bf58c6b038e3569c9/sunw_java_plugin.mo"}, "raw": {"sha1": "a3bc0c43994c53c59bba94982cf95f6d36283dd0", "size": 2420, "url": "https://launcher.mojang.com/v1/objects/a3bc0c43994c53c59bba94982cf95f6d36283dd0/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/sv": {"type": "directory"}, "lib/locale/sv/LC_MESSAGES": {"type": "directory"}, "lib/locale/sv/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "76017835d6261fe2eedbcbe5eb08a7484c3080c5", "size": 946, "url": "https://launcher.mojang.com/v1/objects/76017835d6261fe2eedbcbe5eb08a7484c3080c5/sunw_java_plugin.mo"}, "raw": {"sha1": "09a47686edec4bbb34e82fbd08559f8bb6266544", "size": 2359, "url": "https://launcher.mojang.com/v1/objects/09a47686edec4bbb34e82fbd08559f8bb6266544/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh": {"type": "directory"}, "lib/locale/zh.GBK": {"type": "directory"}, "lib/locale/zh.GBK/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh.GBK/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "75fd04045bf5890b8bb822770bfdb90a2e9ea65b", "size": 902, "url": "https://launcher.mojang.com/v1/objects/75fd04045bf5890b8bb822770bfdb90a2e9ea65b/sunw_java_plugin.mo"}, "raw": {"sha1": "7006fe7767b8807441a1f359a90509b3e507b0d1", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7006fe7767b8807441a1f359a90509b3e507b0d1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "75fd04045bf5890b8bb822770bfdb90a2e9ea65b", "size": 902, "url": "https://launcher.mojang.com/v1/objects/75fd04045bf5890b8bb822770bfdb90a2e9ea65b/sunw_java_plugin.mo"}, "raw": {"sha1": "7006fe7767b8807441a1f359a90509b3e507b0d1", "size": 2002, "url": "https://launcher.mojang.com/v1/objects/7006fe7767b8807441a1f359a90509b3e507b0d1/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_HK.BIG5HK": {"type": "directory"}, "lib/locale/zh_HK.BIG5HK/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_HK.BIG5HK/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3a1397bb1b1741697be1479232b6d9599940c851", "size": 912, "url": "https://launcher.mojang.com/v1/objects/3a1397bb1b1741697be1479232b6d9599940c851/sunw_java_plugin.mo"}, "raw": {"sha1": "c6023544067278c78599921f1032de353ff7da42", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/c6023544067278c78599921f1032de353ff7da42/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_TW": {"type": "directory"}, "lib/locale/zh_TW.BIG5": {"type": "directory"}, "lib/locale/zh_TW.BIG5/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_TW.BIG5/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "3a1397bb1b1741697be1479232b6d9599940c851", "size": 912, "url": "https://launcher.mojang.com/v1/objects/3a1397bb1b1741697be1479232b6d9599940c851/sunw_java_plugin.mo"}, "raw": {"sha1": "c6023544067278c78599921f1032de353ff7da42", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/c6023544067278c78599921f1032de353ff7da42/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/locale/zh_TW/LC_MESSAGES": {"type": "directory"}, "lib/locale/zh_TW/LC_MESSAGES/sunw_java_plugin.mo": {"downloads": {"lzma": {"sha1": "c05e610e75182f0c4e77f3e7a4d9670ed62bf63c", "size": 897, "url": "https://launcher.mojang.com/v1/objects/c05e610e75182f0c4e77f3e7a4d9670ed62bf63c/sunw_java_plugin.mo"}, "raw": {"sha1": "f9b972dd059eae3cd337dfcef6a178e8ed8a7db6", "size": 2025, "url": "https://launcher.mojang.com/v1/objects/f9b972dd059eae3cd337dfcef6a178e8ed8a7db6/sunw_java_plugin.mo"}}, "executable": false, "type": "file"}, "lib/logging.properties": {"downloads": {"lzma": {"sha1": "642202a58e5216d086ad37c0b5a633be802edc78", "size": 896, "url": "https://launcher.mojang.com/v1/objects/642202a58e5216d086ad37c0b5a633be802edc78/logging.properties"}, "raw": {"sha1": "89da8094484891f9ec1fa40c6c8b61f94c5869d0", "size": 2455, "url": "https://launcher.mojang.com/v1/objects/89da8094484891f9ec1fa40c6c8b61f94c5869d0/logging.properties"}}, "executable": false, "type": "file"}, "lib/management": {"type": "directory"}, "lib/management-agent.jar": {"downloads": {"lzma": {"sha1": "3ea0bf17e14b3428296a0f4011bf4025fcbfa4bd", "size": 243, "url": "https://launcher.mojang.com/v1/objects/3ea0bf17e14b3428296a0f4011bf4025fcbfa4bd/management-agent.jar"}, "raw": {"sha1": "9fbed36522aa3a80bac08a328942cbc5ef39ca8e", "size": 381, "url": "https://launcher.mojang.com/v1/objects/9fbed36522aa3a80bac08a328942cbc5ef39ca8e/management-agent.jar"}}, "executable": false, "type": "file"}, "lib/management/jmxremote.access": {"downloads": {"lzma": {"sha1": "69042ff1b14165db19c9d728614639dec16d6a31", "size": 1419, "url": "https://launcher.mojang.com/v1/objects/69042ff1b14165db19c9d728614639dec16d6a31/jmxremote.access"}, "raw": {"sha1": "21200eaad898ba4a2a8834a032efb6616fabb930", "size": 3998, "url": "https://launcher.mojang.com/v1/objects/21200eaad898ba4a2a8834a032efb6616fabb930/jmxremote.access"}}, "executable": false, "type": "file"}, "lib/management/jmxremote.password.template": {"downloads": {"lzma": {"sha1": "556c64b1e920766f8867be3964de6e49f5b81a60", "size": 1129, "url": "https://launcher.mojang.com/v1/objects/556c64b1e920766f8867be3964de6e49f5b81a60/jmxremote.password.template"}, "raw": {"sha1": "c1e0f01408bf20fbbb8b4810520c725f70050db5", "size": 2856, "url": "https://launcher.mojang.com/v1/objects/c1e0f01408bf20fbbb8b4810520c725f70050db5/jmxremote.password.template"}}, "executable": false, "type": "file"}, "lib/management/management.properties": {"downloads": {"lzma": {"sha1": "3e52f9baa6394ca6956845424c607e5cde5d3c67", "size": 3176, "url": "https://launcher.mojang.com/v1/objects/3e52f9baa6394ca6956845424c607e5cde5d3c67/management.properties"}, "raw": {"sha1": "e0451d8d7d9e84d7b1c39ec7d00993307a5cbbf1", "size": 14630, "url": "https://launcher.mojang.com/v1/objects/e0451d8d7d9e84d7b1c39ec7d00993307a5cbbf1/management.properties"}}, "executable": false, "type": "file"}, "lib/management/snmp.acl.template": {"downloads": {"lzma": {"sha1": "9a4aa6396c3b488b0663bed5e5ecb762985669c9", "size": 1121, "url": "https://launcher.mojang.com/v1/objects/9a4aa6396c3b488b0663bed5e5ecb762985669c9/snmp.acl.template"}, "raw": {"sha1": "2e9f9ac287274532eb1f0d1afcefd7f3e97cc794", "size": 3376, "url": "https://launcher.mojang.com/v1/objects/2e9f9ac287274532eb1f0d1afcefd7f3e97cc794/snmp.acl.template"}}, "executable": false, "type": "file"}, "lib/meta-index": {"downloads": {"lzma": {"sha1": "1ac60b31362fda4725c665b591c5fbe384cbc8c1", "size": 788, "url": "https://launcher.mojang.com/v1/objects/1ac60b31362fda4725c665b591c5fbe384cbc8c1/meta-index"}, "raw": {"sha1": "bf204f09242203e713c31785158a0792f9edb600", "size": 2034, "url": "https://launcher.mojang.com/v1/objects/bf204f09242203e713c31785158a0792f9edb600/meta-index"}}, "executable": false, "type": "file"}, "lib/net.properties": {"downloads": {"lzma": {"sha1": "e9ec3981a0797bf55bb87b24d9eb651ce7e6916b", "size": 1830, "url": "https://launcher.mojang.com/v1/objects/e9ec3981a0797bf55bb87b24d9eb651ce7e6916b/net.properties"}, "raw": {"sha1": "fd9471742eb759f4478bb1de9a0dc0527265b6ea", "size": 5352, "url": "https://launcher.mojang.com/v1/objects/fd9471742eb759f4478bb1de9a0dc0527265b6ea/net.properties"}}, "executable": false, "type": "file"}, "lib/oblique-fonts": {"type": "directory"}, "lib/oblique-fonts/LucidaSansDemiOblique.ttf": {"downloads": {"lzma": {"sha1": "49c8980c1b89bbdbab59d0f5bd5bebf0afcb93b2", "size": 38580, "url": "https://launcher.mojang.com/v1/objects/49c8980c1b89bbdbab59d0f5bd5bebf0afcb93b2/LucidaSansDemiOblique.ttf"}, "raw": {"sha1": "53e4e12a675ac222469341c3dbc102464a1be4c7", "size": 91352, "url": "https://launcher.mojang.com/v1/objects/53e4e12a675ac222469341c3dbc102464a1be4c7/LucidaSansDemiOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaSansOblique.ttf": {"downloads": {"lzma": {"sha1": "553123c0edcd08035dede4ffd92b5b81c9a7538a", "size": 116575, "url": "https://launcher.mojang.com/v1/objects/553123c0edcd08035dede4ffd92b5b81c9a7538a/LucidaSansOblique.ttf"}, "raw": {"sha1": "95a195ad4fc520b3e395c85b747fc3024d118dd9", "size": 253724, "url": "https://launcher.mojang.com/v1/objects/95a195ad4fc520b3e395c85b747fc3024d118dd9/LucidaSansOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaTypewriterBoldOblique.ttf": {"downloads": {"lzma": {"sha1": "2475b08151556ad4d89bb1d2b6494c6bee9abd82", "size": 29954, "url": "https://launcher.mojang.com/v1/objects/2475b08151556ad4d89bb1d2b6494c6bee9abd82/LucidaTypewriterBoldOblique.ttf"}, "raw": {"sha1": "f331fc8b0cc494702bc46b690f2b8eed36469a02", "size": 63168, "url": "https://launcher.mojang.com/v1/objects/f331fc8b0cc494702bc46b690f2b8eed36469a02/LucidaTypewriterBoldOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/LucidaTypewriterOblique.ttf": {"downloads": {"lzma": {"sha1": "5b970bc3b7abb21dce1aa28ff7f03459d351e552", "size": 60133, "url": "https://launcher.mojang.com/v1/objects/5b970bc3b7abb21dce1aa28ff7f03459d351e552/LucidaTypewriterOblique.ttf"}, "raw": {"sha1": "f8ea00db73f8a89a27674d050edc37c2280930e1", "size": 137484, "url": "https://launcher.mojang.com/v1/objects/f8ea00db73f8a89a27674d050edc37c2280930e1/LucidaTypewriterOblique.ttf"}}, "executable": false, "type": "file"}, "lib/oblique-fonts/fonts.dir": {"downloads": {"lzma": {"sha1": "067528c789bd713c7c3f34e779aa6e2e8253dcf6", "size": 188, "url": "https://launcher.mojang.com/v1/objects/067528c789bd713c7c3f34e779aa6e2e8253dcf6/fonts.dir"}, "raw": {"sha1": "5aee54ffba9e33de56fd84ef64fa496b898585bb", "size": 2115, "url": "https://launcher.mojang.com/v1/objects/5aee54ffba9e33de56fd84ef64fa496b898585bb/fonts.dir"}}, "executable": false, "type": "file"}, "lib/plugin.jar": {"downloads": {"raw": {"sha1": "3f250842c79112bae5369e372025b166990820e8", "size": 950772, "url": "https://launcher.mojang.com/v1/objects/3f250842c79112bae5369e372025b166990820e8/plugin.jar"}}, "executable": false, "type": "file"}, "lib/psfont.properties.ja": {"downloads": {"lzma": {"sha1": "7ca1cc244ed251cd1eb2347f1eea37d7d18c8ad4", "size": 701, "url": "https://launcher.mojang.com/v1/objects/7ca1cc244ed251cd1eb2347f1eea37d7d18c8ad4/psfont.properties.ja"}, "raw": {"sha1": "56ed1c661eeede17b4fae8c9de7b5edbad387abc", "size": 2796, "url": "https://launcher.mojang.com/v1/objects/56ed1c661eeede17b4fae8c9de7b5edbad387abc/psfont.properties.ja"}}, "executable": false, "type": "file"}, "lib/psfontj2d.properties": {"downloads": {"lzma": {"sha1": "4252fa01af8739a3545e2b705e3383892e22ab40", "size": 2278, "url": "https://launcher.mojang.com/v1/objects/4252fa01af8739a3545e2b705e3383892e22ab40/psfontj2d.properties"}, "raw": {"sha1": "aa327a22a49967f4d74afeee6726f505f209692f", "size": 10393, "url": "https://launcher.mojang.com/v1/objects/aa327a22a49967f4d74afeee6726f505f209692f/psfontj2d.properties"}}, "executable": false, "type": "file"}, "lib/resources.jar": {"downloads": {"lzma": {"sha1": "1b0e08441750dc17efe4b527aa146da6cc14e8a6", "size": 579294, "url": "https://launcher.mojang.com/v1/objects/1b0e08441750dc17efe4b527aa146da6cc14e8a6/resources.jar"}, "raw": {"sha1": "daa021906e4648d4c37e798c11733dc2047f2da1", "size": 3505206, "url": "https://launcher.mojang.com/v1/objects/daa021906e4648d4c37e798c11733dc2047f2da1/resources.jar"}}, "executable": false, "type": "file"}, "lib/rt.jar": {"downloads": {"lzma": {"sha1": "fc4a8681aeda29c2a2a3fd11bad7729543283f3d", "size": 14378994, "url": "https://launcher.mojang.com/v1/objects/fc4a8681aeda29c2a2a3fd11bad7729543283f3d/rt.jar"}, "raw": {"sha1": "5396b0954a20f3210f1f4f1886ead30880d6ebfe", "size": 66334986, "url": "https://launcher.mojang.com/v1/objects/5396b0954a20f3210f1f4f1886ead30880d6ebfe/rt.jar"}}, "executable": false, "type": "file"}, "lib/security": {"type": "directory"}, "lib/security/blacklist": {"downloads": {"lzma": {"sha1": "8206fce6c1d91a39fdf78e8e79e953913994a1cd", "size": 1969, "url": "https://launcher.mojang.com/v1/objects/8206fce6c1d91a39fdf78e8e79e953913994a1cd/blacklist"}, "raw": {"sha1": "d4ffb3857eab403955ce9d156e46d056061e6a5a", "size": 4054, "url": "https://launcher.mojang.com/v1/objects/d4ffb3857eab403955ce9d156e46d056061e6a5a/blacklist"}}, "executable": false, "type": "file"}, "lib/security/blacklisted.certs": {"downloads": {"lzma": {"sha1": "8311bead054caf6cfe678d4b7998de4caaabfa53", "size": 806, "url": "https://launcher.mojang.com/v1/objects/8311bead054caf6cfe678d4b7998de4caaabfa53/blacklisted.certs"}, "raw": {"sha1": "c5c005c29a80493f5c31cd7eb629ac1b9c752404", "size": 1273, "url": "https://launcher.mojang.com/v1/objects/c5c005c29a80493f5c31cd7eb629ac1b9c752404/blacklisted.certs"}}, "executable": false, "type": "file"}, "lib/security/cacerts": {"downloads": {"lzma": {"sha1": "654dd94809655d5b28385cbb5eba8d6ad9f2c1aa", "size": 67802, "url": "https://launcher.mojang.com/v1/objects/654dd94809655d5b28385cbb5eba8d6ad9f2c1aa/cacerts"}, "raw": {"sha1": "2917859c443c68e19f93abcd1315c3c2904cbef9", "size": 104430, "url": "https://launcher.mojang.com/v1/objects/2917859c443c68e19f93abcd1315c3c2904cbef9/cacerts"}}, "executable": false, "type": "file"}, "lib/security/java.policy": {"downloads": {"lzma": {"sha1": "b601c420d02ef3dbd8595453d08fdef91134e8b5", "size": 647, "url": "https://launcher.mojang.com/v1/objects/b601c420d02ef3dbd8595453d08fdef91134e8b5/java.policy"}, "raw": {"sha1": "c0112209a567b3b523cfed7041709f9440227968", "size": 2466, "url": "https://launcher.mojang.com/v1/objects/c0112209a567b3b523cfed7041709f9440227968/java.policy"}}, "executable": false, "type": "file"}, "lib/security/java.security": {"downloads": {"lzma": {"sha1": "531620e82ca0365ce8dc97096bb0ac5a7ace5952", "size": 10959, "url": "https://launcher.mojang.com/v1/objects/531620e82ca0365ce8dc97096bb0ac5a7ace5952/java.security"}, "raw": {"sha1": "5dcc17a168c53d0b366784e520bd4d55aa61ac18", "size": 41528, "url": "https://launcher.mojang.com/v1/objects/5dcc17a168c53d0b366784e520bd4d55aa61ac18/java.security"}}, "executable": false, "type": "file"}, "lib/security/javaws.policy": {"downloads": {"raw": {"sha1": "4384ca5e4d32f7dd86d8baddd1e690730d74e694", "size": 98, "url": "https://launcher.mojang.com/v1/objects/4384ca5e4d32f7dd86d8baddd1e690730d74e694/javaws.policy"}}, "executable": false, "type": "file"}, "lib/security/policy": {"type": "directory"}, "lib/security/policy/limited": {"type": "directory"}, "lib/security/policy/limited/US_export_policy.jar": {"downloads": {"raw": {"sha1": "7d69ea3b385bc067738520f1b5c549e1084be285", "size": 3026, "url": "https://launcher.mojang.com/v1/objects/7d69ea3b385bc067738520f1b5c549e1084be285/US_export_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/limited/local_policy.jar": {"downloads": {"raw": {"sha1": "238b8826e110f58acb2e1959773b0a577cd4d569", "size": 3527, "url": "https://launcher.mojang.com/v1/objects/238b8826e110f58acb2e1959773b0a577cd4d569/local_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/unlimited": {"type": "directory"}, "lib/security/policy/unlimited/US_export_policy.jar": {"downloads": {"raw": {"sha1": "f6fb2af1e87fc622cda194a7d6b5f5f069653ff1", "size": 3023, "url": "https://launcher.mojang.com/v1/objects/f6fb2af1e87fc622cda194a7d6b5f5f069653ff1/US_export_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/policy/unlimited/local_policy.jar": {"downloads": {"raw": {"sha1": "517368ab2cbaf6b42ea0b963f98eeedd996e83e3", "size": 3035, "url": "https://launcher.mojang.com/v1/objects/517368ab2cbaf6b42ea0b963f98eeedd996e83e3/local_policy.jar"}}, "executable": false, "type": "file"}, "lib/security/trusted.libraries": {"downloads": {"raw": {"sha1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", "size": 0, "url": "https://launcher.mojang.com/v1/objects/da39a3ee5e6b4b0d3255bfef95601890afd80709/trusted.libraries"}}, "executable": false, "type": "file"}, "lib/sound.properties": {"downloads": {"lzma": {"sha1": "3b5f7e4ec437d79048af35094290577f483b3fe1", "size": 473, "url": "https://launcher.mojang.com/v1/objects/3b5f7e4ec437d79048af35094290577f483b3fe1/sound.properties"}, "raw": {"sha1": "9afceb218059d981d0fa9f07aad3c5097cf41b0c", "size": 1210, "url": "https://launcher.mojang.com/v1/objects/9afceb218059d981d0fa9f07aad3c5097cf41b0c/sound.properties"}}, "executable": false, "type": "file"}, "lib/tzdb.dat": {"downloads": {"lzma": {"sha1": "39c69339965484afe89c14111baeeb862fdefd97", "size": 32547, "url": "https://launcher.mojang.com/v1/objects/39c69339965484afe89c14111baeeb862fdefd97/tzdb.dat"}, "raw": {"sha1": "b59c07e3619271a3b9861e999f4b138e971baf69", "size": 105734, "url": "https://launcher.mojang.com/v1/objects/b59c07e3619271a3b9861e999f4b138e971baf69/tzdb.dat"}}, "executable": false, "type": "file"}, "man": {"type": "directory"}, "man/ja": {"target": "ja_JP.UTF-8", "type": "link"}, "man/ja_JP.UTF-8": {"type": "directory"}, "man/ja_JP.UTF-8/man1": {"type": "directory"}, "man/ja_JP.UTF-8/man1/java.1": {"downloads": {"lzma": {"sha1": "f9da09710b6c6df23c256e324a0c4df00a0d6ded", "size": 25461, "url": "https://launcher.mojang.com/v1/objects/f9da09710b6c6df23c256e324a0c4df00a0d6ded/java.1"}, "raw": {"sha1": "b0b12a0bb66e6171771ca4b1dfca32fb759bcaec", "size": 148688, "url": "https://launcher.mojang.com/v1/objects/b0b12a0bb66e6171771ca4b1dfca32fb759bcaec/java.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/javaws.1": {"downloads": {"lzma": {"sha1": "6188fae453ca09ccb19be5c9f4d2059926b36267", "size": 2154, "url": "https://launcher.mojang.com/v1/objects/6188fae453ca09ccb19be5c9f4d2059926b36267/javaws.1"}, "raw": {"sha1": "8f39d928870268ace07bedfebd18db1e1d07fc37", "size": 6641, "url": "https://launcher.mojang.com/v1/objects/8f39d928870268ace07bedfebd18db1e1d07fc37/javaws.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/jjs.1": {"downloads": {"lzma": {"sha1": "6e42b989d28b185dc1aab50c0389834e649a37d4", "size": 3452, "url": "https://launcher.mojang.com/v1/objects/6e42b989d28b185dc1aab50c0389834e649a37d4/jjs.1"}, "raw": {"sha1": "e023322a2013912315a2bd1034e6f829a27c76e0", "size": 11365, "url": "https://launcher.mojang.com/v1/objects/e023322a2013912315a2bd1034e6f829a27c76e0/jjs.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/keytool.1": {"downloads": {"lzma": {"sha1": "a78134a4bddd53d684a70aa677e51a215db1c9cb", "size": 20698, "url": "https://launcher.mojang.com/v1/objects/a78134a4bddd53d684a70aa677e51a215db1c9cb/keytool.1"}, "raw": {"sha1": "148583c837eaaf6333ccfd8c9e8df08574e14b0c", "size": 111033, "url": "https://launcher.mojang.com/v1/objects/148583c837eaaf6333ccfd8c9e8df08574e14b0c/keytool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/orbd.1": {"downloads": {"lzma": {"sha1": "326af0dcbff173ef8aee29163dbe146d7389cc3e", "size": 4225, "url": "https://launcher.mojang.com/v1/objects/326af0dcbff173ef8aee29163dbe146d7389cc3e/orbd.1"}, "raw": {"sha1": "95651622d33c08286858ec337edd3ea72acd93dc", "size": 16092, "url": "https://launcher.mojang.com/v1/objects/95651622d33c08286858ec337edd3ea72acd93dc/orbd.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/pack200.1": {"downloads": {"lzma": {"sha1": "e0eedafa748c61a44e5be4355fe9d44b05048e80", "size": 4293, "url": "https://launcher.mojang.com/v1/objects/e0eedafa748c61a44e5be4355fe9d44b05048e80/pack200.1"}, "raw": {"sha1": "aa21a0ab75707f7fc66e83c7a392e69b37ddf80e", "size": 14482, "url": "https://launcher.mojang.com/v1/objects/aa21a0ab75707f7fc66e83c7a392e69b37ddf80e/pack200.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/policytool.1": {"downloads": {"lzma": {"sha1": "3c766ed12dab58166169d35680c392a6be1814a1", "size": 1380, "url": "https://launcher.mojang.com/v1/objects/3c766ed12dab58166169d35680c392a6be1814a1/policytool.1"}, "raw": {"sha1": "80879c74e072a98fad6f32b3283331aaf9bd002f", "size": 4020, "url": "https://launcher.mojang.com/v1/objects/80879c74e072a98fad6f32b3283331aaf9bd002f/policytool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/rmid.1": {"downloads": {"lzma": {"sha1": "1e20779d990beacc32a48237777d670fcc47ca14", "size": 4836, "url": "https://launcher.mojang.com/v1/objects/1e20779d990beacc32a48237777d670fcc47ca14/rmid.1"}, "raw": {"sha1": "7e40cb8003d098d6e36f45640b26f979ac94b5c5", "size": 19715, "url": "https://launcher.mojang.com/v1/objects/7e40cb8003d098d6e36f45640b26f979ac94b5c5/rmid.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/rmiregistry.1": {"downloads": {"lzma": {"sha1": "aaf4ffe07e954f8696eef1ecb7a5e244628d0ad9", "size": 1627, "url": "https://launcher.mojang.com/v1/objects/aaf4ffe07e954f8696eef1ecb7a5e244628d0ad9/rmiregistry.1"}, "raw": {"sha1": "c53c52f3ae7a011c135894c9fc51b741e729c33d", "size": 4557, "url": "https://launcher.mojang.com/v1/objects/c53c52f3ae7a011c135894c9fc51b741e729c33d/rmiregistry.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/servertool.1": {"downloads": {"lzma": {"sha1": "3b9e624e9d1cf2959b438a35061162e2100ddecd", "size": 2626, "url": "https://launcher.mojang.com/v1/objects/3b9e624e9d1cf2959b438a35061162e2100ddecd/servertool.1"}, "raw": {"sha1": "50ab8bcd9dd9d0b1a3d81348fbce1c8f82e7189e", "size": 9081, "url": "https://launcher.mojang.com/v1/objects/50ab8bcd9dd9d0b1a3d81348fbce1c8f82e7189e/servertool.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/tnameserv.1": {"downloads": {"lzma": {"sha1": "bb3106ff74c60a76de3d20659b9c2128c70f3bf2", "size": 4478, "url": "https://launcher.mojang.com/v1/objects/bb3106ff74c60a76de3d20659b9c2128c70f3bf2/tnameserv.1"}, "raw": {"sha1": "01e714671ecd1167edcb5310b16a9c59c33c3eaa", "size": 17722, "url": "https://launcher.mojang.com/v1/objects/01e714671ecd1167edcb5310b16a9c59c33c3eaa/tnameserv.1"}}, "executable": false, "type": "file"}, "man/ja_JP.UTF-8/man1/unpack200.1": {"downloads": {"lzma": {"sha1": "c115a881cf800b08df294df55d9f250ae944e33c", "size": 1973, "url": "https://launcher.mojang.com/v1/objects/c115a881cf800b08df294df55d9f250ae944e33c/unpack200.1"}, "raw": {"sha1": "7c882bba0067367a41ad84868d18793b8a7397a3", "size": 5382, "url": "https://launcher.mojang.com/v1/objects/7c882bba0067367a41ad84868d18793b8a7397a3/unpack200.1"}}, "executable": false, "type": "file"}, "man/man1": {"type": "directory"}, "man/man1/java.1": {"downloads": {"lzma": {"sha1": "06a6b0275c202bf698d73ca71f95618d56d81c15", "size": 25796, "url": "https://launcher.mojang.com/v1/objects/06a6b0275c202bf698d73ca71f95618d56d81c15/java.1"}, "raw": {"sha1": "69fec7a341aa91f18dbdcdb95952dede7e1b689a", "size": 124796, "url": "https://launcher.mojang.com/v1/objects/69fec7a341aa91f18dbdcdb95952dede7e1b689a/java.1"}}, "executable": false, "type": "file"}, "man/man1/javaws.1": {"downloads": {"lzma": {"sha1": "4bae251c6dfb5420f56928815cf80d0b6d517a1f", "size": 1759, "url": "https://launcher.mojang.com/v1/objects/4bae251c6dfb5420f56928815cf80d0b6d517a1f/javaws.1"}, "raw": {"sha1": "e61e44e101b1bc119c2d2d4b10320f38b36a8036", "size": 4897, "url": "https://launcher.mojang.com/v1/objects/e61e44e101b1bc119c2d2d4b10320f38b36a8036/javaws.1"}}, "executable": false, "type": "file"}, "man/man1/jjs.1": {"downloads": {"lzma": {"sha1": "29683cf2bd47015c9461b688749ddffd95f6671d", "size": 1881, "url": "https://launcher.mojang.com/v1/objects/29683cf2bd47015c9461b688749ddffd95f6671d/jjs.1"}, "raw": {"sha1": "78d419bd3a7f3e0802d5220e690429194b5d1beb", "size": 4932, "url": "https://launcher.mojang.com/v1/objects/78d419bd3a7f3e0802d5220e690429194b5d1beb/jjs.1"}}, "executable": false, "type": "file"}, "man/man1/keytool.1": {"downloads": {"lzma": {"sha1": "b67e5126d43713ee3675706724b34061578b42db", "size": 19690, "url": "https://launcher.mojang.com/v1/objects/b67e5126d43713ee3675706724b34061578b42db/keytool.1"}, "raw": {"sha1": "4c976f86057ab779763fcfb98f5702ebef47f629", "size": 86925, "url": "https://launcher.mojang.com/v1/objects/4c976f86057ab779763fcfb98f5702ebef47f629/keytool.1"}}, "executable": false, "type": "file"}, "man/man1/orbd.1": {"downloads": {"lzma": {"sha1": "147064d6f7e027002e296bb246ae572d0ce0495b", "size": 3708, "url": "https://launcher.mojang.com/v1/objects/147064d6f7e027002e296bb246ae572d0ce0495b/orbd.1"}, "raw": {"sha1": "64201e1846fcf1dcc45c786ffeab89426d1c7742", "size": 12180, "url": "https://launcher.mojang.com/v1/objects/64201e1846fcf1dcc45c786ffeab89426d1c7742/orbd.1"}}, "executable": false, "type": "file"}, "man/man1/pack200.1": {"downloads": {"lzma": {"sha1": "fe17486bbe9c58cf4182fa056b9cd124e8295607", "size": 3724, "url": "https://launcher.mojang.com/v1/objects/fe17486bbe9c58cf4182fa056b9cd124e8295607/pack200.1"}, "raw": {"sha1": "26826cf52b89924f2d2a60d6cda798891875eae6", "size": 11623, "url": "https://launcher.mojang.com/v1/objects/26826cf52b89924f2d2a60d6cda798891875eae6/pack200.1"}}, "executable": false, "type": "file"}, "man/man1/policytool.1": {"downloads": {"lzma": {"sha1": "bd154e7c39aca71d15b2098c588866f8d95bc743", "size": 1122, "url": "https://launcher.mojang.com/v1/objects/bd154e7c39aca71d15b2098c588866f8d95bc743/policytool.1"}, "raw": {"sha1": "ab296625155d9a2b25ecc2b4feff2f741b3ad136", "size": 3235, "url": "https://launcher.mojang.com/v1/objects/ab296625155d9a2b25ecc2b4feff2f741b3ad136/policytool.1"}}, "executable": false, "type": "file"}, "man/man1/rmid.1": {"downloads": {"lzma": {"sha1": "6a7da234e7f43ebca5c4ba8cd862fda3be62fbaa", "size": 4255, "url": "https://launcher.mojang.com/v1/objects/6a7da234e7f43ebca5c4ba8cd862fda3be62fbaa/rmid.1"}, "raw": {"sha1": "6f10e214d7950a6a8460524e41dc700f112f89e5", "size": 15979, "url": "https://launcher.mojang.com/v1/objects/6f10e214d7950a6a8460524e41dc700f112f89e5/rmid.1"}}, "executable": false, "type": "file"}, "man/man1/rmiregistry.1": {"downloads": {"lzma": {"sha1": "f40dd17e3a734600ad1828b0c42d3a1685c4c520", "size": 1301, "url": "https://launcher.mojang.com/v1/objects/f40dd17e3a734600ad1828b0c42d3a1685c4c520/rmiregistry.1"}, "raw": {"sha1": "d9a3d23fab689df5bb9a792b88f462f939b49f70", "size": 3449, "url": "https://launcher.mojang.com/v1/objects/d9a3d23fab689df5bb9a792b88f462f939b49f70/rmiregistry.1"}}, "executable": false, "type": "file"}, "man/man1/servertool.1": {"downloads": {"lzma": {"sha1": "74f1e10712202cd3ca0ff5833de05b7ee67092e1", "size": 2307, "url": "https://launcher.mojang.com/v1/objects/74f1e10712202cd3ca0ff5833de05b7ee67092e1/servertool.1"}, "raw": {"sha1": "e6c7b510740ac8681a9bfb5f4ee1f0306125b728", "size": 7237, "url": "https://launcher.mojang.com/v1/objects/e6c7b510740ac8681a9bfb5f4ee1f0306125b728/servertool.1"}}, "executable": false, "type": "file"}, "man/man1/tnameserv.1": {"downloads": {"lzma": {"sha1": "4bec7f4e070d023f124f9352a8971d7acd249a15", "size": 3955, "url": "https://launcher.mojang.com/v1/objects/4bec7f4e070d023f124f9352a8971d7acd249a15/tnameserv.1"}, "raw": {"sha1": "a31dbbe800d49cb371fab9a4b73d22c3bf8799ad", "size": 15747, "url": "https://launcher.mojang.com/v1/objects/a31dbbe800d49cb371fab9a4b73d22c3bf8799ad/tnameserv.1"}}, "executable": false, "type": "file"}, "man/man1/unpack200.1": {"downloads": {"lzma": {"sha1": "f8e73863187929debf2ea6dadefb2995ec7917e7", "size": 1672, "url": "https://launcher.mojang.com/v1/objects/f8e73863187929debf2ea6dadefb2995ec7917e7/unpack200.1"}, "raw": {"sha1": "437f7233d738cb9b822e99003127049005663e0f", "size": 4244, "url": "https://launcher.mojang.com/v1/objects/437f7233d738cb9b822e99003127049005663e0f/unpack200.1"}}, "executable": false, "type": "file"}, "plugin": {"type": "directory"}, "plugin/desktop": {"type": "directory"}, "plugin/desktop/sun_java.desktop": {"downloads": {"lzma": {"sha1": "49ab0ccb54c3be68281d05055bc56a88b1281d3c", "size": 447, "url": "https://launcher.mojang.com/v1/objects/49ab0ccb54c3be68281d05055bc56a88b1281d3c/sun_java.desktop"}, "raw": {"sha1": "79120ee8160ad6f3c9b90c2641fb7edf3af96b5d", "size": 624, "url": "https://launcher.mojang.com/v1/objects/79120ee8160ad6f3c9b90c2641fb7edf3af96b5d/sun_java.desktop"}}, "executable": false, "type": "file"}, "plugin/desktop/sun_java.png": {"downloads": {"raw": {"sha1": "699c41e97a35414e72a80327a54d6e14e874e951", "size": 4351, "url": "https://launcher.mojang.com/v1/objects/699c41e97a35414e72a80327a54d6e14e874e951/sun_java.png"}}, "executable": false, "type": "file"}, "release": {"downloads": {"raw": {"sha1": "cb462682644c0275d94a45b759108815f3112064", "size": 424, "url": "https://launcher.mojang.com/v1/objects/cb462682644c0275d94a45b759108815f3112064/release"}}, "executable": false, "type": "file"}}} \ No newline at end of file
diff --git a/api/logic/mojang/testdata/inspect/a/b.txt b/api/logic/mojang/testdata/inspect/a/b.txt
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/api/logic/mojang/testdata/inspect/a/b.txt
diff --git a/api/logic/mojang/testdata/inspect/a/b/b.txt b/api/logic/mojang/testdata/inspect/a/b/b.txt
new file mode 120000
index 00000000..4e19a044
--- /dev/null
+++ b/api/logic/mojang/testdata/inspect/a/b/b.txt
@@ -0,0 +1 @@
+../b.txt \ No newline at end of file
diff --git a/api/logic/mojang/testdata/inspect_win/a/b.txt b/api/logic/mojang/testdata/inspect_win/a/b.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/api/logic/mojang/testdata/inspect_win/a/b.txt
diff --git a/api/logic/mojang/testdata/inspect_win/a/b/b.txt b/api/logic/mojang/testdata/inspect_win/a/b/b.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/api/logic/mojang/testdata/inspect_win/a/b/b.txt
diff --git a/api/logic/net/Download.cpp b/api/logic/net/Download.cpp
index 229782b0..3f183b7d 100644
--- a/api/logic/net/Download.cpp
+++ b/api/logic/net/Download.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,6 +15,7 @@
#include "Download.h"
+#include "BuildConfig.h"
#include <QFileInfo>
#include <QDateTime>
#include <QDebug>
@@ -94,7 +95,7 @@ void Download::start()
return;
}
- request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0");
+ request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT);
QNetworkReply *rep = ENV.qnam().get(request);
diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h
index 18472911..2c436032 100644
--- a/api/logic/net/Download.h
+++ b/api/logic/net/Download.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/api/logic/net/HttpMetaCache.cpp
index 0e3d41b5..4bc8fbc8 100644
--- a/api/logic/net/HttpMetaCache.cpp
+++ b/api/logic/net/HttpMetaCache.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 aa92f8ff..c3248793 100644
--- a/api/logic/net/HttpMetaCache.h
+++ b/api/logic/net/HttpMetaCache.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/NetAction.h b/api/logic/net/NetAction.h
index da34a04e..02b249a3 100644
--- a/api/logic/net/NetAction.h
+++ b/api/logic/net/NetAction.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/NetJob.cpp b/api/logic/net/NetJob.cpp
index 71e31736..029d9e34 100644
--- a/api/logic/net/NetJob.cpp
+++ b/api/logic/net/NetJob.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -214,3 +214,5 @@ bool NetJob::addNetAction(NetActionPtr action)
}
return true;
}
+
+NetJob::~NetJob() = default;
diff --git a/api/logic/net/NetJob.h b/api/logic/net/NetJob.h
index 0b56bdaa..480d8037 100644
--- a/api/logic/net/NetJob.h
+++ b/api/logic/net/NetJob.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ public:
{
setObjectName(job_name);
}
- virtual ~NetJob() {}
+ virtual ~NetJob();
bool addNetAction(NetActionPtr action);
diff --git a/api/logic/net/PasteUpload.cpp b/api/logic/net/PasteUpload.cpp
index 3526e207..cb470c49 100644
--- a/api/logic/net/PasteUpload.cpp
+++ b/api/logic/net/PasteUpload.cpp
@@ -5,6 +5,7 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QFile>
+#include <BuildConfig.h>
PasteUpload::PasteUpload(QWidget *window, QString text, QString key) : m_window(window)
{
@@ -34,7 +35,7 @@ bool PasteUpload::validateText()
void PasteUpload::executeTask()
{
QNetworkRequest request(QUrl("https://api.paste.ee/v1/pastes"));
- request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
+ request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
request.setRawHeader("Content-Type", "application/json");
request.setRawHeader("Content-Length", QByteArray::number(m_jsonContent.size()));
diff --git a/api/logic/news/NewsChecker.cpp b/api/logic/news/NewsChecker.cpp
index a68022e8..c66f49e1 100644
--- a/api/logic/news/NewsChecker.cpp
+++ b/api/logic/news/NewsChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 5983eeb3..c473ecab 100644
--- a/api/logic/news/NewsChecker.h
+++ b/api/logic/news/NewsChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 2edffb1b..7eff657b 100644
--- a/api/logic/news/NewsEntry.cpp
+++ b/api/logic/news/NewsEntry.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 94a9e761..0dbc70a5 100644
--- a/api/logic/news/NewsEntry.h
+++ b/api/logic/news/NewsEntry.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/screenshots/ImgurAlbumCreation.cpp b/api/logic/screenshots/ImgurAlbumCreation.cpp
index ff9ec6fd..1f195f00 100644
--- a/api/logic/screenshots/ImgurAlbumCreation.cpp
+++ b/api/logic/screenshots/ImgurAlbumCreation.cpp
@@ -20,9 +20,9 @@ void ImgurAlbumCreation::start()
{
m_status = Job_InProgress;
QNetworkRequest request(m_url);
- request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
+ request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
- request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3");
+ request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
request.setRawHeader("Accept", "application/json");
QStringList hashes;
diff --git a/api/logic/screenshots/ImgurUpload.cpp b/api/logic/screenshots/ImgurUpload.cpp
index 1585b061..7e95d5ca 100644
--- a/api/logic/screenshots/ImgurUpload.cpp
+++ b/api/logic/screenshots/ImgurUpload.cpp
@@ -23,8 +23,8 @@ void ImgurUpload::start()
finished = false;
m_status = Job_InProgress;
QNetworkRequest request(m_url);
- request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Uncached)");
- request.setRawHeader("Authorization", "Client-ID 5b97b0713fba4a3");
+ request.setHeader(QNetworkRequest::UserAgentHeader, BuildConfig.USER_AGENT_UNCACHED);
+ request.setRawHeader("Authorization", QString("Client-ID %1").arg(BuildConfig.IMGUR_CLIENT_ID).toStdString().c_str());
request.setRawHeader("Accept", "application/json");
QFile f(m_shot->m_file.absoluteFilePath());
diff --git a/api/logic/settings/INIFile.cpp b/api/logic/settings/INIFile.cpp
index ff6d5cf3..6a3c801d 100644
--- a/api/logic/settings/INIFile.cpp
+++ b/api/logic/settings/INIFile.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 9406f7bd..9e8c68ea 100644
--- a/api/logic/settings/INIFile.h
+++ b/api/logic/settings/INIFile.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 f04a66b5..12513403 100644
--- a/api/logic/settings/INISettingsObject.cpp
+++ b/api/logic/settings/INISettingsObject.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 c8ea99ca..313f1512 100644
--- a/api/logic/settings/INISettingsObject.h
+++ b/api/logic/settings/INISettingsObject.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 1efdebac..4396a381 100644
--- a/api/logic/settings/OverrideSetting.cpp
+++ b/api/logic/settings/OverrideSetting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 f99caff1..9f0c98b5 100644
--- a/api/logic/settings/OverrideSetting.h
+++ b/api/logic/settings/OverrideSetting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 beee4152..8f93b251 100644
--- a/api/logic/settings/PassthroughSetting.cpp
+++ b/api/logic/settings/PassthroughSetting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 00212072..22008f83 100644
--- a/api/logic/settings/PassthroughSetting.h
+++ b/api/logic/settings/PassthroughSetting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 419dfa2f..cfe5a7f9 100644
--- a/api/logic/settings/Setting.cpp
+++ b/api/logic/settings/Setting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 9d827c72..a31193ac 100644
--- a/api/logic/settings/Setting.h
+++ b/api/logic/settings/Setting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 b4fc5a68..8a0bc045 100644
--- a/api/logic/settings/SettingsObject.cpp
+++ b/api/logic/settings/SettingsObject.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 11dc50ca..3ebdebdf 100644
--- a/api/logic/settings/SettingsObject.h
+++ b/api/logic/settings/SettingsObject.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 2a4091c6..38fc2163 100644
--- a/api/logic/status/StatusChecker.cpp
+++ b/api/logic/status/StatusChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 829b139d..e9961aff 100644
--- a/api/logic/status/StatusChecker.h
+++ b/api/logic/status/StatusChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 accb39b6..d0ac7569 100644
--- a/api/logic/tasks/Task.cpp
+++ b/api/logic/tasks/Task.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/logic/tasks/Task.h
index 72bfeca8..7ed7086c 100644
--- a/api/logic/tasks/Task.h
+++ b/api/logic/tasks/Task.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/translations/TranslationsModel.cpp b/api/logic/translations/TranslationsModel.cpp
index adb3fa98..401b64d4 100644
--- a/api/logic/translations/TranslationsModel.cpp
+++ b/api/logic/translations/TranslationsModel.cpp
@@ -15,7 +15,19 @@
#include "POTranslator.h"
-const static QLatin1Literal defaultLangCode("en");
+const static QLatin1Literal defaultLangCode("en_US");
+
+static QLocale getLocaleFromKey(const QString &key) {
+ if(key == "pt") {
+ return QLocale("pt_PT");
+ }
+ else if (key == "en") {
+ return QLocale("en_GB");
+ }
+ else {
+ return QLocale(key);
+ }
+}
enum class FileType
{
@@ -33,12 +45,7 @@ struct Language
Language(const QString & _key)
{
key = _key;
- if(key == "pt") {
- locale = QLocale("pt_PT");
- }
- else {
- locale = QLocale(key);
- }
+ locale = getLocaleFromKey(key);
updated = (key == defaultLangCode);
}
@@ -452,7 +459,7 @@ bool TranslationsModel::selectLanguage(QString key)
* In a multithreaded application, the default locale should be set at application startup, before any non-GUI threads are created.
* This function is not reentrant.
*/
- QLocale locale(langCode);
+ QLocale locale = getLocaleFromKey(langCode);
QLocale::setDefault(locale);
// if it's the default UI language, finish
diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h
index a0327fcd..17e5b124 100644
--- a/api/logic/translations/TranslationsModel.h
+++ b/api/logic/translations/TranslationsModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 dbd8c65a..20b26ebb 100644
--- a/api/logic/updater/DownloadTask.cpp
+++ b/api/logic/updater/DownloadTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/api/logic/updater/DownloadTask.h
index 5c973912..88e60865 100644
--- a/api/logic/updater/DownloadTask.h
+++ b/api/logic/updater/DownloadTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 b3c2641d..be33c73c 100644
--- a/api/logic/updater/UpdateChecker.cpp
+++ b/api/logic/updater/UpdateChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 4ad39c0b..20906207 100644
--- a/api/logic/updater/UpdateChecker.h
+++ b/api/logic/updater/UpdateChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 1d5b8e04..ab2b9960 100644
--- a/application/CMakeLists.txt
+++ b/application/CMakeLists.txt
@@ -124,15 +124,38 @@ SET(MULTIMC_SOURCES
# GUI - platform pages
pages/modplatform/VanillaPage.cpp
pages/modplatform/VanillaPage.h
+
+ pages/modplatform/atlauncher/AtlFilterModel.cpp
+ pages/modplatform/atlauncher/AtlFilterModel.h
+ pages/modplatform/atlauncher/AtlListModel.cpp
+ pages/modplatform/atlauncher/AtlListModel.h
+ pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
+ pages/modplatform/atlauncher/AtlOptionalModDialog.h
+ pages/modplatform/atlauncher/AtlPage.cpp
+ pages/modplatform/atlauncher/AtlPage.h
+
+ pages/modplatform/ftb/FtbFilterModel.cpp
+ pages/modplatform/ftb/FtbFilterModel.h
+ pages/modplatform/ftb/FtbListModel.cpp
+ pages/modplatform/ftb/FtbListModel.h
+ pages/modplatform/ftb/FtbPage.cpp
+ pages/modplatform/ftb/FtbPage.h
+
pages/modplatform/legacy_ftb/Page.cpp
pages/modplatform/legacy_ftb/Page.h
pages/modplatform/legacy_ftb/ListModel.h
pages/modplatform/legacy_ftb/ListModel.cpp
- pages/modplatform/twitch/TwitchData.h
- pages/modplatform/twitch/TwitchModel.cpp
- pages/modplatform/twitch/TwitchModel.h
- pages/modplatform/twitch/TwitchPage.cpp
- pages/modplatform/twitch/TwitchPage.h
+
+ pages/modplatform/flame/FlameModel.cpp
+ pages/modplatform/flame/FlameModel.h
+ pages/modplatform/flame/FlamePage.cpp
+ pages/modplatform/flame/FlamePage.h
+
+ pages/modplatform/technic/TechnicModel.cpp
+ pages/modplatform/technic/TechnicModel.h
+ pages/modplatform/technic/TechnicPage.cpp
+ pages/modplatform/technic/TechnicPage.h
+
pages/modplatform/ImportPage.cpp
pages/modplatform/ImportPage.h
@@ -250,10 +273,16 @@ SET(MULTIMC_UIS
# Platform pages
pages/modplatform/VanillaPage.ui
+ pages/modplatform/atlauncher/AtlPage.ui
+ pages/modplatform/ftb/FtbPage.ui
pages/modplatform/legacy_ftb/Page.ui
- pages/modplatform/twitch/TwitchPage.ui
+ pages/modplatform/flame/FlamePage.ui
+ pages/modplatform/technic/TechnicPage.ui
pages/modplatform/ImportPage.ui
+ # Platform Dialogs
+ pages/modplatform/atlauncher/AtlOptionalModDialog.ui
+
# Dialogs
dialogs/CopyInstanceDialog.ui
dialogs/NewComponentDialog.ui
diff --git a/application/HoeDown.h b/application/HoeDown.h
index c94a312f..b9e06ffb 100644
--- a/application/HoeDown.h
+++ b/application/HoeDown.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 dde36aef..3cb723c4 100644
--- a/application/InstancePageProvider.h
+++ b/application/InstancePageProvider.h
@@ -46,7 +46,7 @@ public:
values.append(new TexturePackPage(onesix.get()));
values.append(new NotesPage(onesix.get()));
values.append(new WorldListPage(onesix.get(), onesix->worldList()));
- values.append(new ServersPage(onesix.get()));
+ values.append(new ServersPage(onesix));
// values.append(new GameOptionsPage(onesix.get()));
values.append(new ScreenshotsPage(FS::PathCombine(onesix->gameRoot(), "screenshots")));
values.append(new InstanceSettingsPage(onesix.get()));
diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp
index 02c1bb23..015ffe1c 100644
--- a/application/InstanceWindow.cpp
+++ b/application/InstanceWindow.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -123,12 +123,14 @@ void InstanceWindow::updateLaunchButtons()
{
m_launchOfflineButton->setEnabled(false);
m_killButton->setText(tr("Kill"));
+ m_killButton->setObjectName("killButton");
m_killButton->setToolTip(tr("Kill the running instance"));
}
else if(!m_instance->canLaunch())
{
m_launchOfflineButton->setEnabled(false);
m_killButton->setText(tr("Launch"));
+ m_killButton->setObjectName("launchButton");
m_killButton->setToolTip(tr("Launch the instance"));
m_killButton->setEnabled(false);
}
@@ -136,8 +138,12 @@ void InstanceWindow::updateLaunchButtons()
{
m_launchOfflineButton->setEnabled(true);
m_killButton->setText(tr("Launch"));
+ m_killButton->setObjectName("launchButton");
m_killButton->setToolTip(tr("Launch the instance"));
}
+ // NOTE: this is a hack to force the button to recalculate its style
+ m_killButton->setStyleSheet("/* */");
+ m_killButton->setStyleSheet(QString());
}
void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h
index e5bd4464..cd7d2494 100644
--- a/application/InstanceWindow.h
+++ b/application/InstanceWindow.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/JavaCommon.cpp b/application/JavaCommon.cpp
index 563dfb35..92a058f0 100644
--- a/application/JavaCommon.cpp
+++ b/application/JavaCommon.cpp
@@ -24,7 +24,8 @@ void JavaCommon::javaWasOk(QWidget *parent, JavaCheckResult result)
{
QString text;
text += QObject::tr("Java test succeeded!<br />Platform reported: %1<br />Java version "
- "reported: %2<br />").arg(result.realPlatform, result.javaVersion.toString());
+ "reported: %2<br />Java vendor "
+ "reported: %3<br />").arg(result.realPlatform, result.javaVersion.toString(), result.javaVendor);
if (result.errorLog.size())
{
auto htmlError = result.errorLog;
diff --git a/application/KonamiCode.cpp b/application/KonamiCode.cpp
index 4c5af837..46a2a0b2 100644
--- a/application/KonamiCode.cpp
+++ b/application/KonamiCode.cpp
@@ -35,7 +35,7 @@ void KonamiCode::input(QEvent* event)
{
m_progress = 0;
}
- if(m_progress == konamiCode.size())
+ if(m_progress == static_cast<int>(konamiCode.size()))
{
m_progress = 0;
emit triggered();
diff --git a/application/LaunchController.cpp b/application/LaunchController.cpp
index bebc3db1..ee764082 100644
--- a/application/LaunchController.cpp
+++ b/application/LaunchController.cpp
@@ -15,6 +15,9 @@
#include <minecraft/auth/YggdrasilTask.h>
#include <launch/steps/TextPrint.h>
#include <QStringList>
+#include <QHostInfo>
+#include <QList>
+#include <QHostAddress>
LaunchController::LaunchController(QObject *parent) : Task(parent)
{
@@ -197,7 +200,7 @@ void LaunchController::launchInstance()
return;
}
- m_launcher = m_instance->createLaunchTask(m_session);
+ m_launcher = m_instance->createLaunchTask(m_session, m_serverToJoin);
if (!m_launcher)
{
emitFailed(tr("Couldn't instantiate a launcher."));
@@ -215,7 +218,46 @@ void LaunchController::launchInstance()
connect(m_launcher.get(), &LaunchTask::failed, this, &LaunchController::onFailed);
connect(m_launcher.get(), &LaunchTask::requestProgress, this, &LaunchController::onProgressRequested);
+ // Prepend Online and Auth Status
+ QString online_mode;
+ if(m_session->wants_online) {
+ online_mode = "online";
+ // Prepend Server Status
+ QStringList servers = {"authserver.mojang.com", "session.minecraft.net", "textures.minecraft.net", "api.mojang.com"};
+ QString resolved_servers = "";
+ QHostInfo host_info;
+
+ for(QString server : servers) {
+ host_info = QHostInfo::fromName(server);
+ resolved_servers = resolved_servers + server + " resolves to:\n [";
+ if(!host_info.addresses().isEmpty()) {
+ for(QHostAddress address : host_info.addresses()) {
+ resolved_servers = resolved_servers + address.toString();
+ if(!host_info.addresses().endsWith(address)) {
+ resolved_servers = resolved_servers + ", ";
+ }
+ }
+ } else {
+ resolved_servers = resolved_servers + "N/A";
+ }
+ resolved_servers = resolved_servers + "]\n\n";
+ }
+ m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::MultiMC));
+ } else {
+ online_mode = "offline";
+ }
+
+ QString auth_server_status;
+ if(m_session->auth_server_online) {
+ auth_server_status = "online";
+ } else {
+ auth_server_status = "offline";
+ }
+
+ m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\nAuthentication server is " + auth_server_status + "\n", MessageLevel::MultiMC));
+
+ // Prepend Version
m_launcher->prependStep(new TextPrint(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC));
m_launcher->start();
}
diff --git a/application/LaunchController.h b/application/LaunchController.h
index 1d879028..5f177e00 100644
--- a/application/LaunchController.h
+++ b/application/LaunchController.h
@@ -3,6 +3,8 @@
#include <BaseInstance.h>
#include <tools/BaseProfiler.h>
+#include "minecraft/launch/MinecraftServerTarget.h"
+
class InstanceWindow;
class LaunchController: public Task
{
@@ -33,6 +35,10 @@ public:
{
m_parentWidget = widget;
}
+ void setServerToJoin(MinecraftServerTargetPtr serverToJoin)
+ {
+ m_serverToJoin = std::move(serverToJoin);
+ }
QString id()
{
return m_instance->id();
@@ -58,4 +64,5 @@ private:
InstanceWindow *m_console = nullptr;
AuthSessionPtr m_session;
shared_qobject_ptr<LaunchTask> m_launcher;
+ MinecraftServerTargetPtr m_serverToJoin;
};
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp
index 00c37084..13a7c7ae 100644
--- a/application/MainWindow.cpp
+++ b/application/MainWindow.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Andrew Okin
* Peterix
@@ -306,29 +306,35 @@ public:
helpMenu = new QMenu(MainWindow);
helpMenu->setToolTipsVisible(true);
- actionReportBug = TranslatedAction(MainWindow);
- actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
- actionReportBug->setIcon(MMC->getThemedIcon("bug"));
- 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);
+ if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
+ actionReportBug = TranslatedAction(MainWindow);
+ actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
+ actionReportBug->setIcon(MMC->getThemedIcon("bug"));
+ 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);
+ }
+
+ if (!BuildConfig.DISCORD_URL.isEmpty()) {
+ 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);
+ }
+
+ if (!BuildConfig.SUBREDDIT_URL.isEmpty()) {
+ 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"));
@@ -732,7 +738,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
repopulateAccountsMenu();
accountMenuButton = new QToolButton(this);
- accountMenuButton->setText(tr("Profiles"));
accountMenuButton->setMenu(accountMenu);
accountMenuButton->setPopupMode(QToolButton::InstantPopup);
accountMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
@@ -831,6 +836,21 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
// removing this looks stupid
view->setFocus();
+
+ retranslateUi();
+}
+
+void MainWindow::retranslateUi()
+{
+ accountMenuButton->setText(tr("Profiles"));
+
+ if (m_selectedInstance) {
+ m_statusLeft->setText(m_selectedInstance->getStatusbarDescription());
+ } else {
+ m_statusLeft->setText(tr("No instance selected"));
+ }
+
+ ui->retranslateUi(this);
}
MainWindow::~MainWindow()
@@ -1455,12 +1475,12 @@ void MainWindow::droppedURLs(QList<QUrl> urls)
void MainWindow::on_actionREDDIT_triggered()
{
- DesktopServices::openUrl(QUrl("https://www.reddit.com/r/MultiMC/"));
+ DesktopServices::openUrl(QUrl(BuildConfig.SUBREDDIT_URL));
}
void MainWindow::on_actionDISCORD_triggered()
{
- DesktopServices::openUrl(QUrl("https://discord.gg/0k2zsXGNHs0fE4Wm"));
+ DesktopServices::openUrl(QUrl(BuildConfig.DISCORD_URL));
}
void MainWindow::on_actionChangeInstIcon_triggered()
@@ -1638,7 +1658,7 @@ void MainWindow::on_actionManageAccounts_triggered()
void MainWindow::on_actionReportBug_triggered()
{
- DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/issues"));
+ DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL));
}
void MainWindow::on_actionPatreon_triggered()
@@ -1745,7 +1765,7 @@ void MainWindow::changeEvent(QEvent* event)
{
if (event->type() == QEvent::LanguageChange)
{
- ui->retranslateUi(this);
+ retranslateUi();
}
QMainWindow::changeEvent(event);
}
diff --git a/application/MainWindow.h b/application/MainWindow.h
index 00b8e043..08c6b969 100644
--- a/application/MainWindow.h
+++ b/application/MainWindow.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -187,6 +187,8 @@ private slots:
void globalSettingsClosed();
private:
+ void retranslateUi();
+
void addInstance(QString url = QString());
void activateInstance(InstancePtr instance);
void setCatBackground(bool enabled);
diff --git a/application/MultiMC.cpp b/application/MultiMC.cpp
index eeab500e..2dd4f83f 100644
--- a/application/MultiMC.cpp
+++ b/application/MultiMC.cpp
@@ -191,6 +191,11 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
parser.addOption("launch");
parser.addShortOpt("launch", 'l');
parser.addDocumentation("launch", "Launch the specified instance (by instance ID)");
+ // --server
+ parser.addOption("server");
+ parser.addShortOpt("server", 's');
+ parser.addDocumentation("server", "Join the specified server on launch "
+ "(only valid in combination with --launch)");
// --alive
parser.addSwitch("alive");
parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts");
@@ -207,8 +212,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
catch (const ParsingError &e)
{
std::cerr << "CommandLineError: " << e.what() << std::endl;
- std::cerr << "Try '%1 -h' to get help on MultiMC's command line parameters."
- << std::endl;
+ if(argc > 0)
+ std::cerr << "Try '" << argv[0] << " -h' to get help on MultiMC's command line parameters."
+ << std::endl;
m_status = MultiMC::Failed;
return;
}
@@ -231,6 +237,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
}
m_instanceIdToLaunch = args["launch"].toString();
+ m_serverToJoin = args["server"].toString();
m_liveCheck = args["alive"].toBool();
m_zipToImport = args["import"].toUrl();
@@ -292,6 +299,13 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
return;
}
+ if(m_instanceIdToLaunch.isEmpty() && !m_serverToJoin.isEmpty())
+ {
+ std::cerr << "--server can only be used in combination with --launch!" << std::endl;
+ m_status = MultiMC::Failed;
+ 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.
@@ -317,7 +331,15 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
}
else
{
- m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout);
+ if(!m_serverToJoin.isEmpty())
+ {
+ m_peerInstance->sendMessage(
+ "launch-with-server " + m_instanceIdToLaunch + " " + m_serverToJoin, timeout);
+ }
+ else
+ {
+ m_peerInstance->sendMessage("launch " + m_instanceIdToLaunch, timeout);
+ }
}
m_status = MultiMC::Succeeded;
return;
@@ -378,7 +400,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) );
#endif
- qDebug() << "MultiMC 5, (c) 2013-2019 MultiMC Contributors";
+ qDebug() << "MultiMC 5, (c) 2013-2021 MultiMC Contributors";
qDebug() << "Version : " << BuildConfig.printableVersionString();
qDebug() << "Git commit : " << BuildConfig.GIT_COMMIT;
qDebug() << "Git refspec : " << BuildConfig.GIT_REFSPEC;
@@ -398,6 +420,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
{
qDebug() << "ID of instance to launch : " << m_instanceIdToLaunch;
}
+ if(!m_serverToJoin.isEmpty())
+ {
+ qDebug() << "Address of server to join :" << m_serverToJoin;
+ }
qDebug() << "<> Paths set.";
}
@@ -505,9 +531,18 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
m_settings->registerSetting("JavaTimestamp", 0);
m_settings->registerSetting("JavaArchitecture", "");
m_settings->registerSetting("JavaVersion", "");
+ m_settings->registerSetting("JavaVendor", "");
m_settings->registerSetting("LastHostname", "");
m_settings->registerSetting("JvmArgs", "");
+ // Native library workarounds
+ m_settings->registerSetting("UseNativeOpenAL", false);
+ m_settings->registerSetting("UseNativeGLFW", false);
+
+ // Game time
+ m_settings->registerSetting("ShowGameTime", true);
+ m_settings->registerSetting("RecordGameTime", true);
+
// Minecraft launch method
m_settings->registerSetting("MCLaunchMethod", "LauncherPart");
@@ -834,8 +869,19 @@ void MultiMC::performMainStartupAction()
auto inst = instances()->getInstanceById(m_instanceIdToLaunch);
if(inst)
{
- qDebug() << "<> Instance launching:" << m_instanceIdToLaunch;
- launch(inst, true, nullptr);
+ MinecraftServerTargetPtr serverToJoin = nullptr;
+
+ if(!m_serverToJoin.isEmpty())
+ {
+ serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(m_serverToJoin)));
+ qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching with server" << m_serverToJoin;
+ }
+ else
+ {
+ qDebug() << "<> Instance" << m_instanceIdToLaunch << "launching";
+ }
+
+ launch(inst, true, nullptr, serverToJoin);
return;
}
}
@@ -917,6 +963,31 @@ void MultiMC::messageReceived(const QString& message)
launch(inst, true, nullptr);
}
}
+ else if(command == "launch-with-server")
+ {
+ QString instanceID = message.section(' ', 1, 1);
+ QString serverToJoin = message.section(' ', 2, 2);
+ if(instanceID.isEmpty())
+ {
+ qWarning() << "Received" << command << "message without an instance ID.";
+ return;
+ }
+ if(serverToJoin.isEmpty())
+ {
+ qWarning() << "Received" << command << "message without a server to join.";
+ return;
+ }
+ auto inst = instances()->getInstanceById(instanceID);
+ if(inst)
+ {
+ launch(
+ inst,
+ true,
+ nullptr,
+ std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(serverToJoin))
+ );
+ }
+ }
else
{
qWarning() << "Received invalid message" << message;
@@ -1004,8 +1075,12 @@ bool MultiMC::openJsonEditor(const QString &filename)
}
}
-bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *profiler)
-{
+bool MultiMC::launch(
+ InstancePtr instance,
+ bool online,
+ BaseProfilerFactory *profiler,
+ MinecraftServerTargetPtr serverToJoin
+) {
if(m_updateRunning)
{
qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
@@ -1026,6 +1101,7 @@ bool MultiMC::launch(InstancePtr instance, bool online, BaseProfilerFactory *pro
controller->setInstance(instance);
controller->setOnline(online);
controller->setProfiler(profiler);
+ controller->setServerToJoin(serverToJoin);
if(window)
{
controller->setParentWidget(window);
diff --git a/application/MultiMC.h b/application/MultiMC.h
index e6588a14..af2b41c1 100644
--- a/application/MultiMC.h
+++ b/application/MultiMC.h
@@ -11,6 +11,8 @@
#include <BaseInstance.h>
+#include "minecraft/launch/MinecraftServerTarget.h"
+
class LaunchController;
class LocalPeer;
class InstanceWindow;
@@ -150,7 +152,12 @@ signals:
void globalSettingsClosed();
public slots:
- bool launch(InstancePtr instance, bool online = true, BaseProfilerFactory *profiler = nullptr);
+ bool launch(
+ InstancePtr instance,
+ bool online = true,
+ BaseProfilerFactory *profiler = nullptr,
+ MinecraftServerTargetPtr serverToJoin = nullptr
+ );
bool kill(InstancePtr instance);
private slots:
@@ -221,6 +228,7 @@ private:
SetupWizard * m_setupWizard = nullptr;
public:
QString m_instanceIdToLaunch;
+ QString m_serverToJoin;
bool m_liveCheck = false;
QUrl m_zipToImport;
std::unique_ptr<QFile> logFile;
diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp
index c4e4ee24..c97c471e 100644
--- a/application/dialogs/AboutDialog.cpp
+++ b/application/dialogs/AboutDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/dialogs/AboutDialog.h
index 21001c07..c7621c37 100644
--- a/application/dialogs/AboutDialog.h
+++ b/application/dialogs/AboutDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 c4096b32..c6de9ebb 100644
--- a/application/dialogs/AboutDialog.ui
+++ b/application/dialogs/AboutDialog.ui
@@ -165,7 +165,7 @@
</font>
</property>
<property name="text">
- <string>© 2012-2019 MultiMC Contributors</string>
+ <string>© 2012-2021 MultiMC Contributors</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp
index ab76e737..5fe90334 100644
--- a/application/dialogs/CopyInstanceDialog.cpp
+++ b/application/dialogs/CopyInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 2b8475b7..bf3cd920 100644
--- a/application/dialogs/CopyInstanceDialog.h
+++ b/application/dialogs/CopyInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 db60b7f3..19029f68 100644
--- a/application/dialogs/CustomMessageBox.cpp
+++ b/application/dialogs/CustomMessageBox.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 92291e17..712c6518 100644
--- a/application/dialogs/CustomMessageBox.h
+++ b/application/dialogs/CustomMessageBox.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 9ae0c745..002c064b 100644
--- a/application/dialogs/EditAccountDialog.cpp
+++ b/application/dialogs/EditAccountDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 4f80284a..6b5eb4aa 100644
--- a/application/dialogs/EditAccountDialog.h
+++ b/application/dialogs/EditAccountDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 49c082e9..a42779d4 100644
--- a/application/dialogs/ExportInstanceDialog.cpp
+++ b/application/dialogs/ExportInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/dialogs/ExportInstanceDialog.h
index aec4cbfb..dea02d1b 100644
--- a/application/dialogs/ExportInstanceDialog.h
+++ b/application/dialogs/ExportInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 7f930717..90436554 100644
--- a/application/dialogs/IconPickerDialog.cpp
+++ b/application/dialogs/IconPickerDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/dialogs/IconPickerDialog.h
index cb870f44..9af6a678 100644
--- a/application/dialogs/IconPickerDialog.h
+++ b/application/dialogs/IconPickerDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/application/dialogs/LoginDialog.cpp
index 484cb8e2..32f8a48f 100644
--- a/application/dialogs/LoginDialog.cpp
+++ b/application/dialogs/LoginDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 30fcb760..16bdddfb 100644
--- a/application/dialogs/LoginDialog.h
+++ b/application/dialogs/LoginDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.ui b/application/dialogs/LoginDialog.ui
index d92fbae3..dbdb3b93 100644
--- a/application/dialogs/LoginDialog.ui
+++ b/application/dialogs/LoginDialog.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>400</width>
- <height>162</height>
+ <width>421</width>
+ <height>238</height>
</rect>
</property>
<property name="sizePolicy">
@@ -21,6 +21,16 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
+ <widget class="QLabel" name="microsoftAccountsNoticeLabel">
+ <property name="text">
+ <string>NOTICE: MultiMC does not currently support Microsoft accounts. This means that accounts created from December 2020 onwards cannot be used.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QLabel" name="label">
<property name="text">
<string notr="true">Message label placeholder.</string>
diff --git a/application/dialogs/NewComponentDialog.cpp b/application/dialogs/NewComponentDialog.cpp
index d0d1d24b..f4d6274f 100644
--- a/application/dialogs/NewComponentDialog.cpp
+++ b/application/dialogs/NewComponentDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/dialogs/NewComponentDialog.h
index 518f342d..8c790beb 100644
--- a/application/dialogs/NewComponentDialog.h
+++ b/application/dialogs/NewComponentDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp
index 511f991e..86963149 100644
--- a/application/dialogs/NewInstanceDialog.cpp
+++ b/application/dialogs/NewInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -34,9 +34,13 @@
#include "widgets/PageContainer.h"
#include <pages/modplatform/VanillaPage.h>
+#include <pages/modplatform/atlauncher/AtlPage.h>
+#include <pages/modplatform/ftb/FtbPage.h>
#include <pages/modplatform/legacy_ftb/Page.h>
-#include <pages/modplatform/twitch/TwitchPage.h>
+#include <pages/modplatform/flame/FlamePage.h>
#include <pages/modplatform/ImportPage.h>
+#include <pages/modplatform/technic/TechnicPage.h>
+
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
@@ -120,13 +124,17 @@ void NewInstanceDialog::accept()
QList<BasePage *> NewInstanceDialog::getPages()
{
importPage = new ImportPage(this);
- twitchPage = new TwitchPage(this);
+ flamePage = new FlamePage(this);
+ auto technicPage = new TechnicPage(this);
return
{
new VanillaPage(this),
importPage,
+ new AtlPage(this),
+ flamePage,
+ new FtbPage(this),
new LegacyFTB::Page(this),
- twitchPage
+ technicPage
};
}
@@ -165,6 +173,14 @@ void NewInstanceDialog::setSuggestedIconFromFile(const QString &path, const QStr
ui->iconButton->setIcon(QIcon(path));
}
+void NewInstanceDialog::setSuggestedIcon(const QString &key)
+{
+ auto icon = MMC->icons()->getIcon(key);
+ importIcon = false;
+
+ ui->iconButton->setIcon(icon);
+}
+
InstanceTask * NewInstanceDialog::extractTask()
{
InstanceTask * extracted = creationTask.get();
diff --git a/application/dialogs/NewInstanceDialog.h b/application/dialogs/NewInstanceDialog.h
index 0b8b2fb8..53abf8cf 100644
--- a/application/dialogs/NewInstanceDialog.h
+++ b/application/dialogs/NewInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ class NewInstanceDialog;
class PageContainer;
class QDialogButtonBox;
class ImportPage;
-class TwitchPage;
+class FlamePage;
class NewInstanceDialog : public QDialog, public BasePageProvider
{
@@ -43,6 +43,7 @@ public:
void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr);
void setSuggestedIconFromFile(const QString &path, const QString &name);
+ void setSuggestedIcon(const QString &key);
InstanceTask * extractTask();
@@ -68,7 +69,7 @@ private:
QString InstIconKey;
ImportPage *importPage = nullptr;
- TwitchPage *twitchPage = nullptr;
+ FlamePage *flamePage = nullptr;
std::unique_ptr<InstanceTask> creationTask;
bool importIcon = false;
diff --git a/application/dialogs/ProfileSelectDialog.cpp b/application/dialogs/ProfileSelectDialog.cpp
index b64de01a..ae34709f 100644
--- a/application/dialogs/ProfileSelectDialog.cpp
+++ b/application/dialogs/ProfileSelectDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 40fa0843..9f95830c 100644
--- a/application/dialogs/ProfileSelectDialog.h
+++ b/application/dialogs/ProfileSelectDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 0e186fc2..4b092859 100644
--- a/application/dialogs/ProgressDialog.cpp
+++ b/application/dialogs/ProgressDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/dialogs/ProgressDialog.h
index a3779e3f..b28ad4fa 100644
--- a/application/dialogs/ProgressDialog.h
+++ b/application/dialogs/ProgressDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 7d2ff829..56133529 100644
--- a/application/dialogs/SkinUploadDialog.cpp
+++ b/application/dialogs/SkinUploadDialog.cpp
@@ -1,7 +1,7 @@
#include <QFileInfo>
#include <QFileDialog>
#include <FileSystem.h>
-#include <minecraft/SkinUpload.h>
+#include <minecraft/services/SkinUpload.h>
#include "SkinUploadDialog.h"
#include "ui_SkinUploadDialog.h"
#include "ProgressDialog.h"
diff --git a/application/dialogs/UpdateDialog.h b/application/dialogs/UpdateDialog.h
index 6a871b1c..ae1799c3 100644
--- a/application/dialogs/UpdateDialog.h
+++ b/application/dialogs/UpdateDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 0b6ba87e..ed1210ba 100644
--- a/application/dialogs/VersionSelectDialog.cpp
+++ b/application/dialogs/VersionSelectDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/dialogs/VersionSelectDialog.h
index 14bc4d76..ed30d3f3 100644
--- a/application/dialogs/VersionSelectDialog.h
+++ b/application/dialogs/VersionSelectDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/GroupView.cpp b/application/groupview/GroupView.cpp
index a1a28532..6bfc9381 100644
--- a/application/groupview/GroupView.cpp
+++ b/application/groupview/GroupView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/GroupView.h b/application/groupview/GroupView.h
index 13bfd234..cc5a58aa 100644
--- a/application/groupview/GroupView.h
+++ b/application/groupview/GroupView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/application/groupview/GroupedProxyModel.cpp
index d5fbd93c..dc4212d5 100644
--- a/application/groupview/GroupedProxyModel.cpp
+++ b/application/groupview/GroupedProxyModel.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 810657e7..fabf11c1 100644
--- a/application/groupview/GroupedProxyModel.h
+++ b/application/groupview/GroupedProxyModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 39830d1a..fc959565 100644
--- a/application/groupview/InstanceDelegate.cpp
+++ b/application/groupview/InstanceDelegate.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 be49f943..d95279f3 100644
--- a/application/groupview/InstanceDelegate.h
+++ b/application/groupview/InstanceDelegate.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 f3e6751d..76bf8678 100644
--- a/application/groupview/VisualGroup.cpp
+++ b/application/groupview/VisualGroup.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 356a6da2..239ee9d7 100644
--- a/application/groupview/VisualGroup.h
+++ b/application/groupview/VisualGroup.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/package/rpm/MultiMC5.spec b/application/package/rpm/MultiMC5.spec
new file mode 100644
index 00000000..78b9000e
--- /dev/null
+++ b/application/package/rpm/MultiMC5.spec
@@ -0,0 +1,47 @@
+Name: MultiMC5
+Version: 1.4
+Release: 2%{?dist}
+Summary: A local install wrapper for MultiMC
+
+License: ASL 2.0
+URL: https://multimc.org
+BuildArch: x86_64
+
+Requires: zenity qt5-qtbase wget xrandr
+Provides: multimc MultiMC multimc5
+
+%description
+A local install wrapper for MultiMC
+
+%prep
+
+
+%build
+
+
+%install
+mkdir -p %{buildroot}/opt/multimc
+install -m 0644 ../ubuntu/multimc/opt/multimc/icon.svg %{buildroot}/opt/multimc/icon.svg
+install -m 0755 ../ubuntu/multimc/opt/multimc/run.sh %{buildroot}/opt/multimc/run.sh
+mkdir -p %{buildroot}/%{_datadir}/applications
+install -m 0644 ../ubuntu/multimc/usr/share/applications/multimc.desktop %{buildroot}/%{_datadir}/applications/multimc.desktop
+mkdir -p %{buildroot}/%{_metainfodir}
+install -m 0644 ../ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml %{buildroot}/%{_metainfodir}/multimc.metainfo.xml
+
+%files
+%dir /opt/multimc
+/opt/multimc/icon.svg
+/opt/multimc/run.sh
+%{_datadir}/applications/multimc.desktop
+%{_metainfodir}/multimc.metainfo.xml
+
+
+%changelog
+* Tue Jun 01 2021 kb1000 <fedora@kb1000.de> - 1.4-2
+- Add xrandr to the dependencies
+
+* Tue Dec 08 00:34:35 CET 2020 joshua-stone <joshua.gage.stone@gmail.com>
+- Add metainfo.xml for improving package metadata
+
+* Wed Nov 25 22:53:59 CET 2020 kb1000 <fedora@kb1000.de>
+- Initial version of the RPM package, based on the Ubuntu package
diff --git a/application/package/rpm/README.md b/application/package/rpm/README.md
new file mode 100644
index 00000000..0c2b1e49
--- /dev/null
+++ b/application/package/rpm/README.md
@@ -0,0 +1,12 @@
+# What is this?
+A simple RPM package for MultiMC that contains a script that downloads and installs real MultiMC on Red Hat based systems.
+
+It contains a `.desktop` file, a `.metainfo.xml` file, an icon, and a simple script that does the heavy lifting.
+
+# How to build this?
+You need the `rpm-build` package. Switch into this directory, then run:
+```
+rpmbuild --build-in-place -bb MultiMC5.spec
+```
+
+Replace the version with whatever is appropriate.
diff --git a/application/package/ubuntu/README.md b/application/package/ubuntu/README.md
new file mode 100644
index 00000000..892abd12
--- /dev/null
+++ b/application/package/ubuntu/README.md
@@ -0,0 +1,14 @@
+# What is this?
+A simple Ubuntu package for MultiMC that contains a script that downloads and installs real MultiMC on Ubuntu based systems.
+
+It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting.
+
+This is also the source for the files in the [RPM package](../rpm). If you rename, create or delete files here, you'll likely also have to update the RPM spec file there.
+
+# How to build this?
+You need dpkg utils. Rename the `multimc` folder to `multimc_1.5-1` and then run:
+```
+fakeroot dpkg-deb --build multimc_1.5-1
+```
+
+Replace the version with whatever is appropriate.
diff --git a/application/package/ubuntu/multimc/DEBIAN/control b/application/package/ubuntu/multimc/DEBIAN/control
index 9386e945..3e0f570c 100644
--- a/application/package/ubuntu/multimc/DEBIAN/control
+++ b/application/package/ubuntu/multimc/DEBIAN/control
@@ -1,11 +1,11 @@
Package: multimc
-Version: 1.4-1
+Version: 1.5-1
Architecture: all
Maintainer: Petr Mrázek <peterix@gmail.com>
Section: games
Priority: optional
Installed-Size: 75
-Depends: zenity, desktop-file-utils, qt5-default, wget
+Depends: zenity, desktop-file-utils, libqt5widgets5, libqt5gui5, libqt5network5, libqt5core5a, libqt5xml5, libqt5concurrent5, wget
Recommends: openjdk-8-jre
Homepage: http://multimc.org
Description: A local install wrapper for MultiMC
diff --git a/application/package/ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml b/application/package/ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml
new file mode 100644
index 00000000..4c6b7450
--- /dev/null
+++ b/application/package/ubuntu/multimc/usr/share/metainfo/multimc.metainfo.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop">
+ <id>multimc</id>
+ <launchable type="desktop-id">multimc.desktop</launchable>
+ <name>MultiMC</name>
+ <summary>Manage Minecraft instances with ease</summary>
+ <description>
+ <p>Overview</p>
+ <p>MultiMC is a free, open source launcher for Minecraft. It allows you to have multiple, cleanly separated instances of Minecraft (each with their own mods, texture packs, saves, etc) and helps you manage them and their associated options with a simple and powerful interface.</p>
+ <p>Features</p>
+ <ul>
+ <li>Manage multiple instances of Minecraft at once</li>
+ <li>Start Minecraft with a custom resolution</li>
+ <li>Change Java's runtime options (including memory options)</li>
+ <li>Shows Minecraft's console output in a colour coded window</li>
+ <li>Kill Minecraft easily if it crashes / freezes</li>
+ <li>Custom icons and groups for instances</li>
+ <li>Forge integration (automatic installation, version downloads, mod management)</li>
+ <li>Minecraft world management</li>
+ <li>Import and export Minecraft instances to share them with anyone</li>
+ <li>Supports every version of Minecraft that the vanilla launcher does</li>
+ </ul>
+ </description>
+ <screenshots>
+ <screenshot type="default">
+ <image type="source" width="936" height="921">https://multimc.org/images/screenshots/main.png</image>
+ </screenshot>
+ <screenshot>
+ <image type="source" width="936" height="998">https://multimc.org/images/screenshots/editmods.png</image>
+ </screenshot>
+ <screenshot>
+ <image type="source" width="936" height="998">https://multimc.org/images/screenshots/version.png</image>
+ </screenshot>
+ <screenshot>
+ <image type="source" width="936" height="998">https://multimc.org/images/screenshots/console.png</image>
+ </screenshot>
+ <screenshot>
+ <image type="source" width="936" height="921">https://multimc.org/images/screenshots/settings.png</image>
+ </screenshot>
+ </screenshots>
+ <releases>
+ <release date="2021-01-07" version="5"/>
+ </releases>
+ <url type="homepage">https://multimc.org/</url>
+ <url type="help">https://discord.com/invite/0k2zsXGNHs0fE4Wm</url>
+ <url type="faq">https://github.com/MultiMC/MultiMC5/wiki/FAQ</url>
+ <url type="bugtracker">https://github.com/MultiMC/MultiMC5/issues</url>
+ <url type="translate">https://translate.multimc.org/</url>
+ <url type="donation">https://www.patreon.com/multimc</url>
+ <developer_name>The MultiMC Team</developer_name>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>Apache-2.0</project_license>
+ <update_contact>peterix_at_gmail.com</update_contact>
+</component>
diff --git a/application/package/ubuntu/readme.md b/application/package/ubuntu/readme.md
deleted file mode 100644
index 5b0d6b27..00000000
--- a/application/package/ubuntu/readme.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# What is this?
-A simple ubuntu package for MultiMC that wraps the contains a script that downloads and installs real MultiMC on ubuntu based systems.
-
-It contains a `.desktop` file, an icon, and a simple script that does the heavy lifting.
-
-# How to build this?
-You need dpkg utils. Rename the `multimc` folder to `multimc_1.3-1` and then run:
-```
-fakeroot dpkg-deb --build multimc_1.3-1
-```
-
-Replace the version with whatever is appropriate.
diff --git a/application/pagedialog/PageDialog.cpp b/application/pagedialog/PageDialog.cpp
index a1a78d78..fd5d36d4 100644
--- a/application/pagedialog/PageDialog.cpp
+++ b/application/pagedialog/PageDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 b92d90b5..1029bc30 100644
--- a/application/pagedialog/PageDialog.h
+++ b/application/pagedialog/PageDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 2ad56d71..408965d0 100644
--- a/application/pages/BasePage.h
+++ b/application/pages/BasePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/BasePageProvider.h b/application/pages/BasePageProvider.h
index ad29f163..7bfaaf3b 100644
--- a/application/pages/BasePageProvider.h
+++ b/application/pages/BasePageProvider.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/application/pages/global/AccountListPage.cpp
index 3c900fab..ff3736ed 100644
--- a/application/pages/global/AccountListPage.cpp
+++ b/application/pages/global/AccountListPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,7 @@
#include "dialogs/SkinUploadDialog.h"
#include "tasks/Task.h"
#include "minecraft/auth/YggdrasilTask.h"
+#include "minecraft/services/SkinDelete.h"
#include "MultiMC.h"
@@ -142,6 +143,7 @@ void AccountListPage::updateButtonStates()
ui->actionRemove->setEnabled(selection.size() > 0);
ui->actionSetDefault->setEnabled(selection.size() > 0);
ui->actionUploadSkin->setEnabled(selection.size() > 0);
+ ui->actionDeleteSkin->setEnabled(selection.size() > 0);
if(m_accounts->activeAccount().get() == nullptr) {
ui->actionNoDefault->setEnabled(false);
@@ -191,3 +193,25 @@ void AccountListPage::on_actionUploadSkin_triggered()
dialog.exec();
}
}
+
+void AccountListPage::on_actionDeleteSkin_triggered()
+{
+ QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
+ if (selection.size() <= 0)
+ return;
+
+ QModelIndex selected = selection.first();
+ AuthSessionPtr session = std::make_shared<AuthSession>();
+ MojangAccountPtr account = selected.data(MojangAccountList::PointerRole).value<MojangAccountPtr>();
+ auto login = account->login(session);
+ ProgressDialog prog(this);
+ if (prog.execWithTask((Task*)login.get()) != QDialog::Accepted) {
+ CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to login!"), QMessageBox::Warning)->exec();
+ return;
+ }
+ auto deleteSkinTask = std::make_shared<SkinDelete>(this, session);
+ if (prog.execWithTask((Task*)deleteSkinTask.get()) != QDialog::Accepted) {
+ CustomMessageBox::selectable(this, tr("Skin Delete"), tr("Failed to delete current skin!"), QMessageBox::Warning)->exec();
+ return;
+ }
+}
diff --git a/application/pages/global/AccountListPage.h b/application/pages/global/AccountListPage.h
index 4c8bc00b..fba1833f 100644
--- a/application/pages/global/AccountListPage.h
+++ b/application/pages/global/AccountListPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,35 +59,26 @@ public:
return "Getting-Started#adding-an-account";
}
-private:
- void changeEvent(QEvent * event) override;
- QMenu * createPopupMenu() override;
-
-public
-slots:
+public slots:
void on_actionAdd_triggered();
-
void on_actionRemove_triggered();
-
void on_actionSetDefault_triggered();
-
void on_actionNoDefault_triggered();
-
void on_actionUploadSkin_triggered();
+ void on_actionDeleteSkin_triggered();
void listChanged();
//! Updates the states of the dialog's buttons.
void updateButtonStates();
-protected:
- std::shared_ptr<MojangAccountList> m_accounts;
-
-protected
-slots:
+protected slots:
void ShowContextMenu(const QPoint &pos);
void addAccount(const QString& errMsg="");
private:
+ void changeEvent(QEvent * event) override;
+ QMenu * createPopupMenu() override;
+ std::shared_ptr<MojangAccountList> m_accounts;
Ui::AccountListPage *ui;
};
diff --git a/application/pages/global/AccountListPage.ui b/application/pages/global/AccountListPage.ui
index ba07445e..71647db3 100644
--- a/application/pages/global/AccountListPage.ui
+++ b/application/pages/global/AccountListPage.ui
@@ -40,7 +40,9 @@
<addaction name="actionRemove"/>
<addaction name="actionSetDefault"/>
<addaction name="actionNoDefault"/>
+ <addaction name="separator"/>
<addaction name="actionUploadSkin"/>
+ <addaction name="actionDeleteSkin"/>
</widget>
<action name="actionAdd">
<property name="text">
@@ -70,6 +72,14 @@
<string>Upload Skin</string>
</property>
</action>
+ <action name="actionDeleteSkin">
+ <property name="text">
+ <string>Delete Skin</string>
+ </property>
+ <property name="toolTip">
+ <string>Delete the currently active skin and go back to the default one</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/application/pages/global/CustomCommandsPage.h b/application/pages/global/CustomCommandsPage.h
index a9047e63..414c3259 100644
--- a/application/pages/global/CustomCommandsPage.h
+++ b/application/pages/global/CustomCommandsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2018-2019 MultiMC Contributors
+/* Copyright 2018-2021 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.cpp b/application/pages/global/ExternalToolsPage.cpp
index 07670264..6a0a38be 100644
--- a/application/pages/global/ExternalToolsPage.cpp
+++ b/application/pages/global/ExternalToolsPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/pages/global/ExternalToolsPage.h
index 06e64273..0fc8ebe1 100644
--- a/application/pages/global/ExternalToolsPage.h
+++ b/application/pages/global/ExternalToolsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/application/pages/global/JavaPage.cpp
index 71c5f803..cde0e035 100644
--- a/application/pages/global/JavaPage.cpp
+++ b/application/pages/global/JavaPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,8 +37,8 @@ JavaPage::JavaPage(QWidget *parent) : QWidget(parent), ui(new Ui::JavaPage)
ui->setupUi(this);
ui->tabWidget->tabBar()->hide();
- auto sysMB = Sys::getSystemRam() / Sys::megabyte;
- ui->maxMemSpinBox->setMaximum(sysMB);
+ auto sysMiB = Sys::getSystemRam() / Sys::mebibyte;
+ ui->maxMemSpinBox->setMaximum(sysMiB);
loadSettings();
}
diff --git a/application/pages/global/JavaPage.h b/application/pages/global/JavaPage.h
index 3efebb78..832f460b 100644
--- a/application/pages/global/JavaPage.h
+++ b/application/pages/global/JavaPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 201b310c..b67e9994 100644
--- a/application/pages/global/JavaPage.ui
+++ b/application/pages/global/JavaPage.ui
@@ -51,7 +51,7 @@
<string>The maximum amount of memory Minecraft is allowed to use.</string>
</property>
<property name="suffix">
- <string notr="true"> MB</string>
+ <string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>128</number>
@@ -87,7 +87,7 @@
<string>The amount of memory Minecraft is started with.</string>
</property>
<property name="suffix">
- <string notr="true"> MB</string>
+ <string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>128</number>
@@ -116,7 +116,7 @@
<string>The amount of memory available to store loaded Java classes.</string>
</property>
<property name="suffix">
- <string notr="true"> MB</string>
+ <string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>64</number>
diff --git a/application/pages/global/LanguagePage.h b/application/pages/global/LanguagePage.h
index c4df2ea9..ca8eecc6 100644
--- a/application/pages/global/LanguagePage.h
+++ b/application/pages/global/LanguagePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/application/pages/global/MinecraftPage.cpp
index 1d7042ad..6c9bd307 100644
--- a/application/pages/global/MinecraftPage.cpp
+++ b/application/pages/global/MinecraftPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -63,6 +63,14 @@ void MinecraftPage::applySettings()
s->set("LaunchMaximized", ui->maximizedCheckBox->isChecked());
s->set("MinecraftWinWidth", ui->windowWidthSpinBox->value());
s->set("MinecraftWinHeight", ui->windowHeightSpinBox->value());
+
+ // Native library workarounds
+ s->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
+ s->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
+
+ // Game time
+ s->set("ShowGameTime", ui->showGameTime->isChecked());
+ s->set("RecordGameTime", ui->recordGameTime->isChecked());
}
void MinecraftPage::loadSettings()
@@ -73,4 +81,10 @@ void MinecraftPage::loadSettings()
ui->maximizedCheckBox->setChecked(s->get("LaunchMaximized").toBool());
ui->windowWidthSpinBox->setValue(s->get("MinecraftWinWidth").toInt());
ui->windowHeightSpinBox->setValue(s->get("MinecraftWinHeight").toInt());
+
+ ui->useNativeOpenALCheck->setChecked(s->get("UseNativeOpenAL").toBool());
+ ui->useNativeGLFWCheck->setChecked(s->get("UseNativeGLFW").toBool());
+
+ ui->showGameTime->setChecked(s->get("ShowGameTime").toBool());
+ ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool());
}
diff --git a/application/pages/global/MinecraftPage.h b/application/pages/global/MinecraftPage.h
index 39265fbe..5e781aed 100644
--- a/application/pages/global/MinecraftPage.h
+++ b/application/pages/global/MinecraftPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.ui b/application/pages/global/MinecraftPage.ui
index 9a18927a..2abd4bd4 100644
--- a/application/pages/global/MinecraftPage.ui
+++ b/application/pages/global/MinecraftPage.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>545</width>
- <height>195</height>
+ <width>936</width>
+ <height>1134</height>
</rect>
</property>
<property name="sizePolicy">
@@ -112,6 +112,52 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="nativeLibWorkaroundGroupBox">
+ <property name="title">
+ <string>Native library workarounds</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QCheckBox" name="useNativeGLFWCheck">
+ <property name="text">
+ <string>Use system installation of GLFW</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <property name="text">
+ <string>Use system installation of OpenAL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="gameTimeGroupBox">
+ <property name="title">
+ <string>Game time</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="QCheckBox" name="showGameTime">
+ <property name="text">
+ <string>Show time spent playing instances</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="recordGameTime">
+ <property name="text">
+ <string>Record time spent playing instances</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacerMinecraft">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -135,6 +181,8 @@
<tabstop>maximizedCheckBox</tabstop>
<tabstop>windowWidthSpinBox</tabstop>
<tabstop>windowHeightSpinBox</tabstop>
+ <tabstop>useNativeGLFWCheck</tabstop>
+ <tabstop>useNativeOpenALCheck</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/application/pages/global/MultiMCPage.cpp b/application/pages/global/MultiMCPage.cpp
index 3a8c46e8..80d5c544 100644
--- a/application/pages/global/MultiMCPage.cpp
+++ b/application/pages/global/MultiMCPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/pages/global/MultiMCPage.h
index 27a801be..e81832eb 100644
--- a/application/pages/global/MultiMCPage.h
+++ b/application/pages/global/MultiMCPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 ba4885e8..f932dede 100644
--- a/application/pages/global/PasteEEPage.cpp
+++ b/application/pages/global/PasteEEPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/pages/global/PasteEEPage.h
index 78cf41f4..001decdb 100644
--- a/application/pages/global/PasteEEPage.h
+++ b/application/pages/global/PasteEEPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/application/pages/global/ProxyPage.cpp
index 3f0e766b..809059ff 100644
--- a/application/pages/global/ProxyPage.cpp
+++ b/application/pages/global/ProxyPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 d87bc120..ff94ec49 100644
--- a/application/pages/global/ProxyPage.h
+++ b/application/pages/global/ProxyPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/GameOptionsPage.cpp b/application/pages/instance/GameOptionsPage.cpp
index 5555fc79..782f2ab3 100644
--- a/application/pages/instance/GameOptionsPage.cpp
+++ b/application/pages/instance/GameOptionsPage.cpp
@@ -35,6 +35,3 @@ void GameOptionsPage::closedImpl()
{
// m_model->unobserve();
}
-
-#include "GameOptionsPage.moc"
-
diff --git a/application/pages/instance/GameOptionsPage.h b/application/pages/instance/GameOptionsPage.h
index ae47747f..0fd2fbff 100644
--- a/application/pages/instance/GameOptionsPage.h
+++ b/application/pages/instance/GameOptionsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/InstanceSettingsPage.cpp b/application/pages/instance/InstanceSettingsPage.cpp
index b7b0a863..7bd424c0 100644
--- a/application/pages/instance/InstanceSettingsPage.cpp
+++ b/application/pages/instance/InstanceSettingsPage.cpp
@@ -19,7 +19,7 @@ InstanceSettingsPage::InstanceSettingsPage(BaseInstance *inst, QWidget *parent)
{
m_settings = inst->settings();
ui->setupUi(this);
- auto sysMB = Sys::getSystemRam() / Sys::megabyte;
+ auto sysMB = Sys::getSystemRam() / Sys::mebibyte;
ui->maxMemSpinBox->setMaximum(sysMB);
connect(ui->openGlobalJavaSettingsButton, &QCommandLinkButton::clicked, this, &InstanceSettingsPage::globalSettingsButtonClicked);
connect(MMC, &MultiMC::globalSettingsAboutToOpen, this, &InstanceSettingsPage::applySettings);
@@ -163,6 +163,46 @@ void InstanceSettingsPage::applySettings()
m_settings->reset("WrapperCommand");
m_settings->reset("PostExitCommand");
}
+
+ // Workarounds
+ bool workarounds = ui->nativeWorkaroundsGroupBox->isChecked();
+ m_settings->set("OverrideNativeWorkarounds", workarounds);
+ if(workarounds)
+ {
+ m_settings->set("UseNativeOpenAL", ui->useNativeOpenALCheck->isChecked());
+ m_settings->set("UseNativeGLFW", ui->useNativeGLFWCheck->isChecked());
+ }
+ else
+ {
+ m_settings->reset("UseNativeOpenAL");
+ m_settings->reset("UseNativeGLFW");
+ }
+
+ // Game time
+ bool gameTime = ui->gameTimeGroupBox->isChecked();
+ m_settings->set("OverrideGameTime", gameTime);
+ if (gameTime)
+ {
+ m_settings->set("ShowGameTime", ui->showGameTime->isChecked());
+ m_settings->set("RecordGameTime", ui->recordGameTime->isChecked());
+ }
+ else
+ {
+ m_settings->reset("ShowGameTime");
+ m_settings->reset("RecordGameTime");
+ }
+
+ // Join server on launch
+ bool joinServerOnLaunch = ui->serverJoinGroupBox->isChecked();
+ m_settings->set("JoinServerOnLaunch", joinServerOnLaunch);
+ if (joinServerOnLaunch)
+ {
+ m_settings->set("JoinServerOnLaunchAddress", ui->serverJoinAddress->text());
+ }
+ else
+ {
+ m_settings->reset("JoinServerOnLaunchAddress");
+ }
}
void InstanceSettingsPage::loadSettings()
@@ -219,6 +259,19 @@ void InstanceSettingsPage::loadSettings()
m_settings->get("WrapperCommand").toString(),
m_settings->get("PostExitCommand").toString()
);
+
+ // Workarounds
+ ui->nativeWorkaroundsGroupBox->setChecked(m_settings->get("OverrideNativeWorkarounds").toBool());
+ ui->useNativeGLFWCheck->setChecked(m_settings->get("UseNativeGLFW").toBool());
+ ui->useNativeOpenALCheck->setChecked(m_settings->get("UseNativeOpenAL").toBool());
+
+ // Miscellanous
+ ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool());
+ ui->showGameTime->setChecked(m_settings->get("ShowGameTime").toBool());
+ ui->recordGameTime->setChecked(m_settings->get("RecordGameTime").toBool());
+
+ ui->serverJoinGroupBox->setChecked(m_settings->get("JoinServerOnLaunch").toBool());
+ ui->serverJoinAddress->setText(m_settings->get("JoinServerOnLaunchAddress").toString());
}
void InstanceSettingsPage::on_javaDetectBtn_clicked()
diff --git a/application/pages/instance/InstanceSettingsPage.h b/application/pages/instance/InstanceSettingsPage.h
index c3c78fd5..068213a8 100644
--- a/application/pages/instance/InstanceSettingsPage.h
+++ b/application/pages/instance/InstanceSettingsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/InstanceSettingsPage.ui b/application/pages/instance/InstanceSettingsPage.ui
index d6de53ee..e569ce56 100644
--- a/application/pages/instance/InstanceSettingsPage.ui
+++ b/application/pages/instance/InstanceSettingsPage.ui
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
- <width>738</width>
- <height>804</height>
+ <width>691</width>
+ <height>581</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -116,7 +116,7 @@
<string>The maximum amount of memory Minecraft is allowed to use.</string>
</property>
<property name="suffix">
- <string notr="true"> MB</string>
+ <string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>128</number>
@@ -138,7 +138,7 @@
<string>The amount of memory Minecraft is started with.</string>
</property>
<property name="suffix">
- <string notr="true"> MB</string>
+ <string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>128</number>
@@ -160,7 +160,7 @@
<string>The amount of memory available to store loaded Java classes.</string>
</property>
<property name="suffix">
- <string notr="true"> MB</string>
+ <string notr="true"> MiB</string>
</property>
<property name="minimum">
<number>64</number>
@@ -364,6 +364,145 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="workaroundsPage">
+ <attribute name="title">
+ <string>Workarounds</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_8">
+ <item>
+ <widget class="QGroupBox" name="nativeWorkaroundsGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Native libraries</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_7">
+ <item>
+ <widget class="QCheckBox" name="useNativeGLFWCheck">
+ <property name="text">
+ <string>Use system installation of GLFW</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useNativeOpenALCheck">
+ <property name="text">
+ <string>Use system installation of OpenAL</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </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>
+ <widget class="QWidget" name="miscellanousPage">
+ <attribute name="title">
+ <string>Miscellanous</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_9">
+ <item>
+ <widget class="QGroupBox" name="gameTimeGroupBox">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="title">
+ <string>Override global game time settings</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_10">
+ <item>
+ <widget class="QCheckBox" name="showGameTime">
+ <property name="text">
+ <string>Show time spent playing this instance</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="recordGameTime">
+ <property name="text">
+ <string>Record time spent playing this instance</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="serverJoinGroupBox">
+ <property name="title">
+ <string>Set a server to join on launch</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <item>
+ <layout class="QGridLayout" name="serverJoinLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="serverJoinAddressLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Server address:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="serverJoinAddress"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacerMiscellanous">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</widget>
</item>
</layout>
@@ -398,6 +537,11 @@
<tabstop>showConsoleCheck</tabstop>
<tabstop>autoCloseConsoleCheck</tabstop>
<tabstop>showConsoleErrorCheck</tabstop>
+ <tabstop>nativeWorkaroundsGroupBox</tabstop>
+ <tabstop>useNativeGLFWCheck</tabstop>
+ <tabstop>useNativeOpenALCheck</tabstop>
+ <tabstop>showGameTime</tabstop>
+ <tabstop>recordGameTime</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/application/pages/instance/LegacyUpgradePage.h b/application/pages/instance/LegacyUpgradePage.h
index 4f86a721..df34e33a 100644
--- a/application/pages/instance/LegacyUpgradePage.h
+++ b/application/pages/instance/LegacyUpgradePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/LogPage.cpp b/application/pages/instance/LogPage.cpp
index 94ada424..3d2085c6 100644
--- a/application/pages/instance/LogPage.cpp
+++ b/application/pages/instance/LogPage.cpp
@@ -236,15 +236,15 @@ void LogPage::on_btnPaste_clicked()
return;
//FIXME: turn this into a proper task and move the upload logic out of GuiUtil!
- m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)));
+ m_model->append(MessageLevel::MultiMC, QString("MultiMC: Log upload triggered at: %1").arg(QDateTime::currentDateTime().toString(Qt::RFC2822Date)));
auto url = GuiUtil::uploadPaste(m_model->toPlainText(), this);
if(!url.isEmpty())
{
- m_model->append(MessageLevel::MultiMC, tr("MultiMC: Log uploaded to: %1").arg(url));
+ m_model->append(MessageLevel::MultiMC, QString("MultiMC: Log uploaded to: %1").arg(url));
}
else
{
- m_model->append(MessageLevel::Error, tr("MultiMC: Log upload failed!"));
+ m_model->append(MessageLevel::Error, "MultiMC: Log upload failed!");
}
}
diff --git a/application/pages/instance/LogPage.h b/application/pages/instance/LogPage.h
index 9c3b56a9..b0b0e04b 100644
--- a/application/pages/instance/LogPage.h
+++ b/application/pages/instance/LogPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp
index 2981ea13..98f20e77 100644
--- a/application/pages/instance/ModFolderPage.cpp
+++ b/application/pages/instance/ModFolderPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -163,7 +163,7 @@ ModFolderPage::ModFolderPage(
auto smodel = ui->modTreeView->selectionModel();
connect(smodel, &QItemSelectionModel::currentChanged, this, &ModFolderPage::modCurrent);
- connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged );
+ connect(ui->filterEdit, &QLineEdit::textChanged, this, &ModFolderPage::on_filterTextChanged);
connect(m_inst, &BaseInstance::runningStatusChanged, this, &ModFolderPage::on_RunningState_changed);
}
diff --git a/application/pages/instance/ModFolderPage.h b/application/pages/instance/ModFolderPage.h
index d49d25c3..f653a8c0 100644
--- a/application/pages/instance/ModFolderPage.h
+++ b/application/pages/instance/ModFolderPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/NotesPage.h b/application/pages/instance/NotesPage.h
index f96e5374..d0c00ac1 100644
--- a/application/pages/instance/NotesPage.h
+++ b/application/pages/instance/NotesPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/OtherLogsPage.cpp b/application/pages/instance/OtherLogsPage.cpp
index 387db74f..b67b84bd 100644
--- a/application/pages/instance/OtherLogsPage.cpp
+++ b/application/pages/instance/OtherLogsPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/OtherLogsPage.h b/application/pages/instance/OtherLogsPage.h
index 226f3af3..7f21c0fa 100644
--- a/application/pages/instance/OtherLogsPage.h
+++ b/application/pages/instance/OtherLogsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/ResourcePackPage.h b/application/pages/instance/ResourcePackPage.h
index e11c78a3..1486bf52 100644
--- a/application/pages/instance/ResourcePackPage.h
+++ b/application/pages/instance/ResourcePackPage.h
@@ -1,4 +1,5 @@
#pragma once
+
#include "ModFolderPage.h"
#include "ui_ModFolderPage.h"
@@ -12,8 +13,8 @@ public:
{
ui->actionView_configs->setVisible(false);
}
-
virtual ~ResourcePackPage() {}
+
virtual bool shouldDisplay() const override
{
return !m_inst->traits().contains("no-texturepacks") &&
diff --git a/application/pages/instance/ScreenshotsPage.h b/application/pages/instance/ScreenshotsPage.h
index 9adf79af..03a809de 100644
--- a/application/pages/instance/ScreenshotsPage.h
+++ b/application/pages/instance/ScreenshotsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/instance/ServersPage.cpp b/application/pages/instance/ServersPage.cpp
index 8b0c655c..d63c6e70 100644
--- a/application/pages/instance/ServersPage.cpp
+++ b/application/pages/instance/ServersPage.cpp
@@ -556,7 +556,7 @@ private:
QTimer m_saveTimer;
};
-ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent)
+ServersPage::ServersPage(InstancePtr inst, QWidget* parent)
: QMainWindow(parent), ui(new Ui::ServersPage)
{
ui->setupUi(this);
@@ -579,7 +579,7 @@ ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent)
auto selectionModel = ui->serversView->selectionModel();
connect(selectionModel, &QItemSelectionModel::currentChanged, this, &ServersPage::currentChanged);
- connect(m_inst, &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed);
+ connect(m_inst.get(), &MinecraftInstance::runningStatusChanged, this, &ServersPage::on_RunningState_changed);
connect(ui->nameLine, &QLineEdit::textEdited, this, &ServersPage::nameEdited);
connect(ui->addressLine, &QLineEdit::textEdited, this, &ServersPage::addressEdited);
connect(ui->resourceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(resourceIndexChanged(int)));
@@ -695,6 +695,7 @@ void ServersPage::updateState()
ui->actionMove_Down->setEnabled(serverEditEnabled);
ui->actionMove_Up->setEnabled(serverEditEnabled);
ui->actionRemove->setEnabled(serverEditEnabled);
+ ui->actionJoin->setEnabled(serverEditEnabled);
if(server)
{
@@ -758,4 +759,10 @@ void ServersPage::on_actionMove_Down_triggered()
}
}
+void ServersPage::on_actionJoin_triggered()
+{
+ const auto &address = m_model->at(currentServer)->m_address;
+ MMC->launch(m_inst, true, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address)));
+}
+
#include "ServersPage.moc"
diff --git a/application/pages/instance/ServersPage.h b/application/pages/instance/ServersPage.h
index c81f47be..8c5b7eb8 100644
--- a/application/pages/instance/ServersPage.h
+++ b/application/pages/instance/ServersPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ class ServersPage : public QMainWindow, public BasePage
Q_OBJECT
public:
- explicit ServersPage(MinecraftInstance *inst, QWidget *parent = 0);
+ explicit ServersPage(InstancePtr inst, QWidget *parent = 0);
virtual ~ServersPage();
void openedImpl() override;
@@ -74,6 +74,7 @@ private slots:
void on_actionRemove_triggered();
void on_actionMove_Up_triggered();
void on_actionMove_Down_triggered();
+ void on_actionJoin_triggered();
void on_RunningState_changed(bool running);
@@ -88,6 +89,6 @@ private: // data
bool m_locked = true;
Ui::ServersPage *ui = nullptr;
ServersModel * m_model = nullptr;
- MinecraftInstance * m_inst = nullptr;
+ InstancePtr m_inst = nullptr;
};
diff --git a/application/pages/instance/ServersPage.ui b/application/pages/instance/ServersPage.ui
index e9518e35..d89b7cba 100644
--- a/application/pages/instance/ServersPage.ui
+++ b/application/pages/instance/ServersPage.ui
@@ -148,6 +148,7 @@
<addaction name="actionRemove"/>
<addaction name="actionMove_Up"/>
<addaction name="actionMove_Down"/>
+ <addaction name="actionJoin"/>
</widget>
<action name="actionAdd">
<property name="text">
@@ -169,6 +170,11 @@
<string>Move Down</string>
</property>
</action>
+ <action name="actionJoin">
+ <property name="text">
+ <string>Join</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/application/pages/instance/TexturePackPage.h b/application/pages/instance/TexturePackPage.h
index a792ba07..3f04997d 100644
--- a/application/pages/instance/TexturePackPage.h
+++ b/application/pages/instance/TexturePackPage.h
@@ -1,4 +1,5 @@
#pragma once
+
#include "ModFolderPage.h"
#include "ui_ModFolderPage.h"
@@ -13,6 +14,7 @@ public:
ui->actionView_configs->setVisible(false);
}
virtual ~TexturePackPage() {}
+
virtual bool shouldDisplay() const override
{
return m_inst->traits().contains("texturepacks");
diff --git a/application/pages/instance/VersionPage.cpp b/application/pages/instance/VersionPage.cpp
index f2d19f25..eff12c9c 100644
--- a/application/pages/instance/VersionPage.cpp
+++ b/application/pages/instance/VersionPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -120,7 +120,15 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
auto proxy = new IconProxy(ui->packageView);
proxy->setSourceModel(m_profile.get());
- ui->packageView->setModel(proxy);
+
+ m_filterModel = new QSortFilterProxyModel();
+ m_filterModel->setDynamicSortFilter(true);
+ m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+ m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
+ m_filterModel->setSourceModel(proxy);
+ m_filterModel->setFilterKeyColumn(-1);
+
+ ui->packageView->setModel(m_filterModel);
ui->packageView->installEventFilter(this);
ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->packageView->setContextMenuPolicy(Qt::CustomContextMenu);
@@ -134,7 +142,8 @@ VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
updateVersionControls();
preselect(0);
connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus);
- connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::ShowContextMenu);
+ connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
+ connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
}
VersionPage::~VersionPage()
@@ -142,7 +151,7 @@ VersionPage::~VersionPage()
delete ui;
}
-void VersionPage::ShowContextMenu(const QPoint& pos)
+void VersionPage::showContextMenu(const QPoint& pos)
{
auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
menu->exec(ui->packageView->mapToGlobal(pos));
@@ -203,11 +212,11 @@ void VersionPage::updateVersionControls()
{
// FIXME: this is a dirty hack
auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft"));
- bool newCraft = controlsEnabled && (minecraftVersion >= Version("1.14"));
- bool oldCraft = controlsEnabled && (minecraftVersion <= Version("1.12.2"));
- ui->actionInstall_Fabric->setEnabled(newCraft);
- ui->actionInstall_Forge->setEnabled(true);
- ui->actionInstall_LiteLoader->setEnabled(oldCraft);
+ bool newCraft = minecraftVersion >= Version("1.14");
+ bool oldCraft = minecraftVersion <= Version("1.12.2");
+ ui->actionInstall_Fabric->setEnabled(controlsEnabled && newCraft);
+ ui->actionInstall_Forge->setEnabled(controlsEnabled);
+ ui->actionInstall_LiteLoader->setEnabled(controlsEnabled && oldCraft);
ui->actionReload->setEnabled(true);
updateButtons();
}
@@ -620,5 +629,10 @@ void VersionPage::on_actionRevert_triggered()
m_container->refreshContainer();
}
+void VersionPage::onFilterTextChanged(const QString &newContents)
+{
+ m_filterModel->setFilterFixedString(newContents);
+}
+
#include "VersionPage.moc"
diff --git a/application/pages/instance/VersionPage.h b/application/pages/instance/VersionPage.h
index c95a0084..b5b4a6f5 100644
--- a/application/pages/instance/VersionPage.h
+++ b/application/pages/instance/VersionPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -86,6 +86,7 @@ protected:
private:
Ui::VersionPage *ui;
+ QSortFilterProxyModel *m_filterModel;
std::shared_ptr<PackProfile> m_profile;
MinecraftInstance *m_inst;
int currentIdx = 0;
@@ -98,5 +99,6 @@ private slots:
void updateRunningStatus(bool running);
void onGameUpdateError(QString error);
void packageCurrent(const QModelIndex &current, const QModelIndex &previous);
- void ShowContextMenu(const QPoint &pos);
+ void showContextMenu(const QPoint &pos);
+ void onFilterTextChanged(const QString & newContents);
};
diff --git a/application/pages/instance/VersionPage.ui b/application/pages/instance/VersionPage.ui
index 718ad067..84d06e2e 100644
--- a/application/pages/instance/VersionPage.ui
+++ b/application/pages/instance/VersionPage.ui
@@ -46,6 +46,24 @@
</widget>
</item>
<item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="filterEdit">
+ <property name="clearButtonEnabled">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="filterLabel">
+ <property name="text">
+ <string>Filter:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
<widget class="MCModInfoFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
diff --git a/application/pages/instance/WorldListPage.cpp b/application/pages/instance/WorldListPage.cpp
index 8358a0f1..119cff3e 100644
--- a/application/pages/instance/WorldListPage.cpp
+++ b/application/pages/instance/WorldListPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +31,33 @@
#include <QProcess>
#include <FileSystem.h>
+class WorldListProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ WorldListProxyModel(QObject *parent) : QSortFilterProxyModel(parent) {}
+
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
+ {
+ QModelIndex sourceIndex = mapToSource(index);
+
+ if (index.column() == 0 && role == Qt::DecorationRole)
+ {
+ WorldList *worlds = qobject_cast<WorldList *>(sourceModel());
+ auto iconFile = worlds->data(sourceIndex, WorldList::IconFileRole).toString();
+ if(iconFile.isNull()) {
+ // NOTE: Minecraft uses the same placeholder for servers AND worlds
+ return MMC->getThemedIcon("unknown_server");
+ }
+ return QIcon(iconFile);
+ }
+
+ return sourceIndex.data(role);
+ }
+};
+
+
WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QWidget *parent)
: QMainWindow(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds)
{
@@ -38,13 +65,14 @@ WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worl
ui->toolBar->insertSpacer(ui->actionRefresh);
- QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this);
+ WorldListProxyModel * proxy = new WorldListProxyModel(this);
proxy->setSortCaseSensitivity(Qt::CaseInsensitive);
proxy->setSourceModel(m_worlds.get());
ui->worldTreeView->setSortingEnabled(true);
ui->worldTreeView->setModel(proxy);
ui->worldTreeView->installEventFilter(this);
ui->worldTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
+ ui->worldTreeView->setIconSize(QSize(64,64));
connect(ui->worldTreeView, &QTreeView::customContextMenuRequested, this, &WorldListPage::ShowContextMenu);
auto head = ui->worldTreeView->header();
@@ -142,6 +170,37 @@ void WorldListPage::on_actionView_Folder_triggered()
DesktopServices::openDirectory(m_worlds->dir().absolutePath(), true);
}
+void WorldListPage::on_actionDatapacks_triggered()
+{
+ QModelIndex index = getSelectedWorld();
+
+ if (!index.isValid())
+ {
+ return;
+ }
+
+ if(!worldSafetyNagQuestion())
+ return;
+
+ auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString();
+
+ DesktopServices::openDirectory(FS::PathCombine(fullPath, "datapacks"), true);
+}
+
+
+void WorldListPage::on_actionReset_Icon_triggered()
+{
+ auto proxiedIndex = getSelectedWorld();
+
+ if(!proxiedIndex.isValid())
+ return;
+
+ if(m_worlds->resetIcon(proxiedIndex.row())) {
+ ui->actionReset_Icon->setEnabled(false);
+ }
+}
+
+
QModelIndex WorldListPage::getSelectedWorld()
{
auto index = ui->worldTreeView->selectionModel()->currentIndex();
@@ -255,6 +314,9 @@ void WorldListPage::worldChanged(const QModelIndex &current, const QModelIndex &
ui->actionRemove->setEnabled(enable);
ui->actionCopy->setEnabled(enable);
ui->actionRename->setEnabled(enable);
+ ui->actionDatapacks->setEnabled(enable);
+ bool hasIcon = !index.data(WorldList::IconFileRole).isNull();
+ ui->actionReset_Icon->setEnabled(enable && hasIcon);
}
void WorldListPage::on_actionAdd_triggered()
@@ -342,3 +404,5 @@ void WorldListPage::on_actionRefresh_triggered()
{
m_worlds->update();
}
+
+#include "WorldListPage.moc"
diff --git a/application/pages/instance/WorldListPage.h b/application/pages/instance/WorldListPage.h
index c39420da..4fc9aa09 100644
--- a/application/pages/instance/WorldListPage.h
+++ b/application/pages/instance/WorldListPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2019 MultiMC Contributors
+/* Copyright 2015-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -90,6 +90,8 @@ private slots:
void on_actionRename_triggered();
void on_actionRefresh_triggered();
void on_actionView_Folder_triggered();
+ void on_actionDatapacks_triggered();
+ void on_actionReset_Icon_triggered();
void worldChanged(const QModelIndex &current, const QModelIndex &previous);
void mceditState(LoggedProcess::State state);
diff --git a/application/pages/instance/WorldListPage.ui b/application/pages/instance/WorldListPage.ui
index ddb3dfa9..ed078d94 100644
--- a/application/pages/instance/WorldListPage.ui
+++ b/application/pages/instance/WorldListPage.ui
@@ -41,6 +41,12 @@
<property name="alternatingRowColors">
<bool>true</bool>
</property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <property name="itemsExpandable">
+ <bool>false</bool>
+ </property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
@@ -79,6 +85,8 @@
<addaction name="actionCopy"/>
<addaction name="actionRemove"/>
<addaction name="actionMCEdit"/>
+ <addaction name="actionDatapacks"/>
+ <addaction name="actionReset_Icon"/>
<addaction name="separator"/>
<addaction name="actionCopy_Seed"/>
<addaction name="actionRefresh"/>
@@ -124,6 +132,22 @@
<string>View Folder</string>
</property>
</action>
+ <action name="actionReset_Icon">
+ <property name="text">
+ <string>Reset Icon</string>
+ </property>
+ <property name="toolTip">
+ <string>Remove world icon to make the game re-generate it on next load.</string>
+ </property>
+ </action>
+ <action name="actionDatapacks">
+ <property name="text">
+ <string>Datapacks</string>
+ </property>
+ <property name="toolTip">
+ <string>Manage datapacks inside the world.</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
diff --git a/application/pages/modplatform/ImportPage.cpp b/application/pages/modplatform/ImportPage.cpp
index 3910dfda..c2369bdc 100644
--- a/application/pages/modplatform/ImportPage.cpp
+++ b/application/pages/modplatform/ImportPage.cpp
@@ -71,6 +71,7 @@ void ImportPage::updateState()
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
+ dialog->setSuggestedIcon("default");
}
}
else
@@ -83,6 +84,7 @@ void ImportPage::updateState()
// hook, line and sinker.
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
+ dialog->setSuggestedIcon("default");
}
}
else
diff --git a/application/pages/modplatform/ImportPage.h b/application/pages/modplatform/ImportPage.h
index 3afb0045..67e3c201 100644
--- a/application/pages/modplatform/ImportPage.h
+++ b/application/pages/modplatform/ImportPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/modplatform/VanillaPage.cpp b/application/pages/modplatform/VanillaPage.cpp
index 17535f1e..02638315 100644
--- a/application/pages/modplatform/VanillaPage.cpp
+++ b/application/pages/modplatform/VanillaPage.cpp
@@ -82,10 +82,19 @@ BaseVersionPtr VanillaPage::selectedVersion() const
void VanillaPage::suggestCurrent()
{
- if(m_selectedVersion && isOpened)
+ if (!isOpened)
{
- dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion));
+ return;
}
+
+ if(!m_selectedVersion)
+ {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion));
+ dialog->setSuggestedIcon("default");
}
void VanillaPage::setSelectedVersion(BaseVersionPtr version)
diff --git a/application/pages/modplatform/VanillaPage.h b/application/pages/modplatform/VanillaPage.h
index cc77733c..af6fd392 100644
--- a/application/pages/modplatform/VanillaPage.h
+++ b/application/pages/modplatform/VanillaPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/modplatform/atlauncher/AtlFilterModel.cpp b/application/pages/modplatform/atlauncher/AtlFilterModel.cpp
new file mode 100644
index 00000000..b5d8f22b
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlFilterModel.cpp
@@ -0,0 +1,81 @@
+#include "AtlFilterModel.h"
+
+#include <QDebug>
+
+#include <modplatform/atlauncher/ATLPackIndex.h>
+#include <Version.h>
+#include <MMCStrings.h>
+
+namespace Atl {
+
+FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
+{
+ currentSorting = Sorting::ByPopularity;
+ sortings.insert(tr("Sort by popularity"), Sorting::ByPopularity);
+ sortings.insert(tr("Sort by name"), Sorting::ByName);
+ sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion);
+
+ searchTerm = "";
+}
+
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
+{
+ return sortings;
+}
+
+QString FilterModel::translateCurrentSorting()
+{
+ return sortings.key(currentSorting);
+}
+
+void FilterModel::setSorting(Sorting sorting)
+{
+ currentSorting = sorting;
+ invalidate();
+}
+
+FilterModel::Sorting FilterModel::getCurrentSorting()
+{
+ return currentSorting;
+}
+
+void FilterModel::setSearchTerm(const QString term)
+{
+ searchTerm = term.trimmed();
+ invalidate();
+}
+
+bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ if (searchTerm.isEmpty()) {
+ return true;
+ }
+
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ ATLauncher::IndexedPack pack = sourceModel()->data(index, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ return pack.name.contains(searchTerm, Qt::CaseInsensitive);
+}
+
+bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ ATLauncher::IndexedPack leftPack = sourceModel()->data(left, Qt::UserRole).value<ATLauncher::IndexedPack>();
+ ATLauncher::IndexedPack rightPack = sourceModel()->data(right, Qt::UserRole).value<ATLauncher::IndexedPack>();
+
+ if (currentSorting == ByPopularity) {
+ return leftPack.position > rightPack.position;
+ }
+ else if (currentSorting == ByGameVersion) {
+ Version lv(leftPack.versions.at(0).minecraft);
+ Version rv(rightPack.versions.at(0).minecraft);
+ return lv < rv;
+ }
+ else if (currentSorting == ByName) {
+ return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ }
+
+ // Invalid sorting set, somehow...
+ qWarning() << "Invalid sorting set!";
+ return true;
+}
+
+}
diff --git a/application/pages/modplatform/atlauncher/AtlFilterModel.h b/application/pages/modplatform/atlauncher/AtlFilterModel.h
new file mode 100644
index 00000000..bd72ad91
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlFilterModel.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <QtCore/QSortFilterProxyModel>
+
+namespace Atl {
+
+class FilterModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+public:
+ FilterModel(QObject* parent = Q_NULLPTR);
+ enum Sorting {
+ ByPopularity,
+ ByGameVersion,
+ ByName,
+ };
+ const QMap<QString, Sorting> getAvailableSortings();
+ QString translateCurrentSorting();
+ void setSorting(Sorting sorting);
+ Sorting getCurrentSorting();
+ void setSearchTerm(QString term);
+
+protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+ bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
+
+private:
+ QMap<QString, Sorting> sortings;
+ Sorting currentSorting;
+ QString searchTerm;
+
+};
+
+}
diff --git a/application/pages/modplatform/atlauncher/AtlListModel.cpp b/application/pages/modplatform/atlauncher/AtlListModel.cpp
new file mode 100644
index 00000000..f3be6198
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlListModel.cpp
@@ -0,0 +1,194 @@
+#include "AtlListModel.h"
+
+#include <BuildConfig.h>
+#include <MultiMC.h>
+#include <Env.h>
+#include <Json.h>
+
+namespace Atl {
+
+ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
+{
+}
+
+ListModel::~ListModel()
+{
+}
+
+int ListModel::rowCount(const QModelIndex &parent) const
+{
+ return modpacks.size();
+}
+
+int ListModel::columnCount(const QModelIndex &parent) const
+{
+ return 1;
+}
+
+QVariant ListModel::data(const QModelIndex &index, int role) const
+{
+ int pos = index.row();
+ if(pos >= modpacks.size() || pos < 0 || !index.isValid())
+ {
+ return QString("INVALID INDEX %1").arg(pos);
+ }
+
+ ATLauncher::IndexedPack pack = modpacks.at(pos);
+ if(role == Qt::DisplayRole)
+ {
+ return pack.name;
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ return pack.name;
+ }
+ else if(role == Qt::DecorationRole)
+ {
+ if(m_logoMap.contains(pack.safeName))
+ {
+ return (m_logoMap.value(pack.safeName));
+ }
+ auto icon = MMC->getThemedIcon("atlauncher-placeholder");
+
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(pack.safeName.toLower());
+ ((ListModel *)this)->requestLogo(pack.safeName, url);
+
+ return icon;
+ }
+ else if(role == Qt::UserRole)
+ {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+
+ return QVariant();
+}
+
+void ListModel::request()
+{
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+
+ auto *netJob = new NetJob("Atl::Request");
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/json/packsnew.json");
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(url), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+
+ QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::requestFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &ListModel::requestFailed);
+}
+
+void ListModel::requestFinished()
+{
+ jobPtr.reset();
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from ATL at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ QList<ATLauncher::IndexedPack> newList;
+
+ auto packs = doc.array();
+ for(auto packRaw : packs) {
+ auto packObj = packRaw.toObject();
+
+ ATLauncher::IndexedPack pack;
+
+ try {
+ ATLauncher::loadIndexedPack(pack, packObj);
+ }
+ catch (const JSONValidationError &e) {
+ qDebug() << QString::fromUtf8(response);
+ qWarning() << "Error while reading pack manifest from ATLauncher: " << e.cause();
+ return;
+ }
+
+ // ignore packs without a published version
+ if(pack.versions.length() == 0) continue;
+ // only display public packs (for now)
+ if(pack.type != ATLauncher::PackType::Public) continue;
+ // ignore "system" packs (Vanilla, Vanilla with Forge, etc)
+ if(pack.system) continue;
+
+ newList.append(pack);
+ }
+
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
+ modpacks.append(newList);
+ endInsertRows();
+}
+
+void ListModel::requestFailed(QString reason)
+{
+ jobPtr.reset();
+}
+
+void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
+{
+ if(m_logoMap.contains(logo))
+ {
+ callback(ENV.metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ }
+ else
+ {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+void ListModel::logoFailed(QString logo)
+{
+ m_failedLogos.append(logo);
+ m_loadingLogos.removeAll(logo);
+}
+
+void ListModel::logoLoaded(QString logo, QIcon out)
+{
+ m_loadingLogos.removeAll(logo);
+ m_logoMap.insert(logo, out);
+
+ for(int i = 0; i < modpacks.size(); i++) {
+ if(modpacks[i].safeName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ }
+ }
+}
+
+void ListModel::requestLogo(QString file, QString url)
+{
+ if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
+ {
+ return;
+ }
+
+ MetaEntryPtr entry = ENV.metacache()->resolveEntry("ATLauncherPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
+ NetJob *job = new NetJob(QString("ATLauncher Icon Download %1").arg(file));
+ job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+
+ auto fullPath = entry->getFullPath();
+ QObject::connect(job, &NetJob::succeeded, this, [this, file, fullPath]
+ {
+ emit logoLoaded(file, QIcon(fullPath));
+ if(waitingCallbacks.contains(file))
+ {
+ waitingCallbacks.value(file)(fullPath);
+ }
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, file]
+ {
+ emit logoFailed(file);
+ });
+
+ job->start();
+
+ m_loadingLogos.append(file);
+}
+
+}
diff --git a/application/pages/modplatform/atlauncher/AtlListModel.h b/application/pages/modplatform/atlauncher/AtlListModel.h
new file mode 100644
index 00000000..2d30a64e
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlListModel.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <QAbstractListModel>
+
+#include "net/NetJob.h"
+#include <QIcon>
+#include <modplatform/atlauncher/ATLPackIndex.h>
+
+namespace Atl {
+
+typedef QMap<QString, QIcon> LogoMap;
+typedef std::function<void(QString)> LogoCallback;
+
+class ListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ ListModel(QObject *parent);
+ virtual ~ListModel();
+
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ void request();
+
+ void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+
+private slots:
+ void requestFinished();
+ void requestFailed(QString reason);
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, QIcon out);
+
+private:
+ void requestLogo(QString file, QString url);
+
+private:
+ QList<ATLauncher::IndexedPack> modpacks;
+
+ QStringList m_failedLogos;
+ QStringList m_loadingLogos;
+ LogoMap m_logoMap;
+ QMap<QString, LogoCallback> waitingCallbacks;
+
+ NetJobPtr jobPtr;
+ QByteArray response;
+};
+
+}
diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
new file mode 100644
index 00000000..14bbd18b
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.cpp
@@ -0,0 +1,209 @@
+#include "AtlOptionalModDialog.h"
+#include "ui_AtlOptionalModDialog.h"
+
+AtlOptionalModListModel::AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
+ : QAbstractListModel(parent), m_mods(mods) {
+
+ // fill mod index
+ for (int i = 0; i < m_mods.size(); i++) {
+ auto mod = m_mods.at(i);
+ m_index[mod.name] = i;
+ }
+ // set initial state
+ for (int i = 0; i < m_mods.size(); i++) {
+ auto mod = m_mods.at(i);
+ m_selection[mod.name] = false;
+ setMod(mod, i, mod.selected, false);
+ }
+}
+
+QVector<QString> AtlOptionalModListModel::getResult() {
+ QVector<QString> result;
+
+ for (const auto& mod : m_mods) {
+ if (m_selection[mod.name]) {
+ result.push_back(mod.name);
+ }
+ }
+
+ return result;
+}
+
+int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const {
+ return m_mods.size();
+}
+
+int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const {
+ // Enabled, Name, Description
+ return 3;
+}
+
+QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const {
+ auto row = index.row();
+ auto mod = m_mods.at(row);
+
+ if (role == Qt::DisplayRole) {
+ if (index.column() == NameColumn) {
+ return mod.name;
+ }
+ if (index.column() == DescriptionColumn) {
+ return mod.description;
+ }
+ }
+ else if (role == Qt::ToolTipRole) {
+ if (index.column() == DescriptionColumn) {
+ return mod.description;
+ }
+ }
+ else if (role == Qt::CheckStateRole) {
+ if (index.column() == EnabledColumn) {
+ return m_selection[mod.name] ? Qt::Checked : Qt::Unchecked;
+ }
+ }
+
+ return QVariant();
+}
+
+bool AtlOptionalModListModel::setData(const QModelIndex &index, const QVariant &value, int role) {
+ if (role == Qt::CheckStateRole) {
+ auto row = index.row();
+ auto mod = m_mods.at(row);
+
+ toggleMod(mod, row);
+ return true;
+ }
+
+ return false;
+}
+
+QVariant AtlOptionalModListModel::headerData(int section, Qt::Orientation orientation, int role) const {
+ if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
+ switch (section) {
+ case EnabledColumn:
+ return QString();
+ case NameColumn:
+ return QString("Name");
+ case DescriptionColumn:
+ return QString("Description");
+ }
+ }
+
+ return QVariant();
+}
+
+Qt::ItemFlags AtlOptionalModListModel::flags(const QModelIndex &index) const {
+ auto flags = QAbstractListModel::flags(index);
+ if (index.isValid() && index.column() == EnabledColumn) {
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ return flags;
+}
+
+void AtlOptionalModListModel::selectRecommended() {
+ for (const auto& mod : m_mods) {
+ m_selection[mod.name] = mod.recommended;
+ }
+
+ emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn),
+ AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
+}
+
+void AtlOptionalModListModel::clearAll() {
+ for (const auto& mod : m_mods) {
+ m_selection[mod.name] = false;
+ }
+
+ emit dataChanged(AtlOptionalModListModel::index(0, EnabledColumn),
+ AtlOptionalModListModel::index(m_mods.size() - 1, EnabledColumn));
+}
+
+void AtlOptionalModListModel::toggleMod(ATLauncher::VersionMod mod, int index) {
+ setMod(mod, index, !m_selection[mod.name]);
+}
+
+void AtlOptionalModListModel::setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit) {
+ if (m_selection[mod.name] == enable) return;
+
+ m_selection[mod.name] = enable;
+
+ // disable other mods in the group, if applicable
+ if (enable && !mod.group.isEmpty()) {
+ for (int i = 0; i < m_mods.size(); i++) {
+ if (index == i) continue;
+ auto other = m_mods.at(i);
+
+ if (mod.group == other.group) {
+ setMod(other, i, false, shouldEmit);
+ }
+ }
+ }
+
+ for (const auto& dependencyName : mod.depends) {
+ auto dependencyIndex = m_index[dependencyName];
+ auto dependencyMod = m_mods.at(dependencyIndex);
+
+ // enable/disable dependencies
+ if (enable) {
+ setMod(dependencyMod, dependencyIndex, true, shouldEmit);
+ }
+
+ // if the dependency is 'effectively hidden', then track which mods
+ // depend on it - so we can efficiently disable it when no more dependents
+ // depend on it.
+ auto dependants = m_dependants[dependencyName];
+
+ if (enable) {
+ dependants.append(mod.name);
+ }
+ else {
+ dependants.removeAll(mod.name);
+
+ // if there are no longer any dependents, let's disable the mod
+ if (dependencyMod.effectively_hidden && dependants.isEmpty()) {
+ setMod(dependencyMod, dependencyIndex, false, shouldEmit);
+ }
+ }
+ }
+
+ // disable mods that depend on this one, if disabling
+ if (!enable) {
+ auto dependants = m_dependants[mod.name];
+ for (const auto& dependencyName : dependants) {
+ auto dependencyIndex = m_index[dependencyName];
+ auto dependencyMod = m_mods.at(dependencyIndex);
+
+ setMod(dependencyMod, dependencyIndex, false, shouldEmit);
+ }
+ }
+
+ if (shouldEmit) {
+ emit dataChanged(AtlOptionalModListModel::index(index, EnabledColumn),
+ AtlOptionalModListModel::index(index, EnabledColumn));
+ }
+}
+
+
+AtlOptionalModDialog::AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods)
+ : QDialog(parent), ui(new Ui::AtlOptionalModDialog) {
+ ui->setupUi(this);
+
+ listModel = new AtlOptionalModListModel(this, mods);
+ ui->treeView->setModel(listModel);
+
+ ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ ui->treeView->header()->setSectionResizeMode(
+ AtlOptionalModListModel::NameColumn, QHeaderView::ResizeToContents);
+ ui->treeView->header()->setSectionResizeMode(
+ AtlOptionalModListModel::DescriptionColumn, QHeaderView::Stretch);
+
+ connect(ui->selectRecommendedButton, &QPushButton::pressed,
+ listModel, &AtlOptionalModListModel::selectRecommended);
+ connect(ui->clearAllButton, &QPushButton::pressed,
+ listModel, &AtlOptionalModListModel::clearAll);
+ connect(ui->installButton, &QPushButton::pressed,
+ this, &QDialog::close);
+}
+
+AtlOptionalModDialog::~AtlOptionalModDialog() {
+ delete ui;
+}
diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h
new file mode 100644
index 00000000..a1df43f6
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <QDialog>
+#include <QAbstractListModel>
+
+#include "modplatform/atlauncher/ATLPackIndex.h"
+
+namespace Ui {
+class AtlOptionalModDialog;
+}
+
+class AtlOptionalModListModel : public QAbstractListModel {
+ Q_OBJECT
+
+public:
+ enum Columns
+ {
+ EnabledColumn = 0,
+ NameColumn,
+ DescriptionColumn,
+ };
+
+ AtlOptionalModListModel(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
+
+ QVector<QString> getResult();
+
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+public slots:
+ void selectRecommended();
+ void clearAll();
+
+private:
+ void toggleMod(ATLauncher::VersionMod mod, int index);
+ void setMod(ATLauncher::VersionMod mod, int index, bool enable, bool shouldEmit = true);
+
+private:
+ QVector<ATLauncher::VersionMod> m_mods;
+ QMap<QString, bool> m_selection;
+ QMap<QString, int> m_index;
+ QMap<QString, QVector<QString>> m_dependants;
+};
+
+class AtlOptionalModDialog : public QDialog {
+ Q_OBJECT
+
+public:
+ AtlOptionalModDialog(QWidget *parent, QVector<ATLauncher::VersionMod> mods);
+ ~AtlOptionalModDialog() override;
+
+ QVector<QString> getResult() {
+ return listModel->getResult();
+ }
+
+private:
+ Ui::AtlOptionalModDialog *ui;
+
+ AtlOptionalModListModel *listModel;
+};
diff --git a/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
new file mode 100644
index 00000000..5d3193a4
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlOptionalModDialog.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AtlOptionalModDialog</class>
+ <widget class="QDialog" name="AtlOptionalModDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>550</width>
+ <height>310</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Select Mods To Install</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="1" column="3">
+ <widget class="QPushButton" name="installButton">
+ <property name="text">
+ <string>Install</string>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="selectRecommendedButton">
+ <property name="text">
+ <string>Select Recommended</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QPushButton" name="shareCodeButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Use Share Code</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="2">
+ <widget class="QPushButton" name="clearAllButton">
+ <property name="text">
+ <string>Clear All</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="4">
+ <widget class="ModListView" name="treeView"/>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>ModListView</class>
+ <extends>QTreeView</extends>
+ <header>widgets/ModListView.h</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/application/pages/modplatform/atlauncher/AtlPage.cpp b/application/pages/modplatform/atlauncher/AtlPage.cpp
new file mode 100644
index 00000000..9fdf111f
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlPage.cpp
@@ -0,0 +1,175 @@
+#include "AtlPage.h"
+#include "ui_AtlPage.h"
+
+#include "dialogs/NewInstanceDialog.h"
+#include "AtlOptionalModDialog.h"
+#include <modplatform/atlauncher/ATLPackInstallTask.h>
+#include <BuildConfig.h>
+#include <dialogs/VersionSelectDialog.h>
+
+AtlPage::AtlPage(NewInstanceDialog* dialog, QWidget *parent)
+ : QWidget(parent), ui(new Ui::AtlPage), dialog(dialog)
+{
+ ui->setupUi(this);
+
+ filterModel = new Atl::FilterModel(this);
+ listModel = new Atl::ListModel(this);
+ filterModel->setSourceModel(listModel);
+ ui->packView->setModel(filterModel);
+ ui->packView->setSortingEnabled(true);
+
+ ui->packView->header()->hide();
+ ui->packView->setIndentation(0);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ for(int i = 0; i < filterModel->getAvailableSortings().size(); i++)
+ {
+ ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i));
+ }
+ ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting());
+
+ connect(ui->searchEdit, &QLineEdit::textChanged, this, &AtlPage::triggerSearch);
+ connect(ui->resetButton, &QPushButton::clicked, this, &AtlPage::resetSearch);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &AtlPage::onSortingSelectionChanged);
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AtlPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &AtlPage::onVersionSelectionChanged);
+}
+
+AtlPage::~AtlPage()
+{
+ delete ui;
+}
+
+bool AtlPage::shouldDisplay() const
+{
+ return true;
+}
+
+void AtlPage::openedImpl()
+{
+ if(!initialized)
+ {
+ listModel->request();
+ initialized = true;
+ }
+
+ suggestCurrent();
+}
+
+void AtlPage::suggestCurrent()
+{
+ if(!isOpened)
+ {
+ return;
+ }
+
+ if (selectedVersion.isEmpty())
+ {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ dialog->setSuggestedPack(selected.name, new ATLauncher::PackInstallTask(this, selected.safeName, selectedVersion));
+ auto editedLogoName = selected.safeName;
+ auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower());
+ listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo)
+ {
+ dialog->setSuggestedIconFromFile(logo, editedLogoName);
+ });
+}
+
+void AtlPage::triggerSearch()
+{
+ filterModel->setSearchTerm(ui->searchEdit->text());
+}
+
+void AtlPage::resetSearch()
+{
+ ui->searchEdit->setText("");
+}
+
+void AtlPage::onSortingSelectionChanged(QString data)
+{
+ auto toSet = filterModel->getAvailableSortings().value(data);
+ filterModel->setSorting(toSet);
+}
+
+void AtlPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ ui->versionSelectionBox->clear();
+
+ if(!first.isValid())
+ {
+ if(isOpened)
+ {
+ dialog->setSuggestedPack();
+ }
+ return;
+ }
+
+ selected = filterModel->data(first, Qt::UserRole).value<ATLauncher::IndexedPack>();
+
+ ui->packDescription->setHtml(selected.description.replace("\n", "<br>"));
+
+ for(const auto& version : selected.versions) {
+ ui->versionSelectionBox->addItem(version.version);
+ }
+
+ suggestCurrent();
+}
+
+void AtlPage::onVersionSelectionChanged(QString data)
+{
+ if(data.isNull() || data.isEmpty())
+ {
+ selectedVersion = "";
+ return;
+ }
+
+ selectedVersion = data;
+ suggestCurrent();
+}
+
+QVector<QString> AtlPage::chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) {
+ AtlOptionalModDialog optionalModDialog(this, mods);
+ optionalModDialog.exec();
+ return optionalModDialog.getResult();
+}
+
+QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) {
+ VersionSelectDialog vselect(vlist.get(), "Choose Version", MMC->activeWindow(), false);
+ if (minecraftVersion != Q_NULLPTR) {
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion);
+ vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion));
+ }
+ else {
+ vselect.setEmptyString(tr("No versions are currently available"));
+ }
+ vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!"));
+
+ // select recommended build
+ for (int i = 0; i < vlist->versions().size(); i++) {
+ auto version = vlist->versions().at(i);
+ auto reqs = version->requires();
+
+ // filter by minecraft version, if the loader depends on a certain version.
+ if (minecraftVersion != Q_NULLPTR) {
+ auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) {
+ return req.uid == "net.minecraft";
+ });
+ if (iter == reqs.end()) continue;
+ if (iter->equalsVersion != minecraftVersion) continue;
+ }
+
+ // first recommended build we find, we use.
+ if (version->isRecommended()) {
+ vselect.setCurrentVersion(version->descriptor());
+ break;
+ }
+ }
+
+ vselect.exec();
+ return vselect.selectedVersion()->descriptor();
+}
diff --git a/application/pages/modplatform/atlauncher/AtlPage.h b/application/pages/modplatform/atlauncher/AtlPage.h
new file mode 100644
index 00000000..932ec6a6
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlPage.h
@@ -0,0 +1,87 @@
+/* Copyright 2013-2019 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 "AtlFilterModel.h"
+#include "AtlListModel.h"
+
+#include <QWidget>
+#include <modplatform/atlauncher/ATLPackInstallTask.h>
+
+#include "MultiMC.h"
+#include "pages/BasePage.h"
+#include "tasks/Task.h"
+
+namespace Ui
+{
+ class AtlPage;
+}
+
+class NewInstanceDialog;
+
+class AtlPage : public QWidget, public BasePage, public ATLauncher::UserInteractionSupport
+{
+Q_OBJECT
+
+public:
+ explicit AtlPage(NewInstanceDialog* dialog, QWidget *parent = 0);
+ virtual ~AtlPage();
+ virtual QString displayName() const override
+ {
+ return tr("ATLauncher");
+ }
+ virtual QIcon icon() const override
+ {
+ return MMC->getThemedIcon("atlauncher");
+ }
+ virtual QString id() const override
+ {
+ return "atl";
+ }
+ virtual QString helpPage() const override
+ {
+ return "ATL-platform";
+ }
+ virtual bool shouldDisplay() const override;
+
+ void openedImpl() override;
+
+private:
+ void suggestCurrent();
+
+ QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override;
+ QVector<QString> chooseOptionalMods(QVector<ATLauncher::VersionMod> mods) override;
+
+private slots:
+ void triggerSearch();
+ void resetSearch();
+
+ void onSortingSelectionChanged(QString data);
+
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+
+private:
+ Ui::AtlPage *ui = nullptr;
+ NewInstanceDialog* dialog = nullptr;
+ Atl::ListModel* listModel = nullptr;
+ Atl::FilterModel* filterModel = nullptr;
+
+ ATLauncher::IndexedPack selected;
+ QString selectedVersion;
+
+ bool initialized = false;
+};
diff --git a/application/pages/modplatform/atlauncher/AtlPage.ui b/application/pages/modplatform/atlauncher/AtlPage.ui
new file mode 100644
index 00000000..f16c24b8
--- /dev/null
+++ b/application/pages/modplatform/atlauncher/AtlPage.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AtlPage</class>
+ <widget class="QWidget" name="AtlPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0">
+ <widget class="QTreeView" name="packView">
+ <property name="iconSize">
+ <size>
+ <width>96</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" colspan="2">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Warning: This is still a work in progress. If you run into issues with the imported modpack, it may be a bug.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="resetButton">
+ <property name="text">
+ <string>Reset</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>resetButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/application/pages/modplatform/twitch/TwitchModel.cpp b/application/pages/modplatform/flame/FlameModel.cpp
index 9e3c3ad2..228a88c5 100644
--- a/application/pages/modplatform/twitch/TwitchModel.cpp
+++ b/application/pages/modplatform/flame/FlameModel.cpp
@@ -1,5 +1,6 @@
-#include "TwitchModel.h"
+#include "FlameModel.h"
#include "MultiMC.h"
+#include <Json.h>
#include <MMCStrings.h>
#include <Version.h>
@@ -10,7 +11,7 @@
#include <RWStorage.h>
#include <Env.h>
-namespace Twitch {
+namespace Flame {
ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
{
@@ -38,7 +39,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const
return QString("INVALID INDEX %1").arg(pos);
}
- Modpack pack = modpacks.at(pos);
+ IndexedPack pack = modpacks.at(pos);
if(role == Qt::DisplayRole)
{
return pack.name;
@@ -99,12 +100,12 @@ void ListModel::requestLogo(QString logo, QString url)
return;
}
- MetaEntryPtr entry = ENV.metacache()->resolveEntry("TwitchPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
- NetJob *job = new NetJob(QString("Twitch Icon Download %1").arg(logo));
+ MetaEntryPtr entry = ENV.metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
+ NetJob *job = new NetJob(QString("Flame Icon Download %1").arg(logo));
job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
auto fullPath = entry->getFullPath();
- QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath]
+ QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath]
{
emit logoLoaded(logo, QIcon(fullPath));
if(waitingCallbacks.contains(logo))
@@ -127,7 +128,7 @@ void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallbac
{
if(m_logoMap.contains(logo))
{
- callback(ENV.metacache()->resolveEntry("TwitchPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ callback(ENV.metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
}
else
{
@@ -158,18 +159,17 @@ void ListModel::fetchMore(const QModelIndex& parent)
void ListModel::performPaginatedSearch()
{
- NetJob *netJob = new NetJob("Twitch::Search");
+ NetJob *netJob = new NetJob("Flame::Search");
auto searchUrl = QString(
"https://addons-ecs.forgesvc.net/api/v2/addon/search?"
"categoryId=0&"
"gameId=432&"
- //"gameVersion=1.12.2&"
"index=%1&"
"pageSize=25&"
"searchFilter=%2&"
"sectionId=4471&"
- "sort=0"
- ).arg(nextSearchOffset).arg(currentSearchTerm);
+ "sort=%3"
+ ).arg(nextSearchOffset).arg(currentSearchTerm).arg(currentSort);
netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
jobPtr = netJob;
jobPtr->start();
@@ -177,12 +177,13 @@ void ListModel::performPaginatedSearch()
QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
}
-void ListModel::searchWithTerm(const QString& term)
+void ListModel::searchWithTerm(const QString& term, int sort)
{
- if(currentSearchTerm == term) {
+ if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) {
return;
}
currentSearchTerm = term;
+ currentSort = sort;
if(jobPtr) {
jobPtr->abort();
searchState = ResetRequested;
@@ -198,90 +199,36 @@ void ListModel::searchWithTerm(const QString& term)
performPaginatedSearch();
}
-void Twitch::ListModel::searchRequestFinished()
+void Flame::ListModel::searchRequestFinished()
{
jobPtr.reset();
QJsonParseError parse_error;
QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
if(parse_error.error != QJsonParseError::NoError) {
- qWarning() << "Error while parsing JSON response from Twitch at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString();
qWarning() << response;
return;
}
- QList<Modpack> newList;
- auto objs = doc.array();
- for(auto projectIter: objs) {
- Modpack pack;
- auto project = projectIter.toObject();
- pack.addonId = project.value("id").toInt(0);
- if (pack.addonId == 0) {
- qWarning() << "Pack without an ID, skipping: " << pack.name;
- continue;
- }
- pack.name = project.value("name").toString();
- pack.websiteUrl = project.value("websiteUrl").toString();
- pack.description = project.value("summary").toString();
- bool thumbnailFound = false;
- auto attachments = project.value("attachments").toArray();
- for(auto attachmentIter: attachments) {
- auto attachment = attachmentIter.toObject();
- bool isDefault = attachment.value("isDefault").toBool(false);
- if(isDefault) {
- thumbnailFound = true;
- pack.logoName = attachment.value("title").toString();
- pack.logoUrl = attachment.value("thumbnailUrl").toString();
- break;
- }
- }
- if(!thumbnailFound) {
- qWarning() << "Pack without an icon, skipping: " << pack.name;
- continue;
- }
- auto authors = project.value("authors").toArray();
- for(auto authorIter: authors) {
- auto author = authorIter.toObject();
- ModpackAuthor packAuthor;
- packAuthor.name = author.value("name").toString();
- packAuthor.url = author.value("url").toString();
- pack.authors.append(packAuthor);
- }
- int defaultFileId = project.value("defaultFileId").toInt(0);
- if(defaultFileId == 0) {
- qWarning() << "Pack without default file, skipping: " << pack.name;
- continue;
- }
- bool found = false;
- auto files = project.value("latestFiles").toArray();
- for(auto fileIter: files) {
- auto file = fileIter.toObject();
- int id = file.value("id").toInt(0);
- // NOTE: for now, ignore everything that's not the default...
- if(id != defaultFileId) {
- continue;
- }
- pack.latestFile.addonId = pack.addonId;
- pack.latestFile.fileId = id;
- // FIXME: what to do when there's more than one, or there's no version?
- auto versionArray = file.value("gameVersion").toArray();
- if(versionArray.size() != 1) {
- continue;
- }
- pack.latestFile.mcVersion = versionArray[0].toString();
- pack.latestFile.version = file.value("displayName").toString();
- pack.latestFile.downloadUrl = file.value("downloadUrl").toString();
- found = true;
- break;
+ QList<Flame::IndexedPack> newList;
+ auto packs = doc.array();
+ for(auto packRaw : packs) {
+ auto packObj = packRaw.toObject();
+
+ Flame::IndexedPack pack;
+ try
+ {
+ Flame::loadIndexedPack(pack, packObj);
+ newList.append(pack);
}
- if(!found) {
- qWarning() << "Pack with no good file, skipping: " << pack.name;
+ catch(const JSONValidationError &e)
+ {
+ qWarning() << "Error while loading pack from CurseForge: " << e.cause();
continue;
}
- pack.broken = false;
- newList.append(pack);
}
- if(objs.size() < 25) {
+ if(packs.size() < 25) {
searchState = Finished;
} else {
nextSearchOffset += 25;
@@ -292,7 +239,7 @@ void Twitch::ListModel::searchRequestFinished()
endInsertRows();
}
-void Twitch::ListModel::searchRequestFailed(QString reason)
+void Flame::ListModel::searchRequestFailed(QString reason)
{
jobPtr.reset();
diff --git a/application/pages/modplatform/twitch/TwitchModel.h b/application/pages/modplatform/flame/FlameModel.h
index ad355c64..24383db0 100644
--- a/application/pages/modplatform/twitch/TwitchModel.h
+++ b/application/pages/modplatform/flame/FlameModel.h
@@ -1,6 +1,5 @@
#pragma once
-#include <modplatform/legacy_ftb/PackHelpers.h>
#include <RWStorage.h>
#include <QAbstractListModel>
@@ -16,9 +15,9 @@
#include <functional>
#include <net/NetJob.h>
-#include "TwitchData.h"
+#include <modplatform/flame/FlamePackIndex.h>
-namespace Twitch {
+namespace Flame {
typedef QMap<QString, QIcon> LogoMap;
@@ -40,7 +39,7 @@ public:
void fetchMore(const QModelIndex & parent) override;
void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
- void searchWithTerm(const QString & term);
+ void searchWithTerm(const QString & term, const int sort);
private slots:
void performPaginatedSearch();
@@ -55,13 +54,14 @@ private:
void requestLogo(QString file, QString url);
private:
- QList<Modpack> modpacks;
+ QList<IndexedPack> modpacks;
QStringList m_failedLogos;
QStringList m_loadingLogos;
LogoMap m_logoMap;
QMap<QString, LogoCallback> waitingCallbacks;
QString currentSearchTerm;
+ int currentSort = 0;
int nextSearchOffset = 0;
enum SearchState {
None,
diff --git a/application/pages/modplatform/flame/FlamePage.cpp b/application/pages/modplatform/flame/FlamePage.cpp
new file mode 100644
index 00000000..ade58431
--- /dev/null
+++ b/application/pages/modplatform/flame/FlamePage.cpp
@@ -0,0 +1,185 @@
+#include "FlamePage.h"
+#include "ui_FlamePage.h"
+
+#include "MultiMC.h"
+#include <Json.h>
+#include "dialogs/NewInstanceDialog.h"
+#include <InstanceImportTask.h>
+#include "FlameModel.h"
+#include <QKeyEvent>
+
+FlamePage::FlamePage(NewInstanceDialog* dialog, QWidget *parent)
+ : QWidget(parent), ui(new Ui::FlamePage), dialog(dialog)
+{
+ ui->setupUi(this);
+ connect(ui->searchButton, &QPushButton::clicked, this, &FlamePage::triggerSearch);
+ ui->searchEdit->installEventFilter(this);
+ listModel = new Flame::ListModel(this);
+ ui->packView->setModel(listModel);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ // index is used to set the sorting with the curseforge api
+ ui->sortByBox->addItem(tr("Sort by featured"));
+ ui->sortByBox->addItem(tr("Sort by popularity"));
+ ui->sortByBox->addItem(tr("Sort by last updated"));
+ ui->sortByBox->addItem(tr("Sort by name"));
+ ui->sortByBox->addItem(tr("Sort by author"));
+ ui->sortByBox->addItem(tr("Sort by total downloads"));
+
+ connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch()));
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FlamePage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FlamePage::onVersionSelectionChanged);
+}
+
+FlamePage::~FlamePage()
+{
+ delete ui;
+}
+
+bool FlamePage::eventFilter(QObject* watched, QEvent* event)
+{
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QWidget::eventFilter(watched, event);
+}
+
+bool FlamePage::shouldDisplay() const
+{
+ return true;
+}
+
+void FlamePage::openedImpl()
+{
+ suggestCurrent();
+ triggerSearch();
+}
+
+void FlamePage::triggerSearch()
+{
+ listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+}
+
+void FlamePage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ ui->versionSelectionBox->clear();
+
+ if(!first.isValid())
+ {
+ if(isOpened)
+ {
+ dialog->setSuggestedPack();
+ }
+ return;
+ }
+
+ current = listModel->data(first, Qt::UserRole).value<Flame::IndexedPack>();
+ QString text = "";
+ QString name = current.name;
+
+ if (current.websiteUrl.isEmpty())
+ text = name;
+ else
+ text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
+ if (!current.authors.empty()) {
+ auto authorToStr = [](Flame::ModpackAuthor & author) {
+ if(author.url.isEmpty()) {
+ return author.name;
+ }
+ return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
+ };
+ QStringList authorStrs;
+ for(auto & author: current.authors) {
+ authorStrs.push_back(authorToStr(author));
+ }
+ text += "<br>" + tr(" by ") + authorStrs.join(", ");
+ }
+ text += "<br><br>";
+
+ ui->packDescription->setHtml(text + current.description);
+
+ if (current.versionsLoaded == false)
+ {
+ qDebug() << "Loading flame modpack versions";
+ NetJob *netJob = new NetJob(QString("Flame::PackVersions(%1)").arg(current.name));
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+ int addonId = current.addonId;
+ netJob->addNetAction(Net::Download::makeByteArray(QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(addonId), response.get()));
+
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response]
+ {
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+ QJsonArray arr = doc.array();
+ try
+ {
+ Flame::loadIndexedPackVersions(current, arr);
+ }
+ catch(const JSONValidationError &e)
+ {
+ qDebug() << *response;
+ qWarning() << "Error while reading flame modpack version: " << e.cause();
+ }
+
+ for(auto version : current.versions) {
+ ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));
+ }
+
+ suggestCurrent();
+ });
+ netJob->start();
+ }
+ else
+ {
+ for(auto version : current.versions) {
+ ui->versionSelectionBox->addItem(version.version, QVariant(version.downloadUrl));
+ }
+
+ suggestCurrent();
+ }
+}
+
+void FlamePage::suggestCurrent()
+{
+ if(!isOpened)
+ {
+ return;
+ }
+
+ if (selectedVersion.isEmpty())
+ {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ dialog->setSuggestedPack(current.name, new InstanceImportTask(selectedVersion));
+ QString editedLogoName;
+ editedLogoName = "curseforge_" + current.logoName.section(".", 0, 0);
+ listModel->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
+ {
+ dialog->setSuggestedIconFromFile(logo, editedLogoName);
+ });
+}
+
+void FlamePage::onVersionSelectionChanged(QString data)
+{
+ if(data.isNull() || data.isEmpty())
+ {
+ selectedVersion = "";
+ return;
+ }
+ selectedVersion = ui->versionSelectionBox->currentData().toString();
+ suggestCurrent();
+}
diff --git a/application/pages/modplatform/twitch/TwitchPage.h b/application/pages/modplatform/flame/FlamePage.h
index 04e3a1c6..467bb44b 100644
--- a/application/pages/modplatform/twitch/TwitchPage.h
+++ b/application/pages/modplatform/flame/FlamePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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,41 +20,41 @@
#include "pages/BasePage.h"
#include <MultiMC.h>
#include "tasks/Task.h"
-#include "TwitchData.h"
+#include <modplatform/flame/FlamePackIndex.h>
namespace Ui
{
-class TwitchPage;
+class FlamePage;
}
class NewInstanceDialog;
-namespace Twitch {
+namespace Flame {
class ListModel;
}
-class TwitchPage : public QWidget, public BasePage
+class FlamePage : public QWidget, public BasePage
{
Q_OBJECT
public:
- explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0);
- virtual ~TwitchPage();
+ explicit FlamePage(NewInstanceDialog* dialog, QWidget *parent = 0);
+ virtual ~FlamePage();
virtual QString displayName() const override
{
- return tr("Twitch");
+ return tr("CurseForge");
}
virtual QIcon icon() const override
{
- return MMC->getThemedIcon("twitch");
+ return MMC->getThemedIcon("flame");
}
virtual QString id() const override
{
- return "twitch";
+ return "flame";
}
virtual QString helpPage() const override
{
- return "Twitch-platform";
+ return "Flame-platform";
}
virtual bool shouldDisplay() const override;
@@ -68,10 +68,13 @@ private:
private slots:
void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
private:
- Ui::TwitchPage *ui = nullptr;
+ Ui::FlamePage *ui = nullptr;
NewInstanceDialog* dialog = nullptr;
- Twitch::ListModel* model = nullptr;
- Twitch::Modpack current;
+ Flame::ListModel* listModel = nullptr;
+ Flame::IndexedPack current;
+
+ QString selectedVersion;
};
diff --git a/application/pages/modplatform/flame/FlamePage.ui b/application/pages/modplatform/flame/FlamePage.ui
new file mode 100644
index 00000000..9723815a
--- /dev/null
+++ b/application/pages/modplatform/flame/FlamePage.ui
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FlamePage</class>
+ <widget class="QWidget" name="FlamePage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>837</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="1" column="0">
+ <widget class="QListView" name="packView">
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ <tabstop>packDescription</tabstop>
+ <tabstop>sortByBox</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/application/pages/modplatform/ftb/FtbFilterModel.cpp b/application/pages/modplatform/ftb/FtbFilterModel.cpp
new file mode 100644
index 00000000..dec3a017
--- /dev/null
+++ b/application/pages/modplatform/ftb/FtbFilterModel.cpp
@@ -0,0 +1,64 @@
+#include "FtbFilterModel.h"
+
+#include <QDebug>
+
+#include "modplatform/modpacksch/FTBPackManifest.h"
+#include <MMCStrings.h>
+
+namespace Ftb {
+
+FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
+{
+ currentSorting = Sorting::ByPlays;
+ sortings.insert(tr("Sort by plays"), Sorting::ByPlays);
+ sortings.insert(tr("Sort by installs"), Sorting::ByInstalls);
+ sortings.insert(tr("Sort by name"), Sorting::ByName);
+}
+
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
+{
+ return sortings;
+}
+
+QString FilterModel::translateCurrentSorting()
+{
+ return sortings.key(currentSorting);
+}
+
+void FilterModel::setSorting(Sorting sorting)
+{
+ currentSorting = sorting;
+ invalidate();
+}
+
+FilterModel::Sorting FilterModel::getCurrentSorting()
+{
+ return currentSorting;
+}
+
+bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ return true;
+}
+
+bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ ModpacksCH::Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<ModpacksCH::Modpack>();
+ ModpacksCH::Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<ModpacksCH::Modpack>();
+
+ if (currentSorting == ByPlays) {
+ return leftPack.plays < rightPack.plays;
+ }
+ else if (currentSorting == ByInstalls) {
+ return leftPack.installs < rightPack.installs;
+ }
+ else if (currentSorting == ByName) {
+ return Strings::naturalCompare(leftPack.name, rightPack.name, Qt::CaseSensitive) >= 0;
+ }
+
+ // Invalid sorting set, somehow...
+ qWarning() << "Invalid sorting set!";
+ return true;
+}
+
+}
diff --git a/application/pages/modplatform/ftb/FtbFilterModel.h b/application/pages/modplatform/ftb/FtbFilterModel.h
new file mode 100644
index 00000000..4fe2a274
--- /dev/null
+++ b/application/pages/modplatform/ftb/FtbFilterModel.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include <QtCore/QSortFilterProxyModel>
+
+namespace Ftb {
+
+class FilterModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ FilterModel(QObject* parent = Q_NULLPTR);
+ enum Sorting {
+ ByPlays,
+ ByInstalls,
+ ByName,
+ };
+ const QMap<QString, Sorting> getAvailableSortings();
+ QString translateCurrentSorting();
+ void setSorting(Sorting sorting);
+ Sorting getCurrentSorting();
+
+protected:
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+ bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
+
+private:
+ QMap<QString, Sorting> sortings;
+ Sorting currentSorting;
+
+};
+
+}
diff --git a/application/pages/modplatform/ftb/FtbListModel.cpp b/application/pages/modplatform/ftb/FtbListModel.cpp
new file mode 100644
index 00000000..98973f2e
--- /dev/null
+++ b/application/pages/modplatform/ftb/FtbListModel.cpp
@@ -0,0 +1,304 @@
+#include "FtbListModel.h"
+
+#include "BuildConfig.h"
+#include "Env.h"
+#include "MultiMC.h"
+#include "Json.h"
+
+#include <QPainter>
+
+namespace Ftb {
+
+ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
+{
+}
+
+ListModel::~ListModel()
+{
+}
+
+int ListModel::rowCount(const QModelIndex &parent) const
+{
+ return modpacks.size();
+}
+
+int ListModel::columnCount(const QModelIndex &parent) const
+{
+ return 1;
+}
+
+QVariant ListModel::data(const QModelIndex &index, int role) const
+{
+ int pos = index.row();
+ if(pos >= modpacks.size() || pos < 0 || !index.isValid())
+ {
+ return QString("INVALID INDEX %1").arg(pos);
+ }
+
+ ModpacksCH::Modpack pack = modpacks.at(pos);
+ if(role == Qt::DisplayRole)
+ {
+ return pack.name;
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ return pack.synopsis;
+ }
+ else if(role == Qt::DecorationRole)
+ {
+ QIcon placeholder = MMC->getThemedIcon("screenshot-placeholder");
+
+ auto iter = m_logoMap.find(pack.name);
+ if (iter != m_logoMap.end()) {
+ auto & logo = *iter;
+ if(!logo.result.isNull()) {
+ return logo.result;
+ }
+ return placeholder;
+ }
+
+ for(auto art : pack.art) {
+ if(art.type == "square") {
+ ((ListModel *)this)->requestLogo(pack.name, art.url);
+ }
+ }
+ return placeholder;
+ }
+ else if(role == Qt::UserRole)
+ {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+
+ return QVariant();
+}
+
+void ListModel::performSearch()
+{
+ auto *netJob = new NetJob("Ftb::Search");
+ QString searchUrl;
+ if(currentSearchTerm.isEmpty()) {
+ searchUrl = BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/all";
+ }
+ else {
+ searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/search/25?term=%1")
+ .arg(currentSearchTerm);
+ }
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+ QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
+}
+
+void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
+{
+ if(m_logoMap.contains(logo))
+ {
+ callback(ENV.metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ }
+ else
+ {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+void ListModel::searchWithTerm(const QString &term)
+{
+ if(searchState != Failed && currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) {
+ // unless the search has failed, then there is no need to perform an identical search.
+ return;
+ }
+ currentSearchTerm = term;
+
+ if(jobPtr) {
+ jobPtr->abort();
+ jobPtr.reset();
+ }
+
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+
+ performSearch();
+}
+
+void ListModel::searchRequestFinished()
+{
+ jobPtr.reset();
+ remainingPacks.clear();
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ auto packs = doc.object().value("packs").toArray();
+ for(auto pack : packs) {
+ auto packId = pack.toInt();
+ remainingPacks.append(packId);
+ }
+
+ if(!remainingPacks.isEmpty()) {
+ currentPack = remainingPacks.at(0);
+ requestPack();
+ }
+}
+
+void ListModel::searchRequestFailed(QString reason)
+{
+ jobPtr.reset();
+ remainingPacks.clear();
+
+ searchState = Failed;
+}
+
+void ListModel::requestPack()
+{
+ auto *netJob = new NetJob("Ftb::Search");
+ auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1")
+ .arg(currentPack);
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+
+ QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::packRequestFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &ListModel::packRequestFailed);
+}
+
+void ListModel::packRequestFinished()
+{
+ jobPtr.reset();
+ remainingPacks.removeOne(currentPack);
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+
+ if(parse_error.error != QJsonParseError::NoError) {
+ qWarning() << "Error while parsing JSON response from FTB at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ auto obj = doc.object();
+
+ ModpacksCH::Modpack pack;
+ try
+ {
+ ModpacksCH::loadModpack(pack, obj);
+ }
+ catch (const JSONValidationError &e)
+ {
+ qDebug() << QString::fromUtf8(response);
+ qWarning() << "Error while reading pack manifest from FTB: " << e.cause();
+ return;
+ }
+
+ // Since there is no guarantee that packs have a version, this will just
+ // ignore those "dud" packs.
+ if (pack.versions.empty())
+ {
+ qWarning() << "FTB Pack " << pack.id << " ignored. reason: lacking any versions";
+ }
+ else
+ {
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size());
+ modpacks.append(pack);
+ endInsertRows();
+ }
+
+ if(!remainingPacks.isEmpty()) {
+ currentPack = remainingPacks.at(0);
+ requestPack();
+ }
+}
+
+void ListModel::packRequestFailed(QString reason)
+{
+ jobPtr.reset();
+ remainingPacks.removeOne(currentPack);
+}
+
+void ListModel::logoLoaded(QString logo, bool stale)
+{
+ auto & logoObj = m_logoMap[logo];
+ logoObj.downloadJob.reset();
+ QString smallPath = logoObj.fullpath + ".small";
+
+ QFileInfo smallInfo(smallPath);
+
+ if(stale || !smallInfo.exists()) {
+ QImage image(logoObj.fullpath);
+ if (image.isNull())
+ {
+ logoObj.failed = true;
+ return;
+ }
+ QImage small;
+ if (image.width() > image.height()) {
+ small = image.scaledToWidth(512).scaledToWidth(256, Qt::SmoothTransformation);
+ }
+ else {
+ small = image.scaledToHeight(512).scaledToHeight(256, Qt::SmoothTransformation);
+ }
+ QPoint offset((256 - small.width()) / 2, (256 - small.height()) / 2);
+ QImage square(QSize(256, 256), QImage::Format_ARGB32);
+ square.fill(Qt::transparent);
+
+ QPainter painter(&square);
+ painter.drawImage(offset, small);
+ painter.end();
+
+ square.save(logoObj.fullpath + ".small", "PNG");
+ }
+
+ logoObj.result = QIcon(logoObj.fullpath + ".small");
+ for(int i = 0; i < modpacks.size(); i++) {
+ if(modpacks[i].name == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ }
+ }
+}
+
+void ListModel::logoFailed(QString logo)
+{
+ m_logoMap[logo].failed = true;
+ m_logoMap[logo].downloadJob.reset();
+}
+
+void ListModel::requestLogo(QString logo, QString url)
+{
+ if(m_logoMap.contains(logo)) {
+ return;
+ }
+
+ MetaEntryPtr entry = ENV.metacache()->resolveEntry("ModpacksCHPacks", QString("logos/%1").arg(logo.section(".", 0, 0)));
+
+ bool stale = entry->isStale();
+
+ NetJob *job = new NetJob(QString("FTB Icon Download %1").arg(logo));
+ job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+
+ auto fullPath = entry->getFullPath();
+ QObject::connect(job, &NetJob::finished, this, [this, logo, fullPath, stale]
+ {
+ logoLoaded(logo, stale);
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, logo]
+ {
+ logoFailed(logo);
+ });
+
+ auto &newLogoEntry = m_logoMap[logo];
+ newLogoEntry.downloadJob = job;
+ newLogoEntry.fullpath = fullPath;
+ job->start();
+}
+
+}
diff --git a/application/pages/modplatform/ftb/FtbListModel.h b/application/pages/modplatform/ftb/FtbListModel.h
new file mode 100644
index 00000000..de94e6ba
--- /dev/null
+++ b/application/pages/modplatform/ftb/FtbListModel.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <QAbstractListModel>
+
+#include "modplatform/modpacksch/FTBPackManifest.h"
+#include "net/NetJob.h"
+#include <QIcon>
+
+namespace Ftb {
+
+struct Logo {
+ QString fullpath;
+ NetJobPtr downloadJob;
+ QIcon result;
+ bool failed = false;
+};
+
+typedef QMap<QString, Logo> LogoMap;
+typedef std::function<void(QString)> LogoCallback;
+
+class ListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ ListModel(QObject *parent);
+ virtual ~ListModel();
+
+ int rowCount(const QModelIndex &parent) const override;
+ int columnCount(const QModelIndex &parent) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+ void searchWithTerm(const QString & term);
+
+private slots:
+ void performSearch();
+ void searchRequestFinished();
+ void searchRequestFailed(QString reason);
+
+ void requestPack();
+ void packRequestFinished();
+ void packRequestFailed(QString reason);
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, bool stale);
+
+private:
+ void requestLogo(QString file, QString url);
+
+private:
+ QList<ModpacksCH::Modpack> modpacks;
+ LogoMap m_logoMap;
+
+ QString currentSearchTerm;
+ enum SearchState {
+ None,
+ CanPossiblyFetchMore,
+ ResetRequested,
+ Finished,
+ Failed,
+ } searchState = None;
+ NetJobPtr jobPtr;
+ int currentPack;
+ QList<int> remainingPacks;
+ QByteArray response;
+};
+
+}
diff --git a/application/pages/modplatform/ftb/FtbPage.cpp b/application/pages/modplatform/ftb/FtbPage.cpp
new file mode 100644
index 00000000..b7f35c5d
--- /dev/null
+++ b/application/pages/modplatform/ftb/FtbPage.cpp
@@ -0,0 +1,145 @@
+#include "FtbPage.h"
+#include "ui_FtbPage.h"
+
+#include <QKeyEvent>
+
+#include "dialogs/NewInstanceDialog.h"
+#include "modplatform/modpacksch/FTBPackInstallTask.h"
+
+#include "HoeDown.h"
+
+FtbPage::FtbPage(NewInstanceDialog* dialog, QWidget *parent)
+ : QWidget(parent), ui(new Ui::FtbPage), dialog(dialog)
+{
+ ui->setupUi(this);
+
+ filterModel = new Ftb::FilterModel(this);
+ listModel = new Ftb::ListModel(this);
+ filterModel->setSourceModel(listModel);
+ ui->packView->setModel(filterModel);
+ ui->packView->setSortingEnabled(true);
+ ui->packView->header()->hide();
+ ui->packView->setIndentation(0);
+
+ ui->searchEdit->installEventFilter(this);
+
+ ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+ ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+
+ for(int i = 0; i < filterModel->getAvailableSortings().size(); i++)
+ {
+ ui->sortByBox->addItem(filterModel->getAvailableSortings().keys().at(i));
+ }
+ ui->sortByBox->setCurrentText(filterModel->translateCurrentSorting());
+
+ connect(ui->searchButton, &QPushButton::clicked, this, &FtbPage::triggerSearch);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FtbPage::onSortingSelectionChanged);
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &FtbPage::onSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FtbPage::onVersionSelectionChanged);
+}
+
+FtbPage::~FtbPage()
+{
+ delete ui;
+}
+
+bool FtbPage::eventFilter(QObject* watched, QEvent* event)
+{
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QWidget::eventFilter(watched, event);
+}
+
+bool FtbPage::shouldDisplay() const
+{
+ return true;
+}
+
+void FtbPage::openedImpl()
+{
+ triggerSearch();
+ suggestCurrent();
+}
+
+void FtbPage::suggestCurrent()
+{
+ if(!isOpened)
+ {
+ return;
+ }
+
+ if (selectedVersion.isEmpty())
+ {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ dialog->setSuggestedPack(selected.name, new ModpacksCH::PackInstallTask(selected, selectedVersion));
+ for(auto art : selected.art) {
+ if(art.type == "square") {
+ QString editedLogoName;
+ editedLogoName = selected.name;
+
+ listModel->getLogo(selected.name, art.url, [this, editedLogoName](QString logo)
+ {
+ dialog->setSuggestedIconFromFile(logo + ".small", editedLogoName);
+ });
+ }
+ }
+}
+
+void FtbPage::triggerSearch()
+{
+ listModel->searchWithTerm(ui->searchEdit->text());
+}
+
+void FtbPage::onSortingSelectionChanged(QString data)
+{
+ auto toSet = filterModel->getAvailableSortings().value(data);
+ filterModel->setSorting(toSet);
+}
+
+void FtbPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ ui->versionSelectionBox->clear();
+
+ if(!first.isValid())
+ {
+ if(isOpened)
+ {
+ dialog->setSuggestedPack();
+ }
+ return;
+ }
+
+ selected = filterModel->data(first, Qt::UserRole).value<ModpacksCH::Modpack>();
+
+ HoeDown hoedown;
+ QString output = hoedown.process(selected.description.toUtf8());
+ ui->packDescription->setHtml(output);
+
+ // reverse foreach, so that the newest versions are first
+ for (auto i = selected.versions.size(); i--;) {
+ ui->versionSelectionBox->addItem(selected.versions.at(i).name);
+ }
+
+ suggestCurrent();
+}
+
+void FtbPage::onVersionSelectionChanged(QString data)
+{
+ if(data.isNull() || data.isEmpty())
+ {
+ selectedVersion = "";
+ return;
+ }
+
+ selectedVersion = data;
+ suggestCurrent();
+}
diff --git a/application/pages/modplatform/ftb/FtbPage.h b/application/pages/modplatform/ftb/FtbPage.h
new file mode 100644
index 00000000..c9c93897
--- /dev/null
+++ b/application/pages/modplatform/ftb/FtbPage.h
@@ -0,0 +1,80 @@
+/* Copyright 2013-2021 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 "FtbFilterModel.h"
+#include "FtbListModel.h"
+
+#include <QWidget>
+
+#include "MultiMC.h"
+#include "pages/BasePage.h"
+#include "tasks/Task.h"
+
+namespace Ui
+{
+ class FtbPage;
+}
+
+class NewInstanceDialog;
+
+class FtbPage : public QWidget, public BasePage
+{
+Q_OBJECT
+
+public:
+ explicit FtbPage(NewInstanceDialog* dialog, QWidget *parent = 0);
+ virtual ~FtbPage();
+ virtual QString displayName() const override
+ {
+ return tr("FTB");
+ }
+ virtual QIcon icon() const override
+ {
+ return MMC->getThemedIcon("ftb_logo");
+ }
+ virtual QString id() const override
+ {
+ return "ftb";
+ }
+ virtual QString helpPage() const override
+ {
+ return "FTB-platform";
+ }
+ virtual bool shouldDisplay() const override;
+
+ void openedImpl() override;
+
+ bool eventFilter(QObject * watched, QEvent * event) override;
+
+private:
+ void suggestCurrent();
+
+private slots:
+ void triggerSearch();
+ void onSortingSelectionChanged(QString data);
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+
+private:
+ Ui::FtbPage *ui = nullptr;
+ NewInstanceDialog* dialog = nullptr;
+ Ftb::ListModel* listModel = nullptr;
+ Ftb::FilterModel* filterModel = nullptr;
+
+ ModpacksCH::Modpack selected;
+ QString selectedVersion;
+};
diff --git a/application/pages/modplatform/ftb/FtbPage.ui b/application/pages/modplatform/ftb/FtbPage.ui
new file mode 100644
index 00000000..135afc6d
--- /dev/null
+++ b/application/pages/modplatform/ftb/FtbPage.ui
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FtbPage</class>
+ <widget class="QWidget" name="FtbPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>875</width>
+ <height>745</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="2" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0" rowminimumheight="0" columnminimumwidth="0,0,0">
+ <item row="0" column="2">
+ <widget class="QComboBox" name="versionSelectionBox"/>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Version selected:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QComboBox" name="sortByBox"/>
+ </item>
+ </layout>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QTreeView" name="packView">
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QTextBrowser" name="packDescription">
+ <property name="openExternalLinks">
+ <bool>true</bool>
+ </property>
+ <property name="openLinks">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>versionSelectionBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/application/pages/modplatform/legacy_ftb/Page.cpp b/application/pages/modplatform/legacy_ftb/Page.cpp
index 8e40ba9e..a438f76c 100644
--- a/application/pages/modplatform/legacy_ftb/Page.cpp
+++ b/application/pages/modplatform/legacy_ftb/Page.cpp
@@ -122,49 +122,50 @@ void Page::openedImpl()
void Page::suggestCurrent()
{
- if(isOpened)
+ if(!isOpened)
{
- if(!selected.broken)
- {
- dialog->setSuggestedPack(selected.name, new PackInstallTask(selected, selectedVersion));
- QString editedLogoName;
- if(selected.logo.toLower().startsWith("ftb"))
- {
- editedLogoName = selected.logo;
- }
- else
- {
- editedLogoName = "ftb_" + selected.logo;
- }
+ return;
+ }
- editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png"));
+ if(selected.broken || selectedVersion.isEmpty())
+ {
+ dialog->setSuggestedPack();
+ return;
+ }
- if(selected.type == PackType::Public)
- {
- publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
- }
- else if (selected.type == PackType::ThirdParty)
- {
- thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
- }
- else if (selected.type == PackType::Private)
- {
- privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
- }
- }
- else
+ dialog->setSuggestedPack(selected.name, new PackInstallTask(selected, selectedVersion));
+ QString editedLogoName;
+ if(selected.logo.toLower().startsWith("ftb"))
+ {
+ editedLogoName = selected.logo;
+ }
+ else
+ {
+ editedLogoName = "ftb_" + selected.logo;
+ }
+
+ editedLogoName = editedLogoName.left(editedLogoName.lastIndexOf(".png"));
+
+ if(selected.type == PackType::Public)
+ {
+ publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
{
- dialog->setSuggestedPack();
- }
+ dialog->setSuggestedIconFromFile(logo, editedLogoName);
+ });
+ }
+ else if (selected.type == PackType::ThirdParty)
+ {
+ thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
+ {
+ dialog->setSuggestedIconFromFile(logo, editedLogoName);
+ });
+ }
+ else if (selected.type == PackType::Private)
+ {
+ privateListModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
+ {
+ dialog->setSuggestedIconFromFile(logo, editedLogoName);
+ });
}
}
diff --git a/application/pages/modplatform/legacy_ftb/Page.h b/application/pages/modplatform/legacy_ftb/Page.h
index ed6d1657..e840216e 100644
--- a/application/pages/modplatform/legacy_ftb/Page.h
+++ b/application/pages/modplatform/legacy_ftb/Page.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/modplatform/legacy_ftb/Page.ui b/application/pages/modplatform/legacy_ftb/Page.ui
index 36fb2359..15e5d432 100644
--- a/application/pages/modplatform/legacy_ftb/Page.ui
+++ b/application/pages/modplatform/legacy_ftb/Page.ui
@@ -29,6 +29,9 @@
<height>16777215</height>
</size>
</property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
</widget>
</item>
<item row="0" column="1">
@@ -52,6 +55,9 @@
<height>16777215</height>
</size>
</property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
</widget>
</item>
</layout>
@@ -69,6 +75,9 @@
<height>16777215</height>
</size>
</property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
</widget>
</item>
<item row="1" column="0">
diff --git a/application/pages/modplatform/technic/TechnicData.h b/application/pages/modplatform/technic/TechnicData.h
new file mode 100644
index 00000000..50fd75e8
--- /dev/null
+++ b/application/pages/modplatform/technic/TechnicData.h
@@ -0,0 +1,42 @@
+/* Copyright 2020-2021 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 <QList>
+#include <QString>
+
+namespace Technic {
+struct Modpack {
+ QString slug;
+
+ QString name;
+ QString logoUrl;
+ QString logoName;
+
+ bool broken = true;
+
+ QString url;
+ bool isSolder = false;
+ QString minecraftVersion;
+
+ bool metadataLoaded = false;
+ QString websiteUrl;
+ QString author;
+ QString description;
+};
+}
+
+Q_DECLARE_METATYPE(Technic::Modpack)
diff --git a/application/pages/modplatform/technic/TechnicModel.cpp b/application/pages/modplatform/technic/TechnicModel.cpp
new file mode 100644
index 00000000..def30783
--- /dev/null
+++ b/application/pages/modplatform/technic/TechnicModel.cpp
@@ -0,0 +1,238 @@
+/* Copyright 2020-2021 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 "TechnicModel.h"
+#include "Env.h"
+#include "MultiMC.h"
+#include "Json.h"
+
+#include <QIcon>
+
+Technic::ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
+{
+}
+
+Technic::ListModel::~ListModel()
+{
+}
+
+QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
+{
+ int pos = index.row();
+ if(pos >= modpacks.size() || pos < 0 || !index.isValid())
+ {
+ return QString("INVALID INDEX %1").arg(pos);
+ }
+
+ Modpack pack = modpacks.at(pos);
+ if(role == Qt::DisplayRole)
+ {
+ return pack.name;
+ }
+ else if(role == Qt::DecorationRole)
+ {
+ if(m_logoMap.contains(pack.logoName))
+ {
+ return (m_logoMap.value(pack.logoName));
+ }
+ QIcon icon = MMC->getThemedIcon("screenshot-placeholder");
+ ((ListModel *)this)->requestLogo(pack.logoName, pack.logoUrl);
+ return icon;
+ }
+ else if(role == Qt::UserRole)
+ {
+ QVariant v;
+ v.setValue(pack);
+ return v;
+ }
+ return QVariant();
+}
+
+int Technic::ListModel::columnCount(const QModelIndex&) const
+{
+ return 1;
+}
+
+int Technic::ListModel::rowCount(const QModelIndex&) const
+{
+ return modpacks.size();
+}
+
+void Technic::ListModel::searchWithTerm(const QString& term)
+{
+ if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull()) {
+ return;
+ }
+ currentSearchTerm = term;
+ if(jobPtr) {
+ jobPtr->abort();
+ searchState = ResetRequested;
+ return;
+ }
+ else {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+ }
+ performSearch();
+}
+
+void Technic::ListModel::performSearch()
+{
+ NetJob *netJob = new NetJob("Technic::Search");
+ QString searchUrl = "";
+ if (currentSearchTerm.isEmpty()) {
+ searchUrl = "https://api.technicpack.net/trending?build=multimc";
+ }
+ else
+ {
+ searchUrl = QString(
+ "https://api.technicpack.net/search?build=multimc&q=%1"
+ ).arg(currentSearchTerm);
+ }
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response));
+ jobPtr = netJob;
+ jobPtr->start();
+ QObject::connect(netJob, &NetJob::succeeded, this, &ListModel::searchRequestFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &ListModel::searchRequestFailed);
+}
+
+void Technic::ListModel::searchRequestFinished()
+{
+ jobPtr.reset();
+
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error);
+ if(parse_error.error != QJsonParseError::NoError)
+ {
+ qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ QList<Modpack> newList;
+ try {
+ auto root = Json::requireObject(doc);
+ auto objs = Json::requireArray(root, "modpacks");
+ for (auto technicPack: objs) {
+ Modpack pack;
+ auto technicPackObject = Json::requireObject(technicPack);
+ pack.name = Json::requireString(technicPackObject, "name");
+ pack.slug = Json::requireString(technicPackObject, "slug");
+ if (pack.slug == "vanilla")
+ continue;
+
+ auto rawURL = Json::ensureString(technicPackObject, "iconUrl", "null");
+ if(rawURL == "null") {
+ pack.logoUrl = "null";
+ pack.logoName = "null";
+ }
+ else {
+ pack.logoUrl = rawURL;
+ pack.logoName = rawURL.section(QLatin1Char('/'), -1).section(QLatin1Char('.'), 0, 0);
+ }
+ pack.broken = false;
+ newList.append(pack);
+ }
+ }
+ catch (const JSONValidationError &err)
+ {
+ qCritical() << "Couldn't parse technic search results:" << err.cause() ;
+ return;
+ }
+ searchState = Finished;
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
+ modpacks.append(newList);
+ endInsertRows();
+}
+
+void Technic::ListModel::getLogo(const QString& logo, const QString& logoUrl, Technic::LogoCallback callback)
+{
+ if(m_logoMap.contains(logo))
+ {
+ callback(ENV.metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo))->getFullPath());
+ }
+ else
+ {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+void Technic::ListModel::searchRequestFailed()
+{
+ jobPtr.reset();
+
+ if(searchState == ResetRequested)
+ {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+
+ performSearch();
+ }
+ else
+ {
+ searchState = Finished;
+ }
+}
+
+
+void Technic::ListModel::logoLoaded(QString logo, QString out)
+{
+ m_loadingLogos.removeAll(logo);
+ m_logoMap.insert(logo, QIcon(out));
+ for(int i = 0; i < modpacks.size(); i++)
+ {
+ if(modpacks[i].logoName == logo)
+ {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ }
+ }
+}
+
+void Technic::ListModel::logoFailed(QString logo)
+{
+ m_failedLogos.append(logo);
+ m_loadingLogos.removeAll(logo);
+}
+
+void Technic::ListModel::requestLogo(QString logo, QString url)
+{
+ if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || logo == "null")
+ {
+ return;
+ }
+
+ MetaEntryPtr entry = ENV.metacache()->resolveEntry("TechnicPacks", QString("logos/%1").arg(logo));
+ NetJob *job = new NetJob(QString("Technic Icon Download %1").arg(logo));
+ job->addNetAction(Net::Download::makeCached(QUrl(url), entry));
+
+ auto fullPath = entry->getFullPath();
+
+ QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath]
+ {
+ logoLoaded(logo, fullPath);
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, logo]
+ {
+ logoFailed(logo);
+ });
+
+ job->start();
+
+ m_loadingLogos.append(logo);
+}
diff --git a/application/pages/modplatform/technic/TechnicModel.h b/application/pages/modplatform/technic/TechnicModel.h
new file mode 100644
index 00000000..82a03842
--- /dev/null
+++ b/application/pages/modplatform/technic/TechnicModel.h
@@ -0,0 +1,70 @@
+/* Copyright 2020-2021 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 <QModelIndex>
+
+#include "TechnicData.h"
+#include "net/NetJob.h"
+
+namespace Technic {
+
+typedef std::function<void(QString)> LogoCallback;
+
+class ListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ ListModel(QObject *parent);
+ virtual ~ListModel();
+
+ virtual QVariant data(const QModelIndex& index, int role) const;
+ virtual int columnCount(const QModelIndex& parent) const;
+ virtual int rowCount(const QModelIndex& parent) const;
+
+ void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+ void searchWithTerm(const QString & term);
+
+private slots:
+ void searchRequestFinished();
+ void searchRequestFailed();
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, QString out);
+
+private:
+ void performSearch();
+ void requestLogo(QString logo, QString url);
+
+private:
+ QList<Modpack> modpacks;
+ QStringList m_failedLogos;
+ QStringList m_loadingLogos;
+ QMap<QString, QIcon> m_logoMap;
+ QMap<QString, LogoCallback> waitingCallbacks;
+
+ QString currentSearchTerm;
+ enum SearchState {
+ None,
+ ResetRequested,
+ Finished
+ } searchState = None;
+ NetJobPtr jobPtr;
+ QByteArray response;
+};
+
+}
diff --git a/application/pages/modplatform/technic/TechnicPage.cpp b/application/pages/modplatform/technic/TechnicPage.cpp
new file mode 100644
index 00000000..e836f767
--- /dev/null
+++ b/application/pages/modplatform/technic/TechnicPage.cpp
@@ -0,0 +1,198 @@
+/* Copyright 2013-2021 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 "TechnicPage.h"
+#include "ui_TechnicPage.h"
+
+#include "MultiMC.h"
+#include "dialogs/NewInstanceDialog.h"
+#include "TechnicModel.h"
+#include <QKeyEvent>
+#include "modplatform/technic/SingleZipPackInstallTask.h"
+#include "modplatform/technic/SolderPackInstallTask.h"
+#include "Json.h"
+
+TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
+ : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
+{
+ ui->setupUi(this);
+ connect(ui->searchButton, &QPushButton::clicked, this, &TechnicPage::triggerSearch);
+ ui->searchEdit->installEventFilter(this);
+ model = new Technic::ListModel(this);
+ ui->packView->setModel(model);
+ connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TechnicPage::onSelectionChanged);
+}
+
+bool TechnicPage::eventFilter(QObject* watched, QEvent* event)
+{
+ if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
+ if (keyEvent->key() == Qt::Key_Return) {
+ triggerSearch();
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QWidget::eventFilter(watched, event);
+}
+
+TechnicPage::~TechnicPage()
+{
+ delete ui;
+}
+
+bool TechnicPage::shouldDisplay() const
+{
+ return true;
+}
+
+void TechnicPage::openedImpl()
+{
+ suggestCurrent();
+ triggerSearch();
+}
+
+void TechnicPage::triggerSearch() {
+ model->searchWithTerm(ui->searchEdit->text());
+}
+
+void TechnicPage::onSelectionChanged(QModelIndex first, QModelIndex second)
+{
+ if(!first.isValid())
+ {
+ if(isOpened)
+ {
+ dialog->setSuggestedPack();
+ }
+ //ui->frame->clear();
+ return;
+ }
+
+ current = model->data(first, Qt::UserRole).value<Technic::Modpack>();
+ suggestCurrent();
+}
+
+void TechnicPage::suggestCurrent()
+{
+ if (!isOpened)
+ {
+ return;
+ }
+ if (current.broken)
+ {
+ dialog->setSuggestedPack();
+ return;
+ }
+
+ QString editedLogoName = "technic_" + current.logoName.section(".", 0, 0);
+ model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
+ {
+ dialog->setSuggestedIconFromFile(logo, editedLogoName);
+ });
+
+ if (current.metadataLoaded)
+ {
+ metadataLoaded();
+ return;
+ }
+
+ NetJob *netJob = new NetJob(QString("Technic::PackMeta(%1)").arg(current.name));
+ std::shared_ptr<QByteArray> response = std::make_shared<QByteArray>();
+ QString slug = current.slug;
+ netJob->addNetAction(Net::Download::makeByteArray(QString("https://api.technicpack.net/modpack/%1?build=multimc").arg(slug), response.get()));
+ QObject::connect(netJob, &NetJob::succeeded, this, [this, response, slug]
+ {
+ if (current.slug != slug)
+ {
+ return;
+ }
+ QJsonParseError parse_error;
+ QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error);
+ QJsonObject obj = doc.object();
+ if(parse_error.error != QJsonParseError::NoError)
+ {
+ qWarning() << "Error while parsing JSON response from Technic at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << *response;
+ return;
+ }
+ if (!obj.contains("url"))
+ {
+ qWarning() << "Json doesn't contain an url key";
+ return;
+ }
+ QJsonValueRef url = obj["url"];
+ if (url.isString())
+ {
+ current.url = url.toString();
+ }
+ else
+ {
+ if (!obj.contains("solder"))
+ {
+ qWarning() << "Json doesn't contain a valid url or solder key";
+ return;
+ }
+ QJsonValueRef solderUrl = obj["solder"];
+ if (solderUrl.isString())
+ {
+ current.url = solderUrl.toString();
+ current.isSolder = true;
+ }
+ else
+ {
+ qWarning() << "Json doesn't contain a valid url or solder key";
+ return;
+ }
+ }
+
+ current.minecraftVersion = Json::ensureString(obj, "minecraft", QString(), "__placeholder__");
+ current.websiteUrl = Json::ensureString(obj, "platformUrl", QString(), "__placeholder__");
+ current.author = Json::ensureString(obj, "user", QString(), "__placeholder__");
+ current.description = Json::ensureString(obj, "description", QString(), "__placeholder__");
+ current.metadataLoaded = true;
+ metadataLoaded();
+ });
+ netJob->start();
+}
+
+// expects current.metadataLoaded to be true
+void TechnicPage::metadataLoaded()
+{
+ QString text = "";
+ QString name = current.name;
+
+ if (current.websiteUrl.isEmpty())
+ // This allows injecting HTML here.
+ text = name;
+ else
+ // URL not properly escaped for inclusion in HTML. The name allows for injecting HTML.
+ text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
+ if (!current.author.isEmpty()) {
+ // This allows injecting HTML here
+ text += tr(" by ") + current.author;
+ }
+
+ ui->frame->setModText(text);
+ ui->frame->setModDescription(current.description);
+ if (!current.isSolder)
+ {
+ dialog->setSuggestedPack(current.name, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion));
+ }
+ else
+ {
+ while (current.url.endsWith('/')) current.url.chop(1);
+ dialog->setSuggestedPack(current.name, new Technic::SolderPackInstallTask(current.url + "/modpack/" + current.slug, current.minecraftVersion));
+ }
+}
diff --git a/application/pages/modplatform/technic/TechnicPage.h b/application/pages/modplatform/technic/TechnicPage.h
new file mode 100644
index 00000000..27e1258a
--- /dev/null
+++ b/application/pages/modplatform/technic/TechnicPage.h
@@ -0,0 +1,78 @@
+/* Copyright 2013-2021 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 "pages/BasePage.h"
+#include <MultiMC.h>
+#include "tasks/Task.h"
+#include "TechnicData.h"
+
+namespace Ui
+{
+class TechnicPage;
+}
+
+class NewInstanceDialog;
+
+namespace Technic {
+ class ListModel;
+}
+
+class TechnicPage : public QWidget, public BasePage
+{
+ Q_OBJECT
+
+public:
+ explicit TechnicPage(NewInstanceDialog* dialog, QWidget *parent = 0);
+ virtual ~TechnicPage();
+ virtual QString displayName() const override
+ {
+ return tr("Technic");
+ }
+ virtual QIcon icon() const override
+ {
+ return MMC->getThemedIcon("technic");
+ }
+ virtual QString id() const override
+ {
+ return "technic";
+ }
+ virtual QString helpPage() const override
+ {
+ return "Technic-platform";
+ }
+ virtual bool shouldDisplay() const override;
+
+ void openedImpl() override;
+
+ bool eventFilter(QObject* watched, QEvent* event) override;
+
+private:
+ void suggestCurrent();
+ void metadataLoaded();
+
+private slots:
+ void triggerSearch();
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+
+private:
+ Ui::TechnicPage *ui = nullptr;
+ NewInstanceDialog* dialog = nullptr;
+ Technic::ListModel* model = nullptr;
+ Technic::Modpack current;
+};
diff --git a/application/pages/modplatform/twitch/TwitchPage.ui b/application/pages/modplatform/technic/TechnicPage.ui
index c78d8ce0..2ca45dd2 100644
--- a/application/pages/modplatform/twitch/TwitchPage.ui
+++ b/application/pages/modplatform/technic/TechnicPage.ui
@@ -1,27 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>TwitchPage</class>
- <widget class="QWidget" name="TwitchPage">
+ <class>TechnicPage</class>
+ <widget class="QWidget" name="TechnicPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>875</width>
- <height>745</height>
+ <width>546</width>
+ <height>405</height>
</rect>
</property>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QLineEdit" name="searchEdit"/>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLineEdit" name="searchEdit">
+ <property name="placeholderText">
+ <string>Search and filter ...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="searchButton">
+ <property name="text">
+ <string>Search</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
- <item row="0" column="1">
- <widget class="QPushButton" name="searchButton">
- <property name="text">
- <string>Search</string>
+ <item>
+ <widget class="QListView" name="packView">
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
</property>
</widget>
</item>
- <item row="2" column="0" colspan="2">
+ <item>
<widget class="MCModInfoFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
@@ -37,22 +75,6 @@
</property>
</widget>
</item>
- <item row="1" column="0" colspan="2">
- <widget class="QListView" name="packView">
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="iconSize">
- <size>
- <width>48</width>
- <height>48</height>
- </size>
- </property>
- </widget>
- </item>
</layout>
</widget>
<customwidgets>
diff --git a/application/pages/modplatform/twitch/TwitchData.h b/application/pages/modplatform/twitch/TwitchData.h
deleted file mode 100644
index dd000b84..00000000
--- a/application/pages/modplatform/twitch/TwitchData.h
+++ /dev/null
@@ -1,38 +0,0 @@
-#pragma once
-
-#include <QString>
-#include <QList>
-
-namespace Twitch {
-
-struct ModpackAuthor {
- QString name;
- QString url;
-};
-
-struct ModpackFile {
- int addonId;
- int fileId;
- QString version;
- QString mcVersion;
- QString downloadUrl;
-};
-
-struct Modpack
-{
- bool broken = true;
- int addonId = 0;
-
- QString name;
- QString description;
- QList<ModpackAuthor> authors;
- QString mcVersion;
- QString logoName;
- QString logoUrl;
- QString websiteUrl;
-
- ModpackFile latestFile;
-};
-}
-
-Q_DECLARE_METATYPE(Twitch::Modpack)
diff --git a/application/pages/modplatform/twitch/TwitchPage.cpp b/application/pages/modplatform/twitch/TwitchPage.cpp
deleted file mode 100644
index 1e9f9dbb..00000000
--- a/application/pages/modplatform/twitch/TwitchPage.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-#include "TwitchPage.h"
-#include "ui_TwitchPage.h"
-
-#include "MultiMC.h"
-#include "dialogs/NewInstanceDialog.h"
-#include <InstanceImportTask.h>
-#include "TwitchModel.h"
-#include <QKeyEvent>
-
-TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog)
-{
- ui->setupUi(this);
- connect(ui->searchButton, &QPushButton::clicked, this, &TwitchPage::triggerSearch);
- ui->searchEdit->installEventFilter(this);
- model = new Twitch::ListModel(this);
- ui->packView->setModel(model);
- connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &TwitchPage::onSelectionChanged);
-}
-
-TwitchPage::~TwitchPage()
-{
- delete ui;
-}
-
-bool TwitchPage::eventFilter(QObject* watched, QEvent* event)
-{
- if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) {
- QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
- if (keyEvent->key() == Qt::Key_Return) {
- triggerSearch();
- keyEvent->accept();
- return true;
- }
- }
- return QWidget::eventFilter(watched, event);
-}
-
-bool TwitchPage::shouldDisplay() const
-{
- return true;
-}
-
-void TwitchPage::openedImpl()
-{
- suggestCurrent();
-}
-
-void TwitchPage::triggerSearch()
-{
- model->searchWithTerm(ui->searchEdit->text());
-}
-
-void TwitchPage::onSelectionChanged(QModelIndex first, QModelIndex second)
-{
- if(!first.isValid())
- {
- if(isOpened)
- {
- dialog->setSuggestedPack();
- }
- ui->frame->clear();
- return;
- }
-
- current = model->data(first, Qt::UserRole).value<Twitch::Modpack>();
- QString text = "";
- QString name = current.name;
-
- if (current.websiteUrl.isEmpty())
- text = name;
- else
- text = "<a href=\"" + current.websiteUrl + "\">" + name + "</a>";
- if (!current.authors.empty()) {
- auto authorToStr = [](Twitch::ModpackAuthor & author) {
- if(author.url.isEmpty()) {
- return author.name;
- }
- return QString("<a href=\"%1\">%2</a>").arg(author.url, author.name);
- };
- QStringList authorStrs;
- for(auto & author: current.authors) {
- authorStrs.push_back(authorToStr(author));
- }
- text += tr(" by ") + authorStrs.join(", ");
- }
-
- ui->frame->setModText(text);
- ui->frame->setModDescription(current.description);
- suggestCurrent();
-}
-
-void TwitchPage::suggestCurrent()
-{
- if(!isOpened)
- {
- return;
- }
- if(current.broken)
- {
- dialog->setSuggestedPack();
- }
-
- dialog->setSuggestedPack(current.name, new InstanceImportTask(current.latestFile.downloadUrl));
- QString editedLogoName;
- editedLogoName = "twitch_" + current.logoName.section(".", 0, 0);
- model->getLogo(current.logoName, current.logoUrl, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
-}
diff --git a/application/resources/MultiMC.ico b/application/resources/MultiMC.ico
index 1846964e..a86a1f0d 100644
--- a/application/resources/MultiMC.ico
+++ b/application/resources/MultiMC.ico
Binary files differ
diff --git a/application/resources/assets/underconstruction.png b/application/resources/assets/underconstruction.png
new file mode 100644
index 00000000..6ae06476
--- /dev/null
+++ b/application/resources/assets/underconstruction.png
Binary files differ
diff --git a/application/resources/multimc/16x16/patreon.png b/application/resources/multimc/16x16/patreon.png
index cde2b326..9150c478 100644
--- a/application/resources/multimc/16x16/patreon.png
+++ b/application/resources/multimc/16x16/patreon.png
Binary files differ
diff --git a/application/resources/multimc/22x22/patreon.png b/application/resources/multimc/22x22/patreon.png
index b6235ad2..f2c2076c 100644
--- a/application/resources/multimc/22x22/patreon.png
+++ b/application/resources/multimc/22x22/patreon.png
Binary files differ
diff --git a/application/resources/multimc/24x24/patreon.png b/application/resources/multimc/24x24/patreon.png
index c1da080f..add80668 100644
--- a/application/resources/multimc/24x24/patreon.png
+++ b/application/resources/multimc/24x24/patreon.png
Binary files differ
diff --git a/application/resources/multimc/32x32/patreon.png b/application/resources/multimc/32x32/patreon.png
index f5ae8a5e..70085aa1 100644
--- a/application/resources/multimc/32x32/patreon.png
+++ b/application/resources/multimc/32x32/patreon.png
Binary files differ
diff --git a/application/resources/multimc/48x48/patreon.png b/application/resources/multimc/48x48/patreon.png
index 2708a85a..7aec4d7d 100644
--- a/application/resources/multimc/48x48/patreon.png
+++ b/application/resources/multimc/48x48/patreon.png
Binary files differ
diff --git a/application/resources/multimc/64x64/patreon.png b/application/resources/multimc/64x64/patreon.png
index 7b4814ec..ef5d690e 100644
--- a/application/resources/multimc/64x64/patreon.png
+++ b/application/resources/multimc/64x64/patreon.png
Binary files differ
diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc
index 4f039a99..249e8e28 100644
--- a/application/resources/multimc/multimc.qrc
+++ b/application/resources/multimc/multimc.qrc
@@ -11,12 +11,17 @@
<!-- REDDIT logo icon, needs reddit license! -->
<file>scalable/reddit-alien.svg</file>
- <!-- twitch logo icon -->
- <file>scalable/twitch.svg</file>
+ <!-- Icon for CurseForge. Unknown license? -->
+ <file alias="32x32/flame.png">32x32/instances/flame.png</file>
+ <file alias="128x128/flame.png">128x128/instances/flame.png</file>
<!-- technic logo icon -->
<file>scalable/technic.svg</file>
+ <!-- ATLauncher logo icon (and related bits) -->
+ <file>scalable/atlauncher.svg</file>
+ <file>scalable/atlauncher-placeholder.png</file>
+
<!-- A proxy icon. Our own. SSSsss -->
<file>scalable/proxy.svg</file>
diff --git a/application/resources/multimc/scalable/atlauncher-placeholder.png b/application/resources/multimc/scalable/atlauncher-placeholder.png
new file mode 100644
index 00000000..f4314c43
--- /dev/null
+++ b/application/resources/multimc/scalable/atlauncher-placeholder.png
Binary files differ
diff --git a/application/resources/multimc/scalable/atlauncher.svg b/application/resources/multimc/scalable/atlauncher.svg
new file mode 100644
index 00000000..1bb5f359
--- /dev/null
+++ b/application/resources/multimc/scalable/atlauncher.svg
@@ -0,0 +1,15 @@
+<svg viewBox="0 0 2084 2084" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd"
+ stroke-linejoin="round" stroke-miterlimit="2">
+ <g fill-rule="nonzero">
+ <path d="M1041.67 81.38l272.437 159.032-825.246 478.685-272.438-157.971L1041.67 81.38zm87.28 371.074l274.024-159.032 463.937 271.945-276.14 153.73-461.821-266.643z"
+ fill="#3b3b3b"/>
+ <path d="M216.42 561.126v961.081l825.247 479.746V1684.95l-551.222-321.774-1.587-644.079L216.42 561.126z"
+ fill="#2e2e2e"/>
+ <path d="M1866.91 1517.97l-825.246 483.986v-317.003l550.164-320.714-1.058-645.139 276.14-153.73v952.6z"
+ fill="#333"/>
+ <path d="M1590.77 719.097l-549.106 310.112v165.393l214.246-122.984v488.757l138.599-81.106V989.451l196.261-115.563V719.097z"
+ fill="#89c236"/>
+ <path d="M488.858 719.097l1.587 644.079 152.353 90.118v-198.79l230.645 132.527v199.319l168.753 98.6v-655.741L488.858 719.097zm383.527 531.166l-227.471-131.466v-150.02l227.471 127.225v154.261z"
+ fill="#7baf31"/>
+ </g>
+</svg>
diff --git a/application/resources/multimc/scalable/technic.svg b/application/resources/multimc/scalable/technic.svg
index 827b590a..91cbd3d7 100644
--- a/application/resources/multimc/scalable/technic.svg
+++ b/application/resources/multimc/scalable/technic.svg
@@ -1 +1,13 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2479" viewBox="0 0 1464.248 1452.156"><path fill="#1389D2" d="M644.592 5.192c159.049-19.094 324.188 14.764 461.941 96.756 143.299 84.188 256.41 218.769 313.509 374.945 62.353 168.133 58.63 359.689-10.432 525.205-63.691 154.529-182.775 285.021-330.227 363.417-149.71 80.709-327.849 105.845-494.333 71.979-172.465-34.279-331.022-133.533-437.232-273.729C51.059 1037.725-2.015 878.924.058 719.941c.548-167.949 63.08-334.559 172.343-461.998C290.451 118.177 462.794 25.934 644.592 5.192m-120.67 181.494c-131.408 49.538-243.479 148.488-308.511 273.003-60.641 114.509-80.038 249.94-54.906 377.018 25.076 130.193 97.609 250.192 200.162 334.012 101.76 84.247 232.924 131.896 365.063 132.754-.795-37.831-.366-75.591-1.04-113.416-.792-31.415-30.866-53.744-59.722-58.563-.917 28.305.119 62.895-24.771 82.113-23.731 16.288-55.638 15.919-82.479 8.782-27.149-7.626-42.216-35.808-43.011-62.532-1.647-40.565-.244-81.196-.729-121.828-10.86-.059-21.781-.059-32.702-.059-.06-35.442-.06-70.892 0-106.273 10.92-.065 21.84-.125 32.762-.125 0-12.076 0-24.093-.06-36.115-14.033-4.635-30.263-10.188-35.934-25.379-14.033-33.186 3.417-70.462-10.188-103.891-13.911-35.203-27.759-74.125-14.825-111.888 7.992-26.598 34.042-43.56 61.007-45.694-.06-76.808 0-153.553-.06-230.358-.429-40.754 16.288-82.725 48.924-108.164 37.215-29.525 86.57-37.092 132.812-36.786 102.921.061 203.701 48.864 270.2 126.893-.124 18.362 0 36.727-.124 55.149-92.185.061-184.359-.063-276.54 0-10.006-1.828-19.523 7.505-17.754 17.569.304 58.812-.549 117.682-.308 176.492 17.021.061 34.104.304 51.125-.488 2.866-11.774 6.038-23.425 9.454-35.019 74.982 18.424 149.893 36.789 224.809 55.335-2.502 8.6-4.938 17.266-7.382 25.927-6.588 2.5-13.177 5.001-19.767 7.504-2.621 203.154-4.331 406.363-6.648 609.514 176.251-60.091 319.795-208.828 369.755-388.73 39.415-137.021 25.262-288.622-39.527-415.699-63.628-126.646-175.579-227.98-307.72-279.104-133.783-52.65-287.092-53.321-421.365-1.954m140.741 710.665c0 11.401 0 22.817.063 34.221 10.125.06 20.253.06 30.381.06.061 35.448.061 70.896 0 106.34-10.128 0-20.315 0-30.442.059v32.333c19.949.06 39.961.06 59.909-.061-.917-57.646-.365-115.305-1.283-172.95a9391.232 9391.232 0 0 0-58.628-.002z"/></svg> \ No newline at end of file
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="2546.7" height="2526" version="1.1" viewBox="0 0 1491.6 1479.7" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <metadata>
+ <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>
+ <path d="m658.25 18.964c159.05-19.094 324.19 14.764 461.94 96.756 143.3 84.188 256.41 218.77 313.51 374.94 62.353 168.13 58.63 359.69-10.432 525.21-63.691 154.53-182.78 285.02-330.23 363.42-149.71 80.709-327.85 105.84-494.33 71.979-172.46-34.279-331.02-133.53-437.23-273.73-96.759-126.04-149.83-284.84-147.76-443.82 0.548-167.95 63.08-334.56 172.34-462 118.05-139.77 290.39-232.01 472.19-252.75m-120.67 181.49c-131.41 49.538-243.48 148.49-308.51 273-60.641 114.51-80.038 249.94-54.906 377.02 25.076 130.19 97.609 250.19 200.16 334.01 101.76 84.247 232.92 131.9 365.06 132.75-0.795-37.831-0.366-75.591-1.04-113.42-0.792-31.415-30.866-53.744-59.722-58.563-0.917 28.305 0.119 62.895-24.771 82.113-23.731 16.288-55.638 15.919-82.479 8.782-27.149-7.626-42.216-35.808-43.011-62.532-1.647-40.565-0.244-81.196-0.729-121.83-10.86-0.059-21.781-0.059-32.702-0.059-0.06-35.442-0.06-70.892 0-106.27 10.92-0.065 21.84-0.125 32.762-0.125 0-12.076 0-24.093-0.06-36.115-14.033-4.635-30.263-10.188-35.934-25.379-14.033-33.186 3.417-70.462-10.188-103.89-13.911-35.203-27.759-74.125-14.825-111.89 7.992-26.598 34.042-43.56 61.007-45.694-0.06-76.808 0-153.55-0.06-230.36-0.429-40.754 16.288-82.725 48.924-108.16 37.215-29.525 86.57-37.092 132.81-36.786 102.92 0.061 203.7 48.864 270.2 126.89-0.124 18.362 0 36.727-0.124 55.149-92.185 0.061-184.36-0.063-276.54 0-10.006-1.828-19.523 7.505-17.754 17.569 0.304 58.812-0.549 117.68-0.308 176.49 17.021 0.061 34.104 0.304 51.125-0.488 2.866-11.774 6.038-23.425 9.454-35.019 74.982 18.424 149.89 36.789 224.81 55.335-2.502 8.6-4.938 17.266-7.382 25.927-6.588 2.5-13.177 5.001-19.767 7.504-2.621 203.15-4.331 406.36-6.648 609.51 176.25-60.091 319.8-208.83 369.76-388.73 39.415-137.02 25.262-288.62-39.527-415.7-63.628-126.65-175.58-227.98-307.72-279.1-133.78-52.65-287.09-53.321-421.36-1.954m140.74 710.66c0 11.401 0 22.817 0.063 34.221 10.125 0.06 20.253 0.06 30.381 0.06 0.061 35.448 0.061 70.896 0 106.34-10.128 0-20.315 0-30.442 0.059v32.333c19.949 0.06 39.961 0.06 59.909-0.061-0.917-57.646-0.365-115.31-1.283-172.95a9391.2 9391.2 0 0 0-58.628 0z" fill="#1389d2" stroke="#000" stroke-width="27.532"/>
+</svg>
diff --git a/application/resources/multimc/scalable/twitch.svg b/application/resources/multimc/scalable/twitch.svg
deleted file mode 100644
index 80999380..00000000
--- a/application/resources/multimc/scalable/twitch.svg
+++ /dev/null
@@ -1,63 +0,0 @@
-<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- id="Layer_1"
- data-name="Layer 1"
- viewBox="0 0 134 134"
- version="1.1"
- sodipodi:docname="twitch.svg"
- inkscape:version="0.92.2 2405546, 2018-03-11"
- width="134"
- height="134">
- <metadata
- id="metadata13">
- <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>Glitch</dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <sodipodi:namedview
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1414"
- inkscape:window-height="944"
- id="namedview11"
- showgrid="false"
- inkscape:zoom="1.761194"
- inkscape:cx="-109.17797"
- inkscape:cy="66.999998"
- inkscape:window-x="2640"
- inkscape:window-y="554"
- inkscape:window-maximized="0"
- inkscape:current-layer="Layer_1" />
- <defs
- id="defs4">
- <style
- id="style2">.cls-1{fill:#6441a4;fill-rule:evenodd;}</style>
- </defs>
- <title
- id="title6">Glitch</title>
- <path
- class="cls-1"
- d="M 9,0 0,23 v 94 h 32 v 17 H 50 L 67,117 H 93 L 128,82 V 0 Z M 116,76 96,96 H 64 L 47,113 V 96 H 20 V 12 h 96 z M 96,35 V 70 H 84 V 35 Z M 64,35 V 70 H 52 V 35 Z"
- id="path8"
- style="fill:#6441a4;fill-opacity:1;fill-rule:evenodd"
- inkscape:connector-curvature="0" />
-</svg>
diff --git a/application/setupwizard/SetupWizard.h b/application/setupwizard/SetupWizard.h
index 08b0d805..9b8adb4d 100644
--- a/application/setupwizard/SetupWizard.h
+++ b/application/setupwizard/SetupWizard.h
@@ -1,4 +1,4 @@
-/* Copyright 2017-2019 MultiMC Contributors
+/* Copyright 2017-2021 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 00b2300d..49b1afaa 100644
--- a/application/themes/SystemTheme.cpp
+++ b/application/themes/SystemTheme.cpp
@@ -6,16 +6,19 @@
SystemTheme::SystemTheme()
{
+ qDebug() << "Determining System Theme...";
const auto & style = QApplication::style();
systemPalette = style->standardPalette();
QString lowerThemeName = style->objectName();
- qDebug() << systemTheme;
+ qDebug() << "System theme seems to be:" << lowerThemeName;
QStringList styles = QStyleFactory::keys();
for(auto &st: styles)
{
+ qDebug() << "Considering theme from theme factory:" << st.toLower();
if(st.toLower() == lowerThemeName)
{
systemTheme = st;
+ qDebug() << "System theme has been determined to be:" << systemTheme;
return;
}
}
diff --git a/application/widgets/CustomCommands.h b/application/widgets/CustomCommands.h
index 4d447f7b..8db991fa 100644
--- a/application/widgets/CustomCommands.h
+++ b/application/widgets/CustomCommands.h
@@ -1,4 +1,4 @@
-/* Copyright 2018-2019 MultiMC Contributors
+/* Copyright 2018-2021 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/JavaSettingsWidget.cpp b/application/widgets/JavaSettingsWidget.cpp
index a11dd1aa..7f53dc23 100644
--- a/application/widgets/JavaSettingsWidget.cpp
+++ b/application/widgets/JavaSettingsWidget.cpp
@@ -19,7 +19,7 @@
JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent)
{
- m_availableMemory = Sys::getSystemRam() / Sys::megabyte;
+ m_availableMemory = Sys::getSystemRam() / Sys::mebibyte;
goodIcon = MMC->getThemedIcon("status-good");
yellowIcon = MMC->getThemedIcon("status-yellow");
diff --git a/application/widgets/LabeledToolButton.cpp b/application/widgets/LabeledToolButton.cpp
index ab273b65..ab2d3278 100644
--- a/application/widgets/LabeledToolButton.cpp
+++ b/application/widgets/LabeledToolButton.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/LabeledToolButton.h b/application/widgets/LabeledToolButton.h
index 136ebd23..51f99e9b 100644
--- a/application/widgets/LabeledToolButton.h
+++ b/application/widgets/LabeledToolButton.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/LanguageSelectionWidget.h b/application/widgets/LanguageSelectionWidget.h
index 03e29bd8..e65936db 100644
--- a/application/widgets/LanguageSelectionWidget.h
+++ b/application/widgets/LanguageSelectionWidget.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.cpp b/application/widgets/MCModInfoFrame.cpp
index 577b32a7..5b1f6230 100644
--- a/application/widgets/MCModInfoFrame.cpp
+++ b/application/widgets/MCModInfoFrame.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -135,6 +135,7 @@ void MCModInfoFrame::setModDescription(QString text)
ui->label_ModDescription->setOpenExternalLinks(false);
ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText);
desc = text;
+ // This allows injecting HTML here.
labeltext.append("<html><body>" + finaltext.left(287) + "<a href=\"#mod_desc\">...</a></body></html>");
QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler);
}
diff --git a/application/widgets/MCModInfoFrame.h b/application/widgets/MCModInfoFrame.h
index 51aa4489..0b7ef537 100644
--- a/application/widgets/MCModInfoFrame.h
+++ b/application/widgets/MCModInfoFrame.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 99972a40..c8ccd292 100644
--- a/application/widgets/ModListView.cpp
+++ b/application/widgets/ModListView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 5a07e868..881e092f 100644
--- a/application/widgets/ModListView.h
+++ b/application/widgets/ModListView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 376e119b..05a5e6b4 100644
--- a/application/widgets/PageContainer.cpp
+++ b/application/widgets/PageContainer.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 925f4ba3..976d34e9 100644
--- a/application/widgets/PageContainer.h
+++ b/application/widgets/PageContainer.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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_p.h b/application/widgets/PageContainer_p.h
index 4a5a9239..da1a66f4 100644
--- a/application/widgets/PageContainer_p.h
+++ b/application/widgets/PageContainer_p.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/ServerStatus.cpp b/application/widgets/ServerStatus.cpp
index ce0aed9c..87c34f70 100644
--- a/application/widgets/ServerStatus.cpp
+++ b/application/widgets/ServerStatus.cpp
@@ -65,7 +65,7 @@ ServerStatus::ServerStatus(QWidget *parent, Qt::WindowFlags f) : QWidget(parent,
addStatus("authserver.mojang.com", tr("Auth"));
addLine();
- addStatus("sessionserver.mojang.com", tr("Session"));
+ addStatus("session.minecraft.net", tr("Session"));
addLine();
addStatus("textures.minecraft.net", tr("Skins"));
addLine();
diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp
index fdcb84e6..8424fedd 100644
--- a/application/widgets/VersionListView.cpp
+++ b/application/widgets/VersionListView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 37f7b27e..4153b314 100644
--- a/application/widgets/VersionListView.h
+++ b/application/widgets/VersionListView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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.h b/application/widgets/VersionSelectWidget.h
index 701f568e..0a649408 100644
--- a/application/widgets/VersionSelectWidget.h
+++ b/application/widgets/VersionSelectWidget.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index 86ea83ee..60d417a6 100644
--- a/buildconfig/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -33,7 +33,12 @@ Config::Config()
VERSION_STR = "@MultiMC_VERSION_STRING@";
NEWS_RSS_URL = "@MultiMC_NEWS_RSS_URL@";
PASTE_EE_KEY = "@MultiMC_PASTE_EE_API_KEY@";
+ IMGUR_CLIENT_ID = "@MultiMC_IMGUR_CLIENT_ID@";
META_URL = "@MultiMC_META_URL@";
+
+ BUG_TRACKER_URL = "@MultiMC_BUG_TRACKER_URL@";
+ DISCORD_URL = "@MultiMC_DISCORD_URL@";
+ SUBREDDIT_URL = "@MultiMC_SUBREDDIT_URL@";
}
QString Config::printableVersionString() const
diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h
index a80af3d2..185bebad 100644
--- a/buildconfig/BuildConfig.h
+++ b/buildconfig/BuildConfig.h
@@ -31,6 +31,11 @@ public:
/// URL for the updater's channel
QString CHANLIST_URL;
+ /// User-Agent to use.
+ QString USER_AGENT = "MultiMC/5.0";
+ /// User-Agent to use for uncached requests.
+ QString USER_AGENT_UNCACHED = "MultiMC/5.0 (Uncached)";
+
/// Google analytics ID
QString ANALYTICS_ID;
@@ -61,22 +66,34 @@ public:
QString PASTE_EE_KEY;
/**
+ * Client ID you can get from Imgur when you register an application
+ */
+ QString IMGUR_CLIENT_ID;
+
+ /**
* MultiMC Metadata repository URL prefix
*/
QString META_URL;
+ QString BUG_TRACKER_URL;
+ QString DISCORD_URL;
+ QString SUBREDDIT_URL;
+
QString RESOURCE_BASE = "https://resources.download.minecraft.net/";
QString LIBRARY_BASE = "https://libraries.minecraft.net/";
QString SKINS_BASE = "https://crafatar.com/skins/";
QString AUTH_BASE = "https://authserver.mojang.com/";
QString MOJANG_STATUS_URL = "https://status.mojang.com/check";
QString IMGUR_BASE_URL = "https://api.imgur.com/3/";
- QString FMLLIBS_OUR_BASE_URL = "https://files.multimc.org/fmllibs/";
- QString FMLLIBS_FORGE_BASE_URL = "https://files.minecraftforge.net/fmllibs/";
+ QString FMLLIBS_BASE_URL = "https://files.multimc.org/fmllibs/";
QString TRANSLATIONS_BASE_URL = "https://files.multimc.org/translations/";
+ QString MODPACKSCH_API_BASE_URL = "https://api.modpacks.ch/";
+
QString LEGACY_FTB_CDN_BASE_URL = "https://dist.creeper.host/FTB2/";
+ QString ATL_DOWNLOAD_SERVER_URL = "https://download.nodecdn.net/containers/atl/";
+
/**
* \brief Converts the Version to a string.
* \return The version number in string format (major.minor.revision.build).
diff --git a/changelog.md b/changelog.md
index e315950c..31b99a6b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,8 +1,70 @@
-# MultiMC 0.6.11
+# MultiMC 0.6.12
+
+After roughly one year of maintenance and development work by various contributors, we're just calling it a good time to release.
+
+What got added since the last time? Quite a bit! But in general, this is more of a spring cleaning before the major changes that we need to make come in.
+
+### Modpack platforms
+
+We've added a whole bunch of new modpack platforms to pick from right into the new instance dialog. If you run into any unusual issues with the imported packs, report them on the bug tracker.
+
+- Added a CurseForge pack browser
+
+- GH-3095: Added an FTB pack browser
+
+ Temporarily, MultiMC ignores download failures for FTB packs (GH-3304). This is because the platform has consistency issues.
+
+- GH-469: Added a Technic/Solder pack browser
+
+- GH-405: Added a ATLauncher pack browser
+
+### Other changes
+
+- Added the option to not use OpenAL and/or GLFW libraries bundled with the game.
+
+ This is interesting if you have ones that come with your system and work better.
+
+- Skins (the part used for account icons) are now rendered with the overlay on.
+
+- GH-3130: Skin upload has been switched over to the new Mojang API and should have less issues.
+
+- MultiMC now shows world icons and allows resetting world icons in `View Worlds`.
+
+- GH-3229: Copy seed button has been updated to be compatible with newer versions of the game.
+
+- GH-3427: `View Worlds` now has a very simple `Datapacks` button - it just opens the system file browser.
+
+- GH-3189: Updated nbt library - this makes `View Worlds` work properly again for newer versions of the game.
+
+- Fixed online saving in Classic versions.
+
+- GH-3131: Fixed not working with proxy ports greater than 32767.
+
+- Proxy login details are no longer logged in files.
+
+- GH-3467: The launch could stall in the ScanModFolders task if the mod folders didn't exist yet.
+
+- GH-3602: Pre-launch commands could fail on first launch of the instance because the .minecraft folder has not been created yet.
+
+### Technical changes
+
+- GH-3234: At build time, the meta URL can be changed.
+
+- Removed some hacks previously required to get Forge working
+
+ MultiMC no longer contains pack200 and the custom lzma format support used by Forge only.
+
+- Some preparations have been done to allow downloading Java runtimes from Mojang - support for the Piston repository.
+
+- Compatibility with unusual build environments has been increased
+
+# Previous releases
+
+## MultiMC 0.6.11
This adds Forge 1.13+ support using [ForgeWrapper](https://github.com/ZekerZhayard/ForgeWrapper) by ZekerZhayard.
-### New or changed features
+#### New or changed features
- GH-2988: You can now import instances and curse modpacks from the command line with the `--import` option followed by either an URL or a local file path.
@@ -14,8 +76,6 @@ This adds Forge 1.13+ support using [ForgeWrapper](https://github.com/ZekerZhaya
This enables ForgeWrapper to work - MultiMC downloads all the files, and ForgeWrapper runs the Forge installer during instance start when needed.
-# Previous releases
-
## MultiMC 0.6.8
This is mostly about removal of the 'curse URL' related features, because they were of low quality and generally unreliable.
diff --git a/libraries/README.md b/libraries/README.md
index cdc72004..ac861148 100644
--- a/libraries/README.md
+++ b/libraries/README.md
@@ -158,3 +158,10 @@ A Google Analytics library for Qt.
BSD licensed, derived from [qt-google-analytics](https://github.com/HSAnet/qt-google-analytics).
Modifications include better handling of IP anonymization (can be enabled) and general improvements of the API (application handles persistence and ID generation instead of the library).
+
+## tomlc99
+A TOML language parser. Used by Forge 1.14+ to store mod metadata.
+
+See [github repo](https://github.com/cktan/tomlc99).
+
+Licenced under the MIT licence.
diff --git a/libraries/classparser/include/classparser.h b/libraries/classparser/include/classparser.h
index 6c619e47..3660026b 100644
--- a/libraries/classparser/include/classparser.h
+++ b/libraries/classparser/include/classparser.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/libraries/classparser/include/classparser_config.h b/libraries/classparser/include/classparser_config.h
index ee053de6..7bfae7cc 100644
--- a/libraries/classparser/include/classparser_config.h
+++ b/libraries/classparser/include/classparser_config.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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/classparser.cpp b/libraries/classparser/src/classparser.cpp
index e0b6728d..8825ea39 100644
--- a/libraries/classparser/src/classparser.cpp
+++ b/libraries/classparser/src/classparser.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
diff --git a/libraries/classparser/src/javaendian.h b/libraries/classparser/src/javaendian.h
index 076bff5e..5a6e107b 100644
--- a/libraries/classparser/src/javaendian.h
+++ b/libraries/classparser/src/javaendian.h
@@ -11,32 +11,17 @@ inline uint64_t bigswap(uint64_t x)
{
return x;
}
-;
+
inline uint32_t bigswap(uint32_t x)
{
return x;
}
-;
+
inline uint16_t bigswap(uint16_t x)
{
return x;
}
-;
-inline int64_t bigswap(int64_t x)
-{
- return x;
-}
-;
-inline int32_t bigswap(int32_t x)
-{
- return x;
-}
-;
-inline int16_t bigswap(int16_t x)
-{
- return x;
-}
-;
+
#else
inline uint64_t bigswap(uint64_t x)
{
@@ -55,22 +40,20 @@ inline uint16_t bigswap(uint16_t x)
return (x >> 8) | (x << 8);
}
+#endif
+
inline int64_t bigswap(int64_t x)
{
- return (x >> 56) | ((x << 40) & 0x00FF000000000000) | ((x << 24) & 0x0000FF0000000000) |
- ((x << 8) & 0x000000FF00000000) | ((x >> 8) & 0x00000000FF000000) |
- ((x >> 24) & 0x0000000000FF0000) | ((x >> 40) & 0x000000000000FF00) | (x << 56);
+ return static_cast<int64_t>(bigswap(static_cast<uint64_t>(x)));
}
inline int32_t bigswap(int32_t x)
{
- return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24);
+ return static_cast<int32_t>(bigswap(static_cast<uint32_t>(x)));
}
inline int16_t bigswap(int16_t x)
{
- return (x >> 8) | (x << 8);
+ return static_cast<int16_t>(bigswap(static_cast<uint16_t>(x)));
}
-
-#endif
}
diff --git a/libraries/ganalytics/src/ganalytics_worker.cpp b/libraries/ganalytics/src/ganalytics_worker.cpp
index 5980d3bd..b0ae75a4 100644
--- a/libraries/ganalytics/src/ganalytics_worker.cpp
+++ b/libraries/ganalytics/src/ganalytics_worker.cpp
@@ -237,7 +237,7 @@ void GAnalyticsWorker::postMessageFinished()
int httpStausCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (httpStausCode < 200 || httpStausCode > 299)
{
- logMessage(GAnalytics::Error, QString("Error posting message: %s").arg(reply->errorString()));
+ logMessage(GAnalytics::Error, QString("Error posting message: %1").arg(reply->errorString()));
// An error ocurred. Try sending later.
m_timer.start();
diff --git a/libraries/javacheck/JavaCheck.java b/libraries/javacheck/JavaCheck.java
index 69933040..560abbc0 100644
--- a/libraries/javacheck/JavaCheck.java
+++ b/libraries/javacheck/JavaCheck.java
@@ -2,7 +2,7 @@ import java.lang.Integer;
public class JavaCheck
{
- private static final String[] keys = {"os.arch", "java.version"};
+ private static final String[] keys = {"os.arch", "java.version", "java.vendor"};
public static void main (String [] args)
{
int ret = 0;
diff --git a/libraries/launcher/net/minecraft/Launcher.java b/libraries/launcher/net/minecraft/Launcher.java
index 29ddbd3e..b6b0a574 100644
--- a/libraries/launcher/net/minecraft/Launcher.java
+++ b/libraries/launcher/net/minecraft/Launcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 MultiMC Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -143,7 +143,11 @@ public class Launcher extends Applet implements AppletStub
public URL getDocumentBase()
{
try {
- return new URL("http", "www.minecraft.net", 80, "/game/", null);
+ // Special case only for Classic versions
+ if (wrappedApplet.getClass().getCanonicalName().startsWith("com.mojang")) {
+ return new URL("http", "www.minecraft.net", 80, "/game/", null);
+ }
+ return new URL("http://www.minecraft.net/game/");
} catch (MalformedURLException e) {
e.printStackTrace();
}
diff --git a/libraries/launcher/org/multimc/EntryPoint.java b/libraries/launcher/org/multimc/EntryPoint.java
index 12a494b9..d47a9b18 100644
--- a/libraries/launcher/org/multimc/EntryPoint.java
+++ b/libraries/launcher/org/multimc/EntryPoint.java
@@ -1,5 +1,5 @@
package org.multimc;/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 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/Launcher.java b/libraries/launcher/org/multimc/Launcher.java
index 8f9b043f..d8cb6d1b 100644
--- a/libraries/launcher/org/multimc/Launcher.java
+++ b/libraries/launcher/org/multimc/Launcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 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 19cfdfb7..985a10e6 100644
--- a/libraries/launcher/org/multimc/LegacyFrame.java
+++ b/libraries/launcher/org/multimc/LegacyFrame.java
@@ -1,5 +1,5 @@
package org.multimc;/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 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,7 +46,16 @@ public class LegacyFrame extends Frame implements WindowListener
this.addWindowListener ( this );
}
- public void start ( Applet mcApplet, String user, String session, int winSizeW, int winSizeH, boolean maximize )
+ public void start (
+ Applet mcApplet,
+ String user,
+ String session,
+ int winSizeW,
+ int winSizeH,
+ boolean maximize,
+ String serverAddress,
+ String serverPort
+ )
{
try {
appletWrap = new Launcher( mcApplet, new URL ( "http://www.minecraft.net/game" ) );
@@ -95,6 +104,13 @@ public class LegacyFrame extends Frame implements WindowListener
e.printStackTrace(System.err);
System.exit(-1);
}
+
+ if (serverAddress != null)
+ {
+ appletWrap.setParameter("server", serverAddress);
+ appletWrap.setParameter("port", serverPort);
+ }
+
appletWrap.setParameter ( "username", user );
appletWrap.setParameter ( "sessionid", session );
appletWrap.setParameter ( "stand-alone", "true" ); // Show the quit button.
diff --git a/libraries/launcher/org/multimc/NotFoundException.java b/libraries/launcher/org/multimc/NotFoundException.java
index c1084fe6..ba12951d 100644
--- a/libraries/launcher/org/multimc/NotFoundException.java
+++ b/libraries/launcher/org/multimc/NotFoundException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 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 f5b40c40..2fde1329 100644
--- a/libraries/launcher/org/multimc/ParamBucket.java
+++ b/libraries/launcher/org/multimc/ParamBucket.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 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 e9c84f6e..7ea44c1f 100644
--- a/libraries/launcher/org/multimc/ParseException.java
+++ b/libraries/launcher/org/multimc/ParseException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 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 b0a2d5b0..fcf3edce 100644
--- a/libraries/launcher/org/multimc/Utils.java
+++ b/libraries/launcher/org/multimc/Utils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2019 MultiMC Contributors
+ * Copyright 2012-2021 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/onesix/OneSixLauncher.java b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
index ec688ee3..ea445995 100644
--- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
+++ b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
@@ -1,4 +1,4 @@
-/* Copyright 2012-2019 MultiMC Contributors
+/* Copyright 2012-2021 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,6 +47,9 @@ public class OneSixLauncher implements Launcher
private boolean maximize;
private String cwd;
+ private String serverAddress;
+ private String serverPort;
+
// the much abused system classloader, for convenience (for further abuse)
private ClassLoader cl;
@@ -64,6 +67,9 @@ public class OneSixLauncher implements Launcher
windowTitle = params.firstSafe("windowTitle", "Minecraft");
windowParams = params.firstSafe("windowParams", "854x480");
+ serverAddress = params.firstSafe("serverAddress", null);
+ serverPort = params.firstSafe("serverPort", null);
+
cwd = System.getProperty("user.dir");
winSizeW = 854;
@@ -122,7 +128,7 @@ public class OneSixLauncher implements Launcher
Class<?> MCAppletClass = cl.loadClass(appletClass);
Applet mcappl = (Applet) MCAppletClass.newInstance();
LegacyFrame mcWindow = new LegacyFrame(windowTitle);
- mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize);
+ mcWindow.start(mcappl, userName, sessionId, winSizeW, winSizeH, maximize, serverAddress, serverPort);
return 0;
} catch (Exception e)
{
@@ -164,6 +170,14 @@ public class OneSixLauncher implements Launcher
mcparams.add(Integer.toString(winSizeH));
}
+ if (serverAddress != null)
+ {
+ mcparams.add("--server");
+ mcparams.add(serverAddress);
+ mcparams.add("--port");
+ mcparams.add(serverPort);
+ }
+
// Get the Minecraft Class.
Class<?> mc;
try
diff --git a/libraries/optional-bare/CMakeLists.txt b/libraries/optional-bare/CMakeLists.txt
new file mode 100644
index 00000000..b8b498c5
--- /dev/null
+++ b/libraries/optional-bare/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.1)
+project(optional-bare)
+
+add_library(optional-bare INTERFACE)
+target_include_directories(optional-bare INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
diff --git a/libraries/optional-bare/LICENSE.txt b/libraries/optional-bare/LICENSE.txt
new file mode 100644
index 00000000..36b7cd93
--- /dev/null
+++ b/libraries/optional-bare/LICENSE.txt
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/libraries/optional-bare/README.md b/libraries/optional-bare/README.md
new file mode 100644
index 00000000..e29ff7c1
--- /dev/null
+++ b/libraries/optional-bare/README.md
@@ -0,0 +1,5 @@
+# optional bare
+
+A simple single-file header-only version of a C++17-like optional for default-constructible, copyable types, for C++98 and later.
+
+Imported from: https://github.com/martinmoene/optional-bare/commit/0bb1d183bcee1e854c4ea196b533252c51f98b81
diff --git a/libraries/optional-bare/include/nonstd/optional b/libraries/optional-bare/include/nonstd/optional
new file mode 100644
index 00000000..ecbfa030
--- /dev/null
+++ b/libraries/optional-bare/include/nonstd/optional
@@ -0,0 +1,508 @@
+//
+// Copyright 2017-2019 by Martin Moene
+//
+// https://github.com/martinmoene/optional-bare
+//
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+
+#ifndef NONSTD_OPTIONAL_BARE_HPP
+#define NONSTD_OPTIONAL_BARE_HPP
+
+#define optional_bare_MAJOR 1
+#define optional_bare_MINOR 1
+#define optional_bare_PATCH 0
+
+#define optional_bare_VERSION optional_STRINGIFY(optional_bare_MAJOR) "." optional_STRINGIFY(optional_bare_MINOR) "." optional_STRINGIFY(optional_bare_PATCH)
+
+#define optional_STRINGIFY( x ) optional_STRINGIFY_( x )
+#define optional_STRINGIFY_( x ) #x
+
+// optional-bare configuration:
+
+#define optional_OPTIONAL_DEFAULT 0
+#define optional_OPTIONAL_NONSTD 1
+#define optional_OPTIONAL_STD 2
+
+#if !defined( optional_CONFIG_SELECT_OPTIONAL )
+# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD )
+#endif
+
+// Control presence of exception handling (try and auto discover):
+
+#ifndef optional_CONFIG_NO_EXCEPTIONS
+# if _MSC_VER
+# include <cstddef> // for _HAS_EXCEPTIONS
+# endif
+# if _MSC_VER
+# include <cstddef> // for _HAS_EXCEPTIONS
+# endif
+# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS)
+# define optional_CONFIG_NO_EXCEPTIONS 0
+# else
+# define optional_CONFIG_NO_EXCEPTIONS 1
+# endif
+#endif
+
+// C++ language version detection (C++20 is speculative):
+// Note: VC14.0/1900 (VS2015) lacks too much from C++14.
+
+#ifndef optional_CPLUSPLUS
+# if defined(_MSVC_LANG ) && !defined(__clang__)
+# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG )
+# else
+# define optional_CPLUSPLUS __cplusplus
+# endif
+#endif
+
+#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L )
+#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L )
+#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L )
+#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L )
+#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L )
+
+// C++ language version (represent 98 as 3):
+
+#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) )
+
+// Use C++17 std::optional if available and requested:
+
+#if optional_CPP17_OR_GREATER && defined(__has_include )
+# if __has_include( <optional> )
+# define optional_HAVE_STD_OPTIONAL 1
+# else
+# define optional_HAVE_STD_OPTIONAL 0
+# endif
+#else
+# define optional_HAVE_STD_OPTIONAL 0
+#endif
+
+#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) )
+
+//
+// Using std::optional:
+//
+
+#if optional_USES_STD_OPTIONAL
+
+#include <optional>
+#include <utility>
+
+namespace nonstd {
+
+ using std::in_place;
+ using std::in_place_type;
+ using std::in_place_index;
+ using std::in_place_t;
+ using std::in_place_type_t;
+ using std::in_place_index_t;
+
+ using std::optional;
+ using std::bad_optional_access;
+ using std::hash;
+
+ using std::nullopt;
+ using std::nullopt_t;
+
+ using std::operator==;
+ using std::operator!=;
+ using std::operator<;
+ using std::operator<=;
+ using std::operator>;
+ using std::operator>=;
+ using std::make_optional;
+ using std::swap;
+}
+
+#else // optional_USES_STD_OPTIONAL
+
+#include <cassert>
+
+#if ! optional_CONFIG_NO_EXCEPTIONS
+# include <stdexcept>
+#endif
+
+namespace nonstd { namespace optional_bare {
+
+// type for nullopt
+
+struct nullopt_t
+{
+ struct init{};
+ nullopt_t( init ) {}
+};
+
+// extra parenthesis to prevent the most vexing parse:
+
+const nullopt_t nullopt(( nullopt_t::init() ));
+
+// optional access error.
+
+#if ! optional_CONFIG_NO_EXCEPTIONS
+
+class bad_optional_access : public std::logic_error
+{
+public:
+ explicit bad_optional_access()
+ : logic_error( "bad optional access" ) {}
+};
+
+#endif // optional_CONFIG_NO_EXCEPTIONS
+
+// Simplistic optional: requires T to be default constructible, copyable.
+
+template< typename T >
+class optional
+{
+private:
+ typedef void (optional::*safe_bool)() const;
+
+public:
+ typedef T value_type;
+
+ optional()
+ : has_value_( false )
+ {}
+
+ optional( nullopt_t )
+ : has_value_( false )
+ {}
+
+ optional( T const & arg )
+ : has_value_( true )
+ , value_ ( arg )
+ {}
+
+ template< class U >
+ optional( optional<U> const & other )
+ : has_value_( other.has_value() )
+ , value_ ( other.value() )
+ {}
+
+ optional & operator=( nullopt_t )
+ {
+ reset();
+ return *this;
+ }
+
+ template< class U >
+ optional & operator=( optional<U> const & other )
+ {
+ has_value_ = other.has_value();
+ value_ = other.value();
+ return *this;
+ }
+
+ void swap( optional & rhs )
+ {
+ using std::swap;
+ if ( has_value() == true && rhs.has_value() == true ) { swap( **this, *rhs ); }
+ else if ( has_value() == false && rhs.has_value() == true ) { initialize( *rhs ); rhs.reset(); }
+ else if ( has_value() == true && rhs.has_value() == false ) { rhs.initialize( **this ); reset(); }
+ }
+
+ // observers
+
+ value_type const * operator->() const
+ {
+ return assert( has_value() ),
+ &value_;
+ }
+
+ value_type * operator->()
+ {
+ return assert( has_value() ),
+ &value_;
+ }
+
+ value_type const & operator*() const
+ {
+ return assert( has_value() ),
+ value_;
+ }
+
+ value_type & operator*()
+ {
+ return assert( has_value() ),
+ value_;
+ }
+
+#if optional_CPP11_OR_GREATER
+ explicit operator bool() const
+ {
+ return has_value();
+ }
+#else
+ operator safe_bool() const
+ {
+ return has_value() ? &optional::this_type_does_not_support_comparisons : 0;
+ }
+#endif
+
+ bool has_value() const
+ {
+ return has_value_;
+ }
+
+ value_type const & value() const
+ {
+#if optional_CONFIG_NO_EXCEPTIONS
+ assert( has_value() );
+#else
+ if ( ! has_value() )
+ throw bad_optional_access();
+#endif
+ return value_;
+ }
+
+ value_type & value()
+ {
+#if optional_CONFIG_NO_EXCEPTIONS
+ assert( has_value() );
+#else
+ if ( ! has_value() )
+ throw bad_optional_access();
+#endif
+ return value_;
+ }
+
+ template< class U >
+ value_type value_or( U const & v ) const
+ {
+ return has_value() ? value() : static_cast<value_type>( v );
+ }
+
+ // modifiers
+
+ void reset()
+ {
+ has_value_ = false;
+ }
+
+private:
+ void this_type_does_not_support_comparisons() const {}
+
+ template< typename V >
+ void initialize( V const & value )
+ {
+ assert( ! has_value() );
+ value_ = value;
+ has_value_ = true;
+ }
+
+private:
+ bool has_value_;
+ value_type value_;
+};
+
+// Relational operators
+
+template< typename T, typename U >
+inline bool operator==( optional<T> const & x, optional<U> const & y )
+{
+ return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y;
+}
+
+template< typename T, typename U >
+inline bool operator!=( optional<T> const & x, optional<U> const & y )
+{
+ return !(x == y);
+}
+
+template< typename T, typename U >
+inline bool operator<( optional<T> const & x, optional<U> const & y )
+{
+ return (!y) ? false : (!x) ? true : *x < *y;
+}
+
+template< typename T, typename U >
+inline bool operator>( optional<T> const & x, optional<U> const & y )
+{
+ return (y < x);
+}
+
+template< typename T, typename U >
+inline bool operator<=( optional<T> const & x, optional<U> const & y )
+{
+ return !(y < x);
+}
+
+template< typename T, typename U >
+inline bool operator>=( optional<T> const & x, optional<U> const & y )
+{
+ return !(x < y);
+}
+
+// Comparison with nullopt
+
+template< typename T >
+inline bool operator==( optional<T> const & x, nullopt_t )
+{
+ return (!x);
+}
+
+template< typename T >
+inline bool operator==( nullopt_t, optional<T> const & x )
+{
+ return (!x);
+}
+
+template< typename T >
+inline bool operator!=( optional<T> const & x, nullopt_t )
+{
+ return bool(x);
+}
+
+template< typename T >
+inline bool operator!=( nullopt_t, optional<T> const & x )
+{
+ return bool(x);
+}
+
+template< typename T >
+inline bool operator<( optional<T> const &, nullopt_t )
+{
+ return false;
+}
+
+template< typename T >
+inline bool operator<( nullopt_t, optional<T> const & x )
+{
+ return bool(x);
+}
+
+template< typename T >
+inline bool operator<=( optional<T> const & x, nullopt_t )
+{
+ return (!x);
+}
+
+template< typename T >
+inline bool operator<=( nullopt_t, optional<T> const & )
+{
+ return true;
+}
+
+template< typename T >
+inline bool operator>( optional<T> const & x, nullopt_t )
+{
+ return bool(x);
+}
+
+template< typename T >
+inline bool operator>( nullopt_t, optional<T> const & )
+{
+ return false;
+}
+
+template< typename T >
+inline bool operator>=( optional<T> const &, nullopt_t )
+{
+ return true;
+}
+
+template< typename T >
+inline bool operator>=( nullopt_t, optional<T> const & x )
+{
+ return (!x);
+}
+
+// Comparison with T
+
+template< typename T, typename U >
+inline bool operator==( optional<T> const & x, U const & v )
+{
+ return bool(x) ? *x == v : false;
+}
+
+template< typename T, typename U >
+inline bool operator==( U const & v, optional<T> const & x )
+{
+ return bool(x) ? v == *x : false;
+}
+
+template< typename T, typename U >
+inline bool operator!=( optional<T> const & x, U const & v )
+{
+ return bool(x) ? *x != v : true;
+}
+
+template< typename T, typename U >
+inline bool operator!=( U const & v, optional<T> const & x )
+{
+ return bool(x) ? v != *x : true;
+}
+
+template< typename T, typename U >
+inline bool operator<( optional<T> const & x, U const & v )
+{
+ return bool(x) ? *x < v : true;
+}
+
+template< typename T, typename U >
+inline bool operator<( U const & v, optional<T> const & x )
+{
+ return bool(x) ? v < *x : false;
+}
+
+template< typename T, typename U >
+inline bool operator<=( optional<T> const & x, U const & v )
+{
+ return bool(x) ? *x <= v : true;
+}
+
+template< typename T, typename U >
+inline bool operator<=( U const & v, optional<T> const & x )
+{
+ return bool(x) ? v <= *x : false;
+}
+
+template< typename T, typename U >
+inline bool operator>( optional<T> const & x, U const & v )
+{
+ return bool(x) ? *x > v : false;
+}
+
+template< typename T, typename U >
+inline bool operator>( U const & v, optional<T> const & x )
+{
+ return bool(x) ? v > *x : true;
+}
+
+template< typename T, typename U >
+inline bool operator>=( optional<T> const & x, U const & v )
+{
+ return bool(x) ? *x >= v : false;
+}
+
+template< typename T, typename U >
+inline bool operator>=( U const & v, optional<T> const & x )
+{
+ return bool(x) ? v >= *x : true;
+}
+
+// Specialized algorithms
+
+template< typename T >
+void swap( optional<T> & x, optional<T> & y )
+{
+ x.swap( y );
+}
+
+// Convenience function to create an optional.
+
+template< typename T >
+inline optional<T> make_optional( T const & v )
+{
+ return optional<T>( v );
+}
+
+} // namespace optional-bare
+
+using namespace optional_bare;
+
+} // namespace nonstd
+
+#endif // optional_USES_STD_OPTIONAL
+
+#endif // NONSTD_OPTIONAL_BARE_HPP
diff --git a/libraries/rainbow/include/rainbow_config.h b/libraries/rainbow/include/rainbow_config.h
index 6e467bc7..52cc7388 100644
--- a/libraries/rainbow/include/rainbow_config.h
+++ b/libraries/rainbow/include/rainbow_config.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2019 MultiMC Contributors
+/* Copyright 2013-2021 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 7980dfdf..914d2555 100644
--- a/libraries/systeminfo/include/sys.h
+++ b/libraries/systeminfo/include/sys.h
@@ -3,7 +3,7 @@
namespace Sys
{
-const uint64_t megabyte = 1024ull * 1024ull;
+const uint64_t mebibyte = 1024ull * 1024ull;
struct KernelInfo
{
QString kernelName;
diff --git a/libraries/systeminfo/src/sys_unix.cpp b/libraries/systeminfo/src/sys_unix.cpp
index ab3f302e..42c0d319 100644
--- a/libraries/systeminfo/src/sys_unix.cpp
+++ b/libraries/systeminfo/src/sys_unix.cpp
@@ -4,6 +4,7 @@
#include <sys/utsname.h>
#include <fstream>
+#include <limits>
Sys::KernelInfo Sys::getKernelInfo()
{
diff --git a/libraries/tomlc99/CMakeLists.txt b/libraries/tomlc99/CMakeLists.txt
new file mode 100644
index 00000000..60786923
--- /dev/null
+++ b/libraries/tomlc99/CMakeLists.txt
@@ -0,0 +1,10 @@
+project(tomlc99)
+
+set(tomlc99_SOURCES
+include/toml.h
+src/toml.c
+)
+
+add_library(tomlc99 STATIC ${tomlc99_SOURCES})
+
+target_include_directories(tomlc99 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
diff --git a/libraries/tomlc99/LICENSE b/libraries/tomlc99/LICENSE
new file mode 100644
index 00000000..a3292b16
--- /dev/null
+++ b/libraries/tomlc99/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2017 CK Tan
+https://github.com/cktan/tomlc99
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md
new file mode 100644
index 00000000..6715b5be
--- /dev/null
+++ b/libraries/tomlc99/README.md
@@ -0,0 +1,194 @@
+# tomlc99
+
+TOML in c99; v1.0 compliant.
+
+If you are looking for a C++ library, you might try this wrapper: [https://github.com/cktan/tomlcpp](https://github.com/cktan/tomlcpp).
+
+* Compatible with [TOML v1.0.0](https://toml.io/en/v1.0.0).
+* Tested with multiple test suites, including
+[BurntSushi/toml-test](https://github.com/BurntSushi/toml-test) and
+[iarna/toml-spec-tests](https://github.com/iarna/toml-spec-tests).
+* Provides very simple and intuitive interface.
+
+
+## Usage
+
+Please see the `toml.h` file for details. What follows is a simple example that
+parses this config file:
+
+```toml
+[server]
+ host = "www.example.com"
+ port = [ 8080, 8181, 8282 ]
+```
+
+The steps for getting values from our file is usually :
+
+1. Parse the TOML file.
+2. Traverse and locate a table in TOML.
+3. Extract values from the table.
+4. Free up allocated memory.
+
+Below is an example of parsing the values from the example table.
+
+```c
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include "toml.h"
+
+static void error(const char* msg, const char* msg1)
+{
+ fprintf(stderr, "ERROR: %s%s\n", msg, msg1?msg1:"");
+ exit(1);
+}
+
+
+int main()
+{
+ FILE* fp;
+ char errbuf[200];
+
+ // 1. Read and parse toml file
+ fp = fopen("sample.toml", "r");
+ if (!fp) {
+ error("cannot open sample.toml - ", strerror(errno));
+ }
+
+ toml_table_t* conf = toml_parse_file(fp, errbuf, sizeof(errbuf));
+ fclose(fp);
+
+ if (!conf) {
+ error("cannot parse - ", errbuf);
+ }
+
+ // 2. Traverse to a table.
+ toml_table_t* server = toml_table_in(conf, "server");
+ if (!server) {
+ error("missing [server]", "");
+ }
+
+ // 3. Extract values
+ toml_datum_t host = toml_string_in(server, "host");
+ if (!host.ok) {
+ error("cannot read server.host", "");
+ }
+
+ toml_array_t* portarray = toml_array_in(server, "port");
+ if (!portarray) {
+ error("cannot read server.port", "");
+ }
+
+ printf("host: %s\n", host.u.s);
+ printf("port: ");
+ for (int i = 0; ; i++) {
+ toml_datum_t port = toml_int_at(portarray, i);
+ if (!port.ok) break;
+ printf("%d ", (int)port.u.i);
+ }
+ printf("\n");
+
+ // 4. Free memory
+ free(host.u.s);
+ toml_free(conf);
+ return 0;
+}
+```
+
+#### Accessing Table Content
+
+TOML tables are dictionaries where lookups are done using string keys. In
+general, all access functions on tables are named `toml_*_in(...)`.
+
+In the normal case, you know the key and its content type, and retrievals can be done
+using one of these functions:
+```c
+toml_string_in(tab, key);
+toml_bool_in(tab, key);
+toml_int_in(tab, key);
+toml_double_in(tab, key);
+toml_timestamp_in(tab, key);
+toml_table_in(tab, key);
+toml_array_in(tab, key);
+```
+
+You can also interrogate the keys in a table using an integer index:
+```c
+toml_table_t* tab = toml_parse_file(...);
+for (int i = 0; ; i++) {
+ const char* key = toml_key_in(tab, i);
+ if (!key) break;
+ printf("key %d: %s\n", i, key);
+}
+```
+
+#### Accessing Array Content
+
+TOML arrays can be deref-ed using integer indices. In general, all access methods on arrays are named `toml_*_at()`.
+
+To obtain the size of an array:
+```c
+int size = toml_array_nelem(arr);
+```
+
+To obtain the content of an array, use a valid index and call one of these functions:
+```c
+toml_string_at(arr, idx);
+toml_bool_at(arr, idx);
+toml_int_at(arr, idx);
+toml_double_at(arr, idx);
+toml_timestamp_at(arr, idx);
+toml_table_at(arr, idx);
+toml_array_at(arr, idx);
+```
+
+#### toml_datum_t
+
+Some `toml_*_at` and `toml_*_in` functions return a toml_datum_t
+structure. The `ok` flag in the structure indicates if the function
+call was successful. If so, you may proceed to read the value
+corresponding to the type of the content.
+
+For example:
+```
+toml_datum_t host = toml_string_in(tab, "host");
+if (host.ok) {
+ printf("host: %s\n", host.u.s);
+ free(host.u.s); /* FREE applies to string and timestamp types only */
+}
+```
+
+** IMPORTANT: if the accessed value is a string or a timestamp, you must call `free(datum.u.s)` or `free(datum.u.ts)` respectively after usage. **
+
+## Building and installing
+
+A normal *make* suffices. You can also simply include the
+`toml.c` and `toml.h` files in your project.
+
+Invoking `make install` will install the header and library files into
+/usr/local/{include,lib}.
+
+Alternatively, specify `make install prefix=/a/file/path` to install into
+/a/file/path/{include,lib}.
+
+## Testing
+
+To test against the standard test set provided by BurntSushi/toml-test:
+
+```sh
+% make
+% cd test1
+% bash build.sh # do this once
+% bash run.sh # this will run the test suite
+```
+
+
+To test against the standard test set provided by iarna/toml:
+
+```sh
+% make
+% cd test2
+% bash build.sh # do this once
+% bash run.sh # this will run the test suite
+```
diff --git a/libraries/tomlc99/include/toml.h b/libraries/tomlc99/include/toml.h
new file mode 100644
index 00000000..b91ef890
--- /dev/null
+++ b/libraries/tomlc99/include/toml.h
@@ -0,0 +1,175 @@
+/*
+ MIT License
+
+ Copyright (c) 2017 - 2019 CK Tan
+ https://github.com/cktan/tomlc99
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+#ifndef TOML_H
+#define TOML_H
+
+
+#include <stdio.h>
+#include <stdint.h>
+
+
+#ifdef __cplusplus
+#define TOML_EXTERN extern "C"
+#else
+#define TOML_EXTERN extern
+#endif
+
+typedef struct toml_timestamp_t toml_timestamp_t;
+typedef struct toml_table_t toml_table_t;
+typedef struct toml_array_t toml_array_t;
+typedef struct toml_datum_t toml_datum_t;
+
+/* Parse a file. Return a table on success, or 0 otherwise.
+ * Caller must toml_free(the-return-value) after use.
+ */
+TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp,
+ char* errbuf,
+ int errbufsz);
+
+/* Parse a string containing the full config.
+ * Return a table on success, or 0 otherwise.
+ * Caller must toml_free(the-return-value) after use.
+ */
+TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */
+ char* errbuf,
+ int errbufsz);
+
+/* Free the table returned by toml_parse() or toml_parse_file(). Once
+ * this function is called, any handles accessed through this tab
+ * directly or indirectly are no longer valid.
+ */
+TOML_EXTERN void toml_free(toml_table_t* tab);
+
+
+/* Timestamp types. The year, month, day, hour, minute, second, z
+ * fields may be NULL if they are not relevant. e.g. In a DATE
+ * type, the hour, minute, second and z fields will be NULLs.
+ */
+struct toml_timestamp_t {
+ struct { /* internal. do not use. */
+ int year, month, day;
+ int hour, minute, second, millisec;
+ char z[10];
+ } __buffer;
+ int *year, *month, *day;
+ int *hour, *minute, *second, *millisec;
+ char* z;
+};
+
+
+/*-----------------------------------------------------------------
+ * Enhanced access methods
+ */
+struct toml_datum_t {
+ int ok;
+ union {
+ toml_timestamp_t* ts; /* ts must be freed after use */
+ char* s; /* string value. s must be freed after use */
+ int b; /* bool value */
+ int64_t i; /* int value */
+ double d; /* double value */
+ } u;
+};
+
+/* on arrays: */
+/* ... retrieve size of array. */
+TOML_EXTERN int toml_array_nelem(const toml_array_t* arr);
+/* ... retrieve values using index. */
+TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx);
+TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx);
+TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx);
+TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx);
+TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx);
+/* ... retrieve array or table using index. */
+TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx);
+TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx);
+
+/* on tables: */
+/* ... retrieve the key in table at keyidx. Return 0 if out of range. */
+TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx);
+/* ... retrieve values using key. */
+TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key);
+TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key);
+TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key);
+TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key);
+TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key);
+/* .. retrieve array or table using key. */
+TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab,
+ const char* key);
+TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab,
+ const char* key);
+
+/*-----------------------------------------------------------------
+ * lesser used
+ */
+/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */
+TOML_EXTERN char toml_array_kind(const toml_array_t* arr);
+
+/* For array kind 'v'alue, return the type of values
+ i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed
+ 0 if unknown
+*/
+TOML_EXTERN char toml_array_type(const toml_array_t* arr);
+
+/* Return the key of an array */
+TOML_EXTERN const char* toml_array_key(const toml_array_t* arr);
+
+/* Return the number of key-values in a table */
+TOML_EXTERN int toml_table_nkval(const toml_table_t* tab);
+
+/* Return the number of arrays in a table */
+TOML_EXTERN int toml_table_narr(const toml_table_t* tab);
+
+/* Return the number of sub-tables in a table */
+TOML_EXTERN int toml_table_ntab(const toml_table_t* tab);
+
+/* Return the key of a table*/
+TOML_EXTERN const char* toml_table_key(const toml_table_t* tab);
+
+/*--------------------------------------------------------------
+ * misc
+ */
+TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret);
+TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]);
+TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t),
+ void (*xxfree)(void*));
+
+
+/*--------------------------------------------------------------
+ * deprecated
+ */
+/* A raw value, must be processed by toml_rto* before using. */
+typedef const char* toml_raw_t;
+TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key);
+TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx);
+TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret);
+TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret);
+TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret);
+TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret);
+TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen);
+TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret);
+
+
+#endif /* TOML_H */
diff --git a/libraries/tomlc99/src/toml.c b/libraries/tomlc99/src/toml.c
new file mode 100644
index 00000000..e46e62e6
--- /dev/null
+++ b/libraries/tomlc99/src/toml.c
@@ -0,0 +1,2300 @@
+/*
+
+ MIT License
+
+ Copyright (c) 2017 - 2021 CK Tan
+ https://github.com/cktan/tomlc99
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+*/
+#define _POSIX_C_SOURCE 200809L
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdbool.h>
+#include "toml.h"
+
+
+static void* (*ppmalloc)(size_t) = malloc;
+static void (*ppfree)(void*) = free;
+
+void toml_set_memutil(void* (*xxmalloc)(size_t),
+ void (*xxfree)(void*))
+{
+ if (xxmalloc) ppmalloc = xxmalloc;
+ if (xxfree) ppfree = xxfree;
+}
+
+
+#define MALLOC(a) ppmalloc(a)
+#define FREE(a) ppfree(a)
+
+static void* CALLOC(size_t nmemb, size_t sz)
+{
+ int nb = sz * nmemb;
+ void* p = MALLOC(nb);
+ if (p) {
+ memset(p, 0, nb);
+ }
+ return p;
+}
+
+
+static char* STRDUP(const char* s)
+{
+ int len = strlen(s);
+ char* p = MALLOC(len+1);
+ if (p) {
+ memcpy(p, s, len);
+ p[len] = 0;
+ }
+ return p;
+}
+
+static char* STRNDUP(const char* s, size_t n)
+{
+ size_t len = strnlen(s, n);
+ char* p = MALLOC(len+1);
+ if (p) {
+ memcpy(p, s, len);
+ p[len] = 0;
+ }
+ return p;
+}
+
+
+
+/**
+ * Convert a char in utf8 into UCS, and store it in *ret.
+ * Return #bytes consumed or -1 on failure.
+ */
+int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret)
+{
+ const unsigned char* buf = (const unsigned char*) orig;
+ unsigned i = *buf++;
+ int64_t v;
+
+ /* 0x00000000 - 0x0000007F:
+ 0xxxxxxx
+ */
+ if (0 == (i >> 7)) {
+ if (len < 1) return -1;
+ v = i;
+ return *ret = v, 1;
+ }
+ /* 0x00000080 - 0x000007FF:
+ 110xxxxx 10xxxxxx
+ */
+ if (0x6 == (i >> 5)) {
+ if (len < 2) return -1;
+ v = i & 0x1f;
+ for (int j = 0; j < 1; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6)) return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char*) buf - orig;
+ }
+
+ /* 0x00000800 - 0x0000FFFF:
+ 1110xxxx 10xxxxxx 10xxxxxx
+ */
+ if (0xE == (i >> 4)) {
+ if (len < 3) return -1;
+ v = i & 0x0F;
+ for (int j = 0; j < 2; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6)) return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char*) buf - orig;
+ }
+
+ /* 0x00010000 - 0x001FFFFF:
+ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (0x1E == (i >> 3)) {
+ if (len < 4) return -1;
+ v = i & 0x07;
+ for (int j = 0; j < 3; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6)) return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char*) buf - orig;
+ }
+
+ /* 0x00200000 - 0x03FFFFFF:
+ 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (0x3E == (i >> 2)) {
+ if (len < 5) return -1;
+ v = i & 0x03;
+ for (int j = 0; j < 4; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6)) return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char*) buf - orig;
+ }
+
+ /* 0x04000000 - 0x7FFFFFFF:
+ 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (0x7e == (i >> 1)) {
+ if (len < 6) return -1;
+ v = i & 0x01;
+ for (int j = 0; j < 5; j++) {
+ i = *buf++;
+ if (0x2 != (i >> 6)) return -1;
+ v = (v << 6) | (i & 0x3f);
+ }
+ return *ret = v, (const char*) buf - orig;
+ }
+ return -1;
+}
+
+
+/**
+ * Convert a UCS char to utf8 code, and return it in buf.
+ * Return #bytes used in buf to encode the char, or
+ * -1 on error.
+ */
+int toml_ucs_to_utf8(int64_t code, char buf[6])
+{
+ /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 */
+ /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well
+ * as 0xfffe and 0xffff (UCS noncharacters) should not appear in
+ * conforming UTF-8 streams.
+ */
+ if (0xd800 <= code && code <= 0xdfff) return -1;
+ if (0xfffe <= code && code <= 0xffff) return -1;
+
+ /* 0x00000000 - 0x0000007F:
+ 0xxxxxxx
+ */
+ if (code < 0) return -1;
+ if (code <= 0x7F) {
+ buf[0] = (unsigned char) code;
+ return 1;
+ }
+
+ /* 0x00000080 - 0x000007FF:
+ 110xxxxx 10xxxxxx
+ */
+ if (code <= 0x000007FF) {
+ buf[0] = 0xc0 | (code >> 6);
+ buf[1] = 0x80 | (code & 0x3f);
+ return 2;
+ }
+
+ /* 0x00000800 - 0x0000FFFF:
+ 1110xxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x0000FFFF) {
+ buf[0] = 0xe0 | (code >> 12);
+ buf[1] = 0x80 | ((code >> 6) & 0x3f);
+ buf[2] = 0x80 | (code & 0x3f);
+ return 3;
+ }
+
+ /* 0x00010000 - 0x001FFFFF:
+ 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x001FFFFF) {
+ buf[0] = 0xf0 | (code >> 18);
+ buf[1] = 0x80 | ((code >> 12) & 0x3f);
+ buf[2] = 0x80 | ((code >> 6) & 0x3f);
+ buf[3] = 0x80 | (code & 0x3f);
+ return 4;
+ }
+
+ /* 0x00200000 - 0x03FFFFFF:
+ 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x03FFFFFF) {
+ buf[0] = 0xf8 | (code >> 24);
+ buf[1] = 0x80 | ((code >> 18) & 0x3f);
+ buf[2] = 0x80 | ((code >> 12) & 0x3f);
+ buf[3] = 0x80 | ((code >> 6) & 0x3f);
+ buf[4] = 0x80 | (code & 0x3f);
+ return 5;
+ }
+
+ /* 0x04000000 - 0x7FFFFFFF:
+ 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ if (code <= 0x7FFFFFFF) {
+ buf[0] = 0xfc | (code >> 30);
+ buf[1] = 0x80 | ((code >> 24) & 0x3f);
+ buf[2] = 0x80 | ((code >> 18) & 0x3f);
+ buf[3] = 0x80 | ((code >> 12) & 0x3f);
+ buf[4] = 0x80 | ((code >> 6) & 0x3f);
+ buf[5] = 0x80 | (code & 0x3f);
+ return 6;
+ }
+
+ return -1;
+}
+
+/*
+ * TOML has 3 data structures: value, array, table.
+ * Each of them can have identification key.
+ */
+typedef struct toml_keyval_t toml_keyval_t;
+struct toml_keyval_t {
+ const char* key; /* key to this value */
+ const char* val; /* the raw value */
+};
+
+typedef struct toml_arritem_t toml_arritem_t;
+struct toml_arritem_t {
+ int valtype; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp */
+ char* val;
+ toml_array_t* arr;
+ toml_table_t* tab;
+};
+
+
+struct toml_array_t {
+ const char* key; /* key to this array */
+ int kind; /* element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed */
+ int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp, 'm'ixed */
+
+ int nitem; /* number of elements */
+ toml_arritem_t* item;
+};
+
+
+struct toml_table_t {
+ const char* key; /* key to this table */
+ bool implicit; /* table was created implicitly */
+ bool readonly; /* no more modification allowed */
+
+ /* key-values in the table */
+ int nkval;
+ toml_keyval_t** kval;
+
+ /* arrays in the table */
+ int narr;
+ toml_array_t** arr;
+
+ /* tables in the table */
+ int ntab;
+ toml_table_t** tab;
+};
+
+
+static inline void xfree(const void* x) { if (x) FREE((void*)(intptr_t)x); }
+
+
+enum tokentype_t {
+ INVALID,
+ DOT,
+ COMMA,
+ EQUAL,
+ LBRACE,
+ RBRACE,
+ NEWLINE,
+ LBRACKET,
+ RBRACKET,
+ STRING,
+};
+typedef enum tokentype_t tokentype_t;
+
+typedef struct token_t token_t;
+struct token_t {
+ tokentype_t tok;
+ int lineno;
+ char* ptr; /* points into context->start */
+ int len;
+ int eof;
+};
+
+
+typedef struct context_t context_t;
+struct context_t {
+ char* start;
+ char* stop;
+ char* errbuf;
+ int errbufsz;
+
+ token_t tok;
+ toml_table_t* root;
+ toml_table_t* curtab;
+
+ struct {
+ int top;
+ char* key[10];
+ token_t tok[10];
+ } tpath;
+
+};
+
+#define STRINGIFY(x) #x
+#define TOSTRING(x) STRINGIFY(x)
+#define FLINE __FILE__ ":" TOSTRING(__LINE__)
+
+static int next_token(context_t* ctx, int dotisspecial);
+
+/*
+ Error reporting. Call when an error is detected. Always return -1.
+*/
+static int e_outofmemory(context_t* ctx, const char* fline)
+{
+ snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline);
+ return -1;
+}
+
+
+static int e_internal(context_t* ctx, const char* fline)
+{
+ snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline);
+ return -1;
+}
+
+static int e_syntax(context_t* ctx, int lineno, const char* msg)
+{
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+ return -1;
+}
+
+static int e_badkey(context_t* ctx, int lineno)
+{
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno);
+ return -1;
+}
+
+static int e_keyexists(context_t* ctx, int lineno)
+{
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno);
+ return -1;
+}
+
+static int e_forbid(context_t* ctx, int lineno, const char* msg)
+{
+ snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg);
+ return -1;
+}
+
+static void* expand(void* p, int sz, int newsz)
+{
+ void* s = MALLOC(newsz);
+ if (!s) return 0;
+
+ memcpy(s, p, sz);
+ FREE(p);
+ return s;
+}
+
+static void** expand_ptrarr(void** p, int n)
+{
+ void** s = MALLOC((n+1) * sizeof(void*));
+ if (!s) return 0;
+
+ s[n] = 0;
+ memcpy(s, p, n * sizeof(void*));
+ FREE(p);
+ return s;
+}
+
+static toml_arritem_t* expand_arritem(toml_arritem_t* p, int n)
+{
+ toml_arritem_t* pp = expand(p, n*sizeof(*p), (n+1)*sizeof(*p));
+ if (!pp) return 0;
+
+ memset(&pp[n], 0, sizeof(pp[n]));
+ return pp;
+}
+
+
+static char* norm_lit_str(const char* src, int srclen,
+ int multiline,
+ char* errbuf, int errbufsz)
+{
+ char* dst = 0; /* will write to dst[] and return it */
+ int max = 0; /* max size of dst[] */
+ int off = 0; /* cur offset in dst[] */
+ const char* sp = src;
+ const char* sq = src + srclen;
+ int ch;
+
+ /* scan forward on src */
+ for (;;) {
+ if (off >= max - 10) { /* have some slack for misc stuff */
+ int newmax = max + 50;
+ char* x = expand(dst, max, newmax);
+ if (!x) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "out of memory");
+ return 0;
+ }
+ dst = x;
+ max = newmax;
+ }
+
+ /* finished? */
+ if (sp >= sq) break;
+
+ ch = *sp++;
+ /* control characters other than tab is not allowed */
+ if ((0 <= ch && ch <= 0x08)
+ || (0x0a <= ch && ch <= 0x1f)
+ || (ch == 0x7f)) {
+ if (! (multiline && (ch == '\r' || ch == '\n'))) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+ return 0;
+ }
+ }
+
+ // a plain copy suffice
+ dst[off++] = ch;
+ }
+
+ dst[off++] = 0;
+ return dst;
+}
+
+
+
+
+/*
+ * Convert src to raw unescaped utf-8 string.
+ * Returns NULL if error with errmsg in errbuf.
+ */
+static char* norm_basic_str(const char* src, int srclen,
+ int multiline,
+ char* errbuf, int errbufsz)
+{
+ char* dst = 0; /* will write to dst[] and return it */
+ int max = 0; /* max size of dst[] */
+ int off = 0; /* cur offset in dst[] */
+ const char* sp = src;
+ const char* sq = src + srclen;
+ int ch;
+
+ /* scan forward on src */
+ for (;;) {
+ if (off >= max - 10) { /* have some slack for misc stuff */
+ int newmax = max + 50;
+ char* x = expand(dst, max, newmax);
+ if (!x) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "out of memory");
+ return 0;
+ }
+ dst = x;
+ max = newmax;
+ }
+
+ /* finished? */
+ if (sp >= sq) break;
+
+ ch = *sp++;
+ if (ch != '\\') {
+ /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F */
+ if ((0 <= ch && ch <= 0x08)
+ || (0x0a <= ch && ch <= 0x1f)
+ || (ch == 0x7f)) {
+ if (! (multiline && (ch == '\r' || ch == '\n'))) {
+ xfree(dst);
+ snprintf(errbuf, errbufsz, "invalid char U+%04x", ch);
+ return 0;
+ }
+ }
+
+ // a plain copy suffice
+ dst[off++] = ch;
+ continue;
+ }
+
+ /* ch was backslash. we expect the escape char. */
+ if (sp >= sq) {
+ snprintf(errbuf, errbufsz, "last backslash is invalid");
+ xfree(dst);
+ return 0;
+ }
+
+ /* for multi-line, we want to kill line-ending-backslash ... */
+ if (multiline) {
+
+ // if there is only whitespace after the backslash ...
+ if (sp[strspn(sp, " \t\r")] == '\n') {
+ /* skip all the following whitespaces */
+ sp += strspn(sp, " \t\r\n");
+ continue;
+ }
+ }
+
+ /* get the escaped char */
+ ch = *sp++;
+ switch (ch) {
+ case 'u': case 'U':
+ {
+ int64_t ucs = 0;
+ int nhex = (ch == 'u' ? 4 : 8);
+ for (int i = 0; i < nhex; i++) {
+ if (sp >= sq) {
+ snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex);
+ xfree(dst);
+ return 0;
+ }
+ ch = *sp++;
+ int v = ('0' <= ch && ch <= '9')
+ ? ch - '0'
+ : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1);
+ if (-1 == v) {
+ snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U");
+ xfree(dst);
+ return 0;
+ }
+ ucs = ucs * 16 + v;
+ }
+ int n = toml_ucs_to_utf8(ucs, &dst[off]);
+ if (-1 == n) {
+ snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U");
+ xfree(dst);
+ return 0;
+ }
+ off += n;
+ }
+ continue;
+
+ case 'b': ch = '\b'; break;
+ case 't': ch = '\t'; break;
+ case 'n': ch = '\n'; break;
+ case 'f': ch = '\f'; break;
+ case 'r': ch = '\r'; break;
+ case '"': ch = '"'; break;
+ case '\\': ch = '\\'; break;
+ default:
+ snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch);
+ xfree(dst);
+ return 0;
+ }
+
+ dst[off++] = ch;
+ }
+
+ // Cap with NUL and return it.
+ dst[off++] = 0;
+ return dst;
+}
+
+
+/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */
+static char* normalize_key(context_t* ctx, token_t strtok)
+{
+ const char* sp = strtok.ptr;
+ const char* sq = strtok.ptr + strtok.len;
+ int lineno = strtok.lineno;
+ char* ret;
+ int ch = *sp;
+ char ebuf[80];
+
+ /* handle quoted string */
+ if (ch == '\'' || ch == '\"') {
+ /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */
+ int multiline = 0;
+ if (sp[1] == ch && sp[2] == ch) {
+ sp += 3, sq -= 3;
+ multiline = 1;
+ }
+ else
+ sp++, sq--;
+
+ if (ch == '\'') {
+ /* for single quote, take it verbatim. */
+ if (! (ret = STRNDUP(sp, sq - sp))) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ } else {
+ /* for double quote, we need to normalize */
+ ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf));
+ if (!ret) {
+ e_syntax(ctx, lineno, ebuf);
+ return 0;
+ }
+ }
+
+ /* newlines are not allowed in keys */
+ if (strchr(ret, '\n')) {
+ xfree(ret);
+ e_badkey(ctx, lineno);
+ return 0;
+ }
+ return ret;
+ }
+
+ /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */
+ const char* xp;
+ for (xp = sp; xp != sq; xp++) {
+ int k = *xp;
+ if (isalnum(k)) continue;
+ if (k == '_' || k == '-') continue;
+ e_badkey(ctx, lineno);
+ return 0;
+ }
+
+ /* dup and return it */
+ if (! (ret = STRNDUP(sp, sq - sp))) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ return ret;
+}
+
+
+/*
+ * Look up key in tab. Return 0 if not found, or
+ * 'v'alue, 'a'rray or 't'able depending on the element.
+ */
+static int check_key(toml_table_t* tab, const char* key,
+ toml_keyval_t** ret_val,
+ toml_array_t** ret_arr,
+ toml_table_t** ret_tab)
+{
+ int i;
+ void* dummy;
+
+ if (!ret_tab) ret_tab = (toml_table_t**) &dummy;
+ if (!ret_arr) ret_arr = (toml_array_t**) &dummy;
+ if (!ret_val) ret_val = (toml_keyval_t**) &dummy;
+
+ *ret_tab = 0; *ret_arr = 0; *ret_val = 0;
+
+ for (i = 0; i < tab->nkval; i++) {
+ if (0 == strcmp(key, tab->kval[i]->key)) {
+ *ret_val = tab->kval[i];
+ return 'v';
+ }
+ }
+ for (i = 0; i < tab->narr; i++) {
+ if (0 == strcmp(key, tab->arr[i]->key)) {
+ *ret_arr = tab->arr[i];
+ return 'a';
+ }
+ }
+ for (i = 0; i < tab->ntab; i++) {
+ if (0 == strcmp(key, tab->tab[i]->key)) {
+ *ret_tab = tab->tab[i];
+ return 't';
+ }
+ }
+ return 0;
+}
+
+
+static int key_kind(toml_table_t* tab, const char* key)
+{
+ return check_key(tab, key, 0, 0, 0);
+}
+
+/* Create a keyval in the table.
+ */
+static toml_keyval_t* create_keyval_in_table(context_t* ctx, toml_table_t* tab, token_t keytok)
+{
+ /* first, normalize the key to be used for lookup.
+ * remember to free it if we error out.
+ */
+ char* newkey = normalize_key(ctx, keytok);
+ if (!newkey) return 0;
+
+ /* if key exists: error out. */
+ toml_keyval_t* dest = 0;
+ if (key_kind(tab, newkey)) {
+ xfree(newkey);
+ e_keyexists(ctx, keytok.lineno);
+ return 0;
+ }
+
+ /* make a new entry */
+ int n = tab->nkval;
+ toml_keyval_t** base;
+ if (0 == (base = (toml_keyval_t**) expand_ptrarr((void**)tab->kval, n))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ tab->kval = base;
+
+ if (0 == (base[n] = (toml_keyval_t*) CALLOC(1, sizeof(*base[n])))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ dest = tab->kval[tab->nkval++];
+
+ /* save the key in the new value struct */
+ dest->key = newkey;
+ return dest;
+}
+
+
+/* Create a table in the table.
+ */
+static toml_table_t* create_keytable_in_table(context_t* ctx, toml_table_t* tab, token_t keytok)
+{
+ /* first, normalize the key to be used for lookup.
+ * remember to free it if we error out.
+ */
+ char* newkey = normalize_key(ctx, keytok);
+ if (!newkey) return 0;
+
+ /* if key exists: error out */
+ toml_table_t* dest = 0;
+ if (check_key(tab, newkey, 0, 0, &dest)) {
+ xfree(newkey); /* don't need this anymore */
+
+ /* special case: if table exists, but was created implicitly ... */
+ if (dest && dest->implicit) {
+ /* we make it explicit now, and simply return it. */
+ dest->implicit = false;
+ return dest;
+ }
+ e_keyexists(ctx, keytok.lineno);
+ return 0;
+ }
+
+ /* create a new table entry */
+ int n = tab->ntab;
+ toml_table_t** base;
+ if (0 == (base = (toml_table_t**) expand_ptrarr((void**)tab->tab, n))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ tab->tab = base;
+
+ if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ dest = tab->tab[tab->ntab++];
+
+ /* save the key in the new table struct */
+ dest->key = newkey;
+ return dest;
+}
+
+
+/* Create an array in the table.
+ */
+static toml_array_t* create_keyarray_in_table(context_t* ctx,
+ toml_table_t* tab,
+ token_t keytok,
+ char kind)
+{
+ /* first, normalize the key to be used for lookup.
+ * remember to free it if we error out.
+ */
+ char* newkey = normalize_key(ctx, keytok);
+ if (!newkey) return 0;
+
+ /* if key exists: error out */
+ if (key_kind(tab, newkey)) {
+ xfree(newkey); /* don't need this anymore */
+ e_keyexists(ctx, keytok.lineno);
+ return 0;
+ }
+
+ /* make a new array entry */
+ int n = tab->narr;
+ toml_array_t** base;
+ if (0 == (base = (toml_array_t**) expand_ptrarr((void**)tab->arr, n))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ tab->arr = base;
+
+ if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) {
+ xfree(newkey);
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ toml_array_t* dest = tab->arr[tab->narr++];
+
+ /* save the key in the new array struct */
+ dest->key = newkey;
+ dest->kind = kind;
+ return dest;
+}
+
+
+static toml_arritem_t* create_value_in_array(context_t* ctx,
+ toml_array_t* parent)
+{
+ const int n = parent->nitem;
+ toml_arritem_t* base = expand_arritem(parent->item, n);
+ if (!base) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ parent->item = base;
+ parent->nitem++;
+ return &parent->item[n];
+}
+
+/* Create an array in an array
+ */
+static toml_array_t* create_array_in_array(context_t* ctx,
+ toml_array_t* parent)
+{
+ const int n = parent->nitem;
+ toml_arritem_t* base = expand_arritem(parent->item, n);
+ if (!base) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ toml_array_t* ret = (toml_array_t*) CALLOC(1, sizeof(toml_array_t));
+ if (!ret) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ base[n].arr = ret;
+ parent->item = base;
+ parent->nitem++;
+ return ret;
+}
+
+/* Create a table in an array
+ */
+static toml_table_t* create_table_in_array(context_t* ctx,
+ toml_array_t* parent)
+{
+ int n = parent->nitem;
+ toml_arritem_t* base = expand_arritem(parent->item, n);
+ if (!base) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ toml_table_t* ret = (toml_table_t*) CALLOC(1, sizeof(toml_table_t));
+ if (!ret) {
+ e_outofmemory(ctx, FLINE);
+ return 0;
+ }
+ base[n].tab = ret;
+ parent->item = base;
+ parent->nitem++;
+ return ret;
+}
+
+
+static int skip_newlines(context_t* ctx, int isdotspecial)
+{
+ while (ctx->tok.tok == NEWLINE) {
+ if (next_token(ctx, isdotspecial)) return -1;
+ if (ctx->tok.eof) break;
+ }
+ return 0;
+}
+
+
+static int parse_keyval(context_t* ctx, toml_table_t* tab);
+
+static inline int eat_token(context_t* ctx, tokentype_t typ, int isdotspecial, const char* fline)
+{
+ if (ctx->tok.tok != typ)
+ return e_internal(ctx, fline);
+
+ if (next_token(ctx, isdotspecial))
+ return -1;
+
+ return 0;
+}
+
+
+
+/* We are at '{ ... }'.
+ * Parse the table.
+ */
+static int parse_inline_table(context_t* ctx, toml_table_t* tab)
+{
+ if (eat_token(ctx, LBRACE, 1, FLINE))
+ return -1;
+
+ for (;;) {
+ if (ctx->tok.tok == NEWLINE)
+ return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table");
+
+ /* until } */
+ if (ctx->tok.tok == RBRACE)
+ break;
+
+ if (ctx->tok.tok != STRING)
+ return e_syntax(ctx, ctx->tok.lineno, "expect a string");
+
+ if (parse_keyval(ctx, tab))
+ return -1;
+
+ if (ctx->tok.tok == NEWLINE)
+ return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table");
+
+ /* on comma, continue to scan for next keyval */
+ if (ctx->tok.tok == COMMA) {
+ if (eat_token(ctx, COMMA, 1, FLINE))
+ return -1;
+ continue;
+ }
+ break;
+ }
+
+ if (eat_token(ctx, RBRACE, 1, FLINE))
+ return -1;
+
+ tab->readonly = 1;
+
+ return 0;
+}
+
+static int valtype(const char* val)
+{
+ toml_timestamp_t ts;
+ if (*val == '\'' || *val == '"') return 's';
+ if (0 == toml_rtob(val, 0)) return 'b';
+ if (0 == toml_rtoi(val, 0)) return 'i';
+ if (0 == toml_rtod(val, 0)) return 'd';
+ if (0 == toml_rtots(val, &ts)) {
+ if (ts.year && ts.hour) return 'T'; /* timestamp */
+ if (ts.year) return 'D'; /* date */
+ return 't'; /* time */
+ }
+ return 'u'; /* unknown */
+}
+
+
+/* We are at '[...]' */
+static int parse_array(context_t* ctx, toml_array_t* arr)
+{
+ if (eat_token(ctx, LBRACKET, 0, FLINE)) return -1;
+
+ for (;;) {
+ if (skip_newlines(ctx, 0)) return -1;
+
+ /* until ] */
+ if (ctx->tok.tok == RBRACKET) break;
+
+ switch (ctx->tok.tok) {
+ case STRING:
+ {
+ /* set array kind if this will be the first entry */
+ if (arr->kind == 0)
+ arr->kind = 'v';
+ else if (arr->kind != 'v')
+ arr->kind = 'm';
+
+ char* val = ctx->tok.ptr;
+ int vlen = ctx->tok.len;
+
+ /* make a new value in array */
+ toml_arritem_t* newval = create_value_in_array(ctx, arr);
+ if (!newval)
+ return e_outofmemory(ctx, FLINE);
+
+ if (! (newval->val = STRNDUP(val, vlen)))
+ return e_outofmemory(ctx, FLINE);
+
+ newval->valtype = valtype(newval->val);
+
+ /* set array type if this is the first entry */
+ if (arr->nitem == 1)
+ arr->type = newval->valtype;
+ else if (arr->type != newval->valtype)
+ arr->type = 'm'; /* mixed */
+
+ if (eat_token(ctx, STRING, 0, FLINE)) return -1;
+ break;
+ }
+
+ case LBRACKET:
+ { /* [ [array], [array] ... ] */
+ /* set the array kind if this will be the first entry */
+ if (arr->kind == 0)
+ arr->kind = 'a';
+ else if (arr->kind != 'a')
+ arr->kind = 'm';
+
+ toml_array_t* subarr = create_array_in_array(ctx, arr);
+ if (!subarr) return -1;
+ if (parse_array(ctx, subarr)) return -1;
+ break;
+ }
+
+ case LBRACE:
+ { /* [ {table}, {table} ... ] */
+ /* set the array kind if this will be the first entry */
+ if (arr->kind == 0)
+ arr->kind = 't';
+ else if (arr->kind != 't')
+ arr->kind = 'm';
+
+ toml_table_t* subtab = create_table_in_array(ctx, arr);
+ if (!subtab) return -1;
+ if (parse_inline_table(ctx, subtab)) return -1;
+ break;
+ }
+
+ default:
+ return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+ }
+
+ if (skip_newlines(ctx, 0)) return -1;
+
+ /* on comma, continue to scan for next element */
+ if (ctx->tok.tok == COMMA) {
+ if (eat_token(ctx, COMMA, 0, FLINE)) return -1;
+ continue;
+ }
+ break;
+ }
+
+ if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1;
+ return 0;
+}
+
+
+/* handle lines like these:
+ key = "value"
+ key = [ array ]
+ key = { table }
+*/
+static int parse_keyval(context_t* ctx, toml_table_t* tab)
+{
+ if (tab->readonly) {
+ return e_forbid(ctx, ctx->tok.lineno, "cannot insert new entry into existing table");
+ }
+
+ token_t key = ctx->tok;
+ if (eat_token(ctx, STRING, 1, FLINE)) return -1;
+
+ if (ctx->tok.tok == DOT) {
+ /* handle inline dotted key.
+ e.g.
+ physical.color = "orange"
+ physical.shape = "round"
+ */
+ toml_table_t* subtab = 0;
+ {
+ char* subtabstr = normalize_key(ctx, key);
+ if (!subtabstr) return -1;
+
+ subtab = toml_table_in(tab, subtabstr);
+ xfree(subtabstr);
+ }
+ if (!subtab) {
+ subtab = create_keytable_in_table(ctx, tab, key);
+ if (!subtab) return -1;
+ }
+ if (next_token(ctx, 1)) return -1;
+ if (parse_keyval(ctx, subtab)) return -1;
+ return 0;
+ }
+
+ if (ctx->tok.tok != EQUAL) {
+ return e_syntax(ctx, ctx->tok.lineno, "missing =");
+ }
+
+ if (next_token(ctx, 0)) return -1;
+
+ switch (ctx->tok.tok) {
+ case STRING:
+ { /* key = "value" */
+ toml_keyval_t* keyval = create_keyval_in_table(ctx, tab, key);
+ if (!keyval) return -1;
+ token_t val = ctx->tok;
+
+ assert(keyval->val == 0);
+ if (! (keyval->val = STRNDUP(val.ptr, val.len)))
+ return e_outofmemory(ctx, FLINE);
+
+ if (next_token(ctx, 1)) return -1;
+
+ return 0;
+ }
+
+ case LBRACKET:
+ { /* key = [ array ] */
+ toml_array_t* arr = create_keyarray_in_table(ctx, tab, key, 0);
+ if (!arr) return -1;
+ if (parse_array(ctx, arr)) return -1;
+ return 0;
+ }
+
+ case LBRACE:
+ { /* key = { table } */
+ toml_table_t* nxttab = create_keytable_in_table(ctx, tab, key);
+ if (!nxttab) return -1;
+ if (parse_inline_table(ctx, nxttab)) return -1;
+ return 0;
+ }
+
+ default:
+ return e_syntax(ctx, ctx->tok.lineno, "syntax error");
+ }
+ return 0;
+}
+
+
+typedef struct tabpath_t tabpath_t;
+struct tabpath_t {
+ int cnt;
+ token_t key[10];
+};
+
+/* at [x.y.z] or [[x.y.z]]
+ * Scan forward and fill tabpath until it enters ] or ]]
+ * There will be at least one entry on return.
+ */
+static int fill_tabpath(context_t* ctx)
+{
+ int lineno = ctx->tok.lineno;
+ int i;
+
+ /* clear tpath */
+ for (i = 0; i < ctx->tpath.top; i++) {
+ char** p = &ctx->tpath.key[i];
+ xfree(*p);
+ *p = 0;
+ }
+ ctx->tpath.top = 0;
+
+ for (;;) {
+ if (ctx->tpath.top >= 10)
+ return e_syntax(ctx, lineno, "table path is too deep; max allowed is 10.");
+
+ if (ctx->tok.tok != STRING)
+ return e_syntax(ctx, lineno, "invalid or missing key");
+
+ char* key = normalize_key(ctx, ctx->tok);
+ if (!key) return -1;
+ ctx->tpath.tok[ctx->tpath.top] = ctx->tok;
+ ctx->tpath.key[ctx->tpath.top] = key;
+ ctx->tpath.top++;
+
+ if (next_token(ctx, 1)) return -1;
+
+ if (ctx->tok.tok == RBRACKET) break;
+
+ if (ctx->tok.tok != DOT)
+ return e_syntax(ctx, lineno, "invalid key");
+
+ if (next_token(ctx, 1)) return -1;
+ }
+
+ if (ctx->tpath.top <= 0)
+ return e_syntax(ctx, lineno, "empty table selector");
+
+ return 0;
+}
+
+
+/* Walk tabpath from the root, and create new tables on the way.
+ * Sets ctx->curtab to the final table.
+ */
+static int walk_tabpath(context_t* ctx)
+{
+ /* start from root */
+ toml_table_t* curtab = ctx->root;
+
+ for (int i = 0; i < ctx->tpath.top; i++) {
+ const char* key = ctx->tpath.key[i];
+
+ toml_keyval_t* nextval = 0;
+ toml_array_t* nextarr = 0;
+ toml_table_t* nexttab = 0;
+ switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) {
+ case 't':
+ /* found a table. nexttab is where we will go next. */
+ break;
+
+ case 'a':
+ /* found an array. nexttab is the last table in the array. */
+ if (nextarr->kind != 't')
+ return e_internal(ctx, FLINE);
+
+ if (nextarr->nitem == 0)
+ return e_internal(ctx, FLINE);
+
+ nexttab = nextarr->item[nextarr->nitem-1].tab;
+ break;
+
+ case 'v':
+ return e_keyexists(ctx, ctx->tpath.tok[i].lineno);
+
+ default:
+ { /* Not found. Let's create an implicit table. */
+ int n = curtab->ntab;
+ toml_table_t** base = (toml_table_t**) expand_ptrarr((void**)curtab->tab, n);
+ if (0 == base)
+ return e_outofmemory(ctx, FLINE);
+
+ curtab->tab = base;
+
+ if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n]))))
+ return e_outofmemory(ctx, FLINE);
+
+ if (0 == (base[n]->key = STRDUP(key)))
+ return e_outofmemory(ctx, FLINE);
+
+ nexttab = curtab->tab[curtab->ntab++];
+
+ /* tabs created by walk_tabpath are considered implicit */
+ nexttab->implicit = true;
+ }
+ break;
+ }
+
+ /* switch to next tab */
+ curtab = nexttab;
+ }
+
+ /* save it */
+ ctx->curtab = curtab;
+
+ return 0;
+}
+
+
+/* handle lines like [x.y.z] or [[x.y.z]] */
+static int parse_select(context_t* ctx)
+{
+ assert(ctx->tok.tok == LBRACKET);
+
+ /* true if [[ */
+ int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '[');
+ /* need to detect '[[' on our own because next_token() will skip whitespace,
+ and '[ [' would be taken as '[[', which is wrong. */
+
+ /* eat [ or [[ */
+ if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1;
+ if (llb) {
+ assert(ctx->tok.tok == LBRACKET);
+ if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1;
+ }
+
+ if (fill_tabpath(ctx)) return -1;
+
+ /* For [x.y.z] or [[x.y.z]], remove z from tpath.
+ */
+ token_t z = ctx->tpath.tok[ctx->tpath.top-1];
+ xfree(ctx->tpath.key[ctx->tpath.top-1]);
+ ctx->tpath.top--;
+
+ /* set up ctx->curtab */
+ if (walk_tabpath(ctx)) return -1;
+
+ if (! llb) {
+ /* [x.y.z] -> create z = {} in x.y */
+ toml_table_t* curtab = create_keytable_in_table(ctx, ctx->curtab, z);
+ if (!curtab) return -1;
+ ctx->curtab = curtab;
+ } else {
+ /* [[x.y.z]] -> create z = [] in x.y */
+ toml_array_t* arr = 0;
+ {
+ char* zstr = normalize_key(ctx, z);
+ if (!zstr) return -1;
+ arr = toml_array_in(ctx->curtab, zstr);
+ xfree(zstr);
+ }
+ if (!arr) {
+ arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't');
+ if (!arr) return -1;
+ }
+ if (arr->kind != 't')
+ return e_syntax(ctx, z.lineno, "array mismatch");
+
+ /* add to z[] */
+ toml_table_t* dest;
+ {
+ toml_table_t* t = create_table_in_array(ctx, arr);
+ if (!t) return -1;
+
+ if (0 == (t->key = STRDUP("__anon__")))
+ return e_outofmemory(ctx, FLINE);
+
+ dest = t;
+ }
+
+ ctx->curtab = dest;
+ }
+
+ if (ctx->tok.tok != RBRACKET) {
+ return e_syntax(ctx, ctx->tok.lineno, "expects ]");
+ }
+ if (llb) {
+ if (! (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) {
+ return e_syntax(ctx, ctx->tok.lineno, "expects ]]");
+ }
+ if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1;
+ }
+
+ if (eat_token(ctx, RBRACKET, 1, FLINE))
+ return -1;
+
+ if (ctx->tok.tok != NEWLINE)
+ return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]");
+
+ return 0;
+}
+
+
+
+
+toml_table_t* toml_parse(char* conf,
+ char* errbuf,
+ int errbufsz)
+{
+ context_t ctx;
+
+ // clear errbuf
+ if (errbufsz <= 0) errbufsz = 0;
+ if (errbufsz > 0) errbuf[0] = 0;
+
+ // init context
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.start = conf;
+ ctx.stop = ctx.start + strlen(conf);
+ ctx.errbuf = errbuf;
+ ctx.errbufsz = errbufsz;
+
+ // start with an artificial newline of length 0
+ ctx.tok.tok = NEWLINE;
+ ctx.tok.lineno = 1;
+ ctx.tok.ptr = conf;
+ ctx.tok.len = 0;
+
+ // make a root table
+ if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) {
+ e_outofmemory(&ctx, FLINE);
+ // Do not goto fail, root table not set up yet
+ return 0;
+ }
+
+ // set root as default table
+ ctx.curtab = ctx.root;
+
+ /* Scan forward until EOF */
+ for (token_t tok = ctx.tok; ! tok.eof ; tok = ctx.tok) {
+ switch (tok.tok) {
+
+ case NEWLINE:
+ if (next_token(&ctx, 1)) goto fail;
+ break;
+
+ case STRING:
+ if (parse_keyval(&ctx, ctx.curtab)) goto fail;
+
+ if (ctx.tok.tok != NEWLINE) {
+ e_syntax(&ctx, ctx.tok.lineno, "extra chars after value");
+ goto fail;
+ }
+
+ if (eat_token(&ctx, NEWLINE, 1, FLINE)) goto fail;
+ break;
+
+ case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */
+ if (parse_select(&ctx)) goto fail;
+ break;
+
+ default:
+ e_syntax(&ctx, tok.lineno, "syntax error");
+ goto fail;
+ }
+ }
+
+ /* success */
+ for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]);
+ return ctx.root;
+
+fail:
+ // Something bad has happened. Free resources and return error.
+ for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]);
+ toml_free(ctx.root);
+ return 0;
+}
+
+
+toml_table_t* toml_parse_file(FILE* fp,
+ char* errbuf,
+ int errbufsz)
+{
+ int bufsz = 0;
+ char* buf = 0;
+ int off = 0;
+
+ /* read from fp into buf */
+ while (! feof(fp)) {
+
+ if (off == bufsz) {
+ int xsz = bufsz + 1000;
+ char* x = expand(buf, bufsz, xsz);
+ if (!x) {
+ snprintf(errbuf, errbufsz, "out of memory");
+ xfree(buf);
+ return 0;
+ }
+ buf = x;
+ bufsz = xsz;
+ }
+
+ errno = 0;
+ int n = fread(buf + off, 1, bufsz - off, fp);
+ if (ferror(fp)) {
+ snprintf(errbuf, errbufsz, "%s",
+ errno ? strerror(errno) : "Error reading file");
+ xfree(buf);
+ return 0;
+ }
+ off += n;
+ }
+
+ /* tag on a NUL to cap the string */
+ if (off == bufsz) {
+ int xsz = bufsz + 1;
+ char* x = expand(buf, bufsz, xsz);
+ if (!x) {
+ snprintf(errbuf, errbufsz, "out of memory");
+ xfree(buf);
+ return 0;
+ }
+ buf = x;
+ bufsz = xsz;
+ }
+ buf[off] = 0;
+
+ /* parse it, cleanup and finish */
+ toml_table_t* ret = toml_parse(buf, errbuf, errbufsz);
+ xfree(buf);
+ return ret;
+}
+
+
+static void xfree_kval(toml_keyval_t* p)
+{
+ if (!p) return;
+ xfree(p->key);
+ xfree(p->val);
+ xfree(p);
+}
+
+static void xfree_tab(toml_table_t* p);
+
+static void xfree_arr(toml_array_t* p)
+{
+ if (!p) return;
+
+ xfree(p->key);
+ const int n = p->nitem;
+ for (int i = 0; i < n; i++) {
+ toml_arritem_t* a = &p->item[i];
+ if (a->val)
+ xfree(a->val);
+ else if (a->arr)
+ xfree_arr(a->arr);
+ else if (a->tab)
+ xfree_tab(a->tab);
+ }
+ xfree(p->item);
+ xfree(p);
+}
+
+
+static void xfree_tab(toml_table_t* p)
+{
+ int i;
+
+ if (!p) return;
+
+ xfree(p->key);
+
+ for (i = 0; i < p->nkval; i++) xfree_kval(p->kval[i]);
+ xfree(p->kval);
+
+ for (i = 0; i < p->narr; i++) xfree_arr(p->arr[i]);
+ xfree(p->arr);
+
+ for (i = 0; i < p->ntab; i++) xfree_tab(p->tab[i]);
+ xfree(p->tab);
+
+ xfree(p);
+}
+
+
+void toml_free(toml_table_t* tab)
+{
+ xfree_tab(tab);
+}
+
+
+static void set_token(context_t* ctx, tokentype_t tok, int lineno, char* ptr, int len)
+{
+ token_t t;
+ t.tok = tok;
+ t.lineno = lineno;
+ t.ptr = ptr;
+ t.len = len;
+ t.eof = 0;
+ ctx->tok = t;
+}
+
+static void set_eof(context_t* ctx, int lineno)
+{
+ set_token(ctx, NEWLINE, lineno, ctx->stop, 0);
+ ctx->tok.eof = 1;
+}
+
+
+/* Scan p for n digits compositing entirely of [0-9] */
+static int scan_digits(const char* p, int n)
+{
+ int ret = 0;
+ for ( ; n > 0 && isdigit(*p); n--, p++) {
+ ret = 10 * ret + (*p - '0');
+ }
+ return n ? -1 : ret;
+}
+
+static int scan_date(const char* p, int* YY, int* MM, int* DD)
+{
+ int year, month, day;
+ year = scan_digits(p, 4);
+ month = (year >= 0 && p[4] == '-') ? scan_digits(p+5, 2) : -1;
+ day = (month >= 0 && p[7] == '-') ? scan_digits(p+8, 2) : -1;
+ if (YY) *YY = year;
+ if (MM) *MM = month;
+ if (DD) *DD = day;
+ return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1;
+}
+
+static int scan_time(const char* p, int* hh, int* mm, int* ss)
+{
+ int hour, minute, second;
+ hour = scan_digits(p, 2);
+ minute = (hour >= 0 && p[2] == ':') ? scan_digits(p+3, 2) : -1;
+ second = (minute >= 0 && p[5] == ':') ? scan_digits(p+6, 2) : -1;
+ if (hh) *hh = hour;
+ if (mm) *mm = minute;
+ if (ss) *ss = second;
+ return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1;
+}
+
+
+static int scan_string(context_t* ctx, char* p, int lineno, int dotisspecial)
+{
+ char* orig = p;
+ if (0 == strncmp(p, "'''", 3)) {
+ char* q = p + 3;
+
+ while (1) {
+ q = strstr(q, "'''");
+ if (0 == q) {
+ return e_syntax(ctx, lineno, "unterminated triple-s-quote");
+ }
+ while (q[3] == '\'') q++;
+ break;
+ }
+
+ set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+ return 0;
+ }
+
+ if (0 == strncmp(p, "\"\"\"", 3)) {
+ char* q = p + 3;
+
+ while (1) {
+ q = strstr(q, "\"\"\"");
+ if (0 == q) {
+ return e_syntax(ctx, lineno, "unterminated triple-d-quote");
+ }
+ if (q[-1] == '\\') {
+ q++;
+ continue;
+ }
+ while (q[3] == '\"') q++;
+ break;
+ }
+
+ // the string is [p+3, q-1]
+
+ int hexreq = 0; /* #hex required */
+ int escape = 0;
+ for (p += 3; p < q; p++) {
+ if (escape) {
+ escape = 0;
+ if (strchr("btnfr\"\\", *p)) continue;
+ if (*p == 'u') { hexreq = 4; continue; }
+ if (*p == 'U') { hexreq = 8; continue; }
+ if (p[strspn(p, " \t\r")] == '\n') continue; /* allow for line ending backslash */
+ return e_syntax(ctx, lineno, "bad escape char");
+ }
+ if (hexreq) {
+ hexreq--;
+ if (strchr("0123456789ABCDEF", *p)) continue;
+ return e_syntax(ctx, lineno, "expect hex char");
+ }
+ if (*p == '\\') { escape = 1; continue; }
+ }
+ if (escape)
+ return e_syntax(ctx, lineno, "expect an escape char");
+ if (hexreq)
+ return e_syntax(ctx, lineno, "expected more hex char");
+
+ set_token(ctx, STRING, lineno, orig, q + 3 - orig);
+ return 0;
+ }
+
+ if ('\'' == *p) {
+ for (p++; *p && *p != '\n' && *p != '\''; p++);
+ if (*p != '\'') {
+ return e_syntax(ctx, lineno, "unterminated s-quote");
+ }
+
+ set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+ return 0;
+ }
+
+ if ('\"' == *p) {
+ int hexreq = 0; /* #hex required */
+ int escape = 0;
+ for (p++; *p; p++) {
+ if (escape) {
+ escape = 0;
+ if (strchr("btnfr\"\\", *p)) continue;
+ if (*p == 'u') { hexreq = 4; continue; }
+ if (*p == 'U') { hexreq = 8; continue; }
+ return e_syntax(ctx, lineno, "bad escape char");
+ }
+ if (hexreq) {
+ hexreq--;
+ if (strchr("0123456789ABCDEF", *p)) continue;
+ return e_syntax(ctx, lineno, "expect hex char");
+ }
+ if (*p == '\\') { escape = 1; continue; }
+ if (*p == '\'') {
+ if (p[1] == '\'' && p[2] == '\'') {
+ return e_syntax(ctx, lineno, "triple-s-quote inside string lit");
+ }
+ continue;
+ }
+ if (*p == '\n') break;
+ if (*p == '"') break;
+ }
+ if (*p != '"') {
+ return e_syntax(ctx, lineno, "unterminated quote");
+ }
+
+ set_token(ctx, STRING, lineno, orig, p + 1 - orig);
+ return 0;
+ }
+
+ /* check for timestamp without quotes */
+ if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) {
+ // forward thru the timestamp
+ for ( ; strchr("0123456789.:+-T Z", toupper(*p)); p++);
+ // squeeze out any spaces at end of string
+ for ( ; p[-1] == ' '; p--);
+ // tokenize
+ set_token(ctx, STRING, lineno, orig, p - orig);
+ return 0;
+ }
+
+ /* literals */
+ for ( ; *p && *p != '\n'; p++) {
+ int ch = *p;
+ if (ch == '.' && dotisspecial) break;
+ if ('A' <= ch && ch <= 'Z') continue;
+ if ('a' <= ch && ch <= 'z') continue;
+ if (strchr("0123456789+-_.", ch)) continue;
+ break;
+ }
+
+ set_token(ctx, STRING, lineno, orig, p - orig);
+ return 0;
+}
+
+
+static int next_token(context_t* ctx, int dotisspecial)
+{
+ int lineno = ctx->tok.lineno;
+ char* p = ctx->tok.ptr;
+ int i;
+
+ /* eat this tok */
+ for (i = 0; i < ctx->tok.len; i++) {
+ if (*p++ == '\n')
+ lineno++;
+ }
+
+ /* make next tok */
+ while (p < ctx->stop) {
+ /* skip comment. stop just before the \n. */
+ if (*p == '#') {
+ for (p++; p < ctx->stop && *p != '\n'; p++);
+ continue;
+ }
+
+ if (dotisspecial && *p == '.') {
+ set_token(ctx, DOT, lineno, p, 1);
+ return 0;
+ }
+
+ switch (*p) {
+ case ',': set_token(ctx, COMMA, lineno, p, 1); return 0;
+ case '=': set_token(ctx, EQUAL, lineno, p, 1); return 0;
+ case '{': set_token(ctx, LBRACE, lineno, p, 1); return 0;
+ case '}': set_token(ctx, RBRACE, lineno, p, 1); return 0;
+ case '[': set_token(ctx, LBRACKET, lineno, p, 1); return 0;
+ case ']': set_token(ctx, RBRACKET, lineno, p, 1); return 0;
+ case '\n': set_token(ctx, NEWLINE, lineno, p, 1); return 0;
+ case '\r': case ' ': case '\t':
+ /* ignore white spaces */
+ p++;
+ continue;
+ }
+
+ return scan_string(ctx, p, lineno, dotisspecial);
+ }
+
+ set_eof(ctx, lineno);
+ return 0;
+}
+
+
+const char* toml_key_in(const toml_table_t* tab, int keyidx)
+{
+ if (keyidx < tab->nkval) return tab->kval[keyidx]->key;
+
+ keyidx -= tab->nkval;
+ if (keyidx < tab->narr) return tab->arr[keyidx]->key;
+
+ keyidx -= tab->narr;
+ if (keyidx < tab->ntab) return tab->tab[keyidx]->key;
+
+ return 0;
+}
+
+toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key)
+{
+ int i;
+ for (i = 0; i < tab->nkval; i++) {
+ if (0 == strcmp(key, tab->kval[i]->key))
+ return tab->kval[i]->val;
+ }
+ return 0;
+}
+
+toml_array_t* toml_array_in(const toml_table_t* tab, const char* key)
+{
+ int i;
+ for (i = 0; i < tab->narr; i++) {
+ if (0 == strcmp(key, tab->arr[i]->key))
+ return tab->arr[i];
+ }
+ return 0;
+}
+
+
+toml_table_t* toml_table_in(const toml_table_t* tab, const char* key)
+{
+ int i;
+ for (i = 0; i < tab->ntab; i++) {
+ if (0 == strcmp(key, tab->tab[i]->key))
+ return tab->tab[i];
+ }
+ return 0;
+}
+
+toml_raw_t toml_raw_at(const toml_array_t* arr, int idx)
+{
+ return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0;
+}
+
+char toml_array_kind(const toml_array_t* arr)
+{
+ return arr->kind;
+}
+
+char toml_array_type(const toml_array_t* arr)
+{
+ if (arr->kind != 'v')
+ return 0;
+
+ if (arr->nitem == 0)
+ return 0;
+
+ return arr->type;
+}
+
+
+int toml_array_nelem(const toml_array_t* arr)
+{
+ return arr->nitem;
+}
+
+const char* toml_array_key(const toml_array_t* arr)
+{
+ return arr ? arr->key : (const char*) NULL;
+}
+
+int toml_table_nkval(const toml_table_t* tab)
+{
+ return tab->nkval;
+}
+
+int toml_table_narr(const toml_table_t* tab)
+{
+ return tab->narr;
+}
+
+int toml_table_ntab(const toml_table_t* tab)
+{
+ return tab->ntab;
+}
+
+const char* toml_table_key(const toml_table_t* tab)
+{
+ return tab ? tab->key : (const char*) NULL;
+}
+
+toml_array_t* toml_array_at(const toml_array_t* arr, int idx)
+{
+ return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0;
+}
+
+toml_table_t* toml_table_at(const toml_array_t* arr, int idx)
+{
+ return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0;
+}
+
+
+int toml_rtots(toml_raw_t src_, toml_timestamp_t* ret)
+{
+ if (! src_) return -1;
+
+ const char* p = src_;
+ int must_parse_time = 0;
+
+ memset(ret, 0, sizeof(*ret));
+
+ int* year = &ret->__buffer.year;
+ int* month = &ret->__buffer.month;
+ int* day = &ret->__buffer.day;
+ int* hour = &ret->__buffer.hour;
+ int* minute = &ret->__buffer.minute;
+ int* second = &ret->__buffer.second;
+ int* millisec = &ret->__buffer.millisec;
+
+ /* parse date YYYY-MM-DD */
+ if (0 == scan_date(p, year, month, day)) {
+ ret->year = year;
+ ret->month = month;
+ ret->day = day;
+
+ p += 10;
+ if (*p) {
+ // parse the T or space separator
+ if (*p != 'T' && *p != ' ') return -1;
+ must_parse_time = 1;
+ p++;
+ }
+ }
+
+ /* parse time HH:MM:SS */
+ if (0 == scan_time(p, hour, minute, second)) {
+ ret->hour = hour;
+ ret->minute = minute;
+ ret->second = second;
+
+ /* optionally, parse millisec */
+ p += 8;
+ if (*p == '.') {
+ char* qq;
+ p++;
+ errno = 0;
+ *millisec = strtol(p, &qq, 0);
+ if (errno) {
+ return -1;
+ }
+ while (*millisec > 999) {
+ *millisec /= 10;
+ }
+
+ ret->millisec = millisec;
+ p = qq;
+ }
+
+ if (*p) {
+ /* parse and copy Z */
+ char* z = ret->__buffer.z;
+ ret->z = z;
+ if (*p == 'Z' || *p == 'z') {
+ *z++ = 'Z'; p++;
+ *z = 0;
+
+ } else if (*p == '+' || *p == '-') {
+ *z++ = *p++;
+
+ if (! (isdigit(p[0]) && isdigit(p[1]))) return -1;
+ *z++ = *p++;
+ *z++ = *p++;
+
+ if (*p == ':') {
+ *z++ = *p++;
+
+ if (! (isdigit(p[0]) && isdigit(p[1]))) return -1;
+ *z++ = *p++;
+ *z++ = *p++;
+ }
+
+ *z = 0;
+ }
+ }
+ }
+ if (*p != 0)
+ return -1;
+
+ if (must_parse_time && !ret->hour)
+ return -1;
+
+ return 0;
+}
+
+
+/* Raw to boolean */
+int toml_rtob(toml_raw_t src, int* ret_)
+{
+ if (!src) return -1;
+ int dummy;
+ int* ret = ret_ ? ret_ : &dummy;
+
+ if (0 == strcmp(src, "true")) {
+ *ret = 1;
+ return 0;
+ }
+ if (0 == strcmp(src, "false")) {
+ *ret = 0;
+ return 0;
+ }
+ return -1;
+}
+
+
+/* Raw to integer */
+int toml_rtoi(toml_raw_t src, int64_t* ret_)
+{
+ if (!src) return -1;
+
+ char buf[100];
+ char* p = buf;
+ char* q = p + sizeof(buf);
+ const char* s = src;
+ int base = 0;
+ int64_t dummy;
+ int64_t* ret = ret_ ? ret_ : &dummy;
+
+
+ /* allow +/- */
+ if (s[0] == '+' || s[0] == '-')
+ *p++ = *s++;
+
+ /* disallow +_100 */
+ if (s[0] == '_')
+ return -1;
+
+ /* if 0 ... */
+ if ('0' == s[0]) {
+ switch (s[1]) {
+ case 'x': base = 16; s += 2; break;
+ case 'o': base = 8; s += 2; break;
+ case 'b': base = 2; s += 2; break;
+ case '\0': return *ret = 0, 0;
+ default:
+ /* ensure no other digits after it */
+ if (s[1]) return -1;
+ }
+ }
+
+ /* just strip underscores and pass to strtoll */
+ while (*s && p < q) {
+ int ch = *s++;
+ switch (ch) {
+ case '_':
+ // disallow '__'
+ if (s[0] == '_') return -1;
+ continue; /* skip _ */
+ default:
+ break;
+ }
+ *p++ = ch;
+ }
+ if (*s || p == q) return -1;
+
+ /* last char cannot be '_' */
+ if (s[-1] == '_') return -1;
+
+ /* cap with NUL */
+ *p = 0;
+
+ /* Run strtoll on buf to get the integer */
+ char* endp;
+ errno = 0;
+ *ret = strtoll(buf, &endp, base);
+ return (errno || *endp) ? -1 : 0;
+}
+
+
+int toml_rtod_ex(toml_raw_t src, double* ret_, char* buf, int buflen)
+{
+ if (!src) return -1;
+
+ char* p = buf;
+ char* q = p + buflen;
+ const char* s = src;
+ double dummy;
+ double* ret = ret_ ? ret_ : &dummy;
+
+
+ /* allow +/- */
+ if (s[0] == '+' || s[0] == '-')
+ *p++ = *s++;
+
+ /* disallow +_1.00 */
+ if (s[0] == '_')
+ return -1;
+
+ /* decimal point, if used, must be surrounded by at least one digit on each side */
+ {
+ char* dot = strchr(s, '.');
+ if (dot) {
+ if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1]))
+ return -1;
+ }
+ }
+
+ /* zero must be followed by . or 'e', or NUL */
+ if (s[0] == '0' && s[1] && !strchr("eE.", s[1]))
+ return -1;
+
+ /* just strip underscores and pass to strtod */
+ while (*s && p < q) {
+ int ch = *s++;
+ if (ch == '_') {
+ // disallow '__'
+ if (s[0] == '_') return -1;
+ // disallow last char '_'
+ if (s[0] == 0) return -1;
+ continue; /* skip _ */
+ }
+ *p++ = ch;
+ }
+ if (*s || p == q) return -1; /* reached end of string or buffer is full? */
+
+ /* cap with NUL */
+ *p = 0;
+
+ /* Run strtod on buf to get the value */
+ char* endp;
+ errno = 0;
+ *ret = strtod(buf, &endp);
+ return (errno || *endp) ? -1 : 0;
+}
+
+int toml_rtod(toml_raw_t src, double* ret_)
+{
+ char buf[100];
+ return toml_rtod_ex(src, ret_, buf, sizeof(buf));
+}
+
+
+
+
+int toml_rtos(toml_raw_t src, char** ret)
+{
+ int multiline = 0;
+ const char* sp;
+ const char* sq;
+
+ *ret = 0;
+ if (!src) return -1;
+
+ int qchar = src[0];
+ int srclen = strlen(src);
+ if (! (qchar == '\'' || qchar == '"')) {
+ return -1;
+ }
+
+ // triple quotes?
+ if (qchar == src[1] && qchar == src[2]) {
+ multiline = 1;
+ sp = src + 3;
+ sq = src + srclen - 3;
+ /* last 3 chars in src must be qchar */
+ if (! (sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar))
+ return -1;
+
+ /* skip new line immediate after qchar */
+ if (sp[0] == '\n')
+ sp++;
+ else if (sp[0] == '\r' && sp[1] == '\n')
+ sp += 2;
+
+ } else {
+ sp = src + 1;
+ sq = src + srclen - 1;
+ /* last char in src must be qchar */
+ if (! (sp <= sq && *sq == qchar))
+ return -1;
+ }
+
+ if (qchar == '\'') {
+ *ret = norm_lit_str(sp, sq - sp,
+ multiline,
+ 0, 0);
+ } else {
+ *ret = norm_basic_str(sp, sq - sp,
+ multiline,
+ 0, 0);
+ }
+
+ return *ret ? 0 : -1;
+}
+
+
+toml_datum_t toml_string_at(const toml_array_t* arr, int idx)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtos(toml_raw_at(arr, idx), &ret.u.s));
+ return ret;
+}
+
+toml_datum_t toml_bool_at(const toml_array_t* arr, int idx)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtob(toml_raw_at(arr, idx), &ret.u.b));
+ return ret;
+}
+
+toml_datum_t toml_int_at(const toml_array_t* arr, int idx)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtoi(toml_raw_at(arr, idx), &ret.u.i));
+ return ret;
+}
+
+toml_datum_t toml_double_at(const toml_array_t* arr, int idx)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtod(toml_raw_at(arr, idx), &ret.u.d));
+ return ret;
+}
+
+toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx)
+{
+ toml_timestamp_t ts;
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtots(toml_raw_at(arr, idx), &ts));
+ if (ret.ok) {
+ ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts)));
+ if (ret.ok) {
+ *ret.u.ts = ts;
+ if (ret.u.ts->year) ret.u.ts->year = &ret.u.ts->__buffer.year;
+ if (ret.u.ts->month) ret.u.ts->month = &ret.u.ts->__buffer.month;
+ if (ret.u.ts->day) ret.u.ts->day = &ret.u.ts->__buffer.day;
+ if (ret.u.ts->hour) ret.u.ts->hour = &ret.u.ts->__buffer.hour;
+ if (ret.u.ts->minute) ret.u.ts->minute = &ret.u.ts->__buffer.minute;
+ if (ret.u.ts->second) ret.u.ts->second = &ret.u.ts->__buffer.second;
+ if (ret.u.ts->millisec) ret.u.ts->millisec = &ret.u.ts->__buffer.millisec;
+ if (ret.u.ts->z) ret.u.ts->z = ret.u.ts->__buffer.z;
+ }
+ }
+ return ret;
+}
+
+toml_datum_t toml_string_in(const toml_table_t* arr, const char* key)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ toml_raw_t raw = toml_raw_in(arr, key);
+ if (raw) {
+ ret.ok = (0 == toml_rtos(raw, &ret.u.s));
+ }
+ return ret;
+}
+
+toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtob(toml_raw_in(arr, key), &ret.u.b));
+ return ret;
+}
+
+toml_datum_t toml_int_in(const toml_table_t* arr, const char* key)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtoi(toml_raw_in(arr, key), &ret.u.i));
+ return ret;
+}
+
+toml_datum_t toml_double_in(const toml_table_t* arr, const char* key)
+{
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtod(toml_raw_in(arr, key), &ret.u.d));
+ return ret;
+}
+
+toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key)
+{
+ toml_timestamp_t ts;
+ toml_datum_t ret;
+ memset(&ret, 0, sizeof(ret));
+ ret.ok = (0 == toml_rtots(toml_raw_in(arr, key), &ts));
+ if (ret.ok) {
+ ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts)));
+ if (ret.ok) {
+ *ret.u.ts = ts;
+ if (ret.u.ts->year) ret.u.ts->year = &ret.u.ts->__buffer.year;
+ if (ret.u.ts->month) ret.u.ts->month = &ret.u.ts->__buffer.month;
+ if (ret.u.ts->day) ret.u.ts->day = &ret.u.ts->__buffer.day;
+ if (ret.u.ts->hour) ret.u.ts->hour = &ret.u.ts->__buffer.hour;
+ if (ret.u.ts->minute) ret.u.ts->minute = &ret.u.ts->__buffer.minute;
+ if (ret.u.ts->second) ret.u.ts->second = &ret.u.ts->__buffer.second;
+ if (ret.u.ts->millisec) ret.u.ts->millisec = &ret.u.ts->__buffer.millisec;
+ if (ret.u.ts->z) ret.u.ts->z = ret.u.ts->__buffer.z;
+ }
+ }
+ return ret;
+}