mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-22 02:45:49 -04:00
Merge branch 'main' of https://github.com/geode-sdk/geode into main
This commit is contained in:
commit
5e8a321a36
31 changed files with 1191 additions and 1114 deletions
|
@ -4747,6 +4747,7 @@ class PlayerObject : GameObject, AnimatedSpriteDelegate {
|
|||
cocos2d::CCSprite* m_unk500;
|
||||
cocos2d::CCSprite* m_vehicleSpriteWhitener;
|
||||
cocos2d::CCSprite* m_vehicleGlow;
|
||||
PAD = mac 0x8; // idk about windows
|
||||
cocos2d::CCMotionStreak* m_regularTrail;
|
||||
HardStreak* m_waveTrail;
|
||||
double m_xAccel;
|
||||
|
|
|
@ -106,6 +106,8 @@ package_geode_resources_now(
|
|||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
src/
|
||||
src/loader/
|
||||
src/internal/
|
||||
src/platform/
|
||||
src/gui/
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "Log.hpp"
|
||||
#include "../external/filesystem/fs/filesystem.hpp"
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include "../utils/Result.hpp"
|
||||
#include "Log.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
#include "Types.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
namespace geode {
|
||||
using ScheduledFunction = std::function<void GEODE_CALL(void)>;
|
||||
|
@ -16,18 +17,16 @@ namespace geode {
|
|||
std::string reason;
|
||||
};
|
||||
|
||||
class GEODE_DLL Loader {
|
||||
protected:
|
||||
std::vector<ghc::filesystem::path> m_modSearchDirectories;
|
||||
std::vector<ModInfo> m_modsToLoad;
|
||||
std::vector<InvalidGeodeFile> m_invalidMods;
|
||||
std::unordered_map<std::string, Mod*> m_mods;
|
||||
std::vector<ghc::filesystem::path> m_texturePaths;
|
||||
std::vector<ScheduledFunction> m_scheduledFunctions;
|
||||
mutable std::mutex m_scheduledFunctionsMutex;
|
||||
bool m_isSetup = false;
|
||||
std::atomic_bool m_earlyLoadFinished = false;
|
||||
class LoaderImpl;
|
||||
|
||||
class GEODE_DLL Loader {
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
Loader();
|
||||
~Loader();
|
||||
|
||||
protected:
|
||||
void createDirectories();
|
||||
|
||||
void updateModResources(Mod* mod);
|
||||
|
@ -39,31 +38,26 @@ namespace geode {
|
|||
Result<Mod*> loadModFromInfo(ModInfo const& info);
|
||||
|
||||
public:
|
||||
~Loader();
|
||||
// TODO: do we want to expose all of these functions?
|
||||
static Loader* get();
|
||||
|
||||
Result<> setup();
|
||||
|
||||
Result<> saveData();
|
||||
Result<> loadData();
|
||||
|
||||
static VersionInfo getVersion();
|
||||
static VersionInfo minModVersion();
|
||||
static VersionInfo maxModVersion();
|
||||
static bool isModVersionSupported(VersionInfo const& version);
|
||||
VersionInfo getVersion();
|
||||
VersionInfo minModVersion();
|
||||
VersionInfo maxModVersion();
|
||||
bool isModVersionSupported(VersionInfo const& version);
|
||||
|
||||
Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
|
||||
void loadModsFromDirectory(
|
||||
ghc::filesystem::path const& dir,
|
||||
bool recursive = true
|
||||
);
|
||||
void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
|
||||
void refreshModsList();
|
||||
bool isModInstalled(std::string const& id) const;
|
||||
Mod* getInstalledMod(std::string const& id) const;
|
||||
bool isModLoaded(std::string const& id) const;
|
||||
Mod* getLoadedMod(std::string const& id) const;
|
||||
std::vector<Mod*> getAllMods();
|
||||
static Mod* getInternalMod();
|
||||
Mod* getInternalMod();
|
||||
void updateAllDependencies();
|
||||
std::vector<InvalidGeodeFile> getFailedMods() const;
|
||||
|
||||
|
@ -72,16 +66,18 @@ namespace geode {
|
|||
void queueInGDThread(ScheduledFunction func);
|
||||
void scheduleOnModLoad(Mod* mod, ScheduledFunction func);
|
||||
void waitForModsToBeLoaded();
|
||||
|
||||
|
||||
/**
|
||||
* Open the platform-specific external console (if one exists)
|
||||
*/
|
||||
static void openPlatformConsole();
|
||||
void openPlatformConsole();
|
||||
/**
|
||||
* Close the platform-specific external console (if one exists)
|
||||
*/
|
||||
static void closePlatfromConsole();
|
||||
void closePlatformConsole();
|
||||
|
||||
bool didLastLaunchCrash() const;
|
||||
|
||||
friend class LoaderImpl;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "Hook.hpp"
|
||||
#include "Setting.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include "../cocos/support/zip_support/ZipUtils.h"
|
||||
#include "../external/json/json.hpp"
|
||||
#include "../utils/Result.hpp"
|
||||
#include "../utils/VersionInfo.hpp"
|
||||
#include "../external/json/json.hpp"
|
||||
#include "../utils/general.hpp"
|
||||
#include "../cocos/support/zip_support/ZipUtils.h"
|
||||
#include "Hook.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
#include "Setting.hpp"
|
||||
#include "Types.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
@ -18,15 +18,14 @@
|
|||
#include <vector>
|
||||
|
||||
namespace geode {
|
||||
template<class T>
|
||||
template <class T>
|
||||
struct HandleToSaved : public T {
|
||||
Mod* m_mod;
|
||||
std::string m_key;
|
||||
|
||||
HandleToSaved(std::string const& key, Mod* mod, T const& value)
|
||||
: T(value),
|
||||
m_key(key),
|
||||
m_mod(mod) {}
|
||||
HandleToSaved(std::string const& key, Mod* mod, T const& value) :
|
||||
T(value), m_key(key), m_mod(mod) {}
|
||||
|
||||
HandleToSaved(HandleToSaved const&) = delete;
|
||||
HandleToSaved(HandleToSaved&&) = delete;
|
||||
~HandleToSaved();
|
||||
|
@ -104,7 +103,6 @@ namespace geode {
|
|||
|
||||
friend class ::InternalMod;
|
||||
friend class Loader;
|
||||
friend class ::InternalLoader;
|
||||
friend struct ModInfo;
|
||||
|
||||
template <class = void>
|
||||
|
@ -169,41 +167,45 @@ namespace geode {
|
|||
return false;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
T getSavedValue(std::string const& key) {
|
||||
if (m_saved.count(key)) {
|
||||
try {
|
||||
// json -> T may fail
|
||||
return m_saved.at(key);
|
||||
} catch(...) {}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
}
|
||||
return T();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
T getSavedValue(std::string const& key, T const& defaultValue) {
|
||||
if (m_saved.count(key)) {
|
||||
try {
|
||||
// json -> T may fail
|
||||
return m_saved.at(key);
|
||||
} catch(...) {}
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
}
|
||||
m_saved[key] = defaultValue;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
HandleToSaved<T> getSavedMutable(std::string const& key) {
|
||||
return HandleToSaved(key, this, this->getSavedValue<T>(key));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
HandleToSaved<T> getSavedMutable(std::string const& key, T const& defaultValue) {
|
||||
return HandleToSaved(key, this, this->getSavedValue<T>(key, defaultValue));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of an automatically saved variable. When the game is
|
||||
* Set the value of an automatically saved variable. When the game is
|
||||
* closed, the value is automatically saved under the key
|
||||
* @param key Key of the saved value
|
||||
* @param value Value
|
||||
|
@ -337,9 +339,9 @@ namespace geode {
|
|||
Result<> disable();
|
||||
|
||||
/**
|
||||
* Disable & unload this mod (if supported), then delete the mod's
|
||||
* .geode package. If unloading isn't supported, the mod's binary
|
||||
* will stay loaded, and in all cases the Mod* instance will still
|
||||
* Disable & unload this mod (if supported), then delete the mod's
|
||||
* .geode package. If unloading isn't supported, the mod's binary
|
||||
* will stay loaded, and in all cases the Mod* instance will still
|
||||
* exist and be interactable.
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
|
@ -387,7 +389,7 @@ namespace geode {
|
|||
ModJson getRuntimeInfo() const;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
template <class T>
|
||||
HandleToSaved<T>::~HandleToSaved() {
|
||||
m_mod->setSavedValue(m_key, static_cast<T>(*this));
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include "../platform/cplatform.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class InternalLoader;
|
||||
class InternalMod;
|
||||
|
||||
namespace geode {
|
||||
|
@ -130,7 +130,7 @@ namespace geode {
|
|||
};
|
||||
|
||||
constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
|
||||
|
||||
|
||||
class Mod;
|
||||
class Setting;
|
||||
class Loader;
|
||||
|
@ -146,7 +146,7 @@ namespace geode {
|
|||
template <class, class>
|
||||
class FieldIntermediate;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -338,7 +338,7 @@ namespace geode {
|
|||
|
||||
template <class C>
|
||||
static EventListenerNode* create(
|
||||
C* cls, typename EventListener<Filter>::MemberFn<C> callback
|
||||
C* cls, typename EventListener<Filter>::template MemberFn<C> callback
|
||||
) {
|
||||
// for some reason msvc won't let me just call EventListenerNode::create...
|
||||
// it claims no return value...
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
#include <InternalLoader.hpp>
|
||||
#include <array>
|
||||
#include <Geode/modify/LoadingLayer.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include <array>
|
||||
#include <fmt/format.h>
|
||||
#include <loader/LoaderImpl.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
@ -21,23 +21,21 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
|||
|
||||
auto count = Loader::get()->getAllMods().size();
|
||||
|
||||
auto label = CCLabelBMFont::create(
|
||||
fmt::format("Geode: Loaded {} mods", count).c_str(),
|
||||
"goldFont.fnt"
|
||||
);
|
||||
auto label =
|
||||
CCLabelBMFont::create(fmt::format("Geode: Loaded {} mods", count).c_str(), "goldFont.fnt");
|
||||
label->setPosition(winSize.width / 2, 30.f);
|
||||
label->setScale(.45f);
|
||||
label->setID("geode-loaded-info");
|
||||
this->addChild(label);
|
||||
|
||||
// for some reason storing the listener as a field caused the
|
||||
// for some reason storing the listener as a field caused the
|
||||
// destructor for the field not to be run
|
||||
this->addChild(EventListenerNode<ResourceDownloadFilter>::create(
|
||||
this, &CustomLoadingLayer::updateResourcesProgress
|
||||
));
|
||||
|
||||
// verify loader resources
|
||||
if (!InternalLoader::get()->verifyLoaderResources()) {
|
||||
if (!LoaderImpl::get()->verifyLoaderResources()) {
|
||||
m_fields->m_updatingResources = true;
|
||||
this->setUpdateText("Downloading Resources");
|
||||
}
|
||||
|
@ -62,7 +60,7 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
|
|||
this->loadAssets();
|
||||
},
|
||||
[&](UpdateFailed const& error) {
|
||||
InternalLoader::platformMessageBox(
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Error updating resources",
|
||||
"Unable to update Geode resources: " +
|
||||
error + ".\n"
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include "../ids/AddIDs.hpp"
|
||||
#include "../ui/internal/list/ModListLayer.hpp"
|
||||
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/modify/MenuLayer.hpp>
|
||||
#include <Geode/modify/Modify.hpp>
|
||||
#include <Geode/ui/BasedButtonSprite.hpp>
|
||||
#include <Geode/ui/Notification.hpp>
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <Geode/ui/Notification.hpp>
|
||||
#include <Geode/ui/Popup.hpp>
|
||||
#include <Geode/utils/cocos.hpp>
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include "../ids/AddIDs.hpp"
|
||||
#include <InternalMod.hpp>
|
||||
#include <Geode/modify/Modify.hpp>
|
||||
#include <Geode/modify/MenuLayer.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
@ -45,15 +44,14 @@ $execute {
|
|||
struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
||||
CCSprite* m_geodeButton;
|
||||
|
||||
bool init() {
|
||||
if (!MenuLayer::init())
|
||||
return false;
|
||||
|
||||
// make sure to add the string IDs for nodes (Geode has no manual
|
||||
// hook order support yet so gotta do this to ensure)
|
||||
NodeIDs::provideFor(this);
|
||||
bool init() {
|
||||
if (!MenuLayer::init()) return false;
|
||||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
// make sure to add the string IDs for nodes (Geode has no manual
|
||||
// hook order support yet so gotta do this to ensure)
|
||||
NodeIDs::provideFor(this);
|
||||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
|
||||
// add geode button
|
||||
|
||||
|
@ -66,7 +64,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
|||
))
|
||||
.orMake<ButtonSprite>("!!");
|
||||
|
||||
auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
|
||||
auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
|
||||
|
||||
auto btn = CCMenuItemSpriteExtra::create(
|
||||
m_fields->m_geodeButton, this, menu_selector(CustomMenuLayer::onGeode)
|
||||
|
@ -74,46 +72,44 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
|||
btn->setID("geode-button"_spr);
|
||||
bottomMenu->addChild(btn);
|
||||
|
||||
bottomMenu->updateLayout();
|
||||
bottomMenu->updateLayout();
|
||||
|
||||
if (auto node = this->getChildByID("settings-gamepad-icon")) {
|
||||
node->setPositionX(bottomMenu->getChildByID(
|
||||
"settings-button"
|
||||
)->getPositionX() + winSize.width / 2);
|
||||
}
|
||||
|
||||
// show if some mods failed to load
|
||||
static bool shownFailedNotif = false;
|
||||
if (!shownFailedNotif) {
|
||||
shownFailedNotif = true;
|
||||
if (Loader::get()->getFailedMods().size()) {
|
||||
Notification::create(
|
||||
"Some mods failed to load",
|
||||
NotificationIcon::Error
|
||||
)->show();
|
||||
}
|
||||
if (auto node = this->getChildByID("settings-gamepad-icon")) {
|
||||
node->setPositionX(
|
||||
bottomMenu->getChildByID("settings-button")->getPositionX() + winSize.width / 2
|
||||
);
|
||||
}
|
||||
|
||||
// show crash info
|
||||
static bool shownLastCrash = false;
|
||||
if (Loader::get()->didLastLaunchCrash() && !shownLastCrash) {
|
||||
shownLastCrash = true;
|
||||
auto popup = createQuickPopup(
|
||||
"Crashed",
|
||||
"It appears that the last session crashed. Would you like to "
|
||||
"send a <cy>crash report</c>?",
|
||||
"No", "Send",
|
||||
[](auto, bool btn2) {
|
||||
if (btn2) {
|
||||
geode::openIssueReportPopup(InternalMod::get());
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
popup->m_scene = this;
|
||||
popup->m_noElasticity = true;
|
||||
popup->show();
|
||||
}
|
||||
// show if some mods failed to load
|
||||
static bool shownFailedNotif = false;
|
||||
if (!shownFailedNotif) {
|
||||
shownFailedNotif = true;
|
||||
if (Loader::get()->getFailedMods().size()) {
|
||||
Notification::create("Some mods failed to load", NotificationIcon::Error)->show();
|
||||
}
|
||||
}
|
||||
|
||||
// show crash info
|
||||
static bool shownLastCrash = false;
|
||||
if (Loader::get()->didLastLaunchCrash() && !shownLastCrash) {
|
||||
shownLastCrash = true;
|
||||
auto popup = createQuickPopup(
|
||||
"Crashed",
|
||||
"It appears that the last session crashed. Would you like to "
|
||||
"send a <cy>crash report</c>?",
|
||||
"No",
|
||||
"Send",
|
||||
[](auto, bool btn2) {
|
||||
if (btn2) {
|
||||
geode::openIssueReportPopup(InternalMod::get());
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
popup->m_scene = this;
|
||||
popup->m_noElasticity = true;
|
||||
popup->show();
|
||||
}
|
||||
|
||||
// update mods index
|
||||
if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
|
||||
|
@ -153,7 +149,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
|
|||
}
|
||||
}
|
||||
|
||||
void onGeode(CCObject*) {
|
||||
ModListLayer::scene();
|
||||
}
|
||||
void onGeode(CCObject*) {
|
||||
ModListLayer::scene();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,14 +6,14 @@ USE_GEODE_NAMESPACE();
|
|||
|
||||
struct SaveLoader : Modify<SaveLoader, AppDelegate> {
|
||||
void trySaveGame() {
|
||||
log::log(Severity::Info, Loader::getInternalMod(), "Saving...");
|
||||
log::info("Saving...");
|
||||
|
||||
auto r = Loader::get()->saveData();
|
||||
if (!r) {
|
||||
log::log(Severity::Error, Loader::getInternalMod(), "{}", r.unwrapErr());
|
||||
log::info("{}", r.unwrapErr());
|
||||
}
|
||||
|
||||
log::log(Severity::Info, Loader::getInternalMod(), "Saved");
|
||||
log::info("Saved");
|
||||
|
||||
return AppDelegate::trySaveGame();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include <InternalLoader.hpp>
|
||||
#include "../loader/LoaderImpl.hpp"
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
@ -6,7 +6,7 @@ USE_GEODE_NAMESPACE();
|
|||
|
||||
struct FunctionQueue : Modify<FunctionQueue, CCScheduler> {
|
||||
void update(float dt) {
|
||||
InternalLoader::get()->executeGDThreadQueue();
|
||||
LoaderImpl::get()->executeGDThreadQueue();
|
||||
return CCScheduler::update(dt);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,227 +0,0 @@
|
|||
#include "InternalLoader.hpp"
|
||||
|
||||
#include "InternalMod.hpp"
|
||||
#include "resources.hpp"
|
||||
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/IPC.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <Geode/utils/file.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <hash.hpp>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
ResourceDownloadEvent::ResourceDownloadEvent(
|
||||
UpdateStatus const& status
|
||||
) : status(status) {}
|
||||
|
||||
ListenerResult ResourceDownloadFilter::handle(
|
||||
std::function<Callback> fn,
|
||||
ResourceDownloadEvent* event
|
||||
) {
|
||||
fn(event);
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
ResourceDownloadFilter::ResourceDownloadFilter() {}
|
||||
|
||||
InternalLoader::InternalLoader() : Loader() {}
|
||||
|
||||
InternalLoader::~InternalLoader() {
|
||||
this->closePlatformConsole();
|
||||
}
|
||||
|
||||
InternalLoader* InternalLoader::get() {
|
||||
static auto g_geode = new InternalLoader;
|
||||
return g_geode;
|
||||
}
|
||||
|
||||
bool InternalLoader::setup() {
|
||||
log::log(Severity::Debug, InternalMod::get(), "Set up internal mod representation");
|
||||
log::log(Severity::Debug, InternalMod::get(), "Loading hooks... ");
|
||||
|
||||
if (!this->loadHooks()) {
|
||||
log::log(
|
||||
Severity::Error, InternalMod::get(),
|
||||
"There were errors loading some hooks, see console for details"
|
||||
);
|
||||
}
|
||||
|
||||
log::log(Severity::Debug, InternalMod::get(), "Loaded hooks");
|
||||
|
||||
log::log(Severity::Debug, InternalMod::get(), "Setting up IPC...");
|
||||
|
||||
this->setupIPC();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalLoader::isReadyToHook() const {
|
||||
return m_readyToHook;
|
||||
}
|
||||
|
||||
void InternalLoader::addInternalHook(Hook* hook, Mod* mod) {
|
||||
m_internalHooks.push_back({hook, mod});
|
||||
}
|
||||
|
||||
bool InternalLoader::loadHooks() {
|
||||
m_readyToHook = true;
|
||||
auto thereWereErrors = false;
|
||||
for (auto const& hook : m_internalHooks) {
|
||||
auto res = hook.second->addHook(hook.first);
|
||||
if (!res) {
|
||||
log::log(Severity::Error, hook.second, "{}", res.unwrapErr());
|
||||
thereWereErrors = true;
|
||||
}
|
||||
}
|
||||
// free up memory
|
||||
m_internalHooks.clear();
|
||||
return !thereWereErrors;
|
||||
}
|
||||
|
||||
void InternalLoader::queueInGDThread(ScheduledFunction func) {
|
||||
std::lock_guard<std::mutex> lock(m_gdThreadMutex);
|
||||
m_gdThreadQueue.push_back(func);
|
||||
}
|
||||
|
||||
void InternalLoader::executeGDThreadQueue() {
|
||||
// copy queue to avoid locking mutex if someone is
|
||||
// running addToGDThread inside their function
|
||||
m_gdThreadMutex.lock();
|
||||
auto queue = m_gdThreadQueue;
|
||||
m_gdThreadQueue.clear();
|
||||
m_gdThreadMutex.unlock();
|
||||
|
||||
// call queue
|
||||
for (auto const& func : queue) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
void InternalLoader::logConsoleMessage(std::string const& msg) {
|
||||
if (m_platformConsoleOpen) {
|
||||
// TODO: make flushing optional
|
||||
std::cout << msg << '\n' << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
bool InternalLoader::platformConsoleOpen() const {
|
||||
return m_platformConsoleOpen;
|
||||
}
|
||||
|
||||
void InternalLoader::downloadLoaderResources() {
|
||||
auto version = this->getVersion().toString();
|
||||
auto tempResourcesZip = dirs::getTempDir() / "new.zip";
|
||||
auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
|
||||
|
||||
web::AsyncWebRequest()
|
||||
.join("update-geode-loader-resources")
|
||||
.fetch(fmt::format(
|
||||
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version
|
||||
))
|
||||
.into(tempResourcesZip)
|
||||
.then([tempResourcesZip, resourcesDir](auto) {
|
||||
// unzip resources zip
|
||||
auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
|
||||
if (!unzip) {
|
||||
return ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr())
|
||||
).post();
|
||||
}
|
||||
ResourceDownloadEvent(UpdateFinished()).post();
|
||||
})
|
||||
.expect([](std::string const& info) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to download resources: " + info)
|
||||
).post();
|
||||
})
|
||||
.progress([](auto&, double now, double total) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateProgress(
|
||||
static_cast<uint8_t>(now / total * 100.0),
|
||||
"Downloading resources"
|
||||
)
|
||||
).post();
|
||||
});
|
||||
}
|
||||
|
||||
bool InternalLoader::verifyLoaderResources() {
|
||||
static std::optional<bool> CACHED = std::nullopt;
|
||||
if (CACHED.has_value()) {
|
||||
return CACHED.value();
|
||||
}
|
||||
|
||||
// geode/resources/geode.loader
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure every file was covered
|
||||
size_t coverage = 0;
|
||||
|
||||
// verify hashes
|
||||
for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
|
||||
auto name = file.path().filename().string();
|
||||
// skip unknown files
|
||||
if (!LOADER_RESOURCE_HASHES.count(name)) {
|
||||
continue;
|
||||
}
|
||||
// verify hash
|
||||
auto hash = calculateSHA256(file.path());
|
||||
if (hash != LOADER_RESOURCE_HASHES.at(name)) {
|
||||
log::debug(
|
||||
"compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)
|
||||
);
|
||||
this->downloadLoaderResources();
|
||||
return false;
|
||||
}
|
||||
coverage += 1;
|
||||
}
|
||||
|
||||
// make sure every file was found
|
||||
if (coverage != LOADER_RESOURCE_HASHES.size()) {
|
||||
this->downloadLoaderResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nlohmann::json InternalLoader::processRawIPC(void* rawHandle, std::string const& buffer) {
|
||||
nlohmann::json reply;
|
||||
try {
|
||||
// parse received message
|
||||
auto json = nlohmann::json::parse(buffer);
|
||||
if (!json.contains("mod") || !json["mod"].is_string()) {
|
||||
log::warn("Received IPC message without 'mod' field");
|
||||
return reply;
|
||||
}
|
||||
if (!json.contains("message") || !json["message"].is_string()) {
|
||||
log::warn("Received IPC message without 'message' field");
|
||||
return reply;
|
||||
}
|
||||
nlohmann::json data;
|
||||
if (json.contains("data")) {
|
||||
data = json["data"];
|
||||
}
|
||||
// log::debug("Posting IPC event");
|
||||
// ! warning: if the event system is ever made asynchronous this will break!
|
||||
IPCEvent(rawHandle, json["mod"], json["message"], data, reply).post();
|
||||
} catch(...) {
|
||||
log::warn("Received IPC message that isn't valid JSON");
|
||||
}
|
||||
return reply;
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "FileWatcher.hpp"
|
||||
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/utils/Result.hpp>
|
||||
#include <Geode/external/json/json.hpp>
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
struct ResourceDownloadEvent : public Event {
|
||||
const UpdateStatus status;
|
||||
ResourceDownloadEvent(UpdateStatus const& status);
|
||||
};
|
||||
|
||||
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
|
||||
*/
|
||||
class InternalLoader : public Loader {
|
||||
protected:
|
||||
std::vector<std::function<void(void)>> m_gdThreadQueue;
|
||||
mutable std::mutex m_gdThreadMutex;
|
||||
bool m_platformConsoleOpen = false;
|
||||
|
||||
std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
|
||||
bool m_readyToHook;
|
||||
|
||||
void downloadLoaderResources();
|
||||
|
||||
bool loadHooks();
|
||||
void setupIPC();
|
||||
|
||||
InternalLoader();
|
||||
~InternalLoader();
|
||||
|
||||
friend class Loader;
|
||||
|
||||
public:
|
||||
static InternalLoader* get();
|
||||
|
||||
bool setup();
|
||||
|
||||
static nlohmann::json processRawIPC(void* rawHandle, std::string const& buffer);
|
||||
|
||||
void queueInGDThread(ScheduledFunction func);
|
||||
void executeGDThreadQueue();
|
||||
|
||||
void logConsoleMessage(std::string const& msg);
|
||||
bool platformConsoleOpen() const;
|
||||
void openPlatformConsole();
|
||||
void closePlatformConsole();
|
||||
static void platformMessageBox(char const* title, std::string const& info);
|
||||
|
||||
bool verifyLoaderResources();
|
||||
|
||||
bool isReadyToHook() const;
|
||||
void addInternalHook(Hook* hook, Mod* mod);
|
||||
|
||||
friend int geodeEntry(void* platformData);
|
||||
};
|
|
@ -1,8 +1,10 @@
|
|||
#include "InternalMod.hpp"
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include "InternalLoader.hpp"
|
||||
|
||||
#include "about.hpp"
|
||||
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <LoaderImpl.hpp>
|
||||
|
||||
static constexpr char const* SUPPORT_INFO = R"MD(
|
||||
**Geode** is funded through your gracious <cy>**donations**</c>!
|
||||
You can support our work by sending <cp>**catgirl pictures**</c> to [HJfod](user:104257) :))
|
||||
|
@ -13,7 +15,7 @@ static ModInfo getInternalModInfo() {
|
|||
auto json = ModJson::parse(LOADER_MOD_JSON);
|
||||
auto infoRes = ModInfo::create(json);
|
||||
if (infoRes.isErr()) {
|
||||
InternalLoader::platformMessageBox(
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Fatal Internal Error",
|
||||
"Unable to parse loader mod.json: \"" + infoRes.unwrapErr() +
|
||||
"\"\n"
|
||||
|
@ -29,7 +31,7 @@ static ModInfo getInternalModInfo() {
|
|||
return info;
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
InternalLoader::platformMessageBox(
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Fatal Internal Error",
|
||||
"Unable to parse loader mod.json: \"" + std::string(e.what()) +
|
||||
"\"\n"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <cocos2d.h>
|
||||
#include <crashlog.hpp>
|
||||
#include <filesystem>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
@ -10,30 +11,26 @@ ghc::filesystem::path dirs::getGameDir() {
|
|||
}
|
||||
|
||||
ghc::filesystem::path dirs::getSaveDir() {
|
||||
#ifdef GEODE_IS_MACOS
|
||||
// not using ~/Library/Caches
|
||||
return ghc::filesystem::path("/Users/Shared/Geode");
|
||||
#elif defined(GEODE_IS_WINDOWS)
|
||||
return ghc::filesystem::path(
|
||||
ghc::filesystem::weakly_canonical(
|
||||
CCFileUtils::sharedFileUtils()->getWritablePath().c_str()
|
||||
).string()
|
||||
);
|
||||
#else
|
||||
return ghc::filesystem::path(
|
||||
CCFileUtils::sharedFileUtils()->getWritablePath().c_str()
|
||||
);
|
||||
#endif
|
||||
#ifdef GEODE_IS_MACOS
|
||||
// not using ~/Library/Caches
|
||||
return ghc::filesystem::path("/Users/Shared/Geode");
|
||||
#elif defined(GEODE_IS_WINDOWS)
|
||||
// this is std::filesystem intentionally because ghc version doesnt want to work with softlinked directories
|
||||
return ghc::filesystem::path(
|
||||
std::filesystem::weakly_canonical(CCFileUtils::sharedFileUtils()->getWritablePath().c_str())
|
||||
.string()
|
||||
);
|
||||
#else
|
||||
return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
ghc::filesystem::path dirs::getGeodeDir() {
|
||||
#ifdef GEODE_IS_MACOS
|
||||
char cwd[PATH_MAX];
|
||||
getcwd(cwd, sizeof(cwd));
|
||||
return ghc::filesystem::path(cwd) / "geode";
|
||||
#else
|
||||
#ifdef GEODE_IS_MACOS
|
||||
return ghc::filesystem::current_path() / "geode";
|
||||
#else
|
||||
return dirs::getGameDir() / "geode";
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
ghc::filesystem::path dirs::getGeodeSaveDir() {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
#include <Geode/utils/ranges.hpp>
|
||||
#include <vector>
|
||||
// #include <hook/hook.hpp>
|
||||
#include "InternalLoader.hpp"
|
||||
#include "InternalMod.hpp"
|
||||
|
||||
#include <Geode/hook-core/Hook.hpp>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include <Geode/loader/IPC.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
@ -25,8 +24,5 @@ ListenerResult IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) {
|
|||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
IPCFilter::IPCFilter(
|
||||
std::string const& modID,
|
||||
std::string const& messageID
|
||||
) : m_modID(modID),
|
||||
m_messageID(messageID) {}
|
||||
IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID) :
|
||||
m_modID(modID), m_messageID(messageID) {}
|
||||
|
|
|
@ -1,395 +1,126 @@
|
|||
#include "LoaderImpl.hpp"
|
||||
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <about.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <crashlog.hpp>
|
||||
Loader::Loader() : m_impl(new Impl) {}
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
Loader::~Loader() {}
|
||||
|
||||
Loader* Loader::get() {
|
||||
return InternalLoader::get();
|
||||
static auto g_geode = new Loader;
|
||||
return g_geode;
|
||||
}
|
||||
|
||||
Loader::~Loader() {
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
delete mod;
|
||||
}
|
||||
m_mods.clear();
|
||||
log::Logs::clear();
|
||||
ghc::filesystem::remove_all(dirs::getModRuntimeDir());
|
||||
ghc::filesystem::remove_all(dirs::getTempDir());
|
||||
}
|
||||
|
||||
// Initialization
|
||||
|
||||
void Loader::createDirectories() {
|
||||
#ifdef GEODE_IS_MACOS
|
||||
ghc::filesystem::create_directory(dirs::getSaveDir());
|
||||
#endif
|
||||
|
||||
ghc::filesystem::create_directories(dirs::getGeodeResourcesDir());
|
||||
ghc::filesystem::create_directory(dirs::getModConfigDir());
|
||||
ghc::filesystem::create_directory(dirs::getModsDir());
|
||||
ghc::filesystem::create_directory(dirs::getGeodeLogDir());
|
||||
ghc::filesystem::create_directory(dirs::getTempDir());
|
||||
ghc::filesystem::create_directory(dirs::getModRuntimeDir());
|
||||
|
||||
if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) {
|
||||
m_modSearchDirectories.push_back(dirs::getModsDir());
|
||||
}
|
||||
}
|
||||
|
||||
Result<> Loader::setup() {
|
||||
if (m_isSetup) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
log::Logs::setup();
|
||||
|
||||
if (crashlog::setupPlatformHandler()) {
|
||||
log::debug("Set up platform crash logger");
|
||||
}
|
||||
else {
|
||||
log::debug("Unable to set up platform crash logger");
|
||||
}
|
||||
|
||||
log::debug("Setting up Loader...");
|
||||
|
||||
this->createDirectories();
|
||||
auto sett = this->loadData();
|
||||
if (!sett) {
|
||||
log::warn("Unable to load loader settings: {}", sett.unwrapErr());
|
||||
}
|
||||
this->refreshModsList();
|
||||
|
||||
this->queueInGDThread([]() {
|
||||
Loader::get()->addSearchPaths();
|
||||
});
|
||||
|
||||
m_isSetup = true;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
void Loader::addSearchPaths() {
|
||||
CCFileUtils::get()->addPriorityPath(dirs::getGeodeResourcesDir().string().c_str());
|
||||
CCFileUtils::get()->addPriorityPath(dirs::getModRuntimeDir().string().c_str());
|
||||
}
|
||||
|
||||
void Loader::updateResources() {
|
||||
log::debug("Adding resources");
|
||||
|
||||
// add own spritesheets
|
||||
this->updateModResources(InternalMod::get());
|
||||
|
||||
// add mods' spritesheets
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
this->updateModResources(mod);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Mod*> Loader::getAllMods() {
|
||||
return map::values(m_mods);
|
||||
}
|
||||
|
||||
Mod* Loader::getInternalMod() {
|
||||
return InternalMod::get();
|
||||
}
|
||||
|
||||
std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
|
||||
return m_invalidMods;
|
||||
}
|
||||
|
||||
// Version info
|
||||
|
||||
VersionInfo Loader::getVersion() {
|
||||
return LOADER_VERSION;
|
||||
}
|
||||
|
||||
VersionInfo Loader::minModVersion() {
|
||||
return VersionInfo { 0, 3, 1 };
|
||||
}
|
||||
|
||||
VersionInfo Loader::maxModVersion() {
|
||||
return VersionInfo {
|
||||
Loader::getVersion().getMajor(),
|
||||
Loader::getVersion().getMinor(),
|
||||
// todo: dynamic version info (vM.M.*)
|
||||
99999999,
|
||||
};
|
||||
}
|
||||
|
||||
bool Loader::isModVersionSupported(VersionInfo const& version) {
|
||||
return
|
||||
version >= Loader::minModVersion() &&
|
||||
version <= Loader::maxModVersion();
|
||||
}
|
||||
|
||||
// Data saving
|
||||
|
||||
Result<> Loader::saveData() {
|
||||
// save mods' data
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
auto r = mod->saveData();
|
||||
if (!r) {
|
||||
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
}
|
||||
}
|
||||
// save loader data
|
||||
GEODE_UNWRAP(InternalMod::get()->saveData());
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Loader::loadData() {
|
||||
auto e = InternalMod::get()->loadData();
|
||||
if (!e) {
|
||||
log::warn("Unable to load loader settings: {}", e.unwrapErr());
|
||||
}
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
auto r = mod->loadData();
|
||||
if (!r) {
|
||||
log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Mod loading
|
||||
|
||||
Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) {
|
||||
if (m_mods.count(info.id)) {
|
||||
return Err(fmt::format("Mod with ID '{}' already loaded", info.id));
|
||||
}
|
||||
|
||||
// create Mod instance
|
||||
auto mod = new Mod(info);
|
||||
m_mods.insert({ info.id, mod });
|
||||
mod->m_enabled = InternalMod::get()->getSavedValue<bool>(
|
||||
"should-load-" + info.id, true
|
||||
);
|
||||
|
||||
// add mod resources
|
||||
this->queueInGDThread([this, mod]() {
|
||||
auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
|
||||
|
||||
CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
|
||||
this->updateModResources(mod);
|
||||
});
|
||||
|
||||
// this loads the mod if its dependencies are resolved
|
||||
GEODE_UNWRAP(mod->updateDependencies());
|
||||
|
||||
return Ok(mod);
|
||||
}
|
||||
|
||||
Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) {
|
||||
auto res = ModInfo::createFromGeodeFile(file);
|
||||
if (!res) {
|
||||
m_invalidMods.push_back(InvalidGeodeFile {
|
||||
.path = file,
|
||||
.reason = res.unwrapErr(),
|
||||
});
|
||||
return Err(res.unwrapErr());
|
||||
}
|
||||
return this->loadModFromInfo(res.unwrap());
|
||||
}
|
||||
|
||||
bool Loader::isModInstalled(std::string const& id) const {
|
||||
return m_mods.count(id) && !m_mods.at(id)->isUninstalled();
|
||||
}
|
||||
|
||||
Mod* Loader::getInstalledMod(std::string const& id) const {
|
||||
if (m_mods.count(id) && !m_mods.at(id)->isUninstalled()) {
|
||||
return m_mods.at(id);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Loader::isModLoaded(std::string const& id) const {
|
||||
return m_mods.count(id) && m_mods.at(id)->isLoaded();
|
||||
}
|
||||
|
||||
Mod* Loader::getLoadedMod(std::string const& id) const {
|
||||
if (m_mods.count(id)) {
|
||||
auto mod = m_mods.at(id);
|
||||
if (mod->isLoaded()) {
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Loader::dispatchScheduledFunctions(Mod* mod) {
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
for (auto& func : m_scheduledFunctions) {
|
||||
func();
|
||||
}
|
||||
m_scheduledFunctions.clear();
|
||||
}
|
||||
|
||||
void Loader::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
if (mod) {
|
||||
return func();
|
||||
}
|
||||
m_scheduledFunctions.push_back(func);
|
||||
return m_impl->createDirectories();
|
||||
}
|
||||
|
||||
void Loader::updateModResources(Mod* mod) {
|
||||
if (!mod->m_info.spritesheets.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
|
||||
|
||||
log::debug("Adding resources for {}", mod->getID());
|
||||
|
||||
// add spritesheets
|
||||
for (auto const& sheet : mod->m_info.spritesheets) {
|
||||
log::debug("Adding sheet {}", sheet);
|
||||
auto png = sheet + ".png";
|
||||
auto plist = sheet + ".plist";
|
||||
auto ccfu = CCFileUtils::get();
|
||||
|
||||
if (png == std::string(ccfu->fullPathForFilename(png.c_str(), false)) ||
|
||||
plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) {
|
||||
log::warn(
|
||||
"The resource dir of \"{}\" is missing \"{}\" png and/or plist files",
|
||||
mod->m_info.id, sheet
|
||||
);
|
||||
}
|
||||
else {
|
||||
CCTextureCache::get()->addImage(png.c_str(), false);
|
||||
CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
|
||||
}
|
||||
}
|
||||
return m_impl->updateModResources(mod);
|
||||
}
|
||||
|
||||
// Dependencies and refreshing
|
||||
void Loader::addSearchPaths() {
|
||||
return m_impl->addSearchPaths();
|
||||
}
|
||||
|
||||
void Loader::loadModsFromDirectory(
|
||||
ghc::filesystem::path const& dir,
|
||||
bool recursive
|
||||
) {
|
||||
log::debug("Searching {}", dir);
|
||||
for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
|
||||
// recursively search directories
|
||||
if (ghc::filesystem::is_directory(entry) && recursive) {
|
||||
this->loadModsFromDirectory(entry.path(), true);
|
||||
continue;
|
||||
}
|
||||
void Loader::dispatchScheduledFunctions(Mod* mod) {
|
||||
return m_impl->dispatchScheduledFunctions(mod);
|
||||
}
|
||||
|
||||
// skip this entry if it's not a file
|
||||
if (!ghc::filesystem::is_regular_file(entry)) {
|
||||
continue;
|
||||
}
|
||||
Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) {
|
||||
return m_impl->loadModFromInfo(info);
|
||||
}
|
||||
|
||||
// skip this entry if its extension is not .geode
|
||||
if (entry.path().extension() != GEODE_MOD_EXTENSION) {
|
||||
continue;
|
||||
}
|
||||
// skip this entry if it's already loaded
|
||||
if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool {
|
||||
return p->m_info.path == entry.path();
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
Result<> Loader::saveData() {
|
||||
return m_impl->saveData();
|
||||
}
|
||||
|
||||
// if mods should be loaded immediately, do that
|
||||
if (m_earlyLoadFinished) {
|
||||
auto load = this->loadModFromFile(entry);
|
||||
if (!load) {
|
||||
log::error("Unable to load {}: {}", entry, load.unwrapErr());
|
||||
}
|
||||
}
|
||||
// otherwise collect mods to load first to make sure the correct
|
||||
// versions of the mods are loaded and that early-loaded mods are
|
||||
// loaded early
|
||||
else {
|
||||
auto res = ModInfo::createFromGeodeFile(entry.path());
|
||||
if (!res) {
|
||||
m_invalidMods.push_back(InvalidGeodeFile {
|
||||
.path = entry.path(),
|
||||
.reason = res.unwrapErr(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
auto info = res.unwrap();
|
||||
Result<> Loader::loadData() {
|
||||
return m_impl->loadData();
|
||||
}
|
||||
|
||||
// skip this entry if it's already set to be loaded
|
||||
if (ranges::contains(m_modsToLoad, info)) {
|
||||
continue;
|
||||
}
|
||||
VersionInfo Loader::getVersion() {
|
||||
return m_impl->getVersion();
|
||||
}
|
||||
|
||||
// add to list of mods to load
|
||||
m_modsToLoad.push_back(info);
|
||||
}
|
||||
}
|
||||
VersionInfo Loader::minModVersion() {
|
||||
return m_impl->minModVersion();
|
||||
}
|
||||
|
||||
VersionInfo Loader::maxModVersion() {
|
||||
return m_impl->maxModVersion();
|
||||
}
|
||||
|
||||
bool Loader::isModVersionSupported(VersionInfo const& version) {
|
||||
return m_impl->isModVersionSupported(version);
|
||||
}
|
||||
|
||||
Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) {
|
||||
return m_impl->loadModFromFile(file);
|
||||
}
|
||||
|
||||
void Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
|
||||
return m_impl->loadModsFromDirectory(dir, recursive);
|
||||
}
|
||||
|
||||
void Loader::refreshModsList() {
|
||||
log::debug("Loading mods...");
|
||||
return m_impl->refreshModsList();
|
||||
}
|
||||
|
||||
// find mods
|
||||
for (auto& dir : m_modSearchDirectories) {
|
||||
this->loadModsFromDirectory(dir);
|
||||
}
|
||||
|
||||
// load early-load mods first
|
||||
for (auto& mod : m_modsToLoad) {
|
||||
if (mod.needsEarlyLoad) {
|
||||
auto load = this->loadModFromInfo(mod);
|
||||
if (!load) {
|
||||
log::error("Unable to load {}: {}", mod.id, load.unwrapErr());
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Loader::isModInstalled(std::string const& id) const {
|
||||
return m_impl->isModInstalled(id);
|
||||
}
|
||||
|
||||
// UI can be loaded now
|
||||
m_earlyLoadFinished = true;
|
||||
Mod* Loader::getInstalledMod(std::string const& id) const {
|
||||
return m_impl->getInstalledMod(id);
|
||||
}
|
||||
|
||||
// load the rest of the mods
|
||||
for (auto& mod : m_modsToLoad) {
|
||||
if (!mod.needsEarlyLoad) {
|
||||
auto load = this->loadModFromInfo(mod);
|
||||
if (!load) {
|
||||
log::error("Unable to load {}: {}", mod.id, load.unwrapErr());
|
||||
}
|
||||
}
|
||||
}
|
||||
m_modsToLoad.clear();
|
||||
bool Loader::isModLoaded(std::string const& id) const {
|
||||
return m_impl->isModLoaded(id);
|
||||
}
|
||||
|
||||
Mod* Loader::getLoadedMod(std::string const& id) const {
|
||||
return m_impl->getLoadedMod(id);
|
||||
}
|
||||
|
||||
std::vector<Mod*> Loader::getAllMods() {
|
||||
return m_impl->getAllMods();
|
||||
}
|
||||
|
||||
Mod* Loader::getInternalMod() {
|
||||
return m_impl->getInternalMod();
|
||||
}
|
||||
|
||||
void Loader::updateAllDependencies() {
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
(void)mod->updateDependencies();
|
||||
}
|
||||
return m_impl->updateAllDependencies();
|
||||
}
|
||||
|
||||
std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
|
||||
return m_impl->getFailedMods();
|
||||
}
|
||||
|
||||
void Loader::updateResources() {
|
||||
return m_impl->updateResources();
|
||||
}
|
||||
|
||||
void Loader::queueInGDThread(ScheduledFunction func) {
|
||||
return m_impl->queueInGDThread(func);
|
||||
}
|
||||
|
||||
void Loader::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
|
||||
return m_impl->scheduleOnModLoad(mod, func);
|
||||
}
|
||||
|
||||
void Loader::waitForModsToBeLoaded() {
|
||||
while (!m_earlyLoadFinished) {}
|
||||
}
|
||||
|
||||
// Misc
|
||||
|
||||
void Loader::queueInGDThread(ScheduledFunction func) {
|
||||
InternalLoader::get()->queueInGDThread(func);
|
||||
}
|
||||
|
||||
bool Loader::didLastLaunchCrash() const {
|
||||
return crashlog::didLastLaunchCrash();
|
||||
return m_impl->waitForModsToBeLoaded();
|
||||
}
|
||||
|
||||
void Loader::openPlatformConsole() {
|
||||
InternalLoader::get()->openPlatformConsole();
|
||||
return m_impl->openPlatformConsole();
|
||||
}
|
||||
|
||||
void Loader::closePlatfromConsole() {
|
||||
InternalLoader::get()->closePlatformConsole();
|
||||
void Loader::closePlatformConsole() {
|
||||
return m_impl->closePlatformConsole();
|
||||
}
|
||||
|
||||
bool Loader::didLastLaunchCrash() const {
|
||||
return m_impl->didLastLaunchCrash();
|
||||
}
|
596
loader/src/loader/LoaderImpl.cpp
Normal file
596
loader/src/loader/LoaderImpl.cpp
Normal file
|
@ -0,0 +1,596 @@
|
|||
|
||||
#include "LoaderImpl.hpp"
|
||||
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/IPC.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/utils/file.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <about.hpp>
|
||||
#include <crashlog.hpp>
|
||||
#include <fmt/format.h>
|
||||
#include <hash.hpp>
|
||||
#include <iostream>
|
||||
#include <resources.hpp>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
Loader::Impl* LoaderImpl::get() {
|
||||
return Loader::get()->m_impl.get();
|
||||
}
|
||||
|
||||
Loader::Impl::Impl() {}
|
||||
|
||||
Loader::Impl::~Impl() {}
|
||||
|
||||
// Initialization
|
||||
|
||||
void Loader::Impl::createDirectories() {
|
||||
#ifdef GEODE_IS_MACOS
|
||||
ghc::filesystem::create_directory(dirs::getSaveDir());
|
||||
#endif
|
||||
|
||||
ghc::filesystem::create_directories(dirs::getGeodeResourcesDir());
|
||||
ghc::filesystem::create_directory(dirs::getModConfigDir());
|
||||
ghc::filesystem::create_directory(dirs::getModsDir());
|
||||
ghc::filesystem::create_directory(dirs::getGeodeLogDir());
|
||||
ghc::filesystem::create_directory(dirs::getTempDir());
|
||||
ghc::filesystem::create_directory(dirs::getModRuntimeDir());
|
||||
|
||||
if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) {
|
||||
m_modSearchDirectories.push_back(dirs::getModsDir());
|
||||
}
|
||||
}
|
||||
|
||||
Result<> Loader::Impl::setup() {
|
||||
if (m_isSetup) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
log::Logs::setup();
|
||||
|
||||
if (crashlog::setupPlatformHandler()) {
|
||||
log::debug("Set up platform crash logger");
|
||||
}
|
||||
else {
|
||||
log::debug("Unable to set up platform crash logger");
|
||||
}
|
||||
|
||||
log::debug("Setting up Loader...");
|
||||
|
||||
this->createDirectories();
|
||||
auto sett = this->loadData();
|
||||
if (!sett) {
|
||||
log::warn("Unable to load loader settings: {}", sett.unwrapErr());
|
||||
}
|
||||
this->refreshModsList();
|
||||
|
||||
this->queueInGDThread([]() {
|
||||
Loader::get()->addSearchPaths();
|
||||
});
|
||||
|
||||
m_isSetup = true;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
void Loader::Impl::addSearchPaths() {
|
||||
CCFileUtils::get()->addPriorityPath(dirs::getGeodeResourcesDir().string().c_str());
|
||||
CCFileUtils::get()->addPriorityPath(dirs::getModRuntimeDir().string().c_str());
|
||||
}
|
||||
|
||||
void Loader::Impl::updateResources() {
|
||||
log::debug("Adding resources");
|
||||
|
||||
// add own spritesheets
|
||||
this->updateModResources(InternalMod::get());
|
||||
|
||||
// add mods' spritesheets
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
this->updateModResources(mod);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Mod*> Loader::Impl::getAllMods() {
|
||||
return map::values(m_mods);
|
||||
}
|
||||
|
||||
Mod* Loader::Impl::getInternalMod() {
|
||||
return InternalMod::get();
|
||||
}
|
||||
|
||||
std::vector<InvalidGeodeFile> Loader::Impl::getFailedMods() const {
|
||||
return m_invalidMods;
|
||||
}
|
||||
|
||||
// Version info
|
||||
|
||||
VersionInfo Loader::Impl::getVersion() {
|
||||
return LOADER_VERSION;
|
||||
}
|
||||
|
||||
VersionInfo Loader::Impl::minModVersion() {
|
||||
return VersionInfo { 0, 3, 1 };
|
||||
}
|
||||
|
||||
VersionInfo Loader::Impl::maxModVersion() {
|
||||
return VersionInfo {
|
||||
this->getVersion().getMajor(),
|
||||
this->getVersion().getMinor(),
|
||||
// todo: dynamic version info (vM.M.*)
|
||||
99999999,
|
||||
};
|
||||
}
|
||||
|
||||
bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
|
||||
return
|
||||
version >= this->minModVersion() &&
|
||||
version <= this->maxModVersion();
|
||||
}
|
||||
|
||||
// Data saving
|
||||
|
||||
Result<> Loader::Impl::saveData() {
|
||||
// save mods' data
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
auto r = mod->saveData();
|
||||
if (!r) {
|
||||
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
}
|
||||
}
|
||||
// save loader data
|
||||
GEODE_UNWRAP(InternalMod::get()->saveData());
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Loader::Impl::loadData() {
|
||||
auto e = InternalMod::get()->loadData();
|
||||
if (!e) {
|
||||
log::warn("Unable to load loader settings: {}", e.unwrapErr());
|
||||
}
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
auto r = mod->loadData();
|
||||
if (!r) {
|
||||
log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Mod loading
|
||||
|
||||
Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
|
||||
if (m_mods.count(info.id)) {
|
||||
return Err(fmt::format("Mod with ID '{}' already loaded", info.id));
|
||||
}
|
||||
|
||||
// create Mod instance
|
||||
auto mod = new Mod(info);
|
||||
m_mods.insert({ info.id, mod });
|
||||
mod->m_enabled = InternalMod::get()->getSavedValue<bool>(
|
||||
"should-load-" + info.id, true
|
||||
);
|
||||
|
||||
// add mod resources
|
||||
this->queueInGDThread([this, mod]() {
|
||||
auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
|
||||
|
||||
CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
|
||||
this->updateModResources(mod);
|
||||
});
|
||||
|
||||
// this loads the mod if its dependencies are resolved
|
||||
GEODE_UNWRAP(mod->updateDependencies());
|
||||
|
||||
return Ok(mod);
|
||||
}
|
||||
|
||||
Result<Mod*> Loader::Impl::loadModFromFile(ghc::filesystem::path const& file) {
|
||||
auto res = ModInfo::createFromGeodeFile(file);
|
||||
if (!res) {
|
||||
m_invalidMods.push_back(InvalidGeodeFile {
|
||||
.path = file,
|
||||
.reason = res.unwrapErr(),
|
||||
});
|
||||
return Err(res.unwrapErr());
|
||||
}
|
||||
return this->loadModFromInfo(res.unwrap());
|
||||
}
|
||||
|
||||
bool Loader::Impl::isModInstalled(std::string const& id) const {
|
||||
return m_mods.count(id) && !m_mods.at(id)->isUninstalled();
|
||||
}
|
||||
|
||||
Mod* Loader::Impl::getInstalledMod(std::string const& id) const {
|
||||
if (m_mods.count(id) && !m_mods.at(id)->isUninstalled()) {
|
||||
return m_mods.at(id);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Loader::Impl::isModLoaded(std::string const& id) const {
|
||||
return m_mods.count(id) && m_mods.at(id)->isLoaded();
|
||||
}
|
||||
|
||||
Mod* Loader::Impl::getLoadedMod(std::string const& id) const {
|
||||
if (m_mods.count(id)) {
|
||||
auto mod = m_mods.at(id);
|
||||
if (mod->isLoaded()) {
|
||||
return mod;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Loader::Impl::dispatchScheduledFunctions(Mod* mod) {
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
for (auto& func : m_scheduledFunctions) {
|
||||
func();
|
||||
}
|
||||
m_scheduledFunctions.clear();
|
||||
}
|
||||
|
||||
void Loader::Impl::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
if (mod) {
|
||||
return func();
|
||||
}
|
||||
m_scheduledFunctions.push_back(func);
|
||||
}
|
||||
|
||||
void Loader::Impl::updateModResources(Mod* mod) {
|
||||
if (!mod->m_info.spritesheets.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
|
||||
|
||||
log::debug("Adding resources for {}", mod->getID());
|
||||
|
||||
// add spritesheets
|
||||
for (auto const& sheet : mod->m_info.spritesheets) {
|
||||
log::debug("Adding sheet {}", sheet);
|
||||
auto png = sheet + ".png";
|
||||
auto plist = sheet + ".plist";
|
||||
auto ccfu = CCFileUtils::get();
|
||||
|
||||
if (png == std::string(ccfu->fullPathForFilename(png.c_str(), false)) ||
|
||||
plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) {
|
||||
log::warn(
|
||||
"The resource dir of \"{}\" is missing \"{}\" png and/or plist files",
|
||||
mod->m_info.id, sheet
|
||||
);
|
||||
}
|
||||
else {
|
||||
CCTextureCache::get()->addImage(png.c_str(), false);
|
||||
CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dependencies and refreshing
|
||||
|
||||
void Loader::Impl::loadModsFromDirectory(
|
||||
ghc::filesystem::path const& dir,
|
||||
bool recursive
|
||||
) {
|
||||
log::debug("Searching {}", dir);
|
||||
for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
|
||||
// recursively search directories
|
||||
if (ghc::filesystem::is_directory(entry) && recursive) {
|
||||
this->loadModsFromDirectory(entry.path(), true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip this entry if it's not a file
|
||||
if (!ghc::filesystem::is_regular_file(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip this entry if its extension is not .geode
|
||||
if (entry.path().extension() != GEODE_MOD_EXTENSION) {
|
||||
continue;
|
||||
}
|
||||
// skip this entry if it's already loaded
|
||||
if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool {
|
||||
return p->m_info.path == entry.path();
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if mods should be loaded immediately, do that
|
||||
if (m_earlyLoadFinished) {
|
||||
auto load = this->loadModFromFile(entry);
|
||||
if (!load) {
|
||||
log::error("Unable to load {}: {}", entry, load.unwrapErr());
|
||||
}
|
||||
}
|
||||
// otherwise collect mods to load first to make sure the correct
|
||||
// versions of the mods are loaded and that early-loaded mods are
|
||||
// loaded early
|
||||
else {
|
||||
auto res = ModInfo::createFromGeodeFile(entry.path());
|
||||
if (!res) {
|
||||
m_invalidMods.push_back(InvalidGeodeFile {
|
||||
.path = entry.path(),
|
||||
.reason = res.unwrapErr(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
auto info = res.unwrap();
|
||||
|
||||
// skip this entry if it's already set to be loaded
|
||||
if (ranges::contains(m_modsToLoad, info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add to list of mods to load
|
||||
m_modsToLoad.push_back(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::Impl::refreshModsList() {
|
||||
log::debug("Loading mods...");
|
||||
|
||||
// find mods
|
||||
for (auto& dir : m_modSearchDirectories) {
|
||||
this->loadModsFromDirectory(dir);
|
||||
}
|
||||
|
||||
// load early-load mods first
|
||||
for (auto& mod : m_modsToLoad) {
|
||||
if (mod.needsEarlyLoad) {
|
||||
auto load = this->loadModFromInfo(mod);
|
||||
if (!load) {
|
||||
log::error("Unable to load {}: {}", mod.id, load.unwrapErr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UI can be loaded now
|
||||
m_earlyLoadFinished = true;
|
||||
|
||||
// load the rest of the mods
|
||||
for (auto& mod : m_modsToLoad) {
|
||||
if (!mod.needsEarlyLoad) {
|
||||
auto load = this->loadModFromInfo(mod);
|
||||
if (!load) {
|
||||
log::error("Unable to load {}: {}", mod.id, load.unwrapErr());
|
||||
}
|
||||
}
|
||||
}
|
||||
m_modsToLoad.clear();
|
||||
}
|
||||
|
||||
void Loader::Impl::updateAllDependencies() {
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
(void)mod->updateDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::Impl::waitForModsToBeLoaded() {
|
||||
while (!m_earlyLoadFinished) {}
|
||||
}
|
||||
|
||||
bool Loader::Impl::didLastLaunchCrash() const {
|
||||
return crashlog::didLastLaunchCrash();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void Loader::Impl::reset() {
|
||||
this->closePlatformConsole();
|
||||
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
delete mod;
|
||||
}
|
||||
m_mods.clear();
|
||||
log::Logs::clear();
|
||||
ghc::filesystem::remove_all(dirs::getModRuntimeDir());
|
||||
ghc::filesystem::remove_all(dirs::getTempDir());
|
||||
}
|
||||
bool Loader::Impl::isReadyToHook() const {
|
||||
return m_readyToHook;
|
||||
}
|
||||
|
||||
void Loader::Impl::addInternalHook(Hook* hook, Mod* mod) {
|
||||
m_internalHooks.push_back({hook, mod});
|
||||
}
|
||||
|
||||
bool Loader::Impl::loadHooks() {
|
||||
m_readyToHook = true;
|
||||
auto thereWereErrors = false;
|
||||
for (auto const& hook : m_internalHooks) {
|
||||
auto res = hook.second->addHook(hook.first);
|
||||
if (!res) {
|
||||
log::log(Severity::Error, hook.second, "{}", res.unwrapErr());
|
||||
thereWereErrors = true;
|
||||
}
|
||||
}
|
||||
// free up memory
|
||||
m_internalHooks.clear();
|
||||
return !thereWereErrors;
|
||||
}
|
||||
|
||||
void Loader::Impl::queueInGDThread(ScheduledFunction func) {
|
||||
std::lock_guard<std::mutex> lock(m_gdThreadMutex);
|
||||
m_gdThreadQueue.push_back(func);
|
||||
}
|
||||
|
||||
void Loader::Impl::executeGDThreadQueue() {
|
||||
// copy queue to avoid locking mutex if someone is
|
||||
// running addToGDThread inside their function
|
||||
m_gdThreadMutex.lock();
|
||||
auto queue = m_gdThreadQueue;
|
||||
m_gdThreadQueue.clear();
|
||||
m_gdThreadMutex.unlock();
|
||||
|
||||
// call queue
|
||||
for (auto const& func : queue) {
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::Impl::logConsoleMessage(std::string const& msg) {
|
||||
if (m_platformConsoleOpen) {
|
||||
// TODO: make flushing optional
|
||||
std::cout << msg << '\n' << std::flush;
|
||||
}
|
||||
}
|
||||
|
||||
bool Loader::Impl::platformConsoleOpen() const {
|
||||
return m_platformConsoleOpen;
|
||||
}
|
||||
|
||||
bool Loader::Impl::shownInfoAlert(std::string const& key) {
|
||||
if (m_shownInfoAlerts.count(key)) {
|
||||
return true;
|
||||
}
|
||||
m_shownInfoAlerts.insert(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
void Loader::Impl::saveInfoAlerts(nlohmann::json& json) {
|
||||
json["alerts"] = m_shownInfoAlerts;
|
||||
}
|
||||
|
||||
void Loader::Impl::loadInfoAlerts(nlohmann::json& json) {
|
||||
m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>();
|
||||
}
|
||||
|
||||
void Loader::Impl::downloadLoaderResources() {
|
||||
auto version = this->getVersion().toString();
|
||||
auto tempResourcesZip = dirs::getTempDir() / "new.zip";
|
||||
auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
|
||||
|
||||
web::AsyncWebRequest()
|
||||
.join("update-geode-loader-resources")
|
||||
.fetch(fmt::format(
|
||||
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version
|
||||
))
|
||||
.into(tempResourcesZip)
|
||||
.then([tempResourcesZip, resourcesDir](auto) {
|
||||
// unzip resources zip
|
||||
auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
|
||||
if (!unzip) {
|
||||
return ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr())
|
||||
).post();
|
||||
}
|
||||
ResourceDownloadEvent(UpdateFinished()).post();
|
||||
})
|
||||
.expect([](std::string const& info) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateFailed("Unable to download resources: " + info)
|
||||
).post();
|
||||
})
|
||||
.progress([](auto&, double now, double total) {
|
||||
ResourceDownloadEvent(
|
||||
UpdateProgress(
|
||||
static_cast<uint8_t>(now / total * 100.0),
|
||||
"Downloading resources"
|
||||
)
|
||||
).post();
|
||||
});
|
||||
}
|
||||
|
||||
bool Loader::Impl::verifyLoaderResources() {
|
||||
static std::optional<bool> CACHED = std::nullopt;
|
||||
if (CACHED.has_value()) {
|
||||
return CACHED.value();
|
||||
}
|
||||
|
||||
// geode/resources/geode.loader
|
||||
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();
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure every file was covered
|
||||
size_t coverage = 0;
|
||||
|
||||
// verify hashes
|
||||
for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
|
||||
auto name = file.path().filename().string();
|
||||
// skip unknown files
|
||||
if (!LOADER_RESOURCE_HASHES.count(name)) {
|
||||
continue;
|
||||
}
|
||||
// verify hash
|
||||
auto hash = calculateSHA256(file.path());
|
||||
if (hash != LOADER_RESOURCE_HASHES.at(name)) {
|
||||
log::debug(
|
||||
"compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)
|
||||
);
|
||||
this->downloadLoaderResources();
|
||||
return false;
|
||||
}
|
||||
coverage += 1;
|
||||
}
|
||||
|
||||
// make sure every file was found
|
||||
if (coverage != LOADER_RESOURCE_HASHES.size()) {
|
||||
this->downloadLoaderResources();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
nlohmann::json Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) {
|
||||
nlohmann::json reply;
|
||||
try {
|
||||
// parse received message
|
||||
auto json = nlohmann::json::parse(buffer);
|
||||
if (!json.contains("mod") || !json["mod"].is_string()) {
|
||||
log::warn("Received IPC message without 'mod' field");
|
||||
return reply;
|
||||
}
|
||||
if (!json.contains("message") || !json["message"].is_string()) {
|
||||
log::warn("Received IPC message without 'message' field");
|
||||
return reply;
|
||||
}
|
||||
nlohmann::json data;
|
||||
if (json.contains("data")) {
|
||||
data = json["data"];
|
||||
}
|
||||
// log::debug("Posting IPC event");
|
||||
// ! warning: if the event system is ever made asynchronous this will break!
|
||||
IPCEvent(rawHandle, json["mod"], json["message"], data, reply).post();
|
||||
} catch(...) {
|
||||
log::warn("Received IPC message that isn't valid JSON");
|
||||
}
|
||||
return reply;
|
||||
}
|
||||
|
||||
ResourceDownloadEvent::ResourceDownloadEvent(
|
||||
UpdateStatus const& status
|
||||
) : status(status) {}
|
||||
|
||||
ListenerResult ResourceDownloadFilter::handle(
|
||||
std::function<Callback> fn,
|
||||
ResourceDownloadEvent* event
|
||||
) {
|
||||
fn(event);
|
||||
return ListenerResult::Propagate;
|
||||
}
|
||||
|
||||
ResourceDownloadFilter::ResourceDownloadFilter() {}
|
143
loader/src/loader/LoaderImpl.hpp
Normal file
143
loader/src/loader/LoaderImpl.hpp
Normal file
|
@ -0,0 +1,143 @@
|
|||
#include "FileWatcher.hpp"
|
||||
|
||||
#include <Geode/external/json/json.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/utils/Result.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <about.hpp>
|
||||
#include <crashlog.hpp>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
struct ResourceDownloadEvent : public Event {
|
||||
const UpdateStatus status;
|
||||
ResourceDownloadEvent(UpdateStatus const& status);
|
||||
};
|
||||
|
||||
class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
|
||||
public:
|
||||
using Callback = void(ResourceDownloadEvent*);
|
||||
|
||||
ListenerResult handle(std::function<Callback> fn, ResourceDownloadEvent* event);
|
||||
ResourceDownloadFilter();
|
||||
};
|
||||
|
||||
// TODO: Find a file convention for impl headers
|
||||
namespace geode {
|
||||
class LoaderImpl;
|
||||
}
|
||||
|
||||
class Loader::Impl {
|
||||
public:
|
||||
mutable std::mutex m_mutex;
|
||||
|
||||
std::vector<ghc::filesystem::path> m_modSearchDirectories;
|
||||
std::vector<ModInfo> m_modsToLoad;
|
||||
std::vector<InvalidGeodeFile> m_invalidMods;
|
||||
std::unordered_map<std::string, Mod*> m_mods;
|
||||
std::vector<ghc::filesystem::path> m_texturePaths;
|
||||
std::vector<ScheduledFunction> m_scheduledFunctions;
|
||||
mutable std::mutex m_scheduledFunctionsMutex;
|
||||
bool m_isSetup = false;
|
||||
std::atomic_bool m_earlyLoadFinished = false;
|
||||
|
||||
// InternalLoader
|
||||
std::vector<std::function<void(void)>> m_gdThreadQueue;
|
||||
mutable std::mutex m_gdThreadMutex;
|
||||
bool m_platformConsoleOpen = false;
|
||||
std::unordered_set<std::string> m_shownInfoAlerts;
|
||||
|
||||
std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
|
||||
bool m_readyToHook = false;
|
||||
|
||||
void saveInfoAlerts(nlohmann::json& json);
|
||||
void loadInfoAlerts(nlohmann::json& json);
|
||||
|
||||
void downloadLoaderResources();
|
||||
|
||||
bool loadHooks();
|
||||
void setupIPC();
|
||||
|
||||
Impl();
|
||||
~Impl();
|
||||
|
||||
void createDirectories();
|
||||
|
||||
void updateModResources(Mod* mod);
|
||||
void addSearchPaths();
|
||||
|
||||
void dispatchScheduledFunctions(Mod* mod);
|
||||
friend void GEODE_CALL ::geode_implicit_load(Mod*);
|
||||
|
||||
Result<Mod*> loadModFromInfo(ModInfo const& info);
|
||||
|
||||
Result<> setup();
|
||||
void reset();
|
||||
|
||||
Result<> saveData();
|
||||
Result<> loadData();
|
||||
|
||||
VersionInfo getVersion();
|
||||
VersionInfo minModVersion();
|
||||
VersionInfo maxModVersion();
|
||||
bool isModVersionSupported(VersionInfo const& version);
|
||||
|
||||
Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
|
||||
void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
|
||||
void refreshModsList();
|
||||
bool isModInstalled(std::string const& id) const;
|
||||
Mod* getInstalledMod(std::string const& id) const;
|
||||
bool isModLoaded(std::string const& id) const;
|
||||
Mod* getLoadedMod(std::string const& id) const;
|
||||
std::vector<Mod*> getAllMods();
|
||||
Mod* getInternalMod();
|
||||
void updateAllDependencies();
|
||||
std::vector<InvalidGeodeFile> getFailedMods() const;
|
||||
|
||||
void updateResources();
|
||||
|
||||
void scheduleOnModLoad(Mod* mod, ScheduledFunction func);
|
||||
void waitForModsToBeLoaded();
|
||||
|
||||
bool didLastLaunchCrash() const;
|
||||
|
||||
nlohmann::json processRawIPC(void* rawHandle, std::string const& buffer);
|
||||
|
||||
/**
|
||||
* Check if a one-time event has been shown to the user,
|
||||
* and set it to true if not. Will return the previous
|
||||
* state of the event before setting it to true
|
||||
*/
|
||||
bool shownInfoAlert(std::string const& key);
|
||||
|
||||
void queueInGDThread(ScheduledFunction func);
|
||||
void executeGDThreadQueue();
|
||||
|
||||
void logConsoleMessage(std::string const& msg);
|
||||
bool platformConsoleOpen() const;
|
||||
void openPlatformConsole();
|
||||
void closePlatformConsole();
|
||||
void platformMessageBox(char const* title, std::string const& info);
|
||||
|
||||
bool verifyLoaderResources();
|
||||
|
||||
bool isReadyToHook() const;
|
||||
void addInternalHook(Hook* hook, Mod* mod);
|
||||
};
|
||||
|
||||
namespace geode {
|
||||
class LoaderImpl {
|
||||
public:
|
||||
static Loader::Impl* get();
|
||||
};
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
#include "LoaderImpl.hpp"
|
||||
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/utils/casts.hpp>
|
||||
#include <Geode/utils/general.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <fmt/chrono.h>
|
||||
#include <fmt/format.h>
|
||||
#include <iomanip>
|
||||
|
@ -46,8 +47,13 @@ std::string log::parse(CCNode* obj) {
|
|||
if (obj) {
|
||||
auto bb = obj->boundingBox();
|
||||
return fmt::format(
|
||||
"{{ {}, {}, ({}, {} | {} : {}) }}", typeid(*obj).name(), utils::intToHex(obj),
|
||||
bb.origin.x, bb.origin.y, bb.size.width, bb.size.height
|
||||
"{{ {}, {}, ({}, {} | {} : {}) }}",
|
||||
typeid(*obj).name(),
|
||||
utils::intToHex(obj),
|
||||
bb.origin.x,
|
||||
bb.origin.y,
|
||||
bb.size.width,
|
||||
bb.size.height
|
||||
);
|
||||
}
|
||||
else {
|
||||
|
@ -89,10 +95,7 @@ std::string log::parse(cocos2d::ccColor4B const& col) {
|
|||
return fmt::format("rgba({}, {}, {}, {})", col.r, col.g, col.b, col.a);
|
||||
}
|
||||
|
||||
Log::Log(Mod* mod, Severity sev)
|
||||
: m_sender(mod),
|
||||
m_time(log_clock::now()),
|
||||
m_severity(sev) {}
|
||||
Log::Log(Mod* mod, Severity sev) : m_sender(mod), m_time(log_clock::now()), m_severity(sev) {}
|
||||
|
||||
bool Log::operator==(Log const& l) {
|
||||
return this == &l;
|
||||
|
@ -121,7 +124,7 @@ void Logs::setup() {
|
|||
void Logs::push(Log&& log) {
|
||||
std::string logStr = log.toString(true);
|
||||
|
||||
InternalLoader::get()->logConsoleMessage(logStr);
|
||||
LoaderImpl::get()->logConsoleMessage(logStr);
|
||||
s_logStream << logStr << std::endl;
|
||||
|
||||
s_logs.emplace_back(std::forward<Log>(log));
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#include "LoaderImpl.hpp"
|
||||
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Hook.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/utils/file.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
@ -417,7 +418,7 @@ Result<> Mod::disableHook(Hook* hook) {
|
|||
}
|
||||
|
||||
Result<Hook*> Mod::addHook(Hook* hook) {
|
||||
if (InternalLoader::get()->isReadyToHook()) {
|
||||
if (LoaderImpl::get()->isReadyToHook()) {
|
||||
auto res = this->enableHook(hook);
|
||||
if (!res) {
|
||||
delete hook;
|
||||
|
@ -425,7 +426,7 @@ Result<Hook*> Mod::addHook(Hook* hook) {
|
|||
}
|
||||
}
|
||||
else {
|
||||
InternalLoader::get()->addInternalHook(hook, this);
|
||||
LoaderImpl::get()->addInternalHook(hook, this);
|
||||
}
|
||||
|
||||
return Ok(hook);
|
||||
|
@ -486,7 +487,7 @@ Result<> Mod::createTempDir() {
|
|||
if (!file::createDirectoryAll(tempDir)) {
|
||||
return Err("Unable to create mods' runtime directory");
|
||||
}
|
||||
|
||||
|
||||
// Create geode/temp/mod.id
|
||||
auto tempPath = tempDir / m_info.id;
|
||||
if (!file::createDirectoryAll(tempPath)) {
|
||||
|
|
|
@ -133,19 +133,19 @@ Result<ModInfo> ModInfo::create(ModJson const& json) {
|
|||
"specified, or it is invalidally formatted (required: \"[v]X.X.X\")!"
|
||||
);
|
||||
}
|
||||
if (schema < Loader::minModVersion()) {
|
||||
if (schema < Loader::get()->minModVersion()) {
|
||||
return Err(
|
||||
"[mod.json] is built for an older version (" + schema.toString() +
|
||||
") of Geode (current: " + Loader::getVersion().toString() +
|
||||
") of Geode (current: " + Loader::get()->getVersion().toString() +
|
||||
"). Please update the mod to the latest version, "
|
||||
"and if the problem persists, contact the developer "
|
||||
"to update it."
|
||||
);
|
||||
}
|
||||
if (schema > Loader::maxModVersion()) {
|
||||
if (schema > Loader::get()->maxModVersion()) {
|
||||
return Err(
|
||||
"[mod.json] is built for a newer version (" + schema.toString() +
|
||||
") of Geode (current: " + Loader::getVersion().toString() +
|
||||
") of Geode (current: " + Loader::get()->getVersion().toString() +
|
||||
"). You need to update Geode in order to use "
|
||||
"this mod."
|
||||
);
|
||||
|
@ -195,8 +195,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) {
|
|||
|
||||
// Read mod.json & parse if possible
|
||||
GEODE_UNWRAP_INTO(
|
||||
auto jsonData,
|
||||
unzip.extract("mod.json").expect("Unable to read mod.json: {error}")
|
||||
auto jsonData, unzip.extract("mod.json").expect("Unable to read mod.json: {error}")
|
||||
);
|
||||
ModJson json;
|
||||
try {
|
||||
|
@ -213,10 +212,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) {
|
|||
auto info = res.unwrap();
|
||||
info.path = unzip.getPath();
|
||||
|
||||
GEODE_UNWRAP(
|
||||
info.addSpecialFiles(unzip)
|
||||
.expect("Unable to add extra files: {error}")
|
||||
);
|
||||
GEODE_UNWRAP(info.addSpecialFiles(unzip).expect("Unable to add extra files: {error}"));
|
||||
|
||||
return Ok(info);
|
||||
}
|
||||
|
@ -225,9 +221,7 @@ Result<> ModInfo::addSpecialFiles(file::Unzip& unzip) {
|
|||
// unzip known MD files
|
||||
for (auto& [file, target] : getSpecialFiles()) {
|
||||
if (unzip.hasEntry(file)) {
|
||||
GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect(
|
||||
"Unable to extract \"{}\"", file
|
||||
));
|
||||
GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect("Unable to extract \"{}\"", file));
|
||||
*target = sanitizeDetailsData(std::string(data.begin(), data.end()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "../core/Core.hpp"
|
||||
#include "loader/LoaderImpl.hpp"
|
||||
|
||||
#include <Geode/loader/IPC.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
|
@ -6,7 +7,6 @@
|
|||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/loader/Setting.hpp>
|
||||
#include <Geode/loader/SettingEvent.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <array>
|
||||
|
||||
|
@ -108,7 +108,7 @@ static auto $_ =
|
|||
Loader::get()->openPlatformConsole();
|
||||
}
|
||||
else {
|
||||
Loader::get()->closePlatfromConsole();
|
||||
Loader::get()->closePlatformConsole();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -147,18 +147,18 @@ static auto $_ = listenForIPC("list-mods", [](IPCEvent* event) -> nlohmann::json
|
|||
int geodeEntry(void* platformData) {
|
||||
// setup internals
|
||||
|
||||
if (!InternalLoader::get()) {
|
||||
InternalLoader::platformMessageBox(
|
||||
if (!Loader::get()) {
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Unable to Load Geode!",
|
||||
"There was an unknown fatal error setting up "
|
||||
"internal tools and Geode can not be loaded. "
|
||||
"(InternalLoader::get returned nullptr)"
|
||||
"(Loader::get returned nullptr)"
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!geode::core::hook::initialize()) {
|
||||
InternalLoader::platformMessageBox(
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Unable to load Geode!",
|
||||
"There was an unknown fatal error setting up "
|
||||
"internal tools and Geode can not be loaded. "
|
||||
|
@ -169,30 +169,14 @@ int geodeEntry(void* platformData) {
|
|||
|
||||
geode_implicit_load(InternalMod::get());
|
||||
|
||||
if (!InternalLoader::get()->setup()) {
|
||||
// if we've made it here, Geode will
|
||||
// be gettable (otherwise the call to
|
||||
// setup would've immediately crashed)
|
||||
|
||||
InternalLoader::platformMessageBox(
|
||||
"Unable to Load Geode!",
|
||||
"There was an unknown fatal error setting up "
|
||||
"internal tools and Geode can not be loaded. "
|
||||
"(InternalLoader::setup) returned false"
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
log::debug("Loaded internal Geode class");
|
||||
|
||||
// set up loader, load mods, etc.
|
||||
if (!Loader::get()->setup()) {
|
||||
InternalLoader::platformMessageBox(
|
||||
if (!LoaderImpl::get()->setup()) {
|
||||
LoaderImpl::get()->platformMessageBox(
|
||||
"Unable to Load Geode!",
|
||||
"There was an unknown fatal error setting up "
|
||||
"the loader and Geode can not be loaded."
|
||||
);
|
||||
delete InternalLoader::get();
|
||||
LoaderImpl::get()->reset();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
#include <InternalLoader.hpp>
|
||||
|
||||
#ifdef GEODE_IS_IOS
|
||||
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <iostream>
|
||||
#include <InternalMod.hpp>
|
||||
#include <pwd.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void InternalLoader::platformMessageBox(char const* title, std::string const& info) {
|
||||
std::cout << title << ": " << info << std::endl;
|
||||
}
|
||||
|
||||
void InternalLoader::openPlatformConsole() {
|
||||
ghc::filesystem::path(getpwuid(getuid())->pw_dir);
|
||||
freopen(
|
||||
ghc::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w",
|
||||
stdout
|
||||
);
|
||||
InternalLoader::m_platformConsoleOpen = true;
|
||||
}
|
||||
|
||||
void InternalLoader::closePlatformConsole() {}
|
||||
|
||||
void InternalLoader::postIPCReply(
|
||||
void* rawPipeHandle,
|
||||
std::string const& replyID,
|
||||
nlohmann::json const& data
|
||||
) {}
|
||||
|
||||
void InternalLoader::setupIPC() {
|
||||
#warning "Set up pipes or smth for this platform"
|
||||
log::log(Severity::Warning, InternalMod::get(), "IPC is not supported on this platform");
|
||||
}
|
||||
|
||||
#endif
|
35
loader/src/platform/ios/LoaderImpl.cpp
Normal file
35
loader/src/platform/ios/LoaderImpl.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include <loader/LoaderImpl.hpp>
|
||||
|
||||
#ifdef GEODE_IS_IOS
|
||||
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <iostream>
|
||||
#include <pwd.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
|
||||
std::cout << title << ": " << info << std::endl;
|
||||
}
|
||||
|
||||
void Loader::Impl::openPlatformConsole() {
|
||||
ghc::filesystem::path(getpwuid(getuid())->pw_dir);
|
||||
freopen(ghc::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w", stdout);
|
||||
m_platformConsoleOpen = true;
|
||||
}
|
||||
|
||||
void Loader::Impl::closePlatformConsole() {}
|
||||
|
||||
void Loader::Impl::postIPCReply(
|
||||
void* rawPipeHandle, std::string const& replyID, nlohmann::json const& data
|
||||
) {}
|
||||
|
||||
void Loader::Impl::setupIPC() {
|
||||
#warning "Set up pipes or smth for this platform"
|
||||
log::warning("IPC is not supported on this platform");
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,14 +1,14 @@
|
|||
#include <Geode/loader/IPC.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <iostream>
|
||||
#include <loader/LoaderImpl.hpp>
|
||||
|
||||
#ifdef GEODE_IS_MACOS
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
void InternalLoader::platformMessageBox(char const* title, std::string const& info) {
|
||||
void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
|
||||
CFStringRef cfTitle = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8);
|
||||
CFStringRef cfMessage = CFStringCreateWithCString(NULL, info.c_str(), kCFStringEncodingUTF8);
|
||||
|
||||
|
@ -17,7 +17,7 @@ void InternalLoader::platformMessageBox(char const* title, std::string const& in
|
|||
);
|
||||
}
|
||||
|
||||
void InternalLoader::openPlatformConsole() {
|
||||
void Loader::Impl::openPlatformConsole() {
|
||||
m_platformConsoleOpen = true;
|
||||
|
||||
for (auto const& log : log::Logs::list()) {
|
||||
|
@ -25,7 +25,7 @@ void InternalLoader::openPlatformConsole() {
|
|||
}
|
||||
}
|
||||
|
||||
void InternalLoader::closePlatformConsole() {
|
||||
void Loader::Impl::closePlatformConsole() {
|
||||
m_platformConsoleOpen = false;
|
||||
}
|
||||
|
||||
|
@ -34,11 +34,11 @@ CFDataRef msgPortCallback(CFMessagePortRef port, SInt32 messageID, CFDataRef dat
|
|||
|
||||
std::string cdata(reinterpret_cast<char const*>(CFDataGetBytePtr(data)), CFDataGetLength(data));
|
||||
|
||||
std::string reply = InternalLoader::processRawIPC(port, cdata);
|
||||
std::string reply = LoaderImpl::get()->processRawIPC(port, cdata);
|
||||
return CFDataCreate(NULL, (UInt8 const*)reply.data(), reply.size());
|
||||
}
|
||||
|
||||
void InternalLoader::setupIPC() {
|
||||
void Loader::Impl::setupIPC() {
|
||||
std::thread([]() {
|
||||
CFStringRef portName = CFStringCreateWithCString(NULL, IPC_PORT_NAME, kCFStringEncodingUTF8);
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#include <Geode/loader/IPC.hpp>
|
||||
#include <Geode/loader/Log.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <iostream>
|
||||
#include <loader/LoaderImpl.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
|
@ -10,11 +10,11 @@ USE_GEODE_NAMESPACE();
|
|||
|
||||
static constexpr auto IPC_BUFFER_SIZE = 512;
|
||||
|
||||
void InternalLoader::platformMessageBox(char const* title, std::string const& info) {
|
||||
void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
|
||||
MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR);
|
||||
}
|
||||
|
||||
void InternalLoader::openPlatformConsole() {
|
||||
void Loader::Impl::openPlatformConsole() {
|
||||
if (m_platformConsoleOpen) return;
|
||||
if (AllocConsole() == 0) return;
|
||||
SetConsoleCP(CP_UTF8);
|
||||
|
@ -29,7 +29,7 @@ void InternalLoader::openPlatformConsole() {
|
|||
}
|
||||
}
|
||||
|
||||
void InternalLoader::closePlatformConsole() {
|
||||
void Loader::Impl::closePlatformConsole() {
|
||||
if (!m_platformConsoleOpen) return;
|
||||
|
||||
fclose(stdin);
|
||||
|
@ -43,11 +43,13 @@ void ipcPipeThread(HANDLE pipe) {
|
|||
char buffer[IPC_BUFFER_SIZE * sizeof(TCHAR)];
|
||||
DWORD read;
|
||||
|
||||
std::optional<std::string> replyID = std::nullopt;
|
||||
|
||||
// log::debug("Waiting for I/O");
|
||||
if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
|
||||
buffer[read] = '\0';
|
||||
|
||||
auto reply = InternalLoader::processRawIPC((void*)pipe, buffer).dump();
|
||||
std::string reply = LoaderImpl::get()->processRawIPC((void*)pipe, buffer);
|
||||
|
||||
DWORD written;
|
||||
WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr);
|
||||
|
@ -61,7 +63,7 @@ void ipcPipeThread(HANDLE pipe) {
|
|||
// log::debug("Disconnected pipe");
|
||||
}
|
||||
|
||||
void InternalLoader::setupIPC() {
|
||||
void Loader::Impl::setupIPC() {
|
||||
std::thread([]() {
|
||||
while (true) {
|
||||
auto pipe = CreateNamedPipeA(
|
|
@ -1,11 +1,12 @@
|
|||
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include "info/ModInfoPopup.hpp"
|
||||
#include "list/ModListLayer.hpp"
|
||||
#include "settings/ModSettingsPopup.hpp"
|
||||
#include <Geode/ui/MDPopup.hpp>
|
||||
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Index.hpp>
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <Geode/ui/MDPopup.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
|
||||
void geode::openModsList() {
|
||||
|
@ -38,9 +39,11 @@ void geode::openIssueReportPopup(Mod* mod) {
|
|||
"[#support](https://discord.com/channels/911701438269386882/979352389985390603) "
|
||||
"channnel in the [Geode Discord Server](https://discord.gg/9e43WMKzhp)\n\n"
|
||||
"If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
|
||||
"latest crash log(s) from `" + dirs::getCrashlogsDir().string() + "`",
|
||||
"latest crash log(s) from `" +
|
||||
dirs::getCrashlogsDir().string() + "`",
|
||||
"OK"
|
||||
)->show();
|
||||
)
|
||||
->show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,13 +74,11 @@ CCNode* geode::createDefaultLogo(CCSize const& size) {
|
|||
|
||||
CCNode* geode::createModLogo(Mod* mod, CCSize const& size) {
|
||||
CCNode* spr = nullptr;
|
||||
if (mod == Loader::getInternalMod()) {
|
||||
if (mod == Loader::get()->getInternalMod()) {
|
||||
spr = CCSprite::createWithSpriteFrameName("geode-logo.png"_spr);
|
||||
}
|
||||
else {
|
||||
spr = CCSprite::create(
|
||||
fmt::format("{}/logo.png", mod->getID()).c_str()
|
||||
);
|
||||
spr = CCSprite::create(fmt::format("{}/logo.png", mod->getID()).c_str());
|
||||
}
|
||||
if (!spr) spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
|
||||
if (!spr) spr = CCLabelBMFont::create("N/A", "goldFont.fnt");
|
||||
|
@ -101,7 +102,7 @@ CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
|
|||
auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr);
|
||||
logoGlow->setScaleX(glowSize.width / logoGlow->getContentSize().width);
|
||||
logoGlow->setScaleY(glowSize.height / logoGlow->getContentSize().height);
|
||||
|
||||
|
||||
// i dont know why + 1 is needed and its too late for me to figure out why
|
||||
spr->setPosition(
|
||||
logoGlow->getContentSize().width / 2 + 1,
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
#include "../dev/HookListLayer.hpp"
|
||||
#include "../list/ModListLayer.hpp"
|
||||
#include "../settings/ModSettingsPopup.hpp"
|
||||
#include <InternalLoader.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
|
||||
#include <Geode/binding/ButtonSprite.hpp>
|
||||
|
@ -12,15 +11,17 @@
|
|||
#include <Geode/binding/Slider.hpp>
|
||||
#include <Geode/binding/SliderThumb.hpp>
|
||||
#include <Geode/binding/SliderTouchLogic.hpp>
|
||||
#include <Geode/loader/Dirs.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/ui/BasedButton.hpp>
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <Geode/ui/IconButtonSprite.hpp>
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <Geode/ui/MDPopup.hpp>
|
||||
#include <Geode/utils/casts.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
#include <Geode/utils/web.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <loader/LoaderImpl.hpp>
|
||||
|
||||
// TODO: die
|
||||
#undef min
|
||||
|
@ -28,7 +29,7 @@
|
|||
|
||||
static constexpr int const TAG_CONFIRM_UNINSTALL = 5;
|
||||
static constexpr int const TAG_DELETE_SAVEDATA = 6;
|
||||
static const CCSize LAYER_SIZE = { 440.f, 290.f };
|
||||
static const CCSize LAYER_SIZE = {440.f, 290.f};
|
||||
|
||||
bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
|
||||
m_noElasticity = true;
|
||||
|
@ -36,11 +37,11 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
|
|||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
|
||||
if (!this->initWithColor({ 0, 0, 0, 105 })) return false;
|
||||
if (!this->initWithColor({0, 0, 0, 105})) return false;
|
||||
m_mainLayer = CCLayer::create();
|
||||
this->addChild(m_mainLayer);
|
||||
|
||||
auto bg = CCScale9Sprite::create("GJ_square01.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||
auto bg = CCScale9Sprite::create("GJ_square01.png", {0.0f, 0.0f, 80.0f, 80.0f});
|
||||
bg->setContentSize(LAYER_SIZE);
|
||||
bg->setPosition(winSize.width / 2, winSize.height / 2);
|
||||
bg->setZOrder(-10);
|
||||
|
@ -57,33 +58,27 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
|
|||
nameLabel->limitLabelWidth(200.f, .7f, .1f);
|
||||
m_mainLayer->addChild(nameLabel, 2);
|
||||
|
||||
auto logoSpr = this->createLogo({ logoSize, logoSize });
|
||||
auto logoSpr = this->createLogo({logoSize, logoSize});
|
||||
m_mainLayer->addChild(logoSpr);
|
||||
|
||||
auto developerStr = "by " + info.developer;
|
||||
auto developerLabel = CCLabelBMFont::create(developerStr.c_str(), "goldFont.fnt");
|
||||
developerLabel->setScale(.5f);
|
||||
developerLabel->setAnchorPoint({ .0f, .5f });
|
||||
developerLabel->setAnchorPoint({.0f, .5f});
|
||||
m_mainLayer->addChild(developerLabel);
|
||||
|
||||
auto logoTitleWidth =
|
||||
std::max(
|
||||
nameLabel->getScaledContentSize().width,
|
||||
developerLabel->getScaledContentSize().width
|
||||
) +
|
||||
std::max(nameLabel->getScaledContentSize().width, developerLabel->getScaledContentSize().width) +
|
||||
logoSize + logoOffset;
|
||||
|
||||
nameLabel->setPosition(
|
||||
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset,
|
||||
winSize.height / 2 + 125.f
|
||||
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 125.f
|
||||
);
|
||||
logoSpr->setPosition(
|
||||
{winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2, winSize.height / 2 + 115.f}
|
||||
);
|
||||
logoSpr->setPosition({
|
||||
winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2,
|
||||
winSize.height / 2 + 115.f
|
||||
});
|
||||
developerLabel->setPosition(
|
||||
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset,
|
||||
winSize.height / 2 + 105.f
|
||||
winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 105.f
|
||||
);
|
||||
|
||||
auto versionLabel = CCLabelBMFont::create(
|
||||
|
@ -96,7 +91,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
|
|||
nameLabel->getPositionX() + nameLabel->getScaledContentSize().width + 5.f,
|
||||
winSize.height / 2 + 125.f
|
||||
);
|
||||
versionLabel->setColor({ 0, 255, 0 });
|
||||
versionLabel->setColor({0, 255, 0});
|
||||
m_mainLayer->addChild(versionLabel);
|
||||
|
||||
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
|
||||
|
@ -123,27 +118,33 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
|
|||
if (info.changelog) {
|
||||
m_changelogArea = MDTextArea::create(info.changelog.value(), { 350.f, 137.5f });
|
||||
m_changelogArea->setPosition(
|
||||
-5000.f, winSize.height / 2 -
|
||||
m_changelogArea->getScaledContentSize().height / 2 - 20.f
|
||||
-5000.f, winSize.height / 2 - m_changelogArea->getScaledContentSize().height / 2 - 20.f
|
||||
);
|
||||
m_changelogArea->setVisible(false);
|
||||
m_mainLayer->addChild(m_changelogArea);
|
||||
|
||||
auto changelogBtnOffSpr = ButtonSprite::create(
|
||||
CCSprite::createWithSpriteFrameName("changelog.png"_spr),
|
||||
0x20, true, 32.f, "GJ_button_01.png", 1.f
|
||||
0x20,
|
||||
true,
|
||||
32.f,
|
||||
"GJ_button_01.png",
|
||||
1.f
|
||||
);
|
||||
changelogBtnOffSpr->setScale(.65f);
|
||||
|
||||
auto changelogBtnOnSpr = ButtonSprite::create(
|
||||
CCSprite::createWithSpriteFrameName("changelog.png"_spr),
|
||||
0x20, true, 32.f, "GJ_button_02.png", 1.f
|
||||
0x20,
|
||||
true,
|
||||
32.f,
|
||||
"GJ_button_02.png",
|
||||
1.f
|
||||
);
|
||||
changelogBtnOnSpr->setScale(.65f);
|
||||
|
||||
auto changelogBtn = CCMenuItemToggler::create(
|
||||
changelogBtnOffSpr, changelogBtnOnSpr,
|
||||
this, menu_selector(ModInfoPopup::onChangelog)
|
||||
changelogBtnOffSpr, changelogBtnOnSpr, this, menu_selector(ModInfoPopup::onChangelog)
|
||||
);
|
||||
changelogBtn->setPosition(-LAYER_SIZE.width / 2 + 21.5f, .0f);
|
||||
m_buttonMenu->addChild(changelogBtn);
|
||||
|
@ -153,51 +154,38 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
|
|||
auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
|
||||
infoSpr->setScale(.85f);
|
||||
|
||||
m_infoBtn = CCMenuItemSpriteExtra::create(
|
||||
infoSpr, this, menu_selector(ModInfoPopup::onInfo)
|
||||
);
|
||||
m_infoBtn->setPosition(
|
||||
LAYER_SIZE.width / 2 - 25.f,
|
||||
LAYER_SIZE.height / 2 - 25.f
|
||||
);
|
||||
m_infoBtn = CCMenuItemSpriteExtra::create(infoSpr, this, menu_selector(ModInfoPopup::onInfo));
|
||||
m_infoBtn->setPosition(LAYER_SIZE.width / 2 - 25.f, LAYER_SIZE.height / 2 - 25.f);
|
||||
m_buttonMenu->addChild(m_infoBtn);
|
||||
|
||||
// repo button
|
||||
if (info.repository) {
|
||||
auto repoBtn = CCMenuItemSpriteExtra::create(
|
||||
CCSprite::createWithSpriteFrameName("github.png"_spr), this,
|
||||
CCSprite::createWithSpriteFrameName("github.png"_spr),
|
||||
this,
|
||||
menu_selector(ModInfoPopup::onRepository)
|
||||
);
|
||||
repoBtn->setPosition(
|
||||
LAYER_SIZE.width / 2 - 25.f,
|
||||
-LAYER_SIZE.height / 2 + 25.f
|
||||
);
|
||||
repoBtn->setPosition(LAYER_SIZE.width / 2 - 25.f, -LAYER_SIZE.height / 2 + 25.f);
|
||||
m_buttonMenu->addChild(repoBtn);
|
||||
}
|
||||
|
||||
// support button
|
||||
if (info.supportInfo) {
|
||||
auto supportBtn = CCMenuItemSpriteExtra::create(
|
||||
CCSprite::createWithSpriteFrameName("gift.png"_spr), this,
|
||||
CCSprite::createWithSpriteFrameName("gift.png"_spr),
|
||||
this,
|
||||
menu_selector(ModInfoPopup::onSupport)
|
||||
);
|
||||
supportBtn->setPosition(
|
||||
LAYER_SIZE.width / 2 - 60.f,
|
||||
-LAYER_SIZE.height / 2 + 25.f
|
||||
);
|
||||
supportBtn->setPosition(LAYER_SIZE.width / 2 - 60.f, -LAYER_SIZE.height / 2 + 25.f);
|
||||
m_buttonMenu->addChild(supportBtn);
|
||||
}
|
||||
|
||||
auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
|
||||
closeSpr->setScale(.8f);
|
||||
|
||||
auto closeBtn = CCMenuItemSpriteExtra::create(
|
||||
closeSpr, this, menu_selector(ModInfoPopup::onClose)
|
||||
);
|
||||
closeBtn->setPosition(
|
||||
-LAYER_SIZE.width / 2 + 3.f,
|
||||
LAYER_SIZE.height / 2 - 3.f
|
||||
);
|
||||
auto closeBtn =
|
||||
CCMenuItemSpriteExtra::create(closeSpr, this, menu_selector(ModInfoPopup::onClose));
|
||||
closeBtn->setPosition(-LAYER_SIZE.width / 2 + 3.f, LAYER_SIZE.height / 2 - 3.f);
|
||||
m_buttonMenu->addChild(closeBtn);
|
||||
|
||||
this->setKeypadEnabled(true);
|
||||
|
@ -233,8 +221,11 @@ void ModInfoPopup::onInfo(CCObject*) {
|
|||
info.developer,
|
||||
info.path.string()
|
||||
),
|
||||
"OK", nullptr, 400.f
|
||||
)->show();
|
||||
"OK",
|
||||
nullptr,
|
||||
400.f
|
||||
)
|
||||
->show();
|
||||
}
|
||||
|
||||
void ModInfoPopup::onChangelog(CCObject* sender) {
|
||||
|
@ -245,18 +236,16 @@ void ModInfoPopup::onChangelog(CCObject* sender) {
|
|||
// as it turns out, cocos2d is stupid and still passes touch
|
||||
// events to invisible nodes
|
||||
m_detailsArea->setPositionX(
|
||||
toggle->isToggled() ?
|
||||
winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2 :
|
||||
-5000.f
|
||||
toggle->isToggled() ? winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2 :
|
||||
-5000.f
|
||||
);
|
||||
|
||||
m_changelogArea->setVisible(!toggle->isToggled());
|
||||
// as it turns out, cocos2d is stupid and still passes touch
|
||||
// events to invisible nodes
|
||||
m_changelogArea->setPositionX(
|
||||
!toggle->isToggled() ?
|
||||
winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2 :
|
||||
-5000.f
|
||||
!toggle->isToggled() ? winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2 :
|
||||
-5000.f
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -287,24 +276,17 @@ void ModInfoPopup::setInstallStatus(std::optional<UpdateProgress> const& progres
|
|||
bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
||||
m_mod = mod;
|
||||
|
||||
if (!ModInfoPopup::init(mod->getModInfo(), list))
|
||||
return false;
|
||||
if (!ModInfoPopup::init(mod->getModInfo(), list)) return false;
|
||||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
|
||||
// mod settings
|
||||
auto settingsSpr = CCSprite::createWithSpriteFrameName(
|
||||
"GJ_optionsBtn_001.png"
|
||||
);
|
||||
auto settingsSpr = CCSprite::createWithSpriteFrameName("GJ_optionsBtn_001.png");
|
||||
settingsSpr->setScale(.65f);
|
||||
|
||||
auto settingsBtn = CCMenuItemSpriteExtra::create(
|
||||
settingsSpr, this, menu_selector(LocalModInfoPopup::onSettings)
|
||||
);
|
||||
settingsBtn->setPosition(
|
||||
-LAYER_SIZE.width / 2 + 25.f,
|
||||
-LAYER_SIZE.height / 2 + 25.f
|
||||
);
|
||||
auto settingsBtn =
|
||||
CCMenuItemSpriteExtra::create(settingsSpr, this, menu_selector(LocalModInfoPopup::onSettings));
|
||||
settingsBtn->setPosition(-LAYER_SIZE.width / 2 + 25.f, -LAYER_SIZE.height / 2 + 25.f);
|
||||
m_buttonMenu->addChild(settingsBtn);
|
||||
|
||||
// Check if a config directory for the mod exists
|
||||
|
@ -317,31 +299,23 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
|||
auto configBtn = CCMenuItemSpriteExtra::create(
|
||||
configSpr, this, menu_selector(LocalModInfoPopup::onOpenConfigDir)
|
||||
);
|
||||
configBtn->setPosition(
|
||||
-LAYER_SIZE.width / 2 + 65.f,
|
||||
-LAYER_SIZE.height / 2 + 25.f
|
||||
);
|
||||
configBtn->setPosition(-LAYER_SIZE.width / 2 + 65.f, -LAYER_SIZE.height / 2 + 25.f);
|
||||
m_buttonMenu->addChild(configBtn);
|
||||
}
|
||||
|
||||
if (!mod->hasSettings()) {
|
||||
settingsSpr->setColor({ 150, 150, 150 });
|
||||
settingsSpr->setColor({150, 150, 150});
|
||||
settingsBtn->setTarget(this, menu_selector(LocalModInfoPopup::onNoSettings));
|
||||
}
|
||||
|
||||
auto enableBtnSpr = ButtonSprite::create(
|
||||
"Enable", "bigFont.fnt", "GJ_button_01.png", .6f
|
||||
);
|
||||
auto enableBtnSpr = ButtonSprite::create("Enable", "bigFont.fnt", "GJ_button_01.png", .6f);
|
||||
enableBtnSpr->setScale(.6f);
|
||||
|
||||
auto disableBtnSpr = ButtonSprite::create(
|
||||
"Disable", "bigFont.fnt", "GJ_button_06.png", .6f
|
||||
);
|
||||
auto disableBtnSpr = ButtonSprite::create("Disable", "bigFont.fnt", "GJ_button_06.png", .6f);
|
||||
disableBtnSpr->setScale(.6f);
|
||||
|
||||
auto enableBtn = CCMenuItemToggler::create(
|
||||
disableBtnSpr, enableBtnSpr,
|
||||
this, menu_selector(LocalModInfoPopup::onEnableMod)
|
||||
disableBtnSpr, enableBtnSpr, this, menu_selector(LocalModInfoPopup::onEnableMod)
|
||||
);
|
||||
enableBtn->setPosition(-155.f, 75.f);
|
||||
enableBtn->toggle(!mod->isEnabled());
|
||||
|
@ -349,8 +323,8 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
|||
|
||||
if (!mod->supportsDisabling()) {
|
||||
enableBtn->setTarget(this, menu_selector(LocalModInfoPopup::onDisablingNotSupported));
|
||||
enableBtnSpr->setColor({ 150, 150, 150 });
|
||||
disableBtnSpr->setColor({ 150, 150, 150 });
|
||||
enableBtnSpr->setColor({150, 150, 150});
|
||||
disableBtnSpr->setColor({150, 150, 150});
|
||||
}
|
||||
|
||||
if (mod != Loader::get()->getInternalMod()) {
|
||||
|
@ -372,21 +346,17 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
|||
m_installBtnSpr = IconButtonSprite::create(
|
||||
"GE_button_01.png"_spr,
|
||||
CCSprite::createWithSpriteFrameName("install.png"_spr),
|
||||
"Update", "bigFont.fnt"
|
||||
"Update",
|
||||
"bigFont.fnt"
|
||||
);
|
||||
m_installBtnSpr->setScale(.6f);
|
||||
|
||||
m_installBtn = CCMenuItemSpriteExtra::create(
|
||||
m_installBtnSpr, this, nullptr
|
||||
);
|
||||
m_installBtn = CCMenuItemSpriteExtra::create(m_installBtnSpr, this, nullptr);
|
||||
m_installBtn->setPosition(-8.0f, 75.f);
|
||||
m_buttonMenu->addChild(m_installBtn);
|
||||
|
||||
m_installStatus = DownloadStatusNode::create();
|
||||
m_installStatus->setPosition(
|
||||
winSize.width / 2 + 105.f,
|
||||
winSize.height / 2 + 75.f
|
||||
);
|
||||
m_installStatus->setPosition(winSize.width / 2 + 105.f, winSize.height / 2 + 75.f);
|
||||
m_installStatus->setVisible(false);
|
||||
m_mainLayer->addChild(m_installStatus);
|
||||
|
||||
|
@ -395,15 +365,13 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
|
|||
"bigFont.fnt"
|
||||
);
|
||||
m_updateVersionLabel->setScale(.35f);
|
||||
m_updateVersionLabel->setAnchorPoint({ .0f, .5f });
|
||||
m_updateVersionLabel->setColor({ 94, 219, 255 });
|
||||
m_updateVersionLabel->setPosition(
|
||||
winSize.width / 2 + 35.f, winSize.height / 2 + 75.f
|
||||
);
|
||||
m_updateVersionLabel->setAnchorPoint({.0f, .5f});
|
||||
m_updateVersionLabel->setColor({94, 219, 255});
|
||||
m_updateVersionLabel->setPosition(winSize.width / 2 + 35.f, winSize.height / 2 + 75.f);
|
||||
m_mainLayer->addChild(m_updateVersionLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// issue report button
|
||||
if (mod->getModInfo().issues) {
|
||||
auto issuesBtnSpr = ButtonSprite::create(
|
||||
|
@ -435,12 +403,11 @@ void LocalModInfoPopup::onIssues(CCObject*) {
|
|||
|
||||
void LocalModInfoPopup::onUninstall(CCObject*) {
|
||||
auto layer = FLAlertLayer::create(
|
||||
this, "Confirm Uninstall",
|
||||
fmt::format(
|
||||
"Are you sure you want to uninstall <cr>{}</c>?",
|
||||
m_mod->getName()
|
||||
),
|
||||
"Cancel", "OK"
|
||||
this,
|
||||
"Confirm Uninstall",
|
||||
fmt::format("Are you sure you want to uninstall <cr>{}</c>?", m_mod->getName()),
|
||||
"Cancel",
|
||||
"OK"
|
||||
);
|
||||
layer->setTag(TAG_CONFIRM_UNINSTALL);
|
||||
layer->show();
|
||||
|
@ -462,19 +429,13 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
|
|||
if (as<CCMenuItemToggler*>(sender)->isToggled()) {
|
||||
auto res = m_mod->loadBinary();
|
||||
if (!res) {
|
||||
FLAlertLayer::create(
|
||||
nullptr, "Error Loading Mod",
|
||||
res.unwrapErr(), "OK", nullptr
|
||||
)->show();
|
||||
FLAlertLayer::create(nullptr, "Error Loading Mod", res.unwrapErr(), "OK", nullptr)->show();
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto res = m_mod->disable();
|
||||
if (!res) {
|
||||
FLAlertLayer::create(
|
||||
nullptr, "Error Disabling Mod",
|
||||
res.unwrapErr(), "OK", nullptr
|
||||
)->show();
|
||||
FLAlertLayer::create(nullptr, "Error Disabling Mod", res.unwrapErr(), "OK", nullptr)->show();
|
||||
}
|
||||
}
|
||||
if (m_layer) m_layer->updateAllStates(nullptr);
|
||||
|
@ -486,11 +447,7 @@ void LocalModInfoPopup::onOpenConfigDir(CCObject*) {
|
|||
}
|
||||
|
||||
void LocalModInfoPopup::onDisablingNotSupported(CCObject* pSender) {
|
||||
FLAlertLayer::create(
|
||||
"Unsupported",
|
||||
"<cr>Disabling</c> is not supported for this mod.",
|
||||
"OK"
|
||||
)->show();
|
||||
FLAlertLayer::create("Unsupported", "<cr>Disabling</c> is not supported for this mod.", "OK")->show();
|
||||
as<CCMenuItemToggler*>(pSender)->toggle(m_mod->isEnabled());
|
||||
}
|
||||
|
||||
|
@ -514,14 +471,10 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
|||
case TAG_DELETE_SAVEDATA: {
|
||||
if (btn2) {
|
||||
if (ghc::filesystem::remove_all(m_mod->getSaveDir())) {
|
||||
FLAlertLayer::create(
|
||||
"Deleted", "The mod's save data was deleted.", "OK"
|
||||
)->show();
|
||||
FLAlertLayer::create("Deleted", "The mod's save data was deleted.", "OK")->show();
|
||||
}
|
||||
else {
|
||||
FLAlertLayer::create(
|
||||
"Error", "Unable to delete mod's save directory!", "OK"
|
||||
)->show();
|
||||
FLAlertLayer::create("Error", "Unable to delete mod's save directory!", "OK")->show();
|
||||
}
|
||||
}
|
||||
if (m_layer) m_layer->reloadList();
|
||||
|
@ -533,18 +486,19 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
|
|||
void LocalModInfoPopup::doUninstall() {
|
||||
auto res = m_mod->uninstall();
|
||||
if (!res) {
|
||||
return FLAlertLayer::create(
|
||||
"Uninstall failed :(", res.unwrapErr(), "OK"
|
||||
)->show();
|
||||
return FLAlertLayer::create("Uninstall failed :(", res.unwrapErr(), "OK")->show();
|
||||
}
|
||||
auto layer = FLAlertLayer::create(
|
||||
this, "Uninstall complete",
|
||||
this,
|
||||
"Uninstall complete",
|
||||
"Mod was succesfully uninstalled! :) "
|
||||
"(You may need to <cy>restart the game</c> "
|
||||
"for the mod to take full effect). "
|
||||
"<co>Would you also like to delete the mod's "
|
||||
"save data?</c>",
|
||||
"Cancel", "Delete", 350.f
|
||||
"Cancel",
|
||||
"Delete",
|
||||
350.f
|
||||
);
|
||||
layer->setTag(TAG_DELETE_SAVEDATA);
|
||||
layer->show();
|
||||
|
@ -574,12 +528,13 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
|
|||
|
||||
auto winSize = CCDirector::sharedDirector()->getWinSize();
|
||||
|
||||
if (!ModInfoPopup::init(item->info, list))
|
||||
return false;
|
||||
if (!ModInfoPopup::init(item->info, list)) return false;
|
||||
|
||||
m_installBtnSpr = IconButtonSprite::create(
|
||||
"GE_button_01.png"_spr, CCSprite::createWithSpriteFrameName("install.png"_spr),
|
||||
"Install", "bigFont.fnt"
|
||||
"GE_button_01.png"_spr,
|
||||
CCSprite::createWithSpriteFrameName("install.png"_spr),
|
||||
"Install",
|
||||
"bigFont.fnt"
|
||||
);
|
||||
m_installBtnSpr->setScale(.6f);
|
||||
|
||||
|
@ -590,13 +545,10 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
|
|||
m_buttonMenu->addChild(m_installBtn);
|
||||
|
||||
m_installStatus = DownloadStatusNode::create();
|
||||
m_installStatus->setPosition(
|
||||
winSize.width / 2 - 25.f,
|
||||
winSize.height / 2 + 75.f
|
||||
);
|
||||
m_installStatus->setPosition(winSize.width / 2 - 25.f, winSize.height / 2 + 75.f);
|
||||
m_installStatus->setVisible(false);
|
||||
m_mainLayer->addChild(m_installStatus);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
#include "ModListCell.hpp"
|
||||
#include "ModListLayer.hpp"
|
||||
#include "../info/ModInfoPopup.hpp"
|
||||
#include <Geode/binding/StatsCell.hpp>
|
||||
#include <Geode/binding/FLAlertLayer.hpp>
|
||||
|
||||
#include <Geode/binding/ButtonSprite.hpp>
|
||||
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
|
||||
#include <Geode/binding/CCMenuItemToggler.hpp>
|
||||
#include <Geode/binding/FLAlertLayer.hpp>
|
||||
#include <Geode/binding/StatsCell.hpp>
|
||||
#include <Geode/ui/GeodeUI.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include "../../../loader/LoaderImpl.hpp" // how should i include this src/loader/LoaderImpl.hpp
|
||||
#include "../info/TagNode.hpp"
|
||||
|
||||
template <class T>
|
||||
|
@ -93,11 +94,11 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForTags) {
|
|||
m_menu->addChild(creatorBtn);
|
||||
|
||||
if (hasDesc) {
|
||||
auto descBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
|
||||
descBG->setColor({ 0, 0, 0 });
|
||||
auto descBG = CCScale9Sprite::create("square02b_001.png", {0.0f, 0.0f, 80.0f, 80.0f});
|
||||
descBG->setColor({0, 0, 0});
|
||||
descBG->setOpacity(90);
|
||||
descBG->setContentSize({ m_width * 2, 60.f });
|
||||
descBG->setAnchorPoint({ .0f, .5f });
|
||||
descBG->setContentSize({m_width * 2, 60.f});
|
||||
descBG->setAnchorPoint({.0f, .5f});
|
||||
descBG->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
|
||||
if (spaceForTags) {
|
||||
descBG->setPositionY(m_height / 2 - 7.5f);
|
||||
|
@ -211,20 +212,15 @@ bool ModCell::init(
|
|||
|
||||
this->setupInfo(mod->getModInfo(), false);
|
||||
|
||||
auto viewSpr = ButtonSprite::create(
|
||||
"View", "bigFont.fnt", "GJ_button_01.png", .8f
|
||||
);
|
||||
auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
|
||||
viewSpr->setScale(.65f);
|
||||
|
||||
auto viewBtn = CCMenuItemSpriteExtra::create(
|
||||
viewSpr, this, menu_selector(ModCell::onInfo)
|
||||
);
|
||||
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo));
|
||||
m_menu->addChild(viewBtn);
|
||||
|
||||
if (m_mod->wasSuccesfullyLoaded() && m_mod->supportsDisabling()) {
|
||||
m_enableToggle = CCMenuItemToggler::createWithStandardSprites(
|
||||
this, menu_selector(ModCell::onEnable), .7f
|
||||
);
|
||||
m_enableToggle =
|
||||
CCMenuItemToggler::createWithStandardSprites(this, menu_selector(ModCell::onEnable), .7f);
|
||||
m_enableToggle->setPosition(-45.f, 0.f);
|
||||
m_menu->addChild(m_enableToggle);
|
||||
}
|
||||
|
@ -232,23 +228,22 @@ bool ModCell::init(
|
|||
auto exMark = CCSprite::createWithSpriteFrameName("exMark_001.png");
|
||||
exMark->setScale(.5f);
|
||||
|
||||
m_unresolvedExMark = CCMenuItemSpriteExtra::create(
|
||||
exMark, this, menu_selector(ModCell::onUnresolvedInfo)
|
||||
);
|
||||
m_unresolvedExMark =
|
||||
CCMenuItemSpriteExtra::create(exMark, this, menu_selector(ModCell::onUnresolvedInfo));
|
||||
m_unresolvedExMark->setPosition(-80.f, 0.f);
|
||||
m_unresolvedExMark->setVisible(false);
|
||||
m_menu->addChild(m_unresolvedExMark);
|
||||
|
||||
// if (m_mod->wasSuccesfullyLoaded()) {
|
||||
// if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) {
|
||||
// viewSpr->updateBGImage("GE_button_01.png"_spr);
|
||||
// if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) {
|
||||
// viewSpr->updateBGImage("GE_button_01.png"_spr);
|
||||
|
||||
// auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
|
||||
// updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
|
||||
// updateIcon->setZOrder(99);
|
||||
// updateIcon->setScale(.5f);
|
||||
// viewSpr->addChild(updateIcon);
|
||||
// }
|
||||
// auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
|
||||
// updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
|
||||
// updateIcon->setZOrder(99);
|
||||
// updateIcon->setScale(.5f);
|
||||
// viewSpr->addChild(updateIcon);
|
||||
// }
|
||||
// }
|
||||
|
||||
this->updateState();
|
||||
|
@ -300,9 +295,7 @@ bool IndexItemCell::init(
|
|||
);
|
||||
viewSpr->setScale(.65f);
|
||||
|
||||
auto viewBtn = CCMenuItemSpriteExtra::create(
|
||||
viewSpr, this, menu_selector(IndexItemCell::onInfo)
|
||||
);
|
||||
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
|
||||
m_menu->addChild(viewBtn);
|
||||
|
||||
if (item->tags.size()) {
|
||||
|
@ -370,7 +363,8 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
|
|||
"Unable to remove <cy>" + m_info.path.string() + "</c>: <cr>" +
|
||||
std::string(e.what()) + "</c>",
|
||||
"OK"
|
||||
)->show();
|
||||
)
|
||||
->show();
|
||||
}
|
||||
Loader::get()->refreshModsList();
|
||||
m_layer->reloadList();
|
||||
|
@ -392,7 +386,7 @@ bool InvalidGeodeFileCell::init(
|
|||
this->addChild(menu);
|
||||
|
||||
auto titleLabel = CCLabelBMFont::create("Failed to Load", "bigFont.fnt");
|
||||
titleLabel->setAnchorPoint({ .0f, .5f });
|
||||
titleLabel->setAnchorPoint({.0f, .5f});
|
||||
titleLabel->setScale(.5f);
|
||||
titleLabel->setPosition(m_height / 2, m_height / 2 + 7.f);
|
||||
this->addChild(titleLabel);
|
||||
|
@ -407,14 +401,11 @@ bool InvalidGeodeFileCell::init(
|
|||
pathLabel->setColor({ 255, 255, 0 });
|
||||
this->addChild(pathLabel);
|
||||
|
||||
auto whySpr = ButtonSprite::create(
|
||||
"Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f
|
||||
);
|
||||
auto whySpr = ButtonSprite::create("Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f);
|
||||
whySpr->setScale(.65f);
|
||||
|
||||
auto viewBtn = CCMenuItemSpriteExtra::create(
|
||||
whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo)
|
||||
);
|
||||
auto viewBtn =
|
||||
CCMenuItemSpriteExtra::create(whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo));
|
||||
menu->addChild(viewBtn);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -166,7 +166,7 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
|
|||
// something matches query better
|
||||
// the sorted list is in reverse so adding it last = adding it at the
|
||||
// top
|
||||
auto imod = Loader::getInternalMod();
|
||||
auto imod = Mod::get();
|
||||
if (auto match = queryMatch(query, imod)) {
|
||||
sorted.insert({ match.value(), imod });
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue