Initial mod import tests
Signed-off-by: crueter <crueter@eden-emu.dev>
This commit is contained in:
parent
5acddfde16
commit
f222c28c7c
|
|
@ -13,7 +13,8 @@ add_library(frontend_common STATIC
|
|||
data_manager.h data_manager.cpp
|
||||
play_time_manager.cpp
|
||||
play_time_manager.h
|
||||
settings_generator.h settings_generator.cpp)
|
||||
settings_generator.h settings_generator.cpp
|
||||
mod_manager.h mod_manager.cpp)
|
||||
|
||||
if (ENABLE_UPDATE_CHECKER)
|
||||
target_link_libraries(frontend_common PRIVATE httplib::httplib)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fmt/format.h>
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_types.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "frontend_common/data_manager.h"
|
||||
#include "mod_manager.h"
|
||||
|
||||
namespace FrontendCommon {
|
||||
|
||||
// TODO: Handle cases where the folder appears to contain multiple mods.
|
||||
std::optional<std::filesystem::path> GetModFolder(const std::string& root) {
|
||||
std::filesystem::path path;
|
||||
bool found;
|
||||
|
||||
auto callback = [&path, &found](const std::filesystem::directory_entry& entry) -> bool {
|
||||
const auto name = entry.path().filename().string();
|
||||
static constexpr const std::array<std::string, 5> valid_names = {"exefs",
|
||||
"romfs"
|
||||
"romfs_ext",
|
||||
"cheats", "romfslite"};
|
||||
|
||||
if (std::ranges::find(valid_names, name) != valid_names.end()) {
|
||||
path = entry.path().parent_path();
|
||||
found = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Common::FS::IterateDirEntriesRecursively(root, callback, Common::FS::DirEntryFilter::Directory);
|
||||
|
||||
if (found)
|
||||
return path;
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool InstallMod(const std::filesystem::path& path, const u64 program_id, const bool copy) {
|
||||
const auto program_id_string = fmt::format("{:016X}", program_id);
|
||||
const auto mod_name = path.filename();
|
||||
const auto mod_dir =
|
||||
DataManager::GetDataDir(DataManager::DataDir::Mods) / program_id_string / mod_name;
|
||||
|
||||
// pre-emptively remove any existing mod here
|
||||
std::filesystem::remove_all(mod_dir);
|
||||
|
||||
// now copy
|
||||
try {
|
||||
if (copy)
|
||||
std::filesystem::copy(path, mod_dir, std::filesystem::copy_options::recursive);
|
||||
else
|
||||
std::filesystem::rename(path, mod_dir);
|
||||
} catch (std::exception& e) {
|
||||
LOG_ERROR(Frontend, "Mod install failed with message {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO(Frontend, "Copied mod from {} to {}", path.string(), mod_dir.string());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace FrontendCommon
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace FrontendCommon {
|
||||
|
||||
std::optional<std::filesystem::path> GetModFolder(const std::string& root);
|
||||
|
||||
bool InstallMod(const std::filesystem::path &path, const u64 program_id, const bool copy = true);
|
||||
}
|
||||
|
|
@ -22,6 +22,8 @@ add_library(qt_common STATIC
|
|||
util/rom.h util/rom.cpp
|
||||
util/applet.h util/applet.cpp
|
||||
util/compress.h util/compress.cpp
|
||||
util/fs.h util/fs.cpp
|
||||
util/mod.h util/mod.cpp
|
||||
|
||||
abstract/frontend.h abstract/frontend.cpp
|
||||
abstract/qt_progress_dialog.h abstract/qt_progress_dialog.cpp
|
||||
|
|
@ -29,9 +31,7 @@ add_library(qt_common STATIC
|
|||
qt_string_lookup.h
|
||||
qt_compat.h
|
||||
|
||||
discord/discord.h
|
||||
util/fs.h util/fs.cpp
|
||||
)
|
||||
discord/discord.h)
|
||||
|
||||
if (UNIX)
|
||||
target_sources(qt_common PRIVATE gui_settings.cpp gui_settings.h)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QLineEdit>
|
||||
#include "frontend.h"
|
||||
#include "qt_common/qt_common.h"
|
||||
|
||||
|
|
@ -8,6 +9,9 @@
|
|||
#include <QFileDialog>
|
||||
#endif
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QInputDialog>
|
||||
|
||||
namespace QtCommon::Frontend {
|
||||
|
||||
StandardButton ShowMessage(
|
||||
|
|
@ -50,4 +54,25 @@ const QString GetExistingDirectory(const QString& caption, const QString& dir,
|
|||
#endif
|
||||
}
|
||||
|
||||
int Choice(const QString& title, const QString& caption, const QStringList& options) {
|
||||
QMessageBox box(rootObject);
|
||||
box.setText(caption);
|
||||
box.setWindowTitle(title);
|
||||
|
||||
for (const QString &opt : options) {
|
||||
box.addButton(opt, QMessageBox::AcceptRole);
|
||||
}
|
||||
|
||||
box.addButton(QMessageBox::Cancel);
|
||||
|
||||
box.exec();
|
||||
auto button = box.clickedButton();
|
||||
return options.indexOf(button->text());
|
||||
}
|
||||
|
||||
const QString GetTextInput(const QString& title, const QString& caption,
|
||||
const QString& defaultText) {
|
||||
return QInputDialog::getText(rootObject, title, caption, QLineEdit::Normal, defaultText);
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
|
|
|
|||
|
|
@ -139,5 +139,11 @@ const QString GetExistingDirectory(const QString &caption = QString(),
|
|||
const QString &dir = QString(),
|
||||
Options options = Option::ShowDirsOnly);
|
||||
|
||||
int Choice(const QString& title = QString(), const QString& caption = QString(),
|
||||
const QStringList& options = {});
|
||||
|
||||
const QString GetTextInput(const QString& title = QString(), const QString& caption = QString(),
|
||||
const QString& defaultText = QString());
|
||||
|
||||
} // namespace QtCommon::Frontend
|
||||
#endif // FRONTEND_H
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
#include <filesystem>
|
||||
#include "frontend_common/mod_manager.h"
|
||||
#include "mod.h"
|
||||
#include "qt_common/abstract/frontend.h"
|
||||
|
||||
namespace QtCommon::Mod {
|
||||
QString GetModFolder(const QString& root, const QString& fallbackName) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const auto std_root = root.toStdString();
|
||||
auto std_path = FrontendCommon::GetModFolder(std_root);
|
||||
|
||||
QString default_name;
|
||||
if (std_path)
|
||||
default_name = QString::fromStdString(std_path->filename());
|
||||
else if (fallbackName.isEmpty())
|
||||
default_name = root.split(QLatin1Char('/')).last();
|
||||
else
|
||||
default_name = fallbackName;
|
||||
|
||||
QString name = QtCommon::Frontend::GetTextInput(
|
||||
tr("Mod Name"), tr("What should this mod be called?"), default_name);
|
||||
|
||||
// if std_path is empty, frontend_common could not determine mod type and/or name.
|
||||
// so we have to prompt the user and set up the structure ourselves
|
||||
if (!std_path) {
|
||||
// TODO: Carboxyl impl.
|
||||
const QStringList choices = {
|
||||
tr("RomFS"),
|
||||
tr("ExeFS/Patch"),
|
||||
tr("Cheat"),
|
||||
};
|
||||
|
||||
int choice = QtCommon::Frontend::Choice(
|
||||
tr("Mod Type"),
|
||||
tr("Could not detect mod type automatically. Please manually "
|
||||
"specify the type of mod you downloaded.\n\nMost mods are RomFS mods, but patches "
|
||||
"(.pchtxt) are typically ExeFS mods."),
|
||||
choices);
|
||||
|
||||
std::string to_make;
|
||||
|
||||
switch (choice) {
|
||||
case 0:
|
||||
to_make = "romfs";
|
||||
break;
|
||||
case 1:
|
||||
to_make = "exefs";
|
||||
break;
|
||||
case 2:
|
||||
to_make = "cheats";
|
||||
break;
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
|
||||
// now make a temp directory...
|
||||
const auto mod_dir = fs::temp_directory_path() / "eden" / "mod" / name.toStdString();
|
||||
const auto tmp = mod_dir / to_make;
|
||||
fs::remove_all(mod_dir);
|
||||
if (!fs::create_directories(tmp))
|
||||
return QString();
|
||||
|
||||
std_path = mod_dir;
|
||||
|
||||
// ... and copy everything from the root to the temp dir
|
||||
for (const auto& entry : fs::directory_iterator(root.toStdString())) {
|
||||
const auto target = tmp / entry.path().filename();
|
||||
|
||||
fs::copy(entry.path(), target,
|
||||
fs::copy_options::recursive | fs::copy_options::overwrite_existing);
|
||||
}
|
||||
}
|
||||
|
||||
return QString::fromStdString(std_path->string());
|
||||
}
|
||||
|
||||
bool InstallMod(const QString& path, const QString& fallbackName, const u64 program_id,
|
||||
const bool copy) {
|
||||
const auto target = GetModFolder(path, fallbackName);
|
||||
return FrontendCommon::InstallMod(target.toStdString(), program_id, copy);
|
||||
}
|
||||
|
||||
} // namespace QtCommon::Mod
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace QtCommon::Mod {
|
||||
|
||||
QString GetModFolder(const QString &root, const QString &fallbackName);
|
||||
|
||||
bool InstallMod(const QString &path, const QString &fallbackName, const u64 program_id, const bool copy);
|
||||
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
// Qt on macOS doesn't define VMA shit
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include "frontend_common/mod_manager.h"
|
||||
#include "frontend_common/settings_generator.h"
|
||||
#include "qt_common/qt_string_lookup.h"
|
||||
#if defined(QT_STATICPLUGIN) && !defined(__APPLE__)
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
#include "qt_common/util/meta.h"
|
||||
#include "qt_common/util/content.h"
|
||||
#include "qt_common/util/fs.h"
|
||||
#include "qt_common/util/mod.h"
|
||||
|
||||
// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows
|
||||
// defines.
|
||||
|
|
|
|||
Loading…
Reference in New Issue