Launch Arguments ()

* Loader launch args

* Implement launch args on loader/mod

* Add to test mod

* Documentation

* Rename methods and better docs

* Expand API

* Fix loader impls

* Expand tests

* Add an extra hyphen to the launch arg prefix

* Update comments with extra hyphen
This commit is contained in:
Skye Prince 2024-01-24 11:04:00 -08:00 committed by GitHub
parent 79d9184344
commit a2b164af29
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 245 additions and 2 deletions

View file

@ -83,6 +83,29 @@ namespace geode {
std::vector<Mod*> getAllMods();
std::vector<LoadProblem> getProblems() const;
/**
* Returns the available launch argument names.
*/
std::vector<std::string> getLaunchArgumentNames() const;
/**
* Returns whether the specified launch argument was passed in via the command line.
* @param name The argument name
*/
bool hasLaunchArgument(std::string_view const name) const;
/**
* Get a launch argument. These are passed into the game as command-line arguments
* with the format `--geode:argName=value`.
* @param name The argument name
* @return The value, if present
*/
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
/**
* Get a boolean launch argument. Returns whether the argument is present and its
* value is exactly `true`.
* @param name The argument name
*/
bool getLaunchBool(std::string_view const name) const;
void queueInMainThread(ScheduledFunction func);
friend class LoaderImpl;

View file

@ -150,6 +150,31 @@ namespace geode {
this->registerCustomSetting(key, std::make_unique<T>(std::string(key), this->getID(), value));
}
/**
* Returns the names of the available mod-specific launch arguments.
*/
std::vector<std::string> getLaunchArgumentNames() const;
/**
* Equivalent to a prefixed `Loader::hasLaunchArgument` call. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
* @param name The argument name
*/
bool hasLaunchArgument(std::string_view const name) const;
/**
* Get a mod-specific launch argument. This is equivalent to `Loader::getLaunchArgument`
* with the argument name prefixed by the mod ID. For example, a launch argument named
* `modArg` for the mod `author.test` would be specified with `--geode:author.test.modArg=value`.
* @param name The argument name
* @return The value, if present
*/
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
/**
* Equivalent to a prefixed `Loader::getLaunchBool` call. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
* @param name The argument name
*/
bool getLaunchBool(std::string_view const name) const;
matjson::Value& getSaveContainer();
template <class T>

View file

@ -76,3 +76,19 @@ void Loader::queueInMainThread(ScheduledFunction func) {
Mod* Loader::takeNextMod() {
return m_impl->takeNextMod();
}
std::vector<std::string> Loader::getLaunchArgumentNames() const {
return m_impl->getLaunchArgumentNames();
}
bool Loader::hasLaunchArgument(std::string_view const name) const {
return m_impl->hasLaunchArgument(name);
}
std::optional<std::string> Loader::getLaunchArgument(std::string_view const name) const {
return m_impl->getLaunchArgument(name);
}
bool Loader::getLaunchBool(std::string_view const name) const {
return m_impl->getLaunchBool(name);
}

View file

@ -22,8 +22,11 @@
#include <fmt/format.h>
#include <hash.hpp>
#include <iostream>
#include <iterator>
#include <optional>
#include <resources.hpp>
#include <string>
#include <string_view>
#include <vector>
using namespace geode::prelude;
@ -68,6 +71,13 @@ Result<> Loader::Impl::setup() {
return Ok();
}
if (this->supportsLaunchArguments()) {
log::debug("Loading launch arguments");
log::pushNest();
this->initLaunchArguments();
log::popNest();
}
log::debug("Setting up crash handler");
log::pushNest();
if (!crashlog::setupPlatformHandler()) {
@ -726,6 +736,52 @@ void Loader::Impl::releaseNextMod() {
m_nextModLock.unlock();
}
// TODO: Support for quoted launch args w/ spaces (this will be backwards compatible)
// e.g. "--geode:arg=My spaced value"
void Loader::Impl::initLaunchArguments() {
auto launchStr = this->getLaunchCommand();
log::debug("Found launch string: {}", launchStr);
auto args = string::split(launchStr, " ");
for (const auto& arg : args) {
if (!arg.starts_with(LAUNCH_ARG_PREFIX)) {
continue;
}
auto pair = arg.substr(LAUNCH_ARG_PREFIX.size());
auto sep = pair.find('=');
if (sep == std::string::npos) {
m_launchArgs.insert({ pair, "true" });
continue;
}
auto key = pair.substr(0, sep);
auto value = pair.substr(sep + 1);
m_launchArgs.insert({ key, value });
}
for (const auto& pair : m_launchArgs) {
log::debug("Loaded '{}' as '{}'", pair.first, pair.second);
}
}
std::vector<std::string> Loader::Impl::getLaunchArgumentNames() const {
return map::keys(m_launchArgs);
}
bool Loader::Impl::hasLaunchArgument(std::string_view const name) const {
return m_launchArgs.find(std::string(name)) != m_launchArgs.end();
}
std::optional<std::string> Loader::Impl::getLaunchArgument(std::string_view const name) const {
auto value = m_launchArgs.find(std::string(name));
if (value == m_launchArgs.end()) {
return std::nullopt;
}
return std::optional(value->second);
}
bool Loader::Impl::getLaunchBool(std::string_view const name) const {
auto arg = this->getLaunchArgument(name);
return arg.has_value() && arg.value() == "true";
}
Result<tulip::hook::HandlerHandle> Loader::Impl::getHandler(void* address) {
if (!m_handlerHandles.count(address)) {
return Err("Handler does not exist at address");

View file

@ -25,6 +25,8 @@
// TODO: Find a file convention for impl headers
namespace geode {
constexpr std::string LAUNCH_ARG_PREFIX = "--geode:";
class Loader::Impl {
public:
mutable std::mutex m_mutex;
@ -58,6 +60,8 @@ namespace geode {
int m_refreshedModCount = 0;
int m_lateRefreshedModCount = 0;
std::unordered_map<std::string, std::string> m_launchArgs;
std::chrono::time_point<std::chrono::high_resolution_clock> m_timerBegin;
std::string getGameVersion();
@ -109,6 +113,14 @@ namespace geode {
std::vector<Mod*> getAllMods();
std::vector<LoadProblem> getProblems() const;
bool supportsLaunchArguments() const;
std::string getLaunchCommand() const;
void initLaunchArguments();
std::vector<std::string> getLaunchArgumentNames() const;
bool hasLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
bool getLaunchBool(std::string_view const name) const;
void updateResources(bool forceReload);
void queueInMainThread(const ScheduledFunction& func);

View file

@ -1,7 +1,10 @@
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/Dirs.hpp>
#include "ModImpl.hpp"
#include <Geode/loader/Dirs.hpp>
#include <Geode/loader/Mod.hpp>
#include <optional>
#include <string_view>
using namespace geode::prelude;
Mod::Mod(ModMetadata const& metadata) : m_impl(std::make_unique<Impl>(this, metadata)) {}
@ -72,6 +75,7 @@ ghc::filesystem::path Mod::getResourcesDir() const {
void Mod::setMetadata(ModMetadata const& metadata) {
m_impl->setMetadata(metadata);
}
std::vector<Mod*> Mod::getDependants() const {
return m_impl->getDependants();
}
@ -117,6 +121,22 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr<Sett
return m_impl->registerCustomSetting(key, std::move(value));
}
std::vector<std::string> Mod::getLaunchArgumentNames() const {
return m_impl->getLaunchArgumentNames();
}
bool Mod::hasLaunchArgument(std::string_view const name) const {
return m_impl->hasLaunchArgument(name);
}
std::optional<std::string> Mod::getLaunchArgument(std::string_view const name) const {
return m_impl->getLaunchArgument(name);
}
bool Mod::getLaunchBool(std::string_view const name) const {
return m_impl->getLaunchBool(name);
}
Result<Hook*> Mod::claimHook(std::shared_ptr<Hook> hook) {
return m_impl->claimHook(hook);
}

View file

@ -327,6 +327,37 @@ bool Mod::Impl::hasSetting(std::string_view const key) const {
return false;
}
std::string Mod::Impl::getLaunchArgPrefix() const {
return m_metadata.getID() + ".";
}
std::string Mod::Impl::getLaunchArgName(std::string_view const name) const {
return this->getLaunchArgPrefix() + std::string(name);
}
std::vector<std::string> Mod::Impl::getLaunchArgumentNames() const {
auto prefix = getLaunchArgPrefix();
std::vector<std::string> names;
for (const auto& name : Loader::get()->getLaunchArgumentNames()) {
if (name.starts_with(prefix)) {
names.push_back(name.substr(prefix.size()));
}
}
return names;
}
bool Mod::Impl::hasLaunchArgument(std::string_view const name) const {
return Loader::get()->hasLaunchArgument(this->getLaunchArgName(name));
}
std::optional<std::string> Mod::Impl::getLaunchArgument(std::string_view const name) const {
return Loader::get()->getLaunchArgument(this->getLaunchArgName(name));
}
bool Mod::Impl::getLaunchBool(std::string_view const name) const {
return Loader::get()->getLaunchBool(this->getLaunchArgName(name));
}
// Loading, Toggling, Installing
Result<> Mod::Impl::loadBinary() {

View file

@ -113,6 +113,13 @@ namespace geode {
SettingValue* getSetting(std::string_view const key) const;
void registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value);
std::string getLaunchArgPrefix() const;
std::string getLaunchArgName(std::string_view const name) const;
std::vector<std::string> getLaunchArgumentNames() const;
bool hasLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
bool getLaunchBool(std::string_view const name) const;
Result<Hook*> claimHook(std::shared_ptr<Hook> hook);
Result<> disownHook(Hook* hook);
[[nodiscard]] std::vector<Hook*> getHooks() const;

View file

@ -36,3 +36,11 @@ bool Loader::Impl::userTriedToLoadDLLs() const {
void Loader::Impl::addNativeBinariesPath(ghc::filesystem::path const& path) {
log::warn("LoaderImpl::addNativeBinariesPath not implement on this platform, not adding path {}", path.string());
}
bool Loader::Impl::supportsLaunchArguments() const {
return false;
}
std::string Loader::Impl::getLaunchCommand() const {
return std::string(); // Empty
}

View file

@ -39,3 +39,11 @@ void Loader::Impl::setupIPC() {
bool Loader::Impl::userTriedToLoadDLLs() const {
return false;
}
bool Loader::Impl::supportsLaunchArguments() const {
return false;
}
std::string Loader::Impl::getLaunchCommand() const {
return std::string(); // Empty
}

View file

@ -145,3 +145,12 @@ void Loader::Impl::addNativeBinariesPath(ghc::filesystem::path const& path) {
std::string Loader::Impl::getGameVersion() {
return GEODE_STR(GEODE_GD_VERSION); // TODO implement
}
// TODO
bool Loader::Impl::supportsLaunchArguments() const {
return false;
}
std::string Loader::Impl::getLaunchCommand() const {
return std::string(); // Empty
}

View file

@ -3,6 +3,7 @@
#include <loader/ModImpl.hpp>
#include <loader/LoaderImpl.hpp>
#include <Geode/utils/string.hpp>
#include <processenv.h>
using namespace geode::prelude;
@ -83,3 +84,11 @@ void Loader::Impl::addNativeBinariesPath(ghc::filesystem::path const& path) {
}();
AddDllDirectory(path.wstring().c_str());
}
bool Loader::Impl::supportsLaunchArguments() const {
return true;
}
std::string Loader::Impl::getLaunchCommand() const {
return GetCommandLineA();
}

View file

@ -44,6 +44,25 @@ struct $modify(MenuLayer) {
node->release();
log::info("ref: {}", ref.lock().data());
// Launch arguments
log::info("Testing launch args...");
log::pushNest();
log::info("For global context:");
log::pushNest();
for (const auto& arg : Loader::get()->getLaunchArgumentNames()) {
log::info(arg);
}
log::popNest();
log::info("For this mod:");
log::pushNest();
for (const auto& arg : Mod::get()->getLaunchArgumentNames()) {
log::info(arg);
}
log::popNest();
log::info("Mod has launch arg 'modArg': {}", Mod::get()->hasLaunchArgument("modArg"));
log::info("Loader bool arg 'boolArg': {}", Loader::get()->getLaunchBool("boolArg"));
log::popNest();
return true;
}
};