mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-24 00:17:52 -05:00
merge geode-sdk/main
This commit is contained in:
commit
a776b167a9
7 changed files with 594 additions and 552 deletions
|
@ -312,30 +312,88 @@ namespace gd {
|
||||||
return std::vector<T>(m_start, m_finish);
|
return std::vector<T>(m_start, m_finish);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector(std::vector<T> const& input) {
|
vector() {
|
||||||
m_start = this->allocator().allocate(input.size());
|
m_start = nullptr;
|
||||||
m_finish = m_start + input.size();
|
m_finish = nullptr;
|
||||||
m_reserveEnd = m_start + input.size();
|
m_reserveEnd = nullptr;
|
||||||
|
|
||||||
std::copy(input.begin(), input.end(), m_start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vector(std::initializer_list<T> const& input) {
|
vector(std::vector<T> const& input) : vector() {
|
||||||
m_start = this->allocator().allocate(input.size());
|
if (input.size()) {
|
||||||
m_finish = m_start + input.size();
|
m_start = this->allocator().allocate(input.size());
|
||||||
m_reserveEnd = m_start + 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<T> 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<T>&& 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<T> 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<T>&& 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<T> 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() {
|
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_start = nullptr;
|
||||||
m_finish = m_start;
|
m_finish = nullptr;
|
||||||
m_reserveEnd = m_start;
|
m_reserveEnd = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
T& operator[](size_t index) {
|
T& operator[](size_t index) {
|
||||||
|
@ -380,10 +438,6 @@ namespace gd {
|
||||||
return m_finish;
|
return m_finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector(vector const& lol) : vector(std::vector<T>(lol)) {}
|
|
||||||
|
|
||||||
vector() : vector(std::vector<T>()) {}
|
|
||||||
|
|
||||||
~vector() {
|
~vector() {
|
||||||
for (auto i = m_start; i != m_finish; ++i) {
|
for (auto i = m_start; i != m_finish; ++i) {
|
||||||
delete i;
|
delete i;
|
||||||
|
@ -464,55 +518,63 @@ namespace gd {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// template <>
|
template <>
|
||||||
// class vector<bool> {
|
class vector<bool> {
|
||||||
// protected:
|
protected:
|
||||||
// _bit_iterator m_start;
|
_bit_iterator m_start;
|
||||||
// _bit_iterator m_end;
|
_bit_iterator m_end;
|
||||||
// uintptr_t* m_capacity_end;
|
uintptr_t* m_capacity_end;
|
||||||
|
|
||||||
// public:
|
public:
|
||||||
// vector(std::vector<bool> input) : m_start(0), m_end(0) {
|
auto allocator() const {
|
||||||
// auto realsize = input.size() / int(sizeof(uintptr_t));
|
return std::allocator<uintptr_t>();
|
||||||
// auto tmp = new uintptr_t[realsize];
|
}
|
||||||
|
|
||||||
// m_start = _bit_iterator(tmp);
|
vector() : m_start(nullptr), m_end(nullptr), m_capacity_end(nullptr) {}
|
||||||
// m_end = _bit_iterator(tmp + realsize, input.size() % sizeof(uintptr_t));
|
|
||||||
// m_capacity_end = tmp + realsize;
|
|
||||||
|
|
||||||
// auto itmp = m_start;
|
// vector(std::vector<bool> input) : vector() {
|
||||||
// for (auto i : input) {
|
// auto realsize = input.size() / int(sizeof(uintptr_t));
|
||||||
// *itmp = i;
|
// auto start = this->allocator().allocate(realsize);
|
||||||
// ++itmp;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// vector(vector<bool> const& lol) : vector(std::vector<bool>(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<bool>()) {}
|
// auto itmp = m_start;
|
||||||
|
// for (auto i : input) {
|
||||||
|
// *itmp = i;
|
||||||
|
// ++itmp;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// ~vector() {
|
// vector(vector<bool> const& input) : vector() {
|
||||||
// delete[] m_start.m_bitptr;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// operator std::vector<bool>() const {
|
// }
|
||||||
// std::vector<bool> out;
|
|
||||||
// for (auto i = m_start; i != m_end; ++i) {
|
|
||||||
// out.push_back(*i);
|
|
||||||
// }
|
|
||||||
// return out;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _bit_reference operator[](size_t index) {
|
// vector() : vector(std::vector<bool>()) {}
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// bool operator[](size_t index) const {
|
~vector() {
|
||||||
// return const_cast<vector&>(*this)[index];
|
delete[] m_start.m_bitptr;
|
||||||
// }
|
}
|
||||||
// };
|
|
||||||
|
operator std::vector<bool>() const {
|
||||||
|
std::vector<bool> 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<vector&>(*this)[index];
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#elif defined(GEODE_IS_IOS)
|
#elif defined(GEODE_IS_IOS)
|
||||||
|
@ -563,7 +625,7 @@ namespace gd {
|
||||||
operator std::vector<T>() {
|
operator std::vector<T>() {
|
||||||
return m_internal;
|
return m_internal;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clear() {
|
void clear() {
|
||||||
m_internal.clear();
|
m_internal.clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,9 +43,16 @@ namespace geode {
|
||||||
Hook(Hook const&) = delete;
|
Hook(Hook const&) = delete;
|
||||||
Hook operator=(Hook const&) = delete;
|
Hook operator=(Hook const&) = delete;
|
||||||
|
|
||||||
|
// Used by Mod
|
||||||
|
Result<> enable();
|
||||||
|
Result<> disable();
|
||||||
|
|
||||||
friend class Mod;
|
friend class Mod;
|
||||||
friend class Loader;
|
friend class Loader;
|
||||||
|
|
||||||
|
static std::vector<std::pair<Hook*, Mod*>> internalHooks;
|
||||||
|
static bool readyToHook;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* Get the address of the function hooked.
|
* Get the address of the function hooked.
|
||||||
|
|
|
@ -1,13 +1,3 @@
|
||||||
#include "InternalLoader.hpp"
|
|
||||||
|
|
||||||
#include "InternalMod.hpp"
|
|
||||||
#include "resources.hpp"
|
|
||||||
|
|
||||||
#include <Geode/loader/Loader.hpp>
|
|
||||||
#include <Geode/loader/IPC.hpp>
|
|
||||||
#include <Geode/loader/Log.hpp>
|
|
||||||
#include <Geode/utils/web.hpp>
|
|
||||||
#include <Geode/utils/file.hpp>
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include <hash.hpp>
|
#include <hash.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -16,6 +6,16 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <Geode/loader/Loader.hpp>
|
||||||
|
#include <Geode/loader/IPC.hpp>
|
||||||
|
#include <Geode/loader/Log.hpp>
|
||||||
|
#include <Geode/utils/web.hpp>
|
||||||
|
#include <Geode/utils/file.hpp>
|
||||||
|
|
||||||
|
#include "InternalLoader.hpp"
|
||||||
|
#include "InternalMod.hpp"
|
||||||
|
#include "resources.hpp"
|
||||||
|
|
||||||
InternalLoader::InternalLoader() : Loader() {}
|
InternalLoader::InternalLoader() : Loader() {}
|
||||||
|
|
||||||
InternalLoader::~InternalLoader() {
|
InternalLoader::~InternalLoader() {
|
||||||
|
@ -47,6 +47,29 @@ bool InternalLoader::setup() {
|
||||||
return true;
|
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) {
|
void InternalLoader::queueInGDThread(ScheduledFunction func) {
|
||||||
std::lock_guard<std::mutex> lock(m_gdThreadMutex);
|
std::lock_guard<std::mutex> lock(m_gdThreadMutex);
|
||||||
m_gdThreadQueue.push_back(func);
|
m_gdThreadQueue.push_back(func);
|
||||||
|
|
|
@ -28,6 +28,9 @@ protected:
|
||||||
bool m_platformConsoleOpen = false;
|
bool m_platformConsoleOpen = false;
|
||||||
std::unordered_set<std::string> m_shownInfoAlerts;
|
std::unordered_set<std::string> m_shownInfoAlerts;
|
||||||
|
|
||||||
|
std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
|
||||||
|
bool m_readyToHook;
|
||||||
|
|
||||||
void saveInfoAlerts(nlohmann::json& json);
|
void saveInfoAlerts(nlohmann::json& json);
|
||||||
void loadInfoAlerts(nlohmann::json& json);
|
void loadInfoAlerts(nlohmann::json& json);
|
||||||
|
|
||||||
|
@ -66,5 +69,8 @@ public:
|
||||||
|
|
||||||
bool verifyLoaderResources(IndexUpdateCallback callback);
|
bool verifyLoaderResources(IndexUpdateCallback callback);
|
||||||
|
|
||||||
|
bool isReadyToHook() const;
|
||||||
|
void addInternalHook(Hook* hook, Mod* mod);
|
||||||
|
|
||||||
friend int geodeEntry(void* platformData);
|
friend int geodeEntry(void* platformData);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,33 +12,18 @@
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
USE_GEODE_NAMESPACE();
|
||||||
|
|
||||||
struct hook_info {
|
Result<> Hook::enable() {
|
||||||
Hook* hook;
|
if (!m_enabled) {
|
||||||
Mod* mod;
|
auto res = std::invoke(m_addFunction, m_address);
|
||||||
};
|
|
||||||
|
|
||||||
// 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<hook_info>, internalHooks);
|
|
||||||
GEODE_STATIC_VAR(bool, readyToHook);
|
|
||||||
|
|
||||||
Result<> Mod::enableHook(Hook* hook) {
|
|
||||||
if (!hook->isEnabled()) {
|
|
||||||
auto res = std::invoke(hook->m_addFunction, hook->m_address);
|
|
||||||
if (res) {
|
if (res) {
|
||||||
log::debug("Enabling hook at function {}", hook->m_displayName);
|
log::debug("Enabling hook at function {}", m_displayName);
|
||||||
this->m_hooks.push_back(hook);
|
m_enabled = true;
|
||||||
hook->m_enabled = true;
|
m_handle = res.unwrap();
|
||||||
hook->m_handle = res.unwrap();
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return Err(
|
return Err(
|
||||||
"Unable to create hook at " +
|
"Unable to create hook at " + std::to_string(reinterpret_cast<uintptr_t>(m_address))
|
||||||
std::to_string(reinterpret_cast<uintptr_t>(hook->m_address))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Err("Hook already has a handle");
|
return Err("Hook already has a handle");
|
||||||
|
@ -46,56 +31,16 @@ Result<> Mod::enableHook(Hook* hook) {
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<> Mod::disableHook(Hook* hook) {
|
Result<> Hook::disable() {
|
||||||
if (hook->isEnabled()) {
|
if (m_enabled) {
|
||||||
if (geode::core::hook::remove(hook->m_handle)) {
|
if (!geode::core::hook::remove(m_handle)) return Err("Unable to remove hook");
|
||||||
log::debug("Disabling hook at function {}", hook->m_displayName);
|
|
||||||
hook->m_enabled = false;
|
log::debug("Disabling hook at function {}", m_displayName);
|
||||||
return Ok();
|
m_enabled = false;
|
||||||
}
|
|
||||||
return Err("Unable to remove hook");
|
|
||||||
}
|
}
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<> Mod::removeHook(Hook* hook) {
|
|
||||||
auto res = this->disableHook(hook);
|
|
||||||
if (res) {
|
|
||||||
ranges::remove(m_hooks, hook);
|
|
||||||
delete hook;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<Hook*> 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 {
|
nlohmann::json Hook::getRuntimeInfo() const {
|
||||||
auto json = nlohmann::json::object();
|
auto json = nlohmann::json::object();
|
||||||
json["address"] = reinterpret_cast<uintptr_t>(m_address);
|
json["address"] = reinterpret_cast<uintptr_t>(m_address);
|
||||||
|
|
|
@ -1,24 +1,17 @@
|
||||||
|
|
||||||
#include <Geode/cocos/support/zip_support/ZipUtils.h>
|
|
||||||
#include <Geode/loader/Hook.hpp>
|
#include <Geode/loader/Hook.hpp>
|
||||||
#include <Geode/loader/Loader.hpp>
|
|
||||||
#include <Geode/loader/Log.hpp>
|
|
||||||
#include <Geode/loader/Mod.hpp>
|
#include <Geode/loader/Mod.hpp>
|
||||||
#include <Geode/loader/Setting.hpp>
|
|
||||||
#include <Geode/utils/JsonValidation.hpp>
|
|
||||||
#include <Geode/utils/file.hpp>
|
#include <Geode/utils/file.hpp>
|
||||||
#include <Geode/utils/map.hpp>
|
#include <InternalLoader.hpp>
|
||||||
#include <Geode/utils/ranges.hpp>
|
|
||||||
#include <Geode/utils/string.hpp>
|
|
||||||
#include <InternalMod.hpp>
|
#include <InternalMod.hpp>
|
||||||
#include <about.hpp>
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
USE_GEODE_NAMESPACE();
|
||||||
|
|
||||||
Mod::Mod(ModInfo const& info) {
|
Mod::Mod(ModInfo const& info) {
|
||||||
m_info = info;
|
m_info = info;
|
||||||
m_saveDirPath = Loader::get()->getGeodeSaveDirectory() /
|
m_saveDirPath = Loader::get()->getGeodeSaveDirectory() / GEODE_MOD_DIRECTORY / info.m_id;
|
||||||
GEODE_MOD_DIRECTORY / info.m_id;
|
|
||||||
ghc::filesystem::create_directories(m_saveDirPath);
|
ghc::filesystem::create_directories(m_saveDirPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,338 +19,7 @@ Mod::~Mod() {
|
||||||
(void)this->unloadBinary();
|
(void)this->unloadBinary();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<> Mod::loadData() {
|
// Getters
|
||||||
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<Dependency> Mod::getUnresolvedDependencies() {
|
|
||||||
std::vector<Dependency> res;
|
|
||||||
for (auto const& dep : m_info.m_dependencies) {
|
|
||||||
if (dep.isUnresolved()) {
|
|
||||||
res.push_back(dep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
ghc::filesystem::path Mod::getSaveDir() const {
|
ghc::filesystem::path Mod::getSaveDir() const {
|
||||||
return m_saveDirPath;
|
return m_saveDirPath;
|
||||||
|
@ -399,14 +61,6 @@ ghc::filesystem::path Mod::getPackagePath() const {
|
||||||
return m_info.m_path;
|
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 {
|
VersionInfo Mod::getVersion() const {
|
||||||
return m_info.m_version;
|
return m_info.m_version;
|
||||||
}
|
}
|
||||||
|
@ -435,24 +89,6 @@ std::vector<Hook*> Mod::getHooks() const {
|
||||||
return m_hooks;
|
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<std::string, const char*> 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 {
|
bool Mod::hasSettings() const {
|
||||||
return m_info.m_settings.size();
|
return m_info.m_settings.size();
|
||||||
}
|
}
|
||||||
|
@ -461,24 +97,424 @@ decltype(ModInfo::m_settings) Mod::getSettings() const {
|
||||||
return m_info.m_settings;
|
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<Setting> Mod::getSetting(std::string const& key) const {
|
std::shared_ptr<Setting> Mod::getSetting(std::string const& key) const {
|
||||||
for (auto& sett : m_info.m_settings) {
|
for (auto& setting : m_info.m_settings) {
|
||||||
if (sett.first == key) {
|
if (setting.first == key) {
|
||||||
return sett.second;
|
return setting.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mod::hasSetting(std::string const& key) const {
|
bool Mod::hasSetting(std::string const& key) const {
|
||||||
for (auto& sett : m_info.m_settings) {
|
for (auto& setting : m_info.m_settings) {
|
||||||
if (sett.first == key) {
|
if (setting.first == key) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
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<Dependency> Mod::getUnresolvedDependencies() {
|
||||||
|
std::vector<Dependency> 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<Hook*> 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<uint8_t*>(reinterpret_cast<uintptr_t>(address) + i));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<Patch*> 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<std::string, char const*> 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 {
|
ModJson Mod::getRuntimeInfo() const {
|
||||||
auto json = m_info.toJSON();
|
auto json = m_info.toJSON();
|
||||||
|
|
||||||
|
|
|
@ -1,45 +1,8 @@
|
||||||
#include <Geode/loader/Hook.hpp>
|
#include <Geode/loader/Hook.hpp>
|
||||||
#include <Geode/loader/Loader.hpp>
|
|
||||||
#include <Geode/loader/Mod.hpp>
|
|
||||||
#include <Geode/utils/casts.hpp>
|
|
||||||
#include <Geode/utils/ranges.hpp>
|
|
||||||
#include <InternalLoader.hpp>
|
|
||||||
#include <lilac/include/geode/core/hook/hook.hpp>
|
#include <lilac/include/geode/core/hook/hook.hpp>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
USE_GEODE_NAMESPACE();
|
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<uint8_t*>(reinterpret_cast<uintptr_t>(address) + i));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<Patch*> 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() {
|
bool Patch::apply() {
|
||||||
return lilac::hook::write_memory(m_address, m_patch.data(), m_patch.size());
|
return lilac::hook::write_memory(m_address, m_patch.data(), m_patch.size());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue