mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-15 03:25:01 -05:00
index is looking good
- mod dependencies reworked to be much less bad and actually good now - mod dependencies finally check the version - support for dynamic versions for dependencies (<=vX.X.X, ==vX.X.X, >=vX.X.X) - index is now ready for implementing mod installation, then we're done with this massive sausage package
This commit is contained in:
parent
28b413d636
commit
7397193e57
16 changed files with 392 additions and 182 deletions
|
@ -113,6 +113,7 @@ elseif(EXISTS ${GEODE_PLATFORM_BIN_PATH})
|
|||
else()
|
||||
message(FATAL_ERROR
|
||||
"No valid loader binary to link to! Install prebuilts with `geode sdk install-prebuilts`, "
|
||||
"or build Geode from source and add `set(GEODE_LINK_NIGHTLY On)` to your CMakeLists.txt."
|
||||
"or build Geode from source and add `set(GEODE_LINK_NIGHTLY On)` to your CMakeLists.txt "
|
||||
"in the line before calling add_subdirectory for Geode."
|
||||
)
|
||||
endif()
|
||||
|
|
|
@ -97,12 +97,23 @@ namespace geode {
|
|||
std::vector<std::string> getSources() const;
|
||||
|
||||
std::vector<IndexItemHandle> getItems() const;
|
||||
bool isKnownItem(std::string const& id, std::optional<size_t> version) const;
|
||||
IndexItemHandle getItem(std::string const& id, std::optional<size_t> version) const;
|
||||
bool isKnownItem(std::string const& id, std::optional<VersionInfo> version) const;
|
||||
IndexItemHandle getItem(
|
||||
std::string const& id,
|
||||
std::optional<VersionInfo> version
|
||||
) const;
|
||||
IndexItemHandle getItem(
|
||||
std::string const& id,
|
||||
ComparableVersionInfo version
|
||||
) const;
|
||||
IndexItemHandle getItem(ModInfo const& info) const;
|
||||
IndexItemHandle getItem(Mod* mod) const;
|
||||
bool isUpdateAvailable(IndexItemHandle item) const;
|
||||
bool areUpdatesAvailable() const;
|
||||
void install(IndexItemHandle item);
|
||||
Result<std::vector<IndexItemHandle>> getInstallList(
|
||||
IndexItemHandle item
|
||||
) const;
|
||||
|
||||
bool hasTriedToUpdate() const;
|
||||
bool isUpToDate() const;
|
||||
|
|
|
@ -63,10 +63,6 @@ namespace geode {
|
|||
* Whether the mod binary is loaded or not
|
||||
*/
|
||||
bool m_binaryLoaded = false;
|
||||
/**
|
||||
* Whether the mod is loadable or not
|
||||
*/
|
||||
bool m_resolved = false;
|
||||
/**
|
||||
* Mod temp directory name
|
||||
*/
|
||||
|
@ -370,7 +366,7 @@ namespace geode {
|
|||
* @returns True if the mod has unresolved
|
||||
* dependencies, false if not.
|
||||
*/
|
||||
bool updateDependencyStates();
|
||||
Result<> updateDependencies();
|
||||
/**
|
||||
* Get a list of all the unresolved
|
||||
* dependencies this mod has
|
||||
|
|
|
@ -12,14 +12,12 @@ namespace geode {
|
|||
|
||||
using ModJson = nlohmann::ordered_json;
|
||||
|
||||
struct Dependency {
|
||||
std::string m_id;
|
||||
// todo: Dynamic versions (1.*.*)
|
||||
VersionInfo m_version { 1, 0, 0 };
|
||||
ModResolveState m_state = ModResolveState::Unloaded;
|
||||
bool m_required = false;
|
||||
Mod* m_mod = nullptr;
|
||||
bool isUnresolved() const;
|
||||
struct GEODE_DLL Dependency {
|
||||
std::string id;
|
||||
ComparableVersionInfo version;
|
||||
bool required = false;
|
||||
Mod* mod = nullptr;
|
||||
bool isResolved() const;
|
||||
};
|
||||
|
||||
struct IssuesInfo {
|
||||
|
|
|
@ -129,25 +129,6 @@ namespace geode {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents if a mod has been loaded &
|
||||
* its dependencies resolved
|
||||
*/
|
||||
enum class ModResolveState {
|
||||
// Mod has not been loaded at all
|
||||
Unloaded,
|
||||
// Mod has unresolved dependencies
|
||||
Unresolved,
|
||||
// Mod has all dependencies resolved,
|
||||
// but is not loaded yet
|
||||
Resolved,
|
||||
// Mod is loaded
|
||||
Loaded,
|
||||
// Mod is loaded, however it is also
|
||||
// disabled and therefore can't be used
|
||||
Disabled,
|
||||
};
|
||||
|
||||
constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
|
||||
|
||||
class Mod;
|
||||
|
|
|
@ -5,11 +5,17 @@
|
|||
#include "../external/json/json.hpp"
|
||||
|
||||
namespace geode {
|
||||
enum class VersionCompare {
|
||||
LessEq,
|
||||
Exact,
|
||||
MoreEq,
|
||||
};
|
||||
|
||||
/**
|
||||
* Class representing version information
|
||||
* @class VersionInfo
|
||||
*/
|
||||
class GEODE_DLL VersionInfo {
|
||||
class GEODE_DLL VersionInfo final {
|
||||
protected:
|
||||
int m_major = 1;
|
||||
int m_minor = 0;
|
||||
|
@ -23,6 +29,7 @@ namespace geode {
|
|||
m_patch = patch;
|
||||
}
|
||||
VersionInfo(std::string const& versionString);
|
||||
static bool validate(std::string const& string);
|
||||
|
||||
constexpr int getMajor() const {
|
||||
return m_major;
|
||||
|
@ -40,12 +47,40 @@ namespace geode {
|
|||
return std::tie(m_major, m_minor, m_patch) <=>
|
||||
std::tie(other.m_major, other.m_minor, other.m_patch);
|
||||
}
|
||||
|
||||
static bool validate(std::string const& string);
|
||||
constexpr bool operator==(VersionInfo const& other) const = default;
|
||||
|
||||
std::string toString() const;
|
||||
};
|
||||
void GEODE_DLL to_json(nlohmann::json& json, VersionInfo const& info);
|
||||
void GEODE_DLL from_json(nlohmann::json const& json, VersionInfo& info);
|
||||
GEODE_DLL std::ostream& operator<<(std::ostream& stream, VersionInfo const& version);
|
||||
|
||||
std::ostream& GEODE_DLL operator<<(std::ostream& stream, VersionInfo const& version);
|
||||
class GEODE_DLL ComparableVersionInfo final {
|
||||
protected:
|
||||
VersionInfo m_version;
|
||||
VersionCompare m_compare = VersionCompare::Exact;
|
||||
|
||||
public:
|
||||
constexpr ComparableVersionInfo() = default;
|
||||
constexpr ComparableVersionInfo(
|
||||
VersionInfo const& version,
|
||||
VersionCompare const& compare
|
||||
) : m_version(version), m_compare(compare) {}
|
||||
ComparableVersionInfo(std::string const& versionString);
|
||||
static bool validate(std::string const& string);
|
||||
|
||||
constexpr bool compare(VersionInfo const& version) const {
|
||||
switch (m_compare) {
|
||||
case VersionCompare::Exact: return m_version == version; break;
|
||||
case VersionCompare::LessEq: return m_version <= version; break;
|
||||
case VersionCompare::MoreEq: return m_version >= version; break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
};
|
||||
void GEODE_DLL to_json(nlohmann::json& json, ComparableVersionInfo const& info);
|
||||
void GEODE_DLL from_json(nlohmann::json const& json, ComparableVersionInfo& info);
|
||||
GEODE_DLL std::ostream& operator<<(std::ostream& stream, ComparableVersionInfo const& version);
|
||||
}
|
||||
|
|
|
@ -275,4 +275,24 @@ namespace geode::utils::ranges {
|
|||
}
|
||||
return member(*it);
|
||||
}
|
||||
|
||||
template <ValidConstContainer C>
|
||||
struct ConstReverseWrapper {
|
||||
C const& iter;
|
||||
};
|
||||
|
||||
template <ValidConstContainer C>
|
||||
auto begin(ConstReverseWrapper<C> const& c) {
|
||||
return std::rbegin(c.iter);
|
||||
}
|
||||
|
||||
template <ValidConstContainer C>
|
||||
auto end(ConstReverseWrapper<C> const& c) {
|
||||
return std::rend(c.iter);
|
||||
}
|
||||
|
||||
template <ValidConstContainer C>
|
||||
ConstReverseWrapper<C> reverse(C const& iter) {
|
||||
return { iter };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -454,28 +454,21 @@ std::vector<IndexItemHandle> Index::getItems() const {
|
|||
|
||||
bool Index::isKnownItem(
|
||||
std::string const& id,
|
||||
std::optional<size_t> version
|
||||
std::optional<VersionInfo> version
|
||||
) const {
|
||||
if (m_items.count(id)) {
|
||||
if (version) {
|
||||
return m_items.at(id).count(version.value());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return this->getItem(id, version).get();
|
||||
}
|
||||
|
||||
IndexItemHandle Index::getItem(
|
||||
std::string const& id,
|
||||
std::optional<size_t> version
|
||||
std::optional<VersionInfo> version
|
||||
) const {
|
||||
if (m_items.count(id)) {
|
||||
auto versions = m_items.at(id);
|
||||
if (version) {
|
||||
if (versions.count(version.value())) {
|
||||
return versions.at(version.value());
|
||||
auto major = version.value().getMajor();
|
||||
if (versions.count(major)) {
|
||||
return versions.at(major);
|
||||
}
|
||||
} else {
|
||||
if (versions.size()) {
|
||||
|
@ -486,12 +479,27 @@ IndexItemHandle Index::getItem(
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
IndexItemHandle Index::getItem(
|
||||
std::string const& id,
|
||||
ComparableVersionInfo version
|
||||
) const {
|
||||
if (m_items.count(id)) {
|
||||
// prefer most major version
|
||||
for (auto& [_, item] : ranges::reverse(m_items.at(id))) {
|
||||
if (version.compare(item->info.m_version)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IndexItemHandle Index::getItem(ModInfo const& info) const {
|
||||
return this->getItem(info.m_id, info.m_version.getMajor());
|
||||
return this->getItem(info.m_id, info.m_version);
|
||||
}
|
||||
|
||||
IndexItemHandle Index::getItem(Mod* mod) const {
|
||||
return this->getItem(mod->getID(), mod->getVersion().getMajor());
|
||||
return this->getItem(mod->getID(), mod->getVersion());
|
||||
}
|
||||
|
||||
bool Index::isUpdateAvailable(IndexItemHandle item) const {
|
||||
|
@ -511,3 +519,39 @@ bool Index::areUpdatesAvailable() const {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Result<std::vector<IndexItemHandle>> Index::getInstallList(
|
||||
IndexItemHandle item
|
||||
) const {
|
||||
std::vector<IndexItemHandle> list;
|
||||
for (auto& dep : item->info.m_dependencies) {
|
||||
if (!dep.isResolved()) {
|
||||
// check if this dep is available in the index
|
||||
if (auto depItem = this->getItem(dep.id, dep.version)) {
|
||||
// recursively add dependencies
|
||||
GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem));
|
||||
ranges::push(list, deps);
|
||||
}
|
||||
// otherwise user must get this dependency manually from somewhere
|
||||
// else
|
||||
else {
|
||||
return Err(
|
||||
"Dependency {} version {} not found in the index! Likely "
|
||||
"reason is that the version of the dependency this mod "
|
||||
"depends on is not available. Please let the the developer "
|
||||
"({}) of the mod know!",
|
||||
dep.id, dep.version.toString(), item->info.m_developer
|
||||
);
|
||||
}
|
||||
}
|
||||
// if the dep is resolved, then all its dependencies must be installed
|
||||
// already in order for that to have happened
|
||||
}
|
||||
// add this item to the end of the list
|
||||
list.push_back(item);
|
||||
return Ok(list);
|
||||
}
|
||||
|
||||
void Index::install(IndexItemHandle item) {
|
||||
// todo
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) {
|
|||
"should-load-" + info.m_id, true
|
||||
);
|
||||
// this loads the mod if its dependencies are resolved
|
||||
mod->updateDependencyStates();
|
||||
GEODE_UNWRAP(mod->updateDependencies());
|
||||
|
||||
// add mod resources
|
||||
this->queueInGDThread([this, mod]() {
|
||||
|
@ -288,7 +288,7 @@ std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
|
|||
|
||||
void Loader::updateAllDependencies() {
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
mod->updateDependencyStates();
|
||||
(void)mod->updateDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -192,71 +192,82 @@ bool Mod::hasSetting(std::string const& key) const {
|
|||
// Loading, Toggling, Installing
|
||||
|
||||
Result<> Mod::loadBinary() {
|
||||
if (!m_binaryLoaded) {
|
||||
GEODE_UNWRAP(this->createTempDir());
|
||||
|
||||
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());
|
||||
if (m_binaryLoaded) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
GEODE_UNWRAP(this->createTempDir());
|
||||
|
||||
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();
|
||||
if (!m_binaryLoaded) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
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();
|
||||
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())
|
||||
if (!patch->apply()) {
|
||||
return Err("Unable to apply patch at " + std::to_string(patch->getAddress()));
|
||||
}
|
||||
}
|
||||
|
||||
ModStateEvent(this, ModEventType::Enabled).post();
|
||||
|
@ -266,21 +277,25 @@ Result<> Mod::enable() {
|
|||
}
|
||||
|
||||
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;
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
|
@ -309,73 +324,59 @@ bool Mod::isUninstalled() const {
|
|||
|
||||
// Dependencies
|
||||
|
||||
bool Dependency::isUnresolved() const {
|
||||
return m_required &&
|
||||
(m_state == ModResolveState::Unloaded || m_state == ModResolveState::Unresolved ||
|
||||
m_state == ModResolveState::Disabled);
|
||||
}
|
||||
|
||||
bool Mod::updateDependencyStates() {
|
||||
Result<> Mod::updateDependencies() {
|
||||
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;
|
||||
// set the dependency's loaded mod if such exists
|
||||
if (!dep.mod) {
|
||||
dep.mod = Loader::get()->getLoadedMod(dep.id);
|
||||
// verify loaded dependency version
|
||||
if (dep.mod && !dep.version.compare(dep.mod->getVersion())) {
|
||||
dep.mod = nullptr;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the dependency is loaded
|
||||
if (dep.mod) {
|
||||
// update the dependency recursively
|
||||
GEODE_UNWRAP(dep.mod->updateDependencies());
|
||||
|
||||
// enable mod if it's resolved & enabled
|
||||
if (!dep.mod->hasUnresolvedDependencies()) {
|
||||
if (dep.mod->isEnabled()) {
|
||||
GEODE_UNWRAP(dep.mod->loadBinary()
|
||||
.expect("Unable to load dependency: {error}")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
dep.m_state = ModResolveState::Unloaded;
|
||||
}
|
||||
if (dep.isUnresolved()) {
|
||||
m_resolved = false;
|
||||
(void)this->unloadBinary();
|
||||
// check if the dependency is resolved now
|
||||
if (!dep.isResolved()) {
|
||||
GEODE_UNWRAP(this->unloadBinary()
|
||||
.expect("Unable to unload mod: {error}")
|
||||
);
|
||||
hasUnresolved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasUnresolved && !m_resolved) {
|
||||
// load if there weren't any unresolved dependencies
|
||||
if (!hasUnresolved) {
|
||||
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());
|
||||
}
|
||||
GEODE_UNWRAP(this->loadBinary());
|
||||
}
|
||||
else {
|
||||
log::debug("Resolved {}, however not loading it as it is disabled", m_info.m_id);
|
||||
}
|
||||
}
|
||||
return hasUnresolved;
|
||||
return Ok();
|
||||
}
|
||||
|
||||
bool Mod::hasUnresolvedDependencies() const {
|
||||
for (auto const& dep : m_info.m_dependencies) {
|
||||
if (dep.isUnresolved()) return true;
|
||||
if (!dep.isResolved()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -383,15 +384,20 @@ bool Mod::hasUnresolvedDependencies() const {
|
|||
std::vector<Dependency> Mod::getUnresolvedDependencies() {
|
||||
std::vector<Dependency> unresolved;
|
||||
for (auto const& dep : m_info.m_dependencies) {
|
||||
if (dep.isUnresolved()) unresolved.push_back(dep);
|
||||
if (!dep.isResolved()) {
|
||||
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;
|
||||
});
|
||||
return utils::ranges::contains(
|
||||
m_info.m_dependencies,
|
||||
[id](Dependency const& t) {
|
||||
return t.id == id;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Hooks
|
||||
|
|
|
@ -6,6 +6,17 @@
|
|||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
bool Dependency::isResolved() const {
|
||||
return
|
||||
!this->required ||
|
||||
(
|
||||
this->mod &&
|
||||
this->mod->isLoaded() &&
|
||||
this->mod->isEnabled() &&
|
||||
this->version.compare(this->mod->getVersion())
|
||||
);
|
||||
}
|
||||
|
||||
static std::string sanitizeDetailsData(std::string const& str) {
|
||||
// delete CRLF
|
||||
return utils::string::replace(str, "\r", "");
|
||||
|
@ -37,7 +48,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
using nlohmann::detail::value_t;
|
||||
|
||||
root.needs("id").validate(&ModInfo::validateID).into(info.m_id);
|
||||
root.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(info.m_version);
|
||||
root.needs("version").validate(&VersionInfo::validate).into(info.m_version);
|
||||
root.needs("name").into(info.m_name);
|
||||
root.needs("developer").into(info.m_developer);
|
||||
root.has("description").into(info.m_description);
|
||||
|
@ -50,9 +61,11 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
auto obj = dep.obj();
|
||||
|
||||
auto depobj = Dependency {};
|
||||
obj.needs("id").validate(&ModInfo::validateID).into(depobj.m_id);
|
||||
obj.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(depobj.m_version);
|
||||
obj.has("required").into(depobj.m_required);
|
||||
obj.needs("id").validate(&ModInfo::validateID).into(depobj.id);
|
||||
obj.needs("version")
|
||||
.validate(&ComparableVersionInfo::validate)
|
||||
.into(depobj.version);
|
||||
obj.has("required").into(depobj.required);
|
||||
obj.checkUnknownKeys();
|
||||
|
||||
info.m_dependencies.push_back(depobj);
|
||||
|
|
|
@ -49,9 +49,7 @@ void geode::openInfoPopup(Mod* mod) {
|
|||
}
|
||||
|
||||
void geode::openIndexPopup(Mod* mod) {
|
||||
if (auto item = Index::get()->getItem(
|
||||
mod->getID(), mod->getVersion().getMajor()
|
||||
)) {
|
||||
if (auto item = Index::get()->getItem(mod)) {
|
||||
IndexItemInfoPopup::create(item, nullptr)->show();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -514,7 +514,7 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
|||
switch (layer->getTag()) {
|
||||
case TAG_CONFIRM_UNINSTALL: {
|
||||
if (btn2) {
|
||||
this->uninstall();
|
||||
this->doUninstall();
|
||||
}
|
||||
} break;
|
||||
|
||||
|
@ -537,7 +537,7 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
|||
}
|
||||
}
|
||||
|
||||
void LocalModInfoPopup::uninstall() {
|
||||
void LocalModInfoPopup::doUninstall() {
|
||||
auto res = m_mod->uninstall();
|
||||
if (!res) {
|
||||
return FLAlertLayer::create(
|
||||
|
@ -584,7 +584,7 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
|
|||
m_installBtnSpr->setScale(.6f);
|
||||
|
||||
m_installBtn = CCMenuItemSpriteExtra::create(
|
||||
m_installBtnSpr, this, nullptr
|
||||
m_installBtnSpr, this, menu_selector(IndexItemInfoPopup::onInstall)
|
||||
);
|
||||
m_installBtn->setPosition(-143.0f, 75.f);
|
||||
m_buttonMenu->addChild(m_installBtn);
|
||||
|
@ -600,6 +600,48 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void IndexItemInfoPopup::onInstall(CCObject*) {
|
||||
auto list = Index::get()->getInstallList(m_item);
|
||||
if (!list) {
|
||||
return FLAlertLayer::create(
|
||||
"Unable to Install",
|
||||
list.unwrapErr(),
|
||||
"OK"
|
||||
)->show();
|
||||
}
|
||||
FLAlertLayer::create(
|
||||
this,
|
||||
"Confirm Install",
|
||||
fmt::format(
|
||||
"The following mods will be installed:\n {}",
|
||||
// le nest
|
||||
ranges::join(
|
||||
ranges::map<std::vector<std::string>>(
|
||||
list.unwrap(),
|
||||
[](IndexItemHandle handle) {
|
||||
return fmt::format(
|
||||
" - <cr>{}</c> (<cy>{}</c>)",
|
||||
handle->info.m_name, handle->info.m_id
|
||||
);
|
||||
}
|
||||
),
|
||||
"\n "
|
||||
)
|
||||
),
|
||||
"Cancel", "OK"
|
||||
)->show();
|
||||
}
|
||||
|
||||
void IndexItemInfoPopup::doInstall() {
|
||||
Index::get()->install(m_item);
|
||||
}
|
||||
|
||||
void IndexItemInfoPopup::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
|
||||
if (btn2) {
|
||||
this->doInstall();
|
||||
}
|
||||
}
|
||||
|
||||
CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) {
|
||||
return geode::createIndexItemLogo(m_item, size);
|
||||
}
|
||||
|
|
|
@ -66,7 +66,7 @@ protected:
|
|||
void onUninstall(CCObject*);
|
||||
void onOpenConfigDir(CCObject*);
|
||||
void onAdvancedSettings(CCObject*);
|
||||
void uninstall();
|
||||
void doUninstall();
|
||||
|
||||
void FLAlert_Clicked(FLAlertLayer*, bool) override;
|
||||
|
||||
|
@ -77,12 +77,17 @@ public:
|
|||
static LocalModInfoPopup* create(Mod* mod, ModListLayer* list);
|
||||
};
|
||||
|
||||
class IndexItemInfoPopup : public ModInfoPopup {
|
||||
class IndexItemInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol {
|
||||
protected:
|
||||
IndexItemHandle m_item;
|
||||
|
||||
bool init(IndexItemHandle item, ModListLayer* list);
|
||||
|
||||
void onInstall(CCObject*);
|
||||
void doInstall();
|
||||
|
||||
void FLAlert_Clicked(FLAlertLayer*, bool) override;
|
||||
|
||||
CCNode* createLogo(CCSize const& size) override;
|
||||
ModInfo getModInfo() const override;
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ void ModCell::onUnresolvedInfo(CCObject*) {
|
|||
for (auto const& dep : m_mod->getUnresolvedDependencies()) {
|
||||
info += fmt::format(
|
||||
"<cg>{}</c> (<cy>{}</c>), ",
|
||||
dep.m_id, dep.m_version.toString()
|
||||
dep.id, dep.version.toString()
|
||||
);
|
||||
}
|
||||
info.pop_back();
|
||||
|
|
|
@ -13,6 +13,8 @@ USE_GEODE_NAMESPACE();
|
|||
#define GEODE_SSCANF sscanf
|
||||
#endif
|
||||
|
||||
// VersionInfo
|
||||
|
||||
bool VersionInfo::validate(std::string const& string) {
|
||||
int buf0, buf1, buf2;
|
||||
if (GEODE_SSCANF(string.c_str(), "v%d.%d.%d", &buf0, &buf1, &buf2)) return true;
|
||||
|
@ -34,7 +36,65 @@ void geode::to_json(nlohmann::json& json, VersionInfo const& info) {
|
|||
json = info.toString();
|
||||
}
|
||||
|
||||
std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version) {
|
||||
stream << version.toString();
|
||||
return stream;
|
||||
void geode::from_json(nlohmann::json const& json, VersionInfo& info) {
|
||||
info = VersionInfo(json.template get<std::string>());
|
||||
}
|
||||
|
||||
std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version) {
|
||||
return stream << version.toString();
|
||||
}
|
||||
|
||||
// ComparableVersionInfo
|
||||
|
||||
ComparableVersionInfo::ComparableVersionInfo(std::string const& rawStr) {
|
||||
auto version = rawStr;
|
||||
if (version.starts_with("<=")) {
|
||||
m_compare = VersionCompare::LessEq;
|
||||
version.erase(0, 2);
|
||||
}
|
||||
else if (version.starts_with(">=")) {
|
||||
m_compare = VersionCompare::MoreEq;
|
||||
version.erase(0, 2);
|
||||
}
|
||||
else if (version.starts_with("==")) {
|
||||
m_compare = VersionCompare::Exact;
|
||||
version.erase(0, 2);
|
||||
}
|
||||
m_version = VersionInfo(version);
|
||||
}
|
||||
|
||||
bool ComparableVersionInfo::validate(std::string const& rawStr) {
|
||||
auto version = rawStr;
|
||||
// remove prefix
|
||||
if (
|
||||
version.starts_with("<=") ||
|
||||
version.starts_with(">=") ||
|
||||
version.starts_with("==")
|
||||
) {
|
||||
version.erase(0, 2);
|
||||
}
|
||||
// otherwise there's no prefix or it's invalid
|
||||
return VersionInfo::validate(version);
|
||||
}
|
||||
|
||||
std::string ComparableVersionInfo::toString() const {
|
||||
std::string prefix = "";
|
||||
switch (m_compare) {
|
||||
case VersionCompare::Exact: prefix = "=="; break;
|
||||
case VersionCompare::LessEq: prefix = "<="; break;
|
||||
case VersionCompare::MoreEq: prefix = ">="; break;
|
||||
}
|
||||
return prefix + m_version.toString();
|
||||
}
|
||||
|
||||
void geode::to_json(nlohmann::json& json, ComparableVersionInfo const& info) {
|
||||
json = info.toString();
|
||||
}
|
||||
|
||||
void geode::from_json(nlohmann::json const& json, ComparableVersionInfo& info) {
|
||||
info = ComparableVersionInfo(json.template get<std::string>());
|
||||
}
|
||||
|
||||
std::ostream& geode::operator<<(std::ostream& stream, ComparableVersionInfo const& version) {
|
||||
return stream << version.toString();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue