Merge branch 'main' of https://github.com/geode-sdk/geode into main

This commit is contained in:
HJfod 2022-12-12 13:47:22 +02:00
commit 5e8a321a36
31 changed files with 1191 additions and 1114 deletions

View file

@ -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;

View file

@ -106,6 +106,8 @@ package_geode_resources_now(
)
target_include_directories(${PROJECT_NAME} PRIVATE
src/
src/loader/
src/internal/
src/platform/
src/gui/

View file

@ -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;
};
}

View file

@ -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));
}

View file

@ -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;
}
}
/**

View file

@ -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...

View file

@ -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"

View file

@ -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();
}
};

View file

@ -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();
}

View file

@ -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);
}
};

View file

@ -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;
}

View file

@ -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);
};

View file

@ -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"

View file

@ -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() {

View file

@ -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>

View file

@ -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) {}

View file

@ -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();
}

View 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() {}

View 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();
};
}

View file

@ -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));

View file

@ -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)) {

View file

@ -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()));
}
}

View file

@ -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;
}

View file

@ -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

View 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

View file

@ -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);

View file

@ -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(

View file

@ -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,

View file

@ -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;
}

View file

@ -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;

View file

@ -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 });
}