mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-22 15:37:53 -05:00
work on new index
- add PlatformID::from string
This commit is contained in:
parent
f9b6595073
commit
66d12395e1
21 changed files with 614 additions and 1028 deletions
|
@ -22,7 +22,6 @@ file(GLOB SOURCES CONFIGURE_DEPENDS
|
|||
src/loader/*.cpp
|
||||
src/main.cpp
|
||||
src/utils/*.cpp
|
||||
src/index/*.cpp
|
||||
src/ui/nodes/*.cpp
|
||||
src/ui/internal/*.cpp
|
||||
src/ui/internal/credits/*.cpp
|
||||
|
@ -110,7 +109,6 @@ target_include_directories(${PROJECT_NAME} PRIVATE
|
|||
src/internal/
|
||||
src/platform/
|
||||
src/gui/
|
||||
src/index/
|
||||
md4c/src/
|
||||
hash/
|
||||
./ # lilac
|
||||
|
|
|
@ -99,7 +99,8 @@ namespace geode {
|
|||
public:
|
||||
void postFrom(Mod* sender);
|
||||
|
||||
inline void post() {
|
||||
template<class = void>
|
||||
void post() {
|
||||
postFrom(Mod::get());
|
||||
}
|
||||
|
||||
|
|
86
loader/include/Geode/loader/Index.hpp
Normal file
86
loader/include/Geode/loader/Index.hpp
Normal file
|
@ -0,0 +1,86 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
#include "Event.hpp"
|
||||
#include <unordered_set>
|
||||
|
||||
namespace geode {
|
||||
using UpdateFinished = std::monostate;
|
||||
using UpdateProgress = std::pair<uint8_t, std::string>;
|
||||
using UpdateError = std::string;
|
||||
using UpdateStatus = std::variant<UpdateFinished, UpdateProgress, UpdateError>;
|
||||
|
||||
class GEODE_DLL IndexUpdateEvent : public Event {
|
||||
protected:
|
||||
std::string m_sourceRepository;
|
||||
UpdateStatus m_status;
|
||||
|
||||
public:
|
||||
IndexUpdateEvent(
|
||||
std::string const& src,
|
||||
UpdateStatus status
|
||||
);
|
||||
std::string getSource() const;
|
||||
UpdateStatus getStatus() const;
|
||||
};
|
||||
|
||||
class GEODE_DLL IndexUpdateFilter : public EventFilter<IndexUpdateEvent> {
|
||||
public:
|
||||
using Callback = void(IndexUpdateEvent*);
|
||||
|
||||
ListenerResult handle(std::function<Callback> fn, IndexUpdateEvent* event);
|
||||
IndexUpdateFilter();
|
||||
};
|
||||
|
||||
struct GEODE_DLL IndexItem {
|
||||
std::string sourceRepository;
|
||||
ghc::filesystem::path path;
|
||||
ModInfo info;
|
||||
struct {
|
||||
std::string url;
|
||||
std::string hash;
|
||||
std::unordered_set<PlatformID> platforms;
|
||||
} download;
|
||||
bool isFeatured;
|
||||
};
|
||||
|
||||
struct GEODE_DLL IndexSource final {
|
||||
std::string repository;
|
||||
bool isUpToDate = false;
|
||||
|
||||
std::string dirname() const;
|
||||
};
|
||||
|
||||
class GEODE_DLL Index final {
|
||||
protected:
|
||||
// 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())
|
||||
using ItemVersions = std::map<size_t, IndexItem>;
|
||||
|
||||
std::vector<IndexSource> m_sources;
|
||||
std::unordered_map<std::string, ItemVersions> m_items;
|
||||
|
||||
Index();
|
||||
|
||||
void checkSourceUpdates(IndexSource& src);
|
||||
void downloadSource(IndexSource& src);
|
||||
void updateSourceFromLocal(IndexSource& src);
|
||||
void cleanupItems();
|
||||
|
||||
public:
|
||||
static Index* get();
|
||||
|
||||
void addSource(std::string const& repository);
|
||||
void removeSource(std::string const& repository);
|
||||
std::vector<IndexSource> getSources() const;
|
||||
|
||||
std::vector<IndexItem> getItems() const;
|
||||
bool isKnownItem(std::string const& id, std::optional<size_t> version) const;
|
||||
std::optional<IndexItem> getItem(std::string const& id, std::optional<size_t> version) const;
|
||||
|
||||
bool isUpToDate() const;
|
||||
void update(bool force = false);
|
||||
};
|
||||
|
||||
}
|
|
@ -1,88 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "cplatform.h"
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
namespace geode {
|
||||
class PlatformID {
|
||||
public:
|
||||
enum {
|
||||
Unknown = -1,
|
||||
Windows,
|
||||
MacOS,
|
||||
iOS,
|
||||
Android,
|
||||
Linux,
|
||||
};
|
||||
|
||||
using Type = decltype(Unknown);
|
||||
|
||||
Type m_value;
|
||||
|
||||
PlatformID(Type t) {
|
||||
m_value = t;
|
||||
}
|
||||
|
||||
PlatformID& operator=(Type t) {
|
||||
m_value = t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator==(int other) const {
|
||||
return m_value == other;
|
||||
}
|
||||
|
||||
bool operator==(PlatformID const& other) const {
|
||||
return m_value == other.m_value;
|
||||
}
|
||||
|
||||
bool operator<(PlatformID const& other) const {
|
||||
return m_value < other.m_value;
|
||||
}
|
||||
|
||||
bool operator>(PlatformID const& other) const {
|
||||
return m_value > other.m_value;
|
||||
}
|
||||
|
||||
operator int() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static PlatformID from(T t) {
|
||||
return static_cast<Type>(t);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T to() const {
|
||||
return static_cast<T>(m_value);
|
||||
}
|
||||
|
||||
static constexpr char const* toString(Type lp) {
|
||||
switch (lp) {
|
||||
case Unknown: return "Unknown";
|
||||
case Windows: return "Windows";
|
||||
case MacOS: return "MacOS";
|
||||
case iOS: return "iOS";
|
||||
case Android: return "Android";
|
||||
case Linux: return "Linux";
|
||||
default: break;
|
||||
}
|
||||
return "Undefined";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<geode::PlatformID> {
|
||||
inline std::size_t operator()(geode::PlatformID const& id) const {
|
||||
return std::hash<geode::PlatformID::Type>()(id.m_value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#if !defined(__PRETTY_FUNCTION__) && !defined(__GNUC__)
|
||||
#define GEODE_PRETTY_FUNCTION std::string(__FUNCSIG__)
|
||||
#else
|
||||
|
@ -92,7 +13,6 @@ namespace std {
|
|||
// Windows
|
||||
#ifdef GEODE_IS_WINDOWS
|
||||
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::Windows
|
||||
#define GEODE_HIDDEN
|
||||
#define GEODE_INLINE __forceinline
|
||||
#define GEODE_VIRTUAL_CONSTEXPR
|
||||
|
@ -111,7 +31,6 @@ namespace std {
|
|||
|
||||
#elif defined(GEODE_IS_MACOS)
|
||||
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::MacOS
|
||||
#define GEODE_HIDDEN __attribute__((visibility("hidden")))
|
||||
#define GEODE_INLINE inline __attribute__((always_inline))
|
||||
#define GEODE_VIRTUAL_CONSTEXPR constexpr
|
||||
|
@ -130,7 +49,6 @@ namespace std {
|
|||
|
||||
#elif defined(GEODE_IS_IOS)
|
||||
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::iOS
|
||||
#define GEODE_HIDDEN __attribute__((visibility("hidden")))
|
||||
#define GEODE_INLINE inline __attribute__((always_inline))
|
||||
#define GEODE_VIRTUAL_CONSTEXPR constexpr
|
||||
|
@ -149,7 +67,6 @@ namespace std {
|
|||
|
||||
#elif defined(GEODE_IS_ANDROID)
|
||||
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::Android
|
||||
#define GEODE_HIDDEN __attribute__((visibility("hidden")))
|
||||
#define GEODE_INLINE inline __attribute__((always_inline))
|
||||
#define GEODE_VIRTUAL_CONSTEXPR constexpr
|
||||
|
@ -171,3 +88,105 @@ namespace std {
|
|||
#error "Unsupported Platform!"
|
||||
|
||||
#endif
|
||||
|
||||
namespace geode {
|
||||
class PlatformID {
|
||||
public:
|
||||
enum {
|
||||
Unknown = -1,
|
||||
Windows,
|
||||
MacOS,
|
||||
iOS,
|
||||
Android,
|
||||
Linux,
|
||||
};
|
||||
|
||||
using Type = decltype(Unknown);
|
||||
|
||||
Type m_value;
|
||||
|
||||
constexpr PlatformID(Type t) {
|
||||
m_value = t;
|
||||
}
|
||||
|
||||
constexpr PlatformID& operator=(Type t) {
|
||||
m_value = t;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator==(int other) const {
|
||||
return m_value == other;
|
||||
}
|
||||
|
||||
constexpr bool operator==(PlatformID const& other) const {
|
||||
return m_value == other.m_value;
|
||||
}
|
||||
|
||||
constexpr bool operator<(PlatformID const& other) const {
|
||||
return m_value < other.m_value;
|
||||
}
|
||||
|
||||
constexpr bool operator>(PlatformID const& other) const {
|
||||
return m_value > other.m_value;
|
||||
}
|
||||
|
||||
constexpr operator int() const {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse string into PlatformID. String should be all-lowercase, for
|
||||
* example "windows" or "linux"
|
||||
*/
|
||||
static GEODE_DLL PlatformID from(const char* str);
|
||||
static GEODE_DLL PlatformID from(std::string const& str);
|
||||
|
||||
static constexpr char const* toString(Type lp) {
|
||||
switch (lp) {
|
||||
case Unknown: return "Unknown";
|
||||
case Windows: return "Windows";
|
||||
case MacOS: return "MacOS";
|
||||
case iOS: return "iOS";
|
||||
case Android: return "Android";
|
||||
case Linux: return "Linux";
|
||||
default: break;
|
||||
}
|
||||
return "Undefined";
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires requires(T t) {
|
||||
static_cast<Type>(t);
|
||||
}
|
||||
constexpr static PlatformID from(T t) {
|
||||
return static_cast<Type>(t);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
requires requires(Type t) {
|
||||
static_cast<T>(t);
|
||||
}
|
||||
constexpr T to() const {
|
||||
return static_cast<T>(m_value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<geode::PlatformID> {
|
||||
inline std::size_t operator()(geode::PlatformID const& id) const {
|
||||
return std::hash<geode::PlatformID::Type>()(id.m_value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#ifdef GEODE_IS_WINDOWS
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::Windows
|
||||
#elif defined(GEODE_IS_MACOS)
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::MacOS
|
||||
#elif defined(GEODE_IS_IOS)
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::iOS
|
||||
#elif defined(GEODE_IS_ANDROID)
|
||||
#define GEODE_PLATFORM_TARGET PlatformID::Android
|
||||
#endif
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
#include "casts.hpp"
|
||||
#include "../external/json/json.hpp"
|
||||
#include "general.hpp"
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include <cocos2d.h>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "../loader/Event.hpp"
|
||||
|
||||
// support converting ccColor3B / ccColor4B to / from json
|
||||
namespace cocos2d {
|
||||
|
@ -183,7 +184,7 @@ namespace geode {
|
|||
}
|
||||
}
|
||||
|
||||
// Ref
|
||||
// Ref & Bug
|
||||
namespace geode {
|
||||
/**
|
||||
* A smart pointer to a managed CCObject-deriving class. Retains shared
|
||||
|
|
|
@ -71,15 +71,20 @@ namespace geode::utils::file {
|
|||
* @param dir Directory to unzip the contents to
|
||||
*/
|
||||
Result<> extractAllTo(Path const& dir);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unzip file to directory
|
||||
* @param from File to unzip
|
||||
* @param to Directory to unzip to
|
||||
* @returns Ok on success, Error on error
|
||||
*/
|
||||
GEODE_DLL Result<> unzipTo(ghc::filesystem::path const& from, ghc::filesystem::path const& to);
|
||||
/**
|
||||
* Helper method for quickly unzipping a file
|
||||
* @param from ZIP file to unzip
|
||||
* @param to Directory to unzip to
|
||||
* @param deleteZipAfter Whether to delete the zip after unzipping
|
||||
* @returns Succesful result on success, errorful result on error
|
||||
*/
|
||||
static Result<> intoDir(
|
||||
Path const& from,
|
||||
Path const& to,
|
||||
bool deleteZipAfter = false
|
||||
);
|
||||
};
|
||||
|
||||
GEODE_DLL bool openFolder(ghc::filesystem::path const& path);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
#include "Result.hpp"
|
||||
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
|
|
@ -18,7 +18,6 @@ namespace geode::utils::map {
|
|||
* beeing looked for, and false if not.
|
||||
* @returns True if value matching `containFunc` was found,
|
||||
* false if not.
|
||||
* @author HJfod
|
||||
*/
|
||||
template <typename T, typename R>
|
||||
bool contains(std::unordered_map<T, R> const& map, std::function<bool(R)> containFunc) {
|
||||
|
@ -38,7 +37,6 @@ namespace geode::utils::map {
|
|||
* @returns The value matching `selectFunc` if one was found,
|
||||
* otherwise the default value for type R or `nullptr` if R is
|
||||
* a pointer.
|
||||
* @author HJfod
|
||||
*/
|
||||
template <class T, class R>
|
||||
R select(std::unordered_map<T, R> const& map, std::function<bool(R)> selectFunc) {
|
||||
|
@ -58,7 +56,6 @@ namespace geode::utils::map {
|
|||
* return true if the item matches what is
|
||||
* beeing looked for, and false if not.
|
||||
* @returns Vector of all values that matched.
|
||||
* @author HJfod
|
||||
*/
|
||||
template <class T, class R>
|
||||
std::vector<R> selectAll(
|
||||
|
@ -77,10 +74,9 @@ namespace geode::utils::map {
|
|||
* Get all values in a map.
|
||||
* @param map Map to get values from
|
||||
* @returns Vector of all values.
|
||||
* @author HJfod
|
||||
*/
|
||||
template <class T, class R>
|
||||
std::vector<R> getValues(std::unordered_map<T, R> const& map) {
|
||||
std::vector<R> values(std::unordered_map<T, R> const& map) {
|
||||
std::vector<R> res;
|
||||
for (auto const& [_, r] : map) {
|
||||
res.push_back(r);
|
||||
|
@ -92,10 +88,9 @@ namespace geode::utils::map {
|
|||
* Get all keys in a map.
|
||||
* @param map Map to get keys from
|
||||
* @returns Vector of all keys.
|
||||
* @author HJfod
|
||||
*/
|
||||
template <class T, class R>
|
||||
std::vector<T> getKeys(std::unordered_map<T, R> const& map) {
|
||||
std::vector<T> keys(std::unordered_map<T, R> const& map) {
|
||||
std::vector<T> res;
|
||||
for (auto const& [t, _] : map) {
|
||||
res.push_back(t);
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
|
||||
#include <InternalLoader.hpp>
|
||||
#include <array>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
#include <Geode/modify/LoadingLayer.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
||||
bool m_updatingResources;
|
||||
EventListener<ResourceDownloadFilter> m_resourceListener;
|
||||
|
||||
CustomLoadingLayer() : m_updatingResources(false) {}
|
||||
|
||||
|
@ -27,28 +28,14 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
|||
label->setID("geode-loaded-info");
|
||||
this->addChild(label);
|
||||
|
||||
m_fields->m_resourceListener.bind(std::bind(
|
||||
&CustomLoadingLayer::updateResourcesProgress,
|
||||
this, std::placeholders::_1
|
||||
));
|
||||
|
||||
// verify loader resources
|
||||
if (!InternalLoader::get()->verifyLoaderResources(std::bind(
|
||||
&CustomLoadingLayer::updateResourcesProgress, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3
|
||||
))) {
|
||||
// auto bg = CCScale9Sprite::create(
|
||||
// "square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
|
||||
// );
|
||||
// bg->setScale(.6f);
|
||||
// bg->setColor({ 0, 0, 0 });
|
||||
// bg->setOpacity(150);
|
||||
// bg->setPosition(winSize / 2);
|
||||
// this->addChild(bg);
|
||||
|
||||
// m_fields->m_updatingResourcesBG = bg;
|
||||
|
||||
// auto label = CCLabelBMFont::create("", "goldFont.fnt");
|
||||
// label->setScale(1.1f);
|
||||
// bg->addChild(label);
|
||||
|
||||
if (!InternalLoader::get()->verifyLoaderResources()) {
|
||||
m_fields->m_updatingResources = true;
|
||||
|
||||
this->setUpdateText("Downloading Resources");
|
||||
}
|
||||
|
||||
|
@ -67,30 +54,28 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
|||
// );
|
||||
}
|
||||
|
||||
void updateResourcesProgress(UpdateStatus status, std::string const& info, uint8_t progress) {
|
||||
switch (status) {
|
||||
case UpdateStatus::Progress: {
|
||||
this->setUpdateText("Downloading Resources: " + std::to_string(progress) + "%");
|
||||
} break;
|
||||
|
||||
case UpdateStatus::Finished: {
|
||||
this->setUpdateText("Resources Downloaded");
|
||||
m_fields->m_updatingResources = false;
|
||||
this->loadAssets();
|
||||
} break;
|
||||
|
||||
case UpdateStatus::Failed: {
|
||||
InternalLoader::platformMessageBox(
|
||||
"Error updating resources",
|
||||
"Unable to update Geode resources: " + info +
|
||||
".\n"
|
||||
"The game will be loaded as normal, but please be aware "
|
||||
"that it may very likely crash."
|
||||
);
|
||||
this->setUpdateText("Resource Download Failed");
|
||||
m_fields->m_updatingResources = false;
|
||||
this->loadAssets();
|
||||
} break;
|
||||
void updateResourcesProgress(ResourceDownloadEvent* event) {
|
||||
auto status = event->getStatus();
|
||||
if (std::holds_alternative<UpdateProgress>(status)) {
|
||||
auto prog = std::get<UpdateProgress>(status);
|
||||
this->setUpdateText("Downloading Resources: " + std::to_string(prog.first) + "%");
|
||||
}
|
||||
else if (std::holds_alternative<UpdateFinished>(status)) {
|
||||
this->setUpdateText("Resources Downloaded");
|
||||
m_fields->m_updatingResources = false;
|
||||
this->loadAssets();
|
||||
}
|
||||
else {
|
||||
InternalLoader::platformMessageBox(
|
||||
"Error updating resources",
|
||||
"Unable to update Geode resources: " +
|
||||
std::get<UpdateError>(status) + ".\n"
|
||||
"The game will be loaded as normal, but please be aware "
|
||||
"that it may very likely crash."
|
||||
);
|
||||
this->setUpdateText("Resource Download Failed");
|
||||
m_fields->m_updatingResources = false;
|
||||
this->loadAssets();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,703 +0,0 @@
|
|||
// #include "Index.hpp"
|
||||
|
||||
// #include <Geode/binding/FLAlertLayer.hpp>
|
||||
// #include <Geode/loader/Loader.hpp>
|
||||
// #include <Geode/loader/Mod.hpp>
|
||||
// #include <Geode/loader/Index.hpp>
|
||||
// #include <Geode/utils/JsonValidation.hpp>
|
||||
// #include <Geode/utils/web.hpp>
|
||||
// #include <Geode/utils/file.hpp>
|
||||
// #include <Geode/utils/general.hpp>
|
||||
// #include <Geode/external/json/json.hpp>
|
||||
// #include <Geode/utils/map.hpp>
|
||||
// #include <Geode/utils/ranges.hpp>
|
||||
// #include <Geode/utils/string.hpp>
|
||||
// #include <fmt/format.h>
|
||||
// #include <hash.hpp>
|
||||
// #include <thread>
|
||||
|
||||
// #define GITHUB_DONT_RATE_LIMIT_ME_PLS 0
|
||||
|
||||
// template <class Json = nlohmann::json>
|
||||
// static Result<Json> readJSON(ghc::filesystem::path const& path) {
|
||||
// GEODE_UNWRAP_INTO(
|
||||
// std::string indexJsonData,
|
||||
// utils::file::readString(path).expect("Unable to read {}", path.string())
|
||||
// );
|
||||
// try {
|
||||
// return Ok(Json::parse(indexJsonData));
|
||||
// }
|
||||
// catch (std::exception& e) {
|
||||
// return Err("Error parsing JSON: " + std::string(e.what()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// static PlatformID platformFromString(std::string const& str) {
|
||||
// switch (hash(utils::string::trim(utils::string::toLower(str)).c_str())) {
|
||||
// default:
|
||||
// case hash("unknown"): return PlatformID::Unknown;
|
||||
// case hash("windows"): return PlatformID::Windows;
|
||||
// case hash("macos"): return PlatformID::MacOS;
|
||||
// case hash("ios"): return PlatformID::iOS;
|
||||
// case hash("android"): return PlatformID::Android;
|
||||
// case hash("linux"): return PlatformID::Linux;
|
||||
// }
|
||||
// }
|
||||
|
||||
// Index* Index::get() {
|
||||
// static auto ret = new Index();
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
// bool Index::isIndexUpdated() const {
|
||||
// return m_upToDate;
|
||||
// }
|
||||
|
||||
// std::vector<IndexItem> Index::getFeaturedItems() const {
|
||||
// std::vector<IndexItem> items;
|
||||
// items.reserve(m_featured.size());
|
||||
// std::transform(
|
||||
// m_featured.begin(), m_featured.end(), std::back_inserter(items),
|
||||
// [this](auto const& item) {
|
||||
// return this->getKnownItem(item);
|
||||
// }
|
||||
// );
|
||||
// return items;
|
||||
// }
|
||||
|
||||
// bool Index::isFeaturedItem(std::string const& item) const {
|
||||
// return m_featured.count(item);
|
||||
// }
|
||||
|
||||
// void Index::updateIndex(IndexUpdateCallback callback, bool force) {
|
||||
// #define RETURN_ERROR(str) \
|
||||
// std::string err__ = (str); \
|
||||
// if (callback) callback(UpdateStatus::Failed, err__, 0); \
|
||||
// log::info("Index update failed: {}", err__); \
|
||||
// return;
|
||||
|
||||
// // if already updated and no force, let
|
||||
// // delegate know
|
||||
// if (!force && m_upToDate) {
|
||||
// if (callback) {
|
||||
// callback(UpdateStatus::Finished, "Index already updated", 100);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // create directory for the local clone of
|
||||
// // the index
|
||||
// auto indexDir = dirs::getIndexDir();
|
||||
// ghc::filesystem::create_directories(indexDir);
|
||||
|
||||
// #if GITHUB_DONT_RATE_LIMIT_ME_PLS == 1
|
||||
|
||||
// auto err = this->updateIndexFromLocalCache();
|
||||
// if (!err) {
|
||||
// RETURN_ERROR(err);
|
||||
// }
|
||||
|
||||
// m_upToDate = true;
|
||||
// m_updating = false;
|
||||
|
||||
// if (callback) callback(UpdateStatus::Finished, "", 100);
|
||||
// return;
|
||||
|
||||
// #endif
|
||||
|
||||
// // read sha of currently installed commit
|
||||
// std::string currentCommitSHA = "";
|
||||
// if (ghc::filesystem::exists(indexDir / "current")) {
|
||||
// auto data = utils::file::readString(indexDir / "current");
|
||||
// if (data) {
|
||||
// currentCommitSHA = data.unwrap();
|
||||
// }
|
||||
// }
|
||||
|
||||
// web::AsyncWebRequest()
|
||||
// .join("index-update")
|
||||
// .header(fmt::format("If-None-Match: \"{}\"", currentCommitSHA))
|
||||
// .header("Accept: application/vnd.github.sha")
|
||||
// .fetch("https://api.github.com/repos/geode-sdk/mods/commits/main")
|
||||
// .text()
|
||||
// .then([this, force, callback, currentCommitSHA](std::string const& upcomingCommitSHA) {
|
||||
// auto indexDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY;
|
||||
|
||||
// // gee i sure hope no one does 60 commits to the mod index an hour and download every
|
||||
// // single one of them
|
||||
// if (upcomingCommitSHA == "") {
|
||||
// auto err = this->updateIndexFromLocalCache();
|
||||
// if (!err) {
|
||||
// RETURN_ERROR(err.unwrapErr());
|
||||
// }
|
||||
|
||||
// m_upToDate = true;
|
||||
// m_updating = false;
|
||||
|
||||
// if (callback) callback(UpdateStatus::Finished, "", 100);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // update if forced or latest commit has
|
||||
// // different sha
|
||||
// if (force || currentCommitSHA != upcomingCommitSHA) {
|
||||
// // save new sha in file
|
||||
// (void)utils::file::writeString(indexDir / "current", upcomingCommitSHA);
|
||||
|
||||
// web::AsyncWebRequest()
|
||||
// .join("index-download")
|
||||
// .fetch("https://github.com/geode-sdk/mods/zipball/main")
|
||||
// .into(indexDir / "index.zip")
|
||||
// .then([this, indexDir, callback](auto) {
|
||||
// // delete old index
|
||||
// try {
|
||||
// if (ghc::filesystem::exists(indexDir / "index")) {
|
||||
// ghc::filesystem::remove_all(indexDir / "index");
|
||||
// }
|
||||
// }
|
||||
// catch (std::exception& e) {
|
||||
// RETURN_ERROR("Unable to delete old index " + std::string(e.what()));
|
||||
// }
|
||||
|
||||
// // unzip new index
|
||||
// auto unzip = file::unzipTo(indexDir / "index.zip", indexDir);
|
||||
// if (!unzip) {
|
||||
// RETURN_ERROR(unzip.unwrapErr());
|
||||
// }
|
||||
|
||||
// // update index
|
||||
// auto err = this->updateIndexFromLocalCache();
|
||||
// if (!err) {
|
||||
// RETURN_ERROR(err.unwrapErr());
|
||||
// }
|
||||
|
||||
// m_upToDate = true;
|
||||
// m_updating = false;
|
||||
|
||||
// if (callback) callback(UpdateStatus::Finished, "", 100);
|
||||
// })
|
||||
// .expect([callback](std::string const& err) {
|
||||
// RETURN_ERROR(err);
|
||||
// })
|
||||
// .progress([callback](web::SentAsyncWebRequest& req, double now, double total) {
|
||||
// if (callback)
|
||||
// callback(
|
||||
// UpdateStatus::Progress, "Downloading",
|
||||
// static_cast<int>(now / total * 100.0)
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
// else {
|
||||
// auto err = this->updateIndexFromLocalCache();
|
||||
// if (!err) {
|
||||
// RETURN_ERROR(err.unwrapErr());
|
||||
// }
|
||||
|
||||
// m_upToDate = true;
|
||||
// m_updating = false;
|
||||
|
||||
// if (callback) callback(UpdateStatus::Finished, "", 100);
|
||||
// }
|
||||
// })
|
||||
// .expect([callback](std::string const& err) {
|
||||
// RETURN_ERROR(err);
|
||||
// })
|
||||
// .progress([callback](web::SentAsyncWebRequest& req, double now, double total) {
|
||||
// if (callback)
|
||||
// callback(
|
||||
// UpdateStatus::Progress, "Downloading", static_cast<int>(now / total * 100.0)
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
// void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
|
||||
// if (ghc::filesystem::exists(dir / "index.json")) {
|
||||
// auto readJson = readJSON(dir / "index.json");
|
||||
// if (!readJson) {
|
||||
// log::warn("Error reading index.json: {}, skipping", readJson.unwrapErr());
|
||||
// return;
|
||||
// }
|
||||
// auto json = readJson.unwrap();
|
||||
// if (!json.is_object()) {
|
||||
// log::warn("[index.json] is not an object, skipping");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// auto infoRes = ModInfo::createFromFile(dir / "mod.json");
|
||||
// if (!infoRes) {
|
||||
// log::warn("{}: {}, skipping", dir, infoRes.unwrapErr());
|
||||
// return;
|
||||
// }
|
||||
// auto info = infoRes.unwrap();
|
||||
|
||||
// // make sure only latest version is present in index
|
||||
// auto old = std::find_if(m_items.begin(), m_items.end(), [info](IndexItem const& item) {
|
||||
// return item.m_info.m_id == info.m_id;
|
||||
// });
|
||||
// if (old != m_items.end()) {
|
||||
// // this one is newer
|
||||
// if (old->m_info.m_version < info.m_version) {
|
||||
// m_items.erase(old);
|
||||
// } else {
|
||||
// log::warn(
|
||||
// "Found older version of ({} < {}) of {}, skipping",
|
||||
// info.m_version, old->m_info.m_version, info.m_id
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
// IndexItem item;
|
||||
|
||||
// item.m_path = dir;
|
||||
// item.m_info = info;
|
||||
|
||||
// if (!json.contains("download") || !json["download"].is_object()) {
|
||||
// log::warn("[index.json].download is not an object, skipping");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// #define REQUIRE_DOWNLOAD_KEY(key, type) \
|
||||
// if (!download.contains(key) || !download[key].is_##type()) { \
|
||||
// log::warn("[index.json].download." key " is not a " #type ", skipping"); \
|
||||
// return; \
|
||||
// }
|
||||
|
||||
// try {
|
||||
// auto download = json["download"];
|
||||
|
||||
// REQUIRE_DOWNLOAD_KEY("url", string);
|
||||
// REQUIRE_DOWNLOAD_KEY("name", string);
|
||||
// REQUIRE_DOWNLOAD_KEY("hash", string);
|
||||
// REQUIRE_DOWNLOAD_KEY("platforms", array);
|
||||
|
||||
// item.m_download.m_url = download["url"];
|
||||
// item.m_download.m_filename = download["name"];
|
||||
// item.m_download.m_hash = download["hash"];
|
||||
// for (auto& platform : download["platforms"]) {
|
||||
// item.m_download.m_platforms.insert(platformFromString(platform));
|
||||
// }
|
||||
|
||||
// if (json.contains("categories")) {
|
||||
// if (!json["categories"].is_array()) {
|
||||
// log::warn("[index.json].categories is not an array, skipping");
|
||||
// return;
|
||||
// }
|
||||
// item.m_categories = json["categories"].template get<std::unordered_set<std::string>>();
|
||||
// m_categories.insert(item.m_categories.begin(), item.m_categories.end());
|
||||
// }
|
||||
// }
|
||||
// catch (std::exception& e) {
|
||||
// log::warn("[index.json] parsing error: {}, skipping", e.what());
|
||||
// return;
|
||||
// }
|
||||
|
||||
// m_items.push_back(item);
|
||||
// }
|
||||
// else {
|
||||
// log::warn("Index directory {} is missing index.json, skipping", dir);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Result<> Index::updateIndexFromLocalCache() {
|
||||
// m_items.clear();
|
||||
// auto baseIndexDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY;
|
||||
|
||||
// // load geode.json (index settings)
|
||||
// if (auto baseIndexJson = readJSON(baseIndexDir / "geode.json")) {
|
||||
// auto json = baseIndexJson.unwrap();
|
||||
// auto checker = JsonChecker(json);
|
||||
// checker.root("[index/geode.json]").obj().has("featured").into(m_featured);
|
||||
// }
|
||||
|
||||
// // load index mods
|
||||
// auto modsDir = baseIndexDir / "index";
|
||||
// if (ghc::filesystem::exists(modsDir)) {
|
||||
// for (auto const& dir : ghc::filesystem::directory_iterator(modsDir)) {
|
||||
// if (ghc::filesystem::is_directory(dir)) {
|
||||
// this->addIndexItemFromFolder(dir);
|
||||
// }
|
||||
// }
|
||||
// log::info("Index updated");
|
||||
// return Ok();
|
||||
// }
|
||||
// else {
|
||||
// return Err(
|
||||
// "Index appears not to have been "
|
||||
// "downloaded, or is fully empty"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// std::vector<IndexItem> Index::getItems() const {
|
||||
// return m_items;
|
||||
// }
|
||||
|
||||
// std::unordered_set<std::string> Index::getCategories() const {
|
||||
// return m_categories;
|
||||
// }
|
||||
|
||||
// bool Index::isKnownItem(std::string const& id) const {
|
||||
// for (auto& item : m_items) {
|
||||
// if (item.m_info.m_id == id) return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// IndexItem Index::getKnownItem(std::string const& id) const {
|
||||
// for (auto& item : m_items) {
|
||||
// if (item.m_info.m_id == id) {
|
||||
// return item;
|
||||
// }
|
||||
// }
|
||||
// return IndexItem();
|
||||
// }
|
||||
|
||||
// struct UninstalledDependency {
|
||||
// std::string m_id;
|
||||
// bool m_isInIndex;
|
||||
// };
|
||||
|
||||
// static void getUninstalledDependenciesRecursive(
|
||||
// ModInfo const& info, std::vector<UninstalledDependency>& deps
|
||||
// ) {
|
||||
// for (auto& dep : info.m_dependencies) {
|
||||
// UninstalledDependency d;
|
||||
// d.m_isInIndex = Index::get()->isKnownItem(dep.m_id);
|
||||
// if (!Loader::get()->isModInstalled(dep.m_id)) {
|
||||
// d.m_id = dep.m_id;
|
||||
// deps.push_back(d);
|
||||
// }
|
||||
// if (d.m_isInIndex) {
|
||||
// getUninstalledDependenciesRecursive(Index::get()->getKnownItem(dep.m_id).m_info, deps);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Result<std::vector<std::string>> Index::checkDependenciesForItem(IndexItem const& item) {
|
||||
// // todo: check versions
|
||||
// std::vector<UninstalledDependency> deps;
|
||||
// getUninstalledDependenciesRecursive(item.m_info, deps);
|
||||
// if (deps.size()) {
|
||||
// std::vector<std::string> unknownDeps;
|
||||
// for (auto& dep : deps) {
|
||||
// if (!dep.m_isInIndex) {
|
||||
// unknownDeps.push_back(dep.m_id);
|
||||
// }
|
||||
// }
|
||||
// if (unknownDeps.size()) {
|
||||
// std::string list = "";
|
||||
// for (auto& ud : unknownDeps) {
|
||||
// list += "<cp>" + ud + "</c>, ";
|
||||
// }
|
||||
// list.pop_back();
|
||||
// list.pop_back();
|
||||
// return Err(
|
||||
// "This mod or its dependencies <cb>depends</c> on the "
|
||||
// "following unknown mods: " +
|
||||
// list +
|
||||
// ". You will have "
|
||||
// "to manually install these mods before you can install "
|
||||
// "this one."
|
||||
// );
|
||||
// }
|
||||
// std::vector<std::string> list = {};
|
||||
// for (auto& d : deps) {
|
||||
// list.push_back(d.m_id);
|
||||
// }
|
||||
// list.push_back(item.m_info.m_id);
|
||||
// return Ok(list);
|
||||
// }
|
||||
// else {
|
||||
// return Ok<std::vector<std::string>>({ item.m_info.m_id });
|
||||
// }
|
||||
// }
|
||||
|
||||
// Result<InstallHandle> Index::installItems(std::vector<IndexItem> const& items) {
|
||||
// std::vector<std::string> ids {};
|
||||
// for (auto& item : items) {
|
||||
// if (!item.m_download.m_platforms.count(GEODE_PLATFORM_TARGET)) {
|
||||
// return Err(
|
||||
// "This mod is not available on your "
|
||||
// "current platform \"" GEODE_PLATFORM_NAME "\" - Sorry! :("
|
||||
// );
|
||||
// }
|
||||
// if (!item.m_download.m_url.size()) {
|
||||
// return Err(
|
||||
// "Download URL not set! Report this bug to "
|
||||
// "the Geode developers - this should not happen, ever."
|
||||
// );
|
||||
// }
|
||||
// if (!item.m_download.m_filename.size()) {
|
||||
// return Err(
|
||||
// "Download filename not set! Report this bug to "
|
||||
// "the Geode developers - this should not happen, ever."
|
||||
// );
|
||||
// }
|
||||
// if (!item.m_download.m_hash.size()) {
|
||||
// return Err(
|
||||
// "Checksum not set! Report this bug to "
|
||||
// "the Geode developers - this should not happen, ever."
|
||||
// );
|
||||
// }
|
||||
// GEODE_UNWRAP_INTO(auto list, checkDependenciesForItem(item));
|
||||
// ranges::push(ids, list);
|
||||
// }
|
||||
// auto ret = std::make_shared<InstallItems>(std::unordered_set(ids.begin(), ids.end()));
|
||||
// m_installations.insert(ret);
|
||||
// return Ok(ret);
|
||||
// }
|
||||
|
||||
// Result<InstallHandle> Index::installItem(IndexItem const& item) {
|
||||
// return this->installItems({ item });
|
||||
// }
|
||||
|
||||
// bool Index::isUpdateAvailableForItem(std::string const& id) const {
|
||||
// if (!this->isKnownItem(id)) {
|
||||
// return false;
|
||||
// }
|
||||
// return this->isUpdateAvailableForItem(this->getKnownItem(id));
|
||||
// }
|
||||
|
||||
// bool Index::isUpdateAvailableForItem(IndexItem const& item) const {
|
||||
// if (!Loader::get()->isModInstalled(item.m_info.m_id)) {
|
||||
// return false;
|
||||
// }
|
||||
// // has the mod been updated (but update not yet been applied by restarting game)
|
||||
// if (m_updated.count(item.m_info.m_id)) {
|
||||
// return false;
|
||||
// }
|
||||
// return item.m_info.m_version > Loader::get()->getInstalledMod(item.m_info.m_id)->getVersion();
|
||||
// }
|
||||
|
||||
// bool Index::areUpdatesAvailable() const {
|
||||
// for (auto& item : m_items) {
|
||||
// if (this->isUpdateAvailableForItem(item.m_info.m_id)) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// Result<InstallHandle> Index::installAllUpdates() {
|
||||
// // find items that need updating
|
||||
// std::vector<IndexItem> itemsToUpdate {};
|
||||
// for (auto& item : m_items) {
|
||||
// if (this->isUpdateAvailableForItem(item)) {
|
||||
// itemsToUpdate.push_back(item);
|
||||
// }
|
||||
// }
|
||||
// return this->installItems(itemsToUpdate);
|
||||
// }
|
||||
|
||||
// std::vector<InstallHandle> Index::getRunningInstallations() const {
|
||||
// return std::vector<InstallHandle>(m_installations.begin(), m_installations.end());
|
||||
// }
|
||||
|
||||
// InstallHandle Index::isInstallingItem(std::string const& id) {
|
||||
// for (auto& inst : m_installations) {
|
||||
// if (inst->m_toInstall.count(id)) {
|
||||
// return inst;
|
||||
// }
|
||||
// }
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
// std::unordered_set<std::string> InstallItems::toInstall() const {
|
||||
// return m_toInstall;
|
||||
// }
|
||||
|
||||
// InstallItems::CallbackID InstallItems::join(ItemInstallCallback callback) {
|
||||
// // already finished?
|
||||
// if (m_started && this->finished()) {
|
||||
// callback(shared_from_this(), UpdateStatus::Finished, "", 100);
|
||||
// return 0;
|
||||
// }
|
||||
// // start at one because 0 means invalid callback
|
||||
// static CallbackID COUNTER = 1;
|
||||
// if (callback) {
|
||||
// auto id = COUNTER++;
|
||||
// m_callbacks.insert({ id, callback });
|
||||
// return id;
|
||||
// }
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
// void InstallItems::leave(InstallItems::CallbackID id) {
|
||||
// m_callbacks.erase(id);
|
||||
// }
|
||||
|
||||
// void InstallItems::post(UpdateStatus status, std::string const& info, uint8_t progress) {
|
||||
// for (auto& [_, cb] : m_callbacks) {
|
||||
// cb(shared_from_this(), status, info, progress);
|
||||
// }
|
||||
// }
|
||||
|
||||
// void InstallItems::progress(std::string const& info, uint8_t progress) {
|
||||
// this->post(UpdateStatus::Progress, info, progress);
|
||||
// }
|
||||
|
||||
// void InstallItems::error(std::string const& info) {
|
||||
// this->post(UpdateStatus::Failed, info, 0);
|
||||
// }
|
||||
|
||||
// void InstallItems::finish(bool replaceFiles) {
|
||||
// // move files from temp dir to geode directory
|
||||
// auto tempDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY / "temp";
|
||||
// for (auto& file : ghc::filesystem::directory_iterator(tempDir)) {
|
||||
// try {
|
||||
// auto modDir = Loader::get()->getGeodeDirectory() / "mods";
|
||||
// auto targetFile = modDir / file.path().filename();
|
||||
// auto targetName = file.path().stem();
|
||||
|
||||
// if (!replaceFiles) {
|
||||
// // find valid filename that doesn't exist yet
|
||||
// auto filename = ghc::filesystem::path(targetName).replace_extension("").string();
|
||||
|
||||
// size_t number = 0;
|
||||
// while (ghc::filesystem::exists(targetFile)) {
|
||||
// targetFile = modDir / (filename + std::to_string(number) + ".geode");
|
||||
// number++;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // move file
|
||||
// ghc::filesystem::rename(file, targetFile);
|
||||
// }
|
||||
// catch (std::exception& e) {
|
||||
// try {
|
||||
// ghc::filesystem::remove_all(tempDir);
|
||||
// }
|
||||
// catch (...) {
|
||||
// }
|
||||
// return this->error(
|
||||
// "Unable to move downloaded file to mods directory: \"" + std::string(e.what()) +
|
||||
// " \" "
|
||||
// "(This might be due to insufficient permissions to "
|
||||
// "write files under SteamLibrary, try running GD as "
|
||||
// "administrator)"
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// // load mods
|
||||
// (void)Loader::get()->refreshModsList();
|
||||
|
||||
// // finished
|
||||
// this->post(UpdateStatus::Finished, "", 100);
|
||||
|
||||
// // let index know these mods have been updated
|
||||
// for (auto& inst : m_toInstall) {
|
||||
// Index::get()->m_updated.insert(inst);
|
||||
// }
|
||||
|
||||
// // if no one is listening, show a popup anyway
|
||||
// if (!m_callbacks.size()) {
|
||||
// FLAlertLayer::create(
|
||||
// "Mods installed",
|
||||
// "The following <cy>mods</c> have been installed: " +
|
||||
// ranges::join(m_toInstall, std::string(",")) +
|
||||
// "\n"
|
||||
// "Please <cr>restart the game</c> to apply",
|
||||
// "OK"
|
||||
// )
|
||||
// ->show();
|
||||
// }
|
||||
|
||||
// // no longer need to ensure aliveness
|
||||
// Index::get()->m_installations.erase(shared_from_this());
|
||||
// }
|
||||
|
||||
// InstallItems::CallbackID InstallItems::start(ItemInstallCallback callback, bool replaceFiles) {
|
||||
// auto id = this->join(callback);
|
||||
|
||||
// // check if started already, if so, behave like join
|
||||
// if (m_started) return id;
|
||||
// m_started = true;
|
||||
|
||||
// for (auto& inst : m_toInstall) {
|
||||
// // by virtue of running this function we know item must be valid
|
||||
// auto item = Index::get()->getKnownItem(inst);
|
||||
|
||||
// auto indexDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY;
|
||||
// (void)file::createDirectoryAll(indexDir / "temp");
|
||||
// auto tempFile = indexDir / "temp" / item.m_download.m_filename;
|
||||
|
||||
// m_downloaded.push_back(tempFile);
|
||||
|
||||
// auto handle =
|
||||
// web::AsyncWebRequest()
|
||||
// .join("install_mod_" + inst)
|
||||
// .fetch(item.m_download.m_url)
|
||||
// .into(tempFile)
|
||||
// .then([this, replaceFiles, item, inst, indexDir, tempFile](auto) {
|
||||
// // check for 404
|
||||
// auto notFound = utils::file::readString(tempFile);
|
||||
// if (notFound && notFound.unwrap() == "Not Found") {
|
||||
// try {
|
||||
// ghc::filesystem::remove(tempFile);
|
||||
// }
|
||||
// catch (...) {
|
||||
// }
|
||||
// return this->error(
|
||||
// "Binary file download returned \"Not found\". Report "
|
||||
// "this to the Geode development team."
|
||||
// );
|
||||
// }
|
||||
|
||||
// // verify checksum
|
||||
// this->progress("Verifying", 100);
|
||||
// if (::calculateHash(tempFile) != item.m_download.m_hash) {
|
||||
// try {
|
||||
// ghc::filesystem::remove(tempFile);
|
||||
// }
|
||||
// catch (...) {
|
||||
// }
|
||||
// return this->error(
|
||||
// "Checksum mismatch! (Downloaded file did not match what "
|
||||
// "was expected. Try again, and if the download fails another time, "
|
||||
// "report this to the Geode development team."
|
||||
// );
|
||||
// }
|
||||
|
||||
// // finished() just checks if the web requests are done
|
||||
// if (this->finished()) {
|
||||
// this->finish(replaceFiles);
|
||||
// }
|
||||
// })
|
||||
// .expect([this, inst](std::string const& error) {
|
||||
// this->error(error);
|
||||
// this->cancel();
|
||||
// })
|
||||
// .cancelled([this, item](auto&) {
|
||||
// this->cancel();
|
||||
// })
|
||||
// .progress([this, inst](web::SentAsyncWebRequest&, double now, double total) {
|
||||
// this->progress("Downloading binary", static_cast<uint8_t>(now / total * 100.0));
|
||||
// })
|
||||
// .send();
|
||||
|
||||
// m_handles.push_back(handle);
|
||||
// }
|
||||
// // manage installation in the index until it's finished so
|
||||
// // even if no one listens to it it doesn't get freed from
|
||||
// // memory
|
||||
// Index::get()->m_installations.insert(shared_from_this());
|
||||
|
||||
// return id;
|
||||
// }
|
||||
|
||||
// bool InstallItems::finished() const {
|
||||
// for (auto& inst : m_handles) {
|
||||
// if (!inst->finished()) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// void InstallItems::cancel() {
|
||||
// for (auto& inst : m_handles) {
|
||||
// inst->cancel();
|
||||
// }
|
||||
// }
|
|
@ -1,117 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
class Index;
|
||||
struct ModInstallUpdate;
|
||||
struct InstallItems;
|
||||
|
||||
using InstallHandle = std::shared_ptr<InstallItems>;
|
||||
|
||||
// todo: make index use events
|
||||
|
||||
enum class UpdateStatus {
|
||||
Progress,
|
||||
Failed,
|
||||
Finished,
|
||||
};
|
||||
|
||||
using ItemInstallCallback =
|
||||
std::function<void(InstallHandle, UpdateStatus, std::string const&, uint8_t)>;
|
||||
using IndexUpdateCallback = std::function<void(UpdateStatus, std::string const&, uint8_t)>;
|
||||
|
||||
struct IndexItem {
|
||||
struct Download {
|
||||
std::string m_url;
|
||||
std::string m_filename;
|
||||
std::string m_hash;
|
||||
std::unordered_set<PlatformID> m_platforms;
|
||||
};
|
||||
|
||||
ghc::filesystem::path m_path;
|
||||
ModInfo m_info;
|
||||
Download m_download;
|
||||
std::unordered_set<std::string> m_categories;
|
||||
};
|
||||
|
||||
struct InstallItems final : public std::enable_shared_from_this<InstallItems> {
|
||||
public:
|
||||
using CallbackID = size_t;
|
||||
|
||||
private:
|
||||
bool m_started = false;
|
||||
std::unordered_set<std::string> m_toInstall;
|
||||
std::vector<web::SentAsyncWebRequestHandle> m_handles;
|
||||
std::unordered_map<CallbackID, ItemInstallCallback> m_callbacks;
|
||||
std::vector<ghc::filesystem::path> m_downloaded;
|
||||
|
||||
void post(UpdateStatus status, std::string const& info, uint8_t progress);
|
||||
void progress(std::string const& info, uint8_t progress);
|
||||
void error(std::string const& info);
|
||||
void finish(bool replaceFiles);
|
||||
|
||||
friend class Index;
|
||||
|
||||
public:
|
||||
std::unordered_set<std::string> toInstall() const;
|
||||
|
||||
inline InstallItems(std::unordered_set<std::string> const& toInstall) :
|
||||
m_toInstall(toInstall) {}
|
||||
|
||||
void cancel();
|
||||
bool finished() const;
|
||||
|
||||
CallbackID join(ItemInstallCallback callback);
|
||||
void leave(CallbackID id);
|
||||
|
||||
CallbackID start(ItemInstallCallback callback, bool replaceFiles = true);
|
||||
};
|
||||
|
||||
class Index {
|
||||
protected:
|
||||
bool m_upToDate = false;
|
||||
bool m_updating = false;
|
||||
mutable std::mutex m_callbacksMutex;
|
||||
std::vector<IndexItem> m_items;
|
||||
std::unordered_set<InstallHandle> m_installations;
|
||||
mutable std::mutex m_ticketsMutex;
|
||||
std::unordered_set<std::string> m_featured;
|
||||
std::unordered_set<std::string> m_categories;
|
||||
std::unordered_set<std::string> m_updated;
|
||||
|
||||
void addIndexItemFromFolder(ghc::filesystem::path const& dir);
|
||||
Result<> updateIndexFromLocalCache();
|
||||
|
||||
Result<std::vector<std::string>> checkDependenciesForItem(IndexItem const& item);
|
||||
|
||||
friend struct InstallItems;
|
||||
|
||||
public:
|
||||
static Index* get();
|
||||
|
||||
std::vector<IndexItem> getItems() const;
|
||||
bool isKnownItem(std::string const& id) const;
|
||||
IndexItem getKnownItem(std::string const& id) const;
|
||||
|
||||
std::unordered_set<std::string> getCategories() const;
|
||||
std::vector<IndexItem> getFeaturedItems() const;
|
||||
bool isFeaturedItem(std::string const& item) const;
|
||||
|
||||
Result<InstallHandle> installItems(std::vector<IndexItem> const& item);
|
||||
Result<InstallHandle> installItem(IndexItem const& item);
|
||||
std::vector<InstallHandle> getRunningInstallations() const;
|
||||
InstallHandle isInstallingItem(std::string const& id);
|
||||
|
||||
bool isUpdateAvailableForItem(std::string const& id) const;
|
||||
bool isUpdateAvailableForItem(IndexItem const& item) const;
|
||||
bool areUpdatesAvailable() const;
|
||||
Result<InstallHandle> installAllUpdates();
|
||||
|
||||
bool isIndexUpdated() const;
|
||||
void updateIndex(IndexUpdateCallback callback, bool force = false);
|
||||
};
|
|
@ -16,6 +16,16 @@
|
|||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
ListenerResult ResourceDownloadFilter::handle(
|
||||
std::function<Callback> fn,
|
||||
ResourceDownloadEvent* event
|
||||
) {
|
||||
fn(event);
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
ResourceDownloadFilter::ResourceDownloadFilter() {}
|
||||
|
||||
InternalLoader::InternalLoader() : Loader() {}
|
||||
|
||||
InternalLoader::~InternalLoader() {
|
||||
|
@ -93,7 +103,7 @@ void InternalLoader::loadInfoAlerts(nlohmann::json& json) {
|
|||
m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>();
|
||||
}
|
||||
|
||||
void InternalLoader::downloadLoaderResources(IndexUpdateCallback callback) {
|
||||
void InternalLoader::downloadLoaderResources() {
|
||||
auto version = this->getVersion().toString();
|
||||
auto tempResourcesZip = dirs::getTempDir() / "new.zip";
|
||||
auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
|
||||
|
@ -104,38 +114,32 @@ void InternalLoader::downloadLoaderResources(IndexUpdateCallback callback) {
|
|||
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version
|
||||
))
|
||||
.into(tempResourcesZip)
|
||||
.then([tempResourcesZip, resourcesDir, callback](auto) {
|
||||
.then([tempResourcesZip, resourcesDir](auto) {
|
||||
// unzip resources zip
|
||||
auto unzip = file::unzipTo(tempResourcesZip, resourcesDir);
|
||||
auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
|
||||
if (!unzip) {
|
||||
if (callback)
|
||||
callback(
|
||||
UpdateStatus::Failed, "Unable to unzip new resources: " + unzip.unwrapErr(), 0
|
||||
);
|
||||
return;
|
||||
return ResourceDownloadEvent(
|
||||
UpdateError("Unable to unzip new resources: " + unzip.unwrapErr())
|
||||
).post();
|
||||
}
|
||||
// delete resources zip
|
||||
try {
|
||||
ghc::filesystem::remove(tempResourcesZip);
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
|
||||
if (callback) callback(UpdateStatus::Finished, "Resources updated", 100);
|
||||
ResourceDownloadEvent(UpdateFinished()).post();
|
||||
})
|
||||
.expect([callback](std::string const& info) {
|
||||
if (callback) callback(UpdateStatus::Failed, info, 0);
|
||||
.expect([](std::string const& info) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateError("Unable to download resources: " + info)
|
||||
).post();
|
||||
})
|
||||
.progress([callback](auto&, double now, double total) {
|
||||
if (callback)
|
||||
callback(
|
||||
UpdateStatus::Progress, "Downloading resources",
|
||||
static_cast<uint8_t>(now / total * 100.0)
|
||||
);
|
||||
.progress([](auto&, double now, double total) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateProgress(
|
||||
static_cast<uint8_t>(now / total * 100.0),
|
||||
"Downloading resources"
|
||||
)
|
||||
).post();
|
||||
});
|
||||
}
|
||||
|
||||
bool InternalLoader::verifyLoaderResources(IndexUpdateCallback callback) {
|
||||
bool InternalLoader::verifyLoaderResources() {
|
||||
static std::optional<bool> CACHED = std::nullopt;
|
||||
if (CACHED.has_value()) {
|
||||
return CACHED.value();
|
||||
|
@ -145,8 +149,11 @@ bool InternalLoader::verifyLoaderResources(IndexUpdateCallback callback) {
|
|||
auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
|
||||
|
||||
// if the resources dir doesn't exist, then it's probably incorrect
|
||||
if (!(ghc::filesystem::exists(resourcesDir) && ghc::filesystem::is_directory(resourcesDir))) {
|
||||
this->downloadLoaderResources(callback);
|
||||
if (!(
|
||||
ghc::filesystem::exists(resourcesDir) &&
|
||||
ghc::filesystem::is_directory(resourcesDir)
|
||||
)) {
|
||||
this->downloadLoaderResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -166,7 +173,7 @@ bool InternalLoader::verifyLoaderResources(IndexUpdateCallback callback) {
|
|||
log::debug(
|
||||
"compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)
|
||||
);
|
||||
this->downloadLoaderResources(callback);
|
||||
this->downloadLoaderResources();
|
||||
return false;
|
||||
}
|
||||
coverage += 1;
|
||||
|
@ -174,7 +181,7 @@ bool InternalLoader::verifyLoaderResources(IndexUpdateCallback callback) {
|
|||
|
||||
// make sure every file was found
|
||||
if (coverage != LOADER_RESOURCE_HASHES.size()) {
|
||||
this->downloadLoaderResources(callback);
|
||||
this->downloadLoaderResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "../index/Index.hpp"
|
||||
#include "FileWatcher.hpp"
|
||||
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/utils/Result.hpp>
|
||||
|
@ -15,6 +15,23 @@
|
|||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
class ResourceDownloadEvent : public Event {
|
||||
protected:
|
||||
UpdateStatus m_status;
|
||||
|
||||
public:
|
||||
ResourceDownloadEvent(UpdateStatus status);
|
||||
UpdateStatus getStatus() const;
|
||||
};
|
||||
|
||||
class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
|
||||
public:
|
||||
using Callback = void(ResourceDownloadEvent*);
|
||||
|
||||
ListenerResult handle(std::function<Callback> fn, ResourceDownloadEvent* event);
|
||||
ResourceDownloadFilter();
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal extension of Loader for private information
|
||||
* @class InternalLoader
|
||||
|
@ -29,7 +46,7 @@ protected:
|
|||
void saveInfoAlerts(nlohmann::json& json);
|
||||
void loadInfoAlerts(nlohmann::json& json);
|
||||
|
||||
void downloadLoaderResources(IndexUpdateCallback callback);
|
||||
void downloadLoaderResources();
|
||||
|
||||
bool loadHooks();
|
||||
void setupIPC();
|
||||
|
@ -66,7 +83,7 @@ public:
|
|||
void closePlatformConsole();
|
||||
static void platformMessageBox(char const* title, std::string const& info);
|
||||
|
||||
bool verifyLoaderResources(IndexUpdateCallback callback);
|
||||
bool verifyLoaderResources();
|
||||
|
||||
friend int geodeEntry(void* platformData);
|
||||
};
|
||||
|
|
256
loader/src/loader/Index.cpp
Normal file
256
loader/src/loader/Index.cpp
Normal file
|
@ -0,0 +1,256 @@
|
|||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <Geode/utils/string.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
struct IndexSourceSaveData {
|
||||
std::string downloadedCommitSHA;
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IndexSourceSaveData, downloadedCommitSHA);
|
||||
|
||||
struct IndexSaveData {
|
||||
std::unordered_map<std::string, IndexSourceSaveData> sources;
|
||||
};
|
||||
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(IndexSaveData, sources);
|
||||
|
||||
std::string IndexSource::dirname() const {
|
||||
return string::replace(this->repository, "/", "_");
|
||||
}
|
||||
|
||||
IndexUpdateEvent::IndexUpdateEvent(
|
||||
std::string const& src,
|
||||
UpdateStatus status
|
||||
) : m_sourceRepository(src), m_status(status) {}
|
||||
|
||||
std::string IndexUpdateEvent::getSource() const {
|
||||
return m_sourceRepository;
|
||||
}
|
||||
|
||||
UpdateStatus IndexUpdateEvent::getStatus() const {
|
||||
return m_status;
|
||||
}
|
||||
|
||||
ListenerResult IndexUpdateFilter::handle(
|
||||
std::function<Callback> fn,
|
||||
IndexUpdateEvent* event
|
||||
) {
|
||||
fn(event);
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
IndexUpdateFilter::IndexUpdateFilter() {}
|
||||
|
||||
Index::Index() {
|
||||
this->addSource("https://github.com/geode-sdk/index-test");
|
||||
}
|
||||
|
||||
Index* Index::get() {
|
||||
auto inst = new Index();
|
||||
return inst;
|
||||
}
|
||||
|
||||
void Index::addSource(std::string const& repository) {
|
||||
m_sources.push_back(IndexSource {
|
||||
.repository = repository
|
||||
});
|
||||
}
|
||||
|
||||
void Index::removeSource(std::string const& repository) {
|
||||
ranges::remove(m_sources, [repository](IndexSource const& src) {
|
||||
return src.repository == repository;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<IndexSource> Index::getSources() const {
|
||||
return m_sources;
|
||||
}
|
||||
|
||||
bool Index::isUpToDate() const {
|
||||
for (auto& source : m_sources) {
|
||||
if (!source.isUpToDate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Index::checkSourceUpdates(IndexSource& src) {
|
||||
if (src.isUpToDate) {
|
||||
IndexUpdateEvent(src.repository, UpdateFinished()).post();
|
||||
return;
|
||||
}
|
||||
IndexUpdateEvent(src.repository, UpdateProgress(0, "Checking status")).post();
|
||||
auto data = Mod::get()->getSavedMutable<IndexSaveData>("index");
|
||||
auto oldSHA = data.sources[src.repository].downloadedCommitSHA;
|
||||
web::AsyncWebRequest()
|
||||
.join(fmt::format("index-update-{}", src.repository))
|
||||
.header(fmt::format("If-None-Match: \"{}\"", oldSHA))
|
||||
.header("Accept: application/vnd.github.sha")
|
||||
.fetch(fmt::format("https://api.github.com/repos/{}/commits/main", src.repository))
|
||||
.text()
|
||||
.then([this, &src, oldSHA](std::string const& newSHA) {
|
||||
// if no new hash was given (rate limited) or the new hash is the
|
||||
// same as old, then just update from local cache
|
||||
if (newSHA.empty() || oldSHA == newSHA) {
|
||||
this->updateSourceFromLocal(src);
|
||||
}
|
||||
// otherwise save hash and download source
|
||||
else {
|
||||
auto data = Mod::get()->getSavedMutable<IndexSaveData>("index");
|
||||
data.sources[src.repository].downloadedCommitSHA = newSHA;
|
||||
this->downloadSource(src);
|
||||
}
|
||||
})
|
||||
.expect([&src](std::string const& err) {
|
||||
IndexUpdateEvent(
|
||||
src.repository,
|
||||
UpdateError(fmt::format("Error checking for updates: {}", err))
|
||||
).post();
|
||||
});
|
||||
}
|
||||
|
||||
void Index::downloadSource(IndexSource& src) {
|
||||
IndexUpdateEvent(src.repository, UpdateProgress(0, "Beginning download")).post();
|
||||
|
||||
auto targetFile = dirs::getIndexDir() / fmt::format("{}.zip", src.dirname());
|
||||
|
||||
web::AsyncWebRequest()
|
||||
.join(fmt::format("index-download-{}", src.repository))
|
||||
.fetch(fmt::format("https://github.com/{}/zipball/main", src.repository))
|
||||
.into(targetFile)
|
||||
.then([this, &src, targetFile](auto) {
|
||||
auto targetDir = dirs::getIndexDir() / src.dirname();
|
||||
// delete old unzipped index
|
||||
try {
|
||||
if (ghc::filesystem::exists(targetDir)) {
|
||||
ghc::filesystem::remove_all(targetDir);
|
||||
}
|
||||
}
|
||||
catch(...) {
|
||||
return IndexUpdateEvent(
|
||||
src.repository,
|
||||
UpdateError("Unable to clear cached index")
|
||||
).post();
|
||||
}
|
||||
|
||||
// unzip new index
|
||||
auto unzip = file::Unzip::intoDir(targetFile, targetDir, true);
|
||||
if (!unzip) {
|
||||
return IndexUpdateEvent(
|
||||
src.repository,
|
||||
UpdateError("Unable to unzip new index")
|
||||
).post();
|
||||
}
|
||||
|
||||
// update index
|
||||
this->updateSourceFromLocal(src);
|
||||
})
|
||||
.expect([&src](std::string const& err) {
|
||||
IndexUpdateEvent(
|
||||
src.repository,
|
||||
UpdateError(fmt::format("Error downloading: {}", err))
|
||||
).post();
|
||||
})
|
||||
.progress([&src](auto&, double now, double total) {
|
||||
IndexUpdateEvent(
|
||||
src.repository,
|
||||
UpdateProgress(static_cast<uint8_t>(now / total * 100.0), "Downloading")
|
||||
).post();
|
||||
});
|
||||
}
|
||||
|
||||
void Index::updateSourceFromLocal(IndexSource& src) {
|
||||
IndexUpdateEvent(src.repository, UpdateProgress(100, "Updating local cache")).post();
|
||||
// delete old items from this url if such exist
|
||||
for (auto& [_, versions] : m_items) {
|
||||
for (auto it = versions.begin(); it != versions.end(); ) {
|
||||
if (it->second.sourceRepository == src.repository) {
|
||||
it = versions.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: add shit
|
||||
|
||||
this->cleanupItems();
|
||||
src.isUpToDate = true;
|
||||
IndexUpdateEvent(src.repository, UpdateFinished()).post();
|
||||
}
|
||||
|
||||
void Index::cleanupItems() {
|
||||
// delete mods with no versions
|
||||
for (auto it = m_items.begin(); it != m_items.end(); ) {
|
||||
if (!it->second.size()) {
|
||||
it = m_items.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Index::update(bool force) {
|
||||
// create index dir if it doesn't exist
|
||||
(void)file::createDirectoryAll(dirs::getIndexDir());
|
||||
|
||||
// update all sources in GD thread
|
||||
Loader::get()->queueInGDThread([force, this]() {
|
||||
for (auto& src : m_sources) {
|
||||
if (force) {
|
||||
this->downloadSource(src);
|
||||
} else {
|
||||
this->checkSourceUpdates(src);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<IndexItem> Index::getItems() const {
|
||||
std::vector<IndexItem> res;
|
||||
for (auto& items : map::values(m_items)) {
|
||||
if (items.size()) {
|
||||
res.push_back(items.rbegin()->second);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Index::isKnownItem(
|
||||
std::string const& id,
|
||||
std::optional<size_t> version
|
||||
) const {
|
||||
if (m_items.count(id)) {
|
||||
if (version) {
|
||||
return m_items.at(id).count(version.value());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<IndexItem> Index::getItem(
|
||||
std::string const& id,
|
||||
std::optional<size_t> version
|
||||
) const {
|
||||
if (m_items.count(id)) {
|
||||
auto versions = m_items.at(id);
|
||||
if (version) {
|
||||
if (versions.count(version.value())) {
|
||||
return versions.at(version.value());
|
||||
}
|
||||
} else {
|
||||
if (versions.size()) {
|
||||
return m_items.at(id).rbegin()->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
|
@ -275,7 +275,7 @@ Mod* Loader::getLoadedMod(std::string const& id) const {
|
|||
}
|
||||
|
||||
std::vector<Mod*> Loader::getAllMods() {
|
||||
return map::getValues(m_mods);
|
||||
return map::values(m_mods);
|
||||
}
|
||||
|
||||
Mod* Loader::getInternalMod() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "../index/Index.hpp"
|
||||
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include "info/ModInfoLayer.hpp"
|
||||
#include "list/ModListLayer.hpp"
|
||||
#include "settings/ModSettingsPopup.hpp"
|
||||
|
@ -15,8 +16,9 @@ void geode::openInfoPopup(Mod* mod) {
|
|||
|
||||
void geode::openIndexPopup(Mod* mod) {
|
||||
if (Index::get()->isKnownItem(mod->getID())) {
|
||||
ModInfoLayer::create(new ModObject(Index::get()->getKnownItem(mod->getID())), nullptr)
|
||||
->show();
|
||||
ModInfoLayer::create(
|
||||
new ModObject(Index::get()->getKnownItem(mod->getID()))
|
||||
)->show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include <Geode/binding/FLAlertLayerProtocol.hpp>
|
||||
#include <Geode/ui/MDTextArea.hpp>
|
||||
#include <Geode/ui/Scrollbar.hpp>
|
||||
#include <Index.hpp>
|
||||
#include <Geode/loader/Index.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "ModListView.hpp"
|
||||
|
||||
#include <Geode/binding/TextInputDelegate.hpp>
|
||||
#include <Index.hpp>
|
||||
#include <Geode/loader/Index.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
#include <Geode/binding/CustomListView.hpp>
|
||||
#include <Geode/binding/FLAlertLayerProtocol.hpp>
|
||||
#include <Geode/binding/TableViewCell.hpp>
|
||||
#include <Index.hpp>
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <optional>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
@ -18,7 +19,7 @@ enum class ModListType {
|
|||
|
||||
enum class ModObjectType {
|
||||
Mod,
|
||||
Unloaded,
|
||||
Invalid,
|
||||
Index,
|
||||
};
|
||||
|
||||
|
@ -27,22 +28,25 @@ class ModListLayer;
|
|||
// Wrapper so you can pass Mods in a CCArray
|
||||
struct ModObject : public CCObject {
|
||||
ModObjectType m_type;
|
||||
Mod* m_mod;
|
||||
InvalidGeodeFile m_info;
|
||||
IndexItem m_index;
|
||||
std::variant<Mod*, InvalidGeodeFile, IndexItem> m_data;
|
||||
|
||||
inline ModObject(Mod* mod) : m_mod(mod), m_type(ModObjectType::Mod) {
|
||||
inline ModObject(Mod* mod)
|
||||
: m_data(mod), m_type(ModObjectType::Mod)
|
||||
{
|
||||
this->autorelease();
|
||||
};
|
||||
}
|
||||
|
||||
inline ModObject(InvalidGeodeFile const& info) :
|
||||
m_info(info), m_type(ModObjectType::Unloaded) {
|
||||
inline ModObject(InvalidGeodeFile const& info)
|
||||
: m_data(info), m_type(ModObjectType::Invalid)
|
||||
{
|
||||
this->autorelease();
|
||||
};
|
||||
}
|
||||
|
||||
inline ModObject(IndexItem const& index) : m_index(index), m_type(ModObjectType::Index) {
|
||||
inline ModObject(IndexItem const& index)
|
||||
: m_data(index), m_type(ModObjectType::Index)
|
||||
{
|
||||
this->autorelease();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
class ModListView;
|
||||
|
|
22
loader/src/utils/PlatformID.cpp
Normal file
22
loader/src/utils/PlatformID.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
#include <Geode/platform/platform.hpp>
|
||||
#include <Geode/utils/general.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
PlatformID PlatformID::from(const char* str) {
|
||||
switch (hash(str)) {
|
||||
default:
|
||||
case hash("unknown"): return PlatformID::Unknown;
|
||||
case hash("windows"): return PlatformID::Windows;
|
||||
case hash("macos"): return PlatformID::MacOS;
|
||||
case hash("ios"): return PlatformID::iOS;
|
||||
case hash("android"): return PlatformID::Android;
|
||||
case hash("linux"): return PlatformID::Linux;
|
||||
}
|
||||
}
|
||||
|
||||
PlatformID PlatformID::from(std::string const& str) {
|
||||
return PlatformID::from(str.c_str());
|
||||
}
|
||||
|
|
@ -116,11 +116,6 @@ Result<std::vector<std::string>> utils::file::listFilesRecursively(std::string c
|
|||
return Ok(res);
|
||||
}
|
||||
|
||||
Result<> utils::file::unzipTo(ghc::filesystem::path const& from, ghc::filesystem::path const& to) {
|
||||
GEODE_UNWRAP_INTO(auto unzip, Unzip::create(from));
|
||||
return unzip.extractAllTo(to);
|
||||
}
|
||||
|
||||
static constexpr auto MAX_ENTRY_PATH_LEN = 256;
|
||||
|
||||
struct ZipEntry {
|
||||
|
@ -244,7 +239,7 @@ ghc::filesystem::path Unzip::getPath() const {
|
|||
}
|
||||
|
||||
std::vector<ghc::filesystem::path> Unzip::getEntries() const {
|
||||
return map::getKeys(m_impl->entries());
|
||||
return map::keys(m_impl->entries());
|
||||
}
|
||||
|
||||
bool Unzip::hasEntry(Path const& name) {
|
||||
|
@ -268,3 +263,16 @@ Result<> Unzip::extractAllTo(Path const& dir) {
|
|||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Unzip::intoDir(
|
||||
Path const& from,
|
||||
Path const& to,
|
||||
bool deleteZipAfter
|
||||
) {
|
||||
GEODE_UNWRAP_INTO(auto unzip, Unzip::create(from));
|
||||
GEODE_UNWRAP(unzip.extractAllTo(to));
|
||||
if (deleteZipAfter) {
|
||||
try { ghc::filesystem::remove(from); } catch(...) {}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue