aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.arcconfig5
-rw-r--r--.clang-format25
-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--.gitignore7
-rw-r--r--.travis.yml38
-rw-r--r--BUILD.md76
-rw-r--r--CMakeLists.txt39
-rw-r--r--COPYING.md84
-rw-r--r--README.md50
-rw-r--r--api/gui/SkinUtils.cpp13
-rw-r--r--api/gui/SkinUtils.h2
-rw-r--r--api/gui/icons/IconList.cpp5
-rw-r--r--api/gui/icons/IconList.h4
-rw-r--r--api/gui/icons/MMCIcon.cpp16
-rw-r--r--api/gui/icons/MMCIcon.h4
-rw-r--r--api/logic/BaseInstaller.cpp2
-rw-r--r--api/logic/BaseInstaller.h2
-rw-r--r--api/logic/BaseInstance.cpp32
-rw-r--r--api/logic/BaseInstance.h22
-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.txt135
-rw-r--r--api/logic/Commandline.cpp2
-rw-r--r--api/logic/Commandline.h6
-rw-r--r--api/logic/Env.cpp10
-rw-r--r--api/logic/FileSystem.cpp7
-rw-r--r--api/logic/InstanceCopyTask.cpp6
-rw-r--r--api/logic/InstanceCopyTask.h3
-rw-r--r--api/logic/InstanceCreationTask.cpp4
-rw-r--r--api/logic/InstanceImportTask.cpp65
-rw-r--r--api/logic/InstanceImportTask.h25
-rw-r--r--api/logic/InstanceList.cpp119
-rw-r--r--api/logic/InstanceList.h22
-rw-r--r--api/logic/LoggedProcess.h2
-rw-r--r--api/logic/MMCZip.cpp71
-rw-r--r--api/logic/MMCZip.h31
-rw-r--r--api/logic/NullInstance.h26
-rw-r--r--api/logic/RWStorage.h3
-rw-r--r--api/logic/Version.cpp2
-rw-r--r--api/logic/Version_test.cpp2
-rw-r--r--api/logic/icons/IconUtils.cpp62
-rw-r--r--api/logic/icons/IconUtils.h14
-rw-r--r--api/logic/java/JavaChecker.cpp8
-rw-r--r--api/logic/java/JavaChecker.h7
-rw-r--r--api/logic/java/JavaCheckerJob.cpp2
-rw-r--r--api/logic/java/JavaCheckerJob.h4
-rw-r--r--api/logic/java/JavaInstallList.cpp4
-rw-r--r--api/logic/java/JavaInstallList.h6
-rw-r--r--api/logic/java/JavaUtils.cpp78
-rw-r--r--api/logic/java/JavaUtils.h4
-rw-r--r--api/logic/java/launch/CheckJava.cpp35
-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.cpp10
-rw-r--r--api/logic/launch/LaunchTask.h10
-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.cpp4
-rw-r--r--api/logic/launch/steps/Update.h2
-rw-r--r--api/logic/meta/BaseEntity.cpp14
-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.cpp4
-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.cpp138
-rw-r--r--api/logic/minecraft/AssetsUtils.h11
-rw-r--r--api/logic/minecraft/Component.cpp8
-rw-r--r--api/logic/minecraft/Component.h10
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.cpp45
-rw-r--r--api/logic/minecraft/ComponentUpdateTask.h4
-rw-r--r--api/logic/minecraft/ComponentUpdateTask_p.h6
-rw-r--r--api/logic/minecraft/GradleSpecifier.h42
-rw-r--r--api/logic/minecraft/GradleSpecifier_test.cpp5
-rw-r--r--api/logic/minecraft/LaunchProfile.cpp22
-rw-r--r--api/logic/minecraft/LaunchProfile.h7
-rw-r--r--api/logic/minecraft/Library.cpp111
-rw-r--r--api/logic/minecraft/Library.h9
-rw-r--r--api/logic/minecraft/Library_test.cpp9
-rw-r--r--api/logic/minecraft/MinecraftInstance.cpp293
-rw-r--r--api/logic/minecraft/MinecraftInstance.h46
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.cpp8
-rw-r--r--api/logic/minecraft/MinecraftLoadAndCheck.h2
-rw-r--r--api/logic/minecraft/MinecraftUpdate.cpp48
-rw-r--r--api/logic/minecraft/MinecraftUpdate.h8
-rw-r--r--api/logic/minecraft/Mod.cpp378
-rw-r--r--api/logic/minecraft/ModsModel.cpp374
-rw-r--r--api/logic/minecraft/ModsModel.h123
-rw-r--r--api/logic/minecraft/MojangVersionFormat.cpp12
-rw-r--r--api/logic/minecraft/MojangVersionFormat.h3
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.cpp60
-rw-r--r--api/logic/minecraft/OneSixVersionFormat.h11
-rw-r--r--api/logic/minecraft/OpSys.cpp2
-rw-r--r--api/logic/minecraft/OpSys.h2
-rw-r--r--api/logic/minecraft/PackProfile.cpp (renamed from api/logic/minecraft/ComponentList.cpp)157
-rw-r--r--api/logic/minecraft/PackProfile.h (renamed from api/logic/minecraft/ComponentList.h)22
-rw-r--r--api/logic/minecraft/PackProfile_p.h (renamed from api/logic/minecraft/ComponentList_p.h)4
-rw-r--r--api/logic/minecraft/ParseUtils_test.cpp2
-rw-r--r--api/logic/minecraft/Rule.cpp2
-rw-r--r--api/logic/minecraft/Rule.h2
-rw-r--r--api/logic/minecraft/SimpleModList.cpp367
-rw-r--r--api/logic/minecraft/VersionFile.cpp8
-rw-r--r--api/logic/minecraft/VersionFile.h5
-rw-r--r--api/logic/minecraft/VersionFilterData.cpp53
-rw-r--r--api/logic/minecraft/VersionFilterData.h5
-rw-r--r--api/logic/minecraft/World.cpp239
-rw-r--r--api/logic/minecraft/World.h34
-rw-r--r--api/logic/minecraft/WorldList.cpp28
-rw-r--r--api/logic/minecraft/WorldList.h10
-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.cpp8
-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/forge/ForgeXzDownload.cpp393
-rw-r--r--api/logic/minecraft/forge/ForgeXzDownload.h61
-rw-r--r--api/logic/minecraft/gameoptions/GameOptions.cpp144
-rw-r--r--api/logic/minecraft/gameoptions/GameOptions.h34
-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.cpp6
-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.cpp36
-rw-r--r--api/logic/minecraft/launch/ReconstructAssets.h33
-rw-r--r--api/logic/minecraft/launch/ScanModFolders.cpp59
-rw-r--r--api/logic/minecraft/launch/ScanModFolders.h42
-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.cpp10
-rw-r--r--api/logic/minecraft/legacy/LegacyInstance.h12
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.cpp51
-rw-r--r--api/logic/minecraft/legacy/LegacyModList.h13
-rw-r--r--api/logic/minecraft/legacy/LegacyUpgradeTask.cpp9
-rw-r--r--api/logic/minecraft/mod/LocalModParseTask.cpp467
-rw-r--r--api/logic/minecraft/mod/LocalModParseTask.h37
-rw-r--r--api/logic/minecraft/mod/Mod.cpp151
-rw-r--r--api/logic/minecraft/mod/Mod.h (renamed from api/logic/minecraft/Mod.h)115
-rw-r--r--api/logic/minecraft/mod/ModDetails.h17
-rw-r--r--api/logic/minecraft/mod/ModFolderLoadTask.cpp18
-rw-r--r--api/logic/minecraft/mod/ModFolderLoadTask.h29
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.cpp554
-rw-r--r--api/logic/minecraft/mod/ModFolderModel.h (renamed from api/logic/minecraft/SimpleModList.h)50
-rw-r--r--api/logic/minecraft/mod/ModFolderModel_test.cpp (renamed from api/logic/minecraft/SimpleModList_test.cpp)12
-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/AssetUpdateTask.cpp10
-rw-r--r--api/logic/minecraft/update/FMLLibrariesTask.cpp8
-rw-r--r--api/logic/minecraft/update/LibrariesTask.cpp62
-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/FileResolvingTask.cpp66
-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.cpp64
-rw-r--r--api/logic/modplatform/flame/PackManifest.h3
-rw-r--r--api/logic/modplatform/legacy_ftb/PackFetchTask.cpp (renamed from api/logic/modplatform/ftb/FtbPackFetchTask.cpp)46
-rw-r--r--api/logic/modplatform/legacy_ftb/PackFetchTask.h (renamed from api/logic/modplatform/ftb/FtbPackFetchTask.h)20
-rw-r--r--api/logic/modplatform/legacy_ftb/PackHelpers.h (renamed from api/logic/modplatform/ftb/PackHelpers.h)16
-rw-r--r--api/logic/modplatform/legacy_ftb/PackInstallTask.cpp (renamed from api/logic/modplatform/ftb/FtbPackInstallTask.cpp)56
-rw-r--r--api/logic/modplatform/legacy_ftb/PackInstallTask.h (renamed from api/logic/modplatform/ftb/FtbPackInstallTask.h)21
-rw-r--r--api/logic/modplatform/legacy_ftb/PrivatePackManager.cpp (renamed from api/logic/modplatform/ftb/FtbPrivatePackManager.cpp)10
-rw-r--r--api/logic/modplatform/legacy_ftb/PrivatePackManager.h (renamed from api/logic/modplatform/ftb/FtbPrivatePackManager.h)8
-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.cpp23
-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/net/URLConstants.cpp16
-rw-r--r--api/logic/net/URLConstants.h40
-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.cpp8
-rw-r--r--api/logic/screenshots/ImgurUpload.cpp8
-rw-r--r--api/logic/settings/INIFile.cpp18
-rw-r--r--api/logic/settings/INIFile.h2
-rw-r--r--api/logic/settings/INIFile_test.cpp3
-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.cpp8
-rw-r--r--api/logic/status/StatusChecker.h2
-rw-r--r--api/logic/tasks/Task.cpp58
-rw-r--r--api/logic/tasks/Task.h16
-rw-r--r--api/logic/tools/BaseProfiler.cpp3
-rw-r--r--api/logic/tools/BaseProfiler.h5
-rw-r--r--api/logic/tools/JProfiler.cpp4
-rw-r--r--api/logic/tools/JVisualVM.cpp4
-rw-r--r--api/logic/translations/POTranslator.cpp373
-rw-r--r--api/logic/translations/POTranslator.h16
-rw-r--r--api/logic/translations/TranslationsModel.cpp446
-rw-r--r--api/logic/translations/TranslationsModel.h14
-rw-r--r--api/logic/updater/DownloadTask.cpp11
-rw-r--r--api/logic/updater/DownloadTask.h2
-rw-r--r--api/logic/updater/DownloadTask_test.cpp19
-rw-r--r--api/logic/updater/GoUpdate.cpp23
-rw-r--r--api/logic/updater/GoUpdate.h9
-rw-r--r--api/logic/updater/UpdateChecker.cpp2
-rw-r--r--api/logic/updater/UpdateChecker.h2
-rw-r--r--api/logic/updater/UpdateChecker_test.cpp9
-rw-r--r--application/CMakeLists.txt82
-rw-r--r--application/HoeDown.h2
-rw-r--r--application/InstancePageProvider.h25
-rw-r--r--application/InstanceWindow.cpp16
-rw-r--r--application/InstanceWindow.h6
-rw-r--r--application/JavaCommon.cpp3
-rw-r--r--application/KonamiCode.cpp2
-rw-r--r--application/LaunchController.cpp66
-rw-r--r--application/LaunchController.h9
-rw-r--r--application/MainWindow.cpp231
-rw-r--r--application/MainWindow.h17
-rw-r--r--application/MultiMC.cpp214
-rw-r--r--application/MultiMC.h21
-rw-r--r--application/SettingsUI.h26
-rw-r--r--application/VersionProxyModel.cpp13
-rw-r--r--application/VersionProxyModel.h2
-rw-r--r--application/dialogs/AboutDialog.cpp85
-rw-r--r--application/dialogs/AboutDialog.h2
-rw-r--r--application/dialogs/AboutDialog.ui48
-rw-r--r--application/dialogs/CopyInstanceDialog.cpp21
-rw-r--r--application/dialogs/CopyInstanceDialog.h5
-rw-r--r--application/dialogs/CopyInstanceDialog.ui16
-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/EditAccountDialog.ui2
-rw-r--r--application/dialogs/ExportInstanceDialog.cpp62
-rw-r--r--application/dialogs/ExportInstanceDialog.h2
-rw-r--r--application/dialogs/IconPickerDialog.cpp7
-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.ui16
-rw-r--r--application/dialogs/ModEditDialogCommon.cpp40
-rw-r--r--application/dialogs/ModEditDialogCommon.h9
-rw-r--r--application/dialogs/NewComponentDialog.cpp2
-rw-r--r--application/dialogs/NewComponentDialog.h2
-rw-r--r--application/dialogs/NewComponentDialog.ui2
-rw-r--r--application/dialogs/NewInstanceDialog.cpp31
-rw-r--r--application/dialogs/NewInstanceDialog.h5
-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.cpp1
-rw-r--r--application/dialogs/UpdateDialog.h2
-rw-r--r--application/dialogs/UpdateDialog.ui7
-rw-r--r--application/dialogs/VersionSelectDialog.cpp2
-rw-r--r--application/dialogs/VersionSelectDialog.h2
-rw-r--r--application/groupview/AccessibleGroupView.cpp778
-rw-r--r--application/groupview/AccessibleGroupView.h6
-rw-r--r--application/groupview/AccessibleGroupView_p.h118
-rw-r--r--application/groupview/GroupView.cpp69
-rw-r--r--application/groupview/GroupView.h15
-rw-r--r--application/groupview/GroupedProxyModel.cpp2
-rw-r--r--application/groupview/GroupedProxyModel.h2
-rw-r--r--application/groupview/InstanceDelegate.cpp95
-rw-r--r--application/groupview/InstanceDelegate.h19
-rw-r--r--application/groupview/VisualGroup.cpp2
-rw-r--r--application/groupview/VisualGroup.h2
-rw-r--r--application/main.cpp4
-rwxr-xr-xapplication/package/linux/multimc.desktop2
-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
-rwxr-xr-xapplication/package/ubuntu/multimc/opt/multimc/run.sh6
-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.cpp3
-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.cpp102
-rw-r--r--application/pages/global/AccountListPage.h34
-rw-r--r--application/pages/global/AccountListPage.ui186
-rw-r--r--application/pages/global/CustomCommandsPage.cpp1
-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/ExternalToolsPage.ui4
-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.cpp51
-rw-r--r--application/pages/global/LanguagePage.h60
-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.cpp26
-rw-r--r--application/pages/global/MultiMCPage.h4
-rw-r--r--application/pages/global/MultiMCPage.ui15
-rw-r--r--application/pages/global/PackagesPage.cpp224
-rw-r--r--application/pages/global/PackagesPage.h57
-rw-r--r--application/pages/global/PackagesPage.ui252
-rw-r--r--application/pages/global/PasteEEPage.cpp2
-rw-r--r--application/pages/global/PasteEEPage.h2
-rw-r--r--application/pages/global/ProxyPage.cpp8
-rw-r--r--application/pages/global/ProxyPage.h2
-rw-r--r--application/pages/instance/GameOptionsPage.cpp37
-rw-r--r--application/pages/instance/GameOptionsPage.h (renamed from application/pages/modplatform/TwitchPage.h)36
-rw-r--r--application/pages/instance/GameOptionsPage.ui88
-rw-r--r--application/pages/instance/InstanceSettingsPage.cpp91
-rw-r--r--application/pages/instance/InstanceSettingsPage.h4
-rw-r--r--application/pages/instance/InstanceSettingsPage.ui165
-rw-r--r--application/pages/instance/LegacyUpgradePage.cpp2
-rw-r--r--application/pages/instance/LegacyUpgradePage.h2
-rw-r--r--application/pages/instance/LogPage.cpp10
-rw-r--r--application/pages/instance/LogPage.h8
-rw-r--r--application/pages/instance/ModFolderPage.cpp205
-rw-r--r--application/pages/instance/ModFolderPage.h47
-rw-r--r--application/pages/instance/ModFolderPage.ui272
-rw-r--r--application/pages/instance/NewModFolderPage.cpp177
-rw-r--r--application/pages/instance/NewModFolderPage.h97
-rw-r--r--application/pages/instance/NewModFolderPage.ui180
-rw-r--r--application/pages/instance/NotesPage.cpp1
-rw-r--r--application/pages/instance/NotesPage.h2
-rw-r--r--application/pages/instance/NotesPage.ui40
-rw-r--r--application/pages/instance/OtherLogsPage.cpp2
-rw-r--r--application/pages/instance/OtherLogsPage.h2
-rw-r--r--application/pages/instance/ResourcePackPage.h6
-rw-r--r--application/pages/instance/ScreenshotsPage.cpp66
-rw-r--r--application/pages/instance/ScreenshotsPage.h19
-rw-r--r--application/pages/instance/ScreenshotsPage.ui159
-rw-r--r--application/pages/instance/ServersPage.cpp53
-rw-r--r--application/pages/instance/ServersPage.h28
-rw-r--r--application/pages/instance/ServersPage.ui341
-rw-r--r--application/pages/instance/TexturePackPage.h5
-rw-r--r--application/pages/instance/VersionPage.cpp248
-rw-r--r--application/pages/instance/VersionPage.h55
-rw-r--r--application/pages/instance/VersionPage.ui551
-rw-r--r--application/pages/instance/WorldListPage.cpp132
-rw-r--r--application/pages/instance/WorldListPage.h47
-rw-r--r--application/pages/instance/WorldListPage.ui291
-rw-r--r--application/pages/modplatform/ImportPage.cpp7
-rw-r--r--application/pages/modplatform/ImportPage.h2
-rw-r--r--application/pages/modplatform/TechnicPage.cpp26
-rw-r--r--application/pages/modplatform/TechnicPage.ui113
-rw-r--r--application/pages/modplatform/TwitchPage.cpp26
-rw-r--r--application/pages/modplatform/TwitchPage.ui35
-rw-r--r--application/pages/modplatform/VanillaPage.cpp16
-rw-r--r--application/pages/modplatform/VanillaPage.h2
-rw-r--r--application/pages/modplatform/VanillaPage.ui20
-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.cpp259
-rw-r--r--application/pages/modplatform/flame/FlameModel.h76
-rw-r--r--application/pages/modplatform/flame/FlamePage.cpp185
-rw-r--r--application/pages/modplatform/flame/FlamePage.h80
-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/ListModel.cpp (renamed from application/pages/modplatform/FtbListModel.cpp)70
-rw-r--r--application/pages/modplatform/legacy_ftb/ListModel.h (renamed from application/pages/modplatform/FtbListModel.h)31
-rw-r--r--application/pages/modplatform/legacy_ftb/Page.cpp (renamed from application/pages/modplatform/FTBPage.cpp)185
-rw-r--r--application/pages/modplatform/legacy_ftb/Page.h (renamed from application/pages/modplatform/FTBPage.h)61
-rw-r--r--application/pages/modplatform/legacy_ftb/Page.ui (renamed from application/pages/modplatform/FTBPage.ui)13
-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.h (renamed from application/pages/modplatform/TechnicPage.h)19
-rw-r--r--application/pages/modplatform/technic/TechnicPage.ui95
-rw-r--r--application/resources/MultiMC.icobin85182 -> 55224 bytes
-rw-r--r--application/resources/OSX/OSX.qrc1
-rw-r--r--application/resources/OSX/scalable/language.svg40
-rw-r--r--application/resources/assets/assets.qrc7
-rw-r--r--application/resources/assets/deadglitch.svg66
-rw-r--r--application/resources/backgrounds/backgrounds.qrc1
-rw-r--r--application/resources/backgrounds/catbgrnd2.pngbin78285 -> 62973 bytes
-rw-r--r--application/resources/backgrounds/catmas.pngbin0 -> 72818 bytes
-rw-r--r--application/resources/flat/flat.qrc1
-rw-r--r--application/resources/flat/scalable/language.svg103
-rw-r--r--application/resources/iOS/iOS.qrc1
-rw-r--r--application/resources/iOS/scalable/language.svg32
-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/instances/brick.pngbin713 -> 2388 bytes
-rw-r--r--application/resources/multimc/32x32/instances/diamond.pngbin708 -> 2444 bytes
-rw-r--r--application/resources/multimc/32x32/instances/gold.pngbin978 -> 2366 bytes
-rw-r--r--application/resources/multimc/32x32/instances/iron.pngbin532 -> 1772 bytes
-rw-r--r--application/resources/multimc/32x32/instances/planks.pngbin461 -> 2299 bytes
-rw-r--r--application/resources/multimc/32x32/instances/stone.pngbin438 -> 1866 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/index.theme7
-rw-r--r--application/resources/multimc/multimc.qrc15
-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/instances/bee.svg159
-rw-r--r--application/resources/multimc/scalable/instances/fox.svg290
-rw-r--r--application/resources/multimc/scalable/language.svg109
-rw-r--r--application/resources/multimc/scalable/technic.svg14
-rw-r--r--application/resources/multimc/scalable/twitch.svg63
-rw-r--r--application/resources/pe_blue/pe_blue.qrc1
-rw-r--r--application/resources/pe_blue/scalable/language.svg46
-rw-r--r--application/resources/pe_colored/pe_colored.qrc1
-rw-r--r--application/resources/pe_colored/scalable/language.svg44
-rw-r--r--application/resources/pe_dark/pe_dark.qrc1
-rw-r--r--application/resources/pe_dark/scalable/language.svg45
-rw-r--r--application/resources/pe_light/pe_light.qrc1
-rw-r--r--application/resources/pe_light/scalable/language.svg80
-rw-r--r--application/setupwizard/LanguageWizardPage.cpp34
-rw-r--r--application/setupwizard/LanguageWizardPage.h9
-rw-r--r--application/setupwizard/SetupWizard.h2
-rw-r--r--application/themes/SystemTheme.cpp5
-rw-r--r--application/widgets/CustomCommands.cpp1
-rw-r--r--application/widgets/CustomCommands.h4
-rw-r--r--application/widgets/CustomCommands.ui15
-rw-r--r--application/widgets/DropLabel.cpp41
-rw-r--r--application/widgets/DropLabel.h20
-rw-r--r--application/widgets/InstanceCardWidget.ui3
-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.cpp66
-rw-r--r--application/widgets/LanguageSelectionWidget.h41
-rw-r--r--application/widgets/MCModInfoFrame.cpp5
-rw-r--r--application/widgets/MCModInfoFrame.h4
-rw-r--r--application/widgets/ModListView.cpp2
-rw-r--r--application/widgets/ModListView.h4
-rw-r--r--application/widgets/PageContainer.cpp13
-rw-r--r--application/widgets/PageContainer.h2
-rw-r--r--application/widgets/PageContainer_p.h2
-rw-r--r--application/widgets/ServerStatus.cpp4
-rw-r--r--application/widgets/VersionListView.cpp107
-rw-r--r--application/widgets/VersionListView.h7
-rw-r--r--application/widgets/VersionSelectWidget.cpp1
-rw-r--r--application/widgets/VersionSelectWidget.h2
-rw-r--r--application/widgets/WideBar.cpp116
-rw-r--r--application/widgets/WideBar.h26
-rw-r--r--buildconfig/BuildConfig.cpp.in (renamed from application/BuildConfig.cpp.in)8
-rw-r--r--buildconfig/BuildConfig.h (renamed from application/BuildConfig.h)38
-rw-r--r--buildconfig/CMakeLists.txt11
-rw-r--r--changelog.md377
-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.java6
-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
m---------libraries/libnbtplusplus0
-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/pack200/CMakeLists.txt50
-rw-r--r--libraries/pack200/LICENSE347
-rw-r--r--libraries/pack200/anti200.cpp43
-rw-r--r--libraries/pack200/include/unpack200.h37
-rw-r--r--libraries/pack200/src/bands.cpp423
-rw-r--r--libraries/pack200/src/bands.h489
-rw-r--r--libraries/pack200/src/bytes.cpp217
-rw-r--r--libraries/pack200/src/bytes.h286
-rw-r--r--libraries/pack200/src/coding.cpp1044
-rw-r--r--libraries/pack200/src/coding.h247
-rw-r--r--libraries/pack200/src/constants.h442
-rw-r--r--libraries/pack200/src/defines.h65
-rw-r--r--libraries/pack200/src/unpack.cpp4790
-rw-r--r--libraries/pack200/src/unpack.h549
-rw-r--r--libraries/pack200/src/unpack200.cpp162
-rw-r--r--libraries/pack200/src/utils.cpp71
-rw-r--r--libraries/pack200/src/utils.h53
-rw-r--r--libraries/pack200/src/zip.cpp589
-rw-r--r--libraries/pack200/src/zip.h112
-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
-rw-r--r--travis/prepare.sh50
573 files changed, 21813 insertions, 16879 deletions
diff --git a/.arcconfig b/.arcconfig
deleted file mode 100644
index ab5cbe89..00000000
--- a/.arcconfig
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "project_id": "MultiMC5",
- "conduit_uri": "http://ph.multimc.org"
-}
-
diff --git a/.clang-format b/.clang-format
deleted file mode 100644
index f4732803..00000000
--- a/.clang-format
+++ /dev/null
@@ -1,25 +0,0 @@
-UseTab: false
-IndentWidth: 4
-TabWidth: 4
-ConstructorInitializerIndentWidth: 4
-AccessModifierOffset: -4
-IndentCaseLabels: false
-IndentFunctionDeclarationAfterType: false
-NamespaceIndentation: None
-
-BreakBeforeBraces: Allman
-AllowShortIfStatementsOnASingleLine: false
-AllowShortFunctionsOnASingleLine: None
-ColumnLimit: 160
-MaxEmptyLinesToKeep: 1
-
-Standard: Cpp11
-Cpp11BracedListStyle: true
-
-SpacesInParentheses: false
-SpaceInEmptyParentheses: false
-SpacesInCStyleCastParentheses: false
-SpaceAfterControlStatementKeyword: true
-
-AlignTrailingComments: true
-SpacesBeforeTrailingComments: 1
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/.gitignore b/.gitignore
index d49ca85b..496c382e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
Thumbs.db
-.kdev4
+*.kdev4
.user
.directory
resources/CMakeFiles
@@ -9,12 +9,13 @@ resources/MultiMCLauncher.jar
html/
# Project Files
-MultiMC5.kdev4
-MultiMC.pro.user
+*.pro.user
CMakeLists.txt.user
CMakeLists.txt.user.*
/.project
/.settings
+/.idea
+cmake-build-*/
# Build dirs
build
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 19f39c2f..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-# General set up
-language: cpp
-cache: apt
-
-matrix:
- include:
- - os: linux
- dist: precise
- sudo: required
- compiler: gcc
- env: TRAVIS_DIST=precise QT_VERSION=5.4.2
- - os: linux
- dist: precise
- sudo: required
- compiler: gcc
- env: TRAVIS_DIST=precise QT_VERSION=5.6.2
- - os: linux
- dist: trusty
- sudo: required
- compiler: gcc
- env: TRAVIS_DIST=trusty QT_VERSION=5.4.2
- - os: linux
- dist: trusty
- sudo: required
- compiler: gcc
- env: TRAVIS_DIST=trusty QT_VERSION=5.6.2
-
-# Install dependencies
-install:
- - source travis/prepare.sh # installs qt and cmake. need to source because some env vars are set from there
-
-# Actual work
-before_script:
- - mkdir build
- - cd build
- - cmake -DCMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH ..
-script:
- - make -j4 && make test ARGS="-V"
diff --git a/BUILD.md b/BUILD.md
index d347ba14..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
```
@@ -50,7 +50,7 @@ mkdir ~/MultiMC && cd ~/MultiMC
mkdir build
mkdir install
# clone the complete source
-git clone --recursive git@github.com:MultiMC/MultiMC5.git src
+git clone --recursive https://github.com/MultiMC/MultiMC5.git src
# configure the project
cd build
cmake -DCMAKE_INSTALL_PREFIX=../install ../src
@@ -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 submodule init
-git submodule update
+git clone --recursive https://github.com/MultiMC/MultiMC5.git
cd MultiMC5
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 e920708a..5e3d6cea 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,15 @@ cmake_minimum_required(VERSION 3.1)
string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BUILD_DIR}" IS_IN_SOURCE_BUILD)
if(IS_IN_SOURCE_BUILD)
- message(AUTHOR_WARNING "You are building MultiMC in-source. This is NOT recommended!")
+ message(FATAL_ERROR "You are building MultiMC in-source. Please separate the build tree from the source tree.")
+endif()
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ if(CMAKE_HOST_SYSTEM_VERSION MATCHES ".*[Mm]icrosoft.*" OR
+ CMAKE_HOST_SYSTEM_VERSION MATCHES ".*WSL.*"
+ )
+ message(FATAL_ERROR "Building MultiMC is not supported in Linux-on-Windows distributions.")
+ endif()
endif()
if(WIN32)
@@ -13,6 +21,7 @@ endif()
project(MultiMC)
enable_testing()
+
##################################### Set CMake options #####################################
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -32,7 +41,7 @@ set(CMAKE_C_STANDARD_REQUIRED true)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_C_STANDARD 11)
include(GenerateExportHeader)
-set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
+set(CMAKE_CXX_FLAGS " -Wall -pedantic -Werror -Wno-deprecated-declarations -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 -O3 -D_FORTIFY_SOURCE=2 ${CMAKE_CXX_FLAGS}")
if(UNIX AND APPLE)
set(CMAKE_CXX_FLAGS " -stdlib=libc++ ${CMAKE_CXX_FLAGS}")
endif()
@@ -41,12 +50,12 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror=return-type")
##################################### Set Application options #####################################
######## Set URLs ########
-set(MultiMC_NEWS_RSS_URL "http://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
+set(MultiMC_NEWS_RSS_URL "https://multimc.org/rss.xml" CACHE STRING "URL to fetch MultiMC's news RSS feed from.")
######## Set version numbers ########
set(MultiMC_VERSION_MAJOR 0)
set(MultiMC_VERSION_MINOR 6)
-set(MultiMC_VERSION_HOTFIX 2)
+set(MultiMC_VERSION_HOTFIX 12)
# Build number
set(MultiMC_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.")
@@ -60,12 +69,27 @@ set(MultiMC_CHANLIST_URL "" CACHE STRING "URL for the channel list.")
# Notification URL
set(MultiMC_NOTIFICATION_URL "" CACHE STRING "URL for checking for notifications.")
+# The metadata server
+set(MultiMC_META_URL "https://meta.multimc.org/v1/" CACHE STRING "URL to fetch MultiMC's meta files from.")
+
# 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)
@@ -77,6 +101,7 @@ set(MultiMC_RELEASE_VERSION_NAME "${MultiMC_VERSION_MAJOR}.${MultiMC_VERSION_MIN
#### Custom target to just print the version.
add_custom_target(version echo "Version: ${MultiMC_RELEASE_VERSION_NAME}")
+add_custom_target(tcversion echo "\\#\\#teamcity[setParameter name=\\'env.MULTIMC_VERSION\\' value=\\'${MultiMC_RELEASE_VERSION_NAME}\\']")
################################ 3rd Party Libs ################################
@@ -141,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-2018 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})
@@ -247,14 +272,16 @@ add_subdirectory(libraries/launcher) # java based launcher part for Minecraft
add_subdirectory(libraries/javacheck) # java compatibility checker
add_subdirectory(libraries/xz-embedded) # xz compression
add_subdirectory(libraries/quazip) # zip manipulation library
-add_subdirectory(libraries/pack200) # java pack200 compression
add_subdirectory(libraries/rainbow) # Qt extension for colors
add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader
add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions
add_subdirectory(libraries/classparser) # google analytics library
+add_subdirectory(libraries/optional-bare)
+add_subdirectory(libraries/tomlc99) # toml parser
############################### Built Artifacts ###############################
+add_subdirectory(buildconfig)
add_subdirectory(api/logic)
add_subdirectory(api/gui)
diff --git a/COPYING.md b/COPYING.md
index 21c773b7..caa4bed5 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -1,6 +1,6 @@
# MultiMC
- Copyright 2012-2018 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
@@ -128,35 +128,6 @@
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-# Pack200
-
- The GNU General Public License (GPL)
-
- Version 2, June 1991
-
- + "CLASSPATH" EXCEPTION TO THE GPL
-
- Certain source files distributed by Oracle America and/or its affiliates are
- subject to the following clarification and special exception to the GPL, but
- only where Oracle has expressly included in the particular source file's header
- the words "Oracle designates this particular file as subject to the "Classpath"
- exception as provided by Oracle in the LICENSE file that accompanied this code."
-
- Linking this library statically or dynamically with other modules is making
- a combined work based on this library. Thus, the terms and conditions of
- the GNU General Public License cover the whole combination.
-
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent modules,
- and to copy and distribute the resulting executable under terms of your
- choice, provided that you also meet, for each linked independent module,
- the terms and conditions of the license of that module. An independent
- module is a module which is not derived from or based on this library. If
- you modify this library, you may extend this exception to your version of
- the library, but you are not obligated to do so. If you do not wish to do
- so, delete this exception statement from your version.
-
# Quazip
Copyright (C) 2005-2011 Sergey A. Tachenov
@@ -252,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 db76dfa2..ede8f88f 100644
--- a/README.md
+++ b/README.md
@@ -13,21 +13,22 @@ 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 [workflowy](https://workflowy.com/s/2EyDMcp7CU) - there are many.
+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.
-The ci server is running at [ci.multimc.org](http://ci.multimc.org), where you can watch the builds happen in (or very close to) real time. It can also serve as a nice reference on how to set up the build environment on your end.
-
-According to travis.ci, the builds are currently [![Build Status](https://travis-ci.org/MultiMC/MultiMC5.svg?branch=develop)](https://travis-ci.org/MultiMC/MultiMC5)
-
### Code formatting
-We use [Clang Format](http://clang.llvm.org/docs/ClangFormat.html) to format the project. We highly recommend setting it up so the project stays well formatted.
+Just follow the existing formatting.
+
+In general:
+* Indent with 4 space unless it's in a submodule
+* Keep lists (of arguments, parameters, initializators...) as lists, not paragraphs.
+* Prefer readability over dogma.
## Translations
-Translations can be done either directly in the [translations repository](https://github.com/MultiMC/MultiMC5). For more details, see: [Translating-MultiMC](https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC).
+Translations can be done [on crowdin](https://translate.multimc.org).
## Forking/Redistributing
We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.
@@ -38,8 +39,41 @@ Apache covers reasonable use for the name - a mention of the project's origins i
## License
-Copyright &copy; 2013-2018 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).
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.
+
+## Build status
+### Linux (Intel32)
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux32_Build&guest=1">
+Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux32_Build)/statusIcon"/>
+</a>
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux32_Deploy&guest=1">
+Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux32_Deploy)/statusIcon"/>
+</a>
+
+### Linux (AMD64)
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux64_Build&guest=1">
+Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux64_Build)/statusIcon"/>
+</a>
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Linux64_Deploy&guest=1">
+Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Linux64_Deploy)/statusIcon"/>
+</a>
+
+### macOS (AMD64)
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_MacOS_Build&guest=1">
+Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_MacOS_Build)/statusIcon"/>
+</a>
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_MacOS_Deploy&guest=1">
+Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_MacOS_Deploy)/statusIcon"/>
+</a>
+
+### Windows (Intel32)
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Windows_Build&guest=1">
+Build: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Windows_Build)/statusIcon"/>
+</a>
+<a href="https://teamcity.multimc.org/viewType.html?buildTypeId=MultiMC_Launcher_Windows_Deploy&guest=1">
+Deploy: <img src="https://teamcity.multimc.org/app/rest/builds/buildType:(id:MultiMC_Launcher_Windows_Deploy)/statusIcon"/>
+</a>
diff --git a/api/gui/SkinUtils.cpp b/api/gui/SkinUtils.cpp
index 79edb4b9..ec969889 100644
--- a/api/gui/SkinUtils.cpp
+++ b/api/gui/SkinUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,6 +18,7 @@
#include "Env.h"
#include <QFile>
+#include <QPainter>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
@@ -35,10 +36,14 @@ QPixmap getFaceFromCache(QString username, int height, int width)
if (fskin.exists())
{
- QPixmap skin(fskin.fileName());
- if(!skin.isNull())
+ QPixmap skinTexture(fskin.fileName());
+ if(!skinTexture.isNull())
{
- return skin.copy(8, 8, 8, 8).scaled(height, width, Qt::KeepAspectRatio);
+ QPixmap skin = QPixmap(8, 8);
+ QPainter painter(&skin);
+ painter.drawPixmap(0, 0, skinTexture.copy(8, 8, 8, 8));
+ painter.drawPixmap(0, 0, skinTexture.copy(40, 8, 8, 8));
+ return skin.scaled(height, width, Qt::KeepAspectRatio);
}
}
diff --git a/api/gui/SkinUtils.h b/api/gui/SkinUtils.h
index f042b908..b44f4228 100644
--- a/api/gui/SkinUtils.h
+++ b/api/gui/SkinUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 5bf5f5e7..70350534 100644
--- a/api/gui/icons/IconList.cpp
+++ b/api/gui/icons/IconList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,8 +187,7 @@ Qt::DropActions IconList::supportedDropActions() const
return Qt::CopyAction;
}
-bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
- const QModelIndex &parent)
+bool IconList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
return true;
diff --git a/api/gui/icons/IconList.h b/api/gui/icons/IconList.h
index 97ed3956..f07415fa 100644
--- a/api/gui/icons/IconList.h
+++ b/api/gui/icons/IconList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -80,7 +80,7 @@ protected slots:
void fileChanged(const QString &path);
void SettingChanged(const Setting & setting, QVariant value);
private:
- std::shared_ptr<QFileSystemWatcher> m_watcher;
+ shared_qobject_ptr<QFileSystemWatcher> m_watcher;
bool is_watching;
QMap<QString, int> name_index;
QVector<MMCIcon> icons;
diff --git a/api/gui/icons/MMCIcon.cpp b/api/gui/icons/MMCIcon.cpp
index a5ab548e..f0b82ec9 100644
--- a/api/gui/icons/MMCIcon.cpp
+++ b/api/gui/icons/MMCIcon.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -102,3 +102,17 @@ void MMCIcon::replace(IconType new_type, const QString& key)
m_images[new_type].filename = QString();
m_images[new_type].key = key;
}
+
+QString MMCIcon::getFilePath() const
+{
+ if(m_current_type == IconType::ToBeDeleted){
+ return QString();
+ }
+ return m_images[m_current_type].filename;
+}
+
+
+bool MMCIcon::isBuiltIn() const
+{
+ return m_current_type == IconType::Builtin;
+}
diff --git a/api/gui/icons/MMCIcon.h b/api/gui/icons/MMCIcon.h
index d7618bee..fd14b5b7 100644
--- a/api/gui/icons/MMCIcon.h
+++ b/api/gui/icons/MMCIcon.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -46,4 +46,6 @@ struct MULTIMC_GUI_EXPORT MMCIcon
void remove(IconType rm_type);
void replace(IconType new_type, QIcon icon, QString path = QString());
void replace(IconType new_type, const QString &key);
+ bool isBuiltIn() const;
+ QString getFilePath() const;
};
diff --git a/api/logic/BaseInstaller.cpp b/api/logic/BaseInstaller.cpp
index e25683a9..d61c3fe9 100644
--- a/api/logic/BaseInstaller.cpp
+++ b/api/logic/BaseInstaller.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 121d35ef..3e40b355 100644
--- a/api/logic/BaseInstaller.h
+++ b/api/logic/BaseInstaller.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 7ddcc549..46b45827 100644
--- a/api/logic/BaseInstance.cpp
+++ b/api/logic/BaseInstance.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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
@@ -175,11 +196,6 @@ QString BaseInstance::instanceRoot() const
return m_rootDir;
}
-InstancePtr BaseInstance::getSharedPtr()
-{
- return shared_from_this();
-}
-
SettingsObjectPtr BaseInstance::settings() const
{
return m_settings;
@@ -253,7 +269,7 @@ QStringList BaseInstance::extraArguments() const
return Commandline::splitArgs(settings()->get("JvmArgs").toString());
}
-std::shared_ptr<LaunchTask> BaseInstance::getLaunchTask()
+shared_qobject_ptr<LaunchTask> BaseInstance::getLaunchTask()
{
return m_launchProcess;
}
diff --git a/api/logic/BaseInstance.h b/api/logic/BaseInstance.h
index 04788eb7..d250e03e 100644
--- a/api/logic/BaseInstance.h
+++ b/api/logic/BaseInstance.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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
@@ -134,8 +137,6 @@ public:
/// Sets the last launched time to 'val' milliseconds since epoch
void setLastLaunch(qint64 val = QDateTime::currentMSecsSinceEpoch());
- InstancePtr getSharedPtr();
-
/*!
* \brief Gets this instance's settings object.
* This settings object stores instance-specific settings.
@@ -147,10 +148,11 @@ public:
virtual shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) = 0;
/// returns a valid launcher (task container)
- virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) = 0;
+ virtual shared_qobject_ptr<LaunchTask> createLaunchTask(
+ AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) = 0;
/// returns the current launch task (if any)
- std::shared_ptr<LaunchTask> getLaunchTask();
+ shared_qobject_ptr<LaunchTask> getLaunchTask();
/*!
* Create envrironment variables for running the instance
@@ -223,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;
@@ -241,7 +243,7 @@ signals:
*/
void propertiesChanged(BaseInstance *inst);
- void launchTaskChanged(std::shared_ptr<LaunchTask>);
+ void launchTaskChanged(shared_qobject_ptr<LaunchTask>);
void runningStatusChanged(bool running);
@@ -255,7 +257,7 @@ protected: /* data */
SettingsObjectPtr m_settings;
// InstanceFlags m_flags;
bool m_isRunning = false;
- std::shared_ptr<LaunchTask> m_launchProcess;
+ shared_qobject_ptr<LaunchTask> m_launchProcess;
QDateTime m_timeStarted;
private: /* data */
@@ -265,6 +267,6 @@ private: /* data */
bool m_hasBrokenVersion = false;
};
-Q_DECLARE_METATYPE(std::shared_ptr<BaseInstance>)
+Q_DECLARE_METATYPE(shared_qobject_ptr<BaseInstance>)
//Q_DECLARE_METATYPE(BaseInstance::InstanceFlag)
//Q_DECLARE_OPERATORS_FOR_FLAGS(BaseInstance::InstanceFlags)
diff --git a/api/logic/BaseVersion.h b/api/logic/BaseVersion.h
index 4f06c3bc..b88105fb 100644
--- a/api/logic/BaseVersion.h
+++ b/api/logic/BaseVersion.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 b1f73529..aa9cb6cf 100644
--- a/api/logic/BaseVersionList.cpp
+++ b/api/logic/BaseVersionList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 a70a414c..29e21bdb 100644
--- a/api/logic/BaseVersionList.h
+++ b/api/logic/BaseVersionList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 5d20b7a9..6d269714 100644
--- a/api/logic/CMakeLists.txt
+++ b/api/logic/CMakeLists.txt
@@ -119,13 +119,13 @@ set(NET_SOURCES
net/PasteUpload.cpp
net/PasteUpload.h
net/Sink.h
- net/URLConstants.cpp
- net/URLConstants.h
net/Validator.h
)
# 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
@@ -182,9 +182,11 @@ set(NEWS_SOURCES
# Icon interface
set(ICONS_SOURCES
- # News System
+ # Icons System and related code
icons/IIconList.h
icons/IIconList.cpp
+ icons/IconUtils.h
+ icons/IconUtils.cpp
)
# Minecraft services status checker
@@ -211,6 +213,10 @@ set(MINECRAFT_SOURCES
minecraft/auth/flows/RefreshTask.cpp
minecraft/auth/flows/ValidateTask.h
minecraft/auth/flows/ValidateTask.cpp
+
+ minecraft/gameoptions/GameOptions.h
+ minecraft/gameoptions/GameOptions.cpp
+
minecraft/update/AssetUpdateTask.h
minecraft/update/AssetUpdateTask.cpp
minecraft/update/FMLLibrariesTask.cpp
@@ -219,10 +225,11 @@ set(MINECRAFT_SOURCES
minecraft/update/FoldersTask.h
minecraft/update/LibrariesTask.cpp
minecraft/update/LibrariesTask.h
+
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
@@ -231,14 +238,24 @@ 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
minecraft/legacy/LegacyInstance.h
minecraft/legacy/LegacyInstance.cpp
minecraft/legacy/LegacyUpgradeTask.h
minecraft/legacy/LegacyUpgradeTask.cpp
+
minecraft/GradleSpecifier.h
minecraft/MinecraftInstance.cpp
minecraft/MinecraftInstance.h
@@ -246,8 +263,8 @@ set(MINECRAFT_SOURCES
minecraft/LaunchProfile.h
minecraft/Component.cpp
minecraft/Component.h
- minecraft/ComponentList.cpp
- minecraft/ComponentList.h
+ minecraft/PackProfile.cpp
+ minecraft/PackProfile.h
minecraft/ComponentUpdateTask.cpp
minecraft/ComponentUpdateTask.h
minecraft/MinecraftLoadAndCheck.h
@@ -273,28 +290,37 @@ set(MINECRAFT_SOURCES
minecraft/VersionFile.h
minecraft/VersionFilterData.h
minecraft/VersionFilterData.cpp
- minecraft/Mod.h
- minecraft/Mod.cpp
- minecraft/ModsModel.h
- minecraft/ModsModel.cpp
- minecraft/SimpleModList.h
- minecraft/SimpleModList.cpp
minecraft/World.h
minecraft/World.cpp
minecraft/WorldList.h
minecraft/WorldList.cpp
+ minecraft/mod/Mod.h
+ minecraft/mod/Mod.cpp
+ minecraft/mod/ModDetails.h
+ minecraft/mod/ModFolderModel.h
+ minecraft/mod/ModFolderModel.cpp
+ minecraft/mod/ModFolderLoadTask.h
+ 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
- # Forge and all things forge related
- minecraft/forge/ForgeXzDownload.h
- minecraft/forge/ForgeXzDownload.cpp
+ # Minecraft services
+ minecraft/services/SkinUpload.cpp
+ minecraft/services/SkinUpload.h
+ minecraft/services/SkinDelete.cpp
+ minecraft/services/SkinDelete.h
- # Skin upload utilities
- minecraft/SkinUpload.cpp
- minecraft/SkinUpload.h
+ mojang/PackageManifest.h
+ mojang/PackageManifest.cpp
)
add_unit_test(GradleSpecifier
@@ -302,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
@@ -314,8 +356,8 @@ add_unit_test(Library
)
# FIXME: shares data with FileSystem test
-add_unit_test(SimpleModList
- SOURCES minecraft/SimpleModList_test.cpp
+add_unit_test(ModFolderModel
+ SOURCES minecraft/mod/ModFolderModel_test.cpp
DATA testdata
LIBS MultiMC_logic
)
@@ -389,6 +431,8 @@ add_unit_test(JavaVersion
set(TRANSLATIONS_SOURCES
translations/TranslationsModel.h
translations/TranslationsModel.cpp
+ translations/POTranslator.h
+ translations/POTranslator.cpp
)
set(TOOLS_SOURCES
@@ -420,25 +464,51 @@ set(META_SOURCES
)
set(FTB_SOURCES
- modplatform/ftb/FtbPackFetchTask.h
- modplatform/ftb/FtbPackFetchTask.cpp
- modplatform/ftb/FtbPackInstallTask.h
- modplatform/ftb/FtbPackInstallTask.cpp
-
- modplatform/ftb/FtbPrivatePackManager.h
- modplatform/ftb/FtbPrivatePackManager.cpp
-
- modplatform/ftb/PackHelpers.h
+ modplatform/legacy_ftb/PackFetchTask.h
+ modplatform/legacy_ftb/PackFetchTask.cpp
+ modplatform/legacy_ftb/PackInstallTask.h
+ modplatform/legacy_ftb/PackInstallTask.cpp
+ modplatform/legacy_ftb/PrivatePackManager.h
+ modplatform/legacy_ftb/PrivatePackManager.cpp
+
+ modplatform/legacy_ftb/PackHelpers.h
)
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
@@ -469,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})
@@ -477,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 xz-embedded MultiMC_unpack200 systeminfo MultiMC_quazip MultiMC_classparser ${NBT_NAME} ${ZLIB_LIBRARIES})
+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 81ae7d0b..2c0fde64 100644
--- a/api/logic/Commandline.cpp
+++ b/api/logic/Commandline.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 586d644f..09c1707e 100644
--- a/api/logic/Commandline.h
+++ b/api/logic/Commandline.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -69,8 +69,8 @@ namespace ArgumentStyle
{
enum Enum
{
- Space, /**< --option=value */
- Equals, /**< --option value */
+ Space, /**< --option value */
+ Equals, /**< --option=value */
SpaceAndEquals, /**< --option[= ]value */
#ifdef Q_OS_WIN32
Default = Equals
diff --git a/api/logic/Env.cpp b/api/logic/Env.cpp
index 77546bbc..71b49d95 100644
--- a/api/logic/Env.cpp
+++ b/api/logic/Env.cpp
@@ -96,7 +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("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());
@@ -159,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/FileSystem.cpp b/api/logic/FileSystem.cpp
index 192d868b..13f05b86 100644
--- a/api/logic/FileSystem.cpp
+++ b/api/logic/FileSystem.cpp
@@ -174,6 +174,11 @@ bool copy::operator()(const QString &offset)
bool deletePath(QString path)
{
bool OK = true;
+ QFileInfo finfo(path);
+ if(finfo.isFile()) {
+ return QFile::remove(path);
+ }
+
QDir dir(path);
if (!dir.exists())
@@ -294,7 +299,7 @@ QString NormalizePath(QString path)
}
}
-QString badFilenameChars = "\"\\/?<>:*|!+\r\n";
+QString badFilenameChars = "\"\\/?<>:;*|!+\r\n";
QString RemoveInvalidFilenameChars(QString string, QChar replaceWith)
{
diff --git a/api/logic/InstanceCopyTask.cpp b/api/logic/InstanceCopyTask.cpp
index a576a0fd..35adeaf9 100644
--- a/api/logic/InstanceCopyTask.cpp
+++ b/api/logic/InstanceCopyTask.cpp
@@ -5,9 +5,10 @@
#include "pathmatcher/RegexpMatcher.h"
#include <QtConcurrentRun>
-InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves)
+InstanceCopyTask::InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime)
{
m_origInstance = origInstance;
+ m_keepPlaytime = keepPlaytime;
if(!copySaves)
{
@@ -46,6 +47,9 @@ void InstanceCopyTask::copyFinished()
InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath));
inst->setName(m_instName);
inst->setIconKey(m_instIcon);
+ if(!m_keepPlaytime) {
+ inst->resetTimePlayed();
+ }
emitSucceeded();
}
diff --git a/api/logic/InstanceCopyTask.h b/api/logic/InstanceCopyTask.h
index 8dd55b40..6465e92d 100644
--- a/api/logic/InstanceCopyTask.h
+++ b/api/logic/InstanceCopyTask.h
@@ -15,7 +15,7 @@ class MULTIMC_LOGIC_EXPORT InstanceCopyTask : public InstanceTask
{
Q_OBJECT
public:
- explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves);
+ explicit InstanceCopyTask(InstancePtr origInstance, bool copySaves, bool keepPlaytime);
protected:
//! Entry point for tasks.
@@ -28,4 +28,5 @@ private: /* data */
QFuture<bool> m_copyFuture;
QFutureWatcher<bool> m_copyFutureWatcher;
std::unique_ptr<IPathMatcher> m_matcher;
+ bool m_keepPlaytime;
};
diff --git a/api/logic/InstanceCreationTask.cpp b/api/logic/InstanceCreationTask.cpp
index 978d4687..eafc5126 100644
--- a/api/logic/InstanceCreationTask.cpp
+++ b/api/logic/InstanceCreationTask.cpp
@@ -4,7 +4,7 @@
//FIXME: remove this
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version)
{
@@ -20,7 +20,7 @@ void InstanceCreationTask::executeTask()
instanceSettings->registerSetting("InstanceType", "Legacy");
instanceSettings->set("InstanceType", "OneSix");
MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath);
- auto components = inst.getComponentList();
+ auto components = inst.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_version->descriptor(), true);
inst.setName(m_instName);
diff --git a/api/logic/InstanceImportTask.cpp b/api/logic/InstanceImportTask.cpp
index bbab557a..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"
@@ -6,14 +21,17 @@
#include "NullInstance.h"
#include "settings/INISettingsObject.h"
#include "icons/IIconList.h"
+#include "icons/IconUtils.h"
#include <QtConcurrentRun>
// FIXME: this does not belong here, it's Minecraft/Flame specific
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#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)
{
@@ -22,8 +40,6 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl)
void InstanceImportTask::executeTask()
{
- InstancePtr newInstance;
-
if (m_sourceUrl.isLocalFile())
{
m_archivePath = m_sourceUrl.toLocalFile();
@@ -81,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())
@@ -90,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
@@ -97,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."));
@@ -114,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;
@@ -160,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;
@@ -211,6 +238,7 @@ void InstanceImportTask::processFlame()
}
QString forgeVersion;
+ QString fabricVersion;
for(auto &loader: pack.minecraft.modLoaders)
{
auto id = loader.id;
@@ -220,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));
}
@@ -235,7 +269,7 @@ void InstanceImportTask::processFlame()
mcVersion.remove(QRegExp("[.]+$"));
logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack."));
}
- auto components = instance.getComponentList();
+ auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", mcVersion, true);
if(!forgeVersion.isEmpty())
@@ -254,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);
@@ -287,7 +325,7 @@ void InstanceImportTask::processFlame()
qDebug() << info.fileName();
jarMods.push_back(info.absoluteFilePath());
}
- auto profile = instance.getComponentList();
+ auto profile = instance.getPackProfile();
profile->installJarMods(jarMods);
// nuke the original files
FS::deletePath(jarmodsPath);
@@ -370,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!!!
@@ -393,8 +439,9 @@ void InstanceImportTask::processMultiMC()
else
{
m_instIcon = instance.iconKey();
- auto importIconPath = FS::PathCombine(instance.instanceRoot(), m_instIcon + ".png");
- if (QFile::exists(importIconPath))
+
+ auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon);
+ if (!importIconPath.isNull() && QFile::exists(importIconPath))
{
// import icon
auto iconList = ENV.icons();
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 4a209454..02fae6ac 100644
--- a/api/logic/InstanceList.cpp
+++ b/api/logic/InstanceList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -95,10 +95,15 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
{
return pdata->id();
}
+ case Qt::EditRole:
case Qt::DisplayRole:
{
return pdata->name();
}
+ case Qt::AccessibleTextRole:
+ {
+ return tr("%1 Instance").arg(pdata->name());
+ }
case Qt::ToolTipRole:
{
return pdata->instanceRoot();
@@ -118,12 +123,32 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const
return QVariant();
}
+bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role)
+{
+ if (!index.isValid())
+ {
+ return false;
+ }
+ if(role != Qt::EditRole)
+ {
+ return false;
+ }
+ BaseInstance *pdata = static_cast<BaseInstance *>(index.internalPointer());
+ auto newName = value.toString();
+ if(pdata->name() == newName)
+ {
+ return true;
+ }
+ pdata->setName(newName);
+ return true;
+}
+
Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const
{
Qt::ItemFlags f;
if (index.isValid())
{
- f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+ f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
}
return f;
}
@@ -135,8 +160,8 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const
{
return GroupId();
}
- auto iter = m_groupMap.find(inst->id());
- if(iter != m_groupMap.end())
+ auto iter = m_instanceGroupIndex.find(inst->id());
+ if(iter != m_instanceGroupIndex.end())
{
return *iter;
}
@@ -153,8 +178,8 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
}
bool changed = false;
- auto iter = m_groupMap.find(inst->id());
- if(iter != m_groupMap.end())
+ auto iter = m_instanceGroupIndex.find(inst->id());
+ if(iter != m_instanceGroupIndex.end())
{
if(*iter != name)
{
@@ -165,19 +190,21 @@ void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name)
else
{
changed = true;
- m_groupMap[id] = name;
+ m_instanceGroupIndex[id] = name;
}
if(changed)
{
- m_groups.insert(name);
+ m_groupNameCache.insert(name);
+ auto idx = getInstIndex(inst.get());
+ emit dataChanged(index(idx), index(idx), {GroupRole});
saveGroupList();
}
}
QStringList InstanceList::getGroups()
{
- return m_groups.toList();
+ return m_groupNameCache.toList();
}
void InstanceList::deleteGroup(const QString& name)
@@ -190,7 +217,7 @@ void InstanceList::deleteGroup(const QString& name)
auto instGroupName = getInstanceGroup(instID);
if(instGroupName == name)
{
- m_groupMap.remove(instID);
+ m_instanceGroupIndex.remove(instID);
qDebug() << "Remove" << instID << "from group" << name;
removed = true;
auto idx = getInstIndex(instance.get());
@@ -206,16 +233,21 @@ void InstanceList::deleteGroup(const QString& name)
}
}
+bool InstanceList::isGroupCollapsed(const QString& group)
+{
+ return m_collapsedGroups.contains(group);
+}
+
void InstanceList::deleteInstance(const InstanceId& id)
{
auto inst = getInstanceById(id);
if(!inst)
{
- qDebug() << "Cannot delete instance" << id << " No such instance is present.";
+ qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?).";
return;
}
- if(m_groupMap.remove(id))
+ if(m_instanceGroupIndex.remove(id))
{
saveGroupList();
}
@@ -488,7 +520,7 @@ void InstanceList::saveGroupList()
WatchLock foo(m_watcher, m_instDir);
QString groupFileName = m_instDir + "/instgroups.json";
QMap<QString, QSet<QString>> reverseGroupMap;
- for (auto iter = m_groupMap.begin(); iter != m_groupMap.end(); iter++)
+ for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++)
{
QString id = iter.key();
QString group = iter.value();
@@ -521,7 +553,7 @@ void InstanceList::saveGroupList()
auto name = iter.key();
QJsonObject groupObj;
QJsonArray instanceArr;
- groupObj.insert("hidden", QJsonValue(QString("false")));
+ groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name)));
for (auto item : list)
{
instanceArr.append(QJsonValue(item));
@@ -545,7 +577,6 @@ void InstanceList::saveGroupList()
void InstanceList::loadGroupList()
{
qDebug() << "Will load group list now.";
- QSet<QString> groupSet;
QString groupFileName = m_instDir + "/instgroups.json";
@@ -596,7 +627,8 @@ void InstanceList::loadGroupList()
return;
}
- m_groupMap.clear();
+ QSet<QString> groupSet;
+ m_instanceGroupIndex.clear();
// Iterate through all the groups.
QJsonObject groupMapping = rootObj.value("groups").toObject();
@@ -607,37 +639,35 @@ void InstanceList::loadGroupList()
// If not an object, complain and skip to the next one.
if (!iter.value().isObject())
{
- qWarning() << QString("Group '%1' in the group list should "
- "be an object.")
- .arg(groupName)
- .toUtf8();
+ qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8();
continue;
}
QJsonObject groupObj = iter.value().toObject();
if (!groupObj.value("instances").isArray())
{
- qWarning() << QString("Group '%1' in the group list is invalid. "
- "It should contain an array "
- "called 'instances'.")
- .arg(groupName)
- .toUtf8();
+ qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8();
continue;
}
// keep a list/set of groups for choosing
groupSet.insert(groupName);
+ auto hidden = groupObj.value("hidden").toBool(false);
+ if(hidden) {
+ m_collapsedGroups.insert(groupName);
+ }
+
// Iterate through the list of instances in the group.
QJsonArray instancesArray = groupObj.value("instances").toArray();
for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++)
{
- m_groupMap[(*iter2).toString()] = groupName;
+ m_instanceGroupIndex[(*iter2).toString()] = groupName;
}
}
m_groupsLoaded = true;
- m_groups.unite(groupSet);
+ m_groupNameCache.unite(groupSet);
qDebug() << "Group list loaded.";
}
@@ -662,6 +692,17 @@ void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value)
}
}
+void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed)
+{
+ qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded");
+ if(collapsed) {
+ m_collapsedGroups.insert(group);
+ } else {
+ m_collapsedGroups.remove(group);
+ }
+ saveGroupList();
+}
+
class InstanceStaging : public Task
{
Q_OBJECT
@@ -691,6 +732,25 @@ public:
virtual ~InstanceStaging() {};
+
+ // FIXME/TODO: add ability to abort during instance commit retries
+ bool abort() override
+ {
+ if(m_child && m_child->canAbort())
+ {
+ return m_child->abort();
+ }
+ return false;
+ }
+ bool canAbort() const override
+ {
+ if(m_child && m_child->canAbort())
+ {
+ return true;
+ }
+ return false;
+ }
+
protected:
virtual void executeTask() override
{
@@ -773,10 +833,11 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst
qWarning() << "Failed to move" << path << "to" << destination;
return false;
}
- m_groupMap[instID] = groupName;
+ m_instanceGroupIndex[instID] = groupName;
instanceSet.insert(instID);
- m_groups.insert(groupName);
+ m_groupNameCache.insert(groupName);
emit instancesChanged();
+ emit instanceSelectRequest(instID);
}
saveGroupList();
return true;
diff --git a/api/logic/InstanceList.h b/api/logic/InstanceList.h
index 5b966b33..8215cb66 100644
--- a/api/logic/InstanceList.h
+++ b/api/logic/InstanceList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -58,10 +58,12 @@ public:
virtual ~InstanceList();
public:
- QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const;
- int rowCount(const QModelIndex &parent = QModelIndex()) const;
- QVariant data(const QModelIndex &index, int role) const;
- Qt::ItemFlags flags(const QModelIndex &index) const;
+ QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ bool setData(const QModelIndex & index, const QVariant & value, int role) override;
enum AdditionalRoles
{
@@ -97,6 +99,8 @@ public:
InstancePtr getInstanceById(QString id) const;
QModelIndex getInstanceIndexById(const QString &id) const;
QStringList getGroups();
+ bool isGroupCollapsed(const QString &groupName);
+
GroupId getInstanceGroup(const InstanceId & id) const;
void setInstanceGroup(const InstanceId & id, const GroupId& name);
@@ -127,10 +131,12 @@ public:
signals:
void dataIsInvalid();
void instancesChanged();
+ void instanceSelectRequest(QString instanceId);
void groupsChanged(QSet<QString> groups);
public slots:
void on_InstFolderChanged(const Setting &setting, QVariant value);
+ void on_GroupStateChanged(const QString &group, bool collapsed);
private slots:
void propertiesChanged(BaseInstance *inst);
@@ -151,12 +157,14 @@ private:
int m_watchLevel = 0;
bool m_dirty = false;
QList<InstancePtr> m_instances;
- QSet<QString> m_groups;
+ QSet<QString> m_groupNameCache;
SettingsObjectPtr m_globalSettings;
QString m_instDir;
QFileSystemWatcher * m_watcher;
- QMap<InstanceId, GroupId> m_groupMap;
+ // FIXME: this is so inefficient that looking at it is almost painful.
+ QSet<QString> m_collapsedGroups;
+ QMap<InstanceId, GroupId> m_instanceGroupIndex;
QSet<InstanceId> instanceSet;
bool m_groupsLoaded = false;
bool m_instancesProbed = false;
diff --git a/api/logic/LoggedProcess.h b/api/logic/LoggedProcess.h
index 2cbc28bc..327cdc6a 100644
--- a/api/logic/LoggedProcess.h
+++ b/api/logic/LoggedProcess.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 21a55493..b25c61e7 100644
--- a/api/logic/MMCZip.cpp
+++ b/api/logic/MMCZip.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 4eb86361..98d9cd5b 100644
--- a/api/logic/MMCZip.h
+++ b/api/logic/MMCZip.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,12 +18,13 @@
#include <QString>
#include <QFileInfo>
#include <QSet>
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
#include <functional>
#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 bef6bc4f..94ed6c3a 100644
--- a/api/logic/NullInstance.h
+++ b/api/logic/NullInstance.h
@@ -1,8 +1,10 @@
#pragma once
#include "BaseInstance.h"
+#include "launch/LaunchTask.h"
class NullInstance: public BaseInstance
{
+ Q_OBJECT
public:
NullInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString& rootDir)
:BaseInstance(globalSettings, settings, rootDir)
@@ -10,46 +12,46 @@ public:
setVersionBroken(true);
}
virtual ~NullInstance() {};
- virtual void saveNow() override
+ void saveNow() override
{
}
- virtual QString getStatusbarDescription() override
+ QString getStatusbarDescription() override
{
return tr("Unknown instance type");
};
- virtual QSet< QString > traits() const override
+ QSet< QString > traits() const override
{
return {};
};
- virtual QString instanceConfigFolder() const override
+ QString instanceConfigFolder() const override
{
return instanceRoot();
};
- virtual std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr) override
+ shared_qobject_ptr<LaunchTask> createLaunchTask(AuthSessionPtr, MinecraftServerTargetPtr) override
{
return nullptr;
}
- virtual shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
+ shared_qobject_ptr< Task > createUpdateTask(Net::Mode mode) override
{
return nullptr;
}
- virtual QProcessEnvironment createEnvironment() override
+ QProcessEnvironment createEnvironment() override
{
return QProcessEnvironment();
}
- virtual QMap<QString, QString> getVariables() const override
+ QMap<QString, QString> getVariables() const override
{
return QMap<QString, QString>();
}
- virtual IPathMatcher::Ptr getLogFileMatcher() override
+ IPathMatcher::Ptr getLogFileMatcher() override
{
return nullptr;
}
- virtual QString getLogFileRoot() override
+ QString getLogFileRoot() override
{
return instanceRoot();
}
- virtual QString typeName() const override
+ QString typeName() const override
{
return "Null";
}
@@ -65,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.cpp b/api/logic/Version.cpp
index 42eac669..6392a50f 100644
--- a/api/logic/Version.cpp
+++ b/api/logic/Version.cpp
@@ -78,7 +78,7 @@ void Version::parse()
// FIXME: this is bad. versions can contain a lot more separators...
QStringList parts = m_string.split('.');
- for (const auto part : parts)
+ for (const auto &part : parts)
{
m_sections.append(Section(part));
}
diff --git a/api/logic/Version_test.cpp b/api/logic/Version_test.cpp
index bfbec12d..b2d657a6 100644
--- a/api/logic/Version_test.cpp
+++ b/api/logic/Version_test.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/icons/IconUtils.cpp b/api/logic/icons/IconUtils.cpp
new file mode 100644
index 00000000..bf530c16
--- /dev/null
+++ b/api/logic/icons/IconUtils.cpp
@@ -0,0 +1,62 @@
+#include "IconUtils.h"
+
+#include "FileSystem.h"
+#include <QDirIterator>
+
+#include <array>
+
+namespace {
+std::array<const char *, 6> validIconExtensions = {{
+ "svg",
+ "png",
+ "ico",
+ "gif",
+ "jpg",
+ "jpeg"
+}};
+}
+
+namespace IconUtils{
+
+QString findBestIconIn(const QString &folder, const QString & iconKey) {
+ int best_found = validIconExtensions.size();
+ QString best_filename;
+
+ QDirIterator it(folder, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::NoIteratorFlags);
+ while (it.hasNext()) {
+ it.next();
+ auto fileInfo = it.fileInfo();
+
+ if(fileInfo.completeBaseName() != iconKey)
+ continue;
+
+ auto extension = fileInfo.suffix();
+
+ for(int i = 0; i < best_found; i++) {
+ if(extension == validIconExtensions[i]) {
+ best_found = i;
+ qDebug() << i << " : " << fileInfo.fileName();
+ best_filename = fileInfo.fileName();
+ }
+ }
+ }
+ return FS::PathCombine(folder, best_filename);
+}
+
+QString getIconFilter() {
+ QString out;
+ QTextStream stream(&out);
+ stream << '(';
+ for(size_t i = 0; i < validIconExtensions.size() - 1; i++) {
+ if(i > 0) {
+ stream << " ";
+ }
+ stream << "*." << validIconExtensions[i];
+ }
+ stream << " *." << validIconExtensions[validIconExtensions.size() - 1];
+ stream << ')';
+ return out;
+}
+
+}
+
diff --git a/api/logic/icons/IconUtils.h b/api/logic/icons/IconUtils.h
new file mode 100644
index 00000000..ce236946
--- /dev/null
+++ b/api/logic/icons/IconUtils.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <QString>
+#include "multimc_logic_export.h"
+
+namespace IconUtils {
+
+// Given a folder and an icon key, find 'best' of the icons with the given key in there and return its path
+MULTIMC_LOGIC_EXPORT QString findBestIconIn(const QString &folder, const QString & iconKey);
+
+// Get icon file type filter for file browser dialogs
+MULTIMC_LOGIC_EXPORT QString getIconFilter();
+
+}
diff --git a/api/logic/java/JavaChecker.cpp b/api/logic/java/JavaChecker.cpp
index 9ba3933f..d78d6505 100644
--- a/api/logic/java/JavaChecker.cpp
+++ b/api/logic/java/JavaChecker.cpp
@@ -75,8 +75,8 @@ void JavaChecker::stderrReady()
void JavaChecker::finished(int exitcode, QProcess::ExitStatus status)
{
killTimer.stop();
- QProcessPtr _process;
- _process.swap(process);
+ QProcessPtr _process = process;
+ process.reset();
JavaCheckResult result;
{
@@ -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 d5d4b0de..0a96249a 100644
--- a/api/logic/java/JavaChecker.h
+++ b/api/logic/java/JavaChecker.h
@@ -3,6 +3,8 @@
#include <QTimer>
#include <memory>
+#include "QObjectPtr.h"
+
#include "multimc_logic_export.h"
#include "JavaVersion.h"
@@ -15,6 +17,7 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
QString mojangPlatform;
QString realPlatform;
JavaVersion javaVersion;
+ QString javaVendor;
QString outLog;
QString errorLog;
bool is_64bit = false;
@@ -27,8 +30,8 @@ struct MULTIMC_LOGIC_EXPORT JavaCheckResult
} validity = Validity::Errored;
};
-typedef std::shared_ptr<QProcess> QProcessPtr;
-typedef std::shared_ptr<JavaChecker> JavaCheckerPtr;
+typedef shared_qobject_ptr<QProcess> QProcessPtr;
+typedef shared_qobject_ptr<JavaChecker> JavaCheckerPtr;
class MULTIMC_LOGIC_EXPORT JavaChecker : public QObject
{
Q_OBJECT
diff --git a/api/logic/java/JavaCheckerJob.cpp b/api/logic/java/JavaCheckerJob.cpp
index a26846f2..67d70066 100644
--- a/api/logic/java/JavaCheckerJob.cpp
+++ b/api/logic/java/JavaCheckerJob.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e52970c1..c0986420 100644
--- a/api/logic/java/JavaCheckerJob.h
+++ b/api/logic/java/JavaCheckerJob.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,7 +20,7 @@
#include "tasks/Task.h"
class JavaCheckerJob;
-typedef std::shared_ptr<JavaCheckerJob> JavaCheckerJobPtr;
+typedef shared_qobject_ptr<JavaCheckerJob> JavaCheckerJobPtr;
// FIXME: this just seems horribly redundant
class JavaCheckerJob : public Task
diff --git a/api/logic/java/JavaInstallList.cpp b/api/logic/java/JavaInstallList.cpp
index 605d4c0c..0bded03c 100644
--- a/api/logic/java/JavaInstallList.cpp
+++ b/api/logic/java/JavaInstallList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -149,7 +149,7 @@ void JavaListLoadTask::executeTask()
JavaUtils ju;
QList<QString> candidate_paths = ju.FindJavaPaths();
- m_job = std::shared_ptr<JavaCheckerJob>(new JavaCheckerJob("Java detection"));
+ m_job = new JavaCheckerJob("Java detection");
connect(m_job.get(), &Task::finished, this, &JavaListLoadTask::javaCheckerFinished);
connect(m_job.get(), &Task::progress, this, &Task::setProgress);
diff --git a/api/logic/java/JavaInstallList.h b/api/logic/java/JavaInstallList.h
index 7e72b5ef..1785a7b6 100644
--- a/api/logic/java/JavaInstallList.h
+++ b/api/logic/java/JavaInstallList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,8 @@
#include "JavaCheckerJob.h"
#include "JavaInstall.h"
+#include "QObjectPtr.h"
+
#include "multimc_logic_export.h"
class JavaListLoadTask;
@@ -75,7 +77,7 @@ public slots:
void javaCheckerFinished();
protected:
- std::shared_ptr<JavaCheckerJob> m_job;
+ shared_qobject_ptr<JavaCheckerJob> m_job;
JavaInstallList *m_list;
JavaInstall *m_currentRecommended;
};
diff --git a/api/logic/java/JavaUtils.cpp b/api/logic/java/JavaUtils.cpp
index 9f7fdcb0..4b231e6a 100644
--- a/api/logic/java/JavaUtils.cpp
+++ b/api/logic/java/JavaUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 40745ad6..206acf89 100644
--- a/api/logic/java/JavaUtils.h
+++ b/api/logic/java/JavaUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 4ea40084..f58602f0 100644
--- a/api/logic/java/launch/CheckJava.cpp
+++ b/api/logic/java/launch/CheckJava.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 = std::make_shared<JavaChecker>();
- emit logLine(tr("Checking Java version..."), MessageLevel::MultiMC);
+ m_JavaChecker = new JavaChecker();
+ 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 be247a1b..68cd618b 100644
--- a/api/logic/java/launch/CheckJava.h
+++ b/api/logic/java/launch/CheckJava.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e2327712..d6bb6e88 100644
--- a/api/logic/launch/LaunchStep.cpp
+++ b/api/logic/launch/LaunchStep.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 1aaf64af..3939f960 100644
--- a/api/logic/launch/LaunchStep.h
+++ b/api/logic/launch/LaunchStep.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 c00fa32a..e6f6bbac 100644
--- a/api/logic/launch/LaunchTask.cpp
+++ b/api/logic/launch/LaunchTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -33,9 +33,9 @@ void LaunchTask::init()
m_instance->setRunning(true);
}
-std::shared_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
+shared_qobject_ptr<LaunchTask> LaunchTask::create(InstancePtr inst)
{
- std::shared_ptr<LaunchTask> proc(new LaunchTask(inst));
+ shared_qobject_ptr<LaunchTask> proc(new LaunchTask(inst));
proc->init();
return proc;
}
@@ -44,12 +44,12 @@ LaunchTask::LaunchTask(InstancePtr instance): m_instance(instance)
{
}
-void LaunchTask::appendStep(std::shared_ptr<LaunchStep> step)
+void LaunchTask::appendStep(shared_qobject_ptr<LaunchStep> step)
{
m_steps.append(step);
}
-void LaunchTask::prependStep(std::shared_ptr<LaunchStep> step)
+void LaunchTask::prependStep(shared_qobject_ptr<LaunchStep> step)
{
m_steps.prepend(step);
}
diff --git a/api/logic/launch/LaunchTask.h b/api/logic/launch/LaunchTask.h
index f5e226b5..2be59c83 100644
--- a/api/logic/launch/LaunchTask.h
+++ b/api/logic/launch/LaunchTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Orochimarufan <orochimarufan.x3@gmail.com>
*
@@ -45,11 +45,11 @@ public:
};
public: /* methods */
- static std::shared_ptr<LaunchTask> create(InstancePtr inst);
+ static shared_qobject_ptr<LaunchTask> create(InstancePtr inst);
virtual ~LaunchTask() {};
- void appendStep(std::shared_ptr<LaunchStep> step);
- void prependStep(std::shared_ptr<LaunchStep> step);
+ void appendStep(shared_qobject_ptr<LaunchStep> step);
+ void prependStep(shared_qobject_ptr<LaunchStep> step);
void setCensorFilter(QMap<QString, QString> filter);
InstancePtr instance()
@@ -117,7 +117,7 @@ private: /*methods */
protected: /* data */
InstancePtr m_instance;
shared_qobject_ptr<LogModel> m_logModel;
- QList <std::shared_ptr<LaunchStep>> m_steps;
+ QList <shared_qobject_ptr<LaunchStep>> m_steps;
QMap<QString, QString> m_censorFilter;
int currentStep = -1;
State state = NotStarted;
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 3730f71c..d48d03d1 100644
--- a/api/logic/launch/steps/PostLaunchCommand.cpp
+++ b/api/logic/launch/steps/PostLaunchCommand.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 efba62d9..ab4c494f 100644
--- a/api/logic/launch/steps/PostLaunchCommand.h
+++ b/api/logic/launch/steps/PostLaunchCommand.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 c5e1e373..20e089e2 100644
--- a/api/logic/launch/steps/PreLaunchCommand.cpp
+++ b/api/logic/launch/steps/PreLaunchCommand.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 861e5be7..dc069f71 100644
--- a/api/logic/launch/steps/PreLaunchCommand.h
+++ b/api/logic/launch/steps/PreLaunchCommand.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 48b8cf6f..2937c64a 100644
--- a/api/logic/launch/steps/TextPrint.h
+++ b/api/logic/launch/steps/TextPrint.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 65f24391..28bd153d 100644
--- a/api/logic/launch/steps/Update.cpp
+++ b/api/logic/launch/steps/Update.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -49,7 +49,7 @@ void Update::updateFinished()
}
else
{
- QString reason = tr("Instance update failed because: %1.\n\n").arg(m_updateTask->failReason());
+ QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason());
m_updateTask.reset();
emit logLine(reason, MessageLevel::Fatal);
emitFailed(reason);
diff --git a/api/logic/launch/steps/Update.h b/api/logic/launch/steps/Update.h
index baa0b80e..0c9d91e0 100644
--- a/api/logic/launch/steps/Update.h
+++ b/api/logic/launch/steps/Update.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 9ea712fa..5ff7a59a 100644
--- a/api/logic/meta/BaseEntity.cpp
+++ b/api/logic/meta/BaseEntity.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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.
@@ -24,6 +24,8 @@
#include "Env.h"
#include "Json.h"
+#include "BuildConfig.h"
+
class ParsingValidator : public Net::Validator
{
public: /* con/des */
@@ -53,7 +55,9 @@ public: /* methods */
auto fname = m_entity->localFilename();
try
{
- m_entity->parse(Json::requireObject(Json::requireDocument(data, fname), fname));
+ auto doc = Json::requireDocument(data, fname);
+ auto obj = Json::requireObject(doc, fname);
+ m_entity->parse(obj);
return true;
}
catch (const Exception &e)
@@ -74,7 +78,7 @@ Meta::BaseEntity::~BaseEntity()
QUrl Meta::BaseEntity::url() const
{
- return QUrl("https://v1.meta.multimc.org").resolved(localFilename());
+ return QUrl(BuildConfig.META_URL).resolved(localFilename());
}
bool Meta::BaseEntity::loadLocalFile()
@@ -87,7 +91,9 @@ bool Meta::BaseEntity::loadLocalFile()
// TODO: check if the file has the expected checksum
try
{
- parse(Json::requireObject(Json::requireDocument(fname, fname), fname));
+ auto doc = Json::requireDocument(fname, fname);
+ auto obj = Json::requireObject(doc, fname);
+ parse(obj);
return true;
}
catch (const Exception &e)
diff --git a/api/logic/meta/BaseEntity.h b/api/logic/meta/BaseEntity.h
index 19dd2c18..04a37420 100644
--- a/api/logic/meta/BaseEntity.h
+++ b/api/logic/meta/BaseEntity.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 4dbc0a86..6802470d 100644
--- a/api/logic/meta/Index.cpp
+++ b/api/logic/meta/Index.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 c81b4c54..e9412e70 100644
--- a/api/logic/meta/Index.h
+++ b/api/logic/meta/Index.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 12da266f..796da4bb 100644
--- a/api/logic/meta/JsonFormat.cpp
+++ b/api/logic/meta/JsonFormat.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 62351ad6..93217b7e 100644
--- a/api/logic/meta/JsonFormat.h
+++ b/api/logic/meta/JsonFormat.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 05a76b41..a8dc3169 100644
--- a/api/logic/meta/Version.cpp
+++ b/api/logic/meta/Version.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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.
@@ -18,7 +18,7 @@
#include <QDateTime>
#include "JsonFormat.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
Meta::Version::Version(const QString &uid, const QString &version)
: BaseVersion(), m_uid(uid), m_version(version)
diff --git a/api/logic/meta/Version.h b/api/logic/meta/Version.h
index 6506d486..a38d7bcd 100644
--- a/api/logic/meta/Version.h
+++ b/api/logic/meta/Version.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 526fe165..607007eb 100644
--- a/api/logic/meta/VersionList.cpp
+++ b/api/logic/meta/VersionList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 0be074dd..bba32ca3 100644
--- a/api/logic/meta/VersionList.h
+++ b/api/logic/meta/VersionList.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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 c6db2a40..c01733b6 100644
--- a/api/logic/minecraft/AssetsUtils.cpp
+++ b/api/logic/minecraft/AssetsUtils.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,6 +27,34 @@
#include "FileSystem.h"
#include "net/Download.h"
#include "net/ChecksumValidator.h"
+#include "BuildConfig.h"
+
+namespace {
+QSet<QString> collectPathsFromDir(QString dirPath)
+{
+ QFileInfo dirInfo(dirPath);
+
+ if (!dirInfo.exists())
+ {
+ return {};
+ }
+
+ QSet<QString> out;
+
+ QDirIterator iter(dirPath, QDirIterator::Subdirectories);
+ while (iter.hasNext())
+ {
+ QString value = iter.next();
+ QFileInfo info(value);
+ if(info.isFile())
+ {
+ out.insert(value);
+ qDebug() << value;
+ }
+ }
+ return out;
+}
+}
namespace AssetsUtils
@@ -36,7 +64,7 @@ namespace AssetsUtils
* Returns true on success, with index populated
* index is undefined otherwise
*/
-bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
+bool loadAssetsIndexJson(const QString &assetsId, const QString &path, AssetsIndex& index)
{
/*
{
@@ -60,7 +88,7 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
qCritical() << "Failed to read assets index file" << path;
return false;
}
- index->id = assetsId;
+ index.id = assetsId;
// Read the file and close it.
QByteArray jsonData = file.readAll();
@@ -89,7 +117,13 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
QJsonValue isVirtual = root.value("virtual");
if (!isVirtual.isUndefined())
{
- index->isVirtual = isVirtual.toBool(false);
+ index.isVirtual = isVirtual.toBool(false);
+ }
+
+ QJsonValue mapToResources = root.value("map_to_resources");
+ if (!mapToResources.isUndefined())
+ {
+ index.mapToResources = mapToResources.toBool(false);
}
QJsonValue objects = root.value("objects");
@@ -121,13 +155,14 @@ bool loadAssetsIndexJson(QString assetsId, QString path, AssetsIndex *index)
}
}
- index->objects.insert(iter.key(), object);
+ index.objects.insert(iter.key(), object);
}
return true;
}
-QDir reconstructAssets(QString assetsId)
+// FIXME: ugly code duplication
+QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder)
{
QDir assetsDir = QDir("assets/");
QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
@@ -140,24 +175,77 @@ QDir reconstructAssets(QString assetsId)
if (!indexFile.exists())
{
- qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets";
+ qCritical() << "No assets index file" << indexPath << "; can't determine assets path!";
return virtualRoot;
}
- qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path()
- << objectDir.path() << virtualDir.path() << virtualRoot.path();
+ AssetsIndex index;
+ if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
+ {
+ qCritical() << "Failed to load asset index file" << indexPath << "; can't determine assets path!";
+ return virtualRoot;
+ }
+
+ QString targetPath;
+ if(index.isVirtual)
+ {
+ return virtualRoot;
+ }
+ else if(index.mapToResources)
+ {
+ return QDir(resourcesFolder);
+ }
+ return virtualRoot;
+}
+
+// FIXME: ugly code duplication
+bool reconstructAssets(QString assetsId, QString resourcesFolder)
+{
+ QDir assetsDir = QDir("assets/");
+ QDir indexDir = QDir(FS::PathCombine(assetsDir.path(), "indexes"));
+ QDir objectDir = QDir(FS::PathCombine(assetsDir.path(), "objects"));
+ QDir virtualDir = QDir(FS::PathCombine(assetsDir.path(), "virtual"));
+
+ QString indexPath = FS::PathCombine(indexDir.path(), assetsId + ".json");
+ QFile indexFile(indexPath);
+ QDir virtualRoot(FS::PathCombine(virtualDir.path(), assetsId));
+
+ if (!indexFile.exists())
+ {
+ qCritical() << "No assets index file" << indexPath << "; can't reconstruct assets!";
+ return false;
+ }
+
+ qDebug() << "reconstructAssets" << assetsDir.path() << indexDir.path() << objectDir.path() << virtualDir.path() << virtualRoot.path();
AssetsIndex index;
- bool loadAssetsIndex = AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, &index);
+ if(!AssetsUtils::loadAssetsIndexJson(assetsId, indexPath, index))
+ {
+ qCritical() << "Failed to load asset index file" << indexPath << "; can't reconstruct assets!";
+ return false;
+ }
- if (loadAssetsIndex && index.isVirtual)
+ QString targetPath;
+ bool removeLeftovers = false;
+ if(index.isVirtual)
+ {
+ targetPath = virtualRoot.path();
+ removeLeftovers = true;
+ qDebug() << "Reconstructing virtual assets folder at" << targetPath;
+ }
+ else if(index.mapToResources)
{
- qDebug() << "Reconstructing virtual assets folder at" << virtualRoot.path();
+ targetPath = resourcesFolder;
+ qDebug() << "Reconstructing resources folder at" << targetPath;
+ }
+ if (!targetPath.isNull())
+ {
+ auto presentFiles = collectPathsFromDir(targetPath);
for (QString map : index.objects.keys())
{
AssetObject asset_object = index.objects.value(map);
- QString target_path = FS::PathCombine(virtualRoot.path(), map);
+ QString target_path = FS::PathCombine(targetPath, map);
QFile target(target_path);
QString tlk = asset_object.hash.left(2);
@@ -166,24 +254,32 @@ QDir reconstructAssets(QString assetsId)
QFile original(original_path);
if (!original.exists())
continue;
+
+ presentFiles.remove(target_path);
+
if (!target.exists())
{
QFileInfo info(target_path);
QDir target_dir = info.dir();
- // qDebug() << target_dir;
- if (!target_dir.exists())
- QDir("").mkpath(target_dir.path());
+
+ qDebug() << target_dir.path();
+ FS::ensureFolderPathExists(target_dir.path());
bool couldCopy = original.copy(target_path);
- qDebug() << " Copying" << original_path << "to" << target_path
- << QString::number(couldCopy); // << original.errorString();
+ qDebug() << " Copying" << original_path << "to" << target_path << QString::number(couldCopy);
}
}
// TODO: Write last used time to virtualRoot/.lastused
+ if(removeLeftovers)
+ {
+ for(auto & file: presentFiles)
+ {
+ qDebug() << "Would remove" << file;
+ }
+ }
}
-
- return virtualRoot;
+ return true;
}
}
@@ -212,7 +308,7 @@ QString AssetObject::getLocalPath()
QUrl AssetObject::getUrl()
{
- return QUrl("https://resources.download.minecraft.net/" + getRelPath());
+ return BuildConfig.RESOURCE_BASE + getRelPath();
}
QString AssetObject::getRelPath()
diff --git a/api/logic/minecraft/AssetsUtils.h b/api/logic/minecraft/AssetsUtils.h
index 063c1237..32e57060 100644
--- a/api/logic/minecraft/AssetsUtils.h
+++ b/api/logic/minecraft/AssetsUtils.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -38,11 +38,16 @@ struct AssetsIndex
QString id;
QMap<QString, AssetObject> objects;
bool isVirtual = false;
+ bool mapToResources = false;
};
+/// FIXME: this is absolutely horrendous. REDO!!!!
namespace AssetsUtils
{
-bool loadAssetsIndexJson(QString id, QString file, AssetsIndex* index);
+bool loadAssetsIndexJson(const QString &id, const QString &file, AssetsIndex& index);
+
+QDir getAssetsDir(const QString &assetsId, const QString &resourcesFolder);
+
/// Reconstruct a virtual assets folder for the given assets ID and return the folder
-QDir reconstructAssets(QString assetsId);
+bool reconstructAssets(QString assetsId, QString resourcesFolder);
}
diff --git a/api/logic/minecraft/Component.cpp b/api/logic/minecraft/Component.cpp
index 51957d17..92821065 100644
--- a/api/logic/minecraft/Component.cpp
+++ b/api/logic/minecraft/Component.cpp
@@ -5,13 +5,13 @@
#include "meta/Version.h"
#include "VersionFile.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include <FileSystem.h>
#include <QSaveFile>
#include "OneSixVersionFormat.h"
#include <assert.h>
-Component::Component(ComponentList * parent, const QString& uid)
+Component::Component(PackProfile * parent, const QString& uid)
{
assert(parent);
m_parent = parent;
@@ -19,7 +19,7 @@ Component::Component(ComponentList * parent, const QString& uid)
m_uid = uid;
}
-Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> version)
+Component::Component(PackProfile * parent, std::shared_ptr<Meta::Version> version)
{
assert(parent);
m_parent = parent;
@@ -31,7 +31,7 @@ Component::Component(ComponentList * parent, std::shared_ptr<Meta::Version> vers
m_loaded = version->isLoaded();
}
-Component::Component(ComponentList * parent, const QString& uid, std::shared_ptr<VersionFile> file)
+Component::Component(PackProfile * parent, const QString& uid, std::shared_ptr<VersionFile> file)
{
assert(parent);
m_parent = parent;
diff --git a/api/logic/minecraft/Component.h b/api/logic/minecraft/Component.h
index 6a0f86c8..cb202f7f 100644
--- a/api/logic/minecraft/Component.h
+++ b/api/logic/minecraft/Component.h
@@ -9,7 +9,7 @@
#include "QObjectPtr.h"
#include "multimc_logic_export.h"
-class ComponentList;
+class PackProfile;
class LaunchProfile;
namespace Meta
{
@@ -22,11 +22,11 @@ class MULTIMC_LOGIC_EXPORT Component : public QObject, public ProblemProvider
{
Q_OBJECT
public:
- Component(ComponentList * parent, const QString &uid);
+ Component(PackProfile * parent, const QString &uid);
// DEPRECATED: remove these constructors?
- Component(ComponentList * parent, std::shared_ptr<Meta::Version> version);
- Component(ComponentList * parent, const QString & uid, std::shared_ptr<VersionFile> file);
+ Component(PackProfile * parent, std::shared_ptr<Meta::Version> version);
+ Component(PackProfile * parent, const QString & uid, std::shared_ptr<VersionFile> file);
virtual ~Component(){};
void applyTo(LaunchProfile *profile);
@@ -73,7 +73,7 @@ signals:
void dataChanged();
public: /* data */
- ComponentList * m_parent;
+ PackProfile * m_parent;
// BEGIN: persistent component list properties
/// ID of the component
diff --git a/api/logic/minecraft/ComponentUpdateTask.cpp b/api/logic/minecraft/ComponentUpdateTask.cpp
index 37cc488d..241d9a49 100644
--- a/api/logic/minecraft/ComponentUpdateTask.cpp
+++ b/api/logic/minecraft/ComponentUpdateTask.cpp
@@ -1,7 +1,7 @@
#include "ComponentUpdateTask.h"
-#include "ComponentList_p.h"
-#include "ComponentList.h"
+#include "PackProfile_p.h"
+#include "PackProfile.h"
#include "Component.h"
#include <Env.h>
#include <meta/Index.h>
@@ -22,16 +22,16 @@
* Really, it should be a reactor/state machine that receives input from the application
* and dynamically adapts to changing requirements...
*
- * The reactor should be the only entry into manipulating the ComponentList.
+ * The reactor should be the only entry into manipulating the PackProfile.
* See: https://en.wikipedia.org/wiki/Reactor_pattern
*/
/*
- * Or make this operate on a snapshot of the ComponentList state, then merge results in as long as the snapshot and ComponentList didn't change?
+ * Or make this operate on a snapshot of the PackProfile state, then merge results in as long as the snapshot and PackProfile didn't change?
* If the component list changes, start over.
*/
-ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList* list, QObject* parent)
+ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent)
: Task(parent)
{
d.reset(new ComponentUpdateTaskData);
@@ -126,7 +126,7 @@ static LoadResult loadComponent(ComponentPtr component, shared_qobject_ptr<Task>
// FIXME: dead code. determine if this can still be useful?
/*
-static LoadResult loadComponentList(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
+static LoadResult loadPackProfile(ComponentPtr component, shared_qobject_ptr<Task>& loadTask, Net::Mode netmode)
{
if(component->m_loaded)
{
@@ -217,7 +217,7 @@ void ComponentUpdateTask::loadComponents()
}
case Mode::Resolution:
{
- singleResult = loadComponentList(component, loadTask, d->netmode);
+ singleResult = loadPackProfile(component, loadTask, d->netmode);
loadType = RemoteLoadStatus::Type::List;
break;
}
@@ -244,7 +244,7 @@ void ComponentUpdateTask::loadComponents()
});
RemoteLoadStatus status;
status.type = loadType;
- status.componentListIndex = componentIndex;
+ status.PackProfileIndex = componentIndex;
d->remoteLoadStatusList.append(status);
taskIndex++;
}
@@ -451,13 +451,17 @@ static bool getTrivialComponentChanges(const ComponentIndex & index, const Requi
auto & comp = (*compIter);
if(comp->getVersion() != req.equalsVersion)
{
- if(comp->m_dependencyOnly)
- {
- decision = Decision::VersionNotSame;
- }
- else
- {
+ if(comp->isCustom()) {
decision = Decision::LockedVersionNotSame;
+ } else {
+ if(comp->m_dependencyOnly)
+ {
+ decision = Decision::VersionNotSame;
+ }
+ else
+ {
+ decision = Decision::LockedVersionNotSame;
+ }
}
break;
}
@@ -491,7 +495,7 @@ static bool getTrivialComponentChanges(const ComponentIndex & index, const Requi
}
// FIXME, TODO: decouple dependency resolution from loading
-// FIXME: This works directly with the ComponentList internals. It shouldn't! It needs richer data types than ComponentList uses.
+// FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses.
// FIXME: throw all this away and use a graph
void ComponentUpdateTask::resolveDependencies(bool checkOnly)
{
@@ -586,6 +590,15 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly)
{
component->m_version = "3.1.2";
}
+ else if (add.uid == "net.fabricmc.intermediary")
+ {
+ auto minecraft = std::find_if(components.begin(), components.end(), [](ComponentPtr & cmp){
+ return cmp->getID() == "net.minecraft";
+ });
+ if(minecraft != components.end()) {
+ component->m_version = (*minecraft)->getVersion();
+ }
+ }
}
// HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded.
// ############################################################################################################
@@ -635,7 +648,7 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex)
// update the cached data of the component from the downloaded version file.
if (taskSlot.type == RemoteLoadStatus::Type::Version)
{
- auto component = d->m_list->getComponent(taskSlot.componentListIndex);
+ auto component = d->m_list->getComponent(taskSlot.PackProfileIndex);
component->m_loaded = true;
component->updateCachedData();
}
diff --git a/api/logic/minecraft/ComponentUpdateTask.h b/api/logic/minecraft/ComponentUpdateTask.h
index 4cb29a89..4274cabb 100644
--- a/api/logic/minecraft/ComponentUpdateTask.h
+++ b/api/logic/minecraft/ComponentUpdateTask.h
@@ -4,7 +4,7 @@
#include "net/Mode.h"
#include <memory>
-class ComponentList;
+class PackProfile;
struct ComponentUpdateTaskData;
class ComponentUpdateTask : public Task
@@ -18,7 +18,7 @@ public:
};
public:
- explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, ComponentList * list, QObject *parent = 0);
+ explicit ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile * list, QObject *parent = 0);
virtual ~ComponentUpdateTask();
protected:
diff --git a/api/logic/minecraft/ComponentUpdateTask_p.h b/api/logic/minecraft/ComponentUpdateTask_p.h
index 5989cf07..5b02431b 100644
--- a/api/logic/minecraft/ComponentUpdateTask_p.h
+++ b/api/logic/minecraft/ComponentUpdateTask_p.h
@@ -5,7 +5,7 @@
#include <QList>
#include "net/Mode.h"
-class ComponentList;
+class PackProfile;
struct RemoteLoadStatus
{
@@ -15,7 +15,7 @@ struct RemoteLoadStatus
List,
Version
} type = Type::Version;
- size_t componentListIndex = 0;
+ size_t PackProfileIndex = 0;
bool finished = false;
bool succeeded = false;
QString error;
@@ -23,7 +23,7 @@ struct RemoteLoadStatus
struct ComponentUpdateTaskData
{
- ComponentList * m_list = nullptr;
+ PackProfile * m_list = nullptr;
QList<RemoteLoadStatus> remoteLoadStatusList;
bool remoteLoadSuccessful = true;
size_t remoteTasksInProgress = 0;
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/LaunchProfile.cpp b/api/logic/minecraft/LaunchProfile.cpp
index c39bdf04..41705187 100644
--- a/api/logic/minecraft/LaunchProfile.cpp
+++ b/api/logic/minecraft/LaunchProfile.cpp
@@ -11,6 +11,7 @@ void LaunchProfile::clear()
m_mainClass.clear();
m_appletClass.clear();
m_libraries.clear();
+ m_mavenFiles.clear();
m_traits.clear();
m_jarMods.clear();
m_mainJar.reset();
@@ -157,6 +158,22 @@ void LaunchProfile::applyLibrary(LibraryPtr library)
}
}
+void LaunchProfile::applyMavenFile(LibraryPtr mavenFile)
+{
+ if(!mavenFile->isActive())
+ {
+ return;
+ }
+
+ if(mavenFile->isNative())
+ {
+ return;
+ }
+
+ // unlike libraries, we do not keep only one version or try to dedupe them
+ m_mavenFiles.append(Library::limitedCopy(mavenFile));
+}
+
const LibraryPtr LaunchProfile::getMainJar() const
{
return m_mainJar;
@@ -253,6 +270,11 @@ const QList<LibraryPtr> & LaunchProfile::getNativeLibraries() const
return m_nativeLibraries;
}
+const QList<LibraryPtr> & LaunchProfile::getMavenFiles() const
+{
+ return m_mavenFiles;
+}
+
void LaunchProfile::getLibraryFiles(
const QString& architecture,
QStringList& jars,
diff --git a/api/logic/minecraft/LaunchProfile.h b/api/logic/minecraft/LaunchProfile.h
index 77174079..c1752531 100644
--- a/api/logic/minecraft/LaunchProfile.h
+++ b/api/logic/minecraft/LaunchProfile.h
@@ -20,6 +20,7 @@ public: /* application of profile variables from patches */
void applyJarMods(const QList<LibraryPtr> &jarMods);
void applyMods(const QList<LibraryPtr> &jarMods);
void applyLibrary(LibraryPtr library);
+ void applyMavenFile(LibraryPtr library);
void applyMainJar(LibraryPtr jar);
void applyProblemSeverity(ProblemSeverity severity);
/// clear the profile
@@ -37,6 +38,7 @@ public: /* getters for profile variables */
const QList<LibraryPtr> & getJarMods() const;
const QList<LibraryPtr> & getLibraries() const;
const QList<LibraryPtr> & getNativeLibraries() const;
+ const QList<LibraryPtr> & getMavenFiles() const;
const LibraryPtr getMainJar() const;
void getLibraryFiles(
const QString & architecture,
@@ -79,10 +81,13 @@ private:
/// the list of libraries
QList<LibraryPtr> m_libraries;
+ /// the list of maven files to be placed in the libraries folder, but not acted upon
+ QList<LibraryPtr> m_mavenFiles;
+
/// the main jar
LibraryPtr m_mainJar;
- /// the list of libraries
+ /// the list of native libraries
QList<LibraryPtr> m_nativeLibraries;
/// traits, collected from all the version files (version files can only add)
diff --git a/api/logic/minecraft/Library.cpp b/api/logic/minecraft/Library.cpp
index a6ec0301..f2293679 100644
--- a/api/logic/minecraft/Library.cpp
+++ b/api/logic/minecraft/Library.cpp
@@ -3,9 +3,9 @@
#include <net/Download.h>
#include <net/ChecksumValidator.h>
-#include <minecraft/forge/ForgeXzDownload.h>
#include <Env.h>
#include <FileSystem.h>
+#include <BuildConfig.h>
void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32,
@@ -18,13 +18,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
if(local && !overridePath.isEmpty())
{
QString fileName = out.fileName();
- auto fullPath = FS::PathCombine(overridePath, fileName);
- qDebug() << fullPath;
- QFileInfo fileinfo(fullPath);
- if(fileinfo.exists())
- {
- return fileinfo.absoluteFilePath();
- }
+ return QFileInfo(FS::PathCombine(overridePath, fileName)).absoluteFilePath();
}
return out.absoluteFilePath();
};
@@ -51,77 +45,62 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na
}
}
-QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class HttpMetaCache* cache,
- QStringList& failedFiles, const QString & overridePath) const
+QList< std::shared_ptr< NetAction > > Library::getDownloads(
+ OpSys system,
+ class HttpMetaCache* cache,
+ QStringList& failedLocalFiles,
+ const QString & overridePath
+) const
{
QList<NetActionPtr> out;
- bool isAlwaysStale = (hint() == "always-stale");
+ bool stale = isAlwaysStale();
bool local = isLocal();
- bool isForge = (hint() == "forge-pack-xz");
+
+ auto check_local_file = [&](QString storage)
+ {
+ QFileInfo fileinfo(storage);
+ QString fileName = fileinfo.fileName();
+ auto fullPath = FS::PathCombine(overridePath, fileName);
+ QFileInfo localFileInfo(fullPath);
+ if(!localFileInfo.exists())
+ {
+ failedLocalFiles.append(localFileInfo.filePath());
+ return false;
+ }
+ return true;
+ };
auto add_download = [&](QString storage, QString url, QString sha1)
{
+ if(local)
+ {
+ return check_local_file(storage);
+ }
auto entry = cache->resolveEntry("libraries", storage);
- if(isAlwaysStale)
+ if(stale)
{
entry->setStale(true);
}
if (!entry->isStale())
return true;
- if(local)
- {
- if(!overridePath.isEmpty())
- {
- QString fileName;
- int position = storage.lastIndexOf('/');
- if(position == -1)
- {
- fileName = storage;
- }
- else
- {
- fileName = storage.mid(position);
- }
- auto fullPath = FS::PathCombine(overridePath, fileName);
- QFileInfo fileinfo(fullPath);
- if(fileinfo.exists())
- {
- return true;
- }
- }
- QFileInfo fileinfo(entry->getFullPath());
- if(!fileinfo.exists())
- {
- failedFiles.append(entry->getFullPath());
- return false;
- }
- return true;
- }
Net::Download::Options options;
- if(isAlwaysStale)
+ if(stale)
{
options |= Net::Download::Option::AcceptLocalFiles;
}
- if (isForge)
+
+ if(sha1.size())
{
- qDebug() << "XzDownload for:" << rawName() << "storage:" << storage << "url:" << url;
- out.append(ForgeXzDownload::make(storage, entry));
+ 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().serialize() << "storage:" << storage << "url:" << url;
+ out.append(dl);
}
else
{
- if(sha1.size())
- {
- auto rawSha1 = QByteArray::fromHex(sha1.toLatin1());
- auto dl = Net::Download::makeCached(url, entry, options);
- dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1));
- qDebug() << "Checksummed Download for:" << rawName() << "storage:" << storage << "url:" << url;
- out.append(dl);
- }
- else
- {
- out.append(Net::Download::makeCached(url, entry, options));
- qDebug() << "Download for:" << rawName() << "storage:" << storage << "url:" << url;
- }
+ out.append(Net::Download::makeCached(url, entry, options));
+ qDebug() << "Download for:" << rawName().serialize() << "storage:" << storage << "url:" << url;
}
return true;
};
@@ -166,7 +145,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
}
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
@@ -178,13 +157,14 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
}
else
{
- qDebug() << "Ignoring java library" << m_name << "because it has no artifact";
+ qDebug() << "Ignoring java library" << m_name.serialize() << "because it has no artifact";
}
}
}
else
{
- auto raw_dl = [&](){
+ auto raw_dl = [&]()
+ {
if (!m_absoluteURL.isEmpty())
{
return m_absoluteURL;
@@ -192,7 +172,7 @@ QList< std::shared_ptr< NetAction > > Library::getDownloads(OpSys system, class
if (m_repositoryURL.isEmpty())
{
- return QString("https://" + URLConstants::LIBRARY_BASE) + raw_storage;
+ return BuildConfig.LIBRARY_BASE + raw_storage;
}
if(m_repositoryURL.endsWith('/'))
@@ -251,6 +231,11 @@ bool Library::isLocal() const
return m_hint == "local";
}
+bool Library::isAlwaysStale() const
+{
+ return m_hint == "always-stale";
+}
+
void Library::setStoragePrefix(QString prefix)
{
m_storagePrefix = prefix;
diff --git a/api/logic/minecraft/Library.h b/api/logic/minecraft/Library.h
index 5fcff316..acdd6c9c 100644
--- a/api/logic/minecraft/Library.h
+++ b/api/logic/minecraft/Library.h
@@ -12,7 +12,6 @@
#include "Rule.h"
#include "minecraft/OpSys.h"
#include "GradleSpecifier.h"
-#include "net/URLConstants.h"
#include "MojangDownloadInfo.h"
#include "multimc_logic_export.h"
@@ -148,9 +147,15 @@ public: /* methods */
/// Returns true if the library is contained in an instance and false if it is shared
bool isLocal() const;
+ /// Returns true if the library is to always be checked for updates
+ bool isAlwaysStale() const;
+
+ /// Return true if the library requires forge XZ hacks
+ bool isForge() const;
+
// Get a list of downloads for this library
QList<NetActionPtr> getDownloads(OpSys system, class HttpMetaCache * cache,
- QStringList & failedFiles, const QString & overridePath) const;
+ QStringList & failedLocalFiles, const QString & overridePath) const;
private: /* methods */
/// the default storage prefix used by MultiMC
diff --git a/api/logic/minecraft/Library_test.cpp b/api/logic/minecraft/Library_test.cpp
index 4f9c8006..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)
@@ -65,7 +66,7 @@ slots:
test.setHint("local");
auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString());
QCOMPARE(downloads.size(), 0);
- QCOMPARE(failedFiles, getStorage("test/package/testname/testversion/testname-testversion.jar"));
+ QCOMPARE(failedFiles, {"testname-testversion.jar"});
}
void test_legacy_url_local_override()
{
@@ -170,11 +171,11 @@ slots:
QCOMPARE(jar, {});
QCOMPARE(native, {});
QCOMPARE(native32, {QFileInfo("data/testname-testversion-linux-32.jar").absoluteFilePath()});
- QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar"));
+ QCOMPARE(native64, {QFileInfo("data/testname-testversion-linux-64.jar").absoluteFilePath()});
QStringList failedFiles;
auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString("data"));
QCOMPARE(dls.size(), 0);
- QCOMPARE(failedFiles, {getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")});
+ QCOMPARE(failedFiles, {"data/testname-testversion-linux-64.jar"});
}
}
void test_onenine()
diff --git a/api/logic/minecraft/MinecraftInstance.cpp b/api/logic/minecraft/MinecraftInstance.cpp
index 7c83b890..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"
@@ -20,22 +21,28 @@
#include "minecraft/launch/DirectJavaLaunch.h"
#include "minecraft/launch/ModMinecraftJar.h"
#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 "SimpleModList.h"
-#include "ModsModel.h"
+#include "mod/ModFolderModel.h"
+#include "mod/ResourcePackFolderModel.h"
+#include "mod/TexturePackFolderModel.h"
#include "WorldList.h"
#include "icons/IIconList.h"
#include <QCoreApplication>
-#include "ComponentList.h"
+#include "PackProfile.h"
#include "AssetsUtils.h"
#include "MinecraftUpdate.h"
#include "MinecraftLoadAndCheck.h"
+#include <minecraft/gameoptions/GameOptions.h>
+#include <minecraft/update/FoldersTask.h>
#define IBUS "@im=ibus"
@@ -98,13 +105,27 @@ 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", "");
m_settings->registerSetting("ForgeVersion", "");
m_settings->registerSetting("LiteloaderVersion", "");
- m_components.reset(new ComponentList(this));
+ m_components.reset(new PackProfile(this));
m_components->setOldConfigVersion("net.minecraft", m_settings->get("IntendedVersion").toString());
auto setting = m_settings->getSetting("LWJGLVersion");
m_components->setOldConfigVersion("org.lwjgl", m_settings->get("LWJGLVersion").toString());
@@ -122,14 +143,14 @@ QString MinecraftInstance::typeName() const
return "Minecraft";
}
-std::shared_ptr<ComponentList> MinecraftInstance::getComponentList() const
+std::shared_ptr<PackProfile> MinecraftInstance::getPackProfile() const
{
return m_components;
}
QSet<QString> MinecraftInstance::traits() const
{
- auto components = getComponentList();
+ auto components = getPackProfile();
if (!components)
{
return {"version-incomplete"};
@@ -170,6 +191,12 @@ QString MinecraftInstance::getLocalLibraryPath() const
return libraries_dir.absolutePath();
}
+QString MinecraftInstance::jarModsDir() const
+{
+ QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/"));
+ return jarmods_dir.absolutePath();
+}
+
QString MinecraftInstance::loaderModsDir() const
{
return FS::PathCombine(gameRoot(), "mods");
@@ -200,11 +227,6 @@ QString MinecraftInstance::instanceConfigFolder() const
return FS::PathCombine(gameRoot(), "config");
}
-QString MinecraftInstance::jarModsDir() const
-{
- return FS::PathCombine(instanceRoot(), "jarmods");
-}
-
QString MinecraftInstance::libDir() const
{
return FS::PathCombine(gameRoot(), "lib");
@@ -215,6 +237,11 @@ QString MinecraftInstance::worldDir() const
return FS::PathCombine(gameRoot(), "saves");
}
+QString MinecraftInstance::resourcesDir() const
+{
+ return FS::PathCombine(gameRoot(), "resources");
+}
+
QDir MinecraftInstance::librariesPath() const
{
return QDir::current().absoluteFilePath("libraries");
@@ -257,7 +284,7 @@ QStringList MinecraftInstance::getNativeJars() const
QStringList MinecraftInstance::extraArguments() const
{
auto list = BaseInstance::extraArguments();
- auto version = getComponentList();
+ auto version = getPackProfile();
if (!version)
return list;
auto jarMods = getJarMods();
@@ -376,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();
@@ -385,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)
@@ -407,8 +441,7 @@ QStringList MinecraftInstance::processMinecraftArgs(AuthSessionPtr session) cons
token_mapping["game_directory"] = absRootDir;
QString absAssetsDir = QDir("assets/").absolutePath();
auto assets = profile->getMinecraftAssets();
- // FIXME: this is wrong and should be run as an async task
- token_mapping["game_assets"] = AssetsUtils::reconstructAssets(assets->id).absolutePath();
+ token_mapping["game_assets"] = AssetsUtils::getAssetsDir(assets->id, resourcesDir()).absolutePath();
// 1.7.3+ assets tokens
token_mapping["assets_root"] = absAssetsDir;
@@ -422,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;
@@ -443,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";
}
@@ -494,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() << "";
@@ -513,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)
{
@@ -544,37 +598,38 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session)
out << "";
}
- if(loaderModList()->size())
- {
- out << "Mods:";
- for(auto & mod: loaderModList()->allMods())
+ auto printModList = [&](const QString & label, ModFolderModel & model) {
+ if(model.size())
{
- if(!mod.enabled())
- continue;
- if(mod.type() == Mod::MOD_FOLDER)
- continue;
- // TODO: proper implementation would need to descend into folders.
+ out << QString("%1:").arg(label);
+ auto modList = model.allMods();
+ std::sort(modList.begin(), modList.end(), [](Mod &a, Mod &b) {
+ auto aName = a.filename().completeBaseName();
+ auto bName = b.filename().completeBaseName();
+ return aName.localeAwareCompare(bName) < 0;
+ });
+ for(auto & mod: modList)
+ {
+ if(mod.type() == Mod::MOD_FOLDER)
+ {
+ out << u8" [📁] " + mod.filename().completeBaseName() + " (folder)";
+ continue;
+ }
+
+ if(mod.enabled()) {
+ out << u8" [✔️] " + mod.filename().completeBaseName();
+ }
+ else {
+ out << u8" [❌] " + mod.filename().completeBaseName() + " (disabled)";
+ }
- out << " " + mod.filename().completeBaseName();
+ }
+ out << "";
}
- out << "";
- }
-
- if(coreModList()->size())
- {
- out << "Core Mods:";
- for(auto & coremod: coreModList()->allMods())
- {
- if(!coremod.enabled())
- continue;
- if(coremod.type() == Mod::MOD_FOLDER)
- continue;
- // TODO: proper implementation would need to descend into folders.
+ };
- out << " " + coremod.filename().completeBaseName();
- }
- out << "";
- }
+ printModList("Mods", *(loaderModList().get()));
+ printModList("Core Mods", *(coreModList().get()));
auto & jarMods = profile->getJarMods();
if(jarMods.size())
@@ -596,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 << "";
@@ -642,8 +697,7 @@ QMap<QString, QString> MinecraftInstance::createCensorFilterFromSession(AuthSess
auto i = sessionRef.u.properties.begin();
while (i != sessionRef.u.properties.end())
{
- if(i.key() == "preferredLanguage")
- {
+ if(i.value().length() <= 3) {
++i;
continue;
}
@@ -744,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())
{
@@ -765,28 +825,28 @@ shared_qobject_ptr<Task> MinecraftInstance::createUpdateTask(Net::Mode mode)
}
case Net::Mode::Online:
{
- return shared_qobject_ptr<Task>(new OneSixUpdate(this));
+ return shared_qobject_ptr<Task>(new MinecraftUpdate(this));
}
}
return nullptr;
}
-std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session)
+shared_qobject_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin)
{
- auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(getSharedPtr()));
+ // FIXME: get rid of shared_from_this ...
+ auto process = LaunchTask::create(std::dynamic_pointer_cast<MinecraftInstance>(shared_from_this()));
auto pptr = process.get();
ENV.icons()->saveIcon(iconKey(), FS::PathCombine(gameRoot(), "icon.png"), "PNG");
// print a header
{
- process->appendStep(std::make_shared<TextPrint>(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC));
+ process->appendStep(new TextPrint(pptr, "Minecraft folder is:\n" + gameRoot() + "\n\n", MessageLevel::MultiMC));
}
// check java
{
- auto step = std::make_shared<CheckJava>(pptr);
- process->appendStep(step);
+ process->appendStep(new CheckJava(pptr));
}
// check launch method
@@ -794,14 +854,34 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
QString method = launchMethod();
if(!validMethods.contains(method))
{
- process->appendStep(std::make_shared<TextPrint>(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
+ process->appendStep(new TextPrint(pptr, "Selected launch method \"" + method + "\" is not valid.\n", MessageLevel::Fatal));
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())
{
- auto step = std::make_shared<PreLaunchCommand>(pptr);
+ auto step = new PreLaunchCommand(pptr);
step->setWorkingDirectory(gameRoot());
process->appendStep(step);
}
@@ -809,36 +889,42 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
// if we aren't in offline mode,.
if(session->status != AuthSession::PlayableOffline)
{
- process->appendStep(std::make_shared<ClaimAccount>(pptr, session));
- process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Online));
+ process->appendStep(new ClaimAccount(pptr, session));
+ process->appendStep(new Update(pptr, Net::Mode::Online));
}
else
{
- process->appendStep(std::make_shared<Update>(pptr, Net::Mode::Offline));
+ process->appendStep(new Update(pptr, Net::Mode::Offline));
}
// if there are any jar mods
{
- auto step = std::make_shared<ModMinecraftJar>(pptr);
- process->appendStep(step);
+ process->appendStep(new ModMinecraftJar(pptr));
}
- // print some instance info here...
+ // Scan mods folders for mods
{
- auto step = std::make_shared<PrintInstanceInfo>(pptr, session);
- process->appendStep(step);
+ process->appendStep(new ScanModFolders(pptr));
}
- // create the server-resource-packs folder (workaround for Minecraft bug MCL-3732)
+ // print some instance info here...
{
- auto step = std::make_shared<CreateServerResourcePacksFolder>(pptr);
- process->appendStep(step);
+ process->appendStep(new PrintInstanceInfo(pptr, session, serverToJoin));
}
// extract native jars if needed
{
- auto step = std::make_shared<ExtractNatives>(pptr);
- process->appendStep(step);
+ process->appendStep(new ExtractNatives(pptr));
+ }
+
+ // reconstruct assets if needed
+ {
+ process->appendStep(new ReconstructAssets(pptr));
+ }
+
+ // verify that minimum Java requirements are met
+ {
+ process->appendStep(new VerifyJavaInstall(pptr));
}
{
@@ -846,16 +932,18 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
auto method = launchMethod();
if(method == "LauncherPart")
{
- auto step = std::make_shared<LauncherPartLaunch>(pptr);
+ auto step = new LauncherPartLaunch(pptr);
step->setWorkingDirectory(gameRoot());
step->setAuthSession(session);
+ step->setServerToJoin(serverToJoin);
process->appendStep(step);
}
else if (method == "DirectJava")
{
- auto step = std::make_shared<DirectJavaLaunch>(pptr);
+ auto step = new DirectJavaLaunch(pptr);
step->setWorkingDirectory(gameRoot());
step->setAuthSession(session);
+ step->setServerToJoin(serverToJoin);
process->appendStep(step);
}
}
@@ -863,7 +951,7 @@ std::shared_ptr<LaunchTask> MinecraftInstance::createLaunchTask(AuthSessionPtr s
// run post-exit command if that's needed
if(getPostExitCommand().size())
{
- auto step = std::make_shared<PostLaunchCommand>(pptr);
+ auto step = new PostLaunchCommand(pptr);
step->setWorkingDirectory(gameRoot());
process->appendStep(step);
}
@@ -886,53 +974,47 @@ JavaVersion MinecraftInstance::getJavaVersion() const
return JavaVersion(settings()->get("JavaVersion").toString());
}
-std::shared_ptr<SimpleModList> MinecraftInstance::loaderModList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::loaderModList() const
{
if (!m_loader_mod_list)
{
- m_loader_mod_list.reset(new SimpleModList(loaderModsDir()));
+ m_loader_mod_list.reset(new ModFolderModel(loaderModsDir()));
+ m_loader_mod_list->disableInteraction(isRunning());
+ connect(this, &BaseInstance::runningStatusChanged, m_loader_mod_list.get(), &ModFolderModel::disableInteraction);
}
- m_loader_mod_list->update();
return m_loader_mod_list;
}
-std::shared_ptr<ModsModel> MinecraftInstance::modsModel() const
-{
- if (!m_mods_model)
- {
- m_mods_model.reset(new ModsModel(loaderModsDir(), coreModsDir(), modsCacheLocation()));
- }
- m_mods_model->update();
- return m_mods_model;
-}
-
-std::shared_ptr<SimpleModList> MinecraftInstance::coreModList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::coreModList() const
{
if (!m_core_mod_list)
{
- m_core_mod_list.reset(new SimpleModList(coreModsDir()));
+ m_core_mod_list.reset(new ModFolderModel(coreModsDir()));
+ m_core_mod_list->disableInteraction(isRunning());
+ connect(this, &BaseInstance::runningStatusChanged, m_core_mod_list.get(), &ModFolderModel::disableInteraction);
}
- m_core_mod_list->update();
return m_core_mod_list;
}
-std::shared_ptr<SimpleModList> MinecraftInstance::resourcePackList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::resourcePackList() const
{
if (!m_resource_pack_list)
{
- m_resource_pack_list.reset(new SimpleModList(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);
}
- m_resource_pack_list->update();
return m_resource_pack_list;
}
-std::shared_ptr<SimpleModList> MinecraftInstance::texturePackList() const
+std::shared_ptr<ModFolderModel> MinecraftInstance::texturePackList() const
{
if (!m_texture_pack_list)
{
- m_texture_pack_list.reset(new SimpleModList(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);
}
- m_texture_pack_list->update();
return m_texture_pack_list;
}
@@ -945,6 +1027,15 @@ std::shared_ptr<WorldList> MinecraftInstance::worldList() const
return m_world_list;
}
+std::shared_ptr<GameOptions> MinecraftInstance::gameOptionsModel() const
+{
+ if (!m_game_options)
+ {
+ m_game_options.reset(new GameOptions(FS::PathCombine(gameRoot(), "options.txt")));
+ }
+ return m_game_options;
+}
+
QList< Mod > MinecraftInstance::getJarMods() const
{
auto profile = m_components->getProfile();
diff --git a/api/logic/minecraft/MinecraftInstance.h b/api/logic/minecraft/MinecraftInstance.h
index 5f0fa353..05600797 100644
--- a/api/logic/minecraft/MinecraftInstance.h
+++ b/api/logic/minecraft/MinecraftInstance.h
@@ -1,16 +1,17 @@
#pragma once
#include "BaseInstance.h"
#include <java/JavaVersion.h>
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
#include <QProcess>
#include <QDir>
#include "multimc_logic_export.h"
+#include "minecraft/launch/MinecraftServerTarget.h"
-class ModsModel;
-class SimpleModList;
+class ModFolderModel;
class WorldList;
+class GameOptions;
class LaunchStep;
-class ComponentList;
+class PackProfile;
class MULTIMC_LOGIC_EXPORT MinecraftInstance: public BaseInstance
{
@@ -44,6 +45,7 @@ public:
QString modsCacheLocation() const;
QString libDir() const;
QString worldDir() const;
+ QString resourcesDir() const;
QDir jarmodsPath() const;
QDir librariesPath() const;
QDir versionsPath() const;
@@ -63,24 +65,23 @@ public:
////// Profile management //////
- std::shared_ptr<ComponentList> getComponentList() const;
+ std::shared_ptr<PackProfile> getPackProfile() const;
////// Mod Lists //////
- std::shared_ptr<ModsModel> modsModel() const;
- std::shared_ptr<SimpleModList> loaderModList() const;
- std::shared_ptr<SimpleModList> coreModList() const;
- std::shared_ptr<SimpleModList> resourcePackList() const;
- std::shared_ptr<SimpleModList> texturePackList() const;
+ std::shared_ptr<ModFolderModel> loaderModList() const;
+ std::shared_ptr<ModFolderModel> coreModList() const;
+ std::shared_ptr<ModFolderModel> resourcePackList() const;
+ std::shared_ptr<ModFolderModel> texturePackList() const;
std::shared_ptr<WorldList> worldList() const;
-
+ std::shared_ptr<GameOptions> gameOptionsModel() const;
////// Launch stuff //////
shared_qobject_ptr<Task> createUpdateTask(Net::Mode mode) override;
- std::shared_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,13 +108,10 @@ 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;
-signals:
- void versionReloaded();
-
protected:
QMap<QString, QString> createCensorFilterFromSession(AuthSessionPtr session);
QStringList validLaunchMethods();
@@ -123,13 +121,13 @@ private:
QString prettifyTimeDuration(int64_t duration);
protected: // data
- std::shared_ptr<ComponentList> m_components;
- mutable std::shared_ptr<ModsModel> m_mods_model;
- mutable std::shared_ptr<SimpleModList> m_loader_mod_list;
- mutable std::shared_ptr<SimpleModList> m_core_mod_list;
- mutable std::shared_ptr<SimpleModList> m_resource_pack_list;
- mutable std::shared_ptr<SimpleModList> m_texture_pack_list;
+ std::shared_ptr<PackProfile> m_components;
+ mutable std::shared_ptr<ModFolderModel> m_loader_mod_list;
+ mutable std::shared_ptr<ModFolderModel> m_core_mod_list;
+ mutable std::shared_ptr<ModFolderModel> m_resource_pack_list;
+ mutable std::shared_ptr<ModFolderModel> m_texture_pack_list;
mutable std::shared_ptr<WorldList> m_world_list;
+ mutable std::shared_ptr<GameOptions> m_game_options;
};
typedef std::shared_ptr<MinecraftInstance> MinecraftInstancePtr;
diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.cpp b/api/logic/minecraft/MinecraftLoadAndCheck.cpp
index a5052b53..79b0c484 100644
--- a/api/logic/minecraft/MinecraftLoadAndCheck.cpp
+++ b/api/logic/minecraft/MinecraftLoadAndCheck.cpp
@@ -1,6 +1,6 @@
#include "MinecraftLoadAndCheck.h"
#include "MinecraftInstance.h"
-#include "ComponentList.h"
+#include "PackProfile.h"
MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
{
@@ -9,7 +9,7 @@ MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance *inst, QObject *p
void MinecraftLoadAndCheck::executeTask()
{
// add offline metadata load task
- auto components = m_inst->getComponentList();
+ auto components = m_inst->getPackProfile();
components->reload(Net::Mode::Offline);
m_task = components->getCurrentTask();
@@ -28,7 +28,7 @@ void MinecraftLoadAndCheck::subtaskSucceeded()
{
if(isFinished())
{
- qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
+ qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
return;
}
emitSucceeded();
@@ -38,7 +38,7 @@ void MinecraftLoadAndCheck::subtaskFailed(QString error)
{
if(isFinished())
{
- qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
+ qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
return;
}
emitFailed(error);
diff --git a/api/logic/minecraft/MinecraftLoadAndCheck.h b/api/logic/minecraft/MinecraftLoadAndCheck.h
index 1f5c2018..3435b52b 100644
--- a/api/logic/minecraft/MinecraftLoadAndCheck.h
+++ b/api/logic/minecraft/MinecraftLoadAndCheck.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e62ff745..8f1565b0 100644
--- a/api/logic/minecraft/MinecraftUpdate.cpp
+++ b/api/logic/minecraft/MinecraftUpdate.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -14,7 +14,6 @@
*/
#include "Env.h"
-#include <minecraft/forge/ForgeXzDownload.h>
#include "MinecraftUpdate.h"
#include "MinecraftInstance.h"
@@ -24,9 +23,8 @@
#include <QDataStream>
#include "BaseInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include "minecraft/Library.h"
-#include "net/URLConstants.h"
#include <FileSystem.h>
#include "update/FoldersTask.h"
@@ -37,11 +35,11 @@
#include <meta/Index.h>
#include <meta/Version.h>
-OneSixUpdate::OneSixUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
+MinecraftUpdate::MinecraftUpdate(MinecraftInstance *inst, QObject *parent) : Task(parent), m_inst(inst)
{
}
-void OneSixUpdate::executeTask()
+void MinecraftUpdate::executeTask()
{
m_tasks.clear();
// create folders
@@ -51,7 +49,7 @@ void OneSixUpdate::executeTask()
// add metadata update task if necessary
{
- auto components = m_inst->getComponentList();
+ auto components = m_inst->getPackProfile();
components->reload(Net::Mode::Online);
auto task = components->getCurrentTask();
if(task)
@@ -83,7 +81,7 @@ void OneSixUpdate::executeTask()
next();
}
-void OneSixUpdate::next()
+void MinecraftUpdate::next()
{
if(m_abort)
{
@@ -99,10 +97,10 @@ void OneSixUpdate::next()
if(m_currentTask > 0)
{
auto task = m_tasks[m_currentTask - 1];
- disconnect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
- disconnect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
- disconnect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
- disconnect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
+ disconnect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
+ disconnect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
+ disconnect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
+ disconnect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
}
if(m_currentTask == m_tasks.size())
{
@@ -113,13 +111,13 @@ void OneSixUpdate::next()
// if the task is already finished by the time we look at it, skip it
if(task->isFinished())
{
- qCritical() << "OneSixUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
+ qCritical() << "MinecraftUpdate: Skipping finished subtask" << m_currentTask << ":" << task.get();
next();
}
- connect(task.get(), &Task::succeeded, this, &OneSixUpdate::subtaskSucceeded);
- connect(task.get(), &Task::failed, this, &OneSixUpdate::subtaskFailed);
- connect(task.get(), &Task::progress, this, &OneSixUpdate::progress);
- connect(task.get(), &Task::status, this, &OneSixUpdate::setStatus);
+ connect(task.get(), &Task::succeeded, this, &MinecraftUpdate::subtaskSucceeded);
+ connect(task.get(), &Task::failed, this, &MinecraftUpdate::subtaskFailed);
+ connect(task.get(), &Task::progress, this, &MinecraftUpdate::progress);
+ connect(task.get(), &Task::status, this, &MinecraftUpdate::setStatus);
// if the task is already running, do not start it again
if(!task->isRunning())
{
@@ -127,35 +125,35 @@ void OneSixUpdate::next()
}
}
-void OneSixUpdate::subtaskSucceeded()
+void MinecraftUpdate::subtaskSucceeded()
{
if(isFinished())
{
- qCritical() << "OneSixUpdate: Subtask" << sender() << "succeeded, but work was already done!";
+ qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if(senderTask != currentTask)
{
- qDebug() << "OneSixUpdate: Subtask" << sender() << "succeeded out of order.";
+ qDebug() << "MinecraftUpdate: Subtask" << sender() << "succeeded out of order.";
return;
}
next();
}
-void OneSixUpdate::subtaskFailed(QString error)
+void MinecraftUpdate::subtaskFailed(QString error)
{
if(isFinished())
{
- qCritical() << "OneSixUpdate: Subtask" << sender() << "failed, but work was already done!";
+ qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!";
return;
}
auto senderTask = QObject::sender();
auto currentTask = m_tasks[m_currentTask].get();
if(senderTask != currentTask)
{
- qDebug() << "OneSixUpdate: Subtask" << sender() << "failed out of order.";
+ qDebug() << "MinecraftUpdate: Subtask" << sender() << "failed out of order.";
m_failed_out_of_order = true;
m_fail_reason = error;
return;
@@ -164,7 +162,7 @@ void OneSixUpdate::subtaskFailed(QString error)
}
-bool OneSixUpdate::abort()
+bool MinecraftUpdate::abort()
{
if(!m_abort)
{
@@ -178,7 +176,7 @@ bool OneSixUpdate::abort()
return true;
}
-bool OneSixUpdate::canAbort() const
+bool MinecraftUpdate::canAbort() const
{
return true;
}
diff --git a/api/logic/minecraft/MinecraftUpdate.h b/api/logic/minecraft/MinecraftUpdate.h
index f7b37d73..fadebff9 100644
--- a/api/logic/minecraft/MinecraftUpdate.h
+++ b/api/logic/minecraft/MinecraftUpdate.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,12 +27,12 @@
class MinecraftVersion;
class MinecraftInstance;
-class OneSixUpdate : public Task
+class MinecraftUpdate : public Task
{
Q_OBJECT
public:
- explicit OneSixUpdate(MinecraftInstance *inst, QObject *parent = 0);
- virtual ~OneSixUpdate() {};
+ explicit MinecraftUpdate(MinecraftInstance *inst, QObject *parent = 0);
+ virtual ~MinecraftUpdate() {};
void executeTask() override;
bool canAbort() const override;
diff --git a/api/logic/minecraft/Mod.cpp b/api/logic/minecraft/Mod.cpp
deleted file mode 100644
index bd209211..00000000
--- a/api/logic/minecraft/Mod.cpp
+++ /dev/null
@@ -1,378 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <QDir>
-#include <QString>
-#include <QJsonDocument>
-#include <QJsonObject>
-#include <QJsonArray>
-#include <QJsonValue>
-#include <quazip.h>
-#include <quazipfile.h>
-
-#include "Mod.h"
-#include "settings/INIFile.h"
-#include <FileSystem.h>
-#include <QDebug>
-
-Mod::Mod(const QFileInfo &file)
-{
- repath(file);
- m_changedDateTime = file.lastModified();
-}
-
-void Mod::repath(const QFileInfo &file)
-{
- m_file = file;
- QString name_base = file.fileName();
-
- m_type = Mod::MOD_UNKNOWN;
-
- if (m_file.isDir())
- {
- m_type = MOD_FOLDER;
- m_name = name_base;
- m_mmc_id = name_base;
- }
- else if (m_file.isFile())
- {
- if (name_base.endsWith(".disabled"))
- {
- m_enabled = false;
- name_base.chop(9);
- }
- else
- {
- m_enabled = true;
- }
- m_mmc_id = name_base;
- if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
- {
- m_type = MOD_ZIPFILE;
- name_base.chop(4);
- }
- else if (name_base.endsWith(".litemod"))
- {
- m_type = MOD_LITEMOD;
- name_base.chop(8);
- }
- else
- {
- m_type = MOD_SINGLEFILE;
- }
- m_name = name_base;
- }
-
- if (m_type == MOD_ZIPFILE)
- {
- QuaZip zip(m_file.filePath());
- if (!zip.open(QuaZip::mdUnzip))
- return;
-
- QuaZipFile file(&zip);
-
- if (zip.setCurrentFile("mcmod.info"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- ReadMCModInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
- else if (zip.setCurrentFile("forgeversion.properties"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- ReadForgeInfo(file.readAll());
- file.close();
- zip.close();
- return;
- }
-
- zip.close();
- }
- else if (m_type == MOD_FOLDER)
- {
- QFileInfo mcmod_info(FS::PathCombine(m_file.filePath(), "mcmod.info"));
- if (mcmod_info.isFile())
- {
- QFile mcmod(mcmod_info.filePath());
- if (!mcmod.open(QIODevice::ReadOnly))
- return;
- auto data = mcmod.readAll();
- if (data.isEmpty() || data.isNull())
- return;
- ReadMCModInfo(data);
- }
- }
- else if (m_type == MOD_LITEMOD)
- {
- QuaZip zip(m_file.filePath());
- if (!zip.open(QuaZip::mdUnzip))
- return;
-
- QuaZipFile file(&zip);
-
- if (zip.setCurrentFile("litemod.json"))
- {
- if (!file.open(QIODevice::ReadOnly))
- {
- zip.close();
- return;
- }
-
- ReadLiteModInfo(file.readAll());
- file.close();
- }
- zip.close();
- }
-}
-
-// NEW format
-// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
-
-// OLD format:
-// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
-void Mod::ReadMCModInfo(QByteArray contents)
-{
- auto getInfoFromArray = [&](QJsonArray arr)->void
- {
- if (!arr.at(0).isObject())
- return;
- auto firstObj = arr.at(0).toObject();
- m_mod_id = firstObj.value("modid").toString();
- m_name = firstObj.value("name").toString();
- m_version = firstObj.value("version").toString();
- m_homeurl = firstObj.value("url").toString();
- m_updateurl = firstObj.value("updateUrl").toString();
- m_homeurl = m_homeurl.trimmed();
- if(!m_homeurl.isEmpty())
- {
- // fix up url.
- if (!m_homeurl.startsWith("http://") && !m_homeurl.startsWith("https://") &&
- !m_homeurl.startsWith("ftp://"))
- {
- m_homeurl.prepend("http://");
- }
- }
- m_description = firstObj.value("description").toString();
- QJsonArray authors = firstObj.value("authorList").toArray();
- if (authors.size() == 0)
- authors = firstObj.value("authors").toArray();
-
- if (authors.size() == 0)
- m_authors = "";
- else if (authors.size() >= 1)
- {
- m_authors = authors.at(0).toString();
- for (int i = 1; i < authors.size(); i++)
- {
- m_authors += ", " + authors.at(i).toString();
- }
- }
- m_credits = firstObj.value("credits").toString();
- return;
- }
- ;
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- // this is the very old format that had just the array
- if (jsonDoc.isArray())
- {
- getInfoFromArray(jsonDoc.array());
- }
- else if (jsonDoc.isObject())
- {
- auto val = jsonDoc.object().value("modinfoversion");
- if(val.isUndefined())
- val = jsonDoc.object().value("modListVersion");
- int version = val.toDouble();
- if (version != 2)
- {
- qCritical() << "BAD stuff happened to mod json:";
- qCritical() << contents;
- return;
- }
- auto arrVal = jsonDoc.object().value("modlist");
- if(arrVal.isUndefined())
- arrVal = jsonDoc.object().value("modList");
- if (arrVal.isArray())
- {
- getInfoFromArray(arrVal.toArray());
- }
- }
-}
-
-void Mod::ReadForgeInfo(QByteArray contents)
-{
- // Read the data
- m_name = "Minecraft Forge";
- m_mod_id = "Forge";
- m_homeurl = "http://www.minecraftforge.net/forum/";
- INIFile ini;
- if (!ini.loadFile(contents))
- return;
-
- QString major = ini.get("forge.major.number", "0").toString();
- QString minor = ini.get("forge.minor.number", "0").toString();
- QString revision = ini.get("forge.revision.number", "0").toString();
- QString build = ini.get("forge.build.number", "0").toString();
-
- m_version = major + "." + minor + "." + revision + "." + build;
-}
-
-void Mod::ReadLiteModInfo(QByteArray contents)
-{
- QJsonParseError jsonError;
- QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
- auto object = jsonDoc.object();
- if (object.contains("name"))
- {
- m_mod_id = m_name = object.value("name").toString();
- }
- if (object.contains("version"))
- {
- m_version = object.value("version").toString("");
- }
- else
- {
- m_version = object.value("revision").toString("");
- }
- m_mcversion = object.value("mcversion").toString();
- m_authors = object.value("author").toString();
- m_description = object.value("description").toString();
- m_homeurl = object.value("url").toString();
-}
-
-bool Mod::replace(Mod &with)
-{
- if (!destroy())
- return false;
- bool success = false;
- auto t = with.type();
-
- if (t == MOD_ZIPFILE || t == MOD_SINGLEFILE || t == MOD_LITEMOD)
- {
- qDebug() << "Copy: " << with.m_file.filePath() << " to " << m_file.filePath();
- success = QFile::copy(with.m_file.filePath(), m_file.filePath());
- }
- if (t == MOD_FOLDER)
- {
- success = FS::copy(with.m_file.filePath(), m_file.path())();
- }
- if (success)
- {
- m_name = with.m_name;
- m_mmc_id = with.m_mmc_id;
- m_mod_id = with.m_mod_id;
- m_version = with.m_version;
- m_mcversion = with.m_mcversion;
- m_description = with.m_description;
- m_authors = with.m_authors;
- m_credits = with.m_credits;
- m_homeurl = with.m_homeurl;
- m_type = with.m_type;
- m_file.refresh();
- }
- return success;
-}
-
-bool Mod::destroy()
-{
- if (m_type == MOD_FOLDER)
- {
- QDir d(m_file.filePath());
- if (d.removeRecursively())
- {
- m_type = MOD_UNKNOWN;
- return true;
- }
- return false;
- }
- else if (m_type == MOD_SINGLEFILE || m_type == MOD_ZIPFILE || m_type == MOD_LITEMOD)
- {
- QFile f(m_file.filePath());
- if (f.remove())
- {
- m_type = MOD_UNKNOWN;
- return true;
- }
- return false;
- }
- return true;
-}
-
-QString Mod::version() const
-{
- switch (type())
- {
- case MOD_ZIPFILE:
- case MOD_LITEMOD:
- return m_version;
- case MOD_FOLDER:
- return "Folder";
- case MOD_SINGLEFILE:
- return "File";
- default:
- return "VOID";
- }
-}
-
-bool Mod::enable(bool value)
-{
- if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
- return false;
-
- if (m_enabled == value)
- return false;
-
- QString path = m_file.absoluteFilePath();
- if (value)
- {
- QFile foo(path);
- if (!path.endsWith(".disabled"))
- return false;
- path.chop(9);
- if (!foo.rename(path))
- return false;
- }
- else
- {
- QFile foo(path);
- path += ".disabled";
- if (!foo.rename(path))
- return false;
- }
- m_file = QFileInfo(path);
- m_enabled = value;
- return true;
-}
-bool Mod::operator==(const Mod &other) const
-{
- return mmc_id() == other.mmc_id();
-}
-bool Mod::strongCompare(const Mod &other) const
-{
- return mmc_id() == other.mmc_id() && version() == other.version() && type() == other.type();
-}
diff --git a/api/logic/minecraft/ModsModel.cpp b/api/logic/minecraft/ModsModel.cpp
deleted file mode 100644
index e401618a..00000000
--- a/api/logic/minecraft/ModsModel.cpp
+++ /dev/null
@@ -1,374 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "ModsModel.h"
-#include <FileSystem.h>
-#include <QMimeData>
-#include <QUrl>
-#include <QUuid>
-#include <QString>
-#include <QFileSystemWatcher>
-#include <QDebug>
-
-ModsModel::ModsModel(const QString &mainDir, const QString &coreDir, const QString &cacheLocation)
- :QAbstractListModel(), m_mainDir(mainDir), m_coreDir(coreDir)
-{
- FS::ensureFolderPathExists(m_mainDir.absolutePath());
- m_mainDir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
- QDir::NoSymLinks);
- m_mainDir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
- m_watcher = new QFileSystemWatcher(this);
- connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
-}
-
-void ModsModel::startWatching()
-{
- if(is_watching)
- return;
-
- update();
-
- is_watching = m_watcher->addPath(m_mainDir.absolutePath());
- if (is_watching)
- {
- qDebug() << "Started watching " << m_mainDir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to start watching " << m_mainDir.absolutePath();
- }
-}
-
-void ModsModel::stopWatching()
-{
- if(!is_watching)
- return;
-
- is_watching = !m_watcher->removePath(m_mainDir.absolutePath());
- if (!is_watching)
- {
- qDebug() << "Stopped watching " << m_mainDir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to stop watching " << m_mainDir.absolutePath();
- }
-}
-
-bool ModsModel::update()
-{
- if (!isValid())
- return false;
-
- QList<Mod> orderedMods;
- QList<Mod> newMods;
- m_mainDir.refresh();
- auto folderContents = m_mainDir.entryInfoList();
- bool orderOrStateChanged = false;
-
- // if there are any untracked files...
- if (folderContents.size())
- {
- // the order surely changed!
- for (auto entry : folderContents)
- {
- newMods.append(Mod(entry));
- }
- orderedMods.append(newMods);
- orderOrStateChanged = true;
- }
- // otherwise, if we were already tracking some mods
- else if (mods.size())
- {
- // if the number doesn't match, order changed.
- if (mods.size() != orderedMods.size())
- orderOrStateChanged = true;
- // if it does match, compare the mods themselves
- else
- for (int i = 0; i < mods.size(); i++)
- {
- if (!mods[i].strongCompare(orderedMods[i]))
- {
- orderOrStateChanged = true;
- break;
- }
- }
- }
- beginResetModel();
- mods.swap(orderedMods);
- endResetModel();
- if (orderOrStateChanged)
- {
- emit changed();
- }
- return true;
-}
-
-void ModsModel::directoryChanged(QString path)
-{
- update();
-}
-
-bool ModsModel::isValid()
-{
- return m_mainDir.exists() && m_mainDir.isReadable();
-}
-
-bool ModsModel::installMod(const QString &filename)
-{
- // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
- QFileInfo fileinfo(FS::NormalizePath(filename));
-
- qDebug() << "installing: " << fileinfo.absoluteFilePath();
-
- if (!fileinfo.exists() || !fileinfo.isReadable())
- {
- return false;
- }
- Mod m(fileinfo);
- if (!m.valid())
- return false;
-
- auto type = m.type();
- if (type == Mod::MOD_UNKNOWN)
- return false;
- if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
- {
- QString newpath = FS::PathCombine(m_mainDir.path(), fileinfo.fileName());
- if (!QFile::copy(fileinfo.filePath(), newpath))
- return false;
- FS::updateTimestamp(newpath);
- m.repath(newpath);
- update();
- return true;
- }
- else if (type == Mod::MOD_FOLDER)
- {
- QString from = fileinfo.filePath();
- QString to = FS::PathCombine(m_mainDir.path(), fileinfo.fileName());
- if (!FS::copy(from, to)())
- return false;
- m.repath(to);
- update();
- return true;
- }
- return false;
-}
-
-bool ModsModel::enableMods(const QModelIndexList& indexes, bool enable)
-{
- if(indexes.isEmpty())
- return true;
-
- for (auto i: indexes)
- {
- Mod &m = mods[i.row()];
- m.enable(enable);
- emit dataChanged(i, i);
- }
- emit changed();
- return true;
-}
-
-bool ModsModel::deleteMods(const QModelIndexList& indexes)
-{
- if(indexes.isEmpty())
- return true;
-
- for (auto i: indexes)
- {
- Mod &m = mods[i.row()];
- m.destroy();
- }
- emit changed();
- return true;
-}
-
-int ModsModel::columnCount(const QModelIndex &parent) const
-{
- return NUM_COLUMNS;
-}
-
-QVariant ModsModel::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
- int column = index.column();
-
- if (row < 0 || row >= mods.size())
- return QVariant();
-
- switch (role)
- {
- case Qt::DisplayRole:
- switch (column)
- {
- case NameColumn:
- return mods[row].name();
- case VersionColumn:
- return mods[row].version();
- case DateColumn:
- return mods[row].dateTimeChanged();
- case LocationColumn:
- return "Unknown";
-
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- return mods[row].mmc_id();
-
- case Qt::CheckStateRole:
- switch (column)
- {
- case ActiveColumn:
- return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
-}
-
-bool ModsModel::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
- {
- return false;
- }
-
- if (role == Qt::CheckStateRole)
- {
- auto &mod = mods[index.row()];
- if (mod.enable(!mod.enabled()))
- {
- emit dataChanged(index, index);
- return true;
- }
- }
- return false;
-}
-
-QVariant ModsModel::headerData(int section, Qt::Orientation orientation, int role) const
-{
- switch (role)
- {
- case Qt::DisplayRole:
- switch (section)
- {
- case ActiveColumn:
- return QString();
- case NameColumn:
- return tr("Name");
- case VersionColumn:
- return tr("Version");
- case DateColumn:
- return tr("Last changed");
- case LocationColumn:
- return tr("Location");
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- switch (section)
- {
- case ActiveColumn:
- return tr("Is the mod enabled?");
- case NameColumn:
- return tr("The name of the mod.");
- case VersionColumn:
- return tr("The version of the mod.");
- case DateColumn:
- return tr("The date and time this mod was last changed (or added).");
- case LocationColumn:
- return tr("Where the mod is located (inside or outside the instance).");
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
- return QVariant();
-}
-
-Qt::ItemFlags ModsModel::flags(const QModelIndex &index) const
-{
- Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- if (index.isValid())
- return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
- defaultFlags;
- else
- return Qt::ItemIsDropEnabled | defaultFlags;
-}
-
-Qt::DropActions ModsModel::supportedDropActions() const
-{
- // copy from outside, move from within and other mod lists
- return Qt::CopyAction | Qt::MoveAction;
-}
-
-QStringList ModsModel::mimeTypes() const
-{
- QStringList types;
- types << "text/uri-list";
- return types;
-}
-
-bool ModsModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
-{
- if (action == Qt::IgnoreAction)
- {
- return true;
- }
-
- // check if the action is supported
- if (!data || !(action & supportedDropActions()))
- {
- return false;
- }
-
- // files dropped from outside?
- if (data->hasUrls())
- {
- bool was_watching = is_watching;
- if (was_watching)
- {
- stopWatching();
- }
- auto urls = data->urls();
- for (auto url : urls)
- {
- // only local files may be dropped...
- if (!url.isLocalFile())
- {
- continue;
- }
- // TODO: implement not only copy, but also move
- // FIXME: handle errors here
- installMod(url.toLocalFile());
- }
- if (was_watching)
- {
- startWatching();
- }
- return true;
- }
- return false;
-}
diff --git a/api/logic/minecraft/ModsModel.h b/api/logic/minecraft/ModsModel.h
deleted file mode 100644
index b8980ac4..00000000
--- a/api/logic/minecraft/ModsModel.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QList>
-#include <QString>
-#include <QDir>
-#include <QAbstractListModel>
-
-#include "minecraft/Mod.h"
-
-#include "multimc_logic_export.h"
-
-class LegacyInstance;
-class BaseInstance;
-class QFileSystemWatcher;
-
-/**
- * A legacy mod list.
- * Backed by a folder.
- */
-class MULTIMC_LOGIC_EXPORT ModsModel : public QAbstractListModel
-{
- Q_OBJECT
-public:
- enum Columns
- {
- ActiveColumn = 0,
- NameColumn,
- DateColumn,
- VersionColumn,
- LocationColumn,
- NUM_COLUMNS
- };
- ModsModel(const QString &mainDir, const QString &coreDir, const QString &cacheFile);
-
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
- Qt::DropActions supportedDropActions() const override;
-
- /// flags, mostly to support drag&drop
- virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
- QStringList mimeTypes() const override;
- bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override;
-
- virtual int rowCount(const QModelIndex &) const override
- {
- return size();
- }
- ;
- virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
- virtual int columnCount(const QModelIndex &parent) const override;
-
- size_t size() const
- {
- return mods.size();
- }
- ;
- bool empty() const
- {
- return size() == 0;
- }
- Mod &operator[](size_t index)
- {
- return mods[index];
- }
-
- /// Reloads the mod list and returns true if the list changed.
- virtual bool update();
-
- /**
- * Adds the given mod to the list at the given index - if the list supports custom ordering
- */
- bool installMod(const QString& filename);
-
- /// Deletes all the selected mods
- virtual bool deleteMods(const QModelIndexList &indexes);
-
- /// Enable or disable listed mods
- virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
-
- void startWatching();
- void stopWatching();
-
- virtual bool isValid();
-
- QDir dir()
- {
- return m_mainDir;
- }
-
- const QList<Mod> & allMods()
- {
- return mods;
- }
-
-private
-slots:
- void directoryChanged(QString path);
-
-signals:
- void changed();
-
-protected:
- QFileSystemWatcher *m_watcher;
- bool is_watching = false;
- QDir m_mainDir;
- QDir m_coreDir;
- QList<Mod> mods;
-};
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 6f3b926b..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)
+ 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->libraries.append(lib);
+ auto lib = libraryFromJson(*out, libObj, filename);
+ outList.append(lib);
}
};
bool hasPlusLibs = root.contains("+libraries");
@@ -160,23 +160,27 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
{
out->addProblem(ProblemSeverity::Warning,
QObject::tr("Version file has both '+libraries' and 'libraries'. This is no longer supported."));
- readLibs("libraries");
- readLibs("+libraries");
+ readLibs("libraries", out->libraries);
+ readLibs("+libraries", out->libraries);
}
else if (hasLibs)
{
- readLibs("libraries");
+ readLibs("libraries", out->libraries);
}
else if(hasPlusLibs)
{
- readLibs("+libraries");
+ readLibs("+libraries", out->libraries);
+ }
+
+ if(root.contains("mavenFiles")) {
+ readLibs("mavenFiles", out->mavenFiles);
}
// if we have mainJar, just use it
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())
@@ -194,7 +198,10 @@ VersionFilePtr OneSixVersionFormat::versionFileFromJson(const QJsonDocument &doc
// FIXME: this will eventually break...
else
{
- lib->setAbsoluteUrl(URLConstants::getLegacyJarUrl(out->minecraftVersion));
+ out->addProblem(
+ ProblemSeverity::Error,
+ QObject::tr("URL for the main jar could not be determined - Mojang removed the server that we used as fallback.")
+ );
}
out->mainJar = lib;
}
@@ -276,6 +283,15 @@ QJsonDocument OneSixVersionFormat::versionFileToJson(const VersionFilePtr &patch
}
root.insert("libraries", array);
}
+ if (!patch->mavenFiles.isEmpty())
+ {
+ QJsonArray array;
+ for (auto value: patch->mavenFiles)
+ {
+ array.append(OneSixVersionFormat::libraryToJson(value.get()));
+ }
+ root.insert("mavenFiles", array);
+ }
if (!patch->jarMods.isEmpty())
{
QJsonArray array;
@@ -314,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"))
{
@@ -350,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);
}
@@ -361,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 6bbebca0..1a091d88 100644
--- a/api/logic/minecraft/OneSixVersionFormat.h
+++ b/api/logic/minecraft/OneSixVersionFormat.h
@@ -1,9 +1,10 @@
#pragma once
#include <minecraft/VersionFile.h>
-#include <minecraft/ComponentList.h>
+#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 2e18634b..f6a4ed1c 100644
--- a/api/logic/minecraft/OpSys.cpp
+++ b/api/logic/minecraft/OpSys.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 8ea84587..63c750b1 100644
--- a/api/logic/minecraft/OpSys.h
+++ b/api/logic/minecraft/OpSys.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/ComponentList.cpp b/api/logic/minecraft/PackProfile.cpp
index 7a865c60..f6918116 100644
--- a/api/logic/minecraft/ComponentList.cpp
+++ b/api/logic/minecraft/PackProfile.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -32,21 +32,23 @@
#include <QTimer>
#include <Json.h>
-#include "ComponentList.h"
-#include "ComponentList_p.h"
+#include "PackProfile.h"
+#include "PackProfile_p.h"
#include "ComponentUpdateTask.h"
-ComponentList::ComponentList(MinecraftInstance * instance)
+PackProfile::PackProfile(MinecraftInstance * instance)
: QAbstractListModel()
{
- d.reset(new ComponentListData);
+ d.reset(new PackProfileData);
d->m_instance = instance;
d->m_saveTimer.setSingleShot(true);
d->m_saveTimer.setInterval(5000);
- connect(&d->m_saveTimer, &QTimer::timeout, this, &ComponentList::save_internal);
+ d->interactionDisabled = instance->isRunning();
+ connect(d->m_instance, &BaseInstance::runningStatusChanged, this, &PackProfile::disableInteraction);
+ connect(&d->m_saveTimer, &QTimer::timeout, this, &PackProfile::save_internal);
}
-ComponentList::~ComponentList()
+PackProfile::~PackProfile()
{
saveNow();
}
@@ -95,7 +97,7 @@ static QJsonObject componentToJsonV1(ComponentPtr component)
return obj;
}
-static ComponentPtr componentFromJsonV1(ComponentList * parent, const QString & componentJsonPattern, const QJsonObject &obj)
+static ComponentPtr componentFromJsonV1(PackProfile * parent, const QString & componentJsonPattern, const QJsonObject &obj)
{
// critical
auto uid = Json::requireString(obj.value("uid"));
@@ -118,7 +120,7 @@ static ComponentPtr componentFromJsonV1(ComponentList * parent, const QString &
}
// Save the given component container data to a file
-static bool saveComponentList(const QString & filename, const ComponentContainer & container)
+static bool savePackProfile(const QString & filename, const ComponentContainer & container)
{
QJsonObject obj;
obj.insert("formatVersion", currentComponentsFileVersion);
@@ -151,7 +153,7 @@ static bool saveComponentList(const QString & filename, const ComponentContainer
}
// Read the given file into component containers
-static bool loadComponentList(ComponentList * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container)
+static bool loadPackProfile(PackProfile * parent, const QString & filename, const QString & componentJsonPattern, ComponentContainer & container)
{
QFile componentsFile(filename);
if (!componentsFile.exists())
@@ -208,7 +210,7 @@ static bool loadComponentList(ComponentList * parent, const QString & filename,
// BEGIN: save/load logic
-void ComponentList::saveNow()
+void PackProfile::saveNow()
{
if(saveIsScheduled())
{
@@ -217,18 +219,18 @@ void ComponentList::saveNow()
}
}
-bool ComponentList::saveIsScheduled() const
+bool PackProfile::saveIsScheduled() const
{
return d->dirty;
}
-void ComponentList::buildingFromScratch()
+void PackProfile::buildingFromScratch()
{
d->loaded = true;
d->dirty = true;
}
-void ComponentList::scheduleSave()
+void PackProfile::scheduleSave()
{
if(!d->loaded)
{
@@ -243,30 +245,30 @@ void ComponentList::scheduleSave()
d->m_saveTimer.start();
}
-QString ComponentList::componentsFilePath() const
+QString PackProfile::componentsFilePath() const
{
return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json");
}
-QString ComponentList::patchesPattern() const
+QString PackProfile::patchesPattern() const
{
return FS::PathCombine(d->m_instance->instanceRoot(), "patches", "%1.json");
}
-QString ComponentList::patchFilePathForUid(const QString& uid) const
+QString PackProfile::patchFilePathForUid(const QString& uid) const
{
return patchesPattern().arg(uid);
}
-void ComponentList::save_internal()
+void PackProfile::save_internal()
{
qDebug() << "Component list save performed now for" << d->m_instance->name();
auto filename = componentsFilePath();
- saveComponentList(filename, d->components);
+ savePackProfile(filename, d->components);
d->dirty = false;
}
-bool ComponentList::load()
+bool PackProfile::load()
{
auto filename = componentsFilePath();
QFile componentsFile(filename);
@@ -284,7 +286,7 @@ bool ComponentList::load()
// load the new component list and swap it with the current one...
ComponentContainer newComponents;
- if(!loadComponentList(this, filename, patchesPattern(), newComponents))
+ if(!loadPackProfile(this, filename, patchesPattern(), newComponents))
{
qCritical() << "Failed to load the component config for instance" << d->m_instance->name();
return false;
@@ -296,7 +298,7 @@ bool ComponentList::load()
// disconnect all the old components
for(auto component: d->components)
{
- disconnect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ disconnect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
}
d->components.clear();
d->componentIndex.clear();
@@ -307,7 +309,7 @@ bool ComponentList::load()
qWarning() << "Ignoring duplicate component entry" << component->m_uid;
continue;
}
- connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
d->components.append(component);
d->componentIndex[component->m_uid] = component;
}
@@ -317,7 +319,7 @@ bool ComponentList::load()
}
}
-void ComponentList::reload(Net::Mode netmode)
+void PackProfile::reload(Net::Mode netmode)
{
// Do not reload when the update/resolve task is running. It is in control.
if(d->m_updateTask)
@@ -337,29 +339,29 @@ void ComponentList::reload(Net::Mode netmode)
}
}
-shared_qobject_ptr<Task> ComponentList::getCurrentTask()
+shared_qobject_ptr<Task> PackProfile::getCurrentTask()
{
return d->m_updateTask;
}
-void ComponentList::resolve(Net::Mode netmode)
+void PackProfile::resolve(Net::Mode netmode)
{
auto updateTask = new ComponentUpdateTask(ComponentUpdateTask::Mode::Resolution, netmode, this);
d->m_updateTask.reset(updateTask);
- connect(updateTask, &ComponentUpdateTask::succeeded, this, &ComponentList::updateSucceeded);
- connect(updateTask, &ComponentUpdateTask::failed, this, &ComponentList::updateFailed);
+ connect(updateTask, &ComponentUpdateTask::succeeded, this, &PackProfile::updateSucceeded);
+ connect(updateTask, &ComponentUpdateTask::failed, this, &PackProfile::updateFailed);
d->m_updateTask->start();
}
-void ComponentList::updateSucceeded()
+void PackProfile::updateSucceeded()
{
qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name();
d->m_updateTask.reset();
invalidateLaunchProfile();
}
-void ComponentList::updateFailed(const QString& error)
+void PackProfile::updateFailed(const QString& error)
{
qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error;
d->m_updateTask.reset();
@@ -429,7 +431,7 @@ static void upgradeDeprecatedFiles(QString root, QString instanceName)
* - Part is taken from the old order.json file.
* - Part is loaded from loose json files in the instance's `patches` directory.
*/
-bool ComponentList::migratePreComponentConfig()
+bool PackProfile::migratePreComponentConfig()
{
// upgrade the very old files from the beginnings of MultiMC 5
upgradeDeprecatedFiles(d->m_instance->instanceRoot(), d->m_instance->name());
@@ -596,17 +598,17 @@ bool ComponentList::migratePreComponentConfig()
}
}
// new we have a complete list of components...
- return saveComponentList(componentsFilePath(), components);
+ return savePackProfile(componentsFilePath(), components);
}
// END: save/load
-void ComponentList::appendComponent(ComponentPtr component)
+void PackProfile::appendComponent(ComponentPtr component)
{
insertComponent(d->components.size(), component);
}
-void ComponentList::insertComponent(size_t index, ComponentPtr component)
+void PackProfile::insertComponent(size_t index, ComponentPtr component)
{
auto id = component->getID();
if(id.isEmpty())
@@ -623,18 +625,21 @@ void ComponentList::insertComponent(size_t index, ComponentPtr component)
d->components.insert(index, component);
d->componentIndex[id] = component;
endInsertRows();
- connect(component.get(), &Component::dataChanged, this, &ComponentList::componentDataChanged);
+ connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged);
scheduleSave();
}
-void ComponentList::componentDataChanged()
+void PackProfile::componentDataChanged()
{
auto objPtr = qobject_cast<Component *>(sender());
if(!objPtr)
{
- qWarning() << "ComponentList got dataChenged signal from a non-Component!";
+ qWarning() << "PackProfile got dataChenged signal from a non-Component!";
return;
}
+ if(objPtr->getID() == "net.minecraft") {
+ emit minecraftChanged();
+ }
// figure out which one is it... in a seriously dumb way.
int index = 0;
for (auto component: d->components)
@@ -647,10 +652,10 @@ void ComponentList::componentDataChanged()
}
index++;
}
- qWarning() << "ComponentList got dataChenged signal from a Component which does not belong to it!";
+ qWarning() << "PackProfile got dataChenged signal from a Component which does not belong to it!";
}
-bool ComponentList::remove(const int index)
+bool PackProfile::remove(const int index)
{
auto patch = getComponent(index);
if (!patch->isRemovable())
@@ -674,7 +679,7 @@ bool ComponentList::remove(const int index)
return true;
}
-bool ComponentList::remove(const QString id)
+bool PackProfile::remove(const QString id)
{
int i = 0;
for (auto patch : d->components)
@@ -688,7 +693,7 @@ bool ComponentList::remove(const QString id)
return false;
}
-bool ComponentList::customize(int index)
+bool PackProfile::customize(int index)
{
auto patch = getComponent(index);
if (!patch->isCustomizable())
@@ -706,7 +711,7 @@ bool ComponentList::customize(int index)
return true;
}
-bool ComponentList::revertToBase(int index)
+bool PackProfile::revertToBase(int index)
{
auto patch = getComponent(index);
if (!patch->isRevertible())
@@ -724,7 +729,7 @@ bool ComponentList::revertToBase(int index)
return true;
}
-Component * ComponentList::getComponent(const QString &id)
+Component * PackProfile::getComponent(const QString &id)
{
auto iter = d->componentIndex.find(id);
if (iter == d->componentIndex.end())
@@ -734,7 +739,7 @@ Component * ComponentList::getComponent(const QString &id)
return (*iter).get();
}
-Component * ComponentList::getComponent(int index)
+Component * PackProfile::getComponent(int index)
{
if(index < 0 || index >= d->components.size())
{
@@ -743,7 +748,7 @@ Component * ComponentList::getComponent(int index)
return d->components[index].get();
}
-QVariant ComponentList::data(const QModelIndex &index, int role) const
+QVariant PackProfile::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
@@ -762,8 +767,9 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
{
switch (column)
{
- case NameColumn:
- return d->components.at(row)->isEnabled() ? Qt::Checked : Qt::Unchecked;
+ case NameColumn: {
+ return patch->isEnabled() ? Qt::Checked : Qt::Unchecked;
+ }
default:
return QVariant();
}
@@ -773,7 +779,7 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
switch (column)
{
case NameColumn:
- return d->components.at(row)->getName();
+ return patch->getName();
case VersionColumn:
{
if(patch->isCustom())
@@ -816,7 +822,7 @@ QVariant ComponentList::data(const QModelIndex &index, int role) const
return QVariant();
}
-bool ComponentList::setData(const QModelIndex& index, const QVariant& value, int role)
+bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
{
@@ -834,7 +840,7 @@ bool ComponentList::setData(const QModelIndex& index, const QVariant& value, int
return false;
}
-QVariant ComponentList::headerData(int section, Qt::Orientation orientation, int role) const
+QVariant PackProfile::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal)
{
@@ -853,38 +859,42 @@ QVariant ComponentList::headerData(int section, Qt::Orientation orientation, int
}
return QVariant();
}
-Qt::ItemFlags ComponentList::flags(const QModelIndex &index) const
+
+// FIXME: zero precision mess
+Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
{
- if (!index.isValid())
+ if (!index.isValid()) {
return Qt::NoItemFlags;
+ }
Qt::ItemFlags outFlags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
int row = index.row();
- if (row < 0 || row >= d->components.size())
+ if (row < 0 || row >= d->components.size()) {
return Qt::NoItemFlags;
+ }
auto patch = d->components.at(row);
// TODO: this will need fine-tuning later...
- if(patch->canBeDisabled())
+ if(patch->canBeDisabled() && !d->interactionDisabled)
{
outFlags |= Qt::ItemIsUserCheckable;
}
return outFlags;
}
-int ComponentList::rowCount(const QModelIndex &parent) const
+int PackProfile::rowCount(const QModelIndex &parent) const
{
return d->components.size();
}
-int ComponentList::columnCount(const QModelIndex &parent) const
+int PackProfile::columnCount(const QModelIndex &parent) const
{
return NUM_COLUMNS;
}
-void ComponentList::move(const int index, const MoveDirection direction)
+void PackProfile::move(const int index, const MoveDirection direction)
{
int theirIndex;
if (direction == MoveUp)
@@ -920,22 +930,22 @@ void ComponentList::move(const int index, const MoveDirection direction)
scheduleSave();
}
-void ComponentList::invalidateLaunchProfile()
+void PackProfile::invalidateLaunchProfile()
{
d->m_profile.reset();
}
-void ComponentList::installJarMods(QStringList selectedFiles)
+void PackProfile::installJarMods(QStringList selectedFiles)
{
installJarMods_internal(selectedFiles);
}
-void ComponentList::installCustomJar(QString selectedFile)
+void PackProfile::installCustomJar(QString selectedFile)
{
installCustomJar_internal(selectedFile);
}
-bool ComponentList::installEmpty(const QString& uid, const QString& name)
+bool PackProfile::installEmpty(const QString& uid, const QString& name)
{
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
if(!FS::ensureFolderPathExists(patchDir))
@@ -963,7 +973,7 @@ bool ComponentList::installEmpty(const QString& uid, const QString& name)
return true;
}
-bool ComponentList::removeComponent_internal(ComponentPtr patch)
+bool PackProfile::removeComponent_internal(ComponentPtr patch)
{
bool ok = true;
// first, remove the patch file. this ensures it's not used anymore
@@ -1013,7 +1023,7 @@ bool ComponentList::removeComponent_internal(ComponentPtr patch)
return ok;
}
-bool ComponentList::installJarMods_internal(QStringList filepaths)
+bool PackProfile::installJarMods_internal(QStringList filepaths)
{
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
if(!FS::ensureFolderPathExists(patchDir))
@@ -1075,7 +1085,7 @@ bool ComponentList::installJarMods_internal(QStringList filepaths)
return true;
}
-bool ComponentList::installCustomJar_internal(QString filepath)
+bool PackProfile::installCustomJar_internal(QString filepath)
{
QString patchDir = FS::PathCombine(d->m_instance->instanceRoot(), "patches");
if(!FS::ensureFolderPathExists(patchDir))
@@ -1136,7 +1146,7 @@ bool ComponentList::installCustomJar_internal(QString filepath)
return true;
}
-std::shared_ptr<LaunchProfile> ComponentList::getProfile() const
+std::shared_ptr<LaunchProfile> PackProfile::getProfile() const
{
if(!d->m_profile)
{
@@ -1158,7 +1168,7 @@ std::shared_ptr<LaunchProfile> ComponentList::getProfile() const
return d->m_profile;
}
-void ComponentList::setOldConfigVersion(const QString& uid, const QString& version)
+void PackProfile::setOldConfigVersion(const QString& uid, const QString& version)
{
if(version.isEmpty())
{
@@ -1167,7 +1177,7 @@ void ComponentList::setOldConfigVersion(const QString& uid, const QString& versi
d->m_oldConfigVersions[uid] = version;
}
-bool ComponentList::setComponentVersion(const QString& uid, const QString& version, bool important)
+bool PackProfile::setComponentVersion(const QString& uid, const QString& version, bool important)
{
auto iter = d->componentIndex.find(uid);
if(iter != d->componentIndex.end())
@@ -1193,7 +1203,7 @@ bool ComponentList::setComponentVersion(const QString& uid, const QString& versi
}
}
-QString ComponentList::getComponentVersion(const QString& uid) const
+QString PackProfile::getComponentVersion(const QString& uid) const
{
const auto iter = d->componentIndex.find(uid);
if (iter != d->componentIndex.end())
@@ -1202,3 +1212,14 @@ QString ComponentList::getComponentVersion(const QString& uid) const
}
return QString();
}
+
+void PackProfile::disableInteraction(bool disable)
+{
+ if(d->interactionDisabled != disable) {
+ d->interactionDisabled = disable;
+ auto size = d->components.size();
+ if(size) {
+ emit dataChanged(index(0), index(size - 1));
+ }
+ }
+}
diff --git a/api/logic/minecraft/ComponentList.h b/api/logic/minecraft/PackProfile.h
index 6ddc09eb..e55e6a58 100644
--- a/api/logic/minecraft/ComponentList.h
+++ b/api/logic/minecraft/PackProfile.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -31,10 +31,10 @@
#include "net/Mode.h"
class MinecraftInstance;
-struct ComponentListData;
+struct PackProfileData;
class ComponentUpdateTask;
-class MULTIMC_LOGIC_EXPORT ComponentList : public QAbstractListModel
+class MULTIMC_LOGIC_EXPORT PackProfile : public QAbstractListModel
{
Q_OBJECT
friend ComponentUpdateTask;
@@ -46,8 +46,8 @@ public:
NUM_COLUMNS
};
- explicit ComponentList(MinecraftInstance * instance);
- virtual ~ComponentList();
+ explicit PackProfile(MinecraftInstance * instance);
+ virtual ~PackProfile();
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
@@ -104,6 +104,9 @@ public:
/// if there is a save scheduled, do it now.
void saveNow();
+signals:
+ void minecraftChanged();
+
public:
/// get the profile component by id
Component * getComponent(const QString &id);
@@ -111,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;
@@ -118,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);
@@ -131,6 +136,7 @@ private slots:
void updateSucceeded();
void updateFailed(const QString & error);
void componentDataChanged();
+ void disableInteraction(bool disable);
private:
bool load();
@@ -142,5 +148,5 @@ private:
private: /* data */
- std::unique_ptr<ComponentListData> d;
+ std::unique_ptr<PackProfileData> d;
};
diff --git a/api/logic/minecraft/ComponentList_p.h b/api/logic/minecraft/PackProfile_p.h
index aed65337..6cd2a4e5 100644
--- a/api/logic/minecraft/ComponentList_p.h
+++ b/api/logic/minecraft/PackProfile_p.h
@@ -9,9 +9,8 @@
class MinecraftInstance;
using ComponentContainer = QList<ComponentPtr>;
using ComponentIndex = QMap<QString, ComponentPtr>;
-using ConnectionList = QList<QMetaObject::Connection>;
-struct ComponentListData
+struct PackProfileData
{
// the instance this belongs to
MinecraftInstance *m_instance;
@@ -38,5 +37,6 @@ struct ComponentListData
QTimer m_saveTimer;
shared_qobject_ptr<Task> m_updateTask;
bool loaded = false;
+ bool interactionDisabled = true;
};
diff --git a/api/logic/minecraft/ParseUtils_test.cpp b/api/logic/minecraft/ParseUtils_test.cpp
index fde9cdbf..fcc137e5 100644
--- a/api/logic/minecraft/ParseUtils_test.cpp
+++ b/api/logic/minecraft/ParseUtils_test.cpp
@@ -33,7 +33,7 @@ slots:
auto time_parsed = timeFromS3Time(timestamp);
auto time_serialized = timeToS3Time(time_parsed);
-
+
QCOMPARE(time_serialized, timestamp);
}
diff --git a/api/logic/minecraft/Rule.cpp b/api/logic/minecraft/Rule.cpp
index a7640635..af2861e3 100644
--- a/api/logic/minecraft/Rule.cpp
+++ b/api/logic/minecraft/Rule.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 ec9b26bd..7aa34d96 100644
--- a/api/logic/minecraft/Rule.h
+++ b/api/logic/minecraft/Rule.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/SimpleModList.cpp b/api/logic/minecraft/SimpleModList.cpp
deleted file mode 100644
index a9fb42eb..00000000
--- a/api/logic/minecraft/SimpleModList.cpp
+++ /dev/null
@@ -1,367 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "SimpleModList.h"
-#include <FileSystem.h>
-#include <QMimeData>
-#include <QUrl>
-#include <QUuid>
-#include <QString>
-#include <QFileSystemWatcher>
-#include <QDebug>
-
-SimpleModList::SimpleModList(const QString &dir) : QAbstractListModel(), m_dir(dir)
-{
- FS::ensureFolderPathExists(m_dir.absolutePath());
- m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
- QDir::NoSymLinks);
- m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
- m_watcher = new QFileSystemWatcher(this);
- connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
-}
-
-void SimpleModList::startWatching()
-{
- if(is_watching)
- return;
-
- update();
-
- is_watching = m_watcher->addPath(m_dir.absolutePath());
- if (is_watching)
- {
- qDebug() << "Started watching " << m_dir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to start watching " << m_dir.absolutePath();
- }
-}
-
-void SimpleModList::stopWatching()
-{
- if(!is_watching)
- return;
-
- is_watching = !m_watcher->removePath(m_dir.absolutePath());
- if (!is_watching)
- {
- qDebug() << "Stopped watching " << m_dir.absolutePath();
- }
- else
- {
- qDebug() << "Failed to stop watching " << m_dir.absolutePath();
- }
-}
-
-bool SimpleModList::update()
-{
- if (!isValid())
- return false;
-
- QList<Mod> orderedMods;
- QList<Mod> newMods;
- m_dir.refresh();
- auto folderContents = m_dir.entryInfoList();
- bool orderOrStateChanged = false;
-
- // if there are any untracked files...
- if (folderContents.size())
- {
- // the order surely changed!
- for (auto entry : folderContents)
- {
- newMods.append(Mod(entry));
- }
- orderedMods.append(newMods);
- orderOrStateChanged = true;
- }
- // otherwise, if we were already tracking some mods
- else if (mods.size())
- {
- // if the number doesn't match, order changed.
- if (mods.size() != orderedMods.size())
- orderOrStateChanged = true;
- // if it does match, compare the mods themselves
- else
- for (int i = 0; i < mods.size(); i++)
- {
- if (!mods[i].strongCompare(orderedMods[i]))
- {
- orderOrStateChanged = true;
- break;
- }
- }
- }
- beginResetModel();
- mods.swap(orderedMods);
- endResetModel();
- if (orderOrStateChanged)
- {
- emit changed();
- }
- return true;
-}
-
-void SimpleModList::directoryChanged(QString path)
-{
- update();
-}
-
-bool SimpleModList::isValid()
-{
- return m_dir.exists() && m_dir.isReadable();
-}
-
-bool SimpleModList::installMod(const QString &filename)
-{
- // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
- QFileInfo fileinfo(FS::NormalizePath(filename));
-
- qDebug() << "installing: " << fileinfo.absoluteFilePath();
-
- if (!fileinfo.exists() || !fileinfo.isReadable())
- {
- return false;
- }
- Mod m(fileinfo);
- if (!m.valid())
- return false;
-
- auto type = m.type();
- if (type == Mod::MOD_UNKNOWN)
- return false;
- if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
- {
- QString newpath = FS::PathCombine(m_dir.path(), fileinfo.fileName());
- if (!QFile::copy(fileinfo.filePath(), newpath))
- return false;
- FS::updateTimestamp(newpath);
- m.repath(newpath);
- update();
- return true;
- }
- else if (type == Mod::MOD_FOLDER)
- {
- QString from = fileinfo.filePath();
- QString to = FS::PathCombine(m_dir.path(), fileinfo.fileName());
- if (!FS::copy(from, to)())
- return false;
- m.repath(to);
- update();
- return true;
- }
- return false;
-}
-
-bool SimpleModList::enableMods(const QModelIndexList& indexes, bool enable)
-{
- if(indexes.isEmpty())
- return true;
-
- for (auto i: indexes)
- {
- Mod &m = mods[i.row()];
- m.enable(enable);
- emit dataChanged(i, i);
- }
- emit changed();
- return true;
-}
-
-bool SimpleModList::deleteMods(const QModelIndexList& indexes)
-{
- if(indexes.isEmpty())
- return true;
-
- for (auto i: indexes)
- {
- Mod &m = mods[i.row()];
- m.destroy();
- }
- emit changed();
- return true;
-}
-
-int SimpleModList::columnCount(const QModelIndex &parent) const
-{
- return NUM_COLUMNS;
-}
-
-QVariant SimpleModList::data(const QModelIndex &index, int role) const
-{
- if (!index.isValid())
- return QVariant();
-
- int row = index.row();
- int column = index.column();
-
- if (row < 0 || row >= mods.size())
- return QVariant();
-
- switch (role)
- {
- case Qt::DisplayRole:
- switch (column)
- {
- case NameColumn:
- return mods[row].name();
- case VersionColumn:
- return mods[row].version();
- case DateColumn:
- return mods[row].dateTimeChanged();
-
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- return mods[row].mmc_id();
-
- case Qt::CheckStateRole:
- switch (column)
- {
- case ActiveColumn:
- return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
-}
-
-bool SimpleModList::setData(const QModelIndex &index, const QVariant &value, int role)
-{
- if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
- {
- return false;
- }
-
- if (role == Qt::CheckStateRole)
- {
- auto &mod = mods[index.row()];
- if (mod.enable(!mod.enabled()))
- {
- emit dataChanged(index, index);
- return true;
- }
- }
- return false;
-}
-
-QVariant SimpleModList::headerData(int section, Qt::Orientation orientation, int role) const
-{
- switch (role)
- {
- case Qt::DisplayRole:
- switch (section)
- {
- case ActiveColumn:
- return QString();
- case NameColumn:
- return tr("Name");
- case VersionColumn:
- return tr("Version");
- case DateColumn:
- return tr("Last changed");
- default:
- return QVariant();
- }
-
- case Qt::ToolTipRole:
- switch (section)
- {
- case ActiveColumn:
- return tr("Is the mod enabled?");
- case NameColumn:
- return tr("The name of the mod.");
- case VersionColumn:
- return tr("The version of the mod.");
- case DateColumn:
- return tr("The date and time this mod was last changed (or added).");
- default:
- return QVariant();
- }
- default:
- return QVariant();
- }
- return QVariant();
-}
-
-Qt::ItemFlags SimpleModList::flags(const QModelIndex &index) const
-{
- Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
- if (index.isValid())
- return Qt::ItemIsUserCheckable | Qt::ItemIsDropEnabled |
- defaultFlags;
- else
- return Qt::ItemIsDropEnabled | defaultFlags;
-}
-
-Qt::DropActions SimpleModList::supportedDropActions() const
-{
- // copy from outside, move from within and other mod lists
- return Qt::CopyAction | Qt::MoveAction;
-}
-
-QStringList SimpleModList::mimeTypes() const
-{
- QStringList types;
- types << "text/uri-list";
- return types;
-}
-
-bool SimpleModList::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
-{
- if (action == Qt::IgnoreAction)
- {
- return true;
- }
-
- // check if the action is supported
- if (!data || !(action & supportedDropActions()))
- {
- return false;
- }
-
- // files dropped from outside?
- if (data->hasUrls())
- {
- bool was_watching = is_watching;
- if (was_watching)
- {
- stopWatching();
- }
- auto urls = data->urls();
- for (auto url : urls)
- {
- // only local files may be dropped...
- if (!url.isLocalFile())
- {
- continue;
- }
- // TODO: implement not only copy, but also move
- // FIXME: handle errors here
- installMod(url.toLocalFile());
- }
- if (was_watching)
- {
- startWatching();
- }
- return true;
- }
- return false;
-}
diff --git a/api/logic/minecraft/VersionFile.cpp b/api/logic/minecraft/VersionFile.cpp
index cfb9e504..d0a1a507 100644
--- a/api/logic/minecraft/VersionFile.cpp
+++ b/api/logic/minecraft/VersionFile.cpp
@@ -5,7 +5,7 @@
#include "minecraft/VersionFile.h"
#include "minecraft/Library.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include "ParseUtils.h"
#include <Version.h>
@@ -41,6 +41,10 @@ void VersionFile::applyTo(LaunchProfile *profile)
{
profile->applyLibrary(library);
}
+ for (auto mavenFile : mavenFiles)
+ {
+ profile->applyMavenFile(mavenFile);
+ }
profile->applyProblemSeverity(getProblemSeverity());
}
@@ -53,4 +57,4 @@ void VersionFile::applyTo(LaunchProfile *profile)
throw MinecraftVersionMismatch(uid, dependsOnMinecraftVersion, theirVersion);
}
}
-*/ \ No newline at end of file
+*/
diff --git a/api/logic/minecraft/VersionFile.h b/api/logic/minecraft/VersionFile.h
index 3dc9db96..b79fcd4f 100644
--- a/api/logic/minecraft/VersionFile.h
+++ b/api/logic/minecraft/VersionFile.h
@@ -12,7 +12,7 @@
#include "Library.h"
#include <meta/JsonFormat.h>
-class ComponentList;
+class PackProfile;
class VersionFile;
class LaunchProfile;
struct MojangDownloadInfo;
@@ -75,6 +75,9 @@ public: /* data */
/// Mojang: list of libraries to add to the version
QList<LibraryPtr> libraries;
+ /// MultiMC: list of maven files to put in the libraries folder, but not in classpath
+ QList<LibraryPtr> mavenFiles;
+
/// The main jar (Minecraft version library, normally)
LibraryPtr mainJar;
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 b39f940e..a2b4dac7 100644
--- a/api/logic/minecraft/World.cpp
+++ b/api/logic/minecraft/World.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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.
@@ -30,6 +30,79 @@
#include <quazipfile.h>
#include <quazipdir.h>
+#include <QCoreApplication>
+
+#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)
+ {
+ case GameType::Survival:
+ return QCoreApplication::translate("GameType", "Survival");
+ case GameType::Creative:
+ return QCoreApplication::translate("GameType", "Creative");
+ case GameType::Adventure:
+ return QCoreApplication::translate("GameType", "Adventure");
+ case GameType::Spectator:
+ return QCoreApplication::translate("GameType", "Spectator");
+ default:
+ break;
+ }
+ 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)
{
QByteArray output;
@@ -38,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)
@@ -118,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);
@@ -192,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())
{
@@ -251,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());
@@ -266,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();
@@ -292,58 +391,98 @@ 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;
}
}
-void World::loadFromLevelDat(QByteArray data)
+optional<int> read_int (nbt::value& parent, const char * name)
{
try
{
- auto levelData = parseLevelDat(data);
- if(!levelData)
+ auto &namedValue = parent.at(name);
+ if(namedValue.get_type() != nbt::tag_type::Int)
{
- is_valid = false;
- return;
+ return nullopt;
}
+ auto & tag_str = namedValue.as<nbt::tag_int>();
+ return tag_str.get();
+ }
+ catch (const std::out_of_range &e)
+ {
+ // fallback for old world formats
+ 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.";
+ return nullopt;
+ }
+}
- auto &val = levelData->at("Data");
- is_valid = val.get_type() == nbt::tag_type::Compound;
- if(!is_valid)
- return;
-
- m_actualName = read_string(val, "LevelName", m_folderName);
-
+GameType read_gametype(nbt::value& parent, const char * name) {
+ return GameType(read_int(parent, name));
+}
- int64_t temp = read_long(val, "LastPlayed", 0);
- if(temp == 0)
- {
- m_lastPlayed = levelDatTime;
- }
- else
- {
- m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp);
- }
+}
- m_randomSeed = read_long(val, "RandomSeed", 0);
+void World::loadFromLevelDat(QByteArray data)
+{
+ auto levelData = parseLevelDat(data);
+ if(!levelData)
+ {
+ is_valid = false;
+ return;
+ }
- qDebug() << "World Name:" << m_actualName;
- qDebug() << "Last Played:" << m_lastPlayed.toString();
- qDebug() << "Seed:" << m_randomSeed;
+ nbt::value * valPtr = nullptr;
+ try {
+ valPtr = &levelData->at("Data");
}
- catch (const nbt::io::input_error &e)
- {
- qWarning() << "Unable to load" << m_folderName << ":" << e.what();
+ 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;
+
+ auto name = read_string(val, "LevelName");
+ m_actualName = name ? *name : m_folderName;
+
+ auto timestamp = read_long(val, "LastPlayed");
+ m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime;
+
+ m_gameType = read_gametype(val, "GameType");
+
+ optional<int64_t> randomSeed;
+ try {
+ auto &WorldGen_val = val.at("WorldGenSettings");
+ randomSeed = read_long(WorldGen_val, "seed");
+ }
+ 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)
@@ -379,7 +518,3 @@ bool World::operator==(const World &other) const
{
return is_valid == other.is_valid && folderName() == other.folderName();
}
-bool World::strongCompare(const World &other) const
-{
- return is_valid == other.is_valid && folderName() == other.folderName();
-}
diff --git a/api/logic/minecraft/World.h b/api/logic/minecraft/World.h
index 2cce85a2..1d94d54d 100644
--- a/api/logic/minecraft/World.h
+++ b/api/logic/minecraft/World.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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,9 +16,28 @@
#pragma once
#include <QFileInfo>
#include <QDateTime>
+#include <nonstd/optional>
#include "multimc_logic_export.h"
+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;
+};
+
class MULTIMC_LOGIC_EXPORT World
{
public:
@@ -31,10 +50,18 @@ public:
{
return m_actualName;
}
+ QString iconFile() const
+ {
+ return m_iconFile;
+ }
QDateTime lastPlayed() const
{
return m_lastPlayed;
}
+ GameType gameType() const
+ {
+ return m_gameType;
+ }
int64_t seed() const
{
return m_randomSeed;
@@ -57,13 +84,14 @@ 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());
// WEAK compare operator - used for replacing worlds
bool operator==(const World &other) const;
- bool strongCompare(const World &other) const;
private:
void readFromZip(const QFileInfo &file);
@@ -76,8 +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;
bool is_valid = false;
};
diff --git a/api/logic/minecraft/WorldList.cpp b/api/logic/minecraft/WorldList.cpp
index 79a5bf38..f6309dbd 100644
--- a/api/logic/minecraft/WorldList.cpp
+++ b/api/logic/minecraft/WorldList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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,9 +136,22 @@ 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 2;
+ return 3;
}
QVariant WorldList::data(const QModelIndex &index, int role) const
@@ -161,6 +174,9 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
case NameColumn:
return world.name();
+ case GameModeColumn:
+ return world.gameType().toTranslatedString();
+
case LastPlayedColumn:
return world.lastPlayed();
@@ -192,6 +208,10 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
{
return world.lastPlayed();
}
+ case IconFileRole:
+ {
+ return world.iconFile();
+ }
default:
return QVariant();
}
@@ -206,6 +226,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
{
case NameColumn:
return tr("Name");
+ case GameModeColumn:
+ return tr("Game Mode");
case LastPlayedColumn:
return tr("Last Played");
default:
@@ -217,6 +239,8 @@ QVariant WorldList::headerData(int section, Qt::Orientation orientation, int rol
{
case NameColumn:
return tr("The name of the world.");
+ case GameModeColumn:
+ return tr("Game mode of the world.");
case LastPlayedColumn:
return tr("Date and time the world was last played.");
default:
diff --git a/api/logic/minecraft/WorldList.h b/api/logic/minecraft/WorldList.h
index a1cd8f51..740b1461 100644
--- a/api/logic/minecraft/WorldList.h
+++ b/api/logic/minecraft/WorldList.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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.
@@ -33,6 +33,7 @@ public:
enum Columns
{
NameColumn,
+ GameModeColumn,
LastPlayedColumn
};
@@ -42,7 +43,9 @@ public:
FolderRole,
SeedRole,
NameRole,
- LastPlayedRole
+ GameModeRole,
+ LastPlayedRole,
+ IconFileRole
};
WorldList(const QString &dir);
@@ -79,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 edf1e4e3..f5853fe3 100644
--- a/api/logic/minecraft/auth/MojangAccount.cpp
+++ b/api/logic/minecraft/auth/MojangAccount.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 8f9bec95..30a5f2ff 100644
--- a/api/logic/minecraft/auth/MojangAccount.h
+++ b/api/logic/minecraft/auth/MojangAccount.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 de671add..e584cb3b 100644
--- a/api/logic/minecraft/auth/MojangAccountList.cpp
+++ b/api/logic/minecraft/auth/MojangAccountList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 33018636..cc3a61a2 100644
--- a/api/logic/minecraft/auth/MojangAccountList.h
+++ b/api/logic/minecraft/auth/MojangAccountList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 666e57d6..0857b46b 100644
--- a/api/logic/minecraft/auth/YggdrasilTask.cpp
+++ b/api/logic/minecraft/auth/YggdrasilTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -25,7 +25,7 @@
#include <Env.h>
-#include <net/URLConstants.h>
+#include <BuildConfig.h>
#include <QDebug>
@@ -42,7 +42,7 @@ void YggdrasilTask::executeTask()
// Get the content of the request we're going to send to the server.
QJsonDocument doc(getRequestContent());
- QUrl reqUrl("https://" + URLConstants::AUTH_BASE + getEndpoint());
+ QUrl reqUrl(BuildConfig.AUTH_BASE + getEndpoint());
QNetworkRequest netRequest(reqUrl);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
@@ -122,7 +122,7 @@ void YggdrasilTask::processReply()
tr("<b>SSL Handshake failed.</b><br/>There might be a few causes for it:<br/>"
"<ul>"
"<li>You use Windows XP and need to <a "
- "href=\"http://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
+ "href=\"https://www.microsoft.com/en-us/download/details.aspx?id=38918\">update "
"your root certificates</a></li>"
"<li>Some device on your network is interfering with SSL traffic. In that case, "
"you have bigger worries than Minecraft not starting.</li>"
diff --git a/api/logic/minecraft/auth/YggdrasilTask.h b/api/logic/minecraft/auth/YggdrasilTask.h
index 4e302195..8af2e132 100644
--- a/api/logic/minecraft/auth/YggdrasilTask.h
+++ b/api/logic/minecraft/auth/YggdrasilTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 5e37f0ca..2e8dc859 100644
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
+++ b/api/logic/minecraft/auth/flows/AuthenticateTask.cpp
@@ -1,5 +1,5 @@
-/* Copyright 2013-2018 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 71f0eab7..4c14eec7 100644
--- a/api/logic/minecraft/auth/flows/AuthenticateTask.h
+++ b/api/logic/minecraft/auth/flows/AuthenticateTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 f6fe4757..ecba178d 100644
--- a/api/logic/minecraft/auth/flows/RefreshTask.cpp
+++ b/api/logic/minecraft/auth/flows/RefreshTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 ff586396..f0840dda 100644
--- a/api/logic/minecraft/auth/flows/RefreshTask.h
+++ b/api/logic/minecraft/auth/flows/RefreshTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e82678c8..6b3f0a65 100644
--- a/api/logic/minecraft/auth/flows/ValidateTask.cpp
+++ b/api/logic/minecraft/auth/flows/ValidateTask.cpp
@@ -1,5 +1,5 @@
-/* Copyright 2013-2018 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 87876fb8..986c2e9f 100644
--- a/api/logic/minecraft/auth/flows/ValidateTask.h
+++ b/api/logic/minecraft/auth/flows/ValidateTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/forge/ForgeXzDownload.cpp b/api/logic/minecraft/forge/ForgeXzDownload.cpp
deleted file mode 100644
index 4083bdea..00000000
--- a/api/logic/minecraft/forge/ForgeXzDownload.cpp
+++ /dev/null
@@ -1,393 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Env.h"
-#include "ForgeXzDownload.h"
-#include <FileSystem.h>
-
-#include <QCryptographicHash>
-#include <QFileInfo>
-#include <QDateTime>
-#include <QDir>
-#include <QDebug>
-
-ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction()
-{
- m_entry = entry;
- m_target_path = entry->getFullPath();
- m_pack200_xz_file.setFileTemplate("./dl_temp.XXXXXX");
- m_status = Job_NotStarted;
- m_url_path = relative_path;
- m_url = "http://files.minecraftforge.net/maven/" + m_url_path + ".pack.xz";
-}
-
-void ForgeXzDownload::start()
-{
- if(m_status == Job_Aborted)
- {
- qWarning() << "Attempt to start an aborted Download:" << m_url.toString();
- emit aborted(m_index_within_job);
- return;
- }
- m_status = Job_InProgress;
- if (!m_entry->isStale())
- {
- m_status = Job_Finished;
- emit succeeded(m_index_within_job);
- return;
- }
- // can we actually create the real, final file?
- if (!FS::ensureFilePathExists(m_target_path))
- {
- m_status = Job_Failed;
- emit failed(m_index_within_job);
- return;
- }
-
- qDebug() << "Downloading " << m_url.toString();
- QNetworkRequest request(m_url);
- request.setRawHeader(QString("If-None-Match").toLatin1(), m_entry->getETag().toLatin1());
- request.setHeader(QNetworkRequest::UserAgentHeader, "MultiMC/5.0 (Cached)");
-
- QNetworkReply *rep = ENV.qnam().get(request);
-
- m_reply.reset(rep);
- connect(rep, SIGNAL(downloadProgress(qint64, qint64)), SLOT(downloadProgress(qint64, qint64)));
- connect(rep, SIGNAL(finished()), SLOT(downloadFinished()));
- connect(rep, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(downloadError(QNetworkReply::NetworkError)));
- connect(rep, SIGNAL(readyRead()), SLOT(downloadReadyRead()));
-}
-
-void ForgeXzDownload::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
-{
- m_total_progress = bytesTotal;
- m_progress = bytesReceived;
- emit netActionProgress(m_index_within_job, bytesReceived, bytesTotal);
-}
-
-void ForgeXzDownload::downloadError(QNetworkReply::NetworkError error)
-{
- if(error == QNetworkReply::OperationCanceledError)
- {
- qCritical() << "Aborted " << m_url.toString();
- m_status = Job_Aborted;
- }
- else
- {
- // error happened during download.
- qCritical() << "Failed " << m_url.toString() << " with reason " << error;
- m_status = Job_Failed;
- }
-}
-
-void ForgeXzDownload::failAndTryNextMirror()
-{
- m_status = Job_Failed;
- emit failed(m_index_within_job);
-}
-
-void ForgeXzDownload::downloadFinished()
-{
- // if the download succeeded
- if (m_status != Job_Failed && m_status != Job_Aborted)
- {
- // nothing went wrong...
- m_status = Job_Finished;
- if (m_pack200_xz_file.isOpen())
- {
- // we actually downloaded something! process and isntall it
- decompressAndInstall();
- return;
- }
- else
- {
- // something bad happened -- on the local machine!
- m_status = Job_Failed;
- m_pack200_xz_file.remove();
- m_reply.reset();
- emit failed(m_index_within_job);
- return;
- }
- }
- else if(m_status == Job_Aborted)
- {
- m_pack200_xz_file.remove();
- m_reply.reset();
- emit failed(m_index_within_job);
- emit aborted(m_index_within_job);
- return;
- }
- // else the download failed
- else
- {
- m_status = Job_Failed;
- m_pack200_xz_file.close();
- m_pack200_xz_file.remove();
- m_reply.reset();
- failAndTryNextMirror();
- return;
- }
-}
-
-void ForgeXzDownload::downloadReadyRead()
-{
-
- if (!m_pack200_xz_file.isOpen())
- {
- if (!m_pack200_xz_file.open())
- {
- /*
- * Can't open the file... the job failed
- */
- m_reply->abort();
- emit failed(m_index_within_job);
- return;
- }
- }
- m_pack200_xz_file.write(m_reply->readAll());
-}
-
-#include "xz.h"
-#include "unpack200.h"
-#include <stdexcept>
-#include <unistd.h>
-
-const size_t buffer_size = 8196;
-
-// NOTE: once this gets here, it can't be aborted anymore. we don't care.
-void ForgeXzDownload::decompressAndInstall()
-{
- // rewind the downloaded temp file
- m_pack200_xz_file.seek(0);
- // de-xz'd file
- QTemporaryFile pack200_file("./dl_temp.XXXXXX");
- pack200_file.open();
-
- bool xz_success = false;
- // first, de-xz
- {
- uint8_t in[buffer_size];
- uint8_t out[buffer_size];
- struct xz_buf b;
- struct xz_dec *s;
- enum xz_ret ret;
- xz_crc32_init();
- xz_crc64_init();
- s = xz_dec_init(XZ_DYNALLOC, 1 << 26);
- if (s == nullptr)
- {
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
- }
- b.in = in;
- b.in_pos = 0;
- b.in_size = 0;
- b.out = out;
- b.out_pos = 0;
- b.out_size = buffer_size;
- while (!xz_success)
- {
- if (b.in_pos == b.in_size)
- {
- b.in_size = m_pack200_xz_file.read((char *)in, sizeof(in));
- b.in_pos = 0;
- }
-
- ret = xz_dec_run(s, &b);
-
- if (b.out_pos == sizeof(out))
- {
- auto wresult = pack200_file.write((char *)out, b.out_pos);
- if (wresult < 0 || size_t(wresult) != b.out_pos)
- {
- // msg = "Write error\n";
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
- }
-
- b.out_pos = 0;
- }
-
- if (ret == XZ_OK)
- continue;
-
- if (ret == XZ_UNSUPPORTED_CHECK)
- {
- // unsupported check. this is OK, but we should log this
- continue;
- }
-
- auto wresult = pack200_file.write((char *)out, b.out_pos);
- if (wresult < 0 || size_t(wresult) != b.out_pos)
- {
- // write error
- pack200_file.close();
- xz_dec_end(s);
- return;
- }
-
- switch (ret)
- {
- case XZ_STREAM_END:
- xz_dec_end(s);
- xz_success = true;
- break;
-
- case XZ_MEM_ERROR:
- qCritical() << "Memory allocation failed\n";
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
-
- case XZ_MEMLIMIT_ERROR:
- qCritical() << "Memory usage limit reached\n";
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
-
- case XZ_FORMAT_ERROR:
- qCritical() << "Not a .xz file\n";
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
-
- case XZ_OPTIONS_ERROR:
- qCritical() << "Unsupported options in the .xz headers\n";
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
-
- case XZ_DATA_ERROR:
- case XZ_BUF_ERROR:
- qCritical() << "File is corrupt\n";
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
-
- default:
- qCritical() << "Bug!\n";
- xz_dec_end(s);
- failAndTryNextMirror();
- return;
- }
- }
- }
- m_pack200_xz_file.remove();
-
- // revert pack200
- pack200_file.seek(0);
- int handle_in = pack200_file.handle();
- // FIXME: dispose of file handles, pointers and the like. Ideally wrap in objects.
- if(handle_in == -1)
- {
- qCritical() << "Error reopening " << pack200_file.fileName();
- failAndTryNextMirror();
- return;
- }
- int handle_in_dup = dup (handle_in);
- if(handle_in_dup == -1)
- {
- qCritical() << "Error reopening " << pack200_file.fileName();
- failAndTryNextMirror();
- return;
- }
- FILE *file_in = fdopen (handle_in_dup, "rb");
- if(!file_in)
- {
- qCritical() << "Error reopening " << pack200_file.fileName();
- failAndTryNextMirror();
- return;
- }
- QFile qfile_out(m_target_path);
- if(!qfile_out.open(QIODevice::WriteOnly))
- {
- qCritical() << "Error opening " << qfile_out.fileName();
- failAndTryNextMirror();
- return;
- }
- int handle_out = qfile_out.handle();
- if(handle_out == -1)
- {
- qCritical() << "Error opening " << qfile_out.fileName();
- failAndTryNextMirror();
- return;
- }
- int handle_out_dup = dup (handle_out);
- if(handle_out_dup == -1)
- {
- qCritical() << "Error reopening " << qfile_out.fileName();
- failAndTryNextMirror();
- return;
- }
- FILE *file_out = fdopen (handle_out_dup, "wb");
- if(!file_out)
- {
- qCritical() << "Error opening " << qfile_out.fileName();
- failAndTryNextMirror();
- return;
- }
- try
- {
- // NOTE: this takes ownership of both FILE pointers. That's why we duplicate them above.
- unpack_200(file_in, file_out);
- }
- catch (const std::runtime_error &err)
- {
- m_status = Job_Failed;
- qCritical() << "Error unpacking " << pack200_file.fileName() << " : " << err.what();
- QFile f(m_target_path);
- if (f.exists())
- f.remove();
- failAndTryNextMirror();
- return;
- }
- pack200_file.remove();
-
- QFile jar_file(m_target_path);
-
- if (!jar_file.open(QIODevice::ReadOnly))
- {
- jar_file.remove();
- failAndTryNextMirror();
- return;
- }
- auto hash = QCryptographicHash::hash(jar_file.readAll(), QCryptographicHash::Md5);
- m_entry->setMD5Sum(hash.toHex().constData());
- jar_file.close();
-
- QFileInfo output_file_info(m_target_path);
- m_entry->setETag(m_reply->rawHeader("ETag").constData());
- m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch());
- m_entry->setStale(false);
- ENV.metacache()->updateEntry(m_entry);
-
- m_reply.reset();
- emit succeeded(m_index_within_job);
-}
-
-bool ForgeXzDownload::abort()
-{
- if(m_reply)
- m_reply->abort();
- m_status = Job_Aborted;
- return true;
-}
-
-bool ForgeXzDownload::canAbort()
-{
- return true;
-}
diff --git a/api/logic/minecraft/forge/ForgeXzDownload.h b/api/logic/minecraft/forge/ForgeXzDownload.h
deleted file mode 100644
index 728a7f7a..00000000
--- a/api/logic/minecraft/forge/ForgeXzDownload.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include "net/NetAction.h"
-#include "net/HttpMetaCache.h"
-#include <QFile>
-#include <QTemporaryFile>
-
-typedef std::shared_ptr<class ForgeXzDownload> ForgeXzDownloadPtr;
-
-class ForgeXzDownload : public NetAction
-{
- Q_OBJECT
-public:
- MetaEntryPtr m_entry;
- /// if saving to file, use the one specified in this string
- QString m_target_path;
- /// this is the output file, if any
- QTemporaryFile m_pack200_xz_file;
- /// path relative to the mirror base
- QString m_url_path;
-
-public:
- explicit ForgeXzDownload(QString relative_path, MetaEntryPtr entry);
- static ForgeXzDownloadPtr make(QString relative_path, MetaEntryPtr entry)
- {
- return ForgeXzDownloadPtr(new ForgeXzDownload(relative_path, entry));
- }
- virtual ~ForgeXzDownload(){};
- bool canAbort() override;
-
-protected
-slots:
- void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) override;
- void downloadError(QNetworkReply::NetworkError error) override;
- void downloadFinished() override;
- void downloadReadyRead() override;
-
-public
-slots:
- void start() override;
- bool abort() override;
-
-private:
- void decompressAndInstall();
- void failAndTryNextMirror();
-};
diff --git a/api/logic/minecraft/gameoptions/GameOptions.cpp b/api/logic/minecraft/gameoptions/GameOptions.cpp
new file mode 100644
index 00000000..e547b32a
--- /dev/null
+++ b/api/logic/minecraft/gameoptions/GameOptions.cpp
@@ -0,0 +1,144 @@
+#include "GameOptions.h"
+#include "FileSystem.h"
+#include <QDebug>
+#include <QSaveFile>
+
+namespace {
+bool load(const QString& path, std::vector<GameOptionItem> &contents, int & version)
+{
+ contents.clear();
+ QFile file(path);
+ if (!file.open(QFile::ReadOnly))
+ {
+ qWarning() << "Failed to read options file.";
+ return false;
+ }
+ version = 0;
+ while(!file.atEnd())
+ {
+ auto line = file.readLine();
+ if(line.endsWith('\n'))
+ {
+ line.chop(1);
+ }
+ auto separatorIndex = line.indexOf(':');
+ if(separatorIndex == -1)
+ {
+ continue;
+ }
+ auto key = QString::fromUtf8(line.data(), separatorIndex);
+ auto value = QString::fromUtf8(line.data() + separatorIndex + 1, line.size() - 1 - separatorIndex);
+ qDebug() << "!!" << key << "!!";
+ if(key == "version")
+ {
+ version = value.toInt();
+ continue;
+ }
+ contents.emplace_back(GameOptionItem{key, value});
+ }
+ qDebug() << "Loaded" << path << "with version:" << version;
+ return true;
+}
+bool save(const QString& path, std::vector<GameOptionItem> &mapping, int version)
+{
+ QSaveFile out(path);
+ if(!out.open(QIODevice::WriteOnly))
+ {
+ return false;
+ }
+ if(version != 0)
+ {
+ QString versionLine = QString("version:%1\n").arg(version);
+ out.write(versionLine.toUtf8());
+ }
+ auto iter = mapping.begin();
+ while (iter != mapping.end())
+ {
+ out.write(iter->key.toUtf8());
+ out.write(":");
+ out.write(iter->value.toUtf8());
+ out.write("\n");
+ iter++;
+ }
+ return out.commit();
+}
+}
+
+GameOptions::GameOptions(const QString& path):
+ path(path)
+{
+ reload();
+}
+
+QVariant GameOptions::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if(role != Qt::DisplayRole)
+ {
+ return QAbstractListModel::headerData(section, orientation, role);
+ }
+ switch(section)
+ {
+ case 0:
+ return tr("Key");
+ case 1:
+ return tr("Value");
+ default:
+ return QVariant();
+ }
+}
+
+QVariant GameOptions::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ int row = index.row();
+ int column = index.column();
+
+ if (row < 0 || row >= int(contents.size()))
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ if(column == 0)
+ {
+ return contents[row].key;
+ }
+ else
+ {
+ return contents[row].value;
+ }
+ default:
+ return QVariant();
+ }
+ return QVariant();
+}
+
+int GameOptions::rowCount(const QModelIndex&) const
+{
+ return contents.size();
+}
+
+int GameOptions::columnCount(const QModelIndex&) const
+{
+ return 2;
+}
+
+bool GameOptions::isLoaded() const
+{
+ return loaded;
+}
+
+bool GameOptions::reload()
+{
+ beginResetModel();
+ loaded = load(path, contents, version);
+ endResetModel();
+ return loaded;
+}
+
+bool GameOptions::save()
+{
+ return ::save(path, contents, version);
+}
diff --git a/api/logic/minecraft/gameoptions/GameOptions.h b/api/logic/minecraft/gameoptions/GameOptions.h
new file mode 100644
index 00000000..c6d25492
--- /dev/null
+++ b/api/logic/minecraft/gameoptions/GameOptions.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <map>
+#include <QString>
+#include <QAbstractListModel>
+
+struct GameOptionItem
+{
+ QString key;
+ QString value;
+};
+
+class GameOptions : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+ explicit GameOptions(const QString& path);
+ virtual ~GameOptions() = default;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex & parent) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+ bool isLoaded() const;
+ bool reload();
+ bool save();
+
+private:
+ std::vector<GameOptionItem> contents;
+ bool loaded = false;
+ QString path;
+ int version = 0;
+};
diff --git a/api/logic/minecraft/launch/ClaimAccount.h b/api/logic/minecraft/launch/ClaimAccount.h
index 64891406..c5bd75f3 100644
--- a/api/logic/minecraft/launch/ClaimAccount.h
+++ b/api/logic/minecraft/launch/ClaimAccount.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 b29496c9..9c7d3c94 100644
--- a/api/logic/minecraft/launch/CreateServerResourcePacksFolder.h
+++ b/api/logic/minecraft/launch/CreateGameFolders.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 7ead8324..2110384f 100644
--- a/api/logic/minecraft/launch/DirectJavaLaunch.cpp
+++ b/api/logic/minecraft/launch/DirectJavaLaunch.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 fb555e3e..58b119b8 100644
--- a/api/logic/minecraft/launch/DirectJavaLaunch.h
+++ b/api/logic/minecraft/launch/DirectJavaLaunch.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 336eddbd..d57499aa 100644
--- a/api/logic/minecraft/launch/ExtractNatives.cpp
+++ b/api/logic/minecraft/launch/ExtractNatives.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 d9587991..094fcd6b 100644
--- a/api/logic/minecraft/launch/ExtractNatives.h
+++ b/api/logic/minecraft/launch/ExtractNatives.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 466c1e46..ee469770 100644
--- a/api/logic/minecraft/launch/LauncherPartLaunch.cpp
+++ b/api/logic/minecraft/launch/LauncherPartLaunch.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 7fadbd66..6a7ee0e5 100644
--- a/api/logic/minecraft/launch/LauncherPartLaunch.h
+++ b/api/logic/minecraft/launch/LauncherPartLaunch.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 34472bb3..93de9d59 100644
--- a/api/logic/minecraft/launch/ModMinecraftJar.cpp
+++ b/api/logic/minecraft/launch/ModMinecraftJar.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,7 +19,7 @@
#include "minecraft/OpSys.h"
#include "FileSystem.h"
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
void ModMinecraftJar::executeTask()
{
@@ -43,7 +43,7 @@ void ModMinecraftJar::executeTask()
}
// create temporary modded jar, if needed
- auto components = m_inst->getComponentList();
+ auto components = m_inst->getPackProfile();
auto profile = components->getProfile();
auto jarMods = m_inst->getJarMods();
if(jarMods.size())
diff --git a/api/logic/minecraft/launch/ModMinecraftJar.h b/api/logic/minecraft/launch/ModMinecraftJar.h
index 48e11736..081c6a91 100644
--- a/api/logic/minecraft/launch/ModMinecraftJar.h
+++ b/api/logic/minecraft/launch/ModMinecraftJar.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 6d5b93ae..0b9611ad 100644
--- a/api/logic/minecraft/launch/PrintInstanceInfo.cpp
+++ b/api/logic/minecraft/launch/PrintInstanceInfo.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 ae0a0400..fdc30f31 100644
--- a/api/logic/minecraft/launch/PrintInstanceInfo.h
+++ b/api/logic/minecraft/launch/PrintInstanceInfo.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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
new file mode 100644
index 00000000..4d206665
--- /dev/null
+++ b/api/logic/minecraft/launch/ReconstructAssets.cpp
@@ -0,0 +1,36 @@
+/* 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 "ReconstructAssets.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+#include "minecraft/AssetsUtils.h"
+#include "launch/LaunchTask.h"
+
+void ReconstructAssets::executeTask()
+{
+ auto instance = m_parent->instance();
+ std::shared_ptr<MinecraftInstance> minecraftInstance = std::dynamic_pointer_cast<MinecraftInstance>(instance);
+ auto components = minecraftInstance->getPackProfile();
+ auto profile = components->getProfile();
+ auto assets = profile->getMinecraftAssets();
+
+ if(!AssetsUtils::reconstructAssets(assets->id, minecraftInstance->resourcesDir()))
+ {
+ emit logLine("Failed to reconstruct Minecraft assets.", MessageLevel::Error);
+ }
+
+ emitSucceeded();
+}
diff --git a/api/logic/minecraft/launch/ReconstructAssets.h b/api/logic/minecraft/launch/ReconstructAssets.h
new file mode 100644
index 00000000..58d7febd
--- /dev/null
+++ b/api/logic/minecraft/launch/ReconstructAssets.h
@@ -0,0 +1,33 @@
+/* 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 <memory>
+
+class ReconstructAssets: public LaunchStep
+{
+ Q_OBJECT
+public:
+ explicit ReconstructAssets(LaunchTask *parent) : LaunchStep(parent){};
+ virtual ~ReconstructAssets(){};
+
+ void executeTask() override;
+ bool canAbort() const override
+ {
+ return false;
+ }
+};
diff --git a/api/logic/minecraft/launch/ScanModFolders.cpp b/api/logic/minecraft/launch/ScanModFolders.cpp
new file mode 100644
index 00000000..2a0e21b3
--- /dev/null
+++ b/api/logic/minecraft/launch/ScanModFolders.cpp
@@ -0,0 +1,59 @@
+/* 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 "ScanModFolders.h"
+#include "launch/LaunchTask.h"
+#include "MMCZip.h"
+#include "minecraft/OpSys.h"
+#include "FileSystem.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/mod/ModFolderModel.h"
+
+void ScanModFolders::executeTask()
+{
+ auto m_inst = std::dynamic_pointer_cast<MinecraftInstance>(m_parent->instance());
+
+ auto loaders = m_inst->loaderModList();
+ connect(loaders.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::modsDone);
+ if(!loaders->update()) {
+ m_modsDone = true;
+ }
+
+ auto cores = m_inst->coreModList();
+ connect(cores.get(), &ModFolderModel::updateFinished, this, &ScanModFolders::coreModsDone);
+ if(!cores->update()) {
+ m_coreModsDone = true;
+ }
+ checkDone();
+}
+
+void ScanModFolders::modsDone()
+{
+ m_modsDone = true;
+ checkDone();
+}
+
+void ScanModFolders::coreModsDone()
+{
+ m_coreModsDone = true;
+ checkDone();
+}
+
+void ScanModFolders::checkDone()
+{
+ if(m_modsDone && m_coreModsDone) {
+ emitSucceeded();
+ }
+}
diff --git a/api/logic/minecraft/launch/ScanModFolders.h b/api/logic/minecraft/launch/ScanModFolders.h
new file mode 100644
index 00000000..d5989170
--- /dev/null
+++ b/api/logic/minecraft/launch/ScanModFolders.h
@@ -0,0 +1,42 @@
+/* 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 <memory>
+
+class ScanModFolders: public LaunchStep
+{
+ Q_OBJECT
+public:
+ explicit ScanModFolders(LaunchTask *parent) : LaunchStep(parent) {};
+ virtual ~ScanModFolders(){};
+
+ virtual void executeTask() override;
+ virtual bool canAbort() const override
+ {
+ return false;
+ }
+private slots:
+ void coreModsDone();
+ void modsDone();
+private:
+ void checkDone();
+
+private: // DATA
+ bool m_modsDone = false;
+ bool m_coreModsDone = false;
+};
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 a35101e3..9f9bda5a 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.cpp
+++ b/api/logic/minecraft/legacy/LegacyInstance.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -21,7 +21,6 @@
#include "LegacyInstance.h"
#include "minecraft/legacy/LegacyModList.h"
-#include "minecraft/SimpleModList.h"
#include "minecraft/WorldList.h"
#include <MMCZip.h>
#include <FileSystem.h>
@@ -107,11 +106,6 @@ std::shared_ptr<LegacyModList> LegacyInstance::jarModList() const
return jar_mod_list;
}
-QList<Mod> LegacyInstance::getJarMods() const
-{
- return jarModList()->allMods();
-}
-
QString LegacyInstance::gameRoot() const
{
QFileInfo mcDir(FS::PathCombine(instanceRoot(), "minecraft"));
@@ -231,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 619e2c83..325bac7a 100644
--- a/api/logic/minecraft/legacy/LegacyInstance.h
+++ b/api/logic/minecraft/legacy/LegacyInstance.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,11 +16,11 @@
#pragma once
#include "BaseInstance.h"
-#include "minecraft/Mod.h"
+#include "launch/LaunchTask.h"
#include "multimc_logic_export.h"
-class SimpleModList;
+class ModFolderModel;
class LegacyModList;
class WorldList;
class Task;
@@ -77,7 +77,6 @@ public:
QString customBaseJar() const;
std::shared_ptr<LegacyModList> jarModList() const;
- QList<Mod> getJarMods() const;
std::shared_ptr<WorldList> worldList() const;
/*!
@@ -112,7 +111,8 @@ public:
{
return false;
}
- std::shared_ptr<LaunchTask> createLaunchTask(AuthSessionPtr account) override
+ shared_qobject_ptr<LaunchTask> createLaunchTask(
+ AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override
{
return nullptr;
}
@@ -126,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 ca171b08..7301eb8c 100644
--- a/api/logic/minecraft/legacy/LegacyModList.cpp
+++ b/api/logic/minecraft/legacy/LegacyModList.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -22,8 +22,7 @@ LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
: m_dir(dir), m_list_file(list_file)
{
FS::ensureFolderPathExists(m_dir.absolutePath());
- m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs |
- QDir::NoSymLinks);
+ m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks);
m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
}
@@ -34,15 +33,11 @@ LegacyModList::LegacyModList(const QString &dir, const QString &list_file)
};
typedef QList<OrderItem> OrderList;
-static void internalSort(QList<Mod> &what)
+static void internalSort(QList<LegacyModList::Mod> &what)
{
- auto predicate = [](const Mod &left, const Mod &right)
+ auto predicate = [](const LegacyModList::Mod &left, const LegacyModList::Mod &right)
{
- if (left.name() == right.name())
- {
- return left.mmc_id().localeAwareCompare(right.mmc_id()) < 0;
- }
- return left.name().localeAwareCompare(right.name()) < 0;
+ return left.fileName().localeAwareCompare(right.fileName()) < 0;
};
std::sort(what.begin(), what.end(), predicate);
}
@@ -90,7 +85,6 @@ bool LegacyModList::update()
QList<Mod> newMods;
m_dir.refresh();
auto folderContents = m_dir.entryInfoList();
- bool orderOrStateChanged = false;
// first, process the ordered items (if any)
OrderList listOrder = readListFile(m_list_file);
@@ -124,48 +118,19 @@ bool LegacyModList::update()
// remove from the actual folder contents list
folderContents.takeAt(idx);
// append the new mod
- orderedMods.append(Mod(info));
- if (isEnabled != item.enabled)
- orderOrStateChanged = true;
- }
- else
- {
- orderOrStateChanged = true;
+ orderedMods.append(info);
}
}
- // if there are any untracked files...
+ // if there are any untracked files... append them sorted at the end
if (folderContents.size())
{
- // the order surely changed!
for (auto entry : folderContents)
{
- newMods.append(Mod(entry));
+ newMods.append(entry);
}
internalSort(newMods);
orderedMods.append(newMods);
- orderOrStateChanged = true;
- }
- // otherwise, if we were already tracking some mods
- else if (mods.size())
- {
- // if the number doesn't match, order changed.
- if (mods.size() != orderedMods.size())
- orderOrStateChanged = true;
- // if it does match, compare the mods themselves
- else
- for (int i = 0; i < mods.size(); i++)
- {
- if (!mods[i].strongCompare(orderedMods[i]))
- {
- orderOrStateChanged = true;
- break;
- }
- }
}
mods.swap(orderedMods);
- if (orderOrStateChanged && !m_list_file.isEmpty())
- {
- qDebug() << "Mod list " << m_list_file << " changed!";
- }
return true;
}
diff --git a/api/logic/minecraft/legacy/LegacyModList.h b/api/logic/minecraft/legacy/LegacyModList.h
index 8bc68b87..8881d471 100644
--- a/api/logic/minecraft/legacy/LegacyModList.h
+++ b/api/logic/minecraft/legacy/LegacyModList.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,21 +19,14 @@
#include <QString>
#include <QDir>
-#include "minecraft/Mod.h"
-
#include "multimc_logic_export.h"
-class LegacyInstance;
-class BaseInstance;
-
-/**
- * A legacy mod list.
- * Backed by a folder.
- */
class MULTIMC_LOGIC_EXPORT LegacyModList
{
public:
+ using Mod = QFileInfo;
+
LegacyModList(const QString &dir, const QString &list_file = QString());
/// Reloads the mod list and returns true if the list changed.
diff --git a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
index 9a832a24..a4ea60cd 100644
--- a/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
+++ b/api/logic/minecraft/legacy/LegacyUpgradeTask.cpp
@@ -6,7 +6,8 @@
#include <QtConcurrentRun>
#include "LegacyInstance.h"
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
+#include "LegacyModList.h"
#include "classparser.h"
LegacyUpgradeTask::LegacyUpgradeTask(InstancePtr origInstance)
@@ -83,7 +84,7 @@ void LegacyUpgradeTask::copyFinished()
}
}
}
- auto components = inst.getComponentList();
+ auto components = inst.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", preferredVersionNumber, true);
@@ -96,10 +97,10 @@ void LegacyUpgradeTask::copyFinished()
components->installCustomJar(jarPath);
}
- auto jarMods = legacyInst->getJarMods();
+ auto jarMods = legacyInst->jarModList()->allMods();
for(auto & jarMod: jarMods)
{
- QString modPath = jarMod.filename().absoluteFilePath();
+ QString modPath = jarMod.absoluteFilePath();
qDebug() << "jarMod: " << modPath;
components->installJarMods({modPath});
}
diff --git a/api/logic/minecraft/mod/LocalModParseTask.cpp b/api/logic/minecraft/mod/LocalModParseTask.cpp
new file mode 100644
index 00000000..0d6972fb
--- /dev/null
+++ b/api/logic/minecraft/mod/LocalModParseTask.cpp
@@ -0,0 +1,467 @@
+#include "LocalModParseTask.h"
+
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <QJsonValue>
+#include <quazip.h>
+#include <quazipfile.h>
+#include <toml.h>
+
+#include "settings/INIFile.h"
+#include "FileSystem.h"
+
+namespace {
+
+// NEW format
+// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/6f62b37cea040daf350dc253eae6326dd9c822c3
+
+// OLD format:
+// https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc
+std::shared_ptr<ModDetails> ReadMCModInfo(QByteArray contents)
+{
+ auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr<ModDetails>
+ {
+ if (!arr.at(0).isObject()) {
+ return nullptr;
+ }
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ auto firstObj = arr.at(0).toObject();
+ details->mod_id = firstObj.value("modid").toString();
+ auto name = firstObj.value("name").toString();
+ // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name
+ if(name != "Example Mod") {
+ details->name = name;
+ }
+ details->version = firstObj.value("version").toString();
+ details->updateurl = firstObj.value("updateUrl").toString();
+ auto homeurl = firstObj.value("url").toString().trimmed();
+ if(!homeurl.isEmpty())
+ {
+ // fix up url.
+ if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://"))
+ {
+ homeurl.prepend("http://");
+ }
+ }
+ details->homeurl = homeurl;
+ details->description = firstObj.value("description").toString();
+ QJsonArray authors = firstObj.value("authorList").toArray();
+ if (authors.size() == 0) {
+ // FIXME: what is the format of this? is there any?
+ authors = firstObj.value("authors").toArray();
+ }
+
+ for (auto author: authors)
+ {
+ details->authors.append(author.toString());
+ }
+ details->credits = firstObj.value("credits").toString();
+ return details;
+ };
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ // this is the very old format that had just the array
+ if (jsonDoc.isArray())
+ {
+ return getInfoFromArray(jsonDoc.array());
+ }
+ else if (jsonDoc.isObject())
+ {
+ auto val = jsonDoc.object().value("modinfoversion");
+ if(val.isUndefined()) {
+ val = jsonDoc.object().value("modListVersion");
+ }
+ int version = val.toDouble();
+ if (version != 2)
+ {
+ qCritical() << "BAD stuff happened to mod json:";
+ qCritical() << contents;
+ return nullptr;
+ }
+ auto arrVal = jsonDoc.object().value("modlist");
+ if(arrVal.isUndefined()) {
+ arrVal = jsonDoc.object().value("modList");
+ }
+ if (arrVal.isArray())
+ {
+ return getInfoFromArray(arrVal.toArray());
+ }
+ }
+ 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)
+{
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ auto object = jsonDoc.object();
+ auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0;
+
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+
+ details->mod_id = object.value("id").toString();
+ details->version = object.value("version").toString();
+
+ details->name = object.contains("name") ? object.value("name").toString() : details->mod_id;
+ details->description = object.value("description").toString();
+
+ if (schemaVersion >= 1)
+ {
+ QJsonArray authors = object.value("authors").toArray();
+ for (auto author: authors)
+ {
+ if(author.isObject()) {
+ details->authors.append(author.toObject().value("name").toString());
+ }
+ else {
+ details->authors.append(author.toString());
+ }
+ }
+
+ if (object.contains("contact"))
+ {
+ QJsonObject contact = object.value("contact").toObject();
+
+ if (contact.contains("homepage"))
+ {
+ details->homeurl = contact.value("homepage").toString();
+ }
+ }
+ }
+ return details;
+}
+
+std::shared_ptr<ModDetails> ReadForgeInfo(QByteArray contents)
+{
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ // Read the data
+ details->name = "Minecraft Forge";
+ details->mod_id = "Forge";
+ details->homeurl = "http://www.minecraftforge.net/forum/";
+ INIFile ini;
+ if (!ini.loadFile(contents))
+ return details;
+
+ QString major = ini.get("forge.major.number", "0").toString();
+ QString minor = ini.get("forge.minor.number", "0").toString();
+ QString revision = ini.get("forge.revision.number", "0").toString();
+ QString build = ini.get("forge.build.number", "0").toString();
+
+ details->version = major + "." + minor + "." + revision + "." + build;
+ return details;
+}
+
+std::shared_ptr<ModDetails> ReadLiteModInfo(QByteArray contents)
+{
+ std::shared_ptr<ModDetails> details = std::make_shared<ModDetails>();
+ QJsonParseError jsonError;
+ QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError);
+ auto object = jsonDoc.object();
+ if (object.contains("name"))
+ {
+ details->mod_id = details->name = object.value("name").toString();
+ }
+ if (object.contains("version"))
+ {
+ details->version = object.value("version").toString("");
+ }
+ else
+ {
+ details->version = object.value("revision").toString("");
+ }
+ details->mcversion = object.value("mcversion").toString();
+ auto author = object.value("author").toString();
+ if(!author.isEmpty()) {
+ details->authors.append(author);
+ }
+ details->description = object.value("description").toString();
+ details->homeurl = object.value("url").toString();
+ return details;
+}
+
+}
+
+LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile):
+ m_token(token),
+ m_type(type),
+ m_modFile(modFile),
+ m_result(new Result())
+{
+}
+
+void LocalModParseTask::processAsZip()
+{
+ QuaZip zip(m_modFile.filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ 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))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadMCModInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+ else if (zip.setCurrentFile("fabric.mod.json"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadFabricModInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+ else if (zip.setCurrentFile("forgeversion.properties"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadForgeInfo(file.readAll());
+ file.close();
+ zip.close();
+ return;
+ }
+
+ zip.close();
+}
+
+void LocalModParseTask::processAsFolder()
+{
+ QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info"));
+ if (mcmod_info.isFile())
+ {
+ QFile mcmod(mcmod_info.filePath());
+ if (!mcmod.open(QIODevice::ReadOnly))
+ return;
+ auto data = mcmod.readAll();
+ if (data.isEmpty() || data.isNull())
+ return;
+ m_result->details = ReadMCModInfo(data);
+ }
+}
+
+void LocalModParseTask::processAsLitemod()
+{
+ QuaZip zip(m_modFile.filePath());
+ if (!zip.open(QuaZip::mdUnzip))
+ return;
+
+ QuaZipFile file(&zip);
+
+ if (zip.setCurrentFile("litemod.json"))
+ {
+ if (!file.open(QIODevice::ReadOnly))
+ {
+ zip.close();
+ return;
+ }
+
+ m_result->details = ReadLiteModInfo(file.readAll());
+ file.close();
+ }
+ zip.close();
+}
+
+void LocalModParseTask::run()
+{
+ switch(m_type)
+ {
+ case Mod::MOD_ZIPFILE:
+ processAsZip();
+ break;
+ case Mod::MOD_FOLDER:
+ processAsFolder();
+ break;
+ case Mod::MOD_LITEMOD:
+ processAsLitemod();
+ break;
+ default:
+ break;
+ }
+ emit finished(m_token);
+}
diff --git a/api/logic/minecraft/mod/LocalModParseTask.h b/api/logic/minecraft/mod/LocalModParseTask.h
new file mode 100644
index 00000000..0f119ba6
--- /dev/null
+++ b/api/logic/minecraft/mod/LocalModParseTask.h
@@ -0,0 +1,37 @@
+#pragma once
+#include <QRunnable>
+#include <QDebug>
+#include <QObject>
+#include "Mod.h"
+#include "ModDetails.h"
+
+class LocalModParseTask : public QObject, public QRunnable
+{
+ Q_OBJECT
+public:
+ struct Result {
+ QString id;
+ std::shared_ptr<ModDetails> details;
+ };
+ using ResultPtr = std::shared_ptr<Result>;
+ ResultPtr result() const {
+ return m_result;
+ }
+
+ LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile);
+ void run();
+
+signals:
+ void finished(int token);
+
+private:
+ void processAsZip();
+ void processAsFolder();
+ void processAsLitemod();
+
+private:
+ int m_token;
+ Mod::ModType m_type;
+ QFileInfo m_modFile;
+ ResultPtr m_result;
+};
diff --git a/api/logic/minecraft/mod/Mod.cpp b/api/logic/minecraft/mod/Mod.cpp
new file mode 100644
index 00000000..b6bff29b
--- /dev/null
+++ b/api/logic/minecraft/mod/Mod.cpp
@@ -0,0 +1,151 @@
+/* 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 <QDir>
+#include <QString>
+
+#include "Mod.h"
+#include <QDebug>
+#include <FileSystem.h>
+
+namespace {
+
+ModDetails invalidDetails;
+
+}
+
+
+Mod::Mod(const QFileInfo &file)
+{
+ repath(file);
+ m_changedDateTime = file.lastModified();
+}
+
+void Mod::repath(const QFileInfo &file)
+{
+ m_file = file;
+ QString name_base = file.fileName();
+
+ m_type = Mod::MOD_UNKNOWN;
+
+ m_mmc_id = name_base;
+
+ if (m_file.isDir())
+ {
+ m_type = MOD_FOLDER;
+ m_name = name_base;
+ }
+ else if (m_file.isFile())
+ {
+ if (name_base.endsWith(".disabled"))
+ {
+ m_enabled = false;
+ name_base.chop(9);
+ }
+ else
+ {
+ m_enabled = true;
+ }
+ if (name_base.endsWith(".zip") || name_base.endsWith(".jar"))
+ {
+ m_type = MOD_ZIPFILE;
+ name_base.chop(4);
+ }
+ else if (name_base.endsWith(".litemod"))
+ {
+ m_type = MOD_LITEMOD;
+ name_base.chop(8);
+ }
+ else
+ {
+ m_type = MOD_SINGLEFILE;
+ }
+ m_name = name_base;
+ }
+}
+
+bool Mod::enable(bool value)
+{
+ if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER)
+ return false;
+
+ if (m_enabled == value)
+ return false;
+
+ QString path = m_file.absoluteFilePath();
+ if (value)
+ {
+ QFile foo(path);
+ if (!path.endsWith(".disabled"))
+ return false;
+ path.chop(9);
+ if (!foo.rename(path))
+ return false;
+ }
+ else
+ {
+ QFile foo(path);
+ path += ".disabled";
+ if (!foo.rename(path))
+ return false;
+ }
+ repath(QFileInfo(path));
+ m_enabled = value;
+ return true;
+}
+
+bool Mod::destroy()
+{
+ m_type = MOD_UNKNOWN;
+ return FS::deletePath(m_file.filePath());
+}
+
+
+const ModDetails & Mod::details() const
+{
+ if(!m_localDetails)
+ return invalidDetails;
+ return *m_localDetails;
+}
+
+
+QString Mod::version() const
+{
+ return details().version;
+}
+
+QString Mod::name() const
+{
+ auto & d = details();
+ if(!d.name.isEmpty()) {
+ return d.name;
+ }
+ return m_name;
+}
+
+QString Mod::homeurl() const
+{
+ return details().homeurl;
+}
+
+QString Mod::description() const
+{
+ return details().description;
+}
+
+QStringList Mod::authors() const
+{
+ return details().authors;
+}
diff --git a/api/logic/minecraft/Mod.h b/api/logic/minecraft/mod/Mod.h
index 0c1adf24..f77ffd41 100644
--- a/api/logic/minecraft/Mod.h
+++ b/api/logic/minecraft/mod/Mod.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,8 +16,16 @@
#pragma once
#include <QFileInfo>
#include <QDateTime>
+#include <QList>
+#include <memory>
-class Mod
+#include "multimc_logic_export.h"
+
+#include "ModDetails.h"
+
+
+
+class MULTIMC_LOGIC_EXPORT Mod
{
public:
enum ModType
@@ -29,6 +37,7 @@ public:
MOD_LITEMOD, //!< The mod is a litemod
};
+ Mod() = default;
Mod(const QFileInfo &file);
QFileInfo filename() const
@@ -39,54 +48,14 @@ public:
{
return m_mmc_id;
}
- QString mod_id() const
- {
- return m_mod_id;
- }
ModType type() const
{
return m_type;
}
- QString mcversion() const
- {
- return m_mcversion;
- }
- ;
bool valid()
{
return m_type != MOD_UNKNOWN;
}
- QString name() const
- {
- QString name = m_name.trimmed();
- if(name.isEmpty() || name == "Example Mod")
- {
- return m_mmc_id;
- }
- return m_name;
- }
-
- QString version() const;
-
- QString homeurl() const
- {
- return m_homeurl;
- }
-
- QString description() const
- {
- return m_description;
- }
-
- QString authors() const
- {
- return m_authors;
- }
-
- QString credits() const
- {
- return m_credits;
- }
QDateTime dateTimeChanged() const
{
@@ -98,45 +67,51 @@ public:
return m_enabled;
}
+ const ModDetails &details() const;
+
+ QString name() const;
+ QString version() const;
+ QString homeurl() const;
+ QString description() const;
+ QStringList authors() const;
+
bool enable(bool value);
// delete all the files of this mod
bool destroy();
- // replace this mod with a copy of the other
- bool replace(Mod &with);
+
// change the mod's filesystem path (used by mod lists for *MAGIC* purposes)
void repath(const QFileInfo &file);
- // WEAK compare operator - used for replacing mods
- bool operator==(const Mod &other) const;
- bool strongCompare(const Mod &other) const;
-
-private:
- void ReadMCModInfo(QByteArray contents);
- void ReadForgeInfo(QByteArray contents);
- void ReadLiteModInfo(QByteArray contents);
+ bool shouldResolve() {
+ return !m_resolving && !m_resolved;
+ }
+ bool isResolving() {
+ return m_resolving;
+ }
+ int resolutionTicket()
+ {
+ return m_resolutionTicket;
+ }
+ void setResolving(bool resolving, int resolutionTicket) {
+ m_resolving = resolving;
+ m_resolutionTicket = resolutionTicket;
+ }
+ void finishResolvingWithDetails(std::shared_ptr<ModDetails> details){
+ m_resolving = false;
+ m_resolved = true;
+ m_localDetails = details;
+ }
protected:
-
- // FIXME: what do do with those? HMM...
- /*
- void ReadModInfoData(QString info);
- void ReadForgeInfoData(QString infoFileData);
- */
-
QFileInfo m_file;
QDateTime m_changedDateTime;
QString m_mmc_id;
- QString m_mod_id;
- bool m_enabled = true;
QString m_name;
- QString m_version;
- QString m_mcversion;
- QString m_homeurl;
- QString m_updateurl;
- QString m_description;
- QString m_authors;
- QString m_credits;
-
- ModType m_type;
+ bool m_enabled = true;
+ bool m_resolving = false;
+ bool m_resolved = false;
+ int m_resolutionTicket = 0;
+ ModType m_type = MOD_UNKNOWN;
+ std::shared_ptr<ModDetails> m_localDetails;
};
diff --git a/api/logic/minecraft/mod/ModDetails.h b/api/logic/minecraft/mod/ModDetails.h
new file mode 100644
index 00000000..6ab4aee7
--- /dev/null
+++ b/api/logic/minecraft/mod/ModDetails.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <QString>
+#include <QStringList>
+
+struct ModDetails
+{
+ QString mod_id;
+ QString name;
+ QString version;
+ QString mcversion;
+ QString homeurl;
+ QString updateurl;
+ QString description;
+ QStringList authors;
+ QString credits;
+};
diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.cpp b/api/logic/minecraft/mod/ModFolderLoadTask.cpp
new file mode 100644
index 00000000..88349877
--- /dev/null
+++ b/api/logic/minecraft/mod/ModFolderLoadTask.cpp
@@ -0,0 +1,18 @@
+#include "ModFolderLoadTask.h"
+#include <QDebug>
+
+ModFolderLoadTask::ModFolderLoadTask(QDir dir) :
+ m_dir(dir), m_result(new Result())
+{
+}
+
+void ModFolderLoadTask::run()
+{
+ m_dir.refresh();
+ for (auto entry : m_dir.entryInfoList())
+ {
+ Mod m(entry);
+ m_result->mods[m.mmc_id()] = m;
+ }
+ emit succeeded();
+}
diff --git a/api/logic/minecraft/mod/ModFolderLoadTask.h b/api/logic/minecraft/mod/ModFolderLoadTask.h
new file mode 100644
index 00000000..8d720e65
--- /dev/null
+++ b/api/logic/minecraft/mod/ModFolderLoadTask.h
@@ -0,0 +1,29 @@
+#pragma once
+#include <QRunnable>
+#include <QObject>
+#include <QDir>
+#include <QMap>
+#include "Mod.h"
+#include <memory>
+
+class ModFolderLoadTask : public QObject, public QRunnable
+{
+ Q_OBJECT
+public:
+ struct Result {
+ QMap<QString, Mod> mods;
+ };
+ using ResultPtr = std::shared_ptr<Result>;
+ ResultPtr result() const {
+ return m_result;
+ }
+
+public:
+ ModFolderLoadTask(QDir dir);
+ void run();
+signals:
+ void succeeded();
+private:
+ QDir m_dir;
+ ResultPtr m_result;
+};
diff --git a/api/logic/minecraft/mod/ModFolderModel.cpp b/api/logic/minecraft/mod/ModFolderModel.cpp
new file mode 100644
index 00000000..031eebe5
--- /dev/null
+++ b/api/logic/minecraft/mod/ModFolderModel.cpp
@@ -0,0 +1,554 @@
+/* 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 "ModFolderModel.h"
+#include <FileSystem.h>
+#include <QMimeData>
+#include <QUrl>
+#include <QUuid>
+#include <QString>
+#include <QFileSystemWatcher>
+#include <QDebug>
+#include "ModFolderLoadTask.h"
+#include <QThreadPool>
+#include <algorithm>
+#include "LocalModParseTask.h"
+
+ModFolderModel::ModFolderModel(const QString &dir) : QAbstractListModel(), m_dir(dir)
+{
+ FS::ensureFolderPathExists(m_dir.absolutePath());
+ m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs | QDir::NoSymLinks);
+ m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
+ m_watcher = new QFileSystemWatcher(this);
+ connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
+}
+
+void ModFolderModel::startWatching()
+{
+ if(is_watching)
+ return;
+
+ update();
+
+ is_watching = m_watcher->addPath(m_dir.absolutePath());
+ if (is_watching)
+ {
+ qDebug() << "Started watching " << m_dir.absolutePath();
+ }
+ else
+ {
+ qDebug() << "Failed to start watching " << m_dir.absolutePath();
+ }
+}
+
+void ModFolderModel::stopWatching()
+{
+ if(!is_watching)
+ return;
+
+ is_watching = !m_watcher->removePath(m_dir.absolutePath());
+ if (!is_watching)
+ {
+ qDebug() << "Stopped watching " << m_dir.absolutePath();
+ }
+ else
+ {
+ qDebug() << "Failed to stop watching " << m_dir.absolutePath();
+ }
+}
+
+bool ModFolderModel::update()
+{
+ if (!isValid()) {
+ return false;
+ }
+ if(m_update) {
+ scheduled_update = true;
+ return true;
+ }
+
+ auto task = new ModFolderLoadTask(m_dir);
+ m_update = task->result();
+ QThreadPool *threadPool = QThreadPool::globalInstance();
+ connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate);
+ threadPool->start(task);
+ return true;
+}
+
+void ModFolderModel::finishUpdate()
+{
+ QSet<QString> currentSet = modsIndex.keys().toSet();
+ auto & newMods = m_update->mods;
+ QSet<QString> newSet = newMods.keys().toSet();
+
+ // see if the kept mods changed in some way
+ {
+ QSet<QString> kept = currentSet;
+ kept.intersect(newSet);
+ for(auto & keptMod: kept) {
+ auto & newMod = newMods[keptMod];
+ auto row = modsIndex[keptMod];
+ auto & currentMod = mods[row];
+ if(newMod.dateTimeChanged() == currentMod.dateTimeChanged()) {
+ // no significant change, ignore...
+ continue;
+ }
+ auto & oldMod = mods[row];
+ if(oldMod.isResolving()) {
+ activeTickets.remove(oldMod.resolutionTicket());
+ }
+ oldMod = newMod;
+ resolveMod(mods[row]);
+ emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
+ }
+ }
+
+ // remove mods no longer present
+ {
+ QSet<QString> removed = currentSet;
+ QList<int> removedRows;
+ removed.subtract(newSet);
+ for(auto & removedMod: removed) {
+ removedRows.append(modsIndex[removedMod]);
+ }
+ std::sort(removedRows.begin(), removedRows.end(), std::greater<int>());
+ for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) {
+ int removedIndex = *iter;
+ beginRemoveRows(QModelIndex(), removedIndex, removedIndex);
+ auto removedIter = mods.begin() + removedIndex;
+ if(removedIter->isResolving()) {
+ activeTickets.remove(removedIter->resolutionTicket());
+ }
+ mods.erase(removedIter);
+ endRemoveRows();
+ }
+ }
+
+ // add new mods to the end
+ {
+ QSet<QString> added = newSet;
+ added.subtract(currentSet);
+ beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1);
+ for(auto & addedMod: added) {
+ mods.append(newMods[addedMod]);
+ resolveMod(mods.last());
+ }
+ endInsertRows();
+ }
+
+ // update index
+ {
+ modsIndex.clear();
+ int idx = 0;
+ for(auto & mod: mods) {
+ modsIndex[mod.mmc_id()] = idx;
+ idx++;
+ }
+ }
+
+ m_update.reset();
+
+ emit updateFinished();
+
+ if(scheduled_update) {
+ scheduled_update = false;
+ update();
+ }
+}
+
+void ModFolderModel::resolveMod(Mod& m)
+{
+ if(!m.shouldResolve()) {
+ return;
+ }
+
+ auto task = new LocalModParseTask(nextResolutionTicket, m.type(), m.filename());
+ auto result = task->result();
+ result->id = m.mmc_id();
+ activeTickets.insert(nextResolutionTicket, result);
+ m.setResolving(true, nextResolutionTicket);
+ nextResolutionTicket++;
+ QThreadPool *threadPool = QThreadPool::globalInstance();
+ connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse);
+ threadPool->start(task);
+}
+
+void ModFolderModel::finishModParse(int token)
+{
+ auto iter = activeTickets.find(token);
+ if(iter == activeTickets.end()) {
+ return;
+ }
+ auto result = *iter;
+ activeTickets.remove(token);
+ int row = modsIndex[result->id];
+ auto & mod = mods[row];
+ mod.finishResolvingWithDetails(result->details);
+ emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1));
+}
+
+void ModFolderModel::disableInteraction(bool disabled)
+{
+ if (interaction_disabled == disabled) {
+ return;
+ }
+ interaction_disabled = disabled;
+ if(size()) {
+ emit dataChanged(index(0), index(size() - 1));
+ }
+}
+
+void ModFolderModel::directoryChanged(QString path)
+{
+ update();
+}
+
+bool ModFolderModel::isValid()
+{
+ return m_dir.exists() && m_dir.isReadable();
+}
+
+// FIXME: this does not take disabled mod (with extra .disable extension) into account...
+bool ModFolderModel::installMod(const QString &filename)
+{
+ if(interaction_disabled) {
+ return false;
+ }
+
+ // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName
+ auto originalPath = FS::NormalizePath(filename);
+ QFileInfo fileinfo(originalPath);
+
+ if (!fileinfo.exists() || !fileinfo.isReadable())
+ {
+ qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath;
+ return false;
+ }
+ qDebug() << "installing: " << fileinfo.absoluteFilePath();
+
+ Mod installedMod(fileinfo);
+ if (!installedMod.valid())
+ {
+ qDebug() << originalPath << "is not a valid mod. Ignoring it.";
+ return false;
+ }
+
+ auto type = installedMod.type();
+ if (type == Mod::MOD_UNKNOWN)
+ {
+ qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it.";
+ return false;
+ }
+
+ auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName()));
+ if(originalPath == newpath)
+ {
+ qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense...";
+ return false;
+ }
+
+ if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD)
+ {
+ if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled")))
+ {
+ if(!QFile::remove(newpath))
+ {
+ // FIXME: report error in a user-visible way
+ qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
+ return false;
+ }
+ qDebug() << newpath << "has been deleted.";
+ }
+ if (!QFile::copy(fileinfo.filePath(), newpath))
+ {
+ qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed.";
+ // FIXME: report error in a user-visible way
+ return false;
+ }
+ FS::updateTimestamp(newpath);
+ installedMod.repath(newpath);
+ update();
+ return true;
+ }
+ else if (type == Mod::MOD_FOLDER)
+ {
+ QString from = fileinfo.filePath();
+ if(QFile::exists(newpath))
+ {
+ qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath;
+ return false;
+ }
+
+ if (!FS::copy(from, newpath)())
+ {
+ qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed.";
+ return false;
+ }
+ installedMod.repath(newpath);
+ update();
+ return true;
+ }
+ return false;
+}
+
+bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable)
+{
+ if(interaction_disabled) {
+ return false;
+ }
+
+ if(indexes.isEmpty())
+ return true;
+
+ for (auto index: indexes)
+ {
+ if(index.column() != 0) {
+ continue;
+ }
+ setModStatus(index.row(), enable);
+ }
+ return true;
+}
+
+bool ModFolderModel::deleteMods(const QModelIndexList& indexes)
+{
+ if(interaction_disabled) {
+ return false;
+ }
+
+ if(indexes.isEmpty())
+ return true;
+
+ for (auto i: indexes)
+ {
+ Mod &m = mods[i.row()];
+ m.destroy();
+ }
+ return true;
+}
+
+int ModFolderModel::columnCount(const QModelIndex &parent) const
+{
+ return NUM_COLUMNS;
+}
+
+QVariant ModFolderModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ int row = index.row();
+ int column = index.column();
+
+ if (row < 0 || row >= mods.size())
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch (column)
+ {
+ case NameColumn:
+ return mods[row].name();
+ case VersionColumn: {
+ switch(mods[row].type()) {
+ case Mod::MOD_FOLDER:
+ return tr("Folder");
+ case Mod::MOD_SINGLEFILE:
+ return tr("File");
+ default:
+ break;
+ }
+ return mods[row].version();
+ }
+ case DateColumn:
+ return mods[row].dateTimeChanged();
+
+ default:
+ return QVariant();
+ }
+
+ case Qt::ToolTipRole:
+ return mods[row].mmc_id();
+
+ case Qt::CheckStateRole:
+ switch (column)
+ {
+ case ActiveColumn:
+ return mods[row].enabled() ? Qt::Checked : Qt::Unchecked;
+ default:
+ return QVariant();
+ }
+ default:
+ return QVariant();
+ }
+}
+
+bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
+ {
+ return false;
+ }
+
+ if (role == Qt::CheckStateRole)
+ {
+ return setModStatus(index.row(), Toggle);
+ }
+ return false;
+}
+
+bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action)
+{
+ if(row < 0 || row >= mods.size()) {
+ return false;
+ }
+
+ auto &mod = mods[row];
+ bool desiredStatus;
+ switch(action) {
+ case Enable:
+ desiredStatus = true;
+ break;
+ case Disable:
+ desiredStatus = false;
+ break;
+ case Toggle:
+ default:
+ desiredStatus = !mod.enabled();
+ break;
+ }
+
+ if(desiredStatus == mod.enabled()) {
+ return true;
+ }
+
+ // preserve the row, but change its ID
+ auto oldId = mod.mmc_id();
+ if(!mod.enable(!mod.enabled())) {
+ return false;
+ }
+ auto newId = mod.mmc_id();
+ if(modsIndex.contains(newId)) {
+ // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled
+ // But is it necessary?
+ }
+ modsIndex.remove(oldId);
+ modsIndex[newId] = row;
+ emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1));
+ return true;
+}
+
+QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch (section)
+ {
+ case ActiveColumn:
+ return QString();
+ case NameColumn:
+ return tr("Name");
+ case VersionColumn:
+ return tr("Version");
+ case DateColumn:
+ return tr("Last changed");
+ default:
+ return QVariant();
+ }
+
+ case Qt::ToolTipRole:
+ switch (section)
+ {
+ case ActiveColumn:
+ return tr("Is the mod enabled?");
+ case NameColumn:
+ return tr("The name of the mod.");
+ case VersionColumn:
+ return tr("The version of the mod.");
+ case DateColumn:
+ return tr("The date and time this mod was last changed (or added).");
+ default:
+ return QVariant();
+ }
+ default:
+ return QVariant();
+ }
+ return QVariant();
+}
+
+Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
+ auto flags = defaultFlags;
+ if(interaction_disabled) {
+ flags &= ~Qt::ItemIsDropEnabled;
+ }
+ else
+ {
+ flags |= Qt::ItemIsDropEnabled;
+ if(index.isValid()) {
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ }
+ return flags;
+}
+
+Qt::DropActions ModFolderModel::supportedDropActions() const
+{
+ // copy from outside, move from within and other mod lists
+ return Qt::CopyAction | Qt::MoveAction;
+}
+
+QStringList ModFolderModel::mimeTypes() const
+{
+ QStringList types;
+ types << "text/uri-list";
+ return types;
+}
+
+bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&)
+{
+ if (action == Qt::IgnoreAction)
+ {
+ return true;
+ }
+
+ // check if the action is supported
+ if (!data || !(action & supportedDropActions()))
+ {
+ return false;
+ }
+
+ // files dropped from outside?
+ if (data->hasUrls())
+ {
+ auto urls = data->urls();
+ for (auto url : urls)
+ {
+ // only local files may be dropped...
+ if (!url.isLocalFile())
+ {
+ continue;
+ }
+ // TODO: implement not only copy, but also move
+ // FIXME: handle errors here
+ installMod(url.toLocalFile());
+ }
+ return true;
+ }
+ return false;
+}
diff --git a/api/logic/minecraft/SimpleModList.h b/api/logic/minecraft/mod/ModFolderModel.h
index d623a295..b0a76121 100644
--- a/api/logic/minecraft/SimpleModList.h
+++ b/api/logic/minecraft/mod/ModFolderModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,13 +16,17 @@
#pragma once
#include <QList>
+#include <QMap>
+#include <QSet>
#include <QString>
#include <QDir>
#include <QAbstractListModel>
-#include "minecraft/Mod.h"
+#include "Mod.h"
#include "multimc_logic_export.h"
+#include "ModFolderLoadTask.h"
+#include "LocalModParseTask.h"
class LegacyInstance;
class BaseInstance;
@@ -32,7 +36,7 @@ class QFileSystemWatcher;
* A legacy mod list.
* Backed by a folder.
*/
-class MULTIMC_LOGIC_EXPORT SimpleModList : public QAbstractListModel
+class MULTIMC_LOGIC_EXPORT ModFolderModel : public QAbstractListModel
{
Q_OBJECT
public:
@@ -40,11 +44,16 @@ public:
{
ActiveColumn = 0,
NameColumn,
- DateColumn,
VersionColumn,
+ DateColumn,
NUM_COLUMNS
};
- SimpleModList(const QString &dir);
+ enum ModStatusAction {
+ Disable,
+ Enable,
+ Toggle
+ };
+ ModFolderModel(const QString &dir);
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
@@ -59,7 +68,7 @@ public:
{
return size();
}
- ;
+
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
virtual int columnCount(const QModelIndex &parent) const override;
@@ -76,9 +85,13 @@ public:
{
return mods[index];
}
+ const Mod &at(size_t index) const
+ {
+ return mods.at(index);
+ }
/// Reloads the mod list and returns true if the list changed.
- virtual bool update();
+ bool update();
/**
* Adds the given mod to the list at the given index - if the list supports custom ordering
@@ -86,15 +99,15 @@ public:
bool installMod(const QString& filename);
/// Deletes all the selected mods
- virtual bool deleteMods(const QModelIndexList &indexes);
+ bool deleteMods(const QModelIndexList &indexes);
/// Enable or disable listed mods
- virtual bool enableMods(const QModelIndexList &indexes, bool enable = true);
+ bool setModStatus(const QModelIndexList &indexes, ModStatusAction action);
void startWatching();
void stopWatching();
- virtual bool isValid();
+ bool isValid();
QDir dir()
{
@@ -106,16 +119,31 @@ public:
return mods;
}
+public slots:
+ void disableInteraction(bool disabled);
+
private
slots:
void directoryChanged(QString path);
+ void finishUpdate();
+ void finishModParse(int token);
signals:
- void changed();
+ void updateFinished();
+
+private:
+ void resolveMod(Mod& m);
+ bool setModStatus(int index, ModStatusAction action);
protected:
QFileSystemWatcher *m_watcher;
bool is_watching = false;
+ ModFolderLoadTask::ResultPtr m_update;
+ bool scheduled_update = false;
+ bool interaction_disabled = false;
QDir m_dir;
+ QMap<QString, int> modsIndex;
+ QMap<int, LocalModParseTask::ResultPtr> activeTickets;
+ int nextResolutionTicket = 0;
QList<Mod> mods;
};
diff --git a/api/logic/minecraft/SimpleModList_test.cpp b/api/logic/minecraft/mod/ModFolderModel_test.cpp
index a100b539..76f16ed5 100644
--- a/api/logic/minecraft/SimpleModList_test.cpp
+++ b/api/logic/minecraft/mod/ModFolderModel_test.cpp
@@ -4,9 +4,9 @@
#include "TestUtil.h"
#include "FileSystem.h"
-#include "minecraft/SimpleModList.h"
+#include "minecraft/mod/ModFolderModel.h"
-class SimpleModListTest : public QObject
+class ModFolderModelTest : public QObject
{
Q_OBJECT
@@ -32,7 +32,7 @@ slots:
{
QString folder = source;
QTemporaryDir tempDir;
- SimpleModList m(tempDir.path());
+ ModFolderModel m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
@@ -41,13 +41,13 @@ slots:
{
QString folder = source + '/';
QTemporaryDir tempDir;
- SimpleModList m(tempDir.path());
+ ModFolderModel m(tempDir.path());
m.installMod(folder);
verify(tempDir.path());
}
}
};
-QTEST_GUILESS_MAIN(SimpleModListTest)
+QTEST_GUILESS_MAIN(ModFolderModelTest)
-#include "SimpleModList_test.moc"
+#include "ModFolderModel_test.moc"
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/AssetUpdateTask.cpp b/api/logic/minecraft/update/AssetUpdateTask.cpp
index 1661822d..e26ab4ef 100644
--- a/api/logic/minecraft/update/AssetUpdateTask.cpp
+++ b/api/logic/minecraft/update/AssetUpdateTask.cpp
@@ -1,7 +1,7 @@
#include "Env.h"
#include "AssetUpdateTask.h"
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include "net/ChecksumValidator.h"
#include "minecraft/AssetsUtils.h"
@@ -17,7 +17,7 @@ AssetUpdateTask::~AssetUpdateTask()
void AssetUpdateTask::executeTask()
{
setStatus(tr("Updating assets index..."));
- auto components = m_inst->getComponentList();
+ auto components = m_inst->getPackProfile();
auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
QUrl indexUrl = assets->url;
@@ -54,13 +54,13 @@ void AssetUpdateTask::assetIndexFinished()
AssetsIndex index;
qDebug() << m_inst->name() << ": Finished asset index download";
- auto components = m_inst->getComponentList();
+ auto components = m_inst->getPackProfile();
auto profile = components->getProfile();
auto assets = profile->getMinecraftAssets();
QString asset_fname = "assets/indexes/" + assets->id + ".json";
// FIXME: this looks like a job for a generic validator based on json schema?
- if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, &index))
+ if (!AssetsUtils::loadAssetsIndexJson(assets->id, asset_fname, index))
{
auto metacache = ENV.metacache();
auto entry = metacache->resolveEntry("asset_indexes", assets->id + ".json");
@@ -101,7 +101,7 @@ bool AssetUpdateTask::abort()
}
else
{
- qWarning() << "Prematurely aborted FMLLibrariesTask";
+ qWarning() << "Prematurely aborted AssetUpdateTask";
}
return true;
}
diff --git a/api/logic/minecraft/update/FMLLibrariesTask.cpp b/api/logic/minecraft/update/FMLLibrariesTask.cpp
index 5b4975ab..a05a7c2a 100644
--- a/api/logic/minecraft/update/FMLLibrariesTask.cpp
+++ b/api/logic/minecraft/update/FMLLibrariesTask.cpp
@@ -3,7 +3,8 @@
#include <minecraft/VersionFilterData.h>
#include "FMLLibrariesTask.h"
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
+#include "BuildConfig.h"
FMLLibrariesTask::FMLLibrariesTask(MinecraftInstance * inst)
{
@@ -13,7 +14,7 @@ void FMLLibrariesTask::executeTask()
{
// Get the mod list
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
- auto components = inst->getComponentList();
+ auto components = inst->getPackProfile();
auto profile = components->getProfile();
if (!profile->hasTrait("legacyFML"))
@@ -63,8 +64,7 @@ void FMLLibrariesTask::executeTask()
for (auto &lib : fmlLibsToProcess)
{
auto entry = metacache->resolveEntry("fmllibs", lib.filename);
- QString urlString = lib.ours ? URLConstants::FMLLIBS_OUR_BASE_URL + lib.filename
- : URLConstants::FMLLIBS_FORGE_BASE_URL + lib.filename;
+ QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename;
dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry));
}
diff --git a/api/logic/minecraft/update/LibrariesTask.cpp b/api/logic/minecraft/update/LibrariesTask.cpp
index 6dcb149c..7f66a651 100644
--- a/api/logic/minecraft/update/LibrariesTask.cpp
+++ b/api/logic/minecraft/update/LibrariesTask.cpp
@@ -1,7 +1,7 @@
#include "Env.h"
#include "LibrariesTask.h"
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
LibrariesTask::LibrariesTask(MinecraftInstance * inst)
{
@@ -15,51 +15,51 @@ void LibrariesTask::executeTask()
MinecraftInstance *inst = (MinecraftInstance *)m_inst;
// Build a list of URLs that will need to be downloaded.
- auto components = inst->getComponentList();
+ auto components = inst->getPackProfile();
auto profile = components->getProfile();
auto job = new NetJob(tr("Libraries for instance %1").arg(inst->name()));
downloadJob.reset(job);
auto metacache = ENV.metacache();
- QList<LibraryPtr> brokenLocalLibs;
- QStringList failedFiles;
- auto createJob = [&](const LibraryPtr & lib)
- {
- if(!lib)
- {
- emitFailed(tr("Null jar is specified in the metadata, aborting."));
- return;
- }
- auto dls = lib->getDownloads(currentSystem, metacache.get(), failedFiles, inst->getLocalLibraryPath());
- for(auto dl : dls)
- {
- downloadJob->addNetAction(dl);
- }
- };
- auto createJobs = [&](const QList<LibraryPtr> & libs)
+
+ auto processArtifactPool = [&](const QList<LibraryPtr> & pool, QStringList & errors, const QString & localPath)
{
- for (auto lib : libs)
+ for (auto lib : pool)
{
- createJob(lib);
+ if(!lib)
+ {
+ emitFailed(tr("Null jar is specified in the metadata, aborting."));
+ return false;
+ }
+ auto dls = lib->getDownloads(currentSystem, metacache.get(), errors, localPath);
+ for(auto dl : dls)
+ {
+ downloadJob->addNetAction(dl);
+ }
}
+ return true;
};
- createJobs(profile->getLibraries());
- createJobs(profile->getNativeLibraries());
- createJobs(profile->getJarMods());
- createJob(profile->getMainJar());
- // FIXME: this is never filled!!!!
- if (!brokenLocalLibs.empty())
+ QStringList failedLocalLibraries;
+ QList<LibraryPtr> libArtifactPool;
+ libArtifactPool.append(profile->getLibraries());
+ libArtifactPool.append(profile->getNativeLibraries());
+ libArtifactPool.append(profile->getMavenFiles());
+ libArtifactPool.append(profile->getMainJar());
+ processArtifactPool(libArtifactPool, failedLocalLibraries, inst->getLocalLibraryPath());
+
+ QStringList failedLocalJarMods;
+ processArtifactPool(profile->getJarMods(), failedLocalJarMods, inst->jarModsDir());
+
+ if (!failedLocalJarMods.empty() || !failedLocalLibraries.empty())
{
downloadJob.reset();
- QString failed_all = failedFiles.join("\n");
- emitFailed(tr("Some libraries marked as 'local' are missing their jar "
- "files:\n%1\n\nYou'll have to correct this problem manually. If this is "
- "an externally tracked instance, make sure to run it at least once "
- "outside of MultiMC.").arg(failed_all));
+ QString failed_all = (failedLocalLibraries + failedLocalJarMods).join("\n");
+ emitFailed(tr("Some artifacts marked as 'local' are missing their files:\n%1\n\nYou need to either add the files, or removed the packages that require them.\nYou'll have to correct this problem manually.").arg(failed_all));
return;
}
+
connect(downloadJob.get(), &NetJob::succeeded, this, &LibrariesTask::emitSucceeded);
connect(downloadJob.get(), &NetJob::failed, this, &LibrariesTask::jarlibFailed);
connect(downloadJob.get(), &NetJob::progress, this, &LibrariesTask::progress);
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/FileResolvingTask.cpp b/api/logic/modplatform/flame/FileResolvingTask.cpp
index 24cafcdd..295574f0 100644
--- a/api/logic/modplatform/flame/FileResolvingTask.cpp
+++ b/api/logic/modplatform/flame/FileResolvingTask.cpp
@@ -1,7 +1,9 @@
#include "FileResolvingTask.h"
#include "Json.h"
-const char * metabase = "https://cursemeta.dries007.net";
+namespace {
+ const char * metabase = "https://cursemeta.dries007.net";
+}
Flame::FileResolvingTask::FileResolvingTask(Flame::Manifest& toProcess)
: m_toProcess(toProcess)
@@ -34,70 +36,14 @@ void Flame::FileResolvingTask::netJobFinished()
int index = 0;
for(auto & bytes: results)
{
+ auto & out = m_toProcess.files[index];
try
{
- auto doc = Json::requireDocument(bytes);
- auto obj = Json::requireObject(doc);
- auto & out = m_toProcess.files[index];
- // result code signifies true failure.
- if(obj.contains("code"))
- {
- qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a negative result:";
- qCritical() << bytes;
- failed = true;
- continue;
- }
- out.fileName = Json::requireString(obj, "FileNameOnDisk");
- QString rawUrl = Json::requireString(obj, "DownloadURL");
- out.url = QUrl(rawUrl, QUrl::TolerantMode);
- if(!out.url.isValid())
- {
- throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
- }
- // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
- // It is also optional
- QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
- if(!projObj.isEmpty())
- {
- QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
- if(strType == "singlefile")
- {
- out.type = File::Type::SingleFile;
- }
- else if(strType == "ctoc")
- {
- out.type = File::Type::Ctoc;
- }
- else if(strType == "cmod2")
- {
- out.type = File::Type::Cmod2;
- }
- else if(strType == "mod")
- {
- out.type = File::Type::Mod;
- }
- else if(strType == "folder")
- {
- out.type = File::Type::Folder;
- }
- else if(strType == "modpack")
- {
- out.type = File::Type::Modpack;
- }
- else
- {
- qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of unknown file type:" << strType;
- out.type = File::Type::Unknown;
- failed = true;
- continue;
- }
- out.targetFolder = Json::ensureString(projObj, "Path", "mods");
- }
- out.resolved = true;
+ failed &= (!out.parseFromBytes(bytes));
}
catch (const JSONValidationError &e)
{
- auto & out = m_toProcess.files[index];
+
qCritical() << "Resolving of" << out.projectId << out.fileId << "failed because of a parsing error:";
qCritical() << e.cause();
qCritical() << "JSON:";
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 0f57c9bc..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;
@@ -64,3 +64,63 @@ void Flame::loadManifest(Flame::Manifest & m, const QString &filepath)
}
loadManifestV1(m, obj);
}
+
+bool Flame::File::parseFromBytes(const QByteArray& bytes)
+{
+ auto doc = Json::requireDocument(bytes);
+ auto obj = Json::requireObject(doc);
+ // result code signifies true failure.
+ if(obj.contains("code"))
+ {
+ qCritical() << "Resolving of" << projectId << fileId << "failed because of a negative result:";
+ qCritical() << bytes;
+ return false;
+ }
+ fileName = Json::requireString(obj, "FileNameOnDisk");
+ QString rawUrl = Json::requireString(obj, "DownloadURL");
+ url = QUrl(rawUrl, QUrl::TolerantMode);
+ if(!url.isValid())
+ {
+ throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl));
+ }
+ // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience
+ // It is also optional
+ QJsonObject projObj = Json::ensureObject(obj, "_Project", {});
+ if(!projObj.isEmpty())
+ {
+ QString strType = Json::ensureString(projObj, "PackageType", "mod").toLower();
+ if(strType == "singlefile")
+ {
+ type = File::Type::SingleFile;
+ }
+ else if(strType == "ctoc")
+ {
+ type = File::Type::Ctoc;
+ }
+ else if(strType == "cmod2")
+ {
+ type = File::Type::Cmod2;
+ }
+ else if(strType == "mod")
+ {
+ type = File::Type::Mod;
+ }
+ else if(strType == "folder")
+ {
+ type = File::Type::Folder;
+ }
+ else if(strType == "modpack")
+ {
+ type = File::Type::Modpack;
+ }
+ else
+ {
+ qCritical() << "Resolving of" << projectId << fileId << "failed because of unknown file type:" << strType;
+ type = File::Type::Unknown;
+ return false;
+ }
+ targetFolder = Json::ensureString(projObj, "Path", "mods");
+ }
+ resolved = true;
+ return true;
+}
diff --git a/api/logic/modplatform/flame/PackManifest.h b/api/logic/modplatform/flame/PackManifest.h
index 34232eee..02f39f0e 100644
--- a/api/logic/modplatform/flame/PackManifest.h
+++ b/api/logic/modplatform/flame/PackManifest.h
@@ -8,6 +8,9 @@ namespace Flame
{
struct File
{
+ // NOTE: throws JSONValidationError
+ bool parseFromBytes(const QByteArray &bytes);
+
int projectId = 0;
int fileId = 0;
// NOTE: the opposite to 'optional'. This is at the time of writing unused.
diff --git a/api/logic/modplatform/ftb/FtbPackFetchTask.cpp b/api/logic/modplatform/legacy_ftb/PackFetchTask.cpp
index 19f6c31a..c2ef6436 100644
--- a/api/logic/modplatform/ftb/FtbPackFetchTask.cpp
+++ b/api/logic/modplatform/legacy_ftb/PackFetchTask.cpp
@@ -1,32 +1,36 @@
-#include "FtbPackFetchTask.h"
+#include "PackFetchTask.h"
+#include "PrivatePackManager.h"
+
#include <QDomDocument>
-#include "FtbPrivatePackManager.h"
+#include <BuildConfig.h>
+
+namespace LegacyFTB {
-void FtbPackFetchTask::fetch()
+void PackFetchTask::fetch()
{
publicPacks.clear();
thirdPartyPacks.clear();
- NetJob *netJob = new NetJob("FtbModpackFetch");
+ NetJob *netJob = new NetJob("LegacyFTB::ModpackFetch");
- QUrl publicPacksUrl = QUrl("https://ftb.cursecdn.com/FTB2/static/modpacks.xml");
+ QUrl publicPacksUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/modpacks.xml");
qDebug() << "Downloading public version info from" << publicPacksUrl.toString();
netJob->addNetAction(Net::Download::makeByteArray(publicPacksUrl, &publicModpacksXmlFileData));
- QUrl thirdPartyUrl = QUrl("https://ftb.cursecdn.com/FTB2/static/thirdparty.xml");
+ QUrl thirdPartyUrl = QUrl(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/thirdparty.xml");
qDebug() << "Downloading thirdparty version info from" << thirdPartyUrl.toString();
netJob->addNetAction(Net::Download::makeByteArray(thirdPartyUrl, &thirdPartyModpacksXmlFileData));
- QObject::connect(netJob, &NetJob::succeeded, this, &FtbPackFetchTask::fileDownloadFinished);
- QObject::connect(netJob, &NetJob::failed, this, &FtbPackFetchTask::fileDownloadFailed);
+ QObject::connect(netJob, &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished);
+ QObject::connect(netJob, &NetJob::failed, this, &PackFetchTask::fileDownloadFailed);
jobPtr.reset(netJob);
netJob->start();
}
-void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch)
+void PackFetchTask::fetchPrivate(const QStringList & toFetch)
{
- QString privatePackBaseUrl = QString("https://ftb.cursecdn.com/FTB2/static/%1.xml");
+ QString privatePackBaseUrl = BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1.xml";
for (auto &packCode: toFetch)
{
@@ -36,9 +40,9 @@ void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch)
QObject::connect(job, &NetJob::succeeded, this, [this, job, data, packCode]
{
- FtbModpackList packs;
- parseAndAddPacks(*data, FtbPackType::Private, packs);
- foreach(FtbModpack currentPack, packs)
+ ModpackList packs;
+ parseAndAddPacks(*data, PackType::Private, packs);
+ foreach(Modpack currentPack, packs)
{
currentPack.packCode = packCode;
emit privateFileDownloadFinished(currentPack);
@@ -63,18 +67,18 @@ void FtbPackFetchTask::fetchPrivate(const QStringList & toFetch)
}
}
-void FtbPackFetchTask::fileDownloadFinished()
+void PackFetchTask::fileDownloadFinished()
{
jobPtr.reset();
QStringList failedLists;
- if(!parseAndAddPacks(publicModpacksXmlFileData, FtbPackType::Public, publicPacks))
+ if(!parseAndAddPacks(publicModpacksXmlFileData, PackType::Public, publicPacks))
{
failedLists.append(tr("Public Packs"));
}
- if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, FtbPackType::ThirdParty, thirdPartyPacks))
+ if(!parseAndAddPacks(thirdPartyModpacksXmlFileData, PackType::ThirdParty, thirdPartyPacks))
{
failedLists.append(tr("Third Party Packs"));
}
@@ -89,7 +93,7 @@ void FtbPackFetchTask::fileDownloadFinished()
}
}
-bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list)
+bool PackFetchTask::parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list)
{
QDomDocument doc;
@@ -110,7 +114,7 @@ bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType,
{
QDomElement element = nodes.at(i).toElement();
- FtbModpack modpack;
+ Modpack modpack;
modpack.name = element.attribute("name");
modpack.currentVersion = element.attribute("version");
modpack.mcVersion = element.attribute("mcVersion");
@@ -159,8 +163,10 @@ bool FtbPackFetchTask::parseAndAddPacks(QByteArray &data, FtbPackType packType,
return true;
}
-void FtbPackFetchTask::fileDownloadFailed(QString reason)
+void PackFetchTask::fileDownloadFailed(QString reason)
{
- qWarning() << "Fetching FtbPacks failed:" << reason;
+ qWarning() << "Fetching FTBPacks failed:" << reason;
emit failed(reason);
}
+
+}
diff --git a/api/logic/modplatform/ftb/FtbPackFetchTask.h b/api/logic/modplatform/legacy_ftb/PackFetchTask.h
index f955fe83..4a8469b1 100644
--- a/api/logic/modplatform/ftb/FtbPackFetchTask.h
+++ b/api/logic/modplatform/legacy_ftb/PackFetchTask.h
@@ -6,13 +6,15 @@
#include <QObject>
#include "PackHelpers.h"
-class MULTIMC_LOGIC_EXPORT FtbPackFetchTask : public QObject {
+namespace LegacyFTB {
+
+class MULTIMC_LOGIC_EXPORT PackFetchTask : public QObject {
Q_OBJECT
public:
- FtbPackFetchTask() = default;
- virtual ~FtbPackFetchTask() = default;
+ PackFetchTask() = default;
+ virtual ~PackFetchTask() = default;
void fetch();
void fetchPrivate(const QStringList &toFetch);
@@ -23,18 +25,20 @@ private:
QByteArray publicModpacksXmlFileData;
QByteArray thirdPartyModpacksXmlFileData;
- bool parseAndAddPacks(QByteArray &data, FtbPackType packType, FtbModpackList &list);
- FtbModpackList publicPacks;
- FtbModpackList thirdPartyPacks;
+ bool parseAndAddPacks(QByteArray &data, PackType packType, ModpackList &list);
+ ModpackList publicPacks;
+ ModpackList thirdPartyPacks;
protected slots:
void fileDownloadFinished();
void fileDownloadFailed(QString reason);
signals:
- void finished(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks);
+ void finished(ModpackList publicPacks, ModpackList thirdPartyPacks);
void failed(QString reason);
- void privateFileDownloadFinished(FtbModpack modpack);
+ void privateFileDownloadFinished(Modpack modpack);
void privateFileDownloadFailed(QString reason, QString packCode);
};
+
+}
diff --git a/api/logic/modplatform/ftb/PackHelpers.h b/api/logic/modplatform/legacy_ftb/PackHelpers.h
index 4306caee..566210d0 100644
--- a/api/logic/modplatform/ftb/PackHelpers.h
+++ b/api/logic/modplatform/legacy_ftb/PackHelpers.h
@@ -5,15 +5,17 @@
#include <QStringList>
#include <QMetaType>
+namespace LegacyFTB {
+
//Header for structs etc...
-enum class FtbPackType
+enum class PackType
{
Public,
ThirdParty,
Private
};
-struct FtbModpack
+struct Modpack
{
QString name;
QString description;
@@ -31,11 +33,13 @@ struct FtbModpack
bool bugged = false;
bool broken = false;
- FtbPackType type;
+ PackType type;
QString packCode;
};
-//We need it for the proxy model
-Q_DECLARE_METATYPE(FtbModpack)
+typedef QList<Modpack> ModpackList;
-typedef QList<FtbModpack> FtbModpackList;
+}
+
+//We need it for the proxy model
+Q_DECLARE_METATYPE(LegacyFTB::Modpack)
diff --git a/api/logic/modplatform/ftb/FtbPackInstallTask.cpp b/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp
index 9b7689d9..c77f3250 100644
--- a/api/logic/modplatform/ftb/FtbPackInstallTask.cpp
+++ b/api/logic/modplatform/legacy_ftb/PackInstallTask.cpp
@@ -1,26 +1,32 @@
-#include "FtbPackInstallTask.h"
+#include "PackInstallTask.h"
+
#include "Env.h"
#include "MMCZip.h"
-#include "QtConcurrent"
+
#include "BaseInstance.h"
#include "FileSystem.h"
#include "settings/INISettingsObject.h"
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include "minecraft/GradleSpecifier.h"
+#include "BuildConfig.h"
+
+#include <QtConcurrent>
+
+namespace LegacyFTB {
-FtbPackInstallTask::FtbPackInstallTask(FtbModpack pack, QString version)
+PackInstallTask::PackInstallTask(Modpack pack, QString version)
{
m_pack = pack;
m_version = version;
}
-void FtbPackInstallTask::executeTask()
+void PackInstallTask::executeTask()
{
downloadPack();
}
-void FtbPackInstallTask::downloadPack()
+void PackInstallTask::downloadPack()
{
setStatus(tr("Downloading zip for %1").arg(m_pack.name));
@@ -30,45 +36,46 @@ void FtbPackInstallTask::downloadPack()
entry->setStale(true);
QString url;
- if(m_pack.type == FtbPackType::Private)
+ if(m_pack.type == PackType::Private)
{
- url = QString("http://ftb.cursecdn.com/FTB2/privatepacks/%1").arg(packoffset);
+ url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "privatepacks/%1").arg(packoffset);
}
else
{
- url = QString("http://ftb.cursecdn.com/FTB2/modpacks/%1").arg(packoffset);
+ url = QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "modpacks/%1").arg(packoffset);
}
job->addNetAction(Net::Download::makeCached(url, entry));
archivePath = entry->getFullPath();
netJobContainer.reset(job);
- connect(job, &NetJob::succeeded, this, &FtbPackInstallTask::onDownloadSucceeded);
- connect(job, &NetJob::failed, this, &FtbPackInstallTask::onDownloadFailed);
- connect(job, &NetJob::progress, this, &FtbPackInstallTask::onDownloadProgress);
+ connect(job, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded);
+ connect(job, &NetJob::failed, this, &PackInstallTask::onDownloadFailed);
+ connect(job, &NetJob::progress, this, &PackInstallTask::onDownloadProgress);
job->start();
progress(1, 4);
}
-void FtbPackInstallTask::onDownloadSucceeded()
+void PackInstallTask::onDownloadSucceeded()
{
abortable = false;
unzip();
}
-void FtbPackInstallTask::onDownloadFailed(QString reason)
+void PackInstallTask::onDownloadFailed(QString reason)
{
+ abortable = false;
emitFailed(reason);
}
-void FtbPackInstallTask::onDownloadProgress(qint64 current, qint64 total)
+void PackInstallTask::onDownloadProgress(qint64 current, qint64 total)
{
abortable = true;
progress(current, total * 4);
setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10));
}
-void FtbPackInstallTask::unzip()
+void PackInstallTask::unzip()
{
progress(2, 4);
setStatus(tr("Extracting modpack"));
@@ -82,22 +89,22 @@ void FtbPackInstallTask::unzip()
}
m_extractFuture = QtConcurrent::run(QThreadPool::globalInstance(), MMCZip::extractDir, archivePath, extractDir.absolutePath() + "/unzip");
- connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &FtbPackInstallTask::onUnzipFinished);
- connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &FtbPackInstallTask::onUnzipCanceled);
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::finished, this, &PackInstallTask::onUnzipFinished);
+ connect(&m_extractFutureWatcher, &QFutureWatcher<QStringList>::canceled, this, &PackInstallTask::onUnzipCanceled);
m_extractFutureWatcher.setFuture(m_extractFuture);
}
-void FtbPackInstallTask::onUnzipFinished()
+void PackInstallTask::onUnzipFinished()
{
install();
}
-void FtbPackInstallTask::onUnzipCanceled()
+void PackInstallTask::onUnzipCanceled()
{
emitAborted();
}
-void FtbPackInstallTask::install()
+void PackInstallTask::install()
{
progress(3, 4);
setStatus(tr("Installing modpack"));
@@ -114,11 +121,12 @@ void FtbPackInstallTask::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");
MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath);
- auto components = instance.getComponentList();
+ auto components = instance.getPackProfile();
components->buildingFromScratch();
components->setComponentVersion("net.minecraft", m_pack.mcVersion, true);
@@ -194,7 +202,7 @@ void FtbPackInstallTask::install()
emitSucceeded();
}
-bool FtbPackInstallTask::abort()
+bool PackInstallTask::abort()
{
if(abortable)
{
@@ -202,3 +210,5 @@ bool FtbPackInstallTask::abort()
}
return false;
}
+
+}
diff --git a/api/logic/modplatform/ftb/FtbPackInstallTask.h b/api/logic/modplatform/legacy_ftb/PackInstallTask.h
index 3319025e..f3515781 100644
--- a/api/logic/modplatform/ftb/FtbPackInstallTask.h
+++ b/api/logic/modplatform/legacy_ftb/PackInstallTask.h
@@ -6,16 +6,21 @@
#include "meta/Index.h"
#include "meta/Version.h"
#include "meta/VersionList.h"
-#include "modplatform/ftb/PackHelpers.h"
+#include "PackHelpers.h"
-class MULTIMC_LOGIC_EXPORT FtbPackInstallTask : public InstanceTask
+#include <nonstd/optional>
+
+namespace LegacyFTB {
+
+class MULTIMC_LOGIC_EXPORT PackInstallTask : public InstanceTask
{
Q_OBJECT
public:
- explicit FtbPackInstallTask(FtbModpack pack, QString version);
- virtual ~FtbPackInstallTask(){}
+ explicit PackInstallTask(Modpack pack, QString version);
+ virtual ~PackInstallTask(){}
+ bool canAbort() const override { return true; }
bool abort() override;
protected:
@@ -38,11 +43,13 @@ 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;
- FtbModpack m_pack;
+ Modpack m_pack;
QString m_version;
};
+
+}
diff --git a/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp b/api/logic/modplatform/legacy_ftb/PrivatePackManager.cpp
index c3477cec..501e6003 100644
--- a/api/logic/modplatform/ftb/FtbPrivatePackManager.cpp
+++ b/api/logic/modplatform/legacy_ftb/PrivatePackManager.cpp
@@ -1,10 +1,12 @@
-#include "FtbPrivatePackManager.h"
+#include "PrivatePackManager.h"
#include <QDebug>
#include "FileSystem.h"
-void FtbPrivatePackManager::load()
+namespace LegacyFTB {
+
+void PrivatePackManager::load()
{
try
{
@@ -18,7 +20,7 @@ void FtbPrivatePackManager::load()
}
}
-void FtbPrivatePackManager::save() const
+void PrivatePackManager::save() const
{
if(!dirty)
{
@@ -35,3 +37,5 @@ void FtbPrivatePackManager::save() const
qWarning() << "Failed to write third party FTB pack codes to" << m_filename;
}
}
+
+}
diff --git a/api/logic/modplatform/ftb/FtbPrivatePackManager.h b/api/logic/modplatform/legacy_ftb/PrivatePackManager.h
index 388224d6..0232bac7 100644
--- a/api/logic/modplatform/ftb/FtbPrivatePackManager.h
+++ b/api/logic/modplatform/legacy_ftb/PrivatePackManager.h
@@ -5,10 +5,12 @@
#include <QFile>
#include "multimc_logic_export.h"
-class MULTIMC_LOGIC_EXPORT FtbPrivatePackManager
+namespace LegacyFTB {
+
+class MULTIMC_LOGIC_EXPORT PrivatePackManager
{
public:
- ~FtbPrivatePackManager()
+ ~PrivatePackManager()
{
save();
}
@@ -38,3 +40,5 @@ private:
QString m_filename = "private_packs.txt";
mutable bool dirty = false;
};
+
+}
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 fc634e3d..3f183b7d 100644
--- a/api/logic/net/Download.cpp
+++ b/api/logic/net/Download.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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);
@@ -167,14 +168,24 @@ bool Download::handleRedirect()
}
QString redirectStr = QString::fromUtf8(redirectBA);
- /*
- * IF the URL begins with //, we need to insert the URL scheme.
- * See: https://bugreports.qt-project.org/browse/QTBUG-41061
- */
if(redirectStr.startsWith("//"))
{
+ /*
+ * IF the URL begins with //, we need to insert the URL scheme.
+ * See: https://bugreports.qt.io/browse/QTBUG-41061
+ * See: http://tools.ietf.org/html/rfc3986#section-4.2
+ */
redirectStr = m_reply->url().scheme() + ":" + redirectStr;
}
+ else if(redirectStr.startsWith("/"))
+ {
+ /*
+ * IF the URL begins with /, we need to process it as a relative URL
+ */
+ auto url = m_reply->url();
+ url.setPath(redirectStr, QUrl::TolerantMode);
+ redirectStr = url.toString();
+ }
/*
* Next, make sure the URL is parsed in tolerant mode. Qt doesn't parse the location header in tolerant mode, which causes issues.
diff --git a/api/logic/net/Download.h b/api/logic/net/Download.h
index 9d9d7743..2c436032 100644
--- a/api/logic/net/Download.h
+++ b/api/logic/net/Download.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 66325f71..4bc8fbc8 100644
--- a/api/logic/net/HttpMetaCache.cpp
+++ b/api/logic/net/HttpMetaCache.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 6e240723..c3248793 100644
--- a/api/logic/net/HttpMetaCache.h
+++ b/api/logic/net/HttpMetaCache.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e1d2881e..02b249a3 100644
--- a/api/logic/net/NetAction.h
+++ b/api/logic/net/NetAction.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 19ef742e..029d9e34 100644
--- a/api/logic/net/NetJob.cpp
+++ b/api/logic/net/NetJob.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 3051b943..480d8037 100644
--- a/api/logic/net/NetJob.h
+++ b/api/logic/net/NetJob.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/net/URLConstants.cpp b/api/logic/net/URLConstants.cpp
deleted file mode 100644
index 5d848d80..00000000
--- a/api/logic/net/URLConstants.cpp
+++ /dev/null
@@ -1,16 +0,0 @@
-#include "URLConstants.h"
-
-namespace URLConstants {
-
-QString getLegacyJarUrl(QString version)
-{
- return "http://" + AWS_DOWNLOAD_VERSIONS + getJarPath(version);
-}
-
-QString getJarPath(QString version)
-{
- return version + "/" + version + ".jar";
-}
-
-
-}
diff --git a/api/logic/net/URLConstants.h b/api/logic/net/URLConstants.h
deleted file mode 100644
index 22d128b2..00000000
--- a/api/logic/net/URLConstants.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QString>
-
-namespace URLConstants
-{
-const QString AWS_DOWNLOAD_VERSIONS("s3.amazonaws.com/Minecraft.Download/versions/");
-const QString RESOURCE_BASE("resources.download.minecraft.net/");
-const QString LIBRARY_BASE("libraries.minecraft.net/");
-//const QString SKINS_BASE("skins.minecraft.net/MinecraftSkins/");
-const QString SKINS_BASE("crafatar.com/skins/");
-const QString AUTH_BASE("authserver.mojang.com/");
-const QString FORGE_LEGACY_URL("http://files.minecraftforge.net/minecraftforge/json");
-const QString FORGE_GRADLE_URL("http://files.minecraftforge.net/maven/net/minecraftforge/forge/json");
-const QString MOJANG_STATUS_URL("http://status.mojang.com/check");
-const QString MOJANG_STATUS_NEWS_URL("http://status.mojang.com/news");
-const QString LITELOADER_URL("http://dl.liteloader.com/versions/versions.json");
-const QString IMGUR_BASE_URL("https://api.imgur.com/3/");
-const QString FMLLIBS_OUR_BASE_URL("http://files.multimc.org/fmllibs/");
-const QString FMLLIBS_FORGE_BASE_URL("http://files.minecraftforge.net/fmllibs/");
-const QString TRANSLATIONS_BASE_URL("http://files.multimc.org/translations/");
-
-QString getJarPath(QString version);
-QString getLegacyJarUrl(QString version);
-}
diff --git a/api/logic/news/NewsChecker.cpp b/api/logic/news/NewsChecker.cpp
index 1f5058ef..c66f49e1 100644
--- a/api/logic/news/NewsChecker.cpp
+++ b/api/logic/news/NewsChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 7af5bd5d..c473ecab 100644
--- a/api/logic/news/NewsChecker.h
+++ b/api/logic/news/NewsChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 82d654be..7eff657b 100644
--- a/api/logic/news/NewsEntry.cpp
+++ b/api/logic/news/NewsEntry.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 832766fb..0dbc70a5 100644
--- a/api/logic/news/NewsEntry.h
+++ b/api/logic/news/NewsEntry.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 3d32f597..1f195f00 100644
--- a/api/logic/screenshots/ImgurAlbumCreation.cpp
+++ b/api/logic/screenshots/ImgurAlbumCreation.cpp
@@ -6,13 +6,13 @@
#include <QUrl>
#include <QStringList>
-#include "net/URLConstants.h"
+#include "BuildConfig.h"
#include "Env.h"
#include <QDebug>
ImgurAlbumCreation::ImgurAlbumCreation(QList<ScreenshotPtr> screenshots) : NetAction(), m_screenshots(screenshots)
{
- m_url = URLConstants::IMGUR_BASE_URL + "album.json";
+ m_url = BuildConfig.IMGUR_BASE_URL + "album.json";
m_status = Job_NotStarted;
}
@@ -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 74165869..7e95d5ca 100644
--- a/api/logic/screenshots/ImgurUpload.cpp
+++ b/api/logic/screenshots/ImgurUpload.cpp
@@ -8,13 +8,13 @@
#include <QFile>
#include <QUrl>
-#include "net/URLConstants.h"
+#include "BuildConfig.h"
#include "Env.h"
#include <QDebug>
ImgurUpload::ImgurUpload(ScreenshotPtr shot) : NetAction(), m_shot(shot)
{
- m_url = URLConstants::IMGUR_BASE_URL + "upload.json";
+ m_url = BuildConfig.IMGUR_BASE_URL + "upload.json";
m_status = Job_NotStarted;
}
@@ -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 5431443e..6a3c801d 100644
--- a/api/logic/settings/INIFile.cpp
+++ b/api/logic/settings/INIFile.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -36,8 +36,10 @@ QString INIFile::unescape(QString orig)
{
if(c == 'n')
out += '\n';
- else if (c == 't')
+ else if(c == 't')
out += '\t';
+ else if(c == '#')
+ out += '#';
else
out += c;
prev = 0;
@@ -67,6 +69,8 @@ QString INIFile::escape(QString orig)
out += "\\t";
else if(c == '\\')
out += "\\\\";
+ else if(c == '#')
+ out += "\\#";
else
out += c;
}
@@ -120,7 +124,15 @@ bool INIFile::loadFile(QByteArray file)
{
QString &lineRaw = lines[i];
// Ignore comments.
- QString line = lineRaw.left(lineRaw.indexOf('#')).trimmed();
+ int commentIndex = 0;
+ QString line = lineRaw;
+ // Search for comments until no more escaped # are available
+ while((commentIndex = line.indexOf('#', commentIndex + 1)) != -1) {
+ if(commentIndex > 0 && line.at(commentIndex - 1) == '\\') {
+ continue;
+ }
+ line = line.left(lineRaw.indexOf('#')).trimmed();
+ }
int eqPos = line.indexOf('=');
if (eqPos == -1)
diff --git a/api/logic/settings/INIFile.h b/api/logic/settings/INIFile.h
index bdf7fd9a..9e8c68ea 100644
--- a/api/logic/settings/INIFile.h
+++ b/api/logic/settings/INIFile.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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_test.cpp b/api/logic/settings/INIFile_test.cpp
index 45f70973..08c2155e 100644
--- a/api/logic/settings/INIFile_test.cpp
+++ b/api/logic/settings/INIFile_test.cpp
@@ -26,6 +26,7 @@ slots:
QTest::newRow("Plain text") << "Lorem ipsum dolor sit amet.";
QTest::newRow("Escape sequences") << "Lorem\n\t\n\\n\\tAAZ\nipsum dolor\n\nsit amet.";
QTest::newRow("Escape sequences 2") << "\"\n\n\"";
+ QTest::newRow("Hashtags") << "some data#something";
}
void test_Escape()
{
@@ -40,7 +41,7 @@ slots:
void test_SaveLoad()
{
QString a = "a";
- QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\";
+ QString b = "a\nb\t\n\\\\\\C:\\Program files\\terrible\\name\\of something\\#thisIsNotAComment";
QString filename = "test_SaveLoad.ini";
// save
diff --git a/api/logic/settings/INISettingsObject.cpp b/api/logic/settings/INISettingsObject.cpp
index 43791c24..12513403 100644
--- a/api/logic/settings/INISettingsObject.cpp
+++ b/api/logic/settings/INISettingsObject.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 3fc09593..313f1512 100644
--- a/api/logic/settings/INISettingsObject.h
+++ b/api/logic/settings/INISettingsObject.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e0e407bd..4396a381 100644
--- a/api/logic/settings/OverrideSetting.cpp
+++ b/api/logic/settings/OverrideSetting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 db28e2fc..9f0c98b5 100644
--- a/api/logic/settings/OverrideSetting.h
+++ b/api/logic/settings/OverrideSetting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 58522385..8f93b251 100644
--- a/api/logic/settings/PassthroughSetting.cpp
+++ b/api/logic/settings/PassthroughSetting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 7999be97..22008f83 100644
--- a/api/logic/settings/PassthroughSetting.h
+++ b/api/logic/settings/PassthroughSetting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 8bb42f5e..cfe5a7f9 100644
--- a/api/logic/settings/Setting.cpp
+++ b/api/logic/settings/Setting.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 0e9224c5..a31193ac 100644
--- a/api/logic/settings/Setting.h
+++ b/api/logic/settings/Setting.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 d1851415..8a0bc045 100644
--- a/api/logic/settings/SettingsObject.cpp
+++ b/api/logic/settings/SettingsObject.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 10a34f81..3ebdebdf 100644
--- a/api/logic/settings/SettingsObject.h
+++ b/api/logic/settings/SettingsObject.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 ffadcc74..38fc2163 100644
--- a/api/logic/status/StatusChecker.cpp
+++ b/api/logic/status/StatusChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,12 +15,12 @@
#include "StatusChecker.h"
-#include <net/URLConstants.h>
-
#include <QByteArray>
#include <QDebug>
+#include <BuildConfig.h>
+
StatusChecker::StatusChecker()
{
@@ -43,7 +43,7 @@ void StatusChecker::reloadStatus()
// qDebug() << "Reloading status.";
NetJob* job = new NetJob("Status JSON");
- job->addNetAction(Net::Download::makeByteArray(URLConstants::MOJANG_STATUS_URL, &dataSink));
+ job->addNetAction(Net::Download::makeByteArray(BuildConfig.MOJANG_STATUS_URL, &dataSink));
QObject::connect(job, &NetJob::succeeded, this, &StatusChecker::statusDownloadFinished);
QObject::connect(job, &NetJob::failed, this, &StatusChecker::statusDownloadFailed);
m_statusNetJob.reset(job);
diff --git a/api/logic/status/StatusChecker.h b/api/logic/status/StatusChecker.h
index 51a86910..e9961aff 100644
--- a/api/logic/status/StatusChecker.h
+++ b/api/logic/status/StatusChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 f7f2d491..d0ac7569 100644
--- a/api/logic/tasks/Task.cpp
+++ b/api/logic/tasks/Task.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,23 +39,49 @@ void Task::setProgress(qint64 current, qint64 total)
void Task::start()
{
- m_running = true;
+ switch(m_state)
+ {
+ case State::Inactive:
+ {
+ qDebug() << "Task" << describe() << "starting for the first time";
+ break;
+ }
+ case State::AbortedByUser:
+ {
+ qDebug() << "Task" << describe() << "restarting for after being aborted by user";
+ break;
+ }
+ case State::Failed:
+ {
+ qDebug() << "Task" << describe() << "restarting for after failing at first";
+ break;
+ }
+ case State::Succeeded:
+ {
+ qDebug() << "Task" << describe() << "restarting for after succeeding at first";
+ break;
+ }
+ case State::Running:
+ {
+ qWarning() << "MultiMC tried to start task" << describe() << "while it was already running!";
+ return;
+ }
+ }
+ // NOTE: only fall thorugh to here in end states
+ m_state = State::Running;
emit started();
- qDebug() << "Task" << describe() << "started";
executeTask();
}
void Task::emitFailed(QString reason)
{
// Don't fail twice.
- if (!m_running)
+ if (!isRunning())
{
qCritical() << "Task" << describe() << "failed while not running!!!!: " << reason;
return;
}
- m_running = false;
- m_finished = true;
- m_succeeded = false;
+ m_state = State::Failed;
m_failReason = reason;
qCritical() << "Task" << describe() << "failed: " << reason;
emit failed(reason);
@@ -65,14 +91,12 @@ void Task::emitFailed(QString reason)
void Task::emitAborted()
{
// Don't abort twice.
- if (!m_running)
+ if (!isRunning())
{
qCritical() << "Task" << describe() << "aborted while not running!!!!";
return;
}
- m_running = false;
- m_finished = true;
- m_succeeded = false;
+ m_state = State::AbortedByUser;
m_failReason = "Aborted.";
qDebug() << "Task" << describe() << "aborted.";
emit failed(m_failReason);
@@ -82,14 +106,12 @@ void Task::emitAborted()
void Task::emitSucceeded()
{
// Don't succeed twice.
- if (!m_running)
+ if (!isRunning())
{
qCritical() << "Task" << describe() << "succeeded while not running!!!!";
return;
}
- m_running = false;
- m_finished = true;
- m_succeeded = true;
+ m_state = State::Succeeded;
qDebug() << "Task" << describe() << "succeeded";
emit succeeded();
emit finished();
@@ -116,17 +138,17 @@ QString Task::describe()
bool Task::isRunning() const
{
- return m_running;
+ return m_state == State::Running;
}
bool Task::isFinished() const
{
- return m_finished;
+ return m_state != State::Running && m_state != State::Inactive;
}
bool Task::wasSuccessful() const
{
- return m_succeeded;
+ return m_state == State::Succeeded;
}
QString Task::failReason() const
diff --git a/api/logic/tasks/Task.h b/api/logic/tasks/Task.h
index 58dbc4ca..7ed7086c 100644
--- a/api/logic/tasks/Task.h
+++ b/api/logic/tasks/Task.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -25,6 +25,16 @@ class MULTIMC_LOGIC_EXPORT Task : public QObject
{
Q_OBJECT
public:
+ enum class State
+ {
+ Inactive,
+ Running,
+ Succeeded,
+ Failed,
+ AbortedByUser
+ };
+
+public:
explicit Task(QObject *parent = 0);
virtual ~Task() {};
@@ -88,9 +98,7 @@ public slots:
void setProgress(qint64 current, qint64 total);
private:
- bool m_running = false;
- bool m_finished = false;
- bool m_succeeded = false;
+ State m_state = State::Inactive;
QStringList m_Warnings;
QString m_failReason = "";
QString m_status;
diff --git a/api/logic/tools/BaseProfiler.cpp b/api/logic/tools/BaseProfiler.cpp
index c7d83549..300d1a73 100644
--- a/api/logic/tools/BaseProfiler.cpp
+++ b/api/logic/tools/BaseProfiler.cpp
@@ -1,4 +1,5 @@
#include "BaseProfiler.h"
+#include "QObjectPtr.h"
#include <QProcess>
@@ -7,7 +8,7 @@ BaseProfiler::BaseProfiler(SettingsObjectPtr settings, InstancePtr instance, QOb
{
}
-void BaseProfiler::beginProfiling(std::shared_ptr<LaunchTask> process)
+void BaseProfiler::beginProfiling(shared_qobject_ptr<LaunchTask> process)
{
beginProfilingImpl(process);
}
diff --git a/api/logic/tools/BaseProfiler.h b/api/logic/tools/BaseProfiler.h
index f3e1ce3d..da817f52 100644
--- a/api/logic/tools/BaseProfiler.h
+++ b/api/logic/tools/BaseProfiler.h
@@ -1,6 +1,7 @@
#pragma once
#include "BaseExternalTool.h"
+#include "QObjectPtr.h"
#include "multimc_logic_export.h"
@@ -17,13 +18,13 @@ public:
public
slots:
- void beginProfiling(std::shared_ptr<LaunchTask> process);
+ void beginProfiling(shared_qobject_ptr<LaunchTask> process);
void abortProfiling();
protected:
QProcess *m_profilerProcess;
- virtual void beginProfilingImpl(std::shared_ptr<LaunchTask> process) = 0;
+ virtual void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process) = 0;
virtual void abortProfilingImpl();
signals:
diff --git a/api/logic/tools/JProfiler.cpp b/api/logic/tools/JProfiler.cpp
index b50322fe..1dc0d109 100644
--- a/api/logic/tools/JProfiler.cpp
+++ b/api/logic/tools/JProfiler.cpp
@@ -17,7 +17,7 @@ private slots:
void profilerFinished(int exit, QProcess::ExitStatus status);
protected:
- void beginProfilingImpl(std::shared_ptr<LaunchTask> process);
+ void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process);
private:
int listeningPort = 0;
@@ -47,7 +47,7 @@ void JProfiler::profilerFinished(int exit, QProcess::ExitStatus status)
}
}
-void JProfiler::beginProfilingImpl(std::shared_ptr<LaunchTask> process)
+void JProfiler::beginProfilingImpl(shared_qobject_ptr<LaunchTask> process)
{
listeningPort = globalSettings->get("JProfilerPort").toInt();
QProcess *profiler = new QProcess(this);
diff --git a/api/logic/tools/JVisualVM.cpp b/api/logic/tools/JVisualVM.cpp
index 10886857..b1acc3c0 100644
--- a/api/logic/tools/JVisualVM.cpp
+++ b/api/logic/tools/JVisualVM.cpp
@@ -18,7 +18,7 @@ private slots:
void profilerFinished(int exit, QProcess::ExitStatus status);
protected:
- void beginProfilingImpl(std::shared_ptr<LaunchTask> process);
+ void beginProfilingImpl(shared_qobject_ptr<LaunchTask> process);
};
@@ -45,7 +45,7 @@ void JVisualVM::profilerFinished(int exit, QProcess::ExitStatus status)
}
}
-void JVisualVM::beginProfilingImpl(std::shared_ptr<LaunchTask> process)
+void JVisualVM::beginProfilingImpl(shared_qobject_ptr<LaunchTask> process)
{
QProcess *profiler = new QProcess(this);
QStringList profilerArgs =
diff --git a/api/logic/translations/POTranslator.cpp b/api/logic/translations/POTranslator.cpp
new file mode 100644
index 00000000..1ffcb9a4
--- /dev/null
+++ b/api/logic/translations/POTranslator.cpp
@@ -0,0 +1,373 @@
+#include "POTranslator.h"
+
+#include <QDebug>
+#include "FileSystem.h"
+
+struct POEntry
+{
+ QString text;
+ bool fuzzy;
+};
+
+struct POTranslatorPrivate
+{
+ QString filename;
+ QHash<QByteArray, POEntry> mapping;
+ QHash<QByteArray, POEntry> mapping_disambiguatrion;
+ bool loaded = false;
+
+ void reload();
+};
+
+class ParserArray : public QByteArray
+{
+public:
+ ParserArray(const QByteArray &in) : QByteArray(in)
+ {
+ }
+ bool chomp(const char * data, int length)
+ {
+ if(startsWith(data))
+ {
+ remove(0, length);
+ return true;
+ }
+ return false;
+ }
+ bool chompString(QByteArray & appendHere)
+ {
+ QByteArray msg;
+ bool escape = false;
+ if(size() < 2)
+ {
+ qDebug() << "String fragment is too short";
+ return false;
+ }
+ if(!startsWith('"'))
+ {
+ qDebug() << "String fragment does not start with \"";
+ return false;
+ }
+ if(!endsWith('"'))
+ {
+ qDebug() << "String fragment does not end with \", instead, there is" << at(size() - 1);
+ return false;
+ }
+ for(int i = 1; i < size() - 1; i++)
+ {
+ char c = operator[](i);
+ if(escape)
+ {
+ switch(c)
+ {
+ case 'r':
+ msg += '\r';
+ break;
+ case 'n':
+ msg += '\n';
+ break;
+ case 't':
+ msg += '\t';
+ break;
+ case 'v':
+ msg += '\v';
+ break;
+ case 'a':
+ msg += '\a';
+ break;
+ case 'b':
+ msg += '\b';
+ break;
+ case 'f':
+ msg += '\f';
+ break;
+ case '"':
+ msg += '"';
+ break;
+ case '\\':
+ msg.append('\\');
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ {
+ int octal_start = i;
+ while ((c = operator[](i)) >= '0' && c <= '7')
+ {
+ i++;
+ if (i == length() - 1)
+ {
+ qDebug() << "Something went bad while parsing an octal escape string...";
+ return false;
+ }
+ }
+ msg += mid(octal_start, i - octal_start).toUInt(0, 8);
+ break;
+ }
+ case 'x':
+ {
+ // chomp the 'x'
+ i++;
+ int hex_start = i;
+ while (isxdigit(operator[](i)))
+ {
+ i++;
+ if (i == length() - 1)
+ {
+ qDebug() << "Something went bad while parsing a hex escape string...";
+ return false;
+ }
+ }
+ msg += mid(hex_start, i - hex_start).toUInt(0, 16);
+ break;
+ }
+ default:
+ {
+ qDebug() << "Invalid escape sequence character:" << c;
+ return false;
+ }
+ }
+ escape = false;
+ }
+ else if(c == '\\')
+ {
+ escape = true;
+ }
+ else
+ {
+ msg += c;
+ }
+ }
+ if(escape)
+ {
+ qDebug() << "Unterminated escape sequence...";
+ return false;
+ }
+ appendHere += msg;
+ return true;
+ }
+};
+
+void POTranslatorPrivate::reload()
+{
+ QFile file(filename);
+ if(!file.open(QFile::OpenMode::enum_type::ReadOnly | QFile::OpenMode::enum_type::Text))
+ {
+ qDebug() << "Failed to open PO file:" << filename;
+ return;
+ }
+
+ QByteArray context;
+ QByteArray disambiguation;
+ QByteArray id;
+ QByteArray str;
+ bool fuzzy = false;
+ bool nextFuzzy = false;
+
+ enum class Mode
+ {
+ First,
+ MessageContext,
+ MessageId,
+ MessageString
+ } mode = Mode::First;
+
+ int lineNumber = 0;
+ QHash<QByteArray, POEntry> newMapping;
+ QHash<QByteArray, POEntry> newMapping_disambiguation;
+ auto endEntry = [&]() {
+ auto strStr = QString::fromUtf8(str);
+ // NOTE: PO header has empty id. We skip it.
+ if(!id.isEmpty())
+ {
+ auto normalKey = context + "|" + id;
+ newMapping.insert(normalKey, {strStr, fuzzy});
+ if(!disambiguation.isEmpty())
+ {
+ auto disambiguationKey = context + "|" + id + "@" + disambiguation;
+ newMapping_disambiguation.insert(disambiguationKey, {strStr, fuzzy});
+ }
+ }
+ context.clear();
+ disambiguation.clear();
+ id.clear();
+ str.clear();
+ fuzzy = nextFuzzy;
+ nextFuzzy = false;
+ };
+ while (!file.atEnd())
+ {
+ ParserArray line = file.readLine();
+ if(line.endsWith('\n'))
+ {
+ line.resize(line.size() - 1);
+ }
+ if(line.endsWith('\r'))
+ {
+ line.resize(line.size() - 1);
+ }
+
+ if(!line.size())
+ {
+ // NIL
+ }
+ else if(line[0] == '#')
+ {
+ if(line.contains(", fuzzy"))
+ {
+ nextFuzzy = true;
+ }
+ }
+ else if(line.startsWith('"'))
+ {
+ QByteArray temp;
+ QByteArray *out = &temp;
+
+ switch(mode)
+ {
+ case Mode::First:
+ qDebug() << "Unexpected escaped string during initial state... line:" << lineNumber;
+ return;
+ case Mode::MessageString:
+ out = &str;
+ break;
+ case Mode::MessageContext:
+ out = &context;
+ break;
+ case Mode::MessageId:
+ out = &id;
+ break;
+ }
+ if(!line.chompString(*out))
+ {
+ qDebug() << "Badly formatted string on line:" << lineNumber;
+ return;
+ }
+ }
+ else if(line.chomp("msgctxt ", 8))
+ {
+ switch(mode)
+ {
+ case Mode::First:
+ break;
+ case Mode::MessageString:
+ endEntry();
+ break;
+ case Mode::MessageContext:
+ case Mode::MessageId:
+ qDebug() << "Unexpected msgctxt line:" << lineNumber;
+ return;
+ }
+ if(line.chompString(context))
+ {
+ auto parts = context.split('|');
+ context = parts[0];
+ if(parts.size() > 1 && !parts[1].isEmpty())
+ {
+ disambiguation = parts[1];
+ }
+ mode = Mode::MessageContext;
+ }
+ }
+ else if (line.chomp("msgid ", 6))
+ {
+ switch(mode)
+ {
+ case Mode::MessageContext:
+ case Mode::First:
+ break;
+ case Mode::MessageString:
+ endEntry();
+ break;
+ case Mode::MessageId:
+ qDebug() << "Unexpected msgid line:" << lineNumber;
+ return;
+ }
+ if(line.chompString(id))
+ {
+ mode = Mode::MessageId;
+ }
+ }
+ else if (line.chomp("msgstr ", 7))
+ {
+ switch(mode)
+ {
+ case Mode::First:
+ case Mode::MessageString:
+ case Mode::MessageContext:
+ qDebug() << "Unexpected msgstr line:" << lineNumber;
+ return;
+ case Mode::MessageId:
+ break;
+ }
+ if(line.chompString(str))
+ {
+ mode = Mode::MessageString;
+ }
+ }
+ else
+ {
+ qDebug() << "I did not understand line: " << lineNumber << ":" << QString::fromUtf8(line);
+ }
+ lineNumber++;
+ }
+ endEntry();
+ mapping = std::move(newMapping);
+ mapping_disambiguatrion = std::move(newMapping_disambiguation);
+ loaded = true;
+}
+
+POTranslator::POTranslator(const QString& filename, QObject* parent) : QTranslator(parent)
+{
+ d = new POTranslatorPrivate;
+ d->filename = filename;
+ d->reload();
+}
+
+QString POTranslator::translate(const char* context, const char* sourceText, const char* disambiguation, int n) const
+{
+ if(disambiguation)
+ {
+ auto disambiguationKey = QByteArray(context) + "|" + QByteArray(sourceText) + "@" + QByteArray(disambiguation);
+ auto iter = d->mapping_disambiguatrion.find(disambiguationKey);
+ if(iter != d->mapping_disambiguatrion.end())
+ {
+ auto & entry = *iter;
+ if(entry.text.isEmpty())
+ {
+ qDebug() << "Translation entry has no content:" << disambiguationKey;
+ }
+ if(entry.fuzzy)
+ {
+ qDebug() << "Translation entry is fuzzy:" << disambiguationKey << "->" << entry.text;
+ }
+ return entry.text;
+ }
+ }
+ auto key = QByteArray(context) + "|" + QByteArray(sourceText);
+ auto iter = d->mapping.find(key);
+ if(iter != d->mapping.end())
+ {
+ auto & entry = *iter;
+ if(entry.text.isEmpty())
+ {
+ qDebug() << "Translation entry has no content:" << key;
+ }
+ if(entry.fuzzy)
+ {
+ qDebug() << "Translation entry is fuzzy:" << key << "->" << entry.text;
+ }
+ return entry.text;
+ }
+ return QString();
+}
+
+bool POTranslator::isEmpty() const
+{
+ return !d->loaded;
+}
diff --git a/api/logic/translations/POTranslator.h b/api/logic/translations/POTranslator.h
new file mode 100644
index 00000000..6d518560
--- /dev/null
+++ b/api/logic/translations/POTranslator.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <QTranslator>
+
+struct POTranslatorPrivate;
+
+class POTranslator : public QTranslator
+{
+ Q_OBJECT
+public:
+ explicit POTranslator(const QString& filename, QObject * parent = nullptr);
+ QString translate(const char * context, const char * sourceText, const char * disambiguation, int n) const override;
+ bool isEmpty() const override;
+private:
+ POTranslatorPrivate * d;
+};
diff --git a/api/logic/translations/TranslationsModel.cpp b/api/logic/translations/TranslationsModel.cpp
index 185cc8c4..29a952b0 100644
--- a/api/logic/translations/TranslationsModel.cpp
+++ b/api/logic/translations/TranslationsModel.cpp
@@ -8,24 +8,113 @@
#include <QDebug>
#include <FileSystem.h>
#include <net/NetJob.h>
+#include <net/ChecksumValidator.h>
#include <Env.h>
-#include <net/URLConstants.h>
+#include <BuildConfig.h>
+#include "Json.h"
-const static QLatin1Literal defaultLangCode("en");
+#include "POTranslator.h"
+
+const static QLatin1Literal defaultLangCode("en_US");
+
+enum class FileType
+{
+ NONE,
+ QM,
+ PO
+};
struct Language
{
+ Language()
+ {
+ updated = true;
+ }
+ Language(const QString & _key)
+ {
+ key = _key;
+ locale = QLocale(key);
+ updated = (key == defaultLangCode);
+ }
+
+ float percentTranslated() const
+ {
+ if (total == 0)
+ {
+ return 100.0f;
+ }
+ return 100.0f * float(translated) / float(total);
+ }
+
+ void setTranslationStats(unsigned _translated, unsigned _untranslated, unsigned _fuzzy)
+ {
+ translated = _translated;
+ untranslated = _untranslated;
+ fuzzy = _fuzzy;
+ total = translated + untranslated + fuzzy;
+ }
+
+ bool isOfSameNameAs(const Language& other) const
+ {
+ return key == other.key;
+ }
+
+ bool isIdenticalTo(const Language& other) const
+ {
+ return
+ (
+ key == other.key &&
+ file_name == other.file_name &&
+ file_size == other.file_size &&
+ file_sha1 == other.file_sha1 &&
+ translated == other.translated &&
+ fuzzy == other.fuzzy &&
+ total == other.fuzzy &&
+ localFileType == other.localFileType
+ );
+ }
+
+ Language & apply(Language & other)
+ {
+ if(!isOfSameNameAs(other))
+ {
+ return *this;
+ }
+ file_name = other.file_name;
+ file_size = other.file_size;
+ file_sha1 = other.file_sha1;
+ translated = other.translated;
+ fuzzy = other.fuzzy;
+ total = other.total;
+ localFileType = other.localFileType;
+ return *this;
+ }
+
QString key;
QLocale locale;
bool updated;
+
+ QString file_name = QString();
+ std::size_t file_size = 0;
+ QString file_sha1 = QString();
+
+ unsigned translated = 0;
+ unsigned untranslated = 0;
+ unsigned fuzzy = 0;
+ unsigned total = 0;
+
+ FileType localFileType = FileType::NONE;
};
+
+
struct TranslationsModel::Private
{
QDir m_dir;
// initial state is just english
- QVector<Language> m_languages = {{defaultLangCode, QLocale(defaultLangCode), false}};
+ QVector<Language> m_languages = {Language (defaultLangCode)};
+
QString m_selectedLanguage = defaultLangCode;
std::unique_ptr<QTranslator> m_qt_translator;
std::unique_ptr<QTranslator> m_app_translator;
@@ -35,45 +124,285 @@ struct TranslationsModel::Private
NetJobPtr m_dl_job;
NetJobPtr m_index_job;
QString m_nextDownload;
+
+ std::unique_ptr<POTranslator> m_po_translator;
+ QFileSystemWatcher *watcher;
};
TranslationsModel::TranslationsModel(QString path, QObject* parent): QAbstractListModel(parent)
{
d.reset(new Private);
d->m_dir.setPath(path);
- loadLocalIndex();
+ FS::ensureFolderPathExists(path);
+ reloadLocalFiles();
+
+ d->watcher = new QFileSystemWatcher(this);
+ connect(d->watcher, &QFileSystemWatcher::directoryChanged, this, &TranslationsModel::translationDirChanged);
+ d->watcher->addPath(d->m_dir.canonicalPath());
}
TranslationsModel::~TranslationsModel()
{
}
+void TranslationsModel::translationDirChanged(const QString& path)
+{
+ qDebug() << "Dir changed:" << path;
+ reloadLocalFiles();
+ selectLanguage(selectedLanguage());
+}
+
+void TranslationsModel::indexReceived()
+{
+ qDebug() << "Got translations index!";
+ d->m_index_job.reset();
+ if(d->m_selectedLanguage != defaultLangCode)
+ {
+ downloadTranslation(d->m_selectedLanguage);
+ }
+}
+
+namespace {
+void readIndex(const QString & path, QMap<QString, Language>& languages)
+{
+ QByteArray data;
+ try
+ {
+ data = FS::read(path);
+ }
+ catch (const Exception &e)
+ {
+ qCritical() << "Translations Download Failed: index file not readable";
+ return;
+ }
+
+ int index = 1;
+ try
+ {
+ auto toplevel_doc = Json::requireDocument(data);
+ auto doc = Json::requireObject(toplevel_doc);
+ auto file_type = Json::requireString(doc, "file_type");
+ if(file_type != "MMC-TRANSLATION-INDEX")
+ {
+ qCritical() << "Translations Download Failed: index file is of unknown file type" << file_type;
+ return;
+ }
+ auto version = Json::requireInteger(doc, "version");
+ if(version > 2)
+ {
+ qCritical() << "Translations Download Failed: index file is of unknown format version" << file_type;
+ return;
+ }
+ auto langObjs = Json::requireObject(doc, "languages");
+ for(auto iter = langObjs.begin(); iter != langObjs.end(); iter++)
+ {
+ Language lang(iter.key());
+
+ auto langObj = Json::requireObject(iter.value());
+ lang.setTranslationStats(
+ Json::ensureInteger(langObj, "translated", 0),
+ Json::ensureInteger(langObj, "untranslated", 0),
+ Json::ensureInteger(langObj, "fuzzy", 0)
+ );
+ lang.file_name = Json::requireString(langObj, "file");
+ lang.file_sha1 = Json::requireString(langObj, "sha1");
+ lang.file_size = Json::requireInteger(langObj, "size");
+
+ languages.insert(lang.key, lang);
+ index++;
+ }
+ }
+ catch (Json::JsonException & e)
+ {
+ qCritical() << "Translations Download Failed: index file could not be parsed as json";
+ }
+}
+}
+
+void TranslationsModel::reloadLocalFiles()
+{
+ QMap<QString, Language> languages = {{defaultLangCode, Language(defaultLangCode)}};
+
+ readIndex(d->m_dir.absoluteFilePath("index_v2.json"), languages);
+ auto entries = d->m_dir.entryInfoList({"mmc_*.qm", "*.po"}, QDir::Files | QDir::NoDotAndDotDot);
+ for(auto & entry: entries)
+ {
+ auto completeSuffix = entry.completeSuffix();
+ QString langCode;
+ FileType fileType = FileType::NONE;
+ if(completeSuffix == "qm")
+ {
+ langCode = entry.baseName().remove(0,4);
+ fileType = FileType::QM;
+ }
+ else if(completeSuffix == "po")
+ {
+ langCode = entry.baseName();
+ fileType = FileType::PO;
+ }
+ else
+ {
+ continue;
+ }
+
+ auto langIter = languages.find(langCode);
+ if(langIter != languages.end())
+ {
+ auto & language = *langIter;
+ if(int(fileType) > int(language.localFileType))
+ {
+ language.localFileType = fileType;
+ }
+ }
+ else
+ {
+ if(fileType == FileType::PO)
+ {
+ Language localFound(langCode);
+ localFound.localFileType = FileType::PO;
+ languages.insert(langCode, localFound);
+ }
+ }
+ }
+
+ // changed and removed languages
+ for(auto iter = d->m_languages.begin(); iter != d->m_languages.end();)
+ {
+ auto &language = *iter;
+ auto row = iter - d->m_languages.begin();
+
+ auto updatedLanguageIter = languages.find(language.key);
+ if(updatedLanguageIter != languages.end())
+ {
+ if(language.isIdenticalTo(*updatedLanguageIter))
+ {
+ languages.remove(language.key);
+ }
+ else
+ {
+ language.apply(*updatedLanguageIter);
+ emit dataChanged(index(row), index(row));
+ languages.remove(language.key);
+ }
+ iter++;
+ }
+ else
+ {
+ beginRemoveRows(QModelIndex(), row, row);
+ iter = d->m_languages.erase(iter);
+ endRemoveRows();
+ }
+ }
+ // added languages
+ if(languages.isEmpty())
+ {
+ return;
+ }
+ beginInsertRows(QModelIndex(), 0, d->m_languages.size() + languages.size() - 1);
+ for(auto & language: languages)
+ {
+ d->m_languages.append(language);
+ }
+ std::sort(d->m_languages.begin(), d->m_languages.end(), [](const Language& a, const Language& b) {
+ return a.key.compare(b.key) < 0;
+ });
+ endInsertRows();
+}
+
+namespace {
+enum class Column
+{
+ Language,
+ Completeness
+};
+}
+
+
QVariant TranslationsModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
int row = index.row();
+ auto column = static_cast<Column>(index.column());
if (row < 0 || row >= d->m_languages.size())
return QVariant();
+ auto & lang = d->m_languages[row];
switch (role)
{
case Qt::DisplayRole:
- return d->m_languages[row].locale.nativeLanguageName();
+ {
+ switch(column)
+ {
+ case Column::Language:
+ {
+ return lang.locale.nativeLanguageName();
+ }
+ case Column::Completeness:
+ {
+ QString text;
+ text.sprintf("%3.1f %%", lang.percentTranslated());
+ return text;
+ }
+ }
+ }
+ case Qt::ToolTipRole:
+ {
+ return tr("%1:\n%2 translated\n%3 fuzzy\n%4 total").arg(lang.key, QString::number(lang.translated), QString::number(lang.fuzzy), QString::number(lang.total));
+ }
case Qt::UserRole:
- return d->m_languages[row].key;
+ return lang.key;
default:
return QVariant();
}
}
+QVariant TranslationsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ auto column = static_cast<Column>(section);
+ if(role == Qt::DisplayRole)
+ {
+ switch(column)
+ {
+ case Column::Language:
+ {
+ return tr("Language");
+ }
+ case Column::Completeness:
+ {
+ return tr("Completeness");
+ }
+ }
+ }
+ else if(role == Qt::ToolTipRole)
+ {
+ switch(column)
+ {
+ case Column::Language:
+ {
+ return tr("The native language name.");
+ }
+ case Column::Completeness:
+ {
+ return tr("Completeness is the percentage of fully translated strings, not counting automatically guessed ones.");
+ }
+ }
+ }
+ return QAbstractListModel::headerData(section, orientation, role);
+}
+
int TranslationsModel::rowCount(const QModelIndex& parent) const
{
return d->m_languages.size();
}
+int TranslationsModel::columnCount(const QModelIndex& parent) const
+{
+ return 2;
+}
+
Language * TranslationsModel::findLanguage(const QString& key)
{
auto found = std::find_if(d->m_languages.begin(), d->m_languages.end(), [&](Language & lang)
@@ -121,7 +450,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 = QLocale(langCode);
QLocale::setDefault(locale);
// if it's the default UI language, finish
@@ -153,18 +482,48 @@ bool TranslationsModel::selectLanguage(QString key)
d->m_qt_translator.reset();
}
- d->m_app_translator.reset(new QTranslator());
- if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path()))
+ if(langPtr->localFileType == FileType::PO)
{
qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "...";
- if (!QCoreApplication::installTranslator(d->m_app_translator.get()))
+ auto poTranslator = new POTranslator(FS::PathCombine(d->m_dir.path(), langCode + ".po"));
+ if(!poTranslator->isEmpty())
+ {
+ if (!QCoreApplication::installTranslator(poTranslator))
+ {
+ delete poTranslator;
+ qCritical() << "Installing Application Language File failed.";
+ }
+ else
+ {
+ d->m_app_translator.reset(poTranslator);
+ successful = true;
+ }
+ }
+ else
{
qCritical() << "Loading Application Language File failed.";
d->m_app_translator.reset();
}
+ }
+ else if(langPtr->localFileType == FileType::QM)
+ {
+ d->m_app_translator.reset(new QTranslator());
+ if (d->m_app_translator->load("mmc_" + langCode, d->m_dir.path()))
+ {
+ qDebug() << "Loading Application Language File for" << langCode.toLocal8Bit().constData() << "...";
+ if (!QCoreApplication::installTranslator(d->m_app_translator.get()))
+ {
+ qCritical() << "Installing Application Language File failed.";
+ d->m_app_translator.reset();
+ }
+ else
+ {
+ successful = true;
+ }
+ }
else
{
- successful = true;
+ d->m_app_translator.reset();
}
}
else
@@ -199,55 +558,15 @@ void TranslationsModel::downloadIndex()
}
qDebug() << "Downloading Translations Index...";
d->m_index_job.reset(new NetJob("Translations Index"));
- MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index");
- d->m_index_task = Net::Download::makeCached(QUrl("http://files.multimc.org/translations/index"), entry);
+ MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "index_v2.json");
+ entry->setStale(true);
+ d->m_index_task = Net::Download::makeCached(QUrl("https://files.multimc.org/translations/index_v2.json"), entry);
d->m_index_job->addNetAction(d->m_index_task);
connect(d->m_index_job.get(), &NetJob::failed, this, &TranslationsModel::indexFailed);
- connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexRecieved);
+ connect(d->m_index_job.get(), &NetJob::succeeded, this, &TranslationsModel::indexReceived);
d->m_index_job->start();
}
-void TranslationsModel::indexRecieved()
-{
- qDebug() << "Got translations index!";
- d->m_index_job.reset();
- loadLocalIndex();
- if(d->m_selectedLanguage != defaultLangCode)
- {
- downloadTranslation(d->m_selectedLanguage);
- }
-}
-
-void TranslationsModel::loadLocalIndex()
-{
- QByteArray data;
- try
- {
- data = FS::read(d->m_dir.absoluteFilePath("index"));
- }
- catch (const Exception &e)
- {
- qCritical() << "Translations Download Failed: index file not readable";
- return;
- }
- QVector<Language> languages;
- QList<QByteArray> lines = data.split('\n');
- // add the default english.
- languages.append({defaultLangCode, QLocale(defaultLangCode), true});
- for (const auto line : lines)
- {
- if(!line.isEmpty())
- {
- auto str = QString::fromLatin1(line);
- str.remove(".qm");
- languages.append({str, QLocale(str), false});
- }
- }
- beginResetModel();
- d->m_languages.swap(languages);
- endResetModel();
-}
-
void TranslationsModel::updateLanguage(QString key)
{
if(key == defaultLangCode)
@@ -274,13 +593,28 @@ void TranslationsModel::downloadTranslation(QString key)
d->m_nextDownload = key;
return;
}
+ auto lang = findLanguage(key);
+ if(!lang)
+ {
+ qWarning() << "Will not download an unknown translation" << key;
+ return;
+ }
+
d->m_downloadingTranslation = key;
MetaEntryPtr entry = ENV.metacache()->resolveEntry("translations", "mmc_" + key + ".qm");
entry->setStale(true);
+
+ auto dl = Net::Download::makeCached(QUrl(BuildConfig.TRANSLATIONS_BASE_URL + lang->file_name), entry);
+ auto rawHash = QByteArray::fromHex(lang->file_sha1.toLatin1());
+ dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawHash));
+ dl->m_total_progress = lang->file_size;
+
d->m_dl_job.reset(new NetJob("Translation for " + key));
- d->m_dl_job->addNetAction(Net::Download::makeCached(QUrl(URLConstants::TRANSLATIONS_BASE_URL + key + ".qm"), entry));
+ d->m_dl_job->addNetAction(dl);
+
connect(d->m_dl_job.get(), &NetJob::succeeded, this, &TranslationsModel::dlGood);
connect(d->m_dl_job.get(), &NetJob::failed, this, &TranslationsModel::dlFailed);
+
d->m_dl_job->start();
}
diff --git a/api/logic/translations/TranslationsModel.h b/api/logic/translations/TranslationsModel.h
index 8a9298d4..17e5b124 100644
--- a/api/logic/translations/TranslationsModel.h
+++ b/api/logic/translations/TranslationsModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -28,8 +28,10 @@ public:
explicit TranslationsModel(QString path, QObject *parent = 0);
virtual ~TranslationsModel();
- virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
- virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex & parent) const override;
bool selectLanguage(QString key);
void updateLanguage(QString key);
@@ -40,7 +42,7 @@ public:
private:
Language *findLanguage(const QString & key);
- void loadLocalIndex();
+ void reloadLocalFiles();
void downloadTranslation(QString key);
void downloadNext();
@@ -50,10 +52,12 @@ private:
TranslationsModel &operator=(const TranslationsModel &) = delete;
private slots:
- void indexRecieved();
+ void indexReceived();
void indexFailed(QString reason);
void dlFailed(QString reason);
void dlGood();
+ void translationDirChanged(const QString &path);
+
private: /* data */
struct Private;
diff --git a/api/logic/updater/DownloadTask.cpp b/api/logic/updater/DownloadTask.cpp
index cb92018d..20b26ebb 100644
--- a/api/logic/updater/DownloadTask.cpp
+++ b/api/logic/updater/DownloadTask.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -131,7 +131,14 @@ void DownloadTask::processDownloadedVersionInfo()
QObject::connect(netJob.get(), &NetJob::progress, this, &DownloadTask::fileDownloadProgressChanged);
QObject::connect(netJob.get(), &NetJob::failed, this, &DownloadTask::fileDownloadFailed);
- setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
+ if(netJob->size() == 1) // Translation issues... see https://github.com/MultiMC/MultiMC5/issues/1701
+ {
+ setStatus(tr("Downloading one update file."));
+ }
+ else
+ {
+ setStatus(tr("Downloading %1 update files.").arg(QString::number(netJob->size())));
+ }
qDebug() << "Begin downloading update files to" << m_updateFilesDir.path();
m_filesNetJob = netJob;
m_filesNetJob->start();
diff --git a/api/logic/updater/DownloadTask.h b/api/logic/updater/DownloadTask.h
index 30b4b9ec..88e60865 100644
--- a/api/logic/updater/DownloadTask.h
+++ b/api/logic/updater/DownloadTask.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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_test.cpp b/api/logic/updater/DownloadTask_test.cpp
index 531b2527..8d5375e8 100644
--- a/api/logic/updater/DownloadTask_test.cpp
+++ b/api/logic/updater/DownloadTask_test.cpp
@@ -185,25 +185,6 @@ slots:
qDebug() << expectedOperations;
QCOMPARE(operations, expectedOperations);
}
-
- void test_OSXPathFixup()
- {
- QString path, pathOrig;
- bool result;
- // Proper OSX path
- pathOrig = path = "MultiMC.app/Foo/Bar/Baz";
- qDebug() << "Proper OSX path: " << path;
- result = fixPathForOSX(path);
- QCOMPARE(path, QString("Foo/Bar/Baz"));
- QCOMPARE(result, true);
-
- // Bad OSX path
- pathOrig = path = "translations/klingon.lol";
- qDebug() << "Bad OSX path: " << path;
- result = fixPathForOSX(path);
- QCOMPARE(path, pathOrig);
- QCOMPARE(result, false);
- }
};
extern "C"
diff --git a/api/logic/updater/GoUpdate.cpp b/api/logic/updater/GoUpdate.cpp
index ef040db6..6167418e 100644
--- a/api/logic/updater/GoUpdate.cpp
+++ b/api/logic/updater/GoUpdate.cpp
@@ -33,13 +33,7 @@ bool parseVersionInfo(const QByteArray &data, VersionFileList &list, QString &er
QJsonObject fileObj = fileValue.toObject();
QString file_path = fileObj.value("Path").toString();
-#ifdef Q_OS_MAC
- // On OSX, the paths for the updater need to be fixed.
- // basically, anything that isn't in the .app folder is ignored.
- // everything else is changed so the code that processes the files actually finds
- // them and puts the replacements in the right spots.
- fixPathForOSX(file_path);
-#endif
+
VersionFileEntry file{file_path, fileObj.value("Perms").toVariant().toInt(),
FileSourceList(), fileObj.value("MD5").toString(), };
qDebug() << "File" << file.path << "with perms" << file.mode;
@@ -201,19 +195,4 @@ bool processFileLists
}
return true;
}
-
-bool fixPathForOSX(QString &path)
-{
- if (path.startsWith("MultiMC.app/"))
- {
- // remove the prefix and add a new, more appropriate one.
- path.remove(0, 12);
- return true;
- }
- else
- {
- qCritical() << "Update path not within .app: " << path;
- return false;
- }
}
-} \ No newline at end of file
diff --git a/api/logic/updater/GoUpdate.h b/api/logic/updater/GoUpdate.h
index 54559a3c..8f92bb99 100644
--- a/api/logic/updater/GoUpdate.h
+++ b/api/logic/updater/GoUpdate.h
@@ -123,14 +123,5 @@ bool MULTIMC_LOGIC_EXPORT processFileLists
OperationList &ops
);
-/*!
- * This fixes destination paths for OSX - removes 'MultiMC.app' prefix
- * The updater runs in MultiMC.app/Contents/MacOs by default
- * The destination paths are such as this: MultiMC.app/blah/blah
- *
- * @return false if the path couldn't be fixed (is invalid)
- */
-bool MULTIMC_LOGIC_EXPORT fixPathForOSX(QString &path);
-
}
Q_DECLARE_METATYPE(GoUpdate::Status)
diff --git a/api/logic/updater/UpdateChecker.cpp b/api/logic/updater/UpdateChecker.cpp
index 9e610d52..be33c73c 100644
--- a/api/logic/updater/UpdateChecker.cpp
+++ b/api/logic/updater/UpdateChecker.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 bb5013de..20906207 100644
--- a/api/logic/updater/UpdateChecker.h
+++ b/api/logic/updater/UpdateChecker.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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_test.cpp b/api/logic/updater/UpdateChecker_test.cpp
index 59f2a5a1..5702d9c6 100644
--- a/api/logic/updater/UpdateChecker_test.cpp
+++ b/api/logic/updater/UpdateChecker_test.cpp
@@ -21,6 +21,11 @@ QDebug operator<<(QDebug dbg, const UpdateChecker::ChannelListEntry &c)
return dbg.maybeSpace();
}
+QString findTestDataUrl(const char *file)
+{
+ return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString();
+}
+
class UpdateCheckerTest : public QObject
{
Q_OBJECT
@@ -35,10 +40,6 @@ slots:
}
- static QString findTestDataUrl(const char *file)
- {
- return QUrl::fromLocalFile(QFINDTESTDATA(file)).toString();
- }
void tst_ChannelListParsing_data()
{
QTest::addColumn<QString>("channel");
diff --git a/application/CMakeLists.txt b/application/CMakeLists.txt
index 2ba23828..ab2b9960 100644
--- a/application/CMakeLists.txt
+++ b/application/CMakeLists.txt
@@ -1,8 +1,5 @@
project(application)
-######## Configure the file with build properties ########
-configure_file("${PROJECT_SOURCE_DIR}/BuildConfig.cpp.in" "${PROJECT_BINARY_DIR}/BuildConfig.cpp")
-
################################ FILES ################################
######## Sources and headers ########
@@ -11,8 +8,6 @@ SET(MULTIMC_SOURCES
main.cpp
MultiMC.h
MultiMC.cpp
- BuildConfig.h
- ${PROJECT_BINARY_DIR}/BuildConfig.cpp
UpdateController.cpp
UpdateController.h
@@ -64,9 +59,6 @@ SET(MULTIMC_SOURCES
themes/SystemTheme.cpp
themes/SystemTheme.h
- # GUI - settings-specific wrappers for paged dialog
- SettingsUI.h
-
# Processes
LaunchController.h
LaunchController.cpp
@@ -84,14 +76,14 @@ SET(MULTIMC_SOURCES
pages/BasePageProvider.h
# GUI - instance pages
+ pages/instance/GameOptionsPage.cpp
+ pages/instance/GameOptionsPage.h
pages/instance/VersionPage.cpp
pages/instance/VersionPage.h
pages/instance/TexturePackPage.h
pages/instance/ResourcePackPage.h
pages/instance/ModFolderPage.cpp
pages/instance/ModFolderPage.h
- pages/instance/NewModFolderPage.cpp
- pages/instance/NewModFolderPage.h
pages/instance/NotesPage.cpp
pages/instance/NotesPage.h
pages/instance/LogPage.cpp
@@ -118,6 +110,8 @@ SET(MULTIMC_SOURCES
pages/global/ExternalToolsPage.h
pages/global/JavaPage.cpp
pages/global/JavaPage.h
+ pages/global/LanguagePage.cpp
+ pages/global/LanguagePage.h
pages/global/MinecraftPage.cpp
pages/global/MinecraftPage.h
pages/global/MultiMCPage.cpp
@@ -126,20 +120,42 @@ SET(MULTIMC_SOURCES
pages/global/ProxyPage.h
pages/global/PasteEEPage.cpp
pages/global/PasteEEPage.h
- pages/global/PackagesPage.cpp
- pages/global/PackagesPage.h
# GUI - platform pages
pages/modplatform/VanillaPage.cpp
pages/modplatform/VanillaPage.h
- pages/modplatform/FTBPage.cpp
- pages/modplatform/FTBPage.h
- pages/modplatform/FtbListModel.h
- pages/modplatform/FtbListModel.cpp
- pages/modplatform/TwitchPage.cpp
- pages/modplatform/TwitchPage.h
- pages/modplatform/TechnicPage.cpp
- pages/modplatform/TechnicPage.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/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
@@ -160,8 +176,6 @@ SET(MULTIMC_SOURCES
dialogs/IconPickerDialog.h
dialogs/LoginDialog.cpp
dialogs/LoginDialog.h
- dialogs/ModEditDialogCommon.cpp
- dialogs/ModEditDialogCommon.h
dialogs/NewComponentDialog.cpp
dialogs/NewComponentDialog.h
dialogs/NewInstanceDialog.cpp
@@ -185,6 +199,8 @@ SET(MULTIMC_SOURCES
widgets/Common.h
widgets/CustomCommands.cpp
widgets/CustomCommands.h
+ widgets/DropLabel.cpp
+ widgets/DropLabel.h
widgets/FocusLineEdit.cpp
widgets/FocusLineEdit.h
widgets/IconLabel.cpp
@@ -193,6 +209,8 @@ SET(MULTIMC_SOURCES
widgets/JavaSettingsWidget.h
widgets/LabeledToolButton.cpp
widgets/LabeledToolButton.h
+ widgets/LanguageSelectionWidget.cpp
+ widgets/LanguageSelectionWidget.h
widgets/LineSeparator.cpp
widgets/LineSeparator.h
widgets/LogView.cpp
@@ -212,10 +230,15 @@ SET(MULTIMC_SOURCES
widgets/VersionSelectWidget.h
widgets/ProgressWidget.h
widgets/ProgressWidget.cpp
+ widgets/WideBar.h
+ widgets/WideBar.cpp
# GUI - instance group view
groupview/GroupedProxyModel.cpp
groupview/GroupedProxyModel.h
+ groupview/AccessibleGroupView.cpp
+ groupview/AccessibleGroupView.h
+ groupview/AccessibleGroupView_p.h
groupview/GroupView.cpp
groupview/GroupView.h
groupview/InstanceDelegate.cpp
@@ -227,9 +250,9 @@ SET(MULTIMC_SOURCES
######## UIs ########
SET(MULTIMC_UIS
# Instance pages
+ pages/instance/GameOptionsPage.ui
pages/instance/VersionPage.ui
pages/instance/ModFolderPage.ui
- pages/instance/NewModFolderPage.ui
pages/instance/LogPage.ui
pages/instance/InstanceSettingsPage.ui
pages/instance/NotesPage.ui
@@ -247,15 +270,19 @@ SET(MULTIMC_UIS
pages/global/MultiMCPage.ui
pages/global/ProxyPage.ui
pages/global/PasteEEPage.ui
- pages/global/PackagesPage.ui
# Platform pages
pages/modplatform/VanillaPage.ui
- pages/modplatform/FTBPage.ui
- pages/modplatform/TwitchPage.ui
- pages/modplatform/TechnicPage.ui
+ pages/modplatform/atlauncher/AtlPage.ui
+ pages/modplatform/ftb/FtbPage.ui
+ pages/modplatform/legacy_ftb/Page.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
@@ -277,7 +304,6 @@ SET(MULTIMC_UIS
)
set(MULTIMC_QRCS
- resources/assets/assets.qrc
resources/backgrounds/backgrounds.qrc
resources/multimc/multimc.qrc
resources/pe_dark/pe_dark.qrc
diff --git a/application/HoeDown.h b/application/HoeDown.h
index ba94da8c..b9e06ffb 100644
--- a/application/HoeDown.h
+++ b/application/HoeDown.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 ae279d94..3cb723c4 100644
--- a/application/InstancePageProvider.h
+++ b/application/InstancePageProvider.h
@@ -7,7 +7,6 @@
#include "pages/instance/LogPage.h"
#include "pages/instance/VersionPage.h"
#include "pages/instance/ModFolderPage.h"
-#include "pages/instance/NewModFolderPage.h"
#include "pages/instance/ResourcePackPage.h"
#include "pages/instance/TexturePackPage.h"
#include "pages/instance/NotesPage.h"
@@ -17,6 +16,7 @@
#include "pages/instance/LegacyUpgradePage.h"
#include "pages/instance/WorldListPage.h"
#include "pages/instance/ServersPage.h"
+#include "pages/instance/GameOptionsPage.h"
#include "Env.h"
@@ -38,25 +38,16 @@ public:
if(onesix)
{
values.append(new VersionPage(onesix.get()));
- if(ENV.isFeatureEnabled("NewModsPage"))
- {
- auto modsPage = new NewModFolderPage(onesix.get(), onesix->modsModel(), "mods", "loadermods", tr("Mods"), "Mods-page");
- modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
- values.append(modsPage);
- }
- else
- {
- auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods");
- modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
- values.append(modsPage);
- }
-
+ auto modsPage = new ModFolderPage(onesix.get(), onesix->loaderModList(), "mods", "loadermods", tr("Loader mods"), "Loader-mods");
+ modsPage->setFilter("%1 (*.zip *.jar *.litemod)");
+ values.append(modsPage);
values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList(), "coremods", "coremods", tr("Core mods"), "Core-mods"));
values.append(new ResourcePackPage(onesix.get()));
values.append(new TexturePackPage(onesix.get()));
values.append(new NotesPage(onesix.get()));
- values.append(new WorldListPage(onesix.get(), onesix->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds"));
- values.append(new ServersPage(onesix.get()));
+ values.append(new WorldListPage(onesix.get(), onesix->worldList()));
+ 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()));
}
@@ -65,7 +56,7 @@ public:
{
values.append(new LegacyUpgradePage(legacy));
values.append(new NotesPage(legacy.get()));
- values.append(new WorldListPage(legacy.get(), legacy->worldList(), "worlds", "worlds", tr("Worlds"), "Worlds"));
+ values.append(new WorldListPage(legacy.get(), legacy->worldList()));
values.append(new ScreenshotsPage(FS::PathCombine(legacy->gameRoot(), "screenshots")));
}
auto logMatcher = inst->getLogFileMatcher();
diff --git a/application/InstanceWindow.cpp b/application/InstanceWindow.cpp
index 711141f2..015ffe1c 100644
--- a/application/InstanceWindow.cpp
+++ b/application/InstanceWindow.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -50,6 +50,7 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent)
m_container = new PageContainer(provider.get(), "console", this);
m_container->setParentContainer(this);
setCentralWidget(m_container);
+ setContentsMargins(0, 0, 0, 0);
}
// Add custom buttons to the page container layout.
@@ -122,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);
}
@@ -135,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()
@@ -144,15 +151,18 @@ void InstanceWindow::on_btnLaunchMinecraftOffline_clicked()
MMC->launch(m_instance, false, nullptr);
}
-void InstanceWindow::on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc)
+void InstanceWindow::on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc)
{
m_proc = proc;
}
-void InstanceWindow::on_RunningState_changed(bool)
+void InstanceWindow::on_RunningState_changed(bool running)
{
updateLaunchButtons();
m_container->refreshContainer();
+ if(running) {
+ selectPage("log");
+ }
}
void InstanceWindow::on_closeButton_clicked()
diff --git a/application/InstanceWindow.h b/application/InstanceWindow.h
index c1d56143..cd7d2494 100644
--- a/application/InstanceWindow.h
+++ b/application/InstanceWindow.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -52,7 +52,7 @@ slots:
void on_btnKillMinecraft_clicked();
void on_btnLaunchMinecraftOffline_clicked();
- void on_InstanceLaunchTask_changed(std::shared_ptr<LaunchTask> proc);
+ void on_InstanceLaunchTask_changed(shared_qobject_ptr<LaunchTask> proc);
void on_RunningState_changed(bool running);
void on_instanceStatusChanged(BaseInstance::Status, BaseInstance::Status newStatus);
@@ -63,7 +63,7 @@ private:
void updateLaunchButtons();
private:
- std::shared_ptr<LaunchTask> m_proc;
+ shared_qobject_ptr<LaunchTask> m_proc;
InstancePtr m_instance;
bool m_doNotSave = false;
PageContainer *m_container = nullptr;
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 0115bba4..ee764082 100644
--- a/application/LaunchController.cpp
+++ b/application/LaunchController.cpp
@@ -9,13 +9,15 @@
#include "InstanceWindow.h"
#include "BuildConfig.h"
#include "JavaCommon.h"
-#include "SettingsUI.h"
#include <QLineEdit>
#include <QInputDialog>
#include <tasks/Task.h>
#include <minecraft/auth/YggdrasilTask.h>
#include <launch/steps/TextPrint.h>
#include <QStringList>
+#include <QHostInfo>
+#include <QList>
+#include <QHostAddress>
LaunchController::LaunchController(QObject *parent) : Task(parent)
{
@@ -25,7 +27,7 @@ void LaunchController::executeTask()
{
if (!m_instance)
{
- emitFailed(tr("No instance specified"));
+ emitFailed(tr("No instance specified!"));
return;
}
@@ -53,7 +55,7 @@ void LaunchController::login()
if (reply == QMessageBox::Yes)
{
// Open the account manager.
- SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), m_parentWidget, "accounts");
+ MMC->ShowGlobalSettings(m_parentWidget, "accounts");
}
}
else if (account.get() == nullptr)
@@ -75,7 +77,7 @@ void LaunchController::login()
// if no account is selected, we bail
if (!account.get())
{
- emitFailed(tr("No account selected for launch"));
+ emitFailed(tr("No account selected for launch."));
return;
}
@@ -84,8 +86,7 @@ void LaunchController::login()
// we loop until the user succeeds in logging in or gives up
bool tryagain = true;
// the failure. the default failure.
- const QString needLoginAgain = tr("Your account is currently not logged in. Please enter "
- "your password to log in again.");
+ const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again. <br /> <br /> This could be caused by a password change.");
QString failReason = needLoginAgain;
while (tryagain)
@@ -194,12 +195,12 @@ void LaunchController::launchInstance()
if(!m_instance->reloadSettings())
{
- QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't load the instance profile."));
+ QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't load the instance profile."));
emitFailed(tr("Couldn't load the instance profile."));
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."));
@@ -217,8 +218,47 @@ 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";
- m_launcher->prependStep(std::make_shared<TextPrint>(m_launcher.get(), "MultiMC version: " + BuildConfig.printableVersionString() + "\n\n", MessageLevel::MultiMC));
+ // 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();
}
@@ -234,8 +274,8 @@ void LaunchController::readyForLaunch()
if (!m_profiler->check(&error))
{
m_launcher->abort();
- QMessageBox::critical(m_parentWidget, tr("Error"), tr("Couldn't start profiler: %1").arg(error));
- emitFailed("Profiler startup failed");
+ QMessageBox::critical(m_parentWidget, tr("Error!"), tr("Couldn't start profiler: %1").arg(error));
+ emitFailed("Profiler startup failed!");
return;
}
BaseProfiler *profilerInstance = m_profiler->createProfiler(m_launcher->instance(), this);
@@ -246,7 +286,7 @@ void LaunchController::readyForLaunch()
msg.setText(tr("The game launch is delayed until you press the "
"button. This is the right time to setup the profiler, as the "
"profiler server is running now.\n\n%1").arg(message));
- msg.setWindowTitle(tr("Waiting"));
+ msg.setWindowTitle(tr("Waiting."));
msg.setIcon(QMessageBox::Information);
msg.addButton(tr("Launch"), QMessageBox::AcceptRole);
msg.setModal(true);
@@ -263,7 +303,7 @@ void LaunchController::readyForLaunch()
msg.setModal(true);
msg.exec();
m_launcher->abort();
- emitFailed("Profiler startup failed");
+ emitFailed("Profiler startup failed!");
});
profilerInstance->beginProfiling(m_launcher);
}
diff --git a/application/LaunchController.h b/application/LaunchController.h
index 1434dec9..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();
@@ -57,5 +63,6 @@ private:
QWidget * m_parentWidget = nullptr;
InstanceWindow *m_console = nullptr;
AuthSessionPtr m_session;
- std::shared_ptr <LaunchTask> m_launcher;
+ shared_qobject_ptr<LaunchTask> m_launcher;
+ MinecraftServerTargetPtr m_serverToJoin;
};
diff --git a/application/MainWindow.cpp b/application/MainWindow.cpp
index 4ee49b80..13a7c7ae 100644
--- a/application/MainWindow.cpp
+++ b/application/MainWindow.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 MultiMC Contributors
+/* Copyright 2013-2021 MultiMC Contributors
*
* Authors: Andrew Okin
* Peterix
@@ -56,7 +56,7 @@
#include <launch/LaunchTask.h>
#include <minecraft/auth/MojangAccountList.h>
#include <SkinUtils.h>
-#include <net/URLConstants.h>
+#include <BuildConfig.h>
#include <net/NetJob.h>
#include <net/Download.h>
#include <news/NewsChecker.h>
@@ -70,7 +70,6 @@
#include "InstanceProxyModel.h"
#include "JavaCommon.h"
#include "LaunchController.h"
-#include "SettingsUI.h"
#include "groupview/GroupView.h"
#include "groupview/InstanceDelegate.h"
#include "widgets/LabeledToolButton.h"
@@ -289,6 +288,7 @@ public:
foldersMenuButton->setPopupMode(QToolButton::InstantPopup);
foldersMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
foldersMenuButton->setIcon(MMC->getThemedIcon("viewfolder"));
+ foldersMenuButton->setFocusPolicy(Qt::NoFocus);
all_toolbuttons.append(&foldersMenuButton);
QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow);
foldersButtonAction->setDefaultWidget(foldersMenuButton);
@@ -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"));
@@ -346,6 +352,7 @@ public:
helpMenuButton->setPopupMode(QToolButton::InstantPopup);
helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
helpMenuButton->setIcon(MMC->getThemedIcon("help"));
+ helpMenuButton->setFocusPolicy(Qt::NoFocus);
all_toolbuttons.append(&helpMenuButton);
QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow);
helpButtonAction->setDefaultWidget(helpMenuButton);
@@ -574,9 +581,12 @@ public:
{
MainWindow->setObjectName(QStringLiteral("MainWindow"));
}
- MainWindow->resize(694, 563);
+ MainWindow->resize(800, 600);
MainWindow->setWindowIcon(MMC->getThemedIcon("logo"));
MainWindow->setWindowTitle("MultiMC 5");
+#ifndef QT_NO_ACCESSIBILITY
+ MainWindow->setAccessibleName("MultiMC");
+#endif
createMainToolbar(MainWindow);
@@ -584,7 +594,6 @@ public:
centralWidget->setObjectName(QStringLiteral("centralWidget"));
horizontalLayout = new QHBoxLayout(centralWidget);
horizontalLayout->setSpacing(0);
- horizontalLayout->setContentsMargins(11, 11, 11, 11);
horizontalLayout->setObjectName(QStringLiteral("horizontalLayout"));
horizontalLayout->setSizeConstraint(QLayout::SetDefaultConstraint);
horizontalLayout->setContentsMargins(0, 0, 0, 0);
@@ -653,6 +662,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
newsLabel->setIcon(MMC->getThemedIcon("news"));
newsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
newsLabel->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
+ newsLabel->setFocusPolicy(Qt::NoFocus);
ui->newsToolBar->insertWidget(ui->actionMoreNews, newsLabel);
QObject::connect(newsLabel, &QAbstractButton::clicked, this, &MainWindow::newsButtonClicked);
QObject::connect(m_newsChecker.get(), &NewsChecker::newsLoaded, this, &MainWindow::updateNewsLabel);
@@ -681,6 +691,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
connect(proxymodel, &InstanceProxyModel::dataChanged, this, &MainWindow::instanceDataChanged);
view->setModel(proxymodel);
+ view->setSourceOfGroupCollapseStatus([](const QString & groupName)->bool {
+ return MMC->instances()->isGroupCollapsed(groupName);
+ });
+ connect(view, &GroupView::groupStateChanged, MMC->instances().get(), &InstanceList::on_GroupStateChanged);
ui->horizontalLayout->addWidget(view);
}
// The cat background
@@ -692,7 +706,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
setCatBackground(cat_enable);
}
// start instance when double-clicked
- connect(view, &GroupView::doubleClicked, this, &MainWindow::instanceActivated);
+ connect(view, &GroupView::activated, this, &MainWindow::instanceActivated);
// track the selection -- update the instance toolbar
connect(view->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::instanceChanged);
@@ -703,6 +717,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
// model reset -> selection is invalid. All the instance pointers are wrong.
connect(MMC->instances().get(), &InstanceList::dataIsInvalid, this, &MainWindow::selectionBad);
+ // handle newly added instances
+ connect(MMC->instances().get(), &InstanceList::instanceSelectRequest, this, &MainWindow::instanceSelectRequest);
+
+ // When the global settings page closes, we want to know about it and update our state
+ connect(MMC, &MultiMC::globalSettingsClosed, this, &MainWindow::globalSettingsClosed);
+
m_statusLeft = new QLabel(tr("No instance selected"), this);
m_statusRight = new ServerStatus(this);
statusBar()->addPermanentWidget(m_statusLeft, 1);
@@ -718,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);
@@ -758,7 +777,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow
for (auto profile : account->profiles())
{
auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png");
- auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta);
+ auto action = Net::Download::makeCached(QUrl(BuildConfig.SKINS_BASE + profile.id + ".png"), meta);
skin_dls.append(action);
meta->setStale(true);
}
@@ -817,15 +836,37 @@ 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()
{
}
+QMenu * MainWindow::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction( ui->mainToolBar->toggleViewAction() );
+ return filteredMenu;
+}
+
void MainWindow::konamiTriggered()
{
- ENV.enableFeature("NewModsPage");
+ // ENV.enableFeature("NewModsPage");
qDebug() << "Super Secret Mode ACTIVATED!";
}
@@ -902,15 +943,21 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos)
void MainWindow::updateToolsMenu()
{
QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance));
+ QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline));
+
if(!m_selectedInstance || m_selectedInstance->isRunning())
{
ui->actionLaunchInstance->setMenu(nullptr);
+ ui->actionLaunchInstanceOffline->setMenu(nullptr);
launchButton->setPopupMode(QToolButton::InstantPopup);
+ launchOfflineButton->setPopupMode(QToolButton::InstantPopup);
return;
}
QMenu *launchMenu = ui->actionLaunchInstance->menu();
+ QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu();
launchButton->setPopupMode(QToolButton::MenuButtonPopup);
+ launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup);
if (launchMenu)
{
launchMenu->clear();
@@ -919,21 +966,39 @@ void MainWindow::updateToolsMenu()
{
launchMenu = new QMenu(this);
}
+ if (launchOfflineMenu) {
+ launchOfflineMenu->clear();
+ }
+ else
+ {
+ launchOfflineMenu = new QMenu(this);
+ }
QAction *normalLaunch = launchMenu->addAction(tr("Launch"));
+ QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline"));
connect(normalLaunch, &QAction::triggered, [this]()
{
- MMC->launch(m_selectedInstance);
+ MMC->launch(m_selectedInstance, true);
});
- launchMenu->addSeparator()->setText(tr("Profilers"));
+ connect(normalLaunchOffline, &QAction::triggered, [this]()
+ {
+ MMC->launch(m_selectedInstance, false);
+ });
+ QString profilersTitle = tr("Profilers");
+ launchMenu->addSeparator()->setText(profilersTitle);
+ launchOfflineMenu->addSeparator()->setText(profilersTitle);
for (auto profiler : MMC->profilers().values())
{
QAction *profilerAction = launchMenu->addAction(profiler->name());
+ QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name());
QString error;
if (!profiler->check(&error))
{
profilerAction->setDisabled(true);
- profilerAction->setToolTip(tr("Profiler not setup correctly. Go into settings, \"External Tools\"."));
+ profilerOfflineAction->setDisabled(true);
+ QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\".");
+ profilerAction->setToolTip(profilerToolTip);
+ profilerOfflineAction->setToolTip(profilerToolTip);
}
else
{
@@ -941,9 +1006,14 @@ void MainWindow::updateToolsMenu()
{
MMC->launch(m_selectedInstance, true, profiler.get());
});
+ connect(profilerOfflineAction, &QAction::triggered, [this, profiler]()
+ {
+ MMC->launch(m_selectedInstance, false, profiler.get());
+ });
}
}
ui->actionLaunchInstance->setMenu(launchMenu);
+ ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu);
}
QString profileInUseFilter(const QString & profile, bool used)
@@ -1091,10 +1161,12 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *ev)
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
switch (keyEvent->key())
{
+ /*
case Qt::Key_Enter:
case Qt::Key_Return:
activateInstance(m_selectedInstance);
return true;
+ */
case Qt::Key_Delete:
on_actionDeleteInstance_triggered();
return true;
@@ -1239,20 +1311,34 @@ void MainWindow::onCatToggled(bool state)
MMC->settings()->set("TheCat", state);
}
+namespace {
+template <typename T>
+T non_stupid_abs(T in)
+{
+ if (in < 0)
+ return -in;
+ return in;
+}
+}
+
void MainWindow::setCatBackground(bool enabled)
{
if (enabled)
{
- view->setStyleSheet(R"(
+ QDateTime now = QDateTime::currentDateTime();
+ QDateTime xmas(QDate(now.date().year(), 12, 25), QTime(0, 0));
+ ;
+ QString cat = (non_stupid_abs(now.daysTo(xmas)) <= 4) ? "catmas" : "kitteh";
+ view->setStyleSheet(QString(R"(
GroupView
{
- background-image: url(:/backgrounds/kitteh);
+ background-image: url(:/backgrounds/%1);
background-attachment: fixed;
background-clip: padding;
background-position: top right;
background-repeat: none;
background-color:palette(base);
-})");
+})").arg(cat));
}
else
{
@@ -1281,7 +1367,7 @@ void MainWindow::runModalTask(Task *task)
void MainWindow::instanceFromInstanceTask(InstanceTask *rawTask)
{
- std::unique_ptr<Task> task(MMC->instances()->wrapInstanceTask(rawTask));
+ unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(rawTask));
runModalTask(task.get());
}
@@ -1294,11 +1380,11 @@ void MainWindow::on_actionCopyInstance_triggered()
if (!copyInstDlg.exec())
return;
- auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves());
+ auto copyTask = new InstanceCopyTask(m_selectedInstance, copyInstDlg.shouldCopySaves(), copyInstDlg.shouldKeepPlaytime());
copyTask->setName(copyInstDlg.instName());
copyTask->setGroup(copyInstDlg.instGroup());
copyTask->setIcon(copyInstDlg.iconKey());
- std::unique_ptr<Task> task(MMC->instances()->wrapInstanceTask(copyTask));
+ unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(copyTask));
runModalTask(task.get());
}
@@ -1389,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()
@@ -1478,7 +1564,12 @@ void MainWindow::deleteGroup()
QString groupName = map["group"].toString();
if(!groupName.isEmpty())
{
- MMC->instances()->deleteGroup(groupName);
+ auto reply = QMessageBox::question(this, tr("Delete group"), tr("Are you sure you want to delete the group %1")
+ .arg(groupName), QMessageBox::Yes | QMessageBox::No);
+ if(reply == QMessageBox::Yes)
+ {
+ MMC->instances()->deleteGroup(groupName);
+ }
}
}
@@ -1522,7 +1613,11 @@ void MainWindow::checkForUpdates()
void MainWindow::on_actionSettings_triggered()
{
- SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "global-settings");
+ MMC->ShowGlobalSettings(this, "global-settings");
+}
+
+void MainWindow::globalSettingsClosed()
+{
// FIXME: quick HACK to make this work. improve, optimize.
MMC->instances()->loadList();
proxymodel->invalidate();
@@ -1558,22 +1653,22 @@ void MainWindow::on_actionScreenshots_triggered()
void MainWindow::on_actionManageAccounts_triggered()
{
- SettingsUI::ShowPageDialog(MMC->globalSettingsPages(), this, "accounts");
+ MMC->ShowGlobalSettings(this, "accounts");
}
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()
{
- DesktopServices::openUrl(QUrl("http://www.patreon.com/multimc"));
+ DesktopServices::openUrl(QUrl("https://www.patreon.com/multimc"));
}
void MainWindow::on_actionMoreNews_triggered()
{
- DesktopServices::openUrl(QUrl("http://multimc.org/posts.html"));
+ DesktopServices::openUrl(QUrl("https://multimc.org/posts.html"));
}
void MainWindow::newsButtonClicked()
@@ -1585,7 +1680,7 @@ void MainWindow::newsButtonClicked()
}
else
{
- DesktopServices::openUrl(QUrl("http://multimc.org/posts.html"));
+ DesktopServices::openUrl(QUrl("https://multimc.org/posts.html"));
}
}
@@ -1595,29 +1690,24 @@ void MainWindow::on_actionAbout_triggered()
dialog.exec();
}
-void MainWindow::on_mainToolBar_visibilityChanged(bool)
-{
- // Don't allow hiding the main toolbar.
- // This is the only way I could find to prevent it... :/
- ui->mainToolBar->setVisible(true);
-}
-
void MainWindow::on_actionDeleteInstance_triggered()
{
if (!m_selectedInstance)
{
return;
}
+ auto id = m_selectedInstance->id();
auto response = CustomMessageBox::selectable(
this,
tr("CAREFUL!"),
tr("About to delete: %1\nThis is permanent and will completely delete the instance.\n\nAre you sure?").arg(m_selectedInstance->name()),
QMessageBox::Warning,
- QMessageBox::Yes | QMessageBox::No
+ QMessageBox::Yes | QMessageBox::No,
+ QMessageBox::No
)->exec();
if (response == QMessageBox::Yes)
{
- MMC->instances()->deleteInstance(m_selectedInstance->id());
+ MMC->instances()->deleteInstance(id);
}
}
@@ -1634,19 +1724,7 @@ void MainWindow::on_actionRenameInstance_triggered()
{
if (m_selectedInstance)
{
- bool ok = false;
- QString name(m_selectedInstance->name());
- name = QInputDialog::getText(this, tr("Instance name"), tr("Enter a new instance name."), QLineEdit::Normal, name, &ok);
-
- name = name.trimmed();
- if (name.length() > 0)
- {
- if (ok && name.length())
- {
- m_selectedInstance->setName(name);
- ui->renameButton->setText(name);
- }
- }
+ view->edit(view->currentIndex());
}
}
@@ -1687,7 +1765,7 @@ void MainWindow::changeEvent(QEvent* event)
{
if (event->type() == QEvent::LanguageChange)
{
- ui->retranslateUi(this);
+ retranslateUi();
}
QMainWindow::changeEvent(event);
}
@@ -1791,6 +1869,11 @@ void MainWindow::instanceChanged(const QModelIndex &current, const QModelIndex &
}
}
+void MainWindow::instanceSelectRequest(QString id)
+{
+ setSelectedInstanceById(id);
+}
+
void MainWindow::instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
auto current = view->selectionModel()->currentIndex();
@@ -1840,7 +1923,7 @@ void MainWindow::checkInstancePathForProblems()
warning.setDefaultButton(QMessageBox::Ok);
warning.exec();
}
- else if (pathfoldername.contains(QDir::tempPath()))
+ else if (pathfoldername.startsWith(QDir::tempPath()) || pathfoldername.contains("/TempState/"))
{
QMessageBox warning(this);
warning.setText(tr("Your instance folder is in a temporary folder: \'%1\'!").arg(QDir::tempPath()));
diff --git a/application/MainWindow.h b/application/MainWindow.h
index 3f370fda..08c6b969 100644
--- a/application/MainWindow.h
+++ b/application/MainWindow.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -57,9 +57,14 @@ public:
void checkInstancePathForProblems();
void updatesAllowedChanged(bool allowed);
+
+ void droppedURLs(QList<QUrl> urls);
signals:
void isClosing();
+protected:
+ QMenu * createPopupMenu() override;
+
private slots:
void onCatToggled(bool);
@@ -109,8 +114,6 @@ private slots:
void newsButtonClicked();
- void on_mainToolBar_visibilityChanged(bool);
-
void on_actionLaunchInstance_triggered();
void on_actionLaunchInstanceOffline_triggered();
@@ -152,6 +155,8 @@ private slots:
void instanceChanged(const QModelIndex &current, const QModelIndex &previous);
+ void instanceSelectRequest(QString id);
+
void instanceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight);
void selectionBad();
@@ -177,11 +182,13 @@ private slots:
*/
void downloadUpdates(GoUpdate::Status status);
- void droppedURLs(QList<QUrl> urls);
-
void konamiTriggered();
+ 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 3a9c281e..2dd4f83f 100644
--- a/application/MultiMC.cpp
+++ b/application/MultiMC.cpp
@@ -2,15 +2,19 @@
#include "BuildConfig.h"
#include "MainWindow.h"
#include "InstanceWindow.h"
+
+#include "groupview/AccessibleGroupView.h"
+#include <QAccessible>
+
#include "pages/BasePageProvider.h"
#include "pages/global/MultiMCPage.h"
#include "pages/global/MinecraftPage.h"
#include "pages/global/JavaPage.h"
+#include "pages/global/LanguagePage.h"
#include "pages/global/ProxyPage.h"
#include "pages/global/ExternalToolsPage.h"
#include "pages/global/AccountListPage.h"
#include "pages/global/PasteEEPage.h"
-#include "pages/global/PackagesPage.h"
#include "pages/global/CustomCommandsPage.h"
#include "themes/ITheme.h"
@@ -30,6 +34,7 @@
#include <QNetworkAccessManager>
#include <QTranslator>
#include <QLibraryInfo>
+#include <QList>
#include <QStringList>
#include <QDebug>
#include <QStyleFactory>
@@ -40,7 +45,6 @@
#include <minecraft/auth/MojangAccountList.h>
#include "icons/IconList.h"
#include "net/HttpMetaCache.h"
-#include "net/URLConstants.h"
#include "Env.h"
#include "java/JavaUtils.h"
@@ -65,6 +69,8 @@
#include <ganalytics.h>
#include <sys.h>
+#include "pagedialog/PageDialog.h"
+
#if defined Q_OS_WIN32
#ifndef WIN32_LEAN_AND_MEAN
@@ -139,6 +145,27 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
startTime = QDateTime::currentDateTime();
+#ifdef Q_OS_LINUX
+ {
+ QFile osrelease("/proc/sys/kernel/osrelease");
+ if (osrelease.open(QFile::ReadOnly | QFile::Text)) {
+ QTextStream in(&osrelease);
+ auto contents = in.readAll();
+ if(
+ contents.contains("WSL", Qt::CaseInsensitive) ||
+ contents.contains("Microsoft", Qt::CaseInsensitive)
+ ) {
+ showFatalErrorMessage(
+ "Unsupported system detected!",
+ "Linux-on-Windows distributions are not supported.\n\n"
+ "Please use the Windows MultiMC binary when playing on Windows."
+ );
+ return;
+ }
+ }
+ }
+#endif
+
// Don't quit on hiding the last window
this->setQuitOnLastWindowClosed(false);
@@ -150,23 +177,32 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
// --help
parser.addSwitch("help");
parser.addShortOpt("help", 'h');
- parser.addDocumentation("help", "display this help and exit.");
+ parser.addDocumentation("help", "Display this help and exit.");
// --version
parser.addSwitch("version");
parser.addShortOpt("version", 'V');
- parser.addDocumentation("version", "display program version and exit.");
+ parser.addDocumentation("version", "Display program version and exit.");
// --dir
parser.addOption("dir");
parser.addShortOpt("dir", 'd');
- parser.addDocumentation("dir", "use the supplied folder as MultiMC root instead of "
+ parser.addDocumentation("dir", "Use the supplied folder as MultiMC root instead of "
"the binary location (use '.' for current)");
// --launch
parser.addOption("launch");
parser.addShortOpt("launch", 'l');
- parser.addDocumentation("launch", "launch the specified instance (by instance ID)");
+ 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");
+ parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after MultiMC starts");
+ // --import
+ parser.addOption("import");
+ parser.addShortOpt("import", 'I');
+ parser.addDocumentation("import", "Import instance from specified zip (local path or URL)");
// parse the arguments
try
@@ -176,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;
}
@@ -200,7 +237,9 @@ 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();
QString origcwdPath = QDir::currentPath();
QString binPath = applicationDirPath();
@@ -260,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.
@@ -272,13 +318,28 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
connect(m_peerInstance, &LocalPeer::messageReceived, this, &MultiMC::messageReceived);
if(m_peerInstance->isClient())
{
+ int timeout = 2000;
+
if(m_instanceIdToLaunch.isEmpty())
{
- m_peerInstance->sendMessage("activate", 2000);
+ m_peerInstance->sendMessage("activate", timeout);
+
+ if(!m_zipToImport.isEmpty())
+ {
+ m_peerInstance->sendMessage("import " + m_zipToImport.toString(), timeout);
+ }
}
else
{
- m_peerInstance->sendMessage(m_instanceIdToLaunch, 2000);
+ 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;
@@ -339,7 +400,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
ENV.setJarsPath( TOSTRING(MULTIMC_JARS_LOCATION) );
#endif
- qDebug() << "MultiMC 5, (c) 2013-2018 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;
@@ -359,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.";
}
@@ -375,7 +440,7 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
auto payload = appID.toString().toUtf8();
if(check.write(payload) != payload.size())
{
- qWarning() << "Could not write into" << liveCheckFile;
+ qWarning() << "Could not write into" << liveCheckFile << "!";
check.remove();
break;
}
@@ -466,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");
@@ -517,9 +591,9 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
m_globalSettingsProvider->addPage<MultiMCPage>();
m_globalSettingsProvider->addPage<MinecraftPage>();
m_globalSettingsProvider->addPage<JavaPage>();
+ m_globalSettingsProvider->addPage<LanguagePage>();
m_globalSettingsProvider->addPage<CustomCommandsPage>();
m_globalSettingsProvider->addPage<ProxyPage>();
- // m_globalSettingsProvider->addPage<PackagesPage>();
m_globalSettingsProvider->addPage<ExternalToolsPage>();
m_globalSettingsProvider->addPage<AccountListPage>();
m_globalSettingsProvider->addPage<PasteEEPage>();
@@ -527,6 +601,10 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
qDebug() << "<> Settings loaded.";
}
+#ifndef QT_NO_ACCESSIBILITY
+ QAccessible::installFactory(groupViewAccessibleFactory);
+#endif /* !QT_NO_ACCESSIBILITY */
+
// load translations
{
m_translations.reset(new TranslationsModel("translations"));
@@ -550,7 +628,8 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
{
":/icons/multimc/32x32/instances/",
":/icons/multimc/50x50/instances/",
- ":/icons/multimc/128x128/instances/"
+ ":/icons/multimc/128x128/instances/",
+ ":/icons/multimc/scalable/instances/"
};
m_icons.reset(new IconList(instFolders, setting->get().toString()));
connect(setting.get(), &Setting::SettingChanged,[&](const Setting &, QVariant value)
@@ -589,12 +668,12 @@ MultiMC::MultiMC(int &argc, char **argv) : QApplication(argc, argv)
{
auto InstDirSetting = m_settings->getSetting("InstanceDir");
// instance path: check for problems with '!' in instance path and warn the user in the log
- // and rememer that we have to show him a dialog when the gui starts (if it does so)
+ // and remember that we have to show him a dialog when the gui starts (if it does so)
QString instDir = InstDirSetting->get().toString();
qDebug() << "Instance path : " << instDir;
if (FS::checkProblemticPathJava(QDir(instDir)))
{
- qWarning() << "Your instance path contains \'!\' and this is known to cause java problems";
+ qWarning() << "Your instance path contains \'!\' and this is known to cause java problems!";
}
m_instances.reset(new InstanceList(m_settings, instDir, this));
connect(InstDirSetting.get(), &Setting::SettingChanged, m_instances.get(), &InstanceList::on_InstFolderChanged);
@@ -790,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;
}
}
@@ -801,6 +891,11 @@ void MultiMC::performMainStartupAction()
showMainWindow(false);
qDebug() << "<> Main window shown.";
}
+ if(!m_zipToImport.isEmpty())
+ {
+ qDebug() << "<> Importing instance from zip:" << m_zipToImport;
+ m_mainWindow->droppedURLs({ m_zipToImport });
+ }
}
void MultiMC::showFatalErrorMessage(const QString& title, const QString& content)
@@ -837,18 +932,66 @@ void MultiMC::messageReceived(const QString& message)
qDebug() << "Received message" << message << "while still initializing. It will be ignored.";
return;
}
- if(message == "activate")
+
+ QString command = message.section(' ', 0, 0);
+
+ if(command == "activate")
{
showMainWindow();
}
- else
+ else if(command == "import")
+ {
+ QString arg = message.section(' ', 1);
+ if(arg.isEmpty())
+ {
+ qWarning() << "Received" << command << "message without a zip path/URL.";
+ return;
+ }
+ m_mainWindow->droppedURLs({ QUrl(arg) });
+ }
+ else if(command == "launch")
{
- auto inst = instances()->getInstanceById(message);
+ QString arg = message.section(' ', 1);
+ if(arg.isEmpty())
+ {
+ qWarning() << "Received" << command << "message without an instance ID.";
+ return;
+ }
+ auto inst = instances()->getInstanceById(arg);
if(inst)
{
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;
+ }
}
void MultiMC::analyticsSettingChanged(const Setting&, QVariant value)
@@ -932,11 +1075,15 @@ 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.";
+ qDebug() << "Cannot launch instances while an update is running. Please try again when updates are completed.";
}
else if(instance->canLaunch())
{
@@ -954,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);
@@ -985,7 +1133,7 @@ bool MultiMC::kill(InstancePtr instance)
{
if (!instance->isRunning())
{
- qWarning() << "Attempted to kill instance" << instance->id() << "which isn't running.";
+ qWarning() << "Attempted to kill instance" << instance->id() << ", which isn't running.";
return false;
}
auto & extras = m_instanceExtras[instance->id()];
@@ -1085,6 +1233,20 @@ void MultiMC::controllerFailed(const QString& error)
}
}
+void MultiMC::ShowGlobalSettings(class QWidget* parent, QString open_page)
+{
+ if(!m_globalSettingsProvider) {
+ return;
+ }
+ emit globalSettingsAboutToOpen();
+ {
+ SettingsObject::Lock lock(MMC->settings());
+ PageDialog dlg(m_globalSettingsProvider.get(), open_page, parent);
+ dlg.exec();
+ }
+ emit globalSettingsClosed();
+}
+
MainWindow* MultiMC::showMainWindow(bool minimized)
{
if(m_mainWindow)
diff --git a/application/MultiMC.h b/application/MultiMC.h
index 9fe98aa1..af2b41c1 100644
--- a/application/MultiMC.h
+++ b/application/MultiMC.h
@@ -6,10 +6,13 @@
#include <QFlag>
#include <QIcon>
#include <QDateTime>
+#include <QUrl>
#include <updater/GoUpdate.h>
#include <BaseInstance.h>
+#include "minecraft/launch/MinecraftServerTarget.h"
+
class LaunchController;
class LocalPeer;
class InstanceWindow;
@@ -65,11 +68,6 @@ public:
return m_settings;
}
- std::shared_ptr<GenericPageProvider> globalSettingsPages() const
- {
- return m_globalSettingsProvider;
- }
-
qint64 timeSinceStart() const
{
return startTime.msecsTo(QDateTime::currentDateTime());
@@ -146,11 +144,20 @@ public:
void updateIsRunning(bool running);
bool updatesAreAllowed();
+ void ShowGlobalSettings(class QWidget * parent, QString open_page = QString());
+
signals:
void updateAllowedChanged(bool status);
+ void globalSettingsAboutToOpen();
+ 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,8 @@ 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/SettingsUI.h b/application/SettingsUI.h
deleted file mode 100644
index 474bc1ab..00000000
--- a/application/SettingsUI.h
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-#include "pages/BasePageProvider.h"
-#include "MultiMC.h"
-#include "pagedialog/PageDialog.h"
-#include "InstancePageProvider.h"
-#include <settings/SettingsObject.h>
-#include <BaseInstance.h>
-
-/*
- * FIXME: this is a fragment. find a better place for it.
- */
-namespace SettingsUI
-{
-template <typename T>
-void ShowPageDialog(T raw_provider, QWidget * parent, QString open_page = QString())
-{
- auto provider = std::dynamic_pointer_cast<BasePageProvider>(raw_provider);
- if(!provider)
- return;
- {
- SettingsObject::Lock lock(MMC->settings());
- PageDialog dlg(provider.get(), open_page, parent);
- dlg.exec();
- }
-}
-}
diff --git a/application/VersionProxyModel.cpp b/application/VersionProxyModel.cpp
index 338a6064..5587136f 100644
--- a/application/VersionProxyModel.cpp
+++ b/application/VersionProxyModel.cpp
@@ -126,7 +126,14 @@ QVariant VersionProxyModel::data(const QModelIndex &index, int role) const
switch(column)
{
case Name:
- return sourceModel()->data(parentIndex, BaseVersionList::VersionRole);
+ {
+ QString version = sourceModel()->data(parentIndex, BaseVersionList::VersionRole).toString();
+ if(version == m_currentVersion)
+ {
+ return tr("%1 (installed)").arg(version);
+ }
+ return version;
+ }
case ParentVersion:
return sourceModel()->data(parentIndex, BaseVersionList::ParentVersionRole);
case Branch:
@@ -432,5 +439,9 @@ void VersionProxyModel::sourceRowsRemoved(const QModelIndex& parent, int first,
endRemoveRows();
}
+void VersionProxyModel::setCurrentVersion(const QString &version)
+{
+ m_currentVersion = version;
+}
#include "VersionProxyModel.moc"
diff --git a/application/VersionProxyModel.h b/application/VersionProxyModel.h
index 457acfeb..8991c31b 100644
--- a/application/VersionProxyModel.h
+++ b/application/VersionProxyModel.h
@@ -42,6 +42,7 @@ public:
void clearFilters();
QModelIndex getRecommended() const;
QModelIndex getVersion(const QString & version) const;
+ void setCurrentVersion(const QString &version);
private slots:
void sourceDataChanged(const QModelIndex &source_top_left,const QModelIndex &source_bottom_right);
@@ -62,4 +63,5 @@ private:
VersionFilterModel * filterModel;
bool hasRecommended = false;
bool hasLatest = false;
+ QString m_currentVersion;
};
diff --git a/application/dialogs/AboutDialog.cpp b/application/dialogs/AboutDialog.cpp
index 411f10fc..c97c471e 100644
--- a/application/dialogs/AboutDialog.cpp
+++ b/application/dialogs/AboutDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -23,56 +23,44 @@
#include "HoeDown.h"
+namespace {
// Credits
// This is a hack, but I can't think of a better way to do this easily without screwing with QTextDocument...
-static QString getCreditsHtml(QStringList patrons)
+QString getCreditsHtml(QStringList patrons)
{
- QString creditsHtml = QObject::tr(
- "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0//EN' 'http://www.w3.org/TR/REC-html40/strict.dtd'>"
- "<html>"
- ""
- "<head>"
- "<meta name='qrichtext' content='1' />"
- "<style type='text/css'>"
- "p { white-space: pre-wrap; margin-top:2px; margin-bottom:2px; }"
- "</style>"
- "</head>"
- ""
- "<body style=' font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;'>"
- ""
- "<h3>MultiMC Developers</h3>"
- "<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>"
- "<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>"
- "<p>Sky Welch &lt;<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>&gt;</p>"
- "<p>Jan (02JanDal) &lt;<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>&gt;</p>"
- "<p>RoboSky &lt;<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>&gt;</p>"
- ""
- "<h3>With thanks to</h3>"
- "<p>Orochimarufan &lt;<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>&gt;</p>"
- "<p>TakSuyu &lt;<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>&gt;</p>"
- "<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>"
- "<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt;</p>"
- ""
- "<h3>Patrons</h3>"
- "%1"
- ""
- "</body>"
- "</html>");
- if (patrons.isEmpty())
- return creditsHtml.arg(QObject::tr("<p>Loading...</p>"));
- else
- {
- QString patronsStr;
+ QString patronsHeading = QObject::tr("Patrons", "About Credits");
+ QString output;
+ QTextStream stream(&output);
+ stream << "<center>\n";
+ // TODO: possibly retrieve from git history at build time?
+ stream << "<h3>" << QObject::tr("MultiMC Developers", "About Credits") << "</h3>\n";
+ stream << "<p>Andrew Okin &lt;<a href='mailto:forkk@forkk.net'>forkk@forkk.net</a>&gt;</p>\n";
+ stream << "<p>Petr Mrázek &lt;<a href='mailto:peterix@gmail.com'>peterix@gmail.com</a>&gt;</p>\n";
+ stream << "<p>Sky Welch &lt;<a href='mailto:multimc@bunnies.io'>multimc@bunnies.io</a>&gt;</p>\n";
+ stream << "<p>Jan (02JanDal) &lt;<a href='mailto:02jandal@gmail.com'>02jandal@gmail.com</a>&gt;</p>\n";
+ stream << "<p>RoboSky &lt;<a href='https://twitter.com/RoboSky_'>@RoboSky_</a>&gt;</p>\n";
+ stream << "<br />\n";
+
+ stream << "<h3>" << QObject::tr("With thanks to", "About Credits") << "</h3>\n";
+ stream << "<p>Orochimarufan &lt;<a href='mailto:orochimarufan.x3@gmail.com'>orochimarufan.x3@gmail.com</a>&gt;</p>\n";
+ stream << "<p>TakSuyu &lt;<a href='mailto:taksuyu@gmail.com'>taksuyu@gmail.com</a>&gt;</p>\n";
+ stream << "<p>Kilobyte &lt;<a href='mailto:stiepen22@gmx.de'>stiepen22@gmx.de</a>&gt;</p>\n";
+ stream << "<p>Rootbear75 &lt;<a href='https://twitter.com/rootbear75'>@rootbear75</a>&gt;</p>\n";
+ stream << "<p>Zeker Zhayard &lt;<a href='https://twitter.com/zeker_zhayard'>@Zeker_Zhayard</a>&gt;</p>\n";
+ stream << "<br />\n";
+
+ if(!patrons.isEmpty()) {
+ stream << "<h3>" << QObject::tr("Patrons", "About Credits") << "</h3>\n";
for (QString patron : patrons)
{
- patronsStr.append(QString("<p>%1</p>").arg(patron));
+ stream << "<p>" << patron << "</p>\n";
}
-
- return creditsHtml.arg(patronsStr);
}
+ stream << "</center>\n";
+ return output;
}
-static QString getLicenseHtml()
+QString getLicenseHtml()
{
HoeDown hoedown;
QFile dataFile(":/documents/COPYING.md");
@@ -81,6 +69,8 @@ static QString getLicenseHtml()
return output;
}
+}
+
AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDialog)
{
ui->setupUi(this);
@@ -109,6 +99,15 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia
else
ui->channelLabel->setVisible(false);
+ ui->redistributionText->setHtml(tr(
+"<p>We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.</p>\n"
+"<p>Part of the reason for using the Apache license is we don't want people using the &quot;MultiMC&quot; name when redistributing the project. "
+"This means people must take the time to go through the source code and remove all references to &quot;MultiMC&quot;, including but not limited to the project "
+"icon and the title of windows, (no <b>MultiMC-fork</b> in the title).</p>\n"
+"<p>The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. "
+"However, it should be abundantly clear that the project is a fork <b>without</b> implying that you have our blessing.</p>"
+ ));
+
connect(ui->closeButton, SIGNAL(clicked()), SLOT(close()));
connect(ui->aboutQt, &QPushButton::clicked, &QApplication::aboutQt);
@@ -124,7 +123,7 @@ AboutDialog::~AboutDialog()
void AboutDialog::loadPatronList()
{
netJob.reset(new NetJob("Patreon Patron List"));
- netJob->addNetAction(Net::Download::makeByteArray(QUrl("http://files.multimc.org/patrons.txt"), &dataSink));
+ netJob->addNetAction(Net::Download::makeByteArray(QUrl("https://files.multimc.org/patrons.txt"), &dataSink));
connect(netJob.get(), &NetJob::succeeded, this, &AboutDialog::patronListLoaded);
netJob->start();
}
diff --git a/application/dialogs/AboutDialog.h b/application/dialogs/AboutDialog.h
index 96624041..c7621c37 100644
--- a/application/dialogs/AboutDialog.h
+++ b/application/dialogs/AboutDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 5e8e3e68..c6de9ebb 100644
--- a/application/dialogs/AboutDialog.ui
+++ b/application/dialogs/AboutDialog.ui
@@ -165,7 +165,7 @@
</font>
</property>
<property name="text">
- <string>© 2012-2018 MultiMC Contributors</string>
+ <string>© 2012-2021 MultiMC Contributors</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -180,7 +180,7 @@
</font>
</property>
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://github.com/MultiMC/MultiMC5&quot;&gt;http://github.com/MultiMC/MultiMC5&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/MultiMC/MultiMC5&quot;&gt;https://github.com/MultiMC/MultiMC5&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
@@ -212,28 +212,11 @@
<property name="readOnly">
<bool>true</bool>
</property>
- <property name="html">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
- <item>
- <widget class="QLineEdit" name="translationInfo">
- <property name="text">
- <string extracomment="Hey, Translator, feel free to put credit to you here">No Language file loaded.</string>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
</layout>
</widget>
<widget class="QWidget" name="licenseTab">
@@ -257,13 +240,6 @@ p, li { white-space: pre-wrap; }
<property name="readOnly">
<bool>true</bool>
</property>
- <property name="html">
- <string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'DejaVu Sans Mono'; font-size:12pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
@@ -277,18 +253,7 @@ p, li { white-space: pre-wrap; }
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <widget class="QTextEdit" name="textEdit">
- <property name="html">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;We keep MultiMC open source because we think it's important to be able to see the source code for a project like this, and we do so using the Apache license.&lt;/span&gt;&lt;/p&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;Part of the reason for using the Apache license is we don't want people using the &amp;quot;MultiMC&amp;quot; name when redistributing the project. This means people must take the time to go through the source code and remove all references to &amp;quot;MultiMC&amp;quot;, including but not limited to the project icon and the title of windows, (no *MultiMC-fork* in the title).&lt;/span&gt;&lt;/p&gt;
-&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt;The Apache license covers reasonable use for the name - a mention of the project's origins in the About dialog and the license is acceptable. However, it should be abundantly clear that the project is a fork &lt;/span&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:600;&quot;&gt;without&lt;/span&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:11pt;&quot;&gt; implying that you have our blessing.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
+ <widget class="QTextEdit" name="redistributionText">
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
@@ -337,14 +302,11 @@ p, li { white-space: pre-wrap; }
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>creditsText</tabstop>
- <tabstop>translationInfo</tabstop>
<tabstop>licenseText</tabstop>
- <tabstop>textEdit</tabstop>
+ <tabstop>redistributionText</tabstop>
<tabstop>aboutQt</tabstop>
<tabstop>closeButton</tabstop>
</tabstops>
- <resources>
- <include location="../../resources/multimc/multimc.qrc"/>
- </resources>
+ <resources/>
<connections/>
</ui>
diff --git a/application/dialogs/CopyInstanceDialog.cpp b/application/dialogs/CopyInstanceDialog.cpp
index 78f7512e..5fe90334 100644
--- a/application/dialogs/CopyInstanceDialog.cpp
+++ b/application/dialogs/CopyInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -53,6 +53,7 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->groupBox->setCurrentIndex(index);
ui->groupBox->lineEdit()->setPlaceholderText(tr("No group"));
ui->copySavesCheckbox->setChecked(m_copySaves);
+ ui->keepPlaytimeCheckbox->setChecked(m_keepPlaytime);
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -123,3 +124,21 @@ void CopyInstanceDialog::on_copySavesCheckbox_stateChanged(int state)
m_copySaves = true;
}
}
+
+bool CopyInstanceDialog::shouldKeepPlaytime() const
+{
+ return m_keepPlaytime;
+}
+
+
+void CopyInstanceDialog::on_keepPlaytimeCheckbox_stateChanged(int state)
+{
+ if(state == Qt::Unchecked)
+ {
+ m_keepPlaytime = false;
+ }
+ else if(state == Qt::Checked)
+ {
+ m_keepPlaytime = true;
+ }
+}
diff --git a/application/dialogs/CopyInstanceDialog.h b/application/dialogs/CopyInstanceDialog.h
index 83ef2b3a..bf3cd920 100644
--- a/application/dialogs/CopyInstanceDialog.h
+++ b/application/dialogs/CopyInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -40,16 +40,19 @@ public:
QString instGroup() const;
QString iconKey() const;
bool shouldCopySaves() const;
+ bool shouldKeepPlaytime() const;
private
slots:
void on_iconButton_clicked();
void on_instNameTextBox_textChanged(const QString &arg1);
void on_copySavesCheckbox_stateChanged(int state);
+ void on_keepPlaytimeCheckbox_stateChanged(int state);
private:
Ui::CopyInstanceDialog *ui;
QString InstIconKey;
InstancePtr m_original;
bool m_copySaves = true;
+ bool m_keepPlaytime = true;
};
diff --git a/application/dialogs/CopyInstanceDialog.ui b/application/dialogs/CopyInstanceDialog.ui
index bbb1bbb3..fa675455 100644
--- a/application/dialogs/CopyInstanceDialog.ui
+++ b/application/dialogs/CopyInstanceDialog.ui
@@ -10,7 +10,7 @@
<x>0</x>
<y>0</y>
<width>345</width>
- <height>240</height>
+ <height>323</height>
</rect>
</property>
<property name="windowTitle">
@@ -117,6 +117,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="keepPlaytimeCheckbox">
+ <property name="text">
+ <string>Keep play time</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -128,6 +135,13 @@
</item>
</layout>
</widget>
+ <tabstops>
+ <tabstop>iconButton</tabstop>
+ <tabstop>instNameTextBox</tabstop>
+ <tabstop>groupBox</tabstop>
+ <tabstop>copySavesCheckbox</tabstop>
+ <tabstop>keepPlaytimeCheckbox</tabstop>
+ </tabstops>
<resources>
<include location="../../graphics.qrc"/>
</resources>
diff --git a/application/dialogs/CustomMessageBox.cpp b/application/dialogs/CustomMessageBox.cpp
index 0ef3c8ce..19029f68 100644
--- a/application/dialogs/CustomMessageBox.cpp
+++ b/application/dialogs/CustomMessageBox.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 09e7b46a..712c6518 100644
--- a/application/dialogs/CustomMessageBox.h
+++ b/application/dialogs/CustomMessageBox.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 5ab3b025..002c064b 100644
--- a/application/dialogs/EditAccountDialog.cpp
+++ b/application/dialogs/EditAccountDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 1346d00e..6b5eb4aa 100644
--- a/application/dialogs/EditAccountDialog.h
+++ b/application/dialogs/EditAccountDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.ui b/application/dialogs/EditAccountDialog.ui
index 85e235ce..e87509bc 100644
--- a/application/dialogs/EditAccountDialog.ui
+++ b/application/dialogs/EditAccountDialog.ui
@@ -30,7 +30,7 @@
<item>
<widget class="QLineEdit" name="userTextBox">
<property name="placeholderText">
- <string>Email / Username</string>
+ <string>Email</string>
</property>
</widget>
</item>
diff --git a/application/dialogs/ExportInstanceDialog.cpp b/application/dialogs/ExportInstanceDialog.cpp
index a9045829..a42779d4 100644
--- a/application/dialogs/ExportInstanceDialog.cpp
+++ b/application/dialogs/ExportInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -343,43 +343,37 @@ void SaveIcon(InstancePtr m_instance)
auto iconKey = m_instance->iconKey();
auto iconList = MMC->icons();
auto mmcIcon = iconList->icon(iconKey);
- if(mmcIcon)
+ if(!mmcIcon || mmcIcon->isBuiltIn()) {
+ return;
+ }
+ auto path = mmcIcon->getFilePath();
+ if(!path.isNull()) {
+ QFileInfo inInfo (path);
+ FS::copy(path, FS::PathCombine(m_instance->instanceRoot(), inInfo.fileName())) ();
+ return;
+ }
+ auto & image = mmcIcon->m_images[mmcIcon->type()];
+ auto & icon = image.icon;
+ auto sizes = icon.availableSizes();
+ if(sizes.size() == 0)
{
- bool saveIcon = false;
- switch(mmcIcon->type())
- {
- case IconType::FileBased:
- case IconType::Transient:
- saveIcon = true;
- default:
- break;
- }
- if(saveIcon)
+ return;
+ }
+ auto areaOf = [](QSize size)
+ {
+ return size.width() * size.height();
+ };
+ QSize largest = sizes[0];
+ // find variant with largest area
+ for(auto size: sizes)
+ {
+ if(areaOf(largest) < areaOf(size))
{
- auto & image = mmcIcon->m_images[mmcIcon->type()];
- auto & icon = image.icon;
- auto sizes = icon.availableSizes();
- if(sizes.size() == 0)
- {
- return;
- }
- auto areaOf = [](QSize size)
- {
- return size.width() * size.height();
- };
- QSize largest = sizes[0];
- // find variant with largest area
- for(auto size: sizes)
- {
- if(areaOf(largest) < areaOf(size))
- {
- largest = size;
- }
- }
- auto pixmap = icon.pixmap(largest);
- pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png"));
+ largest = size;
}
}
+ auto pixmap = icon.pixmap(largest);
+ pixmap.save(FS::PathCombine(m_instance->instanceRoot(), iconKey + ".png"));
}
bool ExportInstanceDialog::doExport()
diff --git a/application/dialogs/ExportInstanceDialog.h b/application/dialogs/ExportInstanceDialog.h
index 4032314d..dea02d1b 100644
--- a/application/dialogs/ExportInstanceDialog.h
+++ b/application/dialogs/ExportInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 b4260861..90436554 100644
--- a/application/dialogs/IconPickerDialog.cpp
+++ b/application/dialogs/IconPickerDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -25,6 +25,7 @@
#include "groupview/InstanceDelegate.h"
#include "icons/IconList.h"
+#include "icons/IconUtils.h"
#include <DesktopServices.h>
IconPickerDialog::IconPickerDialog(QWidget *parent)
@@ -103,8 +104,8 @@ void IconPickerDialog::addNewIcon()
//: The title of the select icons open file dialog
QString selectIcons = tr("Select Icons");
//: The type of icon files
- QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(),
- tr("Icons") + "(*.png *.jpg *.jpeg *.ico *.svg *.gif)");
+ auto filter = IconUtils::getIconFilter();
+ QStringList fileNames = QFileDialog::getOpenFileNames(this, selectIcons, QString(), tr("Icons %1").arg(filter));
MMC->icons()->installIcons(fileNames);
}
diff --git a/application/dialogs/IconPickerDialog.h b/application/dialogs/IconPickerDialog.h
index c76f9a34..9af6a678 100644
--- a/application/dialogs/IconPickerDialog.h
+++ b/application/dialogs/IconPickerDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 276aebb9..32f8a48f 100644
--- a/application/dialogs/LoginDialog.cpp
+++ b/application/dialogs/LoginDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 355599ec..16bdddfb 100644
--- a/application/dialogs/LoginDialog.h
+++ b/application/dialogs/LoginDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 99b98215..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>
@@ -36,7 +46,7 @@
<item>
<widget class="QLineEdit" name="userTextBox">
<property name="placeholderText">
- <string>Email / Username</string>
+ <string>Email</string>
</property>
</widget>
</item>
diff --git a/application/dialogs/ModEditDialogCommon.cpp b/application/dialogs/ModEditDialogCommon.cpp
deleted file mode 100644
index e92c5c4d..00000000
--- a/application/dialogs/ModEditDialogCommon.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#include "ModEditDialogCommon.h"
-#include "CustomMessageBox.h"
-#include <QUrl>
-
-bool lastfirst(QModelIndexList &list, int &first, int &last)
-{
- if (list.isEmpty())
- return false;
- first = last = list[0].row();
- for (auto item : list)
- {
- int row = item.row();
- if (row < first)
- first = row;
- if (row > last)
- last = row;
- }
- return true;
-}
-
-void showWebsiteForMod(QWidget *parentDlg, Mod &m)
-{
- QString url = m.homeurl();
- if (url.size())
- {
- // catch the cases where the protocol is missing
- if (!url.startsWith("http"))
- {
- url = "http://" + url;
- }
- DesktopServices::openUrl(url);
- }
- else
- {
- CustomMessageBox::selectable(
- parentDlg, QObject::tr("How sad!"),
- QObject::tr("The mod author didn't provide a website link for this mod."),
- QMessageBox::Warning);
- }
-}
diff --git a/application/dialogs/ModEditDialogCommon.h b/application/dialogs/ModEditDialogCommon.h
deleted file mode 100644
index fc5e3c2b..00000000
--- a/application/dialogs/ModEditDialogCommon.h
+++ /dev/null
@@ -1,9 +0,0 @@
-#pragma once
-#include <QModelIndex>
-#include <DesktopServices.h>
-#include <QWidget>
-#include <minecraft/Mod.h>
-
-bool lastfirst(QModelIndexList &list, int &first, int &last);
-
-void showWebsiteForMod(QWidget *parentDlg, Mod &m);
diff --git a/application/dialogs/NewComponentDialog.cpp b/application/dialogs/NewComponentDialog.cpp
index c82ba62b..f4d6274f 100644
--- a/application/dialogs/NewComponentDialog.cpp
+++ b/application/dialogs/NewComponentDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 23192605..8c790beb 100644
--- a/application/dialogs/NewComponentDialog.h
+++ b/application/dialogs/NewComponentDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.ui b/application/dialogs/NewComponentDialog.ui
index b30c2431..03b0d222 100644
--- a/application/dialogs/NewComponentDialog.ui
+++ b/application/dialogs/NewComponentDialog.ui
@@ -14,7 +14,7 @@
</rect>
</property>
<property name="windowTitle">
- <string>Copy Instance</string>
+ <string>Add Empty Component</string>
</property>
<property name="windowIcon">
<iconset>
diff --git a/application/dialogs/NewInstanceDialog.cpp b/application/dialogs/NewInstanceDialog.cpp
index bdcd1bce..86963149 100644
--- a/application/dialogs/NewInstanceDialog.cpp
+++ b/application/dialogs/NewInstanceDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,10 +34,14 @@
#include "widgets/PageContainer.h"
#include <pages/modplatform/VanillaPage.h>
-#include <pages/modplatform/FTBPage.h>
-#include <pages/modplatform/TwitchPage.h>
+#include <pages/modplatform/atlauncher/AtlPage.h>
+#include <pages/modplatform/ftb/FtbPage.h>
+#include <pages/modplatform/legacy_ftb/Page.h>
+#include <pages/modplatform/flame/FlamePage.h>
#include <pages/modplatform/ImportPage.h>
-#include <pages/modplatform/TechnicPage.h>
+#include <pages/modplatform/technic/TechnicPage.h>
+
+
NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString & url, QWidget *parent)
: QDialog(parent), ui(new Ui::NewInstanceDialog)
@@ -94,6 +98,7 @@ NewInstanceDialog::NewInstanceDialog(const QString & initialGroup, const QString
if(!url.isEmpty())
{
+ QUrl actualUrl(url);
m_container->selectPage("import");
importPage->setUrl(url);
}
@@ -119,13 +124,17 @@ void NewInstanceDialog::accept()
QList<BasePage *> NewInstanceDialog::getPages()
{
importPage = new ImportPage(this);
+ flamePage = new FlamePage(this);
+ auto technicPage = new TechnicPage(this);
return
{
new VanillaPage(this),
- new FTBPage(this),
importPage,
- new TwitchPage(this),
- new TechnicPage(this)
+ new AtlPage(this),
+ flamePage,
+ new FtbPage(this),
+ new LegacyFTB::Page(this),
+ technicPage
};
}
@@ -164,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 5d4ec5ed..53abf8cf 100644
--- a/application/dialogs/NewInstanceDialog.h
+++ b/application/dialogs/NewInstanceDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,6 +29,7 @@ class NewInstanceDialog;
class PageContainer;
class QDialogButtonBox;
class ImportPage;
+class FlamePage;
class NewInstanceDialog : public QDialog, public BasePageProvider
{
@@ -42,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();
@@ -67,6 +69,7 @@ private:
QString InstIconKey;
ImportPage *importPage = 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 bf31ed02..ae34709f 100644
--- a/application/dialogs/ProfileSelectDialog.cpp
+++ b/application/dialogs/ProfileSelectDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 d4017eb3..9f95830c 100644
--- a/application/dialogs/ProfileSelectDialog.h
+++ b/application/dialogs/ProfileSelectDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 0951607d..4b092859 100644
--- a/application/dialogs/ProgressDialog.cpp
+++ b/application/dialogs/ProgressDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 6856f4c0..b28ad4fa 100644
--- a/application/dialogs/ProgressDialog.h
+++ b/application/dialogs/ProgressDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.cpp b/application/dialogs/UpdateDialog.cpp
index 242a5b70..2baaf5e9 100644
--- a/application/dialogs/UpdateDialog.cpp
+++ b/application/dialogs/UpdateDialog.cpp
@@ -22,6 +22,7 @@ UpdateDialog::UpdateDialog(bool hasUpdate, QWidget *parent) : QDialog(parent), u
ui->btnUpdateNow->setHidden(true);
ui->btnUpdateLater->setText(tr("Close"));
}
+ ui->changelogBrowser->setHtml(tr("<center><h1>Loading changelog...</h1></center>"));
loadChangelog();
restoreGeometry(QByteArray::fromBase64(MMC->settings()->get("UpdateDialogGeometry").toByteArray()));
}
diff --git a/application/dialogs/UpdateDialog.h b/application/dialogs/UpdateDialog.h
index 6ee3df1d..ae1799c3 100644
--- a/application/dialogs/UpdateDialog.h
+++ b/application/dialogs/UpdateDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/UpdateDialog.ui b/application/dialogs/UpdateDialog.ui
index 8e34a521..b0b3dd83 100644
--- a/application/dialogs/UpdateDialog.ui
+++ b/application/dialogs/UpdateDialog.ui
@@ -42,13 +42,6 @@
</item>
<item>
<widget class="QTextBrowser" name="changelogBrowser">
- <property name="html">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Noto Sans'; font-size:12pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p align=&quot;center&quot; style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Bitstream Vera Sans'; font-size:22pt;&quot;&gt;Loading changelog...&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
diff --git a/application/dialogs/VersionSelectDialog.cpp b/application/dialogs/VersionSelectDialog.cpp
index 59287ee9..ed1210ba 100644
--- a/application/dialogs/VersionSelectDialog.cpp
+++ b/application/dialogs/VersionSelectDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 8b5d108f..ed30d3f3 100644
--- a/application/dialogs/VersionSelectDialog.h
+++ b/application/dialogs/VersionSelectDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/AccessibleGroupView.cpp b/application/groupview/AccessibleGroupView.cpp
new file mode 100644
index 00000000..c6541f18
--- /dev/null
+++ b/application/groupview/AccessibleGroupView.cpp
@@ -0,0 +1,778 @@
+#include "GroupView.h"
+#include "AccessibleGroupView.h"
+#include "AccessibleGroupView_p.h"
+
+#include <qvariant.h>
+#include <qaccessible.h>
+#include <qheaderview.h>
+
+#ifndef QT_NO_ACCESSIBILITY
+
+QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object)
+{
+ QAccessibleInterface *iface = 0;
+ if (!object || !object->isWidgetType())
+ return iface;
+
+ QWidget *widget = static_cast<QWidget*>(object);
+
+ if (classname == QLatin1String("GroupView")) {
+ iface = new AccessibleGroupView((GroupView *)widget);
+ }
+ return iface;
+}
+
+
+QAbstractItemView *AccessibleGroupView::view() const
+{
+ return qobject_cast<QAbstractItemView*>(object());
+}
+
+int AccessibleGroupView::logicalIndex(const QModelIndex &index) const
+{
+ if (!view()->model() || !index.isValid())
+ return -1;
+ return index.row() * (index.model()->columnCount()) + index.column();
+}
+
+AccessibleGroupView::AccessibleGroupView(QWidget *w)
+ : QAccessibleObject(w)
+{
+ Q_ASSERT(view());
+}
+
+bool AccessibleGroupView::isValid() const
+{
+ return view();
+}
+
+AccessibleGroupView::~AccessibleGroupView()
+{
+ for (QAccessible::Id id : childToId) {
+ QAccessible::deleteAccessibleInterface(id);
+ }
+}
+
+QAccessibleInterface *AccessibleGroupView::cellAt(int row, int column) const
+{
+ if (!view()->model()) {
+ return 0;
+ }
+
+ QModelIndex index = view()->model()->index(row, column, view()->rootIndex());
+ if (Q_UNLIKELY(!index.isValid())) {
+ qWarning() << "AccessibleGroupView::cellAt: invalid index: " << index << " for " << view();
+ return 0;
+ }
+
+ return child(logicalIndex(index));
+}
+
+QAccessibleInterface *AccessibleGroupView::caption() const
+{
+ return 0;
+}
+
+QString AccessibleGroupView::columnDescription(int column) const
+{
+ if (!view()->model())
+ return QString();
+
+ return view()->model()->headerData(column, Qt::Horizontal).toString();
+}
+
+int AccessibleGroupView::columnCount() const
+{
+ if (!view()->model())
+ return 0;
+ return 1;
+}
+
+int AccessibleGroupView::rowCount() const
+{
+ if (!view()->model())
+ return 0;
+ return view()->model()->rowCount();
+}
+
+int AccessibleGroupView::selectedCellCount() const
+{
+ if (!view()->selectionModel())
+ return 0;
+ return view()->selectionModel()->selectedIndexes().count();
+}
+
+int AccessibleGroupView::selectedColumnCount() const
+{
+ if (!view()->selectionModel())
+ return 0;
+ return view()->selectionModel()->selectedColumns().count();
+}
+
+int AccessibleGroupView::selectedRowCount() const
+{
+ if (!view()->selectionModel())
+ return 0;
+ return view()->selectionModel()->selectedRows().count();
+}
+
+QString AccessibleGroupView::rowDescription(int row) const
+{
+ if (!view()->model())
+ return QString();
+ return view()->model()->headerData(row, Qt::Vertical).toString();
+}
+
+QList<QAccessibleInterface *> AccessibleGroupView::selectedCells() const
+{
+ QList<QAccessibleInterface*> cells;
+ if (!view()->selectionModel())
+ return cells;
+ const QModelIndexList selectedIndexes = view()->selectionModel()->selectedIndexes();
+ cells.reserve(selectedIndexes.size());
+ for (const QModelIndex &index : selectedIndexes)
+ cells.append(child(logicalIndex(index)));
+ return cells;
+}
+
+QList<int> AccessibleGroupView::selectedColumns() const
+{
+ if (!view()->selectionModel()) {
+ return QList<int>();
+ }
+
+ const QModelIndexList selectedColumns = view()->selectionModel()->selectedColumns();
+
+ QList<int> columns;
+ columns.reserve(selectedColumns.size());
+ for (const QModelIndex &index : selectedColumns) {
+ columns.append(index.column());
+ }
+
+ return columns;
+}
+
+QList<int> AccessibleGroupView::selectedRows() const
+{
+ if (!view()->selectionModel()) {
+ return QList<int>();
+ }
+
+ QList<int> rows;
+
+ const QModelIndexList selectedRows = view()->selectionModel()->selectedRows();
+
+ rows.reserve(selectedRows.size());
+ for (const QModelIndex &index : selectedRows) {
+ rows.append(index.row());
+ }
+
+ return rows;
+}
+
+QAccessibleInterface *AccessibleGroupView::summary() const
+{
+ return 0;
+}
+
+bool AccessibleGroupView::isColumnSelected(int column) const
+{
+ if (!view()->selectionModel()) {
+ return false;
+ }
+
+ return view()->selectionModel()->isColumnSelected(column, QModelIndex());
+}
+
+bool AccessibleGroupView::isRowSelected(int row) const
+{
+ if (!view()->selectionModel()) {
+ return false;
+ }
+
+ return view()->selectionModel()->isRowSelected(row, QModelIndex());
+}
+
+bool AccessibleGroupView::selectRow(int row)
+{
+ if (!view()->model() || !view()->selectionModel()) {
+ return false;
+ }
+ QModelIndex index = view()->model()->index(row, 0, view()->rootIndex());
+
+ if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectColumns) {
+ return false;
+ }
+
+ switch (view()->selectionMode()) {
+ case QAbstractItemView::NoSelection: {
+ return false;
+ }
+ case QAbstractItemView::SingleSelection: {
+ if (view()->selectionBehavior() != QAbstractItemView::SelectRows && columnCount() > 1 )
+ return false;
+ view()->clearSelection();
+ break;
+ }
+ case QAbstractItemView::ContiguousSelection: {
+ if ((!row || !view()->selectionModel()->isRowSelected(row - 1, view()->rootIndex())) && !view()->selectionModel()->isRowSelected(row + 1, view()->rootIndex())) {
+ view()->clearSelection();
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
+ return true;
+}
+
+bool AccessibleGroupView::selectColumn(int column)
+{
+ if (!view()->model() || !view()->selectionModel()) {
+ return false;
+ }
+ QModelIndex index = view()->model()->index(0, column, view()->rootIndex());
+
+ if (!index.isValid() || view()->selectionBehavior() == QAbstractItemView::SelectRows) {
+ return false;
+ }
+
+ switch (view()->selectionMode()) {
+ case QAbstractItemView::NoSelection: {
+ return false;
+ }
+ case QAbstractItemView::SingleSelection: {
+ if (view()->selectionBehavior() != QAbstractItemView::SelectColumns && rowCount() > 1) {
+ return false;
+ }
+ // fallthrough intentional
+ }
+ case QAbstractItemView::ContiguousSelection: {
+ if ((!column || !view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex())) && !view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
+ view()->clearSelection();
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ view()->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Columns);
+ return true;
+}
+
+bool AccessibleGroupView::unselectRow(int row)
+{
+ if (!view()->model() || !view()->selectionModel()) {
+ return false;
+ }
+
+ QModelIndex index = view()->model()->index(row, 0, view()->rootIndex());
+ if (!index.isValid()) {
+ return false;
+ }
+
+ QItemSelection selection(index, index);
+ auto selectionModel = view()->selectionModel();
+
+ switch (view()->selectionMode()) {
+ case QAbstractItemView::SingleSelection:
+ // no unselect
+ if (selectedRowCount() == 1) {
+ return false;
+ }
+ break;
+ case QAbstractItemView::ContiguousSelection: {
+ // no unselect
+ if (selectedRowCount() == 1) {
+ return false;
+ }
+
+
+ if ((!row || selectionModel->isRowSelected(row - 1, view()->rootIndex())) && selectionModel->isRowSelected(row + 1, view()->rootIndex())) {
+ //If there are rows selected both up the current row and down the current rown,
+ //the ones which are down the current row will be deselected
+ selection = QItemSelection(index, view()->model()->index(rowCount() - 1, 0, view()->rootIndex()));
+ }
+ }
+ default: {
+ break;
+ }
+ }
+
+ selectionModel->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
+ return true;
+}
+
+bool AccessibleGroupView::unselectColumn(int column)
+{
+ auto model = view()->model();
+ if (!model || !view()->selectionModel()) {
+ return false;
+ }
+
+ QModelIndex index = model->index(0, column, view()->rootIndex());
+ if (!index.isValid()) {
+ return false;
+ }
+
+ QItemSelection selection(index, index);
+
+ switch (view()->selectionMode()) {
+ case QAbstractItemView::SingleSelection: {
+ //In SingleSelection and ContiguousSelection once an item
+ //is selected, there's no way for the user to unselect all items
+ if (selectedColumnCount() == 1) {
+ return false;
+ }
+ break;
+ }
+ case QAbstractItemView::ContiguousSelection:
+ if (selectedColumnCount() == 1) {
+ return false;
+ }
+
+ if ((!column || view()->selectionModel()->isColumnSelected(column - 1, view()->rootIndex()))
+ && view()->selectionModel()->isColumnSelected(column + 1, view()->rootIndex())) {
+ //If there are columns selected both at the left of the current row and at the right
+ //of the current row, the ones which are at the right will be deselected
+ selection = QItemSelection(index, model->index(0, columnCount() - 1, view()->rootIndex()));
+ }
+ default:
+ break;
+ }
+
+ view()->selectionModel()->select(selection, QItemSelectionModel::Deselect | QItemSelectionModel::Columns);
+ return true;
+}
+
+QAccessible::Role AccessibleGroupView::role() const
+{
+ return QAccessible::List;
+}
+
+QAccessible::State AccessibleGroupView::state() const
+{
+ return QAccessible::State();
+}
+
+QAccessibleInterface *AccessibleGroupView::childAt(int x, int y) const
+{
+ QPoint viewportOffset = view()->viewport()->mapTo(view(), QPoint(0,0));
+ QPoint indexPosition = view()->mapFromGlobal(QPoint(x, y) - viewportOffset);
+ // FIXME: if indexPosition < 0 in one coordinate, return header
+
+ QModelIndex index = view()->indexAt(indexPosition);
+ if (index.isValid()) {
+ return child(logicalIndex(index));
+ }
+ return 0;
+}
+
+int AccessibleGroupView::childCount() const
+{
+ if (!view()->model()) {
+ return 0;
+ }
+ return (view()->model()->rowCount()) * (view()->model()->columnCount());
+}
+
+int AccessibleGroupView::indexOfChild(const QAccessibleInterface *iface) const
+{
+ if (!view()->model())
+ return -1;
+ QAccessibleInterface *parent = iface->parent();
+ if (parent->object() != view())
+ return -1;
+
+ Q_ASSERT(iface->role() != QAccessible::TreeItem); // should be handled by tree class
+ if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
+ const AccessibleGroupViewItem* cell = static_cast<const AccessibleGroupViewItem*>(iface);
+ return logicalIndex(cell->m_index);
+ } else if (iface->role() == QAccessible::Pane) {
+ return 0; // corner button
+ } else {
+ qWarning() << "AccessibleGroupView::indexOfChild has a child with unknown role..." << iface->role() << iface->text(QAccessible::Name);
+ }
+ // FIXME: we are in denial of our children. this should stop.
+ return -1;
+}
+
+QString AccessibleGroupView::text(QAccessible::Text t) const
+{
+ if (t == QAccessible::Description)
+ return view()->accessibleDescription();
+ return view()->accessibleName();
+}
+
+QRect AccessibleGroupView::rect() const
+{
+ if (!view()->isVisible())
+ return QRect();
+ QPoint pos = view()->mapToGlobal(QPoint(0, 0));
+ return QRect(pos.x(), pos.y(), view()->width(), view()->height());
+}
+
+QAccessibleInterface *AccessibleGroupView::parent() const
+{
+ if (view() && view()->parent()) {
+ if (qstrcmp("QComboBoxPrivateContainer", view()->parent()->metaObject()->className()) == 0) {
+ return QAccessible::queryAccessibleInterface(view()->parent()->parent());
+ }
+ return QAccessible::queryAccessibleInterface(view()->parent());
+ }
+ return 0;
+}
+
+QAccessibleInterface *AccessibleGroupView::child(int logicalIndex) const
+{
+ if (!view()->model())
+ return 0;
+
+ auto id = childToId.constFind(logicalIndex);
+ if (id != childToId.constEnd())
+ return QAccessible::accessibleInterface(id.value());
+
+ int columns = view()->model()->columnCount();
+
+ int row = logicalIndex / columns;
+ int column = logicalIndex % columns;
+
+ QAccessibleInterface *iface = 0;
+
+ QModelIndex index = view()->model()->index(row, column, view()->rootIndex());
+ if (Q_UNLIKELY(!index.isValid())) {
+ qWarning("AccessibleGroupView::child: Invalid index at: %d %d", row, column);
+ return 0;
+ }
+ iface = new AccessibleGroupViewItem(view(), index);
+
+ QAccessible::registerAccessibleInterface(iface);
+ childToId.insert(logicalIndex, QAccessible::uniqueId(iface));
+ return iface;
+}
+
+void *AccessibleGroupView::interface_cast(QAccessible::InterfaceType t)
+{
+ if (t == QAccessible::TableInterface)
+ return static_cast<QAccessibleTableInterface*>(this);
+ return 0;
+}
+
+void AccessibleGroupView::modelChange(QAccessibleTableModelChangeEvent *event)
+{
+ // if there is no cache yet, we don't update anything
+ if (childToId.isEmpty())
+ return;
+
+ switch (event->modelChangeType()) {
+ case QAccessibleTableModelChangeEvent::ModelReset:
+ for (QAccessible::Id id : childToId)
+ QAccessible::deleteAccessibleInterface(id);
+ childToId.clear();
+ break;
+
+ // rows are inserted: move every row after that
+ case QAccessibleTableModelChangeEvent::RowsInserted:
+ case QAccessibleTableModelChangeEvent::ColumnsInserted: {
+
+ ChildCache newCache;
+ ChildCache::ConstIterator iter = childToId.constBegin();
+
+ while (iter != childToId.constEnd()) {
+ QAccessible::Id id = iter.value();
+ QAccessibleInterface *iface = QAccessible::accessibleInterface(id);
+ Q_ASSERT(iface);
+ if (indexOfChild(iface) >= 0) {
+ newCache.insert(indexOfChild(iface), id);
+ } else {
+ // ### This should really not happen,
+ // but it might if the view has a root index set.
+ // This needs to be fixed.
+ QAccessible::deleteAccessibleInterface(id);
+ }
+ ++iter;
+ }
+ childToId = newCache;
+ break;
+ }
+
+ case QAccessibleTableModelChangeEvent::ColumnsRemoved:
+ case QAccessibleTableModelChangeEvent::RowsRemoved: {
+ ChildCache newCache;
+ ChildCache::ConstIterator iter = childToId.constBegin();
+ while (iter != childToId.constEnd()) {
+ QAccessible::Id id = iter.value();
+ QAccessibleInterface *iface = QAccessible::accessibleInterface(id);
+ Q_ASSERT(iface);
+ if (iface->role() == QAccessible::Cell || iface->role() == QAccessible::ListItem) {
+ Q_ASSERT(iface->tableCellInterface());
+ AccessibleGroupViewItem *cell = static_cast<AccessibleGroupViewItem*>(iface->tableCellInterface());
+ // Since it is a QPersistentModelIndex, we only need to check if it is valid
+ if (cell->m_index.isValid())
+ newCache.insert(indexOfChild(cell), id);
+ else
+ QAccessible::deleteAccessibleInterface(id);
+ }
+ ++iter;
+ }
+ childToId = newCache;
+ break;
+ }
+
+ case QAccessibleTableModelChangeEvent::DataChanged:
+ // nothing to do in this case
+ break;
+ }
+}
+
+// TABLE CELL
+
+AccessibleGroupViewItem::AccessibleGroupViewItem(QAbstractItemView *view_, const QModelIndex &index_)
+ : view(view_), m_index(index_)
+{
+ if (Q_UNLIKELY(!index_.isValid()))
+ qWarning() << "AccessibleGroupViewItem::AccessibleGroupViewItem with invalid index: " << index_;
+}
+
+void *AccessibleGroupViewItem::interface_cast(QAccessible::InterfaceType t)
+{
+ if (t == QAccessible::TableCellInterface)
+ return static_cast<QAccessibleTableCellInterface*>(this);
+ if (t == QAccessible::ActionInterface)
+ return static_cast<QAccessibleActionInterface*>(this);
+ return 0;
+}
+
+int AccessibleGroupViewItem::columnExtent() const { return 1; }
+int AccessibleGroupViewItem::rowExtent() const { return 1; }
+
+QList<QAccessibleInterface*> AccessibleGroupViewItem::rowHeaderCells() const
+{
+ return {};
+}
+
+QList<QAccessibleInterface*> AccessibleGroupViewItem::columnHeaderCells() const
+{
+ return {};
+}
+
+int AccessibleGroupViewItem::columnIndex() const
+{
+ if (!isValid()) {
+ return -1;
+ }
+
+ return m_index.column();
+}
+
+int AccessibleGroupViewItem::rowIndex() const
+{
+ if (!isValid()) {
+ return -1;
+ }
+
+ return m_index.row();
+}
+
+bool AccessibleGroupViewItem::isSelected() const
+{
+ if (!isValid()) {
+ return false;
+ }
+
+ return view->selectionModel()->isSelected(m_index);
+}
+
+QStringList AccessibleGroupViewItem::actionNames() const
+{
+ QStringList names;
+ names << toggleAction();
+ return names;
+}
+
+void AccessibleGroupViewItem::doAction(const QString& actionName)
+{
+ if (actionName == toggleAction()) {
+ if (isSelected()) {
+ unselectCell();
+ }
+ else {
+ selectCell();
+ }
+ }
+}
+
+QStringList AccessibleGroupViewItem::keyBindingsForAction(const QString &) const
+{
+ return QStringList();
+}
+
+
+void AccessibleGroupViewItem::selectCell()
+{
+ if (!isValid()) {
+ return;
+ }
+ QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
+ if (selectionMode == QAbstractItemView::NoSelection) {
+ return;
+ }
+
+ Q_ASSERT(table());
+ QAccessibleTableInterface *cellTable = table()->tableInterface();
+
+ switch (view->selectionBehavior()) {
+ case QAbstractItemView::SelectItems:
+ break;
+ case QAbstractItemView::SelectColumns:
+ if (cellTable)
+ cellTable->selectColumn(m_index.column());
+ return;
+ case QAbstractItemView::SelectRows:
+ if (cellTable)
+ cellTable->selectRow(m_index.row());
+ return;
+ }
+
+ if (selectionMode == QAbstractItemView::SingleSelection) {
+ view->clearSelection();
+ }
+
+ view->selectionModel()->select(m_index, QItemSelectionModel::Select);
+}
+
+void AccessibleGroupViewItem::unselectCell()
+{
+ if (!isValid())
+ return;
+ QAbstractItemView::SelectionMode selectionMode = view->selectionMode();
+ if (selectionMode == QAbstractItemView::NoSelection)
+ return;
+
+ QAccessibleTableInterface *cellTable = table()->tableInterface();
+
+ switch (view->selectionBehavior()) {
+ case QAbstractItemView::SelectItems:
+ break;
+ case QAbstractItemView::SelectColumns:
+ if (cellTable)
+ cellTable->unselectColumn(m_index.column());
+ return;
+ case QAbstractItemView::SelectRows:
+ if (cellTable)
+ cellTable->unselectRow(m_index.row());
+ return;
+ }
+
+ //If the mode is not MultiSelection or ExtendedSelection and only
+ //one cell is selected it cannot be unselected by the user
+ if ((selectionMode != QAbstractItemView::MultiSelection) && (selectionMode != QAbstractItemView::ExtendedSelection) && (view->selectionModel()->selectedIndexes().count() <= 1))
+ return;
+
+ view->selectionModel()->select(m_index, QItemSelectionModel::Deselect);
+}
+
+QAccessibleInterface *AccessibleGroupViewItem::table() const
+{
+ return QAccessible::queryAccessibleInterface(view);
+}
+
+QAccessible::Role AccessibleGroupViewItem::role() const
+{
+ return QAccessible::ListItem;
+}
+
+QAccessible::State AccessibleGroupViewItem::state() const
+{
+ QAccessible::State st;
+ if (!isValid())
+ return st;
+
+ QRect globalRect = view->rect();
+ globalRect.translate(view->mapToGlobal(QPoint(0,0)));
+ if (!globalRect.intersects(rect()))
+ st.invisible = true;
+
+ if (view->selectionModel()->isSelected(m_index))
+ st.selected = true;
+ if (view->selectionModel()->currentIndex() == m_index)
+ st.focused = true;
+ if (m_index.model()->data(m_index, Qt::CheckStateRole).toInt() == Qt::Checked)
+ st.checked = true;
+
+ Qt::ItemFlags flags = m_index.flags();
+ if (flags & Qt::ItemIsSelectable) {
+ st.selectable = true;
+ st.focusable = true;
+ if (view->selectionMode() == QAbstractItemView::MultiSelection)
+ st.multiSelectable = true;
+ if (view->selectionMode() == QAbstractItemView::ExtendedSelection)
+ st.extSelectable = true;
+ }
+ return st;
+}
+
+
+QRect AccessibleGroupViewItem::rect() const
+{
+ QRect r;
+ if (!isValid())
+ return r;
+ r = view->visualRect(m_index);
+
+ if (!r.isNull()) {
+ r.translate(view->viewport()->mapTo(view, QPoint(0,0)));
+ r.translate(view->mapToGlobal(QPoint(0, 0)));
+ }
+ return r;
+}
+
+QString AccessibleGroupViewItem::text(QAccessible::Text t) const
+{
+ QString value;
+ if (!isValid())
+ return value;
+ QAbstractItemModel *model = view->model();
+ switch (t) {
+ case QAccessible::Name:
+ value = model->data(m_index, Qt::AccessibleTextRole).toString();
+ if (value.isEmpty())
+ value = model->data(m_index, Qt::DisplayRole).toString();
+ break;
+ case QAccessible::Description:
+ value = model->data(m_index, Qt::AccessibleDescriptionRole).toString();
+ break;
+ default:
+ break;
+ }
+ return value;
+}
+
+void AccessibleGroupViewItem::setText(QAccessible::Text /*t*/, const QString &text)
+{
+ if (!isValid() || !(m_index.flags() & Qt::ItemIsEditable))
+ return;
+ view->model()->setData(m_index, text);
+}
+
+bool AccessibleGroupViewItem::isValid() const
+{
+ return view && view->model() && m_index.isValid();
+}
+
+QAccessibleInterface *AccessibleGroupViewItem::parent() const
+{
+ return QAccessible::queryAccessibleInterface(view);
+}
+
+QAccessibleInterface *AccessibleGroupViewItem::child(int) const
+{
+ return 0;
+}
+
+#endif /* !QT_NO_ACCESSIBILITY */
diff --git a/application/groupview/AccessibleGroupView.h b/application/groupview/AccessibleGroupView.h
new file mode 100644
index 00000000..9bfd1745
--- /dev/null
+++ b/application/groupview/AccessibleGroupView.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include <QString>
+class QAccessibleInterface;
+
+QAccessibleInterface *groupViewAccessibleFactory(const QString &classname, QObject *object);
diff --git a/application/groupview/AccessibleGroupView_p.h b/application/groupview/AccessibleGroupView_p.h
new file mode 100644
index 00000000..e74da3be
--- /dev/null
+++ b/application/groupview/AccessibleGroupView_p.h
@@ -0,0 +1,118 @@
+#pragma once
+
+#include "QtCore/qpointer.h"
+#include <QtGui/qaccessible.h>
+#include <QAccessibleWidget>
+#include <QAbstractItemView>
+#ifndef QT_NO_ACCESSIBILITY
+#include "GroupView.h"
+// #include <QHeaderView>
+
+class QAccessibleTableCell;
+class QAccessibleTableHeaderCell;
+
+class AccessibleGroupView :public QAccessibleTableInterface, public QAccessibleObject
+{
+public:
+ explicit AccessibleGroupView(QWidget *w);
+ bool isValid() const override;
+
+ QAccessible::Role role() const override;
+ QAccessible::State state() const override;
+ QString text(QAccessible::Text t) const override;
+ QRect rect() const override;
+
+ QAccessibleInterface *childAt(int x, int y) const override;
+ int childCount() const override;
+ int indexOfChild(const QAccessibleInterface *) const override;
+
+ QAccessibleInterface *parent() const override;
+ QAccessibleInterface *child(int index) const override;
+
+ void *interface_cast(QAccessible::InterfaceType t) override;
+
+ // table interface
+ QAccessibleInterface *cellAt(int row, int column) const override;
+ QAccessibleInterface *caption() const override;
+ QAccessibleInterface *summary() const override;
+ QString columnDescription(int column) const override;
+ QString rowDescription(int row) const override;
+ int columnCount() const override;
+ int rowCount() const override;
+
+ // selection
+ int selectedCellCount() const override;
+ int selectedColumnCount() const override;
+ int selectedRowCount() const override;
+ QList<QAccessibleInterface*> selectedCells() const override;
+ QList<int> selectedColumns() const override;
+ QList<int> selectedRows() const override;
+ bool isColumnSelected(int column) const override;
+ bool isRowSelected(int row) const override;
+ bool selectRow(int row) override;
+ bool selectColumn(int column) override;
+ bool unselectRow(int row) override;
+ bool unselectColumn(int column) override;
+
+ QAbstractItemView *view() const;
+
+ void modelChange(QAccessibleTableModelChangeEvent *event) override;
+
+protected:
+ // maybe vector
+ typedef QHash<int, QAccessible::Id> ChildCache;
+ mutable ChildCache childToId;
+
+ virtual ~AccessibleGroupView();
+
+private:
+ inline int logicalIndex(const QModelIndex &index) const;
+};
+
+class AccessibleGroupViewItem: public QAccessibleInterface, public QAccessibleTableCellInterface, public QAccessibleActionInterface
+{
+public:
+ AccessibleGroupViewItem(QAbstractItemView *view, const QModelIndex &m_index);
+
+ void *interface_cast(QAccessible::InterfaceType t) override;
+ QObject *object() const override { return nullptr; }
+ QAccessible::Role role() const override;
+ QAccessible::State state() const override;
+ QRect rect() const override;
+ bool isValid() const override;
+
+ QAccessibleInterface *childAt(int, int) const override { return nullptr; }
+ int childCount() const override { return 0; }
+ int indexOfChild(const QAccessibleInterface *) const override { return -1; }
+
+ QString text(QAccessible::Text t) const override;
+ void setText(QAccessible::Text t, const QString &text) override;
+
+ QAccessibleInterface *parent() const override;
+ QAccessibleInterface *child(int) const override;
+
+ // cell interface
+ int columnExtent() const override;
+ QList<QAccessibleInterface*> columnHeaderCells() const override;
+ int columnIndex() const override;
+ int rowExtent() const override;
+ QList<QAccessibleInterface*> rowHeaderCells() const override;
+ int rowIndex() const override;
+ bool isSelected() const override;
+ QAccessibleInterface* table() const override;
+
+ //action interface
+ QStringList actionNames() const override;
+ void doAction(const QString &actionName) override;
+ QStringList keyBindingsForAction(const QString &actionName) const override;
+
+private:
+ QPointer<QAbstractItemView > view;
+ QPersistentModelIndex m_index;
+
+ void selectCell();
+ void unselectCell();
+
+ friend class AccessibleGroupView;
+};
+#endif /* !QT_NO_ACCESSIBILITY */
diff --git a/application/groupview/GroupView.cpp b/application/groupview/GroupView.cpp
index a1b44e64..6bfc9381 100644
--- a/application/groupview/GroupView.cpp
+++ b/application/groupview/GroupView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -25,6 +25,7 @@
#include <QMimeData>
#include <QCache>
#include <QScrollBar>
+#include <QAccessible>
#include "VisualGroup.h"
#include <QDebug>
@@ -88,6 +89,20 @@ void GroupView::rowsRemoved()
scheduleDelayedItemsLayout();
}
+void GroupView::currentChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+ QAbstractItemView::currentChanged(current, previous);
+ // TODO: for accessibility support, implement+register a factory, steal QAccessibleTable from Qt and return an instance of it for GroupView.
+#ifndef QT_NO_ACCESSIBILITY
+ if (QAccessible::isActive() && current.isValid()) {
+ QAccessibleEvent event(this, QAccessible::Focus);
+ event.setChild(current.row());
+ QAccessible::updateAccessibility(&event);
+ }
+#endif /* !QT_NO_ACCESSIBILITY */
+}
+
+
class LocaleString : public QString
{
public:
@@ -162,6 +177,9 @@ void GroupView::updateGeometries()
else
{
auto cat = new VisualGroup(groupName, this);
+ if(fVisibility) {
+ cat->collapsed = fVisibility(groupName);
+ }
cats.insert(groupName, cat);
cat->update();
}
@@ -220,6 +238,8 @@ VisualGroup *GroupView::categoryAt(const QPoint &pos, VisualGroup::HitResults &
QString GroupView::groupNameAt(const QPoint &point)
{
+ executeDelayedItemsLayout();
+
VisualGroup::HitResults hitresult;
auto group = categoryAt(point + offset(), hitresult);
if(group && (hitresult & (VisualGroup::HeaderHit | VisualGroup::BodyHit)))
@@ -246,7 +266,7 @@ int GroupView::itemWidth() const
void GroupView::mousePressEvent(QMouseEvent *event)
{
- // endCategoryEditor();
+ executeDelayedItemsLayout();
QPoint visualPos = event->pos();
QPoint geometryPos = event->pos() + offset();
@@ -295,6 +315,8 @@ void GroupView::mousePressEvent(QMouseEvent *event)
void GroupView::mouseMoveEvent(QMouseEvent *event)
{
+ executeDelayedItemsLayout();
+
QPoint topLeft;
QPoint visualPos = event->pos();
QPoint geometryPos = event->pos() + offset();
@@ -351,6 +373,8 @@ void GroupView::mouseMoveEvent(QMouseEvent *event)
void GroupView::mouseReleaseEvent(QMouseEvent *event)
{
+ executeDelayedItemsLayout();
+
QPoint visualPos = event->pos();
QPoint geometryPos = event->pos() + offset();
QPersistentModelIndex index = indexAt(visualPos);
@@ -365,17 +389,25 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event)
if (state() == ExpandingState)
{
m_pressedCategory->collapsed = false;
+ emit groupStateChanged(m_pressedCategory->text, false);
+
updateGeometries();
viewport()->update();
event->accept();
+ m_pressedCategory = nullptr;
+ setState(NoState);
return;
}
else if (state() == CollapsingState)
{
m_pressedCategory->collapsed = true;
+ emit groupStateChanged(m_pressedCategory->text, true);
+
updateGeometries();
viewport()->update();
event->accept();
+ m_pressedCategory = nullptr;
+ setState(NoState);
return;
}
}
@@ -405,6 +437,8 @@ void GroupView::mouseReleaseEvent(QMouseEvent *event)
void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
{
+ executeDelayedItemsLayout();
+
QModelIndex index = indexAt(event->pos());
if (!index.isValid() || !(index.flags() & Qt::ItemIsEnabled) || (m_pressedIndex != index))
{
@@ -417,6 +451,12 @@ void GroupView::mouseDoubleClickEvent(QMouseEvent *event)
// signal handlers may change the model
QPersistentModelIndex persistent = index;
emit doubleClicked(persistent);
+
+ QStyleOptionViewItem option = viewOptions();
+ if ((model()->flags(index) & Qt::ItemIsEnabled) && !style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, &option, this))
+ {
+ emit activated(index);
+ }
}
void GroupView::paintEvent(QPaintEvent *event)
@@ -522,6 +562,8 @@ void GroupView::resizeEvent(QResizeEvent *event)
void GroupView::dragEnterEvent(QDragEnterEvent *event)
{
+ executeDelayedItemsLayout();
+
if (!isDragEventAccepted(event))
{
return;
@@ -533,6 +575,8 @@ void GroupView::dragEnterEvent(QDragEnterEvent *event)
void GroupView::dragMoveEvent(QDragMoveEvent *event)
{
+ executeDelayedItemsLayout();
+
if (!isDragEventAccepted(event))
{
return;
@@ -544,12 +588,16 @@ void GroupView::dragMoveEvent(QDragMoveEvent *event)
void GroupView::dragLeaveEvent(QDragLeaveEvent *event)
{
+ executeDelayedItemsLayout();
+
m_lastDragPosition = QPoint();
viewport()->update();
}
void GroupView::dropEvent(QDropEvent *event)
{
+ executeDelayedItemsLayout();
+
m_lastDragPosition = QPoint();
stopAutoScroll();
@@ -572,8 +620,7 @@ void GroupView::dropEvent(QDropEvent *event)
const QString categoryText = category->text;
if (model()->dropMimeData(event->mimeData(), Qt::MoveAction, row, 0, QModelIndex()))
{
- model()->setData(model()->index(row, 0), categoryText,
- GroupViewRoles::GroupRole);
+ model()->setData(model()->index(row, 0), categoryText, GroupViewRoles::GroupRole);
event->setDropAction(Qt::MoveAction);
event->accept();
}
@@ -600,6 +647,8 @@ void GroupView::dropEvent(QDropEvent *event)
void GroupView::startDrag(Qt::DropActions supportedActions)
{
+ executeDelayedItemsLayout();
+
QModelIndexList indexes = selectionModel()->selectedIndexes();
if(indexes.count() == 0)
return;
@@ -645,11 +694,15 @@ void GroupView::startDrag(Qt::DropActions supportedActions)
QRect GroupView::visualRect(const QModelIndex &index) const
{
+ const_cast<GroupView*>(this)->executeDelayedItemsLayout();
+
return geometryRect(index).translated(-offset());
}
QRect GroupView::geometryRect(const QModelIndex &index) const
{
+ const_cast<GroupView*>(this)->executeDelayedItemsLayout();
+
if (!index.isValid() || isIndexHidden(index) || index.column() > 0)
{
return QRect();
@@ -689,9 +742,10 @@ QModelIndex GroupView::indexAt(const QPoint &point) const
return QModelIndex();
}
-void GroupView::setSelection(const QRect &rect,
- const QItemSelectionModel::SelectionFlags commands)
+void GroupView::setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands)
{
+ executeDelayedItemsLayout();
+
for (int i = 0; i < model()->rowCount(); ++i)
{
QModelIndex index = model()->index(i, 0);
@@ -726,8 +780,7 @@ QPixmap GroupView::renderToPixmap(const QModelIndexList &indices, QRect *r) cons
return pixmap;
}
-QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices,
- QRect *r) const
+QList<QPair<QRect, QModelIndex>> GroupView::draggablePaintPairs(const QModelIndexList &indices, QRect *r) const
{
Q_ASSERT(r);
QRect &rect = *r;
diff --git a/application/groupview/GroupView.h b/application/groupview/GroupView.h
index ceb602fc..cc5a58aa 100644
--- a/application/groupview/GroupView.h
+++ b/application/groupview/GroupView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,6 +20,7 @@
#include <QScrollBar>
#include <QCache>
#include "VisualGroup.h"
+#include <functional>
struct GroupViewRoles
{
@@ -41,6 +42,11 @@ public:
void setModel(QAbstractItemModel *model) override;
+ using visibilityFunction = std::function<bool(const QString &)>;
+ void setSourceOfGroupCollapseStatus(visibilityFunction f) {
+ fVisibility = f;
+ }
+
/// return geometry rectangle occupied by the specified model item
QRect geometryRect(const QModelIndex &index) const;
/// return visual rectangle occupied by the specified model item
@@ -48,8 +54,7 @@ public:
/// get the model index at the specified visual point
virtual QModelIndex indexAt(const QPoint &point) const override;
QString groupNameAt(const QPoint &point);
- void setSelection(const QRect &rect,
- const QItemSelectionModel::SelectionFlags commands) override;
+ void setSelection(const QRect &rect, const QItemSelectionModel::SelectionFlags commands) override;
virtual int horizontalOffset() const override;
virtual int verticalOffset() const override;
@@ -76,9 +81,11 @@ protected slots:
virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;
void modelReset();
void rowsRemoved();
+ void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
signals:
void droppedURLs(QList<QUrl> urls);
+ void groupStateChanged(QString group, bool collapsed);
protected:
virtual bool isIndexHidden(const QModelIndex &index) const override;
@@ -102,6 +109,8 @@ private:
friend struct VisualGroup;
QList<VisualGroup *> m_groups;
+ visibilityFunction fVisibility;
+
// geometry
int m_leftMargin = 5;
int m_rightMargin = 5;
diff --git a/application/groupview/GroupedProxyModel.cpp b/application/groupview/GroupedProxyModel.cpp
index 5617a1ee..dc4212d5 100644
--- a/application/groupview/GroupedProxyModel.cpp
+++ b/application/groupview/GroupedProxyModel.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e1c51c0f..fabf11c1 100644
--- a/application/groupview/GroupedProxyModel.h
+++ b/application/groupview/GroupedProxyModel.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 42860aef..fc959565 100644
--- a/application/groupview/InstanceDelegate.cpp
+++ b/application/groupview/InstanceDelegate.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,11 +19,13 @@
#include <QTextLayout>
#include <QApplication>
#include <QtMath>
+#include <QDebug>
#include "GroupView.h"
#include "BaseInstance.h"
#include "InstanceList.h"
#include <xdgicon.h>
+#include <QTextEdit>
// Origin: Qt
static void viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height,
@@ -165,8 +167,7 @@ static QSize viewItemTextSize(const QStyleOptionViewItem *option)
textLayout.setTextOption(textOption);
textLayout.setFont(option->font);
textLayout.setText(option->text);
- const int textMargin =
- style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1;
+ const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, option, option->widget) + 1;
QRect bounds(0, 0, 100 - 2 * textMargin, 600);
qreal height = 0, widthUsed = 0;
viewItemTextLayout(textLayout, bounds.width(), height, widthUsed);
@@ -204,6 +205,8 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
{
// FIXME: unused
// QSize textSize = viewItemTextSize ( &opt );
+ drawSelectionRect(painter, opt, textHighlightRect);
+ /*
QPalette::ColorGroup cg;
QStyleOptionViewItem opt2(opt);
@@ -218,10 +221,13 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
{
cg = QPalette::Disabled;
}
+ */
+ /*
opt2.palette.setCurrentColorGroup(cg);
// fill in background, if any
+
if (opt.backgroundBrush.style() != Qt::NoBrush)
{
QPointF oldBO = painter->brushOrigin();
@@ -231,6 +237,7 @@ void ListViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
}
drawSelectionRect(painter, opt2, textHighlightRect);
+ */
/*
if (opt.showDecorationSelected)
@@ -331,8 +338,7 @@ QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option,
opt.displayAlignment = Qt::AlignTop | Qt::AlignHCenter;
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
- const int textMargin =
- style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1;
+ const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, opt.widget) + 1;
int height = 48 + textMargin * 2 + 5; // TODO: turn constants into variables
QSize szz = viewItemTextSize(&opt);
height += szz.height();
@@ -341,3 +347,82 @@ QSize ListViewDelegate::sizeHint(const QStyleOptionViewItem &option,
return sz;
}
+class NoReturnTextEdit: public QTextEdit
+{
+ Q_OBJECT
+public:
+ explicit NoReturnTextEdit(QWidget *parent) : QTextEdit(parent)
+ {
+ setTextInteractionFlags(Qt::TextEditorInteraction);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
+ setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff);
+ }
+ bool event(QEvent * event) override
+ {
+ auto eventType = event->type();
+ if(eventType == QEvent::KeyPress || eventType == QEvent::KeyRelease)
+ {
+ QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
+ auto key = keyEvent->key();
+ if (key == Qt::Key_Return || key == Qt::Key_Enter)
+ {
+ emit editingDone();
+ return true;
+ }
+ if(key == Qt::Key_Tab)
+ {
+ return true;
+ }
+ }
+ return QTextEdit::event(event);
+ }
+signals:
+ void editingDone();
+};
+
+void ListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ const int iconSize = 48;
+ QRect textRect = option.rect;
+ // QStyle *style = option.widget ? option.widget->style() : QApplication::style();
+ textRect.adjust(0, iconSize + 5, 0, 0);
+ editor->setGeometry(textRect);
+}
+
+void ListViewDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
+{
+ auto text = index.data(Qt::EditRole).toString();
+ QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor);
+ realeditor->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
+ realeditor->append(text);
+ realeditor->selectAll();
+ realeditor->document()->clearUndoRedoStacks();
+}
+
+void ListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
+{
+ QTextEdit * realeditor = qobject_cast<NoReturnTextEdit *>(editor);
+ QString text = realeditor->toPlainText();
+ text.replace(QChar('\n'), QChar(' '));
+ text = text.trimmed();
+ if(text.size() != 0)
+ {
+ model->setData(index, text);
+ }
+}
+
+QWidget * ListViewDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
+{
+ auto editor = new NoReturnTextEdit(parent);
+ connect(editor, &NoReturnTextEdit::editingDone, this, &ListViewDelegate::editingDone);
+ return editor;
+}
+
+void ListViewDelegate::editingDone()
+{
+ NoReturnTextEdit *editor = qobject_cast<NoReturnTextEdit *>(sender());
+ emit commitData(editor);
+ emit closeEditor(editor);
+}
+
+#include "InstanceDelegate.moc"
diff --git a/application/groupview/InstanceDelegate.h b/application/groupview/InstanceDelegate.h
index d0076e60..d95279f3 100644
--- a/application/groupview/InstanceDelegate.h
+++ b/application/groupview/InstanceDelegate.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,11 +20,20 @@
class ListViewDelegate : public QStyledItemDelegate
{
+ Q_OBJECT
+
public:
explicit ListViewDelegate(QObject *parent = 0);
+ virtual ~ListViewDelegate() {}
+
+ void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+ QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+ void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
+ QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
+
+ void setEditorData(QWidget * editor, const QModelIndex & index) const override;
+ void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override;
-protected:
- void paint(QPainter *painter, const QStyleOptionViewItem &option,
- const QModelIndex &index) const;
- QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
+private slots:
+ void editingDone();
};
diff --git a/application/groupview/VisualGroup.cpp b/application/groupview/VisualGroup.cpp
index e08cb241..76bf8678 100644
--- a/application/groupview/VisualGroup.cpp
+++ b/application/groupview/VisualGroup.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 0ffcf236..239ee9d7 100644
--- a/application/groupview/VisualGroup.h
+++ b/application/groupview/VisualGroup.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/main.cpp b/application/main.cpp
index f724845e..b0360c7e 100644
--- a/application/main.cpp
+++ b/application/main.cpp
@@ -29,7 +29,8 @@ int main(int argc, char *argv[])
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
// initialize Qt
@@ -42,7 +43,6 @@ int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(multimc);
Q_INIT_RESOURCE(backgrounds);
- Q_INIT_RESOURCE(assets);
Q_INIT_RESOURCE(pe_dark);
Q_INIT_RESOURCE(pe_light);
diff --git a/application/package/linux/multimc.desktop b/application/package/linux/multimc.desktop
index 770f24f1..c25be047 100755
--- a/application/package/linux/multimc.desktop
+++ b/application/package/linux/multimc.desktop
@@ -1,7 +1,7 @@
[Desktop Entry]
Version=1.0
Name=MultiMC
-GenericName=MultiMC
+GenericName=Minecraft Launcher
Comment=Free, open source launcher and instance manager for Minecraft.
Type=Application
Terminal=false
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 158ce5a4..3e0f570c 100644
--- a/application/package/ubuntu/multimc/DEBIAN/control
+++ b/application/package/ubuntu/multimc/DEBIAN/control
@@ -1,11 +1,11 @@
Package: multimc
-Version: 1.2-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
+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/opt/multimc/run.sh b/application/package/ubuntu/multimc/opt/multimc/run.sh
index f4f2fa42..c493a513 100755
--- a/application/package/ubuntu/multimc/opt/multimc/run.sh
+++ b/application/package/ubuntu/multimc/opt/multimc/run.sh
@@ -22,12 +22,12 @@ deploy() {
runmmc() {
cd ${INSTDIR}
- ./MultiMC
+ ./MultiMC "$@"
}
if [[ ! -f ${INSTDIR}/MultiMC ]]; then
deploy
- runmmc
+ runmmc "$@"
else
- runmmc
+ runmmc "$@"
fi
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 50ce65fd..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.2-1` and then run:
-```
-fakeroot dpkg-deb --build multimc_1.2-1
-```
-
-Replace the version with whatever is appropriate.
diff --git a/application/pagedialog/PageDialog.cpp b/application/pagedialog/PageDialog.cpp
index c9ee93d8..fd5d36d4 100644
--- a/application/pagedialog/PageDialog.cpp
+++ b/application/pagedialog/PageDialog.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,7 @@ PageDialog::PageDialog(BasePageProvider *pageProvider, QString defaultId, QWidge
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Close);
buttons->button(QDialogButtonBox::Close)->setDefault(true);
+ buttons->setContentsMargins(6, 0, 6, 0);
m_container->addButtons(buttons);
connect(buttons->button(QDialogButtonBox::Close), SIGNAL(clicked()), this, SLOT(close()));
diff --git a/application/pagedialog/PageDialog.h b/application/pagedialog/PageDialog.h
index 4b7ea708..1029bc30 100644
--- a/application/pagedialog/PageDialog.h
+++ b/application/pagedialog/PageDialog.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e1169c08..408965d0 100644
--- a/application/pages/BasePage.h
+++ b/application/pages/BasePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e403800c..7bfaaf3b 100644
--- a/application/pages/BasePageProvider.h
+++ b/application/pages/BasePageProvider.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 b89c410f..ff3736ed 100644
--- a/application/pages/global/AccountListPage.cpp
+++ b/application/pages/global/AccountListPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -17,11 +17,11 @@
#include "ui_AccountListPage.h"
#include <QItemSelectionModel>
+#include <QMenu>
#include <QDebug>
#include "net/NetJob.h"
-#include "net/URLConstants.h"
#include "Env.h"
#include "dialogs/ProgressDialog.h"
@@ -30,28 +30,38 @@
#include "dialogs/SkinUploadDialog.h"
#include "tasks/Task.h"
#include "minecraft/auth/YggdrasilTask.h"
+#include "minecraft/services/SkinDelete.h"
#include "MultiMC.h"
+#include "BuildConfig.h"
+
AccountListPage::AccountListPage(QWidget *parent)
- : QWidget(parent), ui(new Ui::AccountListPage)
+ : QMainWindow(parent), ui(new Ui::AccountListPage)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
+ ui->listView->setEmptyString(tr(
+ "Welcome!\n"
+ "If you're new here, you can click the \"Add\" button to add your Mojang or Minecraft account."
+ ));
+ ui->listView->setEmptyMode(VersionListView::String);
+ ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
m_accounts = MMC->accounts();
ui->listView->setModel(m_accounts.get());
ui->listView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
// Expand the account column
ui->listView->header()->setSectionResizeMode(1, QHeaderView::Stretch);
QItemSelectionModel *selectionModel = ui->listView->selectionModel();
- connect(selectionModel, &QItemSelectionModel::selectionChanged,
- [this](const QItemSelection &sel, const QItemSelection &dsel)
- { updateButtonStates(); });
+ connect(selectionModel, &QItemSelectionModel::selectionChanged, [this](const QItemSelection &sel, const QItemSelection &dsel) {
+ updateButtonStates();
+ });
+ connect(ui->listView, &VersionListView::customContextMenuRequested, this, &AccountListPage::ShowContextMenu);
connect(m_accounts.get(), SIGNAL(listChanged()), SLOT(listChanged()));
connect(m_accounts.get(), SIGNAL(activeAccountChanged()), SLOT(listChanged()));
@@ -64,18 +74,41 @@ AccountListPage::~AccountListPage()
delete ui;
}
+void AccountListPage::ShowContextMenu(const QPoint& pos)
+{
+ auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->listView->mapToGlobal(pos));
+ delete menu;
+}
+
+void AccountListPage::changeEvent(QEvent* event)
+{
+ if (event->type() == QEvent::LanguageChange)
+ {
+ ui->retranslateUi(this);
+ }
+ QMainWindow::changeEvent(event);
+}
+
+QMenu * AccountListPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction(ui->toolBar->toggleViewAction() );
+ return filteredMenu;
+}
+
+
void AccountListPage::listChanged()
{
updateButtonStates();
}
-void AccountListPage::on_addAccountBtn_clicked()
+void AccountListPage::on_actionAdd_triggered()
{
- addAccount(tr("Please enter your Mojang or Minecraft account username and password to add "
- "your account."));
+ addAccount(tr("Please enter your Minecraft account email and password to add your account."));
}
-void AccountListPage::on_rmAccountBtn_clicked()
+void AccountListPage::on_actionRemove_triggered()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.size() > 0)
@@ -85,7 +118,7 @@ void AccountListPage::on_rmAccountBtn_clicked()
}
}
-void AccountListPage::on_setDefaultBtn_clicked()
+void AccountListPage::on_actionSetDefault_triggered()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.size() > 0)
@@ -97,7 +130,7 @@ void AccountListPage::on_setDefaultBtn_clicked()
}
}
-void AccountListPage::on_noDefaultBtn_clicked()
+void AccountListPage::on_actionNoDefault_triggered()
{
m_accounts->setActiveAccount("");
}
@@ -107,11 +140,20 @@ void AccountListPage::updateButtonStates()
// If there is no selection, disable buttons that require something selected.
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
- ui->rmAccountBtn->setEnabled(selection.size() > 0);
- ui->setDefaultBtn->setEnabled(selection.size() > 0);
- ui->uploadSkinBtn->setEnabled(selection.size() > 0);
+ 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);
+ ui->actionNoDefault->setChecked(true);
+ }
+ else {
+ ui->actionNoDefault->setEnabled(true);
+ ui->actionNoDefault->setChecked(false);
+ }
- ui->noDefaultBtn->setDown(m_accounts->activeAccount().get() == nullptr);
}
void AccountListPage::addAccount(const QString &errMsg)
@@ -131,7 +173,7 @@ void AccountListPage::addAccount(const QString &errMsg)
for (AccountProfile profile : account->profiles())
{
auto meta = Env::getInstance().metacache()->resolveEntry("skins", profile.id + ".png");
- auto action = Net::Download::makeCached(QUrl("https://" + URLConstants::SKINS_BASE + profile.id + ".png"), meta);
+ auto action = Net::Download::makeCached(QUrl(BuildConfig.SKINS_BASE + profile.id + ".png"), meta);
job->addNetAction(action);
meta->setStale(true);
}
@@ -140,7 +182,7 @@ void AccountListPage::addAccount(const QString &errMsg)
}
}
-void AccountListPage::on_uploadSkinBtn_clicked()
+void AccountListPage::on_actionUploadSkin_triggered()
{
QModelIndexList selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.size() > 0)
@@ -151,3 +193,25 @@ void AccountListPage::on_uploadSkinBtn_clicked()
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 ad93c904..fba1833f 100644
--- a/application/pages/global/AccountListPage.h
+++ b/application/pages/global/AccountListPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,7 +15,7 @@
#pragma once
-#include <QDialog>
+#include <QMainWindow>
#include <memory>
#include "pages/BasePage.h"
@@ -30,7 +30,7 @@ class AccountListPage;
class AuthenticateTask;
-class AccountListPage : public QWidget, public BasePage
+class AccountListPage : public QMainWindow, public BasePage
{
Q_OBJECT
public:
@@ -59,30 +59,26 @@ public:
return "Getting-Started#adding-an-account";
}
-public
-slots:
- void on_addAccountBtn_clicked();
-
- void on_rmAccountBtn_clicked();
-
- void on_setDefaultBtn_clicked();
-
- void on_noDefaultBtn_clicked();
-
- void on_uploadSkinBtn_clicked();
+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 f4e87680..71647db3 100644
--- a/application/pages/global/AccountListPage.ui
+++ b/application/pages/global/AccountListPage.ui
@@ -1,122 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AccountListPage</class>
- <widget class="QWidget" name="AccountListPage">
+ <widget class="QMainWindow" name="AccountListPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>694</width>
- <height>609</height>
+ <width>800</width>
+ <height>600</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <property name="leftMargin">
- <number>0</number>
+ <widget class="QWidget" name="centralwidget">
+ <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="VersionListView" name="listView"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WideBar" name="toolBar">
+ <attribute name="toolBarArea">
+ <enum>RightToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionAdd"/>
+ <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">
+ <string>Add</string>
</property>
- <property name="topMargin">
- <number>0</number>
+ </action>
+ <action name="actionRemove">
+ <property name="text">
+ <string>Remove</string>
</property>
- <property name="rightMargin">
- <number>0</number>
+ </action>
+ <action name="actionSetDefault">
+ <property name="text">
+ <string>Set Default</string>
</property>
- <property name="bottomMargin">
- <number>0</number>
+ </action>
+ <action name="actionNoDefault">
+ <property name="checkable">
+ <bool>true</bool>
</property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLabel" name="welcomeLabel">
- <property name="text">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Welcome! If you're new here, you can click the &amp;quot;Add&amp;quot; button to add your Mojang or Minecraft account.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QTreeView" name="listView"/>
- </item>
- <item>
- <layout class="QVBoxLayout" name="manageAcctsBtnBox">
- <item>
- <widget class="QPushButton" name="addAccountBtn">
- <property name="text">
- <string>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmAccountBtn">
- <property name="text">
- <string>&amp;Remove</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="buttonSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="setDefaultBtn">
- <property name="toolTip">
- <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set the currently selected account as the active account. The active account is the account that is used to log in (unless it is overridden in an instance-specific setting).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
- <property name="text">
- <string>&amp;Set Default</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="noDefaultBtn">
- <property name="toolTip">
- <string>Set no default account. This will cause MultiMC to prompt you to select an account every time you launch an instance that doesn't have its own default set.</string>
- </property>
- <property name="text">
- <string>&amp;No Default</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="uploadSkinBtn">
- <property name="toolTip">
- <string>Opens a dialog to select and upload a skin image to the selected account.</string>
- </property>
- <property name="text">
- <string>&amp;Upload Skin</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
+ <property name="text">
+ <string>No Default</string>
+ </property>
+ </action>
+ <action name="actionUploadSkin">
+ <property name="text">
+ <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>
+ <class>VersionListView</class>
+ <extends>QTreeView</extends>
+ <header>widgets/VersionListView.h</header>
+ </customwidget>
+ <customwidget>
+ <class>WideBar</class>
+ <extends>QToolBar</extends>
+ <header>widgets/WideBar.h</header>
+ </customwidget>
+ </customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/application/pages/global/CustomCommandsPage.cpp b/application/pages/global/CustomCommandsPage.cpp
index f2c3b185..3b182319 100644
--- a/application/pages/global/CustomCommandsPage.cpp
+++ b/application/pages/global/CustomCommandsPage.cpp
@@ -13,6 +13,7 @@ CustomCommandsPage::CustomCommandsPage(QWidget* parent): QWidget(parent)
auto tabWidget = new QTabWidget(this);
tabWidget->setObjectName(QStringLiteral("tabWidget"));
commands = new CustomCommands(this);
+ commands->setContentsMargins(6, 6, 6, 6);
tabWidget->addTab(commands, "Foo");
tabWidget->tabBar()->hide();
verticalLayout->addWidget(tabWidget);
diff --git a/application/pages/global/CustomCommandsPage.h b/application/pages/global/CustomCommandsPage.h
index d7206dfa..414c3259 100644
--- a/application/pages/global/CustomCommandsPage.h
+++ b/application/pages/global/CustomCommandsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2018-2018 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 41ed3f7c..6a0a38be 100644
--- a/application/pages/global/ExternalToolsPage.cpp
+++ b/application/pages/global/ExternalToolsPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 bc42d2dd..0fc8ebe1 100644
--- a/application/pages/global/ExternalToolsPage.h
+++ b/application/pages/global/ExternalToolsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.ui b/application/pages/global/ExternalToolsPage.ui
index 5f19898b..e79e9388 100644
--- a/application/pages/global/ExternalToolsPage.ui
+++ b/application/pages/global/ExternalToolsPage.ui
@@ -63,7 +63,7 @@
<item>
<widget class="QLabel" name="jprofilerLink">
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.ej-technologies.com/products/jprofiler/overview.html&quot;&gt;http://www.ej-technologies.com/products/jprofiler/overview.html&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://www.ej-technologies.com/products/jprofiler/overview.html&quot;&gt;https://www.ej-technologies.com/products/jprofiler/overview.html&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
@@ -137,7 +137,7 @@
<item>
<widget class="QLabel" name="mceditLink">
<property name="text">
- <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://www.mcedit.net/&quot;&gt;http://www.mcedit.net/&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+ <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://www.mcedit.net/&quot;&gt;https://www.mcedit.net/&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
diff --git a/application/pages/global/JavaPage.cpp b/application/pages/global/JavaPage.cpp
index 57e60ddf..cde0e035 100644
--- a/application/pages/global/JavaPage.cpp
+++ b/application/pages/global/JavaPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 dc53402a..832f460b 100644
--- a/application/pages/global/JavaPage.h
+++ b/application/pages/global/JavaPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.cpp b/application/pages/global/LanguagePage.cpp
new file mode 100644
index 00000000..ae3168cc
--- /dev/null
+++ b/application/pages/global/LanguagePage.cpp
@@ -0,0 +1,51 @@
+#include "LanguagePage.h"
+
+#include "widgets/LanguageSelectionWidget.h"
+#include <QVBoxLayout>
+
+LanguagePage::LanguagePage(QWidget* parent) :
+ QWidget(parent)
+{
+ setObjectName(QStringLiteral("languagePage"));
+ auto layout = new QVBoxLayout(this);
+ mainWidget = new LanguageSelectionWidget(this);
+ layout->setContentsMargins(0,0,0,0);
+ layout->addWidget(mainWidget);
+ retranslate();
+}
+
+LanguagePage::~LanguagePage()
+{
+}
+
+bool LanguagePage::apply()
+{
+ applySettings();
+ return true;
+}
+
+void LanguagePage::applySettings()
+{
+ auto settings = MMC->settings();
+ QString key = mainWidget->getSelectedLanguageKey();
+ settings->set("Language", key);
+}
+
+void LanguagePage::loadSettings()
+{
+ // NIL
+}
+
+void LanguagePage::retranslate()
+{
+ mainWidget->retranslate();
+}
+
+void LanguagePage::changeEvent(QEvent* event)
+{
+ if (event->type() == QEvent::LanguageChange)
+ {
+ retranslate();
+ }
+ QWidget::changeEvent(event);
+}
diff --git a/application/pages/global/LanguagePage.h b/application/pages/global/LanguagePage.h
new file mode 100644
index 00000000..ca8eecc6
--- /dev/null
+++ b/application/pages/global/LanguagePage.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 <memory>
+#include "pages/BasePage.h"
+#include <MultiMC.h>
+#include <QWidget>
+
+class LanguageSelectionWidget;
+
+class LanguagePage : public QWidget, public BasePage
+{
+ Q_OBJECT
+
+public:
+ explicit LanguagePage(QWidget *parent = 0);
+ virtual ~LanguagePage();
+
+ QString displayName() const override
+ {
+ return tr("Language");
+ }
+ QIcon icon() const override
+ {
+ return MMC->getThemedIcon("language");
+ }
+ QString id() const override
+ {
+ return "language-settings";
+ }
+ QString helpPage() const override
+ {
+ return "Language-settings";
+ }
+ bool apply() override;
+
+ void changeEvent(QEvent * ) override;
+
+private:
+ void applySettings();
+ void loadSettings();
+ void retranslate();
+
+private:
+ LanguageSelectionWidget *mainWidget;
+};
diff --git a/application/pages/global/MinecraftPage.cpp b/application/pages/global/MinecraftPage.cpp
index 06429449..6c9bd307 100644
--- a/application/pages/global/MinecraftPage.cpp
+++ b/application/pages/global/MinecraftPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e5d5f854..5e781aed 100644
--- a/application/pages/global/MinecraftPage.h
+++ b/application/pages/global/MinecraftPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 1991c9ba..80d5c544 100644
--- a/application/pages/global/MultiMCPage.cpp
+++ b/application/pages/global/MultiMCPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -78,12 +78,12 @@ MultiMCPage::MultiMCPage(QWidget *parent) : QWidget(parent), ui(new Ui::MultiMCP
}
connect(ui->fontSizeBox, SIGNAL(valueChanged(int)), SLOT(refreshFontPreview()));
connect(ui->consoleFont, SIGNAL(currentFontChanged(QFont)), SLOT(refreshFontPreview()));
- connect(ui->languageBox, SIGNAL(currentIndexChanged(int)), SLOT(languageIndexChanged(int)));
}
MultiMCPage::~MultiMCPage()
{
delete ui;
+ delete defaultFormat;
}
bool MultiMCPage::apply()
@@ -147,19 +147,6 @@ void MultiMCPage::on_modsDirBrowseBtn_clicked()
}
}
-void MultiMCPage::languageIndexChanged(int index)
-{
- auto languageCode = ui->languageBox->itemData(ui->languageBox->currentIndex()).toString();
- if(languageCode.isEmpty())
- {
- qWarning() << "Unknown language at index" << index;
- return;
- }
- auto translations = MMC->translations();
- translations->selectLanguage(languageCode);
- translations->updateLanguage(languageCode);
-}
-
void MultiMCPage::refreshUpdateChannelList()
{
// Stop listening for selection changes. It's going to change a lot while we update it and
@@ -236,9 +223,6 @@ void MultiMCPage::applySettings()
{
auto s = MMC->settings();
- // Language
- s->set("Language", ui->languageBox->itemData(ui->languageBox->currentIndex()).toString());
-
if (ui->resetNotificationsBtn->isChecked())
{
s->set("ShownNotifications", QString());
@@ -331,12 +315,6 @@ void MultiMCPage::applySettings()
void MultiMCPage::loadSettings()
{
auto s = MMC->settings();
- // Language
- {
- ui->languageBox->setModel(m_languageModel.get());
- ui->languageBox->setCurrentIndex(ui->languageBox->findData(s->get("Language").toString()));
- }
-
// Updates
ui->autoUpdateCheckBox->setChecked(s->get("AutoUpdate").toBool());
m_currentUpdateChannel = s->get("UpdateChannel").toString();
diff --git a/application/pages/global/MultiMCPage.h b/application/pages/global/MultiMCPage.h
index bf21305e..e81832eb 100644
--- a/application/pages/global/MultiMCPage.h
+++ b/application/pages/global/MultiMCPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -68,8 +68,6 @@ slots:
void on_modsDirBrowseBtn_clicked();
void on_iconsDirBrowseBtn_clicked();
- void languageIndexChanged(int index);
-
/*!
* Updates the list of update channels in the combo box.
*/
diff --git a/application/pages/global/MultiMCPage.ui b/application/pages/global/MultiMCPage.ui
index 124401c3..ea034919 100644
--- a/application/pages/global/MultiMCPage.ui
+++ b/application/pages/global/MultiMCPage.ui
@@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
- <width>467</width>
+ <width>514</width>
<height>629</height>
</rect>
</property>
@@ -229,18 +229,6 @@
</widget>
</item>
<item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Language:</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QComboBox" name="languageBox"/>
- </item>
- </layout>
- </widget>
- </item>
- <item>
<widget class="QGroupBox" name="themeBox">
<property name="title">
<string>Theme</string>
@@ -570,7 +558,6 @@
<tabstop>resetNotificationsBtn</tabstop>
<tabstop>sortLastLaunchedBtn</tabstop>
<tabstop>sortByNameBtn</tabstop>
- <tabstop>languageBox</tabstop>
<tabstop>themeComboBox</tabstop>
<tabstop>themeComboBoxColors</tabstop>
<tabstop>showConsoleCheck</tabstop>
diff --git a/application/pages/global/PackagesPage.cpp b/application/pages/global/PackagesPage.cpp
deleted file mode 100644
index b6a7887b..00000000
--- a/application/pages/global/PackagesPage.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/* Copyright 2015-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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 "PackagesPage.h"
-#include "ui_PackagesPage.h"
-
-#include <QDateTime>
-#include <QSortFilterProxyModel>
-#include <QRegularExpression>
-
-#include "dialogs/ProgressDialog.h"
-#include "VersionProxyModel.h"
-
-#include "meta/Index.h"
-#include "meta/VersionList.h"
-#include "meta/Version.h"
-#include "Env.h"
-#include "MultiMC.h"
-
-using namespace Meta;
-
-static QString formatRequires(const VersionPtr &version)
-{
- QStringList lines;
- auto & reqs = version->requires();
- auto iter = reqs.begin();
- while (iter != reqs.end())
- {
- auto &uid = iter->uid;
- auto &version = iter->equalsVersion;
- const QString readable = ENV.metadataIndex()->hasUid(uid) ? ENV.metadataIndex()->get(uid)->humanReadable() : uid;
- if(!version.isEmpty())
- {
- lines.append(QString("%1 (%2)").arg(readable, version));
- }
- else
- {
- lines.append(QString("%1").arg(readable));
- }
- iter++;
- }
- return lines.join('\n');
-}
-
-PackagesPage::PackagesPage(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::PackagesPage)
-{
- ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
-
- m_fileProxy = new QSortFilterProxyModel(this);
- m_fileProxy->setSortRole(Qt::DisplayRole);
- m_fileProxy->setSortCaseSensitivity(Qt::CaseInsensitive);
- m_fileProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
- m_fileProxy->setFilterRole(Qt::DisplayRole);
- m_fileProxy->setFilterKeyColumn(0);
- m_fileProxy->sort(0);
- m_fileProxy->setSourceModel(ENV.metadataIndex().get());
- ui->indexView->setModel(m_fileProxy);
-
- m_filterProxy = new QSortFilterProxyModel(this);
- m_filterProxy->setSortRole(VersionList::SortRole);
- m_filterProxy->setFilterCaseSensitivity(Qt::CaseInsensitive);
- m_filterProxy->setFilterRole(Qt::DisplayRole);
- m_filterProxy->setFilterKeyColumn(0);
- m_filterProxy->sort(0, Qt::DescendingOrder);
- ui->versionsView->setModel(m_filterProxy);
-
- m_versionProxy = new VersionProxyModel(this);
- m_filterProxy->setSourceModel(m_versionProxy);
-
- connect(ui->indexView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateCurrentVersionList);
- connect(ui->versionsView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PackagesPage::updateVersion);
- connect(m_filterProxy, &QSortFilterProxyModel::dataChanged, this, &PackagesPage::versionListDataChanged);
-
- updateCurrentVersionList(QModelIndex());
- updateVersion();
-}
-
-PackagesPage::~PackagesPage()
-{
- delete ui;
-}
-
-QIcon PackagesPage::icon() const
-{
- return MMC->getThemedIcon("packages");
-}
-
-void PackagesPage::on_refreshIndexBtn_clicked()
-{
- ENV.metadataIndex()->load(Net::Mode::Online);
-}
-void PackagesPage::on_refreshFileBtn_clicked()
-{
- VersionListPtr list = ui->indexView->currentIndex().data(Index::ListPtrRole).value<VersionListPtr>();
- if (!list)
- {
- return;
- }
- list->load(Net::Mode::Online);
-}
-void PackagesPage::on_refreshVersionBtn_clicked()
-{
- VersionPtr version = ui->versionsView->currentIndex().data(VersionList::VersionPtrRole).value<VersionPtr>();
- if (!version)
- {
- return;
- }
- version->load(Net::Mode::Online);
-}
-
-void PackagesPage::on_fileSearchEdit_textChanged(const QString &search)
-{
- if (search.isEmpty())
- {
- m_fileProxy->setFilterFixedString(QString());
- }
- else
- {
- QStringList parts = search.split(' ');
- std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape);
- m_fileProxy->setFilterRegExp(".*" + parts.join(".*") + ".*");
- }
-}
-void PackagesPage::on_versionSearchEdit_textChanged(const QString &search)
-{
- if (search.isEmpty())
- {
- m_filterProxy->setFilterFixedString(QString());
- }
- else
- {
- QStringList parts = search.split(' ');
- std::transform(parts.begin(), parts.end(), parts.begin(), &QRegularExpression::escape);
- m_filterProxy->setFilterRegExp(".*" + parts.join(".*") + ".*");
- }
-}
-
-void PackagesPage::updateCurrentVersionList(const QModelIndex &index)
-{
- if (index.isValid())
- {
- VersionListPtr list = index.data(Index::ListPtrRole).value<VersionListPtr>();
- ui->versionsBox->setEnabled(true);
- ui->refreshFileBtn->setEnabled(true);
- ui->fileUidLabel->setEnabled(true);
- ui->fileUid->setText(list->uid());
- ui->fileNameLabel->setEnabled(true);
- ui->fileName->setText(list->name());
- m_versionProxy->setSourceModel(list.get());
- ui->refreshFileBtn->setText(tr("Refresh %1").arg(list->humanReadable()));
- list->load(Net::Mode::Offline);
- }
- else
- {
- ui->versionsBox->setEnabled(false);
- ui->refreshFileBtn->setEnabled(false);
- ui->fileUidLabel->setEnabled(false);
- ui->fileUid->clear();
- ui->fileNameLabel->setEnabled(false);
- ui->fileName->clear();
- m_versionProxy->setSourceModel(nullptr);
- ui->refreshFileBtn->setText(tr("Refresh"));
- }
-}
-
-void PackagesPage::versionListDataChanged(const QModelIndex &tl, const QModelIndex &br)
-{
- if (QItemSelection(tl, br).contains(ui->versionsView->currentIndex()))
- {
- updateVersion();
- }
-}
-
-void PackagesPage::updateVersion()
-{
- VersionPtr version = std::dynamic_pointer_cast<Version>(
- ui->versionsView->currentIndex().data(VersionList::VersionPointerRole).value<BaseVersionPtr>());
- if (version)
- {
- ui->refreshVersionBtn->setEnabled(true);
- ui->versionVersionLabel->setEnabled(true);
- ui->versionVersion->setText(version->version());
- ui->versionTimeLabel->setEnabled(true);
- ui->versionTime->setText(version->time().toString("yyyy-MM-dd HH:mm"));
- ui->versionTypeLabel->setEnabled(true);
- ui->versionType->setText(version->type());
- ui->versionRequiresLabel->setEnabled(true);
- ui->versionRequires->setText(formatRequires(version));
- ui->refreshVersionBtn->setText(tr("Refresh %1").arg(version->version()));
- }
- else
- {
- ui->refreshVersionBtn->setEnabled(false);
- ui->versionVersionLabel->setEnabled(false);
- ui->versionVersion->clear();
- ui->versionTimeLabel->setEnabled(false);
- ui->versionTime->clear();
- ui->versionTypeLabel->setEnabled(false);
- ui->versionType->clear();
- ui->versionRequiresLabel->setEnabled(false);
- ui->versionRequires->clear();
- ui->refreshVersionBtn->setText(tr("Refresh"));
- }
-}
-
-void PackagesPage::openedImpl()
-{
- ENV.metadataIndex()->load(Net::Mode::Offline);
-}
diff --git a/application/pages/global/PackagesPage.h b/application/pages/global/PackagesPage.h
deleted file mode 100644
index c8aa6da6..00000000
--- a/application/pages/global/PackagesPage.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/* Copyright 2015-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * 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"
-
-namespace Ui {
-class PackagesPage;
-}
-
-class QSortFilterProxyModel;
-class VersionProxyModel;
-
-class PackagesPage : public QWidget, public BasePage
-{
- Q_OBJECT
-public:
- explicit PackagesPage(QWidget *parent = 0);
- ~PackagesPage();
-
- QString id() const override { return "packages-global"; }
- QString displayName() const override { return tr("Packages"); }
- QIcon icon() const override;
- void openedImpl() override;
-
-private slots:
- void on_refreshIndexBtn_clicked();
- void on_refreshFileBtn_clicked();
- void on_refreshVersionBtn_clicked();
- void on_fileSearchEdit_textChanged(const QString &search);
- void on_versionSearchEdit_textChanged(const QString &search);
- void updateCurrentVersionList(const QModelIndex &index);
- void versionListDataChanged(const QModelIndex &tl, const QModelIndex &br);
-
-private:
- Ui::PackagesPage *ui;
- QSortFilterProxyModel *m_fileProxy;
- QSortFilterProxyModel *m_filterProxy;
- VersionProxyModel *m_versionProxy;
-
- void updateVersion();
-};
diff --git a/application/pages/global/PackagesPage.ui b/application/pages/global/PackagesPage.ui
deleted file mode 100644
index 158bf1b4..00000000
--- a/application/pages/global/PackagesPage.ui
+++ /dev/null
@@ -1,252 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>PackagesPage</class>
- <widget class="QWidget" name="PackagesPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>636</width>
- <height>621</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string>Tab 1</string>
- </attribute>
- <layout class="QGridLayout" name="gridLayout">
- <item row="1" column="2">
- <widget class="QGroupBox" name="versionsBox">
- <property name="title">
- <string>Versions</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QLineEdit" name="versionSearchEdit">
- <property name="placeholderText">
- <string>Search...</string>
- </property>
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QTreeView" name="versionsView">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <attribute name="headerVisible">
- <bool>false</bool>
- </attribute>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_4">
- <item>
- <widget class="QPushButton" name="refreshVersionBtn">
- <property name="text">
- <string>Refresh</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QFormLayout" name="formLayout_2">
- <item row="0" column="0">
- <widget class="QLabel" name="versionVersionLabel">
- <property name="text">
- <string>Version:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="versionVersion">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="versionTimeLabel">
- <property name="text">
- <string>Time:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="versionTime">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="versionTypeLabel">
- <property name="text">
- <string>Type:</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLabel" name="versionType">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="versionRequiresLabel">
- <property name="text">
- <string>Dependencies:</string>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLabel" name="versionRequires">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QGroupBox" name="versionListsBox">
- <property name="title">
- <string>Resources</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLineEdit" name="fileSearchEdit">
- <property name="placeholderText">
- <string>Search...</string>
- </property>
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QTreeView" name="indexView">
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <attribute name="headerVisible">
- <bool>false</bool>
- </attribute>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="QPushButton" name="refreshFileBtn">
- <property name="text">
- <string>Refresh</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QFormLayout" name="formLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="fileUidLabel">
- <property name="text">
- <string>UID:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QLabel" name="fileUid">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="fileNameLabel">
- <property name="text">
- <string>Name:</string>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="fileName">
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- </layout>
- </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>
- </item>
- <item row="0" column="1" colspan="2">
- <widget class="QPushButton" name="refreshIndexBtn">
- <property name="text">
- <string>Refresh Index</string>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/application/pages/global/PasteEEPage.cpp b/application/pages/global/PasteEEPage.cpp
index b144c832..f932dede 100644
--- a/application/pages/global/PasteEEPage.cpp
+++ b/application/pages/global/PasteEEPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 5d64d567..001decdb 100644
--- a/application/pages/global/PasteEEPage.h
+++ b/application/pages/global/PasteEEPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 6dbd0a5d..809059ff 100644
--- a/application/pages/global/ProxyPage.cpp
+++ b/application/pages/global/ProxyPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,6 +20,7 @@
#include "settings/SettingsObject.h"
#include "MultiMC.h"
+#include "Env.h"
ProxyPage::ProxyPage(QWidget *parent) : QWidget(parent), ui(new Ui::ProxyPage)
{
@@ -75,6 +76,9 @@ void ProxyPage::applySettings()
s->set("ProxyPort", ui->proxyPortEdit->value());
s->set("ProxyUser", ui->proxyUserEdit->text());
s->set("ProxyPass", ui->proxyPassEdit->text());
+
+ ENV.updateProxySettings(proxyType, ui->proxyAddrEdit->text(), ui->proxyPortEdit->value(),
+ ui->proxyUserEdit->text(), ui->proxyPassEdit->text());
}
void ProxyPage::loadSettings()
{
@@ -91,7 +95,7 @@ void ProxyPage::loadSettings()
ui->proxyHTTPBtn->setChecked(true);
ui->proxyAddrEdit->setText(s->get("ProxyAddr").toString());
- ui->proxyPortEdit->setValue(s->get("ProxyPort").value<qint16>());
+ ui->proxyPortEdit->setValue(s->get("ProxyPort").value<uint16_t>());
ui->proxyUserEdit->setText(s->get("ProxyUser").toString());
ui->proxyPassEdit->setText(s->get("ProxyPass").toString());
}
diff --git a/application/pages/global/ProxyPage.h b/application/pages/global/ProxyPage.h
index 47b3004e..ff94ec49 100644
--- a/application/pages/global/ProxyPage.h
+++ b/application/pages/global/ProxyPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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
new file mode 100644
index 00000000..782f2ab3
--- /dev/null
+++ b/application/pages/instance/GameOptionsPage.cpp
@@ -0,0 +1,37 @@
+#include "GameOptionsPage.h"
+#include "ui_GameOptionsPage.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/gameoptions/GameOptions.h"
+
+GameOptionsPage::GameOptionsPage(MinecraftInstance * inst, QWidget* parent)
+ : QWidget(parent), ui(new Ui::GameOptionsPage)
+{
+ ui->setupUi(this);
+ ui->tabWidget->tabBar()->hide();
+ m_model = inst->gameOptionsModel();
+ ui->optionsView->setModel(m_model.get());
+ auto head = ui->optionsView->header();
+ if(head->count())
+ {
+ head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
+ for(int i = 1; i < head->count(); i++)
+ {
+ head->setSectionResizeMode(i, QHeaderView::Stretch);
+ }
+ }
+}
+
+GameOptionsPage::~GameOptionsPage()
+{
+ // m_model->save();
+}
+
+void GameOptionsPage::openedImpl()
+{
+ // m_model->observe();
+}
+
+void GameOptionsPage::closedImpl()
+{
+ // m_model->unobserve();
+}
diff --git a/application/pages/modplatform/TwitchPage.h b/application/pages/instance/GameOptionsPage.h
index 36080016..0fd2fbff 100644
--- a/application/pages/modplatform/TwitchPage.h
+++ b/application/pages/instance/GameOptionsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,46 +16,48 @@
#pragma once
#include <QWidget>
+#include <QString>
#include "pages/BasePage.h"
#include <MultiMC.h>
-#include "tasks/Task.h"
namespace Ui
{
-class TwitchPage;
+class GameOptionsPage;
}
-class NewInstanceDialog;
+class GameOptions;
+class MinecraftInstance;
-class TwitchPage : public QWidget, public BasePage
+class GameOptionsPage : public QWidget, public BasePage
{
Q_OBJECT
public:
- explicit TwitchPage(NewInstanceDialog* dialog, QWidget *parent = 0);
- virtual ~TwitchPage();
+ explicit GameOptionsPage(MinecraftInstance *inst, QWidget *parent = 0);
+ virtual ~GameOptionsPage();
+
+ void openedImpl() override;
+ void closedImpl() override;
+
virtual QString displayName() const override
{
- return tr("Twitch");
+ return tr("Game Options");
}
virtual QIcon icon() const override
{
- return MMC->getThemedIcon("twitch");
+ return MMC->getThemedIcon("settings");
}
virtual QString id() const override
{
- return "twitch";
+ return "gameoptions";
}
virtual QString helpPage() const override
{
- return "Twitch-platform";
+ return "Game-Options-management";
}
- virtual bool shouldDisplay() const override;
-
- void openedImpl() override;
-private:
- Ui::TwitchPage *ui = nullptr;
- NewInstanceDialog* dialog = nullptr;
+private: // data
+ Ui::GameOptionsPage *ui = nullptr;
+ std::shared_ptr<GameOptions> m_model;
};
diff --git a/application/pages/instance/GameOptionsPage.ui b/application/pages/instance/GameOptionsPage.ui
new file mode 100644
index 00000000..f0a5ce0e
--- /dev/null
+++ b/application/pages/instance/GameOptionsPage.ui
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>GameOptionsPage</class>
+ <widget class="QWidget" name="GameOptionsPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>706</width>
+ <height>575</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <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 row="0" column="0">
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <attribute name="title">
+ <string notr="true">Tab 1</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0" colspan="2">
+ <widget class="QTreeView" name="optionsView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>optionsView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/application/pages/instance/InstanceSettingsPage.cpp b/application/pages/instance/InstanceSettingsPage.cpp
index 0704ffc5..7bd424c0 100644
--- a/application/pages/instance/InstanceSettingsPage.cpp
+++ b/application/pages/instance/InstanceSettingsPage.cpp
@@ -19,8 +19,11 @@ 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);
+ connect(MMC, &MultiMC::globalSettingsClosed, this, &InstanceSettingsPage::loadSettings);
loadSettings();
}
@@ -34,6 +37,21 @@ InstanceSettingsPage::~InstanceSettingsPage()
delete ui;
}
+void InstanceSettingsPage::globalSettingsButtonClicked(bool)
+{
+ switch(ui->settingsTabs->currentIndex()) {
+ case 0:
+ MMC->ShowGlobalSettings(this, "java-settings");
+ return;
+ case 1:
+ MMC->ShowGlobalSettings(this, "minecraft-settings");
+ return;
+ case 2:
+ MMC->ShowGlobalSettings(this, "custom-commands");
+ return;
+ }
+}
+
bool InstanceSettingsPage::apply()
{
applySettings();
@@ -145,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()
@@ -176,6 +234,11 @@ void InstanceSettingsPage::loadSettings()
ui->maxMemSpinBox->setValue(min);
}
ui->permGenSpinBox->setValue(m_settings->get("PermGen").toInt());
+ bool permGenVisible = m_settings->get("PermGenVisible").toBool();
+ ui->permGenSpinBox->setVisible(permGenVisible);
+ ui->labelPermGen->setVisible(permGenVisible);
+ ui->labelPermgenNote->setVisible(permGenVisible);
+
// Java Settings
bool overrideJava = m_settings->get("OverrideJava").toBool();
@@ -196,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()
@@ -210,6 +286,11 @@ void InstanceSettingsPage::on_javaDetectBtn_clicked()
{
java = std::dynamic_pointer_cast<JavaInstall>(vselect.selectedVersion());
ui->javaPathTextBox->setText(java->path);
+ bool visible = java->id.requiresPermGen() && m_settings->get("OverrideMemory").toBool();
+ ui->permGenSpinBox->setVisible(visible);
+ ui->labelPermGen->setVisible(visible);
+ ui->labelPermgenNote->setVisible(visible);
+ m_settings->set("PermGenVisible", visible);
}
}
@@ -224,12 +305,18 @@ void InstanceSettingsPage::on_javaBrowseBtn_clicked()
}
QString cooked_path = FS::NormalizePath(raw_path);
- QFileInfo javaInfo(cooked_path);;
+ QFileInfo javaInfo(cooked_path);
if(!javaInfo.exists() || !javaInfo.isExecutable())
{
return;
}
ui->javaPathTextBox->setText(cooked_path);
+
+ // custom Java could be anything... enable perm gen option
+ ui->permGenSpinBox->setVisible(true);
+ ui->labelPermGen->setVisible(true);
+ ui->labelPermgenNote->setVisible(true);
+ m_settings->set("PermGenVisible", true);
}
void InstanceSettingsPage::on_javaTestBtn_clicked()
diff --git a/application/pages/instance/InstanceSettingsPage.h b/application/pages/instance/InstanceSettingsPage.h
index cc35732e..068213a8 100644
--- a/application/pages/instance/InstanceSettingsPage.h
+++ b/application/pages/instance/InstanceSettingsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -66,6 +66,8 @@ private slots:
void checkerFinished();
+ void globalSettingsButtonClicked(bool checked);
+
private:
Ui::InstanceSettingsPage *ui;
BaseInstance *m_instance;
diff --git a/application/pages/instance/InstanceSettingsPage.ui b/application/pages/instance/InstanceSettingsPage.ui
index 0c180df3..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>553</width>
- <height>522</height>
+ <width>691</width>
+ <height>581</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
@@ -24,6 +24,16 @@
<number>0</number>
</property>
<item>
+ <widget class="QCommandLinkButton" name="openGlobalJavaSettingsButton">
+ <property name="text">
+ <string>Open Global Settings</string>
+ </property>
+ <property name="description">
+ <string>The settings here are overrides for global settings.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QTabWidget" name="settingsTabs">
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
@@ -106,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>
@@ -128,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>
@@ -150,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>
@@ -354,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>
@@ -367,6 +516,7 @@
</customwidget>
</customwidgets>
<tabstops>
+ <tabstop>openGlobalJavaSettingsButton</tabstop>
<tabstop>settingsTabs</tabstop>
<tabstop>javaSettingsGroupBox</tabstop>
<tabstop>javaPathTextBox</tabstop>
@@ -387,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.cpp b/application/pages/instance/LegacyUpgradePage.cpp
index 15fd10cc..af800b03 100644
--- a/application/pages/instance/LegacyUpgradePage.cpp
+++ b/application/pages/instance/LegacyUpgradePage.cpp
@@ -40,7 +40,7 @@ void LegacyUpgradePage::on_upgradeButton_clicked()
upgradeTask->setName(newName);
upgradeTask->setGroup(MMC->instances()->getInstanceGroup(m_inst->id()));
upgradeTask->setIcon(m_inst->iconKey());
- std::unique_ptr<Task> task(MMC->instances()->wrapInstanceTask(upgradeTask));
+ unique_qobject_ptr<Task> task(MMC->instances()->wrapInstanceTask(upgradeTask));
runModalTask(task.get());
}
diff --git a/application/pages/instance/LegacyUpgradePage.h b/application/pages/instance/LegacyUpgradePage.h
index 4136d703..df34e33a 100644
--- a/application/pages/instance/LegacyUpgradePage.h
+++ b/application/pages/instance/LegacyUpgradePage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 0e480a3a..3d2085c6 100644
--- a/application/pages/instance/LogPage.cpp
+++ b/application/pages/instance/LogPage.cpp
@@ -192,7 +192,7 @@ void LogPage::UIToModelState()
m_model->suspend(ui->trackLogCheckbox->checkState() != Qt::Checked);
}
-void LogPage::setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial)
+void LogPage::setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial)
{
m_process = proc;
if(m_process)
@@ -215,7 +215,7 @@ void LogPage::setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, boo
}
}
-void LogPage::onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc)
+void LogPage::onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc)
{
setInstanceLaunchTaskChanged(proc, false);
}
@@ -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 b9c4d302..b0b0e04b 100644
--- a/application/pages/instance/LogPage.h
+++ b/application/pages/instance/LogPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -69,17 +69,17 @@ private slots:
void findNextActivated();
void findPreviousActivated();
- void onInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc);
+ void onInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc);
private:
void modelStateToUI();
void UIToModelState();
- void setInstanceLaunchTaskChanged(std::shared_ptr<LaunchTask> proc, bool initial);
+ void setInstanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc, bool initial);
private:
Ui::LogPage *ui;
InstancePtr m_instance;
- std::shared_ptr<LaunchTask> m_process;
+ shared_qobject_ptr<LaunchTask> m_process;
LogFormatProxyModel * m_proxy;
shared_qobject_ptr <LogModel> m_model;
diff --git a/application/pages/instance/ModFolderPage.cpp b/application/pages/instance/ModFolderPage.cpp
index d891c068..98f20e77 100644
--- a/application/pages/instance/ModFolderPage.cpp
+++ b/application/pages/instance/ModFolderPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,32 +20,135 @@
#include <QEvent>
#include <QKeyEvent>
#include <QAbstractItemModel>
+#include <QMenu>
#include "MultiMC.h"
#include "dialogs/CustomMessageBox.h"
-#include "dialogs/ModEditDialogCommon.h"
#include <GuiUtil.h>
-#include "minecraft/SimpleModList.h"
-#include "minecraft/Mod.h"
+#include "minecraft/mod/ModFolderModel.h"
+#include "minecraft/mod/Mod.h"
#include "minecraft/VersionFilterData.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include <DesktopServices.h>
-ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id,
- QString iconName, QString displayName, QString helpPage,
- QWidget *parent)
- : QWidget(parent), ui(new Ui::ModFolderPage)
+#include <QSortFilterProxyModel>
+#include "Version.h"
+
+namespace {
+ // FIXME: wasteful
+ void RemoveThePrefix(QString & string) {
+ QRegularExpression regex(QStringLiteral("^(([Tt][Hh][eE])|([Tt][eE][Hh])) +"));
+ string.remove(regex);
+ string = string.trimmed();
+ }
+}
+
+class ModSortProxy : public QSortFilterProxyModel
+{
+public:
+ explicit ModSortProxy(QObject *parent = 0) : QSortFilterProxyModel(parent)
+ {
+ }
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override {
+ ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
+ if(!model) {
+ return false;
+ }
+ const auto &mod = model->at(source_row);
+ if(mod.name().contains(filterRegExp())) {
+ return true;
+ }
+ if(mod.description().contains(filterRegExp())) {
+ return true;
+ }
+ for(auto & author: mod.authors()) {
+ if (author.contains(filterRegExp())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override
+ {
+ ModFolderModel *model = qobject_cast<ModFolderModel *>(sourceModel());
+ if(
+ !model ||
+ !source_left.isValid() ||
+ !source_right.isValid() ||
+ source_left.column() != source_right.column()
+ ) {
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+ }
+
+ // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and proceed.
+
+ auto column = (ModFolderModel::Columns) source_left.column();
+ bool invert = false;
+ switch(column) {
+ // GH-2550 - sort by enabled/disabled
+ case ModFolderModel::ActiveColumn: {
+ auto dataL = source_left.data(Qt::CheckStateRole).toBool();
+ auto dataR = source_right.data(Qt::CheckStateRole).toBool();
+ if(dataL != dataR) {
+ return dataL > dataR;
+ }
+ // fallthrough
+ invert = sortOrder() == Qt::DescendingOrder;
+ }
+ // GH-2722 - sort mod names in a way that discards "The" prefixes
+ case ModFolderModel::NameColumn: {
+ auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString();
+ RemoveThePrefix(dataL);
+ auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString();
+ RemoveThePrefix(dataR);
+
+ auto less = dataL.compare(dataR, sortCaseSensitivity());
+ if(less != 0) {
+ return invert ? (less > 0) : (less < 0);
+ }
+ // fallthrough
+ invert = sortOrder() == Qt::DescendingOrder;
+ }
+ // GH-2762 - sort versions by parsing them as versions
+ case ModFolderModel::VersionColumn: {
+ auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString());
+ auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString());
+ return invert ? (dataL > dataR) : (dataL < dataR);
+ }
+ default: {
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+ }
+ }
+ }
+};
+
+ModFolderPage::ModFolderPage(
+ BaseInstance *inst,
+ std::shared_ptr<ModFolderModel> mods,
+ QString id,
+ QString iconName,
+ QString displayName,
+ QString helpPage,
+ QWidget *parent
+) :
+ QMainWindow(parent),
+ ui(new Ui::ModFolderPage)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
+ ui->actionsToolbar->insertSpacer(ui->actionView_configs);
+
m_inst = inst;
+ on_RunningState_changed(m_inst && m_inst->isRunning());
m_mods = mods;
m_id = id;
m_displayName = displayName;
m_iconName = iconName;
m_helpName = helpPage;
m_fileSelectionFilter = "%1 (*.zip *.jar)";
- m_filterModel = new QSortFilterProxyModel(this);
+ m_filterModel = new ModSortProxy(this);
m_filterModel->setDynamicSortFilter(true);
m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
@@ -54,9 +157,37 @@ ModFolderPage::ModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList>
ui->modTreeView->setModel(m_filterModel);
ui->modTreeView->installEventFilter(this);
ui->modTreeView->sortByColumn(1, Qt::AscendingOrder);
+ ui->modTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->modTreeView, &ModListView::customContextMenuRequested, this, &ModFolderPage::ShowContextMenu);
+ connect(ui->modTreeView, &ModListView::activated, this, &ModFolderPage::modItemActivated);
+
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);
+}
+
+void ModFolderPage::modItemActivated(const QModelIndex&)
+{
+ if(!m_controlsEnabled) {
+ return;
+ }
+ auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
+ m_mods->setModStatus(selection.indexes(), ModFolderModel::Toggle);
+}
+
+QMenu * ModFolderPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction(ui->actionsToolbar->toggleViewAction() );
+ return filteredMenu;
+}
+
+void ModFolderPage::ShowContextMenu(const QPoint& pos)
+{
+ auto menu = ui->actionsToolbar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->modTreeView->mapToGlobal(pos));
+ delete menu;
}
void ModFolderPage::openedImpl()
@@ -76,7 +207,7 @@ void ModFolderPage::on_filterTextChanged(const QString& newContents)
}
-CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods,
+CoreModFolderPage::CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods,
QString id, QString iconName, QString displayName,
QString helpPage, QWidget *parent)
: ModFolderPage(inst, mods, id, iconName, displayName, helpPage, parent)
@@ -89,10 +220,20 @@ ModFolderPage::~ModFolderPage()
delete ui;
}
+void ModFolderPage::on_RunningState_changed(bool running)
+{
+ if(m_controlsEnabled == !running) {
+ return;
+ }
+ m_controlsEnabled = !running;
+ ui->actionAdd->setEnabled(m_controlsEnabled);
+ ui->actionDisable->setEnabled(m_controlsEnabled);
+ ui->actionEnable->setEnabled(m_controlsEnabled);
+ ui->actionRemove->setEnabled(m_controlsEnabled);
+}
+
bool ModFolderPage::shouldDisplay() const
{
- if (m_inst)
- return !m_inst->isRunning();
return true;
}
@@ -103,7 +244,7 @@ bool CoreModFolderPage::shouldDisplay() const
auto inst = dynamic_cast<MinecraftInstance *>(m_inst);
if (!inst)
return true;
- auto version = inst->getComponentList();
+ auto version = inst->getPackProfile();
if (!version)
return true;
if(!version->getComponent("net.minecraftforge"))
@@ -127,10 +268,10 @@ bool ModFolderPage::modListFilter(QKeyEvent *keyEvent)
switch (keyEvent->key())
{
case Qt::Key_Delete:
- on_rmModBtn_clicked();
+ on_actionRemove_triggered();
return true;
case Qt::Key_Plus:
- on_addModBtn_clicked();
+ on_actionAdd_triggered();
return true;
default:
break;
@@ -150,8 +291,11 @@ bool ModFolderPage::eventFilter(QObject *obj, QEvent *ev)
return QWidget::eventFilter(obj, ev);
}
-void ModFolderPage::on_addModBtn_clicked()
+void ModFolderPage::on_actionAdd_triggered()
{
+ if(!m_controlsEnabled) {
+ return;
+ }
auto list = GuiUtil::BrowseForFiles(
m_helpName,
tr("Select %1",
@@ -168,30 +312,39 @@ void ModFolderPage::on_addModBtn_clicked()
}
}
-void ModFolderPage::on_enableModBtn_clicked()
+void ModFolderPage::on_actionEnable_triggered()
{
+ if(!m_controlsEnabled) {
+ return;
+ }
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->enableMods(selection.indexes(), true);
+ m_mods->setModStatus(selection.indexes(), ModFolderModel::Enable);
}
-void ModFolderPage::on_disableModBtn_clicked()
+void ModFolderPage::on_actionDisable_triggered()
{
+ if(!m_controlsEnabled) {
+ return;
+ }
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->enableMods(selection.indexes(), false);
+ m_mods->setModStatus(selection.indexes(), ModFolderModel::Disable);
}
-void ModFolderPage::on_rmModBtn_clicked()
+void ModFolderPage::on_actionRemove_triggered()
{
+ if(!m_controlsEnabled) {
+ return;
+ }
auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
m_mods->deleteMods(selection.indexes());
}
-void ModFolderPage::on_configFolderBtn_clicked()
+void ModFolderPage::on_actionView_configs_triggered()
{
DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true);
}
-void ModFolderPage::on_viewModBtn_clicked()
+void ModFolderPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_mods->dir().absolutePath(), true);
}
diff --git a/application/pages/instance/ModFolderPage.h b/application/pages/instance/ModFolderPage.h
index 52f19e87..f653a8c0 100644
--- a/application/pages/instance/ModFolderPage.h
+++ b/application/pages/instance/ModFolderPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,26 +15,32 @@
#pragma once
-#include <QWidget>
+#include <QMainWindow>
#include "minecraft/MinecraftInstance.h"
#include "pages/BasePage.h"
#include <MultiMC.h>
-class SimpleModList;
+class ModFolderModel;
namespace Ui
{
class ModFolderPage;
}
-class ModFolderPage : public QWidget, public BasePage
+class ModFolderPage : public QMainWindow, public BasePage
{
Q_OBJECT
public:
- explicit ModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id,
- QString iconName, QString displayName, QString helpPage = "",
- QWidget *parent = 0);
+ explicit ModFolderPage(
+ BaseInstance *inst,
+ std::shared_ptr<ModFolderModel> mods,
+ QString id,
+ QString iconName,
+ QString displayName,
+ QString helpPage = "",
+ QWidget *parent = 0
+ );
virtual ~ModFolderPage();
void setFilter(const QString & filter)
@@ -65,20 +71,22 @@ public:
protected:
bool eventFilter(QObject *obj, QEvent *ev) override;
bool modListFilter(QKeyEvent *ev);
+ QMenu * createPopupMenu() override;
protected:
- BaseInstance *m_inst;
+ BaseInstance *m_inst = nullptr;
protected:
- Ui::ModFolderPage *ui;
- std::shared_ptr<SimpleModList> m_mods;
- QSortFilterProxyModel *m_filterModel;
+ Ui::ModFolderPage *ui = nullptr;
+ std::shared_ptr<ModFolderModel> m_mods;
+ QSortFilterProxyModel *m_filterModel = nullptr;
QString m_iconName;
QString m_id;
QString m_displayName;
QString m_helpName;
QString m_fileSelectionFilter;
QString m_viewFilter;
+ bool m_controlsEnabled = true;
public
slots:
@@ -86,19 +94,22 @@ slots:
private
slots:
+ void modItemActivated(const QModelIndex &index);
void on_filterTextChanged(const QString & newContents);
- void on_addModBtn_clicked();
- void on_rmModBtn_clicked();
- void on_viewModBtn_clicked();
- void on_enableModBtn_clicked();
- void on_disableModBtn_clicked();
- void on_configFolderBtn_clicked();
+ void on_RunningState_changed(bool running);
+ void on_actionAdd_triggered();
+ void on_actionRemove_triggered();
+ void on_actionEnable_triggered();
+ void on_actionDisable_triggered();
+ void on_actionView_Folder_triggered();
+ void on_actionView_configs_triggered();
+ void ShowContextMenu(const QPoint &pos);
};
class CoreModFolderPage : public ModFolderPage
{
public:
- explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<SimpleModList> mods, QString id,
+ explicit CoreModFolderPage(BaseInstance *inst, std::shared_ptr<ModFolderModel> mods, QString id,
QString iconName, QString displayName, QString helpPage = "",
QWidget *parent = 0);
virtual ~CoreModFolderPage()
diff --git a/application/pages/instance/ModFolderPage.ui b/application/pages/instance/ModFolderPage.ui
index b5597bdc..954a0167 100644
--- a/application/pages/instance/ModFolderPage.ui
+++ b/application/pages/instance/ModFolderPage.ui
@@ -1,155 +1,141 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ModFolderPage</class>
- <widget class="QWidget" name="ModFolderPage">
+ <widget class="QMainWindow" name="ModFolderPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>723</width>
- <height>532</height>
+ <width>1042</width>
+ <height>501</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout">
+ <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 row="4" column="1" colspan="3">
+ <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 row="2" column="1" colspan="3">
+ <widget class="MCModInfoFrame" name="frame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="3">
+ <widget class="ModListView" name="modTreeView">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
- <item row="0" column="2">
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QPushButton" name="addModBtn">
- <property name="text">
- <string>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmModBtn">
- <property name="text">
- <string>&amp;Remove</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="enableModBtn">
- <property name="text">
- <string>Enable</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="disableModBtn">
- <property name="text">
- <string>Disable</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="configFolderBtn">
- <property name="toolTip">
- <string>Open the 'config' folder in the system file manager.</string>
- </property>
- <property name="text">
- <string>View configs</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="viewModBtn">
- <property name="text">
- <string>&amp;View Folder</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0" colspan="3">
- <widget class="MCModInfoFrame" name="frame">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="1">
- <widget class="QLineEdit" name="filterEdit">
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="filterLabel">
- <property name="text">
- <string>Filter:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="3">
- <widget class="ModListView" name="modTreeView">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="acceptDrops">
- <bool>true</bool>
- </property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::DropOnly</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DropOnly</enum>
+ </property>
</widget>
- </widget>
- </item>
- </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WideBar" name="actionsToolbar">
+ <property name="windowTitle">
+ <string>Actions</string>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextOnly</enum>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>RightToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionAdd"/>
+ <addaction name="separator"/>
+ <addaction name="actionRemove"/>
+ <addaction name="actionEnable"/>
+ <addaction name="actionDisable"/>
+ <addaction name="actionView_configs"/>
+ <addaction name="actionView_Folder"/>
+ </widget>
+ <action name="actionAdd">
+ <property name="text">
+ <string>&amp;Add</string>
+ </property>
+ <property name="toolTip">
+ <string>Add mods</string>
+ </property>
+ </action>
+ <action name="actionRemove">
+ <property name="text">
+ <string>&amp;Remove</string>
+ </property>
+ <property name="toolTip">
+ <string>Remove selected mods</string>
+ </property>
+ </action>
+ <action name="actionEnable">
+ <property name="text">
+ <string>&amp;Enable</string>
+ </property>
+ <property name="toolTip">
+ <string>Enable selected mods</string>
+ </property>
+ </action>
+ <action name="actionDisable">
+ <property name="text">
+ <string>&amp;Disable</string>
+ </property>
+ <property name="toolTip">
+ <string>Disable selected mods</string>
+ </property>
+ </action>
+ <action name="actionView_configs">
+ <property name="text">
+ <string>View &amp;Configs</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the 'config' folder in the system file manager.</string>
+ </property>
+ </action>
+ <action name="actionView_Folder">
+ <property name="text">
+ <string>View &amp;Folder</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
@@ -163,17 +149,15 @@
<header>widgets/MCModInfoFrame.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>WideBar</class>
+ <extends>QToolBar</extends>
+ <header>widgets/WideBar.h</header>
+ </customwidget>
</customwidgets>
<tabstops>
- <tabstop>tabWidget</tabstop>
<tabstop>modTreeView</tabstop>
<tabstop>filterEdit</tabstop>
- <tabstop>addModBtn</tabstop>
- <tabstop>rmModBtn</tabstop>
- <tabstop>enableModBtn</tabstop>
- <tabstop>disableModBtn</tabstop>
- <tabstop>configFolderBtn</tabstop>
- <tabstop>viewModBtn</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/application/pages/instance/NewModFolderPage.cpp b/application/pages/instance/NewModFolderPage.cpp
deleted file mode 100644
index da65bc9a..00000000
--- a/application/pages/instance/NewModFolderPage.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "NewModFolderPage.h"
-#include "ui_NewModFolderPage.h"
-
-#include <QMessageBox>
-#include <QEvent>
-#include <QKeyEvent>
-#include <QAbstractItemModel>
-
-#include "MultiMC.h"
-#include "dialogs/CustomMessageBox.h"
-#include "dialogs/ModEditDialogCommon.h"
-#include <GuiUtil.h>
-#include "minecraft/ModsModel.h"
-#include "minecraft/Mod.h"
-#include "minecraft/VersionFilterData.h"
-#include "minecraft/ComponentList.h"
-#include <DesktopServices.h>
-
-NewModFolderPage::NewModFolderPage(BaseInstance *inst, std::shared_ptr<ModsModel> mods, QString id,
- QString iconName, QString displayName, QString helpPage,
- QWidget *parent)
- : QWidget(parent), ui(new Ui::NewModFolderPage)
-{
- ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
- m_inst = inst;
- m_mods = mods;
- m_id = id;
- m_displayName = displayName;
- m_iconName = iconName;
- m_helpName = helpPage;
- m_fileSelectionFilter = "%1 (*.zip *.jar)";
- m_filterModel = new QSortFilterProxyModel(this);
- m_filterModel->setDynamicSortFilter(true);
- m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
- m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive);
- m_filterModel->setSourceModel(m_mods.get());
- m_filterModel->setFilterKeyColumn(-1);
- ui->modTreeView->setModel(m_filterModel);
- ui->modTreeView->installEventFilter(this);
- ui->modTreeView->sortByColumn(1, Qt::AscendingOrder);
- auto smodel = ui->modTreeView->selectionModel();
- connect(smodel, &QItemSelectionModel::currentChanged, this, &NewModFolderPage::modCurrent);
- connect(ui->filterEdit, &QLineEdit::textChanged, this, &NewModFolderPage::on_filterTextChanged );
-}
-
-void NewModFolderPage::openedImpl()
-{
- m_mods->startWatching();
-}
-
-void NewModFolderPage::closedImpl()
-{
- m_mods->stopWatching();
-}
-
-void NewModFolderPage::on_filterTextChanged(const QString& newContents)
-{
- m_viewFilter = newContents;
- m_filterModel->setFilterFixedString(m_viewFilter);
-}
-
-
-NewModFolderPage::~NewModFolderPage()
-{
- m_mods->stopWatching();
- delete ui;
-}
-
-bool NewModFolderPage::shouldDisplay() const
-{
- if (m_inst)
- return !m_inst->isRunning();
- return true;
-}
-
-bool NewModFolderPage::modListFilter(QKeyEvent *keyEvent)
-{
- switch (keyEvent->key())
- {
- case Qt::Key_Delete:
- on_rmModBtn_clicked();
- return true;
- case Qt::Key_Plus:
- on_addModBtn_clicked();
- return true;
- default:
- break;
- }
- return QWidget::eventFilter(ui->modTreeView, keyEvent);
-}
-
-bool NewModFolderPage::eventFilter(QObject *obj, QEvent *ev)
-{
- if (ev->type() != QEvent::KeyPress)
- {
- return QWidget::eventFilter(obj, ev);
- }
- QKeyEvent *keyEvent = static_cast<QKeyEvent *>(ev);
- if (obj == ui->modTreeView)
- return modListFilter(keyEvent);
- return QWidget::eventFilter(obj, ev);
-}
-
-void NewModFolderPage::on_addModBtn_clicked()
-{
- auto list = GuiUtil::BrowseForFiles(
- m_helpName,
- tr("Select %1",
- "Select whatever type of files the page contains. Example: 'Loader Mods'")
- .arg(m_displayName),
- m_fileSelectionFilter.arg(m_displayName), MMC->settings()->get("CentralModsDir").toString(),
- this->parentWidget());
- if (!list.empty())
- {
- for (auto filename : list)
- {
- m_mods->installMod(filename);
- }
- }
-}
-
-void NewModFolderPage::on_enableModBtn_clicked()
-{
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->enableMods(selection.indexes(), true);
-}
-
-void NewModFolderPage::on_disableModBtn_clicked()
-{
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->enableMods(selection.indexes(), false);
-}
-
-void NewModFolderPage::on_rmModBtn_clicked()
-{
- auto selection = m_filterModel->mapSelectionToSource(ui->modTreeView->selectionModel()->selection());
- m_mods->deleteMods(selection.indexes());
-}
-
-void NewModFolderPage::on_configFolderBtn_clicked()
-{
- DesktopServices::openDirectory(m_inst->instanceConfigFolder(), true);
-}
-
-void NewModFolderPage::on_viewModBtn_clicked()
-{
- DesktopServices::openDirectory(m_mods->dir().absolutePath(), true);
-}
-
-void NewModFolderPage::modCurrent(const QModelIndex &current, const QModelIndex &previous)
-{
- if (!current.isValid())
- {
- ui->frame->clear();
- return;
- }
- auto sourceCurrent = m_filterModel->mapToSource(current);
- int row = sourceCurrent.row();
- Mod &m = m_mods->operator[](row);
- ui->frame->updateWithMod(m);
-}
diff --git a/application/pages/instance/NewModFolderPage.h b/application/pages/instance/NewModFolderPage.h
deleted file mode 100644
index 5446a607..00000000
--- a/application/pages/instance/NewModFolderPage.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/* Copyright 2013-2018 MultiMC Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <QWidget>
-
-#include "minecraft/MinecraftInstance.h"
-#include "pages/BasePage.h"
-#include <MultiMC.h>
-
-class SimpleModList;
-namespace Ui
-{
-class NewModFolderPage;
-}
-
-class NewModFolderPage : public QWidget, public BasePage
-{
- Q_OBJECT
-
-public:
- explicit NewModFolderPage(BaseInstance *inst, std::shared_ptr<ModsModel> mods, QString id,
- QString iconName, QString displayName, QString helpPage = "",
- QWidget *parent = 0);
- virtual ~NewModFolderPage();
-
- void setFilter(const QString & filter)
- {
- m_fileSelectionFilter = filter;
- }
-
- virtual QString displayName() const override
- {
- return m_displayName;
- }
- virtual QIcon icon() const override
- {
- return MMC->getThemedIcon(m_iconName);
- }
- virtual QString id() const override
- {
- return m_id;
- }
- virtual QString helpPage() const override
- {
- return m_helpName;
- }
- virtual bool shouldDisplay() const override;
-
- virtual void openedImpl() override;
- virtual void closedImpl() override;
-protected:
- bool eventFilter(QObject *obj, QEvent *ev) override;
- bool modListFilter(QKeyEvent *ev);
-
-protected:
- BaseInstance *m_inst;
-
-protected:
- Ui::NewModFolderPage *ui;
- std::shared_ptr<ModsModel> m_mods;
- QSortFilterProxyModel *m_filterModel;
- QString m_iconName;
- QString m_id;
- QString m_displayName;
- QString m_helpName;
- QString m_fileSelectionFilter;
- QString m_viewFilter;
-
-public
-slots:
- void modCurrent(const QModelIndex &current, const QModelIndex &previous);
-
-private
-slots:
- void on_filterTextChanged(const QString & newContents);
- void on_addModBtn_clicked();
- void on_rmModBtn_clicked();
- void on_viewModBtn_clicked();
- void on_enableModBtn_clicked();
- void on_disableModBtn_clicked();
- void on_configFolderBtn_clicked();
-};
-
diff --git a/application/pages/instance/NewModFolderPage.ui b/application/pages/instance/NewModFolderPage.ui
deleted file mode 100644
index 48c36383..00000000
--- a/application/pages/instance/NewModFolderPage.ui
+++ /dev/null
@@ -1,180 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>NewModFolderPage</class>
- <widget class="QWidget" name="NewModFolderPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>723</width>
- <height>532</height>
- </rect>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
- <item row="0" column="2">
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QPushButton" name="addModBtn">
- <property name="text">
- <string>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmModBtn">
- <property name="text">
- <string>&amp;Remove</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="enableModBtn">
- <property name="text">
- <string>Enable</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="disableModBtn">
- <property name="text">
- <string>Disable</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="configFolderBtn">
- <property name="toolTip">
- <string>Open the 'config' folder in the system file manager.</string>
- </property>
- <property name="text">
- <string>View configs</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="viewModBtn">
- <property name="text">
- <string>&amp;View Folder</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="1" column="0" colspan="3">
- <widget class="MCModInfoFrame" name="frame">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_2">
- <item row="1" column="1">
- <widget class="QLineEdit" name="filterEdit">
- <property name="clearButtonEnabled">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="filterLabel">
- <property name="text">
- <string>Filter:</string>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="3">
- <widget class="ModListView" name="modTreeView">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="acceptDrops">
- <bool>true</bool>
- </property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::DropOnly</enum>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
- </widget>
- <customwidgets>
- <customwidget>
- <class>ModListView</class>
- <extends>QTreeView</extends>
- <header>widgets/ModListView.h</header>
- </customwidget>
- <customwidget>
- <class>MCModInfoFrame</class>
- <extends>QFrame</extends>
- <header>widgets/MCModInfoFrame.h</header>
- <container>1</container>
- </customwidget>
- </customwidgets>
- <tabstops>
- <tabstop>tabWidget</tabstop>
- <tabstop>modTreeView</tabstop>
- <tabstop>filterEdit</tabstop>
- <tabstop>addModBtn</tabstop>
- <tabstop>rmModBtn</tabstop>
- <tabstop>enableModBtn</tabstop>
- <tabstop>disableModBtn</tabstop>
- <tabstop>configFolderBtn</tabstop>
- <tabstop>viewModBtn</tabstop>
- </tabstops>
- <resources/>
- <connections/>
-</ui>
diff --git a/application/pages/instance/NotesPage.cpp b/application/pages/instance/NotesPage.cpp
index 6cc2c2f4..fa966c91 100644
--- a/application/pages/instance/NotesPage.cpp
+++ b/application/pages/instance/NotesPage.cpp
@@ -6,7 +6,6 @@ NotesPage::NotesPage(BaseInstance *inst, QWidget *parent)
: QWidget(parent), ui(new Ui::NotesPage), m_inst(inst)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
ui->noteEditor->setText(m_inst->notes());
}
diff --git a/application/pages/instance/NotesPage.h b/application/pages/instance/NotesPage.h
index 9941be4f..d0c00ac1 100644
--- a/application/pages/instance/NotesPage.h
+++ b/application/pages/instance/NotesPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.ui b/application/pages/instance/NotesPage.ui
index 88cca92f..67cb261c 100644
--- a/application/pages/instance/NotesPage.ui
+++ b/application/pages/instance/NotesPage.ui
@@ -10,7 +10,7 @@
<height>538</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
@@ -24,34 +24,26 @@
<number>0</number>
</property>
<item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
+ <widget class="QTextEdit" name="noteEditor">
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="tabChangesFocus">
+ <bool>true</bool>
+ </property>
+ <property name="acceptRichText">
+ <bool>false</bool>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QTextEdit" name="noteEditor">
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOn</enum>
- </property>
- <property name="acceptRichText">
- <bool>false</bool>
- </property>
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextEditable|Qt::TextEditorInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
</widget>
</item>
</layout>
</widget>
+ <tabstops>
+ <tabstop>noteEditor</tabstop>
+ </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/application/pages/instance/OtherLogsPage.cpp b/application/pages/instance/OtherLogsPage.cpp
index 69c33a85..b67b84bd 100644
--- a/application/pages/instance/OtherLogsPage.cpp
+++ b/application/pages/instance/OtherLogsPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 15e9f422..7f21c0fa 100644
--- a/application/pages/instance/OtherLogsPage.h
+++ b/application/pages/instance/OtherLogsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 00e215da..1486bf52 100644
--- a/application/pages/instance/ResourcePackPage.h
+++ b/application/pages/instance/ResourcePackPage.h
@@ -1,18 +1,20 @@
#pragma once
+
#include "ModFolderPage.h"
#include "ui_ModFolderPage.h"
class ResourcePackPage : public ModFolderPage
{
+ Q_OBJECT
public:
explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0)
: ModFolderPage(instance, instance->resourcePackList(), "resourcepacks",
"resourcepacks", tr("Resource packs"), "Resource-packs", parent)
{
- ui->configFolderBtn->setHidden(true);
+ 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.cpp b/application/pages/instance/ScreenshotsPage.cpp
index 3420e86b..efa0f9f2 100644
--- a/application/pages/instance/ScreenshotsPage.cpp
+++ b/application/pages/instance/ScreenshotsPage.cpp
@@ -13,6 +13,7 @@
#include <QPainter>
#include <QClipboard>
#include <QKeyEvent>
+#include <QMenu>
#include <MultiMC.h>
@@ -209,7 +210,7 @@ public:
};
ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)
- : QWidget(parent), ui(new Ui::ScreenshotsPage)
+ : QMainWindow(parent), ui(new Ui::ScreenshotsPage)
{
m_model.reset(new QFileSystemModel());
m_filterModel.reset(new FilterModel());
@@ -222,7 +223,8 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)
m_valid = FS::ensureFolderPathExists(m_folder);
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
+ ui->toolBar->insertSpacer(ui->actionView_Folder);
+
ui->listView->setIconSize(QSize(128, 128));
ui->listView->setGridSize(QSize(192, 160));
ui->listView->setSpacing(9);
@@ -233,6 +235,8 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent)
ui->listView->installEventFilter(this);
ui->listView->setEditTriggers(0);
ui->listView->setItemDelegate(new CenteredEditingDelegate(this));
+ ui->listView->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->listView, &QListView::customContextMenuRequested, this, &ScreenshotsPage::ShowContextMenu);
connect(ui->listView, SIGNAL(activated(QModelIndex)), SLOT(onItemActivated(QModelIndex)));
}
@@ -248,10 +252,10 @@ bool ScreenshotsPage::eventFilter(QObject *obj, QEvent *evt)
switch (keyEvent->key())
{
case Qt::Key_Delete:
- on_deleteBtn_clicked();
+ on_actionDelete_triggered();
return true;
case Qt::Key_F2:
- on_renameBtn_clicked();
+ on_actionRename_triggered();
return true;
default:
break;
@@ -264,6 +268,20 @@ ScreenshotsPage::~ScreenshotsPage()
delete ui;
}
+void ScreenshotsPage::ShowContextMenu(const QPoint& pos)
+{
+ auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->listView->mapToGlobal(pos));
+ delete menu;
+}
+
+QMenu * ScreenshotsPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
+ return filteredMenu;
+}
+
void ScreenshotsPage::onItemActivated(QModelIndex index)
{
if (!index.isValid())
@@ -273,12 +291,12 @@ void ScreenshotsPage::onItemActivated(QModelIndex index)
DesktopServices::openFile(info.absoluteFilePath());
}
-void ScreenshotsPage::on_viewFolderBtn_clicked()
+void ScreenshotsPage::on_actionView_Folder_triggered()
{
DesktopServices::openDirectory(m_folder, true);
}
-void ScreenshotsPage::on_uploadBtn_clicked()
+void ScreenshotsPage::on_actionUpload_triggered()
{
auto selection = ui->listView->selectionModel()->selectedRows();
if (selection.isEmpty())
@@ -286,6 +304,38 @@ void ScreenshotsPage::on_uploadBtn_clicked()
QList<ScreenshotPtr> uploaded;
auto job = NetJobPtr(new NetJob("Screenshot Upload"));
+ if(selection.size() < 2)
+ {
+ auto item = selection.at(0);
+ auto info = m_model->fileInfo(item);
+ auto screenshot = std::make_shared<ScreenShot>(info);
+ job->addNetAction(ImgurUpload::make(screenshot));
+
+ m_uploadActive = true;
+ ProgressDialog dialog(this);
+ if(dialog.execWithTask(job.get()) != QDialog::Accepted)
+ {
+ CustomMessageBox::selectable(this, tr("Failed to upload screenshots!"),
+ tr("Unknown error"), QMessageBox::Warning)->exec();
+ }
+ else
+ {
+ auto link = screenshot->m_url;
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(link);
+ CustomMessageBox::selectable(
+ this,
+ tr("Upload finished"),
+ tr("The <a href=\"%1\">link to the uploaded screenshot</a> has been placed in your clipboard.")
+ .arg(link),
+ QMessageBox::Information
+ )->exec();
+ }
+
+ m_uploadActive = false;
+ return;
+ }
+
for (auto item : selection)
{
auto info = m_model->fileInfo(item);
@@ -321,7 +371,7 @@ void ScreenshotsPage::on_uploadBtn_clicked()
m_uploadActive = false;
}
-void ScreenshotsPage::on_deleteBtn_clicked()
+void ScreenshotsPage::on_actionDelete_triggered()
{
auto mbox = CustomMessageBox::selectable(
this, tr("Are you sure?"), tr("This will delete all selected screenshots."),
@@ -338,7 +388,7 @@ void ScreenshotsPage::on_deleteBtn_clicked()
}
}
-void ScreenshotsPage::on_renameBtn_clicked()
+void ScreenshotsPage::on_actionRename_triggered()
{
auto selection = ui->listView->selectionModel()->selectedIndexes();
if (selection.isEmpty())
diff --git a/application/pages/instance/ScreenshotsPage.h b/application/pages/instance/ScreenshotsPage.h
index 88a64dfb..03a809de 100644
--- a/application/pages/instance/ScreenshotsPage.h
+++ b/application/pages/instance/ScreenshotsPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,7 +15,7 @@
#pragma once
-#include <QWidget>
+#include <QMainWindow>
#include "pages/BasePage.h"
#include <MultiMC.h>
@@ -31,7 +31,7 @@ struct ScreenShot;
class ScreenshotList;
class ImgurAlbumCreation;
-class ScreenshotsPage : public QWidget, public BasePage
+class ScreenshotsPage : public QMainWindow, public BasePage
{
Q_OBJECT
@@ -67,12 +67,17 @@ public:
{
return !m_uploadActive;
}
+
+protected:
+ QMenu * createPopupMenu() override;
+
private slots:
- void on_uploadBtn_clicked();
- void on_deleteBtn_clicked();
- void on_renameBtn_clicked();
- void on_viewFolderBtn_clicked();
+ void on_actionUpload_triggered();
+ void on_actionDelete_triggered();
+ void on_actionRename_triggered();
+ void on_actionView_Folder_triggered();
void onItemActivated(QModelIndex);
+ void ShowContextMenu(const QPoint &pos);
private:
Ui::ScreenshotsPage *ui;
diff --git a/application/pages/instance/ScreenshotsPage.ui b/application/pages/instance/ScreenshotsPage.ui
index d05c4384..f11f4cd4 100644
--- a/application/pages/instance/ScreenshotsPage.ui
+++ b/application/pages/instance/ScreenshotsPage.ui
@@ -1,106 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ScreenshotsPage</class>
- <widget class="QWidget" name="ScreenshotsPage">
+ <widget class="QMainWindow" name="ScreenshotsPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>723</width>
- <height>532</height>
+ <width>800</width>
+ <height>600</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
+ <widget class="QWidget" name="centralwidget">
+ <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="QListView" name="listView">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::ExtendedSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WideBar" name="toolBar">
+ <property name="windowTitle">
+ <string>Actions</string>
</property>
- <property name="topMargin">
- <number>0</number>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextOnly</enum>
</property>
- <property name="rightMargin">
- <number>0</number>
+ <attribute name="toolBarArea">
+ <enum>RightToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionUpload"/>
+ <addaction name="actionDelete"/>
+ <addaction name="actionRename"/>
+ <addaction name="actionView_Folder"/>
+ </widget>
+ <action name="actionUpload">
+ <property name="text">
+ <string>Upload</string>
</property>
- <property name="bottomMargin">
- <number>0</number>
+ </action>
+ <action name="actionDelete">
+ <property name="text">
+ <string>Delete</string>
</property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <widget class="QListView" name="listView">
- <property name="selectionMode">
- <enum>QAbstractItemView::ExtendedSelection</enum>
- </property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QPushButton" name="uploadBtn">
- <property name="text">
- <string>&amp;Upload</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="deleteBtn">
- <property name="text">
- <string>&amp;Delete</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="renameBtn">
- <property name="text">
- <string>&amp;Rename</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="viewFolderBtn">
- <property name="text">
- <string>&amp;View Folder</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
+ </action>
+ <action name="actionRename">
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </action>
+ <action name="actionView_Folder">
+ <property name="text">
+ <string>View Folder</string>
+ </property>
+ </action>
</widget>
- <tabstops>
- <tabstop>listView</tabstop>
- <tabstop>uploadBtn</tabstop>
- <tabstop>deleteBtn</tabstop>
- <tabstop>renameBtn</tabstop>
- <tabstop>viewFolderBtn</tabstop>
- </tabstops>
+ <customwidgets>
+ <customwidget>
+ <class>WideBar</class>
+ <extends>QToolBar</extends>
+ <header>widgets/WideBar.h</header>
+ </customwidget>
+ </customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/application/pages/instance/ServersPage.cpp b/application/pages/instance/ServersPage.cpp
index 35f167aa..d63c6e70 100644
--- a/application/pages/instance/ServersPage.cpp
+++ b/application/pages/instance/ServersPage.cpp
@@ -11,6 +11,7 @@
#include <minecraft/MinecraftInstance.h>
#include <QFileSystemWatcher>
+#include <QMenu>
static const int COLUMN_COUNT = 2; // 3 , TBD: latency and other nice things.
@@ -64,8 +65,8 @@ struct Server
void serialize(nbt::tag_compound& server)
{
- server.insert("name", m_name.toUtf8().toStdString());
- server.insert("ip", m_address.toUtf8().toStdString());
+ server.insert("name", m_name.trimmed().toUtf8().toStdString());
+ server.insert("ip", m_address.trimmed().toUtf8().toStdString());
if(m_icon.size())
{
server.insert("icon", m_icon.toBase64().toStdString());
@@ -555,15 +556,17 @@ private:
QTimer m_saveTimer;
};
-ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent)
- : QWidget(parent), ui(new Ui::ServersPage)
+ServersPage::ServersPage(InstancePtr inst, QWidget* parent)
+ : QMainWindow(parent), ui(new Ui::ServersPage)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
m_inst = inst;
m_model = new ServersModel(inst->gameRoot(), this);
ui->serversView->setIconSize(QSize(64,64));
ui->serversView->setModel(m_model);
+ ui->serversView->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->serversView, &QTreeView::customContextMenuRequested, this, &ServersPage::ShowContextMenu);
+
auto head = ui->serversView->header();
if(head->count())
{
@@ -576,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)));
@@ -594,6 +597,21 @@ ServersPage::ServersPage(MinecraftInstance * inst, QWidget* parent)
ServersPage::~ServersPage()
{
m_model->saveNow();
+ delete ui;
+}
+
+void ServersPage::ShowContextMenu(const QPoint& pos)
+{
+ auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->serversView->mapToGlobal(pos));
+ delete menu;
+}
+
+QMenu * ServersPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
+ return filteredMenu;
}
void ServersPage::on_RunningState_changed(bool running)
@@ -674,9 +692,10 @@ void ServersPage::updateState()
ui->addressLine->setEnabled(serverEditEnabled);
ui->nameLine->setEnabled(serverEditEnabled);
ui->resourceComboBox->setEnabled(serverEditEnabled);
- ui->moveDownBtn->setEnabled(serverEditEnabled);
- ui->moveUpBtn->setEnabled(serverEditEnabled);
- ui->removeBtn->setEnabled(serverEditEnabled);
+ ui->actionMove_Down->setEnabled(serverEditEnabled);
+ ui->actionMove_Up->setEnabled(serverEditEnabled);
+ ui->actionRemove->setEnabled(serverEditEnabled);
+ ui->actionJoin->setEnabled(serverEditEnabled);
if(server)
{
@@ -691,7 +710,7 @@ void ServersPage::updateState()
ui->resourceComboBox->setCurrentIndex(0);
}
- ui->addBtn->setDisabled(m_locked);
+ ui->actionAdd->setDisabled(m_locked);
}
void ServersPage::openedImpl()
@@ -704,7 +723,7 @@ void ServersPage::closedImpl()
m_model->unobserve();
}
-void ServersPage::on_addBtn_clicked()
+void ServersPage::on_actionAdd_triggered()
{
int position = m_model->addEmptyRow(currentServer + 1);
if(position < 0)
@@ -719,12 +738,12 @@ void ServersPage::on_addBtn_clicked()
currentServer = position;
}
-void ServersPage::on_removeBtn_clicked()
+void ServersPage::on_actionRemove_triggered()
{
m_model->removeRow(currentServer);
}
-void ServersPage::on_moveUpBtn_clicked()
+void ServersPage::on_actionMove_Up_triggered()
{
if(m_model->moveUp(currentServer))
{
@@ -732,7 +751,7 @@ void ServersPage::on_moveUpBtn_clicked()
}
}
-void ServersPage::on_moveDownBtn_clicked()
+void ServersPage::on_actionMove_Down_triggered()
{
if(m_model->moveDown(currentServer))
{
@@ -740,4 +759,10 @@ void ServersPage::on_moveDownBtn_clicked()
}
}
+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 fd690e9e..8c5b7eb8 100644
--- a/application/pages/instance/ServersPage.h
+++ b/application/pages/instance/ServersPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,7 +15,7 @@
#pragma once
-#include <QWidget>
+#include <QMainWindow>
#include <QString>
#include "pages/BasePage.h"
@@ -30,12 +30,12 @@ struct Server;
class ServersModel;
class MinecraftInstance;
-class ServersPage : public QWidget, public BasePage
+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;
@@ -57,6 +57,10 @@ public:
{
return "Servers-management";
}
+
+protected:
+ QMenu * createPopupMenu() override;
+
private:
void updateState();
void scheduleSave();
@@ -66,21 +70,25 @@ private slots:
void currentChanged(const QModelIndex &current, const QModelIndex &previous);
void rowsRemoved(const QModelIndex &parent, int first, int last);
- void on_addBtn_clicked();
- void on_removeBtn_clicked();
- void on_moveUpBtn_clicked();
- void on_moveDownBtn_clicked();
+ void on_actionAdd_triggered();
+ void on_actionRemove_triggered();
+ void on_actionMove_Up_triggered();
+ void on_actionMove_Down_triggered();
+ void on_actionJoin_triggered();
+
void on_RunningState_changed(bool running);
void nameEdited(const QString & name);
void addressEdited(const QString & address);
- void resourceIndexChanged(int index);
+ void resourceIndexChanged(int index);\
+
+ void ShowContextMenu(const QPoint &pos);
private: // data
int currentServer = -1;
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 faa918f4..d89b7cba 100644
--- a/application/pages/instance/ServersPage.ui
+++ b/application/pages/instance/ServersPage.ui
@@ -1,198 +1,193 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ServersPage</class>
- <widget class="QWidget" name="ServersPage">
+ <widget class="QMainWindow" name="ServersPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>706</width>
- <height>575</height>
+ <width>1318</width>
+ <height>879</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeView" name="serversView">
<property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
- <attribute name="title">
- <string notr="true">Tab 1</string>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="iconSize">
+ <size>
+ <width>64</width>
+ <height>64</height>
+ </size>
+ </property>
+ <property name="rootIsDecorated">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
</attribute>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <item>
- <layout class="QGridLayout" name="gridLayout">
- <item row="3" column="1">
- <widget class="QComboBox" name="resourceComboBox">
- <item>
- <property name="text">
- <string>Ask to download</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Always download</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Never download</string>
- </property>
- </item>
- </widget>
- </item>
- <item row="3" column="0">
- <widget class="QLabel" name="resourcesLabel">
- <property name="text">
- <string>Reso&amp;urces</string>
- </property>
- <property name="buddy">
- <cstring>resourceComboBox</cstring>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QLineEdit" name="addressLine"/>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="nameLabel">
- <property name="text">
- <string>&amp;Name</string>
- </property>
- <property name="buddy">
- <cstring>nameLine</cstring>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="nameLine"/>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="addressLabel">
- <property name="text">
- <string>Address</string>
- </property>
- <property name="buddy">
- <cstring>addressLine</cstring>
- </property>
- </widget>
- </item>
- <item row="0" column="0" colspan="2">
- <widget class="QTreeView" name="serversView">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="acceptDrops">
- <bool>true</bool>
- </property>
- <property name="alternatingRowColors">
- <bool>true</bool>
- </property>
- <property name="selectionMode">
- <enum>QAbstractItemView::SingleSelection</enum>
- </property>
- <property name="selectionBehavior">
- <enum>QAbstractItemView::SelectRows</enum>
- </property>
- <property name="iconSize">
- <size>
- <width>64</width>
- <height>64</height>
- </size>
- </property>
- <property name="rootIsDecorated">
- <bool>false</bool>
- </property>
- <attribute name="headerStretchLastSection">
- <bool>false</bool>
- </attribute>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QPushButton" name="addBtn">
- <property name="text">
- <string>&amp;Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="removeBtn">
- <property name="text">
- <string>&amp;Remove</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="moveUpBtn">
- <property name="text">
- <string>Move Up</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="moveDownBtn">
- <property name="text">
- <string>Move Down</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- </layout>
</widget>
- </widget>
- </item>
- </layout>
+ </item>
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="leftMargin">
+ <number>6</number>
+ </property>
+ <property name="rightMargin">
+ <number>6</number>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="nameLabel">
+ <property name="text">
+ <string>&amp;Name</string>
+ </property>
+ <property name="buddy">
+ <cstring>nameLine</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="nameLine"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="addressLabel">
+ <property name="text">
+ <string>Address</string>
+ </property>
+ <property name="buddy">
+ <cstring>addressLine</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="addressLine"/>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="resourcesLabel">
+ <property name="text">
+ <string>Reso&amp;urces</string>
+ </property>
+ <property name="buddy">
+ <cstring>resourceComboBox</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QComboBox" name="resourceComboBox">
+ <item>
+ <property name="text">
+ <string>Ask to download</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Always download</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Never download</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WideBar" name="toolBar">
+ <property name="windowTitle">
+ <string>Actions</string>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextOnly</enum>
+ </property>
+ <property name="floatable">
+ <bool>false</bool>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>RightToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionAdd"/>
+ <addaction name="actionRemove"/>
+ <addaction name="actionMove_Up"/>
+ <addaction name="actionMove_Down"/>
+ <addaction name="actionJoin"/>
+ </widget>
+ <action name="actionAdd">
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </action>
+ <action name="actionRemove">
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </action>
+ <action name="actionMove_Up">
+ <property name="text">
+ <string>Move Up</string>
+ </property>
+ </action>
+ <action name="actionMove_Down">
+ <property name="text">
+ <string>Move Down</string>
+ </property>
+ </action>
+ <action name="actionJoin">
+ <property name="text">
+ <string>Join</string>
+ </property>
+ </action>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>WideBar</class>
+ <extends>QToolBar</extends>
+ <header>widgets/WideBar.h</header>
+ </customwidget>
+ </customwidgets>
<tabstops>
- <tabstop>tabWidget</tabstop>
<tabstop>serversView</tabstop>
<tabstop>nameLine</tabstop>
<tabstop>addressLine</tabstop>
<tabstop>resourceComboBox</tabstop>
- <tabstop>addBtn</tabstop>
- <tabstop>removeBtn</tabstop>
- <tabstop>moveUpBtn</tabstop>
- <tabstop>moveDownBtn</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/application/pages/instance/TexturePackPage.h b/application/pages/instance/TexturePackPage.h
index 094cc199..3f04997d 100644
--- a/application/pages/instance/TexturePackPage.h
+++ b/application/pages/instance/TexturePackPage.h
@@ -1,17 +1,20 @@
#pragma once
+
#include "ModFolderPage.h"
#include "ui_ModFolderPage.h"
class TexturePackPage : public ModFolderPage
{
+ Q_OBJECT
public:
explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0)
: ModFolderPage(instance, instance->texturePackList(), "texturepacks", "resourcepacks",
tr("Texture packs"), "Texture-packs", parent)
{
- ui->configFolderBtn->setHidden(true);
+ 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 54a228a4..eff12c9c 100644
--- a/application/pages/instance/VersionPage.cpp
+++ b/application/pages/instance/VersionPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,8 +16,10 @@
#include "MultiMC.h"
#include <QMessageBox>
+#include <QLabel>
#include <QEvent>
#include <QKeyEvent>
+#include <QMenu>
#include "VersionPage.h"
#include "ui_VersionPage.h"
@@ -25,7 +27,6 @@
#include "dialogs/CustomMessageBox.h"
#include "dialogs/VersionSelectDialog.h"
#include "dialogs/NewComponentDialog.h"
-#include "dialogs/ModEditDialogCommon.h"
#include "dialogs/ProgressDialog.h"
#include <GuiUtil.h>
@@ -36,13 +37,13 @@
#include <QString>
#include <QUrl>
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include "minecraft/auth/MojangAccountList.h"
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
#include "icons/IconList.h"
#include "Exception.h"
-
-#include "MultiMC.h"
+#include "Version.h"
+#include "DesktopServices.h"
#include <meta/Index.h>
#include <meta/VersionList.h>
@@ -60,7 +61,7 @@ public:
virtual QVariant data(const QModelIndex &proxyIndex, int role = Qt::DisplayRole) const override
{
- QVariant var = QIdentityProxyModel::data(mapToSource(proxyIndex), role);
+ QVariant var = QIdentityProxyModel::data(proxyIndex, role);
int column = proxyIndex.column();
if(column == 0 && role == Qt::DecorationRole && m_parentWidget)
{
@@ -96,38 +97,53 @@ QIcon VersionPage::icon() const
}
bool VersionPage::shouldDisplay() const
{
- return !m_inst->isRunning();
+ return true;
+}
+
+QMenu * VersionPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
+ return filteredMenu;
}
VersionPage::VersionPage(MinecraftInstance *inst, QWidget *parent)
- : QWidget(parent), ui(new Ui::VersionPage), m_inst(inst)
+ : QMainWindow(parent), ui(new Ui::VersionPage), m_inst(inst)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
- m_profile = m_inst->getComponentList();
- reloadComponentList();
+ ui->toolBar->insertSpacer(ui->actionReload);
- if (m_profile)
- {
- auto proxy = new IconProxy(ui->packageView);
- proxy->setSourceModel(m_profile.get());
- ui->packageView->setModel(proxy);
- ui->packageView->installEventFilter(this);
- ui->packageView->setSelectionMode(QAbstractItemView::SingleSelection);
- connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
- auto smodel = ui->packageView->selectionModel();
- connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
- updateVersionControls();
- // select first item.
- preselect(0);
- }
- else
- {
- disableVersionControls();
- }
- connect(m_inst, &MinecraftInstance::versionReloaded, this,
- &VersionPage::updateVersionControls);
+ m_profile = m_inst->getPackProfile();
+
+ reloadPackProfile();
+
+ auto proxy = new IconProxy(ui->packageView);
+ proxy->setSourceModel(m_profile.get());
+
+ 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);
+
+ connect(ui->packageView->selectionModel(), &QItemSelectionModel::currentChanged, this, &VersionPage::versionCurrent);
+ auto smodel = ui->packageView->selectionModel();
+ connect(smodel, &QItemSelectionModel::currentChanged, this, &VersionPage::packageCurrent);
+
+ connect(m_profile.get(), &PackProfile::minecraftChanged, this, &VersionPage::updateVersionControls);
+ controlsEnabled = !m_inst->isRunning();
+ updateVersionControls();
+ preselect(0);
+ connect(m_inst, &BaseInstance::runningStatusChanged, this, &VersionPage::updateRunningStatus);
+ connect(ui->packageView, &ModListView::customContextMenuRequested, this, &VersionPage::showContextMenu);
+ connect(ui->filterEdit, &QLineEdit::textChanged, this, &VersionPage::onFilterTextChanged);
}
VersionPage::~VersionPage()
@@ -135,6 +151,13 @@ VersionPage::~VersionPage()
delete ui;
}
+void VersionPage::showContextMenu(const QPoint& pos)
+{
+ auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->packageView->mapToGlobal(pos));
+ delete menu;
+}
+
void VersionPage::packageCurrent(const QModelIndex &current, const QModelIndex &previous)
{
if (!current.isValid())
@@ -177,23 +200,48 @@ void VersionPage::packageCurrent(const QModelIndex &current, const QModelIndex &
ui->frame->setModDescription(problemOut);
}
+void VersionPage::updateRunningStatus(bool running)
+{
+ if(controlsEnabled == running) {
+ controlsEnabled = !running;
+ updateVersionControls();
+ }
+}
void VersionPage::updateVersionControls()
{
- ui->forgeBtn->setEnabled(true);
- ui->liteloaderBtn->setEnabled(true);
+ // FIXME: this is a dirty hack
+ auto minecraftVersion = Version(m_profile->getComponentVersion("net.minecraft"));
+ 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();
}
-void VersionPage::disableVersionControls()
+void VersionPage::updateButtons(int row)
{
- ui->forgeBtn->setEnabled(false);
- ui->liteloaderBtn->setEnabled(false);
- ui->reloadBtn->setEnabled(false);
- updateButtons();
+ if(row == -1)
+ row = currentRow();
+ auto patch = m_profile->getComponent(row);
+ ui->actionRemove->setEnabled(controlsEnabled && patch && patch->isRemovable());
+ ui->actionMove_down->setEnabled(controlsEnabled && patch && patch->isMoveable());
+ ui->actionMove_up->setEnabled(controlsEnabled && patch && patch->isMoveable());
+ ui->actionChange_version->setEnabled(controlsEnabled && patch && patch->isVersionChangeable());
+ ui->actionEdit->setEnabled(controlsEnabled && patch && patch->isCustom());
+ ui->actionCustomize->setEnabled(controlsEnabled && patch && patch->isCustomizable());
+ ui->actionRevert->setEnabled(controlsEnabled && patch && patch->isRevertible());
+ ui->actionDownload_All->setEnabled(controlsEnabled);
+ ui->actionAdd_Empty->setEnabled(controlsEnabled);
+ ui->actionReload->setEnabled(controlsEnabled);
+ ui->actionInstall_mods->setEnabled(controlsEnabled);
+ ui->actionReplace_Minecraft_jar->setEnabled(controlsEnabled);
+ ui->actionAdd_to_Minecraft_jar->setEnabled(controlsEnabled);
}
-bool VersionPage::reloadComponentList()
+bool VersionPage::reloadPackProfile()
{
try
{
@@ -214,13 +262,13 @@ bool VersionPage::reloadComponentList()
}
}
-void VersionPage::on_reloadBtn_clicked()
+void VersionPage::on_actionReload_triggered()
{
- reloadComponentList();
+ reloadPackProfile();
m_container->refreshContainer();
}
-void VersionPage::on_removeBtn_clicked()
+void VersionPage::on_actionRemove_triggered()
{
if (ui->packageView->currentIndex().isValid())
{
@@ -231,11 +279,11 @@ void VersionPage::on_removeBtn_clicked()
}
}
updateButtons();
- reloadComponentList();
+ reloadPackProfile();
m_container->refreshContainer();
}
-void VersionPage::on_modBtn_clicked()
+void VersionPage::on_actionInstall_mods_triggered()
{
if(m_container)
{
@@ -243,7 +291,7 @@ void VersionPage::on_modBtn_clicked()
}
}
-void VersionPage::on_jarmodBtn_clicked()
+void VersionPage::on_actionAdd_to_Minecraft_jar_triggered()
{
auto list = GuiUtil::BrowseForFiles("jarmod", tr("Select jar mods"), tr("Minecraft.jar mods (*.zip *.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget());
if(!list.empty())
@@ -253,7 +301,7 @@ void VersionPage::on_jarmodBtn_clicked()
updateButtons();
}
-void VersionPage::on_jarBtn_clicked()
+void VersionPage::on_actionReplace_Minecraft_jar_triggered()
{
auto jarPath = GuiUtil::BrowseForFile("jar", tr("Select jar"), tr("Minecraft.jar replacement (*.jar)"), MMC->settings()->get("CentralModsDir").toString(), this->parentWidget());
if(!jarPath.isEmpty())
@@ -263,11 +311,11 @@ void VersionPage::on_jarBtn_clicked()
updateButtons();
}
-void VersionPage::on_moveUpBtn_clicked()
+void VersionPage::on_actionMove_up_triggered()
{
try
{
- m_profile->move(currentRow(), ComponentList::MoveUp);
+ m_profile->move(currentRow(), PackProfile::MoveUp);
}
catch (const Exception &e)
{
@@ -276,11 +324,11 @@ void VersionPage::on_moveUpBtn_clicked()
updateButtons();
}
-void VersionPage::on_moveDownBtn_clicked()
+void VersionPage::on_actionMove_down_triggered()
{
try
{
- m_profile->move(currentRow(), ComponentList::MoveDown);
+ m_profile->move(currentRow(), PackProfile::MoveDown);
}
catch (const Exception &e)
{
@@ -289,7 +337,7 @@ void VersionPage::on_moveDownBtn_clicked()
updateButtons();
}
-void VersionPage::on_changeVersionBtn_clicked()
+void VersionPage::on_actionChange_version_triggered()
{
auto versionRow = currentRow();
if(versionRow == -1)
@@ -307,15 +355,21 @@ void VersionPage::on_changeVersionBtn_clicked()
// FIXME: this is a horrible HACK. Get version filtering information from the actual metadata...
if(uid == "net.minecraftforge")
{
- on_forgeBtn_clicked();
+ on_actionInstall_Forge_triggered();
return;
}
else if (uid == "com.mumfrey.liteloader")
{
- on_liteloaderBtn_clicked();
+ on_actionInstall_LiteLoader_triggered();
return;
}
VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this);
+ if (uid == "net.fabricmc.intermediary")
+ {
+ vselect.setEmptyString(tr("No intermediary mappings versions are currently available."));
+ vselect.setEmptyErrorString(tr("Couldn't load or download the intermediary mappings version lists!"));
+ vselect.setExactFilter(BaseVersionList::ParentVersionRole, m_profile->getComponentVersion("net.minecraft"));
+ }
auto currentVersion = patch->getVersion();
if(!currentVersion.isEmpty())
{
@@ -335,7 +389,7 @@ void VersionPage::on_changeVersionBtn_clicked()
m_container->refreshContainer();
}
-void VersionPage::on_downloadBtn_clicked()
+void VersionPage::on_actionDownload_All_triggered()
{
if (!MMC->accounts()->anyAccountIsValid())
{
@@ -360,7 +414,7 @@ void VersionPage::on_downloadBtn_clicked()
m_container->refreshContainer();
}
-void VersionPage::on_forgeBtn_clicked()
+void VersionPage::on_actionInstall_Forge_triggered()
{
auto vlist = ENV.metadataIndex()->get("net.minecraftforge");
if(!vlist)
@@ -389,7 +443,34 @@ void VersionPage::on_forgeBtn_clicked()
}
}
-void VersionPage::on_addEmptyBtn_clicked()
+void VersionPage::on_actionInstall_Fabric_triggered()
+{
+ auto vlist = ENV.metadataIndex()->get("net.fabricmc.fabric-loader");
+ if(!vlist)
+ {
+ return;
+ }
+ VersionSelectDialog vselect(vlist.get(), tr("Select Fabric Loader version"), this);
+ vselect.setEmptyString(tr("No Fabric Loader versions are currently available."));
+ vselect.setEmptyErrorString(tr("Couldn't load or download the Fabric Loader version lists!"));
+
+ auto currentVersion = m_profile->getComponentVersion("net.fabricmc.fabric-loader");
+ if(!currentVersion.isEmpty())
+ {
+ vselect.setCurrentVersion(currentVersion);
+ }
+
+ if (vselect.exec() && vselect.selectedVersion())
+ {
+ auto vsn = vselect.selectedVersion();
+ m_profile->setComponentVersion("net.fabricmc.fabric-loader", vsn->descriptor());
+ m_profile->resolve(Net::Mode::Online);
+ preselect(m_profile->rowCount(QModelIndex())-1);
+ m_container->refreshContainer();
+ }
+}
+
+void VersionPage::on_actionAdd_Empty_triggered()
{
NewComponentDialog compdialog(QString(), QString(), this);
QStringList blacklist;
@@ -407,7 +488,7 @@ void VersionPage::on_addEmptyBtn_clicked()
}
}
-void VersionPage::on_liteloaderBtn_clicked()
+void VersionPage::on_actionInstall_LiteLoader_triggered()
{
auto vlist = ENV.metadataIndex()->get("com.mumfrey.liteloader");
if(!vlist)
@@ -436,6 +517,16 @@ void VersionPage::on_liteloaderBtn_clicked()
}
}
+void VersionPage::on_actionLibrariesFolder_triggered()
+{
+ DesktopServices::openDirectory(m_inst->getLocalLibraryPath(), true);
+}
+
+void VersionPage::on_actionMinecraftFolder_triggered()
+{
+ DesktopServices::openDirectory(m_inst->gameRoot(), true);
+}
+
void VersionPage::versionCurrent(const QModelIndex &current, const QModelIndex &previous)
{
currentIdx = current.row();
@@ -461,37 +552,9 @@ void VersionPage::preselect(int row)
updateButtons(row);
}
-void VersionPage::updateButtons(int row)
-{
- if(row == -1)
- row = currentRow();
- auto patch = m_profile->getComponent(row);
- if (!patch)
- {
- ui->removeBtn->setDisabled(true);
- ui->moveDownBtn->setDisabled(true);
- ui->moveUpBtn->setDisabled(true);
- ui->changeVersionBtn->setDisabled(true);
- ui->editBtn->setDisabled(true);
- ui->customizeBtn->setDisabled(true);
- ui->revertBtn->setDisabled(true);
- }
- else
- {
- ui->removeBtn->setEnabled(patch->isRemovable());
- ui->moveDownBtn->setEnabled(patch->isMoveable());
- ui->moveUpBtn->setEnabled(patch->isMoveable());
- ui->changeVersionBtn->setEnabled(patch->isVersionChangeable());
- ui->editBtn->setEnabled(patch->isCustom());
- ui->customizeBtn->setEnabled(patch->isCustomizable());
- ui->revertBtn->setEnabled(patch->isRevertible());
- }
-}
-
void VersionPage::onGameUpdateError(QString error)
{
- CustomMessageBox::selectable(this, tr("Error updating instance"), error,
- QMessageBox::Warning)->show();
+ CustomMessageBox::selectable(this, tr("Error updating instance"), error, QMessageBox::Warning)->show();
}
Component * VersionPage::current()
@@ -513,7 +576,7 @@ int VersionPage::currentRow()
return ui->packageView->selectionModel()->selectedRows().first().row();
}
-void VersionPage::on_customizeBtn_clicked()
+void VersionPage::on_actionCustomize_triggered()
{
auto version = currentRow();
if(version == -1)
@@ -534,7 +597,7 @@ void VersionPage::on_customizeBtn_clicked()
preselect(currentIdx);
}
-void VersionPage::on_editBtn_clicked()
+void VersionPage::on_actionEdit_triggered()
{
auto version = current();
if(!version)
@@ -550,7 +613,7 @@ void VersionPage::on_editBtn_clicked()
MMC->openJsonEditor(filename);
}
-void VersionPage::on_revertBtn_clicked()
+void VersionPage::on_actionRevert_triggered()
{
auto version = currentRow();
if(version == -1)
@@ -566,5 +629,10 @@ void VersionPage::on_revertBtn_clicked()
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 cc990614..b5b4a6f5 100644
--- a/application/pages/instance/VersionPage.h
+++ b/application/pages/instance/VersionPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,10 +15,10 @@
#pragma once
-#include <QWidget>
+#include <QMainWindow>
#include "minecraft/MinecraftInstance.h"
-#include "minecraft/ComponentList.h"
+#include "minecraft/PackProfile.h"
#include "pages/BasePage.h"
namespace Ui
@@ -26,7 +26,7 @@ namespace Ui
class VersionPage;
}
-class VersionPage : public QWidget, public BasePage
+class VersionPage : public QMainWindow, public BasePage
{
Q_OBJECT
@@ -49,24 +49,27 @@ public:
virtual bool shouldDisplay() const override;
private slots:
- void on_forgeBtn_clicked();
- void on_addEmptyBtn_clicked();
- void on_liteloaderBtn_clicked();
- void on_reloadBtn_clicked();
- void on_removeBtn_clicked();
- void on_moveUpBtn_clicked();
- void on_moveDownBtn_clicked();
- void on_jarmodBtn_clicked();
- void on_jarBtn_clicked();
- void on_revertBtn_clicked();
- void on_editBtn_clicked();
- void on_modBtn_clicked();
- void on_customizeBtn_clicked();
- void on_downloadBtn_clicked();
+ void on_actionChange_version_triggered();
+ void on_actionInstall_Forge_triggered();
+ void on_actionInstall_Fabric_triggered();
+ void on_actionAdd_Empty_triggered();
+ void on_actionInstall_LiteLoader_triggered();
+ void on_actionReload_triggered();
+ void on_actionRemove_triggered();
+ void on_actionMove_up_triggered();
+ void on_actionMove_down_triggered();
+ void on_actionAdd_to_Minecraft_jar_triggered();
+ void on_actionReplace_Minecraft_jar_triggered();
+ void on_actionRevert_triggered();
+ void on_actionEdit_triggered();
+ void on_actionInstall_mods_triggered();
+ void on_actionCustomize_triggered();
+ void on_actionDownload_All_triggered();
+
+ void on_actionMinecraftFolder_triggered();
+ void on_actionLibrariesFolder_triggered();
void updateVersionControls();
- void disableVersionControls();
- void on_changeVersionBtn_clicked();
private:
Component * current();
@@ -76,20 +79,26 @@ private:
int doUpdate();
protected:
+ QMenu * createPopupMenu() override;
+
/// FIXME: this shouldn't be necessary!
- bool reloadComponentList();
+ bool reloadPackProfile();
private:
Ui::VersionPage *ui;
- std::shared_ptr<ComponentList> m_profile;
+ QSortFilterProxyModel *m_filterModel;
+ std::shared_ptr<PackProfile> m_profile;
MinecraftInstance *m_inst;
int currentIdx = 0;
+ bool controlsEnabled = false;
public slots:
void versionCurrent(const QModelIndex &current, const QModelIndex &previous);
private slots:
+ void updateRunningStatus(bool running);
void onGameUpdateError(QString error);
void packageCurrent(const QModelIndex &current, const QModelIndex &previous);
-
+ 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 d54dd840..84d06e2e 100644
--- a/application/pages/instance/VersionPage.ui
+++ b/application/pages/instance/VersionPage.ui
@@ -1,279 +1,266 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>VersionPage</class>
- <widget class="QWidget" name="VersionPage">
+ <widget class="QMainWindow" name="VersionPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>870</width>
- <height>1008</height>
+ <width>961</width>
+ <height>1091</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string notr="true">Tab 1</string>
- </attribute>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="ModListView" name="packageView">
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOn</enum>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="headerHidden">
- <bool>false</bool>
- </property>
- <attribute name="headerVisible">
- <bool>true</bool>
- </attribute>
- </widget>
- </item>
- <item row="0" column="1">
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <item>
- <widget class="QLabel" name="label">
- <property name="text">
- <string>Selection</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="changeVersionBtn">
- <property name="toolTip">
- <string>Change version of the selected package.</string>
- </property>
- <property name="text">
- <string>Change version</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="moveUpBtn">
- <property name="toolTip">
- <string>Make the selected package apply sooner.</string>
- </property>
- <property name="text">
- <string>Move up</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="moveDownBtn">
- <property name="toolTip">
- <string>Make the selected package apply later.</string>
- </property>
- <property name="text">
- <string>Move down</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="removeBtn">
- <property name="toolTip">
- <string>Remove selected package from the instance.</string>
- </property>
- <property name="text">
- <string>Remove</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="LineSeparator" name="separator_4" native="true"/>
- </item>
- <item>
- <widget class="QLabel" name="label_10">
- <property name="text">
- <string>Edit</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="customizeBtn">
- <property name="toolTip">
- <string>Customize selected package.</string>
- </property>
- <property name="text">
- <string>Customize</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="editBtn">
- <property name="toolTip">
- <string>Edit selected package.</string>
- </property>
- <property name="text">
- <string>Edit</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="revertBtn">
- <property name="toolTip">
- <string>Revert the selected package to default.</string>
- </property>
- <property name="text">
- <string>Revert</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="LineSeparator" name="separator" native="true"/>
- </item>
- <item>
- <widget class="QLabel" name="label_2">
- <property name="text">
- <string>Install</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="forgeBtn">
- <property name="toolTip">
- <string>Install the Minecraft Forge package.</string>
- </property>
- <property name="text">
- <string>Install Forge</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="liteloaderBtn">
- <property name="toolTip">
- <string>Install the LiteLoader package.</string>
- </property>
- <property name="text">
- <string>Install LiteLoader</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="modBtn">
- <property name="toolTip">
- <string>Install normal mods.</string>
- </property>
- <property name="text">
- <string>Install mods</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="LineSeparator" name="widget" native="true"/>
- </item>
- <item>
- <widget class="QLabel" name="label_5">
- <property name="text">
- <string>Advanced</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="jarmodBtn">
- <property name="toolTip">
- <string>Add a mod into the Minecraft jar file.</string>
- </property>
- <property name="text">
- <string>Add to Minecraft.jar</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="jarBtn">
- <property name="text">
- <string>Replace Minecraft.jar</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="addEmptyBtn">
- <property name="text">
- <string>Add Empty</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="reloadBtn">
- <property name="toolTip">
- <string>Reload all packages.</string>
- </property>
- <property name="text">
- <string>Reload</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="downloadBtn">
- <property name="toolTip">
- <string>Download the files needed to launch the instance now.</string>
- </property>
- <property name="text">
- <string>Download All</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer_7">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>111</width>
- <height>13</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </item>
- <item row="1" column="0" colspan="2">
- <widget class="MCModInfoFrame" name="frame">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="centralwidget">
+ <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>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="ModListView" name="packageView">
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOn</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="sortingEnabled">
+ <bool>false</bool>
+ </property>
+ <property name="headerHidden">
+ <bool>false</bool>
+ </property>
+ <attribute name="headerVisible">
+ <bool>true</bool>
+ </attribute>
+ </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">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WideBar" name="toolBar">
+ <property name="windowTitle">
+ <string>Actions</string>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextOnly</enum>
+ </property>
+ <property name="floatable">
+ <bool>false</bool>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>RightToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionChange_version"/>
+ <addaction name="actionMove_up"/>
+ <addaction name="actionMove_down"/>
+ <addaction name="actionRemove"/>
+ <addaction name="separator"/>
+ <addaction name="actionCustomize"/>
+ <addaction name="actionEdit"/>
+ <addaction name="actionRevert"/>
+ <addaction name="separator"/>
+ <addaction name="actionInstall_Forge"/>
+ <addaction name="actionInstall_Fabric"/>
+ <addaction name="actionInstall_LiteLoader"/>
+ <addaction name="actionInstall_mods"/>
+ <addaction name="separator"/>
+ <addaction name="actionAdd_to_Minecraft_jar"/>
+ <addaction name="actionReplace_Minecraft_jar"/>
+ <addaction name="actionAdd_Empty"/>
+ <addaction name="separator"/>
+ <addaction name="actionMinecraftFolder"/>
+ <addaction name="actionLibrariesFolder"/>
+ <addaction name="separator"/>
+ <addaction name="actionReload"/>
+ <addaction name="actionDownload_All"/>
+ </widget>
+ <action name="actionChange_version">
+ <property name="text">
+ <string>Change version</string>
+ </property>
+ <property name="toolTip">
+ <string>Change version of the selected package.</string>
+ </property>
+ </action>
+ <action name="actionMove_up">
+ <property name="text">
+ <string>Move up</string>
+ </property>
+ <property name="toolTip">
+ <string>Make the selected package apply sooner.</string>
+ </property>
+ </action>
+ <action name="actionMove_down">
+ <property name="text">
+ <string>Move down</string>
+ </property>
+ <property name="toolTip">
+ <string>Make the selected package apply later.</string>
+ </property>
+ </action>
+ <action name="actionRemove">
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ <property name="toolTip">
+ <string>Remove selected package from the instance.</string>
+ </property>
+ </action>
+ <action name="actionCustomize">
+ <property name="text">
+ <string>Customize</string>
+ </property>
+ <property name="toolTip">
+ <string>Customize selected package.</string>
+ </property>
+ </action>
+ <action name="actionEdit">
+ <property name="text">
+ <string>Edit</string>
+ </property>
+ <property name="toolTip">
+ <string>Edit selected package.</string>
+ </property>
+ </action>
+ <action name="actionRevert">
+ <property name="text">
+ <string>Revert</string>
+ </property>
+ <property name="toolTip">
+ <string>Revert the selected package to default.</string>
+ </property>
+ </action>
+ <action name="actionInstall_Forge">
+ <property name="text">
+ <string>Install Forge</string>
+ </property>
+ <property name="toolTip">
+ <string>Install the Minecraft Forge package.</string>
+ </property>
+ </action>
+ <action name="actionInstall_Fabric">
+ <property name="text">
+ <string>Install Fabric</string>
+ </property>
+ <property name="toolTip">
+ <string>Install the Fabric Loader package.</string>
+ </property>
+ </action>
+ <action name="actionInstall_LiteLoader">
+ <property name="text">
+ <string>Install LiteLoader</string>
+ </property>
+ <property name="toolTip">
+ <string>Install the LiteLoader package.</string>
+ </property>
+ </action>
+ <action name="actionInstall_mods">
+ <property name="text">
+ <string>Install mods</string>
+ </property>
+ <property name="toolTip">
+ <string>Install normal mods.</string>
+ </property>
+ </action>
+ <action name="actionAdd_to_Minecraft_jar">
+ <property name="text">
+ <string>Add to Minecraft.jar</string>
+ </property>
+ <property name="toolTip">
+ <string>Add a mod into the Minecraft jar file.</string>
+ </property>
+ </action>
+ <action name="actionReplace_Minecraft_jar">
+ <property name="text">
+ <string>Replace Minecraft.jar</string>
+ </property>
+ </action>
+ <action name="actionAdd_Empty">
+ <property name="text">
+ <string>Add Empty</string>
+ </property>
+ <property name="toolTip">
+ <string>Add an empty custom package.</string>
+ </property>
+ </action>
+ <action name="actionReload">
+ <property name="text">
+ <string>Reload</string>
+ </property>
+ <property name="toolTip">
+ <string>Reload all packages.</string>
+ </property>
+ </action>
+ <action name="actionDownload_All">
+ <property name="text">
+ <string>Download All</string>
+ </property>
+ <property name="toolTip">
+ <string>Download the files needed to launch the instance now.</string>
+ </property>
+ </action>
+ <action name="actionMinecraftFolder">
+ <property name="text">
+ <string>Open .minecraft</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the instance's .minecraft folder.</string>
+ </property>
+ </action>
+ <action name="actionLibrariesFolder">
+ <property name="text">
+ <string>Open libraries</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the instance's local libraries folder.</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
@@ -282,37 +269,17 @@
<header>widgets/ModListView.h</header>
</customwidget>
<customwidget>
- <class>LineSeparator</class>
- <extends>QWidget</extends>
- <header>widgets/LineSeparator.h</header>
- <container>1</container>
- </customwidget>
- <customwidget>
<class>MCModInfoFrame</class>
<extends>QFrame</extends>
<header>widgets/MCModInfoFrame.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>WideBar</class>
+ <extends>QToolBar</extends>
+ <header>widgets/WideBar.h</header>
+ </customwidget>
</customwidgets>
- <tabstops>
- <tabstop>packageView</tabstop>
- <tabstop>changeVersionBtn</tabstop>
- <tabstop>moveUpBtn</tabstop>
- <tabstop>moveDownBtn</tabstop>
- <tabstop>removeBtn</tabstop>
- <tabstop>customizeBtn</tabstop>
- <tabstop>editBtn</tabstop>
- <tabstop>revertBtn</tabstop>
- <tabstop>forgeBtn</tabstop>
- <tabstop>liteloaderBtn</tabstop>
- <tabstop>modBtn</tabstop>
- <tabstop>jarmodBtn</tabstop>
- <tabstop>jarBtn</tabstop>
- <tabstop>addEmptyBtn</tabstop>
- <tabstop>reloadBtn</tabstop>
- <tabstop>downloadBtn</tabstop>
- <tabstop>tabWidget</tabstop>
- </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/application/pages/instance/WorldListPage.cpp b/application/pages/instance/WorldListPage.cpp
index 67a36fd9..119cff3e 100644
--- a/application/pages/instance/WorldListPage.cpp
+++ b/application/pages/instance/WorldListPage.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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.
@@ -17,8 +17,8 @@
#include "ui_WorldListPage.h"
#include "minecraft/WorldList.h"
#include <DesktopServices.h>
-#include "dialogs/ModEditDialogCommon.h"
#include <QEvent>
+#include <QMenu>
#include <QKeyEvent>
#include <QClipboard>
#include <QMessageBox>
@@ -31,27 +31,55 @@
#include <QProcess>
#include <FileSystem.h>
-WorldListPage::WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QString id,
- QString iconName, QString displayName, QString helpPage,
- QWidget *parent)
- : QWidget(parent), m_inst(inst), ui(new Ui::WorldListPage), m_worlds(worlds), m_iconName(iconName), m_id(id), m_displayName(displayName), m_helpName(helpPage)
+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)
{
ui->setupUi(this);
- ui->tabWidget->tabBar()->hide();
- QSortFilterProxyModel * proxy = new QSortFilterProxyModel(this);
+
+ ui->toolBar->insertSpacer(ui->actionRefresh);
+
+ 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();
-
head->setSectionResizeMode(0, QHeaderView::Stretch);
head->setSectionResizeMode(1, QHeaderView::ResizeToContents);
- connect(ui->worldTreeView->selectionModel(),
- SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this,
- SLOT(worldChanged(const QModelIndex &, const QModelIndex &)));
+
+ connect(ui->worldTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &WorldListPage::worldChanged);
worldChanged(QModelIndex(), QModelIndex());
}
@@ -71,6 +99,20 @@ WorldListPage::~WorldListPage()
delete ui;
}
+void WorldListPage::ShowContextMenu(const QPoint& pos)
+{
+ auto menu = ui->toolBar->createContextMenu(this, tr("Context menu"));
+ menu->exec(ui->worldTreeView->mapToGlobal(pos));
+ delete menu;
+}
+
+QMenu * WorldListPage::createPopupMenu()
+{
+ QMenu* filteredMenu = QMainWindow::createPopupMenu();
+ filteredMenu->removeAction( ui->toolBar->toggleViewAction() );
+ return filteredMenu;
+}
+
bool WorldListPage::shouldDisplay() const
{
return true;
@@ -81,7 +123,7 @@ bool WorldListPage::worldListFilter(QKeyEvent *keyEvent)
switch (keyEvent->key())
{
case Qt::Key_Delete:
- on_rmWorldBtn_clicked();
+ on_actionRemove_triggered();
return true;
default:
break;
@@ -101,7 +143,7 @@ bool WorldListPage::eventFilter(QObject *obj, QEvent *ev)
return QWidget::eventFilter(obj, ev);
}
-void WorldListPage::on_rmWorldBtn_clicked()
+void WorldListPage::on_actionRemove_triggered()
{
auto proxiedIndex = getSelectedWorld();
@@ -123,11 +165,42 @@ void WorldListPage::on_rmWorldBtn_clicked()
m_worlds->startWatching();
}
-void WorldListPage::on_viewFolderBtn_clicked()
+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();
@@ -136,7 +209,7 @@ QModelIndex WorldListPage::getSelectedWorld()
return proxy->mapToSource(index);
}
-void WorldListPage::on_copySeedBtn_clicked()
+void WorldListPage::on_actionCopy_Seed_triggered()
{
QModelIndex index = getSelectedWorld();
@@ -148,7 +221,7 @@ void WorldListPage::on_copySeedBtn_clicked()
MMC->clipboard()->setText(QString::number(seed));
}
-void WorldListPage::on_mcEditBtn_clicked()
+void WorldListPage::on_actionMCEdit_triggered()
{
if(m_mceditStarting)
return;
@@ -236,17 +309,20 @@ void WorldListPage::worldChanged(const QModelIndex &current, const QModelIndex &
{
QModelIndex index = getSelectedWorld();
bool enable = index.isValid();
- ui->copySeedBtn->setEnabled(enable);
- ui->mcEditBtn->setEnabled(enable);
- ui->rmWorldBtn->setEnabled(enable);
- ui->copyBtn->setEnabled(enable);
- ui->renameBtn->setEnabled(enable);
+ ui->actionCopy_Seed->setEnabled(enable);
+ ui->actionMCEdit->setEnabled(enable);
+ 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_addBtn_clicked()
+void WorldListPage::on_actionAdd_triggered()
{
auto list = GuiUtil::BrowseForFiles(
- m_helpName,
+ displayName(),
tr("Select a Minecraft world zip"),
tr("Minecraft World Zip File (*.zip)"), QString(), this->parentWidget());
if (!list.empty())
@@ -279,7 +355,7 @@ bool WorldListPage::worldSafetyNagQuestion()
}
-void WorldListPage::on_copyBtn_clicked()
+void WorldListPage::on_actionCopy_triggered()
{
QModelIndex index = getSelectedWorld();
if (!index.isValid())
@@ -301,7 +377,7 @@ void WorldListPage::on_copyBtn_clicked()
}
}
-void WorldListPage::on_renameBtn_clicked()
+void WorldListPage::on_actionRename_triggered()
{
QModelIndex index = getSelectedWorld();
if (!index.isValid())
@@ -324,7 +400,9 @@ void WorldListPage::on_renameBtn_clicked()
}
}
-void WorldListPage::on_refreshBtn_clicked()
+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 ae9ad4a3..4fc9aa09 100644
--- a/application/pages/instance/WorldListPage.h
+++ b/application/pages/instance/WorldListPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2015-2018 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.
@@ -15,7 +15,7 @@
#pragma once
-#include <QWidget>
+#include <QMainWindow>
#include "minecraft/MinecraftInstance.h"
#include "pages/BasePage.h"
@@ -28,31 +28,33 @@ namespace Ui
class WorldListPage;
}
-class WorldListPage : public QWidget, public BasePage
+class WorldListPage : public QMainWindow, public BasePage
{
Q_OBJECT
public:
- explicit WorldListPage(BaseInstance *inst, std::shared_ptr<WorldList> worlds, QString id,
- QString iconName, QString displayName, QString helpPage = "",
- QWidget *parent = 0);
+ explicit WorldListPage(
+ BaseInstance *inst,
+ std::shared_ptr<WorldList> worlds,
+ QWidget *parent = 0
+ );
virtual ~WorldListPage();
virtual QString displayName() const override
{
- return m_displayName;
+ return tr("Worlds");
}
virtual QIcon icon() const override
{
- return MMC->getThemedIcon(m_iconName);
+ return MMC->getThemedIcon("worlds");
}
virtual QString id() const override
{
- return m_id;
+ return "worlds";
}
virtual QString helpPage() const override
{
- return m_helpName;
+ return "Worlds";
}
virtual bool shouldDisplay() const override;
@@ -62,6 +64,7 @@ public:
protected:
bool eventFilter(QObject *obj, QEvent *ev) override;
bool worldListFilter(QKeyEvent *ev);
+ QMenu * createPopupMenu() override;
protected:
BaseInstance *m_inst;
@@ -77,20 +80,20 @@ private:
std::shared_ptr<WorldList> m_worlds;
unique_qobject_ptr<LoggedProcess> m_mceditProcess;
bool m_mceditStarting = false;
- QString m_iconName;
- QString m_id;
- QString m_displayName;
- QString m_helpName;
private slots:
- void on_copySeedBtn_clicked();
- void on_mcEditBtn_clicked();
- void on_rmWorldBtn_clicked();
- void on_addBtn_clicked();
- void on_copyBtn_clicked();
- void on_renameBtn_clicked();
- void on_refreshBtn_clicked();
- void on_viewFolderBtn_clicked();
+ void on_actionCopy_Seed_triggered();
+ void on_actionMCEdit_triggered();
+ void on_actionRemove_triggered();
+ void on_actionAdd_triggered();
+ void on_actionCopy_triggered();
+ 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);
+
+ void ShowContextMenu(const QPoint &pos);
};
diff --git a/application/pages/instance/WorldListPage.ui b/application/pages/instance/WorldListPage.ui
index 0018ddf3..ed078d94 100644
--- a/application/pages/instance/WorldListPage.ui
+++ b/application/pages/instance/WorldListPage.ui
@@ -1,168 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WorldListPage</class>
- <widget class="QWidget" name="WorldListPage">
+ <widget class="QMainWindow" name="WorldListPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>723</width>
- <height>532</height>
+ <width>800</width>
+ <height>600</height>
</rect>
</property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QTabWidget" name="tabWidget">
- <property name="currentIndex">
- <number>0</number>
- </property>
- <widget class="QWidget" name="tab">
- <attribute name="title">
- <string notr="true">Tab 1</string>
+ <widget class="QWidget" name="centralwidget">
+ <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="QTreeView" name="worldTreeView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="acceptDrops">
+ <bool>true</bool>
+ </property>
+ <property name="dragDropMode">
+ <enum>QAbstractItemView::DragDrop</enum>
+ </property>
+ <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>
+ <property name="allColumnsShowFocus">
+ <bool>true</bool>
+ </property>
+ <attribute name="headerStretchLastSection">
+ <bool>false</bool>
</attribute>
- <layout class="QGridLayout" name="gridLayout">
- <item row="0" column="2">
- <layout class="QVBoxLayout" name="verticalLayout_2">
- <item>
- <widget class="QPushButton" name="addBtn">
- <property name="text">
- <string>Add</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="LineSeparator" name="separator" native="true"/>
- </item>
- <item>
- <widget class="QPushButton" name="renameBtn">
- <property name="text">
- <string>Rename</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="copyBtn">
- <property name="text">
- <string>Copy</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="rmWorldBtn">
- <property name="text">
- <string>&amp;Remove</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="mcEditBtn">
- <property name="text">
- <string notr="true">MCEdit</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="LineSeparator" name="separator_2" native="true"/>
- </item>
- <item>
- <widget class="QPushButton" name="copySeedBtn">
- <property name="text">
- <string>Copy Seed</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="refreshBtn">
- <property name="text">
- <string>Refresh</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="viewFolderBtn">
- <property name="text">
- <string>&amp;View Folder</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item row="0" column="1">
- <widget class="QTreeView" name="worldTreeView">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="acceptDrops">
- <bool>true</bool>
- </property>
- <property name="dragDropMode">
- <enum>QAbstractItemView::DragDrop</enum>
- </property>
- <property name="sortingEnabled">
- <bool>true</bool>
- </property>
- <property name="allColumnsShowFocus">
- <bool>true</bool>
- </property>
- <attribute name="headerStretchLastSection">
- <bool>false</bool>
- </attribute>
- </widget>
- </item>
- </layout>
</widget>
- </widget>
- </item>
- </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="WideBar" name="toolBar">
+ <property name="windowTitle">
+ <string>Actions</string>
+ </property>
+ <property name="allowedAreas">
+ <set>Qt::LeftToolBarArea|Qt::RightToolBarArea</set>
+ </property>
+ <property name="toolButtonStyle">
+ <enum>Qt::ToolButtonTextOnly</enum>
+ </property>
+ <property name="floatable">
+ <bool>false</bool>
+ </property>
+ <attribute name="toolBarArea">
+ <enum>RightToolBarArea</enum>
+ </attribute>
+ <attribute name="toolBarBreak">
+ <bool>false</bool>
+ </attribute>
+ <addaction name="actionAdd"/>
+ <addaction name="separator"/>
+ <addaction name="actionRename"/>
+ <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"/>
+ <addaction name="actionView_Folder"/>
+ </widget>
+ <action name="actionAdd">
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </action>
+ <action name="actionRename">
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </action>
+ <action name="actionCopy">
+ <property name="text">
+ <string>Copy</string>
+ </property>
+ </action>
+ <action name="actionRemove">
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </action>
+ <action name="actionMCEdit">
+ <property name="text">
+ <string>MCEdit</string>
+ </property>
+ </action>
+ <action name="actionCopy_Seed">
+ <property name="text">
+ <string>Copy Seed</string>
+ </property>
+ </action>
+ <action name="actionRefresh">
+ <property name="text">
+ <string>Refresh</string>
+ </property>
+ </action>
+ <action name="actionView_Folder">
+ <property name="text">
+ <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>
- <class>LineSeparator</class>
- <extends>QWidget</extends>
- <header>widgets/LineSeparator.h</header>
- <container>1</container>
+ <class>WideBar</class>
+ <extends>QToolBar</extends>
+ <header>widgets/WideBar.h</header>
</customwidget>
</customwidgets>
- <tabstops>
- <tabstop>tabWidget</tabstop>
- <tabstop>worldTreeView</tabstop>
- <tabstop>addBtn</tabstop>
- <tabstop>renameBtn</tabstop>
- <tabstop>copyBtn</tabstop>
- <tabstop>rmWorldBtn</tabstop>
- <tabstop>mcEditBtn</tabstop>
- <tabstop>copySeedBtn</tabstop>
- <tabstop>refreshBtn</tabstop>
- <tabstop>viewFolderBtn</tabstop>
- </tabstops>
<resources/>
<connections/>
</ui>
diff --git a/application/pages/modplatform/ImportPage.cpp b/application/pages/modplatform/ImportPage.cpp
index 3cd7c2cf..c2369bdc 100644
--- a/application/pages/modplatform/ImportPage.cpp
+++ b/application/pages/modplatform/ImportPage.cpp
@@ -71,13 +71,20 @@ void ImportPage::updateState()
{
QFileInfo fi(url.fileName());
dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url));
+ dialog->setSuggestedIcon("default");
}
}
else
{
+ if(input.endsWith("?client=y")) {
+ input.chop(9);
+ input.append("/file");
+ url = QUrl::fromUserInput(input);
+ }
// 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 120e7b56..67e3c201 100644
--- a/application/pages/modplatform/ImportPage.h
+++ b/application/pages/modplatform/ImportPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/TechnicPage.cpp b/application/pages/modplatform/TechnicPage.cpp
deleted file mode 100644
index 2f95bec8..00000000
--- a/application/pages/modplatform/TechnicPage.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "TechnicPage.h"
-#include "ui_TechnicPage.h"
-
-#include "MultiMC.h"
-#include "dialogs/NewInstanceDialog.h"
-
-TechnicPage::TechnicPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::TechnicPage), dialog(dialog)
-{
- ui->setupUi(this);
-}
-
-TechnicPage::~TechnicPage()
-{
- delete ui;
-}
-
-bool TechnicPage::shouldDisplay() const
-{
- return true;
-}
-
-void TechnicPage::openedImpl()
-{
- dialog->setSuggestedPack();
-}
diff --git a/application/pages/modplatform/TechnicPage.ui b/application/pages/modplatform/TechnicPage.ui
deleted file mode 100644
index 702427b5..00000000
--- a/application/pages/modplatform/TechnicPage.ui
+++ /dev/null
@@ -1,113 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>TechnicPage</class>
- <widget class="QWidget" name="TechnicPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>546</width>
- <height>405</height>
- </rect>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QLabel" name="label">
- <property name="font">
- <font>
- <pointsize>40</pointsize>
- </font>
- </property>
- <property name="styleSheet">
- <string notr="true">color:#ffc000</string>
- </property>
- <property name="text">
- <string notr="true">UNDER</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_3">
- <property name="text">
- <string notr="true"/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../resources/assets/assets.qrc">:/assets/underconstruction</pixmap>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_2">
- <property name="font">
- <font>
- <pointsize>40</pointsize>
- </font>
- </property>
- <property name="styleSheet">
- <string notr="true">color:#7ca32b</string>
- </property>
- <property name="text">
- <string notr="true">CONSTRUCTION</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="verticalSpacer_2">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
- </widget>
- <resources>
- <include location="../../resources/assets/assets.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/application/pages/modplatform/TwitchPage.cpp b/application/pages/modplatform/TwitchPage.cpp
deleted file mode 100644
index a984c01c..00000000
--- a/application/pages/modplatform/TwitchPage.cpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "TwitchPage.h"
-#include "ui_TwitchPage.h"
-
-#include "MultiMC.h"
-#include "dialogs/NewInstanceDialog.h"
-
-TwitchPage::TwitchPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), ui(new Ui::TwitchPage), dialog(dialog)
-{
- ui->setupUi(this);
-}
-
-TwitchPage::~TwitchPage()
-{
- delete ui;
-}
-
-bool TwitchPage::shouldDisplay() const
-{
- return false;
-}
-
-void TwitchPage::openedImpl()
-{
- dialog->setSuggestedPack();
-}
diff --git a/application/pages/modplatform/TwitchPage.ui b/application/pages/modplatform/TwitchPage.ui
deleted file mode 100644
index 0930f541..00000000
--- a/application/pages/modplatform/TwitchPage.ui
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>TwitchPage</class>
- <widget class="QWidget" name="TwitchPage">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>546</width>
- <height>405</height>
- </rect>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <item>
- <widget class="QLabel" name="label_3">
- <property name="font">
- <font>
- <pointsize>40</pointsize>
- </font>
- </property>
- <property name="pixmap">
- <pixmap resource="../../resources/assets/assets.qrc">:/assets/deadglitch</pixmap>
- </property>
- <property name="alignment">
- <set>Qt::AlignCenter</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources>
- <include location="../../resources/assets/assets.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/application/pages/modplatform/VanillaPage.cpp b/application/pages/modplatform/VanillaPage.cpp
index 77362fcc..02638315 100644
--- a/application/pages/modplatform/VanillaPage.cpp
+++ b/application/pages/modplatform/VanillaPage.cpp
@@ -23,6 +23,7 @@ VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent)
connect(ui->snapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->oldSnapshotFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->releaseFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
+ connect(ui->experimentsFilter, &QCheckBox::stateChanged, this, &VanillaPage::filterChanged);
connect(ui->refreshBtn, &QPushButton::clicked, this, &VanillaPage::refresh);
}
@@ -58,6 +59,8 @@ void VanillaPage::filterChanged()
out << "(old_snapshot)";
if(ui->releaseFilter->isChecked())
out << "(release)";
+ if(ui->experimentsFilter->isChecked())
+ out << "(experiment)";
auto regexp = out.join('|');
ui->versionList->setFilter(BaseVersionList::TypeRole, new RegexpFilter(regexp, false));
}
@@ -79,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 2b292b01..af6fd392 100644
--- a/application/pages/modplatform/VanillaPage.h
+++ b/application/pages/modplatform/VanillaPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.ui b/application/pages/modplatform/VanillaPage.ui
index ae9cab47..47effc86 100644
--- a/application/pages/modplatform/VanillaPage.ui
+++ b/application/pages/modplatform/VanillaPage.ui
@@ -99,6 +99,16 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="experimentsFilter">
+ <property name="text">
+ <string>Experiments</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -144,6 +154,16 @@
<container>1</container>
</customwidget>
</customwidgets>
+ <tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>releaseFilter</tabstop>
+ <tabstop>snapshotFilter</tabstop>
+ <tabstop>oldSnapshotFilter</tabstop>
+ <tabstop>betaFilter</tabstop>
+ <tabstop>alphaFilter</tabstop>
+ <tabstop>experimentsFilter</tabstop>
+ <tabstop>refreshBtn</tabstop>
+ </tabstops>
<resources/>
<connections/>
</ui>
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/flame/FlameModel.cpp b/application/pages/modplatform/flame/FlameModel.cpp
new file mode 100644
index 00000000..228a88c5
--- /dev/null
+++ b/application/pages/modplatform/flame/FlameModel.cpp
@@ -0,0 +1,259 @@
+#include "FlameModel.h"
+#include "MultiMC.h"
+#include <Json.h>
+
+#include <MMCStrings.h>
+#include <Version.h>
+
+#include <QtMath>
+#include <QLabel>
+
+#include <RWStorage.h>
+#include <Env.h>
+
+namespace Flame {
+
+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);
+ }
+
+ IndexedPack pack = modpacks.at(pos);
+ if(role == Qt::DisplayRole)
+ {
+ return pack.name;
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ if(pack.description.length() > 100)
+ {
+ //some magic to prevent to long tooltips and replace html linebreaks
+ QString edit = pack.description.left(97);
+ edit = edit.left(edit.lastIndexOf("<br>")).left(edit.lastIndexOf(" ")).append("...");
+ return edit;
+
+ }
+ return pack.description;
+ }
+ 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();
+}
+
+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].logoName == logo) {
+ emit dataChanged(createIndex(i, 0), createIndex(i, 0), {Qt::DecorationRole});
+ }
+ }
+}
+
+void ListModel::logoFailed(QString logo)
+{
+ m_failedLogos.append(logo);
+ m_loadingLogos.removeAll(logo);
+}
+
+void ListModel::requestLogo(QString logo, QString url)
+{
+ if(m_loadingLogos.contains(logo) || m_failedLogos.contains(logo))
+ {
+ return;
+ }
+
+ 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::succeeded, this, [this, logo, fullPath]
+ {
+ emit logoLoaded(logo, QIcon(fullPath));
+ if(waitingCallbacks.contains(logo))
+ {
+ waitingCallbacks.value(logo)(fullPath);
+ }
+ });
+
+ QObject::connect(job, &NetJob::failed, this, [this, logo]
+ {
+ emit logoFailed(logo);
+ });
+
+ job->start();
+
+ m_loadingLogos.append(logo);
+}
+
+void ListModel::getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback)
+{
+ if(m_logoMap.contains(logo))
+ {
+ callback(ENV.metacache()->resolveEntry("FlamePacks", QString("logos/%1").arg(logo.section(".", 0, 0)))->getFullPath());
+ }
+ else
+ {
+ requestLogo(logo, logoUrl);
+ }
+}
+
+Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
+{
+ return QAbstractListModel::flags(index);
+}
+
+bool ListModel::canFetchMore(const QModelIndex& parent) const
+{
+ return searchState == CanPossiblyFetchMore;
+}
+
+void ListModel::fetchMore(const QModelIndex& parent)
+{
+ if (parent.isValid())
+ return;
+ if(nextSearchOffset == 0) {
+ qWarning() << "fetchMore with 0 offset is wrong...";
+ return;
+ }
+ performPaginatedSearch();
+}
+
+void ListModel::performPaginatedSearch()
+{
+ NetJob *netJob = new NetJob("Flame::Search");
+ auto searchUrl = QString(
+ "https://addons-ecs.forgesvc.net/api/v2/addon/search?"
+ "categoryId=0&"
+ "gameId=432&"
+ "index=%1&"
+ "pageSize=25&"
+ "searchFilter=%2&"
+ "sectionId=4471&"
+ "sort=%3"
+ ).arg(nextSearchOffset).arg(currentSearchTerm).arg(currentSort);
+ 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::searchWithTerm(const QString& term, int sort)
+{
+ if(currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) {
+ return;
+ }
+ currentSearchTerm = term;
+ currentSort = sort;
+ if(jobPtr) {
+ jobPtr->abort();
+ searchState = ResetRequested;
+ return;
+ }
+ else {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+ searchState = None;
+ }
+ nextSearchOffset = 0;
+ performPaginatedSearch();
+}
+
+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 CurseForge at " << parse_error.offset << " reason: " << parse_error.errorString();
+ qWarning() << response;
+ return;
+ }
+
+ 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);
+ }
+ catch(const JSONValidationError &e)
+ {
+ qWarning() << "Error while loading pack from CurseForge: " << e.cause();
+ continue;
+ }
+ }
+ if(packs.size() < 25) {
+ searchState = Finished;
+ } else {
+ nextSearchOffset += 25;
+ searchState = CanPossiblyFetchMore;
+ }
+ beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1);
+ modpacks.append(newList);
+ endInsertRows();
+}
+
+void Flame::ListModel::searchRequestFailed(QString reason)
+{
+ jobPtr.reset();
+
+ if(searchState == ResetRequested) {
+ beginResetModel();
+ modpacks.clear();
+ endResetModel();
+
+ nextSearchOffset = 0;
+ performPaginatedSearch();
+ } else {
+ searchState = Finished;
+ }
+}
+
+}
+
diff --git a/application/pages/modplatform/flame/FlameModel.h b/application/pages/modplatform/flame/FlameModel.h
new file mode 100644
index 00000000..24383db0
--- /dev/null
+++ b/application/pages/modplatform/flame/FlameModel.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <RWStorage.h>
+
+#include <QAbstractListModel>
+#include <QSortFilterProxyModel>
+#include <QThreadPool>
+#include <QIcon>
+#include <QStyledItemDelegate>
+#include <QList>
+#include <QString>
+#include <QStringList>
+#include <QMetaType>
+
+#include <functional>
+#include <net/NetJob.h>
+
+#include <modplatform/flame/FlamePackIndex.h>
+
+namespace Flame {
+
+
+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;
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ bool canFetchMore(const QModelIndex & parent) const override;
+ void fetchMore(const QModelIndex & parent) override;
+
+ void getLogo(const QString &logo, const QString &logoUrl, LogoCallback callback);
+ void searchWithTerm(const QString & term, const int sort);
+
+private slots:
+ void performPaginatedSearch();
+
+ void logoFailed(QString logo);
+ void logoLoaded(QString logo, QIcon out);
+
+ void searchRequestFinished();
+ void searchRequestFailed(QString reason);
+
+private:
+ void requestLogo(QString file, QString url);
+
+private:
+ 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,
+ CanPossiblyFetchMore,
+ ResetRequested,
+ Finished
+ } searchState = None;
+ NetJobPtr jobPtr;
+ QByteArray response;
+};
+
+}
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/flame/FlamePage.h b/application/pages/modplatform/flame/FlamePage.h
new file mode 100644
index 00000000..467bb44b
--- /dev/null
+++ b/application/pages/modplatform/flame/FlamePage.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 <QWidget>
+
+#include "pages/BasePage.h"
+#include <MultiMC.h>
+#include "tasks/Task.h"
+#include <modplatform/flame/FlamePackIndex.h>
+
+namespace Ui
+{
+class FlamePage;
+}
+
+class NewInstanceDialog;
+
+namespace Flame {
+ class ListModel;
+}
+
+class FlamePage : public QWidget, public BasePage
+{
+ Q_OBJECT
+
+public:
+ explicit FlamePage(NewInstanceDialog* dialog, QWidget *parent = 0);
+ virtual ~FlamePage();
+ virtual QString displayName() const override
+ {
+ return tr("CurseForge");
+ }
+ virtual QIcon icon() const override
+ {
+ return MMC->getThemedIcon("flame");
+ }
+ virtual QString id() const override
+ {
+ return "flame";
+ }
+ virtual QString helpPage() const override
+ {
+ return "Flame-platform";
+ }
+ virtual bool shouldDisplay() const override;
+
+ void openedImpl() override;
+
+ bool eventFilter(QObject * watched, QEvent * event) override;
+
+private:
+ void suggestCurrent();
+
+private slots:
+ void triggerSearch();
+ void onSelectionChanged(QModelIndex first, QModelIndex second);
+ void onVersionSelectionChanged(QString data);
+
+private:
+ Ui::FlamePage *ui = nullptr;
+ NewInstanceDialog* dialog = nullptr;
+ 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/FtbListModel.cpp b/application/pages/modplatform/legacy_ftb/ListModel.cpp
index f4311afb..32596fb3 100644
--- a/application/pages/modplatform/FtbListModel.cpp
+++ b/application/pages/modplatform/legacy_ftb/ListModel.cpp
@@ -1,4 +1,4 @@
-#include "FtbListModel.h"
+#include "ListModel.h"
#include "MultiMC.h"
#include <MMCStrings.h>
@@ -10,17 +10,21 @@
#include <RWStorage.h>
#include <Env.h>
-FtbFilterModel::FtbFilterModel(QObject *parent) : QSortFilterProxyModel(parent)
+#include <BuildConfig.h>
+
+namespace LegacyFTB {
+
+FilterModel::FilterModel(QObject *parent) : QSortFilterProxyModel(parent)
{
currentSorting = Sorting::ByGameVersion;
sortings.insert(tr("Sort by name"), Sorting::ByName);
sortings.insert(tr("Sort by game version"), Sorting::ByGameVersion);
}
-bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+bool FilterModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
- FtbModpack leftPack = sourceModel()->data(left, Qt::UserRole).value<FtbModpack>();
- FtbModpack rightPack = sourceModel()->data(right, Qt::UserRole).value<FtbModpack>();
+ Modpack leftPack = sourceModel()->data(left, Qt::UserRole).value<Modpack>();
+ Modpack rightPack = sourceModel()->data(right, Qt::UserRole).value<Modpack>();
if(currentSorting == Sorting::ByGameVersion) {
Version lv(leftPack.mcVersion);
@@ -36,66 +40,66 @@ bool FtbFilterModel::lessThan(const QModelIndex &left, const QModelIndex &right)
return true;
}
-bool FtbFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+bool FilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
return true;
}
-const QMap<QString, FtbFilterModel::Sorting> FtbFilterModel::getAvailableSortings()
+const QMap<QString, FilterModel::Sorting> FilterModel::getAvailableSortings()
{
return sortings;
}
-QString FtbFilterModel::translateCurrentSorting()
+QString FilterModel::translateCurrentSorting()
{
return sortings.key(currentSorting);
}
-void FtbFilterModel::setSorting(Sorting s)
+void FilterModel::setSorting(Sorting s)
{
currentSorting = s;
invalidate();
}
-FtbFilterModel::Sorting FtbFilterModel::getCurrentSorting()
+FilterModel::Sorting FilterModel::getCurrentSorting()
{
return currentSorting;
}
-FtbListModel::FtbListModel(QObject *parent) : QAbstractListModel(parent)
+ListModel::ListModel(QObject *parent) : QAbstractListModel(parent)
{
}
-FtbListModel::~FtbListModel()
+ListModel::~ListModel()
{
}
-QString FtbListModel::translatePackType(FtbPackType type) const
+QString ListModel::translatePackType(PackType type) const
{
switch(type)
{
- case FtbPackType::Public:
+ case PackType::Public:
return tr("Public Modpack");
- case FtbPackType::ThirdParty:
+ case PackType::ThirdParty:
return tr("Third Party Modpack");
- case FtbPackType::Private:
+ case PackType::Private:
return tr("Private Modpack");
}
qWarning() << "Unknown FTB modpack type:" << int(type);
return QString();
}
-int FtbListModel::rowCount(const QModelIndex &parent) const
+int ListModel::rowCount(const QModelIndex &parent) const
{
return modpacks.size();
}
-int FtbListModel::columnCount(const QModelIndex &parent) const
+int ListModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
-QVariant FtbListModel::data(const QModelIndex &index, int role) const
+QVariant ListModel::data(const QModelIndex &index, int role) const
{
int pos = index.row();
if(pos >= modpacks.size() || pos < 0 || !index.isValid())
@@ -103,7 +107,7 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const
return QString("INVALID INDEX %1").arg(pos);
}
- FtbModpack pack = modpacks.at(pos);
+ Modpack pack = modpacks.at(pos);
if(role == Qt::DisplayRole)
{
return pack.name + "\n" + translatePackType(pack.type);
@@ -127,7 +131,7 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const
return (m_logoMap.value(pack.logo));
}
QIcon icon = MMC->getThemedIcon("screenshot-placeholder");
- ((FtbListModel *)this)->requestLogo(pack.logo);
+ ((ListModel *)this)->requestLogo(pack.logo);
return icon;
}
else if(role == Qt::TextColorRole)
@@ -154,33 +158,33 @@ QVariant FtbListModel::data(const QModelIndex &index, int role) const
return QVariant();
}
-void FtbListModel::fill(FtbModpackList modpacks)
+void ListModel::fill(ModpackList modpacks)
{
beginResetModel();
this->modpacks = modpacks;
endResetModel();
}
-void FtbListModel::addPack(FtbModpack modpack)
+void ListModel::addPack(Modpack modpack)
{
beginResetModel();
this->modpacks.append(modpack);
endResetModel();
}
-void FtbListModel::clear()
+void ListModel::clear()
{
beginResetModel();
modpacks.clear();
endResetModel();
}
-FtbModpack FtbListModel::at(int row)
+Modpack ListModel::at(int row)
{
return modpacks.at(row);
}
-void FtbListModel::remove(int row)
+void ListModel::remove(int row)
{
if(row < 0 || row >= modpacks.size())
{
@@ -192,20 +196,20 @@ void FtbListModel::remove(int row)
endRemoveRows();
}
-void FtbListModel::logoLoaded(QString logo, QIcon out)
+void ListModel::logoLoaded(QString logo, QIcon out)
{
m_loadingLogos.removeAll(logo);
m_logoMap.insert(logo, out);
emit dataChanged(createIndex(0, 0), createIndex(1, 0));
}
-void FtbListModel::logoFailed(QString logo)
+void ListModel::logoFailed(QString logo)
{
m_failedLogos.append(logo);
m_loadingLogos.removeAll(logo);
}
-void FtbListModel::requestLogo(QString file)
+void ListModel::requestLogo(QString file)
{
if(m_loadingLogos.contains(file) || m_failedLogos.contains(file))
{
@@ -214,7 +218,7 @@ void FtbListModel::requestLogo(QString file)
MetaEntryPtr entry = ENV.metacache()->resolveEntry("FTBPacks", QString("logos/%1").arg(file.section(".", 0, 0)));
NetJob *job = new NetJob(QString("FTB Icon Download for %1").arg(file));
- job->addNetAction(Net::Download::makeCached(QUrl(QString("https://ftb.cursecdn.com/FTB2/static/%1").arg(file)), entry));
+ job->addNetAction(Net::Download::makeCached(QUrl(QString(BuildConfig.LEGACY_FTB_CDN_BASE_URL + "static/%1").arg(file)), entry));
auto fullPath = entry->getFullPath();
QObject::connect(job, &NetJob::finished, this, [this, file, fullPath]
@@ -236,7 +240,7 @@ void FtbListModel::requestLogo(QString file)
m_loadingLogos.append(file);
}
-void FtbListModel::getLogo(const QString &logo, LogoCallback callback)
+void ListModel::getLogo(const QString &logo, LogoCallback callback)
{
if(m_logoMap.contains(logo))
{
@@ -248,7 +252,9 @@ void FtbListModel::getLogo(const QString &logo, LogoCallback callback)
}
}
-Qt::ItemFlags FtbListModel::flags(const QModelIndex &index) const
+Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
{
return QAbstractListModel::flags(index);
}
+
+}
diff --git a/application/pages/modplatform/FtbListModel.h b/application/pages/modplatform/legacy_ftb/ListModel.h
index 98301fd7..c55df000 100644
--- a/application/pages/modplatform/FtbListModel.h
+++ b/application/pages/modplatform/legacy_ftb/ListModel.h
@@ -1,6 +1,6 @@
#pragma once
-#include <modplatform/ftb/PackHelpers.h>
+#include <modplatform/legacy_ftb/PackHelpers.h>
#include <RWStorage.h>
#include <QAbstractListModel>
@@ -11,13 +11,16 @@
#include <functional>
-typedef QMap<QString, QIcon> FtbLogoMap;
+namespace LegacyFTB {
+
+typedef QMap<QString, QIcon> FTBLogoMap;
typedef std::function<void(QString)> LogoCallback;
-class FtbFilterModel : public QSortFilterProxyModel
+class FilterModel : public QSortFilterProxyModel
{
+ Q_OBJECT
public:
- FtbFilterModel(QObject* parent = Q_NULLPTR);
+ FilterModel(QObject* parent = Q_NULLPTR);
enum Sorting {
ByName,
ByGameVersion
@@ -37,18 +40,18 @@ private:
};
-class FtbListModel : public QAbstractListModel
+class ListModel : public QAbstractListModel
{
Q_OBJECT
private:
- FtbModpackList modpacks;
+ ModpackList modpacks;
QStringList m_failedLogos;
QStringList m_loadingLogos;
- FtbLogoMap m_logoMap;
+ FTBLogoMap m_logoMap;
QMap<QString, LogoCallback> waitingCallbacks;
void requestLogo(QString file);
- QString translatePackType(FtbPackType type) const;
+ QString translatePackType(PackType type) const;
private slots:
@@ -56,18 +59,20 @@ private slots:
void logoLoaded(QString logo, QIcon out);
public:
- FtbListModel(QObject *parent);
- ~FtbListModel();
+ ListModel(QObject *parent);
+ ~ListModel();
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
- void fill(FtbModpackList modpacks);
- void addPack(FtbModpack modpack);
+ void fill(ModpackList modpacks);
+ void addPack(Modpack modpack);
void clear();
void remove(int row);
- FtbModpack at(int row);
+ Modpack at(int row);
void getLogo(const QString &logo, LogoCallback callback);
};
+
+}
diff --git a/application/pages/modplatform/FTBPage.cpp b/application/pages/modplatform/legacy_ftb/Page.cpp
index dca86efd..a438f76c 100644
--- a/application/pages/modplatform/FTBPage.cpp
+++ b/application/pages/modplatform/legacy_ftb/Page.cpp
@@ -1,27 +1,29 @@
-#include "FTBPage.h"
-#include "ui_FTBPage.h"
+#include "Page.h"
+#include "ui_Page.h"
#include <QInputDialog>
#include "MultiMC.h"
#include "dialogs/CustomMessageBox.h"
#include "dialogs/NewInstanceDialog.h"
-#include "modplatform/ftb/FtbPackFetchTask.h"
-#include "modplatform/ftb/FtbPackInstallTask.h"
-#include "modplatform/ftb/FtbPrivatePackManager.h"
-#include "FtbListModel.h"
+#include "modplatform/legacy_ftb/PackFetchTask.h"
+#include "modplatform/legacy_ftb/PackInstallTask.h"
+#include "modplatform/legacy_ftb/PrivatePackManager.h"
+#include "ListModel.h"
-FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent)
- : QWidget(parent), dialog(dialog), ui(new Ui::FTBPage)
+namespace LegacyFTB {
+
+Page::Page(NewInstanceDialog* dialog, QWidget *parent)
+ : QWidget(parent), dialog(dialog), ui(new Ui::Page)
{
- ftbFetchTask.reset(new FtbPackFetchTask());
- ftbPrivatePacks.reset(new FtbPrivatePackManager());
+ ftbFetchTask.reset(new PackFetchTask());
+ ftbPrivatePacks.reset(new PrivatePackManager());
ui->setupUi(this);
{
- publicFilterModel = new FtbFilterModel(this);
- publicListModel = new FtbListModel(this);
+ publicFilterModel = new FilterModel(this);
+ publicListModel = new ListModel(this);
publicFilterModel->setSourceModel(publicListModel);
ui->publicPackList->setModel(publicFilterModel);
@@ -39,8 +41,8 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent)
}
{
- thirdPartyFilterModel = new FtbFilterModel(this);
- thirdPartyModel = new FtbListModel(this);
+ thirdPartyFilterModel = new FilterModel(this);
+ thirdPartyModel = new ListModel(this);
thirdPartyFilterModel->setSourceModel(thirdPartyModel);
ui->thirdPartyPackList->setModel(thirdPartyFilterModel);
@@ -53,8 +55,8 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent)
}
{
- privateFilterModel = new FtbFilterModel(this);
- privateListModel = new FtbListModel(this);
+ privateFilterModel = new FilterModel(this);
+ privateListModel = new ListModel(this);
privateFilterModel->setSourceModel(privateListModel);
ui->privatePackList->setModel(privateFilterModel);
@@ -69,17 +71,17 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent)
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
- connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &FTBPage::onSortingSelectionChanged);
- connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &FTBPage::onVersionSelectionItemChanged);
+ connect(ui->sortByBox, &QComboBox::currentTextChanged, this, &Page::onSortingSelectionChanged);
+ connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &Page::onVersionSelectionItemChanged);
- connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPublicPackSelectionChanged);
- connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onThirdPartyPackSelectionChanged);
- connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &FTBPage::onPrivatePackSelectionChanged);
+ connect(ui->publicPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPublicPackSelectionChanged);
+ connect(ui->thirdPartyPackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onThirdPartyPackSelectionChanged);
+ connect(ui->privatePackList->selectionModel(), &QItemSelectionModel::currentChanged, this, &Page::onPrivatePackSelectionChanged);
- connect(ui->addPackBtn, &QPushButton::pressed, this, &FTBPage::onAddPackClicked);
- connect(ui->removePackBtn, &QPushButton::pressed, this, &FTBPage::onRemovePackClicked);
+ connect(ui->addPackBtn, &QPushButton::pressed, this, &Page::onAddPackClicked);
+ connect(ui->removePackBtn, &QPushButton::pressed, this, &Page::onRemovePackClicked);
- connect(ui->tabWidget, &QTabWidget::currentChanged, this, &FTBPage::onTabChanged);
+ connect(ui->tabWidget, &QTabWidget::currentChanged, this, &Page::onTabChanged);
// ui->modpackInfo->setOpenExternalLinks(true);
@@ -90,25 +92,25 @@ FTBPage::FTBPage(NewInstanceDialog* dialog, QWidget *parent)
onTabChanged(ui->tabWidget->currentIndex());
}
-FTBPage::~FTBPage()
+Page::~Page()
{
delete ui;
}
-bool FTBPage::shouldDisplay() const
+bool Page::shouldDisplay() const
{
return true;
}
-void FTBPage::openedImpl()
+void Page::openedImpl()
{
if(!initialized)
{
- connect(ftbFetchTask.get(), &FtbPackFetchTask::finished, this, &FTBPage::ftbPackDataDownloadSuccessfully);
- connect(ftbFetchTask.get(), &FtbPackFetchTask::failed, this, &FTBPage::ftbPackDataDownloadFailed);
+ connect(ftbFetchTask.get(), &PackFetchTask::finished, this, &Page::ftbPackDataDownloadSuccessfully);
+ connect(ftbFetchTask.get(), &PackFetchTask::failed, this, &Page::ftbPackDataDownloadFailed);
- connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFinished, this, &FTBPage::ftbPrivatePackDataDownloadSuccessfully);
- connect(ftbFetchTask.get(), &FtbPackFetchTask::privateFileDownloadFailed, this, &FTBPage::ftbPrivatePackDataDownloadFailed);
+ connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFinished, this, &Page::ftbPrivatePackDataDownloadSuccessfully);
+ connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFailed, this, &Page::ftbPrivatePackDataDownloadFailed);
ftbFetchTask->fetch();
ftbPrivatePacks->load();
@@ -118,71 +120,72 @@ void FTBPage::openedImpl()
suggestCurrent();
}
-void FTBPage::suggestCurrent()
+void Page::suggestCurrent()
{
- if(isOpened)
+ if(!isOpened)
{
- if(!selected.broken)
- {
- dialog->setSuggestedPack(selected.name, new FtbPackInstallTask(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 == FtbPackType::Public)
- {
- publicListModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
- }
- else if (selected.type == FtbPackType::ThirdParty)
- {
- thirdPartyModel->getLogo(selected.logo, [this, editedLogoName](QString logo)
- {
- dialog->setSuggestedIconFromFile(logo, editedLogoName);
- });
- }
- else if (selected.type == FtbPackType::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);
+ });
}
}
-void FTBPage::ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks)
+void Page::ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks)
{
publicListModel->fill(publicPacks);
thirdPartyModel->fill(thirdPartyPacks);
}
-void FTBPage::ftbPackDataDownloadFailed(QString reason)
+void Page::ftbPackDataDownloadFailed(QString reason)
{
//TODO: Display the error
}
-void FTBPage::ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack)
+void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack)
{
privateListModel->addPack(pack);
}
-void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
+void Page::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
{
auto reply = QMessageBox::question(
this,
@@ -195,40 +198,40 @@ void FTBPage::ftbPrivatePackDataDownloadFailed(QString reason, QString packCode)
}
}
-void FTBPage::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
+void Page::onPublicPackSelectionChanged(QModelIndex now, QModelIndex prev)
{
if(!now.isValid())
{
onPackSelectionChanged();
return;
}
- FtbModpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<FtbModpack>();
+ Modpack selectedPack = publicFilterModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
-void FTBPage::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)
+void Page::onThirdPartyPackSelectionChanged(QModelIndex now, QModelIndex prev)
{
if(!now.isValid())
{
onPackSelectionChanged();
return;
}
- FtbModpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<FtbModpack>();
+ Modpack selectedPack = thirdPartyFilterModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
-void FTBPage::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev)
+void Page::onPrivatePackSelectionChanged(QModelIndex now, QModelIndex prev)
{
if(!now.isValid())
{
onPackSelectionChanged();
return;
}
- FtbModpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<FtbModpack>();
+ Modpack selectedPack = privateFilterModel->data(now, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&selectedPack);
}
-void FTBPage::onPackSelectionChanged(FtbModpack* pack)
+void Page::onPackSelectionChanged(Modpack* pack)
{
ui->versionSelectionBox->clear();
if(pack)
@@ -266,7 +269,7 @@ void FTBPage::onPackSelectionChanged(FtbModpack* pack)
suggestCurrent();
}
-void FTBPage::onVersionSelectionItemChanged(QString data)
+void Page::onVersionSelectionItemChanged(QString data)
{
if(data.isNull() || data.isEmpty())
{
@@ -278,15 +281,15 @@ void FTBPage::onVersionSelectionItemChanged(QString data)
suggestCurrent();
}
-void FTBPage::onSortingSelectionChanged(QString data)
+void Page::onSortingSelectionChanged(QString data)
{
- FtbFilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data);
+ FilterModel::Sorting toSet = publicFilterModel->getAvailableSortings().value(data);
publicFilterModel->setSorting(toSet);
thirdPartyFilterModel->setSorting(toSet);
privateFilterModel->setSorting(toSet);
}
-void FTBPage::onTabChanged(int tab)
+void Page::onTabChanged(int tab)
{
if(tab == 1)
{
@@ -311,7 +314,7 @@ void FTBPage::onTabChanged(int tab)
QModelIndex idx = currentList->currentIndex();
if(idx.isValid())
{
- auto pack = currentModel->data(idx, Qt::UserRole).value<FtbModpack>();
+ auto pack = currentModel->data(idx, Qt::UserRole).value<Modpack>();
onPackSelectionChanged(&pack);
}
else
@@ -320,7 +323,7 @@ void FTBPage::onTabChanged(int tab)
}
}
-void FTBPage::onAddPackClicked()
+void Page::onAddPackClicked()
{
bool ok;
QString text = QInputDialog::getText(
@@ -338,7 +341,7 @@ void FTBPage::onAddPackClicked()
}
}
-void FTBPage::onRemovePackClicked()
+void Page::onRemovePackClicked()
{
auto index = ui->privatePackList->currentIndex();
if(!index.isValid())
@@ -346,7 +349,7 @@ void FTBPage::onRemovePackClicked()
return;
}
auto row = index.row();
- FtbModpack pack = privateListModel->at(row);
+ Modpack pack = privateListModel->at(row);
auto answer = QMessageBox::question(
this,
tr("Remove pack"),
@@ -362,3 +365,5 @@ void FTBPage::onRemovePackClicked()
privateListModel->remove(row);
onPackSelectionChanged();
}
+
+}
diff --git a/application/pages/modplatform/FTBPage.h b/application/pages/modplatform/legacy_ftb/Page.h
index 6467decc..e840216e 100644
--- a/application/pages/modplatform/FTBPage.h
+++ b/application/pages/modplatform/legacy_ftb/Page.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -22,29 +22,32 @@
#include "pages/BasePage.h"
#include <MultiMC.h>
#include "tasks/Task.h"
-#include "modplatform/ftb/PackHelpers.h"
-#include "modplatform/ftb/FtbPackFetchTask.h"
+#include "modplatform/legacy_ftb/PackHelpers.h"
+#include "modplatform/legacy_ftb/PackFetchTask.h"
#include "QObjectPtr.h"
+class NewInstanceDialog;
+
+namespace LegacyFTB {
+
namespace Ui
{
-class FTBPage;
+class Page;
}
-class FtbListModel;
-class FtbFilterModel;
-class NewInstanceDialog;
-class FtbPrivatePackListModel;
-class FtbPrivatePackFilterModel;
-class FtbPrivatePackManager;
+class ListModel;
+class FilterModel;
+class PrivatePackListModel;
+class PrivatePackFilterModel;
+class PrivatePackManager;
-class FTBPage : public QWidget, public BasePage
+class Page : public QWidget, public BasePage
{
Q_OBJECT
public:
- explicit FTBPage(NewInstanceDialog * dialog, QWidget *parent = 0);
- virtual ~FTBPage();
+ explicit Page(NewInstanceDialog * dialog, QWidget *parent = 0);
+ virtual ~Page();
QString displayName() const override
{
return tr("FTB Legacy");
@@ -55,7 +58,7 @@ public:
}
QString id() const override
{
- return "ftb";
+ return "legacy_ftb";
}
QString helpPage() const override
{
@@ -66,13 +69,13 @@ public:
private:
void suggestCurrent();
- void onPackSelectionChanged(FtbModpack *pack = nullptr);
+ void onPackSelectionChanged(Modpack *pack = nullptr);
private slots:
- void ftbPackDataDownloadSuccessfully(FtbModpackList publicPacks, FtbModpackList thirdPartyPacks);
+ void ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks);
void ftbPackDataDownloadFailed(QString reason);
- void ftbPrivatePackDataDownloadSuccessfully(FtbModpack pack);
+ void ftbPrivatePackDataDownloadSuccessfully(Modpack pack);
void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode);
void onSortingSelectionChanged(QString data);
@@ -88,27 +91,29 @@ private slots:
void onRemovePackClicked();
private:
- FtbFilterModel* currentModel = nullptr;
+ FilterModel* currentModel = nullptr;
QTreeView* currentList = nullptr;
QTextBrowser* currentModpackInfo = nullptr;
bool initialized = false;
- FtbModpack selected;
+ Modpack selected;
QString selectedVersion;
- FtbListModel* publicListModel = nullptr;
- FtbFilterModel* publicFilterModel = nullptr;
+ ListModel* publicListModel = nullptr;
+ FilterModel* publicFilterModel = nullptr;
- FtbListModel *thirdPartyModel = nullptr;
- FtbFilterModel *thirdPartyFilterModel = nullptr;
+ ListModel *thirdPartyModel = nullptr;
+ FilterModel *thirdPartyFilterModel = nullptr;
- FtbListModel *privateListModel = nullptr;
- FtbFilterModel *privateFilterModel = nullptr;
+ ListModel *privateListModel = nullptr;
+ FilterModel *privateFilterModel = nullptr;
- unique_qobject_ptr<FtbPackFetchTask> ftbFetchTask;
- std::unique_ptr<FtbPrivatePackManager> ftbPrivatePacks;
+ unique_qobject_ptr<PackFetchTask> ftbFetchTask;
+ std::unique_ptr<PrivatePackManager> ftbPrivatePacks;
NewInstanceDialog* dialog = nullptr;
- Ui::FTBPage *ui = nullptr;
+ Ui::Page *ui = nullptr;
};
+
+}
diff --git a/application/pages/modplatform/FTBPage.ui b/application/pages/modplatform/legacy_ftb/Page.ui
index e5ed78cb..15e5d432 100644
--- a/application/pages/modplatform/FTBPage.ui
+++ b/application/pages/modplatform/legacy_ftb/Page.ui
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>FTBPage</class>
- <widget class="QWidget" name="FTBPage">
+ <class>LegacyFTB::Page</class>
+ <widget class="QWidget" name="LegacyFTB::Page">
<property name="geometry">
<rect>
<x>0</x>
@@ -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/TechnicPage.h b/application/pages/modplatform/technic/TechnicPage.h
index 84ea4636..27e1258a 100644
--- a/application/pages/modplatform/TechnicPage.h
+++ b/application/pages/modplatform/technic/TechnicPage.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,6 +20,7 @@
#include "pages/BasePage.h"
#include <MultiMC.h>
#include "tasks/Task.h"
+#include "TechnicData.h"
namespace Ui
{
@@ -28,6 +29,10 @@ class TechnicPage;
class NewInstanceDialog;
+namespace Technic {
+ class ListModel;
+}
+
class TechnicPage : public QWidget, public BasePage
{
Q_OBJECT
@@ -55,7 +60,19 @@ public:
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/technic/TechnicPage.ui b/application/pages/modplatform/technic/TechnicPage.ui
new file mode 100644
index 00000000..2ca45dd2
--- /dev/null
+++ b/application/pages/modplatform/technic/TechnicPage.ui
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>TechnicPage</class>
+ <widget class="QWidget" name="TechnicPage">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>546</width>
+ <height>405</height>
+ </rect>
+ </property>
+ <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>
+ <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>
+ <widget class="MCModInfoFrame" name="frame">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Raised</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>MCModInfoFrame</class>
+ <extends>QFrame</extends>
+ <header>widgets/MCModInfoFrame.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <tabstops>
+ <tabstop>searchEdit</tabstop>
+ <tabstop>searchButton</tabstop>
+ <tabstop>packView</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
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/OSX/OSX.qrc b/application/resources/OSX/OSX.qrc
index a5c40894..19fd4b6a 100644
--- a/application/resources/OSX/OSX.qrc
+++ b/application/resources/OSX/OSX.qrc
@@ -14,6 +14,7 @@
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>
diff --git a/application/resources/OSX/scalable/language.svg b/application/resources/OSX/scalable/language.svg
new file mode 100644
index 00000000..4f7d002a
--- /dev/null
+++ b/application/resources/OSX/scalable/language.svg
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ id="Calque_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 24 24"
+ enable-background="new 0 0 24 24"
+ xml:space="preserve"><metadata
+ id="metadata22"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs20" /><path
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ fill="#E6E6E6"
+ d="M21,20H9c-1.1,0-2-0.9-2-2V6c0-1.1,0.9-2,2-2h12c1.1,0,2,0.9,2,2 v12C23,19.1,22.1,20,21,20z"
+ id="path2" /><rect
+ fill="none"
+ width="24"
+ height="24"
+ id="rect4" /><g
+ id="_x36__8_"><g
+ id="g8"><path
+ d="M 21,4 H 9 C 7.9,4 7,4.9 7,6 v 12 c 0,1.1 0.9,2 2,2 h 12 c 1.1,0 2,-0.9 2,-2 V 6 C 23,4.9 22.1,4 21,4 Z m 1,14 -1,1 H 9 L 8,18 V 6 C 8,5.4 8.4,5 9,5 h 12 c 0.6,0 1,0.4 1,1 z"
+ id="path6"
+ style="fill:#585858" /></g></g><circle
+ cx="-3.8492424"
+ cy="11.559504"
+ r="1"
+ id="circle11"
+ style="clip-rule:evenodd;fill:#ffffff;fill-rule:evenodd" /><path
+ d="m 10.985086,8.5301236 c -0.285244,0 -0.531238,0.2015787 -0.587348,0.480998 L 9.2012867,14.998205 c -0.064883,0.324431 0.1450781,0.638482 0.4689122,0.703366 0.3279037,0.0672 0.6390781,-0.145078 0.7033681,-0.468911 l 0.384313,-1.91432 h 1.69195 l 0.381898,1.91432 c 0.06544,0.328662 0.388374,0.53424 0.705784,0.468911 0.323873,-0.06488 0.533795,-0.378935 0.468912,-0.703366 L 12.809973,9.0111216 c -0.05611,-0.2794193 -0.302101,-0.480998 -0.587347,-0.480998 z m 0.362559,1.3487256 h 0.256212 l 0.478579,2.3953168 h -1.21337 z m 6.429409,-1.5928481 c -0.330857,0 -0.599434,0.2698964 -0.599434,0.6018494 v 0.5994329 h -1.795884 c -0.330855,0 -0.597015,0.269898 -0.597015,0.6018506 0,0.331951 0.266159,0.599434 0.597015,0.599434 h 0.161945 c 0.340555,1.096391 0.840589,1.953495 1.401899,2.629772 -0.439717,0.403341 -0.871563,0.734012 -1.339055,1.104601 -0.25779,0.207035 -0.298799,0.58673 -0.09185,0.845975 0.205993,0.258844 0.58387,0.299404 0.841141,0.09185 0.507959,-0.402258 0.944446,-0.738141 1.421237,-1.177114 0.476792,0.438973 0.95437,0.774856 1.462328,1.177114 0.257273,0.207554 0.635147,0.166994 0.841141,-0.09185 0.206952,-0.259245 0.163525,-0.63894 -0.09426,-0.845975 -0.467495,-0.370589 -0.938012,-0.70126 -1.377731,-1.104601 0.561312,-0.676317 1.102396,-1.53342 1.442991,-2.629772 h 0.159526 c 0.330857,0 0.599433,-0.267483 0.599433,-0.599434 0,-0.3319526 -0.268576,-0.6018506 -0.599433,-0.6018506 H 18.376487 V 8.8878505 c 0,-0.331953 -0.268576,-0.6018494 -0.599433,-0.6018494 z m -0.983747,2.4025669 h 2.006168 c -0.25847,0.696576 -0.641961,1.260479 -1.022421,1.74029 -0.380459,-0.479811 -0.725279,-1.043674 -0.983747,-1.74029 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#585858;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.7126205;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect977" /></svg> \ No newline at end of file
diff --git a/application/resources/assets/assets.qrc b/application/resources/assets/assets.qrc
deleted file mode 100644
index 38638e7f..00000000
--- a/application/resources/assets/assets.qrc
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE RCC>
-<RCC version="1.0">
- <qresource prefix="/assets">
- <file alias="underconstruction">underconstruction.png</file>
- <file alias="deadglitch">deadglitch.svg</file>
- </qresource>
-</RCC>
diff --git a/application/resources/assets/deadglitch.svg b/application/resources/assets/deadglitch.svg
deleted file mode 100644
index 5682b8a3..00000000
--- a/application/resources/assets/deadglitch.svg
+++ /dev/null
@@ -1,66 +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"
- class="tw-svg__asset tw-svg__asset--deadglitch tw-svg__asset--inherit"
- width="92px"
- height="96px"
- version="1.1"
- viewBox="0 0 30 30"
- x="0px"
- y="0px"
- id="svg8"
- sodipodi:docname="deadglitch.svg"
- inkscape:version="0.92.2 2405546, 2018-03-11">
- <metadata
- id="metadata14">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <defs
- id="defs12" />
- <sodipodi:namedview
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1"
- objecttolerance="10"
- gridtolerance="10"
- guidetolerance="10"
- inkscape:pageopacity="0"
- inkscape:pageshadow="2"
- inkscape:window-width="1353"
- inkscape:window-height="828"
- id="namedview10"
- showgrid="false"
- inkscape:zoom="4.9166667"
- inkscape:cx="44.285787"
- inkscape:cy="52.833458"
- inkscape:window-x="2958"
- inkscape:window-y="702"
- inkscape:window-maximized="0"
- inkscape:current-layer="svg8" />
- <g
- id="g6"
- style="fill:#898395;fill-opacity:1">
- <path
- d="M26,17.4589613 L26,3 L4,3 L4,22.0601057 L10.0032868,22.0601057 L10.0032868,26 L14.0004537,22.0601057 L21.3322933,22.0601057 L26,17.4589613 L26,17.4589613 Z M21.0896458,26.0850335 L15.1583403,26.0850335 L11.2051771,30 L7.24798611,30 L7.24798611,26.0850335 L0,26.0850335 L0,5.21746493 L1.97773958,0 L29,0 L29,18.2620736 L21.0896458,26.0850335 L21.0896458,26.0850335 Z"
- id="path2"
- style="fill:#898395;fill-opacity:1" />
- <path
- d="M20.8587626,12.1710126 L22.4052753,13.7175252 L23.7175252,12.4052753 L22.1710126,10.8587626 L23.7175252,9.31224999 L22.4052753,8 L20.8587626,9.54651264 L19.31225,8 L18,9.31224999 L19.5465126,10.8587626 L18,12.4052753 L19.31225,13.7175252 L20.8587626,12.1710126 Z M11.8587626,12.1710126 L13.4052753,13.7175252 L14.7175252,12.4052753 L13.1710126,10.8587626 L14.7175252,9.31224999 L13.4052753,8 L11.8587626,9.54651264 L10.31225,8 L9,9.31224999 L10.5465126,10.8587626 L9,12.4052753 L10.31225,13.7175252 L11.8587626,12.1710126 Z"
- id="path4"
- style="fill:#898395;fill-opacity:1" />
- </g>
-</svg>
diff --git a/application/resources/backgrounds/backgrounds.qrc b/application/resources/backgrounds/backgrounds.qrc
index 55de139e..83505635 100644
--- a/application/resources/backgrounds/backgrounds.qrc
+++ b/application/resources/backgrounds/backgrounds.qrc
@@ -2,5 +2,6 @@
<RCC version="1.0">
<qresource prefix="/backgrounds">
<file alias="kitteh">catbgrnd2.png</file>
+ <file alias="catmas">catmas.png</file>
</qresource>
</RCC>
diff --git a/application/resources/backgrounds/catbgrnd2.png b/application/resources/backgrounds/catbgrnd2.png
index 2b949e0b..e9de7f27 100644
--- a/application/resources/backgrounds/catbgrnd2.png
+++ b/application/resources/backgrounds/catbgrnd2.png
Binary files differ
diff --git a/application/resources/backgrounds/catmas.png b/application/resources/backgrounds/catmas.png
new file mode 100644
index 00000000..8bdb1d5c
--- /dev/null
+++ b/application/resources/backgrounds/catmas.png
Binary files differ
diff --git a/application/resources/flat/flat.qrc b/application/resources/flat/flat.qrc
index 67c8d291..b6e2ee38 100644
--- a/application/resources/flat/flat.qrc
+++ b/application/resources/flat/flat.qrc
@@ -16,6 +16,7 @@
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>
diff --git a/application/resources/flat/scalable/language.svg b/application/resources/flat/scalable/language.svg
new file mode 100644
index 00000000..f4d3f2f4
--- /dev/null
+++ b/application/resources/flat/scalable/language.svg
@@ -0,0 +1,103 @@
+<?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"
+ fill="#757575"
+ height="24"
+ viewBox="0 0 24 24"
+ width="24"
+ version="1.1"
+ id="svg4"
+ sodipodi:docname="language.svg"
+ inkscape:version="0.92.2 2405546, 2018-03-11">
+ <metadata
+ id="metadata10">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs8">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ </defs>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="3840"
+ inkscape:window-height="2123"
+ id="namedview6"
+ showgrid="true"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:zoom="6.9532167"
+ inkscape:cx="-18.49351"
+ inkscape:cy="-12.477971"
+ inkscape:window-x="1200"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4">
+ <inkscape:grid
+ type="xygrid"
+ id="grid981" />
+ <sodipodi:guide
+ position="-8,11.440678"
+ orientation="1,0"
+ id="guide1060"
+ inkscape:locked="false" />
+ <sodipodi:guide
+ position="-28.34375,24"
+ orientation="0,1"
+ id="guide1062"
+ inkscape:locked="false" />
+ </sodipodi:namedview>
+ <path
+ d="M 5,3 C 3.89,3 3,3.89 3,5 v 14 c 0,1.104569 0.895431,2 2,2 h 14 c 1.104569,0 2,-0.895431 2,-2 V 5 C 21,3.89 20.1,3 19,3 Z m 10.359375,4.505859 c 0.400344,0 0.726563,0.326845 0.726563,0.728516 v 0.724609 h 2.21875 c 0.400344,0 0.726562,0.326845 0.726562,0.728516 0,0.401669 -0.326217,0.726562 -0.726562,0.726562 h -0.191407 c -0.412128,1.326612 -1.066893,2.363281 -1.746093,3.181641 0.53207,0.488052 1.100334,0.887517 1.666015,1.335938 0.311924,0.250518 0.365651,0.709744 0.115235,1.023437 -0.249259,0.313205 -0.708226,0.362473 -1.019532,0.111328 -0.614642,-0.486742 -1.192601,-0.892661 -1.769531,-1.423828 -0.576929,0.531167 -1.104107,0.937086 -1.71875,1.423828 -0.311303,0.251148 -0.768322,0.201879 -1.017578,-0.111328 -0.250416,-0.313693 -0.200604,-0.772919 0.111328,-1.023437 0.565677,-0.448421 1.087072,-0.847886 1.619141,-1.335938 -0.679199,-0.818312 -1.283234,-1.854981 -1.695313,-3.181641 h -0.197265 c -0.400344,0 -0.720704,-0.324893 -0.720704,-0.726562 0,-0.401671 0.320361,-0.728516 0.720704,-0.728516 h 2.173828 V 8.234375 c 0,-0.401671 0.324264,-0.728516 0.724609,-0.728516 z M 7.142578,7.800781 h 1.496094 c 0.345155,0 0.643047,0.243927 0.710937,0.582031 l 1.447266,7.244141 c 0.07851,0.39257 -0.174512,0.773053 -0.566406,0.851563 -0.384074,0.07905 -0.774336,-0.168718 -0.853516,-0.566407 L 8.914062,13.595703 H 6.867188 L 6.402344,15.912109 C 6.324551,16.303955 5.947553,16.559826 5.550781,16.478516 5.158934,16.400005 4.905865,16.019523 4.984375,15.626953 L 6.431641,8.382812 C 6.499536,8.044708 6.797425,7.800781 7.142578,7.800781 Z m 0.4375,1.632813 -0.578125,2.898437 H 8.46875 L 7.890625,9.433594 Z m 6.589844,0.980468 c 0.312752,0.842923 0.729088,1.524886 1.189453,2.105469 0.460366,-0.580583 0.925527,-1.262594 1.238281,-2.105469 z"
+ id="path1072"
+ inkscape:connector-curvature="0" />
+ <g
+ style="fill:#000000"
+ id="g821"
+ transform="matrix(0.0322459,0,0,0.0322459,-17.878956,30.647558)">
+ <g
+ style="fill:#000000"
+ id="g819" />
+ </g>
+</svg>
diff --git a/application/resources/iOS/iOS.qrc b/application/resources/iOS/iOS.qrc
index 7212ce77..511e390b 100644
--- a/application/resources/iOS/iOS.qrc
+++ b/application/resources/iOS/iOS.qrc
@@ -14,6 +14,7 @@
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>
diff --git a/application/resources/iOS/scalable/language.svg b/application/resources/iOS/scalable/language.svg
new file mode 100644
index 00000000..fcc3436e
--- /dev/null
+++ b/application/resources/iOS/scalable/language.svg
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata15"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs13" />
+<g
+ id="g8">
+
+ <path
+ id="path6"
+ d="M28,32H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h24c2.2,0,4,1.8,4,4 v24C32,30.2,30.2,32,28,32z M30,4c0-1.1-0.9-2-2-2H4C2.9,2,2,2.9,2,4v24c0,1.1,0.9,2,2,2h24c1.1,0,2-0.9,2-2V4z"
+ fill="#3366CC"
+ clip-rule="evenodd"
+ fill-rule="evenodd" />
+</g>
+<path
+ d="m 8.8837922,9.8498439 c -0.5055801,0 -0.9415895,0.3572871 -1.0410421,0.8525411 L 5.7221093,21.314148 c -0.1150014,0.575036 0.2571428,1.131673 0.8311203,1.246676 0.5811915,0.119103 1.1327283,-0.257142 1.2466794,-0.831119 l 0.6811731,-3.393021 h 2.9988849 l 0.676892,3.393021 c 0.115982,0.582535 0.688371,0.946911 1.250962,0.831119 0.574046,-0.115001 0.94612,-0.67164 0.831119,-1.246676 L 12.118299,10.702385 C 12.018854,10.207131 11.582843,9.8498439 11.07726,9.8498439 Z m 0.642615,2.3905391 h 0.4541203 l 0.8482535,4.245562 H 8.6781534 Z M 20.922167,9.4171507 c -0.586426,0 -1.062464,0.4783758 -1.062464,1.0667433 v 1.062461 h -3.183102 c -0.58642,0 -1.058175,0.478378 -1.058175,1.066747 0,0.588364 0.471753,1.062461 1.058175,1.062461 h 0.28704 c 0.603613,1.943292 1.489894,3.462457 2.484785,4.661121 -0.779374,0.714899 -1.544795,1.300994 -2.373399,1.957842 -0.456918,0.366958 -0.529604,1.039945 -0.162795,1.499442 0.36511,0.458786 1.034877,0.530676 1.490873,0.162795 0.900329,-0.71298 1.673977,-1.308312 2.519064,-2.086367 0.845087,0.778055 1.691565,1.373387 2.591893,2.086367 0.456001,0.367877 1.125761,0.295987 1.490873,-0.162795 0.366811,-0.459497 0.289838,-1.132484 -0.167067,-1.499442 -0.82861,-0.656848 -1.662574,-1.242943 -2.441951,-1.957842 0.994894,-1.198734 1.953935,-2.7179 2.55762,-4.661121 h 0.28275 c 0.586426,0 1.062461,-0.474097 1.062461,-1.062461 0,-0.588369 -0.476035,-1.066747 -1.062461,-1.066747 h -3.251659 v -1.062461 c 0,-0.5883675 -0.476037,-1.0667433 -1.062461,-1.0667433 z m -1.743636,4.2584123 h 3.555818 c -0.458122,1.234642 -1.137838,2.234128 -1.812182,3.084565 -0.674344,-0.850437 -1.285517,-1.849853 -1.743636,-3.084565 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#3366cc;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.58040667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect977" /></svg> \ No newline at end of file
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/instances/brick.png b/application/resources/multimc/32x32/instances/brick.png
index 0b534366..c324fda0 100644
--- a/application/resources/multimc/32x32/instances/brick.png
+++ b/application/resources/multimc/32x32/instances/brick.png
Binary files differ
diff --git a/application/resources/multimc/32x32/instances/diamond.png b/application/resources/multimc/32x32/instances/diamond.png
index 376ab901..1eb26469 100644
--- a/application/resources/multimc/32x32/instances/diamond.png
+++ b/application/resources/multimc/32x32/instances/diamond.png
Binary files differ
diff --git a/application/resources/multimc/32x32/instances/gold.png b/application/resources/multimc/32x32/instances/gold.png
index 9bedda16..593410fa 100644
--- a/application/resources/multimc/32x32/instances/gold.png
+++ b/application/resources/multimc/32x32/instances/gold.png
Binary files differ
diff --git a/application/resources/multimc/32x32/instances/iron.png b/application/resources/multimc/32x32/instances/iron.png
index 28960782..3e811bd6 100644
--- a/application/resources/multimc/32x32/instances/iron.png
+++ b/application/resources/multimc/32x32/instances/iron.png
Binary files differ
diff --git a/application/resources/multimc/32x32/instances/planks.png b/application/resources/multimc/32x32/instances/planks.png
index 7fcf8467..a94b7502 100644
--- a/application/resources/multimc/32x32/instances/planks.png
+++ b/application/resources/multimc/32x32/instances/planks.png
Binary files differ
diff --git a/application/resources/multimc/32x32/instances/stone.png b/application/resources/multimc/32x32/instances/stone.png
index 34f9a751..1b6ef7a4 100644
--- a/application/resources/multimc/32x32/instances/stone.png
+++ b/application/resources/multimc/32x32/instances/stone.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/index.theme b/application/resources/multimc/index.theme
index 290f42fb..6061b7f8 100644
--- a/application/resources/multimc/index.theme
+++ b/application/resources/multimc/index.theme
@@ -2,7 +2,7 @@
Name=multimc
Comment=MultiMC Default Icons
Inherits=default
-Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable
+Directories=8x8,16x16,22x22,24x24,32x32,32x32/instances,48x48,50x50/instances,64x64,128x128/instances,256x256,scalable,scalable/instances
[8x8]
Size=8
@@ -51,3 +51,8 @@ Size=48
Type=Scalable
MinSize=16
MaxSize=256
+
+[scalable/instances]
+Size=128
+MinSize=16
+MaxSize=256
diff --git a/application/resources/multimc/multimc.qrc b/application/resources/multimc/multimc.qrc
index f99cfca2..249e8e28 100644
--- a/application/resources/multimc/multimc.qrc
+++ b/application/resources/multimc/multimc.qrc
@@ -11,15 +11,23 @@
<!-- 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>
+ <!-- A free language icon. http://www.languageicon.org/ -->
+ <file>scalable/language.svg</file>
+
<!-- Java icon. From Oracle, fixed because it was derpy. -->
<file>scalable/java.svg</file>
@@ -305,5 +313,8 @@
<file>32x32/instances/tnt.png</file>
<file>50x50/instances/enderman.png</file>
+
+ <file>scalable/instances/fox.svg</file>
+ <file>scalable/instances/bee.svg</file>
</qresource>
</RCC>
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/instances/bee.svg b/application/resources/multimc/scalable/instances/bee.svg
new file mode 100644
index 00000000..49f216c8
--- /dev/null
+++ b/application/resources/multimc/scalable/instances/bee.svg
@@ -0,0 +1,159 @@
+<?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"
+ version="1.1"
+ viewBox="0 0 25.399999 25.4"
+ height="96"
+ width="96">
+ <g transform="translate(-33.928467,-255.46043)">
+ <g transform="rotate(-9.9635201,-96.932986,622.95265)">
+ <path style="fill:#f1f2e0;fill-opacity:1;fill-rule:evenodd;stroke:#999999;stroke-width:0.28753757px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="m 101.98199,286.42583 -1.1502,0.57512 1.14992,0.5755 2.30058,1.14996 1.15019,-0.57514 -2.30058,-1.14995 z m 2.3003,2.30058 -1.1502,0.57512 1.15002,0.57493 1.15019,-0.57513 z m -1.8e-4,1.15005 -1.1502,0.57512 1.15057,0.57502 1.15019,-0.57512 z m 3.7e-4,1.15014 -1.15019,0.57514 -1.1502,0.57512 1.15001,0.57493 1.1502,-0.57513 1.15019,-0.57512 z m -2.30039,1.15026 -1.15001,-0.57494 -1.150566,-0.57502 -1.150193,0.57512 1.150565,0.57502 1.150014,0.57494 z m -2.300576,-1.14996 1.150196,-0.57513 -1.150014,-0.57493 -1.150097,0.57456 z m -1.149915,-0.5755 1.150193,-0.57512 -1.150012,-0.57492 -1.150194,0.57512 z m 1.81e-4,-1.15004 1.150194,-0.57513 -1.150567,-0.57503 -1.150193,0.57512 z m -3.73e-4,-1.15016 1.150194,-0.57512 1.150189,-0.57513 -1.150008,-0.57492 -1.150193,0.57512 -1.150194,0.57512 z m 1.150567,0.57503 1.149916,0.57548 1.15001,0.57494 1.15019,-0.57512 -1.15001,-0.57494 -1.15047,-0.57558 z" />
+ </g>
+ <g style="fill:none;stroke:#999999" transform="matrix(1.0012918,0.26829532,-0.26829532,1.0012918,86.112205,-31.978257)">
+ <path style="stroke:#999999;stroke-width:1.03661346px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 85.501953 51.128906 C 82.473901 51.748417 79.445938 52.368362 76.417969 52.988281 L 79.886719 56.064453 C 82.914656 55.444381 85.942682 54.824742 88.970703 54.205078 L 85.501953 51.128906 z "
+ transform="matrix(0.24654113,-0.06606049,0.06606049,0.24654113,23.141685,280.86706)" />
+ <path style="stroke:#999999;stroke-width:1.03661346px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 79.636719 40.972656 L 75.095703 41.902344 C 78.563735 44.978675 82.032556 48.054115 85.501953 51.128906 L 90.042969 50.201172 L 79.636719 40.972656 z "
+ transform="matrix(0.24654113,-0.06606049,0.06606049,0.24654113,23.141685,280.86706)" />
+ </g>
+ <path style="fill:#fed668" d="m 41.865965,262.07502 -4.233333,2.11667 7.408333,3.70417 4.233334,-2.11667 z" />
+ <path style="fill:#0a0707" d="m 50.332633,272.39378 v 1.32291 l 1.058333,0.52917 v -1.32292 z" />
+ <path style="fill:#422117" d="m 50.332633,273.7167 v 1.32291 l 1.058333,0.52917 v -1.32292 z" />
+ <path style="fill:#0a0707" d="m 48.215966,273.45211 v 1.32291 l 1.058333,0.52917 v -1.32292 z" />
+ <path style="fill:#422117" d="m 48.215966,274.77503 v 1.32291 l 1.058333,0.52917 v -1.32292 z" />
+ <path style="fill:#422117" d="m 45.040966,275.3042 v 1.32291 l 1.058333,0.52917 v -1.32292 z" />
+ <path style="fill:#78621d" d="m 45.040965,277.15626 4.233334,-2.11665 v -9.26042 l -4.233334,2.11667 z" />
+ <path style="fill:#1d0c08" d="m 50.332632,265.25002 -1.058333,0.52917 v 9.26042 l 1.058333,-0.52917 z" />
+ <path style="fill:#1d0c08" d="m 52.449299,264.19169 -1.058333,0.52917 v 9.26042 l 1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 38.690965,263.66252 1.058334,0.52917 1.058333,-0.52917 1.058333,0.52917 2.116667,-1.05834 -2.116667,-1.05833 z" />
+ <path style="fill:#fed668" d="m 42.924299,261.54586 7.408334,3.70417 1.058333,-0.52917 -7.408334,-3.70417 z" />
+ <path style="fill:#e4ae3b" d="m 40.807633,262.60419 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#fed668" d="m 45.040966,260.48752 7.408333,3.70417 1.058333,-0.52917 -7.408333,-3.70417 z" />
+ <path style="fill:#5f3225" d="m 42.924299,261.54585 -1.058334,0.52917 7.408333,3.70417 1.058334,-0.52917 z" />
+ <path style="fill:#e4ae3b" d="m 42.9243,261.54585 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#5f3225" d="m 45.040965,260.48752 -1.058333,0.52917 7.408333,3.70416 1.058333,-0.52916 z" />
+ <path style="fill:#e4ae3b" d="m 45.040966,260.48752 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#5f3225" d="m 46.099299,259.95835 7.408333,3.70417 2.116666,-1.05833 -7.408333,-3.70417 z" />
+ <path style="fill:#552e22" d="m 47.157633,259.42919 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 43.982633,262.07502 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 46.099299,261.01669 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 40.807633,264.72086 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#e4ae3b" d="m 45.040967,262.60419 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#552e22" d="m 49.2743,260.48753 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 48.215966,262.07502 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 41.865966,266.30836 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#552e22" d="m 50.332633,262.07502 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 46.099299,265.25002 -1.058334,0.52917 1.058334,0.52916 -2.116667,1.05834 1.058333,0.52916 3.175,-1.5875 z" />
+ <path style="fill:#edc343" d="m 48.215967,264.19169 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 50.332633,263.13336 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#e4ae3b" d="m 47.157633,265.77919 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#e4ae3b" d="m 49.2743,264.72085 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#e4ae3b" d="m 51.390966,263.66252 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#edc343" d="m 38.690966,264.72086 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#ac8d2e" d="m 37.632633,273.45211 7.408333,3.70415 v -9.2604 l -7.408333,-3.70417 z" />
+ <path style="fill:#12121a" d="m 37.632633,268.16044 2.116666,1.05834 v 3.96875 l -2.116666,-1.05834 z" />
+ <path style="fill:#78621d" d="m 50.332633,265.25002 v 9.26041 l 1.058333,-0.52916 v -9.26042 z" />
+ <path style="fill:#78621d" d="m 52.449299,264.19169 v 9.26041 l 1.058333,-0.52916 v -9.26042 z" />
+ <path style="fill:#27120b" d="m 49.274299,265.77919 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" />
+ <path style="fill:#09090e" d="m 45.040966,271.86461 1.058333,-0.52917 v 3.96875 l -1.058333,0.52917 -2.116667,-1.05834 v -3.96875 z" />
+ <path style="fill:#150704" d="m 49.2743,271.07085 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" />
+ <path style="fill:#050303" d="m 49.2743,273.71669 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#150704" d="m 51.390966,268.68961 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" />
+ <path style="fill:#050303" d="m 51.390966,271.33544 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" />
+ <path style="fill:#2c140d" d="m 51.390966,264.72086 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#1d0c08" d="m 55.624299,262.60419 -2.116666,1.05833 v 9.26042 l 2.116666,-1.05833 z" />
+ <path style="fill:#150704" d="m 53.507633,268.95419 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#050303" d="m 53.507633,270.2771 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" />
+ <path style="fill:#2c140d" d="m 53.507633,263.66252 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#27120b" d="m 54.565966,263.13335 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" />
+ <path style="fill:#150704" d="m 54.565966,269.74794 v 2.64583 l 1.058333,-0.52916 v -2.64584 z" />
+ <path style="fill:#27120b" d="m 54.565966,267.10211 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#633f19" d="m 45.040965,277.15626 3.175,-1.58749 v -1.32291 l -3.175,1.5875 z" />
+ <path style="fill:#55300a" d="m 48.215966,274.24586 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#694520" d="m 48.215966,272.92294 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#735619" d="m 48.215966,271.60002 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#735619" d="m 47.157633,273.45211 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#131016" d="m 45.040966,271.86461 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#131016" d="m 45.040966,274.51044 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#694520" d="m 45.040966,270.54169 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#735619" d="m 46.0993,270.01252 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#816c31" d="m 45.040966,267.89586 v 1.32291 l 1.058333,-0.52916 v 1.32291 l 1.058334,-0.52916 v -1.32292 l 2.116666,-1.05833 v -1.32292 z" />
+ <path style="fill:#49250c" d="m 50.332633,273.18752 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#633f19" d="m 50.332633,271.86461 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#735619" d="m 50.332633,269.21878 v 2.64582 l 1.058333,-0.52916 v -2.64583 z" />
+ <path style="fill:#816c31" d="m 50.332633,265.25002 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#49250c" d="m 52.449299,270.80627 10e-7,2.64583 1.058333,-0.52916 -10e-7,-2.64584 z" />
+ <path style="fill:#55300a" d="m 52.4493,269.48336 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#694520" d="m 52.4493,268.16044 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#755719" d="m 52.4493,266.83752 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#816c31" d="m 52.4493,264.19169 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#b89b49" d="m 37.632632,265.51461 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" />
+ <path style="fill:#b89b49" d="m 40.807633,267.10211 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" />
+ <path style="fill:#b89b49" d="m 43.982633,270.01253 1.058333,0.52916 v -2.64584 l -1.058333,-0.52916 z" />
+ <path style="fill:#966531" d="m 43.982632,271.33545 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#a57d28" d="m 42.924299,270.80627 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#b89b49" d="m 41.865965,270.27712 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#966531" d="m 43.982632,271.33545 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#a57d28" d="m 39.749299,273.18753 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#a57d28" d="m 41.865965,274.24587 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#a57d28" d="m 40.807633,275.03961 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#8e5c28" d="m 43.982633,276.62711 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#966531" d="m 41.865966,275.56877 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" />
+ <path style="fill:#966531" d="m 38.690966,273.98128 2.116667,1.05833 v -1.32292 l -2.116667,-1.05833 z" />
+ <path style="fill:#8e5c28" d="m 37.632632,273.45212 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#956531" d="m 37.632633,268.16044 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#b89b49" d="m 39.749299,267.89587 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#1f1c25" d="m 38.690966,267.36669 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#1f1c25" d="m 42.924299,269.48337 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#589197" d="m 42.924299,272.12919 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#589197" d="m 38.690966,270.01252 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#12121a" d="m 43.982633,272.65836 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#12121a" d="m 42.924299,273.45211 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#12121a" d="m 43.982633,275.30419 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#1f1c25" d="m 43.982633,273.98127 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#1f1c25" d="m 42.924299,274.77503 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#1f1c25" d="m 38.690966,272.65836 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#1f1c25" d="m 37.632633,270.80627 1.058334,0.52916 v -1.32292 l -1.058334,-0.52916 z" />
+ <path style="fill:#131016" d="m 38.690966,267.10211 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#131016" d="m 36.574299,266.83752 v 1.32291 l 2.116667,-1.05832 v -1.32292 z" />
+ <path style="fill:#131016" d="m 41.865966,268.68961 v 1.32291 l 1.058333,-0.52916 v -1.32292 z" />
+ <path style="fill:#131016" d="m 39.749299,268.42502 v 1.32291 l 2.116667,-1.05832 v -1.32292 z" />
+ <g transform="matrix(1.0012918,0.26829532,-0.26829532,1.0012918,86.112205,-31.978257)">
+ <path style="fill:#f1f2e0" d="m 38.073986,286.23688 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#5f3225" d="m 37.015652,286.76606 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#5f3225" d="m 37.015652,287.82438 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 38.073986,288.35355 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 40.190652,286.23689 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 42.307319,286.23688 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 42.307319,287.29521 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 43.365653,287.82437 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 43.365653,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 41.248986,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 40.190653,288.35354 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 45.482319,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 46.540653,288.35355 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 46.540653,287.29521 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 44.423986,286.23688 2.116667,1.05833 1.058333,-0.52917 -2.116667,-1.05833 z" />
+ </g>
+ <g transform="matrix(1.0703659,-0.18803179,0.18803179,1.0703659,-63.348962,-38.123102)">
+ <path style="fill:#f1f2e0" d="m 48.392735,288.88271 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#5f3225" d="m 47.334402,289.41187 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#5f3225" d="m 45.217735,289.41188 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 44.159402,288.8827 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 43.101069,286.23687 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 43.101068,285.17854 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 46.276068,284.64938 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 45.217735,286.23688 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 44.159402,284.64937 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 43.101068,287.2952 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 44.159402,287.82438 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 48.392735,286.76604 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 48.392735,287.82438 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f1f2e0" d="m 46.276068,286.76604 1.058333,0.52917 1.058333,-0.52917 -1.058333,-0.52917 z" />
+ <path style="fill:#f7fdfd" d="m 47.334402,285.17854 2.116667,1.05833 1.058333,-0.52917 -2.116667,-1.05833 z" />
+ </g>
+ </g>
+</svg>
diff --git a/application/resources/multimc/scalable/instances/fox.svg b/application/resources/multimc/scalable/instances/fox.svg
new file mode 100644
index 00000000..fcf16b2f
--- /dev/null
+++ b/application/resources/multimc/scalable/instances/fox.svg
@@ -0,0 +1,290 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ id="svg8"
+ version="1.1"
+ viewBox="0 0 33.866666 33.866666"
+ height="128"
+ width="128">
+ <defs
+ id="defs2" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-263.13334)"
+ id="layer1">
+ <path
+ id="rect4750"
+ d="m 4.233333,267.36667 v 6.35 6.35 3.175 3.175 3.175 3.175 h 25.4 v -3.175 -3.175 -3.175 -3.175 -6.35 -6.35 h -6.35 v 6.35 h -12.7 v -3.175 -3.175 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:0.79375;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <g
+ id="g4748">
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4553"
+ width="6.3500032"
+ height="6.3499975"
+ x="4.233326"
+ y="267.36667" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b48f83;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4553-6-2"
+ width="3.1750014"
+ height="3.1749992"
+ x="7.4083276"
+ y="270.54166" />
+ <rect
+ y="267.36667"
+ x="23.283335"
+ height="6.3499975"
+ width="6.3500032"
+ id="rect4623"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062567;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ y="270.54166"
+ x="23.283335"
+ height="3.1749992"
+ width="3.1750014"
+ id="rect4627"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b48f83;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e27c21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4672"
+ width="25.400013"
+ height="15.875009"
+ x="4.233326"
+ y="273.71667" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4629"
+ width="3.1750016"
+ height="6.3500032"
+ x="4.2333255"
+ y="273.71667" />
+ <rect
+ y="273.71667"
+ x="26.458338"
+ height="6.3500032"
+ width="3.1750016"
+ id="rect4631"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e78f41;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4636"
+ width="6.3500032"
+ height="3.1750016"
+ x="13.758331"
+ y="283.24167" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b05122;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4638"
+ width="3.1750016"
+ height="3.1750016"
+ x="4.2333255"
+ y="280.06668" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4640"
+ width="3.1750016"
+ height="3.1750016"
+ x="7.4083276"
+ y="280.06668" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84189939;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4644"
+ width="6.3500028"
+ height="3.1750016"
+ x="4.233326"
+ y="283.24167" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4642"
+ width="3.1750016"
+ height="3.1750016"
+ x="4.2333255"
+ y="283.24167" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.84189939;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4646"
+ width="6.3500028"
+ height="3.1750016"
+ x="23.283337"
+ y="283.24167" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4648"
+ width="3.1750016"
+ height="3.1750016"
+ x="26.45834"
+ y="283.24167" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4650"
+ width="3.1750016"
+ height="3.1750016"
+ x="23.283337"
+ y="280.06668" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#b05122;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4652"
+ width="3.1750016"
+ height="3.1750016"
+ x="26.45834"
+ y="280.06668" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#cc6920;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4654"
+ width="25.400013"
+ height="3.1750016"
+ x="4.2333255"
+ y="286.41666" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.59531283;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4656"
+ width="25.400013"
+ height="3.1750016"
+ x="4.2333255"
+ y="289.59167" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.5612604;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4917"
+ width="25.4"
+ height="0.26457807"
+ x="4.2333331"
+ y="273.71667" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.48607069;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4921"
+ width="0.2645835"
+ height="19.049997"
+ x="4.2333331"
+ y="273.71667" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#f9f4f4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4658"
+ width="12.700007"
+ height="6.3500013"
+ x="10.583333"
+ y="286.41666" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#06040e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4660"
+ width="6.3500037"
+ height="3.1750007"
+ x="13.758333"
+ y="286.41669" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4662"
+ width="3.1750019"
+ height="3.1750007"
+ x="10.583333"
+ y="286.41669" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#e7d9d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.19062555;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4664"
+ width="3.1750019"
+ height="3.1750007"
+ x="20.108334"
+ y="286.41669" />
+ <rect
+ y="286.41669"
+ x="10.583333"
+ height="6.3499832"
+ width="0.2645835"
+ id="rect4923"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2806327;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.39686465;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4951"
+ width="12.699996"
+ height="0.26456967"
+ x="10.583333"
+ y="286.41669" />
+ <rect
+ y="273.71667"
+ x="29.36875"
+ height="19.049982"
+ width="0.26458356"
+ id="rect4953"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.48607057;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ y="292.50208"
+ x="23.283333"
+ height="0.26457682"
+ width="6.3499994"
+ id="rect4957"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28062955;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.2806325;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4959"
+ width="0.2645838"
+ height="6.3499656"
+ x="23.018749"
+ y="286.41669" />
+ <rect
+ y="292.50208"
+ x="4.2333331"
+ height="0.26457968"
+ width="6.3499999"
+ id="rect4961"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063107;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ y="267.36667"
+ x="4.2333331"
+ height="6.3499956"
+ width="0.2645835"
+ id="rect4963"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063297;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063536;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4965"
+ width="6.349998"
+ height="0.26458791"
+ x="4.2333331"
+ y="267.36667" />
+ <rect
+ y="267.36667"
+ x="23.283333"
+ height="6.3499956"
+ width="0.2645835"
+ id="rect4963-9"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.280633;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063536;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4965-1"
+ width="6.349998"
+ height="0.26458791"
+ x="23.283333"
+ y="267.36667" />
+ <rect
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063306;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect4985"
+ width="0.26458356"
+ height="6.3499975"
+ x="29.36875"
+ y="267.36667" />
+ <rect
+ y="267.36667"
+ x="10.318749"
+ height="6.3499975"
+ width="0.26458356"
+ id="rect4987"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:0.25;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.28063306;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" />
+ </g>
+ </g>
+</svg>
diff --git a/application/resources/multimc/scalable/language.svg b/application/resources/multimc/scalable/language.svg
new file mode 100644
index 00000000..968e3538
--- /dev/null
+++ b/application/resources/multimc/scalable/language.svg
@@ -0,0 +1,109 @@
+<?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"
+ viewBox="0 0 128 128"
+ height="128"
+ width="128"
+ xml:space="preserve"
+ id="svg2"
+ version="1.1"
+ sodipodi:docname="language.svg"
+ inkscape:version="0.92.2 2405546, 2018-03-11"><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="3840"
+ inkscape:window-height="2123"
+ id="namedview30"
+ showgrid="false"
+ inkscape:zoom="1.84375"
+ inkscape:cx="325.0346"
+ inkscape:cy="134.81864"
+ inkscape:window-x="1200"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="g888" /><metadata
+ id="metadata8"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs6" /><g
+ transform="matrix(1.3333333,0,0,-1.3333333,0,128.00004)"
+ id="g10"><g
+ transform="scale(0.1)"
+ id="g12"><g
+ transform="matrix(0.0334181,0,0,0.0334181,77.111273,13.149509)"
+ id="g888"><g
+ id="g14"
+ transform="scale(1.69573)"
+ style="fill:#0066cc;fill-opacity:1"><path
+ d="M 7103.55,14358.7 1603.03,16300 V 4328.22 l 5500.52,1779.59 v 8250.89"
+ style="fill:#0066cc;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path16" /></g><g
+ id="g18"
+ transform="scale(1.69635)"><path
+ d="M 6968.96,14359.4 12678,16300 V 4332.6 L 6968.96,6111.53 v 8247.87"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path20" /></g><g
+ id="g22"
+ transform="scale(1.49453)"><path
+ d="M 200.466,2533.04 7910.06,5102.75 V 16300 L 200.466,13730.3 V 2533.04"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path24" /></g><g
+ id="g26"
+ transform="scale(1.20038)"
+ style="fill:#003399;fill-opacity:1"><path
+ d="M 14222.2,2943.15 15582.6,705.027 16300,2784.12 14222.2,2943.15"
+ style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path28" /></g><g
+ id="g30"
+ transform="scale(1.16442)"
+ style="fill:#003399;fill-opacity:1"><path
+ d="m 3621.88,15967.1 c -52.56,51.6 68.45,-421.7 236.85,-592 298.61,-301.3 531.85,-340.1 656.04,-345.1 274.81,-11 613.95,68.5 815.34,152.9 194.86,83.1 536.31,257.4 665.56,511.7 27.4,54.4 102.2,145.7 55.22,371.2 -35.64,173.5 -146.08,234.2 -280.74,224.6 -134.66,-9.1 -542.33,-117.8 -739.51,-178.5 -197.26,-59.8 -603.56,-183.5 -780.64,-221.9 -176.65,-38.3 -566.12,17.8 -628.12,77.1"
+ style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path32" /></g><g
+ id="g34"
+ transform="scale(1.0917)"
+ style="fill:#003399;fill-opacity:1"><path
+ d="m 9188.43,10995.1 c -83.18,30.2 -1803.98,743 -2047.91,859.8 -199.6,96 -689.02,302.9 -919.3,396.9 648.62,1000.1 1058.07,1754.8 1112.58,1869.8 100.85,210.3 787.39,1553.7 803.42,1636.4 15.57,83.8 35.08,393.4 19.97,467 -15.11,75 -266.83,-69.2 -608.59,-185.1 -342.31,-115.4 -992.86,-538.5 -1244.12,-591.5 -252.17,-52.6 -1058.07,-357.9 -1470.46,-494.7 -412.38,-136.9 -1192.45,-375 -1513.33,-461.6 -321.33,-86.7 -601.81,-93.5 -781.53,-148 0,0 23.91,-251.8 71.63,-327.2 47.18,-75.5 217.19,-260.5 414.86,-312.2 197.67,-52 524.87,-31.2 673.9,2.9 148.95,34.6 406.98,160.7 441.61,215.7 34.99,56 -18.05,228.4 40.85,280.5 59.45,51.6 844.83,235.2 1141.34,324.7 296.51,91.2 1431.53,482.1 1585.42,462.2 C 6860.04,14829 5947.06,13020.6 5653.02,12481.1 5358.89,11941.7 3650.37,9568.39 3286.62,9150.14 3010.54,8832.19 2341.49,8018.6 2109.74,7835.03 c 58.44,-16.12 472.75,19.42 548.23,66.14 470.36,289.73 1253.82,1265 1506.09,1562.06 749.84,879.37 1408.63,1803.07 1931.02,2595.77 h 0.56 c 101.76,-42.4 924.61,-712.8 1139.32,-861.4 214.71,-148.5 1062.01,-621.2 1245.58,-699.7 183.57,-79.4 889.07,-404.6 918.75,-294.5 29.68,111 -127.6,760.1 -210.86,791.7"
+ style="fill:#003399;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path36" /></g><g
+ id="g38"
+ transform="scale(1.12147)"
+ style="fill:#003399;fill-opacity:1"><path
+ d="m 5073.69,1979.54 c 160.5,-98.08 312.09,-178.34 481.51,-258.59 338.84,-169.42 722.26,-347.76 1087.85,-481.51 499.35,-187.25 998.69,-338.838 1498.03,-454.757 276.43,-62.418 579.6,-115.919 873.85,-160.504 26.76,0 820.35,-98.085 980.86,-98.085 h 802.51 c 312.1,26.751 606.4,44.584 918.4,89.169 249.7,35.667 526.1,80.251 793.6,142.669 196.2,44.584 401.3,89.169 597.5,151.587 187.2,53.501 401.2,124.831 606.3,196.171 133.8,44.58 276.4,107 419.1,160.5 115.9,53.5 258.6,115.92 392.3,169.42 160.6,71.34 347.8,169.42 526.1,258.59 142.7,71.34 303.2,160.5 454.8,249.67 115.9,62.42 383.4,267.51 526.1,267.51 160.5,0 267.5,-142.67 267.5,-267.51 0,-258.59 -347.8,-338.84 -508.3,-454.76 -169.4,-115.92 -374.5,-205.08 -552.8,-303.17 -356.7,-187.253 -722.3,-347.756 -1070,-481.509 -454.8,-169.42 -954.1,-329.923 -1400,-436.926 -169.4,-35.667 -338.8,-80.251 -508.2,-107.002 C 12171.5,142.67 11244.1,0 10985.6,0 H 9808.53 c -312.09,26.7505 -642.01,62.4179 -954.1,107.002 -276.42,44.584 -570.68,98.086 -847.1,160.503 -214,44.585 -445.84,107.003 -650.93,169.421 -356.67,98.085 -704.43,222.921 -1043.27,356.674 -615.26,231.84 -1257.28,535.01 -1863.62,936.27 -107,71.33 -115.92,142.67 -115.92,222.92 0,133.75 98.08,258.59 258.59,258.59 142.67,0 428.01,-205.09 481.51,-231.84"
+ style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="path40" /></g><g
+ id="g42"
+ transform="scale(1.51227)"
+ style="fill:#003399;fill-opacity:1"><path
+ d="M 8014.44,16134.7 V 5025.56 c -6.61,-33.07 -19.84,-66.13 -46.29,-99.19 -13.22,-19.84 -39.67,-46.29 -59.51,-52.9 C 7743.33,4807.34 297.566,2307.79 198.377,2307.79 c -79.351,0 -152.089,52.9 -191.76442,138.86 0,6.62 -6.61258,13.23 -6.61258,26.45 v 11115.7 c 13.2252,33.1 19.8377,79.4 46.288,105.8 52.9006,72.8 145.477,86 204.99,105.8 112.414,39.7 7445.762,2499.6 7551.562,2499.6 66.13,0 211.6,-46.3 211.6,-165.3 z M 7611.07,5190.87 403.367,2790.51 V 13423.5 L 7611.07,15823.9 V 5190.87"
+ style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="path44" /></g><g
+ id="g46"
+ transform="scale(1.71411)"
+ style="fill:#003399;fill-opacity:1"><path
+ d="M 12723.8,16113.3 V 4311.27 c -5.8,-134.18 -99.2,-192.52 -186.7,-192.52 -75.8,0 -624.2,186.69 -717.6,215.86 -735,227.52 -1475.9,455.05 -2205.18,682.57 -163.35,52.51 -332.54,105.01 -490.05,157.52 -140.02,40.83 -291.7,87.5 -431.71,134.18 -624.23,192.52 -1260.13,385.04 -1884.36,595.06 -23.34,5.83 -81.68,87.51 -81.68,105.01 v 8243.35 c 11.67,29.2 23.34,64.2 52.51,87.5 46.67,52.5 2047.71,717.6 2835.29,980.1 210.02,75.8 2841.08,980.1 2922.78,980.1 105,0 186.7,-75.8 186.7,-186.7 z M 12367.9,4532.96 7076.56,6178.13 V 14083.1 L 12367.9,15880 V 4532.96"
+ style="fill:#003399;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="path48" /></g><g
+ id="g50"
+ transform="scale(1.48515)"
+ style="fill:#0066cc;fill-opacity:1"><path
+ d="M 16235.4,2378.81 8076.61,4979.28 8110.74,16300 16235.4,13714.1 V 2378.81"
+ style="fill:#0066cc;fill-opacity:1;fill-rule:nonzero;stroke:none"
+ id="path52" /></g><g
+ id="g54"
+ transform="scale(1.33098)"><path
+ d="m 12990.3,14581.8 1172.9,-355.3 2136.8,-7701.24 -1204.8,365.52 -432.8,1581.01 -2489.7,754.63 -535.4,-1287.92 -1205.1,365.6 z m 536.2,-2038.8 -893.6,-2159.8 1642.8,-497.94 -749.2,2657.74"
+ style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ id="path56" /></g></g></g></g></svg> \ No newline at end of file
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/resources/pe_blue/pe_blue.qrc b/application/resources/pe_blue/pe_blue.qrc
index 7d28d3d7..98445d88 100644
--- a/application/resources/pe_blue/pe_blue.qrc
+++ b/application/resources/pe_blue/pe_blue.qrc
@@ -14,6 +14,7 @@
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>
diff --git a/application/resources/pe_blue/scalable/language.svg b/application/resources/pe_blue/scalable/language.svg
new file mode 100644
index 00000000..92868516
--- /dev/null
+++ b/application/resources/pe_blue/scalable/language.svg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ id="Calque_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 32 32"
+ enable-background="new 0 0 32 32"
+ xml:space="preserve"><metadata
+ id="metadata45"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs43" /><path
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ fill="#3366CC"
+ d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z"
+ id="path2" /><path
+ fill="#DAEEFF"
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
+ id="path4" /><g
+ id="g10" /><g
+ id="g12" /><g
+ id="g14" /><g
+ id="g16" /><g
+ id="g18" /><g
+ id="g20" /><g
+ id="g22" /><g
+ id="g24" /><g
+ id="g26" /><g
+ id="g28" /><g
+ id="g30" /><g
+ id="g32" /><g
+ id="g34" /><g
+ id="g36" /><g
+ id="g38" /><path
+ d="m 9.1897907,10.114301 c -0.4838401,0 -0.9011011,0.341924 -0.9962772,0.815883 L 6.1640606,21.085639 c -0.1100564,0.55031 0.2460855,1.083011 0.7953819,1.193069 0.5562003,0.113982 1.0840209,-0.246085 1.1930722,-0.79538 l 0.6518824,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.6149825,2.287746 h 0.4345928 l 0.811779,4.063002 H 8.9929943 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#c1272d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect977" /></svg> \ No newline at end of file
diff --git a/application/resources/pe_colored/pe_colored.qrc b/application/resources/pe_colored/pe_colored.qrc
index 9a7a89cc..fbaaf9e4 100644
--- a/application/resources/pe_colored/pe_colored.qrc
+++ b/application/resources/pe_colored/pe_colored.qrc
@@ -14,6 +14,7 @@
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>
diff --git a/application/resources/pe_colored/scalable/language.svg b/application/resources/pe_colored/scalable/language.svg
new file mode 100644
index 00000000..80c1dcad
--- /dev/null
+++ b/application/resources/pe_colored/scalable/language.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata19"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs17" />
+<path
+ id="path2"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
+ fill="#F2F2F2"
+ clip-rule="evenodd"
+ fill-rule="evenodd" />
+
+<g
+ id="g12">
+ <path
+ id="path6"
+ d="M6,28h20c1.1,0,2-0.9,2-2V9V6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v3v17C4,27.1,4.9,28,6,28z"
+ fill="none" />
+ <path
+ id="path8"
+ d="M26,0H6C2.7,0,0,2.7,0,6v3h4V6c0-1.1,0.9-2,2-2h20c1.1,0,2,0.9,2,2v3h4V6C32,2.7,29.3,0,26,0z"
+ fill="#39B54A" />
+ <path
+ id="path10"
+ d="M28,26c0,1.1-0.9,2-2,2H6c-1.1,0-2-0.9-2-2V9H0v17c0,3.3,2.7,6,6,6h20c3.3,0,6-2.7,6-6V9h-4V26z"
+ fill="#8C6239" />
+</g>
+<path
+ d="m 9.1897919,10.114302 c -0.483841,0 -0.901102,0.341924 -0.996278,0.815883 l -2.029453,10.155454 c -0.110056,0.55031 0.246086,1.083011 0.795382,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247121 H 11.67433 l 0.647785,3.247121 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285214,10.930185 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345931 l 0.811779,4.063002 H 8.9929949 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578073 -1.016777,1.0208753 v 1.016774 h -3.046227 c -0.561204,0 -1.012672,0.457809 -1.012672,1.020876 0,0.563065 0.451466,1.016776 1.012672,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873653 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628601 -1.591082,-1.189495 -2.336946,-1.873653 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208753 -1.016775,-1.0208753 z m -1.668658,4.0753013 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#c1272d;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect977" /></svg> \ No newline at end of file
diff --git a/application/resources/pe_dark/pe_dark.qrc b/application/resources/pe_dark/pe_dark.qrc
index 0a49b0bb..a57b6a14 100644
--- a/application/resources/pe_dark/pe_dark.qrc
+++ b/application/resources/pe_dark/pe_dark.qrc
@@ -14,6 +14,7 @@
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>
diff --git a/application/resources/pe_dark/scalable/language.svg b/application/resources/pe_dark/scalable/language.svg
new file mode 100644
index 00000000..1a9b4c5c
--- /dev/null
+++ b/application/resources/pe_dark/scalable/language.svg
@@ -0,0 +1,45 @@
+<?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"
+ version="1.1"
+ id="Calque_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 32 32"
+ enable-background="new 0 0 32 32"
+ xml:space="preserve"><metadata
+ id="metadata45"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs43" /><path
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6v20 C32,29.3,29.3,32,26,32z"
+ id="path2" /><path
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ fill="#F2F2F2"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
+ id="path4" /><g
+ id="g10" /><g
+ id="g12" /><g
+ id="g14" /><g
+ id="g16" /><g
+ id="g18" /><g
+ id="g20" /><g
+ id="g22" /><g
+ id="g24" /><g
+ id="g26" /><g
+ id="g28" /><g
+ id="g30" /><g
+ id="g32" /><g
+ id="g34" /><g
+ id="g36" /><g
+ id="g38" /><path
+ d="m 9.1897911,10.114301 c -0.48384,0 -0.901101,0.341924 -0.996278,0.815883 l -2.029452,10.155455 c -0.110057,0.55031 0.246085,1.083011 0.795381,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345929 l 0.811779,4.063002 H 8.9929941 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect977" /></svg> \ No newline at end of file
diff --git a/application/resources/pe_light/pe_light.qrc b/application/resources/pe_light/pe_light.qrc
index 98c29e9e..6d77c835 100644
--- a/application/resources/pe_light/pe_light.qrc
+++ b/application/resources/pe_light/pe_light.qrc
@@ -14,6 +14,7 @@
<file>scalable/instance-settings.svg</file>
<file>scalable/jarmods.svg</file>
<file>scalable/java.svg</file>
+ <file>scalable/language.svg</file>
<file>scalable/loadermods.svg</file>
<file>scalable/log.svg</file>
<file>scalable/minecraft.svg</file>
diff --git a/application/resources/pe_light/scalable/language.svg b/application/resources/pe_light/scalable/language.svg
new file mode 100644
index 00000000..57d5e3de
--- /dev/null
+++ b/application/resources/pe_light/scalable/language.svg
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xml:space="preserve"
+ enable-background="new 0 0 32 32"
+ viewBox="0 0 32 32"
+ y="0px"
+ x="0px"
+ id="Calque_1"
+ version="1.1"><metadata
+ id="metadata43"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+ id="defs41" />
+<path
+ id="path2"
+ d="M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20 c1.1,0,2-0.9,2-2V6z"
+ fill="#4D4D4D"
+ clip-rule="evenodd"
+ fill-rule="evenodd" />
+
+<path
+ id="path6"
+ d="M26,32H6c-3.3,0-6-2.7-6-6V6c0-3.3,2.7-6,6-6h20c3.3,0,6,2.7,6,6 v20C32,29.3,29.3,32,26,32z M28,6c0-1.1-0.9-2-2-2H6C4.9,4,4,4.9,4,6v20c0,1.1,0.9,2,2,2h20c1.1,0,2-0.9,2-2V6z"
+ fill="#F2F2F2"
+ clip-rule="evenodd"
+ fill-rule="evenodd" />
+<g
+ id="g8">
+</g>
+<g
+ id="g10">
+</g>
+<g
+ id="g12">
+</g>
+<g
+ id="g14">
+</g>
+<g
+ id="g16">
+</g>
+<g
+ id="g18">
+</g>
+<g
+ id="g20">
+</g>
+<g
+ id="g22">
+</g>
+<g
+ id="g24">
+</g>
+<g
+ id="g26">
+</g>
+<g
+ id="g28">
+</g>
+<g
+ id="g30">
+</g>
+<g
+ id="g32">
+</g>
+<g
+ id="g34">
+</g>
+<g
+ id="g36">
+</g>
+<path
+ d="m 9.1897911,10.114301 c -0.48384,0 -0.901101,0.341924 -0.996278,0.815883 l -2.029452,10.155455 c -0.110057,0.55031 0.246085,1.083011 0.795381,1.193069 0.556201,0.113982 1.084021,-0.246085 1.193073,-0.79538 l 0.651882,-3.247122 h 2.8699319 l 0.647785,3.247122 c 0.110996,0.557485 0.658771,0.906193 1.197171,0.79538 0.549363,-0.110056 0.905437,-0.642759 0.795381,-1.193069 L 12.285213,10.930184 c -0.09517,-0.473959 -0.512431,-0.815883 -0.996274,-0.815883 z m 0.614982,2.287746 h 0.4345929 l 0.811779,4.063002 H 8.9929941 Z M 20.710512,9.7002137 c -0.561209,0 -1.016777,0.4578063 -1.016777,1.0208743 v 1.016774 h -3.046227 c -0.561205,0 -1.012673,0.457809 -1.012673,1.020876 0,0.563065 0.451466,1.016776 1.012673,1.016776 h 0.274696 c 0.577658,1.85973 1.425829,3.313572 2.37794,4.460692 -0.745862,0.684158 -1.478369,1.245052 -2.271343,1.873654 -0.437271,0.35118 -0.506831,0.995228 -0.155795,1.434967 0.349411,0.439057 0.990377,0.507857 1.426766,0.155794 0.861615,-0.682321 1.601995,-1.252055 2.410742,-1.996652 0.808748,0.744597 1.618828,1.314331 2.480441,1.996652 0.436393,0.352059 1.077353,0.283261 1.426766,-0.155794 0.351038,-0.439739 0.277375,-1.083787 -0.159884,-1.434967 -0.792979,-0.628602 -1.591082,-1.189496 -2.336946,-1.873654 0.952113,-1.147188 1.869915,-2.601029 2.447642,-4.460692 h 0.270592 c 0.561209,0 1.016774,-0.453711 1.016774,-1.016776 0,-0.563067 -0.455565,-1.020876 -1.016774,-1.020876 h -3.111838 v -1.016774 c 0,-0.563068 -0.455567,-1.0208743 -1.016775,-1.0208743 z m -1.668658,4.0753003 h 3.402916 c -0.438423,1.181552 -1.08891,2.13806 -1.734258,2.951929 -0.645345,-0.813869 -1.230238,-1.770309 -1.668658,-2.951929 z"
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6.29744864;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate"
+ id="rect977" /></svg> \ No newline at end of file
diff --git a/application/setupwizard/LanguageWizardPage.cpp b/application/setupwizard/LanguageWizardPage.cpp
index dbbe5e7f..ca93c6f5 100644
--- a/application/setupwizard/LanguageWizardPage.cpp
+++ b/application/setupwizard/LanguageWizardPage.cpp
@@ -2,25 +2,19 @@
#include <MultiMC.h>
#include <translations/TranslationsModel.h>
+#include "widgets/LanguageSelectionWidget.h"
#include <QVBoxLayout>
-#include <QListView>
LanguageWizardPage::LanguageWizardPage(QWidget *parent)
: BaseWizardPage(parent)
{
setObjectName(QStringLiteral("languagePage"));
- verticalLayout = new QVBoxLayout(this);
- verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
- languageView = new QListView(this);
- languageView->setObjectName(QStringLiteral("languageView"));
- verticalLayout->addWidget(languageView);
- retranslate();
+ auto layout = new QVBoxLayout(this);
+ mainWidget = new LanguageSelectionWidget(this);
+ layout->setContentsMargins(0,0,0,0);
+ layout->addWidget(mainWidget);
- auto translations = MMC->translations();
- auto index = translations->selectedIndex();
- languageView->setModel(translations.get());
- languageView->setCurrentIndex(index);
- connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageWizardPage::languageRowChanged);
+ retranslate();
}
LanguageWizardPage::~LanguageWizardPage()
@@ -41,8 +35,7 @@ void LanguageWizardPage::refresh()
bool LanguageWizardPage::validatePage()
{
auto settings = MMC->settings();
- auto translations = MMC->translations();
- QString key = translations->data(languageView->currentIndex(), Qt::UserRole).toString();
+ QString key = mainWidget->getSelectedLanguageKey();
settings->set("Language", key);
return true;
}
@@ -51,16 +44,5 @@ void LanguageWizardPage::retranslate()
{
setTitle(tr("Language"));
setSubTitle(tr("Select the language to use in MultiMC"));
-}
-
-void LanguageWizardPage::languageRowChanged(const QModelIndex &current, const QModelIndex &previous)
-{
- if (current == previous)
- {
- return;
- }
- auto translations = MMC->translations();
- QString key = translations->data(current, Qt::UserRole).toString();
- translations->selectLanguage(key);
- translations->updateLanguage(key);
+ mainWidget->retranslate();
}
diff --git a/application/setupwizard/LanguageWizardPage.h b/application/setupwizard/LanguageWizardPage.h
index 866f81c3..45a0e5c0 100644
--- a/application/setupwizard/LanguageWizardPage.h
+++ b/application/setupwizard/LanguageWizardPage.h
@@ -2,8 +2,7 @@
#include "BaseWizardPage.h"
-class QVBoxLayout;
-class QListView;
+class LanguageSelectionWidget;
class LanguageWizardPage : public BaseWizardPage
{
@@ -22,10 +21,6 @@ public:
protected:
void retranslate() override;
-protected slots:
- void languageRowChanged(const QModelIndex &current, const QModelIndex &previous);
-
private:
- QVBoxLayout *verticalLayout = nullptr;
- QListView *languageView = nullptr;
+ LanguageSelectionWidget *mainWidget = nullptr;
};
diff --git a/application/setupwizard/SetupWizard.h b/application/setupwizard/SetupWizard.h
index aa3fb32c..9b8adb4d 100644
--- a/application/setupwizard/SetupWizard.h
+++ b/application/setupwizard/SetupWizard.h
@@ -1,4 +1,4 @@
-/* Copyright 2017-2018 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.cpp b/application/widgets/CustomCommands.cpp
index 9e7673fd..24bdc07d 100644
--- a/application/widgets/CustomCommands.cpp
+++ b/application/widgets/CustomCommands.cpp
@@ -3,6 +3,7 @@
CustomCommands::~CustomCommands()
{
+ delete ui;
}
CustomCommands::CustomCommands(QWidget* parent):
diff --git a/application/widgets/CustomCommands.h b/application/widgets/CustomCommands.h
index 07041479..8db991fa 100644
--- a/application/widgets/CustomCommands.h
+++ b/application/widgets/CustomCommands.h
@@ -1,4 +1,4 @@
-/* Copyright 2018-2018 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.
@@ -28,7 +28,7 @@ class CustomCommands : public QWidget
public:
explicit CustomCommands(QWidget *parent = 0);
- ~CustomCommands();
+ virtual ~CustomCommands();
void initialize(bool checkable, bool checked, const QString & prelaunch, const QString & wrapper, const QString & postexit);
bool checked() const;
diff --git a/application/widgets/CustomCommands.ui b/application/widgets/CustomCommands.ui
index d3bc86b8..25b2681b 100644
--- a/application/widgets/CustomCommands.ui
+++ b/application/widgets/CustomCommands.ui
@@ -10,10 +10,19 @@
<height>646</height>
</rect>
</property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
<layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
<item>
<widget class="QGroupBox" name="customCommandsGroupBox">
<property name="enabled">
diff --git a/application/widgets/DropLabel.cpp b/application/widgets/DropLabel.cpp
new file mode 100644
index 00000000..a900e57c
--- /dev/null
+++ b/application/widgets/DropLabel.cpp
@@ -0,0 +1,41 @@
+#include "DropLabel.h"
+
+#include <QMimeData>
+#include <QDropEvent>
+
+DropLabel::DropLabel(QWidget *parent) : QLabel(parent)
+{
+ setAcceptDrops(true);
+}
+
+void DropLabel::dragEnterEvent(QDragEnterEvent *event)
+{
+ event->acceptProposedAction();
+}
+
+void DropLabel::dragMoveEvent(QDragMoveEvent *event)
+{
+ event->acceptProposedAction();
+}
+
+void DropLabel::dragLeaveEvent(QDragLeaveEvent *event)
+{
+ event->accept();
+}
+
+void DropLabel::dropEvent(QDropEvent *event)
+{
+ const QMimeData *mimeData = event->mimeData();
+
+ if (!mimeData)
+ {
+ return;
+ }
+
+ if (mimeData->hasUrls()) {
+ auto urls = mimeData->urls();
+ emit droppedURLs(urls);
+ }
+
+ event->acceptProposedAction();
+}
diff --git a/application/widgets/DropLabel.h b/application/widgets/DropLabel.h
new file mode 100644
index 00000000..c5ca0bcc
--- /dev/null
+++ b/application/widgets/DropLabel.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include <QLabel>
+
+class DropLabel : public QLabel
+{
+ Q_OBJECT
+
+public:
+ explicit DropLabel(QWidget *parent = nullptr);
+
+signals:
+ void droppedURLs(QList<QUrl> urls);
+
+protected:
+ void dropEvent(QDropEvent *event) override;
+ void dragEnterEvent(QDragEnterEvent *event) override;
+ void dragMoveEvent(QDragMoveEvent *event) override;
+ void dragLeaveEvent(QDragLeaveEvent *event) override;
+};
diff --git a/application/widgets/InstanceCardWidget.ui b/application/widgets/InstanceCardWidget.ui
index 3ea3e85f..6eeeb076 100644
--- a/application/widgets/InstanceCardWidget.ui
+++ b/application/widgets/InstanceCardWidget.ui
@@ -10,9 +10,6 @@
<height>118</height>
</rect>
</property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="2">
<widget class="QToolButton" name="iconButton">
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 ae79fd99..ab2d3278 100644
--- a/application/widgets/LabeledToolButton.cpp
+++ b/application/widgets/LabeledToolButton.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 e7ba5924..51f99e9b 100644
--- a/application/widgets/LabeledToolButton.h
+++ b/application/widgets/LabeledToolButton.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.cpp b/application/widgets/LanguageSelectionWidget.cpp
new file mode 100644
index 00000000..8d23bdc5
--- /dev/null
+++ b/application/widgets/LanguageSelectionWidget.cpp
@@ -0,0 +1,66 @@
+#include "LanguageSelectionWidget.h"
+
+#include <QVBoxLayout>
+#include <QTreeView>
+#include <QHeaderView>
+#include <QLabel>
+#include "MultiMC.h"
+#include "translations/TranslationsModel.h"
+
+LanguageSelectionWidget::LanguageSelectionWidget(QWidget *parent) :
+ QWidget(parent)
+{
+ verticalLayout = new QVBoxLayout(this);
+ verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
+ languageView = new QTreeView(this);
+ languageView->setObjectName(QStringLiteral("languageView"));
+ languageView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ languageView->setAlternatingRowColors(true);
+ languageView->setRootIsDecorated(false);
+ languageView->setItemsExpandable(false);
+ languageView->setWordWrap(true);
+ languageView->header()->setCascadingSectionResizes(true);
+ languageView->header()->setStretchLastSection(false);
+ verticalLayout->addWidget(languageView);
+ helpUsLabel = new QLabel(this);
+ helpUsLabel->setObjectName(QStringLiteral("helpUsLabel"));
+ helpUsLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
+ helpUsLabel->setOpenExternalLinks(true);
+ helpUsLabel->setWordWrap(true);
+ verticalLayout->addWidget(helpUsLabel);
+
+ auto translations = MMC->translations();
+ auto index = translations->selectedIndex();
+ languageView->setModel(translations.get());
+ languageView->setCurrentIndex(index);
+ languageView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ languageView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+ connect(languageView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &LanguageSelectionWidget::languageRowChanged);
+ verticalLayout->setContentsMargins(0,0,0,0);
+}
+
+QString LanguageSelectionWidget::getSelectedLanguageKey() const
+{
+ auto translations = MMC->translations();
+ return translations->data(languageView->currentIndex(), Qt::UserRole).toString();
+}
+
+void LanguageSelectionWidget::retranslate()
+{
+ QString text = tr("Don't see your language or the quality is poor?<br/><a href=\"%1\">Help us with translations!</a>")
+ .arg("https://github.com/MultiMC/MultiMC5/wiki/Translating-MultiMC");
+ helpUsLabel->setText(text);
+
+}
+
+void LanguageSelectionWidget::languageRowChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+ if (current == previous)
+ {
+ return;
+ }
+ auto translations = MMC->translations();
+ QString key = translations->data(current, Qt::UserRole).toString();
+ translations->selectLanguage(key);
+ translations->updateLanguage(key);
+}
diff --git a/application/widgets/LanguageSelectionWidget.h b/application/widgets/LanguageSelectionWidget.h
new file mode 100644
index 00000000..e65936db
--- /dev/null
+++ b/application/widgets/LanguageSelectionWidget.h
@@ -0,0 +1,41 @@
+/* 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>
+
+class QVBoxLayout;
+class QTreeView;
+class QLabel;
+
+class LanguageSelectionWidget: public QWidget
+{
+ Q_OBJECT
+public:
+ explicit LanguageSelectionWidget(QWidget *parent = 0);
+ virtual ~LanguageSelectionWidget() { };
+
+ QString getSelectedLanguageKey() const;
+ void retranslate();
+
+protected slots:
+ void languageRowChanged(const QModelIndex &current, const QModelIndex &previous);
+
+private:
+ QVBoxLayout *verticalLayout = nullptr;
+ QTreeView *languageView = nullptr;
+ QLabel *helpUsLabel = nullptr;
+};
diff --git a/application/widgets/MCModInfoFrame.cpp b/application/widgets/MCModInfoFrame.cpp
index 3bd2af1e..5b1f6230 100644
--- a/application/widgets/MCModInfoFrame.cpp
+++ b/application/widgets/MCModInfoFrame.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -40,7 +40,7 @@ void MCModInfoFrame::updateWithMod(Mod &m)
else
text = "<a href=\"" + m.homeurl() + "\">" + name + "</a>";
if (!m.authors().isEmpty())
- text += " by " + m.authors();
+ text += " by " + m.authors().join(", ");
setModText(text);
@@ -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 5d27449a..0b7ef537 100644
--- a/application/widgets/MCModInfoFrame.h
+++ b/application/widgets/MCModInfoFrame.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,7 +16,7 @@
#pragma once
#include <QFrame>
-#include "minecraft/Mod.h"
+#include "minecraft/mod/Mod.h"
namespace Ui
{
diff --git a/application/widgets/ModListView.cpp b/application/widgets/ModListView.cpp
index 6f084ba7..c8ccd292 100644
--- a/application/widgets/ModListView.cpp
+++ b/application/widgets/ModListView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 ee88d634..881e092f 100644
--- a/application/widgets/ModListView.h
+++ b/application/widgets/ModListView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,8 +16,6 @@
#pragma once
#include <QTreeView>
-class Mod;
-
class ModListView: public QTreeView
{
Q_OBJECT
diff --git a/application/widgets/PageContainer.cpp b/application/widgets/PageContainer.cpp
index 7e681592..05a5e6b4 100644
--- a/application/widgets/PageContainer.cpp
+++ b/application/widgets/PageContainer.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -140,15 +140,13 @@ void PageContainer::createUI()
QHBoxLayout *headerHLayout = new QHBoxLayout;
const int leftMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
- headerHLayout->addSpacerItem(
- new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
+ headerHLayout->addSpacerItem(new QSpacerItem(leftMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
headerHLayout->addWidget(m_header);
- headerHLayout->addSpacerItem(
- new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
+ headerHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
headerHLayout->addWidget(m_iconHeader);
const int rightMargin = MMC->style()->pixelMetric(QStyle::PM_LayoutRightMargin);
- headerHLayout->addSpacerItem(
- new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
+ headerHLayout->addSpacerItem(new QSpacerItem(rightMargin, 0, QSizePolicy::Fixed, QSizePolicy::Ignored));
+ headerHLayout->setContentsMargins(0, 6, 0, 0);
m_pageStack->setMargin(0);
m_pageStack->addWidget(new QWidget(this));
@@ -158,6 +156,7 @@ void PageContainer::createUI()
m_layout->addWidget(m_pageList, 0, 0, 2, 1);
m_layout->addLayout(m_pageStack, 1, 1, 1, 1);
m_layout->setColumnStretch(1, 4);
+ m_layout->setContentsMargins(0,0,0,6);
setLayout(m_layout);
}
diff --git a/application/widgets/PageContainer.h b/application/widgets/PageContainer.h
index 9aa1ebb0..976d34e9 100644
--- a/application/widgets/PageContainer.h
+++ b/application/widgets/PageContainer.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 8f1c52ea..da1a66f4 100644
--- a/application/widgets/PageContainer_p.h
+++ b/application/widgets/PageContainer_p.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 a7016c0c..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();
@@ -125,7 +125,7 @@ void ServerStatus::addStatus(QString key, QString name)
void ServerStatus::clicked()
{
- DesktopServices::openUrl(QUrl("https://help.mojang.com/"));
+ DesktopServices::openUrl(QUrl("https://github.com/MultiMC/MultiMC5/wiki/Mojang-Services-Status"));
}
void ServerStatus::setStatus(QString key, int value)
diff --git a/application/widgets/VersionListView.cpp b/application/widgets/VersionListView.cpp
index 215905f5..8424fedd 100644
--- a/application/widgets/VersionListView.cpp
+++ b/application/widgets/VersionListView.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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,9 +29,8 @@ VersionListView::VersionListView(QWidget *parent)
void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end)
{
- if(!m_itemCount)
- viewport()->update();
m_itemCount += end-start+1;
+ updateEmptyViewPort();
QTreeView::rowsInserted(parent, start, end);
}
@@ -39,16 +38,14 @@ void VersionListView::rowsInserted(const QModelIndex &parent, int start, int end
void VersionListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
m_itemCount -= end-start+1;
- if(!m_itemCount)
- viewport()->update();
+ updateEmptyViewPort();
QTreeView::rowsInserted(parent, start, end);
}
void VersionListView::setModel(QAbstractItemModel *model)
{
m_itemCount = model->rowCount();
- if(!m_itemCount)
- viewport()->update();
+ updateEmptyViewPort();
QTreeView::setModel(model);
}
@@ -58,7 +55,10 @@ void VersionListView::reset()
{
m_itemCount = model()->rowCount();
}
- viewport()->update();
+ else {
+ m_itemCount = 0;
+ }
+ updateEmptyViewPort();
QTreeView::reset();
}
@@ -82,6 +82,10 @@ void VersionListView::setEmptyMode(VersionListView::EmptyMode mode)
void VersionListView::updateEmptyViewPort()
{
+#ifndef QT_NO_ACCESSIBILITY
+ setAccessibleDescription(currentEmptyString());
+#endif /* !QT_NO_ACCESSIBILITY */
+
if(!m_itemCount)
{
viewport()->update();
@@ -100,77 +104,60 @@ void VersionListView::paintEvent(QPaintEvent *event)
}
}
-void VersionListView::paintInfoLabel(QPaintEvent *event)
+QString VersionListView::currentEmptyString() const
{
- QString emptyString;
+ if(m_itemCount) {
+ return QString();
+ }
switch(m_emptyMode)
{
+ default:
case VersionListView::Empty:
- return;
+ return QString();
case VersionListView::String:
- emptyString = m_emptyString;
- break;
+ return m_emptyString;
case VersionListView::ErrorString:
- emptyString = m_emptyErrorString;
- break;
+ return m_emptyErrorString;
}
+}
+
+
+void VersionListView::paintInfoLabel(QPaintEvent *event) const
+{
+ QString emptyString = currentEmptyString();
+
//calculate the rect for the overlay
QPainter painter(viewport());
painter.setRenderHint(QPainter::Antialiasing, true);
QFont font("sans", 20);
font.setBold(true);
-
+
QRect bounds = viewport()->geometry();
bounds.moveTop(0);
- QTextLayout layout(emptyString, font);
- qreal height = 0.0;
- qreal widthUsed = 0.0;
- QStringList lines = viewItemTextLayout(layout, bounds.width() - 20, height, widthUsed);
- QRect rect (0,0, widthUsed, height);
- rect.setWidth(rect.width()+20);
- rect.setHeight(rect.height()+20);
- rect.moveCenter(bounds.center());
+ auto innerBounds = bounds;
+ innerBounds.adjust(10, 10, -10, -10);
+
+ QColor background = QApplication::palette().color(QPalette::Foreground);
+ QColor foreground = QApplication::palette().color(QPalette::Base);
+ foreground.setAlpha(190);
+ painter.setFont(font);
+ auto fontMetrics = painter.fontMetrics();
+ auto textRect = fontMetrics.boundingRect(innerBounds, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
+ textRect.moveCenter(bounds.center());
+
+ auto wrapRect = textRect;
+ wrapRect.adjust(-10, -10, 10, 10);
+
//check if we are allowed to draw in our area
- if (!event->rect().intersects(rect)) {
+ if (!event->rect().intersects(wrapRect)) {
return;
}
- //draw the letter of the topmost item semitransparent in the middle
- QColor background = QApplication::palette().color(QPalette::Foreground);
- QColor foreground = QApplication::palette().color(QPalette::Base);
- /*
- background.setAlpha(128 - scrollFade);
- foreground.setAlpha(128 - scrollFade);
- */
+
painter.setBrush(QBrush(background));
painter.setPen(foreground);
- painter.drawRoundedRect(rect, 5.0, 5.0);
- foreground.setAlpha(190);
+ painter.drawRoundedRect(wrapRect, 5.0, 5.0);
+
painter.setPen(foreground);
painter.setFont(font);
- painter.drawText(rect, Qt::AlignCenter, lines.join("\n"));
-
-}
-
-/*
-void ModListView::setModel ( QAbstractItemModel* model )
-{
- QTreeView::setModel ( model );
- auto head = header();
- head->setStretchLastSection(false);
- // HACK: this is true for the checkbox column of mod lists
- auto string = model->headerData(0,head->orientation()).toString();
- if(!string.size())
- {
- head->setSectionResizeMode(0, QHeaderView::ResizeToContents);
- head->setSectionResizeMode(1, QHeaderView::Stretch);
- for(int i = 2; i < head->count(); i++)
- head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
- }
- else
- {
- head->setSectionResizeMode(0, QHeaderView::Stretch);
- for(int i = 1; i < head->count(); i++)
- head->setSectionResizeMode(i, QHeaderView::ResizeToContents);
- }
+ painter.drawText(textRect, Qt::AlignHCenter | Qt::TextWordWrap, emptyString);
}
-*/
diff --git a/application/widgets/VersionListView.h b/application/widgets/VersionListView.h
index a7195b38..4153b314 100644
--- a/application/widgets/VersionListView.h
+++ b/application/widgets/VersionListView.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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.
@@ -16,8 +16,6 @@
#pragma once
#include <QTreeView>
-class Mod;
-
class VersionListView : public QTreeView
{
Q_OBJECT
@@ -46,8 +44,9 @@ protected slots:
virtual void rowsInserted(const QModelIndex &parent, int start, int end) override;
private: /* methods */
- void paintInfoLabel(QPaintEvent *event);
+ void paintInfoLabel(QPaintEvent *event) const;
void updateEmptyViewPort();
+ QString currentEmptyString() const;
private: /* variables */
int m_itemCount = 0;
diff --git a/application/widgets/VersionSelectWidget.cpp b/application/widgets/VersionSelectWidget.cpp
index 8e7a0953..9925a6b4 100644
--- a/application/widgets/VersionSelectWidget.cpp
+++ b/application/widgets/VersionSelectWidget.cpp
@@ -41,6 +41,7 @@ VersionSelectWidget::VersionSelectWidget(QWidget* parent)
void VersionSelectWidget::setCurrentVersion(const QString& version)
{
m_currentVersion = version;
+ m_proxyModel->setCurrentVersion(version);
}
void VersionSelectWidget::setEmptyString(QString emptyString)
diff --git a/application/widgets/VersionSelectWidget.h b/application/widgets/VersionSelectWidget.h
index 32b56c60..0a649408 100644
--- a/application/widgets/VersionSelectWidget.h
+++ b/application/widgets/VersionSelectWidget.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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/WideBar.cpp b/application/widgets/WideBar.cpp
new file mode 100644
index 00000000..cbd6c617
--- /dev/null
+++ b/application/widgets/WideBar.cpp
@@ -0,0 +1,116 @@
+#include "WideBar.h"
+#include <QToolButton>
+#include <QMenu>
+
+class ActionButton : public QToolButton
+{
+ Q_OBJECT
+public:
+ ActionButton(QAction * action, QWidget * parent = 0) : QToolButton(parent), m_action(action) {
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ connect(action, &QAction::changed, this, &ActionButton::actionChanged);
+ connect(this, &ActionButton::clicked, action, &QAction::trigger);
+ actionChanged();
+ };
+private slots:
+ void actionChanged() {
+ setEnabled(m_action->isEnabled());
+ setChecked(m_action->isChecked());
+ setCheckable(m_action->isCheckable());
+ setText(m_action->text());
+ setIcon(m_action->icon());
+ setToolTip(m_action->toolTip());
+ setHidden(!m_action->isVisible());
+ setFocusPolicy(Qt::NoFocus);
+ }
+private:
+ QAction * m_action;
+};
+
+
+WideBar::WideBar(const QString& title, QWidget* parent) : QToolBar(title, parent)
+{
+ setFloatable(false);
+ setMovable(false);
+}
+
+WideBar::WideBar(QWidget* parent) : QToolBar(parent)
+{
+ setFloatable(false);
+ setMovable(false);
+}
+
+struct WideBar::BarEntry {
+ enum Type {
+ None,
+ Action,
+ Separator,
+ Spacer
+ } type = None;
+ QAction *qAction = nullptr;
+ QAction *wideAction = nullptr;
+};
+
+
+WideBar::~WideBar()
+{
+ for(auto *iter: m_entries) {
+ delete iter;
+ }
+}
+
+void WideBar::addAction(QAction* action)
+{
+ auto entry = new BarEntry();
+ entry->qAction = addWidget(new ActionButton(action, this));
+ entry->wideAction = action;
+ entry->type = BarEntry::Action;
+ m_entries.push_back(entry);
+}
+
+void WideBar::addSeparator()
+{
+ auto entry = new BarEntry();
+ entry->qAction = QToolBar::addSeparator();
+ entry->type = BarEntry::Separator;
+ m_entries.push_back(entry);
+}
+
+void WideBar::insertSpacer(QAction* action)
+{
+ auto iter = std::find_if(m_entries.begin(), m_entries.end(), [action](BarEntry * entry) {
+ return entry->wideAction == action;
+ });
+ if(iter == m_entries.end()) {
+ return;
+ }
+ QWidget* spacer = new QWidget();
+ spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ auto entry = new BarEntry();
+ entry->qAction = insertWidget((*iter)->qAction, spacer);
+ entry->type = BarEntry::Spacer;
+ m_entries.insert(iter, entry);
+}
+
+QMenu * WideBar::createContextMenu(QWidget *parent, const QString & title)
+{
+ QMenu *contextMenu = new QMenu(title, parent);
+ for(auto & item: m_entries) {
+ switch(item->type) {
+ default:
+ case BarEntry::None:
+ break;
+ case BarEntry::Separator:
+ case BarEntry::Spacer:
+ contextMenu->addSeparator();
+ break;
+ case BarEntry::Action:
+ contextMenu->addAction(item->wideAction);
+ break;
+ }
+ }
+ return contextMenu;
+}
+
+#include "WideBar.moc"
diff --git a/application/widgets/WideBar.h b/application/widgets/WideBar.h
new file mode 100644
index 00000000..d1b8cbe7
--- /dev/null
+++ b/application/widgets/WideBar.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <QToolBar>
+#include <QAction>
+#include <QMap>
+
+class QMenu;
+
+class WideBar : public QToolBar
+{
+ Q_OBJECT
+
+public:
+ explicit WideBar(const QString &title, QWidget * parent = nullptr);
+ explicit WideBar(QWidget * parent = nullptr);
+ virtual ~WideBar();
+
+ void addAction(QAction *action);
+ void addSeparator();
+ void insertSpacer(QAction *action);
+ QMenu *createContextMenu(QWidget *parent = nullptr, const QString & title = QString());
+
+private:
+ struct BarEntry;
+ QList<BarEntry *> m_entries;
+};
diff --git a/application/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in
index a1d236b2..60d417a6 100644
--- a/application/BuildConfig.cpp.in
+++ b/buildconfig/BuildConfig.cpp.in
@@ -1,7 +1,7 @@
#include "BuildConfig.h"
#include <QObject>
-Config BuildConfig;
+const Config BuildConfig;
Config::Config()
{
@@ -33,6 +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/application/BuildConfig.h b/buildconfig/BuildConfig.h
index 05fff490..185bebad 100644
--- a/application/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;
@@ -51,7 +56,7 @@ public:
/**
* This is used to fetch the news RSS feed.
- * It defaults in CMakeLists.txt to "http://multimc.org/rss.xml"
+ * It defaults in CMakeLists.txt to "https://multimc.org/rss.xml"
*/
QString NEWS_RSS_URL;
@@ -61,10 +66,39 @@ 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_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).
*/
QString printableVersionString() const;
};
-extern Config BuildConfig;
+extern const Config BuildConfig;
diff --git a/buildconfig/CMakeLists.txt b/buildconfig/CMakeLists.txt
new file mode 100644
index 00000000..de4fd350
--- /dev/null
+++ b/buildconfig/CMakeLists.txt
@@ -0,0 +1,11 @@
+######## Configure the file with build properties ########
+
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/BuildConfig.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp")
+
+add_library(BuildConfig STATIC
+ BuildConfig.h
+ ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.cpp
+)
+
+target_link_libraries(BuildConfig Qt5::Core)
+target_include_directories(BuildConfig PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
diff --git a/changelog.md b/changelog.md
index 7dbe4ce5..31b99a6b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,373 @@
-# MultiMC 0.6.2
+# MultiMC 0.6.12
-## New or changed features
+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
+
+- 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.
+
+- GH-2544: MultiMC now supports downloading library files without including them on the Java classpath.
+
+ This is done by adding them to the `mavenFiles` list instead of the `libraries` list.
+
+ Such downloads are not deduplicated or version upgraded like libraries are.
+
+ This enables ForgeWrapper to work - MultiMC downloads all the files, and ForgeWrapper runs the Forge installer during instance start when needed.
+
+## MultiMC 0.6.8
+
+This is mostly about removal of the 'curse URL' related features, because they were of low quality and generally unreliable.
+
+There are some bug fixes included.
+
+MultiMC also migrated to a new continuous deployment system, which makes everything that much smoother.
+
+### New or changed features
+
+- GH-852: Instance group expansion status now saves/loads as expected.
+
+- The bees have invaded the launcher. We now have a bee icon.
+
+- Translations have been overhauled, yet again...
+
+ - We now have a [crowdin site](https://translate.multimc.org/) for all the translation work.
+
+ - Translations are made based on the development version, and for the development version.
+
+ - Many strings have been tweaked to make translating the application easier.
+
+ - When selecting languages, European Portuguese is now displaying properly.
+
+- Accessibility has been further improved - the main window reads as `MultiMC`, not a long string of nonsensical version numbers, when announced by a screen reader.
+
+- Removed the unimplemented Technic page from instance creation dialog.
+
+- GH-2859: Broken twitch URL import method was removed.
+
+- GH-2819: Filter bar in mod lists now also works with descriptions and author lists.
+
+- GH-2832: Version page now has buttons for opening the Minecraft and internal libraries folders of the instance.
+
+- GH-2769: When copying an instance, there's now an option to keep or remove the total play time from the copy.
+
+### Bugfixes
+
+- GH-2880: Clicking the service status indicators now opens a valid site again, instead of going nowhere.
+
+- GH-2853: When collapsing groups in instance view, the action no longer becomes 'sticky' and doesn't apply to items clicked afterwards.
+
+- GH-2787: "Download All" button works again.
+
+- When a component is customized, the launcher will not try to update it in an infinite loop when something else requires a different version.
+
+
+## MultiMC 0.6.7
+
+The previous release introduced some extra buttons that made the instance window way too big for some displays. This release is aimed at fixing that, along with other UI and performance improvements.
+
+There are some accessibility fixes thrown in too.
+
+### New or changed features
+
+- Mod lists are now asynchronous and heavily threaded.
+
+ Basically, both faster and more responsive.
+
+ The changes necessary for this also pave the way for having other sources of mod metadata, and for adding more mod-related features support in general.
+
+- Mod list printed in log has been improved.
+
+ It now also shows disabled mods, and has prefix and suffix that shows if the mod is enabled, and if it is a folder.
+
+- You can now enable and disable mods with the keyboard.
+
+ Toggle with enter.
+
+- Enabling and disabling mods no longer makes the list forget what was selected.
+
+- GH-358: Switched all the dialog pages from using buttons in layouts to toolbars.
+
+ Toolbar buttons are smaller, and the toolbars can overflow buttons into an overflow space. This allows requiring a lot less space for the windows.
+
+ All of the relevant pages now also have context menus to offset the issues toolbars create when using screen readers.
+
+- Main window instance list is now compatible with screen readers.
+
+ If you have poor or no eyesight, this makes MultiMC usable.
+
+- More instance pages are now visible when the instance is running.
+
+ Mods, version and the like should now be visible, but most of the controls are disabled until the game closes.
+
+- GH-2550, GH-2722, GH-2762: Mod list sorting is much improved.
+
+ You can now sort mods by enabled status.
+
+ Sorting by version actually looks at the versions as versions, not words.
+
+ Sorting by name ignores 'The' prefixes in mod names. For example, 'The Betweenlands' will be sorted as 'Betweenlands'.
+
+ Sorting cascades from 'Enabled' to 'Name' and then 'Version'. This means that if you sort 'Enabled', the enabled and disabled mods are still sorted
+ by name and mods with the same name will be also sorted by version.
+
+## MultiMC 0.6.6
+
+This release is mostly the smaller things that have accumulated over time, along with a big change in linux packaging.
+
+No 1.13+ Forge news yet. That's going to be a major overhaul of many of the internals of MultiMC.
+
+### **IMPORTANT**
+
+On linux, MultiMC no longer bundles the Qt libraries. This fixes many issues, but it might not run after the update unless you have the required libraries installed.
+
+Make sure you have the following packages before you update:
+
+- Arch: `qt5-base`
+- Debian/Ubuntu: `qt5-default`
+- CentOS/RHEL/Fedora: `qt5-qtbase-gui`
+- Suse: `libqt5-qtbase`
+
+MultiMC on linux is built with Qt 5.4 and older versions of Qt will not work.
+
+This should be a massive improvement to system integration on linux and resolves GH-1784, GH-2605, GH-1979, GH-2271, GH-1992, GH-1816 and their many duplicates.
+
+### New or changed features
+
+- GH-2487: No is now the default button when deleting instances.
+
+- It is now possible to launch with profilers in offline mode.
+
+- Massively improved support for icon formats when importing and exporting instances.
+
+ All of the formats MultiMC supports are now supported in exported instances too, instead of just PNG.
+
+- Added the pocket fox icon.
+
+ We still have the big one under the staircase. It's cute. Just hide your chickens.
+
+- Global settings can be opened from instance settings where appropriate.
+
+ Many people use the instance overrides where using the global settings would be more appropriate. Hopefully this makes it clearer that the instance settings are overrides for the global settings.
+
+- Added direct Fabric loader support.
+
+ Much overdue. It's good. Fabric mod metadata is also supported in the mod pages.
+
+- MultiMC now recognizes the new `experimental` Minecraft versions.
+
+ Go mess with the combat experiment. It's interesting.
+
+- Added Twitch URL as an option to the Add Instance dialog.
+
+ You can now drag the purple download buttons from CurseForge into MultiMC and get a modpack out of it. Much easier!
+
+### Bugfixes
+
+- Translation folder is now created sooner, making first launch translation fetch work again.
+
+- GH-2716: MultiMC will no longer try to censor values shorter than 4 characters in logs.
+
+ It was actually leaking information and destroying the logs instead of helping.
+
+- GH-2551: Trim server name and IP before saving them.
+
+- GH-2591: Fix multiple potential memory leaks and crashes related to destroying objects with Qt memory lifecycle model.
+
+- `run.sh` on linux now passes all arguments to MultiMC.
+
+- Adding a disabled mod duplicate now replaces the existing mod.
+
+- GH-2592: Newly created instances are now selected again. This was a very old regression.
+
+- GH-689: MultiMC no longer creates an imgur album for single screenshot uploads.
+
+- GH-1813: `#` is now saved properly when used in instance notes.
+
+- GH-2515: Deleting an instance externally while the delete dialog is open no longer leads to some other instance being deleted when you click OK.
+
+- GH-2499: Proxy settings are applied immediately and no longer need an application restart.
+
+- GH-1701: When downloading updates, the text now reflects the number of downloaded files better.
+
+- Icon scaling issues on macOS should now be fixed.
+
+## MultiMC 0.6.5
+
+Finalizing the translation workflow improvements and adding fixes for sounds missing in old game versions.
+
+### New or changed features
+
+- UI for the language settings has been unified across the application
+
+- GH-2209: Sounds in old (pre-1.6) versions should now work again
+
+ The launcher now downloads the correct assets and reconstructs the `resources` folder inside instances. This mirrors the same fix implemented in vanilla.
+
+ Also, a minor issue with the reconstruction being done twice per launch has been fixed.
+
+
+## MultiMC 0.6.4
+
+Update for a better translation workflow, and new FTB API location.
+
+### New or changed features
+
+- FTB API location has changed
+
+ MultiMC now uses the new location and should keep working.
+
+- Translations have been overhauled, again
+
+ It is now possible to put the translation source `.po` files into the `translations` folder and see changes in MultiMC immediately.
+
+ The new translation workflow is like this:
+ * Get a `.po` file from here the [translations repository](https://github.com/MultiMC/MultiMC5-translate).
+ * Alternatively, get the `template.pot` and start a new translation based on it.
+ * Put it in the `translations` folder.
+ * Edit it with [POEdit](https://poedit.net/).
+ * See the changes in real time.
+ * When done, post the changed files on discord, or github.
+
+ When using a `.po` file, MultiMC logs which strings are missing from the translation on the currently displayed UI screen(s), and which one are marked as fuzzy. This should make it easy to determine what's important.
+
+## MultiMC 0.6.3
+
+This is a release mostly aimed at getting all the small changes and fixes out of the door.
+
+### Potentially breaking changes
+
+- Local libraries are only loaded from inside the instances now.
+
+ Before, MultiMC allowed loading local libraries from the main `libraries` folder.
+ This in turn allowed existence of instances which could not be transported from one installation of MultiMC to another.
+
+ GH-2475: A bug that allowed the launch to continue with missing local libraries has also been fixed.
+
+ Effectively, you will get errors from launching such instances. You can fix the errors by copying the libraries to the locations indicated in the error log.
+
+### New or changed features
+
+- FTB import now has support for third party modpack codes.
+
+ Better late than never?
+
+- Instance creation can now be interrupted / aborted.
+
+- GH-2053: You can now inspect and change the `servers.dat` file from MultiMC.
+
+- MultiMC now uses the https protocol for many more network requests.
+
+- GH-2352: There is now a button to open the `.minecraft` folder inside the selected instance.
+
+- GH-2232: MultiMC can now use `.gif` icons (not animated).
+
+- GH-2101: Instance renaming is now done inline, in the actual instance list.
+
+- GH-2452: When deleting a group, MultiMC asks for confirmation.
+
+- GH-1552: PermGen is no longer shown when it's not appropriate (java 8 and up).
+
+- GH-2144: When changing versions of a component like Forge, the current version is marked with `(installed)`.
+
+- GH-2374: World list has been improved:
+
+ - Alternating line background colors have been added.
+ - The world game type is now shown in a column.
+
+- GH-2384: When installing a mod, existing mod with the same file name will be replaced.
+
+- The background cat sometimes wears a silly hat.
+
+### Bugfixes
+
+- GH-2252: Fixed odd drag and drop behaviour on Windows
+
+ Drag and drop of URLs from a browser locked up the browser. This needs further fixes on macOS.
+
+- Instance naming fixes:
+
+ - GH-2355: Whitespace prefix or suffix is no longer allowed.
+ - GH-2238: Newlines in instance names are no longer allowed either.
+
+- GH-2412: MultiMC no longer leaves behind zombie processes after launch on linux.
+
+- GH-2382: Version filter for the forge/liteloader version lists was not matching the whole version name.
+
+- GH-2488: More issues with broken relative URL redirection in Qt have been fixed.
+
+- Some memory leaks of downloaded data have been fixed.
+
+- MultiMC now handles instance groups and instance group saving better.
+
+ Long deleted groups no longer persist in the group list.
+
+- GH-2467: Broken (and nonsensical) sorting indicators have been removed from the versions page header.
+
+## MultiMC 0.6.2
+
+### New or changed features
- MultiMC now has FTB integration:
@@ -34,7 +401,7 @@
- MultiMC now probes the system for the name of the linux distribution as part of analytics. This will be used to focus future packaging efforts.
- Secret cheat code has been added... What does it do?
-## Bugfixes
+### Bugfixes
- VisualVM integration now works when VisualVM is bundled inside the MultiMC folder (uses a relative path).
- When reinstalling a component, or changing a component version, the custom version is now removed first.
@@ -47,8 +414,6 @@
- GH-2154: MultiMC now ignores the `hidden` flag of instance folders and they should show up correctly.
- When migrating Legacy instances, custom `minecraft.jar` will be preserved.
-# Previous releases
-
## MultiMC 0.6.1
### New or changed features
@@ -977,4 +1342,4 @@ Long time coming, this release brought a lot of incremental improvements and fix
- Added additional information to the about dialog.
## MultiMC 0.0
-- Initial release. \ No newline at end of file
+- Initial release.
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 23a65589..3660026b 100644
--- a/libraries/classparser/include/classparser.h
+++ b/libraries/classparser/include/classparser.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 db8f40a3..7bfae7cc 100644
--- a/libraries/classparser/include/classparser_config.h
+++ b/libraries/classparser/include/classparser_config.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 b5197d7c..8825ea39 100644
--- a/libraries/classparser/src/classparser.cpp
+++ b/libraries/classparser/src/classparser.cpp
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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 0a4bebcd..b6b0a574 100644
--- a/libraries/launcher/net/minecraft/Launcher.java
+++ b/libraries/launcher/net/minecraft/Launcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2018 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,6 +143,10 @@ public class Launcher extends Applet implements AppletStub
public URL getDocumentBase()
{
try {
+ // 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 2a07d20b..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-2018 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 d19ef7b2..d8cb6d1b 100644
--- a/libraries/launcher/org/multimc/Launcher.java
+++ b/libraries/launcher/org/multimc/Launcher.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2018 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 9842eb0e..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-2018 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 2c5da6de..ba12951d 100644
--- a/libraries/launcher/org/multimc/NotFoundException.java
+++ b/libraries/launcher/org/multimc/NotFoundException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2018 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 e198706b..2fde1329 100644
--- a/libraries/launcher/org/multimc/ParamBucket.java
+++ b/libraries/launcher/org/multimc/ParamBucket.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2018 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 1af01a5c..7ea44c1f 100644
--- a/libraries/launcher/org/multimc/ParseException.java
+++ b/libraries/launcher/org/multimc/ParseException.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2018 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 d6f254a1..fcf3edce 100644
--- a/libraries/launcher/org/multimc/Utils.java
+++ b/libraries/launcher/org/multimc/Utils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2018 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 48baba2e..ea445995 100644
--- a/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
+++ b/libraries/launcher/org/multimc/onesix/OneSixLauncher.java
@@ -1,4 +1,4 @@
-/* Copyright 2012-2018 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/libnbtplusplus b/libraries/libnbtplusplus
-Subproject 92f8d57227feb94643378ecf595626c60c0f59b
+Subproject dc72a20b7efd304d12af2025223fad07b4b7846
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/pack200/CMakeLists.txt b/libraries/pack200/CMakeLists.txt
deleted file mode 100644
index 31eb0f73..00000000
--- a/libraries/pack200/CMakeLists.txt
+++ /dev/null
@@ -1,50 +0,0 @@
-cmake_minimum_required(VERSION 3.1)
-
-project(MultiMC_unpack200)
-
-option(PACK200_BUILD_BINARY "Build a tiny utility that decompresses pack200 streams" OFF)
-
-# Find ZLIB for quazip
-find_package(ZLIB REQUIRED)
-
-set(PACK200_SRC
- include/unpack200.h
- src/bands.cpp
- src/bands.h
- src/bytes.cpp
- src/bytes.h
- src/coding.cpp
- src/coding.h
- src/constants.h
- src/defines.h
- src/unpack200.cpp
- src/unpack.cpp
- src/unpack.h
- src/utils.cpp
- src/utils.h
- src/zip.cpp
- src/zip.h
-)
-
-if (Qt5_POSITION_INDEPENDENT_CODE)
- SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
-endif()
-
-add_library(MultiMC_unpack200 SHARED ${PACK200_SRC})
-target_include_directories(MultiMC_unpack200 PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}" PRIVATE ${ZLIB_INCLUDE_DIRS} "${CMAKE_CURRENT_SOURCE_DIR}/src")
-target_link_libraries(MultiMC_unpack200 ${ZLIB_LIBRARIES})
-
-set_target_properties(MultiMC_unpack200 PROPERTIES CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN 1)
-generate_export_header(MultiMC_unpack200)
-
-# Install it
-install(
- TARGETS MultiMC_unpack200
- RUNTIME DESTINATION ${LIBRARY_DEST_DIR}
- LIBRARY DESTINATION ${LIBRARY_DEST_DIR}
-)
-
-if(PACK200_BUILD_BINARY)
- add_executable(anti200 anti200.cpp)
- target_link_libraries(anti200 MultiMC_unpack200)
-endif()
diff --git a/libraries/pack200/LICENSE b/libraries/pack200/LICENSE
deleted file mode 100644
index b40a0f45..00000000
--- a/libraries/pack200/LICENSE
+++ /dev/null
@@ -1,347 +0,0 @@
-The GNU General Public License (GPL)
-
-Version 2, June 1991
-
-Copyright (C) 1989, 1991 Free Software Foundation, Inc.
-59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-Everyone is permitted to copy and distribute verbatim copies of this license
-document, but changing it is not allowed.
-
-Preamble
-
-The licenses for most software are designed to take away your freedom to share
-and change it. By contrast, the GNU General Public License is intended to
-guarantee your freedom to share and change free software--to make sure the
-software is free for all its users. This General Public License applies to
-most of the Free Software Foundation's software and to any other program whose
-authors commit to using it. (Some other Free Software Foundation software is
-covered by the GNU Library General Public License instead.) You can apply it to
-your programs, too.
-
-When we speak of free software, we are referring to freedom, not price. Our
-General Public Licenses are designed to make sure that you have the freedom to
-distribute copies of free software (and charge for this service if you wish),
-that you receive source code or can get it if you want it, that you can change
-the software or use pieces of it in new free programs; and that you know you
-can do these things.
-
-To protect your rights, we need to make restrictions that forbid anyone to deny
-you these rights or to ask you to surrender the rights. These restrictions
-translate to certain responsibilities for you if you distribute copies of the
-software, or if you modify it.
-
-For example, if you distribute copies of such a program, whether gratis or for
-a fee, you must give the recipients all the rights that you have. You must
-make sure that they, too, receive or can get the source code. And you must
-show them these terms so they know their rights.
-
-We protect your rights with two steps: (1) copyright the software, and (2)
-offer you this license which gives you legal permission to copy, distribute
-and/or modify the software.
-
-Also, for each author's protection and ours, we want to make certain that
-everyone understands that there is no warranty for this free software. If the
-software is modified by someone else and passed on, we want its recipients to
-know that what they have is not the original, so that any problems introduced
-by others will not reflect on the original authors' reputations.
-
-Finally, any free program is threatened constantly by software patents. We
-wish to avoid the danger that redistributors of a free program will
-individually obtain patent licenses, in effect making the program proprietary.
-To prevent this, we have made it clear that any patent must be licensed for
-everyone's free use or not licensed at all.
-
-The precise terms and conditions for copying, distribution and modification
-follow.
-
-TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
-0. This License applies to any program or other work which contains a notice
-placed by the copyright holder saying it may be distributed under the terms of
-this General Public License. The "Program", below, refers to any such program
-or work, and a "work based on the Program" means either the Program or any
-derivative work under copyright law: that is to say, a work containing the
-Program or a portion of it, either verbatim or with modifications and/or
-translated into another language. (Hereinafter, translation is included
-without limitation in the term "modification".) Each licensee is addressed as
-"you".
-
-Activities other than copying, distribution and modification are not covered by
-this License; they are outside its scope. The act of running the Program is
-not restricted, and the output from the Program is covered only if its contents
-constitute a work based on the Program (independent of having been made by
-running the Program). Whether that is true depends on what the Program does.
-
-1. You may copy and distribute verbatim copies of the Program's source code as
-you receive it, in any medium, provided that you conspicuously and
-appropriately publish on each copy an appropriate copyright notice and
-disclaimer of warranty; keep intact all the notices that refer to this License
-and to the absence of any warranty; and give any other recipients of the
-Program a copy of this License along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and you may
-at your option offer warranty protection in exchange for a fee.
-
-2. You may modify your copy or copies of the Program or any portion of it, thus
-forming a work based on the Program, and copy and distribute such modifications
-or work under the terms of Section 1 above, provided that you also meet all of
-these conditions:
-
- a) You must cause the modified files to carry prominent notices stating
- that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in whole or
- in part contains or is derived from the Program or any part thereof, to be
- licensed as a whole at no charge to all third parties under the terms of
- this License.
-
- c) If the modified program normally reads commands interactively when run,
- you must cause it, when started running for such interactive use in the
- most ordinary way, to print or display an announcement including an
- appropriate copyright notice and a notice that there is no warranty (or
- else, saying that you provide a warranty) and that users may redistribute
- the program under these conditions, and telling the user how to view a copy
- of this License. (Exception: if the Program itself is interactive but does
- not normally print such an announcement, your work based on the Program is
- not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If identifiable
-sections of that work are not derived from the Program, and can be reasonably
-considered independent and separate works in themselves, then this License, and
-its terms, do not apply to those sections when you distribute them as separate
-works. But when you distribute the same sections as part of a whole which is a
-work based on the Program, the distribution of the whole must be on the terms
-of this License, whose permissions for other licensees extend to the entire
-whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest your
-rights to work written entirely by you; rather, the intent is to exercise the
-right to control the distribution of derivative or collective works based on
-the Program.
-
-In addition, mere aggregation of another work not based on the Program with the
-Program (or with a work based on the Program) on a volume of a storage or
-distribution medium does not bring the other work under the scope of this
-License.
-
-3. You may copy and distribute the Program (or a work based on it, under
-Section 2) in object code or executable form under the terms of Sections 1 and
-2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable source
- code, which must be distributed under the terms of Sections 1 and 2 above
- on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three years, to
- give any third party, for a charge no more than your cost of physically
- performing source distribution, a complete machine-readable copy of the
- corresponding source code, to be distributed under the terms of Sections 1
- and 2 above on a medium customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer to
- distribute corresponding source code. (This alternative is allowed only
- for noncommercial distribution and only if you received the program in
- object code or executable form with such an offer, in accord with
- Subsection b above.)
-
-The source code for a work means the preferred form of the work for making
-modifications to it. For an executable work, complete source code means all
-the source code for all modules it contains, plus any associated interface
-definition files, plus the scripts used to control compilation and installation
-of the executable. However, as a special exception, the source code
-distributed need not include anything that is normally distributed (in either
-source or binary form) with the major components (compiler, kernel, and so on)
-of the operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the source
-code from the same place counts as distribution of the source code, even though
-third parties are not compelled to copy the source along with the object code.
-
-4. You may not copy, modify, sublicense, or distribute the Program except as
-expressly provided under this License. Any attempt otherwise to copy, modify,
-sublicense or distribute the Program is void, and will automatically terminate
-your rights under this License. However, parties who have received copies, or
-rights, from you under this License will not have their licenses terminated so
-long as such parties remain in full compliance.
-
-5. You are not required to accept this License, since you have not signed it.
-However, nothing else grants you permission to modify or distribute the Program
-or its derivative works. These actions are prohibited by law if you do not
-accept this License. Therefore, by modifying or distributing the Program (or
-any work based on the Program), you indicate your acceptance of this License to
-do so, and all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
-6. Each time you redistribute the Program (or any work based on the Program),
-the recipient automatically receives a license from the original licensor to
-copy, distribute or modify the Program subject to these terms and conditions.
-You may not impose any further restrictions on the recipients' exercise of the
-rights granted herein. You are not responsible for enforcing compliance by
-third parties to this License.
-
-7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues), conditions
-are imposed on you (whether by court order, agreement or otherwise) that
-contradict the conditions of this License, they do not excuse you from the
-conditions of this License. If you cannot distribute so as to satisfy
-simultaneously your obligations under this License and any other pertinent
-obligations, then as a consequence you may not distribute the Program at all.
-For example, if a patent license would not permit royalty-free redistribution
-of the Program by all those who receive copies directly or indirectly through
-you, then the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply and
-the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any patents or
-other property right claims or to contest validity of any such claims; this
-section has the sole purpose of protecting the integrity of the free software
-distribution system, which is implemented by public license practices. Many
-people have made generous contributions to the wide range of software
-distributed through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing to
-distribute software through any other system and a licensee cannot impose that
-choice.
-
-This section is intended to make thoroughly clear what is believed to be a
-consequence of the rest of this License.
-
-8. If the distribution and/or use of the Program is restricted in certain
-countries either by patents or by copyrighted interfaces, the original
-copyright holder who places the Program under this License may add an explicit
-geographical distribution limitation excluding those countries, so that
-distribution is permitted only in or among countries not thus excluded. In
-such case, this License incorporates the limitation as if written in the body
-of this License.
-
-9. The Free Software Foundation may publish revised and/or new versions of the
-General Public License from time to time. Such new versions will be similar in
-spirit to the present version, but may differ in detail to address new problems
-or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any later
-version", you have the option of following the terms and conditions either of
-that version or of any later version published by the Free Software Foundation.
-If the Program does not specify a version number of this License, you may
-choose any version ever published by the Free Software Foundation.
-
-10. If you wish to incorporate parts of the Program into other free programs
-whose distribution conditions are different, write to the author to ask for
-permission. For software which is copyrighted by the Free Software Foundation,
-write to the Free Software Foundation; we sometimes make exceptions for this.
-Our decision will be guided by the two goals of preserving the free status of
-all derivatives of our free software and of promoting the sharing and reuse of
-software generally.
-
-NO WARRANTY
-
-11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
-THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
-STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
-PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
-INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
-FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
-PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
-YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
-12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
-ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
-PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
-GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
-INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA
-BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER
-OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
-
-END OF TERMS AND CONDITIONS
-
-How to Apply These Terms to Your New Programs
-
-If you develop a new program, and you want it to be of the greatest possible
-use to the public, the best way to achieve this is to make it free software
-which everyone can redistribute and change under these terms.
-
-To do so, attach the following notices to the program. It is safest to attach
-them to the start of each source file to most effectively convey the exclusion
-of warranty; and each file should have at least the "copyright" line and a
-pointer to where the full notice is found.
-
- One line to give the program's name and a brief idea of what it does.
-
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify it
- under the terms of the GNU General Public License as published by the Free
- Software Foundation; either version 2 of the License, or (at your option)
- any later version.
-
- This program is distributed in the hope that it will be useful, but WITHOUT
- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc., 59
- Temple Place, Suite 330, Boston, MA 02111-1307 USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this when it
-starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author Gnomovision comes
- with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free
- software, and you are welcome to redistribute it under certain conditions;
- type 'show c' for details.
-
-The hypothetical commands 'show w' and 'show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may be
-called something other than 'show w' and 'show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your school,
-if any, to sign a "copyright disclaimer" for the program, if necessary. Here
-is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- 'Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- signature of Ty Coon, 1 April 1989
-
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General Public
-License instead of this License.
-
-
-"CLASSPATH" EXCEPTION TO THE GPL
-
-Certain source files distributed by Oracle America and/or its affiliates are
-subject to the following clarification and special exception to the GPL, but
-only where Oracle has expressly included in the particular source file's header
-the words "Oracle designates this particular file as subject to the "Classpath"
-exception as provided by Oracle in the LICENSE file that accompanied this code."
-
- Linking this library statically or dynamically with other modules is making
- a combined work based on this library. Thus, the terms and conditions of
- the GNU General Public License cover the whole combination.
-
- As a special exception, the copyright holders of this library give you
- permission to link this library with independent modules to produce an
- executable, regardless of the license terms of these independent modules,
- and to copy and distribute the resulting executable under terms of your
- choice, provided that you also meet, for each linked independent module,
- the terms and conditions of the license of that module. An independent
- module is a module which is not derived from or based on this library. If
- you modify this library, you may extend this exception to your version of
- the library, but you are not obligated to do so. If you do not wish to do
- so, delete this exception statement from your version.
diff --git a/libraries/pack200/anti200.cpp b/libraries/pack200/anti200.cpp
deleted file mode 100644
index 1e672847..00000000
--- a/libraries/pack200/anti200.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * This is trivial. Do what thou wilt with it. Public domain.
- */
-
-#include <stdexcept>
-#include <iostream>
-#include "unpack200.h"
-
-int main(int argc, char **argv)
-{
- if (argc != 3)
- {
- std::cerr << "Simple pack200 unpacker!" << std::endl << "Run like this:" << std::endl
- << " " << argv[0] << " input.jar.lzma output.jar" << std::endl;
- return EXIT_FAILURE;
- }
-
- FILE *input = fopen(argv[1], "rb");
- if (!input)
- {
- std::cerr << "Can't open input file";
- return EXIT_FAILURE;
- }
- FILE *output = fopen(argv[2], "wb");
- if (!output)
- {
- fclose(input);
- std::cerr << "Can't open output file";
- return EXIT_FAILURE;
- }
- try
- {
- unpack_200(input, output);
- }
- catch (const std::runtime_error &e)
- {
- std::cerr << "Bad things happened: " << e.what() << std::endl;
- fclose(input);
- fclose(output);
- return EXIT_FAILURE;
- }
- return EXIT_SUCCESS;
-}
diff --git a/libraries/pack200/include/unpack200.h b/libraries/pack200/include/unpack200.h
deleted file mode 100644
index 30ce6656..00000000
--- a/libraries/pack200/include/unpack200.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-#pragma once
-
-#include "multimc_unpack200_export.h"
-
-/**
- * @brief Unpack a PACK200 file
- *
- * @param input_path Path to the input file in PACK200 format. System native string encoding.
- * @param output_path Path to the output file in PACK200 format. System native string encoding.
- * @throw std::runtime_error for any error encountered
- */
-MULTIMC_UNPACK200_EXPORT void unpack_200(FILE * input_path, FILE * output_path);
diff --git a/libraries/pack200/src/bands.cpp b/libraries/pack200/src/bands.cpp
deleted file mode 100644
index e82613b5..00000000
--- a/libraries/pack200/src/bands.cpp
+++ /dev/null
@@ -1,423 +0,0 @@
-/*
- * Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-// -*- C++ -*-
-// Small program for unpacking specially compressed Java packages.
-// John R. Rose
-
-#include <sys/types.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <stdint.h>
-
-#include "defines.h"
-#include "bytes.h"
-#include "utils.h"
-#include "coding.h"
-#include "bands.h"
-
-#include "constants.h"
-#include "unpack.h"
-
-void band::readData(int expectedLength)
-{
- assert(expectedLength >= 0);
- assert(vs[0].cmk == cmk_ERROR);
- if (expectedLength != 0)
- {
- assert(length == 0);
- length = expectedLength;
- }
- if (length == 0)
- {
- assert((rplimit = cm.vs0.rp = u->rp) != nullptr);
- return;
- }
- assert(length > 0);
-
- bool is_BYTE1 = (defc->spec == BYTE1_spec);
-
- if (is_BYTE1)
- {
- // No possibility of coding change. Sizing is exact.
- u->ensure_input(length);
- }
- else
- {
- // Make a conservatively generous estimate of band size in bytes.
- // Assume B == 5 everywhere.
- // Assume awkward pop with all {U} values (2*5 per value)
- int64_t generous = (int64_t)length * (B_MAX * 3 + 1) + C_SLOP;
- u->ensure_input(generous);
- }
-
- // Read one value to see what it might be.
- int XB = _meta_default;
- if (!is_BYTE1)
- {
- // must be a variable-length coding
- assert(defc->B() > 1 && defc->L() > 0);
-
- value_stream xvs;
- coding *valc = defc;
- if (valc->D() != 0)
- {
- valc = coding::findBySpec(defc->B(), defc->H(), defc->S());
- assert(!valc->isMalloc);
- }
- xvs.init(u->rp, u->rplimit, valc);
- int X = xvs.getInt();
- if (valc->S() != 0)
- {
- assert(valc->min <= -256);
- XB = -1 - X;
- }
- else
- {
- int L = valc->L();
- assert(valc->max >= L + 255);
- XB = X - L;
- }
- if (0 <= XB && XB < 256)
- {
- // Skip over the escape value.
- u->rp = xvs.rp;
- }
- else
- {
- // No, it's still default.
- XB = _meta_default;
- }
- }
-
- if (XB <= _meta_canon_max)
- {
- byte XB_byte = (byte)XB;
- byte *XB_ptr = &XB_byte;
- cm.init(u->rp, u->rplimit, XB_ptr, 0, defc, length, nullptr);
- }
- else
- {
- assert(u->meta_rp != nullptr);
- // Scribble the initial byte onto the band.
- byte *save_meta_rp = --u->meta_rp;
- byte save_meta_xb = (*save_meta_rp);
- (*save_meta_rp) = (byte)XB;
- cm.init(u->rp, u->rplimit, u->meta_rp, 0, defc, length, nullptr);
- (*save_meta_rp) = save_meta_xb; // put it back, just to be tidy
- }
- rplimit = u->rp;
-
- rewind();
-}
-
-void band::setIndex(cpindex *ix_)
-{
- assert(ix_ == nullptr || ixTag == ix_->ixTag);
- ix = ix_;
-}
-void band::setIndexByTag(byte tag)
-{
- setIndex(u->cp.getIndex(tag));
-}
-
-entry *band::getRefCommon(cpindex *ix_, bool nullOKwithCaller)
-{
- assert(ix_->ixTag == ixTag ||
- (ixTag == CONSTANT_Literal && ix_->ixTag >= CONSTANT_Integer &&
- ix_->ixTag <= CONSTANT_String));
- int n = vs[0].getInt() - nullOK;
- // Note: band-local nullOK means nullptr encodes as 0.
- // But nullOKwithCaller means caller is willing to tolerate a nullptr.
- entry *ref = ix_->get(n);
- if (ref == nullptr && !(nullOKwithCaller && n == -1))
- unpack_abort(n == -1 ? "nullptr ref" : "bad ref");
- return ref;
-}
-
-int64_t band::getLong(band &lo_band, bool have_hi)
-{
- band &hi_band = (*this);
- assert(lo_band.bn == hi_band.bn + 1);
- uint32_t lo = lo_band.getInt();
- if (!have_hi)
- {
- assert(hi_band.length == 0);
- return makeLong(0, lo);
- }
- uint32_t hi = hi_band.getInt();
- return makeLong(hi, lo);
-}
-
-int band::getIntTotal()
-{
- if (length == 0)
- return 0;
- if (total_memo > 0)
- return total_memo - 1;
- int total = getInt();
- // overflow checks require that none of the addends are <0,
- // and that the partial sums never overflow (wrap negative)
- if (total < 0)
- {
- unpack_abort("overflow detected");
- }
- for (int k = length - 1; k > 0; k--)
- {
- int prev_total = total;
- total += vs[0].getInt();
- if (total < prev_total)
- {
- unpack_abort("overflow detected");
- }
- }
- rewind();
- total_memo = total + 1;
- return total;
-}
-
-int band::getIntCount(int tag)
-{
- if (length == 0)
- return 0;
- if (tag >= HIST0_MIN && tag <= HIST0_MAX)
- {
- if (hist0 == nullptr)
- {
- // Lazily calculate an approximate histogram.
- hist0 = U_NEW(int, (HIST0_MAX - HIST0_MIN) + 1);
- for (int k = length; k > 0; k--)
- {
- int x = vs[0].getInt();
- if (x >= HIST0_MIN && x <= HIST0_MAX)
- hist0[x - HIST0_MIN] += 1;
- }
- rewind();
- }
- return hist0[tag - HIST0_MIN];
- }
- int total = 0;
- for (int k = length; k > 0; k--)
- {
- total += (vs[0].getInt() == tag) ? 1 : 0;
- }
- rewind();
- return total;
-}
-
-#define INDEX_INIT(tag, nullOK, subindex) ((tag) + (subindex) * SUBINDEX_BIT + (nullOK) * 256)
-
-#define INDEX(tag) INDEX_INIT(tag, 0, 0)
-#define NULL_OR_INDEX(tag) INDEX_INIT(tag, 1, 0)
-#define SUB_INDEX(tag) INDEX_INIT(tag, 0, 1)
-#define NO_INDEX 0
-
-struct band_init
-{
- int defc;
- int index;
-};
-
-#define BAND_INIT(name, cspec, ix) \
- { \
- cspec, ix \
- }
-
-const band_init all_band_inits[] =
- {
- // BAND_INIT(archive_magic, BYTE1_spec, 0),
- // BAND_INIT(archive_header, UNSIGNED5_spec, 0),
- // BAND_INIT(band_headers, BYTE1_spec, 0),
- BAND_INIT(cp_Utf8_prefix, DELTA5_spec, 0), BAND_INIT(cp_Utf8_suffix, UNSIGNED5_spec, 0),
- BAND_INIT(cp_Utf8_chars, CHAR3_spec, 0), BAND_INIT(cp_Utf8_big_suffix, DELTA5_spec, 0),
- BAND_INIT(cp_Utf8_big_chars, DELTA5_spec, 0), BAND_INIT(cp_Int, UDELTA5_spec, 0),
- BAND_INIT(cp_Float, UDELTA5_spec, 0), BAND_INIT(cp_Long_hi, UDELTA5_spec, 0),
- BAND_INIT(cp_Long_lo, DELTA5_spec, 0), BAND_INIT(cp_Double_hi, UDELTA5_spec, 0),
- BAND_INIT(cp_Double_lo, DELTA5_spec, 0),
- BAND_INIT(cp_String, UDELTA5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(cp_Class, UDELTA5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(cp_Signature_form, DELTA5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(cp_Signature_classes, UDELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(cp_Descr_name, DELTA5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(cp_Descr_type, UDELTA5_spec, INDEX(CONSTANT_Signature)),
- BAND_INIT(cp_Field_class, DELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(cp_Field_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)),
- BAND_INIT(cp_Method_class, DELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(cp_Method_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)),
- BAND_INIT(cp_Imethod_class, DELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(cp_Imethod_desc, UDELTA5_spec, INDEX(CONSTANT_NameandType)),
- BAND_INIT(attr_definition_headers, BYTE1_spec, 0),
- BAND_INIT(attr_definition_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(attr_definition_layout, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(ic_this_class, UDELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(ic_flags, UNSIGNED5_spec, 0),
- BAND_INIT(ic_outer_class, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Class)),
- BAND_INIT(ic_name, DELTA5_spec, NULL_OR_INDEX(CONSTANT_Utf8)),
- BAND_INIT(class_this, DELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(class_super, DELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(class_interface_count, DELTA5_spec, 0),
- BAND_INIT(class_interface, DELTA5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(class_field_count, DELTA5_spec, 0),
- BAND_INIT(class_method_count, DELTA5_spec, 0),
- BAND_INIT(field_descr, DELTA5_spec, INDEX(CONSTANT_NameandType)),
- BAND_INIT(field_flags_hi, UNSIGNED5_spec, 0),
- BAND_INIT(field_flags_lo, UNSIGNED5_spec, 0),
- BAND_INIT(field_attr_count, UNSIGNED5_spec, 0),
- BAND_INIT(field_attr_indexes, UNSIGNED5_spec, 0),
- BAND_INIT(field_attr_calls, UNSIGNED5_spec, 0),
- BAND_INIT(field_ConstantValue_KQ, UNSIGNED5_spec, INDEX(CONSTANT_Literal)),
- BAND_INIT(field_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)),
- BAND_INIT(field_metadata_bands, -1, -1), BAND_INIT(field_attr_bands, -1, -1),
- BAND_INIT(method_descr, MDELTA5_spec, INDEX(CONSTANT_NameandType)),
- BAND_INIT(method_flags_hi, UNSIGNED5_spec, 0),
- BAND_INIT(method_flags_lo, UNSIGNED5_spec, 0),
- BAND_INIT(method_attr_count, UNSIGNED5_spec, 0),
- BAND_INIT(method_attr_indexes, UNSIGNED5_spec, 0),
- BAND_INIT(method_attr_calls, UNSIGNED5_spec, 0),
- BAND_INIT(method_Exceptions_N, UNSIGNED5_spec, 0),
- BAND_INIT(method_Exceptions_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(method_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)),
- BAND_INIT(method_metadata_bands, -1, -1), BAND_INIT(method_attr_bands, -1, -1),
- BAND_INIT(class_flags_hi, UNSIGNED5_spec, 0),
- BAND_INIT(class_flags_lo, UNSIGNED5_spec, 0),
- BAND_INIT(class_attr_count, UNSIGNED5_spec, 0),
- BAND_INIT(class_attr_indexes, UNSIGNED5_spec, 0),
- BAND_INIT(class_attr_calls, UNSIGNED5_spec, 0),
- BAND_INIT(class_SourceFile_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)),
- BAND_INIT(class_EnclosingMethod_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(class_EnclosingMethod_RDN, UNSIGNED5_spec,
- NULL_OR_INDEX(CONSTANT_NameandType)),
- BAND_INIT(class_Signature_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)),
- BAND_INIT(class_metadata_bands, -1, -1),
- BAND_INIT(class_InnerClasses_N, UNSIGNED5_spec, 0),
- BAND_INIT(class_InnerClasses_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(class_InnerClasses_F, UNSIGNED5_spec, 0),
- BAND_INIT(class_InnerClasses_outer_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)),
- BAND_INIT(class_InnerClasses_name_RUN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Utf8)),
- BAND_INIT(class_ClassFile_version_minor_H, UNSIGNED5_spec, 0),
- BAND_INIT(class_ClassFile_version_major_H, UNSIGNED5_spec, 0),
- BAND_INIT(class_attr_bands, -1, -1), BAND_INIT(code_headers, BYTE1_spec, 0),
- BAND_INIT(code_max_stack, UNSIGNED5_spec, 0),
- BAND_INIT(code_max_na_locals, UNSIGNED5_spec, 0),
- BAND_INIT(code_handler_count, UNSIGNED5_spec, 0),
- BAND_INIT(code_handler_start_P, BCI5_spec, 0),
- BAND_INIT(code_handler_end_PO, BRANCH5_spec, 0),
- BAND_INIT(code_handler_catch_PO, BRANCH5_spec, 0),
- BAND_INIT(code_handler_class_RCN, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)),
- BAND_INIT(code_flags_hi, UNSIGNED5_spec, 0),
- BAND_INIT(code_flags_lo, UNSIGNED5_spec, 0),
- BAND_INIT(code_attr_count, UNSIGNED5_spec, 0),
- BAND_INIT(code_attr_indexes, UNSIGNED5_spec, 0),
- BAND_INIT(code_attr_calls, UNSIGNED5_spec, 0),
- BAND_INIT(code_StackMapTable_N, UNSIGNED5_spec, 0),
- BAND_INIT(code_StackMapTable_frame_T, BYTE1_spec, 0),
- BAND_INIT(code_StackMapTable_local_N, UNSIGNED5_spec, 0),
- BAND_INIT(code_StackMapTable_stack_N, UNSIGNED5_spec, 0),
- BAND_INIT(code_StackMapTable_offset, UNSIGNED5_spec, 0),
- BAND_INIT(code_StackMapTable_T, BYTE1_spec, 0),
- BAND_INIT(code_StackMapTable_RC, UNSIGNED5_spec, INDEX(CONSTANT_Class)),
- BAND_INIT(code_StackMapTable_P, BCI5_spec, 0),
- BAND_INIT(code_LineNumberTable_N, UNSIGNED5_spec, 0),
- BAND_INIT(code_LineNumberTable_bci_P, BCI5_spec, 0),
- BAND_INIT(code_LineNumberTable_line, UNSIGNED5_spec, 0),
- BAND_INIT(code_LocalVariableTable_N, UNSIGNED5_spec, 0),
- BAND_INIT(code_LocalVariableTable_bci_P, BCI5_spec, 0),
- BAND_INIT(code_LocalVariableTable_span_O, BRANCH5_spec, 0),
- BAND_INIT(code_LocalVariableTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(code_LocalVariableTable_type_RS, UNSIGNED5_spec, INDEX(CONSTANT_Signature)),
- BAND_INIT(code_LocalVariableTable_slot, UNSIGNED5_spec, 0),
- BAND_INIT(code_LocalVariableTypeTable_N, UNSIGNED5_spec, 0),
- BAND_INIT(code_LocalVariableTypeTable_bci_P, BCI5_spec, 0),
- BAND_INIT(code_LocalVariableTypeTable_span_O, BRANCH5_spec, 0),
- BAND_INIT(code_LocalVariableTypeTable_name_RU, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(code_LocalVariableTypeTable_type_RS, UNSIGNED5_spec,
- INDEX(CONSTANT_Signature)),
- BAND_INIT(code_LocalVariableTypeTable_slot, UNSIGNED5_spec, 0),
- BAND_INIT(code_attr_bands, -1, -1), BAND_INIT(bc_codes, BYTE1_spec, 0),
- BAND_INIT(bc_case_count, UNSIGNED5_spec, 0), BAND_INIT(bc_case_value, DELTA5_spec, 0),
- BAND_INIT(bc_byte, BYTE1_spec, 0), BAND_INIT(bc_short, DELTA5_spec, 0),
- BAND_INIT(bc_local, UNSIGNED5_spec, 0), BAND_INIT(bc_label, BRANCH5_spec, 0),
- BAND_INIT(bc_intref, DELTA5_spec, INDEX(CONSTANT_Integer)),
- BAND_INIT(bc_floatref, DELTA5_spec, INDEX(CONSTANT_Float)),
- BAND_INIT(bc_longref, DELTA5_spec, INDEX(CONSTANT_Long)),
- BAND_INIT(bc_doubleref, DELTA5_spec, INDEX(CONSTANT_Double)),
- BAND_INIT(bc_stringref, DELTA5_spec, INDEX(CONSTANT_String)),
- BAND_INIT(bc_classref, UNSIGNED5_spec, NULL_OR_INDEX(CONSTANT_Class)),
- BAND_INIT(bc_fieldref, DELTA5_spec, INDEX(CONSTANT_Fieldref)),
- BAND_INIT(bc_methodref, UNSIGNED5_spec, INDEX(CONSTANT_Methodref)),
- BAND_INIT(bc_imethodref, DELTA5_spec, INDEX(CONSTANT_InterfaceMethodref)),
- BAND_INIT(bc_thisfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)),
- BAND_INIT(bc_superfield, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Fieldref)),
- BAND_INIT(bc_thismethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)),
- BAND_INIT(bc_supermethod, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)),
- BAND_INIT(bc_initref, UNSIGNED5_spec, SUB_INDEX(CONSTANT_Methodref)),
- BAND_INIT(bc_escref, UNSIGNED5_spec, INDEX(CONSTANT_All)),
- BAND_INIT(bc_escrefsize, UNSIGNED5_spec, 0), BAND_INIT(bc_escsize, UNSIGNED5_spec, 0),
- BAND_INIT(bc_escbyte, BYTE1_spec, 0),
- BAND_INIT(file_name, UNSIGNED5_spec, INDEX(CONSTANT_Utf8)),
- BAND_INIT(file_size_hi, UNSIGNED5_spec, 0), BAND_INIT(file_size_lo, UNSIGNED5_spec, 0),
- BAND_INIT(file_modtime, DELTA5_spec, 0), BAND_INIT(file_options, UNSIGNED5_spec, 0),
- // BAND_INIT(file_bits, BYTE1_spec, 0),
- {0, 0}};
-
-band *band::makeBands(unpacker *u)
-{
- band *tmp_all_bands = U_NEW(band, BAND_LIMIT);
- for (int i = 0; i < BAND_LIMIT; i++)
- {
- assert((byte *)&all_band_inits[i + 1] <
- (byte *)all_band_inits + sizeof(all_band_inits));
- const band_init &bi = all_band_inits[i];
- band &b = tmp_all_bands[i];
- coding *defc = coding::findBySpec(bi.defc);
- assert((defc == nullptr) == (bi.defc == -1)); // no garbage, please
- assert(defc == nullptr || !defc->isMalloc);
- b.init(u, i, defc);
- if (bi.index > 0)
- {
- b.nullOK = ((bi.index >> 8) & 1);
- b.ixTag = (bi.index & 0xFF);
- }
- }
- return tmp_all_bands;
-}
-
-void band::initIndexes(unpacker *u)
-{
- band *tmp_all_bands = u->all_bands;
- for (int i = 0; i < BAND_LIMIT; i++)
- {
- band *scan = &tmp_all_bands[i];
- uint32_t tag = scan->ixTag; // Cf. #define INDEX(tag) above
- if (tag != 0 && tag != CONSTANT_Literal && (tag & SUBINDEX_BIT) == 0)
- {
- scan->setIndex(u->cp.getIndex(tag));
- }
- }
-}
diff --git a/libraries/pack200/src/bands.h b/libraries/pack200/src/bands.h
deleted file mode 100644
index 66c5aec4..00000000
--- a/libraries/pack200/src/bands.h
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * Copyright (c) 2002, 2005, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-// -*- C++ -*-
-struct entry;
-struct cpindex;
-struct unpacker;
-
-struct band
-{
- int bn; // band_number of this band
- coding *defc; // default coding method
- cpindex *ix; // CP entry mapping, if CPRefBand
- byte ixTag; // 0 or 1; nullptr is coded as (nullOK?0:-1)
- byte nullOK; // 0 or 1; nullptr is coded as (nullOK?0:-1)
- int length; // expected # values
- unpacker *u; // back pointer
-
- value_stream vs[2]; // source of values
- coding_method cm; // method used for initial state of vs[0]
- byte *rplimit; // end of band (encoded, transmitted)
-
- int total_memo; // cached value of getIntTotal, or -1
- int *hist0; // approximate. histogram
- enum
- {
- HIST0_MIN = 0,
- HIST0_MAX = 255
- }; // catches the usual cases
-
- // properties for attribute layout elements:
- byte le_kind; // EK_XXX
- byte le_bci; // 0,EK_BCI,EK_BCD,EK_BCO
- byte le_back; // ==EF_BACK
- byte le_len; // 0,1,2,4 (size in classfile), or call addr
- band **le_body; // body of repl, union, call (nullptr-terminated)
-// Note: EK_CASE elements use hist0 to record union tags.
-#define le_casetags hist0
-
- band &nextBand()
- {
- return this[1];
- }
- band &prevBand()
- {
- return this[-1];
- }
-
- void init(unpacker *u_, int bn_, coding *defc_)
- {
- u = u_;
- cm.u = u_;
- bn = bn_;
- defc = defc_;
- }
- void init(unpacker *u_, int bn_, int defcSpec)
- {
- init(u_, bn_, coding::findBySpec(defcSpec));
- }
- void initRef(int ixTag_ = 0, bool nullOK_ = false)
- {
- ixTag = ixTag_;
- nullOK = nullOK_;
- setIndexByTag(ixTag);
- }
-
- void expectMoreLength(int l)
- {
- assert(length >= 0); // able to accept a length
- assert((int)l >= 0); // no overflow
- assert(rplimit == nullptr); // readData not yet called
- length += l;
- assert(length >= l); // no overflow
- }
-
- void setIndex(cpindex *ix_);
- void setIndexByTag(byte tag);
-
- // Parse the band and its meta-coding header.
- void readData(int expectedLength = 0);
-
- // Reset the band for another pass (Cf. Java Band.resetForSecondPass.)
- void rewind()
- {
- cm.reset(&vs[0]);
- }
-
- byte *&curRP()
- {
- return vs[0].rp;
- }
- byte *minRP()
- {
- return cm.vs0.rp;
- }
- byte *maxRP()
- {
- return rplimit;
- }
- size_t size()
- {
- return maxRP() - minRP();
- }
-
- int getByte()
- {
- assert(ix == nullptr);
- return vs[0].getByte();
- }
- int getInt()
- {
- assert(ix == nullptr);
- return vs[0].getInt();
- }
- entry *getRefN()
- {
- assert(ix != nullptr);
- return getRefCommon(ix, true);
- }
- entry *getRef()
- {
- assert(ix != nullptr);
- return getRefCommon(ix, false);
- }
- entry *getRefUsing(cpindex *ix2)
- {
- assert(ix == nullptr);
- return getRefCommon(ix2, true);
- }
- entry *getRefCommon(cpindex *ix, bool nullOK);
- int64_t getLong(band &lo_band, bool have_hi);
-
- static int64_t makeLong(uint32_t hi, uint32_t lo)
- {
- return ((uint64_t)hi << 32) + (((uint64_t)lo << 32) >> 32);
- }
-
- int getIntTotal();
- int getIntCount(int tag);
-
- static band *makeBands(unpacker *u);
- static void initIndexes(unpacker *u);
-};
-
-extern band all_bands[];
-
-#define BAND_LOCAL /* \
- band* band_temp = all_bands; \
- band* all_bands = band_temp */
-
-// Band schema:
-enum band_number
-{
- // e_archive_magic,
- // e_archive_header,
- // e_band_headers,
-
- // constant pool contents
- e_cp_Utf8_prefix,
- e_cp_Utf8_suffix,
- e_cp_Utf8_chars,
- e_cp_Utf8_big_suffix,
- e_cp_Utf8_big_chars,
- e_cp_Int,
- e_cp_Float,
- e_cp_Long_hi,
- e_cp_Long_lo,
- e_cp_Double_hi,
- e_cp_Double_lo,
- e_cp_String,
- e_cp_Class,
- e_cp_Signature_form,
- e_cp_Signature_classes,
- e_cp_Descr_name,
- e_cp_Descr_type,
- e_cp_Field_class,
- e_cp_Field_desc,
- e_cp_Method_class,
- e_cp_Method_desc,
- e_cp_Imethod_class,
- e_cp_Imethod_desc,
-
- // bands which define transmission of attributes
- e_attr_definition_headers,
- e_attr_definition_name,
- e_attr_definition_layout,
-
- // band for hardwired InnerClasses attribute (shared across the package)
- e_ic_this_class,
- e_ic_flags,
- // These bands contain data only where flags sets ACC_IC_LONG_FORM:
- e_ic_outer_class,
- e_ic_name,
-
- // bands for carrying class schema information:
- e_class_this,
- e_class_super,
- e_class_interface_count,
- e_class_interface,
-
- // bands for class members
- e_class_field_count,
- e_class_method_count,
- e_field_descr,
- e_field_flags_hi,
- e_field_flags_lo,
- e_field_attr_count,
- e_field_attr_indexes,
- e_field_attr_calls,
- e_field_ConstantValue_KQ,
- e_field_Signature_RS,
- e_field_metadata_bands,
- e_field_attr_bands,
- e_method_descr,
- e_method_flags_hi,
- e_method_flags_lo,
- e_method_attr_count,
- e_method_attr_indexes,
- e_method_attr_calls,
- e_method_Exceptions_N,
- e_method_Exceptions_RC,
- e_method_Signature_RS,
- e_method_metadata_bands,
- e_method_attr_bands,
- e_class_flags_hi,
- e_class_flags_lo,
- e_class_attr_count,
- e_class_attr_indexes,
- e_class_attr_calls,
- e_class_SourceFile_RUN,
- e_class_EnclosingMethod_RC,
- e_class_EnclosingMethod_RDN,
- e_class_Signature_RS,
- e_class_metadata_bands,
- e_class_InnerClasses_N,
- e_class_InnerClasses_RC,
- e_class_InnerClasses_F,
- e_class_InnerClasses_outer_RCN,
- e_class_InnerClasses_name_RUN,
- e_class_ClassFile_version_minor_H,
- e_class_ClassFile_version_major_H,
- e_class_attr_bands,
- e_code_headers,
- e_code_max_stack,
- e_code_max_na_locals,
- e_code_handler_count,
- e_code_handler_start_P,
- e_code_handler_end_PO,
- e_code_handler_catch_PO,
- e_code_handler_class_RCN,
-
- // code attributes
- e_code_flags_hi,
- e_code_flags_lo,
- e_code_attr_count,
- e_code_attr_indexes,
- e_code_attr_calls,
- e_code_StackMapTable_N,
- e_code_StackMapTable_frame_T,
- e_code_StackMapTable_local_N,
- e_code_StackMapTable_stack_N,
- e_code_StackMapTable_offset,
- e_code_StackMapTable_T,
- e_code_StackMapTable_RC,
- e_code_StackMapTable_P,
- e_code_LineNumberTable_N,
- e_code_LineNumberTable_bci_P,
- e_code_LineNumberTable_line,
- e_code_LocalVariableTable_N,
- e_code_LocalVariableTable_bci_P,
- e_code_LocalVariableTable_span_O,
- e_code_LocalVariableTable_name_RU,
- e_code_LocalVariableTable_type_RS,
- e_code_LocalVariableTable_slot,
- e_code_LocalVariableTypeTable_N,
- e_code_LocalVariableTypeTable_bci_P,
- e_code_LocalVariableTypeTable_span_O,
- e_code_LocalVariableTypeTable_name_RU,
- e_code_LocalVariableTypeTable_type_RS,
- e_code_LocalVariableTypeTable_slot,
- e_code_attr_bands,
-
- // bands for bytecodes
- e_bc_codes,
- // remaining bands provide typed opcode fields required by the bc_codes
- e_bc_case_count,
- e_bc_case_value,
- e_bc_byte,
- e_bc_short,
- e_bc_local,
- e_bc_label,
-
- // ldc* operands:
- e_bc_intref,
- e_bc_floatref,
- e_bc_longref,
- e_bc_doubleref,
- e_bc_stringref,
- e_bc_classref,
- e_bc_fieldref,
- e_bc_methodref,
- e_bc_imethodref,
-
- // _self_linker_op family
- e_bc_thisfield,
- e_bc_superfield,
- e_bc_thismethod,
- e_bc_supermethod,
-
- // bc_invokeinit family:
- e_bc_initref,
-
- // bytecode escape sequences
- e_bc_escref,
- e_bc_escrefsize,
- e_bc_escsize,
- e_bc_escbyte,
-
- // file attributes and contents
- e_file_name,
- e_file_size_hi,
- e_file_size_lo,
- e_file_modtime,
- e_file_options,
- // e_file_bits, // handled specially as an appendix
- BAND_LIMIT
-};
-
-// Symbolic names for bands, as if in a giant global struct:
-//#define archive_magic all_bands[e_archive_magic]
-//#define archive_header all_bands[e_archive_header]
-//#define band_headers all_bands[e_band_headers]
-#define cp_Utf8_prefix all_bands[e_cp_Utf8_prefix]
-#define cp_Utf8_suffix all_bands[e_cp_Utf8_suffix]
-#define cp_Utf8_chars all_bands[e_cp_Utf8_chars]
-#define cp_Utf8_big_suffix all_bands[e_cp_Utf8_big_suffix]
-#define cp_Utf8_big_chars all_bands[e_cp_Utf8_big_chars]
-#define cp_Int all_bands[e_cp_Int]
-#define cp_Float all_bands[e_cp_Float]
-#define cp_Long_hi all_bands[e_cp_Long_hi]
-#define cp_Long_lo all_bands[e_cp_Long_lo]
-#define cp_Double_hi all_bands[e_cp_Double_hi]
-#define cp_Double_lo all_bands[e_cp_Double_lo]
-#define cp_String all_bands[e_cp_String]
-#define cp_Class all_bands[e_cp_Class]
-#define cp_Signature_form all_bands[e_cp_Signature_form]
-#define cp_Signature_classes all_bands[e_cp_Signature_classes]
-#define cp_Descr_name all_bands[e_cp_Descr_name]
-#define cp_Descr_type all_bands[e_cp_Descr_type]
-#define cp_Field_class all_bands[e_cp_Field_class]
-#define cp_Field_desc all_bands[e_cp_Field_desc]
-#define cp_Method_class all_bands[e_cp_Method_class]
-#define cp_Method_desc all_bands[e_cp_Method_desc]
-#define cp_Imethod_class all_bands[e_cp_Imethod_class]
-#define cp_Imethod_desc all_bands[e_cp_Imethod_desc]
-#define attr_definition_headers all_bands[e_attr_definition_headers]
-#define attr_definition_name all_bands[e_attr_definition_name]
-#define attr_definition_layout all_bands[e_attr_definition_layout]
-#define ic_this_class all_bands[e_ic_this_class]
-#define ic_flags all_bands[e_ic_flags]
-#define ic_outer_class all_bands[e_ic_outer_class]
-#define ic_name all_bands[e_ic_name]
-#define class_this all_bands[e_class_this]
-#define class_super all_bands[e_class_super]
-#define class_interface_count all_bands[e_class_interface_count]
-#define class_interface all_bands[e_class_interface]
-#define class_field_count all_bands[e_class_field_count]
-#define class_method_count all_bands[e_class_method_count]
-#define field_descr all_bands[e_field_descr]
-#define field_flags_hi all_bands[e_field_flags_hi]
-#define field_flags_lo all_bands[e_field_flags_lo]
-#define field_attr_count all_bands[e_field_attr_count]
-#define field_attr_indexes all_bands[e_field_attr_indexes]
-#define field_ConstantValue_KQ all_bands[e_field_ConstantValue_KQ]
-#define field_Signature_RS all_bands[e_field_Signature_RS]
-#define field_attr_bands all_bands[e_field_attr_bands]
-#define method_descr all_bands[e_method_descr]
-#define method_flags_hi all_bands[e_method_flags_hi]
-#define method_flags_lo all_bands[e_method_flags_lo]
-#define method_attr_count all_bands[e_method_attr_count]
-#define method_attr_indexes all_bands[e_method_attr_indexes]
-#define method_Exceptions_N all_bands[e_method_Exceptions_N]
-#define method_Exceptions_RC all_bands[e_method_Exceptions_RC]
-#define method_Signature_RS all_bands[e_method_Signature_RS]
-#define method_attr_bands all_bands[e_method_attr_bands]
-#define class_flags_hi all_bands[e_class_flags_hi]
-#define class_flags_lo all_bands[e_class_flags_lo]
-#define class_attr_count all_bands[e_class_attr_count]
-#define class_attr_indexes all_bands[e_class_attr_indexes]
-#define class_SourceFile_RUN all_bands[e_class_SourceFile_RUN]
-#define class_EnclosingMethod_RC all_bands[e_class_EnclosingMethod_RC]
-#define class_EnclosingMethod_RDN all_bands[e_class_EnclosingMethod_RDN]
-#define class_Signature_RS all_bands[e_class_Signature_RS]
-#define class_InnerClasses_N all_bands[e_class_InnerClasses_N]
-#define class_InnerClasses_RC all_bands[e_class_InnerClasses_RC]
-#define class_InnerClasses_F all_bands[e_class_InnerClasses_F]
-#define class_InnerClasses_outer_RCN all_bands[e_class_InnerClasses_outer_RCN]
-#define class_InnerClasses_name_RUN all_bands[e_class_InnerClasses_name_RUN]
-#define class_ClassFile_version_minor_H all_bands[e_class_ClassFile_version_minor_H]
-#define class_ClassFile_version_major_H all_bands[e_class_ClassFile_version_major_H]
-#define class_attr_bands all_bands[e_class_attr_bands]
-#define code_headers all_bands[e_code_headers]
-#define code_max_stack all_bands[e_code_max_stack]
-#define code_max_na_locals all_bands[e_code_max_na_locals]
-#define code_handler_count all_bands[e_code_handler_count]
-#define code_handler_start_P all_bands[e_code_handler_start_P]
-#define code_handler_end_PO all_bands[e_code_handler_end_PO]
-#define code_handler_catch_PO all_bands[e_code_handler_catch_PO]
-#define code_handler_class_RCN all_bands[e_code_handler_class_RCN]
-#define code_flags_hi all_bands[e_code_flags_hi]
-#define code_flags_lo all_bands[e_code_flags_lo]
-#define code_attr_count all_bands[e_code_attr_count]
-#define code_attr_indexes all_bands[e_code_attr_indexes]
-#define code_StackMapTable_N all_bands[e_code_StackMapTable_N]
-#define code_StackMapTable_frame_T all_bands[e_code_StackMapTable_frame_T]
-#define code_StackMapTable_local_N all_bands[e_code_StackMapTable_local_N]
-#define code_StackMapTable_stack_N all_bands[e_code_StackMapTable_stack_N]
-#define code_StackMapTable_offset all_bands[e_code_StackMapTable_offset]
-#define code_StackMapTable_T all_bands[e_code_StackMapTable_T]
-#define code_StackMapTable_RC all_bands[e_code_StackMapTable_RC]
-#define code_StackMapTable_P all_bands[e_code_StackMapTable_P]
-#define code_LineNumberTable_N all_bands[e_code_LineNumberTable_N]
-#define code_LineNumberTable_bci_P all_bands[e_code_LineNumberTable_bci_P]
-#define code_LineNumberTable_line all_bands[e_code_LineNumberTable_line]
-#define code_LocalVariableTable_N all_bands[e_code_LocalVariableTable_N]
-#define code_LocalVariableTable_bci_P all_bands[e_code_LocalVariableTable_bci_P]
-#define code_LocalVariableTable_span_O all_bands[e_code_LocalVariableTable_span_O]
-#define code_LocalVariableTable_name_RU all_bands[e_code_LocalVariableTable_name_RU]
-#define code_LocalVariableTable_type_RS all_bands[e_code_LocalVariableTable_type_RS]
-#define code_LocalVariableTable_slot all_bands[e_code_LocalVariableTable_slot]
-#define code_LocalVariableTypeTable_N all_bands[e_code_LocalVariableTypeTable_N]
-#define code_LocalVariableTypeTable_bci_P all_bands[e_code_LocalVariableTypeTable_bci_P]
-#define code_LocalVariableTypeTable_span_O all_bands[e_code_LocalVariableTypeTable_span_O]
-#define code_LocalVariableTypeTable_name_RU all_bands[e_code_LocalVariableTypeTable_name_RU]
-#define code_LocalVariableTypeTable_type_RS all_bands[e_code_LocalVariableTypeTable_type_RS]
-#define code_LocalVariableTypeTable_slot all_bands[e_code_LocalVariableTypeTable_slot]
-#define code_attr_bands all_bands[e_code_attr_bands]
-#define bc_codes all_bands[e_bc_codes]
-#define bc_case_count all_bands[e_bc_case_count]
-#define bc_case_value all_bands[e_bc_case_value]
-#define bc_byte all_bands[e_bc_byte]
-#define bc_short all_bands[e_bc_short]
-#define bc_local all_bands[e_bc_local]
-#define bc_label all_bands[e_bc_label]
-#define bc_intref all_bands[e_bc_intref]
-#define bc_floatref all_bands[e_bc_floatref]
-#define bc_longref all_bands[e_bc_longref]
-#define bc_doubleref all_bands[e_bc_doubleref]
-#define bc_stringref all_bands[e_bc_stringref]
-#define bc_classref all_bands[e_bc_classref]
-#define bc_fieldref all_bands[e_bc_fieldref]
-#define bc_methodref all_bands[e_bc_methodref]
-#define bc_imethodref all_bands[e_bc_imethodref]
-#define bc_thisfield all_bands[e_bc_thisfield]
-#define bc_superfield all_bands[e_bc_superfield]
-#define bc_thismethod all_bands[e_bc_thismethod]
-#define bc_supermethod all_bands[e_bc_supermethod]
-#define bc_initref all_bands[e_bc_initref]
-#define bc_escref all_bands[e_bc_escref]
-#define bc_escrefsize all_bands[e_bc_escrefsize]
-#define bc_escsize all_bands[e_bc_escsize]
-#define bc_escbyte all_bands[e_bc_escbyte]
-#define file_name all_bands[e_file_name]
-#define file_size_hi all_bands[e_file_size_hi]
-#define file_size_lo all_bands[e_file_size_lo]
-#define file_modtime all_bands[e_file_modtime]
-#define file_options all_bands[e_file_options]
diff --git a/libraries/pack200/src/bytes.cpp b/libraries/pack200/src/bytes.cpp
deleted file mode 100644
index 767fe0a5..00000000
--- a/libraries/pack200/src/bytes.cpp
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <assert.h>
-#include <stdint.h>
-#include "defines.h"
-#include "bytes.h"
-#include "utils.h"
-
-static byte dummy[1 << 10];
-
-bool bytes::inBounds(const void *p)
-{
- return p >= ptr && p < limit();
-}
-
-void bytes::malloc(size_t len_)
-{
- len = len_;
- ptr = NEW(byte, add_size(len_, 1)); // add trailing zero byte always
- if (ptr == nullptr)
- {
- // set ptr to some victim memory, to ease escape
- set(dummy, sizeof(dummy) - 1);
- unpack_abort(ERROR_ENOMEM);
- }
-}
-
-void bytes::realloc(size_t len_)
-{
- if (len == len_)
- return; // nothing to do
- if (ptr == dummy)
- return; // escaping from an error
- if (ptr == nullptr)
- {
- malloc(len_);
- return;
- }
- byte *oldptr = ptr;
- ptr = (len_ >= PSIZE_MAX) ? nullptr : (byte *)::realloc(ptr, add_size(len_, 1));
- if (ptr != nullptr)
- {
- if (len < len_)
- memset(ptr + len, 0, len_ - len);
- ptr[len_] = 0;
- len = len_;
- }
- else
- {
- ptr = oldptr; // ease our escape
- unpack_abort(ERROR_ENOMEM);
- }
-}
-
-void bytes::free()
-{
- if (ptr == dummy)
- return; // escaping from an error
- if (ptr != nullptr)
- {
- ::free(ptr);
- }
- len = 0;
- ptr = 0;
-}
-
-int bytes::indexOf(byte c)
-{
- byte *p = (byte *)memchr(ptr, c, len);
- return (p == 0) ? -1 : (int)(p - ptr);
-}
-
-byte *bytes::writeTo(byte *bp)
-{
- memcpy(bp, ptr, len);
- return bp + len;
-}
-
-int bytes::compareTo(bytes &other)
-{
- size_t l1 = len;
- size_t l2 = other.len;
- int cmp = memcmp(ptr, other.ptr, (l1 < l2) ? l1 : l2);
- if (cmp != 0)
- return cmp;
- return (l1 < l2) ? -1 : (l1 > l2) ? 1 : 0;
-}
-
-void bytes::saveFrom(const void *ptr_, size_t len_)
-{
- malloc(len_);
- // Save as much as possible.
- if (len_ > len)
- {
- assert(ptr == dummy); // error recovery
- len_ = len;
- }
- copyFrom(ptr_, len_);
-}
-
-//#TODO: Need to fix for exception handling
-void bytes::copyFrom(const void *ptr_, size_t len_, size_t offset)
-{
- assert(len_ == 0 || inBounds(ptr + offset));
- assert(len_ == 0 || inBounds(ptr + offset + len_ - 1));
- memcpy(ptr + offset, ptr_, len_);
-}
-
-// Make sure there are 'o' bytes beyond the fill pointer,
-// advance the fill pointer, and return the old fill pointer.
-byte *fillbytes::grow(size_t s)
-{
- size_t nlen = add_size(b.len, s);
- if (nlen <= allocated)
- {
- b.len = nlen;
- return limit() - s;
- }
- size_t maxlen = nlen;
- if (maxlen < 128)
- maxlen = 128;
- if (maxlen < allocated * 2)
- maxlen = allocated * 2;
- if (allocated == 0)
- {
- // Initial buffer was not malloced. Do not reallocate it.
- bytes old = b;
- b.malloc(maxlen);
- if (b.len == maxlen)
- old.writeTo(b.ptr);
- }
- else
- {
- b.realloc(maxlen);
- }
- allocated = b.len;
- if (allocated != maxlen)
- {
- b.len = nlen - s; // back up
- return dummy; // scribble during error recov.
- }
- // after realloc, recompute pointers
- b.len = nlen;
- assert(b.len <= allocated);
- return limit() - s;
-}
-
-void fillbytes::ensureSize(size_t s)
-{
- if (allocated >= s)
- return;
- size_t len0 = b.len;
- grow(s - size());
- b.len = len0; // put it back
-}
-
-int ptrlist::indexOf(const void *x)
-{
- int len = length();
- for (int i = 0; i < len; i++)
- {
- if (get(i) == x)
- return i;
- }
- return -1;
-}
-
-void ptrlist::freeAll()
-{
- int len = length();
- for (int i = 0; i < len; i++)
- {
- void *p = (void *)get(i);
- if (p != nullptr)
- {
- ::free(p);
- }
- }
- free();
-}
-
-int intlist::indexOf(int x)
-{
- int len = length();
- for (int i = 0; i < len; i++)
- {
- if (get(i) == x)
- return i;
- }
- return -1;
-}
diff --git a/libraries/pack200/src/bytes.h b/libraries/pack200/src/bytes.h
deleted file mode 100644
index 2ce1f7f4..00000000
--- a/libraries/pack200/src/bytes.h
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-#pragma once
-
-struct bytes
-{
- int8_t *ptr;
- size_t len;
- int8_t *limit()
- {
- return ptr + len;
- }
-
- void set(int8_t *ptr_, size_t len_)
- {
- ptr = ptr_;
- len = len_;
- }
- void set(const char *str)
- {
- ptr = (int8_t *)str;
- len = strlen(str);
- }
- bool inBounds(const void *p); // p in [ptr, limit)
- void malloc(size_t len_);
- void realloc(size_t len_);
- void free();
- void copyFrom(const void *ptr_, size_t len_, size_t offset = 0);
- void saveFrom(const void *ptr_, size_t len_);
- void saveFrom(const char *str)
- {
- saveFrom(str, strlen(str));
- }
- void copyFrom(bytes &other, size_t offset = 0)
- {
- copyFrom(other.ptr, other.len, offset);
- }
- void saveFrom(bytes &other)
- {
- saveFrom(other.ptr, other.len);
- }
- void clear(int fill_byte = 0)
- {
- memset(ptr, fill_byte, len);
- }
- int8_t *writeTo(int8_t *bp);
- bool equals(bytes &other)
- {
- return 0 == compareTo(other);
- }
- int compareTo(bytes &other);
- bool contains(int8_t c)
- {
- return indexOf(c) >= 0;
- }
- int indexOf(int8_t c);
- // substrings:
- static bytes of(int8_t *ptr, size_t len)
- {
- bytes res;
- res.set(ptr, len);
- return res;
- }
- bytes slice(size_t beg, size_t end)
- {
- bytes res;
- res.ptr = ptr + beg;
- res.len = end - beg;
- assert(res.len == 0 ||(inBounds(res.ptr) && inBounds(res.limit() - 1)));
- return res;
- }
- // building C strings inside byte buffers:
- bytes &strcat(const char *str)
- {
- ::strcat((char *)ptr, str);
- return *this;
- }
- bytes &strcat(bytes &other)
- {
- ::strncat((char *)ptr, (char *)other.ptr, other.len);
- return *this;
- }
- char *strval()
- {
- assert(strlen((char *)ptr) == len);
- return (char *)ptr;
- }
-};
-#define BYTES_OF(var) (bytes::of((int8_t *)&(var), sizeof(var)))
-
-struct fillbytes
-{
- bytes b;
- size_t allocated;
-
- int8_t *base()
- {
- return b.ptr;
- }
- size_t size()
- {
- return b.len;
- }
- int8_t *limit()
- {
- return b.limit();
- } // logical limit
- void setLimit(int8_t *lp)
- {
- assert(isAllocated(lp));
- b.len = lp - b.ptr;
- }
- int8_t *end()
- {
- return b.ptr + allocated;
- } // physical limit
- int8_t *loc(size_t o)
- {
- assert(o < b.len);
- return b.ptr + o;
- }
- void init()
- {
- allocated = 0;
- b.set(nullptr, 0);
- }
- void init(size_t s)
- {
- init();
- ensureSize(s);
- }
- void free()
- {
- if (allocated != 0)
- b.free();
- allocated = 0;
- }
- void empty()
- {
- b.len = 0;
- }
- int8_t *grow(size_t s); // grow so that limit() += s
- int getByte(uint32_t i)
- {
- return *loc(i) & 0xFF;
- }
- void addByte(int8_t x)
- {
- *grow(1) = x;
- }
- void ensureSize(size_t s); // make sure allocated >= s
- void trimToSize()
- {
- if (allocated > size())
- b.realloc(allocated = size());
- }
- bool canAppend(size_t s)
- {
- return allocated > b.len + s;
- }
- bool isAllocated(int8_t *p)
- {
- return p >= base() && p <= end();
- } // asserts
- void set(bytes &src)
- {
- set(src.ptr, src.len);
- }
-
- void set(int8_t *ptr, size_t len)
- {
- b.set(ptr, len);
- allocated = 0; // mark as not reallocatable
- }
-
- // block operations on resizing byte buffer:
- fillbytes &append(const void *ptr_, size_t len_)
- {
- memcpy(grow(len_), ptr_, len_);
- return (*this);
- }
- fillbytes &append(bytes &other)
- {
- return append(other.ptr, other.len);
- }
- fillbytes &append(const char *str)
- {
- return append(str, strlen(str));
- }
-};
-
-struct ptrlist : fillbytes
-{
- typedef const void *cvptr;
- int length()
- {
- return (int)(size() / sizeof(cvptr));
- }
- cvptr *base()
- {
- return (cvptr *)fillbytes::base();
- }
- cvptr &get(int i)
- {
- return *(cvptr *)loc(i * sizeof(cvptr));
- }
- cvptr *limit()
- {
- return (cvptr *)fillbytes::limit();
- }
- void add(cvptr x)
- {
- *(cvptr *)grow(sizeof(x)) = x;
- }
- void popTo(int l)
- {
- assert(l <= length());
- b.len = l * sizeof(cvptr);
- }
- int indexOf(cvptr x);
- bool contains(cvptr x)
- {
- return indexOf(x) >= 0;
- }
- void freeAll(); // frees every ptr on the list, plus the list itself
-};
-// Use a macro rather than mess with subtle mismatches
-// between member and non-member function pointers.
-#define PTRLIST_QSORT(ptrls, fn) ::qsort((ptrls).base(), (ptrls).length(), sizeof(void *), fn)
-
-struct intlist : fillbytes
-{
- int length()
- {
- return (int)(size() / sizeof(int));
- }
- int *base()
- {
- return (int *)fillbytes::base();
- }
- int &get(int i)
- {
- return *(int *)loc(i * sizeof(int));
- }
- int *limit()
- {
- return (int *)fillbytes::limit();
- }
- void add(int x)
- {
- *(int *)grow(sizeof(x)) = x;
- }
- void popTo(int l)
- {
- assert(l <= length());
- b.len = l * sizeof(int);
- }
- int indexOf(int x);
- bool contains(int x)
- {
- return indexOf(x) >= 0;
- }
-};
diff --git a/libraries/pack200/src/coding.cpp b/libraries/pack200/src/coding.cpp
deleted file mode 100644
index 8e872013..00000000
--- a/libraries/pack200/src/coding.cpp
+++ /dev/null
@@ -1,1044 +0,0 @@
-/*
- * Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-// -*- C++ -*-
-// Small program for unpacking specially compressed Java packages.
-// John R. Rose
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <stdint.h>
-
-#include "defines.h"
-#include "bytes.h"
-#include "utils.h"
-#include "coding.h"
-
-#include "constants.h"
-#include "unpack.h"
-
-extern coding basic_codings[];
-
-// CODING_PRIVATE causes a lot of them
-#pragma GCC diagnostic ignored "-Wunused-variable"
-
-#define CODING_PRIVATE(spec) \
- int spec_ = spec; \
- int B = CODING_B(spec_); \
- int H = CODING_H(spec_); \
- int L = 256 - H; \
- int S = CODING_S(spec_); \
- int D = CODING_D(spec_)
-
-#define IS_NEG_CODE(S, codeVal) ((((int)(codeVal) + 1) & ((1 << S) - 1)) == 0)
-
-#define DECODE_SIGN_S1(ux) (((uint32_t)(ux) >> 1) ^ -((int)(ux) & 1))
-
-static int decode_sign(int S, uint32_t ux)
-{ // == Coding.decodeSign32
- assert(S > 0);
- uint32_t sigbits = (ux >> S);
- if (IS_NEG_CODE(S, ux))
- return (int)(~sigbits);
- else
- return (int)(ux - sigbits);
- // Note that (int)(ux-sigbits) can be negative, if ux is large enough.
-}
-
-coding *coding::init()
-{
- if (umax > 0)
- return this; // already done
- assert(spec != 0); // sanity
-
- // fill in derived fields
- CODING_PRIVATE(spec);
-
- // Return nullptr if 'arb(BHSD)' parameter constraints are not met:
- if (B < 1 || B > B_MAX)
- return nullptr;
- if (H < 1 || H > 256)
- return nullptr;
- if (S < 0 || S > 2)
- return nullptr;
- if (D < 0 || D > 1)
- return nullptr;
- if (B == 1 && H != 256)
- return nullptr; // 1-byte coding must be fixed-size
- if (B >= 5 && H == 256)
- return nullptr; // no 5-byte fixed-size coding
-
- // first compute the range of the coding, in 64 bits
- int64_t range = 0;
- {
- int64_t H_i = 1;
- for (int i = 0; i < B; i++)
- {
- range += H_i;
- H_i *= H;
- }
- range *= L;
- range += H_i;
- }
- assert(range > 0); // no useless codings, please
-
- int this_umax;
-
- // now, compute min and max
- if (range >= ((int64_t)1 << 32))
- {
- this_umax = INT_MAX_VALUE;
- this->umin = INT_MIN_VALUE;
- this->max = INT_MAX_VALUE;
- this->min = INT_MIN_VALUE;
- }
- else
- {
- this_umax = (range > INT_MAX_VALUE) ? INT_MAX_VALUE : (int)range - 1;
- this->max = this_umax;
- this->min = this->umin = 0;
- if (S != 0 && range != 0)
- {
- int64_t maxPosCode = range - 1;
- int64_t maxNegCode = range - 1;
- while (IS_NEG_CODE(S, maxPosCode))
- --maxPosCode;
- while (!IS_NEG_CODE(S, maxNegCode))
- --maxNegCode;
- int maxPos = decode_sign(S, (uint32_t)maxPosCode);
- if (maxPos < 0)
- this->max = INT_MAX_VALUE; // 32-bit wraparound
- else
- this->max = maxPos;
- if (maxNegCode < 0)
- this->min = 0; // No negative codings at all.
- else
- this->min = decode_sign(S, (uint32_t)maxNegCode);
- }
- }
-
- assert(!(isFullRange | isSigned | isSubrange)); // init
- if (min < 0)
- this->isSigned = true;
- if (max < INT_MAX_VALUE && range <= INT_MAX_VALUE)
- this->isSubrange = true;
- if (max == INT_MAX_VALUE && min == INT_MIN_VALUE)
- this->isFullRange = true;
-
- // do this last, to reduce MT exposure (should have a membar too)
- this->umax = this_umax;
-
- return this;
-}
-
-coding *coding::findBySpec(int spec)
-{
- for (coding *scan = &basic_codings[0];; scan++)
- {
- if (scan->spec == spec)
- return scan->init();
- if (scan->spec == 0)
- break;
- }
- coding *ptr = NEW(coding, 1);
- if (!ptr)
- return nullptr;
- coding *c = ptr->initFrom(spec);
- if (c == nullptr)
- {
- ::free(ptr);
- }
- else
- // else caller should free it...
- c->isMalloc = true;
- return c;
-}
-
-coding *coding::findBySpec(int B, int H, int S, int D)
-{
- if (B < 1 || B > B_MAX)
- return nullptr;
- if (H < 1 || H > 256)
- return nullptr;
- if (S < 0 || S > 2)
- return nullptr;
- if (D < 0 || D > 1)
- return nullptr;
- return findBySpec(CODING_SPEC(B, H, S, D));
-}
-
-void coding::free()
-{
- if (isMalloc)
- {
- ::free(this);
- }
-}
-
-void coding_method::reset(value_stream *state)
-{
- assert(state->rp == state->rplimit); // not in mid-stream, please
- // assert(this == vs0.cm);
- state[0] = vs0;
- if (uValues != nullptr)
- {
- uValues->reset(state->helper());
- }
-}
-
-uint32_t coding::parse(byte *&rp, int B, int H)
-{
- int L = 256 - H;
- byte *ptr = rp;
- // hand peel the i==0 part of the loop:
- uint32_t b_i = *ptr++ & 0xFF;
- if (B == 1 || b_i < (uint32_t)L)
- {
- rp = ptr;
- return b_i;
- }
- uint32_t sum = b_i;
- uint32_t H_i = H;
- assert(B <= B_MAX);
- for (int i = 2; i <= B_MAX; i++)
- { // easy for compilers to unroll if desired
- b_i = *ptr++ & 0xFF;
- sum += b_i * H_i;
- if (i == B || b_i < (uint32_t)L)
- {
- rp = ptr;
- return sum;
- }
- H_i *= H;
- }
- assert(false);
- return 0;
-}
-
-uint32_t coding::parse_lgH(byte *&rp, int B, int H, int lgH)
-{
- assert(H == (1 << lgH));
- int L = 256 - (1 << lgH);
- byte *ptr = rp;
- // hand peel the i==0 part of the loop:
- uint32_t b_i = *ptr++ & 0xFF;
- if (B == 1 || b_i < (uint32_t)L)
- {
- rp = ptr;
- return b_i;
- }
- uint32_t sum = b_i;
- uint32_t lg_H_i = lgH;
- assert(B <= B_MAX);
- for (int i = 2; i <= B_MAX; i++)
- { // easy for compilers to unroll if desired
- b_i = *ptr++ & 0xFF;
- sum += b_i << lg_H_i;
- if (i == B || b_i < (uint32_t)L)
- {
- rp = ptr;
- return sum;
- }
- lg_H_i += lgH;
- }
- assert(false);
- return 0;
-}
-
-static const char ERB[] = "EOF reading band";
-
-void coding::parseMultiple(byte *&rp, int N, byte *limit, int B, int H)
-{
- if (N < 0)
- {
- unpack_abort("bad value count");
- return;
- }
- byte *ptr = rp;
- if (B == 1 || H == 256)
- {
- size_t len = (size_t)N * B;
- if (len / B != (size_t)N || ptr + len > limit)
- {
- unpack_abort(ERB);
- return;
- }
- rp = ptr + len;
- return;
- }
- // Note: We assume rp has enough zero-padding.
- int L = 256 - H;
- int n = B;
- while (N > 0)
- {
- ptr += 1;
- if (--n == 0)
- {
- // end of encoding at B bytes, regardless of byte value
- }
- else
- {
- int b = (ptr[-1] & 0xFF);
- if (b >= L)
- {
- // keep going, unless we find a byte < L
- continue;
- }
- }
- // found the last byte
- N -= 1;
- n = B; // reset length counter
- // do an error check here
- if (ptr > limit)
- {
- unpack_abort(ERB);
- return;
- }
- }
- rp = ptr;
- return;
-}
-
-bool value_stream::hasHelper()
-{
- // If my coding method is a pop-style method,
- // then I need a second value stream to transmit
- // unfavored values.
- // This can be determined by examining fValues.
- return cm->fValues != nullptr;
-}
-
-void value_stream::init(byte *rp_, byte *rplimit_, coding *defc)
-{
- rp = rp_;
- rplimit = rplimit_;
- sum = 0;
- cm = nullptr; // no need in the simple case
- setCoding(defc);
-}
-
-void value_stream::setCoding(coding *defc)
-{
- if (defc == nullptr)
- {
- unpack_abort("bad coding");
- defc = coding::findByIndex(_meta_canon_min); // random pick for recovery
- }
-
- c = (*defc);
-
- // choose cmk
- cmk = cmk_ERROR;
- switch (c.spec)
- {
- case BYTE1_spec:
- cmk = cmk_BYTE1;
- break;
- case CHAR3_spec:
- cmk = cmk_CHAR3;
- break;
- case UNSIGNED5_spec:
- cmk = cmk_UNSIGNED5;
- break;
- case DELTA5_spec:
- cmk = cmk_DELTA5;
- break;
- case BCI5_spec:
- cmk = cmk_BCI5;
- break;
- case BRANCH5_spec:
- cmk = cmk_BRANCH5;
- break;
- default:
- if (c.D() == 0)
- {
- switch (c.S())
- {
- case 0:
- cmk = cmk_BHS0;
- break;
- case 1:
- cmk = cmk_BHS1;
- break;
- default:
- cmk = cmk_BHS;
- break;
- }
- }
- else
- {
- if (c.S() == 1)
- {
- if (c.isFullRange)
- cmk = cmk_BHS1D1full;
- if (c.isSubrange)
- cmk = cmk_BHS1D1sub;
- }
- if (cmk == cmk_ERROR)
- cmk = cmk_BHSD1;
- }
- }
-}
-
-static int getPopValue(value_stream *self, uint32_t uval)
-{
- if (uval > 0)
- {
- // note that the initial parse performed a range check
- assert(uval <= (uint32_t)self->cm->fVlength);
- return self->cm->fValues[uval - 1];
- }
- else
- {
- // take an unfavored value
- return self->helper()->getInt();
- }
-}
-
-int coding::sumInUnsignedRange(int x, int y)
-{
- assert(isSubrange);
- int range = (int)(umax + 1);
- assert(range > 0);
- x += y;
- if (x != (int)((int64_t)(x - y) + (int64_t)y))
- {
- // 32-bit overflow interferes with range reduction.
- // Back off from the overflow by adding a multiple of range:
- if (x < 0)
- {
- x -= range;
- assert(x >= 0);
- }
- else
- {
- x += range;
- assert(x < 0);
- }
- }
- if (x < 0)
- {
- x += range;
- if (x >= 0)
- return x;
- }
- else if (x >= range)
- {
- x -= range;
- if (x < range)
- return x;
- }
- else
- {
- // in range
- return x;
- }
- // do it the hard way
- x %= range;
- if (x < 0)
- x += range;
- return x;
-}
-
-static int getDeltaValue(value_stream *self, uint32_t uval, bool isSubrange)
-{
- assert((uint32_t)(self->c.isSubrange) == (uint32_t)isSubrange);
- assert(self->c.isSubrange | self->c.isFullRange);
- if (isSubrange)
- return self->sum = self->c.sumInUnsignedRange(self->sum, (int)uval);
- else
- return self->sum += (int)uval;
-}
-
-bool value_stream::hasValue()
-{
- if (rp < rplimit)
- return true;
- if (cm == nullptr)
- return false;
- if (cm->next == nullptr)
- return false;
- cm->next->reset(this);
- return hasValue();
-}
-
-int value_stream::getInt()
-{
- if (rp >= rplimit)
- {
- // Advance to next coding segment.
- if (rp > rplimit || cm == nullptr || cm->next == nullptr)
- {
- // Must perform this check and throw an exception on bad input.
- unpack_abort(ERB);
- return 0;
- }
- cm->next->reset(this);
- return getInt();
- }
-
- CODING_PRIVATE(c.spec);
- uint32_t uval;
- enum
- {
- B5 = 5,
- B3 = 3,
- H128 = 128,
- H64 = 64,
- H4 = 4
- };
- switch (cmk)
- {
- case cmk_BHS:
- assert(D == 0);
- uval = coding::parse(rp, B, H);
- if (S == 0)
- return (int)uval;
- return decode_sign(S, uval);
-
- case cmk_BHS0:
- assert(S == 0 && D == 0);
- uval = coding::parse(rp, B, H);
- return (int)uval;
-
- case cmk_BHS1:
- assert(S == 1 && D == 0);
- uval = coding::parse(rp, B, H);
- return DECODE_SIGN_S1(uval);
-
- case cmk_BYTE1:
- assert(c.spec == BYTE1_spec);
- assert(B == 1 && H == 256 && S == 0 && D == 0);
- return *rp++ & 0xFF;
-
- case cmk_CHAR3:
- assert(c.spec == CHAR3_spec);
- assert(B == B3 && H == H128 && S == 0 && D == 0);
- return coding::parse_lgH(rp, B3, H128, 7);
-
- case cmk_UNSIGNED5:
- assert(c.spec == UNSIGNED5_spec);
- assert(B == B5 && H == H64 && S == 0 && D == 0);
- return coding::parse_lgH(rp, B5, H64, 6);
-
- case cmk_BHSD1:
- assert(D == 1);
- uval = coding::parse(rp, B, H);
- if (S != 0)
- uval = (uint32_t)decode_sign(S, uval);
- return getDeltaValue(this, uval, (bool)c.isSubrange);
-
- case cmk_BHS1D1full:
- assert(S == 1 && D == 1 && c.isFullRange);
- uval = coding::parse(rp, B, H);
- uval = (uint32_t)DECODE_SIGN_S1(uval);
- return getDeltaValue(this, uval, false);
-
- case cmk_BHS1D1sub:
- assert(S == 1 && D == 1 && c.isSubrange);
- uval = coding::parse(rp, B, H);
- uval = (uint32_t)DECODE_SIGN_S1(uval);
- return getDeltaValue(this, uval, true);
-
- case cmk_DELTA5:
- assert(c.spec == DELTA5_spec);
- assert(B == B5 && H == H64 && S == 1 && D == 1 && c.isFullRange);
- uval = coding::parse_lgH(rp, B5, H64, 6);
- sum += DECODE_SIGN_S1(uval);
- return sum;
-
- case cmk_BCI5:
- assert(c.spec == BCI5_spec);
- assert(B == B5 && H == H4 && S == 0 && D == 0);
- return coding::parse_lgH(rp, B5, H4, 2);
-
- case cmk_BRANCH5:
- assert(c.spec == BRANCH5_spec);
- assert(B == B5 && H == H4 && S == 2 && D == 0);
- uval = coding::parse_lgH(rp, B5, H4, 2);
- return decode_sign(S, uval);
-
- case cmk_pop:
- uval = coding::parse(rp, B, H);
- if (S != 0)
- {
- uval = (uint32_t)decode_sign(S, uval);
- }
- if (D != 0)
- {
- assert(c.isSubrange | c.isFullRange);
- if (c.isSubrange)
- sum = c.sumInUnsignedRange(sum, (int)uval);
- else
- sum += (int)uval;
- uval = (uint32_t)sum;
- }
- return getPopValue(this, uval);
-
- case cmk_pop_BHS0:
- assert(S == 0 && D == 0);
- uval = coding::parse(rp, B, H);
- return getPopValue(this, uval);
-
- case cmk_pop_BYTE1:
- assert(c.spec == BYTE1_spec);
- assert(B == 1 && H == 256 && S == 0 && D == 0);
- return getPopValue(this, *rp++ & 0xFF);
-
- default:
- break;
- }
- assert(false);
- return 0;
-}
-
-static int moreCentral(int x, int y)
-{ // used to find end of Pop.{F}
- // Suggested implementation from the Pack200 specification:
- uint32_t kx = (x >> 31) ^ (x << 1);
- uint32_t ky = (y >> 31) ^ (y << 1);
- return (kx < ky ? x : y);
-}
-// static maybe_inline
-// int moreCentral2(int x, int y, int min) {
-// // Strict implementation of buggy 150.7 specification.
-// // The bug is that the spec. says absolute-value ties are broken
-// // in favor of positive numbers, but the suggested implementation
-// // (also mentioned in the spec.) breaks ties in favor of negative numbers.
-// if ((x + y) != 0)
-// return min;
-// else
-// // return the other value, which breaks a tie in the positive direction
-// return (x > y)? x: y;
-//}
-
-static const byte *no_meta[] = {nullptr};
-#define NO_META (*(byte **)no_meta)
-enum
-{
- POP_FAVORED_N = -2
-};
-
-// mode bits
-#define DISABLE_RUN 1 // used immediately inside ACodee
-#define DISABLE_POP 2 // used recursively in all pop sub-bands
-
-// This function knows all about meta-coding.
-void coding_method::init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode,
- coding *defc, int N, intlist *valueSink)
-{
- assert(N != 0);
-
- assert(u != nullptr); // must be pre-initialized
- // if (u == nullptr) u = unpacker::current(); // expensive
-
- int op = (meta_rp == nullptr) ? _meta_default : (*meta_rp++ & 0xFF);
- coding *foundc = nullptr;
- coding *to_free = nullptr;
-
- if (op == _meta_default)
- {
- foundc = defc;
- // and fall through
- }
- else if (op >= _meta_canon_min && op <= _meta_canon_max)
- {
- foundc = coding::findByIndex(op);
- // and fall through
- }
- else if (op == _meta_arb)
- {
- int args = (*meta_rp++ & 0xFF);
- // args = (D:[0..1] + 2*S[0..2] + 8*(B:[1..5]-1))
- int D = ((args >> 0) & 1);
- int S = ((args >> 1) & 3);
- int B = ((args >> 3) & -1) + 1;
- // & (H[1..256]-1)
- int H = (*meta_rp++ & 0xFF) + 1;
- foundc = coding::findBySpec(B, H, S, D);
- to_free = foundc; // findBySpec may dynamically allocate
- if (foundc == nullptr)
- {
- unpack_abort("illegal arbitrary coding");
- return;
- }
- // and fall through
- }
- else if (op >= _meta_run && op < _meta_pop)
- {
- int args = (op - _meta_run);
- // args: KX:[0..3] + 4*(KBFlag:[0..1]) + 8*(ABDef:[0..2])
- int KX = ((args >> 0) & 3);
- int KBFlag = ((args >> 2) & 1);
- int ABDef = ((args >> 3) & -1);
- assert(ABDef <= 2);
- // & KB: one of [0..255] if KBFlag=1
- int KB = (!KBFlag ? 3 : (*meta_rp++ & 0xFF));
- int K = (KB + 1) << (KX * 4);
- int N2 = (N >= 0) ? N - K : N;
- if (N == 0 || (N2 <= 0 && N2 != N))
- {
- unpack_abort("illegal run encoding");
- }
- if ((mode & DISABLE_RUN) != 0)
- {
- unpack_abort("illegal nested run encoding");
- }
-
- // & Enc{ ACode } if ADef=0 (ABDef != 1)
- // No direct nesting of 'run' in ACode, but in BCode it's OK.
- int disRun = mode | DISABLE_RUN;
- if (ABDef == 1)
- {
- this->init(band_rp, band_limit, NO_META, disRun, defc, K, valueSink);
- }
- else
- {
- this->init(band_rp, band_limit, meta_rp, disRun, defc, K, valueSink);
- }
-
- // & Enc{ BCode } if BDef=0 (ABDef != 2)
- coding_method *tail = U_NEW(coding_method, 1);
- if (!tail)
- return;
- tail->u = u;
-
- // The 'run' codings may be nested indirectly via 'pop' codings.
- // This means that this->next may already be filled in, if
- // ACode was of type 'pop' with a 'run' token coding.
- // No problem: Just chain the upcoming BCode onto the end.
- for (coding_method *self = this;; self = self->next)
- {
- if (self->next == nullptr)
- {
- self->next = tail;
- break;
- }
- }
-
- if (ABDef == 2)
- {
- tail->init(band_rp, band_limit, NO_META, mode, defc, N2, valueSink);
- }
- else
- {
- tail->init(band_rp, band_limit, meta_rp, mode, defc, N2, valueSink);
- }
- // Note: The preceding calls to init should be tail-recursive.
-
- return; // done; no falling through
- }
- else if (op >= _meta_pop && op < _meta_limit)
- {
- int args = (op - _meta_pop);
- // args: (FDef:[0..1]) + 2*UDef:[0..1] + 4*(TDefL:[0..11])
- int FDef = ((args >> 0) & 1);
- int UDef = ((args >> 1) & 1);
- int TDefL = ((args >> 2) & -1);
- assert(TDefL <= 11);
- int TDef = (TDefL > 0);
- int TL = (TDefL <= 6) ? (2 << TDefL) : (256 - (4 << (11 - TDefL)));
- int TH = (256 - TL);
- if (N <= 0)
- {
- unpack_abort("illegal pop encoding");
- }
- if ((mode & DISABLE_POP) != 0)
- {
- unpack_abort("illegal nested pop encoding");
- }
-
- // No indirect nesting of 'pop', but 'run' is OK.
- int disPop = DISABLE_POP;
-
- // & Enc{ FCode } if FDef=0
- int FN = POP_FAVORED_N;
- assert(valueSink == nullptr);
- intlist fValueSink;
- fValueSink.init();
- coding_method fval;
- BYTES_OF(fval).clear();
- fval.u = u;
- if (FDef != 0)
- {
- fval.init(band_rp, band_limit, NO_META, disPop, defc, FN, &fValueSink);
- }
- else
- {
- fval.init(band_rp, band_limit, meta_rp, disPop, defc, FN, &fValueSink);
- }
- bytes fvbuf;
- fValues = (u->saveTo(fvbuf, fValueSink.b), (int *)fvbuf.ptr);
- fVlength = fValueSink.length(); // i.e., the parameter K
- fValueSink.free();
-
- // Skip the first {F} run in all subsequent passes.
- // The next call to this->init(...) will set vs0.rp to point after the {F}.
-
- // & Enc{ TCode } if TDef=0 (TDefL==0)
- if (TDef != 0)
- {
- coding *tcode = coding::findBySpec(1, 256); // BYTE1
- // find the most narrowly sufficient code:
- for (int B = 2; B <= B_MAX; B++)
- {
- if (fVlength <= tcode->umax)
- break; // found it
- tcode->free();
- tcode = coding::findBySpec(B, TH);
- if (!tcode)
- return;
- }
- if (!(fVlength <= tcode->umax))
- {
- unpack_abort("pop.L value too small");
- }
- this->init(band_rp, band_limit, NO_META, disPop, tcode, N, nullptr);
- tcode->free();
- }
- else
- {
- this->init(band_rp, band_limit, meta_rp, disPop, defc, N, nullptr);
- }
-
- // Count the number of zero tokens right now.
- // Also verify that they are in bounds.
- int UN = 0; // one {U} for each zero in {T}
- value_stream vs = vs0;
- for (int i = 0; i < N; i++)
- {
- uint32_t val = vs.getInt();
- if (val == 0)
- UN += 1;
- if (!(val <= (uint32_t)fVlength))
- {
- unpack_abort("pop token out of range");
- }
- }
- vs.done();
-
- // & Enc{ UCode } if UDef=0
- if (UN != 0)
- {
- uValues = U_NEW(coding_method, 1);
- if (uValues == nullptr)
- return;
- uValues->u = u;
- if (UDef != 0)
- {
- uValues->init(band_rp, band_limit, NO_META, disPop, defc, UN, nullptr);
- }
- else
- {
- uValues->init(band_rp, band_limit, meta_rp, disPop, defc, UN, nullptr);
- }
- }
- else
- {
- if (UDef == 0)
- {
- int uop = (*meta_rp++ & 0xFF);
- if (uop > _meta_canon_max)
- // %%% Spec. requires the more strict (uop != _meta_default).
- unpack_abort("bad meta-coding for empty pop/U");
- }
- }
-
- // Bug fix for 6259542
- // Last of all, adjust vs0.cmk to the 'pop' flavor
- for (coding_method *self = this; self != nullptr; self = self->next)
- {
- coding_method_kind cmk2 = cmk_pop;
- switch (self->vs0.cmk)
- {
- case cmk_BHS0:
- cmk2 = cmk_pop_BHS0;
- break;
- case cmk_BYTE1:
- cmk2 = cmk_pop_BYTE1;
- break;
- default:
- break;
- }
- self->vs0.cmk = cmk2;
- if (self != this)
- {
- assert(self->fValues == nullptr); // no double init
- self->fValues = this->fValues;
- self->fVlength = this->fVlength;
- assert(self->uValues == nullptr); // must stay nullptr
- }
- }
-
- return; // done; no falling through
- }
- else
- {
- unpack_abort("bad meta-coding");
- }
-
- // Common code here skips a series of values with one coding.
- assert(foundc != nullptr);
-
- assert(vs0.cmk == cmk_ERROR); // no garbage, please
- assert(vs0.rp == nullptr); // no garbage, please
- assert(vs0.rplimit == nullptr); // no garbage, please
- assert(vs0.sum == 0); // no garbage, please
-
- vs0.init(band_rp, band_limit, foundc);
-
- // Done with foundc. Free if necessary.
- if (to_free != nullptr)
- {
- to_free->free();
- to_free = nullptr;
- }
- foundc = nullptr;
-
- coding &c = vs0.c;
- CODING_PRIVATE(c.spec);
- // assert sane N
- assert((uint32_t)N < INT_MAX_VALUE || N == POP_FAVORED_N);
-
- // Look at the values, or at least skip over them quickly.
- if (valueSink == nullptr)
- {
- // Skip and ignore values in the first pass.
- c.parseMultiple(band_rp, N, band_limit, B, H);
- }
- else if (N >= 0)
- {
- // Pop coding, {F} sequence, initial run of values...
- assert((mode & DISABLE_POP) != 0);
- value_stream vs = vs0;
- for (int n = 0; n < N; n++)
- {
- int val = vs.getInt();
- valueSink->add(val);
- }
- band_rp = vs.rp;
- }
- else
- {
- // Pop coding, {F} sequence, final run of values...
- assert((mode & DISABLE_POP) != 0);
- assert(N == POP_FAVORED_N);
- int min = INT_MIN_VALUE; // farthest from the center
- // min2 is based on the buggy specification of centrality in version 150.7
- // no known implementations transmit this value, but just in case...
- // int min2 = INT_MIN_VALUE;
- int last = 0;
- // if there were initial runs, find the potential sentinels in them:
- for (int i = 0; i < valueSink->length(); i++)
- {
- last = valueSink->get(i);
- min = moreCentral(min, last);
- // min2 = moreCentral2(min2, last, min);
- }
- value_stream vs = vs0;
- for (;;)
- {
- int val = vs.getInt();
- if (valueSink->length() > 0 && (val == last || val == min)) //|| val == min2
- break;
- valueSink->add(val);
- last = val;
- min = moreCentral(min, last);
- // min2 = moreCentral2(min2, last, min);
- }
- band_rp = vs.rp;
- }
-
- // Get an accurate upper limit now.
- vs0.rplimit = band_rp;
- vs0.cm = this;
-
- return; // success
-}
-
-coding basic_codings[] = {
- // This one is not a usable irregular coding, but is used by cp_Utf8_chars.
- CODING_INIT(3, 128, 0, 0),
-
- // Fixed-length codings:
- CODING_INIT(1, 256, 0, 0), CODING_INIT(1, 256, 1, 0), CODING_INIT(1, 256, 0, 1),
- CODING_INIT(1, 256, 1, 1), CODING_INIT(2, 256, 0, 0), CODING_INIT(2, 256, 1, 0),
- CODING_INIT(2, 256, 0, 1), CODING_INIT(2, 256, 1, 1), CODING_INIT(3, 256, 0, 0),
- CODING_INIT(3, 256, 1, 0), CODING_INIT(3, 256, 0, 1), CODING_INIT(3, 256, 1, 1),
- CODING_INIT(4, 256, 0, 0), CODING_INIT(4, 256, 1, 0), CODING_INIT(4, 256, 0, 1),
- CODING_INIT(4, 256, 1, 1),
-
- // Full-range variable-length codings:
- CODING_INIT(5, 4, 0, 0), CODING_INIT(5, 4, 1, 0), CODING_INIT(5, 4, 2, 0),
- CODING_INIT(5, 16, 0, 0), CODING_INIT(5, 16, 1, 0), CODING_INIT(5, 16, 2, 0),
- CODING_INIT(5, 32, 0, 0), CODING_INIT(5, 32, 1, 0), CODING_INIT(5, 32, 2, 0),
- CODING_INIT(5, 64, 0, 0), CODING_INIT(5, 64, 1, 0), CODING_INIT(5, 64, 2, 0),
- CODING_INIT(5, 128, 0, 0), CODING_INIT(5, 128, 1, 0), CODING_INIT(5, 128, 2, 0),
- CODING_INIT(5, 4, 0, 1), CODING_INIT(5, 4, 1, 1), CODING_INIT(5, 4, 2, 1),
- CODING_INIT(5, 16, 0, 1), CODING_INIT(5, 16, 1, 1), CODING_INIT(5, 16, 2, 1),
- CODING_INIT(5, 32, 0, 1), CODING_INIT(5, 32, 1, 1), CODING_INIT(5, 32, 2, 1),
- CODING_INIT(5, 64, 0, 1), CODING_INIT(5, 64, 1, 1), CODING_INIT(5, 64, 2, 1),
- CODING_INIT(5, 128, 0, 1), CODING_INIT(5, 128, 1, 1), CODING_INIT(5, 128, 2, 1),
-
- // Variable length subrange codings:
- CODING_INIT(2, 192, 0, 0), CODING_INIT(2, 224, 0, 0), CODING_INIT(2, 240, 0, 0),
- CODING_INIT(2, 248, 0, 0), CODING_INIT(2, 252, 0, 0), CODING_INIT(2, 8, 0, 1),
- CODING_INIT(2, 8, 1, 1), CODING_INIT(2, 16, 0, 1), CODING_INIT(2, 16, 1, 1),
- CODING_INIT(2, 32, 0, 1), CODING_INIT(2, 32, 1, 1), CODING_INIT(2, 64, 0, 1),
- CODING_INIT(2, 64, 1, 1), CODING_INIT(2, 128, 0, 1), CODING_INIT(2, 128, 1, 1),
- CODING_INIT(2, 192, 0, 1), CODING_INIT(2, 192, 1, 1), CODING_INIT(2, 224, 0, 1),
- CODING_INIT(2, 224, 1, 1), CODING_INIT(2, 240, 0, 1), CODING_INIT(2, 240, 1, 1),
- CODING_INIT(2, 248, 0, 1), CODING_INIT(2, 248, 1, 1), CODING_INIT(3, 192, 0, 0),
- CODING_INIT(3, 224, 0, 0), CODING_INIT(3, 240, 0, 0), CODING_INIT(3, 248, 0, 0),
- CODING_INIT(3, 252, 0, 0), CODING_INIT(3, 8, 0, 1), CODING_INIT(3, 8, 1, 1),
- CODING_INIT(3, 16, 0, 1), CODING_INIT(3, 16, 1, 1), CODING_INIT(3, 32, 0, 1),
- CODING_INIT(3, 32, 1, 1), CODING_INIT(3, 64, 0, 1), CODING_INIT(3, 64, 1, 1),
- CODING_INIT(3, 128, 0, 1), CODING_INIT(3, 128, 1, 1), CODING_INIT(3, 192, 0, 1),
- CODING_INIT(3, 192, 1, 1), CODING_INIT(3, 224, 0, 1), CODING_INIT(3, 224, 1, 1),
- CODING_INIT(3, 240, 0, 1), CODING_INIT(3, 240, 1, 1), CODING_INIT(3, 248, 0, 1),
- CODING_INIT(3, 248, 1, 1), CODING_INIT(4, 192, 0, 0), CODING_INIT(4, 224, 0, 0),
- CODING_INIT(4, 240, 0, 0), CODING_INIT(4, 248, 0, 0), CODING_INIT(4, 252, 0, 0),
- CODING_INIT(4, 8, 0, 1), CODING_INIT(4, 8, 1, 1), CODING_INIT(4, 16, 0, 1),
- CODING_INIT(4, 16, 1, 1), CODING_INIT(4, 32, 0, 1), CODING_INIT(4, 32, 1, 1),
- CODING_INIT(4, 64, 0, 1), CODING_INIT(4, 64, 1, 1), CODING_INIT(4, 128, 0, 1),
- CODING_INIT(4, 128, 1, 1), CODING_INIT(4, 192, 0, 1), CODING_INIT(4, 192, 1, 1),
- CODING_INIT(4, 224, 0, 1), CODING_INIT(4, 224, 1, 1), CODING_INIT(4, 240, 0, 1),
- CODING_INIT(4, 240, 1, 1), CODING_INIT(4, 248, 0, 1), CODING_INIT(4, 248, 1, 1),
- CODING_INIT(0, 0, 0, 0)};
-#define BASIC_INDEX_LIMIT (int)(sizeof(basic_codings) / sizeof(basic_codings[0]) - 1)
-
-coding *coding::findByIndex(int idx)
-{
- int index_limit = BASIC_INDEX_LIMIT;
- assert(_meta_canon_min == 1 && _meta_canon_max + 1 == index_limit);
-
- if (idx >= _meta_canon_min && idx <= _meta_canon_max)
- return basic_codings[idx].init();
- else
- return nullptr;
-}
diff --git a/libraries/pack200/src/coding.h b/libraries/pack200/src/coding.h
deleted file mode 100644
index bfdd252e..00000000
--- a/libraries/pack200/src/coding.h
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-struct unpacker;
-
-#define INT_MAX_VALUE ((int)0x7FFFFFFF)
-#define INT_MIN_VALUE ((int)0x80000000)
-
-#define CODING_SPEC(B, H, S, D) ((B) << 20 | (H) << 8 | (S) << 4 | (D) << 0)
-#define CODING_B(x) ((x) >> 20 & 0xF)
-#define CODING_H(x) ((x) >> 8 & 0xFFF)
-#define CODING_S(x) ((x) >> 4 & 0xF)
-#define CODING_D(x) ((x) >> 0 & 0xF)
-
-#define CODING_INIT(B, H, S, D) \
- { \
- CODING_SPEC(B, H, S, D), 0, 0, 0, 0, 0, 0, 0, 0 \
- }
-
-// For debugging purposes, some compilers do not like this and will complain.
-// #define long do_not_use_C_long_types_use_jlong_or_int
-// Use of the type "long" is problematic, do not use it.
-
-struct coding
-{
- int spec; // B,H,S,D
-
- // Handy values derived from the spec:
- int B()
- {
- return CODING_B(spec);
- }
- int H()
- {
- return CODING_H(spec);
- }
- int S()
- {
- return CODING_S(spec);
- }
- int D()
- {
- return CODING_D(spec);
- }
- int L()
- {
- return 256 - CODING_H(spec);
- }
- int min, max;
- int umin, umax;
- char isSigned, isSubrange, isFullRange, isMalloc;
-
- coding *init(); // returns self or nullptr if error
- coding *initFrom(int spec_)
- {
- assert(this->spec == 0);
- this->spec = spec_;
- return init();
- }
-
- static coding *findBySpec(int spec);
- static coding *findBySpec(int B, int H, int S = 0, int D = 0);
- static coding *findByIndex(int irregularCodingIndex);
-
- static uint32_t parse(byte *&rp, int B, int H);
- static uint32_t parse_lgH(byte *&rp, int B, int H, int lgH);
- static void parseMultiple(byte *&rp, int N, byte *limit, int B, int H);
-
- uint32_t parse(byte *&rp)
- {
- return parse(rp, CODING_B(spec), CODING_H(spec));
- }
- void parseMultiple(byte *&rp, int N, byte *limit)
- {
- parseMultiple(rp, N, limit, CODING_B(spec), CODING_H(spec));
- }
-
- bool canRepresent(int x)
- {
- return (x >= min && x <= max);
- }
- bool canRepresentUnsigned(int x)
- {
- return (x >= umin && x <= umax);
- }
-
- int sumInUnsignedRange(int x, int y);
-
- int readFrom(byte *&rpVar, int *dbase);
- void readArrayFrom(byte *&rpVar, int *dbase, int length, int *values);
- void skipArrayFrom(byte *&rpVar, int length)
- {
- readArrayFrom(rpVar, (int *)NULL, length, (int *)NULL);
- }
-
- void free(); // free self if isMalloc
-};
-
-enum coding_method_kind
-{
- cmk_ERROR,
- cmk_BHS,
- cmk_BHS0,
- cmk_BHS1,
- cmk_BHSD1,
- cmk_BHS1D1full, // isFullRange
- cmk_BHS1D1sub, // isSubRange
-
- // special cases hand-optimized (~50% of all decoded values)
- cmk_BYTE1, //(1,256) 6%
- cmk_CHAR3, //(3,128) 7%
- cmk_UNSIGNED5, //(5,64) 13%
- cmk_DELTA5, //(5,64,1,1) 5%
- cmk_BCI5, //(5,4) 18%
- cmk_BRANCH5, //(5,4,2) 4%
- // cmk_UNSIGNED5H16, //(5,16) 5%
- // cmk_UNSIGNED2H4, //(2,4) 6%
- // cmk_DELTA4H8, //(4,8,1,1) 10%
- // cmk_DELTA3H16, //(3,16,1,1) 9%
- cmk_BHS_LIMIT,
- cmk_pop,
- cmk_pop_BHS0,
- cmk_pop_BYTE1,
- cmk_pop_LIMIT,
- cmk_LIMIT
-};
-
-enum
-{
- BYTE1_spec = CODING_SPEC(1, 256, 0, 0),
- CHAR3_spec = CODING_SPEC(3, 128, 0, 0),
- UNSIGNED4_spec = CODING_SPEC(4, 256, 0, 0),
- UNSIGNED5_spec = CODING_SPEC(5, 64, 0, 0),
- SIGNED5_spec = CODING_SPEC(5, 64, 1, 0),
- DELTA5_spec = CODING_SPEC(5, 64, 1, 1),
- UDELTA5_spec = CODING_SPEC(5, 64, 0, 1),
- MDELTA5_spec = CODING_SPEC(5, 64, 2, 1),
- BCI5_spec = CODING_SPEC(5, 4, 0, 0),
- BRANCH5_spec = CODING_SPEC(5, 4, 2, 0)
-};
-
-enum
-{
- B_MAX = 5,
- C_SLOP = B_MAX * 10
-};
-
-struct coding_method;
-
-// iterator under the control of a meta-coding
-struct value_stream
-{
- // current coding of values or values
- coding c; // B,H,S,D,etc.
- coding_method_kind cmk; // type of decoding needed
- byte *rp; // read pointer
- byte *rplimit; // final value of read pointer
- int sum; // partial sum of all values so far (D=1 only)
- coding_method *cm; // coding method that defines this stream
-
- void init(byte *band_rp, byte *band_limit, coding *defc);
- void init(byte *band_rp, byte *band_limit, int spec)
- {
- init(band_rp, band_limit, coding::findBySpec(spec));
- }
-
- void setCoding(coding *c);
- void setCoding(int spec)
- {
- setCoding(coding::findBySpec(spec));
- }
-
- // Parse and decode a single value.
- int getInt();
-
- // Parse and decode a single byte, with no error checks.
- int getByte()
- {
- assert(cmk == cmk_BYTE1);
- assert(rp < rplimit);
- return *rp++ & 0xFF;
- }
-
- // Used only for asserts.
- bool hasValue();
-
- void done()
- {
- assert(!hasValue());
- }
-
- // Sometimes a value stream has an auxiliary (but there are never two).
- value_stream *helper()
- {
- assert(hasHelper());
- return this + 1;
- }
- bool hasHelper();
-};
-
-struct coding_method
-{
- value_stream vs0; // initial state snapshot (vs.meta==this)
-
- coding_method *next; // what to do when we run out of bytes
-
- // these fields are used for pop codes only:
- int *fValues; // favored value array
- int fVlength; // maximum favored value token
- coding_method *uValues; // unfavored value stream
-
- // pointer to outer unpacker, for error checks etc.
- unpacker *u;
-
- // Initialize a value stream.
- void reset(value_stream *state);
-
- // Parse a band header, size a band, and initialize for further action.
- // band_rp advances (but not past band_limit), and meta_rp advances.
- // The mode gives context, such as "inside a pop".
- // The defc and N are the incoming parameters to a meta-coding.
- // The value sink is used to collect output values, when desired.
- void init(byte *&band_rp, byte *band_limit, byte *&meta_rp, int mode, coding *defc, int N,
- intlist *valueSink);
-};
diff --git a/libraries/pack200/src/constants.h b/libraries/pack200/src/constants.h
deleted file mode 100644
index f1baf42a..00000000
--- a/libraries/pack200/src/constants.h
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/*
- Java Class Version numbers history
- 1.0 to 1.3.X 45,3
- 1.4 to 1.4.X 46,0
- 1.5 to 1.5.X 49,0
- 1.6 to 1.5.x 50,0 NOTE Assumed for now
-*/
-
-// classfile constants
-#define JAVA_MAGIC 0xCAFEBABE
-#define JAVA_MIN_MAJOR_VERSION 45
-#define JAVA_MIN_MINOR_VERSION 3
-#define JAVA5_MAX_MAJOR_VERSION 49
-#define JAVA5_MAX_MINOR_VERSION 0
-// NOTE: Assume for now
-#define JAVA6_MAX_MAJOR_VERSION 50
-#define JAVA6_MAX_MINOR_VERSION 0
-
-// package file constants
-#define JAVA_PACKAGE_MAGIC 0xCAFED00D
-#define JAVA5_PACKAGE_MAJOR_VERSION 150
-#define JAVA5_PACKAGE_MINOR_VERSION 7
-
-#define JAVA6_PACKAGE_MAJOR_VERSION 160
-#define JAVA6_PACKAGE_MINOR_VERSION 1
-
-// magic number for gzip streams (for processing pack200-gzip data)
-#define GZIP_MAGIC 0x1F8B0800
-#define GZIP_MAGIC_MASK 0xFFFFFF00 // last \bchar\b is variable "flg" field
-
-enum
-{
- CONSTANT_None,
- CONSTANT_Utf8,
- CONSTANT_unused2, /* unused, was Unicode */
- CONSTANT_Integer,
- CONSTANT_Float,
- CONSTANT_Long,
- CONSTANT_Double,
- CONSTANT_Class,
- CONSTANT_String,
- CONSTANT_Fieldref,
- CONSTANT_Methodref,
- CONSTANT_InterfaceMethodref,
- CONSTANT_NameandType,
- CONSTANT_Signature = 13,
- CONSTANT_All = 14,
- CONSTANT_Limit = 15,
- CONSTANT_NONE = 0,
- CONSTANT_Literal = 20, // pseudo-tag for debugging
- CONSTANT_Member = 21, // pseudo-tag for debugging
- SUBINDEX_BIT = 64, // combined with CONSTANT_xxx for ixTag
- ACC_STATIC = 0x0008,
- ACC_IC_LONG_FORM = (1 << 16), // for ic_flags
- CLASS_ATTR_SourceFile = 17,
- CLASS_ATTR_EnclosingMethod = 18,
- CLASS_ATTR_InnerClasses = 23,
- CLASS_ATTR_ClassFile_version = 24,
- FIELD_ATTR_ConstantValue = 17,
- METHOD_ATTR_Code = 17,
- METHOD_ATTR_Exceptions = 18,
- METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23,
- METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24,
- METHOD_ATTR_AnnotationDefault = 25,
- CODE_ATTR_StackMapTable = 0,
- CODE_ATTR_LineNumberTable = 1,
- CODE_ATTR_LocalVariableTable = 2,
- CODE_ATTR_LocalVariableTypeTable = 3,
- // X_ATTR_Synthetic = 12, // ACC_SYNTHETIC; not predefined
- X_ATTR_Signature = 19,
- X_ATTR_Deprecated = 20,
- X_ATTR_RuntimeVisibleAnnotations = 21,
- X_ATTR_RuntimeInvisibleAnnotations = 22,
- X_ATTR_OVERFLOW = 16,
- X_ATTR_LIMIT_NO_FLAGS_HI = 32,
- X_ATTR_LIMIT_FLAGS_HI = 63,
-
-#define O_ATTR_DO(F) \
- F(X_ATTR_OVERFLOW, 01) \
- /*(end)*/
-#define X_ATTR_DO(F) \
- O_ATTR_DO(F) F(X_ATTR_Signature, Signature) F(X_ATTR_Deprecated, Deprecated) \
- F(X_ATTR_RuntimeVisibleAnnotations, RuntimeVisibleAnnotations) \
- F(X_ATTR_RuntimeInvisibleAnnotations, RuntimeInvisibleAnnotations) \
- /*F(X_ATTR_Synthetic,Synthetic)*/ \
- /*(end)*/
-#define CLASS_ATTR_DO(F) \
- F(CLASS_ATTR_SourceFile, SourceFile) F(CLASS_ATTR_InnerClasses, InnerClasses) \
- F(CLASS_ATTR_EnclosingMethod, EnclosingMethod) F(CLASS_ATTR_ClassFile_version, 02) \
- /*(end)*/
-#define FIELD_ATTR_DO(F) \
- F(FIELD_ATTR_ConstantValue, ConstantValue) \
- /*(end)*/
-#define METHOD_ATTR_DO(F) \
- F(METHOD_ATTR_Code, Code) F(METHOD_ATTR_Exceptions, Exceptions) \
- F(METHOD_ATTR_RuntimeVisibleParameterAnnotations, RuntimeVisibleParameterAnnotations) \
- F(METHOD_ATTR_RuntimeInvisibleParameterAnnotations, \
- RuntimeInvisibleParameterAnnotations) \
- F(METHOD_ATTR_AnnotationDefault, AnnotationDefault) \
- /*(end)*/
-#define CODE_ATTR_DO(F) \
- F(CODE_ATTR_StackMapTable, StackMapTable) F(CODE_ATTR_LineNumberTable, LineNumberTable) \
- F(CODE_ATTR_LocalVariableTable, LocalVariableTable) \
- F(CODE_ATTR_LocalVariableTypeTable, LocalVariableTypeTable) \
- /*(end)*/
-#define ALL_ATTR_DO(F) \
- X_ATTR_DO(F) CLASS_ATTR_DO(F) FIELD_ATTR_DO(F) METHOD_ATTR_DO(F) CODE_ATTR_DO(F) \
- /*(end)*/
-
- // attribute "context types"
- ATTR_CONTEXT_CLASS = 0,
- ATTR_CONTEXT_FIELD = 1,
- ATTR_CONTEXT_METHOD = 2,
- ATTR_CONTEXT_CODE = 3,
- ATTR_CONTEXT_LIMIT = 4,
-
- // constants for parsed layouts (stored in band::le_kind)
- EK_NONE = 0, // not a layout element
- EK_INT = 'I', // B H I SH etc., also FH etc.
- EK_BCI = 'P', // PH etc.
- EK_BCID = 'Q', // POH etc.
- EK_BCO = 'O', // OH etc.
- EK_REPL = 'N', // NH[...] etc.
- EK_REF = 'R', // RUH, RUNH, KQH, etc.
- EK_UN = 'T', // TB(...)[...] etc.
- EK_CASE = 'K', // (...)[...] etc.
- EK_CALL = '(', // (0), (1), etc.
- EK_CBLE = '[', // [...][...] etc.
- NO_BAND_INDEX = -1,
-
- // File option bits, from LSB in ascending bit position.
- FO_DEFLATE_HINT = 1 << 0,
- FO_IS_CLASS_STUB = 1 << 1,
-
- // Archive option bits, from LSB in ascending bit position:
- AO_HAVE_SPECIAL_FORMATS = 1 << 0,
- AO_HAVE_CP_NUMBERS = 1 << 1,
- AO_HAVE_ALL_CODE_FLAGS = 1 << 2,
- AO_3_UNUSED_MBZ = 1 << 3,
- AO_HAVE_FILE_HEADERS = 1 << 4,
- AO_DEFLATE_HINT = 1 << 5,
- AO_HAVE_FILE_MODTIME = 1 << 6,
- AO_HAVE_FILE_OPTIONS = 1 << 7,
- AO_HAVE_FILE_SIZE_HI = 1 << 8,
- AO_HAVE_CLASS_FLAGS_HI = 1 << 9,
- AO_HAVE_FIELD_FLAGS_HI = 1 << 10,
- AO_HAVE_METHOD_FLAGS_HI = 1 << 11,
- AO_HAVE_CODE_FLAGS_HI = 1 << 12,
-#define ARCHIVE_BIT_DO(F) \
- F(AO_HAVE_SPECIAL_FORMATS) F(AO_HAVE_CP_NUMBERS) F(AO_HAVE_ALL_CODE_FLAGS) \
- /*F(AO_3_UNUSED_MBZ)*/ \
- F(AO_HAVE_FILE_HEADERS) F(AO_DEFLATE_HINT) F(AO_HAVE_FILE_MODTIME) \
- F(AO_HAVE_FILE_OPTIONS) F(AO_HAVE_FILE_SIZE_HI) F(AO_HAVE_CLASS_FLAGS_HI) \
- F(AO_HAVE_FIELD_FLAGS_HI) F(AO_HAVE_METHOD_FLAGS_HI) F(AO_HAVE_CODE_FLAGS_HI) \
- /*(end)*/
-
- // Constants for decoding attribute definition header bytes.
- ADH_CONTEXT_MASK = 0x3, // (hdr & ADH_CONTEXT_MASK)
- ADH_BIT_SHIFT = 0x2, // (hdr >> ADH_BIT_SHIFT)
- ADH_BIT_IS_LSB = 1, // (hdr >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB
-#define ADH_BYTE(context, index) ((((index) + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT) + (context))
-#define ADH_BYTE_CONTEXT(adhb) ((adhb) & ADH_CONTEXT_MASK)
-#define ADH_BYTE_INDEX(adhb) (((adhb) >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB)
- NO_MODTIME = 0, // nullptr modtime value
-
- // meta-coding
- _meta_default = 0,
- _meta_canon_min = 1,
- _meta_canon_max = 115,
- _meta_arb = 116,
- _meta_run = 117,
- _meta_pop = 141,
- _meta_limit = 189,
- _meta_error = 255,
- _xxx_1_end
-};
-
-// Bytecodes.
-
-enum
-{
- bc_nop = 0, // 0x00
- bc_aconst_null = 1, // 0x01
- bc_iconst_m1 = 2, // 0x02
- bc_iconst_0 = 3, // 0x03
- bc_iconst_1 = 4, // 0x04
- bc_iconst_2 = 5, // 0x05
- bc_iconst_3 = 6, // 0x06
- bc_iconst_4 = 7, // 0x07
- bc_iconst_5 = 8, // 0x08
- bc_lconst_0 = 9, // 0x09
- bc_lconst_1 = 10, // 0x0a
- bc_fconst_0 = 11, // 0x0b
- bc_fconst_1 = 12, // 0x0c
- bc_fconst_2 = 13, // 0x0d
- bc_dconst_0 = 14, // 0x0e
- bc_dconst_1 = 15, // 0x0f
- bc_bipush = 16, // 0x10
- bc_sipush = 17, // 0x11
- bc_ldc = 18, // 0x12
- bc_ldc_w = 19, // 0x13
- bc_ldc2_w = 20, // 0x14
- bc_iload = 21, // 0x15
- bc_lload = 22, // 0x16
- bc_fload = 23, // 0x17
- bc_dload = 24, // 0x18
- bc_aload = 25, // 0x19
- bc_iload_0 = 26, // 0x1a
- bc_iload_1 = 27, // 0x1b
- bc_iload_2 = 28, // 0x1c
- bc_iload_3 = 29, // 0x1d
- bc_lload_0 = 30, // 0x1e
- bc_lload_1 = 31, // 0x1f
- bc_lload_2 = 32, // 0x20
- bc_lload_3 = 33, // 0x21
- bc_fload_0 = 34, // 0x22
- bc_fload_1 = 35, // 0x23
- bc_fload_2 = 36, // 0x24
- bc_fload_3 = 37, // 0x25
- bc_dload_0 = 38, // 0x26
- bc_dload_1 = 39, // 0x27
- bc_dload_2 = 40, // 0x28
- bc_dload_3 = 41, // 0x29
- bc_aload_0 = 42, // 0x2a
- bc_aload_1 = 43, // 0x2b
- bc_aload_2 = 44, // 0x2c
- bc_aload_3 = 45, // 0x2d
- bc_iaload = 46, // 0x2e
- bc_laload = 47, // 0x2f
- bc_faload = 48, // 0x30
- bc_daload = 49, // 0x31
- bc_aaload = 50, // 0x32
- bc_baload = 51, // 0x33
- bc_caload = 52, // 0x34
- bc_saload = 53, // 0x35
- bc_istore = 54, // 0x36
- bc_lstore = 55, // 0x37
- bc_fstore = 56, // 0x38
- bc_dstore = 57, // 0x39
- bc_astore = 58, // 0x3a
- bc_istore_0 = 59, // 0x3b
- bc_istore_1 = 60, // 0x3c
- bc_istore_2 = 61, // 0x3d
- bc_istore_3 = 62, // 0x3e
- bc_lstore_0 = 63, // 0x3f
- bc_lstore_1 = 64, // 0x40
- bc_lstore_2 = 65, // 0x41
- bc_lstore_3 = 66, // 0x42
- bc_fstore_0 = 67, // 0x43
- bc_fstore_1 = 68, // 0x44
- bc_fstore_2 = 69, // 0x45
- bc_fstore_3 = 70, // 0x46
- bc_dstore_0 = 71, // 0x47
- bc_dstore_1 = 72, // 0x48
- bc_dstore_2 = 73, // 0x49
- bc_dstore_3 = 74, // 0x4a
- bc_astore_0 = 75, // 0x4b
- bc_astore_1 = 76, // 0x4c
- bc_astore_2 = 77, // 0x4d
- bc_astore_3 = 78, // 0x4e
- bc_iastore = 79, // 0x4f
- bc_lastore = 80, // 0x50
- bc_fastore = 81, // 0x51
- bc_dastore = 82, // 0x52
- bc_aastore = 83, // 0x53
- bc_bastore = 84, // 0x54
- bc_castore = 85, // 0x55
- bc_sastore = 86, // 0x56
- bc_pop = 87, // 0x57
- bc_pop2 = 88, // 0x58
- bc_dup = 89, // 0x59
- bc_dup_x1 = 90, // 0x5a
- bc_dup_x2 = 91, // 0x5b
- bc_dup2 = 92, // 0x5c
- bc_dup2_x1 = 93, // 0x5d
- bc_dup2_x2 = 94, // 0x5e
- bc_swap = 95, // 0x5f
- bc_iadd = 96, // 0x60
- bc_ladd = 97, // 0x61
- bc_fadd = 98, // 0x62
- bc_dadd = 99, // 0x63
- bc_isub = 100, // 0x64
- bc_lsub = 101, // 0x65
- bc_fsub = 102, // 0x66
- bc_dsub = 103, // 0x67
- bc_imul = 104, // 0x68
- bc_lmul = 105, // 0x69
- bc_fmul = 106, // 0x6a
- bc_dmul = 107, // 0x6b
- bc_idiv = 108, // 0x6c
- bc_ldiv = 109, // 0x6d
- bc_fdiv = 110, // 0x6e
- bc_ddiv = 111, // 0x6f
- bc_irem = 112, // 0x70
- bc_lrem = 113, // 0x71
- bc_frem = 114, // 0x72
- bc_drem = 115, // 0x73
- bc_ineg = 116, // 0x74
- bc_lneg = 117, // 0x75
- bc_fneg = 118, // 0x76
- bc_dneg = 119, // 0x77
- bc_ishl = 120, // 0x78
- bc_lshl = 121, // 0x79
- bc_ishr = 122, // 0x7a
- bc_lshr = 123, // 0x7b
- bc_iushr = 124, // 0x7c
- bc_lushr = 125, // 0x7d
- bc_iand = 126, // 0x7e
- bc_land = 127, // 0x7f
- bc_ior = 128, // 0x80
- bc_lor = 129, // 0x81
- bc_ixor = 130, // 0x82
- bc_lxor = 131, // 0x83
- bc_iinc = 132, // 0x84
- bc_i2l = 133, // 0x85
- bc_i2f = 134, // 0x86
- bc_i2d = 135, // 0x87
- bc_l2i = 136, // 0x88
- bc_l2f = 137, // 0x89
- bc_l2d = 138, // 0x8a
- bc_f2i = 139, // 0x8b
- bc_f2l = 140, // 0x8c
- bc_f2d = 141, // 0x8d
- bc_d2i = 142, // 0x8e
- bc_d2l = 143, // 0x8f
- bc_d2f = 144, // 0x90
- bc_i2b = 145, // 0x91
- bc_i2c = 146, // 0x92
- bc_i2s = 147, // 0x93
- bc_lcmp = 148, // 0x94
- bc_fcmpl = 149, // 0x95
- bc_fcmpg = 150, // 0x96
- bc_dcmpl = 151, // 0x97
- bc_dcmpg = 152, // 0x98
- bc_ifeq = 153, // 0x99
- bc_ifne = 154, // 0x9a
- bc_iflt = 155, // 0x9b
- bc_ifge = 156, // 0x9c
- bc_ifgt = 157, // 0x9d
- bc_ifle = 158, // 0x9e
- bc_if_icmpeq = 159, // 0x9f
- bc_if_icmpne = 160, // 0xa0
- bc_if_icmplt = 161, // 0xa1
- bc_if_icmpge = 162, // 0xa2
- bc_if_icmpgt = 163, // 0xa3
- bc_if_icmple = 164, // 0xa4
- bc_if_acmpeq = 165, // 0xa5
- bc_if_acmpne = 166, // 0xa6
- bc_goto = 167, // 0xa7
- bc_jsr = 168, // 0xa8
- bc_ret = 169, // 0xa9
- bc_tableswitch = 170, // 0xaa
- bc_lookupswitch = 171, // 0xab
- bc_ireturn = 172, // 0xac
- bc_lreturn = 173, // 0xad
- bc_freturn = 174, // 0xae
- bc_dreturn = 175, // 0xaf
- bc_areturn = 176, // 0xb0
- bc_return = 177, // 0xb1
- bc_getstatic = 178, // 0xb2
- bc_putstatic = 179, // 0xb3
- bc_getfield = 180, // 0xb4
- bc_putfield = 181, // 0xb5
- bc_invokevirtual = 182, // 0xb6
- bc_invokespecial = 183, // 0xb7
- bc_invokestatic = 184, // 0xb8
- bc_invokeinterface = 185, // 0xb9
- bc_xxxunusedxxx = 186, // 0xba
- bc_new = 187, // 0xbb
- bc_newarray = 188, // 0xbc
- bc_anewarray = 189, // 0xbd
- bc_arraylength = 190, // 0xbe
- bc_athrow = 191, // 0xbf
- bc_checkcast = 192, // 0xc0
- bc_instanceof = 193, // 0xc1
- bc_monitorenter = 194, // 0xc2
- bc_monitorexit = 195, // 0xc3
- bc_wide = 196, // 0xc4
- bc_multianewarray = 197, // 0xc5
- bc_ifnull = 198, // 0xc6
- bc_ifnonnull = 199, // 0xc7
- bc_goto_w = 200, // 0xc8
- bc_jsr_w = 201, // 0xc9
- bc_bytecode_limit = 202 // 0xca
-};
-
-enum
-{
- bc_end_marker = 255,
- bc_byte_escape = 254,
- bc_ref_escape = 253,
- _first_linker_op = bc_getstatic,
- _last_linker_op = bc_invokestatic,
- _num_linker_ops = (_last_linker_op - _first_linker_op) + 1,
- _self_linker_op = bc_bytecode_limit,
- _self_linker_aload_flag = 1 * _num_linker_ops,
- _self_linker_super_flag = 2 * _num_linker_ops,
- _self_linker_limit = _self_linker_op + 4 * _num_linker_ops,
- _invokeinit_op = _self_linker_limit,
- _invokeinit_self_option = 0,
- _invokeinit_super_option = 1,
- _invokeinit_new_option = 2,
- _invokeinit_limit = _invokeinit_op + 3,
- _xldc_op = _invokeinit_limit,
- bc_aldc = bc_ldc,
- bc_cldc = _xldc_op + 0,
- bc_ildc = _xldc_op + 1,
- bc_fldc = _xldc_op + 2,
- bc_aldc_w = bc_ldc_w,
- bc_cldc_w = _xldc_op + 3,
- bc_ildc_w = _xldc_op + 4,
- bc_fldc_w = _xldc_op + 5,
- bc_lldc2_w = bc_ldc2_w,
- bc_dldc2_w = _xldc_op + 6,
- _xldc_limit = _xldc_op + 7,
- _xxx_3_end
-};
diff --git a/libraries/pack200/src/defines.h b/libraries/pack200/src/defines.h
deleted file mode 100644
index cfe5fc28..00000000
--- a/libraries/pack200/src/defines.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2001, 2009, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-// random definitions
-
-#ifdef _MSC_VER
-#include <windows.h>
-#include <winuser.h>
-#else
-#include <unistd.h>
-#endif
-
-// Error messages that we have
-#define ERROR_ENOMEM "Memory allocation failed"
-#define ERROR_FORMAT "Corrupted pack file"
-#define ERROR_RESOURCE "Cannot extract resource file"
-#define ERROR_OVERFLOW "Internal buffer overflow"
-#define ERROR_INTERNAL "Internal error"
-
-#define lengthof(array) (sizeof(array) / sizeof(array[0]))
-
-#define NEW(T, n) (T *) must_malloc((int)(scale_size(n, sizeof(T))))
-#define U_NEW(T, n) (T *) u->alloc(scale_size(n, sizeof(T)))
-#define T_NEW(T, n) (T *) u->temp_alloc(scale_size(n, sizeof(T)))
-
-typedef signed char byte;
-
-#ifdef _MSC_VER
-#define MKDIR(dir) mkdir(dir)
-#define getpid() _getpid()
-#define PATH_MAX MAX_PATH
-#define dup2(a, b) _dup2(a, b)
-#define strcasecmp(s1, s2) _stricmp(s1, s2)
-#define tempname _tempname
-#define sleep Sleep
-#else
-#define MKDIR(dir) mkdir(dir, 0777);
-#endif
-
-/* Must cast to void *, then size_t, then int. */
-#define ptrlowbits(x) ((int)(size_t)(void *)(x))
-
-#define DEFAULT_ARCHIVE_MODTIME 1060000000 // Aug 04, 2003 5:26 PM PDT
diff --git a/libraries/pack200/src/unpack.cpp b/libraries/pack200/src/unpack.cpp
deleted file mode 100644
index 9c4c633c..00000000
--- a/libraries/pack200/src/unpack.cpp
+++ /dev/null
@@ -1,4790 +0,0 @@
-/*
- * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-// -*- C++ -*-
-// Program for unpacking specially compressed Java packages.
-// John R. Rose
-
-/*
- * When compiling for a 64bit LP64 system (longs and pointers being 64bits),
- * the printf format %ld is correct and use of %lld will cause warning
- * errors from some compilers (gcc/g++).
- * _LP64 can be explicitly set (used on Linux).
- * Solaris compilers will define __sparcv9 or __x86_64 on 64bit compilations.
- */
-#include <cinttypes>
-
-#include <sys/types.h>
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <assert.h>
-#include <limits.h>
-#include <time.h>
-#include <stdint.h>
-
-#include "defines.h"
-#include "bytes.h"
-#include "utils.h"
-#include "coding.h"
-#include "bands.h"
-
-#include "constants.h"
-
-#include "zip.h"
-
-#include "unpack.h"
-
-// tags, in canonical order:
-static const byte TAGS_IN_ORDER[] = {
- CONSTANT_Utf8, CONSTANT_Integer, CONSTANT_Float, CONSTANT_Long,
- CONSTANT_Double, CONSTANT_String, CONSTANT_Class, CONSTANT_Signature,
- CONSTANT_NameandType, CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref};
-#define N_TAGS_IN_ORDER (sizeof TAGS_IN_ORDER)
-
-// REQUESTED must be -2 for u2 and REQUESTED_LDC must be -1 for u1
-enum
-{
- NOT_REQUESTED = 0,
- REQUESTED = -2,
- REQUESTED_LDC = -1
-};
-
-#define NO_INORD ((uint32_t) - 1)
-
-struct entry
-{
- byte tag;
- unsigned short nrefs; // pack w/ tag
-
- int outputIndex;
- uint32_t inord; // &cp.entries[cp.tag_base[this->tag]+this->inord] == this
-
- entry **refs;
-
- // put last to pack best
- union
- {
- bytes b;
- int i;
- int64_t l;
- } value;
-
- void requestOutputIndex(constant_pool &cp, int req = REQUESTED);
- int getOutputIndex()
- {
- assert(outputIndex > NOT_REQUESTED);
- return outputIndex;
- }
-
- entry *ref(int refnum)
- {
- assert((uint32_t)refnum < nrefs);
- return refs[refnum];
- }
-
- const char *utf8String()
- {
- assert(tagMatches(CONSTANT_Utf8));
- assert(value.b.len == strlen((const char *)value.b.ptr));
- return (const char *)value.b.ptr;
- }
-
- entry *className()
- {
- assert(tagMatches(CONSTANT_Class));
- return ref(0);
- }
-
- entry *memberClass()
- {
- assert(tagMatches(CONSTANT_Member));
- return ref(0);
- }
-
- entry *memberDescr()
- {
- assert(tagMatches(CONSTANT_Member));
- return ref(1);
- }
-
- entry *descrName()
- {
- assert(tagMatches(CONSTANT_NameandType));
- return ref(0);
- }
-
- entry *descrType()
- {
- assert(tagMatches(CONSTANT_NameandType));
- return ref(1);
- }
-
- int typeSize();
-
- bytes &asUtf8();
- int asInteger()
- {
- assert(tag == CONSTANT_Integer);
- return value.i;
- }
-
- bool isUtf8(bytes &b)
- {
- return tagMatches(CONSTANT_Utf8) && value.b.equals(b);
- }
-
- bool isDoubleWord()
- {
- return tag == CONSTANT_Double || tag == CONSTANT_Long;
- }
-
- bool tagMatches(byte tag2)
- {
- return (tag2 == tag) || (tag2 == CONSTANT_Utf8 && tag == CONSTANT_Signature) ||
- (tag2 == CONSTANT_Literal && tag >= CONSTANT_Integer && tag <= CONSTANT_String &&
- tag != CONSTANT_Class) ||
- (tag2 == CONSTANT_Member && tag >= CONSTANT_Fieldref &&
- tag <= CONSTANT_InterfaceMethodref);
- }
-};
-
-entry *cpindex::get(uint32_t i)
-{
- if (i >= len)
- return nullptr;
- else if (base1 != nullptr)
- // primary index
- return &base1[i];
- else
- // secondary index
- return base2[i];
-}
-
-inline bytes &entry::asUtf8()
-{
- assert(tagMatches(CONSTANT_Utf8));
- return value.b;
-}
-
-int entry::typeSize()
-{
- assert(tagMatches(CONSTANT_Utf8));
- const char *sigp = (char *)value.b.ptr;
- switch (*sigp)
- {
- case '(':
- sigp++;
- break; // skip opening '('
- case 'D':
- case 'J':
- return 2; // double field
- default:
- return 1; // field
- }
- int siglen = 0;
- for (;;)
- {
- int ch = *sigp++;
- switch (ch)
- {
- case 'D':
- case 'J':
- siglen += 1;
- break;
- case '[':
- // Skip rest of array info.
- while (ch == '[')
- {
- ch = *sigp++;
- }
- if (ch != 'L')
- break;
- // else fall through
- case 'L':
- sigp = strchr(sigp, ';');
- if (sigp == nullptr)
- {
- unpack_abort("bad data");
- return 0;
- }
- sigp += 1;
- break;
- case ')': // closing ')'
- return siglen;
- }
- siglen += 1;
- }
-}
-
-inline cpindex *constant_pool::getFieldIndex(entry *classRef)
-{
- assert(classRef->tagMatches(CONSTANT_Class));
- assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]);
- return &member_indexes[classRef->inord * 2 + 0];
-}
-inline cpindex *constant_pool::getMethodIndex(entry *classRef)
-{
- assert(classRef->tagMatches(CONSTANT_Class));
- assert((uint32_t)classRef->inord < (uint32_t)tag_count[CONSTANT_Class]);
- return &member_indexes[classRef->inord * 2 + 1];
-}
-
-struct inner_class
-{
- entry *inner;
- entry *outer;
- entry *name;
- int flags;
- inner_class *next_sibling;
- bool requested;
-};
-
-// Here is where everything gets deallocated:
-void unpacker::free()
-{
- int i;
- if (jarout != nullptr)
- jarout->reset();
- if (gzin != nullptr)
- {
- gzin->free();
- gzin = nullptr;
- }
- if (free_input)
- input.free();
- /*
- * free everybody ever allocated with U_NEW or (recently) with T_NEW
- */
- assert(smallbuf.base() == nullptr || mallocs.contains(smallbuf.base()));
- assert(tsmallbuf.base() == nullptr || tmallocs.contains(tsmallbuf.base()));
- mallocs.freeAll();
- tmallocs.freeAll();
- smallbuf.init();
- tsmallbuf.init();
- bcimap.free();
- class_fixup_type.free();
- class_fixup_offset.free();
- class_fixup_ref.free();
- code_fixup_type.free();
- code_fixup_offset.free();
- code_fixup_source.free();
- requested_ics.free();
- cur_classfile_head.free();
- cur_classfile_tail.free();
- for (i = 0; i < ATTR_CONTEXT_LIMIT; i++)
- attr_defs[i].free();
-
- // free CP state
- cp.outputEntries.free();
- for (i = 0; i < CONSTANT_Limit; i++)
- cp.tag_extras[i].free();
-}
-
-// input handling
-// Attempts to advance rplimit so that (rplimit-rp) is at least 'more'.
-// Will eagerly read ahead by larger chunks, if possible.
-// Returns false if (rplimit-rp) is not at least 'more',
-// unless rplimit hits input.limit().
-bool unpacker::ensure_input(int64_t more)
-{
- uint64_t want = more - input_remaining();
- if ((int64_t)want <= 0)
- return true; // it's already in the buffer
- if (rplimit == input.limit())
- return true; // not expecting any more
-
- if (read_input_fn == nullptr)
- {
- // assume it is already all there
- bytes_read += input.limit() - rplimit;
- rplimit = input.limit();
- return true;
- }
-
- uint64_t remaining = (input.limit() - rplimit); // how much left to read?
- byte *rpgoal = (want >= remaining) ? input.limit() : rplimit + (size_t)want;
- enum
- {
- CHUNK_SIZE = (1 << 14)
- };
- uint64_t fetch = want;
- if (fetch < CHUNK_SIZE)
- fetch = CHUNK_SIZE;
- if (fetch > remaining * 3 / 4)
- fetch = remaining;
- // Try to fetch at least "more" bytes.
- while ((int64_t)fetch > 0)
- {
- int64_t nr = (*read_input_fn)(this, rplimit, fetch, remaining);
- if (nr <= 0)
- {
- return (rplimit >= rpgoal);
- }
- remaining -= nr;
- rplimit += nr;
- fetch -= nr;
- bytes_read += nr;
- assert(remaining == (uint64_t)(input.limit() - rplimit));
- }
- return true;
-}
-
-// output handling
-
-fillbytes *unpacker::close_output(fillbytes *which)
-{
- assert(wp != nullptr);
- if (which == nullptr)
- {
- if (wpbase == cur_classfile_head.base())
- {
- which = &cur_classfile_head;
- }
- else
- {
- which = &cur_classfile_tail;
- }
- }
- assert(wpbase == which->base());
- assert(wplimit == which->end());
- which->setLimit(wp);
- wp = nullptr;
- wplimit = nullptr;
- // wpbase = nullptr;
- return which;
-}
-
-// maybe_inline
-void unpacker::ensure_put_space(size_t size)
-{
- if (wp + size <= wplimit)
- return;
- // Determine which segment needs expanding.
- fillbytes *which = close_output();
- byte *wp0 = which->grow(size);
- wpbase = which->base();
- wplimit = which->end();
- wp = wp0;
-}
-
-byte *unpacker::put_space(size_t size)
-{
- byte *wp0 = wp;
- byte *wp1 = wp0 + size;
- if (wp1 > wplimit)
- {
- ensure_put_space(size);
- wp0 = wp;
- wp1 = wp0 + size;
- }
- wp = wp1;
- return wp0;
-}
-
-void unpacker::putu2_at(byte *wp, int n)
-{
- if (n != (unsigned short)n)
- {
- unpack_abort(ERROR_OVERFLOW);
- return;
- }
- wp[0] = (n) >> 8;
- wp[1] = (n) >> 0;
-}
-
-void unpacker::putu4_at(byte *wp, int n)
-{
- wp[0] = (n) >> 24;
- wp[1] = (n) >> 16;
- wp[2] = (n) >> 8;
- wp[3] = (n) >> 0;
-}
-
-void unpacker::putu8_at(byte *wp, int64_t n)
-{
- putu4_at(wp + 0, (int)((uint64_t)n >> 32));
- putu4_at(wp + 4, (int)((uint64_t)n >> 0));
-}
-
-void unpacker::putu2(int n)
-{
- putu2_at(put_space(2), n);
-}
-
-void unpacker::putu4(int n)
-{
- putu4_at(put_space(4), n);
-}
-
-void unpacker::putu8(int64_t n)
-{
- putu8_at(put_space(8), n);
-}
-
-int unpacker::putref_index(entry *e, int size)
-{
- if (e == nullptr)
- return 0;
- else if (e->outputIndex > NOT_REQUESTED)
- return e->outputIndex;
- else if (e->tag == CONSTANT_Signature)
- return putref_index(e->ref(0), size);
- else
- {
- e->requestOutputIndex(cp, -size);
- // Later on we'll fix the bits.
- class_fixup_type.addByte(size);
- class_fixup_offset.add((int)wpoffset());
- class_fixup_ref.add(e);
- return 0;
- }
-}
-
-void unpacker::putref(entry *e)
-{
- int oidx = putref_index(e, 2);
- putu2_at(put_space(2), oidx);
-}
-
-void unpacker::putu1ref(entry *e)
-{
- int oidx = putref_index(e, 1);
- putu1_at(put_space(1), oidx);
-}
-
-// Allocation of small and large blocks.
-
-enum
-{
- CHUNK = (1 << 14),
- SMALL = (1 << 9)
-};
-
-// Call malloc. Try to combine small blocks and free much later.
-void *unpacker::alloc_heap(size_t size, bool smallOK, bool temp)
-{
- if (!smallOK || size > SMALL)
- {
- void *res = must_malloc((int)size);
- (temp ? &tmallocs : &mallocs)->add(res);
- return res;
- }
- fillbytes &xsmallbuf = *(temp ? &tsmallbuf : &smallbuf);
- if (!xsmallbuf.canAppend(size + 1))
- {
- xsmallbuf.init(CHUNK);
- (temp ? &tmallocs : &mallocs)->add(xsmallbuf.base());
- }
- int growBy = (int)size;
- growBy += -growBy & 7; // round up mod 8
- return xsmallbuf.grow(growBy);
-}
-
-void unpacker::saveTo(bytes &b, byte *ptr, size_t len)
-{
- b.ptr = U_NEW(byte, add_size(len, 1));
- b.len = len;
- b.copyFrom(ptr, len);
-}
-
-// Read up through band_headers.
-// Do the archive_size dance to set the size of the input mega-buffer.
-void unpacker::read_file_header()
-{
- // Read file header to determine file type and total size.
- enum
- {
- MAGIC_BYTES = 4,
- AH_LENGTH_0 = 3, // minver, majver, options are outside of archive_size
- AH_LENGTH_0_MAX = AH_LENGTH_0 + 1, // options might have 2 bytes
- AH_LENGTH = 26, // maximum archive header length (w/ all fields)
- // Length contributions from optional header fields:
- AH_FILE_HEADER_LEN = 5, // sizehi/lo/next/modtime/files
- AH_ARCHIVE_SIZE_LEN = 2, // sizehi/lo only; part of AH_FILE_HEADER_LEN
- AH_CP_NUMBER_LEN = 4, // int/float/long/double
- AH_SPECIAL_FORMAT_LEN = 2, // layouts/band-headers
- AH_LENGTH_MIN =
- AH_LENGTH - (AH_FILE_HEADER_LEN + AH_SPECIAL_FORMAT_LEN + AH_CP_NUMBER_LEN),
- ARCHIVE_SIZE_MIN = AH_LENGTH_MIN - (AH_LENGTH_0 + AH_ARCHIVE_SIZE_LEN),
- FIRST_READ = MAGIC_BYTES + AH_LENGTH_MIN
- };
-
- assert(AH_LENGTH_MIN == 15); // # of UNSIGNED5 fields required after archive_magic
- assert(ARCHIVE_SIZE_MIN == 10); // # of UNSIGNED5 fields required after archive_size
- // An absolute minimum nullptr archive is magic[4], {minver,majver,options}[3],
- // archive_size[0], cp_counts[8], class_counts[4], for a total of 19 bytes.
- // (Note that archive_size is optional; it may be 0..10 bytes in length.)
- // The first read must capture everything up through the options field.
- // This happens to work even if {minver,majver,options} is a pathological
- // 15 bytes long. Legal pack files limit those three fields to 1+1+2 bytes.
- assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0 * B_MAX);
-
- // Up through archive_size, the largest possible archive header is
- // magic[4], {minver,majver,options}[4], archive_size[10].
- // (Note only the low 12 bits of options are allowed to be non-zero.)
- // In order to parse archive_size, we need at least this many bytes
- // in the first read. Of course, if archive_size_hi is more than
- // a byte, we probably will fail to allocate the buffer, since it
- // will be many gigabytes long. This is a practical, not an
- // architectural limit to Pack200 archive sizes.
- assert(FIRST_READ >= MAGIC_BYTES + AH_LENGTH_0_MAX + 2 * B_MAX);
-
- bool foreign_buf = (read_input_fn == nullptr);
- byte initbuf[(int)FIRST_READ + (int)C_SLOP + 200]; // 200 is for JAR I/O
- if (foreign_buf)
- {
- // inbytes is all there is
- input.set(inbytes);
- rp = input.base();
- rplimit = input.limit();
- }
- else
- {
- // inbytes, if not empty, contains some read-ahead we must use first
- // ensure_input will take care of copying it into initbuf,
- // then querying read_input_fn for any additional data needed.
- // However, the caller must assume that we use up all of inbytes.
- // There is no way to tell the caller that we used only part of them.
- // Therefore, the caller must use only a bare minimum of read-ahead.
- if (inbytes.len > FIRST_READ)
- {
- unpack_abort("too much read-ahead");
- }
- input.set(initbuf, sizeof(initbuf));
- input.b.clear();
- input.b.copyFrom(inbytes);
- rplimit = rp = input.base();
- rplimit += inbytes.len;
- bytes_read += inbytes.len;
- }
- // Read only 19 bytes, which is certain to contain #archive_options fields,
- // but is certain not to overflow past the archive_header.
- input.b.len = FIRST_READ;
- if (!ensure_input(FIRST_READ))
- unpack_abort("EOF reading archive magic number");
-
- if (rp[0] == 'P' && rp[1] == 'K')
- {
- // In the Unix-style program, we simply simulate a copy command.
- // Copy until EOF; assume the JAR file is the last segment.
- fprintf(stderr, "Copy-mode.\n");
- for (;;)
- {
- jarout->write_data(rp, (int)input_remaining());
- if (foreign_buf)
- break; // one-time use of a passed in buffer
- if (input.size() < CHUNK)
- {
- // Get some breathing room.
- input.set(U_NEW(byte, (size_t)CHUNK + C_SLOP), (size_t)CHUNK);
- }
- rp = rplimit = input.base();
- if (!ensure_input(1))
- break;
- }
- jarout->closeJarFile(false);
- return;
- }
-
- // Read the magic number.
- magic = 0;
- for (int i1 = 0; i1 < (int)sizeof(magic); i1++)
- {
- magic <<= 8;
- magic += (*rp++ & 0xFF);
- }
-
- // Read the first 3 values from the header.
- value_stream hdr;
- int hdrVals = 0;
- int hdrValsSkipped = 0; // debug only
- hdr.init(rp, rplimit, UNSIGNED5_spec);
- minver = hdr.getInt();
- majver = hdr.getInt();
- hdrVals += 2;
-
- if (magic != (int)JAVA_PACKAGE_MAGIC ||
- (majver != JAVA5_PACKAGE_MAJOR_VERSION && majver != JAVA6_PACKAGE_MAJOR_VERSION) ||
- (minver != JAVA5_PACKAGE_MINOR_VERSION && minver != JAVA6_PACKAGE_MINOR_VERSION))
- {
- char message[200];
- sprintf(message, "@" ERROR_FORMAT ": magic/ver = "
- "%08X/%d.%d should be %08X/%d.%d OR %08X/%d.%d\n",
- magic, majver, minver, JAVA_PACKAGE_MAGIC, JAVA5_PACKAGE_MAJOR_VERSION,
- JAVA5_PACKAGE_MINOR_VERSION, JAVA_PACKAGE_MAGIC, JAVA6_PACKAGE_MAJOR_VERSION,
- JAVA6_PACKAGE_MINOR_VERSION);
- unpack_abort(message);
- }
-
- archive_options = hdr.getInt();
- hdrVals += 1;
- assert(hdrVals == AH_LENGTH_0); // first three fields only
-
-#define ORBIT(bit) | (bit)
- int OPTION_LIMIT = (0 ARCHIVE_BIT_DO(ORBIT));
-#undef ORBIT
- if ((archive_options & ~OPTION_LIMIT) != 0)
- {
- fprintf(stderr, "Warning: Illegal archive options 0x%x\n", archive_options);
- unpack_abort("illegal archive options");
- return;
- }
-
- if ((archive_options & AO_HAVE_FILE_HEADERS) != 0)
- {
- uint32_t hi = hdr.getInt();
- uint32_t lo = hdr.getInt();
- uint64_t x = band::makeLong(hi, lo);
- archive_size = (size_t)x;
- if (archive_size != x)
- {
- // Silly size specified; force overflow.
- archive_size = PSIZE_MAX + 1;
- }
- hdrVals += 2;
- }
- else
- {
- hdrValsSkipped += 2;
- }
-
- // Now we can size the whole archive.
- // Read everything else into a mega-buffer.
- rp = hdr.rp;
- int header_size_0 = (int)(rp - input.base()); // used-up header (4byte + 3int)
- int header_size_1 = (int)(rplimit - rp); // buffered unused initial fragment
- int header_size = header_size_0 + header_size_1;
- unsized_bytes_read = header_size_0;
- if (foreign_buf)
- {
- if (archive_size > (size_t)header_size_1)
- {
- unpack_abort("EOF reading fixed input buffer");
- return;
- }
- }
- else if (archive_size != 0)
- {
- if (archive_size < ARCHIVE_SIZE_MIN)
- {
- unpack_abort("impossible archive size"); // bad input data
- return;
- }
- if (archive_size < (size_t)header_size_1)
- {
- unpack_abort("too much read-ahead"); // somehow we pre-fetched too much?
- return;
- }
- input.set(U_NEW(byte, add_size(header_size_0, archive_size, C_SLOP)),
- (size_t)header_size_0 + archive_size);
- assert(input.limit()[0] == 0);
- // Move all the bytes we read initially into the real buffer.
- input.b.copyFrom(initbuf, header_size);
- rp = input.b.ptr + header_size_0;
- rplimit = input.b.ptr + header_size;
- }
- else
- {
- // It's more complicated and painful.
- // A zero archive_size means that we must read until EOF.
- input.init(CHUNK * 2);
- input.b.len = input.allocated;
- rp = rplimit = input.base();
- // Set up input buffer as if we already read the header:
- input.b.copyFrom(initbuf, header_size);
- rplimit += header_size;
- while (ensure_input(input.limit() - rp))
- {
- size_t dataSoFar = input_remaining();
- size_t nextSize = add_size(dataSoFar, CHUNK);
- input.ensureSize(nextSize);
- input.b.len = input.allocated;
- rp = rplimit = input.base();
- rplimit += dataSoFar;
- }
- size_t dataSize = (rplimit - input.base());
- input.b.len = dataSize;
- input.grow(C_SLOP);
- free_input = true; // free it later
- input.b.len = dataSize;
- assert(input.limit()[0] == 0);
- rp = rplimit = input.base();
- rplimit += dataSize;
- rp += header_size_0; // already scanned these bytes...
- }
- live_input = true; // mark as "do not reuse"
-
- // read the rest of the header fields
- ensure_input((AH_LENGTH - AH_LENGTH_0) * B_MAX);
- hdr.rp = rp;
- hdr.rplimit = rplimit;
-
- if ((archive_options & AO_HAVE_FILE_HEADERS) != 0)
- {
- archive_next_count = hdr.getInt();
- if (archive_next_count < 0)
- unpack_abort("bad archive_next_count");
- archive_modtime = hdr.getInt();
- file_count = hdr.getInt();
- if (file_count < 0)
- unpack_abort("bad file_count");
- hdrVals += 3;
- }
- else
- {
- hdrValsSkipped += 3;
- }
-
- if ((archive_options & AO_HAVE_SPECIAL_FORMATS) != 0)
- {
- band_headers_size = hdr.getInt();
- if (band_headers_size < 0)
- unpack_abort("bad band_headers_size");
- attr_definition_count = hdr.getInt();
- if (attr_definition_count < 0)
- unpack_abort("bad attr_definition_count");
- hdrVals += 2;
- }
- else
- {
- hdrValsSkipped += 2;
- }
-
- int cp_counts[N_TAGS_IN_ORDER];
- for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++)
- {
- if (!(archive_options & AO_HAVE_CP_NUMBERS))
- {
- switch (TAGS_IN_ORDER[k])
- {
- case CONSTANT_Integer:
- case CONSTANT_Float:
- case CONSTANT_Long:
- case CONSTANT_Double:
- cp_counts[k] = 0;
- hdrValsSkipped += 1;
- continue;
- }
- }
- cp_counts[k] = hdr.getInt();
- if (cp_counts[k] < 0)
- unpack_abort("bad cp_counts");
- hdrVals += 1;
- }
-
- ic_count = hdr.getInt();
- if (ic_count < 0)
- unpack_abort("bad ic_count");
-
- default_class_minver = hdr.getInt();
- default_class_majver = hdr.getInt();
-
- class_count = hdr.getInt();
- if (class_count < 0)
- unpack_abort("bad class_count");
-
- hdrVals += 4;
-
- // done with archive_header
- hdrVals += hdrValsSkipped;
- assert(hdrVals == AH_LENGTH);
-
- rp = hdr.rp;
- if (rp > rplimit)
- unpack_abort("EOF reading archive header");
-
- // Now size the CP.
- cp.init(this, cp_counts);
-
- default_file_modtime = archive_modtime;
- if (default_file_modtime == 0 && !(archive_options & AO_HAVE_FILE_MODTIME))
- default_file_modtime = DEFAULT_ARCHIVE_MODTIME; // taken from driver
- if ((archive_options & AO_DEFLATE_HINT) != 0)
- default_file_options |= FO_DEFLATE_HINT;
-
- // meta-bytes, if any, immediately follow archive header
- // band_headers.readData(band_headers_size);
- ensure_input(band_headers_size);
- if (input_remaining() < (size_t)band_headers_size)
- {
- unpack_abort("EOF reading band headers");
- return;
- }
- bytes band_headers;
- // The "1+" allows an initial byte to be pushed on the front.
- band_headers.set(1 + U_NEW(byte, 1 + band_headers_size + C_SLOP), band_headers_size);
-
- // Start scanning band headers here:
- band_headers.copyFrom(rp, band_headers.len);
- rp += band_headers.len;
- assert(rp <= rplimit);
- meta_rp = band_headers.ptr;
- // Put evil meta-codes at the end of the band headers,
- // so we are sure to throw an error if we run off the end.
- bytes::of(band_headers.limit(), C_SLOP).clear(_meta_error);
-}
-
-void unpacker::finish()
-{
- if (verbose >= 1)
- {
- fprintf(stderr, "A total of %" PRIu64 " bytes were read in %d segment(s).\n",
- (bytes_read_before_reset + bytes_read), segments_read_before_reset + 1);
- fprintf(stderr, "A total of %" PRIu64 " file content bytes were written.\n",
- (bytes_written_before_reset + bytes_written));
- fprintf(stderr,
- "A total of %d files (of which %d are classes) were written to output.\n",
- files_written_before_reset + files_written,
- classes_written_before_reset + classes_written);
- }
- if (jarout != nullptr)
- jarout->closeJarFile(true);
-}
-
-// Cf. PackageReader.readConstantPoolCounts
-void constant_pool::init(unpacker *u_, int counts[NUM_COUNTS])
-{
- this->u = u_;
-
- // Fill-pointer for CP.
- int next_entry = 0;
-
- // Size the constant pool:
- for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++)
- {
- byte tag = TAGS_IN_ORDER[k];
- int len = counts[k];
- tag_count[tag] = len;
- tag_base[tag] = next_entry;
- next_entry += len;
- // Detect and defend against constant pool size overflow.
- // (Pack200 forbids the sum of CP counts to exceed 2^29-1.)
- enum
- {
- CP_SIZE_LIMIT = (1 << 29),
- IMPLICIT_ENTRY_COUNT = 1 // empty Utf8 string
- };
- if (len >= (1 << 29) || len < 0 || next_entry >= CP_SIZE_LIMIT + IMPLICIT_ENTRY_COUNT)
- {
- unpack_abort("archive too large: constant pool limit exceeded");
- }
- }
-
- // Close off the end of the CP:
- nentries = next_entry;
-
- // place a limit on future CP growth:
- int generous = 0;
- generous = add_size(generous, u->ic_count); // implicit name
- generous = add_size(generous, u->ic_count); // outer
- generous = add_size(generous, u->ic_count); // outer.utf8
- generous = add_size(generous, 40); // WKUs, misc
- generous = add_size(generous, u->class_count); // implicit SourceFile strings
- maxentries = add_size(nentries, generous);
-
- // Note that this CP does not include "empty" entries
- // for longs and doubles. Those are introduced when
- // the entries are renumbered for classfile output.
-
- entries = U_NEW(entry, maxentries);
-
- first_extra_entry = &entries[nentries];
-
- // Initialize the standard indexes.
- tag_count[CONSTANT_All] = nentries;
- tag_base[CONSTANT_All] = 0;
- for (int tag = 0; tag < CONSTANT_Limit; tag++)
- {
- entry *cpMap = &entries[tag_base[tag]];
- tag_index[tag].init(tag_count[tag], cpMap, tag);
- }
-
- // Initialize hashTab to a generous power-of-two size.
- uint32_t pow2 = 1;
- uint32_t target = maxentries + maxentries / 2; // 60% full
- while (pow2 < target)
- pow2 <<= 1;
- hashTab = U_NEW(entry *, hashTabLength = pow2);
-}
-
-static byte *store_Utf8_char(byte *cp, unsigned short ch)
-{
- if (ch >= 0x001 && ch <= 0x007F)
- {
- *cp++ = (byte)ch;
- }
- else if (ch <= 0x07FF)
- {
- *cp++ = (byte)(0xC0 | ((ch >> 6) & 0x1F));
- *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F));
- }
- else
- {
- *cp++ = (byte)(0xE0 | ((ch >> 12) & 0x0F));
- *cp++ = (byte)(0x80 | ((ch >> 6) & 0x3F));
- *cp++ = (byte)(0x80 | ((ch >> 0) & 0x3F));
- }
- return cp;
-}
-
-static byte *skip_Utf8_chars(byte *cp, int len)
-{
- for (;; cp++)
- {
- int ch = *cp & 0xFF;
- if ((ch & 0xC0) != 0x80)
- {
- if (len-- == 0)
- return cp;
- if (ch < 0x80 && len == 0)
- return cp + 1;
- }
- }
-}
-
-static int compare_Utf8_chars(bytes &b1, bytes &b2)
-{
- int l1 = (int)b1.len;
- int l2 = (int)b2.len;
- int l0 = (l1 < l2) ? l1 : l2;
- byte *p1 = b1.ptr;
- byte *p2 = b2.ptr;
- int c0 = 0;
- for (int i = 0; i < l0; i++)
- {
- int c1 = p1[i] & 0xFF;
- int c2 = p2[i] & 0xFF;
- if (c1 != c2)
- {
- // Before returning the obvious answer,
- // check to see if c1 or c2 is part of a 0x0000,
- // which encodes as {0xC0,0x80}. The 0x0000 is the
- // lowest-sorting Java char value, and yet it encodes
- // as if it were the first char after 0x7F, which causes
- // strings containing nulls to sort too high. All other
- // comparisons are consistent between Utf8 and Java chars.
- if (c1 == 0xC0 && (p1[i + 1] & 0xFF) == 0x80)
- c1 = 0;
- if (c2 == 0xC0 && (p2[i + 1] & 0xFF) == 0x80)
- c2 = 0;
- if (c0 == 0xC0)
- {
- assert(((c1 | c2) & 0xC0) == 0x80); // c1 & c2 are extension chars
- if (c1 == 0x80)
- c1 = 0; // will sort below c2
- if (c2 == 0x80)
- c2 = 0; // will sort below c1
- }
- return c1 - c2;
- }
- c0 = c1; // save away previous char
- }
- // common prefix is identical; return length difference if any
- return l1 - l2;
-}
-
-// Cf. PackageReader.readUtf8Bands
-void unpacker::read_Utf8_values(entry *cpMap, int len)
-{
- // Implicit first Utf8 string is the empty string.
- enum
- {
- // certain bands begin with implicit zeroes
- PREFIX_SKIP_2 = 2,
- SUFFIX_SKIP_1 = 1
- };
-
- int i;
-
- // First band: Read lengths of shared prefixes.
- if (len > PREFIX_SKIP_2)
- cp_Utf8_prefix.readData(len - PREFIX_SKIP_2);
-
- // Second band: Read lengths of unshared suffixes:
- if (len > SUFFIX_SKIP_1)
- cp_Utf8_suffix.readData(len - SUFFIX_SKIP_1);
-
- bytes *allsuffixes = T_NEW(bytes, len);
-
- int nbigsuf = 0;
- fillbytes charbuf; // buffer to allocate small strings
- charbuf.init();
-
- // Third band: Read the char values in the unshared suffixes:
- cp_Utf8_chars.readData(cp_Utf8_suffix.getIntTotal());
- for (i = 0; i < len; i++)
- {
- int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt();
- if (suffix < 0)
- {
- unpack_abort("bad utf8 suffix");
- }
- if (suffix == 0 && i >= SUFFIX_SKIP_1)
- {
- // chars are packed in cp_Utf8_big_chars
- nbigsuf += 1;
- continue;
- }
- bytes &chars = allsuffixes[i];
- uint32_t size3 = suffix * 3; // max Utf8 length
- bool isMalloc = (suffix > SMALL);
- if (isMalloc)
- {
- chars.malloc(size3);
- }
- else
- {
- if (!charbuf.canAppend(size3 + 1))
- {
- assert(charbuf.allocated == 0 || tmallocs.contains(charbuf.base()));
- charbuf.init(CHUNK); // Reset to new buffer.
- tmallocs.add(charbuf.base());
- }
- chars.set(charbuf.grow(size3 + 1), size3);
- }
-
- byte *chp = chars.ptr;
- for (int j = 0; j < suffix; j++)
- {
- unsigned short ch = cp_Utf8_chars.getInt();
- chp = store_Utf8_char(chp, ch);
- }
- // shrink to fit:
- if (isMalloc)
- {
- chars.realloc(chp - chars.ptr);
- tmallocs.add(chars.ptr); // free it later
- }
- else
- {
- int shrink = (int)(chars.limit() - chp);
- chars.len -= shrink;
- charbuf.b.len -= shrink; // ungrow to reclaim buffer space
- // Note that we did not reclaim the final '\0'.
- assert(chars.limit() == charbuf.limit() - 1);
- assert(strlen((char *)chars.ptr) == chars.len);
- }
- }
- // cp_Utf8_chars.done();
-
- // Fourth band: Go back and size the specially packed strings.
- int maxlen = 0;
- cp_Utf8_big_suffix.readData(nbigsuf);
- cp_Utf8_suffix.rewind();
- for (i = 0; i < len; i++)
- {
- int suffix = (i < SUFFIX_SKIP_1) ? 0 : cp_Utf8_suffix.getInt();
- int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt();
- if (prefix < 0 || prefix + suffix < 0)
- {
- unpack_abort("bad utf8 prefix");
- }
- bytes &chars = allsuffixes[i];
- if (suffix == 0 && i >= SUFFIX_SKIP_1)
- {
- suffix = cp_Utf8_big_suffix.getInt();
- assert(chars.ptr == nullptr);
- chars.len = suffix; // just a momentary hack
- }
- else
- {
- assert(chars.ptr != nullptr);
- }
- if (maxlen < prefix + suffix)
- {
- maxlen = prefix + suffix;
- }
- }
- // cp_Utf8_suffix.done(); // will use allsuffixes[i].len (ptr!=nullptr)
- // cp_Utf8_big_suffix.done(); // will use allsuffixes[i].len
-
- // Fifth band(s): Get the specially packed characters.
- cp_Utf8_big_suffix.rewind();
- for (i = 0; i < len; i++)
- {
- bytes &chars = allsuffixes[i];
- if (chars.ptr != nullptr)
- continue; // already input
- int suffix = (int)chars.len; // pick up the hack
- uint32_t size3 = suffix * 3;
- if (suffix == 0)
- continue; // done with empty string
- chars.malloc(size3);
- byte *chp = chars.ptr;
- band saved_band = cp_Utf8_big_chars;
- cp_Utf8_big_chars.readData(suffix);
- for (int j = 0; j < suffix; j++)
- {
- unsigned short ch = cp_Utf8_big_chars.getInt();
- chp = store_Utf8_char(chp, ch);
- }
- chars.realloc(chp - chars.ptr);
- tmallocs.add(chars.ptr); // free it later
- // cp_Utf8_big_chars.done();
- cp_Utf8_big_chars = saved_band; // reset the band for the next string
- }
- cp_Utf8_big_chars.readData(0); // zero chars
- // cp_Utf8_big_chars.done();
-
- // Finally, sew together all the prefixes and suffixes.
- bytes bigbuf;
- bigbuf.malloc(maxlen * 3 + 1); // max Utf8 length, plus slop for nullptr
- int prevlen = 0; // previous string length (in chars)
- tmallocs.add(bigbuf.ptr); // free after this block
- cp_Utf8_prefix.rewind();
- for (i = 0; i < len; i++)
- {
- bytes &chars = allsuffixes[i];
- int prefix = (i < PREFIX_SKIP_2) ? 0 : cp_Utf8_prefix.getInt();
- int suffix = (int)chars.len;
- byte *fillp;
- // by induction, the buffer is already filled with the prefix
- // make sure the prefix value is not corrupted, though:
- if (prefix > prevlen)
- {
- unpack_abort("utf8 prefix overflow");
- return;
- }
- fillp = skip_Utf8_chars(bigbuf.ptr, prefix);
- // copy the suffix into the same buffer:
- fillp = chars.writeTo(fillp);
- assert(bigbuf.inBounds(fillp));
- *fillp = 0; // bigbuf must contain a well-formed Utf8 string
- int length = (int)(fillp - bigbuf.ptr);
- bytes &value = cpMap[i].value.b;
- value.set(U_NEW(byte, add_size(length, 1)), length);
- value.copyFrom(bigbuf.ptr, length);
- // Index all Utf8 strings
- entry *&htref = cp.hashTabRef(CONSTANT_Utf8, value);
- if (htref == nullptr)
- {
- // Note that if two identical strings are transmitted,
- // the first is taken to be the canonical one.
- htref = &cpMap[i];
- }
- prevlen = prefix + suffix;
- }
- // cp_Utf8_prefix.done();
-
- // Free intermediate buffers.
- free_temps();
-}
-
-void unpacker::read_single_words(band &cp_band, entry *cpMap, int len)
-{
- cp_band.readData(len);
- for (int i = 0; i < len; i++)
- {
- cpMap[i].value.i = cp_band.getInt(); // coding handles signs OK
- }
-}
-
-void unpacker::read_double_words(band &cp_bands, entry *cpMap, int len)
-{
- band &cp_band_hi = cp_bands;
- band &cp_band_lo = cp_bands.nextBand();
- cp_band_hi.readData(len);
- cp_band_lo.readData(len);
- for (int i = 0; i < len; i++)
- {
- cpMap[i].value.l = cp_band_hi.getLong(cp_band_lo, true);
- }
- // cp_band_hi.done();
- // cp_band_lo.done();
-}
-
-void unpacker::read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len)
-{
- assert(refTag == CONSTANT_Utf8);
- cp_band.setIndexByTag(refTag);
- cp_band.readData(len);
- int indexTag = (cp_band.bn == e_cp_Class) ? CONSTANT_Class : 0;
- for (int i = 0; i < len; i++)
- {
- entry &e = cpMap[i];
- e.refs = U_NEW(entry *, e.nrefs = 1);
- entry *utf = cp_band.getRef();
- e.refs[0] = utf;
- e.value.b = utf->value.b; // copy value of Utf8 string to self
- if (indexTag != 0)
- {
- // Maintain cross-reference:
- entry *&htref = cp.hashTabRef(indexTag, e.value.b);
- if (htref == nullptr)
- {
- // Note that if two identical classes are transmitted,
- // the first is taken to be the canonical one.
- htref = &e;
- }
- }
- }
- // cp_band.done();
-}
-
-void unpacker::read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap,
- int len)
-{
- band &cp_band1 = cp_band;
- band &cp_band2 = cp_band.nextBand();
- cp_band1.setIndexByTag(ref1Tag);
- cp_band2.setIndexByTag(ref2Tag);
- cp_band1.readData(len);
- cp_band2.readData(len);
- for (int i = 0; i < len; i++)
- {
- entry &e = cpMap[i];
- e.refs = U_NEW(entry *, e.nrefs = 2);
- e.refs[0] = cp_band1.getRef();
- e.refs[1] = cp_band2.getRef();
- }
- // cp_band1.done();
- // cp_band2.done();
-}
-
-// Cf. PackageReader.readSignatureBands
-void unpacker::read_signature_values(entry *cpMap, int len)
-{
- cp_Signature_form.setIndexByTag(CONSTANT_Utf8);
- cp_Signature_form.readData(len);
- int ncTotal = 0;
- int i;
- for (i = 0; i < len; i++)
- {
- entry &e = cpMap[i];
- entry &form = *cp_Signature_form.getRef();
- int nc = 0;
-
- for (const char *ncp = form.utf8String(); *ncp; ncp++)
- {
- if (*ncp == 'L')
- nc++;
- }
-
- ncTotal += nc;
- e.refs = U_NEW(entry *, cpMap[i].nrefs = 1 + nc);
- e.refs[0] = &form;
- }
- // cp_Signature_form.done();
- cp_Signature_classes.setIndexByTag(CONSTANT_Class);
- cp_Signature_classes.readData(ncTotal);
- for (i = 0; i < len; i++)
- {
- entry &e = cpMap[i];
- for (int j = 1; j < e.nrefs; j++)
- {
- e.refs[j] = cp_Signature_classes.getRef();
- }
- }
- // cp_Signature_classes.done();
-}
-
-// Cf. PackageReader.readConstantPool
-void unpacker::read_cp()
-{
- int i;
-
- for (int k = 0; k < (int)N_TAGS_IN_ORDER; k++)
- {
- byte tag = TAGS_IN_ORDER[k];
- int len = cp.tag_count[tag];
- int base = cp.tag_base[tag];
-
- entry *cpMap = &cp.entries[base];
- for (i = 0; i < len; i++)
- {
- cpMap[i].tag = tag;
- cpMap[i].inord = i;
- }
-
- switch (tag)
- {
- case CONSTANT_Utf8:
- read_Utf8_values(cpMap, len);
- break;
- case CONSTANT_Integer:
- read_single_words(cp_Int, cpMap, len);
- break;
- case CONSTANT_Float:
- read_single_words(cp_Float, cpMap, len);
- break;
- case CONSTANT_Long:
- read_double_words(cp_Long_hi /*& cp_Long_lo*/, cpMap, len);
- break;
- case CONSTANT_Double:
- read_double_words(cp_Double_hi /*& cp_Double_lo*/, cpMap, len);
- break;
- case CONSTANT_String:
- read_single_refs(cp_String, CONSTANT_Utf8, cpMap, len);
- break;
- case CONSTANT_Class:
- read_single_refs(cp_Class, CONSTANT_Utf8, cpMap, len);
- break;
- case CONSTANT_Signature:
- read_signature_values(cpMap, len);
- break;
- case CONSTANT_NameandType:
- read_double_refs(cp_Descr_name /*& cp_Descr_type*/, CONSTANT_Utf8,
- CONSTANT_Signature, cpMap, len);
- break;
- case CONSTANT_Fieldref:
- read_double_refs(cp_Field_class /*& cp_Field_desc*/, CONSTANT_Class,
- CONSTANT_NameandType, cpMap, len);
- break;
- case CONSTANT_Methodref:
- read_double_refs(cp_Method_class /*& cp_Method_desc*/, CONSTANT_Class,
- CONSTANT_NameandType, cpMap, len);
- break;
- case CONSTANT_InterfaceMethodref:
- read_double_refs(cp_Imethod_class /*& cp_Imethod_desc*/, CONSTANT_Class,
- CONSTANT_NameandType, cpMap, len);
- break;
- default:
- assert(false);
- break;
- }
- }
-
- cp.expandSignatures();
- cp.initMemberIndexes();
-
-#define SNAME(n, s) #s "\0"
- const char *symNames = (ALL_ATTR_DO(SNAME) "<init>");
-#undef SNAME
-
- for (int sn = 0; sn < constant_pool::s_LIMIT; sn++)
- {
- assert(symNames[0] >= '0' && symNames[0] <= 'Z'); // sanity
- bytes name;
- name.set(symNames);
- if (name.len > 0 && name.ptr[0] != '0')
- {
- cp.sym[sn] = cp.ensureUtf8(name);
- }
- symNames += name.len + 1; // skip trailing nullptr to next name
- }
-
- band::initIndexes(this);
-}
-
-static band *no_bands[] = {nullptr}; // shared empty body
-
-inline band &unpacker::attr_definitions::fixed_band(int e_class_xxx)
-{
- return u->all_bands[xxx_flags_hi_bn + (e_class_xxx - e_class_flags_hi)];
-}
-inline band &unpacker::attr_definitions::xxx_flags_hi()
-{
- return fixed_band(e_class_flags_hi);
-}
-inline band &unpacker::attr_definitions::xxx_flags_lo()
-{
- return fixed_band(e_class_flags_lo);
-}
-inline band &unpacker::attr_definitions::xxx_attr_count()
-{
- return fixed_band(e_class_attr_count);
-}
-inline band &unpacker::attr_definitions::xxx_attr_indexes()
-{
- return fixed_band(e_class_attr_indexes);
-}
-inline band &unpacker::attr_definitions::xxx_attr_calls()
-{
- return fixed_band(e_class_attr_calls);
-}
-
-inline unpacker::layout_definition *
-unpacker::attr_definitions::defineLayout(int idx, entry *nameEntry, const char *layout)
-{
- const char *name = nameEntry->value.b.strval();
- layout_definition *lo = defineLayout(idx, name, layout);
- lo->nameEntry = nameEntry;
- return lo;
-}
-
-unpacker::layout_definition *unpacker::attr_definitions::defineLayout(int idx, const char *name,
- const char *layout)
-{
- assert(flag_limit != 0); // must be set up already
- if (idx >= 0)
- {
- // Fixed attr.
- if (idx >= (int)flag_limit)
- unpack_abort("attribute index too large");
- if (isRedefined(idx))
- unpack_abort("redefined attribute index");
- redef |= ((uint64_t)1 << idx);
- }
- else
- {
- idx = flag_limit + overflow_count.length();
- overflow_count.add(0); // make a new counter
- }
- layout_definition *lo = U_NEW(layout_definition, 1);
- lo->idx = idx;
- lo->name = name;
- lo->layout = layout;
- for (int adds = (idx + 1) - layouts.length(); adds > 0; adds--)
- {
- layouts.add(nullptr);
- }
- layouts.get(idx) = lo;
- return lo;
-}
-
-band **unpacker::attr_definitions::buildBands(unpacker::layout_definition *lo)
-{
- int i;
- if (lo->elems != nullptr)
- return lo->bands();
- if (lo->layout[0] == '\0')
- {
- lo->elems = no_bands;
- }
- else
- {
- // Create bands for this attribute by parsing the layout.
- bool hasCallables = lo->hasCallables();
- bands_made = 0x10000; // base number for bands made
- const char *lp = lo->layout;
- lp = parseLayout(lp, lo->elems, -1);
- if (lp[0] != '\0' || band_stack.length() > 0)
- {
- unpack_abort("garbage at end of layout");
- }
- band_stack.popTo(0);
-
- // Fix up callables to point at their callees.
- band **bands = lo->elems;
- assert(bands == lo->bands());
- int num_callables = 0;
- if (hasCallables)
- {
- while (bands[num_callables] != nullptr)
- {
- if (bands[num_callables]->le_kind != EK_CBLE)
- {
- unpack_abort("garbage mixed with callables");
- break;
- }
- num_callables += 1;
- }
- }
- for (i = 0; i < calls_to_link.length(); i++)
- {
- band &call = *(band *)calls_to_link.get(i);
- assert(call.le_kind == EK_CALL);
- // Determine the callee.
- int call_num = call.le_len;
- if (call_num < 0 || call_num >= num_callables)
- {
- unpack_abort("bad call in layout");
- break;
- }
- band &cble = *bands[call_num];
- // Link the call to it.
- call.le_body[0] = &cble;
- // Distinguish backward calls and callables:
- assert(cble.le_kind == EK_CBLE);
- // FIXME: hit this one
- // assert(cble.le_len == call_num);
- cble.le_back |= call.le_back;
- }
- calls_to_link.popTo(0);
- }
- return lo->elems;
-}
-
-/* attribute layout language parser
-
- attribute_layout:
- ( layout_element )* | ( callable )+
- layout_element:
- ( integral | replication | union | call | reference )
-
- callable:
- '[' body ']'
- body:
- ( layout_element )+
-
- integral:
- ( unsigned_int | signed_int | bc_index | bc_offset | flag )
- unsigned_int:
- uint_type
- signed_int:
- 'S' uint_type
- any_int:
- ( unsigned_int | signed_int )
- bc_index:
- ( 'P' uint_type | 'PO' uint_type )
- bc_offset:
- 'O' any_int
- flag:
- 'F' uint_type
- uint_type:
- ( 'B' | 'H' | 'I' | 'V' )
-
- replication:
- 'N' uint_type '[' body ']'
-
- union:
- 'T' any_int (union_case)* '(' ')' '[' (body)? ']'
- union_case:
- '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']'
- union_case_tag:
- ( numeral | numeral '-' numeral )
- call:
- '(' numeral ')'
-
- reference:
- reference_type ( 'N' )? uint_type
- reference_type:
- ( constant_ref | schema_ref | utf8_ref | untyped_ref )
- constant_ref:
- ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' )
- schema_ref:
- ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' )
- utf8_ref:
- 'RU'
- untyped_ref:
- 'RQ'
-
- numeral:
- '(' ('-')? (digit)+ ')'
- digit:
- ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
-
-*/
-
-const char *unpacker::attr_definitions::parseIntLayout(const char *lp, band *&res, byte le_kind,
- bool can_be_signed)
-{
- band *b = U_NEW(band, 1);
- char le = *lp++;
- int spec = UNSIGNED5_spec;
- if (le == 'S' && can_be_signed)
- {
- // Note: This is the last use of sign. There is no 'EF_SIGN'.
- spec = SIGNED5_spec;
- le = *lp++;
- }
- else if (le == 'B')
- {
- spec = BYTE1_spec; // unsigned byte
- }
- b->init(u, bands_made++, spec);
- b->le_kind = le_kind;
- int le_len = 0;
- switch (le)
- {
- case 'B':
- le_len = 1;
- break;
- case 'H':
- le_len = 2;
- break;
- case 'I':
- le_len = 4;
- break;
- case 'V':
- le_len = 0;
- break;
- default:
- unpack_abort("bad layout element");
- }
- b->le_len = le_len;
- band_stack.add(b);
- res = b;
- return lp;
-}
-
-const char *unpacker::attr_definitions::parseNumeral(const char *lp, int &res)
-{
- bool sgn = false;
- if (*lp == '0')
- {
- res = 0;
- return lp + 1;
- } // special case '0'
- if (*lp == '-')
- {
- sgn = true;
- lp++;
- }
- const char *dp = lp;
- int con = 0;
- while (*dp >= '0' && *dp <= '9')
- {
- int con0 = con;
- con *= 10;
- con += (*dp++) - '0';
- if (con <= con0)
- {
- con = -1;
- break;
- } // numeral overflow
- }
- if (lp == dp)
- {
- unpack_abort("missing numeral in layout");
- }
- lp = dp;
- if (con < 0 && !(sgn && con == -con))
- {
- // (Portability note: Misses the error if int is not 32 bits.)
- unpack_abort("numeral overflow");
- }
- if (sgn)
- con = -con;
- res = con;
- return lp;
-}
-
-band **unpacker::attr_definitions::popBody(int bs_base)
-{
- // Return everything that was pushed, as a nullptr-terminated pointer array.
- int bs_limit = band_stack.length();
- if (bs_base == bs_limit)
- {
- return no_bands;
- }
- else
- {
- int nb = bs_limit - bs_base;
- band **res = U_NEW(band *, add_size(nb, 1));
- for (int i = 0; i < nb; i++)
- {
- band *b = (band *)band_stack.get(bs_base + i);
- res[i] = b;
- }
- band_stack.popTo(bs_base);
- return res;
- }
-}
-
-const char *unpacker::attr_definitions::parseLayout(const char *lp, band **&res, int curCble)
-{
- int bs_base = band_stack.length();
- bool top_level = (bs_base == 0);
- band *b;
- enum
- {
- can_be_signed = true
- }; // optional arg to parseIntLayout
-
- for (bool done = false; !done;)
- {
- switch (*lp++)
- {
- case 'B':
- case 'H':
- case 'I':
- case 'V': // unsigned_int
- case 'S': // signed_int
- --lp; // reparse
- case 'F':
- lp = parseIntLayout(lp, b, EK_INT);
- break;
- case 'P':
- {
- int le_bci = EK_BCI;
- if (*lp == 'O')
- {
- ++lp;
- le_bci = EK_BCID;
- }
- assert(*lp != 'S'); // no PSH, etc.
- lp = parseIntLayout(lp, b, EK_INT);
- b->le_bci = le_bci;
- if (le_bci == EK_BCI)
- b->defc = coding::findBySpec(BCI5_spec);
- else
- b->defc = coding::findBySpec(BRANCH5_spec);
- }
- break;
- case 'O':
- lp = parseIntLayout(lp, b, EK_INT, can_be_signed);
- b->le_bci = EK_BCO;
- b->defc = coding::findBySpec(BRANCH5_spec);
- break;
- case 'N': // replication: 'N' uint32_t '[' elem ... ']'
- lp = parseIntLayout(lp, b, EK_REPL);
- assert(*lp == '[');
- ++lp;
- lp = parseLayout(lp, b->le_body, curCble);
- break;
- case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']'
- lp = parseIntLayout(lp, b, EK_UN, can_be_signed);
- {
- int union_base = band_stack.length();
- for (;;)
- { // for each case
- band &k_case = *U_NEW(band, 1);
- band_stack.add(&k_case);
- k_case.le_kind = EK_CASE;
- k_case.bn = bands_made++;
- if (*lp++ != '(')
- {
- unpack_abort("bad union case");
- return "";
- }
- if (*lp++ != ')')
- {
- --lp; // reparse
- // Read some case values. (Use band_stack for temp. storage.)
- int case_base = band_stack.length();
- for (;;)
- {
- int caseval = 0;
- lp = parseNumeral(lp, caseval);
- band_stack.add((void *)(size_t)caseval);
- if (*lp == '-')
- {
- // new in version 160, allow (1-5) for (1,2,3,4,5)
- if (u->majver < JAVA6_PACKAGE_MAJOR_VERSION)
- {
- unpack_abort(
- "bad range in union case label (old archive format)");
- return "";
- }
- int caselimit = caseval;
- lp++;
- lp = parseNumeral(lp, caselimit);
- if (caseval >= caselimit ||
- (uint32_t)(caselimit - caseval) > 0x10000)
- {
- // Note: 0x10000 is arbitrary implementation restriction.
- // We can remove it later if it's important to.
- unpack_abort("bad range in union case label");
- }
- for (;;)
- {
- ++caseval;
- band_stack.add((void *)(size_t)caseval);
- if (caseval == caselimit)
- break;
- }
- }
- if (*lp != ',')
- break;
- lp++;
- }
- if (*lp++ != ')')
- {
- unpack_abort("bad case label");
- }
- // save away the case labels
- int ntags = band_stack.length() - case_base;
- int *tags = U_NEW(int, add_size(ntags, 1));
- k_case.le_casetags = tags;
- *tags++ = ntags;
- for (int i = 0; i < ntags; i++)
- {
- *tags++ = ptrlowbits(band_stack.get(case_base + i));
- }
- band_stack.popTo(case_base);
- }
- // Got le_casetags. Now grab the body.
- assert(*lp == '[');
- ++lp;
- lp = parseLayout(lp, k_case.le_body, curCble);
- if (k_case.le_casetags == nullptr)
- break; // done
- }
- b->le_body = popBody(union_base);
- }
- break;
- case '(': // call: '(' -?NN* ')'
- {
- band &call = *U_NEW(band, 1);
- band_stack.add(&call);
- call.le_kind = EK_CALL;
- call.bn = bands_made++;
- call.le_body = U_NEW(band *, 2); // fill in later
- int call_num = 0;
- lp = parseNumeral(lp, call_num);
- call.le_back = (call_num <= 0);
- call_num += curCble; // numeral is self-relative offset
- call.le_len = call_num; // use le_len as scratch
- calls_to_link.add(&call);
- if (*lp++ != ')')
- {
- unpack_abort("bad call label");
- }
- }
- break;
- case 'K': // reference_type: constant_ref
- case 'R': // reference_type: schema_ref
- {
- int ixTag = CONSTANT_None;
- if (lp[-1] == 'K')
- {
- switch (*lp++)
- {
- case 'I':
- ixTag = CONSTANT_Integer;
- break;
- case 'J':
- ixTag = CONSTANT_Long;
- break;
- case 'F':
- ixTag = CONSTANT_Float;
- break;
- case 'D':
- ixTag = CONSTANT_Double;
- break;
- case 'S':
- ixTag = CONSTANT_String;
- break;
- case 'Q':
- ixTag = CONSTANT_Literal;
- break;
- }
- }
- else
- {
- switch (*lp++)
- {
- case 'C':
- ixTag = CONSTANT_Class;
- break;
- case 'S':
- ixTag = CONSTANT_Signature;
- break;
- case 'D':
- ixTag = CONSTANT_NameandType;
- break;
- case 'F':
- ixTag = CONSTANT_Fieldref;
- break;
- case 'M':
- ixTag = CONSTANT_Methodref;
- break;
- case 'I':
- ixTag = CONSTANT_InterfaceMethodref;
- break;
- case 'U':
- ixTag = CONSTANT_Utf8;
- break; // utf8_ref
- case 'Q':
- ixTag = CONSTANT_All;
- break; // untyped_ref
- }
- }
- if (ixTag == CONSTANT_None)
- {
- unpack_abort("bad reference layout");
- break;
- }
- bool nullOK = false;
- if (*lp == 'N')
- {
- nullOK = true;
- lp++;
- }
- lp = parseIntLayout(lp, b, EK_REF);
- b->defc = coding::findBySpec(UNSIGNED5_spec);
- b->initRef(ixTag, nullOK);
- }
- break;
- case '[':
- {
- // [callable1][callable2]...
- if (!top_level)
- {
- unpack_abort("bad nested callable");
- break;
- }
- curCble += 1;
- band &cble = *U_NEW(band, 1);
- band_stack.add(&cble);
- cble.le_kind = EK_CBLE;
- cble.bn = bands_made++;
- lp = parseLayout(lp, cble.le_body, curCble);
- }
- break;
- case ']':
- // Hit a closing brace. This ends whatever body we were in.
- done = true;
- break;
- case '\0':
- // Hit a nullptr. Also ends the (top-level) body.
- --lp; // back up, so caller can see the nullptr also
- done = true;
- break;
- default:
- unpack_abort("bad layout");
- }
- }
-
- // Return the accumulated bands:
- res = popBody(bs_base);
- return lp;
-}
-
-void unpacker::read_attr_defs()
-{
- int i;
-
- // Tell each AD which attrc it is and where its fixed flags are:
- attr_defs[ATTR_CONTEXT_CLASS].attrc = ATTR_CONTEXT_CLASS;
- attr_defs[ATTR_CONTEXT_CLASS].xxx_flags_hi_bn = e_class_flags_hi;
- attr_defs[ATTR_CONTEXT_FIELD].attrc = ATTR_CONTEXT_FIELD;
- attr_defs[ATTR_CONTEXT_FIELD].xxx_flags_hi_bn = e_field_flags_hi;
- attr_defs[ATTR_CONTEXT_METHOD].attrc = ATTR_CONTEXT_METHOD;
- attr_defs[ATTR_CONTEXT_METHOD].xxx_flags_hi_bn = e_method_flags_hi;
- attr_defs[ATTR_CONTEXT_CODE].attrc = ATTR_CONTEXT_CODE;
- attr_defs[ATTR_CONTEXT_CODE].xxx_flags_hi_bn = e_code_flags_hi;
-
- // Decide whether bands for the optional high flag words are present.
- attr_defs[ATTR_CONTEXT_CLASS]
- .setHaveLongFlags((archive_options & AO_HAVE_CLASS_FLAGS_HI) != 0);
- attr_defs[ATTR_CONTEXT_FIELD]
- .setHaveLongFlags((archive_options & AO_HAVE_FIELD_FLAGS_HI) != 0);
- attr_defs[ATTR_CONTEXT_METHOD]
- .setHaveLongFlags((archive_options & AO_HAVE_METHOD_FLAGS_HI) != 0);
- attr_defs[ATTR_CONTEXT_CODE]
- .setHaveLongFlags((archive_options & AO_HAVE_CODE_FLAGS_HI) != 0);
-
- // Set up built-in attrs.
- // (The simple ones are hard-coded. The metadata layouts are not.)
- const char *md_layout = (
-// parameter annotations:
-#define MDL0 "[NB[(1)]]"
- MDL0
-// annotations:
-#define MDL1 \
- "[NH[(1)]]" \
- "[RSHNH[RUH(1)]]"
- MDL1
- // member_value:
- "[TB"
- "(66,67,73,83,90)[KIH]"
- "(68)[KDH]"
- "(70)[KFH]"
- "(74)[KJH]"
- "(99)[RSH]"
- "(101)[RSHRUH]"
- "(115)[RUH]"
- "(91)[NH[(0)]]"
- "(64)["
- // nested annotation:
- "RSH"
- "NH[RUH(0)]"
- "]"
- "()[]"
- "]");
-
- const char *md_layout_P = md_layout;
- const char *md_layout_A = md_layout + strlen(MDL0);
- const char *md_layout_V = md_layout + strlen(MDL0 MDL1);
- assert(0 == strncmp(&md_layout_A[-3], ")]][", 4));
- assert(0 == strncmp(&md_layout_V[-3], ")]][", 4));
-
- for (i = 0; i < ATTR_CONTEXT_LIMIT; i++)
- {
- attr_definitions &ad = attr_defs[i];
- ad.defineLayout(X_ATTR_RuntimeVisibleAnnotations, "RuntimeVisibleAnnotations",
- md_layout_A);
- ad.defineLayout(X_ATTR_RuntimeInvisibleAnnotations, "RuntimeInvisibleAnnotations",
- md_layout_A);
- if (i != ATTR_CONTEXT_METHOD)
- continue;
- ad.defineLayout(METHOD_ATTR_RuntimeVisibleParameterAnnotations,
- "RuntimeVisibleParameterAnnotations", md_layout_P);
- ad.defineLayout(METHOD_ATTR_RuntimeInvisibleParameterAnnotations,
- "RuntimeInvisibleParameterAnnotations", md_layout_P);
- ad.defineLayout(METHOD_ATTR_AnnotationDefault, "AnnotationDefault", md_layout_V);
- }
-
- attr_definition_headers.readData(attr_definition_count);
- attr_definition_name.readData(attr_definition_count);
- attr_definition_layout.readData(attr_definition_count);
-
-// Initialize correct predef bits, to distinguish predefs from new defs.
-#define ORBIT(n, s) | ((uint64_t)1 << n)
- attr_defs[ATTR_CONTEXT_CLASS].predef = (0 X_ATTR_DO(ORBIT) CLASS_ATTR_DO(ORBIT));
- attr_defs[ATTR_CONTEXT_FIELD].predef = (0 X_ATTR_DO(ORBIT) FIELD_ATTR_DO(ORBIT));
- attr_defs[ATTR_CONTEXT_METHOD].predef = (0 X_ATTR_DO(ORBIT) METHOD_ATTR_DO(ORBIT));
- attr_defs[ATTR_CONTEXT_CODE].predef = (0 O_ATTR_DO(ORBIT) CODE_ATTR_DO(ORBIT));
-#undef ORBIT
- // Clear out the redef bits, folding them back into predef.
- for (i = 0; i < ATTR_CONTEXT_LIMIT; i++)
- {
- attr_defs[i].predef |= attr_defs[i].redef;
- attr_defs[i].redef = 0;
- }
-
- // Now read the transmitted locally defined attrs.
- // This will set redef bits again.
- for (i = 0; i < attr_definition_count; i++)
- {
- int header = attr_definition_headers.getByte();
- int attrc = ADH_BYTE_CONTEXT(header);
- int idx = ADH_BYTE_INDEX(header);
- entry *name = attr_definition_name.getRef();
- entry *layout = attr_definition_layout.getRef();
- attr_defs[attrc].defineLayout(idx, name, layout->value.b.strval());
- }
-}
-
-#define NO_ENTRY_YET ((entry *)-1)
-
-static bool isDigitString(bytes &x, int beg, int end)
-{
- if (beg == end)
- return false; // nullptr string
- byte *xptr = x.ptr;
- for (int i = beg; i < end; i++)
- {
- char ch = xptr[i];
- if (!(ch >= '0' && ch <= '9'))
- return false;
- }
- return true;
-}
-
-enum
-{ // constants for parsing class names
- SLASH_MIN = '.',
- SLASH_MAX = '/',
- DOLLAR_MIN = 0,
- DOLLAR_MAX = '-'};
-
-static int lastIndexOf(int chmin, int chmax, bytes &x, int pos)
-{
- byte *ptr = x.ptr;
- for (byte *cp = ptr + pos; --cp >= ptr;)
- {
- assert(x.inBounds(cp));
- if (*cp >= chmin && *cp <= chmax)
- return (int)(cp - ptr);
- }
- return -1;
-}
-
-inner_class *constant_pool::getIC(entry *inner)
-{
- if (inner == nullptr)
- return nullptr;
- assert(inner->tag == CONSTANT_Class);
- if (inner->inord == NO_INORD)
- return nullptr;
- inner_class *ic = ic_index[inner->inord];
- assert(ic == nullptr || ic->inner == inner);
- return ic;
-}
-
-inner_class *constant_pool::getFirstChildIC(entry *outer)
-{
- if (outer == nullptr)
- return nullptr;
- assert(outer->tag == CONSTANT_Class);
- if (outer->inord == NO_INORD)
- return nullptr;
- inner_class *ic = ic_child_index[outer->inord];
- assert(ic == nullptr || ic->outer == outer);
- return ic;
-}
-
-inner_class *constant_pool::getNextChildIC(inner_class *child)
-{
- inner_class *ic = child->next_sibling;
- assert(ic == nullptr || ic->outer == child->outer);
- return ic;
-}
-
-void unpacker::read_ics()
-{
- int i;
- int index_size = cp.tag_count[CONSTANT_Class];
- inner_class **ic_index = U_NEW(inner_class *, index_size);
- inner_class **ic_child_index = U_NEW(inner_class *, index_size);
- cp.ic_index = ic_index;
- cp.ic_child_index = ic_child_index;
- ics = U_NEW(inner_class, ic_count);
- ic_this_class.readData(ic_count);
- ic_flags.readData(ic_count);
- // Scan flags to get count of long-form bands.
- int long_forms = 0;
- for (i = 0; i < ic_count; i++)
- {
- int flags = ic_flags.getInt(); // may be long form!
- if ((flags & ACC_IC_LONG_FORM) != 0)
- {
- long_forms += 1;
- ics[i].name = NO_ENTRY_YET;
- }
- flags &= ~ACC_IC_LONG_FORM;
- entry *inner = ic_this_class.getRef();
- uint32_t inord = inner->inord;
- assert(inord < (uint32_t)cp.tag_count[CONSTANT_Class]);
- if (ic_index[inord] != nullptr)
- {
- unpack_abort("identical inner class");
- break;
- }
- ic_index[inord] = &ics[i];
- ics[i].inner = inner;
- ics[i].flags = flags;
- assert(cp.getIC(inner) == &ics[i]);
- }
- // ic_this_class.done();
- // ic_flags.done();
- ic_outer_class.readData(long_forms);
- ic_name.readData(long_forms);
- for (i = 0; i < ic_count; i++)
- {
- if (ics[i].name == NO_ENTRY_YET)
- {
- // Long form.
- ics[i].outer = ic_outer_class.getRefN();
- ics[i].name = ic_name.getRefN();
- }
- else
- {
- // Fill in outer and name based on inner.
- bytes &n = ics[i].inner->value.b;
- bytes pkgOuter;
- bytes number;
- bytes name;
- // Parse n into pkgOuter and name (and number).
- int dollar1, dollar2; // pointers to $ in the pattern
- // parse n = (<pkg>/)*<outer>($<number>)?($<name>)?
- int nlen = (int)n.len;
- int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, nlen) + 1;
- dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, nlen);
- if (dollar2 < 0)
- {
- unpack_abort();
- }
- assert(dollar2 >= pkglen);
- if (isDigitString(n, dollar2 + 1, nlen))
- {
- // n = (<pkg>/)*<outer>$<number>
- number = n.slice(dollar2 + 1, nlen);
- name.set(nullptr, 0);
- dollar1 = dollar2;
- }
- else if (pkglen < (dollar1 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2 - 1)) &&
- isDigitString(n, dollar1 + 1, dollar2))
- {
- // n = (<pkg>/)*<outer>$<number>$<name>
- number = n.slice(dollar1 + 1, dollar2);
- name = n.slice(dollar2 + 1, nlen);
- }
- else
- {
- // n = (<pkg>/)*<outer>$<name>
- dollar1 = dollar2;
- number.set(nullptr, 0);
- name = n.slice(dollar2 + 1, nlen);
- }
- if (number.ptr == nullptr)
- pkgOuter = n.slice(0, dollar1);
- else
- pkgOuter.set(nullptr, 0);
-
- if (pkgOuter.ptr != nullptr)
- ics[i].outer = cp.ensureClass(pkgOuter);
-
- if (name.ptr != nullptr)
- ics[i].name = cp.ensureUtf8(name);
- }
-
- // update child/sibling list
- if (ics[i].outer != nullptr)
- {
- uint32_t outord = ics[i].outer->inord;
- if (outord != NO_INORD)
- {
- assert(outord < (uint32_t)cp.tag_count[CONSTANT_Class]);
- ics[i].next_sibling = ic_child_index[outord];
- ic_child_index[outord] = &ics[i];
- }
- }
- }
- // ic_outer_class.done();
- // ic_name.done();
-}
-
-void unpacker::read_classes()
-{
- class_this.readData(class_count);
- class_super.readData(class_count);
- class_interface_count.readData(class_count);
- class_interface.readData(class_interface_count.getIntTotal());
-
-#if 0
- int i;
- // Make a little mark on super-classes.
- for (i = 0; i < class_count; i++) {
- entry* e = class_super.getRefN();
- if (e != nullptr) e->bits |= entry::EB_SUPER;
- }
- class_super.rewind();
-#endif
-
- // Members.
- class_field_count.readData(class_count);
- class_method_count.readData(class_count);
-
- int field_count = class_field_count.getIntTotal();
- int method_count = class_method_count.getIntTotal();
-
- field_descr.readData(field_count);
- read_attrs(ATTR_CONTEXT_FIELD, field_count);
- method_descr.readData(method_count);
- read_attrs(ATTR_CONTEXT_METHOD, method_count);
- read_attrs(ATTR_CONTEXT_CLASS, class_count);
- read_code_headers();
-}
-
-int unpacker::attr_definitions::predefCount(uint32_t idx)
-{
- return isPredefined(idx) ? flag_count[idx] : 0;
-}
-
-void unpacker::read_attrs(int attrc, int obj_count)
-{
- attr_definitions &ad = attr_defs[attrc];
- assert(ad.attrc == attrc);
-
- int i, idx, count;
-
- bool haveLongFlags = ad.haveLongFlags();
-
- band &xxx_flags_hi = ad.xxx_flags_hi();
- if (haveLongFlags)
- xxx_flags_hi.readData(obj_count);
-
- band &xxx_flags_lo = ad.xxx_flags_lo();
- xxx_flags_lo.readData(obj_count);
-
- // pre-scan flags, counting occurrences of each index bit
- uint64_t indexMask = ad.flagIndexMask(); // which flag bits are index bits?
- for (i = 0; i < obj_count; i++)
- {
- uint64_t indexBits = xxx_flags_hi.getLong(xxx_flags_lo, haveLongFlags);
- if ((indexBits & ~indexMask) > (ushort) - 1)
- {
- unpack_abort("undefined attribute flag bit");
- return;
- }
- indexBits &= indexMask; // ignore classfile flag bits
- for (idx = 0; indexBits != 0; idx++, indexBits >>= 1)
- {
- ad.flag_count[idx] += (int)(indexBits & 1);
- }
- }
- // we'll scan these again later for output:
- xxx_flags_lo.rewind();
- xxx_flags_hi.rewind();
-
- band &xxx_attr_count = ad.xxx_attr_count();
- // There is one count element for each 1<<16 bit set in flags:
- xxx_attr_count.readData(ad.predefCount(X_ATTR_OVERFLOW));
-
- band &xxx_attr_indexes = ad.xxx_attr_indexes();
- int overflowIndexCount = xxx_attr_count.getIntTotal();
- xxx_attr_indexes.readData(overflowIndexCount);
- // pre-scan attr indexes, counting occurrences of each value
- for (i = 0; i < overflowIndexCount; i++)
- {
- idx = xxx_attr_indexes.getInt();
- if (!ad.isIndex(idx))
- {
- unpack_abort("attribute index out of bounds");
- return;
- }
- ad.getCount(idx) += 1;
- }
- xxx_attr_indexes.rewind(); // we'll scan it again later for output
-
- // We will need a backward call count for each used backward callable.
- int backwardCounts = 0;
- for (idx = 0; idx < ad.layouts.length(); idx++)
- {
- layout_definition *lo = ad.getLayout(idx);
- if (lo != nullptr && ad.getCount(idx) != 0)
- {
- // Build the bands lazily, only when they are used.
- band **bands = ad.buildBands(lo);
- if (lo->hasCallables())
- {
- for (i = 0; bands[i] != nullptr; i++)
- {
- if (bands[i]->le_back)
- {
- assert(bands[i]->le_kind == EK_CBLE);
- backwardCounts += 1;
- }
- }
- }
- }
- }
- ad.xxx_attr_calls().readData(backwardCounts);
-
- // Read built-in bands.
- // Mostly, these are hand-coded equivalents to readBandData().
- switch (attrc)
- {
- case ATTR_CONTEXT_CLASS:
-
- count = ad.predefCount(CLASS_ATTR_SourceFile);
- class_SourceFile_RUN.readData(count);
-
- count = ad.predefCount(CLASS_ATTR_EnclosingMethod);
- class_EnclosingMethod_RC.readData(count);
- class_EnclosingMethod_RDN.readData(count);
-
- count = ad.predefCount(X_ATTR_Signature);
- class_Signature_RS.readData(count);
-
- ad.readBandData(X_ATTR_RuntimeVisibleAnnotations);
- ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations);
-
- count = ad.predefCount(CLASS_ATTR_InnerClasses);
- class_InnerClasses_N.readData(count);
-
- count = class_InnerClasses_N.getIntTotal();
- class_InnerClasses_RC.readData(count);
- class_InnerClasses_F.readData(count);
-
- // Drop remaining columns wherever flags are zero:
- count -= class_InnerClasses_F.getIntCount(0);
- class_InnerClasses_outer_RCN.readData(count);
- class_InnerClasses_name_RUN.readData(count);
-
- count = ad.predefCount(CLASS_ATTR_ClassFile_version);
- class_ClassFile_version_minor_H.readData(count);
- class_ClassFile_version_major_H.readData(count);
- break;
-
- case ATTR_CONTEXT_FIELD:
-
- count = ad.predefCount(FIELD_ATTR_ConstantValue);
- field_ConstantValue_KQ.readData(count);
-
- count = ad.predefCount(X_ATTR_Signature);
- field_Signature_RS.readData(count);
-
- ad.readBandData(X_ATTR_RuntimeVisibleAnnotations);
- ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations);
- break;
-
- case ATTR_CONTEXT_METHOD:
-
- code_count = ad.predefCount(METHOD_ATTR_Code);
- // Code attrs are handled very specially below...
-
- count = ad.predefCount(METHOD_ATTR_Exceptions);
- method_Exceptions_N.readData(count);
- count = method_Exceptions_N.getIntTotal();
- method_Exceptions_RC.readData(count);
-
- count = ad.predefCount(X_ATTR_Signature);
- method_Signature_RS.readData(count);
-
- ad.readBandData(X_ATTR_RuntimeVisibleAnnotations);
- ad.readBandData(X_ATTR_RuntimeInvisibleAnnotations);
- ad.readBandData(METHOD_ATTR_RuntimeVisibleParameterAnnotations);
- ad.readBandData(METHOD_ATTR_RuntimeInvisibleParameterAnnotations);
- ad.readBandData(METHOD_ATTR_AnnotationDefault);
- break;
-
- case ATTR_CONTEXT_CODE:
- // (keep this code aligned with its brother in unpacker::write_attrs)
- count = ad.predefCount(CODE_ATTR_StackMapTable);
- // disable this feature in old archives!
- if (count != 0 && majver < JAVA6_PACKAGE_MAJOR_VERSION)
- {
- unpack_abort("undefined StackMapTable attribute (old archive format)");
- return;
- }
- code_StackMapTable_N.readData(count);
- count = code_StackMapTable_N.getIntTotal();
- code_StackMapTable_frame_T.readData(count);
- // the rest of it depends in a complicated way on frame tags
- {
- int fat_frame_count = 0;
- int offset_count = 0;
- int type_count = 0;
- for (int k = 0; k < count; k++)
- {
- int tag = code_StackMapTable_frame_T.getByte();
- if (tag <= 127)
- {
- // (64-127) [(2)]
- if (tag >= 64)
- type_count++;
- }
- else if (tag <= 251)
- {
- // (247) [(1)(2)]
- // (248-251) [(1)]
- if (tag >= 247)
- offset_count++;
- if (tag == 247)
- type_count++;
- }
- else if (tag <= 254)
- {
- // (252) [(1)(2)]
- // (253) [(1)(2)(2)]
- // (254) [(1)(2)(2)(2)]
- offset_count++;
- type_count += (tag - 251);
- }
- else
- {
- // (255) [(1)NH[(2)]NH[(2)]]
- fat_frame_count++;
- }
- }
-
- // done pre-scanning frame tags:
- code_StackMapTable_frame_T.rewind();
-
- // deal completely with fat frames:
- offset_count += fat_frame_count;
- code_StackMapTable_local_N.readData(fat_frame_count);
- type_count += code_StackMapTable_local_N.getIntTotal();
- code_StackMapTable_stack_N.readData(fat_frame_count);
- type_count += code_StackMapTable_stack_N.getIntTotal();
- // read the rest:
- code_StackMapTable_offset.readData(offset_count);
- code_StackMapTable_T.readData(type_count);
- // (7) [RCH]
- count = code_StackMapTable_T.getIntCount(7);
- code_StackMapTable_RC.readData(count);
- // (8) [PH]
- count = code_StackMapTable_T.getIntCount(8);
- code_StackMapTable_P.readData(count);
- }
-
- count = ad.predefCount(CODE_ATTR_LineNumberTable);
- code_LineNumberTable_N.readData(count);
- count = code_LineNumberTable_N.getIntTotal();
- code_LineNumberTable_bci_P.readData(count);
- code_LineNumberTable_line.readData(count);
-
- count = ad.predefCount(CODE_ATTR_LocalVariableTable);
- code_LocalVariableTable_N.readData(count);
- count = code_LocalVariableTable_N.getIntTotal();
- code_LocalVariableTable_bci_P.readData(count);
- code_LocalVariableTable_span_O.readData(count);
- code_LocalVariableTable_name_RU.readData(count);
- code_LocalVariableTable_type_RS.readData(count);
- code_LocalVariableTable_slot.readData(count);
-
- count = ad.predefCount(CODE_ATTR_LocalVariableTypeTable);
- code_LocalVariableTypeTable_N.readData(count);
- count = code_LocalVariableTypeTable_N.getIntTotal();
- code_LocalVariableTypeTable_bci_P.readData(count);
- code_LocalVariableTypeTable_span_O.readData(count);
- code_LocalVariableTypeTable_name_RU.readData(count);
- code_LocalVariableTypeTable_type_RS.readData(count);
- code_LocalVariableTypeTable_slot.readData(count);
- break;
- }
-
- // Read compressor-defined bands.
- for (idx = 0; idx < ad.layouts.length(); idx++)
- {
- if (ad.getLayout(idx) == nullptr)
- continue; // none at this fixed index <32
- if (idx < (int)ad.flag_limit && ad.isPredefined(idx))
- continue; // already handled
- if (ad.getCount(idx) == 0)
- continue; // no attributes of this type (then why transmit layouts?)
- ad.readBandData(idx);
- }
-}
-
-void unpacker::attr_definitions::readBandData(int idx)
-{
- int j;
- uint32_t count = getCount(idx);
- if (count == 0)
- return;
- layout_definition *lo = getLayout(idx);
- bool hasCallables = lo->hasCallables();
- band **bands = lo->bands();
- if (!hasCallables)
- {
- // Read through the rest of the bands in a regular way.
- readBandData(bands, count);
- }
- else
- {
- // Deal with the callables.
- // First set up the forward entry count for each callable.
- // This is stored on band::length of the callable.
- bands[0]->expectMoreLength(count);
- for (j = 0; bands[j] != nullptr; j++)
- {
- band &j_cble = *bands[j];
- assert(j_cble.le_kind == EK_CBLE);
- if (j_cble.le_back)
- {
- // Add in the predicted effects of backward calls, too.
- int back_calls = xxx_attr_calls().getInt();
- j_cble.expectMoreLength(back_calls);
- // In a moment, more forward calls may increment j_cble.length.
- }
- }
- // Now consult whichever callables have non-zero entry counts.
- readBandData(bands, (uint32_t) - 1);
- }
-}
-
-// Recursive helper to the previous function:
-void unpacker::attr_definitions::readBandData(band **body, uint32_t count)
-{
- int j, k;
- for (j = 0; body[j] != nullptr; j++)
- {
- band &b = *body[j];
- if (b.defc != nullptr)
- {
- // It has data, so read it.
- b.readData(count);
- }
- switch (b.le_kind)
- {
- case EK_REPL:
- {
- int reps = b.getIntTotal();
- readBandData(b.le_body, reps);
- }
- break;
- case EK_UN:
- {
- int remaining = count;
- for (k = 0; b.le_body[k] != nullptr; k++)
- {
- band &k_case = *b.le_body[k];
- int k_count = 0;
- if (k_case.le_casetags == nullptr)
- {
- k_count = remaining; // last (empty) case
- }
- else
- {
- int *tags = k_case.le_casetags;
- int ntags = *tags++; // 1st element is length (why not?)
- while (ntags-- > 0)
- {
- int tag = *tags++;
- k_count += b.getIntCount(tag);
- }
- }
- readBandData(k_case.le_body, k_count);
- remaining -= k_count;
- }
- assert(remaining == 0);
- }
- break;
- case EK_CALL:
- // Push the count forward, if it is not a backward call.
- if (!b.le_back)
- {
- band &cble = *b.le_body[0];
- assert(cble.le_kind == EK_CBLE);
- cble.expectMoreLength(count);
- }
- break;
- case EK_CBLE:
- assert((int)count == -1); // incoming count is meaningless
- k = b.length;
- assert(k >= 0);
- // This is intended and required for non production mode.
- assert((b.length = -1)); // make it unable to accept more calls now.
- readBandData(b.le_body, k);
- break;
- }
- }
-}
-
-static inline band **findMatchingCase(int matchTag, band **cases)
-{
- for (int k = 0; cases[k] != nullptr; k++)
- {
- band &k_case = *cases[k];
- if (k_case.le_casetags != nullptr)
- {
- // If it has tags, it must match a tag.
- int *tags = k_case.le_casetags;
- int ntags = *tags++; // 1st element is length
- for (; ntags > 0; ntags--)
- {
- int tag = *tags++;
- if (tag == matchTag)
- break;
- }
- if (ntags == 0)
- continue; // does not match
- }
- return k_case.le_body;
- }
- return nullptr;
-}
-
-// write attribute band data:
-void unpacker::putlayout(band **body)
-{
- int i;
- int prevBII = -1;
- int prevBCI = -1;
- if (body == NULL)
- {
- unpack_abort("putlayout: unexpected NULL for body");
- return;
- }
- for (i = 0; body[i] != nullptr; i++)
- {
- band &b = *body[i];
- byte le_kind = b.le_kind;
-
- // Handle scalar part, if any.
- int x = 0;
- entry *e = nullptr;
- if (b.defc != nullptr)
- {
- // It has data, so unparse an element.
- if (b.ixTag != CONSTANT_None)
- {
- assert(le_kind == EK_REF);
- if (b.ixTag == CONSTANT_Literal)
- e = b.getRefUsing(cp.getKQIndex());
- else
- e = b.getRefN();
- switch (b.le_len)
- {
- case 0:
- break;
- case 1:
- putu1ref(e);
- break;
- case 2:
- putref(e);
- break;
- case 4:
- putu2(0);
- putref(e);
- break;
- default:
- assert(false);
- }
- }
- else
- {
- assert(le_kind == EK_INT || le_kind == EK_REPL || le_kind == EK_UN);
- x = b.getInt();
-
- assert(!b.le_bci || prevBCI == (int)to_bci(prevBII));
- switch (b.le_bci)
- {
- case EK_BCI: // PH: transmit R(bci), store bci
- x = to_bci(prevBII = x);
- prevBCI = x;
- break;
- case EK_BCID: // POH: transmit D(R(bci)), store bci
- x = to_bci(prevBII += x);
- prevBCI = x;
- break;
- case EK_BCO: // OH: transmit D(R(bci)), store D(bci)
- x = to_bci(prevBII += x) - prevBCI;
- prevBCI += x;
- break;
- }
- assert(!b.le_bci || prevBCI == (int)to_bci(prevBII));
-
- switch (b.le_len)
- {
- case 0:
- break;
- case 1:
- putu1(x);
- break;
- case 2:
- putu2(x);
- break;
- case 4:
- putu4(x);
- break;
- default:
- assert(false);
- }
- }
- }
-
- // Handle subparts, if any.
- switch (le_kind)
- {
- case EK_REPL:
- // x is the repeat count
- while (x-- > 0)
- {
- putlayout(b.le_body);
- }
- break;
- case EK_UN:
- // x is the tag
- putlayout(findMatchingCase(x, b.le_body));
- break;
- case EK_CALL:
- {
- band &cble = *b.le_body[0];
- assert(cble.le_kind == EK_CBLE);
- // FIXME: hit this one
- // assert(cble.le_len == b.le_len);
- putlayout(cble.le_body);
- }
- break;
-
- case EK_CBLE:
- case EK_CASE:
- assert(false); // should not reach here
- }
- }
-}
-
-void unpacker::read_files()
-{
- file_name.readData(file_count);
- if ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0)
- file_size_hi.readData(file_count);
- file_size_lo.readData(file_count);
- if ((archive_options & AO_HAVE_FILE_MODTIME) != 0)
- file_modtime.readData(file_count);
- int allFiles = file_count + class_count;
- if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0)
- {
- file_options.readData(file_count);
- // FO_IS_CLASS_STUB might be set, causing overlap between classes and files
- for (int i = 0; i < file_count; i++)
- {
- if ((file_options.getInt() & FO_IS_CLASS_STUB) != 0)
- {
- allFiles -= 1; // this one counts as both class and file
- }
- }
- file_options.rewind();
- }
- assert((default_file_options & FO_IS_CLASS_STUB) == 0);
- files_remaining = allFiles;
-}
-
-void unpacker::get_code_header(int &max_stack, int &max_na_locals, int &handler_count,
- int &cflags)
-{
- int sc = code_headers.getByte();
- if (sc == 0)
- {
- max_stack = max_na_locals = handler_count = cflags = -1;
- return;
- }
- // Short code header is the usual case:
- int nh;
- int mod;
- if (sc < 1 + 12 * 12)
- {
- sc -= 1;
- nh = 0;
- mod = 12;
- }
- else if (sc < 1 + 12 * 12 + 8 * 8)
- {
- sc -= 1 + 12 * 12;
- nh = 1;
- mod = 8;
- }
- else
- {
- assert(sc < 1 + 12 * 12 + 8 * 8 + 7 * 7);
- sc -= 1 + 12 * 12 + 8 * 8;
- nh = 2;
- mod = 7;
- }
- max_stack = sc % mod;
- max_na_locals = sc / mod; // caller must add static, siglen
- handler_count = nh;
- if ((archive_options & AO_HAVE_ALL_CODE_FLAGS) != 0)
- cflags = -1;
- else
- cflags = 0; // this one has no attributes
-}
-
-// Cf. PackageReader.readCodeHeaders
-void unpacker::read_code_headers()
-{
- code_headers.readData(code_count);
- int totalHandlerCount = 0;
- int totalFlagsCount = 0;
- for (int i = 0; i < code_count; i++)
- {
- int max_stack, max_locals, handler_count, cflags;
- get_code_header(max_stack, max_locals, handler_count, cflags);
- if (max_stack < 0)
- code_max_stack.expectMoreLength(1);
- if (max_locals < 0)
- code_max_na_locals.expectMoreLength(1);
- if (handler_count < 0)
- code_handler_count.expectMoreLength(1);
- else
- totalHandlerCount += handler_count;
- if (cflags < 0)
- totalFlagsCount += 1;
- }
- code_headers.rewind(); // replay later during writing
-
- code_max_stack.readData();
- code_max_na_locals.readData();
- code_handler_count.readData();
- totalHandlerCount += code_handler_count.getIntTotal();
-
- // Read handler specifications.
- // Cf. PackageReader.readCodeHandlers.
- code_handler_start_P.readData(totalHandlerCount);
- code_handler_end_PO.readData(totalHandlerCount);
- code_handler_catch_PO.readData(totalHandlerCount);
- code_handler_class_RCN.readData(totalHandlerCount);
-
- read_attrs(ATTR_CONTEXT_CODE, totalFlagsCount);
-}
-
-static inline bool is_in_range(uint32_t n, uint32_t min, uint32_t max)
-{
- return n - min <= max - min; // unsigned arithmetic!
-}
-static inline bool is_field_op(int bc)
-{
- return is_in_range(bc, bc_getstatic, bc_putfield);
-}
-static inline bool is_invoke_init_op(int bc)
-{
- return is_in_range(bc, _invokeinit_op, _invokeinit_limit - 1);
-}
-static inline bool is_self_linker_op(int bc)
-{
- return is_in_range(bc, _self_linker_op, _self_linker_limit - 1);
-}
-static bool is_branch_op(int bc)
-{
- return is_in_range(bc, bc_ifeq, bc_jsr) || is_in_range(bc, bc_ifnull, bc_jsr_w);
-}
-static bool is_local_slot_op(int bc)
-{
- return is_in_range(bc, bc_iload, bc_aload) || is_in_range(bc, bc_istore, bc_astore) ||
- bc == bc_iinc || bc == bc_ret;
-}
-band *unpacker::ref_band_for_op(int bc)
-{
- switch (bc)
- {
- case bc_ildc:
- case bc_ildc_w:
- return &bc_intref;
- case bc_fldc:
- case bc_fldc_w:
- return &bc_floatref;
- case bc_lldc2_w:
- return &bc_longref;
- case bc_dldc2_w:
- return &bc_doubleref;
- case bc_aldc:
- case bc_aldc_w:
- return &bc_stringref;
- case bc_cldc:
- case bc_cldc_w:
- return &bc_classref;
-
- case bc_getstatic:
- case bc_putstatic:
- case bc_getfield:
- case bc_putfield:
- return &bc_fieldref;
-
- case bc_invokevirtual:
- case bc_invokespecial:
- case bc_invokestatic:
- return &bc_methodref;
- case bc_invokeinterface:
- return &bc_imethodref;
-
- case bc_new:
- case bc_anewarray:
- case bc_checkcast:
- case bc_instanceof:
- case bc_multianewarray:
- return &bc_classref;
- }
- return nullptr;
-}
-
-band *unpacker::ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar)
-{
- if (!is_self_linker_op(bc))
- return nullptr;
- int idx = (bc - _self_linker_op);
- bool isSuper = (idx >= _self_linker_super_flag);
- if (isSuper)
- idx -= _self_linker_super_flag;
- bool isAload = (idx >= _self_linker_aload_flag);
- if (isAload)
- idx -= _self_linker_aload_flag;
- int origBC = _first_linker_op + idx;
- bool isField = is_field_op(origBC);
- isAloadVar = isAload;
- origBCVar = _first_linker_op + idx;
- if (!isSuper)
- return isField ? &bc_thisfield : &bc_thismethod;
- else
- return isField ? &bc_superfield : &bc_supermethod;
-}
-
-// Cf. PackageReader.readByteCodes
-inline // called exactly once => inline
- void
-unpacker::read_bcs()
-{
- // read from bc_codes and bc_case_count
- fillbytes all_switch_ops;
- all_switch_ops.init();
-
- // Read directly from rp/rplimit.
- // Do this later: bc_codes.readData(...)
- byte *rp0 = rp;
-
- band *bc_which;
- byte *opptr = rp;
- byte *oplimit = rplimit;
-
- bool isAload; // passed by ref and then ignored
- int junkBC; // passed by ref and then ignored
- for (int k = 0; k < code_count; k++)
- {
- // Scan one method:
- for (;;)
- {
- if (opptr + 2 > oplimit)
- {
- rp = opptr;
- ensure_input(2);
- oplimit = rplimit;
- rp = rp0; // back up
- }
- if (opptr == oplimit)
- {
- unpack_abort();
- }
- int bc = *opptr++ & 0xFF;
- bool isWide = false;
- if (bc == bc_wide)
- {
- if (opptr == oplimit)
- {
- unpack_abort();
- }
- bc = *opptr++ & 0xFF;
- isWide = true;
- }
- // Adjust expectations of various band sizes.
- switch (bc)
- {
- case bc_tableswitch:
- case bc_lookupswitch:
- all_switch_ops.addByte(bc);
- break;
- case bc_iinc:
- bc_local.expectMoreLength(1);
- bc_which = isWide ? &bc_short : &bc_byte;
- bc_which->expectMoreLength(1);
- break;
- case bc_sipush:
- bc_short.expectMoreLength(1);
- break;
- case bc_bipush:
- bc_byte.expectMoreLength(1);
- break;
- case bc_newarray:
- bc_byte.expectMoreLength(1);
- break;
- case bc_multianewarray:
- assert(ref_band_for_op(bc) == &bc_classref);
- bc_classref.expectMoreLength(1);
- bc_byte.expectMoreLength(1);
- break;
- case bc_ref_escape:
- bc_escrefsize.expectMoreLength(1);
- bc_escref.expectMoreLength(1);
- break;
- case bc_byte_escape:
- bc_escsize.expectMoreLength(1);
- // bc_escbyte will have to be counted too
- break;
- default:
- if (is_invoke_init_op(bc))
- {
- bc_initref.expectMoreLength(1);
- break;
- }
- bc_which = ref_band_for_self_op(bc, isAload, junkBC);
- if (bc_which != nullptr)
- {
- bc_which->expectMoreLength(1);
- break;
- }
- if (is_branch_op(bc))
- {
- bc_label.expectMoreLength(1);
- break;
- }
- bc_which = ref_band_for_op(bc);
- if (bc_which != nullptr)
- {
- bc_which->expectMoreLength(1);
- assert(bc != bc_multianewarray); // handled elsewhere
- break;
- }
- if (is_local_slot_op(bc))
- {
- bc_local.expectMoreLength(1);
- break;
- }
- break;
- case bc_end_marker:
- // Increment k and test against code_count.
- goto doneScanningMethod;
- }
- }
- doneScanningMethod:
- {
- }
- }
-
- // Go through the formality, so we can use it in a regular fashion later:
- assert(rp == rp0);
- bc_codes.readData((int)(opptr - rp));
-
- int i = 0;
-
- // To size instruction bands correctly, we need info on switches:
- bc_case_count.readData((int)all_switch_ops.size());
- for (i = 0; i < (int)all_switch_ops.size(); i++)
- {
- int caseCount = bc_case_count.getInt();
- int bc = all_switch_ops.getByte(i);
- bc_label.expectMoreLength(1 + caseCount); // default label + cases
- bc_case_value.expectMoreLength(bc == bc_tableswitch ? 1 : caseCount);
- }
- bc_case_count.rewind(); // uses again for output
-
- all_switch_ops.free();
-
- for (i = e_bc_case_value; i <= e_bc_escsize; i++)
- {
- all_bands[i].readData();
- }
-
- // The bc_escbyte band is counted by the immediately previous band.
- bc_escbyte.readData(bc_escsize.getIntTotal());
-}
-
-void unpacker::read_bands()
-{
- read_file_header();
-
- if (cp.nentries == 0)
- {
- // read_file_header failed to read a CP, because it copied a JAR.
- return;
- }
-
- // Do this after the file header has been read:
- check_options();
-
- read_cp();
- read_attr_defs();
- read_ics();
- read_classes();
- read_bcs();
- read_files();
-}
-
-/// CP routines
-
-entry *&constant_pool::hashTabRef(byte tag, bytes &b)
-{
- uint32_t hash = tag + (int)b.len;
- for (int i = 0; i < (int)b.len; i++)
- {
- hash = hash * 31 + (0xFF & b.ptr[i]);
- }
- entry **ht = hashTab;
- int hlen = hashTabLength;
- assert((hlen & (hlen - 1)) == 0); // must be power of 2
- uint32_t hash1 = hash & (hlen - 1); // == hash % hlen
- uint32_t hash2 = 0; // lazily computed (requires mod op.)
-#ifndef NDEBUG
- int probes = 0;
-#endif
- while (ht[hash1] != nullptr)
- {
- entry &e = *ht[hash1];
- if (e.value.b.equals(b) && e.tag == tag)
- break;
- if (hash2 == 0)
- // Note: hash2 must be relatively prime to hlen, hence the "|1".
- hash2 = (((hash % 499) & (hlen - 1)) | 1);
- hash1 += hash2;
- if (hash1 >= (uint32_t)hlen)
- hash1 -= hlen;
- assert(hash1 < (uint32_t)hlen);
- assert(++probes < hlen);
- }
- return ht[hash1];
-}
-
-static void insert_extra(entry *e, ptrlist &extras)
-{
- // This ordering helps implement the Pack200 requirement
- // of a predictable CP order in the class files produced.
- e->inord = NO_INORD; // mark as an "extra"
- extras.add(e);
- // Note: We will sort the list (by string-name) later.
-}
-
-entry *constant_pool::ensureUtf8(bytes &b)
-{
- entry *&ix = hashTabRef(CONSTANT_Utf8, b);
- if (ix != nullptr)
- return ix;
- // Make one.
- if (nentries == maxentries)
- {
- unpack_abort("cp utf8 overflow");
- return &entries[tag_base[CONSTANT_Utf8]]; // return something
- }
- entry &e = entries[nentries++];
- e.tag = CONSTANT_Utf8;
- u->saveTo(e.value.b, b);
- assert(&e >= first_extra_entry);
- insert_extra(&e, tag_extras[CONSTANT_Utf8]);
- return ix = &e;
-}
-
-entry *constant_pool::ensureClass(bytes &b)
-{
- entry *&ix = hashTabRef(CONSTANT_Class, b);
- if (ix != nullptr)
- return ix;
- // Make one.
- if (nentries == maxentries)
- {
- unpack_abort("cp class overflow");
- return &entries[tag_base[CONSTANT_Class]]; // return something
- }
- entry &e = entries[nentries++];
- e.tag = CONSTANT_Class;
- e.nrefs = 1;
- e.refs = U_NEW(entry *, 1);
- ix = &e; // hold my spot in the index
- entry *utf = ensureUtf8(b);
- e.refs[0] = utf;
- e.value.b = utf->value.b;
- assert(&e >= first_extra_entry);
- insert_extra(&e, tag_extras[CONSTANT_Class]);
- return &e;
-}
-
-void constant_pool::expandSignatures()
-{
- int i;
- int nsigs = 0;
- int nreused = 0;
- int first_sig = tag_base[CONSTANT_Signature];
- int sig_limit = tag_count[CONSTANT_Signature] + first_sig;
- fillbytes buf;
- buf.init(1 << 10);
- for (i = first_sig; i < sig_limit; i++)
- {
- entry &e = entries[i];
- assert(e.tag == CONSTANT_Signature);
- int refnum = 0;
- bytes form = e.refs[refnum++]->asUtf8();
- buf.empty();
- for (int j = 0; j < (int)form.len; j++)
- {
- int c = form.ptr[j];
- buf.addByte(c);
- if (c == 'L')
- {
- entry *cls = e.refs[refnum++];
- buf.append(cls->className()->asUtf8());
- }
- }
- assert(refnum == e.nrefs);
- bytes &sig = buf.b;
-
- // try to find a pre-existing Utf8:
- entry *&e2 = hashTabRef(CONSTANT_Utf8, sig);
- if (e2 != nullptr)
- {
- assert(e2->isUtf8(sig));
- e.value.b = e2->value.b;
- e.refs[0] = e2;
- e.nrefs = 1;
- nreused++;
- }
- else
- {
- // there is no other replacement; reuse this CP entry as a Utf8
- u->saveTo(e.value.b, sig);
- e.tag = CONSTANT_Utf8;
- e.nrefs = 0;
- e2 = &e;
- }
- nsigs++;
- }
- buf.free();
-
- // go expunge all references to remaining signatures:
- for (i = 0; i < (int)nentries; i++)
- {
- entry &e = entries[i];
- for (int j = 0; j < e.nrefs; j++)
- {
- entry *&e2 = e.refs[j];
- if (e2 != nullptr && e2->tag == CONSTANT_Signature)
- e2 = e2->refs[0];
- }
- }
-}
-
-void constant_pool::initMemberIndexes()
-{
- // This function does NOT refer to any class schema.
- // It is totally internal to the cpool.
- int i, j;
-
- // Get the pre-existing indexes:
- int nclasses = tag_count[CONSTANT_Class];
- // entry *classes = tag_base[CONSTANT_Class] + entries; // UNUSED
- int nfields = tag_count[CONSTANT_Fieldref];
- entry *fields = tag_base[CONSTANT_Fieldref] + entries;
- int nmethods = tag_count[CONSTANT_Methodref];
- entry *methods = tag_base[CONSTANT_Methodref] + entries;
-
- int *field_counts = T_NEW(int, nclasses);
- int *method_counts = T_NEW(int, nclasses);
- cpindex *all_indexes = U_NEW(cpindex, nclasses * 2);
- entry **field_ix = U_NEW(entry *, add_size(nfields, nclasses));
- entry **method_ix = U_NEW(entry *, add_size(nmethods, nclasses));
-
- for (j = 0; j < nfields; j++)
- {
- entry &f = fields[j];
- i = f.memberClass()->inord;
- assert(i < nclasses);
- field_counts[i]++;
- }
- for (j = 0; j < nmethods; j++)
- {
- entry &m = methods[j];
- i = m.memberClass()->inord;
- assert(i < nclasses);
- method_counts[i]++;
- }
-
- int fbase = 0, mbase = 0;
- for (i = 0; i < nclasses; i++)
- {
- int fc = field_counts[i];
- int mc = method_counts[i];
- all_indexes[i * 2 + 0].init(fc, field_ix + fbase, CONSTANT_Fieldref + SUBINDEX_BIT);
- all_indexes[i * 2 + 1].init(mc, method_ix + mbase, CONSTANT_Methodref + SUBINDEX_BIT);
- // reuse field_counts and member_counts as fill pointers:
- field_counts[i] = fbase;
- method_counts[i] = mbase;
- fbase += fc + 1;
- mbase += mc + 1;
- // (the +1 leaves a space between every subarray)
- }
- assert(fbase == nfields + nclasses);
- assert(mbase == nmethods + nclasses);
-
- for (j = 0; j < nfields; j++)
- {
- entry &f = fields[j];
- i = f.memberClass()->inord;
- field_ix[field_counts[i]++] = &f;
- }
- for (j = 0; j < nmethods; j++)
- {
- entry &m = methods[j];
- i = m.memberClass()->inord;
- method_ix[method_counts[i]++] = &m;
- }
-
- member_indexes = all_indexes;
-
- // Free intermediate buffers.
- u->free_temps();
-}
-
-void entry::requestOutputIndex(constant_pool &cp, int req)
-{
- assert(outputIndex <= NOT_REQUESTED); // must not have assigned indexes yet
- if (tag == CONSTANT_Signature)
- {
- ref(0)->requestOutputIndex(cp, req);
- return;
- }
- assert(req == REQUESTED || req == REQUESTED_LDC);
- if (outputIndex != NOT_REQUESTED)
- {
- if (req == REQUESTED_LDC)
- outputIndex = req; // this kind has precedence
- return;
- }
- outputIndex = req;
- // assert(!cp.outputEntries.contains(this));
- assert(tag != CONSTANT_Signature);
- cp.outputEntries.add(this);
- for (int j = 0; j < nrefs; j++)
- {
- ref(j)->requestOutputIndex(cp);
- }
-}
-
-void constant_pool::resetOutputIndexes()
-{
- int i;
- int noes = outputEntries.length();
- entry **oes = (entry **)outputEntries.base();
- for (i = 0; i < noes; i++)
- {
- entry &e = *oes[i];
- e.outputIndex = NOT_REQUESTED;
- }
- outputIndexLimit = 0;
- outputEntries.empty();
-}
-
-static const byte TAG_ORDER[CONSTANT_Limit] = {0, 1, 0, 2, 3, 4, 5, 7, 6, 10, 11, 12, 9, 8};
-
-extern "C" int outputEntry_cmp(const void *e1p, const void *e2p)
-{
- // Sort entries according to the Pack200 rules for deterministic
- // constant pool ordering.
- //
- // The four sort keys as follows, in order of decreasing importance:
- // 1. ldc first, then non-ldc guys
- // 2. normal cp_All entries by input order (i.e., address order)
- // 3. after that, extra entries by lexical order (as in tag_extras[*])
- entry &e1 = *(entry *)*(void **)e1p;
- entry &e2 = *(entry *)*(void **)e2p;
- int oi1 = e1.outputIndex;
- int oi2 = e2.outputIndex;
- assert(oi1 == REQUESTED || oi1 == REQUESTED_LDC);
- assert(oi2 == REQUESTED || oi2 == REQUESTED_LDC);
- if (oi1 != oi2)
- {
- if (oi1 == REQUESTED_LDC)
- return 0 - 1;
- if (oi2 == REQUESTED_LDC)
- return 1 - 0;
- // Else fall through; neither is an ldc request.
- }
- if (e1.inord != NO_INORD || e2.inord != NO_INORD)
- {
- // One or both is normal. Use input order.
- if (&e1 > &e2)
- return 1 - 0;
- if (&e1 < &e2)
- return 0 - 1;
- return 0; // equal pointers
- }
- // Both are extras. Sort by tag and then by value.
- if (e1.tag != e2.tag)
- {
- return TAG_ORDER[e1.tag] - TAG_ORDER[e2.tag];
- }
- // If the tags are the same, use string comparison.
- return compare_Utf8_chars(e1.value.b, e2.value.b);
-}
-
-void constant_pool::computeOutputIndexes()
-{
- int i;
-
- int noes = outputEntries.length();
- entry **oes = (entry **)outputEntries.base();
-
- // Sort the output constant pool into the order required by Pack200.
- PTRLIST_QSORT(outputEntries, outputEntry_cmp);
-
- // Allocate a new index for each entry that needs one.
- // We do this in two passes, one for LDC entries and one for the rest.
- int nextIndex = 1; // always skip index #0 in output cpool
- for (i = 0; i < noes; i++)
- {
- entry &e = *oes[i];
- assert(e.outputIndex == REQUESTED || e.outputIndex == REQUESTED_LDC);
- e.outputIndex = nextIndex++;
- if (e.isDoubleWord())
- nextIndex++; // do not use the next index
- }
- outputIndexLimit = nextIndex;
-}
-
-// Unpacker Start
-// Deallocate all internal storage and reset to a clean state.
-// Do not disturb any input or output connections, including
-// infileptr, inbytes, read_input_fn, jarout, or errstrm.
-// Do not reset any unpack options.
-void unpacker::reset()
-{
- bytes_read_before_reset += bytes_read;
- bytes_written_before_reset += bytes_written;
- files_written_before_reset += files_written;
- classes_written_before_reset += classes_written;
- segments_read_before_reset += 1;
- if (verbose >= 2)
- {
- fprintf(stderr, "After segment %d, %" PRIu64 " bytes read and %" PRIu64 " bytes written.\n",
- segments_read_before_reset - 1, bytes_read_before_reset,
- bytes_written_before_reset);
- fprintf(stderr,
- "After segment %d, %d files (of which %d are classes) written to output.\n",
- segments_read_before_reset - 1, files_written_before_reset,
- classes_written_before_reset);
- if (archive_next_count != 0)
- {
- fprintf(stderr, "After segment %d, %d segment%s remaining (estimated).\n",
- segments_read_before_reset - 1, archive_next_count,
- archive_next_count == 1 ? "" : "s");
- }
- }
-
- unpacker save_u = (*this); // save bytewise image
- infileptr = nullptr; // make asserts happy
- jarout = nullptr; // do not close the output jar
- gzin = nullptr; // do not close the input gzip stream
- this->free();
- this->init(read_input_fn);
-
- // restore selected interface state:
- infileptr = save_u.infileptr;
- inbytes = save_u.inbytes;
- jarout = save_u.jarout;
- gzin = save_u.gzin;
- verbose = save_u.verbose;
- deflate_hint_or_zero = save_u.deflate_hint_or_zero;
- modification_time_or_zero = save_u.modification_time_or_zero;
- bytes_read_before_reset = save_u.bytes_read_before_reset;
- bytes_written_before_reset = save_u.bytes_written_before_reset;
- files_written_before_reset = save_u.files_written_before_reset;
- classes_written_before_reset = save_u.classes_written_before_reset;
- segments_read_before_reset = save_u.segments_read_before_reset;
- // Note: If we use strip_names, watch out: They get nuked here.
-}
-
-void unpacker::init(read_input_fn_t input_fn)
-{
- int i;
- BYTES_OF(*this).clear();
- this->u = this; // self-reference for U_NEW macro
- read_input_fn = input_fn;
- all_bands = band::makeBands(this);
- // Make a default jar buffer; caller may safely overwrite it.
- jarout = U_NEW(jar, 1);
- jarout->init(this);
- for (i = 0; i < ATTR_CONTEXT_LIMIT; i++)
- attr_defs[i].u = u; // set up outer ptr
-}
-
-// Usage: unpack a byte buffer
-// packptr is a reference to byte buffer containing a
-// packed file and len is the length of the buffer.
-// If nullptr, the callback is used to fill an internal buffer.
-void unpacker::start(void *packptr, size_t len)
-{
- if (packptr != nullptr && len != 0)
- {
- inbytes.set((byte *)packptr, len);
- }
- read_bands();
-}
-
-void unpacker::check_options()
-{
- if (deflate_hint_or_zero != 0)
- {
- bool force_deflate_hint = (deflate_hint_or_zero > 0);
- if (force_deflate_hint)
- default_file_options |= FO_DEFLATE_HINT;
- else
- default_file_options &= ~FO_DEFLATE_HINT;
- // Turn off per-file deflate hint by force.
- suppress_file_options |= FO_DEFLATE_HINT;
- }
- if (modification_time_or_zero != 0)
- {
- default_file_modtime = modification_time_or_zero;
- // Turn off per-file modtime by force.
- archive_options &= ~AO_HAVE_FILE_MODTIME;
- }
-}
-
-// classfile writing
-
-void unpacker::reset_cur_classfile()
-{
- // set defaults
- cur_class_minver = default_class_minver;
- cur_class_majver = default_class_majver;
-
- // reset constant pool state
- cp.resetOutputIndexes();
-
- // reset fixups
- class_fixup_type.empty();
- class_fixup_offset.empty();
- class_fixup_ref.empty();
- requested_ics.empty();
-}
-
-cpindex *constant_pool::getKQIndex()
-{
- char ch = '?';
- if (u->cur_descr != nullptr)
- {
- entry *type = u->cur_descr->descrType();
- ch = type->value.b.ptr[0];
- }
- byte tag = CONSTANT_Integer;
- switch (ch)
- {
- case 'L':
- tag = CONSTANT_String;
- break;
- case 'I':
- tag = CONSTANT_Integer;
- break;
- case 'J':
- tag = CONSTANT_Long;
- break;
- case 'F':
- tag = CONSTANT_Float;
- break;
- case 'D':
- tag = CONSTANT_Double;
- break;
- case 'B':
- case 'S':
- case 'C':
- case 'Z':
- tag = CONSTANT_Integer;
- break;
- default:
- unpack_abort("bad KQ reference");
- break;
- }
- return getIndex(tag);
-}
-
-uint32_t unpacker::to_bci(uint32_t bii)
-{
- uint32_t len = bcimap.length();
- uint32_t *map = (uint32_t *)bcimap.base();
- assert(len > 0); // must be initialized before using to_bci
- if (bii < len)
- return map[bii];
- // Else it's a fractional or out-of-range BCI.
- uint32_t key = bii - len;
- for (int i = len;; i--)
- {
- if (map[i - 1] - (i - 1) <= key)
- break;
- else
- --bii;
- }
- return bii;
-}
-
-void unpacker::put_stackmap_type()
-{
- int tag = code_StackMapTable_T.getByte();
- putu1(tag);
- switch (tag)
- {
- case 7: // (7) [RCH]
- putref(code_StackMapTable_RC.getRef());
- break;
- case 8: // (8) [PH]
- putu2(to_bci(code_StackMapTable_P.getInt()));
- break;
- }
-}
-
-// Functions for writing code.
-
-void unpacker::put_label(int curIP, int size)
-{
- code_fixup_type.addByte(size);
- code_fixup_offset.add((int)put_empty(size));
- code_fixup_source.add(curIP);
-}
-
-inline // called exactly once => inline
- void
-unpacker::write_bc_ops()
-{
- bcimap.empty();
- code_fixup_type.empty();
- code_fixup_offset.empty();
- code_fixup_source.empty();
-
- band *bc_which;
-
- byte *opptr = bc_codes.curRP();
- // No need for oplimit, since the codes are pre-counted.
-
- size_t codeBase = wpoffset();
-
- bool isAload; // copy-out result
- int origBC;
-
- entry *thisClass = cur_class;
- entry *superClass = cur_super;
- entry *newClass = nullptr; // class of last _new opcode
-
- // overwrite any prior index on these bands; it changes w/ current class:
- bc_thisfield.setIndex(cp.getFieldIndex(thisClass));
- bc_thismethod.setIndex(cp.getMethodIndex(thisClass));
- if (superClass != nullptr)
- {
- bc_superfield.setIndex(cp.getFieldIndex(superClass));
- bc_supermethod.setIndex(cp.getMethodIndex(superClass));
- }
-
- for (int curIP = 0;; curIP++)
- {
- int curPC = (int)(wpoffset() - codeBase);
- bcimap.add(curPC);
- ensure_put_space(10); // covers most instrs w/o further bounds check
- int bc = *opptr++ & 0xFF;
-
- putu1_fast(bc);
- // Note: See '--wp' below for pseudo-bytecodes like bc_end_marker.
-
- bool isWide = false;
- if (bc == bc_wide)
- {
- bc = *opptr++ & 0xFF;
- putu1_fast(bc);
- isWide = true;
- }
- switch (bc)
- {
- case bc_end_marker:
- --wp; // not really part of the code
- assert(opptr <= bc_codes.maxRP());
- bc_codes.curRP() = opptr; // advance over this in bc_codes
- goto doneScanningMethod;
- case bc_tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label))
- case bc_lookupswitch: // apc: (df, nc, nc*(case, label))
- {
- int caseCount = bc_case_count.getInt();
- while (((wpoffset() - codeBase) % 4) != 0)
- putu1_fast(0);
- ensure_put_space(30 + caseCount * 8);
- put_label(curIP, 4); // int df = bc_label.getInt();
- if (bc == bc_tableswitch)
- {
- int lo = bc_case_value.getInt();
- int hi = lo + caseCount - 1;
- putu4(lo);
- putu4(hi);
- for (int j = 0; j < caseCount; j++)
- {
- put_label(curIP, 4); // int lVal = bc_label.getInt();
- // int cVal = lo + j;
- }
- }
- else
- {
- putu4(caseCount);
- for (int j = 0; j < caseCount; j++)
- {
- int cVal = bc_case_value.getInt();
- putu4(cVal);
- put_label(curIP, 4); // int lVal = bc_label.getInt();
- }
- }
- assert((int)to_bci(curIP) == curPC);
- continue;
- }
- case bc_iinc:
- {
- int local = bc_local.getInt();
- int delta = (isWide ? bc_short : bc_byte).getInt();
- if (isWide)
- {
- putu2(local);
- putu2(delta);
- }
- else
- {
- putu1_fast(local);
- putu1_fast(delta);
- }
- continue;
- }
- case bc_sipush:
- {
- int val = bc_short.getInt();
- putu2(val);
- continue;
- }
- case bc_bipush:
- case bc_newarray:
- {
- int val = bc_byte.getByte();
- putu1_fast(val);
- continue;
- }
- case bc_ref_escape:
- {
- // Note that insnMap has one entry for this.
- --wp; // not really part of the code
- int size = bc_escrefsize.getInt();
- entry *ref = bc_escref.getRefN();
- switch (size)
- {
- case 1:
- putu1ref(ref);
- break;
- case 2:
- putref(ref);
- break;
- default:
- assert(false);
- }
- continue;
- }
- case bc_byte_escape:
- {
- // Note that insnMap has one entry for all these bytes.
- --wp; // not really part of the code
- int size = bc_escsize.getInt();
- ensure_put_space(size);
- for (int j = 0; j < size; j++)
- putu1_fast(bc_escbyte.getByte());
- continue;
- }
- default:
- if (is_invoke_init_op(bc))
- {
- origBC = bc_invokespecial;
- entry *classRef;
- switch (bc - _invokeinit_op)
- {
- case _invokeinit_self_option:
- classRef = thisClass;
- break;
- case _invokeinit_super_option:
- classRef = superClass;
- break;
- default:
- assert(bc == _invokeinit_op + _invokeinit_new_option);
- case _invokeinit_new_option:
- classRef = newClass;
- break;
- }
- wp[-1] = origBC; // overwrite with origBC
- int coding = bc_initref.getInt();
- // Find the nth overloading of <init> in classRef.
- entry *ref = nullptr;
- cpindex *ix = (classRef == nullptr) ? nullptr : cp.getMethodIndex(classRef);
- for (int j = 0, which_init = 0;; j++)
- {
- ref = (ix == nullptr) ? nullptr : ix->get(j);
- if (ref == nullptr)
- break; // oops, bad input
- assert(ref->tag == CONSTANT_Methodref);
- if (ref->memberDescr()->descrName() == cp.sym[constant_pool::s_lt_init_gt])
- {
- if (which_init++ == coding)
- break;
- }
- }
- putref(ref);
- continue;
- }
- bc_which = ref_band_for_self_op(bc, isAload, origBC);
- if (bc_which != nullptr)
- {
- if (!isAload)
- {
- wp[-1] = origBC; // overwrite with origBC
- }
- else
- {
- wp[-1] = bc_aload_0; // overwrite with _aload_0
- // Note: insnMap keeps the _aload_0 separate.
- bcimap.add(++curPC);
- ++curIP;
- putu1_fast(origBC);
- }
- entry *ref = bc_which->getRef();
- putref(ref);
- continue;
- }
- if (is_branch_op(bc))
- {
- // int lVal = bc_label.getInt();
- if (bc < bc_goto_w)
- {
- put_label(curIP, 2); // putu2(lVal & 0xFFFF);
- }
- else
- {
- assert(bc <= bc_jsr_w);
- put_label(curIP, 4); // putu4(lVal);
- }
- assert((int)to_bci(curIP) == curPC);
- continue;
- }
- bc_which = ref_band_for_op(bc);
- if (bc_which != nullptr)
- {
- entry *ref = bc_which->getRefCommon(bc_which->ix, bc_which->nullOK);
- if (ref == nullptr && bc_which == &bc_classref)
- {
- // Shorthand for class self-references.
- ref = thisClass;
- }
- origBC = bc;
- switch (bc)
- {
- case bc_ildc:
- case bc_cldc:
- case bc_fldc:
- case bc_aldc:
- origBC = bc_ldc;
- break;
- case bc_ildc_w:
- case bc_cldc_w:
- case bc_fldc_w:
- case bc_aldc_w:
- origBC = bc_ldc_w;
- break;
- case bc_lldc2_w:
- case bc_dldc2_w:
- origBC = bc_ldc2_w;
- break;
- case bc_new:
- newClass = ref;
- break;
- }
- wp[-1] = origBC; // overwrite with origBC
- if (origBC == bc_ldc)
- {
- putu1ref(ref);
- }
- else
- {
- putref(ref);
- }
- if (origBC == bc_multianewarray)
- {
- // Copy the trailing byte also.
- int val = bc_byte.getByte();
- putu1_fast(val);
- }
- else if (origBC == bc_invokeinterface)
- {
- int argSize = ref->memberDescr()->descrType()->typeSize();
- putu1_fast(1 + argSize);
- putu1_fast(0);
- }
- continue;
- }
- if (is_local_slot_op(bc))
- {
- int local = bc_local.getInt();
- if (isWide)
- {
- putu2(local);
- if (bc == bc_iinc)
- {
- int iVal = bc_short.getInt();
- putu2(iVal);
- }
- }
- else
- {
- putu1_fast(local);
- if (bc == bc_iinc)
- {
- int iVal = bc_byte.getByte();
- putu1_fast(iVal);
- }
- }
- continue;
- }
- // Random bytecode. Just copy it.
- assert(bc < bc_bytecode_limit);
- }
- }
-doneScanningMethod:
-{
-}
- // bcimap.add(curPC); // PC limit is already also in map, from bc_end_marker
-
- // Armed with a bcimap, we can now fix up all the labels.
- for (int i = 0; i < (int)code_fixup_type.size(); i++)
- {
- int type = code_fixup_type.getByte(i);
- byte *bp = wp_at(code_fixup_offset.get(i));
- int curIP = code_fixup_source.get(i);
- int destIP = curIP + bc_label.getInt();
- int span = to_bci(destIP) - to_bci(curIP);
- switch (type)
- {
- case 2:
- putu2_at(bp, (ushort)span);
- break;
- case 4:
- putu4_at(bp, span);
- break;
- default:
- assert(false);
- }
- }
-}
-
-inline // called exactly once => inline
- void
-unpacker::write_code()
-{
- int j;
-
- int max_stack, max_locals, handler_count, cflags;
- get_code_header(max_stack, max_locals, handler_count, cflags);
-
- if (max_stack < 0)
- max_stack = code_max_stack.getInt();
- if (max_locals < 0)
- max_locals = code_max_na_locals.getInt();
- if (handler_count < 0)
- handler_count = code_handler_count.getInt();
-
- int siglen = cur_descr->descrType()->typeSize();
- if ((cur_descr_flags & ACC_STATIC) == 0)
- siglen++;
- max_locals += siglen;
-
- putu2(max_stack);
- putu2(max_locals);
- size_t bcbase = put_empty(4);
-
- // Write the bytecodes themselves.
- write_bc_ops();
-
- byte *bcbasewp = wp_at(bcbase);
- putu4_at(bcbasewp, (int)(wp - (bcbasewp + 4))); // size of code attr
-
- putu2(handler_count);
- for (j = 0; j < handler_count; j++)
- {
- int bii = code_handler_start_P.getInt();
- putu2(to_bci(bii));
- bii += code_handler_end_PO.getInt();
- putu2(to_bci(bii));
- bii += code_handler_catch_PO.getInt();
- putu2(to_bci(bii));
- putref(code_handler_class_RCN.getRefN());
- }
-
- uint64_t indexBits = cflags;
- if (cflags < 0)
- {
- bool haveLongFlags = attr_defs[ATTR_CONTEXT_CODE].haveLongFlags();
- indexBits = code_flags_hi.getLong(code_flags_lo, haveLongFlags);
- }
- write_attrs(ATTR_CONTEXT_CODE, indexBits);
-}
-
-int unpacker::write_attrs(int attrc, uint64_t indexBits)
-{
- if (indexBits == 0)
- {
- // Quick short-circuit.
- putu2(0);
- return 0;
- }
-
- attr_definitions &ad = attr_defs[attrc];
-
- int i, j, j2, idx, count;
-
- int oiCount = 0;
- if (ad.isPredefined(X_ATTR_OVERFLOW) && (indexBits & ((uint64_t)1 << X_ATTR_OVERFLOW)) != 0)
- {
- indexBits -= ((uint64_t)1 << X_ATTR_OVERFLOW);
- oiCount = ad.xxx_attr_count().getInt();
- }
-
- int bitIndexes[X_ATTR_LIMIT_FLAGS_HI];
- int biCount = 0;
-
- // Fill bitIndexes with index bits, in order.
- for (idx = 0; indexBits != 0; idx++, indexBits >>= 1)
- {
- if ((indexBits & 1) != 0)
- bitIndexes[biCount++] = idx;
- }
- assert(biCount <= (int)lengthof(bitIndexes));
-
- // Write a provisional attribute count, perhaps to be corrected later.
- int naOffset = (int)wpoffset();
- int na0 = biCount + oiCount;
- putu2(na0);
-
- int na = 0;
- for (i = 0; i < na0; i++)
- {
- if (i < biCount)
- idx = bitIndexes[i];
- else
- idx = ad.xxx_attr_indexes().getInt();
- assert(ad.isIndex(idx));
- entry *aname = nullptr;
- entry *ref; // scratch
- size_t abase = put_empty(2 + 4);
- if (idx < (int)ad.flag_limit && ad.isPredefined(idx))
- {
- // Switch on the attrc and idx simultaneously.
- switch (ADH_BYTE(attrc, idx))
- {
-
- case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_OVERFLOW) :
- case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_OVERFLOW) :
- case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_OVERFLOW) :
- case ADH_BYTE(ATTR_CONTEXT_CODE, X_ATTR_OVERFLOW) :
- // no attribute at all, so back up on this one
- wp = wp_at(abase);
- continue;
-
- case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_ClassFile_version) :
- cur_class_minver = class_ClassFile_version_minor_H.getInt();
- cur_class_majver = class_ClassFile_version_major_H.getInt();
- // back up; not a real attribute
- wp = wp_at(abase);
- continue;
-
- case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_InnerClasses) :
- // note the existence of this attr, but save for later
- if (cur_class_has_local_ics)
- unpack_abort("too many InnerClasses attrs");
- cur_class_has_local_ics = true;
- wp = wp_at(abase);
- continue;
-
- case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_SourceFile) :
- aname = cp.sym[constant_pool::s_SourceFile];
- ref = class_SourceFile_RUN.getRefN();
- if (ref == nullptr)
- {
- bytes &n = cur_class->ref(0)->value.b;
- // parse n = (<pkg>/)*<outer>?($<id>)*
- int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, (int)n.len) + 1;
- bytes prefix = n.slice(pkglen, n.len);
- for (;;)
- {
- // Work backwards, finding all '$', '#', etc.
- int dollar =
- lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, prefix, (int)prefix.len);
- if (dollar < 0)
- break;
- prefix = prefix.slice(0, dollar);
- }
- const char *suffix = ".java";
- int len = (int)(prefix.len + strlen(suffix));
- bytes name;
- name.set(T_NEW(byte, add_size(len, 1)), len);
- name.strcat(prefix).strcat(suffix);
- ref = cp.ensureUtf8(name);
- }
- putref(ref);
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_CLASS, CLASS_ATTR_EnclosingMethod) :
- aname = cp.sym[constant_pool::s_EnclosingMethod];
- putref(class_EnclosingMethod_RC.getRefN());
- putref(class_EnclosingMethod_RDN.getRefN());
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_FIELD, FIELD_ATTR_ConstantValue) :
- aname = cp.sym[constant_pool::s_ConstantValue];
- putref(field_ConstantValue_KQ.getRefUsing(cp.getKQIndex()));
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Code) :
- aname = cp.sym[constant_pool::s_Code];
- write_code();
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_METHOD, METHOD_ATTR_Exceptions) :
- aname = cp.sym[constant_pool::s_Exceptions];
- putu2(count = method_Exceptions_N.getInt());
- for (j = 0; j < count; j++)
- {
- putref(method_Exceptions_RC.getRefN());
- }
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_StackMapTable) :
- aname = cp.sym[constant_pool::s_StackMapTable];
- // (keep this code aligned with its brother in unpacker::read_attrs)
- putu2(count = code_StackMapTable_N.getInt());
- for (j = 0; j < count; j++)
- {
- int tag = code_StackMapTable_frame_T.getByte();
- putu1(tag);
- if (tag <= 127)
- {
- // (64-127) [(2)]
- if (tag >= 64)
- put_stackmap_type();
- }
- else if (tag <= 251)
- {
- // (247) [(1)(2)]
- // (248-251) [(1)]
- if (tag >= 247)
- putu2(code_StackMapTable_offset.getInt());
- if (tag == 247)
- put_stackmap_type();
- }
- else if (tag <= 254)
- {
- // (252) [(1)(2)]
- // (253) [(1)(2)(2)]
- // (254) [(1)(2)(2)(2)]
- putu2(code_StackMapTable_offset.getInt());
- for (int k = (tag - 251); k > 0; k--)
- {
- put_stackmap_type();
- }
- }
- else
- {
- // (255) [(1)NH[(2)]NH[(2)]]
- putu2(code_StackMapTable_offset.getInt());
- putu2(j2 = code_StackMapTable_local_N.getInt());
- while (j2-- > 0)
- put_stackmap_type();
- putu2(j2 = code_StackMapTable_stack_N.getInt());
- while (j2-- > 0)
- put_stackmap_type();
- }
- }
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LineNumberTable) :
- aname = cp.sym[constant_pool::s_LineNumberTable];
- putu2(count = code_LineNumberTable_N.getInt());
- for (j = 0; j < count; j++)
- {
- putu2(to_bci(code_LineNumberTable_bci_P.getInt()));
- putu2(code_LineNumberTable_line.getInt());
- }
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTable) :
- aname = cp.sym[constant_pool::s_LocalVariableTable];
- putu2(count = code_LocalVariableTable_N.getInt());
- for (j = 0; j < count; j++)
- {
- int bii = code_LocalVariableTable_bci_P.getInt();
- int bci = to_bci(bii);
- putu2(bci);
- bii += code_LocalVariableTable_span_O.getInt();
- putu2(to_bci(bii) - bci);
- putref(code_LocalVariableTable_name_RU.getRefN());
- putref(code_LocalVariableTable_type_RS.getRefN());
- putu2(code_LocalVariableTable_slot.getInt());
- }
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_CODE, CODE_ATTR_LocalVariableTypeTable) :
- aname = cp.sym[constant_pool::s_LocalVariableTypeTable];
- putu2(count = code_LocalVariableTypeTable_N.getInt());
- for (j = 0; j < count; j++)
- {
- int bii = code_LocalVariableTypeTable_bci_P.getInt();
- int bci = to_bci(bii);
- putu2(bci);
- bii += code_LocalVariableTypeTable_span_O.getInt();
- putu2(to_bci(bii) - bci);
- putref(code_LocalVariableTypeTable_name_RU.getRefN());
- putref(code_LocalVariableTypeTable_type_RS.getRefN());
- putu2(code_LocalVariableTypeTable_slot.getInt());
- }
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Signature) :
- aname = cp.sym[constant_pool::s_Signature];
- putref(class_Signature_RS.getRefN());
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Signature) :
- aname = cp.sym[constant_pool::s_Signature];
- putref(field_Signature_RS.getRefN());
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Signature) :
- aname = cp.sym[constant_pool::s_Signature];
- putref(method_Signature_RS.getRefN());
- break;
-
- case ADH_BYTE(ATTR_CONTEXT_CLASS, X_ATTR_Deprecated) :
- case ADH_BYTE(ATTR_CONTEXT_FIELD, X_ATTR_Deprecated) :
- case ADH_BYTE(ATTR_CONTEXT_METHOD, X_ATTR_Deprecated) :
- aname = cp.sym[constant_pool::s_Deprecated];
- // no data
- break;
- }
- }
-
- if (aname == nullptr)
- {
- // Unparse a compressor-defined attribute.
- layout_definition *lo = ad.getLayout(idx);
- if (lo == nullptr)
- {
- unpack_abort("bad layout index");
- break;
- }
- assert((int)lo->idx == idx);
- aname = lo->nameEntry;
- if (aname == nullptr)
- {
- bytes nameb;
- nameb.set(lo->name);
- aname = cp.ensureUtf8(nameb);
- // Cache the name entry for next time.
- lo->nameEntry = aname;
- }
- // Execute all the layout elements.
- band **bands = lo->bands();
- if (lo->hasCallables())
- {
- band &cble = *bands[0];
- assert(cble.le_kind == EK_CBLE);
- bands = cble.le_body;
- }
- putlayout(bands);
- }
-
- if (aname == nullptr)
- unpack_abort("bad attribute index");
-
- byte *wp1 = wp;
- wp = wp_at(abase);
-
- // DTRT if this attr is on the strip-list.
- // (Note that we emptied the data out of the band first.)
- if (ad.strip_names.contains(aname))
- {
- continue;
- }
-
- // patch the name and length
- putref(aname);
- putu4((int)(wp1 - (wp + 4))); // put the attr size
- wp = wp1;
- na++; // count the attrs actually written
- }
-
- if (na != na0)
- // Refresh changed count.
- putu2_at(wp_at(naOffset), na);
- return na;
-}
-
-void unpacker::write_members(int num, int attrc)
-{
- attr_definitions &ad = attr_defs[attrc];
- band &member_flags_hi = ad.xxx_flags_hi();
- band &member_flags_lo = ad.xxx_flags_lo();
- band &member_descr = (&member_flags_hi)[e_field_descr - e_field_flags_hi];
- bool haveLongFlags = ad.haveLongFlags();
-
- putu2(num);
- uint64_t indexMask = attr_defs[attrc].flagIndexMask();
- for (int i = 0; i < num; i++)
- {
- uint64_t mflags = member_flags_hi.getLong(member_flags_lo, haveLongFlags);
- entry *mdescr = member_descr.getRef();
- cur_descr = mdescr;
- putu2(cur_descr_flags = (ushort)(mflags & ~indexMask));
- putref(mdescr->descrName());
- putref(mdescr->descrType());
- write_attrs(attrc, (mflags & indexMask));
- }
- cur_descr = nullptr;
-}
-
-extern "C" int raw_address_cmp(const void *p1p, const void *p2p)
-{
- void *p1 = *(void **)p1p;
- void *p2 = *(void **)p2p;
- return (p1 > p2) ? 1 : (p1 < p2) ? -1 : 0;
-}
-
-void unpacker::write_classfile_tail()
-{
- cur_classfile_tail.empty();
- set_output(&cur_classfile_tail);
-
- int i, num;
-
- attr_definitions &ad = attr_defs[ATTR_CONTEXT_CLASS];
-
- bool haveLongFlags = ad.haveLongFlags();
- uint64_t kflags = class_flags_hi.getLong(class_flags_lo, haveLongFlags);
- uint64_t indexMask = ad.flagIndexMask();
-
- cur_class = class_this.getRef();
- cur_super = class_super.getRef();
-
- if (cur_super == cur_class)
- cur_super = nullptr;
- // special representation for java/lang/Object
-
- putu2((ushort)(kflags & ~indexMask));
- putref(cur_class);
- putref(cur_super);
-
- putu2(num = class_interface_count.getInt());
- for (i = 0; i < num; i++)
- {
- putref(class_interface.getRef());
- }
-
- write_members(class_field_count.getInt(), ATTR_CONTEXT_FIELD);
- write_members(class_method_count.getInt(), ATTR_CONTEXT_METHOD);
-
- cur_class_has_local_ics = false; // may be set true by write_attrs
-
- int naOffset = (int)wpoffset();
- int na = write_attrs(ATTR_CONTEXT_CLASS, (kflags & indexMask));
-
-// at the very last, choose which inner classes (if any) pertain to k:
-#ifdef ASSERT
- for (i = 0; i < ic_count; i++)
- {
- assert(!ics[i].requested);
- }
-#endif
- // First, consult the global table and the local constant pool,
- // and decide on the globally implied inner classes.
- // (Note that we read the cpool's outputIndex fields, but we
- // do not yet write them, since the local IC attribute might
- // reverse a global decision to declare an IC.)
- assert(requested_ics.length() == 0); // must start out empty
- // Always include all members of the current class.
- for (inner_class *child = cp.getFirstChildIC(cur_class); child != nullptr;
- child = cp.getNextChildIC(child))
- {
- child->requested = true;
- requested_ics.add(child);
- }
- // And, for each inner class mentioned in the constant pool,
- // include it and all its outers.
- int noes = cp.outputEntries.length();
- entry **oes = (entry **)cp.outputEntries.base();
- for (i = 0; i < noes; i++)
- {
- entry &e = *oes[i];
- if (e.tag != CONSTANT_Class)
- continue; // wrong sort
- for (inner_class *ic = cp.getIC(&e); ic != nullptr; ic = cp.getIC(ic->outer))
- {
- if (ic->requested)
- break; // already processed
- ic->requested = true;
- requested_ics.add(ic);
- }
- }
- int local_ics = requested_ics.length();
- // Second, consult a local attribute (if any) and adjust the global set.
- inner_class *extra_ics = nullptr;
- int num_extra_ics = 0;
- if (cur_class_has_local_ics)
- {
- // adjust the set of ICs by symmetric set difference w/ the locals
- num_extra_ics = class_InnerClasses_N.getInt();
- if (num_extra_ics == 0)
- {
- // Explicit zero count has an irregular meaning: It deletes the attr.
- local_ics = 0; // (short-circuit all tests of requested bits)
- }
- else
- {
- extra_ics = T_NEW(inner_class, num_extra_ics);
- // Note: extra_ics will be freed up by next call to get_next_file().
- }
- }
- for (i = 0; i < num_extra_ics; i++)
- {
- inner_class &extra_ic = extra_ics[i];
- extra_ic.inner = class_InnerClasses_RC.getRef();
- // Find the corresponding equivalent global IC:
- inner_class *global_ic = cp.getIC(extra_ic.inner);
- int flags = class_InnerClasses_F.getInt();
- if (flags == 0)
- {
- // The extra IC is simply a copy of a global IC.
- if (global_ic == nullptr)
- {
- unpack_abort("bad reference to inner class");
- break;
- }
- extra_ic = (*global_ic); // fill in rest of fields
- }
- else
- {
- flags &= ~ACC_IC_LONG_FORM; // clear high bit if set to get clean zero
- extra_ic.flags = flags;
- extra_ic.outer = class_InnerClasses_outer_RCN.getRefN();
- extra_ic.name = class_InnerClasses_name_RUN.getRefN();
- // Detect if this is an exact copy of the global tuple.
- if (global_ic != nullptr)
- {
- if (global_ic->flags != extra_ic.flags || global_ic->outer != extra_ic.outer ||
- global_ic->name != extra_ic.name)
- {
- global_ic = nullptr; // not really the same, so break the link
- }
- }
- }
- if (global_ic != nullptr && global_ic->requested)
- {
- // This local repetition reverses the globally implied request.
- global_ic->requested = false;
- extra_ic.requested = false;
- local_ics -= 1;
- }
- else
- {
- // The global either does not exist, or is not yet requested.
- extra_ic.requested = true;
- local_ics += 1;
- }
- }
- // Finally, if there are any that survived, put them into an attribute.
- // (Note that a zero-count attribute is always deleted.)
- // The putref calls below will tell the constant pool to add any
- // necessary local CP references to support the InnerClasses attribute.
- // This step must be the last round of additions to the local CP.
- if (local_ics > 0)
- {
- // append the new attribute:
- putref(cp.sym[constant_pool::s_InnerClasses]);
- putu4(2 + 2 * 4 * local_ics);
- putu2(local_ics);
- PTRLIST_QSORT(requested_ics, raw_address_cmp);
- int num_global_ics = requested_ics.length();
- for (i = -num_global_ics; i < num_extra_ics; i++)
- {
- inner_class *ic;
- if (i < 0)
- ic = (inner_class *)requested_ics.get(num_global_ics + i);
- else
- ic = &extra_ics[i];
- if (ic->requested)
- {
- putref(ic->inner);
- putref(ic->outer);
- putref(ic->name);
- putu2(ic->flags);
- }
- }
- putu2_at(wp_at(naOffset), ++na); // increment class attr count
- }
-
- // Tidy up global 'requested' bits:
- for (i = requested_ics.length(); --i >= 0;)
- {
- inner_class *ic = (inner_class *)requested_ics.get(i);
- ic->requested = false;
- }
- requested_ics.empty();
-
- close_output();
-
- // rewrite CP references in the tail
- cp.computeOutputIndexes();
- int nextref = 0;
- for (i = 0; i < (int)class_fixup_type.size(); i++)
- {
- int type = class_fixup_type.getByte(i);
- byte *fixp = wp_at(class_fixup_offset.get(i));
- entry *e = (entry *)class_fixup_ref.get(nextref++);
- int idx = e->getOutputIndex();
- switch (type)
- {
- case 1:
- putu1_at(fixp, idx);
- break;
- case 2:
- putu2_at(fixp, idx);
- break;
- default:
- assert(false); // should not reach here
- }
- }
-}
-
-void unpacker::write_classfile_head()
-{
- cur_classfile_head.empty();
- set_output(&cur_classfile_head);
-
- putu4(JAVA_MAGIC);
- putu2(cur_class_minver);
- putu2(cur_class_majver);
- putu2(cp.outputIndexLimit);
-
-#ifndef NDEBUG
- int checkIndex = 1;
-#endif
- int noes = cp.outputEntries.length();
- entry **oes = (entry **)cp.outputEntries.base();
- for (int i = 0; i < noes; i++)
- {
- entry &e = *oes[i];
- assert(e.getOutputIndex() == checkIndex++);
- byte tag = e.tag;
- assert(tag != CONSTANT_Signature);
- putu1(tag);
- switch (tag)
- {
- case CONSTANT_Utf8:
- putu2((int)e.value.b.len);
- put_bytes(e.value.b);
- break;
- case CONSTANT_Integer:
- case CONSTANT_Float:
- putu4(e.value.i);
- break;
- case CONSTANT_Long:
- case CONSTANT_Double:
- putu8(e.value.l);
- assert(checkIndex++);
- break;
- case CONSTANT_Class:
- case CONSTANT_String:
- // just write the ref
- putu2(e.refs[0]->getOutputIndex());
- break;
- case CONSTANT_Fieldref:
- case CONSTANT_Methodref:
- case CONSTANT_InterfaceMethodref:
- case CONSTANT_NameandType:
- putu2(e.refs[0]->getOutputIndex());
- putu2(e.refs[1]->getOutputIndex());
- break;
- default:
- unpack_abort(ERROR_INTERNAL);
- }
- }
- close_output();
-}
-
-unpacker::file *unpacker::get_next_file()
-{
- free_temps();
- if (files_remaining == 0)
- {
- // Leave a clue that we're exhausted.
- cur_file.name = nullptr;
- cur_file.size = 0;
- if (archive_size != 0)
- {
- uint64_t predicted_size = unsized_bytes_read + archive_size;
- if (predicted_size != bytes_read)
- unpack_abort("archive header had incorrect size");
- }
- return nullptr;
- }
- files_remaining -= 1;
- assert(files_written < file_count || classes_written < class_count);
- cur_file.name = "";
- cur_file.size = 0;
- cur_file.modtime = default_file_modtime;
- cur_file.options = default_file_options;
- cur_file.data[0].set(nullptr, 0);
- cur_file.data[1].set(nullptr, 0);
- if (files_written < file_count)
- {
- entry *e = file_name.getRef();
- cur_file.name = e->utf8String();
- bool haveLongSize = ((archive_options & AO_HAVE_FILE_SIZE_HI) != 0);
- cur_file.size = file_size_hi.getLong(file_size_lo, haveLongSize);
- if ((archive_options & AO_HAVE_FILE_MODTIME) != 0)
- cur_file.modtime += file_modtime.getInt(); // relative to archive modtime
- if ((archive_options & AO_HAVE_FILE_OPTIONS) != 0)
- cur_file.options |= file_options.getInt() & ~suppress_file_options;
- }
- else if (classes_written < class_count)
- {
- // there is a class for a missing file record
- cur_file.options |= FO_IS_CLASS_STUB;
- }
- if ((cur_file.options & FO_IS_CLASS_STUB) != 0)
- {
- assert(classes_written < class_count);
- classes_written += 1;
- if (cur_file.size != 0)
- {
- unpack_abort("class file size transmitted");
- }
- reset_cur_classfile();
-
- // write the meat of the classfile:
- write_classfile_tail();
- cur_file.data[1] = cur_classfile_tail.b;
-
- // write the CP of the classfile, second:
- write_classfile_head();
- cur_file.data[0] = cur_classfile_head.b;
-
- cur_file.size += cur_file.data[0].len;
- cur_file.size += cur_file.data[1].len;
- if (cur_file.name[0] == '\0')
- {
- bytes &prefix = cur_class->ref(0)->value.b;
- const char *suffix = ".class";
- int len = (int)(prefix.len + strlen(suffix));
- bytes name;
- name.set(T_NEW(byte, add_size(len, 1)), len);
- cur_file.name = name.strcat(prefix).strcat(suffix).strval();
- }
- }
- else
- {
- // If there is buffered file data, produce a pointer to it.
- if (cur_file.size != (size_t)cur_file.size)
- {
- // Silly size specified.
- unpack_abort("resource file too large");
- }
- size_t rpleft = input_remaining();
- if (rpleft > 0)
- {
- if (rpleft > cur_file.size)
- rpleft = (size_t)cur_file.size;
- cur_file.data[0].set(rp, rpleft);
- rp += rpleft;
- }
- if (rpleft < cur_file.size)
- {
- // Caller must read the rest.
- size_t fleft = (size_t)cur_file.size - rpleft;
- bytes_read += fleft; // Credit it to the overall archive size.
- }
- }
- bytes_written += cur_file.size;
- files_written += 1;
- return &cur_file;
-}
-
-// Write a file to jarout.
-void unpacker::write_file_to_jar(unpacker::file *f)
-{
- size_t htsize = f->data[0].len + f->data[1].len;
- uint64_t fsize = f->size;
- if (htsize == fsize)
- {
- jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, f->data[0], f->data[1]);
- }
- else
- {
- assert(input_remaining() == 0);
- bytes part1, part2;
- part1.len = f->data[0].len;
- part1.set(T_NEW(byte, part1.len), part1.len);
- part1.copyFrom(f->data[0]);
- assert(f->data[1].len == 0);
- part2.set(nullptr, 0);
- size_t fleft = (size_t)fsize - part1.len;
- assert(bytes_read > fleft); // part2 already credited by get_next_file
- bytes_read -= fleft;
- if (fleft > 0)
- {
- // Must read some more.
- if (live_input)
- {
- // Stop using the input buffer. Make a new one:
- if (free_input)
- input.free();
- input.init(fleft > (1 << 12) ? fleft : (1 << 12));
- free_input = true;
- live_input = false;
- }
- else
- {
- // Make it large enough.
- assert(free_input); // must be reallocable
- input.ensureSize(fleft);
- }
- rplimit = rp = input.base();
- input.setLimit(rp + fleft);
- if (!ensure_input(fleft))
- unpack_abort("EOF reading resource file");
- part2.ptr = input_scan();
- part2.len = input_remaining();
- rplimit = rp = input.base();
- }
- jarout->addJarEntry(f->name, f->deflate_hint(), f->modtime, part1, part2);
- }
- if (verbose >= 3)
- {
- fprintf(stderr, "Wrote %" PRIu64 " bytes to: %s\n", fsize, f->name);
- }
-}
diff --git a/libraries/pack200/src/unpack.h b/libraries/pack200/src/unpack.h
deleted file mode 100644
index cc5dd60a..00000000
--- a/libraries/pack200/src/unpack.h
+++ /dev/null
@@ -1,549 +0,0 @@
-/*
- * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-#pragma once
-
-// Global Structures
-struct jar;
-struct gunzip;
-struct band;
-struct constant_pool;
-struct entry;
-struct cpindex;
-struct inner_class;
-struct value_stream;
-
-typedef int64_t (*read_input_fn_t)(unpacker *self, void *buf, int64_t minlen, int64_t maxlen);
-
-struct cpindex
-{
- uint32_t len;
- entry *base1; // base of primary index
- entry **base2; // base of secondary index
- byte ixTag; // type of entries (!= CONSTANT_None), plus 64 if sub-index
- enum
- {
- SUB_TAG = 64
- };
-
- entry *get(uint32_t i);
-
- void init(int len_, entry *base1_, int ixTag_)
- {
- len = len_;
- base1 = base1_;
- base2 = nullptr;
- ixTag = ixTag_;
- }
- void init(int len_, entry **base2_, int ixTag_)
- {
- len = len_;
- base1 = nullptr;
- base2 = base2_;
- ixTag = ixTag_;
- }
-};
-
-struct constant_pool
-{
- uint32_t nentries;
- entry *entries;
- entry *first_extra_entry;
- uint32_t maxentries; // total allocated size of entries
-
- // Position and size of each homogeneous subrange:
- int tag_count[CONSTANT_Limit];
- int tag_base[CONSTANT_Limit];
- cpindex tag_index[CONSTANT_Limit];
- ptrlist tag_extras[CONSTANT_Limit];
-
- cpindex *member_indexes; // indexed by 2*CONSTANT_Class.inord
- cpindex *getFieldIndex(entry *classRef);
- cpindex *getMethodIndex(entry *classRef);
-
- inner_class **ic_index;
- inner_class **ic_child_index;
- inner_class *getIC(entry *inner);
- inner_class *getFirstChildIC(entry *outer);
- inner_class *getNextChildIC(inner_class *child);
-
- int outputIndexLimit; // index limit after renumbering
- ptrlist outputEntries; // list of entry* needing output idx assigned
-
- entry **hashTab;
- uint32_t hashTabLength;
- entry *&hashTabRef(byte tag, bytes &b);
- entry *ensureUtf8(bytes &b);
- entry *ensureClass(bytes &b);
-
- // Well-known Utf8 symbols.
- enum
- {
-#define SNAME(n, s) s_##s,
- ALL_ATTR_DO(SNAME)
-#undef SNAME
- s_lt_init_gt, // <init>
- s_LIMIT
- };
- entry *sym[s_LIMIT];
-
- // read counts from hdr, allocate main arrays
- enum
- {
- NUM_COUNTS = 12
- };
- void init(unpacker *u, int counts[NUM_COUNTS]);
-
- // pointer to outer unpacker, for error checks etc.
- unpacker *u;
-
- int getCount(byte tag)
- {
- assert((uint32_t)tag < CONSTANT_Limit);
- return tag_count[tag];
- }
- cpindex *getIndex(byte tag)
- {
- assert((uint32_t)tag < CONSTANT_Limit);
- return &tag_index[tag];
- }
- cpindex *getKQIndex(); // uses cur_descr
-
- void expandSignatures();
- void initMemberIndexes();
-
- void computeOutputOrder();
- void computeOutputIndexes();
- void resetOutputIndexes();
-};
-
-/*
- * The unpacker provides the entry points to the unpack engine,
- * as well as maintains the state of the engine.
- */
-struct unpacker
-{
- // One element of the resulting JAR.
- struct file
- {
- const char *name;
- uint64_t size;
- int modtime;
- int options;
- bytes data[2];
- // Note: If Sum(data[*].len) < size,
- // remaining bytes must be read directly from the input stream.
- bool deflate_hint()
- {
- return ((options & FO_DEFLATE_HINT) != 0);
- }
- };
-
- // if running Unix-style, here are the inputs and outputs
- FILE *infileptr; // buffered
- bytes inbytes; // direct
- gunzip *gzin; // gunzip filter, if any
- jar *jarout; // output JAR file
-
- // pointer to self, for U_NEW macro
- unpacker *u;
-
- ptrlist mallocs; // list of guys to free when we are all done
- ptrlist tmallocs; // list of guys to free on next client request
- fillbytes smallbuf; // supplies small alloc requests
- fillbytes tsmallbuf; // supplies temporary small alloc requests
-
- // option management members
- int verbose; // verbose level, 0 means no output
- int deflate_hint_or_zero; // ==0 means not set, otherwise -1 or 1
- int modification_time_or_zero;
-
- // input stream
- fillbytes input; // the whole block (size is predicted, has slop too)
- bool live_input; // is the data in this block live?
- bool free_input; // must the input buffer be freed?
- byte *rp; // read pointer (< rplimit <= input.limit())
- byte *rplimit; // how much of the input block has been read?
- uint64_t bytes_read;
- int unsized_bytes_read;
-
- // callback to read at least one byte, up to available input
- read_input_fn_t read_input_fn;
-
- // archive header fields
- int magic, minver, majver;
- size_t archive_size;
- int archive_next_count, archive_options, archive_modtime;
- int band_headers_size;
- int file_count, attr_definition_count, ic_count, class_count;
- int default_class_minver, default_class_majver;
- int default_file_options, suppress_file_options; // not header fields
- int default_archive_modtime, default_file_modtime; // not header fields
- int code_count; // not a header field
- int files_remaining; // not a header field
-
- // engine state
- band *all_bands; // indexed by band_number
- byte *meta_rp; // read-pointer into (copy of) band_headers
- constant_pool cp; // all constant pool information
- inner_class *ics; // InnerClasses
-
- // output stream
- bytes output; // output block (either classfile head or tail)
- byte *wp; // write pointer (< wplimit == output.limit())
- byte *wpbase; // write pointer starting address (<= wp)
- byte *wplimit; // how much of the output block has been written?
-
- // output state
- file cur_file;
- entry *cur_class; // CONSTANT_Class entry
- entry *cur_super; // CONSTANT_Class entry or nullptr
- entry *cur_descr; // CONSTANT_NameandType entry
- int cur_descr_flags; // flags corresponding to cur_descr
- int cur_class_minver, cur_class_majver;
- bool cur_class_has_local_ics;
- fillbytes cur_classfile_head;
- fillbytes cur_classfile_tail;
- int files_written; // also tells which file we're working on
- int classes_written; // also tells which class we're working on
- uint64_t bytes_written;
- intlist bcimap;
- fillbytes class_fixup_type;
- intlist class_fixup_offset;
- ptrlist class_fixup_ref;
- fillbytes code_fixup_type; // which format of branch operand?
- intlist code_fixup_offset; // location of operand needing fixup
- intlist code_fixup_source; // encoded ID of branch insn
- ptrlist requested_ics; // which ics need output?
-
- // stats pertaining to multiple segments (updated on reset)
- uint64_t bytes_read_before_reset;
- uint64_t bytes_written_before_reset;
- int files_written_before_reset;
- int classes_written_before_reset;
- int segments_read_before_reset;
-
- // attribute state
- struct layout_definition
- {
- uint32_t idx; // index (0..31...) which identifies this layout
- const char *name; // name of layout
- entry *nameEntry;
- const char *layout; // string of layout (not yet parsed)
- band **elems; // array of top-level layout elems (or callables)
-
- bool hasCallables()
- {
- return layout[0] == '[';
- }
- band **bands()
- {
- assert(elems != nullptr);
- return elems;
- }
- };
- struct attr_definitions
- {
- unpacker *u; // pointer to self, for U_NEW macro
- int xxx_flags_hi_bn; // locator for flags, count, indexes, calls bands
- int attrc; // ATTR_CONTEXT_CLASS, etc.
- uint32_t flag_limit; // 32 or 63, depending on archive_options bit
- uint64_t predef; // mask of built-in definitions
- uint64_t redef; // mask of local flag definitions or redefinitions
- ptrlist layouts; // local (compressor-defined) defs, in index order
- int flag_count[X_ATTR_LIMIT_FLAGS_HI];
- intlist overflow_count;
- ptrlist strip_names; // what attribute names are being stripped?
- ptrlist band_stack; // Temp., used during layout parsing.
- ptrlist calls_to_link; // (ditto)
- int bands_made; // (ditto)
-
- void free()
- {
- layouts.free();
- overflow_count.free();
- strip_names.free();
- band_stack.free();
- calls_to_link.free();
- }
-
- // Locate the five fixed bands.
- band &xxx_flags_hi();
- band &xxx_flags_lo();
- band &xxx_attr_count();
- band &xxx_attr_indexes();
- band &xxx_attr_calls();
- band &fixed_band(int e_class_xxx);
-
- // Register a new layout, and make bands for it.
- layout_definition *defineLayout(int idx, const char *name, const char *layout);
- layout_definition *defineLayout(int idx, entry *nameEntry, const char *layout);
- band **buildBands(layout_definition *lo);
-
- // Parse a layout string or part of one, recursively if necessary.
- const char *parseLayout(const char *lp, band **&res, int curCble);
- const char *parseNumeral(const char *lp, int &res);
- const char *parseIntLayout(const char *lp, band *&res, byte le_kind,
- bool can_be_signed = false);
- band **popBody(int band_stack_base); // pops a body off band_stack
-
- // Read data into the bands of the idx-th layout.
- void readBandData(int idx); // parse layout, make bands, read data
- void readBandData(band **body, uint32_t count); // recursive helper
-
- layout_definition *getLayout(uint32_t idx)
- {
- if (idx >= (uint32_t)layouts.length())
- return nullptr;
- return (layout_definition *)layouts.get(idx);
- }
-
- void setHaveLongFlags(bool z)
- {
- assert(flag_limit == 0); // not set up yet
- flag_limit = (z ? X_ATTR_LIMIT_FLAGS_HI : X_ATTR_LIMIT_NO_FLAGS_HI);
- }
- bool haveLongFlags()
- {
- assert(flag_limit == X_ATTR_LIMIT_NO_FLAGS_HI ||
- flag_limit == X_ATTR_LIMIT_FLAGS_HI);
- return flag_limit == X_ATTR_LIMIT_FLAGS_HI;
- }
-
- // Return flag_count if idx is predef and not redef, else zero.
- int predefCount(uint32_t idx);
-
- bool isRedefined(uint32_t idx)
- {
- if (idx >= flag_limit)
- return false;
- return (bool)((redef >> idx) & 1);
- }
- bool isPredefined(uint32_t idx)
- {
- if (idx >= flag_limit)
- return false;
- return (bool)(((predef & ~redef) >> idx) & 1);
- }
- uint64_t flagIndexMask()
- {
- return (predef | redef);
- }
- bool isIndex(uint32_t idx)
- {
- assert(flag_limit != 0); // must be set up already
- if (idx < flag_limit)
- return (bool)(((predef | redef) >> idx) & 1);
- else
- return (idx - flag_limit < (uint32_t)overflow_count.length());
- }
- int &getCount(uint32_t idx)
- {
- assert(isIndex(idx));
- if (idx < flag_limit)
- return flag_count[idx];
- else
- return overflow_count.get(idx - flag_limit);
- }
- };
-
- attr_definitions attr_defs[ATTR_CONTEXT_LIMIT];
-
- // Initialization
- void init(read_input_fn_t input_fn = nullptr);
- // Resets to a known sane state
- void reset();
- // Deallocates all storage.
- void free();
- // Deallocates temporary storage (volatile after next client call).
- void free_temps()
- {
- tsmallbuf.init();
- tmallocs.freeAll();
- }
-
- // Option management methods
- bool set_option(const char *option, const char *value);
- const char *get_option(const char *option);
-
- // Fetching input.
- bool ensure_input(int64_t more);
- byte *input_scan()
- {
- return rp;
- }
- size_t input_remaining()
- {
- return rplimit - rp;
- }
- size_t input_consumed()
- {
- return rp - input.base();
- }
-
- // Entry points to the unpack engine
- static int run(int argc, char **argv); // Unix-style entry point.
- void check_options();
- void start(void *packptr = nullptr, size_t len = 0);
- void write_file_to_jar(file *f);
- void finish();
-
- // Public post unpack methods
- int get_files_remaining()
- {
- return files_remaining;
- }
- int get_segments_remaining()
- {
- return archive_next_count;
- }
- file *get_next_file(); // returns nullptr on last file
-
- // General purpose methods
- void *alloc(size_t size)
- {
- return alloc_heap(size, true);
- }
- void *temp_alloc(size_t size)
- {
- return alloc_heap(size, true, true);
- }
- void *alloc_heap(size_t size, bool smallOK = false, bool temp = false);
- void saveTo(bytes &b, const char *str)
- {
- saveTo(b, (byte *)str, strlen(str));
- }
- void saveTo(bytes &b, bytes &data)
- {
- saveTo(b, data.ptr, data.len);
- }
- void saveTo(bytes &b, byte *ptr, size_t len); //{ b.ptr = U_NEW...}
- const char *saveStr(const char *str)
- {
- bytes buf;
- saveTo(buf, str);
- return buf.strval();
- }
- const char *saveIntStr(int num)
- {
- char buf[30];
- sprintf(buf, "%d", num);
- return saveStr(buf);
- }
- static unpacker *current(); // find current instance
-
- // Output management
- void set_output(fillbytes *which)
- {
- assert(wp == nullptr);
- which->ensureSize(1 << 12); // covers the average classfile
- wpbase = which->base();
- wp = which->limit();
- wplimit = which->end();
- }
- fillbytes *close_output(fillbytes *which = nullptr); // inverse of set_output
-
- // These take an implicit parameter of wp/wplimit, and resize as necessary:
- byte *put_space(size_t len); // allocates space at wp, returns pointer
- size_t put_empty(size_t s)
- {
- byte *p = put_space(s);
- return p - wpbase;
- }
- void ensure_put_space(size_t len);
- void put_bytes(bytes &b)
- {
- b.writeTo(put_space(b.len));
- }
- void putu1(int n)
- {
- putu1_at(put_space(1), n);
- }
- void putu1_fast(int n)
- {
- putu1_at(wp++, n);
- }
- void putu2(int n); // { putu2_at(put_space(2), n); }
- void putu4(int n); // { putu4_at(put_space(4), n); }
- void putu8(int64_t n); // { putu8_at(put_space(8), n); }
- void putref(entry *e); // { putu2_at(put_space(2), putref_index(e, 2)); }
- void putu1ref(entry *e); // { putu1_at(put_space(1), putref_index(e, 1)); }
- int putref_index(entry *e, int size); // size in [1..2]
- void put_label(int curIP, int size); // size in {2,4}
- void putlayout(band **body);
- void put_stackmap_type();
-
- size_t wpoffset()
- {
- return (size_t)(wp - wpbase);
- } // (unvariant across overflow)
- byte *wp_at(size_t offset)
- {
- return wpbase + offset;
- }
- uint32_t to_bci(uint32_t bii);
- void get_code_header(int &max_stack, int &max_na_locals, int &handler_count, int &cflags);
- band *ref_band_for_self_op(int bc, bool &isAloadVar, int &origBCVar);
- band *ref_band_for_op(int bc);
-
- // Definitions of standard classfile int formats:
- static void putu1_at(byte *wp, int n)
- {
- assert(n == (n & 0xFF));
- wp[0] = n;
- }
- static void putu2_at(byte *wp, int n);
- static void putu4_at(byte *wp, int n);
- static void putu8_at(byte *wp, int64_t n);
-
- // Private stuff
- void reset_cur_classfile();
- void write_classfile_tail();
- void write_classfile_head();
- void write_code();
- void write_bc_ops();
- void write_members(int num, int attrc); // attrc=ATTR_CONTEXT_FIELD/METHOD
- int write_attrs(int attrc, uint64_t indexBits);
-
- // The readers
- void read_bands();
- void read_file_header();
- void read_cp();
- void read_cp_counts(value_stream &hdr);
- void read_attr_defs();
- void read_ics();
- void read_attrs(int attrc, int obj_count);
- void read_classes();
- void read_code_headers();
- void read_bcs();
- void read_bc_ops();
- void read_files();
- void read_Utf8_values(entry *cpMap, int len);
- void read_single_words(band &cp_band, entry *cpMap, int len);
- void read_double_words(band &cp_bands, entry *cpMap, int len);
- void read_single_refs(band &cp_band, byte refTag, entry *cpMap, int len);
- void read_double_refs(band &cp_band, byte ref1Tag, byte ref2Tag, entry *cpMap, int len);
- void read_signature_values(entry *cpMap, int len);
-};
diff --git a/libraries/pack200/src/unpack200.cpp b/libraries/pack200/src/unpack200.cpp
deleted file mode 100644
index e8826f28..00000000
--- a/libraries/pack200/src/unpack200.cpp
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (c) 2003, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-#include <sys/types.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <assert.h>
-#include <limits.h>
-#include <time.h>
-#include <stdint.h>
-
-#include "constants.h"
-#include "utils.h"
-#include "defines.h"
-#include "bytes.h"
-#include "coding.h"
-#include "unpack200.h"
-#include "unpack.h"
-#include "zip.h"
-
-// Callback for fetching data, Unix style.
-static int64_t read_input_via_stdio(unpacker *u, void *buf, int64_t minlen, int64_t maxlen)
-{
- assert(u->infileptr != nullptr);
- assert(minlen <= maxlen); // don't talk nonsense
- int64_t numread = 0;
- char *bufptr = (char *)buf;
- while (numread < minlen)
- {
- // read available input, up to buf.length or maxlen
- int readlen = (1 << 16);
- if (readlen > (maxlen - numread))
- readlen = (int)(maxlen - numread);
- int nr = 0;
-
- nr = (int)fread(bufptr, 1, readlen, u->infileptr);
- if (nr <= 0)
- {
- if (errno != EINTR)
- break;
- nr = 0;
- }
- numread += nr;
- bufptr += nr;
- assert(numread <= maxlen);
- }
- return numread;
-}
-
-enum
-{
- EOF_MAGIC = 0,
- BAD_MAGIC = -1
-};
-
-static int read_magic(unpacker *u, char peek[], int peeklen)
-{
- assert(peeklen == 4); // magic numbers are always 4 bytes
- int64_t nr = (u->read_input_fn)(u, peek, peeklen, peeklen);
- if (nr != peeklen)
- {
- return (nr == 0) ? EOF_MAGIC : BAD_MAGIC;
- }
- int magic = 0;
- for (int i = 0; i < peeklen; i++)
- {
- magic <<= 8;
- magic += peek[i] & 0xFF;
- }
- return magic;
-}
-
-void unpack_200(FILE *input, FILE *output)
-{
- unpacker u;
- u.init(read_input_via_stdio);
-
- // initialize jar output
- // the output takes ownership of the file handle
- jar jarout;
- jarout.init(&u);
- jarout.jarfp = output;
-
- // the input doesn't
- u.infileptr = input;
-
- // read the magic!
- char peek[4];
- int magic;
- magic = read_magic(&u, peek, (int)sizeof(peek));
-
- // if it is a gzip encoded file, we need an extra gzip input filter
- if ((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC)
- {
- gunzip *gzin = NEW(gunzip, 1);
- gzin->init(&u);
- // FIXME: why the side effects? WHY?
- u.gzin->start(magic);
- u.start();
- }
- else
- {
- // otherwise, feed the bytes to the unpacker directly
- u.start(peek, sizeof(peek));
- }
-
- // Note: The checks to u.aborting() are necessary to gracefully
- // terminate processing when the first segment throws an error.
- for (;;)
- {
- // Each trip through this loop unpacks one segment
- // and then resets the unpacker.
- for (unpacker::file *filep; (filep = u.get_next_file()) != nullptr;)
- {
- u.write_file_to_jar(filep);
- }
-
- // Peek ahead for more data.
- magic = read_magic(&u, peek, (int)sizeof(peek));
- if (magic != (int)JAVA_PACKAGE_MAGIC)
- {
- // we do not feel strongly about this kind of thing...
- /*
- if (magic != EOF_MAGIC)
- unpack_abort("garbage after end of pack archive");
- */
- break; // all done
- }
-
- // Release all storage from parsing the old segment.
- u.reset();
- // Restart, beginning with the peek-ahead.
- u.start(peek, sizeof(peek));
- }
- u.finish();
- u.free(); // tidy up malloc blocks
- fclose(input);
-}
diff --git a/libraries/pack200/src/utils.cpp b/libraries/pack200/src/utils.cpp
deleted file mode 100644
index fd6dad60..00000000
--- a/libraries/pack200/src/utils.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
-#include <assert.h>
-#include <stdint.h>
-
-#include <sys/stat.h>
-
-#ifdef _MSC_VER
-#include <direct.h>
-#include <io.h>
-#include <process.h>
-#else
-#include <unistd.h>
-#endif
-
-#include "constants.h"
-#include "defines.h"
-#include "bytes.h"
-#include "utils.h"
-
-#include "unpack.h"
-
-void *must_malloc(size_t size)
-{
- size_t msize = size;
- void *ptr = (msize > PSIZE_MAX) ? nullptr : malloc(msize);
- if (ptr != nullptr)
- {
- memset(ptr, 0, size);
- }
- else
- {
- throw std::runtime_error(ERROR_ENOMEM);
- }
- return ptr;
-}
-
-void unpack_abort(const char *msg)
-{
- if (msg == nullptr)
- msg = "corrupt pack file or internal error";
- throw std::runtime_error(msg);
-}
diff --git a/libraries/pack200/src/utils.h b/libraries/pack200/src/utils.h
deleted file mode 100644
index 3bd2dae7..00000000
--- a/libraries/pack200/src/utils.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-// Definitions of our util functions
-
-#include <stdexcept>
-
-void *must_malloc(size_t size);
-
-// overflow management
-#define OVERFLOW ((size_t) - 1)
-#define PSIZE_MAX (OVERFLOW / 2) /* normal size limit */
-
-inline size_t scale_size(size_t size, size_t scale)
-{
- return (size > PSIZE_MAX / scale) ? OVERFLOW : size * scale;
-}
-
-inline size_t add_size(size_t size1, size_t size2)
-{
- return ((size1 | size2 | (size1 + size2)) > PSIZE_MAX) ? OVERFLOW : size1 + size2;
-}
-
-inline size_t add_size(size_t size1, size_t size2, int size3)
-{
- return add_size(add_size(size1, size2), size3);
-}
-
-struct unpacker;
-/// This throws an exception!
-extern void unpack_abort(const char *msg = nullptr);
diff --git a/libraries/pack200/src/zip.cpp b/libraries/pack200/src/zip.cpp
deleted file mode 100644
index e776510b..00000000
--- a/libraries/pack200/src/zip.cpp
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/**
- * Note: Lifted from uncrunch.c from jdk sources
- */
-#include <stdio.h>
-#include <string.h>
-#include <errno.h>
-#include <time.h>
-#include <stdint.h>
-
-#include <stdlib.h>
-#include <assert.h>
-
-#ifndef _MSC_VER
-#include <strings.h>
-#endif
-
-#include "defines.h"
-#include "bytes.h"
-#include "utils.h"
-
-#include "constants.h"
-#include "unpack.h"
-
-#include "zip.h"
-
-#include "zlib.h"
-
-inline uint32_t jar::get_crc32(uint32_t c, uchar *ptr, uint32_t len)
-{
- return crc32(c, ptr, len);
-}
-
-// FIXME: this is bullshit. Do real endianness detection.
-#ifdef sparc
-#define SWAP_BYTES(a) ((((a) << 8) & 0xff00) | 0x00ff) & (((a) >> 8) | 0xff00)
-#else
-#define SWAP_BYTES(a) (a)
-#endif
-
-#define GET_INT_LO(a) SWAP_BYTES(a & 0xFFFF)
-
-#define GET_INT_HI(a) SWAP_BYTES((a >> 16) & 0xFFFF);
-
-void jar::init(unpacker *u_)
-{
- BYTES_OF(*this).clear();
- u = u_;
- u->jarout = this;
-}
-
-// Write data to the ZIP output stream.
-void jar::write_data(void *buff, int len)
-{
- while (len > 0)
- {
- int rc = (int)fwrite(buff, 1, len, jarfp);
- if (rc <= 0)
- {
- fprintf(stderr, "Error: write on output file failed err=%d\n", errno);
- exit(1); // Called only from the native standalone unpacker
- }
- output_file_offset += rc;
- buff = ((char *)buff) + rc;
- len -= rc;
- }
-}
-
-void jar::add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen,
- uint32_t crc)
-{
- uint32_t fname_length = (uint32_t)strlen(fname);
- ushort header[23];
- if (modtime == 0)
- modtime = default_modtime;
- uint32_t dostime = get_dostime(modtime);
-
- header[0] = (ushort)SWAP_BYTES(0x4B50);
- header[1] = (ushort)SWAP_BYTES(0x0201);
- header[2] = (ushort)SWAP_BYTES(0xA);
-
- // required version
- header[3] = (ushort)SWAP_BYTES(0xA);
-
- // flags 02 = maximum sub-compression flag
- header[4] = (store) ? 0x0 : SWAP_BYTES(0x2);
-
- // Compression method 8=deflate.
- header[5] = (store) ? 0x0 : SWAP_BYTES(0x08);
-
- // Last modified date and time.
- header[6] = (ushort)GET_INT_LO(dostime);
- header[7] = (ushort)GET_INT_HI(dostime);
-
- // CRC
- header[8] = (ushort)GET_INT_LO(crc);
- header[9] = (ushort)GET_INT_HI(crc);
-
- // Compressed length:
- header[10] = (ushort)GET_INT_LO(clen);
- header[11] = (ushort)GET_INT_HI(clen);
-
- // Uncompressed length.
- header[12] = (ushort)GET_INT_LO(len);
- header[13] = (ushort)GET_INT_HI(len);
-
- // Filename length
- header[14] = (ushort)SWAP_BYTES(fname_length);
- // So called "extra field" length.
- header[15] = 0;
- // So called "comment" length.
- header[16] = 0;
- // Disk number start
- header[17] = 0;
- // File flags => binary
- header[18] = 0;
- // More file flags
- header[19] = 0;
- header[20] = 0;
- // Offset within ZIP file.
- header[21] = (ushort)GET_INT_LO(output_file_offset);
- header[22] = (ushort)GET_INT_HI(output_file_offset);
-
- // Copy the whole thing into the central directory.
- central_directory.append(header, sizeof(header));
-
- // Copy the fname to the header.
- central_directory.append(fname, fname_length);
-
- central_directory_count++;
-}
-
-void jar::write_jar_header(const char *fname, bool store, int modtime, int len, int clen,
- uint32_t crc)
-{
- uint32_t fname_length = (uint32_t)strlen(fname);
- ushort header[15];
- if (modtime == 0)
- modtime = default_modtime;
- uint32_t dostime = get_dostime(modtime);
-
- // ZIP LOC magic.
- header[0] = (ushort)SWAP_BYTES(0x4B50);
- header[1] = (ushort)SWAP_BYTES(0x0403);
-
- // Version
- header[2] = (ushort)SWAP_BYTES(0xA);
-
- // flags 02 = maximum sub-compression flag
- header[3] = (store) ? 0x0 : SWAP_BYTES(0x2);
-
- // Compression method = deflate
- header[4] = (store) ? 0x0 : SWAP_BYTES(0x08);
-
- // Last modified date and time.
- header[5] = (ushort)GET_INT_LO(dostime);
- header[6] = (ushort)GET_INT_HI(dostime);
-
- // CRC
- header[7] = (ushort)GET_INT_LO(crc);
- header[8] = (ushort)GET_INT_HI(crc);
-
- // Compressed length:
- header[9] = (ushort)GET_INT_LO(clen);
- header[10] = (ushort)GET_INT_HI(clen);
-
- // Uncompressed length.
- header[11] = (ushort)GET_INT_LO(len);
- header[12] = (ushort)GET_INT_HI(len);
-
- // Filename length
- header[13] = (ushort)SWAP_BYTES(fname_length);
- // So called "extra field" length.
- header[14] = 0;
-
- // Write the LOC header to the output file.
- write_data(header, (int)sizeof(header));
-
- // Copy the fname to the header.
- write_data((char *)fname, (int)fname_length);
-}
-
-void jar::write_central_directory()
-{
- bytes mc;
- mc.set("PACK200");
-
- ushort header[11];
-
- // Create the End of Central Directory structure.
- header[0] = (ushort)SWAP_BYTES(0x4B50);
- header[1] = (ushort)SWAP_BYTES(0x0605);
- // disk numbers
- header[2] = 0;
- header[3] = 0;
- // Number of entries in central directory.
- header[4] = (ushort)SWAP_BYTES(central_directory_count);
- header[5] = (ushort)SWAP_BYTES(central_directory_count);
- // Size of the central directory}
- header[6] = (ushort)GET_INT_LO((int)central_directory.size());
- header[7] = (ushort)GET_INT_HI((int)central_directory.size());
- // Offset of central directory within disk.
- header[8] = (ushort)GET_INT_LO(output_file_offset);
- header[9] = (ushort)GET_INT_HI(output_file_offset);
- // zipfile comment length;
- header[10] = (ushort)SWAP_BYTES((int)mc.len);
-
- // Write the central directory.
- write_data(central_directory.b);
-
- // Write the End of Central Directory structure.
- write_data(header, (int)sizeof(header));
-
- // Write the comment.
- write_data(mc);
-}
-
-// Public API
-
-// Open a Jar file and initialize.
-void jar::openJarFile(const char *fname)
-{
- if (!jarfp)
- {
- jarfp = fopen(fname, "wb");
- if (!jarfp)
- {
- fprintf(stderr, "Error: Could not open jar file: %s\n", fname);
- exit(3); // Called only from the native standalone unpacker
- }
- }
-}
-
-// Add a ZIP entry and copy the file data
-void jar::addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head,
- bytes &tail)
-{
- int len = (int)(head.len + tail.len);
- int clen = 0;
-
- uint32_t crc = get_crc32(0, Z_NULL, 0);
- if (head.len != 0)
- crc = get_crc32(crc, (uchar *)head.ptr, (uint32_t)head.len);
- if (tail.len != 0)
- crc = get_crc32(crc, (uchar *)tail.ptr, (uint32_t)tail.len);
-
- bool deflate = (deflate_hint && len > 0);
-
- if (deflate)
- {
- if (deflate_bytes(head, tail) == false)
- {
- deflate = false;
- }
- }
- clen = (int)((deflate) ? deflated.size() : len);
- add_to_jar_directory(fname, !deflate, modtime, len, clen, crc);
- write_jar_header(fname, !deflate, modtime, len, clen, crc);
-
- if (deflate)
- {
- write_data(deflated.b);
- }
- else
- {
- write_data(head);
- write_data(tail);
- }
-}
-
-// Add a ZIP entry for a directory name no data
-void jar::addDirectoryToJarFile(const char *dir_name)
-{
- bool store = true;
- add_to_jar_directory((const char *)dir_name, store, default_modtime, 0, 0, 0);
- write_jar_header((const char *)dir_name, store, default_modtime, 0, 0, 0);
-}
-
-// Write out the central directory and close the jar file.
-void jar::closeJarFile(bool central)
-{
- if (jarfp)
- {
- fflush(jarfp);
- if (central)
- write_central_directory();
- fflush(jarfp);
- fclose(jarfp);
- }
- reset();
-}
-
-/* Convert the date y/n/d and time h:m:s to a four byte DOS date and
- * time (date in high two bytes, time in low two bytes allowing magnitude
- * comparison).
- */
-inline uint32_t jar::dostime(int y, int n, int d, int h, int m, int s)
-{
- return y < 1980 ? dostime(1980, 1, 1, 0, 0, 0)
- : (((uint32_t)y - 1980) << 25) | ((uint32_t)n << 21) | ((uint32_t)d << 16) |
- ((uint32_t)h << 11) | ((uint32_t)m << 5) | ((uint32_t)s >> 1);
-}
-/*
-#ifdef _REENTRANT // solaris
-extern "C" struct tm *gmtime_r(const time_t *, struct tm *);
-#else
-#define gmtime_r(t, s) gmtime(t)
-#endif
-*/
-/*
- * Return the Unix time in DOS format
- */
-uint32_t jar::get_dostime(int modtime)
-{
- // see defines.h
- if (modtime != 0 && modtime == modtime_cache)
- return dostime_cache;
- if (modtime != 0 && default_modtime == 0)
- default_modtime = modtime; // catch a reasonable default
- time_t t = modtime;
- struct tm sbuf;
- (void)memset((void *)&sbuf, 0, sizeof(sbuf));
- struct tm *s = gmtime_r(&t, &sbuf);
- modtime_cache = modtime;
- dostime_cache =
- dostime(s->tm_year + 1900, s->tm_mon + 1, s->tm_mday, s->tm_hour, s->tm_min, s->tm_sec);
- // printf("modtime %d => %d\n", modtime_cache, dostime_cache);
- return dostime_cache;
-}
-
-/* Returns true on success, and will set the clen to the compressed
- length, the caller should verify if true and clen less than the
- input data
-*/
-bool jar::deflate_bytes(bytes &head, bytes &tail)
-{
- int len = (int)(head.len + tail.len);
-
- z_stream zs;
- BYTES_OF(zs).clear();
-
- // NOTE: the window size should always be -MAX_WBITS normally -15.
- // unzip/zipup.c and java/Deflater.c
-
- int error =
- deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY);
- if (error != Z_OK)
- {
- /*
- switch (error)
- {
- case Z_MEM_ERROR:
- PRINTCR((2, "Error: deflate error : Out of memory \n"));
- break;
- case Z_STREAM_ERROR:
- PRINTCR((2, "Error: deflate error : Invalid compression level \n"));
- break;
- case Z_VERSION_ERROR:
- PRINTCR((2, "Error: deflate error : Invalid version\n"));
- break;
- default:
- PRINTCR((2, "Error: Internal deflate error error = %d\n", error));
- }
- */
- return false;
- }
-
- deflated.empty();
- zs.next_out = (uchar *)deflated.grow(len + (len / 2));
- zs.avail_out = (int)deflated.size();
-
- zs.next_in = (uchar *)head.ptr;
- zs.avail_in = (int)head.len;
-
- bytes *first = &head;
- bytes *last = &tail;
- if (last->len == 0)
- {
- first = nullptr;
- last = &head;
- }
- else if (first->len == 0)
- {
- first = nullptr;
- }
-
- if (first != nullptr && error == Z_OK)
- {
- zs.next_in = (uchar *)first->ptr;
- zs.avail_in = (int)first->len;
- error = deflate(&zs, Z_NO_FLUSH);
- }
- if (error == Z_OK)
- {
- zs.next_in = (uchar *)last->ptr;
- zs.avail_in = (int)last->len;
- error = deflate(&zs, Z_FINISH);
- }
- if (error == Z_STREAM_END)
- {
- if (len > (int)zs.total_out)
- {
- deflated.b.len = zs.total_out;
- deflateEnd(&zs);
- return true;
- }
- deflateEnd(&zs);
- return false;
- }
-
- deflateEnd(&zs);
- return false;
-}
-
-// Callback for fetching data from a GZIP input stream
-static int64_t read_input_via_gzip(unpacker *u, void *buf, int64_t minlen, int64_t maxlen)
-{
- assert(minlen <= maxlen); // don't talk nonsense
- int64_t numread = 0;
- char *bufptr = (char *)buf;
- char *inbuf = u->gzin->inbuf;
- size_t inbuflen = sizeof(u->gzin->inbuf);
- read_input_fn_t read_gzin_fn = u->gzin->read_input_fn;
- z_stream &zs = *(z_stream *)u->gzin->zstream;
- while (numread < minlen)
- {
- int readlen = (1 << 16); // pretty arbitrary
- if (readlen > (maxlen - numread))
- readlen = (int)(maxlen - numread);
- zs.next_out = (uchar *)bufptr;
- zs.avail_out = readlen;
- if (zs.avail_in == 0)
- {
- zs.avail_in = (int)read_gzin_fn(u, inbuf, 1, inbuflen);
- zs.next_in = (uchar *)inbuf;
- }
- int error = inflate(&zs, Z_NO_FLUSH);
- if (error != Z_OK && error != Z_STREAM_END)
- {
- unpack_abort("error inflating input");
- break;
- }
- int nr = readlen - zs.avail_out;
- numread += nr;
- bufptr += nr;
- assert(numread <= maxlen);
- if (error == Z_STREAM_END)
- {
- enum
- {
- TRAILER_LEN = 8
- };
- // skip 8-byte trailer
- if (zs.avail_in >= TRAILER_LEN)
- {
- zs.avail_in -= TRAILER_LEN;
- }
- else
- {
- // Bug: 5023768,we read past the TRAILER_LEN to see if there is
- // any extraneous data, as we dont support concatenated .gz
- // files just yet.
- int extra = (int)read_gzin_fn(u, inbuf, 1, inbuflen);
- zs.avail_in += extra - TRAILER_LEN;
- }
- // %%% should check final CRC and length here
- // %%% should check for concatenated *.gz files here
- if (zs.avail_in > 0)
- unpack_abort("garbage after end of deflated input stream");
- // pop this filter off:
- u->gzin->free();
- break;
- }
- }
-
- // fprintf(u->errstrm, "readInputFn(%d,%d) => %d (gunzip)\n",
- // (int)minlen, (int)maxlen, (int)numread);
- return numread;
-}
-
-void gunzip::init(unpacker *u_)
-{
- BYTES_OF(*this).clear();
- u = u_;
- assert(u->gzin == nullptr); // once only, please
- read_input_fn = u->read_input_fn;
- zstream = NEW(z_stream, 1);
- u->gzin = this;
- u->read_input_fn = read_input_via_gzip;
-}
-
-void gunzip::start(int magic)
-{
- assert((magic & GZIP_MAGIC_MASK) == GZIP_MAGIC);
- int gz_flg = (magic & 0xFF); // keep "flg", discard other 3 bytes
- enum
- {
- FHCRC = (1 << 1),
- FEXTRA = (1 << 2),
- FNAME = (1 << 3),
- FCOMMENT = (1 << 4)
- };
- char gz_mtime[4];
- char gz_xfl[1];
- char gz_os[1];
- char gz_extra_len[2];
- char gz_hcrc[2];
- char gz_ignore;
- // do not save extra, name, comment
- read_fixed_field(gz_mtime, sizeof(gz_mtime));
- read_fixed_field(gz_xfl, sizeof(gz_xfl));
- read_fixed_field(gz_os, sizeof(gz_os));
- if (gz_flg & FEXTRA)
- {
- read_fixed_field(gz_extra_len, sizeof(gz_extra_len));
- int extra_len = gz_extra_len[0] & 0xFF;
- extra_len += (gz_extra_len[1] & 0xFF) << 8;
- for (; extra_len > 0; extra_len--)
- {
- read_fixed_field(&gz_ignore, 1);
- }
- }
- int null_terms = 0;
- if (gz_flg & FNAME)
- null_terms++;
- if (gz_flg & FCOMMENT)
- null_terms++;
- for (; null_terms; null_terms--)
- {
- for (;;)
- {
- gz_ignore = 0;
- read_fixed_field(&gz_ignore, 1);
- if (gz_ignore == 0)
- break;
- }
- }
- if (gz_flg & FHCRC)
- read_fixed_field(gz_hcrc, sizeof(gz_hcrc));
-
- // now the input stream is ready to read into the inflater
- int error = inflateInit2((z_stream *)zstream, -MAX_WBITS);
- if (error != Z_OK)
- {
- unpack_abort("cannot create input");
- }
-}
-
-void gunzip::free()
-{
- assert(u->gzin == this);
- u->gzin = nullptr;
- u->read_input_fn = this->read_input_fn;
- inflateEnd((z_stream *)zstream);
- ::free(zstream);
- zstream = nullptr;
- ::free(this);
-}
-
-void gunzip::read_fixed_field(char *buf, size_t buflen)
-{
- int64_t nr = read_input_fn(u, buf, buflen, buflen);
- if ((size_t)nr != buflen)
- unpack_abort("short stream header");
-}
diff --git a/libraries/pack200/src/zip.h b/libraries/pack200/src/zip.h
deleted file mode 100644
index ff973a86..00000000
--- a/libraries/pack200/src/zip.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2001, 2008, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-#pragma once
-
-#include <stdint.h>
-typedef unsigned short ushort;
-typedef unsigned int uint32_t;
-typedef unsigned char uchar;
-
-#include "unpack.h"
-
-struct jar
-{
- // JAR file writer
- FILE *jarfp;
- int default_modtime;
-
- // Used by unix2dostime:
- int modtime_cache;
- uint32_t dostime_cache;
-
- // Private members
- fillbytes central_directory;
- ushort central_directory_count;
- uint32_t output_file_offset;
- fillbytes deflated; // temporary buffer
-
- // pointer to outer unpacker, for error checks etc.
- unpacker *u;
-
- // Public Methods
- void openJarFile(const char *fname);
- void addJarEntry(const char *fname, bool deflate_hint, int modtime, bytes &head,
- bytes &tail);
- void addDirectoryToJarFile(const char *dir_name);
- void closeJarFile(bool central);
-
- void init(unpacker *u_);
-
- void free()
- {
- central_directory.free();
- deflated.free();
- }
-
- void reset()
- {
- free();
- init(u);
- }
-
- // Private Methods
- void write_data(void *ptr, int len);
- void write_data(bytes &b)
- {
- write_data(b.ptr, (int)b.len);
- }
- void add_to_jar_directory(const char *fname, bool store, int modtime, int len, int clen,
- uint32_t crc);
- void write_jar_header(const char *fname, bool store, int modtime, int len, int clen,
- unsigned int crc);
- void write_central_directory();
- uint32_t dostime(int y, int n, int d, int h, int m, int s);
- uint32_t get_dostime(int modtime);
-
- // The definitions of these depend on the NO_ZLIB option:
- bool deflate_bytes(bytes &head, bytes &tail);
- static uint32_t get_crc32(uint32_t c, unsigned char *ptr, uint32_t len);
-};
-
-struct gunzip
-{
- // optional gzip input stream control block
-
- // pointer to outer unpacker, for error checks etc.
- unpacker *u;
-
- read_input_fn_t read_input_fn; // underlying \bchar\b stream
- void *zstream; // inflater state
- char inbuf[1 << 14]; // input buffer
-
- void init(unpacker *u_); // pushes new value on u->read_input_fn
-
- void free();
-
- void start(int magic);
-
- // private stuff
- void read_fixed_field(char *buf, size_t buflen);
-};
diff --git a/libraries/rainbow/include/rainbow_config.h b/libraries/rainbow/include/rainbow_config.h
index ce7fc537..52cc7388 100644
--- a/libraries/rainbow/include/rainbow_config.h
+++ b/libraries/rainbow/include/rainbow_config.h
@@ -1,4 +1,4 @@
-/* Copyright 2013-2018 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;
+}
diff --git a/travis/prepare.sh b/travis/prepare.sh
deleted file mode 100644
index 9395bc78..00000000
--- a/travis/prepare.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-set -e
-
-if [ "$TRAVIS_OS_NAME" = "linux" ]
-then
- QT_WITHOUT_DOTS=qt$(echo $QT_VERSION | grep -oP "[^\.]*" | tr -d '\n' | tr '[:upper:]' '[:lower]')
- QT_PKG_PREFIX=$(echo $QT_WITHOUT_DOTS | cut -c1-4)
- QT_PKG_INSTALL=$QT_PKG_PREFIX
- if [ "$QT_PKG_PREFIX" = "qt50" ]; then QT_PKG_PREFIX=qt QT_PKG_INSTALL=qt5; fi
- echo $QT_WITHOUT_DOTS
- echo $QT_PKG_PREFIX
- echo $QT_PKG_INSTALL
- if [ "$TRAVIS_DIST" = "precise" ]; then
- sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS}
- else
- sudo add-apt-repository -y ppa:beineri/opt-${QT_WITHOUT_DOTS}-$TRAVIS_DIST
- fi
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test # for a recent GCC
- sudo add-apt-repository "deb http://llvm.org/apt/${TRAVIS_DIST}/ llvm-toolchain-${TRAVIS_DIST}-3.5 main"
-
- sudo apt-get update -qq
- sudo apt-get install ${QT_PKG_PREFIX}base ${QT_PKG_PREFIX}svg ${QT_PKG_PREFIX}tools
-
- sudo mkdir -p /opt/cmake-3/
- wget --no-check-certificate http://www.cmake.org/files/v3.9/cmake-3.9.3-Linux-x86_64.sh
- sudo sh cmake-3.9.3-Linux-x86_64.sh --skip-license --prefix=/opt/cmake-3/
-
- export CMAKE_PREFIX_PATH=/opt/$QT_PKG_INSTALL/lib/cmake
- export PATH=/opt/cmake-3/bin:/opt/$QT_PKG_INSTALL/bin:$PATH
-
- if [ "$CXX" = "g++" ]; then
- sudo apt-get install -y -qq g++-5
- export CXX='g++-5' CC='gcc-5'
- fi
- if [ "$CXX" = "clang++" ]; then
- wget -O - http://llvm.org/apt/llvm-snapshot.gpg.key|sudo apt-key add -
- sudo apt-get install -y -qq clang-3.5 liblldb-3.5 libclang1-3.5 libllvm3.5 lldb-3.5 llvm-3.5 llvm-3.5-runtime
- export CXX='clang++-3.5' CC='clang-3.5'
- fi
-else
- brew update
- brew install qt5
- brew install cmake
- export CMAKE_PREFIX_PATH=/usr/local/lib/cmake
-fi
-
-# Output versions
-cmake -version
-qmake -version
-$CXX -v
-echo "CMAKE_PREFIX_PATH=$CMAKE_PREFIX_PATH"