Merge branch 'PrismLauncher:develop' into version
This commit is contained in:
commit
e8b871ac72
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@ -7,6 +7,10 @@ on:
|
||||
description: Type of build (Debug, Release, RelWithDebInfo, MinSizeRel)
|
||||
type: string
|
||||
default: Debug
|
||||
is_qt_cached:
|
||||
description: Enable Qt caching or not
|
||||
type: string
|
||||
default: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY:
|
||||
description: Private key for signing Sparkle updates
|
||||
@ -102,14 +106,6 @@ jobs:
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
queries: security-and-quality
|
||||
languages: cpp, java
|
||||
|
||||
- name: 'Setup MSYS2'
|
||||
if: runner.os == 'Windows' && matrix.msystem != ''
|
||||
uses: msys2/setup-msys2@v2
|
||||
@ -200,8 +196,7 @@ jobs:
|
||||
arch: ${{ matrix.qt_arch }}
|
||||
modules: ${{ matrix.qt_modules }}
|
||||
tools: ${{ matrix.qt_tools }}
|
||||
cache: true
|
||||
cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache
|
||||
cache: ${{ inputs.is_qt_cached }}
|
||||
|
||||
- name: Prepare AppImage (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
@ -292,14 +287,6 @@ jobs:
|
||||
run: |
|
||||
ctest -E "^example64|example$" --test-dir build --output-on-failure -C ${{ inputs.build_type }}
|
||||
|
||||
##
|
||||
# CODE SCAN
|
||||
##
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
if: runner.os == 'Linux' && matrix.qt_ver == 6
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
##
|
||||
# PACKAGE BUILDS
|
||||
##
|
||||
@ -510,10 +497,10 @@ jobs:
|
||||
echo "VERSION=$ver_short" >> $GITHUB_ENV
|
||||
- name: Package Snap (Linux)
|
||||
id: snapcraft
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
if: runner.os == 'Linux' && inputs.build_type == 'Debug'
|
||||
uses: snapcore/action-build@v1
|
||||
- name: Upload Snap (Linux)
|
||||
if: runner.os == 'Linux' && matrix.qt_ver != 5
|
||||
if: runner.os == 'Linux' && inputs.build_type == 'Debug'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prismlauncher_${{ env.VERSION }}_amd64.snap
|
||||
|
35
.github/workflows/codeql.yml
vendored
Normal file
35
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: "CodeQL Code Scanning"
|
||||
|
||||
on: [ push, pull_request, workflow_dispatch ]
|
||||
|
||||
jobs:
|
||||
CodeQL:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
queries: security-and-quality
|
||||
languages: cpp, java
|
||||
|
||||
- name: Install Dependencies
|
||||
run:
|
||||
sudo apt-get -y update
|
||||
|
||||
sudo apt-get -y install ninja-build extra-cmake-modules scdoc qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5
|
||||
|
||||
- name: Configure and Build
|
||||
run: |
|
||||
cmake -S . -B build -DCMAKE_INSTALL_PREFIX=/usr -DLauncher_QT_VERSION_MAJOR=5 -G Ninja
|
||||
|
||||
cmake --build build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
1
.github/workflows/trigger_builds.yml
vendored
1
.github/workflows/trigger_builds.yml
vendored
@ -30,5 +30,6 @@ jobs:
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Debug
|
||||
is_qt_cached: true
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
|
1
.github/workflows/trigger_release.yml
vendored
1
.github/workflows/trigger_release.yml
vendored
@ -12,6 +12,7 @@ jobs:
|
||||
uses: ./.github/workflows/build.yml
|
||||
with:
|
||||
build_type: Release
|
||||
is_qt_cached: false
|
||||
secrets:
|
||||
SPARKLE_ED25519_KEY: ${{ secrets.SPARKLE_ED25519_KEY }}
|
||||
|
||||
|
@ -151,7 +151,7 @@ public:
|
||||
void copyManagedPack(BaseInstance& other);
|
||||
|
||||
/// guess log level from a line of game log
|
||||
virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level)
|
||||
virtual MessageLevel::Enum guessLevel([[maybe_unused]] const QString &line, MessageLevel::Enum level)
|
||||
{
|
||||
return level;
|
||||
};
|
||||
|
@ -95,12 +95,12 @@ BaseVersionList::RoleList BaseVersionList::providesRoles() const
|
||||
int BaseVersionList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int BaseVersionList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> BaseVersionList::roleNames() const
|
||||
|
@ -149,6 +149,9 @@ 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
|
||||
bool copy::operator()(const QString& offset)
|
||||
{
|
||||
using copy_opts = fs::copy_options;
|
||||
|
@ -75,6 +75,7 @@ bool ensureFilePathExists(QString filenamepath);
|
||||
*/
|
||||
bool ensureFolderPathExists(QString filenamepath);
|
||||
|
||||
/// @brief Copies a directory and it's contents from src to dest
|
||||
class copy {
|
||||
public:
|
||||
copy(const QString& src, const QString& dst)
|
||||
|
@ -311,14 +311,14 @@ QModelIndex VersionProxyModel::index(int row, int column, const QModelIndex &par
|
||||
|
||||
int VersionProxyModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_columns.size();
|
||||
return parent.isValid() ? 0 : m_columns.size();
|
||||
}
|
||||
|
||||
int VersionProxyModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if(sourceModel())
|
||||
{
|
||||
return sourceModel()->rowCount();
|
||||
return sourceModel()->rowCount(parent);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -242,7 +242,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, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
@ -302,7 +302,7 @@ QVariant IconList::data(const QModelIndex &index, int role) const
|
||||
|
||||
int IconList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return icons.size();
|
||||
return parent.isValid() ? 0 : icons.size();
|
||||
}
|
||||
|
||||
void IconList::installIcons(const QStringList &iconFiles)
|
||||
|
@ -58,11 +58,11 @@ QVariant Index::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
int Index::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return m_lists.size();
|
||||
return parent.isValid() ? 0 : m_lists.size();
|
||||
}
|
||||
int Index::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
QVariant Index::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
|
@ -60,11 +60,6 @@ struct Require
|
||||
QString suggests;
|
||||
};
|
||||
|
||||
inline Q_DECL_PURE_FUNCTION uint qHash(const Require &key, uint seed = 0) Q_DECL_NOTHROW
|
||||
{
|
||||
return qHash(key.uid, seed);
|
||||
}
|
||||
|
||||
using RequireSet = std::set<Require>;
|
||||
|
||||
void parseIndex(const QJsonObject &obj, Index *ptr);
|
||||
|
@ -613,7 +613,7 @@ QVariant PackProfile::data(const QModelIndex &index, int role) const
|
||||
|
||||
bool PackProfile::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index))
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= rowCount(index.parent()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -675,12 +675,12 @@ Qt::ItemFlags PackProfile::flags(const QModelIndex &index) const
|
||||
|
||||
int PackProfile::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return d->components.size();
|
||||
return parent.isValid() ? 0 : d->components.size();
|
||||
}
|
||||
|
||||
int PackProfile::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
void PackProfile::move(const int index, const MoveDirection direction)
|
||||
|
@ -104,7 +104,7 @@ public:
|
||||
class ImplicitRule : public Rule
|
||||
{
|
||||
protected:
|
||||
virtual bool applies(const Library *, const RuntimeContext & runtimeContext)
|
||||
virtual bool applies(const Library *, [[maybe_unused]] const RuntimeContext & runtimeContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ bool WorldList::resetIcon(int row)
|
||||
|
||||
int WorldList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 4;
|
||||
return parent.isValid()? 0 : 4;
|
||||
}
|
||||
|
||||
QVariant WorldList::data(const QModelIndex &index, int role) const
|
||||
@ -398,8 +398,8 @@ void WorldList::installWorld(QFileInfo filename)
|
||||
w.install(m_dir.absolutePath());
|
||||
}
|
||||
|
||||
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column,
|
||||
const QModelIndex &parent)
|
||||
bool WorldList::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column,
|
||||
[[maybe_unused]] const QModelIndex &parent)
|
||||
{
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
|
@ -54,7 +54,7 @@ public:
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const
|
||||
{
|
||||
return size();
|
||||
return parent.isValid() ? 0 : static_cast<int>(size());
|
||||
};
|
||||
virtual QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const;
|
||||
|
@ -408,20 +408,20 @@ QVariant AccountList::headerData(int section, Qt::Orientation orientation, int r
|
||||
}
|
||||
}
|
||||
|
||||
int AccountList::rowCount(const QModelIndex &) const
|
||||
int AccountList::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// Return count
|
||||
return count();
|
||||
return parent.isValid() ? 0 : count();
|
||||
}
|
||||
|
||||
int AccountList::columnCount(const QModelIndex &) const
|
||||
int AccountList::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Qt::ItemFlags AccountList::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid())
|
||||
if (index.row() < 0 || index.row() >= rowCount(index.parent()) || !index.isValid())
|
||||
{
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in
|
||||
|
||||
int ModFolderModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ModFolderModel::createUpdateTask()
|
||||
|
@ -426,7 +426,7 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const
|
||||
bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
||||
{
|
||||
int row = index.row();
|
||||
if (row < 0 || row >= rowCount(index) || !index.isValid())
|
||||
if (row < 0 || row >= rowCount(index.parent()) || !index.isValid())
|
||||
return false;
|
||||
|
||||
if (role == Qt::CheckStateRole)
|
||||
|
@ -90,8 +90,8 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
/* Basic columns */
|
||||
enum Columns { ACTIVE_COLUMN = 0, NAME_COLUMN, DATE_COLUMN, NUM_COLUMNS };
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); }
|
||||
[[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; };
|
||||
[[nodiscard]] int rowCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : static_cast<int>(size()); }
|
||||
[[nodiscard]] int columnCount(const QModelIndex& parent = {}) const override { return parent.isValid() ? 0 : NUM_COLUMNS; };
|
||||
|
||||
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
|
||||
|
||||
@ -176,7 +176,7 @@ class ResourceFolderModel : public QAbstractListModel {
|
||||
* if the resource is complex and has more stuff to parse.
|
||||
*/
|
||||
virtual void onParseSucceeded(int ticket, QString resource_id);
|
||||
virtual void onParseFailed(int ticket, QString resource_id) {}
|
||||
virtual void onParseFailed(int ticket, QString resource_id) { Q_UNUSED(ticket); Q_UNUSED(resource_id); }
|
||||
|
||||
protected:
|
||||
// Represents the relationship between a column's index (represented by the list index), and it's sorting key.
|
||||
|
@ -137,7 +137,7 @@ QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orient
|
||||
|
||||
int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return NUM_COLUMNS;
|
||||
return parent.isValid() ? 0 : NUM_COLUMNS;
|
||||
}
|
||||
|
||||
Task* ResourcePackFolderModel::createUpdateTask()
|
||||
|
@ -121,7 +121,7 @@ ModDetails ReadMCModTOML(QByteArray contents)
|
||||
return {};
|
||||
}
|
||||
auto modsTable = tomlModsTable0->as_table();
|
||||
if (!tomlModsTable0) {
|
||||
if (!modsTable) {
|
||||
qWarning() << "Corrupted mods.toml? [[mods]] was not a table!";
|
||||
return {};
|
||||
}
|
||||
|
@ -372,13 +372,20 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
auto results = m_mod_id_resolver->getResults();
|
||||
|
||||
// first check for blocked mods
|
||||
QString text;
|
||||
QList<QUrl> urls;
|
||||
QList<BlockedMod> blocked_mods;
|
||||
auto anyBlocked = false;
|
||||
for (const auto& result : results.files.values()) {
|
||||
if (!result.resolved || result.url.isEmpty()) {
|
||||
text += QString("%1: <a href='%2'>%2</a><br/>").arg(result.fileName, result.websiteUrl);
|
||||
urls.append(QUrl(result.websiteUrl));
|
||||
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = result.fileName;
|
||||
blocked_mod.websiteUrl = result.websiteUrl;
|
||||
blocked_mod.hash = result.hash;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
|
||||
blocked_mods.append(blocked_mod);
|
||||
|
||||
anyBlocked = true;
|
||||
}
|
||||
}
|
||||
@ -388,11 +395,12 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"),
|
||||
tr("The following mods were blocked on third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the modpack"),
|
||||
text,
|
||||
urls);
|
||||
blocked_mods);
|
||||
message_dialog->setModal(true);
|
||||
|
||||
if (message_dialog->exec()) {
|
||||
qDebug() << "Post dialog blocked mods list: " << blocked_mods;
|
||||
copyBlockedMods(blocked_mods);
|
||||
setupDownloadJob(loop);
|
||||
} else {
|
||||
m_mod_id_resolver.reset();
|
||||
@ -404,6 +412,38 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop)
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief copy the matched blocked mods to the instance staging area
|
||||
/// @param blocked_mods list of the blocked mods and their matched paths
|
||||
void FlameCreationTask::copyBlockedMods(QList<BlockedMod> const& blocked_mods)
|
||||
{
|
||||
setStatus(tr("Copying Blocked Mods..."));
|
||||
setAbortable(false);
|
||||
int i = 0;
|
||||
int total = blocked_mods.length();
|
||||
setProgress(i, total);
|
||||
for (auto const& mod : blocked_mods) {
|
||||
if (!mod.matched) {
|
||||
qDebug() << mod.name << "was not matched to a local file, skipping copy";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dest_path = FS::PathCombine(m_stagingPath, "minecraft", "mods", mod.name);
|
||||
|
||||
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
||||
|
||||
qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
|
||||
|
||||
if (!FS::copy(mod.localPath, dest_path)()) {
|
||||
qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
|
||||
}
|
||||
|
||||
i++;
|
||||
setProgress(i, total);
|
||||
}
|
||||
|
||||
setAbortable(true);
|
||||
}
|
||||
|
||||
void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
{
|
||||
m_files_job = new NetJob(tr("Mod download"), APPLICATION->network());
|
||||
@ -449,7 +489,7 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop)
|
||||
m_files_job.reset();
|
||||
setError(reason);
|
||||
});
|
||||
connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); });
|
||||
connect(m_files_job.get(), &NetJob::progress, this, &FlameCreationTask::setProgress);
|
||||
connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit);
|
||||
|
||||
setStatus(tr("Downloading mods..."));
|
||||
|
@ -10,6 +10,8 @@
|
||||
|
||||
#include "net/NetJob.h"
|
||||
|
||||
#include "ui/dialogs/BlockedModsDialog.h"
|
||||
|
||||
class FlameCreationTask final : public InstanceCreationTask {
|
||||
Q_OBJECT
|
||||
|
||||
@ -29,6 +31,7 @@ class FlameCreationTask final : public InstanceCreationTask {
|
||||
private slots:
|
||||
void idResolverSucceeded(QEventLoop&);
|
||||
void setupDownloadJob(QEventLoop&);
|
||||
void copyBlockedMods(QList<BlockedMod> const& blocked_mods);
|
||||
|
||||
private:
|
||||
QWidget* m_parent = nullptr;
|
||||
|
@ -36,6 +36,18 @@ Hasher::Ptr createFlameHasher(QString file_path)
|
||||
return new FlameHasher(file_path);
|
||||
}
|
||||
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider)
|
||||
{
|
||||
return new BlockedModHasher(file_path, provider);
|
||||
}
|
||||
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type)
|
||||
{
|
||||
auto hasher = new BlockedModHasher(file_path, provider);
|
||||
hasher->useHashType(type);
|
||||
return hasher;
|
||||
}
|
||||
|
||||
void ModrinthHasher::executeTask()
|
||||
{
|
||||
QFile file(m_path);
|
||||
@ -79,4 +91,50 @@ void FlameHasher::executeTask()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BlockedModHasher::BlockedModHasher(QString file_path, ModPlatform::Provider provider)
|
||||
: Hasher(file_path), provider(provider) {
|
||||
setObjectName(QString("BlockedModHasher: %1").arg(file_path));
|
||||
hash_type = ProviderCaps.hashType(provider).first();
|
||||
}
|
||||
|
||||
void BlockedModHasher::executeTask()
|
||||
{
|
||||
QFile file(m_path);
|
||||
|
||||
try {
|
||||
file.open(QFile::ReadOnly);
|
||||
} catch (FS::FileSystemException& e) {
|
||||
qCritical() << QString("Failed to open JAR file in %1").arg(m_path);
|
||||
qCritical() << QString("Reason: ") << e.cause();
|
||||
|
||||
emitFailed("Failed to open file for hashing.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_hash = ProviderCaps.hash(provider, &file, hash_type);
|
||||
|
||||
file.close();
|
||||
|
||||
if (m_hash.isEmpty()) {
|
||||
emitFailed("Empty hash!");
|
||||
} else {
|
||||
emitSucceeded();
|
||||
}
|
||||
}
|
||||
|
||||
QStringList BlockedModHasher::getHashTypes() {
|
||||
return ProviderCaps.hashType(provider);
|
||||
}
|
||||
|
||||
bool BlockedModHasher::useHashType(QString type) {
|
||||
auto types = ProviderCaps.hashType(provider);
|
||||
if (types.contains(type)) {
|
||||
hash_type = type;
|
||||
return true;
|
||||
}
|
||||
qDebug() << "Bad hash type " << type << " for provider";
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Hashing
|
||||
|
@ -40,8 +40,23 @@ class ModrinthHasher : public Hasher {
|
||||
void executeTask() override;
|
||||
};
|
||||
|
||||
class BlockedModHasher : public Hasher {
|
||||
public:
|
||||
BlockedModHasher(QString file_path, ModPlatform::Provider provider);
|
||||
|
||||
void executeTask() override;
|
||||
|
||||
QStringList getHashTypes();
|
||||
bool useHashType(QString type);
|
||||
private:
|
||||
ModPlatform::Provider provider;
|
||||
QString hash_type;
|
||||
};
|
||||
|
||||
Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider);
|
||||
Hasher::Ptr createFlameHasher(QString file_path);
|
||||
Hasher::Ptr createModrinthHasher(QString file_path);
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider);
|
||||
Hasher::Ptr createBlockedModHasher(QString file_path, ModPlatform::Provider provider, QString type);
|
||||
|
||||
} // namespace Hashing
|
||||
|
@ -176,8 +176,6 @@ void PackInstallTask::resolveMods()
|
||||
|
||||
void PackInstallTask::onResolveModsSucceeded()
|
||||
{
|
||||
QString text;
|
||||
QList<QUrl> urls;
|
||||
auto anyBlocked = false;
|
||||
|
||||
Flame::Manifest results = m_mod_id_resolver_task->getResults();
|
||||
@ -191,11 +189,16 @@ void PackInstallTask::onResolveModsSucceeded()
|
||||
|
||||
// First check for blocked mods
|
||||
if (!results_file.resolved || results_file.url.isEmpty()) {
|
||||
QString type(local_file.type);
|
||||
|
||||
type[0] = type[0].toUpper();
|
||||
text += QString("%1: %2 - <a href='%3'>%3</a><br/>").arg(type, local_file.name, results_file.websiteUrl);
|
||||
urls.append(QUrl(results_file.websiteUrl));
|
||||
BlockedMod blocked_mod;
|
||||
blocked_mod.name = local_file.name;
|
||||
blocked_mod.websiteUrl = results_file.websiteUrl;
|
||||
blocked_mod.hash = results_file.hash;
|
||||
blocked_mod.matched = false;
|
||||
blocked_mod.localPath = "";
|
||||
|
||||
m_blocked_mods.append(blocked_mod);
|
||||
|
||||
anyBlocked = true;
|
||||
} else {
|
||||
local_file.url = results_file.url.toString();
|
||||
@ -210,13 +213,16 @@ void PackInstallTask::onResolveModsSucceeded()
|
||||
auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"),
|
||||
tr("The following files are not available for download in third party launchers.<br/>"
|
||||
"You will need to manually download them and add them to the instance."),
|
||||
text,
|
||||
urls);
|
||||
m_blocked_mods);
|
||||
|
||||
if (message_dialog->exec() == QDialog::Accepted)
|
||||
if (message_dialog->exec() == QDialog::Accepted) {
|
||||
qDebug() << "Post dialog blocked mods list: " << m_blocked_mods;
|
||||
createInstance();
|
||||
else
|
||||
}
|
||||
else {
|
||||
abort();
|
||||
}
|
||||
|
||||
} else {
|
||||
createInstance();
|
||||
}
|
||||
@ -320,6 +326,9 @@ void PackInstallTask::downloadPack()
|
||||
void PackInstallTask::onModDownloadSucceeded()
|
||||
{
|
||||
m_net_job.reset();
|
||||
if (!m_blocked_mods.isEmpty()) {
|
||||
copyBlockedMods();
|
||||
}
|
||||
emitSucceeded();
|
||||
}
|
||||
|
||||
@ -343,4 +352,35 @@ void PackInstallTask::onModDownloadFailed(QString reason)
|
||||
emitFailed(reason);
|
||||
}
|
||||
|
||||
/// @brief copy the matched blocked mods to the instance staging area
|
||||
void PackInstallTask::copyBlockedMods()
|
||||
{
|
||||
setStatus(tr("Copying Blocked Mods..."));
|
||||
setAbortable(false);
|
||||
int i = 0;
|
||||
int total = m_blocked_mods.length();
|
||||
setProgress(i, total);
|
||||
for (auto const& mod : m_blocked_mods) {
|
||||
if (!mod.matched) {
|
||||
qDebug() << mod.name << "was not matched to a local file, skipping copy";
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dest_path = FS::PathCombine(m_stagingPath, ".minecraft", "mods", mod.name);
|
||||
|
||||
setStatus(tr("Copying Blocked Mods (%1 out of %2 are done)").arg(QString::number(i), QString::number(total)));
|
||||
|
||||
qDebug() << "Will try to copy" << mod.localPath << "to" << dest_path;
|
||||
|
||||
if (!FS::copy(mod.localPath, dest_path)()) {
|
||||
qDebug() << "Copy of" << mod.localPath << "to" << dest_path << "Failed";
|
||||
}
|
||||
|
||||
i++;
|
||||
setProgress(i, total);
|
||||
}
|
||||
|
||||
setAbortable(true);
|
||||
}
|
||||
|
||||
} // namespace ModpacksCH
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "QObjectPtr.h"
|
||||
#include "modplatform/flame/FileResolvingTask.h"
|
||||
#include "net/NetJob.h"
|
||||
#include "ui/dialogs/BlockedModsDialog.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
@ -76,6 +77,7 @@ private:
|
||||
void resolveMods();
|
||||
void createInstance();
|
||||
void downloadPack();
|
||||
void copyBlockedMods();
|
||||
|
||||
private:
|
||||
NetJob::Ptr m_net_job = nullptr;
|
||||
@ -90,6 +92,7 @@ private:
|
||||
Version m_version;
|
||||
|
||||
QMap<QString, QString> m_files_to_copy;
|
||||
QList<BlockedMod> m_blocked_mods;
|
||||
|
||||
//FIXME: nuke
|
||||
QWidget* m_parent;
|
||||
|
@ -47,7 +47,7 @@
|
||||
auto MetaEntry::getFullPath() -> QString
|
||||
{
|
||||
// FIXME: make local?
|
||||
return FS::PathCombine(basePath, relativePath);
|
||||
return FS::PathCombine(m_basePath, m_relativePath);
|
||||
}
|
||||
|
||||
HttpMetaCache::HttpMetaCache(QString path) : QObject(), m_index_file(path)
|
||||
@ -99,7 +99,7 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
||||
return staleEntry(base, resource_path);
|
||||
}
|
||||
|
||||
if (!expected_etag.isEmpty() && expected_etag != entry->etag) {
|
||||
if (!expected_etag.isEmpty() && expected_etag != entry->m_etag) {
|
||||
// if the etag doesn't match expected, we disown the entry
|
||||
selected_base.entry_list.remove(resource_path);
|
||||
return staleEntry(base, resource_path);
|
||||
@ -107,17 +107,17 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
||||
|
||||
// if the file changed, check md5sum
|
||||
qint64 file_last_changed = finfo.lastModified().toUTC().toMSecsSinceEpoch();
|
||||
if (file_last_changed != entry->local_changed_timestamp) {
|
||||
if (file_last_changed != entry->m_local_changed_timestamp) {
|
||||
QFile input(real_path);
|
||||
input.open(QIODevice::ReadOnly);
|
||||
QString md5sum = QCryptographicHash::hash(input.readAll(), QCryptographicHash::Md5).toHex().constData();
|
||||
if (entry->md5sum != md5sum) {
|
||||
if (entry->m_md5sum != md5sum) {
|
||||
selected_base.entry_list.remove(resource_path);
|
||||
return staleEntry(base, resource_path);
|
||||
}
|
||||
|
||||
// md5sums matched... keep entry and save the new state to file
|
||||
entry->local_changed_timestamp = file_last_changed;
|
||||
entry->m_local_changed_timestamp = file_last_changed;
|
||||
SaveEventually();
|
||||
}
|
||||
|
||||
@ -130,23 +130,23 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex
|
||||
}
|
||||
|
||||
// entry passed all the checks we cared about.
|
||||
entry->basePath = getBasePath(base);
|
||||
entry->m_basePath = getBasePath(base);
|
||||
return entry;
|
||||
}
|
||||
|
||||
auto HttpMetaCache::updateEntry(MetaEntryPtr stale_entry) -> bool
|
||||
{
|
||||
if (!m_entries.contains(stale_entry->baseId)) {
|
||||
qCritical() << "Cannot add entry with unknown base: " << stale_entry->baseId.toLocal8Bit();
|
||||
if (!m_entries.contains(stale_entry->m_baseId)) {
|
||||
qCritical() << "Cannot add entry with unknown base: " << stale_entry->m_baseId.toLocal8Bit();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stale_entry->stale) {
|
||||
if (stale_entry->m_stale) {
|
||||
qCritical() << "Cannot add stale entry: " << stale_entry->getFullPath().toLocal8Bit();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_entries[stale_entry->baseId].entry_list[stale_entry->relativePath] = stale_entry;
|
||||
m_entries[stale_entry->m_baseId].entry_list[stale_entry->m_relativePath] = stale_entry;
|
||||
SaveEventually();
|
||||
|
||||
return true;
|
||||
@ -157,7 +157,7 @@ auto HttpMetaCache::evictEntry(MetaEntryPtr entry) -> bool
|
||||
if (!entry)
|
||||
return false;
|
||||
|
||||
entry->stale = true;
|
||||
entry->m_stale = true;
|
||||
SaveEventually();
|
||||
return true;
|
||||
}
|
||||
@ -169,7 +169,7 @@ void HttpMetaCache::evictAll()
|
||||
qDebug() << "Evicting base" << base;
|
||||
for (MetaEntryPtr entry : map.entry_list) {
|
||||
if (!evictEntry(entry))
|
||||
qWarning() << "Unexpected missing cache entry" << entry->basePath;
|
||||
qWarning() << "Unexpected missing cache entry" << entry->m_basePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,10 +177,10 @@ void HttpMetaCache::evictAll()
|
||||
auto HttpMetaCache::staleEntry(QString base, QString resource_path) -> MetaEntryPtr
|
||||
{
|
||||
auto foo = new MetaEntry();
|
||||
foo->baseId = base;
|
||||
foo->basePath = getBasePath(base);
|
||||
foo->relativePath = resource_path;
|
||||
foo->stale = true;
|
||||
foo->m_baseId = base;
|
||||
foo->m_basePath = getBasePath(base);
|
||||
foo->m_relativePath = resource_path;
|
||||
foo->m_stale = true;
|
||||
|
||||
return MetaEntryPtr(foo);
|
||||
}
|
||||
@ -235,23 +235,23 @@ void HttpMetaCache::Load()
|
||||
auto& entrymap = m_entries[base];
|
||||
|
||||
auto foo = new MetaEntry();
|
||||
foo->baseId = base;
|
||||
foo->relativePath = Json::ensureString(element_obj, "path");
|
||||
foo->md5sum = Json::ensureString(element_obj, "md5sum");
|
||||
foo->etag = Json::ensureString(element_obj, "etag");
|
||||
foo->local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
|
||||
foo->remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
|
||||
foo->m_baseId = base;
|
||||
foo->m_relativePath = Json::ensureString(element_obj, "path");
|
||||
foo->m_md5sum = Json::ensureString(element_obj, "md5sum");
|
||||
foo->m_etag = Json::ensureString(element_obj, "etag");
|
||||
foo->m_local_changed_timestamp = Json::ensureDouble(element_obj, "last_changed_timestamp");
|
||||
foo->m_remote_changed_timestamp = Json::ensureString(element_obj, "remote_changed_timestamp");
|
||||
|
||||
foo->makeEternal(Json::ensureBoolean(element_obj, (const QString)QStringLiteral("eternal"), false));
|
||||
if (!foo->isEternal()) {
|
||||
foo->current_age = Json::ensureDouble(element_obj, "current_age");
|
||||
foo->max_age = Json::ensureDouble(element_obj, "max_age");
|
||||
foo->m_current_age = Json::ensureDouble(element_obj, "current_age");
|
||||
foo->m_max_age = Json::ensureDouble(element_obj, "max_age");
|
||||
}
|
||||
|
||||
// presumed innocent until closer examination
|
||||
foo->stale = false;
|
||||
foo->m_stale = false;
|
||||
|
||||
entrymap.entry_list[foo->relativePath] = MetaEntryPtr(foo);
|
||||
entrymap.entry_list[foo->m_relativePath] = MetaEntryPtr(foo);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,23 +276,23 @@ void HttpMetaCache::SaveNow()
|
||||
for (auto group : m_entries) {
|
||||
for (auto entry : group.entry_list) {
|
||||
// do not save stale entries. they are dead.
|
||||
if (entry->stale) {
|
||||
if (entry->m_stale) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject entryObj;
|
||||
Json::writeString(entryObj, "base", entry->baseId);
|
||||
Json::writeString(entryObj, "path", entry->relativePath);
|
||||
Json::writeString(entryObj, "md5sum", entry->md5sum);
|
||||
Json::writeString(entryObj, "etag", entry->etag);
|
||||
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->local_changed_timestamp)));
|
||||
if (!entry->remote_changed_timestamp.isEmpty())
|
||||
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->remote_changed_timestamp));
|
||||
Json::writeString(entryObj, "base", entry->m_baseId);
|
||||
Json::writeString(entryObj, "path", entry->m_relativePath);
|
||||
Json::writeString(entryObj, "md5sum", entry->m_md5sum);
|
||||
Json::writeString(entryObj, "etag", entry->m_etag);
|
||||
entryObj.insert("last_changed_timestamp", QJsonValue(double(entry->m_local_changed_timestamp)));
|
||||
if (!entry->m_remote_changed_timestamp.isEmpty())
|
||||
entryObj.insert("remote_changed_timestamp", QJsonValue(entry->m_remote_changed_timestamp));
|
||||
if (entry->isEternal()) {
|
||||
entryObj.insert("eternal", true);
|
||||
} else {
|
||||
entryObj.insert("current_age", QJsonValue(double(entry->current_age)));
|
||||
entryObj.insert("max_age", QJsonValue(double(entry->max_age)));
|
||||
entryObj.insert("current_age", QJsonValue(double(entry->m_current_age)));
|
||||
entryObj.insert("max_age", QJsonValue(double(entry->m_max_age)));
|
||||
}
|
||||
entriesArr.append(entryObj);
|
||||
}
|
||||
|
@ -49,47 +49,47 @@ class MetaEntry {
|
||||
MetaEntry() = default;
|
||||
|
||||
public:
|
||||
auto isStale() -> bool { return stale; }
|
||||
void setStale(bool stale) { this->stale = stale; }
|
||||
auto isStale() -> bool { return m_stale; }
|
||||
void setStale(bool stale) { m_stale = stale; }
|
||||
|
||||
auto getFullPath() -> QString;
|
||||
|
||||
auto getRemoteChangedTimestamp() -> QString { return remote_changed_timestamp; }
|
||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { this->remote_changed_timestamp = remote_changed_timestamp; }
|
||||
void setLocalChangedTimestamp(qint64 timestamp) { local_changed_timestamp = timestamp; }
|
||||
auto getRemoteChangedTimestamp() -> QString { return m_remote_changed_timestamp; }
|
||||
void setRemoteChangedTimestamp(QString remote_changed_timestamp) { m_remote_changed_timestamp = remote_changed_timestamp; }
|
||||
void setLocalChangedTimestamp(qint64 timestamp) { m_local_changed_timestamp = timestamp; }
|
||||
|
||||
auto getETag() -> QString { return etag; }
|
||||
void setETag(QString etag) { this->etag = etag; }
|
||||
auto getETag() -> QString { return m_etag; }
|
||||
void setETag(QString etag) { m_etag = etag; }
|
||||
|
||||
auto getMD5Sum() -> QString { return md5sum; }
|
||||
void setMD5Sum(QString md5sum) { this->md5sum = md5sum; }
|
||||
auto getMD5Sum() -> QString { return m_md5sum; }
|
||||
void setMD5Sum(QString md5sum) { m_md5sum = md5sum; }
|
||||
|
||||
/* Whether the entry expires after some time (false) or not (true). */
|
||||
void makeEternal(bool eternal) { is_eternal = eternal; }
|
||||
[[nodiscard]] bool isEternal() const { return is_eternal; }
|
||||
void makeEternal(bool eternal) { m_is_eternal = eternal; }
|
||||
[[nodiscard]] bool isEternal() const { return m_is_eternal; }
|
||||
|
||||
auto getCurrentAge() -> qint64 { return current_age; }
|
||||
void setCurrentAge(qint64 age) { current_age = age; }
|
||||
auto getCurrentAge() -> qint64 { return m_current_age; }
|
||||
void setCurrentAge(qint64 age) { m_current_age = age; }
|
||||
|
||||
auto getMaximumAge() -> qint64 { return max_age; }
|
||||
void setMaximumAge(qint64 age) { max_age = age; }
|
||||
auto getMaximumAge() -> qint64 { return m_max_age; }
|
||||
void setMaximumAge(qint64 age) { m_max_age = age; }
|
||||
|
||||
bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); };
|
||||
bool isExpired(qint64 offset) { return !m_is_eternal && (m_current_age >= m_max_age - offset); };
|
||||
|
||||
protected:
|
||||
QString baseId;
|
||||
QString basePath;
|
||||
QString relativePath;
|
||||
QString md5sum;
|
||||
QString etag;
|
||||
QString m_baseId;
|
||||
QString m_basePath;
|
||||
QString m_relativePath;
|
||||
QString m_md5sum;
|
||||
QString m_etag;
|
||||
|
||||
qint64 local_changed_timestamp = 0;
|
||||
QString remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
||||
qint64 current_age = 0;
|
||||
qint64 max_age = 0;
|
||||
bool is_eternal = false;
|
||||
qint64 m_local_changed_timestamp = 0;
|
||||
QString m_remote_changed_timestamp; // QString for now, RFC 2822 encoded time
|
||||
qint64 m_current_age = 0;
|
||||
qint64 m_max_age = 0;
|
||||
bool m_is_eternal = false;
|
||||
|
||||
bool stale = true;
|
||||
bool m_stale = true;
|
||||
};
|
||||
|
||||
using MetaEntryPtr = std::shared_ptr<MetaEntry>;
|
||||
|
@ -61,6 +61,7 @@
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QToolButton>
|
||||
@ -253,6 +254,9 @@ public:
|
||||
QMenu * helpMenu = nullptr;
|
||||
TranslatedToolButton helpMenuButton;
|
||||
TranslatedAction actionClearMetadata;
|
||||
#ifdef Q_OS_MAC
|
||||
TranslatedAction actionAddToPATH;
|
||||
#endif
|
||||
TranslatedAction actionReportBug;
|
||||
TranslatedAction actionDISCORD;
|
||||
TranslatedAction actionMATRIX;
|
||||
@ -350,6 +354,14 @@ public:
|
||||
actionClearMetadata.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Clear cached metadata"));
|
||||
all_actions.append(&actionClearMetadata);
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
actionAddToPATH = TranslatedAction(MainWindow);
|
||||
actionAddToPATH->setObjectName(QStringLiteral("actionAddToPATH"));
|
||||
actionAddToPATH.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Install to &PATH"));
|
||||
actionAddToPATH.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Install a prismlauncher symlink to /usr/local/bin"));
|
||||
all_actions.append(&actionAddToPATH);
|
||||
#endif
|
||||
|
||||
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
|
||||
actionReportBug = TranslatedAction(MainWindow);
|
||||
actionReportBug->setObjectName(QStringLiteral("actionReportBug"));
|
||||
@ -455,6 +467,10 @@ public:
|
||||
|
||||
helpMenu->addAction(actionClearMetadata);
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
helpMenu->addAction(actionAddToPATH);
|
||||
#endif
|
||||
|
||||
if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) {
|
||||
helpMenu->addAction(actionReportBug);
|
||||
}
|
||||
@ -542,6 +558,9 @@ public:
|
||||
helpMenu = menuBar->addMenu(tr("&Help"));
|
||||
helpMenu->setSeparatorsCollapsible(false);
|
||||
helpMenu->addAction(actionClearMetadata);
|
||||
#ifdef Q_OS_MAC
|
||||
helpMenu->addAction(actionAddToPATH);
|
||||
#endif
|
||||
helpMenu->addSeparator();
|
||||
helpMenu->addAction(actionAbout);
|
||||
helpMenu->addAction(actionOpenWiki);
|
||||
@ -1929,6 +1948,29 @@ void MainWindow::on_actionClearMetadata_triggered()
|
||||
APPLICATION->metacache()->SaveNow();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
void MainWindow::on_actionAddToPATH_triggered()
|
||||
{
|
||||
auto binaryPath = APPLICATION->applicationFilePath();
|
||||
auto targetPath = QString("/usr/local/bin/%1").arg(BuildConfig.LAUNCHER_APP_BINARY_NAME);
|
||||
qDebug() << "Symlinking" << binaryPath << "to" << targetPath;
|
||||
|
||||
QStringList args;
|
||||
args << "-e";
|
||||
args << QString("do shell script \"mkdir -p /usr/local/bin && ln -sf '%1' '%2'\" with administrator privileges")
|
||||
.arg(binaryPath, targetPath);
|
||||
auto outcome = QProcess::execute("/usr/bin/osascript", args);
|
||||
if (!outcome) {
|
||||
QMessageBox::information(this, tr("Successfully added %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME),
|
||||
tr("%1 was successfully added to your PATH. You can now start it by running `%2`.")
|
||||
.arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.LAUNCHER_APP_BINARY_NAME));
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Failed to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME),
|
||||
tr("An error occurred while trying to add %1 to PATH").arg(BuildConfig.LAUNCHER_DISPLAYNAME));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MainWindow::on_actionOpenWiki_triggered()
|
||||
{
|
||||
DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg("")));
|
||||
|
@ -128,6 +128,10 @@ private slots:
|
||||
|
||||
void on_actionClearMetadata_triggered();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
void on_actionAddToPATH_triggered();
|
||||
#endif
|
||||
|
||||
void on_actionOpenWiki_triggered();
|
||||
|
||||
void on_actionMoreNews_triggered();
|
||||
|
@ -1,28 +1,186 @@
|
||||
#include "BlockedModsDialog.h"
|
||||
#include "ui_BlockedModsDialog.h"
|
||||
#include <QPushButton>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDesktopServices>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include "Application.h"
|
||||
#include "ui_BlockedModsDialog.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QStandardPaths>
|
||||
|
||||
BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls) :
|
||||
QDialog(parent), ui(new Ui::BlockedModsDialog), urls(urls) {
|
||||
BlockedModsDialog::BlockedModsDialog(QWidget* parent, const QString& title, const QString& text, QList<BlockedMod>& mods)
|
||||
: QDialog(parent), ui(new Ui::BlockedModsDialog), mods(mods)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
auto openAllButton = ui->buttonBox->addButton(tr("Open All"), QDialogButtonBox::ActionRole);
|
||||
connect(openAllButton, &QPushButton::clicked, this, &BlockedModsDialog::openAll);
|
||||
|
||||
connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &BlockedModsDialog::directoryChanged);
|
||||
|
||||
hashing_task = shared_qobject_ptr<ConcurrentTask>(new ConcurrentTask(this, "MakeHashesTask", 10));
|
||||
|
||||
qDebug() << "Mods List: " << mods;
|
||||
|
||||
setupWatch();
|
||||
scanPaths();
|
||||
|
||||
this->setWindowTitle(title);
|
||||
ui->label->setText(text);
|
||||
ui->textBrowser->setText(body);
|
||||
ui->labelModsFound->setText(tr("Please download the missing mods."));
|
||||
update();
|
||||
}
|
||||
|
||||
BlockedModsDialog::~BlockedModsDialog() {
|
||||
BlockedModsDialog::~BlockedModsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void BlockedModsDialog::openAll() {
|
||||
for(auto &url : urls) {
|
||||
QDesktopServices::openUrl(url);
|
||||
void BlockedModsDialog::openAll()
|
||||
{
|
||||
for (auto& mod : mods) {
|
||||
QDesktopServices::openUrl(mod.websiteUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief update UI with current status of the blocked mod detection
|
||||
void BlockedModsDialog::update()
|
||||
{
|
||||
QString text;
|
||||
QString span;
|
||||
|
||||
for (auto& mod : mods) {
|
||||
if (mod.matched) {
|
||||
// ✔ -> html for HEAVY CHECK MARK : ✔
|
||||
span = QString(tr("<span style=\"color:green\"> ✔ Found at %1 </span>")).arg(mod.localPath);
|
||||
} else {
|
||||
// ✘ -> html for HEAVY BALLOT X : ✘
|
||||
span = QString(tr("<span style=\"color:red\"> ✘ Not Found </span>"));
|
||||
}
|
||||
text += QString(tr("%1: <a href='%2'>%2</a> <p>Hash: %3 %4</p> <br/>")).arg(mod.name, mod.websiteUrl, mod.hash, span);
|
||||
}
|
||||
|
||||
ui->textBrowser->setText(text);
|
||||
|
||||
if (allModsMatched()) {
|
||||
ui->labelModsFound->setText(tr("All mods found ✔"));
|
||||
} else {
|
||||
ui->labelModsFound->setText(tr("Please download the missing mods."));
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Signal fired when a watched direcotry has changed
|
||||
/// @param path the path to the changed directory
|
||||
void BlockedModsDialog::directoryChanged(QString path)
|
||||
{
|
||||
qDebug() << "Directory changed: " << path;
|
||||
scanPath(path);
|
||||
}
|
||||
|
||||
/// @brief add the user downloads folder and the global mods folder to the filesystem watcher
|
||||
void BlockedModsDialog::setupWatch()
|
||||
{
|
||||
const QString downloadsFolder = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
|
||||
const QString modsFolder = APPLICATION->settings()->get("CentralModsDir").toString();
|
||||
watcher.addPath(downloadsFolder);
|
||||
watcher.addPath(modsFolder);
|
||||
}
|
||||
|
||||
/// @brief scan all watched folder
|
||||
void BlockedModsDialog::scanPaths()
|
||||
{
|
||||
for (auto& dir : watcher.directories()) {
|
||||
scanPath(dir);
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Scan the directory at path, skip paths that do not contain a file name
|
||||
/// of a blocked mod we are looking for
|
||||
/// @param path the directory to scan
|
||||
void BlockedModsDialog::scanPath(QString path)
|
||||
{
|
||||
QDir scan_dir(path);
|
||||
QDirIterator scan_it(path, QDir::Filter::Files | QDir::Filter::Hidden, QDirIterator::NoIteratorFlags);
|
||||
while (scan_it.hasNext()) {
|
||||
QString file = scan_it.next();
|
||||
|
||||
if (!checkValidPath(file)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto hash_task = Hashing::createBlockedModHasher(file, ModPlatform::Provider::FLAME, "sha1");
|
||||
|
||||
qDebug() << "Creating Hash task for path: " << file;
|
||||
|
||||
connect(hash_task.get(), &Task::succeeded, [this, hash_task, file] { checkMatchHash(hash_task->getResult(), file); });
|
||||
connect(hash_task.get(), &Task::failed, [file] { qDebug() << "Failed to hash path: " << file; });
|
||||
|
||||
hashing_task->addTask(hash_task);
|
||||
}
|
||||
|
||||
hashing_task->start();
|
||||
}
|
||||
|
||||
/// @brief check if the computed hash for the provided path matches a blocked
|
||||
/// mod we are looking for
|
||||
/// @param hash the computed hash for the provided path
|
||||
/// @param path the path to the local file being compared
|
||||
void BlockedModsDialog::checkMatchHash(QString hash, QString path)
|
||||
{
|
||||
bool match = false;
|
||||
|
||||
qDebug() << "Checking for match on hash: " << hash << "| From path:" << path;
|
||||
|
||||
for (auto& mod : mods) {
|
||||
if (mod.matched) {
|
||||
continue;
|
||||
}
|
||||
if (mod.hash.compare(hash, Qt::CaseInsensitive) == 0) {
|
||||
mod.matched = true;
|
||||
mod.localPath = path;
|
||||
match = true;
|
||||
|
||||
qDebug() << "Hash match found:" << mod.name << hash << "| From path:" << path;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Check if the name of the file at path matches the name of a blocked mod we are searching for
|
||||
/// @param path the path to check
|
||||
/// @return boolean: did the path match the name of a blocked mod?
|
||||
bool BlockedModsDialog::checkValidPath(QString path)
|
||||
{
|
||||
QFileInfo file = QFileInfo(path);
|
||||
QString filename = file.fileName();
|
||||
|
||||
for (auto& mod : mods) {
|
||||
if (mod.name.compare(filename, Qt::CaseInsensitive) == 0) {
|
||||
qDebug() << "Name match found:" << mod.name << "| From path:" << path;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BlockedModsDialog::allModsMatched()
|
||||
{
|
||||
return std::all_of(mods.begin(), mods.end(), [](auto const& mod) { return mod.matched; });
|
||||
}
|
||||
|
||||
/// qDebug print support for the BlockedMod struct
|
||||
QDebug operator<<(QDebug debug, const BlockedMod& m)
|
||||
{
|
||||
QDebugStateSaver saver(debug);
|
||||
|
||||
debug.nospace() << "{ name: " << m.name << ", websiteUrl: " << m.websiteUrl << ", hash: " << m.hash << ", matched: " << m.matched
|
||||
<< ", localPath: " << m.localPath << "}";
|
||||
|
||||
return debug;
|
||||
}
|
||||
|
@ -1,7 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
#include <QFileSystemWatcher>
|
||||
|
||||
#include "modplatform/helpers/HashUtils.h"
|
||||
|
||||
#include "tasks/ConcurrentTask.h"
|
||||
|
||||
struct BlockedMod {
|
||||
QString name;
|
||||
QString websiteUrl;
|
||||
QString hash;
|
||||
bool matched;
|
||||
QString localPath;
|
||||
|
||||
};
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui { class BlockedModsDialog; }
|
||||
@ -11,12 +27,27 @@ class BlockedModsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList<QUrl> &urls);
|
||||
BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, QList<BlockedMod> &mods);
|
||||
|
||||
~BlockedModsDialog() override;
|
||||
|
||||
|
||||
private:
|
||||
Ui::BlockedModsDialog *ui;
|
||||
const QList<QUrl> &urls;
|
||||
QList<BlockedMod> &mods;
|
||||
QFileSystemWatcher watcher;
|
||||
shared_qobject_ptr<ConcurrentTask> hashing_task;
|
||||
|
||||
void openAll();
|
||||
void update();
|
||||
void directoryChanged(QString path);
|
||||
void setupWatch();
|
||||
void scanPaths();
|
||||
void scanPath(QString path);
|
||||
void checkMatchHash(QString hash, QString path);
|
||||
|
||||
bool checkValidPath(QString path);
|
||||
bool allModsMatched();
|
||||
};
|
||||
|
||||
QDebug operator<<(QDebug debug, const BlockedMod &m);
|
||||
|
@ -13,8 +13,8 @@
|
||||
<property name="windowTitle">
|
||||
<string notr="true">BlockedModsDialog</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string notr="true"/>
|
||||
@ -24,17 +24,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="textBrowser">
|
||||
<property name="acceptRichText">
|
||||
<bool>true</bool>
|
||||
@ -44,6 +34,27 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="bottomBoxH">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelModsFound">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
|
@ -27,11 +27,7 @@
|
||||
<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>
|
||||
<widget class="QLineEdit" name="filterEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="filterLabel">
|
||||
|
@ -400,11 +400,11 @@ public:
|
||||
|
||||
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
return m_servers.size();
|
||||
return parent.isValid() ? 0 : m_servers.size();
|
||||
}
|
||||
int columnCount(const QModelIndex & parent) const override
|
||||
{
|
||||
return COLUMN_COUNT;
|
||||
return parent.isValid() ? 0 : COLUMN_COUNT;
|
||||
}
|
||||
|
||||
Server * at(int index)
|
||||
|
@ -48,11 +48,7 @@
|
||||
<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>
|
||||
<widget class="QLineEdit" name="filterEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="filterLabel">
|
||||
|
@ -20,8 +20,8 @@ class ListModel : public QAbstractListModel {
|
||||
ListModel(ModPage* parent);
|
||||
~ListModel() override;
|
||||
|
||||
inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
|
||||
inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
|
||||
inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); };
|
||||
inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; };
|
||||
inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
|
||||
|
||||
auto debugName() const -> QString;
|
||||
@ -41,12 +41,12 @@ class ListModel : public QAbstractListModel {
|
||||
void requestModVersions(const ModPlatform::IndexedPack& current, QModelIndex index);
|
||||
|
||||
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
|
||||
virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) {};
|
||||
virtual void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
|
||||
virtual void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) = 0;
|
||||
|
||||
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
||||
|
||||
inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
|
||||
inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; };
|
||||
|
||||
public slots:
|
||||
void searchRequestFinished(QJsonDocument& doc);
|
||||
|
@ -262,7 +262,7 @@ void ModPage::openUrl(const QUrl& url)
|
||||
|
||||
const QString address = url.host() + url.path();
|
||||
QRegularExpressionMatch match;
|
||||
const char* page;
|
||||
QString page;
|
||||
|
||||
match = modrinth.match(address);
|
||||
if (match.hasMatch())
|
||||
@ -276,7 +276,7 @@ void ModPage::openUrl(const QUrl& url)
|
||||
page = "curseforge";
|
||||
}
|
||||
|
||||
if (match.hasMatch()) {
|
||||
if (!page.isNull()) {
|
||||
const QString slug = match.captured(1);
|
||||
|
||||
// ensure the user isn't opening the same mod
|
||||
|
@ -32,12 +32,12 @@ ListModel::~ListModel()
|
||||
|
||||
int ListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return modpacks.size();
|
||||
return parent.isValid() ? 0 : modpacks.size();
|
||||
}
|
||||
|
||||
int ListModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QVariant ListModel::data(const QModelIndex &index, int role) const
|
||||
|
@ -75,12 +75,12 @@ QVector<QString> AtlOptionalModListModel::getResult() {
|
||||
}
|
||||
|
||||
int AtlOptionalModListModel::rowCount(const QModelIndex &parent) const {
|
||||
return m_mods.size();
|
||||
return parent.isValid() ? 0 : m_mods.size();
|
||||
}
|
||||
|
||||
int AtlOptionalModListModel::columnCount(const QModelIndex &parent) const {
|
||||
// Enabled, Name, Description
|
||||
return 3;
|
||||
return parent.isValid() ? 0 : 3;
|
||||
}
|
||||
|
||||
QVariant AtlOptionalModListModel::data(const QModelIndex &index, int role) const {
|
||||
|
@ -15,12 +15,12 @@ ListModel::~ListModel() {}
|
||||
|
||||
int ListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return modpacks.size();
|
||||
return parent.isValid() ? 0 : modpacks.size();
|
||||
}
|
||||
|
||||
int ListModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QVariant ListModel::data(const QModelIndex& index, int role) const
|
||||
|
@ -34,12 +34,12 @@ ListModel::~ListModel()
|
||||
|
||||
int ListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return modpacks.size();
|
||||
return parent.isValid() ? 0 : modpacks.size();
|
||||
}
|
||||
|
||||
int ListModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QVariant ListModel::data(const QModelIndex &index, int role) const
|
||||
|
@ -125,12 +125,12 @@ QString ListModel::translatePackType(PackType type) const
|
||||
|
||||
int ListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return modpacks.size();
|
||||
return parent.isValid() ? 0 : modpacks.size();
|
||||
}
|
||||
|
||||
int ListModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
QVariant ListModel::data(const QModelIndex &index, int role) const
|
||||
|
@ -55,8 +55,8 @@ class ModpackListModel : public QAbstractListModel {
|
||||
ModpackListModel(ModrinthPage* parent);
|
||||
~ModpackListModel() override = default;
|
||||
|
||||
inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); };
|
||||
inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; };
|
||||
inline auto rowCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : modpacks.size(); };
|
||||
inline auto columnCount(const QModelIndex& parent) const -> int override { return parent.isValid() ? 0 : 1; };
|
||||
inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); };
|
||||
|
||||
auto debugName() const -> QString;
|
||||
@ -74,7 +74,7 @@ class ModpackListModel : public QAbstractListModel {
|
||||
|
||||
void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback);
|
||||
|
||||
inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; };
|
||||
inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return parent.isValid() ? false : searchState == CanPossiblyFetchMore; };
|
||||
|
||||
public slots:
|
||||
void searchRequestFinished(QJsonDocument& doc_all);
|
||||
|
@ -80,14 +80,14 @@ QVariant Technic::ListModel::data(const QModelIndex& index, int role) const
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int Technic::ListModel::columnCount(const QModelIndex&) const
|
||||
int Technic::ListModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return 1;
|
||||
return parent.isValid() ? 0 : 1;
|
||||
}
|
||||
|
||||
int Technic::ListModel::rowCount(const QModelIndex&) const
|
||||
int Technic::ListModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return modpacks.size();
|
||||
return parent.isValid() ? 0 : modpacks.size();
|
||||
}
|
||||
|
||||
void Technic::ListModel::searchWithTerm(const QString& term)
|
||||
|
@ -110,6 +110,142 @@ VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "@Launcher_Copyright@"
|
||||
VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "@Launcher_VERSION_NAME4@"
|
||||
VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@"
|
||||
|
||||
|
||||
;--------------------------------
|
||||
; Shell Associate Macros
|
||||
|
||||
!macro APP_SETUP DESCRIPTION ICON APP_ID APP_NAME APP_EXE COMMANDTEXT COMMAND ; VERB APP_COMPANY
|
||||
; setup APP_ID
|
||||
WriteRegStr ShCtx "Software\Classes\${APP_ID}" "" `${DESCRIPTION}`
|
||||
WriteRegStr ShCtx "Software\Classes\${APP_ID}\DefaultIcon" "" `${ICON}`
|
||||
; default open verb
|
||||
WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell" "" "open"
|
||||
WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\open" "" `${COMMANDTEXT}`
|
||||
WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\open\command" "" `${COMMAND}`
|
||||
|
||||
; if you want the app to use it's own implementation of a verb
|
||||
;WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\${VERB}" "" "${DESCRIPTION}"
|
||||
;WriteRegStr ShCtx "Software\Classes\${APP_ID}\shell\${VERB}\command" "" `${COMMAND}`
|
||||
|
||||
WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\shell\open\command" "" `${COMMAND}`
|
||||
WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}" "FriendlyAppName" `${APP_NAME}` ; [Optional]
|
||||
;WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}" "ApplicationCompany" `${APP_COMPANY}` ; [Optional]
|
||||
;WriteRegNone ShCtx "Software\Classes\Applications\${APP_EXE}\SupportedTypes" ".${EXT}" ; [Optional] Only allow "Open With" with specific extension(s) on WinXP+
|
||||
|
||||
# Register "Default Programs" [Optional]
|
||||
!ifdef REGISTER_DEFAULTPROGRAMS
|
||||
WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities" "ApplicationDescription" `${DESCRIPTION}`
|
||||
WriteRegStr ShCtx "Software\RegisteredApplications" `${APP_NAME}` "Software\Classes\Applications\${APP_EXE}\Capabilities"
|
||||
!endif
|
||||
|
||||
!macroend
|
||||
|
||||
!macro APP_ASSOCIATE EXT APP_ID APP_EXE OVERWIRTE
|
||||
; Backup the previously associated file class
|
||||
${If} ${OVERWIRTE} == true
|
||||
ReadRegStr $R0 ShCtx "Software\Classes\${EXT}" ""
|
||||
WriteRegStr ShCtx "Software\Classes\${EXT}" "${APP_ID}_backup" "$R0"
|
||||
WriteRegStr ShCtx "Software\Classes\${EXT}" "" "${APP_ID}"
|
||||
${EndIf}
|
||||
|
||||
WriteRegNone ShCtx "Software\Classes\${EXT}\OpenWithList" "${APP_EXE}" ; Win2000+
|
||||
WriteRegNone ShCtx "Software\Classes\${EXT}\OpenWithProgids" "${APP_ID}" ; WinXP+
|
||||
|
||||
# Register "Default Programs" [Optional]
|
||||
!ifdef REGISTER_DEFAULTPROGRAMS
|
||||
WriteRegStr ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities\FileAssociations" "${EXT}" "${APP_ID}"
|
||||
!endif
|
||||
|
||||
!macroend
|
||||
|
||||
!macro APP_UNASSOCIATE EXT APP_ID
|
||||
|
||||
# Unregister file type
|
||||
ClearErrors
|
||||
; restore backup
|
||||
ReadRegStr $R1 ShCtx "Software\Classes\${EXT}" ""
|
||||
${If} $R1 == "${APP_ID}"
|
||||
ReadRegStr $R0 ShCtx "Software\Classes\${EXT}" `${APP_ID}_backup`
|
||||
WriteRegStr ShCtx "Software\Classes\${EXT}" "" "$R0"
|
||||
${Else}
|
||||
ReadRegStr $R0 ShCtx "Software\Classes\${EXT}" ""
|
||||
${EndIf}
|
||||
|
||||
DeleteRegKey /IfEmpty ShCtx "Software\Classes\${APP_ID}"
|
||||
${IfNot} ${Errors}
|
||||
${AndIf} $R0 == "${APP_ID}"
|
||||
DeleteRegValue ShCtx "Software\Classes\${EXT}" ""
|
||||
DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}"
|
||||
${EndIf}
|
||||
|
||||
DeleteRegValue ShCtx "Software\Classes\${EXT}\OpenWithList" "${APP_EXE}"
|
||||
DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}\OpenWithList"
|
||||
DeleteRegValue ShCtx "Software\Classes\${EXT}\OpenWithProgids" "${APP_ID}"
|
||||
DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}\OpenWithProgids"
|
||||
DeleteRegKey /IfEmpty ShCtx "Software\Classes\${EXT}"
|
||||
|
||||
# Attempt to clean up junk left behind by the Windows shell
|
||||
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "${APP_ID}_${EXT}"
|
||||
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\ApplicationAssociationToasts" "Applications\${APP_EXE}_${EXT}"
|
||||
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithProgids" "${APP_ID}"
|
||||
DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithProgids"
|
||||
DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}\OpenWithList"
|
||||
DeleteRegKey /IfEmpty HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\${EXT}"
|
||||
;DeleteRegKey HKCU "Software\Microsoft\Windows\Roaming\OpenWith\FileExts\.${EXT}"
|
||||
;DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs\.${EXT}"
|
||||
|
||||
!macroend
|
||||
|
||||
!macro APP_TEARDOWN APP_ID APP_NAME APP_EXE
|
||||
|
||||
# Unregister file type
|
||||
ClearErrors
|
||||
DeleteRegKey /IfEmpty ShCtx "Software\Classes\${APP_ID}\shell"
|
||||
${IfNot} ${Errors}
|
||||
DeleteRegKey ShCtx "Software\Classes\${APP_ID}\DefaultIcon"
|
||||
${EndIf}
|
||||
|
||||
# Unregister "Open With"
|
||||
DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}"
|
||||
|
||||
# Unregister "Default Programs"
|
||||
!ifdef REGISTER_DEFAULTPROGRAMS
|
||||
DeleteRegValue ShCtx "Software\RegisteredApplications" `${APP_NAME}`
|
||||
DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}\Capabilities"
|
||||
DeleteRegKey /IfEmpty ShCtx "Software\Classes\Applications\${APP_EXE}"
|
||||
!endif
|
||||
|
||||
DeleteRegKey ShCtx `Software\Classes\${APP_ID}`
|
||||
DeleteRegKey ShCtx "Software\Classes\Applications\${APP_EXE}"
|
||||
|
||||
# Attempt to clean up junk left behind by the Windows shell
|
||||
DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Search\JumplistData" "$INSTDIR\${APP_EXE}"
|
||||
DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\${APP_EXE}.FriendlyAppName"
|
||||
DeleteRegValue HKCU "Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache" "$INSTDIR\${APP_EXE}.ApplicationCompany"
|
||||
DeleteRegValue HKCU "Software\Microsoft\Windows\ShellNoRoam\MUICache" "$INSTDIR\${APP_EXE}" ; WinXP
|
||||
DeleteRegValue HKCU "Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Compatibility Assistant\Store" "$INSTDIR\${APP_EXE}"
|
||||
|
||||
!macroend
|
||||
|
||||
; !defines for use with SHChangeNotify
|
||||
!ifdef SHCNE_ASSOCCHANGED
|
||||
!undef SHCNE_ASSOCCHANGED
|
||||
!endif
|
||||
!define SHCNE_ASSOCCHANGED 0x08000000
|
||||
!ifdef SHCNF_FLUSH
|
||||
!undef SHCNF_FLUSH
|
||||
!endif
|
||||
!define SHCNF_FLUSH 0x1000
|
||||
|
||||
|
||||
# ensure this is called at the end of any section that changes shell keys
|
||||
!macro NotifyShell_AssocChanged
|
||||
; Using the system.dll plugin to call the SHChangeNotify Win32 API function so we
|
||||
; can update the shell.
|
||||
System::Call "shell32::SHChangeNotify(i,i,i,i) (${SHCNE_ASSOCCHANGED}, ${SHCNF_FLUSH}, 0, 0)"
|
||||
!macroend
|
||||
|
||||
|
||||
;--------------------------------
|
||||
|
||||
; The stuff to install
|
||||
@ -171,6 +307,27 @@ Section /o "Desktop Shortcut" DESKTOP_SHORTCUTS
|
||||
|
||||
SectionEnd
|
||||
|
||||
|
||||
!define APP_ID "@Launcher_CommonName@.App"
|
||||
!define APP_EXE "@Launcher_APP_BINARY_NAME@.exe"
|
||||
!define APP_ICON "$INSTDIR\${APP_EXE},0"
|
||||
!define APP_DESCRIPTION "@Launcher_DisplayName@"
|
||||
!define APP_NAME "@Launcher_DisplayName@"
|
||||
!define APP_CMD_TEXT "Minecraft Modpack"
|
||||
|
||||
!define REGISTER_DEFAULTPROGRAMS ; value doesn't matter
|
||||
|
||||
Section -ShellAssoc
|
||||
|
||||
!insertmacro APP_SETUP `${APP_DESCRIPTION}` `${APP_ICON}` `${APP_ID}` `${APP_CMD_TEXT}` `${APP_EXE}` `${APP_CMD_TEXT}` '$INSTDIR\${APP_EXE} -I "%1"'
|
||||
|
||||
!insertmacro APP_ASSOCIATE ".zip" `${APP_ID}` `${APP_EXE}` false
|
||||
!insertmacro APP_ASSOCIATE ".mrpack" `${APP_ID}` `${APP_EXE}` true
|
||||
|
||||
!insertmacro NotifyShell_AssocChanged
|
||||
SectionEnd
|
||||
|
||||
|
||||
;--------------------------------
|
||||
|
||||
; Uninstaller
|
||||
@ -202,6 +359,16 @@ Section "Uninstall"
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section -un.ShellAssoc
|
||||
|
||||
!insertmacro APP_TEARDOWN `${APP_ID}` `${APP_NAME}` `${APP_EXE}`
|
||||
|
||||
!insertmacro APP_UNASSOCIATE ".zip" `${APP_ID}`
|
||||
!insertmacro APP_UNASSOCIATE ".mrpack" `${APP_ID}`
|
||||
|
||||
!insertmacro NotifyShell_AssocChanged
|
||||
SectionEnd
|
||||
|
||||
;--------------------------------
|
||||
|
||||
; Extra command line parameters
|
||||
|
Loading…
Reference in New Issue
Block a user