From f794e49bb6eadd70c52683e60a700a1d7e9cd17b Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Mon, 6 Feb 2023 23:05:06 -0800
Subject: we want to make links!
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
.gitignore | 2 +
launcher/CMakeLists.txt | 52 ++++++
launcher/FileSystem.cpp | 87 ++++++++-
launcher/FileSystem.h | 68 ++++++-
launcher/InstanceCopyPrefs.cpp | 30 +++
launcher/InstanceCopyPrefs.h | 9 +
launcher/filelink/FileLink.cpp | 119 ++++++++++++
launcher/filelink/FileLink.h | 52 ++++++
launcher/filelink/filelink.exe.manifest | 28 +++
launcher/filelink/main.cpp | 31 ++++
launcher/ui/dialogs/CopyInstanceDialog.cpp | 19 ++
launcher/ui/dialogs/CopyInstanceDialog.h | 3 +
launcher/ui/dialogs/CopyInstanceDialog.ui | 200 +++++++++++++-------
tests/FileSystem_test.cpp | 289 +++++++++++++++++++++++++++++
14 files changed, 918 insertions(+), 71 deletions(-)
create mode 100644 launcher/filelink/FileLink.cpp
create mode 100644 launcher/filelink/FileLink.h
create mode 100644 launcher/filelink/filelink.exe.manifest
create mode 100644 launcher/filelink/main.cpp
diff --git a/.gitignore b/.gitignore
index 3340670b..b3e2ee7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,8 @@ html/
CMakeLists.txt.user
CMakeLists.txt.user.*
CMakeSettings.json
+/CMakeFiles
+CMakeCache.txt
/.project
/.settings
/.idea
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 074570e3..2216aa1b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -559,6 +559,11 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
+set(LINKDAEMON_SOURCES
+ filelink/FileLink.h
+ filelink/FileLink.cpp
+)
+
######## Logging categories ########
ecm_qt_declare_logging_category(CORE_SOURCES
@@ -1107,6 +1112,53 @@ install(TARGETS ${Launcher_Name}
FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
)
+if(WIN32)
+ add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES})
+ target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+ target_link_libraries(filelink_logic
+ systeminfo
+ BuildConfig
+ Qt${QT_VERSION_MAJOR}::Widgets
+ ghcFilesystem::ghc_filesystem
+ )
+ target_link_libraries(filelink_logic
+ Qt${QT_VERSION_MAJOR}::Core
+ Qt${QT_VERSION_MAJOR}::Xml
+ Qt${QT_VERSION_MAJOR}::Network
+ Qt${QT_VERSION_MAJOR}::Concurrent
+ Qt${QT_VERSION_MAJOR}::Gui
+ Qt${QT_VERSION_MAJOR}::Widgets
+ ${Launcher_QT_LIBS}
+ )
+
+ add_executable("${Launcher_Name}_filelink" WIN32 filelink/main.cpp)
+
+ target_sources("${Launcher_Name}_filelink" PRIVATE filelink/filelink.exe.manifest)
+
+ target_link_libraries("${Launcher_Name}_filelink" filelink_logic)
+
+ if(DEFINED Launcher_APP_BINARY_NAME)
+ set_target_properties("${Launcher_Name}_filelink" PROPERTIES OUTPUT_NAME "${Launcher_APP_BINARY_NAME}_filelink")
+ endif()
+ if(DEFINED Launcher_BINARY_RPATH)
+ SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
+ endif()
+
+ if(CMAKE_GENERATOR MATCHES "Visual Studio")
+ SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE")
+ else()
+ SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE")
+ endif()
+
+
+ install(TARGETS "${Launcher_Name}_filelink"
+ BUNDLE DESTINATION "." COMPONENT Runtime
+ LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime
+ RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime
+ FRAMEWORK DESTINATION ${FRAMEWORK_DEST_DIR} COMPONENT Runtime
+ )
+endif()
+
if (UNIX AND APPLE)
# Add Sparkle updater
# It has to be copied here instead of just allowing fixup_bundle to install it, otherwise essential parts of
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index aee5245d..ec4af98c 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -152,9 +152,11 @@ bool ensureFolderPathExists(QString foldernamepath)
return success;
}
-/// @brief Copies a directory and it's contents from src to dest
-/// @param offset subdirectory form src to copy to dest
-/// @return if there was an error during the filecopy
+/**
+ * @brief Copies a directory and it's contents from src to dest
+ * @param offset subdirectory form src to copy to dest
+ * @return if there was an error during the filecopy
+ */
bool copy::operator()(const QString& offset, bool dryRun)
{
using copy_opts = fs::copy_options;
@@ -215,6 +217,85 @@ bool copy::operator()(const QString& offset, bool dryRun)
return err.value() == 0;
}
+
+/**
+ * @brief links a directory and it's contents from src to dest
+ * @param offset subdirectory form src to link to dest
+ * @return if there was an error during the attempt to link
+ */
+bool create_link::operator()(const QString& offset, bool dryRun)
+{
+ m_linked = 0; // reset counter
+
+ auto src = PathCombine(m_src.absolutePath(), offset);
+ auto dst = PathCombine(m_dst.absolutePath(), offset);
+
+ std::error_code err;
+
+ // you can't hard link a directory so make sure if we deal with a directory we do so recursively
+ if (m_useHardLinks)
+ m_recursive = true;
+
+ // Function that'll do the actual linking
+ auto link_file = [&](QString src_path, QString relative_dst_path) {
+ if (m_matcher && (m_matcher->matches(relative_dst_path) != m_whitelist))
+ return;
+
+ auto dst_path = PathCombine(dst, relative_dst_path);
+ if (!dryRun) {
+
+ ensureFilePathExists(dst_path);
+ if (m_useHardLinks) {
+ if (m_debug)
+ qDebug() << "making hard link:" << src_path << "to" << dst_path;
+ fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ } else if (fs::is_directory(StringUtils::toStdString(src_path))) {
+ if (m_debug)
+ qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
+ fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ } else {
+ if (m_debug)
+ qDebug() << "making symlink:" << src_path << "to" << dst_path;
+ fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ }
+
+ }
+ if (err) {
+ qWarning() << "Failed to link files:" << QString::fromStdString(err.message());
+ qDebug() << "Source file:" << src_path;
+ qDebug() << "Destination file:" << dst_path;
+ qDebug() << "Error catagory:" << err.category().name();
+ qDebug() << "Error code:" << err.value();
+ m_last_os_err = err.value();
+ emit linkFailed(src_path, dst_path, err);
+ } else {
+ m_linked++;
+ emit fileLinked(relative_dst_path);
+ }
+
+ };
+
+ if ((!m_recursive) || !fs::is_directory(StringUtils::toStdString(src))) {
+ if (m_debug)
+ qDebug() << "linking single file or dir:" << src << "to" << dst;
+ link_file(src, "");
+ } else {
+ if (m_debug)
+ qDebug() << "linking recursivly:" << src << "to" << dst;
+ QDir src_dir(src);
+ QDirIterator source_it(src, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::Subdirectories);
+
+ while (source_it.hasNext()) {
+ auto src_path = source_it.next();
+ auto relative_path = src_dir.relativeFilePath(src_path);
+
+ link_file(src_path, relative_path);
+ }
+ }
+
+ return err.value() == 0;
+}
+
bool move(const QString& source, const QString& dest)
{
std::error_code err;
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index f083f3c7..98f55f96 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -77,7 +77,9 @@ bool ensureFilePathExists(QString filenamepath);
*/
bool ensureFolderPathExists(QString filenamepath);
-/// @brief Copies a directory and it's contents from src to dest
+/**
+ * @brief Copies a directory and it's contents from src to dest
+ */
class copy : public QObject {
Q_OBJECT
public:
@@ -122,6 +124,70 @@ class copy : public QObject {
int m_copied;
};
+/**
+ * @brief Copies a directory and it's contents from src to dest
+ */
+class create_link : public QObject {
+ Q_OBJECT
+ public:
+ create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
+ {
+ m_src.setPath(src);
+ m_dst.setPath(dst);
+ }
+ create_link& useHardLinks(const bool useHard)
+ {
+ m_useHardLinks = useHard;
+ return *this;
+ }
+ create_link& matcher(const IPathMatcher* filter)
+ {
+ m_matcher = filter;
+ return *this;
+ }
+ create_link& whitelist(bool whitelist)
+ {
+ m_whitelist = whitelist;
+ return *this;
+ }
+ create_link& linkRecursively(bool recursive)
+ {
+ m_recursive = recursive;
+ return *this;
+ }
+ create_link& debug(bool d)
+ {
+ m_debug = d;
+ return *this;
+ }
+
+ int getLastOSError() {
+ return m_last_os_err;
+ }
+
+ bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
+
+ int totalLinked() { return m_linked; }
+
+ signals:
+ void fileLinked(const QString& relativeName);
+ void linkFailed(const QString& srcName, const QString& dstName, std::error_code err);
+
+ private:
+ bool operator()(const QString& offset, bool dryRun = false);
+
+ private:
+ bool m_useHardLinks = false;
+ const IPathMatcher* m_matcher = nullptr;
+ bool m_whitelist = false;
+ bool m_recursive = true;
+ QDir m_src;
+ QDir m_dst;
+ int m_linked;
+ bool m_debug = false;
+ int m_last_os_err = 0;
+};
+
/**
* @brief moves a file by renaming it
* @param source source file path
diff --git a/launcher/InstanceCopyPrefs.cpp b/launcher/InstanceCopyPrefs.cpp
index 7b93a516..18a6d704 100644
--- a/launcher/InstanceCopyPrefs.cpp
+++ b/launcher/InstanceCopyPrefs.cpp
@@ -93,6 +93,21 @@ bool InstanceCopyPrefs::isCopyScreenshotsEnabled() const
return copyScreenshots;
}
+bool InstanceCopyPrefs::isLinkFilesEnabled() const
+{
+ return linkFiles;
+}
+
+bool InstanceCopyPrefs::isUseHardLinksEnabled() const
+{
+ return useHardLinks;
+}
+
+bool InstanceCopyPrefs::isLinkWorldsEnabled() const
+{
+ return linkWorlds;
+}
+
// ======= Setters =======
void InstanceCopyPrefs::enableCopySaves(bool b)
{
@@ -133,3 +148,18 @@ void InstanceCopyPrefs::enableCopyScreenshots(bool b)
{
copyScreenshots = b;
}
+
+void InstanceCopyPrefs::enableLinkFiles(bool b)
+{
+ linkFiles = b;
+}
+
+void InstanceCopyPrefs::enableUseHardLinks(bool b)
+{
+ useHardLinks = b;
+}
+
+void InstanceCopyPrefs::enableLinkWorlds(bool b)
+{
+ linkWorlds = b;
+}
diff --git a/launcher/InstanceCopyPrefs.h b/launcher/InstanceCopyPrefs.h
index 6988b2df..25c0f3fc 100644
--- a/launcher/InstanceCopyPrefs.h
+++ b/launcher/InstanceCopyPrefs.h
@@ -19,6 +19,9 @@ struct InstanceCopyPrefs {
[[nodiscard]] bool isCopyServersEnabled() const;
[[nodiscard]] bool isCopyModsEnabled() const;
[[nodiscard]] bool isCopyScreenshotsEnabled() const;
+ [[nodiscard]] bool isLinkFilesEnabled() const;
+ [[nodiscard]] bool isUseHardLinksEnabled() const;
+ [[nodiscard]] bool isLinkWorldsEnabled() const;
// Setters
void enableCopySaves(bool b);
void enableKeepPlaytime(bool b);
@@ -28,6 +31,9 @@ struct InstanceCopyPrefs {
void enableCopyServers(bool b);
void enableCopyMods(bool b);
void enableCopyScreenshots(bool b);
+ void enableLinkFiles(bool b);
+ void enableUseHardLinks(bool b);
+ void enableLinkWorlds(bool b);
protected: // data
bool copySaves = true;
@@ -38,4 +44,7 @@ struct InstanceCopyPrefs {
bool copyServers = true;
bool copyMods = true;
bool copyScreenshots = true;
+ bool linkFiles = false;
+ bool useHardLinks = false;
+ bool linkWorlds = true;
};
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
new file mode 100644
index 00000000..9b5589ab
--- /dev/null
+++ b/launcher/filelink/FileLink.cpp
@@ -0,0 +1,119 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * 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, version 3.
+ *
+ * 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, see .
+ *
+ */
+
+#include "FileLink.h"
+#include "BuildConfig.h"
+
+
+#include
+
+#include
+#include
+
+#include
+
+
+#include
+#include
+
+#include
+
+#if defined Q_OS_WIN32
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include
+#include
+#endif
+
+
+
+
+FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv)
+{
+#if defined Q_OS_WIN32
+ // attach the parent console
+ if(AttachConsole(ATTACH_PARENT_PROCESS))
+ {
+ // if attach succeeds, reopen and sync all the i/o
+ if(freopen("CON", "w", stdout))
+ {
+ std::cout.sync_with_stdio();
+ }
+ if(freopen("CON", "w", stderr))
+ {
+ std::cerr.sync_with_stdio();
+ }
+ if(freopen("CON", "r", stdin))
+ {
+ std::cin.sync_with_stdio();
+ }
+ auto out = GetStdHandle (STD_OUTPUT_HANDLE);
+ DWORD written;
+ const char * endline = "\n";
+ WriteConsole(out, endline, strlen(endline), &written, NULL);
+ consoleAttached = true;
+ }
+#endif
+ setOrganizationName(BuildConfig.LAUNCHER_NAME);
+ setOrganizationDomain(BuildConfig.LAUNCHER_DOMAIN);
+ setApplicationName(BuildConfig.LAUNCHER_NAME + "FileLink");
+ setApplicationVersion(BuildConfig.printableVersionString() + "\n" + BuildConfig.GIT_COMMIT);
+
+ // Commandline parsing
+ QCommandLineParser parser;
+ parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher"));
+
+ parser.addOptions({
+
+ });
+ parser.addHelpOption();
+ parser.addVersionOption();
+
+ parser.process(arguments());
+
+ qDebug() << "link program launched";
+
+}
+
+
+FileLinkApp::~FileLinkApp()
+{
+ qDebug() << "link program shutting down";
+ // Shut down logger by setting the logger function to nothing
+ qInstallMessageHandler(nullptr);
+
+#if defined Q_OS_WIN32
+ // Detach from Windows console
+ if(consoleAttached)
+ {
+ fclose(stdout);
+ fclose(stdin);
+ fclose(stderr);
+ FreeConsole();
+ }
+#endif
+}
+
+
+
+
diff --git a/launcher/filelink/FileLink.h b/launcher/filelink/FileLink.h
new file mode 100644
index 00000000..253d1394
--- /dev/null
+++ b/launcher/filelink/FileLink.h
@@ -0,0 +1,52 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * 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, version 3.
+ *
+ * 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, see .
+ *
+ */
+
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class FileLinkApp : public QCoreApplication
+{
+ // friends for the purpose of limiting access to deprecated stuff
+ Q_OBJECT
+public:
+
+ FileLinkApp(int &argc, char **argv);
+ virtual ~FileLinkApp();
+
+private:
+ QDateTime m_startTime;
+
+#if defined Q_OS_WIN32
+ // used on Windows to attach the standard IO streams
+ bool consoleAttached = false;
+#endif
+};
diff --git a/launcher/filelink/filelink.exe.manifest b/launcher/filelink/filelink.exe.manifest
new file mode 100644
index 00000000..a4e16264
--- /dev/null
+++ b/launcher/filelink/filelink.exe.manifest
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/launcher/filelink/main.cpp b/launcher/filelink/main.cpp
new file mode 100644
index 00000000..7f06795e
--- /dev/null
+++ b/launcher/filelink/main.cpp
@@ -0,0 +1,31 @@
+// SPDX-FileCopyrightText: 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+//
+// SPDX-License-Identifier: GPL-3.0-only
+
+/*
+ * Prism Launcher - Minecraft Launcher
+ * Copyright (C) 2022 Rachel Powers <508861+Ryex@users.noreply.github.com>
+ *
+ * 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, version 3.
+ *
+ * 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, see .
+ *
+ */
+
+#include "FileLink.h"
+
+int main(int argc, char *argv[])
+{
+
+ FileLinkApp ldh(argc, argv);
+
+ return ldh.exec();
+}
\ No newline at end of file
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.cpp b/launcher/ui/dialogs/CopyInstanceDialog.cpp
index 3f5122f6..981352ae 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.cpp
+++ b/launcher/ui/dialogs/CopyInstanceDialog.cpp
@@ -85,6 +85,10 @@ CopyInstanceDialog::CopyInstanceDialog(InstancePtr original, QWidget *parent)
ui->copyServersCheckbox->setChecked(m_selectedOptions.isCopyServersEnabled());
ui->copyModsCheckbox->setChecked(m_selectedOptions.isCopyModsEnabled());
ui->copyScreenshotsCheckbox->setChecked(m_selectedOptions.isCopyScreenshotsEnabled());
+
+ ui->linkFilesGroup->setChecked(m_selectedOptions.isLinkFilesEnabled());
+ ui->hardLinksCheckbox->setChecked(m_selectedOptions.isUseHardLinksEnabled());
+ ui->linkWorldsCheckbox->setChecked(m_selectedOptions.isLinkWorldsEnabled());
}
CopyInstanceDialog::~CopyInstanceDialog()
@@ -220,3 +224,18 @@ void CopyInstanceDialog::on_copyScreenshotsCheckbox_stateChanged(int state)
m_selectedOptions.enableCopyScreenshots(state == Qt::Checked);
updateSelectAllCheckbox();
}
+
+void CopyInstanceDialog::on_linkFilesGroup_toggled(bool checked)
+{
+ m_selectedOptions.enableLinkFiles(checked);
+}
+
+void CopyInstanceDialog::on_hardLinksCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableUseHardLinks(state == Qt::Checked);
+}
+
+void CopyInstanceDialog::on_linkWorldsCheckbox_stateChanged(int state)
+{
+ m_selectedOptions.enableLinkWorlds(state == Qt::Checked);
+}
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.h b/launcher/ui/dialogs/CopyInstanceDialog.h
index 884501d1..a80faab9 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.h
+++ b/launcher/ui/dialogs/CopyInstanceDialog.h
@@ -55,6 +55,9 @@ slots:
void on_copyServersCheckbox_stateChanged(int state);
void on_copyModsCheckbox_stateChanged(int state);
void on_copyScreenshotsCheckbox_stateChanged(int state);
+ void on_linkFilesGroup_toggled(bool checked);
+ void on_hardLinksCheckbox_stateChanged(int state);
+ void on_linkWorldsCheckbox_stateChanged(int state);
private:
void checkAllCheckboxes(const bool& b);
diff --git a/launcher/ui/dialogs/CopyInstanceDialog.ui b/launcher/ui/dialogs/CopyInstanceDialog.ui
index b7828fe3..e41ad526 100644
--- a/launcher/ui/dialogs/CopyInstanceDialog.ui
+++ b/launcher/ui/dialogs/CopyInstanceDialog.ui
@@ -9,8 +9,8 @@
0
0
- 341
- 399
+ 525
+ 581
@@ -136,70 +136,126 @@
-
-
-
-
-
-
- Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.
-
-
- Copy mods
-
-
-
- -
-
+
+
+ Instance copy options
+
+
+
-
+
+
+ Disabling this will still keep the mod loader (ex: Fabric, Quilt, etc.) but erase the mods folder and their configs.
+
+
+ Copy mods
+
+
+
+ -
+
+
+ Copy the in-game options like FOV, max framerate, etc.
+
+
+ Copy game options
+
+
+
+ -
+
+
+ Copy saves
+
+
+
+ -
+
+
+ Copy shader packs
+
+
+
+ -
+
+
+ Copy servers
+
+
+
+ -
+
+
+ true
+
+
+ Copy resource packs
+
+
+
+ -
+
+
+ Keep play time
+
+
+
+ -
+
+
+ Copy screenshots
+
+
+
+
+
+
+ -
+
+
-
+
- Copy the in-game options like FOV, max framerate, etc.
+ Use symbolic links instead of copying files.
-
- Copy game options
+
+ Link files instead of copying them
-
-
- -
-
-
- Copy saves
-
-
-
- -
-
-
- Copy shader packs
-
-
-
- -
-
-
- Copy servers
+
+ false
-
-
- -
-
-
+
true
-
- Copy resource packs
-
-
-
- -
-
-
- Keep play time
-
-
-
- -
-
-
- Copy screenshots
+
+ false
+
+
-
+
+
+ Use hard links instead of symbolic links
+
+
+ Use hard links
+
+
+
+ -
+
+
+ World save data will be linked and thus shared between instances.
+
+
+ Link worlds
+
+
+ true
+
+
+ false
+
+
+
+
@@ -210,7 +266,7 @@
Qt::Horizontal
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+ QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok
@@ -220,10 +276,20 @@
iconButton
instNameTextBox
groupBox
+ selectAllCheckbox
+ keepPlaytimeCheckbox
+ copyScreenshotsCheckbox
+ copySavesCheckbox
+ copyShaderPacksCheckbox
+ copyGameOptionsCheckbox
+ copyServersCheckbox
+ copyResPacksCheckbox
+ copyModsCheckbox
+ linkFilesGroup
+ hardLinksCheckbox
+ linkWorldsCheckbox
-
-
-
+
buttonBox
@@ -232,8 +298,8 @@
accept()
- 254
- 316
+ 263
+ 571
157
@@ -248,8 +314,8 @@
reject()
- 322
- 316
+ 331
+ 571
286
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 3a5c38d0..ce83aa49 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -248,6 +248,295 @@ slots:
{
QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
}
+
+
+ void test_link()
+ {
+ 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::create_link lnk(folder, target_dir.path());
+ lnk.linkRecursively(false);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(!entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ 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_hard_link()
+ {
+ 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::create_link lnk(folder, target_dir.path());
+ lnk.useHardLinks(true);
+ lnk.debug(true);
+ if(!lnk()){
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(!entry_lnk_info.isSymbolicLink());
+ QFileInfo entry_orig_info(QDir(folder).filePath(entry));
+ if (!entry_lnk_info.isDir()) {
+ qDebug() << "hard link equivalency?" << entry_lnk_info.absoluteFilePath() << "vs" << entry_orig_info.absoluteFilePath();
+ QVERIFY(std::filesystem::equivalent(entry_lnk_info.filesystemAbsoluteFilePath(), entry_orig_info.filesystemAbsoluteFilePath()));
+ }
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(!lnk_info.isSymbolicLink());
+
+ 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_link_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::create_link lnk(folder, target_dir.path());
+ lnk.matcher(new RegexpMatcher("[.]?mcmeta"));
+ lnk.linkRecursively(true);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ 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_link_with_whitelist()
+ {
+ 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::create_link lnk(folder, target_dir.path());
+ lnk.matcher(new RegexpMatcher("[.]?mcmeta"));
+ lnk.whitelist(true);
+ lnk.linkRecursively(true);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ for(auto entry: target_dir.entryList())
+ {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ 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_link_with_dot_hidden()
+ {
+ 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::create_link lnk(folder, target_dir.path());
+ lnk.linkRecursively(true);
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ auto filter = QDir::Filter::Files | QDir::Filter::Dirs | QDir::Filter::Hidden;
+
+ for (auto entry: target_dir.entryList(filter)) {
+ qDebug() << entry;
+ QFileInfo entry_lnk_info(target_dir.filePath(entry));
+ QVERIFY(entry_lnk_info.isSymbolicLink());
+ }
+
+ QFileInfo lnk_info(target_dir.path());
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ QVERIFY(target_dir.entryList(filter).contains(".secret_folder"));
+ target_dir.cd(".secret_folder");
+ QVERIFY(target_dir.entryList(filter).contains(".secret_file.txt"));
+ };
+
+ // first try variant without trailing /
+ QVERIFY(!folder.endsWith('/'));
+ f();
+
+ // then variant with trailing /
+ folder.append('/');
+ QVERIFY(folder.endsWith('/'));
+ f();
+ }
+
+ void test_link_single_file()
+ {
+ QTemporaryDir tempDir;
+ tempDir.setAutoRemove(true);
+
+ {
+ QString file = QFINDTESTDATA("testdata/FileSystem/test_folder/pack.mcmeta");
+
+ qDebug() << "From:" << file << "To:" << tempDir.path();
+
+ QDir target_dir(FS::PathCombine(tempDir.path(), "pack.mcmeta"));
+ qDebug() << tempDir.path();
+ qDebug() << target_dir.path();
+ FS::create_link lnk(file, target_dir.filePath("pack.mcmeta"));
+ lnk.debug(true);
+ if(!lnk()){
+#if defined Q_OS_WIN32
+ qDebug() << "EXPECTED: Link failure, Windows requires permissions for symlinks";
+ QVERIFY(lnk.getLastOSError() == 1314);
+ return;
+#endif
+ qDebug() << "Link Failed!" << lnk.getLastOSError();
+ }
+
+ auto filter = QDir::Filter::Files;
+
+ for (auto entry: target_dir.entryList(filter)) {
+ qDebug() << entry;
+ }
+
+ QFileInfo lnk_info(target_dir.filePath("pack.mcmeta"));
+ QVERIFY(lnk_info.exists());
+ QVERIFY(lnk_info.isSymbolicLink());
+
+ QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta"));
+ }
+ }
};
QTEST_GUILESS_MAIN(FileSystemTest)
--
cgit
From 485f156e57b0fb30e51d1014de745bc6f90b7e3e Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 7 Feb 2023 02:56:16 -0700
Subject: working outside windows
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
tests/FileSystem_test.cpp | 28 +++++++++++++++-------------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index ce83aa49..84671889 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -278,12 +278,13 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(!entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(!entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
+ QVERIFY(lnk_info.isSymLink());
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
@@ -303,8 +304,9 @@ slots:
{
QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder");
auto f = [&folder]()
- {
- QTemporaryDir tempDir;
+ {
+ // use working dir to prevent makeing a hard link to a tmpfs or across devices
+ QTemporaryDir tempDir("./tmp");
tempDir.setAutoRemove(true);
qDebug() << "From:" << folder << "To:" << tempDir.path();
@@ -322,7 +324,7 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(!entry_lnk_info.isSymbolicLink());
+ QVERIFY(!entry_lnk_info.isSymLink());
QFileInfo entry_orig_info(QDir(folder).filePath(entry));
if (!entry_lnk_info.isDir()) {
qDebug() << "hard link equivalency?" << entry_lnk_info.absoluteFilePath() << "vs" << entry_orig_info.absoluteFilePath();
@@ -332,7 +334,7 @@ slots:
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(!lnk_info.isSymbolicLink());
+ QVERIFY(!lnk_info.isSymLink());
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
@@ -377,12 +379,12 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
QVERIFY(!target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(target_dir.entryList().contains("assets"));
@@ -428,12 +430,12 @@ slots:
{
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
QVERIFY(target_dir.entryList().contains("pack.mcmeta"));
QVERIFY(!target_dir.entryList().contains("assets"));
@@ -478,12 +480,12 @@ slots:
for (auto entry: target_dir.entryList(filter)) {
qDebug() << entry;
QFileInfo entry_lnk_info(target_dir.filePath(entry));
- QVERIFY(entry_lnk_info.isSymbolicLink());
+ if (!entry_lnk_info.isDir())
+ QVERIFY(entry_lnk_info.isSymLink());
}
QFileInfo lnk_info(target_dir.path());
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
QVERIFY(target_dir.entryList(filter).contains(".secret_folder"));
target_dir.cd(".secret_folder");
@@ -532,7 +534,7 @@ slots:
QFileInfo lnk_info(target_dir.filePath("pack.mcmeta"));
QVERIFY(lnk_info.exists());
- QVERIFY(lnk_info.isSymbolicLink());
+ QVERIFY(lnk_info.isSymLink());
QVERIFY(target_dir.entryList(filter).contains("pack.mcmeta"));
}
--
cgit
From 2ceefea5f346985bcc3a61c1562e0d836f1a0a83 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 7 Feb 2023 03:27:49 -0700
Subject: qt5 compatability
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
tests/FileSystem_test.cpp | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp
index 84671889..395ca5c0 100644
--- a/tests/FileSystem_test.cpp
+++ b/tests/FileSystem_test.cpp
@@ -3,6 +3,26 @@
#include
#include
+#include
+
+// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header
+
+#ifdef __APPLE__
+#include // for deployment target to support pre-catalina targets without std::fs
+#endif // __APPLE__
+
+#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include)
+#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
+#define GHC_USE_STD_FS
+#include
+namespace fs = std::filesystem;
+#endif // MacOS min version check
+#endif // Other OSes version check
+
+#ifndef GHC_USE_STD_FS
+#include
+namespace fs = ghc::filesystem;
+#endif
#include
@@ -328,7 +348,10 @@ slots:
QFileInfo entry_orig_info(QDir(folder).filePath(entry));
if (!entry_lnk_info.isDir()) {
qDebug() << "hard link equivalency?" << entry_lnk_info.absoluteFilePath() << "vs" << entry_orig_info.absoluteFilePath();
- QVERIFY(std::filesystem::equivalent(entry_lnk_info.filesystemAbsoluteFilePath(), entry_orig_info.filesystemAbsoluteFilePath()));
+ QVERIFY(fs::equivalent(
+ fs::path(StringUtils::toStdString(entry_lnk_info.absoluteFilePath())),
+ fs::path(StringUtils::toStdString(entry_orig_info.absoluteFilePath()))
+ ));
}
}
--
cgit
From 32409a361b797342d625bfc6d0726cc330ced760 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Tue, 7 Feb 2023 03:31:46 -0700
Subject: fix CMakeLits.txt for non MSVC windows builds
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 2216aa1b..18d4ce0b 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -559,7 +559,7 @@ set(ATLAUNCHER_SOURCES
modplatform/atlauncher/ATLShareCode.h
)
-set(LINKDAEMON_SOURCES
+set(LINKEXE_SOURCES
filelink/FileLink.h
filelink/FileLink.cpp
)
@@ -1113,7 +1113,7 @@ install(TARGETS ${Launcher_Name}
)
if(WIN32)
- add_library(filelink_logic STATIC ${LINKDAEMON_SOURCES})
+ add_library(filelink_logic STATIC ${LINKEXE_SOURCES})
target_include_directories(filelink_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(filelink_logic
systeminfo
@@ -1144,10 +1144,11 @@ if(WIN32)
SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES INSTALL_RPATH "${Launcher_BINARY_RPATH}")
endif()
+ # may be unnessacery with manifest
if(CMAKE_GENERATOR MATCHES "Visual Studio")
SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/level='requireAdministrator' /uiAccess='false' /SUBSYSTEM:CONSOLE")
- else()
- SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE")
+ # else() # link arg /MANIFESTUAC only works with MSVC
+ # SET_TARGET_PROPERTIES("${Launcher_Name}_filelink" PROPERTIES LINK_FLAGS "/MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" /SUBSYSTEM:CONSOLE")
endif()
--
cgit
From 6d160a7b7e31034c7a657f30003562c20f9b9c21 Mon Sep 17 00:00:00 2001
From: Rachel Powers <508861+Ryex@users.noreply.github.com>
Date: Wed, 8 Feb 2023 00:35:03 -0800
Subject: feat: successful process elevation and comunication!
Signed-off-by: Rachel Powers <508861+Ryex@users.noreply.github.com>
---
launcher/CMakeLists.txt | 9 +-
launcher/DesktopServices.cpp | 2 +-
launcher/FileSystem.cpp | 140 +++++++++++++++++++++++++++----
launcher/FileSystem.h | 57 +++++++++++--
launcher/StringUtils.cpp | 15 ++++
launcher/StringUtils.h | 2 +
launcher/filelink/FileLink.cpp | 106 ++++++++++++++++++++++--
launcher/filelink/FileLink.h | 15 ++++
tests/FileSystem_test.cpp | 181 +++++++++++++++++++++++++++--------------
9 files changed, 437 insertions(+), 90 deletions(-)
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 18d4ce0b..dd62893c 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -562,6 +562,13 @@ set(ATLAUNCHER_SOURCES
set(LINKEXE_SOURCES
filelink/FileLink.h
filelink/FileLink.cpp
+ FileSystem.h
+ FileSystem.cpp
+ Exception.h
+ StringUtils.h
+ StringUtils.cpp
+ DesktopServices.h
+ DesktopServices.cpp
)
######## Logging categories ########
@@ -1126,8 +1133,6 @@ if(WIN32)
Qt${QT_VERSION_MAJOR}::Xml
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Concurrent
- Qt${QT_VERSION_MAJOR}::Gui
- Qt${QT_VERSION_MAJOR}::Widgets
${Launcher_QT_LIBS}
)
diff --git a/launcher/DesktopServices.cpp b/launcher/DesktopServices.cpp
index 302eaf96..69770e99 100644
--- a/launcher/DesktopServices.cpp
+++ b/launcher/DesktopServices.cpp
@@ -37,7 +37,7 @@
#include
#include
#include
-#include "Application.h"
+//#include "Application.h"
/**
* This shouldn't exist, but until QTBUG-9328 and other unreported bugs are fixed, it needs to be a thing.
diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp
index ec4af98c..9e51f932 100644
--- a/launcher/FileSystem.cpp
+++ b/launcher/FileSystem.cpp
@@ -36,6 +36,8 @@
#include "FileSystem.h"
+#include "BuildConfig.h"
+
#include
#include
#include
@@ -45,6 +47,7 @@
#include
#include
#include
+#include
#include "DesktopServices.h"
#include "StringUtils.h"
@@ -61,6 +64,11 @@
#include
#include
#include
+//for ShellExecute
+#include
+//#include
+#include
+#include
#else
#include
#endif
@@ -218,19 +226,29 @@ bool copy::operator()(const QString& offset, bool dryRun)
}
+bool create_link::operator()(const QString& offset, bool dryRun)
+{
+
+ for (auto pair : m_path_pairs) {
+ if (!make_link(pair.src, pair.dst, offset, dryRun)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
/**
* @brief links a directory and it's contents from src to dest
* @param offset subdirectory form src to link to dest
* @return if there was an error during the attempt to link
*/
-bool create_link::operator()(const QString& offset, bool dryRun)
+bool create_link::make_link(const QString& srcPath, const QString& dstPath, const QString& offset, bool dryRun)
{
m_linked = 0; // reset counter
- auto src = PathCombine(m_src.absolutePath(), offset);
- auto dst = PathCombine(m_dst.absolutePath(), offset);
-
- std::error_code err;
+ auto src = PathCombine(QDir(srcPath).absolutePath(), offset);
+ auto dst = PathCombine(QDir(dstPath).absolutePath(), offset);
// you can't hard link a directory so make sure if we deal with a directory we do so recursively
if (m_useHardLinks)
@@ -248,26 +266,25 @@ bool create_link::operator()(const QString& offset, bool dryRun)
if (m_useHardLinks) {
if (m_debug)
qDebug() << "making hard link:" << src_path << "to" << dst_path;
- fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ fs::create_hard_link(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
} else if (fs::is_directory(StringUtils::toStdString(src_path))) {
if (m_debug)
qDebug() << "making directory_symlink:" << src_path << "to" << dst_path;
- fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ fs::create_directory_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
} else {
if (m_debug)
qDebug() << "making symlink:" << src_path << "to" << dst_path;
- fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), err);
+ fs::create_symlink(StringUtils::toStdString(src_path), StringUtils::toStdString(dst_path), m_os_err);
}
}
- if (err) {
- qWarning() << "Failed to link files:" << QString::fromStdString(err.message());
+ if (m_os_err) {
+ qWarning() << "Failed to link files:" << QString::fromStdString(m_os_err.message());
qDebug() << "Source file:" << src_path;
qDebug() << "Destination file:" << dst_path;
- qDebug() << "Error catagory:" << err.category().name();
- qDebug() << "Error code:" << err.value();
- m_last_os_err = err.value();
- emit linkFailed(src_path, dst_path, err);
+ qDebug() << "Error catagory:" << m_os_err.category().name();
+ qDebug() << "Error code:" << m_os_err.value();
+ emit linkFailed(src_path, dst_path, m_os_err);
} else {
m_linked++;
emit fileLinked(relative_dst_path);
@@ -290,10 +307,103 @@ bool create_link::operator()(const QString& offset, bool dryRun)
auto relative_path = src_dir.relativeFilePath(src_path);
link_file(src_path, relative_path);
+ if (m_os_err) return false;
}
}
- return err.value() == 0;
+ return m_os_err.value() == 0;
+}
+
+bool create_link::runPrivlaged(const QString& offset)
+{
+
+ QString serverName = BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink_server" + StringUtils::getRandomAlphaNumeric(8);
+
+ connect(&m_linkServer, &QLocalServer::newConnection, this, [&](){
+
+ qDebug() << "Client connected, sending out pairs";
+ // construct block of data to send
+ QByteArray block;
+ QDataStream out(&block, QIODevice::WriteOnly);
+ out.setVersion(QDataStream::Qt_5_15); // choose correct version better?
+
+ qint32 blocksize = quint32(sizeof(quint32));
+ for (auto pair : m_path_pairs) {
+ blocksize += quint32(pair.src.size());
+ blocksize += quint32(pair.dst.size());
+ }
+ qDebug() << "About to write block of size:" << blocksize;
+ out << blocksize;
+
+ out << quint32(m_path_pairs.length());
+ for (auto pair : m_path_pairs) {
+ out << pair.src;
+ out << pair.dst;
+ }
+
+ QLocalSocket *clientConnection = m_linkServer.nextPendingConnection();
+ connect(clientConnection, &QLocalSocket::disconnected,
+ clientConnection, &QLocalSocket::deleteLater);
+
+ qint64 byteswritten = clientConnection->write(block);
+ bool bytesflushed = clientConnection->flush();
+ qDebug() << "block flushed" << byteswritten << bytesflushed;
+ //clientConnection->disconnectFromServer();
+ });
+
+ qDebug() << "Listening on pipe" << serverName;
+ if (!m_linkServer.listen(serverName)) {
+ qDebug() << "Unable to start local pipe server on" << serverName << ":" << m_linkServer.errorString();
+ return false;
+ }
+
+ ExternalLinkFileProcess *linkFileProcess = new ExternalLinkFileProcess(serverName, this);
+ connect(linkFileProcess, &ExternalLinkFileProcess::processExited, this, [&](){
+ emit finishedPrivlaged();
+ });
+ connect(linkFileProcess, &ExternalLinkFileProcess::finished, linkFileProcess, &QObject::deleteLater);
+
+ linkFileProcess->start();
+
+ // linkFileProcess->wait();
+
+ return true;
+}
+
+
+void ExternalLinkFileProcess::runLinkFile() {
+ QString fileLinkExe = PathCombine(QCoreApplication::instance()->applicationDirPath(), BuildConfig.LAUNCHER_APP_BINARY_NAME + "_filelink");
+ QString params = "-s " + m_server;
+
+#if defined Q_OS_WIN32
+ SHELLEXECUTEINFO ShExecInfo;
+ HRESULT hr;
+
+ fileLinkExe = fileLinkExe + ".exe";
+
+ qDebug() << "Running: runas" << fileLinkExe << params;
+
+ LPCWSTR programNameWin = (const wchar_t*) fileLinkExe.utf16();
+ LPCWSTR paramsWin = (const wchar_t*) params.utf16();
+
+ // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-shellexecuteinfoa
+ ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
+ ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
+ ShExecInfo.hwnd = NULL; // Optional. A handle to the owner window, used to display and position any UI that the system might produce while executing this function.
+ ShExecInfo.lpVerb = L"runas"; // elevate to admin, show UAC
+ ShExecInfo.lpFile = programNameWin;
+ ShExecInfo.lpParameters = paramsWin;
+ ShExecInfo.lpDirectory = NULL;
+ ShExecInfo.nShow = SW_NORMAL;
+ ShExecInfo.hInstApp = NULL;
+
+ ShellExecuteEx(&ShExecInfo);
+
+ WaitForSingleObject(ShExecInfo.hProcess, INFINITE);
+ CloseHandle(ShExecInfo.hProcess);
+#endif
+
+ qDebug() << "Process exited";
}
bool move(const QString& source, const QString& dest)
diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h
index 98f55f96..b15d1685 100644
--- a/launcher/FileSystem.h
+++ b/launcher/FileSystem.h
@@ -39,9 +39,13 @@
#include "Exception.h"
#include "pathmatcher/IPathMatcher.h"
+#include
+
#include
#include
#include
+#include
+#include
namespace FS {
@@ -124,16 +128,45 @@ class copy : public QObject {
int m_copied;
};
+struct LinkPair {
+ QString src;
+ QString dst;
+};
+
+class ExternalLinkFileProcess : public QThread
+{
+ Q_OBJECT
+ public:
+ ExternalLinkFileProcess(QString server, QObject* parent = nullptr) : QThread(parent), m_server(server) {}
+
+ void run() override {
+ runLinkFile();
+ emit processExited();
+ }
+
+ signals:
+ void processExited();
+
+ private:
+ void runLinkFile();
+
+ QString m_server;
+};
+
/**
- * @brief Copies a directory and it's contents from src to dest
+ * @brief links (a file / a directory and it's contents) from src to dest
*/
class create_link : public QObject {
Q_OBJECT
public:
+ create_link(const QList path_pairs, QObject* parent = nullptr) : QObject(parent)
+ {
+ m_path_pairs.append(path_pairs);
+ }
create_link(const QString& src, const QString& dst, QObject* parent = nullptr) : QObject(parent)
{
- m_src.setPath(src);
- m_dst.setPath(dst);
+ LinkPair pair = {src, dst};
+ m_path_pairs.append(pair);
}
create_link& useHardLinks(const bool useHard)
{
@@ -161,31 +194,39 @@ class create_link : public QObject {
return *this;
}
- int getLastOSError() {
- return m_last_os_err;
+ std::error_code getOSError() {
+ return m_os_err;
}
bool operator()(bool dryRun = false) { return operator()(QString(), dryRun); }
+ bool runPrivlaged() { return runPrivlaged(QString()); }
+ bool runPrivlaged(const QString& offset);
+
int totalLinked() { return m_linked; }
signals:
void fileLinked(const QString& relativeName);
void linkFailed(const QString& srcName, const QString& dstName, std::error_code err);
+ void finishedPrivlaged();
private:
bool operator()(const QString& offset, bool dryRun = false);
+ bool make_link(const QString& src_path, const QString& dst_path, const QString& offset, bool dryRun);
private:
bool m_useHardLinks = false;
const IPathMatcher* m_matcher = nullptr;
bool m_whitelist = false;
bool m_recursive = true;
- QDir m_src;
- QDir m_dst;
+
+ QList m_path_pairs;
+
int m_linked;
bool m_debug = false;
- int m_last_os_err = 0;
+ std::error_code m_os_err;
+
+ QLocalServer m_linkServer;
};
/**
diff --git a/launcher/StringUtils.cpp b/launcher/StringUtils.cpp
index 0f3c3669..93a44d4c 100644
--- a/launcher/StringUtils.cpp
+++ b/launcher/StringUtils.cpp
@@ -1,5 +1,7 @@
#include "StringUtils.h"
+#include
+
/// If you're wondering where these came from exactly, then know you're not the only one =D
/// TAKEN FROM Qt, because it doesn't expose it intelligently
@@ -74,3 +76,16 @@ int StringUtils::naturalCompare(const QString& s1, const QString& s2, Qt::CaseSe
// The two strings are the same (02 == 2) so fall back to the normal sort
return QString::compare(s1, s2, cs);
}
+
+QString StringUtils::getRandomAlphaNumeric(const int length)
+{
+ const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
+ QString randomString;
+ for(int i=0; i < length; ++i)
+ {
+ int index = QRandomGenerator::global()->bounded(0, possibleCharacters.length());
+ QChar nextChar = possibleCharacters.at(index);
+ randomString.append(nextChar);
+ }
+ return randomString;
+}
diff --git a/launcher/StringUtils.h b/launcher/StringUtils.h
index 1799605b..1ba19555 100644
--- a/launcher/StringUtils.h
+++ b/launcher/StringUtils.h
@@ -29,4 +29,6 @@ inline QString fromStdString(string s)
#endif
int naturalCompare(const QString& s1, const QString& s2, Qt::CaseSensitivity cs);
+
+QString getRandomAlphaNumeric(const int length);
} // namespace StringUtils
diff --git a/launcher/filelink/FileLink.cpp b/launcher/filelink/FileLink.cpp
index 9b5589ab..78486507 100644
--- a/launcher/filelink/FileLink.cpp
+++ b/launcher/filelink/FileLink.cpp
@@ -31,8 +31,6 @@
#include
-
-#include
#include
#include
@@ -48,7 +46,7 @@
-FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv)
+FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv), socket(new QLocalSocket(this))
{
#if defined Q_OS_WIN32
// attach the parent console
@@ -81,18 +79,116 @@ FileLinkApp::FileLinkApp(int &argc, char **argv) : QCoreApplication(argc, argv)
// Commandline parsing
QCommandLineParser parser;
- parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be useed with prismlauncher"));
+ parser.setApplicationDescription(QObject::tr("a batch MKLINK program for windows to be used with prismlauncher"));
parser.addOptions({
-
+ {{"s", "server"}, "Join the specified ser