diff options
-rw-r--r-- | .github/workflows/build.yml | 2 | ||||
-rw-r--r-- | launcher/Application.cpp | 5 | ||||
-rw-r--r-- | launcher/Application.h | 1 | ||||
-rw-r--r-- | launcher/FileSystem.cpp | 222 | ||||
-rw-r--r-- | launcher/LaunchController.cpp | 11 | ||||
-rw-r--r-- | launcher/LaunchController.h | 5 | ||||
-rw-r--r-- | launcher/minecraft/MinecraftInstance.cpp | 8 | ||||
-rw-r--r-- | launcher/minecraft/MinecraftInstance.h | 2 | ||||
-rw-r--r-- | launcher/ui/InstanceWindow.cpp | 24 | ||||
-rw-r--r-- | launcher/ui/InstanceWindow.h | 2 | ||||
-rw-r--r-- | launcher/ui/MainWindow.cpp | 48 | ||||
-rw-r--r-- | launcher/ui/MainWindow.h | 2 | ||||
-rw-r--r-- | launcher/ui/pages/instance/ServersPage.cpp | 2 | ||||
-rw-r--r-- | libraries/launcher/org/polymc/applet/LegacyFrame.java | 5 | ||||
-rw-r--r-- | libraries/launcher/org/polymc/impl/OneSixLauncher.java | 9 | ||||
-rw-r--r-- | tests/FileSystem_test.cpp | 36 |
16 files changed, 212 insertions, 172 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 434c5775..ae8947ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: qt_ver: 6 - os: macos-12 - macosx_deployment_target: 10.14 + macosx_deployment_target: 10.15 qt_ver: 6 qt_host: mac qt_version: '6.3.0' diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c4179b49..0d1db11c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1041,7 +1041,7 @@ void Application::performMainStartupAction() qDebug() << " Launching with account" << m_profileToUse; } - launch(inst, true, nullptr, serverToJoin, accountToUse); + launch(inst, true, false, nullptr, serverToJoin, accountToUse); return; } } @@ -1145,6 +1145,7 @@ void Application::messageReceived(const QByteArray& message) launch( instance, true, + false, nullptr, serverObject, accountObject @@ -1245,6 +1246,7 @@ bool Application::openJsonEditor(const QString &filename) bool Application::launch( InstancePtr instance, bool online, + bool demo, BaseProfilerFactory *profiler, MinecraftServerTargetPtr serverToJoin, MinecraftAccountPtr accountToUse @@ -1268,6 +1270,7 @@ bool Application::launch( controller.reset(new LaunchController()); controller->setInstance(instance); controller->setOnline(online); + controller->setDemo(demo); controller->setProfiler(profiler); controller->setServerToJoin(serverToJoin); controller->setAccountToUse(accountToUse); diff --git a/launcher/Application.h b/launcher/Application.h index 41fd4c47..34ad8c15 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -213,6 +213,7 @@ public slots: bool launch( InstancePtr instance, bool online = true, + bool demo = false, BaseProfilerFactory *profiler = nullptr, MinecraftServerTargetPtr serverToJoin = nullptr, MinecraftAccountPtr accountToUse = nullptr diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8eeb2885..5d179641 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -37,6 +37,7 @@ #include <QDebug> #include <QDir> +#include <QDirIterator> #include <QFile> #include <QFileInfo> #include <QSaveFile> @@ -58,6 +59,24 @@ #include <utime.h> #endif +#include <filesystem> + +#if defined Q_OS_WIN32 + +std::wstring toStdString(QString s) +{ + return s.toStdWString(); +} + +#else + +std::string toStdString(QString s) +{ + return s.toStdString(); +} + +#endif + namespace FS { void ensureExists(const QDir& dir) @@ -128,6 +147,8 @@ bool ensureFolderPathExists(QString foldernamepath) bool copy::operator()(const QString& offset) { + using copy_opts = std::filesystem::copy_options; + // NOTE always deep copy on windows. the alternatives are too messy. #if defined Q_OS_WIN32 m_followSymlinks = true; @@ -136,94 +157,53 @@ bool copy::operator()(const QString& offset) auto src = PathCombine(m_src.absolutePath(), offset); auto dst = PathCombine(m_dst.absolutePath(), offset); - QFileInfo currentSrc(src); - if (!currentSrc.exists()) - return false; + std::error_code err; - if (!m_followSymlinks && currentSrc.isSymLink()) { - qDebug() << "creating symlink" << src << " - " << dst; - if (!ensureFilePathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::link(currentSrc.symLinkTarget(), dst); - } else if (currentSrc.isFile()) { - qDebug() << "copying file" << src << " - " << dst; - if (!ensureFilePathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::copy(src, dst); - } else if (currentSrc.isDir()) { - qDebug() << "recursing" << offset; - if (!ensureFolderPathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; - } - QDir currentDir(src); - for (auto& f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) { - auto inner_offset = PathCombine(offset, f); - // ignore and skip stuff that matches the blacklist. - if (m_blacklist && m_blacklist->matches(inner_offset)) { - continue; - } - if (!operator()(inner_offset)) { - qWarning() << "Failed to copy" << inner_offset; - return false; - } + std::filesystem::copy_options opt = copy_opts::none; + + // The default behavior is to follow symlinks + if (!m_followSymlinks) + opt |= copy_opts::copy_symlinks; + + + // We can't use copy_opts::recursive because we need to take into account the + // blacklisted paths, so we iterate over the source directory, and if there's no blacklist + // match, we copy the file. + QDir src_dir(src); + QDirIterator source_it(src, QDir::Filter::Files, QDirIterator::Subdirectories); + + while (source_it.hasNext()) { + auto src_path = source_it.next(); + auto relative_path = src_dir.relativeFilePath(src_path); + + if (m_blacklist && m_blacklist->matches(relative_path)) + continue; + + auto dst_path = PathCombine(dst, relative_path); + ensureFilePathExists(dst_path); + + std::filesystem::copy(toStdString(src_path), toStdString(dst_path), opt, err); + if (err) { + qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; } - } else { - qCritical() << "Copy ERROR: Unknown filesystem object:" << src; - return false; } - return true; + + return err.value() == 0; } bool deletePath(QString path) { - bool OK = true; - QFileInfo finfo(path); - if (finfo.isFile()) { - return QFile::remove(path); - } + std::error_code err; - QDir dir(path); + std::filesystem::remove_all(toStdString(path), err); - if (!dir.exists()) { - return OK; + if (err) { + qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); } - auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst); - for (auto& info : allEntries) { -#if defined Q_OS_WIN32 - QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); - auto wString = nativePath.toStdWString(); - DWORD dwAttrs = GetFileAttributesW(wString.c_str()); - // Windows: check for junctions, reparse points and other nasty things of that sort - if (dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) { - if (info.isFile()) { - OK &= QFile::remove(info.absoluteFilePath()); - } else if (info.isDir()) { - OK &= dir.rmdir(info.absoluteFilePath()); - } - } -#else - // We do not trust Qt with reparse points, but do trust it with unix symlinks. - if (info.isSymLink()) { - OK &= QFile::remove(info.absoluteFilePath()); - } -#endif - else if (info.isDir()) { - OK &= deletePath(info.absoluteFilePath()); - } else if (info.isFile()) { - OK &= QFile::remove(info.absoluteFilePath()); - } else { - OK = false; - qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); - } - } - OK &= dir.rmdir(dir.absolutePath()); - return OK; + return err.value() == 0; } bool trash(QString path, QString *pathInTrash = nullptr) @@ -316,8 +296,7 @@ QString DirNameFromString(QString string, QString inDir) if (num == 0) { dirName = baseName; } else { - dirName = baseName + QString::number(num); - ; + dirName = baseName + "(" + QString::number(num) + ")"; } // If it's over 9000 @@ -336,50 +315,6 @@ bool checkProblemticPathJava(QDir folder) return pathfoldername.contains("!", Qt::CaseInsensitive); } -// Win32 crap -#ifdef Q_OS_WIN - -bool called_coinit = false; - -HRESULT CreateLink(LPCCH linkPath, LPCWSTR targetPath, LPCWSTR args) -{ - HRESULT hres; - - if (!called_coinit) { - hres = CoInitialize(NULL); - called_coinit = true; - - if (!SUCCEEDED(hres)) { - qWarning("Failed to initialize COM. Error 0x%08lX", hres); - return hres; - } - } - - IShellLink* link; - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link); - - if (SUCCEEDED(hres)) { - IPersistFile* persistFile; - - link->SetPath(targetPath); - link->SetArguments(args); - - hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(hres)) { - WCHAR wstr[MAX_PATH]; - - MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); - - hres = persistFile->Save(wstr, TRUE); - persistFile->Release(); - } - link->Release(); - } - return hres; -} - -#endif - QString getDesktopDir() { return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); @@ -439,47 +374,24 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na #endif } -QStringList listFolderPaths(QDir root) -{ - auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); }; - - QStringList entries; - - root.refresh(); - for (auto entry : root.entryInfoList(QDir::Filter::Files)) { - entries.append(createAbsPath(entry)); - } - - for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) { - entries.append(listFolderPaths(createAbsPath(entry))); - } - - return entries; -} - bool overrideFolder(QString overwritten_path, QString override_path) { + using copy_opts = std::filesystem::copy_options; + if (!FS::ensureFolderPathExists(overwritten_path)) return false; - QStringList paths_to_override; - QDir root_override (override_path); - for (auto file : listFolderPaths(root_override)) { - QString destination = file; - destination.replace(override_path, overwritten_path); - ensureFilePathExists(destination); + std::error_code err; + std::filesystem::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; - qDebug() << QString("Applying override %1 in %2").arg(file, destination); + std::filesystem::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); - if (QFile::exists(destination)) - QFile::remove(destination); - if (!QFile::rename(file, destination)) { - qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination); - return false; - } + if (err) { + qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); + qCritical() << "Reason:" << QString::fromStdString(err.message()); } - return true; + return err.value() == 0; } } diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 11f9b2bb..830fcf82 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -167,6 +167,7 @@ void LaunchController::login() { tries++; m_session = std::make_shared<AuthSession>(); m_session->wants_online = m_online; + m_session->demo = m_demo; m_accountToUse->fillSession(m_session); // Launch immediately in true offline mode @@ -184,12 +185,18 @@ void LaunchController::login() { if(!m_session->wants_online) { // we ask the user for a player name bool ok = false; + + QString message = tr("Choose your offline mode player name."); + if(m_session->demo) { + message = tr("Choose your demo mode player name."); + } + QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName; QString name = QInputDialog::getText( m_parentWidget, tr("Player name"), - tr("Choose your offline mode player name."), + message, QLineEdit::Normal, usedname, &ok @@ -369,7 +376,7 @@ void LaunchController::launchInstance() } m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher)); } else { - online_mode = "offline"; + online_mode = m_demo ? "demo" : "offline"; } m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index 2171ad5e..af6c98d1 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -63,6 +63,10 @@ public: m_online = online; } + void setDemo(bool demo) { + m_demo = demo; + } + void setProfiler(BaseProfilerFactory *profiler) { m_profiler = profiler; } @@ -101,6 +105,7 @@ private slots: private: BaseProfilerFactory *m_profiler = nullptr; bool m_online = true; + bool m_demo = false; InstancePtr m_instance; QWidget * m_parentWidget = nullptr; InstanceWindow *m_console = nullptr; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 47f53948..62540c75 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -245,6 +245,14 @@ QString MinecraftInstance::getLocalLibraryPath() const return libraries_dir.absolutePath(); } +bool MinecraftInstance::supportsDemo() const +{ + Version instance_ver { getPackProfile()->getComponentVersion("net.minecraft") }; + // Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History + // FIXME: Due to Version constraints atm, this can't handle well non-release versions + return instance_ver >= Version("1.3.1"); +} + QString MinecraftInstance::jarModsDir() const { QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/")); diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index d62ac655..fe39674a 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -69,6 +69,8 @@ public: // where the instance-local libraries should be QString getLocalLibraryPath() const; + /** Returns whether the instance, with its version, has support for demo mode. */ + [[nodiscard]] bool supportsDemo() const; ////// Profile management ////// std::shared_ptr<PackProfile> getPackProfile() const; diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index 0ad8c594..09ce0d67 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -95,8 +95,14 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) m_launchOfflineButton = new QPushButton(); horizontalLayout->addWidget(m_launchOfflineButton); m_launchOfflineButton->setText(tr("Launch Offline")); + + m_launchDemoButton = new QPushButton(); + horizontalLayout->addWidget(m_launchDemoButton); + m_launchDemoButton->setText(tr("Launch Demo")); + updateLaunchButtons(); connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); + connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked())); m_closeButton = new QPushButton(); m_closeButton->setText(tr("Close")); @@ -143,6 +149,7 @@ void InstanceWindow::updateLaunchButtons() if(m_instance->isRunning()) { m_launchOfflineButton->setEnabled(false); + m_launchDemoButton->setEnabled(false); m_killButton->setText(tr("Kill")); m_killButton->setObjectName("killButton"); m_killButton->setToolTip(tr("Kill the running instance")); @@ -150,6 +157,7 @@ void InstanceWindow::updateLaunchButtons() else if(!m_instance->canLaunch()) { m_launchOfflineButton->setEnabled(false); + m_launchDemoButton->setEnabled(false); m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); @@ -158,6 +166,13 @@ void InstanceWindow::updateLaunchButtons() else { m_launchOfflineButton->setEnabled(true); + + // Disable demo-mode if not available. + auto instance = dynamic_cast<MinecraftInstance*>(m_instance.get()); + if (instance) { + m_launchDemoButton->setEnabled(instance->supportsDemo()); + } + m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); @@ -169,7 +184,12 @@ void InstanceWindow::updateLaunchButtons() void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() { - APPLICATION->launch(m_instance, false, nullptr); + APPLICATION->launch(m_instance, false, false, nullptr); +} + +void InstanceWindow::on_btnLaunchMinecraftDemo_clicked() +{ + APPLICATION->launch(m_instance, false, true, nullptr); } void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc) @@ -223,7 +243,7 @@ void InstanceWindow::on_btnKillMinecraft_clicked() } else { - APPLICATION->launch(m_instance, true, nullptr); + APPLICATION->launch(m_instance, true, false, nullptr); } } diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h index aec07868..554c4c74 100644 --- a/launcher/ui/InstanceWindow.h +++ b/launcher/ui/InstanceWindow.h @@ -74,6 +74,7 @@ slots: void on_closeButton_clicked(); void on_btnKillMinecraft_clicked(); void on_btnLaunchMinecraftOffline_clicked(); + void on_btnLaunchMinecraftDemo_clicked(); void instanceLaunchTaskChanged(shared_qobject_ptr<LaunchTask> proc); void runningStateChanged(bool running); @@ -93,4 +94,5 @@ private: QPushButton *m_closeButton = nullptr; QPushButton *m_killButton = nullptr; QPushButton *m_launchOfflineButton = nullptr; + QPushButton *m_launchDemoButton = nullptr; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 299401f5..58b1ae80 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -240,6 +240,7 @@ public: TranslatedAction actionCAT; TranslatedAction actionCopyInstance; TranslatedAction actionLaunchInstanceOffline; + TranslatedAction actionLaunchInstanceDemo; TranslatedAction actionScreenshots; TranslatedAction actionExportInstance; QVector<TranslatedAction *> all_actions; @@ -499,6 +500,7 @@ public: fileMenu->addAction(actionAddInstance); fileMenu->addAction(actionLaunchInstance); fileMenu->addAction(actionLaunchInstanceOffline); + fileMenu->addAction(actionLaunchInstanceDemo); fileMenu->addAction(actionKillInstance); fileMenu->addAction(actionCloseWindow); fileMenu->addSeparator(); @@ -669,6 +671,12 @@ public: actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); all_actions.append(&actionLaunchInstanceOffline); + actionLaunchInstanceDemo = TranslatedAction(MainWindow); + actionLaunchInstanceDemo->setObjectName(QStringLiteral("actionLaunchInstanceDemo")); + actionLaunchInstanceDemo.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch &Demo")); + actionLaunchInstanceDemo.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in demo mode.")); + all_actions.append(&actionLaunchInstanceDemo); + actionKillInstance = TranslatedAction(MainWindow); actionKillInstance->setObjectName(QStringLiteral("actionKillInstance")); actionKillInstance->setDisabled(true); @@ -1195,6 +1203,7 @@ void MainWindow::updateToolsMenu() ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning); ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning); + ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); QMenu *launchMenu = ui->actionLaunchInstance->menu(); QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); @@ -1220,23 +1229,37 @@ void MainWindow::updateToolsMenu() normalLaunch->setShortcut(QKeySequence::Open); QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); + QAction *normalLaunchDemo = launchOfflineMenu->addAction(tr("Launch Demo")); + normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch()); + normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch()); connect(normalLaunch, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, true); + APPLICATION->launch(m_selectedInstance, true, false); }); connect(normalLaunchOffline, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, false); + APPLICATION->launch(m_selectedInstance, false, false); + }); + connect(normalLaunchDemo, &QAction::triggered, [this]() { + APPLICATION->launch(m_selectedInstance, false, true); }); } else { normalLaunch->setDisabled(true); normalLaunchOffline->setDisabled(true); + normalLaunchDemo->setDisabled(true); + } + + // Disable demo-mode if not available. + auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get()); + if (instance) { + normalLaunchDemo->setEnabled(instance->supportsDemo()); } + QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); @@ -1260,11 +1283,11 @@ void MainWindow::updateToolsMenu() connect(profilerAction, &QAction::triggered, [this, profiler]() { - APPLICATION->launch(m_selectedInstance, true, profiler.get()); + APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); }); connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() { - APPLICATION->launch(m_selectedInstance, false, profiler.get()); + APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); }); } else @@ -2096,6 +2119,14 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered() } } +void MainWindow::on_actionLaunchInstanceDemo_triggered() +{ + if (m_selectedInstance) + { + APPLICATION->launch(m_selectedInstance, false, true); + } +} + void MainWindow::on_actionKillInstance_triggered() { if(m_selectedInstance && m_selectedInstance->isRunning()) @@ -2139,6 +2170,14 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->setInstanceActionsEnabled(true); ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); + ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch()); + + // Disable demo-mode if not available. + auto instance = dynamic_cast<MinecraftInstance*>(m_selectedInstance.get()); + if (instance) { + ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo()); + } + ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); ui->renameButton->setText(m_selectedInstance->name()); @@ -2158,6 +2197,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->setInstanceActionsEnabled(false); ui->actionLaunchInstance->setEnabled(false); ui->actionLaunchInstanceOffline->setEnabled(false); + ui->actionLaunchInstanceDemo->setEnabled(false); ui->actionKillInstance->setEnabled(false); APPLICATION->settings()->set("SelectedInstance", QString()); selectionBad(); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index dde3d02c..8b41bf94 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -140,6 +140,8 @@ private slots: void on_actionLaunchInstanceOffline_triggered(); + void on_actionLaunchInstanceDemo_triggered(); + void on_actionKillInstance_triggered(); void on_actionDeleteInstance_triggered(); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index e5cce96c..5e8bd7cc 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -811,7 +811,7 @@ void ServersPage::on_actionMove_Down_triggered() void ServersPage::on_actionJoin_triggered() { const auto &address = m_model->at(currentServer)->m_address; - APPLICATION->launch(m_inst, true, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address))); + APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared<MinecraftServerTarget>(MinecraftServerTarget::parse(address))); } #include "ServersPage.moc" diff --git a/libraries/launcher/org/polymc/applet/LegacyFrame.java b/libraries/launcher/org/polymc/applet/LegacyFrame.java index 2cdd17d7..7ae56e60 100644 --- a/libraries/launcher/org/polymc/applet/LegacyFrame.java +++ b/libraries/launcher/org/polymc/applet/LegacyFrame.java @@ -63,7 +63,8 @@ public final class LegacyFrame extends Frame { int winSizeH, boolean maximize, String serverAddress, - String serverPort + String serverPort, + boolean isDemo ) { // Implements support for launching in to multiplayer on classic servers using a mpticket // file generated by an external program and stored in the instance's root folder. @@ -106,7 +107,7 @@ public final class LegacyFrame extends Frame { appletWrap.setParameter("sessionid", session); appletWrap.setParameter("stand-alone", "true"); // Show the quit button. appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work. - appletWrap.setParameter("demo", "false"); + appletWrap.setParameter("demo", isDemo ? "true" : "false"); appletWrap.setParameter("fullscreen", "false"); add(appletWrap); diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java index 362ff8d6..d43101eb 100644 --- a/libraries/launcher/org/polymc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -24,8 +24,8 @@ import java.applet.Applet; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Collections; import java.util.List; +import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -58,10 +58,10 @@ public final class OneSixLauncher implements Launcher { public OneSixLauncher(Parameters params) { classLoader = ClassLoader.getSystemClassLoader(); - mcParams = params.allSafe("param", Collections.<String>emptyList()); + mcParams = params.allSafe("param", new ArrayList<String>()); mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); - traits = params.allSafe("traits", Collections.<String>emptyList()); + traits = params.allSafe("traits", new ArrayList<String>()); userName = params.first("userName"); sessionId = params.first("sessionId"); @@ -137,7 +137,8 @@ public final class OneSixLauncher implements Launcher { winSizeH, maximize, serverAddress, - serverPort + serverPort, + mcParams.contains("--demo") ); return; diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 6df13e80..4efb90ac 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -4,6 +4,8 @@ #include <FileSystem.h> +#include <pathmatcher/RegexpMatcher.h> + class FileSystemTest : public QObject { Q_OBJECT @@ -111,6 +113,40 @@ slots: f(); } + void test_copy_with_blacklist() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::copy c(folder, target_dir.path()); + c.blacklist(new RegexpMatcher("[.]?mcmeta")); + c(); + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + } + QVERIFY(!target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + void test_getDesktop() { QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); |