diff --git a/loader/include/Geode/c++stl/gnustl.hpp b/loader/include/Geode/c++stl/gnustl.hpp index f6d67acb..8f1c7bd6 100644 --- a/loader/include/Geode/c++stl/gnustl.hpp +++ b/loader/include/Geode/c++stl/gnustl.hpp @@ -312,30 +312,88 @@ namespace gd { return std::vector(m_start, m_finish); } - vector(std::vector const& input) { - m_start = this->allocator().allocate(input.size()); - m_finish = m_start + input.size(); - m_reserveEnd = m_start + input.size(); - - std::copy(input.begin(), input.end(), m_start); + vector() { + m_start = nullptr; + m_finish = nullptr; + m_reserveEnd = nullptr; } - vector(std::initializer_list const& input) { - m_start = this->allocator().allocate(input.size()); - m_finish = m_start + input.size(); - m_reserveEnd = m_start + input.size(); + vector(std::vector const& input) : vector() { + if (input.size()) { + m_start = this->allocator().allocate(input.size()); + m_finish = m_start + input.size(); + m_reserveEnd = m_start + input.size(); - std::copy(input.begin(), input.end(), m_start); + std::copy(input.begin(), input.end(), m_start); + } } - + + vector(gd::vector const& input) : vector() { + if (input.size()) { + m_start = this->allocator().allocate(input.size()); + m_finish = m_start + input.size(); + m_reserveEnd = m_start + input.size(); + + std::copy(input.begin(), input.end(), m_start); + } + } + + vector(gd::vector&& input) : vector() { + m_start = input.m_start; + m_finish = input.m_finish; + m_reserveEnd = input.m_reserveEnd; + + input.m_start = nullptr; + input.m_finish = nullptr; + input.m_reserveEnd = nullptr; + } + + vector& operator=(gd::vector const& input) { + this->clear(); + + if (input.size()) { + m_start = this->allocator().allocate(input.size()); + m_finish = m_start + input.size(); + m_reserveEnd = m_start + input.size(); + + std::copy(input.begin(), input.end(), m_start); + } + + return *this; + } + + vector& operator=(gd::vector&& input) { + m_start = input.m_start; + m_finish = input.m_finish; + m_reserveEnd = input.m_reserveEnd; + + input.m_start = nullptr; + input.m_finish = nullptr; + input.m_reserveEnd = nullptr; + + return *this; + } + + vector(std::initializer_list const& input) : vector() { + if (input.size()) { + m_start = this->allocator().allocate(input.size()); + m_finish = m_start + input.size(); + m_reserveEnd = m_start + input.size(); + + std::copy(input.begin(), input.end(), m_start); + } + } + void clear() { - std::destroy(m_start, m_finish); + if (m_start) { + std::destroy(m_start, m_finish); - this->allocator().deallocate(m_start, this->size()); + this->allocator().deallocate(m_start, this->size()); + } - m_start = this->allocator().allocate(16); - m_finish = m_start; - m_reserveEnd = m_start; + m_start = nullptr; + m_finish = nullptr; + m_reserveEnd = nullptr; } T& operator[](size_t index) { @@ -380,10 +438,6 @@ namespace gd { return m_finish; } - vector(vector const& lol) : vector(std::vector(lol)) {} - - vector() : vector(std::vector()) {} - ~vector() { for (auto i = m_start; i != m_finish; ++i) { delete i; @@ -464,55 +518,63 @@ namespace gd { } }; - // template <> - // class vector { - // protected: - // _bit_iterator m_start; - // _bit_iterator m_end; - // uintptr_t* m_capacity_end; + template <> + class vector { + protected: + _bit_iterator m_start; + _bit_iterator m_end; + uintptr_t* m_capacity_end; - // public: - // vector(std::vector input) : m_start(0), m_end(0) { - // auto realsize = input.size() / int(sizeof(uintptr_t)); - // auto tmp = new uintptr_t[realsize]; + public: + auto allocator() const { + return std::allocator(); + } - // m_start = _bit_iterator(tmp); - // m_end = _bit_iterator(tmp + realsize, input.size() % sizeof(uintptr_t)); - // m_capacity_end = tmp + realsize; + vector() : m_start(nullptr), m_end(nullptr), m_capacity_end(nullptr) {} - // auto itmp = m_start; - // for (auto i : input) { - // *itmp = i; - // ++itmp; - // } - // } + // vector(std::vector input) : vector() { + // auto realsize = input.size() / int(sizeof(uintptr_t)); + // auto start = this->allocator().allocate(realsize); - // vector(vector const& lol) : vector(std::vector(lol)) {} + // m_start = _bit_iterator(start); + // m_end = _bit_iterator(start + realsize, input.size() % sizeof(uintptr_t)); + // m_capacity_end = start + realsize; - // vector() : vector(std::vector()) {} + // auto itmp = m_start; + // for (auto i : input) { + // *itmp = i; + // ++itmp; + // } + // } - // ~vector() { - // delete[] m_start.m_bitptr; - // } + // vector(vector const& input) : vector() { - // operator std::vector() const { - // std::vector out; - // for (auto i = m_start; i != m_end; ++i) { - // out.push_back(*i); - // } - // return out; - // } + // } - // _bit_reference operator[](size_t index) { - // const auto real_index = index / sizeof(uintptr_t); - // const auto offset = index % sizeof(uintptr_t); - // return _bit_reference(&m_start.m_bitptr[real_index], 1UL << offset); - // } + // vector() : vector(std::vector()) {} - // bool operator[](size_t index) const { - // return const_cast(*this)[index]; - // } - // }; + ~vector() { + delete[] m_start.m_bitptr; + } + + operator std::vector() const { + std::vector out; + for (auto i = m_start; i != m_end; ++i) { + out.push_back(*i); + } + return out; + } + + _bit_reference operator[](size_t index) { + auto const real_index = index / sizeof(uintptr_t); + auto const offset = index % sizeof(uintptr_t); + return _bit_reference(&m_start.m_bitptr[real_index], 1UL << offset); + } + + bool operator[](size_t index) const { + return const_cast(*this)[index]; + } + }; }; #elif defined(GEODE_IS_IOS) @@ -563,7 +625,7 @@ namespace gd { operator std::vector() { return m_internal; } - + void clear() { m_internal.clear(); } diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp index f8c57ee1..a3b2515f 100644 --- a/loader/include/Geode/loader/Hook.hpp +++ b/loader/include/Geode/loader/Hook.hpp @@ -43,9 +43,16 @@ namespace geode { Hook(Hook const&) = delete; Hook operator=(Hook const&) = delete; + // Used by Mod + Result<> enable(); + Result<> disable(); + friend class Mod; friend class Loader; + static std::vector> internalHooks; + static bool readyToHook; + public: /** * Get the address of the function hooked. diff --git a/loader/src/internal/InternalLoader.cpp b/loader/src/internal/InternalLoader.cpp index e60d56ba..73795926 100644 --- a/loader/src/internal/InternalLoader.cpp +++ b/loader/src/internal/InternalLoader.cpp @@ -1,13 +1,3 @@ -#include "InternalLoader.hpp" - -#include "InternalMod.hpp" -#include "resources.hpp" - -#include -#include -#include -#include -#include #include #include #include @@ -16,6 +6,16 @@ #include #include +#include +#include +#include +#include +#include + +#include "InternalLoader.hpp" +#include "InternalMod.hpp" +#include "resources.hpp" + InternalLoader::InternalLoader() : Loader() {} InternalLoader::~InternalLoader() { @@ -47,6 +47,29 @@ bool InternalLoader::setup() { return true; } +bool InternalLoader::isReadyToHook() const { + return m_readyToHook; +} + +void InternalLoader::addInternalHook(Hook* hook, Mod* mod) { + m_internalHooks.push_back({hook, mod}); +} + +bool InternalLoader::loadHooks() { + m_readyToHook = true; + auto thereWereErrors = false; + for (auto const& hook : m_internalHooks) { + auto res = hook.second->addHook(hook.first); + if (!res) { + log::log(Severity::Error, hook.second, "{}", res.unwrapErr()); + thereWereErrors = true; + } + } + // free up memory + m_internalHooks.clear(); + return !thereWereErrors; +} + void InternalLoader::queueInGDThread(ScheduledFunction func) { std::lock_guard lock(m_gdThreadMutex); m_gdThreadQueue.push_back(func); diff --git a/loader/src/internal/InternalLoader.hpp b/loader/src/internal/InternalLoader.hpp index ef332b8a..ea6d1ee7 100644 --- a/loader/src/internal/InternalLoader.hpp +++ b/loader/src/internal/InternalLoader.hpp @@ -28,6 +28,9 @@ protected: bool m_platformConsoleOpen = false; std::unordered_set m_shownInfoAlerts; + std::vector> m_internalHooks; + bool m_readyToHook; + void saveInfoAlerts(nlohmann::json& json); void loadInfoAlerts(nlohmann::json& json); @@ -66,5 +69,8 @@ public: bool verifyLoaderResources(IndexUpdateCallback callback); + bool isReadyToHook() const; + void addInternalHook(Hook* hook, Mod* mod); + friend int geodeEntry(void* platformData); }; diff --git a/loader/src/loader/Hook.cpp b/loader/src/loader/Hook.cpp index 3e61e80d..79d666f9 100644 --- a/loader/src/loader/Hook.cpp +++ b/loader/src/loader/Hook.cpp @@ -12,33 +12,18 @@ USE_GEODE_NAMESPACE(); -struct hook_info { - Hook* hook; - Mod* mod; -}; - -// for some reason this doesn't work as -// a normal static global. the vector just -// gets cleared for no reason somewhere -// between `addHook` and `loadHooks` - -GEODE_STATIC_VAR(std::vector, internalHooks); -GEODE_STATIC_VAR(bool, readyToHook); - -Result<> Mod::enableHook(Hook* hook) { - if (!hook->isEnabled()) { - auto res = std::invoke(hook->m_addFunction, hook->m_address); +Result<> Hook::enable() { + if (!m_enabled) { + auto res = std::invoke(m_addFunction, m_address); if (res) { - log::debug("Enabling hook at function {}", hook->m_displayName); - this->m_hooks.push_back(hook); - hook->m_enabled = true; - hook->m_handle = res.unwrap(); + log::debug("Enabling hook at function {}", m_displayName); + m_enabled = true; + m_handle = res.unwrap(); return Ok(); } else { return Err( - "Unable to create hook at " + - std::to_string(reinterpret_cast(hook->m_address)) + "Unable to create hook at " + std::to_string(reinterpret_cast(m_address)) ); } return Err("Hook already has a handle"); @@ -46,56 +31,16 @@ Result<> Mod::enableHook(Hook* hook) { return Ok(); } -Result<> Mod::disableHook(Hook* hook) { - if (hook->isEnabled()) { - if (geode::core::hook::remove(hook->m_handle)) { - log::debug("Disabling hook at function {}", hook->m_displayName); - hook->m_enabled = false; - return Ok(); - } - return Err("Unable to remove hook"); +Result<> Hook::disable() { + if (m_enabled) { + if (!geode::core::hook::remove(m_handle)) return Err("Unable to remove hook"); + + log::debug("Disabling hook at function {}", m_displayName); + m_enabled = false; } return Ok(); } -Result<> Mod::removeHook(Hook* hook) { - auto res = this->disableHook(hook); - if (res) { - ranges::remove(m_hooks, hook); - delete hook; - } - return res; -} - -Result Mod::addHook(Hook* hook) { - if (readyToHook()) { - auto res = this->enableHook(hook); - if (!res) { - delete hook; - return Err("Can't create hook"); - } - } - else { - internalHooks().push_back({ hook, this }); - } - return Ok(hook); -} - -bool InternalLoader::loadHooks() { - readyToHook() = true; - auto thereWereErrors = false; - for (auto const& hook : internalHooks()) { - auto res = hook.mod->addHook(hook.hook); - if (!res) { - log::log(Severity::Error, hook.mod, "{}", res.unwrapErr()); - thereWereErrors = true; - } - } - // free up memory - internalHooks().clear(); - return !thereWereErrors; -} - nlohmann::json Hook::getRuntimeInfo() const { auto json = nlohmann::json::object(); json["address"] = reinterpret_cast(m_address); diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index 7260b726..ee18f1b3 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -1,24 +1,17 @@ - -#include #include -#include -#include #include -#include -#include #include -#include -#include -#include +#include #include -#include +#include +#include +#include USE_GEODE_NAMESPACE(); Mod::Mod(ModInfo const& info) { m_info = info; - m_saveDirPath = Loader::get()->getGeodeSaveDirectory() / - GEODE_MOD_DIRECTORY / info.m_id; + m_saveDirPath = Loader::get()->getGeodeSaveDirectory() / GEODE_MOD_DIRECTORY / info.m_id; ghc::filesystem::create_directories(m_saveDirPath); } @@ -26,338 +19,7 @@ Mod::~Mod() { (void)this->unloadBinary(); } -Result<> Mod::loadData() { - ModStateEvent(this, ModEventType::DataLoaded).post(); - - // settings - // Check if settings exist - auto settPath = m_saveDirPath / "settings.json"; - if (ghc::filesystem::exists(settPath)) { - GEODE_UNWRAP_INTO(auto settData, utils::file::readString(settPath)); - try { - // parse settings.json - auto data = nlohmann::json::parse(settData); - JsonChecker checker(data); - auto root = checker.root("[settings.json]"); - - for (auto& [key, value] : root.items()) { - // check if this is a known setting - if (auto sett = this->getSetting(key)) { - // load its value - if (!sett->load(value.json())) { - return Err("Unable to load value for setting \"" + key + "\""); - } - } - else { - log::log( - Severity::Warning, this, - "Encountered unknown setting \"{}\" while loading " - "settings", - key - ); - } - } - } - catch (std::exception& e) { - return Err(std::string("Unable to parse settings: ") + e.what()); - } - } - - // Saved values - auto savedPath = m_saveDirPath / "saved.json"; - if (ghc::filesystem::exists(savedPath)) { - GEODE_UNWRAP_INTO(auto data, utils::file::readString(savedPath)); - try { - m_saved = nlohmann::json::parse(data); - } catch(std::exception& e) { - return Err(std::string("Unable to parse saved values: ") + e.what()); - } - } - - return Ok(); -} - -Result<> Mod::saveData() { - ModStateEvent(this, ModEventType::DataSaved).post(); - - // settings - auto settPath = m_saveDirPath / "settings.json"; - auto json = nlohmann::json::object(); - for (auto& [key, sett] : m_info.m_settings) { - if (!sett->save(json[key])) { - return Err("Unable to save setting \"" + key + "\""); - } - } - auto sw = utils::file::writeString(settPath, json.dump(4)); - if (!sw) { - return sw; - } - - // Saved values - auto sdw = utils::file::writeString( - m_saveDirPath / "saved.json", - m_saved.dump(4) - ); - if (!sdw) { - return sdw; - } - - return Ok(); -} - -Result<> Mod::createTempDir() { - // Check if temp dir already exists - if (m_tempDirName.string().size()) { - return Ok(); - } - - // Create geode/temp - auto tempDir = Loader::get()->getGeodeDirectory() / GEODE_TEMP_DIRECTORY; - if (!file::createDirectoryAll(tempDir)) { - return Err("Unable to create Geode temp directory"); - } - - // Create geode/temp/mod.id - auto tempPath = tempDir / m_info.m_id; - if (!file::createDirectoryAll(tempPath)) { - return Err("Unable to create mod temp directory"); - } - - // Unzip .geode file into temp dir - GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_info.m_path)); - if (!unzip.hasEntry(m_info.m_binaryName)) { - return Err(fmt::format( - "Unable to find platform binary under the name \"{}\"", - m_info.m_binaryName - )); - } - GEODE_UNWRAP(unzip.extractAllTo(tempPath)); - - // Mark temp dir creation as succesful - m_tempDirName = tempPath; - - return Ok(); -} - -Result<> Mod::loadBinary() { - if (m_binaryLoaded) { - return Ok(); - } - - GEODE_UNWRAP(this->createTempDir().expect("Unable to create temp directory")); - - if (this->hasUnresolvedDependencies()) { - return Err("Mod has unresolved dependencies"); - } - GEODE_UNWRAP(this->loadPlatformBinary()); - - // Call implicit entry point to place hooks etc. - m_implicitLoadFunc(this); - - m_binaryLoaded = true; - ModStateEvent(this, ModEventType::Loaded).post(); - - auto loadRes = this->loadData(); - if (!loadRes) { - log::warn("Unable to load data for \"{}\": {}", m_info.m_id, loadRes.unwrapErr()); - } - Loader::get()->updateAllDependencies(); - - GEODE_UNWRAP(this->enable()); - - return Ok(); -} - -Result<> Mod::unloadBinary() { - if (!m_binaryLoaded) { - return Ok(); - } - if (!m_info.m_supportsUnloading) { - return Err("Mod does not support unloading"); - } - - auto saveRes = this->saveData(); - if (!saveRes) { - return saveRes; - } - - GEODE_UNWRAP(this->disable()); - ModStateEvent(this, ModEventType::Unloaded).post(); - - // Disabling unhooks and unpatches already - for (auto const& hook : m_hooks) { - delete hook; - } - m_hooks.clear(); - - for (auto const& patch : m_patches) { - delete patch; - } - m_patches.clear(); - - auto res = this->unloadPlatformBinary(); - if (!res) { - return res; - } - m_binaryLoaded = false; - - Loader::get()->updateAllDependencies(); - - return Ok(); -} - -Result<> Mod::enable() { - if (!m_binaryLoaded) { - return this->loadBinary(); - } - - for (auto const& hook : m_hooks) { - auto d = this->enableHook(hook); - if (!d) return d; - } - - for (auto const& patch : m_patches) { - if (!patch->apply()) { - return Err("Unable to apply patch at " + std::to_string(patch->getAddress())); - } - } - - ModStateEvent(this, ModEventType::Enabled).post(); - - m_enabled = true; - - return Ok(); -} - -Result<> Mod::disable() { - if (!m_enabled) { - return Ok(); - } - if (!m_info.m_supportsDisabling) { - return Err("Mod does not support disabling"); - } - - ModStateEvent(this, ModEventType::Disabled).post(); - - for (auto const& hook : m_hooks) { - auto d = this->disableHook(hook); - if (!d) return d; - } - - for (auto const& patch : m_patches) { - if (!patch->restore()) { - return Err("Unable to restore patch at " + std::to_string(patch->getAddress())); - } - } - - m_enabled = false; - - return Ok(); -} - -Result<> Mod::uninstall() { - if (m_info.m_supportsDisabling) { - GEODE_UNWRAP(this->disable()); - if (m_info.m_supportsUnloading) { - GEODE_UNWRAP(this->unloadBinary()); - } - } - if (!ghc::filesystem::remove(m_info.m_path)) { - return Err( - "Unable to delete mod's .geode file! " - "This might be due to insufficient permissions - " - "try running GD as administrator." - ); - } - return Ok(); -} - -bool Mod::isUninstalled() const { - return this != InternalMod::get() && !ghc::filesystem::exists(m_info.m_path); -} - -bool Dependency::isUnresolved() const { - return m_required && - (m_state == ModResolveState::Unloaded || m_state == ModResolveState::Unresolved || - m_state == ModResolveState::Disabled); -} - -bool Mod::updateDependencyStates() { - bool hasUnresolved = false; - for (auto& dep : m_info.m_dependencies) { - if (!dep.m_mod) { - dep.m_mod = Loader::get()->getLoadedMod(dep.m_id); - } - if (dep.m_mod) { - dep.m_mod->updateDependencyStates(); - - if (dep.m_mod->hasUnresolvedDependencies()) { - dep.m_state = ModResolveState::Unresolved; - } - else { - if (!dep.m_mod->m_resolved) { - dep.m_mod->m_resolved = true; - dep.m_state = ModResolveState::Resolved; - auto r = dep.m_mod->loadBinary(); - if (!r) { - dep.m_state = ModResolveState::Unloaded; - log::log(Severity::Error, dep.m_mod, "{}", r.unwrapErr()); - } - } - else { - if (dep.m_mod->isEnabled()) { - dep.m_state = ModResolveState::Loaded; - } - else { - dep.m_state = ModResolveState::Disabled; - } - } - } - } - else { - dep.m_state = ModResolveState::Unloaded; - } - if (dep.isUnresolved()) { - m_resolved = false; - (void)this->unloadBinary(); - hasUnresolved = true; - } - } - if (!hasUnresolved && !m_resolved) { - log::debug("All dependencies for {} found", m_info.m_id); - m_resolved = true; - if (m_enabled) { - log::debug("Resolved & loading {}", m_info.m_id); - auto r = this->loadBinary(); - if (!r) { - log::error("{} Error loading: {}", this, r.unwrapErr()); - } - } - else { - log::debug("Resolved {}, however not loading it as it is disabled", m_info.m_id); - } - } - return hasUnresolved; -} - -bool Mod::hasUnresolvedDependencies() const { - for (auto const& dep : m_info.m_dependencies) { - if (dep.isUnresolved()) { - return true; - } - } - return false; -} - -std::vector Mod::getUnresolvedDependencies() { - std::vector res; - for (auto const& dep : m_info.m_dependencies) { - if (dep.isUnresolved()) { - res.push_back(dep); - } - } - return res; -} +// Getters ghc::filesystem::path Mod::getSaveDir() const { return m_saveDirPath; @@ -399,14 +61,6 @@ ghc::filesystem::path Mod::getPackagePath() const { return m_info.m_path; } -ghc::filesystem::path Mod::getConfigDir(bool create) const { - auto dir = Loader::get()->getGeodeDirectory() / GEODE_CONFIG_DIRECTORY / m_info.m_id; - if (create && !ghc::filesystem::exists(dir)) { - ghc::filesystem::create_directories(dir); - } - return dir; -} - VersionInfo Mod::getVersion() const { return m_info.m_version; } @@ -435,24 +89,6 @@ std::vector Mod::getHooks() const { return m_hooks; } -bool Mod::depends(std::string const& id) const { - return utils::ranges::contains(m_info.m_dependencies, [id](Dependency const& t) { - return t.m_id == id; - }); -} - -const char* Mod::expandSpriteName(const char* name) { - static std::unordered_map expanded = {}; - if (expanded.count(name)) { - return expanded[name]; - } - auto exp = new char[strlen(name) + 2 + m_info.m_id.size()]; - auto exps = m_info.m_id + "/" + name; - memcpy(exp, exps.c_str(), exps.size() + 1); - expanded[name] = exp; - return exp; -} - bool Mod::hasSettings() const { return m_info.m_settings.size(); } @@ -461,24 +97,424 @@ decltype(ModInfo::m_settings) Mod::getSettings() const { return m_info.m_settings; } +// Settings and saved values + +Result<> Mod::loadData() { + ModStateEvent(this, ModEventType::DataLoaded).post(); + + // Settings + // Check if settings exist + auto settingPath = m_saveDirPath / "settings.json"; + if (ghc::filesystem::exists(settingPath)) { + GEODE_UNWRAP_INTO(auto settingData, utils::file::readString(settingPath)); + try { + // parse settings.json + auto json = nlohmann::json::parse(settingData); + JsonChecker checker(json); + auto root = checker.root("[settings.json]"); + + for (auto& [key, value] : root.items()) { + // check if this is a known setting + if (auto setting = this->getSetting(key)) { + // load its value + if (!setting->load(value.json())) + return Err("Unable to load value for setting \"" + key + "\""); + } + else { + log::log( + Severity::Warning, this, + "Encountered unknown setting \"{}\" while loading " + "settings", + key + ); + } + } + } + catch (std::exception& e) { + return Err(std::string("Unable to parse settings: ") + e.what()); + } + } + + // Saved values + auto savedPath = m_saveDirPath / "saved.json"; + if (ghc::filesystem::exists(savedPath)) { + GEODE_UNWRAP_INTO(auto data, utils::file::readString(savedPath)); + try { + m_saved = nlohmann::json::parse(data); + } + catch (std::exception& e) { + return Err(std::string("Unable to parse saved values: ") + e.what()); + } + } + + return Ok(); +} + +Result<> Mod::saveData() { + ModStateEvent(this, ModEventType::DataSaved).post(); + + // Settings + auto json = nlohmann::json::object(); + for (auto& [key, value] : m_info.m_settings) { + if (!value->save(json[key])) return Err("Unable to save setting \"" + key + "\""); + } + + GEODE_UNWRAP(utils::file::writeString(m_saveDirPath / "settings.json", json.dump(4))); + + // Saved values + GEODE_UNWRAP(utils::file::writeString(m_saveDirPath / "saved.json", m_saved.dump(4))); + + return Ok(); +} + std::shared_ptr Mod::getSetting(std::string const& key) const { - for (auto& sett : m_info.m_settings) { - if (sett.first == key) { - return sett.second; + for (auto& setting : m_info.m_settings) { + if (setting.first == key) { + return setting.second; } } return nullptr; } bool Mod::hasSetting(std::string const& key) const { - for (auto& sett : m_info.m_settings) { - if (sett.first == key) { + for (auto& setting : m_info.m_settings) { + if (setting.first == key) { return true; } } return false; } +// Loading, Toggling, Installing + +Result<> Mod::loadBinary() { + if (!m_binaryLoaded) { + GEODE_UNWRAP(this->createTempDir().expect("Unable to create temp directory")); + + if (this->hasUnresolvedDependencies()) return Err("Mod has unresolved dependencies"); + + GEODE_UNWRAP(this->loadPlatformBinary()); + m_binaryLoaded = true; + + // Call implicit entry point to place hooks etc. + m_implicitLoadFunc(this); + + ModStateEvent(this, ModEventType::Loaded).post(); + + auto loadRes = this->loadData(); + if (!loadRes) { + log::warn("Unable to load data for \"{}\": {}", m_info.m_id, loadRes.unwrapErr()); + } + + Loader::get()->updateAllDependencies(); + + GEODE_UNWRAP(this->enable()); + } + + return Ok(); +} + +Result<> Mod::unloadBinary() { + if (m_binaryLoaded) { + if (!m_info.m_supportsUnloading) return Err("Mod does not support unloading"); + + GEODE_UNWRAP(this->saveData()); + + GEODE_UNWRAP(this->disable()); + ModStateEvent(this, ModEventType::Unloaded).post(); + + // Disabling unhooks and unpatches already + for (auto const& hook : m_hooks) { + delete hook; + } + m_hooks.clear(); + + for (auto const& patch : m_patches) { + delete patch; + } + m_patches.clear(); + + GEODE_UNWRAP(this->unloadPlatformBinary()); + m_binaryLoaded = false; + + Loader::get()->updateAllDependencies(); + } + + return Ok(); +} + +Result<> Mod::enable() { + if (!m_binaryLoaded) return this->loadBinary(); + + for (auto const& hook : m_hooks) { + GEODE_UNWRAP(this->enableHook(hook)); + } + + for (auto const& patch : m_patches) { + if (!patch->apply()) + return Err("Unable to apply patch at " + std::to_string(patch->getAddress())); + } + + ModStateEvent(this, ModEventType::Enabled).post(); + m_enabled = true; + + return Ok(); +} + +Result<> Mod::disable() { + if (m_enabled) { + if (!m_info.m_supportsDisabling) return Err("Mod does not support disabling"); + + ModStateEvent(this, ModEventType::Disabled).post(); + + for (auto const& hook : m_hooks) { + GEODE_UNWRAP(this->disableHook(hook)); + } + for (auto const& patch : m_patches) { + if (!patch->restore()) + return Err("Unable to restore patch at " + std::to_string(patch->getAddress())); + } + + m_enabled = false; + } + + return Ok(); +} + +Result<> Mod::uninstall() { + if (m_info.m_supportsDisabling) { + GEODE_UNWRAP(this->disable()); + + if (m_info.m_supportsUnloading) GEODE_UNWRAP(this->unloadBinary()); + } + + if (!ghc::filesystem::remove(m_info.m_path)) { + return Err( + "Unable to delete mod's .geode file! " + "This might be due to insufficient permissions - " + "try running GD as administrator." + ); + } + + return Ok(); +} + +bool Mod::isUninstalled() const { + return this != InternalMod::get() && !ghc::filesystem::exists(m_info.m_path); +} + +// Dependencies + +bool Dependency::isUnresolved() const { + return m_required && + (m_state == ModResolveState::Unloaded || m_state == ModResolveState::Unresolved || + m_state == ModResolveState::Disabled); +} + +bool Mod::updateDependencyStates() { + bool hasUnresolved = false; + for (auto& dep : m_info.m_dependencies) { + if (!dep.m_mod) dep.m_mod = Loader::get()->getLoadedMod(dep.m_id); + + if (dep.m_mod) { + dep.m_mod->updateDependencyStates(); + + if (dep.m_mod->hasUnresolvedDependencies()) { + dep.m_state = ModResolveState::Unresolved; + } + else { + if (!dep.m_mod->m_resolved) { + dep.m_mod->m_resolved = true; + dep.m_state = ModResolveState::Resolved; + auto r = dep.m_mod->loadBinary(); + if (!r) { + dep.m_state = ModResolveState::Unloaded; + log::log(Severity::Error, dep.m_mod, "{}", r.unwrapErr()); + } + } + else { + if (dep.m_mod->isEnabled()) { + dep.m_state = ModResolveState::Loaded; + } + else { + dep.m_state = ModResolveState::Disabled; + } + } + } + } + else { + dep.m_state = ModResolveState::Unloaded; + } + if (dep.isUnresolved()) { + m_resolved = false; + (void)this->unloadBinary(); + hasUnresolved = true; + } + } + + if (!hasUnresolved && !m_resolved) { + log::debug("All dependencies for {} found", m_info.m_id); + m_resolved = true; + if (m_enabled) { + log::debug("Resolved & loading {}", m_info.m_id); + auto r = this->loadBinary(); + if (!r) { + log::error("{} Error loading: {}", this, r.unwrapErr()); + } + } + else { + log::debug("Resolved {}, however not loading it as it is disabled", m_info.m_id); + } + } + return hasUnresolved; +} + +bool Mod::hasUnresolvedDependencies() const { + for (auto const& dep : m_info.m_dependencies) { + if (dep.isUnresolved()) return true; + } + return false; +} + +std::vector Mod::getUnresolvedDependencies() { + std::vector unresolved; + for (auto const& dep : m_info.m_dependencies) { + if (dep.isUnresolved()) unresolved.push_back(dep); + } + return unresolved; +} + +bool Mod::depends(std::string const& id) const { + return utils::ranges::contains(m_info.m_dependencies, [id](Dependency const& t) { + return t.m_id == id; + }); +} + +// Hooks + +Result<> Mod::enableHook(Hook* hook) { + auto res = hook->enable(); + if (res) m_hooks.push_back(hook); + + return res; +} + +Result<> Mod::disableHook(Hook* hook) { + return hook->disable(); +} + +Result Mod::addHook(Hook* hook) { + if (InternalLoader::get()->isReadyToHook()) { + auto res = this->enableHook(hook); + if (!res) { + delete hook; + return Err("Can't create hook"); + } + } + else { + InternalLoader::get()->addInternalHook(hook, this); + } + + return Ok(hook); +} + +Result<> Mod::removeHook(Hook* hook) { + auto res = this->disableHook(hook); + if (res) { + ranges::remove(m_hooks, hook); + delete hook; + } + return res; +} + +// Patches + +byte_array readMemory(void* address, size_t amount) { + byte_array ret; + for (size_t i = 0; i < amount; i++) { + ret.push_back(*reinterpret_cast(reinterpret_cast(address) + i)); + } + return ret; +} + +Result Mod::patch(void* address, byte_array data) { + auto p = new Patch; + p->m_address = address; + p->m_original = readMemory(address, data.size()); + p->m_owner = this; + p->m_patch = data; + if (!p->apply()) { + delete p; + return Err("Unable to enable patch at " + std::to_string(p->getAddress())); + } + m_patches.push_back(p); + return Ok(p); +} + +Result<> Mod::unpatch(Patch* patch) { + if (patch->restore()) { + ranges::remove(m_patches, patch); + delete patch; + return Ok(); + } + return Err("Unable to restore patch!"); +} + +// Misc. + +Result<> Mod::createTempDir() { + // Check if temp dir already exists + if (m_tempDirName.string().empty()) { + // Create geode/temp + auto tempDir = Loader::get()->getGeodeDirectory() / GEODE_TEMP_DIRECTORY; + if (!file::createDirectoryAll(tempDir).isOk()) { + return Err("Unable to create Geode temp directory"); + } + + // Create geode/temp/mod.id + auto tempPath = tempDir / m_info.m_id; + if (!file::createDirectoryAll(tempPath).isOk()) { + return Err("Unable to create mod temp directory"); + } + + // Unzip .geode file into temp dir + GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_info.m_path)); + if (!unzip.hasEntry(m_info.m_binaryName)) { + return Err(fmt::format( + "Unable to find platform binary under the name \"{}\"", m_info.m_binaryName + )); + } + GEODE_UNWRAP(unzip.extractAllTo(tempPath)); + + // Mark temp dir creation as succesful + m_tempDirName = tempPath; + } + + return Ok(); +} + +ghc::filesystem::path Mod::getConfigDir(bool create) const { + auto dir = Loader::get()->getGeodeDirectory() / GEODE_CONFIG_DIRECTORY / m_info.m_id; + if (create && !ghc::filesystem::exists(dir)) { + ghc::filesystem::create_directories(dir); + } + + return dir; +} + +char const* Mod::expandSpriteName(char const* name) { + static std::unordered_map expanded = {}; + if (expanded.count(name)) return expanded[name]; + + auto exp = new char[strlen(name) + 2 + m_info.m_id.size()]; + auto exps = m_info.m_id + "/" + name; + memcpy(exp, exps.c_str(), exps.size() + 1); + + expanded[name] = exp; + + return exp; +} + ModJson Mod::getRuntimeInfo() const { auto json = m_info.toJSON(); diff --git a/loader/src/loader/Patch.cpp b/loader/src/loader/Patch.cpp index e5175db8..e5b971f3 100644 --- a/loader/src/loader/Patch.cpp +++ b/loader/src/loader/Patch.cpp @@ -1,45 +1,8 @@ #include -#include -#include -#include -#include -#include #include -#include USE_GEODE_NAMESPACE(); -byte_array readMemory(void* address, size_t amount) { - byte_array ret; - for (size_t i = 0; i < amount; i++) { - ret.push_back(*reinterpret_cast(reinterpret_cast(address) + i)); - } - return ret; -} - -Result Mod::patch(void* address, byte_array data) { - auto p = new Patch; - p->m_address = address; - p->m_original = readMemory(address, data.size()); - p->m_owner = this; - p->m_patch = data; - if (!p->apply()) { - delete p; - return Err("Unable to enable patch at " + std::to_string(p->getAddress())); - } - m_patches.push_back(p); - return Ok(p); -} - -Result<> Mod::unpatch(Patch* patch) { - if (patch->restore()) { - ranges::remove(m_patches, patch); - delete patch; - return Ok(); - } - return Err("Unable to restore patch!"); -} - bool Patch::apply() { return lilac::hook::write_memory(m_address, m_patch.data(), m_patch.size()); }