geode/loader/include/Geode/loader/Mod.hpp

390 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include "../DefaultInclude.hpp"
#include "../cocos/support/zip_support/ZipUtils.h"
#include "../utils/Result.hpp"
#include "../utils/VersionInfo.hpp"
#include "../utils/general.hpp"
#include "Hook.hpp"
#include "ModMetadata.hpp"
#include "Setting.hpp"
#include "Types.hpp"
#include <matjson.hpp>
#include <optional>
#include <string_view>
#include <tulip/TulipHook.hpp>
#include <type_traits>
#include <unordered_map>
#include <vector>
namespace geode {
template <class T>
struct HandleToSaved : public T {
Mod* m_mod;
std::string m_key;
HandleToSaved(std::string const& key, Mod* mod, T const& value) :
T(value), m_key(key), m_mod(mod) {}
HandleToSaved(HandleToSaved const&) = delete;
HandleToSaved(HandleToSaved&&) = delete;
~HandleToSaved();
};
enum class ModRequestedAction {
None,
Enable,
Disable,
Uninstall,
UninstallWithSaveData
};
GEODE_HIDDEN Mod* takeNextLoaderMod();
class ModImpl;
/**
* Represents a Mod ingame.
* @class Mod
*/
class GEODE_DLL Mod {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
friend class Loader;
template <class = void>
static inline GEODE_HIDDEN Mod* sharedMod = nullptr;
// used internally in geode_implicit_load
template <class = void>
static inline GEODE_HIDDEN void setSharedMod(Mod* mod) {
sharedMod<> = mod;
}
friend void GEODE_CALL ::geode_implicit_load(Mod*);
public:
// no copying
Mod(Mod const&) = delete;
Mod operator=(Mod const&) = delete;
// Protected constructor/destructor
Mod() = delete;
Mod(ModMetadata const& metadata);
~Mod();
std::string getID() const;
std::string getName() const;
std::string getDeveloper() const;
std::optional<std::string> getDescription() const;
std::optional<std::string> getDetails() const;
ghc::filesystem::path getPackagePath() const;
VersionInfo getVersion() const;
bool isEnabled() const;
bool isInternal() const;
bool needsEarlyLoad() const;
ModMetadata getMetadata() const;
ghc::filesystem::path getTempDir() const;
/**
* Get the path to the mod's platform binary (.dll on Windows, .dylib
* on Mac & iOS, .so on Android)
*/
ghc::filesystem::path getBinaryPath() const;
/**
* Get the path to the mod's runtime resources directory (contains all
* of its resources)
*/
ghc::filesystem::path getResourcesDir() const;
#if defined(GEODE_EXPOSE_SECRET_INTERNALS_IN_HEADERS_DO_NOT_DEFINE_PLEASE)
void setMetadata(ModMetadata const& metadata);
std::vector<Mod*> getDependants() const;
#endif
Result<> saveData();
Result<> loadData();
/**
* Get the mod's save directory path
*/
ghc::filesystem::path getSaveDir() const;
/**
* Get the mod's config directory path
*/
ghc::filesystem::path getConfigDir(bool create = true) const;
bool hasSettings() const;
std::vector<std::string> getSettingKeys() const;
bool hasSetting(std::string_view const key) const;
std::optional<Setting> getSettingDefinition(std::string_view const key) const;
SettingValue* getSetting(std::string_view const key) const;
/**
* Register a custom setting's value class. See Mod::addCustomSetting
* for a convenience wrapper that creates the value in-place to avoid
* code duplication. Also see
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for
* more information about custom settings
* @param key The setting's key
* @param value The SettingValue class that shall handle this setting
* @see addCustomSetting
*/
void registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value);
/**
* Register a custom setting's value class. The new SettingValue class
* will be created in-place using `std::make_unique`. See
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for
* more information about custom settings
* @param key The setting's key
* @param value The value of the custom setting
* @example
* $on_mod(Loaded) {
* Mod::get()->addCustomSetting<MySettingValue>("setting-key", DEFAULT_VALUE);
* }
*/
template <class T, class V>
void addCustomSetting(std::string_view const key, V const& value) {
this->registerCustomSetting(key, std::make_unique<T>(std::string(key), this->getID(), value));
}
matjson::Value& getSaveContainer();
template <class T>
T getSettingValue(std::string_view const key) const {
if (auto sett = this->getSetting(key)) {
return SettingValueSetter<T>::get(sett);
}
return T();
}
template <class T>
T setSettingValue(std::string_view const key, T const& value) {
if (auto sett = this->getSetting(key)) {
auto old = this->getSettingValue<T>(key);
SettingValueSetter<T>::set(sett, value);
return old;
}
return T();
}
bool hasSavedValue(std::string_view const key);
template <class T>
T getSavedValue(std::string_view const key) {
auto& saved = this->getSaveContainer();
if (saved.contains(key)) {
try {
// json -> T may fail
return saved.get<T>(key);
}
catch (...) {
}
}
return T();
}
template <class T>
T getSavedValue(std::string_view const key, T const& defaultValue) {
auto& saved = this->getSaveContainer();
if (saved.contains(key)) {
try {
// json -> T may fail
return saved.get<T>(key);
}
catch (...) {
}
}
saved[key] = defaultValue;
return defaultValue;
}
/**
* Set the value of an automatically saved variable. When the game is
* closed, the value is automatically saved under the key
* @param key Key of the saved value
* @param value Value
* @returns The old value
*/
template <class T>
T setSavedValue(std::string_view const key, T const& value) {
auto& saved = this->getSaveContainer();
auto old = this->getSavedValue<T>(key);
saved[key] = value;
return old;
}
/**
* Get the Mod of the current mod being developed
* @returns The current mod
*/
template <class = void>
static inline GEODE_HIDDEN Mod* get() {
if (!sharedMod<>) {
sharedMod<> = takeNextLoaderMod();
}
return sharedMod<>;
}
/**
* Create a hook at an address. Call the original
* function by calling the original function
* no trampoline needed
* @param address The absolute address of
* the function to hook, i.e. gd_base + 0xXXXX
* @param detour Pointer to your detour function
* @param displayName Name of the hook that will be
* displayed in the hook list
* @param convention Calling convention of the hook
* @param hookMetadata Metadata of the hook
* @returns Successful result containing the
* Hook pointer, errorful result with info on
* error
*/
template<class DetourType>
Result<Hook*> hook(
void* address, DetourType detour, std::string const& displayName = "",
tulip::hook::TulipConvention convention = tulip::hook::TulipConvention::Default,
tulip::hook::HookMetadata const& hookMetadata = tulip::hook::HookMetadata()
) {
auto hook = Hook::create(address, detour, displayName, convention, hookMetadata);
GEODE_UNWRAP_INTO(auto ptr, this->claimHook(std::move(hook)));
return Ok(ptr);
}
Result<Hook*> hook(
void* address, void* detour, std::string const& displayName,
tulip::hook::HandlerMetadata const& handlerMetadata,
tulip::hook::HookMetadata const& hookMetadata
) {
auto hook = Hook::create(address, detour, displayName, handlerMetadata, hookMetadata);
GEODE_UNWRAP_INTO(auto ptr, this->claimHook(std::move(hook)));
return Ok(ptr);
}
/**
* Claims an existing hook object, marking this mod as its owner.
* If the hook has "auto enable" set, this will enable the hook.
* @returns Returns a pointer to the hook, or an error if the
* hook already has an owner, or was unable to enable the hook.
*/
Result<Hook*> claimHook(std::shared_ptr<Hook> hook);
/**
* Disowns a hook which this mod owns, making this mod no longer its owner.
* If the hook has "auto enable" set, this will disable the hook.
* @returns Returns an error if this mod doesn't own the hook, or
* if disabling the hook failed.
*/
Result<> disownHook(Hook* hook);
/**
* Get all hooks owned by this Mod
* @returns Vector of hooks
*/
[[nodiscard]] std::vector<Hook*> getHooks() const;
/**
* Write a patch at an address
* @param address The address to write into
* @param data The data to write there
* @returns Successful result on success,
* errorful result with info on error
*/
Result<Patch*> patch(void* address, ByteVector const& data) {
auto patch = Patch::create(address, data);
GEODE_UNWRAP_INTO(auto ptr, this->claimPatch(std::move(patch)));
return Ok(ptr);
}
/**
* Claims an existing patch object, marking this mod as its owner.
* If the patch has "auto enable" set, this will enable the patch.
* @returns Returns a pointer to the patch, or an error if the
* patch already has an owner, or was unable to enable the patch.
*/
Result<Patch*> claimPatch(std::shared_ptr<Patch> patch);
/**
* Disowns a patch which this mod owns, making this mod no longer its owner.
* If the patch has "auto enable" set, this will disable the patch.
* @returns Returns an error if this mod doesn't own the patch, or
* if disabling the patch failed.
*/
Result<> disownPatch(Patch* patch);
/**
* Get all patches owned by this Mod
* @returns Vector of patches
*/
[[nodiscard]] std::vector<Patch*> getPatches() const;
/**
* Enable this mod
* @returns Successful result on success,
* errorful result with info on error
*/
Result<> enable();
/**
* Disable this mod
* @returns Successful result on success,
* errorful result with info on error
*/
Result<> disable();
/**
* Delete the mod's .geode package.
* @param deleteSaveData Whether should also delete the mod's save data
* @returns Successful result on success,
* errorful result with info on error
*/
Result<> uninstall(bool deleteSaveData = false);
bool isUninstalled() const;
ModRequestedAction getRequestedAction() const;
/**
* Check whether or not this Mod
* depends on another mod
*/
bool depends(std::string_view const id) const;
/**
* Check whether all the required
* dependencies for this mod have
* been loaded or not
* @returns True if the mod has unresolved
* dependencies, false if not.
*/
bool hasUnresolvedDependencies() const;
/**
* Check whether none of the
* incompatibilities with this mod are loaded
* @returns True if the mod has unresolved
* incompatibilities, false if not.
*/
bool hasUnresolvedIncompatibilities() const;
char const* expandSpriteName(char const* name);
/**
* Get info about the mod as JSON
* @note For IPC
*/
ModJson getRuntimeInfo() const;
bool isLoggingEnabled() const;
void setLoggingEnabled(bool enabled);
bool shouldLoad() const;
friend class ModImpl;
};
}
GEODE_HIDDEN inline char const* operator"" _spr(char const* str, size_t) {
return geode::Mod::get()->expandSpriteName(str);
}