mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-27 01:45:35 -05:00
commit
fbce0eaaa2
28 changed files with 785 additions and 429 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,5 +1,17 @@
|
||||||
# Geode Changelog
|
# Geode Changelog
|
||||||
|
|
||||||
|
## v1.3.0
|
||||||
|
* Completely remove runtime enabling & disabling of mods (d817749)
|
||||||
|
* Patches auto enabling can be disabled (69821f3)
|
||||||
|
* Move ModEventType::Loaded to after hooks & patches are enabled (23c3095)
|
||||||
|
* Update index to be able to store multiple versions of the same mod (5572f9c)
|
||||||
|
* Implement UI for selection of downloading specific mod versions (5d15eb0)
|
||||||
|
* Change install & uninstall popups to reflect the new changes (d40f467)
|
||||||
|
* Keep the scroll when enabling, installing etc. a mod (b3d444a)
|
||||||
|
* Update MacOS crashlog to include base and offset (7816c43)
|
||||||
|
* Add user agent to AsyncWebRequest (c256207)
|
||||||
|
* Add post and custom requests to AsyncWebRequest (c256207)
|
||||||
|
|
||||||
## v1.2.1
|
## v1.2.1
|
||||||
* Mods now target macOS 10.13 instead of 10.14 (7cc1cd4)
|
* Mods now target macOS 10.13 instead of 10.14 (7cc1cd4)
|
||||||
* Fix CustomizeObjectLayer ids moving around when multiple objects are selected (9ee0994, 87749d4)
|
* Fix CustomizeObjectLayer ids moving around when multiple objects are selected (9ee0994, 87749d4)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
1.2.1
|
1.3.0
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace geode {
|
||||||
class Mod;
|
class Mod;
|
||||||
class Loader;
|
class Loader;
|
||||||
|
|
||||||
class GEODE_DLL Hook {
|
class GEODE_DLL Hook final {
|
||||||
private:
|
private:
|
||||||
class Impl;
|
class Impl;
|
||||||
std::shared_ptr<Impl> m_impl;
|
std::shared_ptr<Impl> m_impl;
|
||||||
|
@ -143,20 +143,23 @@ namespace geode {
|
||||||
void setAutoEnable(bool autoEnable);
|
void setAutoEnable(bool autoEnable);
|
||||||
};
|
};
|
||||||
|
|
||||||
class GEODE_DLL Patch {
|
class GEODE_DLL Patch final {
|
||||||
|
// Change to private in 2.0.0
|
||||||
protected:
|
protected:
|
||||||
Mod* m_owner;
|
Mod* m_owner;
|
||||||
void* m_address;
|
void* m_address;
|
||||||
ByteVector m_original;
|
ByteVector m_original;
|
||||||
ByteVector m_patch;
|
ByteVector m_patch;
|
||||||
bool m_applied;
|
bool m_applied;
|
||||||
|
bool m_autoEnable;
|
||||||
|
|
||||||
// Only allow friend classes to create
|
// Only allow friend classes to create
|
||||||
// patches. Whatever method created the
|
// patches. Whatever method created the
|
||||||
// patches should take care of populating
|
// patches should take care of populating
|
||||||
// m_owner, m_address, m_original and
|
// m_owner, m_address, m_original and
|
||||||
// m_patch.
|
// m_patch.
|
||||||
Patch() : m_applied(false) {}
|
Patch();
|
||||||
|
~Patch();
|
||||||
|
|
||||||
// no copying
|
// no copying
|
||||||
Patch(Patch const&) = delete;
|
Patch(Patch const&) = delete;
|
||||||
|
@ -170,17 +173,13 @@ namespace geode {
|
||||||
* Get the address of the patch.
|
* Get the address of the patch.
|
||||||
* @returns Address
|
* @returns Address
|
||||||
*/
|
*/
|
||||||
uintptr_t getAddress() const {
|
uintptr_t getAddress() const;
|
||||||
return reinterpret_cast<uintptr_t>(m_address);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get whether the patch is applied or not.
|
* Get whether the patch is applied or not.
|
||||||
* @returns True if applied, false if not.
|
* @returns True if applied, false if not.
|
||||||
*/
|
*/
|
||||||
bool isApplied() const {
|
bool isApplied() const;
|
||||||
return m_applied;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool apply();
|
bool apply();
|
||||||
bool restore();
|
bool restore();
|
||||||
|
@ -189,14 +188,24 @@ namespace geode {
|
||||||
* Get the owner of this patch.
|
* Get the owner of this patch.
|
||||||
* @returns Pointer to the owner's Mod handle.
|
* @returns Pointer to the owner's Mod handle.
|
||||||
*/
|
*/
|
||||||
Mod* getOwner() const {
|
Mod* getOwner() const;
|
||||||
return m_owner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get info about the patch as JSON
|
* Get info about the patch as JSON
|
||||||
* @note For IPC
|
* @note For IPC
|
||||||
*/
|
*/
|
||||||
json::Value getRuntimeInfo() const;
|
json::Value getRuntimeInfo() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether the patch should be auto enabled or not.
|
||||||
|
* @returns Auto enable
|
||||||
|
*/
|
||||||
|
bool getAutoEnable() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set whether the patch should be auto enabled or not.
|
||||||
|
* @param autoEnable Auto enable
|
||||||
|
*/
|
||||||
|
void setAutoEnable(bool autoEnable);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,14 @@ namespace geode {
|
||||||
std::unique_ptr<Impl> m_impl;
|
std::unique_ptr<Impl> m_impl;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* Returns the path that contains all the versions
|
||||||
|
*/
|
||||||
|
ghc::filesystem::path getRootPath() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the path to the specific version
|
||||||
|
*/
|
||||||
ghc::filesystem::path getPath() const;
|
ghc::filesystem::path getPath() const;
|
||||||
[[deprecated("use getMetadata instead")]] ModInfo getModInfo() const;
|
[[deprecated("use getMetadata instead")]] ModInfo getModInfo() const;
|
||||||
ModMetadata getMetadata() const;
|
ModMetadata getMetadata() const;
|
||||||
|
@ -124,10 +132,14 @@ namespace geode {
|
||||||
void setAvailablePlatforms(std::unordered_set<PlatformID> const& value);
|
void setAvailablePlatforms(std::unordered_set<PlatformID> const& value);
|
||||||
void setIsFeatured(bool const& value);
|
void setIsFeatured(bool const& value);
|
||||||
void setTags(std::unordered_set<std::string> const& value);
|
void setTags(std::unordered_set<std::string> const& value);
|
||||||
|
void setIsInstalled(bool const& value);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
IndexItem();
|
IndexItem();
|
||||||
~IndexItem();
|
~IndexItem();
|
||||||
|
|
||||||
|
friend class ModMetadata;
|
||||||
|
friend class Index;
|
||||||
};
|
};
|
||||||
using IndexItemHandle = std::shared_ptr<IndexItem>;
|
using IndexItemHandle = std::shared_ptr<IndexItem>;
|
||||||
|
|
||||||
|
@ -168,12 +180,22 @@ namespace geode {
|
||||||
* Get all featured index items
|
* Get all featured index items
|
||||||
*/
|
*/
|
||||||
std::vector<IndexItemHandle> getFeaturedItems() const;
|
std::vector<IndexItemHandle> getFeaturedItems() const;
|
||||||
|
/**
|
||||||
|
* Get all latest index items
|
||||||
|
*/
|
||||||
|
std::vector<IndexItemHandle> getLatestItems() const;
|
||||||
/**
|
/**
|
||||||
* Get all index items by a developer
|
* Get all index items by a developer
|
||||||
*/
|
*/
|
||||||
std::vector<IndexItemHandle> getItemsByDeveloper(
|
std::vector<IndexItemHandle> getItemsByDeveloper(
|
||||||
std::string const& name
|
std::string const& name
|
||||||
) const;
|
) const;
|
||||||
|
/**
|
||||||
|
* Get all index items for a specific mod
|
||||||
|
*/
|
||||||
|
std::vector<IndexItemHandle> getItemsByModID(
|
||||||
|
std::string const& modID
|
||||||
|
) const;
|
||||||
/**
|
/**
|
||||||
* Check if an item with this ID is found on the index, and optionally
|
* Check if an item with this ID is found on the index, and optionally
|
||||||
* provide the version sought after
|
* provide the version sought after
|
||||||
|
|
|
@ -33,6 +33,13 @@ namespace geode {
|
||||||
~HandleToSaved();
|
~HandleToSaved();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ModRequestedAction {
|
||||||
|
None,
|
||||||
|
Enable,
|
||||||
|
Disable,
|
||||||
|
Uninstall,
|
||||||
|
};
|
||||||
|
|
||||||
GEODE_HIDDEN Mod* takeNextLoaderMod();
|
GEODE_HIDDEN Mod* takeNextLoaderMod();
|
||||||
|
|
||||||
class ModImpl;
|
class ModImpl;
|
||||||
|
@ -337,6 +344,8 @@ namespace geode {
|
||||||
Result<> uninstall();
|
Result<> uninstall();
|
||||||
bool isUninstalled() const;
|
bool isUninstalled() const;
|
||||||
|
|
||||||
|
ModRequestedAction getRequestedAction() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether or not this Mod
|
* Check whether or not this Mod
|
||||||
* depends on another mod
|
* depends on another mod
|
||||||
|
|
|
@ -240,6 +240,7 @@ namespace geode {
|
||||||
friend class Loader;
|
friend class Loader;
|
||||||
|
|
||||||
friend class ModMetadataImpl;
|
friend class ModMetadataImpl;
|
||||||
|
friend class IndexItem;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,16 @@ namespace geode::utils::web {
|
||||||
template <class T>
|
template <class T>
|
||||||
using DataConverter = Result<T> (*)(ByteVector const&);
|
using DataConverter = Result<T> (*)(ByteVector const&);
|
||||||
|
|
||||||
|
// Hack until 2.0.0 to store extra members in AsyncWebRequest
|
||||||
|
struct AsyncWebRequestData {
|
||||||
|
std::string m_userAgent;
|
||||||
|
std::string m_customRequest;
|
||||||
|
bool m_isPostRequest = false;
|
||||||
|
std::string m_postFields;
|
||||||
|
bool m_isJsonRequest = false;
|
||||||
|
bool m_sent = false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An asynchronous, thread-safe web request. Downloads data from the
|
* An asynchronous, thread-safe web request. Downloads data from the
|
||||||
* internet without slowing the main thread. All callbacks are run in the
|
* internet without slowing the main thread. All callbacks are run in the
|
||||||
|
@ -111,16 +121,22 @@ namespace geode::utils::web {
|
||||||
*/
|
*/
|
||||||
class GEODE_DLL AsyncWebRequest {
|
class GEODE_DLL AsyncWebRequest {
|
||||||
private:
|
private:
|
||||||
|
// i want to cry whose idea was to not make this pimpl
|
||||||
|
// For 2.0.0: make this pimpl
|
||||||
|
|
||||||
std::optional<std::string> m_joinID;
|
std::optional<std::string> m_joinID;
|
||||||
std::string m_url;
|
std::string m_url;
|
||||||
AsyncThen m_then = nullptr;
|
AsyncThen m_then = nullptr;
|
||||||
AsyncExpectCode m_expect = nullptr;
|
AsyncExpectCode m_expect = nullptr;
|
||||||
AsyncProgress m_progress = nullptr;
|
AsyncProgress m_progress = nullptr;
|
||||||
AsyncCancelled m_cancelled = nullptr;
|
AsyncCancelled m_cancelled = nullptr;
|
||||||
bool m_sent = false;
|
mutable AsyncWebRequestData* m_extra = nullptr;
|
||||||
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
|
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
|
||||||
std::vector<std::string> m_httpHeaders;
|
std::vector<std::string> m_httpHeaders;
|
||||||
|
|
||||||
|
AsyncWebRequestData& extra();
|
||||||
|
AsyncWebRequestData const& extra() const;
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
friend class AsyncWebResult;
|
friend class AsyncWebResult;
|
||||||
friend class SentAsyncWebRequest;
|
friend class SentAsyncWebRequest;
|
||||||
|
@ -151,6 +167,29 @@ namespace geode::utils::web {
|
||||||
* Can be called more than once.
|
* Can be called more than once.
|
||||||
*/
|
*/
|
||||||
AsyncWebRequest& header(std::string const& header);
|
AsyncWebRequest& header(std::string const& header);
|
||||||
|
/**
|
||||||
|
* In order to specify an user agent to the request, give it here.
|
||||||
|
*/
|
||||||
|
AsyncWebRequest& userAgent(std::string const& userAgent);
|
||||||
|
/**
|
||||||
|
* Specify that the request is a POST request.
|
||||||
|
*/
|
||||||
|
AsyncWebRequest& postRequest();
|
||||||
|
/**
|
||||||
|
* Specify that the request is a custom request like PUT and DELETE.
|
||||||
|
*/
|
||||||
|
AsyncWebRequest& customRequest(std::string const& request);
|
||||||
|
/**
|
||||||
|
* Specify the post fields to send with the request. Only valid if
|
||||||
|
* `postRequest` or `customRequest` was called before.
|
||||||
|
*/
|
||||||
|
AsyncWebRequest& postFields(std::string const& fields);
|
||||||
|
/**
|
||||||
|
* Specify the post fields to send with the request. Only valid if
|
||||||
|
* `postRequest` or `customRequest` was called before. Additionally
|
||||||
|
* sets the content type to application/json.
|
||||||
|
*/
|
||||||
|
AsyncWebRequest& postFields(json::Value const& fields);
|
||||||
/**
|
/**
|
||||||
* URL to fetch from the internet asynchronously
|
* URL to fetch from the internet asynchronously
|
||||||
* @param url URL of the data to download. Redirects will be
|
* @param url URL of the data to download. Redirects will be
|
||||||
|
|
|
@ -12,6 +12,7 @@ using namespace geode::prelude;
|
||||||
Hook::Hook(std::shared_ptr<Impl>&& impl) : m_impl(std::move(impl)) {}
|
Hook::Hook(std::shared_ptr<Impl>&& impl) : m_impl(std::move(impl)) {}
|
||||||
Hook::~Hook() {}
|
Hook::~Hook() {}
|
||||||
|
|
||||||
|
// These classes (Hook and Patch) are nasty using new and delete, change them in 2.0.0
|
||||||
Hook* Hook::create(
|
Hook* Hook::create(
|
||||||
Mod* owner,
|
Mod* owner,
|
||||||
void* address,
|
void* address,
|
||||||
|
|
|
@ -47,12 +47,14 @@ IndexUpdateFilter::IndexUpdateFilter() {}
|
||||||
|
|
||||||
class IndexItem::Impl final {
|
class IndexItem::Impl final {
|
||||||
private:
|
private:
|
||||||
|
ghc::filesystem::path m_rootPath;
|
||||||
ghc::filesystem::path m_path;
|
ghc::filesystem::path m_path;
|
||||||
ModMetadata m_metadata;
|
ModMetadata m_metadata;
|
||||||
std::string m_downloadURL;
|
std::string m_downloadURL;
|
||||||
std::string m_downloadHash;
|
std::string m_downloadHash;
|
||||||
std::unordered_set<PlatformID> m_platforms;
|
std::unordered_set<PlatformID> m_platforms;
|
||||||
bool m_isFeatured;
|
bool m_isFeatured = false;
|
||||||
|
bool m_isInstalled = false;
|
||||||
std::unordered_set<std::string> m_tags;
|
std::unordered_set<std::string> m_tags;
|
||||||
|
|
||||||
friend class IndexItem;
|
friend class IndexItem;
|
||||||
|
@ -62,6 +64,7 @@ public:
|
||||||
* Create IndexItem from a directory
|
* Create IndexItem from a directory
|
||||||
*/
|
*/
|
||||||
static Result<std::shared_ptr<IndexItem>> create(
|
static Result<std::shared_ptr<IndexItem>> create(
|
||||||
|
ghc::filesystem::path const& rootDir,
|
||||||
ghc::filesystem::path const& dir
|
ghc::filesystem::path const& dir
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -71,6 +74,10 @@ public:
|
||||||
IndexItem::IndexItem() : m_impl(std::make_unique<Impl>()) {}
|
IndexItem::IndexItem() : m_impl(std::make_unique<Impl>()) {}
|
||||||
IndexItem::~IndexItem() = default;
|
IndexItem::~IndexItem() = default;
|
||||||
|
|
||||||
|
ghc::filesystem::path IndexItem::getRootPath() const {
|
||||||
|
return m_impl->m_rootPath;
|
||||||
|
}
|
||||||
|
|
||||||
ghc::filesystem::path IndexItem::getPath() const {
|
ghc::filesystem::path IndexItem::getPath() const {
|
||||||
return m_impl->m_path;
|
return m_impl->m_path;
|
||||||
}
|
}
|
||||||
|
@ -131,9 +138,13 @@ void IndexItem::setIsFeatured(bool const& value) {
|
||||||
void IndexItem::setTags(std::unordered_set<std::string> const& value) {
|
void IndexItem::setTags(std::unordered_set<std::string> const& value) {
|
||||||
m_impl->m_tags = value;
|
m_impl->m_tags = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IndexItem::setIsInstalled(bool const& value) {
|
||||||
|
m_impl->m_isInstalled = value;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir) {
|
Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& rootDir, ghc::filesystem::path const& dir) {
|
||||||
GEODE_UNWRAP_INTO(
|
GEODE_UNWRAP_INTO(
|
||||||
auto entry, file::readJson(dir / "entry.json")
|
auto entry, file::readJson(dir / "entry.json")
|
||||||
.expect("Unable to read entry.json")
|
.expect("Unable to read entry.json")
|
||||||
|
@ -142,6 +153,10 @@ Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir
|
||||||
auto metadata, ModMetadata::createFromFile(dir / "mod.json")
|
auto metadata, ModMetadata::createFromFile(dir / "mod.json")
|
||||||
.expect("Unable to read mod.json: {error}")
|
.expect("Unable to read mod.json: {error}")
|
||||||
);
|
);
|
||||||
|
auto metadataRes = metadata.addSpecialFiles(rootDir);
|
||||||
|
if (!metadataRes) {
|
||||||
|
log::warn("Unable to add special files from {}: {}", rootDir, metadataRes.unwrapErr());
|
||||||
|
}
|
||||||
|
|
||||||
JsonChecker checker(entry);
|
JsonChecker checker(entry);
|
||||||
auto root = checker.root("[entry.json]").obj();
|
auto root = checker.root("[entry.json]").obj();
|
||||||
|
@ -157,6 +172,7 @@ Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir
|
||||||
}
|
}
|
||||||
|
|
||||||
auto item = std::make_shared<IndexItem>();
|
auto item = std::make_shared<IndexItem>();
|
||||||
|
item->m_impl->m_rootPath = rootDir;
|
||||||
item->m_impl->m_path = dir;
|
item->m_impl->m_path = dir;
|
||||||
item->m_impl->m_metadata = metadata;
|
item->m_impl->m_metadata = metadata;
|
||||||
item->m_impl->m_platforms = platforms;
|
item->m_impl->m_platforms = platforms;
|
||||||
|
@ -172,7 +188,17 @@ Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IndexItem::Impl::isInstalled() const {
|
bool IndexItem::Impl::isInstalled() const {
|
||||||
return ghc::filesystem::exists(dirs::getModsDir() / (m_metadata.getID() + ".geode"));
|
if (m_isInstalled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!Loader::get()->isModInstalled(m_metadata.getID())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto installed = Loader::get()->getInstalledMod(m_metadata.getID());
|
||||||
|
if (installed->getVersion() != m_metadata.getVersion()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
@ -209,7 +235,7 @@ class Index::Impl final {
|
||||||
public:
|
public:
|
||||||
// for once, the fact that std::map is ordered is useful (this makes
|
// for once, the fact that std::map is ordered is useful (this makes
|
||||||
// getting the latest version of a mod as easy as items.rbegin())
|
// getting the latest version of a mod as easy as items.rbegin())
|
||||||
using ItemVersions = std::map<size_t, IndexItemHandle>;
|
using ItemVersions = std::map<VersionInfo, IndexItemHandle>;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<
|
std::unordered_map<
|
||||||
|
@ -335,6 +361,7 @@ void Index::Impl::checkForUpdates() {
|
||||||
auto oldSHA = file::readString(checksum).unwrapOr("");
|
auto oldSHA = file::readString(checksum).unwrapOr("");
|
||||||
web::AsyncWebRequest()
|
web::AsyncWebRequest()
|
||||||
.join("index-update")
|
.join("index-update")
|
||||||
|
.userAgent("github_api/1.0")
|
||||||
.header(fmt::format("If-None-Match: \"{}\"", oldSHA))
|
.header(fmt::format("If-None-Match: \"{}\"", oldSHA))
|
||||||
.header("Accept: application/vnd.github.sha")
|
.header("Accept: application/vnd.github.sha")
|
||||||
.fetch("https://api.github.com/repos/geode-sdk/mods/commits/main")
|
.fetch("https://api.github.com/repos/geode-sdk/mods/commits/main")
|
||||||
|
@ -369,34 +396,36 @@ void Index::Impl::updateFromLocalTree() {
|
||||||
// delete old items
|
// delete old items
|
||||||
m_items.clear();
|
m_items.clear();
|
||||||
|
|
||||||
// read directory and add new items
|
auto indexRoot = dirs::getIndexDir() / "v0";
|
||||||
try {
|
auto entriesRoot = indexRoot / "mods-v2";
|
||||||
for (auto& dir : ghc::filesystem::directory_iterator(dirs::getIndexDir() / "v0" / "mods")) {
|
|
||||||
auto addRes = IndexItem::Impl::create(dir);
|
auto configRes = file::readJson(indexRoot / "config.json");
|
||||||
|
if (!configRes) {
|
||||||
|
IndexUpdateEvent("Unable to read index config").post();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto config = configRes.unwrap();
|
||||||
|
|
||||||
|
JsonChecker checker(config);
|
||||||
|
auto root = checker.root("[config.json]").obj();
|
||||||
|
|
||||||
|
for (auto& [modID, entry] : root.has("entries").items()) {
|
||||||
|
for (auto& version : entry.obj().has("versions").iterate()) {
|
||||||
|
auto rootDir = entriesRoot / modID;
|
||||||
|
auto dir = rootDir / version.get<std::string>();
|
||||||
|
|
||||||
|
auto addRes = IndexItem::Impl::create(rootDir, dir);
|
||||||
if (!addRes) {
|
if (!addRes) {
|
||||||
log::warn("Unable to add index item from {}: {}", dir, addRes.unwrapErr());
|
log::warn("Unable to add index item from {}: {}", dir, addRes.unwrapErr());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto add = addRes.unwrap();
|
auto add = addRes.unwrap();
|
||||||
auto metadata = add->getMetadata();
|
auto metadata = add->getMetadata();
|
||||||
// check if this major version of this item has already been added
|
|
||||||
if (m_items[metadata.getID()].count(metadata.getVersion().getMajor())) {
|
m_items[modID].insert({metadata.getVersion(),
|
||||||
log::warn(
|
|
||||||
"Item {}@{} has already been added, skipping",
|
|
||||||
metadata.getID(),
|
|
||||||
metadata.getVersion()
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// add new major version of this item
|
|
||||||
m_items[metadata.getID()].insert({metadata.getVersion().getMajor(),
|
|
||||||
add
|
add
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch(std::exception& e) {
|
|
||||||
log::error("Unable to read local index tree: {}", e.what());
|
|
||||||
IndexUpdateEvent("Unable to read local index tree").post();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark source as finished
|
// mark source as finished
|
||||||
|
@ -436,6 +465,14 @@ std::vector<IndexItemHandle> Index::getItems() const {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<IndexItemHandle> Index::getLatestItems() const {
|
||||||
|
std::vector<IndexItemHandle> res;
|
||||||
|
for (auto& [modID, versions] : m_impl->m_items) {
|
||||||
|
res.push_back(this->getMajorItem(modID));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<IndexItemHandle> Index::getFeaturedItems() const {
|
std::vector<IndexItemHandle> Index::getFeaturedItems() const {
|
||||||
std::vector<IndexItemHandle> res;
|
std::vector<IndexItemHandle> res;
|
||||||
for (auto& items : map::values(m_impl->m_items)) {
|
for (auto& items : map::values(m_impl->m_items)) {
|
||||||
|
@ -462,6 +499,18 @@ std::vector<IndexItemHandle> Index::getItemsByDeveloper(
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<IndexItemHandle> Index::getItemsByModID(
|
||||||
|
std::string const& modID
|
||||||
|
) const {
|
||||||
|
std::vector<IndexItemHandle> res;
|
||||||
|
if (m_impl->m_items.count(modID)) {
|
||||||
|
for (auto& [versionStr, item] : m_impl->m_items[modID]) {
|
||||||
|
res.push_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
bool Index::isKnownItem(
|
bool Index::isKnownItem(
|
||||||
std::string const& id,
|
std::string const& id,
|
||||||
std::optional<VersionInfo> version
|
std::optional<VersionInfo> version
|
||||||
|
@ -485,19 +534,14 @@ IndexItemHandle Index::getItem(
|
||||||
if (m_impl->m_items.count(id)) {
|
if (m_impl->m_items.count(id)) {
|
||||||
auto versions = m_impl->m_items.at(id);
|
auto versions = m_impl->m_items.at(id);
|
||||||
if (version) {
|
if (version) {
|
||||||
// prefer most major version
|
|
||||||
for (auto& [_, item] : ranges::reverse(m_impl->m_items.at(id))) {
|
for (auto& [_, item] : ranges::reverse(m_impl->m_items.at(id))) {
|
||||||
if (version.value() == item->getMetadata().getVersion()) {
|
if (version.value() == item->getMetadata().getVersion()) {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (!versions.empty()) {
|
|
||||||
return m_impl->m_items.at(id).rbegin()->second;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return this->getMajorItem(id);
|
||||||
return nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IndexItemHandle Index::getItem(
|
IndexItemHandle Index::getItem(
|
||||||
|
@ -718,6 +762,8 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item->setIsInstalled(true);
|
||||||
|
|
||||||
// Install next item in queue
|
// Install next item in queue
|
||||||
this->installNext(index + 1, list);
|
this->installNext(index + 1, list);
|
||||||
})
|
})
|
||||||
|
@ -752,6 +798,10 @@ void Index::cancelInstall(IndexItemHandle item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Index::install(IndexInstallList const& list) {
|
void Index::install(IndexInstallList const& list) {
|
||||||
|
if (list.list.empty()) {
|
||||||
|
ModInstallEvent(list.target->getMetadata().getID(), UpdateFinished()).post();
|
||||||
|
return;
|
||||||
|
}
|
||||||
Loader::get()->queueInMainThread([this, list]() {
|
Loader::get()->queueInMainThread([this, list]() {
|
||||||
m_impl->installNext(0, list);
|
m_impl->installNext(0, list);
|
||||||
});
|
});
|
||||||
|
|
|
@ -166,7 +166,6 @@ bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
|
||||||
Result<> Loader::Impl::saveData() {
|
Result<> Loader::Impl::saveData() {
|
||||||
// save mods' data
|
// save mods' data
|
||||||
for (auto& [id, mod] : m_mods) {
|
for (auto& [id, mod] : m_mods) {
|
||||||
Mod::get()->setSavedValue("should-load-" + id, mod->isUninstalled() || mod->isEnabled());
|
|
||||||
auto r = mod->saveData();
|
auto r = mod->saveData();
|
||||||
if (!r) {
|
if (!r) {
|
||||||
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||||
|
@ -411,6 +410,7 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Mod::get()->getSavedValue<bool>("should-load-" + node->getID(), true)) {
|
||||||
log::debug("Load");
|
log::debug("Load");
|
||||||
auto res = node->m_impl->loadBinary();
|
auto res = node->m_impl->loadBinary();
|
||||||
if (!res) {
|
if (!res) {
|
||||||
|
@ -424,22 +424,6 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Mod::get()->getSavedValue<bool>("should-load-" + node->getID(), true)) {
|
|
||||||
log::debug("Enable");
|
|
||||||
res = node->m_impl->enable();
|
|
||||||
if (!res) {
|
|
||||||
node->m_impl->m_enabled = true;
|
|
||||||
(void)node->m_impl->disable();
|
|
||||||
m_problems.push_back({
|
|
||||||
LoadProblem::Type::EnableFailed,
|
|
||||||
node,
|
|
||||||
res.unwrapErr()
|
|
||||||
});
|
|
||||||
log::error("Failed to enable: {}", res.unwrapErr());
|
|
||||||
log::popNest();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto const& dep : node->m_impl->m_dependants) {
|
for (auto const& dep : node->m_impl->m_dependants) {
|
||||||
this->loadModGraph(dep, early);
|
this->loadModGraph(dep, early);
|
||||||
}
|
}
|
||||||
|
@ -509,19 +493,19 @@ void Loader::Impl::findProblems() {
|
||||||
|
|
||||||
Mod* myEpicMod = mod; // clang fix
|
Mod* myEpicMod = mod; // clang fix
|
||||||
// if the mod is not loaded but there are no problems related to it
|
// if the mod is not loaded but there are no problems related to it
|
||||||
if (!mod->isLoaded() && !std::any_of(m_problems.begin(), m_problems.end(), [myEpicMod](auto& item) {
|
// if (!mod->isLoaded() && !std::any_of(m_problems.begin(), m_problems.end(), [myEpicMod](auto& item) {
|
||||||
return std::holds_alternative<ModMetadata>(item.cause) &&
|
// return std::holds_alternative<ModMetadata>(item.cause) &&
|
||||||
std::get<ModMetadata>(item.cause).getID() == myEpicMod->getID() ||
|
// std::get<ModMetadata>(item.cause).getID() == myEpicMod->getID() ||
|
||||||
std::holds_alternative<Mod*>(item.cause) &&
|
// std::holds_alternative<Mod*>(item.cause) &&
|
||||||
std::get<Mod*>(item.cause) == myEpicMod;
|
// std::get<Mod*>(item.cause) == myEpicMod;
|
||||||
})) {
|
// })) {
|
||||||
m_problems.push_back({
|
// m_problems.push_back({
|
||||||
LoadProblem::Type::Unknown,
|
// LoadProblem::Type::Unknown,
|
||||||
mod,
|
// mod,
|
||||||
""
|
// ""
|
||||||
});
|
// });
|
||||||
log::error("{} failed to load for an unknown reason", id);
|
// log::error("{} failed to load for an unknown reason", id);
|
||||||
}
|
// }
|
||||||
|
|
||||||
log::popNest();
|
log::popNest();
|
||||||
}
|
}
|
||||||
|
@ -533,7 +517,7 @@ void Loader::Impl::refreshModGraph() {
|
||||||
|
|
||||||
auto begin = std::chrono::high_resolution_clock::now();
|
auto begin = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
if (m_mods.size() > 1) {
|
if (m_isSetup) {
|
||||||
log::error("Cannot refresh mod graph after startup");
|
log::error("Cannot refresh mod graph after startup");
|
||||||
log::popNest();
|
log::popNest();
|
||||||
return;
|
return;
|
||||||
|
@ -564,7 +548,7 @@ void Loader::Impl::refreshModGraph() {
|
||||||
m_loadingState = LoadingState::EarlyMods;
|
m_loadingState = LoadingState::EarlyMods;
|
||||||
log::debug("Loading early mods");
|
log::debug("Loading early mods");
|
||||||
log::pushNest();
|
log::pushNest();
|
||||||
for (auto const& dep : Mod::get()->m_impl->m_dependants) {
|
for (auto const& dep : ModImpl::get()->m_dependants) {
|
||||||
this->loadModGraph(dep, true);
|
this->loadModGraph(dep, true);
|
||||||
}
|
}
|
||||||
log::popNest();
|
log::popNest();
|
||||||
|
@ -716,6 +700,7 @@ void Loader::Impl::fetchLatestGithubRelease(
|
||||||
// TODO: add header to not get rate limited
|
// TODO: add header to not get rate limited
|
||||||
web::AsyncWebRequest()
|
web::AsyncWebRequest()
|
||||||
.join("loader-auto-update-check")
|
.join("loader-auto-update-check")
|
||||||
|
.userAgent("github_api/1.0")
|
||||||
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
|
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest")
|
||||||
.json()
|
.json()
|
||||||
.then([this, then](json::Value const& json) {
|
.then([this, then](json::Value const& json) {
|
||||||
|
@ -784,6 +769,7 @@ void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
|
||||||
if (!useLatestRelease) {
|
if (!useLatestRelease) {
|
||||||
web::AsyncWebRequest()
|
web::AsyncWebRequest()
|
||||||
.join("loader-tag-exists-check")
|
.join("loader-tag-exists-check")
|
||||||
|
.userAgent("github_api/1.0")
|
||||||
.fetch(fmt::format(
|
.fetch(fmt::format(
|
||||||
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
|
"https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}",
|
||||||
this->getVersion().toString()
|
this->getVersion().toString()
|
||||||
|
|
|
@ -198,6 +198,10 @@ bool Mod::isUninstalled() const {
|
||||||
return m_impl->isUninstalled();
|
return m_impl->isUninstalled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ModRequestedAction Mod::getRequestedAction() const {
|
||||||
|
return m_impl->getRequestedAction();
|
||||||
|
}
|
||||||
|
|
||||||
bool Mod::depends(std::string const& id) const {
|
bool Mod::depends(std::string const& id) const {
|
||||||
return m_impl->depends(id);
|
return m_impl->depends(id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,23 +117,15 @@ bool Mod::Impl::isLoaded() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mod::Impl::supportsDisabling() const {
|
bool Mod::Impl::supportsDisabling() const {
|
||||||
return m_metadata.getID() != "geode.loader" && !m_metadata.isAPI();
|
return m_metadata.getID() != "geode.loader";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mod::Impl::canDisable() const {
|
bool Mod::Impl::canDisable() const {
|
||||||
auto deps = m_dependants;
|
return true;
|
||||||
return this->supportsDisabling() &&
|
|
||||||
(deps.empty() || std::all_of(deps.begin(), deps.end(), [&](auto& item) {
|
|
||||||
return item->canDisable();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mod::Impl::canEnable() const {
|
bool Mod::Impl::canEnable() const {
|
||||||
auto deps = m_metadata.getDependencies();
|
return true;
|
||||||
return !this->isUninstalled() &&
|
|
||||||
(deps.empty() || std::all_of(deps.begin(), deps.end(), [&](auto& item) {
|
|
||||||
return item.isResolved();
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mod::Impl::needsEarlyLoad() const {
|
bool Mod::Impl::needsEarlyLoad() const {
|
||||||
|
@ -345,119 +337,74 @@ Result<> Mod::Impl::loadBinary() {
|
||||||
|
|
||||||
LoaderImpl::get()->releaseNextMod();
|
LoaderImpl::get()->releaseNextMod();
|
||||||
|
|
||||||
ModStateEvent(m_self, ModEventType::Loaded).post();
|
|
||||||
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<> Mod::Impl::enable() {
|
|
||||||
if (!m_binaryLoaded)
|
|
||||||
return Err("Tried to enable {} but its binary is not loaded", m_metadata.getID());
|
|
||||||
|
|
||||||
bool enabledDependencies = true;
|
|
||||||
for (auto const& item : m_metadata.getDependencies()) {
|
|
||||||
if (!item.isResolved() || !item.mod)
|
|
||||||
continue;
|
|
||||||
auto res = item.mod->enable();
|
|
||||||
if (!res) {
|
|
||||||
enabledDependencies = false;
|
|
||||||
log::error("Failed to enable {}: {}", item.id, res.unwrapErr());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enabledDependencies)
|
|
||||||
return Err("Mod cannot be enabled because one or more of its dependencies cannot be enabled.");
|
|
||||||
|
|
||||||
if (!this->canEnable())
|
|
||||||
return Err("Mod cannot be enabled because it has unresolved dependencies.");
|
|
||||||
|
|
||||||
for (auto const& hook : m_hooks) {
|
for (auto const& hook : m_hooks) {
|
||||||
if (!hook) {
|
if (!hook) {
|
||||||
log::warn("Hook is null in mod \"{}\"", m_metadata.getName());
|
log::warn("Hook is null in mod \"{}\"", m_metadata.getName());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (hook->getAutoEnable()) {
|
if (hook->getAutoEnable()) {
|
||||||
GEODE_UNWRAP(this->enableHook(hook));
|
auto res = this->enableHook(hook);
|
||||||
|
if (!res) {
|
||||||
|
log::error("Can't enable hook {} for mod {}: {}", hook->getDisplayName(), m_metadata.getID(), res.unwrapErr());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const& patch : m_patches) {
|
for (auto const& patch : m_patches) {
|
||||||
|
if (!patch) {
|
||||||
|
log::warn("Patch is null in mod \"{}\"", m_metadata.getName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (patch->getAutoEnable()) {
|
||||||
if (!patch->apply()) {
|
if (!patch->apply()) {
|
||||||
log::warn("Unable to apply patch at {}", patch->getAddress());
|
log::warn("Unable to apply patch at {}", patch->getAddress());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_enabled = true;
|
m_enabled = true;
|
||||||
|
|
||||||
|
ModStateEvent(m_self, ModEventType::Loaded).post();
|
||||||
ModStateEvent(m_self, ModEventType::Enabled).post();
|
ModStateEvent(m_self, ModEventType::Enabled).post();
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<> Mod::Impl::disable() {
|
Result<> Mod::Impl::enable() {
|
||||||
if (!m_enabled)
|
if (m_requestedAction != ModRequestedAction::None) {
|
||||||
|
return Err("Mod already has a requested action");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_requestedAction = ModRequestedAction::Enable;
|
||||||
|
Mod::get()->setSavedValue("should-load-" + m_metadata.getID(), true);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|
||||||
if (!this->supportsDisabling())
|
|
||||||
return Err("Mod does not support disabling.");
|
|
||||||
|
|
||||||
if (!this->canDisable())
|
|
||||||
return Err("Mod cannot be disabled because one or more of its dependants cannot be disabled.");
|
|
||||||
|
|
||||||
// disable dependants
|
|
||||||
bool disabledDependants = true;
|
|
||||||
for (auto& item : m_dependants) {
|
|
||||||
auto res = item->disable();
|
|
||||||
if (res)
|
|
||||||
continue;
|
|
||||||
disabledDependants = false;
|
|
||||||
log::error("Failed to disable {}: {}", item->getID(), res.unwrapErr());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disabledDependants)
|
Result<> Mod::Impl::disable() {
|
||||||
return Err("Mod cannot be disabled because one or more of its dependants cannot be disabled.");
|
if (m_requestedAction != ModRequestedAction::None) {
|
||||||
|
return Err("Mod already has a requested action");
|
||||||
std::vector<std::string> errors;
|
|
||||||
for (auto const& hook : m_hooks) {
|
|
||||||
auto res = this->disableHook(hook);
|
|
||||||
if (!res)
|
|
||||||
errors.push_back(res.unwrapErr());
|
|
||||||
}
|
|
||||||
for (auto const& patch : m_patches) {
|
|
||||||
auto res = this->unpatch(patch);
|
|
||||||
if (!res)
|
|
||||||
errors.push_back(res.unwrapErr());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_enabled = false;
|
m_requestedAction = ModRequestedAction::Disable;
|
||||||
ModStateEvent(m_self, ModEventType::Disabled).post();
|
Mod::get()->setSavedValue("should-load-" + m_metadata.getID(), false);
|
||||||
|
|
||||||
if (!errors.empty())
|
|
||||||
return Err(utils::string::join(errors, "\n"));
|
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<> Mod::Impl::uninstall() {
|
Result<> Mod::Impl::uninstall() {
|
||||||
if (supportsDisabling()) {
|
if (m_requestedAction != ModRequestedAction::None) {
|
||||||
GEODE_UNWRAP(this->disable());
|
return Err("Mod already has a requested action");
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (auto& item : m_dependants) {
|
|
||||||
if (!item->canDisable())
|
|
||||||
continue;
|
|
||||||
GEODE_UNWRAP(item->disable());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
m_requestedAction = ModRequestedAction::Uninstall;
|
||||||
ghc::filesystem::remove(m_metadata.getPath());
|
|
||||||
}
|
std::error_code ec;
|
||||||
catch (std::exception& e) {
|
ghc::filesystem::remove(m_metadata.getPath(), ec);
|
||||||
|
if (ec) {
|
||||||
return Err(
|
return Err(
|
||||||
"Unable to delete mod's .geode file! "
|
"Unable to delete mod's .geode file: " + ec.message()
|
||||||
"This might be due to insufficient permissions - "
|
|
||||||
"try running GD as administrator."
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,7 +412,11 @@ Result<> Mod::Impl::uninstall() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mod::Impl::isUninstalled() const {
|
bool Mod::Impl::isUninstalled() const {
|
||||||
return m_self != Mod::get() && !ghc::filesystem::exists(m_metadata.getPath());
|
return m_requestedAction == ModRequestedAction::Uninstall;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModRequestedAction Mod::Impl::getRequestedAction() const {
|
||||||
|
return m_requestedAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dependencies
|
// Dependencies
|
||||||
|
@ -661,7 +612,7 @@ ModJson Mod::Impl::getRuntimeInfo() const {
|
||||||
for (auto patch : m_patches) {
|
for (auto patch : m_patches) {
|
||||||
obj["patches"].as_array().push_back(ModJson(patch->getRuntimeInfo()));
|
obj["patches"].as_array().push_back(ModJson(patch->getRuntimeInfo()));
|
||||||
}
|
}
|
||||||
obj["enabled"] = m_enabled;
|
// obj["enabled"] = m_enabled;
|
||||||
obj["loaded"] = m_binaryLoaded;
|
obj["loaded"] = m_binaryLoaded;
|
||||||
obj["temp-dir"] = this->getTempDir();
|
obj["temp-dir"] = this->getTempDir();
|
||||||
obj["save-dir"] = this->getSaveDir();
|
obj["save-dir"] = this->getSaveDir();
|
||||||
|
|
|
@ -62,6 +62,9 @@ namespace geode {
|
||||||
*/
|
*/
|
||||||
bool m_resourcesLoaded = false;
|
bool m_resourcesLoaded = false;
|
||||||
|
|
||||||
|
|
||||||
|
ModRequestedAction m_requestedAction = ModRequestedAction::None;
|
||||||
|
|
||||||
Impl(Mod* self, ModMetadata const& metadata);
|
Impl(Mod* self, ModMetadata const& metadata);
|
||||||
~Impl();
|
~Impl();
|
||||||
|
|
||||||
|
@ -122,6 +125,10 @@ namespace geode {
|
||||||
Result<> disable();
|
Result<> disable();
|
||||||
Result<> uninstall();
|
Result<> uninstall();
|
||||||
bool isUninstalled() const;
|
bool isUninstalled() const;
|
||||||
|
|
||||||
|
// 1.3.0 additions
|
||||||
|
ModRequestedAction getRequestedAction() const;
|
||||||
|
|
||||||
bool depends(std::string const& id) const;
|
bool depends(std::string const& id) const;
|
||||||
Result<> updateDependencies();
|
Result<> updateDependencies();
|
||||||
bool hasUnresolvedDependencies() const;
|
bool hasUnresolvedDependencies() const;
|
||||||
|
|
|
@ -4,13 +4,43 @@
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
bool Patch::apply() {
|
bool Patch::apply() {
|
||||||
return bool(tulip::hook::writeMemory(m_address, m_patch.data(), m_patch.size()));
|
bool res = bool(tulip::hook::writeMemory(m_address, m_patch.data(), m_patch.size()));
|
||||||
|
if (res)
|
||||||
|
m_applied = true;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Patch::restore() {
|
bool Patch::restore() {
|
||||||
return bool(tulip::hook::writeMemory(m_address, m_original.data(), m_original.size()));
|
bool res = bool(tulip::hook::writeMemory(m_address, m_original.data(), m_original.size()));
|
||||||
|
if (res)
|
||||||
|
m_applied = false;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Patch::Patch() : m_owner(nullptr), m_address(nullptr), m_applied(false), m_autoEnable(true) {}
|
||||||
|
|
||||||
|
void Patch::setAutoEnable(bool autoEnable) {
|
||||||
|
m_autoEnable = autoEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Patch::getAutoEnable() const {
|
||||||
|
return m_autoEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t Patch::getAddress() const {
|
||||||
|
return reinterpret_cast<uintptr_t>(m_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Patch::isApplied() const {
|
||||||
|
return m_applied;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod* Patch::getOwner() const {
|
||||||
|
return m_owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
Patch::~Patch() {}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct json::Serialize<ByteVector> {
|
struct json::Serialize<ByteVector> {
|
||||||
static json::Value to_json(ByteVector const& bytes) {
|
static json::Value to_json(ByteVector const& bytes) {
|
||||||
|
|
|
@ -251,7 +251,20 @@ static std::string getStacktrace() {
|
||||||
stream >> std::hex >> address >> std::dec;
|
stream >> std::hex >> address >> std::dec;
|
||||||
|
|
||||||
if (!line.empty()) {
|
if (!line.empty()) {
|
||||||
stacktrace << " - " << std::showbase << std::hex << address << std::dec;
|
// log::debug("address: {}", address);
|
||||||
|
auto image = imageFromAddress(reinterpret_cast<void*>(address));
|
||||||
|
// log::debug("image: {}", image);
|
||||||
|
stacktrace << " - " << std::showbase << std::hex;
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
auto baseAddress = image->imageLoadAddress;
|
||||||
|
auto imageName = getImageName(image);
|
||||||
|
stacktrace << imageName << " + " << (address - (uintptr_t)baseAddress);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stacktrace << address;
|
||||||
|
}
|
||||||
|
stacktrace << std::dec;
|
||||||
stacktrace << ": " << line << "\n";
|
stacktrace << ": " << line << "\n";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -319,14 +332,15 @@ static void handlerThread() {
|
||||||
s_cv.wait(lock, [] { return s_signal != 0; });
|
s_cv.wait(lock, [] { return s_signal != 0; });
|
||||||
|
|
||||||
auto signalAddress = reinterpret_cast<void*>(s_context->uc_mcontext->__ss.__rip);
|
auto signalAddress = reinterpret_cast<void*>(s_context->uc_mcontext->__ss.__rip);
|
||||||
Mod* faultyMod = nullptr;
|
// Mod* faultyMod = nullptr;
|
||||||
for (int i = 1; i < s_backtraceSize; ++i) {
|
// for (int i = 1; i < s_backtraceSize; ++i) {
|
||||||
auto mod = modFromAddress(s_backtrace[i]);
|
// auto mod = modFromAddress(s_backtrace[i]);
|
||||||
if (mod != nullptr) {
|
// if (mod != nullptr) {
|
||||||
faultyMod = mod;
|
// faultyMod = mod;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
Mod* faultyMod = modFromAddress(signalAddress);
|
||||||
|
|
||||||
auto text = crashlog::writeCrashlog(faultyMod, getInfo(signalAddress, faultyMod), getStacktrace(), getRegisters());
|
auto text = crashlog::writeCrashlog(faultyMod, getInfo(signalAddress, faultyMod), getStacktrace(), getRegisters());
|
||||||
|
|
||||||
|
@ -334,6 +348,8 @@ static void handlerThread() {
|
||||||
|
|
||||||
s_signal = 0;
|
s_signal = 0;
|
||||||
s_cv.notify_all();
|
s_cv.notify_all();
|
||||||
|
|
||||||
|
log::debug("Notified");
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool s_lastLaunchCrashed;
|
static bool s_lastLaunchCrashed;
|
||||||
|
|
|
@ -57,7 +57,7 @@ namespace gd {
|
||||||
return std::string(*this) == std::string(other);
|
return std::string(*this) == std::string(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: these need to stay for old mods linking against geode <1.2.0
|
// TODO: these need to stay for old mods linking against geode <1.2.0, remove in 2.0.0
|
||||||
template class map<int, int>;
|
template class map<int, int>;
|
||||||
template class map<gd::string, gd::string>;
|
template class map<gd::string, gd::string>;
|
||||||
template class map<gd::string, bool>;
|
template class map<gd::string, bool>;
|
||||||
|
|
|
@ -83,7 +83,7 @@ CCNode* geode::createModLogo(Mod* mod, CCSize const& size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
|
CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
|
||||||
auto logoPath = ghc::filesystem::absolute(item->getPath() / "logo.png");
|
auto logoPath = ghc::filesystem::absolute(item->getRootPath() / "logo.png");
|
||||||
CCNode* spr = CCSprite::create(logoPath.string().c_str());
|
CCNode* spr = CCSprite::create(logoPath.string().c_str());
|
||||||
if (!spr) {
|
if (!spr) {
|
||||||
spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
||||||
|
|
|
@ -277,6 +277,95 @@ void ModInfoPopup::setInstallStatus(std::optional<UpdateProgress> const& progres
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::popupInstallItem(IndexItemHandle item) {
|
||||||
|
auto deps = item->getMetadata().getDependencies();
|
||||||
|
enum class DepState {
|
||||||
|
None,
|
||||||
|
HasOnlyRequired,
|
||||||
|
HasOptional
|
||||||
|
} depState = DepState::None;
|
||||||
|
for (auto const& dep : deps) {
|
||||||
|
// resolved means it's already installed, so
|
||||||
|
// no need to ask the user whether they want to install it
|
||||||
|
if (Loader::get()->isModLoaded(dep.id))
|
||||||
|
continue;
|
||||||
|
if (dep.importance != ModMetadata::Dependency::Importance::Required) {
|
||||||
|
depState = DepState::HasOptional;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
depState = DepState::HasOnlyRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string content;
|
||||||
|
char const* btn1;
|
||||||
|
char const* btn2;
|
||||||
|
switch (depState) {
|
||||||
|
case DepState::None:
|
||||||
|
content = fmt::format(
|
||||||
|
"Are you sure you want to install <cg>{}</c>?",
|
||||||
|
item->getMetadata().getName()
|
||||||
|
);
|
||||||
|
btn1 = "Info";
|
||||||
|
btn2 = "Install";
|
||||||
|
break;
|
||||||
|
case DepState::HasOnlyRequired:
|
||||||
|
content =
|
||||||
|
"Installing this mod requires other mods to be installed. "
|
||||||
|
"Would you like to <cy>proceed</c> with the installation or "
|
||||||
|
"<cb>view</c> which mods are going to be installed?";
|
||||||
|
btn1 = "View";
|
||||||
|
btn2 = "Proceed";
|
||||||
|
break;
|
||||||
|
case DepState::HasOptional:
|
||||||
|
content =
|
||||||
|
"This mod recommends installing other mods alongside it. "
|
||||||
|
"Would you like to continue with <cy>recommended settings</c> or "
|
||||||
|
"<cb>customize</c> which mods to install?";
|
||||||
|
btn1 = "Customize";
|
||||||
|
btn2 = "Recommended";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
createQuickPopup("Confirm Install", content, btn1, btn2, 320.f, [&](FLAlertLayer*, bool btn2) {
|
||||||
|
if (btn2) {
|
||||||
|
auto canInstall = Index::get()->canInstall(m_item);
|
||||||
|
if (!canInstall) {
|
||||||
|
FLAlertLayer::create(
|
||||||
|
"Unable to Install",
|
||||||
|
canInstall.unwrapErr(),
|
||||||
|
"OK"
|
||||||
|
)->show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->preInstall();
|
||||||
|
Index::get()->install(m_item);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
InstallListPopup::create(m_item, [&](IndexInstallList const& list) {
|
||||||
|
this->preInstall();
|
||||||
|
Index::get()->install(list);
|
||||||
|
})->show();
|
||||||
|
}
|
||||||
|
}, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::preInstall() {
|
||||||
|
if (m_latestVersionLabel) {
|
||||||
|
m_latestVersionLabel->setVisible(false);
|
||||||
|
}
|
||||||
|
this->setInstallStatus(UpdateProgress(0, "Starting install"));
|
||||||
|
|
||||||
|
m_installBtn->setTarget(
|
||||||
|
this, menu_selector(ModInfoPopup::onCancelInstall)
|
||||||
|
);
|
||||||
|
m_installBtnSpr->setString("Cancel");
|
||||||
|
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModInfoPopup::onCancelInstall(CCObject*) {
|
||||||
|
Index::get()->cancelInstall(m_item);
|
||||||
|
}
|
||||||
|
|
||||||
// LocalModInfoPopup
|
// LocalModInfoPopup
|
||||||
LocalModInfoPopup::LocalModInfoPopup()
|
LocalModInfoPopup::LocalModInfoPopup()
|
||||||
: m_installListener(
|
: m_installListener(
|
||||||
|
@ -462,8 +551,8 @@ void LocalModInfoPopup::onUpdateProgress(ModInstallEvent* event) {
|
||||||
FLAlertLayer::create(
|
FLAlertLayer::create(
|
||||||
"Update complete",
|
"Update complete",
|
||||||
"Mod successfully updated! :) "
|
"Mod successfully updated! :) "
|
||||||
"(You may need to <cy>restart the game</c> "
|
"(You have to <cy>restart the game</c> "
|
||||||
"for the mod to take full effect)",
|
"for the mod to take effect)",
|
||||||
"OK"
|
"OK"
|
||||||
)->show();
|
)->show();
|
||||||
|
|
||||||
|
@ -493,61 +582,7 @@ void LocalModInfoPopup::onUpdateProgress(ModInstallEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalModInfoPopup::onUpdate(CCObject*) {
|
void LocalModInfoPopup::onUpdate(CCObject*) {
|
||||||
auto list = Index::get()->getInstallList(m_item);
|
this->popupInstallItem(m_item);
|
||||||
if (!list) {
|
|
||||||
return FLAlertLayer::create(
|
|
||||||
"Unable to Update",
|
|
||||||
list.unwrapErr(),
|
|
||||||
"OK"
|
|
||||||
)->show();
|
|
||||||
}
|
|
||||||
auto layer = FLAlertLayer::create(
|
|
||||||
this,
|
|
||||||
"Confirm Update",
|
|
||||||
fmt::format(
|
|
||||||
"The following mods will be updated:\n {}",
|
|
||||||
// le nest
|
|
||||||
ranges::join(
|
|
||||||
ranges::map<std::vector<std::string>>(
|
|
||||||
list.unwrap().list,
|
|
||||||
[](IndexItemHandle handle) {
|
|
||||||
return fmt::format(
|
|
||||||
" - <cr>{}</c> (<cy>{}</c>)",
|
|
||||||
handle->getMetadata().getName(),
|
|
||||||
handle->getMetadata().getID()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
"\n "
|
|
||||||
)
|
|
||||||
),
|
|
||||||
"Cancel", "OK"
|
|
||||||
);
|
|
||||||
layer->setTag(TAG_CONFIRM_UPDATE);
|
|
||||||
layer->show();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalModInfoPopup::onCancel(CCObject*) {
|
|
||||||
Index::get()->cancelInstall(m_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalModInfoPopup::doUpdate() {
|
|
||||||
if (m_latestVersionLabel) {
|
|
||||||
m_latestVersionLabel->setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_minorVersionLabel) {
|
|
||||||
m_minorVersionLabel->setVisible(false);
|
|
||||||
}
|
|
||||||
this->setInstallStatus(UpdateProgress(0, "Starting update"));
|
|
||||||
|
|
||||||
m_installBtn->setTarget(
|
|
||||||
this, menu_selector(LocalModInfoPopup::onCancel)
|
|
||||||
);
|
|
||||||
m_installBtnSpr->setString("Cancel");
|
|
||||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
|
||||||
|
|
||||||
Index::get()->install(m_item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalModInfoPopup::onUninstall(CCObject*) {
|
void LocalModInfoPopup::onUninstall(CCObject*) {
|
||||||
|
@ -631,12 +666,6 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
||||||
}
|
}
|
||||||
this->onClose(nullptr);
|
this->onClose(nullptr);
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case TAG_CONFIRM_UPDATE: {
|
|
||||||
if (btn2) {
|
|
||||||
this->doUpdate();
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -649,8 +678,8 @@ void LocalModInfoPopup::doUninstall() {
|
||||||
this,
|
this,
|
||||||
"Uninstall complete",
|
"Uninstall complete",
|
||||||
"Mod was successfully uninstalled! :) "
|
"Mod was successfully uninstalled! :) "
|
||||||
"(You may need to <cy>restart the game</c> "
|
"(You have to <cy>restart the game</c> "
|
||||||
"for the mod to take full effect). "
|
"for the mod to take effect). "
|
||||||
"<co>Would you also like to delete the mod's "
|
"<co>Would you also like to delete the mod's "
|
||||||
"save data?</c>",
|
"save data?</c>",
|
||||||
"Keep",
|
"Keep",
|
||||||
|
@ -687,7 +716,8 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
|
||||||
|
|
||||||
if (!ModInfoPopup::init(item->getMetadata(), list)) return false;
|
if (!ModInfoPopup::init(item->getMetadata(), list)) return false;
|
||||||
|
|
||||||
if (item->isInstalled()) return true;
|
// bruh why is this here if we are allowing for browsing already installed mods
|
||||||
|
// if (item->isInstalled()) return true;
|
||||||
|
|
||||||
m_installBtnSpr = IconButtonSprite::create(
|
m_installBtnSpr = IconButtonSprite::create(
|
||||||
"GE_button_01.png"_spr,
|
"GE_button_01.png"_spr,
|
||||||
|
@ -719,8 +749,8 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) {
|
||||||
FLAlertLayer::create(
|
FLAlertLayer::create(
|
||||||
"Install complete",
|
"Install complete",
|
||||||
"Mod successfully installed! :) "
|
"Mod successfully installed! :) "
|
||||||
"(You may need to <cy>restart the game</c> "
|
"(You have to <cy>restart the game</c> "
|
||||||
"for the mod to take full effect)",
|
"for the mod to take effect)",
|
||||||
"OK"
|
"OK"
|
||||||
)->show();
|
)->show();
|
||||||
|
|
||||||
|
@ -750,92 +780,7 @@ void IndexItemInfoPopup::onInstallProgress(ModInstallEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void IndexItemInfoPopup::onInstall(CCObject*) {
|
void IndexItemInfoPopup::onInstall(CCObject*) {
|
||||||
auto deps = m_item->getMetadata().getDependencies();
|
this->popupInstallItem(m_item);
|
||||||
enum class DepState {
|
|
||||||
None,
|
|
||||||
HasOnlyRequired,
|
|
||||||
HasOptional
|
|
||||||
} depState = DepState::None;
|
|
||||||
for (auto const& item : deps) {
|
|
||||||
// resolved means it's already installed, so
|
|
||||||
// no need to ask the user whether they want to install it
|
|
||||||
if (Loader::get()->isModLoaded(item.id))
|
|
||||||
continue;
|
|
||||||
if (item.importance != ModMetadata::Dependency::Importance::Required) {
|
|
||||||
depState = DepState::HasOptional;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
depState = DepState::HasOnlyRequired;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string content;
|
|
||||||
char const* btn1;
|
|
||||||
char const* btn2;
|
|
||||||
switch (depState) {
|
|
||||||
case DepState::None:
|
|
||||||
content = fmt::format(
|
|
||||||
"Are you sure you want to install <cg>{}</c>?",
|
|
||||||
m_item->getMetadata().getName()
|
|
||||||
);
|
|
||||||
btn1 = "Info";
|
|
||||||
btn2 = "Install";
|
|
||||||
break;
|
|
||||||
case DepState::HasOnlyRequired:
|
|
||||||
content =
|
|
||||||
"Installing this mod requires other mods to be installed. "
|
|
||||||
"Would you like to <cy>proceed</c> with the installation or "
|
|
||||||
"<cb>view</c> which mods are going to be installed?";
|
|
||||||
btn1 = "View";
|
|
||||||
btn2 = "Proceed";
|
|
||||||
break;
|
|
||||||
case DepState::HasOptional:
|
|
||||||
content =
|
|
||||||
"This mod recommends installing other mods alongside it. "
|
|
||||||
"Would you like to continue with <cy>recommended settings</c> or "
|
|
||||||
"<cb>customize</c> which mods to install?";
|
|
||||||
btn1 = "Customize";
|
|
||||||
btn2 = "Recommended";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
createQuickPopup("Confirm Install", content, btn1, btn2, 320.f, [&](FLAlertLayer*, bool btn2) {
|
|
||||||
if (btn2) {
|
|
||||||
auto canInstall = Index::get()->canInstall(m_item);
|
|
||||||
if (!canInstall) {
|
|
||||||
FLAlertLayer::create(
|
|
||||||
"Unable to Install",
|
|
||||||
canInstall.unwrapErr(),
|
|
||||||
"OK"
|
|
||||||
)->show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->preInstall();
|
|
||||||
Index::get()->install(m_item);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
InstallListPopup::create(m_item, [&](IndexInstallList const& list) {
|
|
||||||
this->preInstall();
|
|
||||||
Index::get()->install(list);
|
|
||||||
})->show();
|
|
||||||
}
|
|
||||||
}, true, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexItemInfoPopup::preInstall() {
|
|
||||||
if (m_latestVersionLabel) {
|
|
||||||
m_latestVersionLabel->setVisible(false);
|
|
||||||
}
|
|
||||||
this->setInstallStatus(UpdateProgress(0, "Starting install"));
|
|
||||||
|
|
||||||
m_installBtn->setTarget(
|
|
||||||
this, menu_selector(IndexItemInfoPopup::onCancel)
|
|
||||||
);
|
|
||||||
m_installBtnSpr->setString("Cancel");
|
|
||||||
m_installBtnSpr->setBG("GJ_button_06.png", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IndexItemInfoPopup::onCancel(CCObject*) {
|
|
||||||
Index::get()->cancelInstall(m_item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) {
|
CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ protected:
|
||||||
MDTextArea* m_detailsArea;
|
MDTextArea* m_detailsArea;
|
||||||
MDTextArea* m_changelogArea = nullptr;
|
MDTextArea* m_changelogArea = nullptr;
|
||||||
Scrollbar* m_scrollbar;
|
Scrollbar* m_scrollbar;
|
||||||
|
IndexItemHandle m_item;
|
||||||
|
|
||||||
void onChangelog(CCObject*);
|
void onChangelog(CCObject*);
|
||||||
void onRepository(CCObject*);
|
void onRepository(CCObject*);
|
||||||
|
@ -51,13 +52,16 @@ protected:
|
||||||
|
|
||||||
void setInstallStatus(std::optional<UpdateProgress> const& progress);
|
void setInstallStatus(std::optional<UpdateProgress> const& progress);
|
||||||
|
|
||||||
|
void popupInstallItem(IndexItemHandle item);
|
||||||
|
void preInstall();
|
||||||
|
void onCancelInstall(CCObject*);
|
||||||
|
|
||||||
virtual CCNode* createLogo(CCSize const& size) = 0;
|
virtual CCNode* createLogo(CCSize const& size) = 0;
|
||||||
virtual ModMetadata getMetadata() const = 0;
|
virtual ModMetadata getMetadata() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LocalModInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol {
|
class LocalModInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol {
|
||||||
protected:
|
protected:
|
||||||
IndexItemHandle m_item;
|
|
||||||
EventListener<ModInstallFilter> m_installListener;
|
EventListener<ModInstallFilter> m_installListener;
|
||||||
Mod* m_mod;
|
Mod* m_mod;
|
||||||
|
|
||||||
|
@ -74,8 +78,6 @@ protected:
|
||||||
|
|
||||||
void onUpdateProgress(ModInstallEvent* event);
|
void onUpdateProgress(ModInstallEvent* event);
|
||||||
void onUpdate(CCObject*);
|
void onUpdate(CCObject*);
|
||||||
void onCancel(CCObject*);
|
|
||||||
void doUpdate();
|
|
||||||
|
|
||||||
|
|
||||||
void FLAlert_Clicked(FLAlertLayer*, bool) override;
|
void FLAlert_Clicked(FLAlertLayer*, bool) override;
|
||||||
|
@ -91,16 +93,12 @@ public:
|
||||||
|
|
||||||
class IndexItemInfoPopup : public ModInfoPopup {
|
class IndexItemInfoPopup : public ModInfoPopup {
|
||||||
protected:
|
protected:
|
||||||
IndexItemHandle m_item;
|
|
||||||
EventListener<ModInstallFilter> m_installListener;
|
EventListener<ModInstallFilter> m_installListener;
|
||||||
|
|
||||||
bool init(IndexItemHandle item, ModListLayer* list);
|
bool init(IndexItemHandle item, ModListLayer* list);
|
||||||
|
|
||||||
void onInstallProgress(ModInstallEvent* event);
|
void onInstallProgress(ModInstallEvent* event);
|
||||||
void onInstall(CCObject*);
|
void onInstall(CCObject*);
|
||||||
void onCancel(CCObject*);
|
|
||||||
|
|
||||||
void preInstall();
|
|
||||||
|
|
||||||
CCNode* createLogo(CCSize const& size) override;
|
CCNode* createLogo(CCSize const& size) override;
|
||||||
ModMetadata getMetadata() const override;
|
ModMetadata getMetadata() const override;
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#include <Geode/ui/GeodeUI.hpp>
|
#include <Geode/ui/GeodeUI.hpp>
|
||||||
#include <loader/LoaderImpl.hpp>
|
#include <loader/LoaderImpl.hpp>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "../info/TagNode.hpp"
|
|
||||||
#include "../info/DevProfilePopup.hpp"
|
#include "../info/DevProfilePopup.hpp"
|
||||||
|
|
||||||
// InstallListCell
|
// InstallListCell
|
||||||
|
@ -27,8 +26,11 @@ void InstallListCell::setupInfo(
|
||||||
std::variant<VersionInfo, ComparableVersionInfo> version,
|
std::variant<VersionInfo, ComparableVersionInfo> version,
|
||||||
bool inactive
|
bool inactive
|
||||||
) {
|
) {
|
||||||
|
m_inactive = inactive;
|
||||||
m_menu = CCMenu::create();
|
m_menu = CCMenu::create();
|
||||||
m_menu->setPosition(m_width - 10.f, m_height / 2);
|
m_menu->setPosition(0, 0);
|
||||||
|
m_menu->setAnchorPoint({ .0f, .0f });
|
||||||
|
m_menu->setContentSize({m_width, m_height});
|
||||||
this->addChild(m_menu);
|
this->addChild(m_menu);
|
||||||
|
|
||||||
auto logoSize = this->getLogoSize();
|
auto logoSize = this->getLogoSize();
|
||||||
|
@ -41,15 +43,15 @@ void InstallListCell::setupInfo(
|
||||||
}
|
}
|
||||||
this->addChild(logoSpr);
|
this->addChild(logoSpr);
|
||||||
|
|
||||||
auto titleLabel = CCLabelBMFont::create(name.c_str(), "bigFont.fnt");
|
m_titleLabel = CCLabelBMFont::create(name.c_str(), "bigFont.fnt");
|
||||||
titleLabel->setAnchorPoint({ .0f, .5f });
|
m_titleLabel->setAnchorPoint({ .0f, .5f });
|
||||||
titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
m_titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
||||||
titleLabel->setPositionY(m_height / 2);
|
m_titleLabel->setPositionY(m_height / 2);
|
||||||
titleLabel->limitLabelWidth(m_width / 2 - 70.f, .4f, .1f);
|
m_titleLabel->limitLabelWidth(m_width / 2 - 70.f, .4f, .1f);
|
||||||
if (inactive) {
|
if (inactive) {
|
||||||
titleLabel->setColor({ 163, 163, 163 });
|
m_titleLabel->setColor({ 163, 163, 163 });
|
||||||
}
|
}
|
||||||
this->addChild(titleLabel);
|
this->addChild(m_titleLabel);
|
||||||
|
|
||||||
m_developerBtn = nullptr;
|
m_developerBtn = nullptr;
|
||||||
if (developer) {
|
if (developer) {
|
||||||
|
@ -64,43 +66,55 @@ void InstallListCell::setupInfo(
|
||||||
creatorLabel, this, menu_selector(InstallListCell::onViewDev)
|
creatorLabel, this, menu_selector(InstallListCell::onViewDev)
|
||||||
);
|
);
|
||||||
m_developerBtn->setPosition(
|
m_developerBtn->setPosition(
|
||||||
titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 3.f +
|
m_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f +
|
||||||
creatorLabel->getScaledContentSize().width / 2 -
|
creatorLabel->getScaledContentSize().width / 2,
|
||||||
m_menu->getPositionX(),
|
m_height / 2
|
||||||
-0.5f
|
|
||||||
);
|
);
|
||||||
m_menu->addChild(m_developerBtn);
|
m_menu->addChild(m_developerBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto versionLabel = CCLabelBMFont::create(
|
this->setupVersion(version);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstallListCell::setupVersion(std::variant<VersionInfo, ComparableVersionInfo> version) {
|
||||||
|
if (m_versionLabel) {
|
||||||
|
m_versionLabel->removeFromParent();
|
||||||
|
m_versionLabel = nullptr;
|
||||||
|
}
|
||||||
|
if (m_tagLabel) {
|
||||||
|
m_tagLabel->removeFromParent();
|
||||||
|
m_tagLabel = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_versionLabel = CCLabelBMFont::create(
|
||||||
std::holds_alternative<VersionInfo>(version) ?
|
std::holds_alternative<VersionInfo>(version) ?
|
||||||
std::get<VersionInfo>(version).toString(false).c_str() :
|
std::get<VersionInfo>(version).toString(false).c_str() :
|
||||||
std::get<ComparableVersionInfo>(version).toString().c_str(),
|
std::get<ComparableVersionInfo>(version).toString().c_str(),
|
||||||
"bigFont.fnt"
|
"bigFont.fnt"
|
||||||
);
|
);
|
||||||
versionLabel->setAnchorPoint({ .0f, .5f });
|
m_versionLabel->setAnchorPoint({ .0f, .5f });
|
||||||
versionLabel->setScale(.2f);
|
m_versionLabel->setScale(.2f);
|
||||||
versionLabel->setPosition(
|
m_versionLabel->setPosition(
|
||||||
titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 3.f +
|
m_titleLabel->getPositionX() + m_titleLabel->getScaledContentSize().width + 3.f +
|
||||||
(m_developerBtn ? m_developerBtn->getScaledContentSize().width + 3.f : 0.f),
|
(m_developerBtn ? m_developerBtn->getScaledContentSize().width + 3.f : 0.f),
|
||||||
titleLabel->getPositionY() - 1.f
|
m_titleLabel->getPositionY() - 1.f
|
||||||
);
|
);
|
||||||
versionLabel->setColor({ 0, 255, 0 });
|
m_versionLabel->setColor({ 0, 255, 0 });
|
||||||
if (inactive) {
|
if (m_inactive) {
|
||||||
versionLabel->setColor({ 0, 163, 0 });
|
m_versionLabel->setColor({ 0, 163, 0 });
|
||||||
}
|
}
|
||||||
this->addChild(versionLabel);
|
this->addChild(m_versionLabel);
|
||||||
|
|
||||||
if (!std::holds_alternative<VersionInfo>(version)) return;
|
if (!std::holds_alternative<VersionInfo>(version)) return;
|
||||||
if (auto tag = std::get<VersionInfo>(version).getTag()) {
|
if (auto tag = std::get<VersionInfo>(version).getTag()) {
|
||||||
auto tagLabel = TagNode::create(tag->toString());
|
m_tagLabel = TagNode::create(tag->toString());
|
||||||
tagLabel->setAnchorPoint({.0f, .5f});
|
m_tagLabel->setAnchorPoint({.0f, .5f});
|
||||||
tagLabel->setScale(.2f);
|
m_tagLabel->setScale(.2f);
|
||||||
tagLabel->setPosition(
|
m_tagLabel->setPosition(
|
||||||
versionLabel->getPositionX() + versionLabel->getScaledContentSize().width + 3.f,
|
m_versionLabel->getPositionX() + m_versionLabel->getScaledContentSize().width + 3.f,
|
||||||
versionLabel->getPositionY()
|
m_versionLabel->getPositionY()
|
||||||
);
|
);
|
||||||
this->addChild(tagLabel);
|
this->addChild(m_tagLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +148,7 @@ bool ModInstallListCell::init(Mod* mod, InstallListPopup* list, CCSize const& si
|
||||||
this->setupInfo(mod->getMetadata(), true);
|
this->setupInfo(mod->getMetadata(), true);
|
||||||
auto message = CCLabelBMFont::create("Installed", "bigFont.fnt");
|
auto message = CCLabelBMFont::create("Installed", "bigFont.fnt");
|
||||||
message->setAnchorPoint({ 1.f, .5f });
|
message->setAnchorPoint({ 1.f, .5f });
|
||||||
message->setPositionX(m_menu->getPositionX());
|
message->setPositionX(m_width - 10.0f);
|
||||||
message->setPositionY(16.f);
|
message->setPositionY(16.f);
|
||||||
message->setScale(0.4f);
|
message->setScale(0.4f);
|
||||||
message->setColor({ 163, 163, 163 });
|
message->setColor({ 163, 163, 163 });
|
||||||
|
@ -174,23 +188,38 @@ bool IndexItemInstallListCell::init(
|
||||||
return false;
|
return false;
|
||||||
m_item = item;
|
m_item = item;
|
||||||
this->setupInfo(item->getMetadata(), item->isInstalled());
|
this->setupInfo(item->getMetadata(), item->isInstalled());
|
||||||
if (item->isInstalled()) {
|
|
||||||
auto message = CCLabelBMFont::create("Installed", "bigFont.fnt");
|
// TODO: show installed label properly
|
||||||
message->setAnchorPoint({ 1.f, .5f });
|
// if (item->isInstalled()) {
|
||||||
message->setPositionX(m_menu->getPositionX());
|
// auto message = CCLabelBMFont::create("Installed", "bigFont.fnt");
|
||||||
message->setPositionY(16.f);
|
// message->setAnchorPoint({ 1.f, .5f });
|
||||||
message->setScale(0.4f);
|
// message->setPositionX(m_width - 10.0f);
|
||||||
message->setColor({ 163, 163, 163 });
|
// message->setPositionY(16.f);
|
||||||
this->addChild(message);
|
// message->setScale(0.4f);
|
||||||
return true;
|
// message->setColor({ 163, 163, 163 });
|
||||||
}
|
// this->addChild(message);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
m_toggle = CCMenuItemToggler::createWithStandardSprites(
|
m_toggle = CCMenuItemToggler::createWithStandardSprites(
|
||||||
m_layer,
|
m_layer,
|
||||||
menu_selector(InstallListPopup::onCellToggle),
|
menu_selector(InstallListPopup::onCellToggle),
|
||||||
.6f
|
.6f
|
||||||
);
|
);
|
||||||
m_toggle->setPosition(-m_toggle->getScaledContentSize().width / 2, 0.f);
|
m_toggle->setAnchorPoint({1.f, .5f});
|
||||||
|
m_toggle->setPosition(m_width - 5, m_height / 2);
|
||||||
|
|
||||||
|
// recycling sprites in my Geode?? noo never
|
||||||
|
auto versionSelectSpr = EditorButtonSprite::createWithSpriteFrameName(
|
||||||
|
"filters.png"_spr, 1.0f, EditorBaseColor::Gray
|
||||||
|
);
|
||||||
|
versionSelectSpr->setScale(.7f);
|
||||||
|
|
||||||
|
auto versionSelectBtn =
|
||||||
|
CCMenuItemSpriteExtra::create(versionSelectSpr, this, menu_selector(IndexItemInstallListCell::onSelectVersion));
|
||||||
|
versionSelectBtn->setAnchorPoint({1.f, .5f});
|
||||||
|
versionSelectBtn->setPosition({m_toggle->getPositionX() - m_toggle->getContentSize().width - 5, m_height / 2});
|
||||||
|
m_menu->addChild(versionSelectBtn);
|
||||||
|
|
||||||
switch (importance) {
|
switch (importance) {
|
||||||
case ModMetadata::Dependency::Importance::Required:
|
case ModMetadata::Dependency::Importance::Required:
|
||||||
|
@ -207,13 +236,18 @@ bool IndexItemInstallListCell::init(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item->isInstalled()) {
|
||||||
|
m_toggle->setClickable(false);
|
||||||
|
m_toggle->toggle(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET) == 0) {
|
if (m_item->getAvailablePlatforms().count(GEODE_PLATFORM_TARGET) == 0) {
|
||||||
m_toggle->setClickable(false);
|
m_toggle->setClickable(false);
|
||||||
m_toggle->toggle(false);
|
m_toggle->toggle(false);
|
||||||
|
|
||||||
auto message = CCLabelBMFont::create("N/A", "bigFont.fnt");
|
auto message = CCLabelBMFont::create("N/A", "bigFont.fnt");
|
||||||
message->setAnchorPoint({ 1.f, .5f });
|
message->setAnchorPoint({ 1.f, .5f });
|
||||||
message->setPositionX(m_menu->getPositionX() - m_toggle->getScaledContentSize().width - 5.f);
|
message->setPositionX(m_width - 5.f);
|
||||||
message->setPositionY(16.f);
|
message->setPositionY(16.f);
|
||||||
message->setScale(0.4f);
|
message->setScale(0.4f);
|
||||||
message->setColor({ 240, 31, 31 });
|
message->setColor({ 240, 31, 31 });
|
||||||
|
@ -269,6 +303,15 @@ IndexItemHandle IndexItemInstallListCell::getItem() {
|
||||||
return m_item;
|
return m_item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IndexItemInstallListCell::setVersionFromItem(IndexItemHandle item) {
|
||||||
|
this->setupVersion(item->getMetadata().getVersion());
|
||||||
|
m_item = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexItemInstallListCell::onSelectVersion(CCObject*) {
|
||||||
|
SelectVersionPopup::create(m_item->getMetadata().getID(), this)->show();
|
||||||
|
}
|
||||||
|
|
||||||
// UnknownInstallListCell
|
// UnknownInstallListCell
|
||||||
|
|
||||||
bool UnknownInstallListCell::init(
|
bool UnknownInstallListCell::init(
|
||||||
|
@ -317,3 +360,50 @@ std::string UnknownInstallListCell::getID() const {
|
||||||
std::string UnknownInstallListCell::getDeveloper() const {
|
std::string UnknownInstallListCell::getDeveloper() const {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectVersionCell
|
||||||
|
|
||||||
|
bool SelectVersionCell::init(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size) {
|
||||||
|
if (!InstallListCell::init(nullptr, size))
|
||||||
|
return false;
|
||||||
|
m_item = item;
|
||||||
|
m_versionPopup = versionPopup;
|
||||||
|
this->setupInfo(item->getMetadata(), item->isInstalled());
|
||||||
|
|
||||||
|
auto selectSpr = ButtonSprite::create(
|
||||||
|
"Select", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .6f
|
||||||
|
);
|
||||||
|
selectSpr->setScale(.6f);
|
||||||
|
|
||||||
|
auto selectBtn = CCMenuItemSpriteExtra::create(
|
||||||
|
selectSpr, this, menu_selector(SelectVersionCell::onSelected)
|
||||||
|
);
|
||||||
|
selectBtn->setAnchorPoint({1.f, .5f});
|
||||||
|
selectBtn->setPosition({m_width - 5, m_height / 2});
|
||||||
|
m_menu->addChild(selectBtn);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectVersionCell::onSelected(CCObject*) {
|
||||||
|
m_versionPopup->selectItem(m_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectVersionCell* SelectVersionCell::create(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size) {
|
||||||
|
auto ret = new SelectVersionCell();
|
||||||
|
if (ret->init(item, versionPopup, size)) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCNode* SelectVersionCell::createLogo(CCSize const& size) {
|
||||||
|
return geode::createIndexItemLogo(m_item, size);
|
||||||
|
}
|
||||||
|
std::string SelectVersionCell::getID() const {
|
||||||
|
return m_item->getMetadata().getID();
|
||||||
|
}
|
||||||
|
std::string SelectVersionCell::getDeveloper() const {
|
||||||
|
return m_item->getMetadata().getDeveloper();
|
||||||
|
}
|
|
@ -6,6 +6,8 @@
|
||||||
#include <Geode/loader/ModMetadata.hpp>
|
#include <Geode/loader/ModMetadata.hpp>
|
||||||
#include <Geode/loader/Index.hpp>
|
#include <Geode/loader/Index.hpp>
|
||||||
|
|
||||||
|
#include "../info/TagNode.hpp"
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
class InstallListPopup;
|
class InstallListPopup;
|
||||||
|
@ -17,10 +19,14 @@ class InstallListCell : public CCLayer {
|
||||||
protected:
|
protected:
|
||||||
float m_width;
|
float m_width;
|
||||||
float m_height;
|
float m_height;
|
||||||
InstallListPopup* m_layer;
|
InstallListPopup* m_layer = nullptr;
|
||||||
CCMenu* m_menu;
|
CCMenu* m_menu = nullptr;
|
||||||
CCMenuItemSpriteExtra* m_developerBtn;
|
CCMenuItemSpriteExtra* m_developerBtn = nullptr;
|
||||||
|
CCLabelBMFont* m_titleLabel = nullptr;
|
||||||
|
CCLabelBMFont* m_versionLabel = nullptr;
|
||||||
|
TagNode* m_tagLabel = nullptr;
|
||||||
CCMenuItemToggler* m_toggle = nullptr;
|
CCMenuItemToggler* m_toggle = nullptr;
|
||||||
|
bool m_inactive = false;
|
||||||
|
|
||||||
void setupInfo(
|
void setupInfo(
|
||||||
std::string name,
|
std::string name,
|
||||||
|
@ -29,6 +35,8 @@ protected:
|
||||||
bool inactive
|
bool inactive
|
||||||
);
|
);
|
||||||
|
|
||||||
|
void setupVersion(std::variant<VersionInfo, ComparableVersionInfo> version);
|
||||||
|
|
||||||
bool init(InstallListPopup* list, CCSize const& size);
|
bool init(InstallListPopup* list, CCSize const& size);
|
||||||
void setupInfo(ModMetadata const& metadata, bool inactive);
|
void setupInfo(ModMetadata const& metadata, bool inactive);
|
||||||
void draw() override;
|
void draw() override;
|
||||||
|
@ -90,6 +98,9 @@ public:
|
||||||
[[nodiscard]] std::string getDeveloper() const override;
|
[[nodiscard]] std::string getDeveloper() const override;
|
||||||
|
|
||||||
IndexItemHandle getItem();
|
IndexItemHandle getItem();
|
||||||
|
void setVersionFromItem(IndexItemHandle item);
|
||||||
|
|
||||||
|
void onSelectVersion(CCObject*);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,3 +123,23 @@ public:
|
||||||
[[nodiscard]] std::string getID() const override;
|
[[nodiscard]] std::string getID() const override;
|
||||||
[[nodiscard]] std::string getDeveloper() const override;
|
[[nodiscard]] std::string getDeveloper() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SelectVersionPopup;
|
||||||
|
/**
|
||||||
|
* Select version list item
|
||||||
|
*/
|
||||||
|
class SelectVersionCell : public InstallListCell {
|
||||||
|
protected:
|
||||||
|
IndexItemHandle m_item;
|
||||||
|
SelectVersionPopup* m_versionPopup;
|
||||||
|
|
||||||
|
bool init(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size);
|
||||||
|
|
||||||
|
void onSelected(CCObject*);
|
||||||
|
public:
|
||||||
|
static SelectVersionCell* create(IndexItemHandle item, SelectVersionPopup* versionPopup, CCSize const& size);
|
||||||
|
|
||||||
|
CCNode* createLogo(CCSize const& size) override;
|
||||||
|
[[nodiscard]] std::string getID() const override;
|
||||||
|
[[nodiscard]] std::string getDeveloper() const override;
|
||||||
|
};
|
|
@ -112,7 +112,8 @@ CCArray* InstallListPopup::createCells(std::unordered_map<std::string, InstallLi
|
||||||
queued.insert(item.id);
|
queued.insert(item.id);
|
||||||
|
|
||||||
// installed
|
// installed
|
||||||
if (item.mod && !item.mod->isUninstalled()) {
|
// TODO: we should be able to select a different version even if its installed
|
||||||
|
if (/*item.mod && !item.mod->isUninstalled()*/item.mod->getMetadata().getID() == "geode.loader") {
|
||||||
bottom.push_back(ModInstallListCell::create(item.mod, this, this->getCellSize()));
|
bottom.push_back(ModInstallListCell::create(item.mod, this, this->getCellSize()));
|
||||||
for (auto const& dep : item.mod->getMetadata().getDependencies()) {
|
for (auto const& dep : item.mod->getMetadata().getDependencies()) {
|
||||||
queue.push(dep);
|
queue.push(dep);
|
||||||
|
@ -204,7 +205,7 @@ void InstallListPopup::onInstall(cocos2d::CCObject* obj) {
|
||||||
CCArray* entries = m_list->m_entries;
|
CCArray* entries = m_list->m_entries;
|
||||||
for (size_t i = entries->count(); i > 0; i--) {
|
for (size_t i = entries->count(); i > 0; i--) {
|
||||||
auto* itemCell = typeinfo_cast<IndexItemInstallListCell*>(entries->objectAtIndex(i - 1));
|
auto* itemCell = typeinfo_cast<IndexItemInstallListCell*>(entries->objectAtIndex(i - 1));
|
||||||
if (!itemCell || !itemCell->isIncluded())
|
if (!itemCell || !itemCell->isIncluded() || itemCell->getItem()->isInstalled())
|
||||||
continue;
|
continue;
|
||||||
IndexItemHandle item = itemCell->getItem();
|
IndexItemHandle item = itemCell->getItem();
|
||||||
list.list.push_back(item);
|
list.list.push_back(item);
|
||||||
|
@ -227,3 +228,70 @@ InstallListPopup* InstallListPopup::create(
|
||||||
ret->autorelease();
|
ret->autorelease();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SelectVersionPopup
|
||||||
|
|
||||||
|
bool SelectVersionPopup::setup(std::string const& modID, IndexItemInstallListCell* installCell) {
|
||||||
|
m_modID = modID;
|
||||||
|
m_installCell = installCell;
|
||||||
|
|
||||||
|
this->setTitle("Select Version");
|
||||||
|
|
||||||
|
this->createList();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectVersionPopup::createList() {
|
||||||
|
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||||
|
|
||||||
|
if (m_listParent) {
|
||||||
|
m_listParent->removeFromParent();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_listParent = CCNode::create();
|
||||||
|
m_mainLayer->addChild(m_listParent);
|
||||||
|
|
||||||
|
auto items = this->createCells();
|
||||||
|
m_list = ListView::create(
|
||||||
|
items,
|
||||||
|
this->getCellSize().height,
|
||||||
|
this->getListSize().width,
|
||||||
|
this->getListSize().height
|
||||||
|
);
|
||||||
|
m_list->setPosition(winSize / 2 - m_list->getScaledContentSize() / 2);
|
||||||
|
m_listParent->addChild(m_list);
|
||||||
|
|
||||||
|
addListBorders(m_listParent, winSize / 2, m_list->getScaledContentSize());
|
||||||
|
}
|
||||||
|
|
||||||
|
CCArray* SelectVersionPopup::createCells() {
|
||||||
|
auto cells = CCArray::create();
|
||||||
|
for (auto& item : ranges::reverse(Index::get()->getItemsByModID(m_modID))) {
|
||||||
|
cells->addObject(SelectVersionCell::create(item, this, this->getCellSize()));
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
CCSize SelectVersionPopup::getCellSize() const {
|
||||||
|
return { getListSize().width, 30.f };
|
||||||
|
}
|
||||||
|
CCSize SelectVersionPopup::getListSize() const {
|
||||||
|
return { 340.f, 170.f };
|
||||||
|
}
|
||||||
|
|
||||||
|
void SelectVersionPopup::selectItem(IndexItemHandle item) {
|
||||||
|
this->onBtn2(nullptr);
|
||||||
|
|
||||||
|
m_installCell->setVersionFromItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectVersionPopup* SelectVersionPopup::create(std::string const& modID, IndexItemInstallListCell* installCell) {
|
||||||
|
auto ret = new SelectVersionPopup();
|
||||||
|
if (!ret->init(380.f, 250.f, modID, installCell)) {
|
||||||
|
CC_SAFE_DELETE(ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
ret->autorelease();
|
||||||
|
return ret;
|
||||||
|
}
|
|
@ -28,3 +28,24 @@ public:
|
||||||
|
|
||||||
static InstallListPopup* create(IndexItemHandle item, MiniFunction<void(IndexInstallList const&)> onInstall);
|
static InstallListPopup* create(IndexItemHandle item, MiniFunction<void(IndexInstallList const&)> onInstall);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SelectVersionPopup : public Popup<std::string const&, IndexItemInstallListCell*> {
|
||||||
|
protected:
|
||||||
|
std::string m_modID;
|
||||||
|
CCNode* m_listParent;
|
||||||
|
ListView* m_list;
|
||||||
|
IndexItemInstallListCell* m_installCell;
|
||||||
|
|
||||||
|
bool setup(std::string const& modID, IndexItemInstallListCell* installCell) override;
|
||||||
|
|
||||||
|
void createList();
|
||||||
|
CCArray* createCells();
|
||||||
|
CCSize getCellSize() const;
|
||||||
|
CCSize getListSize() const;
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
void selectItem(IndexItemHandle item);
|
||||||
|
|
||||||
|
static SelectVersionPopup* create(std::string const& modID, IndexItemInstallListCell* installCell);
|
||||||
|
};
|
||||||
|
|
|
@ -227,11 +227,7 @@ void ModCell::onEnable(CCObject* sender) {
|
||||||
else {
|
else {
|
||||||
tryOrAlert(m_mod->disable(), "Error disabling mod");
|
tryOrAlert(m_mod->disable(), "Error disabling mod");
|
||||||
}
|
}
|
||||||
Loader::get()->queueInMainThread([this]() {
|
m_layer->reloadList();
|
||||||
if (m_layer) {
|
|
||||||
m_layer->updateAllStates();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModCell::onUnresolvedInfo(CCObject*) {
|
void ModCell::onUnresolvedInfo(CCObject*) {
|
||||||
|
@ -278,9 +274,9 @@ bool ModCell::init(
|
||||||
return false;
|
return false;
|
||||||
m_mod = mod;
|
m_mod = mod;
|
||||||
|
|
||||||
this->setupInfo(mod->getMetadata(), false, display, m_mod->isUninstalled());
|
this->setupInfo(mod->getMetadata(), false, display, mod->getRequestedAction() != ModRequestedAction::None);
|
||||||
|
|
||||||
if (mod->isUninstalled()) {
|
if (mod->getRequestedAction() != ModRequestedAction::None) {
|
||||||
auto restartSpr = ButtonSprite::create("Restart", "bigFont.fnt", "GJ_button_03.png", .8f);
|
auto restartSpr = ButtonSprite::create("Restart", "bigFont.fnt", "GJ_button_03.png", .8f);
|
||||||
restartSpr->setScale(.65f);
|
restartSpr->setScale(.65f);
|
||||||
|
|
||||||
|
@ -317,14 +313,16 @@ bool ModCell::init(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (m_mod->wasSuccessfullyLoaded() && m_mod->supportsDisabling() && !m_mod->isUninstalled()) {
|
if (m_mod->wasSuccessfullyLoaded() && m_mod->getMetadata().getID() != "geode.loader") {
|
||||||
m_enableToggle =
|
m_enableToggle =
|
||||||
CCMenuItemToggler::createWithStandardSprites(this, menu_selector(ModCell::onEnable), .7f);
|
CCMenuItemToggler::createWithStandardSprites(this, menu_selector(ModCell::onEnable), .7f);
|
||||||
m_enableToggle->setPosition(-45.f, 0.f);
|
m_enableToggle->setPosition(-45.f, 0.f);
|
||||||
m_menu->addChild(m_enableToggle);
|
m_menu->addChild(m_enableToggle);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
auto exMark = CCSprite::createWithSpriteFrameName("exMark_001.png");
|
auto exMark = CCSprite::createWithSpriteFrameName("exMark_001.png");
|
||||||
exMark->setScale(.5f);
|
exMark->setScale(.5f);
|
||||||
|
|
|
@ -198,7 +198,7 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
|
||||||
std::multimap<int, IndexItemHandle> sorted;
|
std::multimap<int, IndexItemHandle> sorted;
|
||||||
|
|
||||||
auto index = Index::get();
|
auto index = Index::get();
|
||||||
for (auto const& item : index->getItems()) {
|
for (auto const& item : index->getLatestItems()) {
|
||||||
if (auto match = queryMatch(query, item)) {
|
if (auto match = queryMatch(query, item)) {
|
||||||
sorted.insert({ match.value(), item });
|
sorted.insert({ match.value(), item });
|
||||||
}
|
}
|
||||||
|
@ -444,13 +444,18 @@ void ModListLayer::createSearchControl() {
|
||||||
this->addChild(m_searchInput);
|
this->addChild(m_searchInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
|
void ModListLayer::reloadList(bool keepScroll, std::optional<ModListQuery> const& query) {
|
||||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
m_query = query.value();
|
m_query = query.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float scroll = 0.0f;
|
||||||
|
if (keepScroll && m_list) {
|
||||||
|
scroll = m_list->m_listView->m_tableView->m_contentLayer->getPositionY();
|
||||||
|
}
|
||||||
|
|
||||||
// set search query
|
// set search query
|
||||||
m_query.keywords =
|
m_query.keywords =
|
||||||
m_searchInput &&
|
m_searchInput &&
|
||||||
|
@ -489,6 +494,12 @@ void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
|
||||||
m_listLabel->setVisible(false);
|
m_listLabel->setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keepScroll) {
|
||||||
|
list->m_tableView->m_contentLayer->setPosition(
|
||||||
|
{ 0.0f, scroll }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// update index if needed
|
// update index if needed
|
||||||
if (g_tab == ModListType::Download && !Index::get()->hasTriedToUpdate()) {
|
if (g_tab == ModListType::Download && !Index::get()->hasTriedToUpdate()) {
|
||||||
m_listLabel->setVisible(true);
|
m_listLabel->setVisible(true);
|
||||||
|
@ -658,7 +669,7 @@ void ModListLayer::onTab(CCObject* pSender) {
|
||||||
if (pSender) {
|
if (pSender) {
|
||||||
g_tab = static_cast<ModListType>(pSender->getTag());
|
g_tab = static_cast<ModListType>(pSender->getTag());
|
||||||
}
|
}
|
||||||
this->reloadList();
|
this->reloadList(false);
|
||||||
|
|
||||||
auto toggleTab = [this](CCMenuItemToggler* member) -> void {
|
auto toggleTab = [this](CCMenuItemToggler* member) -> void {
|
||||||
auto isSelected = member->getTag() == static_cast<int>(g_tab);
|
auto isSelected = member->getTag() == static_cast<int>(g_tab);
|
||||||
|
@ -670,7 +681,7 @@ void ModListLayer::onTab(CCObject* pSender) {
|
||||||
targetMenu->addChild(member);
|
targetMenu->addChild(member);
|
||||||
member->release();
|
member->release();
|
||||||
}
|
}
|
||||||
if (isSelected)
|
if (isSelected && m_tabsGradientStencil)
|
||||||
m_tabsGradientStencil->setPosition(member->m_onButton->convertToWorldSpace({0.f, -1.f}));
|
m_tabsGradientStencil->setPosition(member->m_onButton->convertToWorldSpace({0.f, -1.f}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -686,7 +697,7 @@ void ModListLayer::keyDown(enumKeyCodes key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModListLayer::textChanged(CCTextInputNode* input) {
|
void ModListLayer::textChanged(CCTextInputNode* input) {
|
||||||
this->reloadList();
|
this->reloadList(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructors etc.
|
// Constructors etc.
|
||||||
|
|
|
@ -94,5 +94,5 @@ public:
|
||||||
ModListDisplay getDisplay() const;
|
ModListDisplay getDisplay() const;
|
||||||
ModListQuery& getQuery();
|
ModListQuery& getQuery();
|
||||||
|
|
||||||
void reloadList(std::optional<ModListQuery> const& query = std::nullopt);
|
void reloadList(bool keepScroll = true, std::optional<ModListQuery> const& query = std::nullopt);
|
||||||
};
|
};
|
||||||
|
|
|
@ -175,7 +175,7 @@ void SearchFilterPopup::onPlatformToggle(CCObject* sender) {
|
||||||
|
|
||||||
void SearchFilterPopup::onClose(CCObject* sender) {
|
void SearchFilterPopup::onClose(CCObject* sender) {
|
||||||
Popup::onClose(sender);
|
Popup::onClose(sender);
|
||||||
m_modLayer->reloadList();
|
m_modLayer->reloadList(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer, ModListType type) {
|
SearchFilterPopup* SearchFilterPopup::create(ModListLayer* layer, ModListType type) {
|
||||||
|
|
|
@ -48,7 +48,6 @@ Result<> web::fetchFile(
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &file);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &file);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBinaryData);
|
||||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
|
|
||||||
if (prog) {
|
if (prog) {
|
||||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||||
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, utils::fetch::progress);
|
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, utils::fetch::progress);
|
||||||
|
@ -81,7 +80,6 @@ Result<ByteVector> web::fetchBytes(std::string const& url) {
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeBytes);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
|
|
||||||
auto res = curl_easy_perform(curl);
|
auto res = curl_easy_perform(curl);
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
@ -120,7 +118,6 @@ Result<std::string> web::fetch(std::string const& url) {
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ret);
|
||||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeString);
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, utils::fetch::writeString);
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
|
|
||||||
auto res = curl_easy_perform(curl);
|
auto res = curl_easy_perform(curl);
|
||||||
if (res != CURLE_OK) {
|
if (res != CURLE_OK) {
|
||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
@ -162,10 +159,11 @@ private:
|
||||||
SentAsyncWebRequest* m_self;
|
SentAsyncWebRequest* m_self;
|
||||||
|
|
||||||
mutable std::mutex m_mutex;
|
mutable std::mutex m_mutex;
|
||||||
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target =
|
AsyncWebRequestData m_extra;
|
||||||
std::monostate();
|
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
|
||||||
std::vector<std::string> m_httpHeaders;
|
std::vector<std::string> m_httpHeaders;
|
||||||
|
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
friend class AsyncWebResult;
|
friend class AsyncWebResult;
|
||||||
friend class AsyncWebRequest;
|
friend class AsyncWebRequest;
|
||||||
|
@ -187,7 +185,7 @@ static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUES
|
||||||
static std::mutex RUNNING_REQUESTS_MUTEX;
|
static std::mutex RUNNING_REQUESTS_MUTEX;
|
||||||
|
|
||||||
SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const& req, std::string const& id) :
|
SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const& req, std::string const& id) :
|
||||||
m_id(id), m_url(req.m_url), m_target(req.m_target), m_httpHeaders(req.m_httpHeaders) {
|
m_id(id), m_url(req.m_url), m_target(req.m_target), m_extra(req.extra()), m_httpHeaders(req.m_httpHeaders) {
|
||||||
|
|
||||||
#define AWAIT_RESUME() \
|
#define AWAIT_RESUME() \
|
||||||
{\
|
{\
|
||||||
|
@ -242,8 +240,30 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
||||||
// No need to verify SSL, we trust our domains :-)
|
// No need to verify SSL, we trust our domains :-)
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
||||||
// Github User Agent
|
// User Agent
|
||||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "github_api/1.0");
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, m_extra.m_userAgent.c_str());
|
||||||
|
|
||||||
|
// Headers
|
||||||
|
curl_slist* headers = nullptr;
|
||||||
|
for (auto& header : m_httpHeaders) {
|
||||||
|
headers = curl_slist_append(headers, header.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post request
|
||||||
|
if (m_extra.m_isPostRequest || m_extra.m_customRequest.size()) {
|
||||||
|
if (m_extra.m_isPostRequest) {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, m_extra.m_customRequest.c_str());
|
||||||
|
}
|
||||||
|
if (m_extra.m_isJsonRequest) {
|
||||||
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
||||||
|
}
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, m_extra.m_postFields.c_str());
|
||||||
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, m_extra.m_postFields.size());
|
||||||
|
}
|
||||||
|
|
||||||
// Track progress
|
// Track progress
|
||||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||||
// Follow redirects
|
// Follow redirects
|
||||||
|
@ -251,10 +271,7 @@ SentAsyncWebRequest::Impl::Impl(SentAsyncWebRequest* self, AsyncWebRequest const
|
||||||
// Fail if response code is 4XX or 5XX
|
// Fail if response code is 4XX or 5XX
|
||||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||||
|
|
||||||
curl_slist* headers = nullptr;
|
// Headers end
|
||||||
for (auto& header : m_httpHeaders) {
|
|
||||||
headers = curl_slist_append(headers, header.c_str());
|
|
||||||
}
|
|
||||||
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||||
|
|
||||||
struct ProgressData {
|
struct ProgressData {
|
||||||
|
@ -410,11 +427,50 @@ void SentAsyncWebRequest::error(std::string const& error, int code) {
|
||||||
return m_impl->error(error, code);
|
return m_impl->error(error, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncWebRequestData& AsyncWebRequest::extra() {
|
||||||
|
if (!m_extra) {
|
||||||
|
m_extra = new AsyncWebRequestData();
|
||||||
|
}
|
||||||
|
return *m_extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRequestData const& AsyncWebRequest::extra() const {
|
||||||
|
if (!m_extra) {
|
||||||
|
m_extra = new AsyncWebRequestData();
|
||||||
|
}
|
||||||
|
return *m_extra;
|
||||||
|
}
|
||||||
|
|
||||||
AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
|
AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
|
||||||
m_joinID = requestID;
|
m_joinID = requestID;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncWebRequest& AsyncWebRequest::userAgent(std::string const& userAgent) {
|
||||||
|
this->extra().m_userAgent = userAgent;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRequest& AsyncWebRequest::postRequest() {
|
||||||
|
this->extra().m_isPostRequest = true;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRequest& AsyncWebRequest::customRequest(std::string const& request) {
|
||||||
|
this->extra().m_customRequest = request;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRequest& AsyncWebRequest::postFields(std::string const& fields) {
|
||||||
|
this->extra().m_postFields = fields;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncWebRequest& AsyncWebRequest::postFields(json::Value const& fields) {
|
||||||
|
this->extra().m_isJsonRequest = true;
|
||||||
|
return this->postFields(fields.dump());
|
||||||
|
}
|
||||||
|
|
||||||
AsyncWebRequest& AsyncWebRequest::header(std::string const& header) {
|
AsyncWebRequest& AsyncWebRequest::header(std::string const& header) {
|
||||||
m_httpHeaders.push_back(header);
|
m_httpHeaders.push_back(header);
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -448,8 +504,8 @@ AsyncWebRequest& AsyncWebRequest::cancelled(AsyncCancelled cancelledFunc) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SentAsyncWebRequestHandle AsyncWebRequest::send() {
|
SentAsyncWebRequestHandle AsyncWebRequest::send() {
|
||||||
if (m_sent) return nullptr;
|
if (this->extra().m_sent) return nullptr;
|
||||||
m_sent = true;
|
this->extra().m_sent = true;
|
||||||
|
|
||||||
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
std::lock_guard __(RUNNING_REQUESTS_MUTEX);
|
||||||
|
|
||||||
|
@ -486,6 +542,7 @@ SentAsyncWebRequestHandle AsyncWebRequest::send() {
|
||||||
|
|
||||||
AsyncWebRequest::~AsyncWebRequest() {
|
AsyncWebRequest::~AsyncWebRequest() {
|
||||||
this->send();
|
this->send();
|
||||||
|
delete m_extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebResult<std::monostate> AsyncWebResponse::into(std::ostream& stream) {
|
AsyncWebResult<std::monostate> AsyncWebResponse::into(std::ostream& stream) {
|
||||||
|
|
Loading…
Reference in a new issue