mirror of
https://github.com/geode-sdk/geode.git
synced 2025-07-28 23:19:06 -04:00
cleanup
- rewrote Loader to have a public loading API - moved logging away from Loader - moved texture path handling away from Loader into CCFileUtils, added new functions there for that - bumped version to v0.7.0 - moved ModInfo to its own header - added early loading support through mod.json instead of loader stuff - wrote a custom Unzip implementation (essentially same as ZipUtils except with a much more simple and clean API) - renamed `src/load` to `src/loader` - other stuff i prolly forgor
This commit is contained in:
parent
8dc67c6631
commit
f18353c2af
48 changed files with 1384 additions and 1606 deletions
VERSION
bindings
entry.cpploader
CMakeLists.txt
include/Geode
cocos
loader
modify
utils
src
cocos2d-ext
hooks
index
internal
load
loader
Event.cppHook.cppIPC.cppLoader.cppLog.cppMod.cppModEvent.cppModInfo.cppPatch.cppSetting.cppSettingNode.cpp
platform
ui
internal
info
list
settings
nodes
utils
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.6.1
|
||||
0.7.0
|
|
@ -206,6 +206,9 @@ class cocos2d::CCFadeTo {
|
|||
|
||||
class cocos2d::CCFileUtils : cocos2d::TypeInfo {
|
||||
static cocos2d::CCFileUtils* sharedFileUtils() = mac 0x377030, ios 0x159450;
|
||||
static void purgeFileUtils();
|
||||
virtual void addSearchPath(const char* path);
|
||||
virtual void removeSearchPath(const char *path);
|
||||
virtual std::string fullPathForFilename(const char* filename, bool unk);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// included by default in every geode project
|
||||
#include <Geode/Loader.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
|
||||
GEODE_API bool GEODE_CALL geode_implicit_load(geode::Mod* m) {
|
||||
GEODE_API void GEODE_CALL geode_implicit_load(geode::Mod* m) {
|
||||
geode::Mod::setSharedMod(m);
|
||||
geode::log::releaseSchedules(m);
|
||||
geode::Loader::get()->releaseScheduledFunctions(m);
|
||||
return true;
|
||||
geode::Loader::get()->dispatchScheduledFunctions(m);
|
||||
}
|
||||
|
|
|
@ -19,10 +19,7 @@ file(GLOB SOURCES CONFIGURE_DEPENDS
|
|||
src/internal/*.cpp
|
||||
src/platform/mac/*.cpp
|
||||
src/platform/ios/*.cpp
|
||||
src/load/*.cpp
|
||||
src/load/windows/*.cpp
|
||||
src/load/mac/*.cpp
|
||||
src/mac/*.cpp
|
||||
src/loader/*.cpp
|
||||
src/main.cpp
|
||||
src/utils/*.cpp
|
||||
src/index/*.cpp
|
||||
|
@ -39,10 +36,6 @@ file(GLOB SOURCES CONFIGURE_DEPENDS
|
|||
file(GLOB OBJC_SOURCES
|
||||
src/platform/ios/*.mm
|
||||
src/platform/mac/*.mm
|
||||
src/load/ios/*.mm
|
||||
src/load/mac/*.mm
|
||||
src/utils/ios/*.mm
|
||||
src/utils/mac/*.mm
|
||||
)
|
||||
set_source_files_properties(${OBJC_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
|
||||
|
||||
|
|
|
@ -40,6 +40,13 @@ class CCArray;
|
|||
* @{
|
||||
*/
|
||||
|
||||
GEODE_ADD(
|
||||
struct CCTexturePack {
|
||||
std::string m_id;
|
||||
std::vector<std::string> m_paths;
|
||||
};
|
||||
)
|
||||
|
||||
//! @brief Helper class to handle file operations
|
||||
class CC_DLL CCFileUtils : public TypeInfo
|
||||
{
|
||||
|
@ -267,13 +274,41 @@ public:
|
|||
* @lua NA
|
||||
*/
|
||||
virtual void setSearchPaths(const gd::vector<gd::string>& searchPaths);
|
||||
|
||||
GEODE_ADD(
|
||||
/**
|
||||
* Add a texture pack. Texture packs are prioritized over other search
|
||||
* paths, so if a texture pack has a replacement for a file, it will be
|
||||
* used over others. Contrary to addSearchPath, this function adds the
|
||||
* pack to the front of the list. If the pack has already been added,
|
||||
* it's moved to the front of the list (equivalent to removing and
|
||||
* re-adding the pack)
|
||||
* @param pack Pack to add
|
||||
*/
|
||||
void addTexturePack(CCTexturePack const& pack);
|
||||
/**
|
||||
* Remove texture pack by ID
|
||||
* @param id ID of the texture pack
|
||||
*/
|
||||
void removeTexturePack(std::string const& id);
|
||||
/**
|
||||
* Add a search path to the front of the list
|
||||
* @param path Path to add
|
||||
*/
|
||||
void addPriorityPath(const char* path);
|
||||
/**
|
||||
* Update search path order; texture packs are added first, then other
|
||||
* paths
|
||||
*/
|
||||
void updatePaths();
|
||||
)
|
||||
|
||||
/**
|
||||
* Adds a path to search paths.
|
||||
*
|
||||
* @since v2.2
|
||||
*/
|
||||
virtual void addSearchPath(const char* path);
|
||||
virtual void addSearchPath(const char* path);
|
||||
|
||||
/**
|
||||
* Removes a path from search paths.
|
||||
|
|
|
@ -216,6 +216,8 @@ namespace cocos2d
|
|||
* @since geode v1.0.0
|
||||
*/
|
||||
bool isLoaded() const;
|
||||
|
||||
bool unzipAllTo(ghc::filesystem::path const& path);
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,296 +1,105 @@
|
|||
#pragma once
|
||||
|
||||
#include "Log.hpp"
|
||||
#include "Types.hpp"
|
||||
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <Geode/utils/Result.hpp>
|
||||
#include <fs/filesystem.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <fs/filesystem.hpp>
|
||||
#include "Log.hpp"
|
||||
#include <mutex>
|
||||
|
||||
// for some reason std::filesystem::path doesn't have std::hash defined in C++17
|
||||
// and ghc seems to have inherited this limitation
|
||||
template<>
|
||||
struct std::hash<ghc::filesystem::path> {
|
||||
std::size_t operator()(ghc::filesystem::path const& path) const noexcept {
|
||||
return ghc::filesystem::hash_value(path);
|
||||
}
|
||||
};
|
||||
|
||||
namespace geode {
|
||||
#pragma warning(disable : 4251)
|
||||
|
||||
static constexpr std::string_view GEODE_DIRECTORY = "geode";
|
||||
static constexpr std::string_view GEODE_MOD_DIRECTORY = "mods";
|
||||
static constexpr std::string_view GEODE_LOG_DIRECTORY = "log";
|
||||
static constexpr std::string_view GEODE_RESOURCE_DIRECTORY = "resources";
|
||||
static constexpr std::string_view GEODE_CONFIG_DIRECTORY = "config";
|
||||
static constexpr std::string_view GEODE_TEMP_DIRECTORY = "temp";
|
||||
static constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
|
||||
static constexpr std::string_view GEODE_INDEX_DIRECTORY = "index";
|
||||
|
||||
class Mod;
|
||||
class Hook;
|
||||
struct ModInfo;
|
||||
class VersionInfo;
|
||||
|
||||
namespace modifier {
|
||||
template <class, class>
|
||||
class FieldIntermediate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The predeclaration of the implicit entry
|
||||
*/
|
||||
GEODE_API bool GEODE_CALL geode_implicit_load(geode::Mod*);
|
||||
|
||||
namespace geode {
|
||||
|
||||
class GEODE_DLL Loader {
|
||||
public:
|
||||
struct FailedModInfo {
|
||||
// todo: change to path
|
||||
std::string m_file;
|
||||
std::string m_reason;
|
||||
};
|
||||
|
||||
protected:
|
||||
struct LoaderSettings {
|
||||
struct ModSettings {
|
||||
bool m_enabled = true;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, ModSettings> m_mods;
|
||||
// todo: in v1.0.0, make this a customizable option in mod.json
|
||||
std::unordered_set<ghc::filesystem::path> m_earlyLoadMods;
|
||||
};
|
||||
|
||||
using ScheduledFunction = std::function<void GEODE_CALL(void)>;
|
||||
|
||||
std::vector<ScheduledFunction> m_scheduledFunctions;
|
||||
std::unordered_map<std::string, Mod*> m_mods;
|
||||
std::vector<log::Log> m_logs;
|
||||
std::ofstream m_logStream;
|
||||
std::vector<ghc::filesystem::path> m_modDirectories;
|
||||
std::vector<FailedModInfo> m_erroredMods;
|
||||
std::vector<ghc::filesystem::path> m_texturePaths;
|
||||
LoaderSettings m_loadedSettings;
|
||||
bool m_isSetup = false;
|
||||
static std::atomic_bool s_unloading;
|
||||
mutable std::mutex m_modLoadMutex;
|
||||
|
||||
Result<std::string> createTempDirectoryForMod(ModInfo const& info);
|
||||
Result<Mod*> loadModFromFile(std::string const& file);
|
||||
size_t loadModsFromDirectory(ghc::filesystem::path const& path, bool recursive);
|
||||
void createDirectories();
|
||||
|
||||
void updateAllDependencies();
|
||||
|
||||
friend class Mod;
|
||||
friend class CustomLoader;
|
||||
friend struct ModInfo;
|
||||
|
||||
size_t getFieldIndexForClass(size_t hash);
|
||||
|
||||
template <class, class>
|
||||
friend class modifier::FieldIntermediate;
|
||||
|
||||
void updateModResources(Mod* mod);
|
||||
|
||||
// used internally in geode_implicit_load
|
||||
void releaseScheduledFunctions(Mod* mod);
|
||||
|
||||
friend bool GEODE_CALL ::geode_implicit_load(Mod*);
|
||||
|
||||
public:
|
||||
~Loader();
|
||||
|
||||
/**
|
||||
* Get the shared Loader instance
|
||||
* @returns Shared loader instance
|
||||
*/
|
||||
static Loader* get();
|
||||
|
||||
static VersionInfo getVersion();
|
||||
static std::string getVersionType();
|
||||
|
||||
Result<> saveSettings();
|
||||
Result<> loadSettings();
|
||||
Result<> saveData();
|
||||
Result<> loadData();
|
||||
|
||||
bool didLastLaunchCrash() const;
|
||||
ghc::filesystem::path getCrashLogDirectory() const;
|
||||
|
||||
/**
|
||||
* Directory where Geometry Dash is
|
||||
*/
|
||||
ghc::filesystem::path getGameDirectory() const;
|
||||
/**
|
||||
* Directory where GD saves its files
|
||||
*/
|
||||
ghc::filesystem::path getSaveDirectory() const;
|
||||
/**
|
||||
* Directory where Geode is
|
||||
*/
|
||||
ghc::filesystem::path getGeodeDirectory() const;
|
||||
/**
|
||||
* Directory where Geode saves its files
|
||||
*/
|
||||
ghc::filesystem::path getGeodeSaveDirectory() const;
|
||||
|
||||
/**
|
||||
* Minimum supported mod version
|
||||
*/
|
||||
static VersionInfo minModVersion();
|
||||
/**
|
||||
* Maximum supported mod version
|
||||
*/
|
||||
static VersionInfo maxModVersion();
|
||||
/**
|
||||
* Check if a mod's version is within the supported range
|
||||
*/
|
||||
static bool supportedModVersion(VersionInfo const& version);
|
||||
|
||||
/**
|
||||
* Whether mod specified with ID is enabled
|
||||
* @param id The ID of the mod
|
||||
*/
|
||||
bool shouldLoadMod(std::string const& id) const;
|
||||
|
||||
/**
|
||||
* Set up the Loader.
|
||||
* @returns True if setup was succesful or
|
||||
* has been done before, false if an error
|
||||
* occurred
|
||||
*/
|
||||
bool setup();
|
||||
|
||||
/**
|
||||
* Refresh the mods list. Scans all search
|
||||
* directories again for unloaded mods
|
||||
* @returns Amount of new mods loaded
|
||||
*/
|
||||
size_t refreshMods();
|
||||
|
||||
/**
|
||||
* Returns true if the Loader is unloading /
|
||||
* currently unloaded. Used for threading;
|
||||
* should be thread-safe
|
||||
* @returns True if the loader is unloading /
|
||||
* unloaded, false otherwise
|
||||
*/
|
||||
static bool isUnloading();
|
||||
|
||||
void pushLog(log::Log&& log);
|
||||
void popLog(log::Log* log);
|
||||
std::vector<log::Log*> getLogs(std::initializer_list<Severity> severityFilter = {});
|
||||
|
||||
void clearLogs();
|
||||
|
||||
/**
|
||||
* Do not call manually unless you know what you're doing.
|
||||
*/
|
||||
void updateResourcePaths();
|
||||
/**
|
||||
* Do not call manually unless you know what you're doing.
|
||||
*/
|
||||
void updateResources();
|
||||
void addTexturePath(ghc::filesystem::path const& path);
|
||||
void removeTexturePath(ghc::filesystem::path const& path);
|
||||
std::vector<ghc::filesystem::path> getTexturePaths() const;
|
||||
|
||||
/**
|
||||
* Check if a mod with an ID is installed. Any
|
||||
* valid .geode file in the mods directory will
|
||||
* be listed as installed
|
||||
* @param id The ID of the mod
|
||||
* @returns True if the mod is installed
|
||||
*/
|
||||
bool isModInstalled(std::string const& id) const;
|
||||
/**
|
||||
* Get an installed mod by its ID
|
||||
* @param id The ID of the mod
|
||||
* @returns Pointer to Mod if it was found, or nullptr if
|
||||
* the mod is not installed
|
||||
*/
|
||||
Mod* getInstalledMod(std::string const& id) const;
|
||||
/**
|
||||
* Check if a mod with an ID is loaded
|
||||
* @param id The ID of the mod
|
||||
* @returns True if the mod was found, or false if
|
||||
* the mod is not loaded nor installed
|
||||
*/
|
||||
bool isModLoaded(std::string const& id) const;
|
||||
/**
|
||||
* Get a loaded mod by its ID
|
||||
* @param id The ID of the mod
|
||||
* @returns Pointer to Mod if it was found, or nullptr if
|
||||
* the mod is not loaded nor installed
|
||||
*/
|
||||
Mod* getLoadedMod(std::string const& id) const;
|
||||
/**
|
||||
* Get a list of all installed mods
|
||||
* @returns List of all installed mods
|
||||
*/
|
||||
std::vector<Mod*> getAllMods() const;
|
||||
/**
|
||||
* Get all mods that are a serious
|
||||
* disappointment to their parents
|
||||
*/
|
||||
std::vector<FailedModInfo> getFailedMods() const;
|
||||
/**
|
||||
* Unload a mod fully. This will remove it
|
||||
* from the mods list and delete the Mod. If
|
||||
* the mod does not properly handle unloading,
|
||||
* this function may cause a crash; Use with
|
||||
* caution!
|
||||
*/
|
||||
void unloadMod(Mod* mod);
|
||||
|
||||
/**
|
||||
* Get Geode's internal representation. Use with
|
||||
* caution!
|
||||
* @returns Pointer to InternalMod
|
||||
*/
|
||||
static Mod* getInternalMod();
|
||||
|
||||
/**
|
||||
* Run a function in the GD thread. Useful if you're
|
||||
* doing logic in another thread and need to interact
|
||||
* with GUI. The function will be run the next time
|
||||
* `CCScheduler::update` is called
|
||||
* @param func Function to run
|
||||
*/
|
||||
void queueInGDThread(ScheduledFunction func);
|
||||
|
||||
/**
|
||||
* Run a function when the Mod is loaded. Useful if for
|
||||
* some reason you need to run some function in
|
||||
* static initialization.
|
||||
* @param func Function to run
|
||||
*/
|
||||
void scheduleOnModLoad(Mod* m, ScheduledFunction func);
|
||||
|
||||
/**
|
||||
* Open the platform-specific external console (if one exists)
|
||||
*/
|
||||
static void openPlatformConsole();
|
||||
/**
|
||||
* Close the platform-specific external console (if one exists)
|
||||
*/
|
||||
static void closePlatfromConsole();
|
||||
|
||||
void waitForModsToBeLoaded();
|
||||
void setEarlyLoadMod(Mod* mod, bool enabled);
|
||||
bool shouldEarlyLoadMod(Mod* mod) const;
|
||||
};
|
||||
}
|
||||
#pragma once
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "Log.hpp"
|
||||
#include "../external/filesystem/fs/filesystem.hpp"
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include "../utils/Result.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
|
||||
namespace geode {
|
||||
using ScheduledFunction = std::function<void GEODE_CALL(void)>;
|
||||
|
||||
struct InvalidGeodeFile {
|
||||
ghc::filesystem::path m_path;
|
||||
std::string m_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;
|
||||
|
||||
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);
|
||||
|
||||
public:
|
||||
~Loader();
|
||||
static Loader* get();
|
||||
|
||||
Result<> setup();
|
||||
|
||||
Result<> saveData();
|
||||
Result<> loadData();
|
||||
|
||||
static VersionInfo getVersion();
|
||||
static VersionInfo minModVersion();
|
||||
static VersionInfo maxModVersion();
|
||||
static bool isModVersionSupported(VersionInfo const& version);
|
||||
|
||||
Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
|
||||
Result<> loadModsFromDirectory(
|
||||
ghc::filesystem::path const& dir,
|
||||
bool recursive = true
|
||||
);
|
||||
Result<> 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();
|
||||
void updateAllDependencies();
|
||||
std::vector<InvalidGeodeFile> getFailedMods() const;
|
||||
|
||||
void updateResources();
|
||||
|
||||
void queueInGDThread(ScheduledFunction func);
|
||||
void scheduleOnModLoad(Mod* mod, ScheduledFunction func);
|
||||
void waitForModsToBeLoaded();
|
||||
|
||||
/**
|
||||
* Open the platform-specific external console (if one exists)
|
||||
*/
|
||||
static void openPlatformConsole();
|
||||
/**
|
||||
* Close the platform-specific external console (if one exists)
|
||||
*/
|
||||
static void closePlatfromConsole();
|
||||
|
||||
bool didLastLaunchCrash() const;
|
||||
ghc::filesystem::path getCrashLogDirectory() const;
|
||||
|
||||
/**
|
||||
* Directory where Geometry Dash is
|
||||
*/
|
||||
ghc::filesystem::path getGameDirectory() const;
|
||||
/**
|
||||
* Directory where GD saves its files
|
||||
*/
|
||||
ghc::filesystem::path getSaveDirectory() const;
|
||||
/**
|
||||
* Directory where Geode is
|
||||
*/
|
||||
ghc::filesystem::path getGeodeDirectory() const;
|
||||
/**
|
||||
* Directory where Geode saves its files
|
||||
*/
|
||||
ghc::filesystem::path getGeodeSaveDirectory() const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,11 +29,11 @@ namespace geode {
|
|||
using log_clock = std::chrono::system_clock;
|
||||
|
||||
GEODE_DLL std::string generateLogName();
|
||||
|
||||
GEODE_DLL std::string parse(cocos2d::CCNode*);
|
||||
template <class T>
|
||||
|
||||
requires std::convertible_to<T*, cocos2d::CCNode*> std::string parse(T* node) {
|
||||
template <class T>
|
||||
requires std::convertible_to<T*, cocos2d::CCNode*>
|
||||
std::string parse(T* node) {
|
||||
return parse(static_cast<cocos2d::CCNode*>(node));
|
||||
}
|
||||
|
||||
|
@ -45,21 +45,22 @@ namespace geode {
|
|||
GEODE_DLL std::string parse(cocos2d::ccColor4B const&);
|
||||
GEODE_DLL std::string parse(cocos2d::ccColor4F const&);
|
||||
GEODE_DLL std::string parse(cocos2d::CCObject*);
|
||||
template <class T>
|
||||
|
||||
requires(std::convertible_to<T*, cocos2d::CCObject*> && !std::convertible_to<T*, cocos2d::CCNode*>)
|
||||
std::string parse(T* node) {
|
||||
template <class T>
|
||||
requires(
|
||||
std::convertible_to<T*, cocos2d::CCObject*> &&
|
||||
!std::convertible_to<T*, cocos2d::CCNode*>
|
||||
)
|
||||
std::string parse(T* node) {
|
||||
return parse(static_cast<cocos2d::CCObject*>(node));
|
||||
}
|
||||
|
||||
GEODE_DLL std::string parse(Mod*);
|
||||
|
||||
template <typename T>
|
||||
|
||||
requires requires(T b) {
|
||||
std::stringstream() << b;
|
||||
}
|
||||
|
||||
requires requires(T b) {
|
||||
std::stringstream() << b;
|
||||
}
|
||||
std::string parse(T const& thing) {
|
||||
std::stringstream buf;
|
||||
buf << thing;
|
||||
|
@ -95,7 +96,7 @@ namespace geode {
|
|||
}
|
||||
};*/
|
||||
|
||||
class GEODE_DLL Log {
|
||||
class GEODE_DLL Log final {
|
||||
private:
|
||||
static std::vector<std::function<void(Mod*)>>& scheduled();
|
||||
|
||||
|
@ -106,36 +107,19 @@ namespace geode {
|
|||
Severity m_severity;
|
||||
|
||||
public:
|
||||
Log(Mod* mod,
|
||||
Severity sev); // : m_sender(mod), m_time(log_clock::now()), m_severity(sev) {}
|
||||
Log(Mod* mod, Severity sev);
|
||||
Log(Log&& l) = default;
|
||||
Log& operator=(Log&& l) = default;
|
||||
bool operator==(Log const& l);
|
||||
|
||||
std::string toString(bool logTime = true) const;
|
||||
void pushToLoader();
|
||||
|
||||
inline std::vector<ComponentTrait*>& getComponents() {
|
||||
return m_components;
|
||||
}
|
||||
std::vector<ComponentTrait*>& getComponents();
|
||||
log_clock::time_point getTime() const;
|
||||
Mod* getSender() const;
|
||||
Severity getSeverity() const;
|
||||
|
||||
inline log_clock::time_point getTime() const {
|
||||
return m_time;
|
||||
}
|
||||
|
||||
inline Mod* getSender() const {
|
||||
return m_sender;
|
||||
}
|
||||
|
||||
inline Severity getSeverity() const {
|
||||
return m_severity;
|
||||
}
|
||||
|
||||
inline ~Log() {
|
||||
for (auto comp : m_components) {
|
||||
delete comp;
|
||||
}
|
||||
}
|
||||
~Log();
|
||||
|
||||
template <typename... Args>
|
||||
friend void schedule(Severity sev, Args... args);
|
||||
|
@ -143,15 +127,30 @@ namespace geode {
|
|||
friend void GEODE_DLL releaseSchedules(Mod* m);
|
||||
};
|
||||
|
||||
void GEODE_DLL
|
||||
vlogImpl(Severity, Mod*, std::string_view, std::function<void(Log&)>*, size_t);
|
||||
class GEODE_DLL Logs {
|
||||
private:
|
||||
static inline std::vector<Log> s_logs;
|
||||
static inline std::ofstream s_logStream;
|
||||
|
||||
Logs() = delete;
|
||||
~Logs() = delete;
|
||||
|
||||
public:
|
||||
static void setup();
|
||||
static void push(Log&& log);
|
||||
static void pop(Log* log);
|
||||
static std::vector<Log*> list();
|
||||
static void clear();
|
||||
};
|
||||
|
||||
void GEODE_DLL vlogImpl(
|
||||
Severity, Mod*, std::string_view, std::function<void(Log&)>*, size_t
|
||||
);
|
||||
|
||||
template <typename... Args>
|
||||
|
||||
requires requires(Args... b) {
|
||||
(parse(b), ...);
|
||||
}
|
||||
|
||||
requires requires(Args... b) {
|
||||
(parse(b), ...);
|
||||
}
|
||||
void log(Severity severity, Mod* mod, std::string_view formatStr, Args... args) {
|
||||
static constexpr auto pushSomething = [](Log& log, auto something) {
|
||||
// i think this line of code is very sad
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "Hook.hpp"
|
||||
#include "Setting.hpp"
|
||||
#include "Types.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include "../utils/Result.hpp"
|
||||
|
@ -16,188 +17,7 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class InternalLoader;
|
||||
class InternalMod;
|
||||
|
||||
namespace geode {
|
||||
struct PlatformInfo;
|
||||
|
||||
class Hook;
|
||||
class Patch;
|
||||
class Loader;
|
||||
class Mod;
|
||||
class Setting;
|
||||
|
||||
class Unknown;
|
||||
using unknownmemfn_t = void (Unknown::*)();
|
||||
using unknownfn_t = void (*)();
|
||||
}
|
||||
|
||||
/**
|
||||
* The predeclaration of the implicit entry
|
||||
*/
|
||||
GEODE_API bool GEODE_CALL geode_implicit_load(geode::Mod*);
|
||||
|
||||
namespace geode {
|
||||
|
||||
struct Dependency {
|
||||
std::string m_id;
|
||||
// todo: Dynamic versions (1.*.*)
|
||||
VersionInfo m_version { 1, 0, 0 };
|
||||
ModResolveState m_state = ModResolveState::Unloaded;
|
||||
bool m_required = false;
|
||||
Mod* m_mod = nullptr;
|
||||
bool isUnresolved() const;
|
||||
};
|
||||
|
||||
struct IssuesInfo {
|
||||
std::string m_info;
|
||||
std::optional<std::string> m_url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents all the data gatherable
|
||||
* from mod.json
|
||||
*/
|
||||
struct GEODE_DLL ModInfo {
|
||||
/**
|
||||
* Path to the mod file
|
||||
*/
|
||||
ghc::filesystem::path m_path;
|
||||
/**
|
||||
* Name of the platform binary within
|
||||
* the mod zip
|
||||
*/
|
||||
// clang-format off
|
||||
std::string m_binaryName = GEODE_WINDOWS("mod.dll")
|
||||
GEODE_MACOS("mod.dylib")
|
||||
GEODE_IOS("mod.dylib")
|
||||
GEODE_ANDROID("mod.so");
|
||||
// clang-format on
|
||||
/**
|
||||
* Mod Version. Should follow semver.
|
||||
*/
|
||||
VersionInfo m_version { 1, 0, 0 };
|
||||
/**
|
||||
* Human-readable ID of the Mod.
|
||||
* Recommended to be in the format
|
||||
* "com.developer.mod". Not
|
||||
* guaranteed to be either case-
|
||||
* nor space-sensitive. Should
|
||||
* be restricted to the ASCII
|
||||
* character set.
|
||||
*/
|
||||
std::string m_id;
|
||||
/**
|
||||
* Name of the mod. May contain
|
||||
* spaces & punctuation, but should
|
||||
* be restricted to the ASCII
|
||||
* character set.
|
||||
*/
|
||||
std::string m_name;
|
||||
/**
|
||||
* The name of the head developer.
|
||||
* Should be a single name, like
|
||||
* "HJfod" or "The Geode Team".
|
||||
* If the mod has multiple
|
||||
* developers, this field should
|
||||
* be one of their name or a team
|
||||
* name, and the rest of the credits
|
||||
* should be named in `m_credits`
|
||||
* instead.
|
||||
*/
|
||||
std::string m_developer;
|
||||
/**
|
||||
* Short & concise description of the
|
||||
* mod.
|
||||
*/
|
||||
std::optional<std::string> m_description;
|
||||
/**
|
||||
* Detailed description of the mod, writtenin Markdown (see
|
||||
* <Geode/ui/MDTextArea.hpp>) for more info
|
||||
*/
|
||||
std::optional<std::string> m_details;
|
||||
/**
|
||||
* Changelog for the mod, written in Markdown (see
|
||||
* <Geode/ui/MDTextArea.hpp>) for more info
|
||||
*/
|
||||
std::optional<std::string> m_changelog;
|
||||
/**
|
||||
* Support info for the mod; this means anything to show ways to
|
||||
* support the mod's development, like donations. Written in Markdown
|
||||
* (see <Geode/ui/MDTextArea.hpp>) for more info
|
||||
*/
|
||||
std::optional<std::string> m_supportInfo;
|
||||
/**
|
||||
* Git Repository of the mod
|
||||
*/
|
||||
std::optional<std::string> m_repository;
|
||||
/**
|
||||
* Info about where users should report issues and request help
|
||||
*/
|
||||
std::optional<IssuesInfo> m_issues;
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
std::vector<Dependency> m_dependencies;
|
||||
/**
|
||||
* Mod spritesheet names
|
||||
*/
|
||||
std::vector<std::string> m_spritesheets;
|
||||
/**
|
||||
* Mod settings
|
||||
*/
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Setting>>> m_settings;
|
||||
/**
|
||||
* Whether the mod can be disabled or not
|
||||
*/
|
||||
bool m_supportsDisabling = true;
|
||||
/**
|
||||
* Whether the mod can be unloaded or not
|
||||
*/
|
||||
bool m_supportsUnloading = false;
|
||||
/**
|
||||
* Create ModInfo from a .geode package
|
||||
*/
|
||||
static Result<ModInfo> createFromGeodeFile(ghc::filesystem::path const& path);
|
||||
/**
|
||||
* Create ModInfo from a mod.json file
|
||||
*/
|
||||
static Result<ModInfo> createFromFile(ghc::filesystem::path const& path);
|
||||
/**
|
||||
* Create ModInfo from a parsed json document
|
||||
*/
|
||||
static Result<ModInfo> create(ModJson const& json);
|
||||
|
||||
/**
|
||||
* Convert to JSON. Essentially same as getRawJSON except dynamically
|
||||
* adds runtime fields like path
|
||||
*/
|
||||
ModJson toJSON() const;
|
||||
/**
|
||||
* Get the raw JSON file
|
||||
*/
|
||||
ModJson getRawJSON() const;
|
||||
|
||||
private:
|
||||
ModJson m_rawJSON;
|
||||
|
||||
/**
|
||||
* Version is passed for backwards
|
||||
* compatibility if we update the mod.json
|
||||
* format
|
||||
*/
|
||||
static Result<ModInfo> createFromSchemaV010(ModJson const& json);
|
||||
|
||||
Result<> addSpecialFiles(ghc::filesystem::path const& dir);
|
||||
Result<> addSpecialFiles(cocos2d::ZipFile& zip);
|
||||
|
||||
std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
|
||||
};
|
||||
|
||||
// For converting ModInfo back to JSON
|
||||
void GEODE_DLL to_json(nlohmann::json& json, ModInfo const& info);
|
||||
|
||||
template<class T>
|
||||
struct HandleToSaved : public T {
|
||||
Mod* m_mod;
|
||||
|
@ -242,7 +62,7 @@ namespace geode {
|
|||
/**
|
||||
* Whether the mod binary is loaded or not
|
||||
*/
|
||||
bool m_loaded = false;
|
||||
bool m_binaryLoaded = false;
|
||||
/**
|
||||
* Whether the mod is loadable or not
|
||||
*/
|
||||
|
@ -262,39 +82,7 @@ namespace geode {
|
|||
* when their dependency is disabled.
|
||||
*/
|
||||
std::vector<Mod*> m_parentDependencies;
|
||||
/**
|
||||
* Pointer to the Mod's implicit load function
|
||||
*/
|
||||
geode_load m_implicitLoadFunc = nullptr;
|
||||
/**
|
||||
* Pointer to the Mod's load function
|
||||
*/
|
||||
geode_load m_loadFunc = nullptr;
|
||||
/**
|
||||
* Pointer to the Mod's unload function
|
||||
*/
|
||||
geode_unload m_unloadFunc = nullptr;
|
||||
/**
|
||||
* Pointer to the Mod's enable function
|
||||
*/
|
||||
geode_enable m_enableFunc = nullptr;
|
||||
/**
|
||||
* Pointer to the Mod's enable function
|
||||
*/
|
||||
geode_disable m_disableFunc = nullptr;
|
||||
geode_load_data m_loadDataFunc = nullptr;
|
||||
geode_save_data m_saveDataFunc = nullptr;
|
||||
geode_setting_updated m_settingUpdatedFunc = nullptr;
|
||||
/**
|
||||
* Whether temp/<mod id>/resources should be
|
||||
* added to CCFileUtils search paths
|
||||
*/
|
||||
bool m_addResourcesToSearchPath = false;
|
||||
/**
|
||||
* Error info in case loading failed
|
||||
*/
|
||||
std::string m_loadErrorInfo = "";
|
||||
|
||||
decltype(geode_implicit_load)* m_implicitLoadFunc;
|
||||
/**
|
||||
* Saved values
|
||||
*/
|
||||
|
@ -305,13 +93,8 @@ namespace geode {
|
|||
*/
|
||||
Result<> loadPlatformBinary();
|
||||
Result<> unloadPlatformBinary();
|
||||
|
||||
Result<> saveSettings();
|
||||
Result<> loadSettings();
|
||||
|
||||
Result<> createTempDir();
|
||||
|
||||
static bool validateID(std::string const& id);
|
||||
// no copying
|
||||
Mod(Mod const&) = delete;
|
||||
Mod operator=(Mod const&) = delete;
|
||||
|
@ -337,7 +120,7 @@ namespace geode {
|
|||
sharedMod<> = mod;
|
||||
}
|
||||
|
||||
friend bool GEODE_CALL ::geode_implicit_load(Mod*);
|
||||
friend void GEODE_CALL ::geode_implicit_load(Mod*);
|
||||
|
||||
public:
|
||||
std::string getID() const;
|
||||
|
@ -345,7 +128,6 @@ namespace geode {
|
|||
std::string getDeveloper() const;
|
||||
std::optional<std::string> getDescription() const;
|
||||
std::optional<std::string> getDetails() const;
|
||||
[[deprecated("Use Mod::getPackagePath instead")]] std::string getPath() const;
|
||||
ghc::filesystem::path getPackagePath() const;
|
||||
VersionInfo getVersion() const;
|
||||
bool isEnabled() const;
|
||||
|
@ -353,7 +135,6 @@ namespace geode {
|
|||
bool supportsDisabling() const;
|
||||
bool supportsUnloading() const;
|
||||
bool wasSuccesfullyLoaded() const;
|
||||
std::string getLoadErrorInfo() const;
|
||||
ModInfo getModInfo() const;
|
||||
ghc::filesystem::path getTempDir() const;
|
||||
ghc::filesystem::path getBinaryPath() const;
|
||||
|
@ -527,20 +308,20 @@ namespace geode {
|
|||
Result<> unpatch(Patch* patch);
|
||||
|
||||
/**
|
||||
* Load this mod
|
||||
* Load & enable this mod
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
*/
|
||||
Result<> load();
|
||||
Result<> loadBinary();
|
||||
|
||||
/**
|
||||
* Unload this mod
|
||||
* Disable & unload this mod
|
||||
* @warning May crash if the mod doesn't
|
||||
* properly handle unloading!
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
*/
|
||||
Result<> unload();
|
||||
Result<> unloadBinary();
|
||||
|
||||
/**
|
||||
* Enable this mod
|
||||
|
@ -550,15 +331,17 @@ namespace geode {
|
|||
Result<> enable();
|
||||
|
||||
/**
|
||||
* Disable this mod if it supports doing so
|
||||
* Disable this mod
|
||||
* @returns Successful result on success,
|
||||
* errorful result with info on error
|
||||
*/
|
||||
Result<> disable();
|
||||
|
||||
/**
|
||||
* Disable & unload this mod (if supported),
|
||||
* then delete the mod's .geode file.
|
||||
* 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
|
||||
*/
|
||||
|
|
179
loader/include/Geode/loader/ModInfo.hpp
Normal file
179
loader/include/Geode/loader/ModInfo.hpp
Normal file
|
@ -0,0 +1,179 @@
|
|||
#pragma once
|
||||
|
||||
#include "Types.hpp"
|
||||
#include "../external/json/json.hpp"
|
||||
#include "../utils/VersionInfo.hpp"
|
||||
#include "../utils/Result.hpp"
|
||||
|
||||
namespace geode {
|
||||
namespace utils::file {
|
||||
class Unzip;
|
||||
}
|
||||
|
||||
using ModJson = nlohmann::ordered_json;
|
||||
|
||||
struct Dependency {
|
||||
std::string m_id;
|
||||
// todo: Dynamic versions (1.*.*)
|
||||
VersionInfo m_version { 1, 0, 0 };
|
||||
ModResolveState m_state = ModResolveState::Unloaded;
|
||||
bool m_required = false;
|
||||
Mod* m_mod = nullptr;
|
||||
bool isUnresolved() const;
|
||||
};
|
||||
|
||||
struct IssuesInfo {
|
||||
std::string m_info;
|
||||
std::optional<std::string> m_url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents all the data gatherable
|
||||
* from mod.json
|
||||
*/
|
||||
struct GEODE_DLL ModInfo {
|
||||
/**
|
||||
* Path to the mod file
|
||||
*/
|
||||
ghc::filesystem::path m_path;
|
||||
/**
|
||||
* Name of the platform binary within
|
||||
* the mod zip
|
||||
*/
|
||||
std::string m_binaryName;
|
||||
/**
|
||||
* Mod Version. Should follow semver.
|
||||
*/
|
||||
VersionInfo m_version { 1, 0, 0 };
|
||||
/**
|
||||
* Human-readable ID of the Mod.
|
||||
* Recommended to be in the format
|
||||
* "com.developer.mod". Not
|
||||
* guaranteed to be either case-
|
||||
* nor space-sensitive. Should
|
||||
* be restricted to the ASCII
|
||||
* character set.
|
||||
*/
|
||||
std::string m_id;
|
||||
/**
|
||||
* Name of the mod. May contain
|
||||
* spaces & punctuation, but should
|
||||
* be restricted to the ASCII
|
||||
* character set.
|
||||
*/
|
||||
std::string m_name;
|
||||
/**
|
||||
* The name of the head developer.
|
||||
* Should be a single name, like
|
||||
* "HJfod" or "The Geode Team".
|
||||
* If the mod has multiple
|
||||
* developers, this field should
|
||||
* be one of their name or a team
|
||||
* name, and the rest of the credits
|
||||
* should be named in `m_credits`
|
||||
* instead.
|
||||
*/
|
||||
std::string m_developer;
|
||||
/**
|
||||
* Short & concise description of the
|
||||
* mod.
|
||||
*/
|
||||
std::optional<std::string> m_description;
|
||||
/**
|
||||
* Detailed description of the mod, writtenin Markdown (see
|
||||
* <Geode/ui/MDTextArea.hpp>) for more info
|
||||
*/
|
||||
std::optional<std::string> m_details;
|
||||
/**
|
||||
* Changelog for the mod, written in Markdown (see
|
||||
* <Geode/ui/MDTextArea.hpp>) for more info
|
||||
*/
|
||||
std::optional<std::string> m_changelog;
|
||||
/**
|
||||
* Support info for the mod; this means anything to show ways to
|
||||
* support the mod's development, like donations. Written in Markdown
|
||||
* (see <Geode/ui/MDTextArea.hpp>) for more info
|
||||
*/
|
||||
std::optional<std::string> m_supportInfo;
|
||||
/**
|
||||
* Git Repository of the mod
|
||||
*/
|
||||
std::optional<std::string> m_repository;
|
||||
/**
|
||||
* Info about where users should report issues and request help
|
||||
*/
|
||||
std::optional<IssuesInfo> m_issues;
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
std::vector<Dependency> m_dependencies;
|
||||
/**
|
||||
* Mod spritesheet names
|
||||
*/
|
||||
std::vector<std::string> m_spritesheets;
|
||||
/**
|
||||
* Mod settings
|
||||
*/
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Setting>>> m_settings;
|
||||
/**
|
||||
* Whether the mod can be disabled or not
|
||||
*/
|
||||
bool m_supportsDisabling = true;
|
||||
/**
|
||||
* Whether the mod can be unloaded or not
|
||||
*/
|
||||
bool m_supportsUnloading = false;
|
||||
/**
|
||||
* Whether this mod has to be loaded before the loading screen or not
|
||||
*/
|
||||
bool m_needsEarlyLoad = false;
|
||||
/**
|
||||
* Create ModInfo from an unzipped .geode package
|
||||
*/
|
||||
static Result<ModInfo> createFromGeodeZip(utils::file::Unzip& zip);
|
||||
/**
|
||||
* Create ModInfo from a .geode package
|
||||
*/
|
||||
static Result<ModInfo> createFromGeodeFile(ghc::filesystem::path const& path);
|
||||
/**
|
||||
* Create ModInfo from a mod.json file
|
||||
*/
|
||||
static Result<ModInfo> createFromFile(ghc::filesystem::path const& path);
|
||||
/**
|
||||
* Create ModInfo from a parsed json document
|
||||
*/
|
||||
static Result<ModInfo> create(ModJson const& json);
|
||||
|
||||
/**
|
||||
* Convert to JSON. Essentially same as getRawJSON except dynamically
|
||||
* adds runtime fields like path
|
||||
*/
|
||||
ModJson toJSON() const;
|
||||
/**
|
||||
* Get the raw JSON file
|
||||
*/
|
||||
ModJson getRawJSON() const;
|
||||
|
||||
bool operator==(ModInfo const& other) const;
|
||||
|
||||
static bool validateID(std::string const& id);
|
||||
|
||||
private:
|
||||
ModJson m_rawJSON;
|
||||
|
||||
/**
|
||||
* Version is passed for backwards
|
||||
* compatibility if we update the mod.json
|
||||
* format
|
||||
*/
|
||||
static Result<ModInfo> createFromSchemaV010(ModJson const& json);
|
||||
|
||||
Result<> addSpecialFiles(ghc::filesystem::path const& dir);
|
||||
Result<> addSpecialFiles(utils::file::Unzip& zip);
|
||||
|
||||
std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
|
||||
};
|
||||
|
||||
// For converting ModInfo back to JSON
|
||||
void GEODE_DLL to_json(nlohmann::json& json, ModInfo const& info);
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
#include "../utils/file.hpp"
|
||||
#include "../utils/ranges.hpp"
|
||||
#include "../utils/cocos.hpp"
|
||||
#include "ModInfo.hpp"
|
||||
#include <optional>
|
||||
#include <regex>
|
||||
#include <unordered_set>
|
||||
|
@ -15,8 +16,6 @@
|
|||
#pragma warning(disable : 4275)
|
||||
|
||||
namespace geode {
|
||||
using ModJson = nlohmann::ordered_json;
|
||||
|
||||
class Setting;
|
||||
class SettingNode;
|
||||
class BoolSetting;
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <Geode/platform/cplatform.h>
|
||||
#include "../DefaultInclude.hpp"
|
||||
#include "../platform/cplatform.h"
|
||||
#include <string>
|
||||
|
||||
class InternalLoader;
|
||||
class InternalMod;
|
||||
|
||||
namespace geode {
|
||||
/**
|
||||
* Describes the severity of the log
|
||||
|
@ -148,22 +151,33 @@ namespace geode {
|
|||
Disabled,
|
||||
};
|
||||
|
||||
/**
|
||||
* These methods will be removed in v1.0.0 and replaced with events
|
||||
*/
|
||||
static constexpr std::string_view GEODE_DIRECTORY = "geode";
|
||||
static constexpr std::string_view GEODE_MOD_DIRECTORY = "mods";
|
||||
static constexpr std::string_view GEODE_LOG_DIRECTORY = "log";
|
||||
static constexpr std::string_view GEODE_RESOURCE_DIRECTORY = "resources";
|
||||
static constexpr std::string_view GEODE_CONFIG_DIRECTORY = "config";
|
||||
static constexpr std::string_view GEODE_TEMP_DIRECTORY = "temp";
|
||||
static constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
|
||||
static constexpr std::string_view GEODE_INDEX_DIRECTORY = "index";
|
||||
|
||||
/**
|
||||
* Default Geode load method for C++
|
||||
* mods: The mod receive a pointer to
|
||||
* its allocated Mod instance. Return
|
||||
* true on a succesful load,
|
||||
* return false on error.
|
||||
*/
|
||||
typedef bool(GEODE_CALL* geode_load)(Mod*);
|
||||
typedef void(GEODE_CALL* geode_unload)();
|
||||
typedef bool(GEODE_CALL* geode_enable)();
|
||||
typedef bool(GEODE_CALL* geode_disable)();
|
||||
typedef bool(GEODE_CALL* geode_save_data)(const char*);
|
||||
typedef bool(GEODE_CALL* geode_load_data)(const char*);
|
||||
typedef void(GEODE_CALL* geode_setting_updated)(const char*, Setting*);
|
||||
class Mod;
|
||||
class Loader;
|
||||
class Hook;
|
||||
struct ModInfo;
|
||||
class VersionInfo;
|
||||
|
||||
class Unknown;
|
||||
using unknownmemfn_t = void (Unknown::*)();
|
||||
using unknownfn_t = void (*)();
|
||||
|
||||
namespace modifier {
|
||||
template <class, class>
|
||||
class FieldIntermediate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The predeclaration of the implicit entry
|
||||
*/
|
||||
GEODE_API void GEODE_CALL geode_implicit_load(geode::Mod*);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "Traits.hpp"
|
||||
|
||||
#include <cocos2d.h>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <vector>
|
||||
|
||||
|
@ -47,6 +47,11 @@ namespace geode::modifier {
|
|||
// Padding used for guaranteeing any member of parents
|
||||
// will be in between sizeof(Intermediate) and sizeof(Parent)
|
||||
uintptr_t m_padding;
|
||||
static inline std::unordered_map<size_t, size_t> nextIndex;
|
||||
|
||||
static size_t getFieldIndexForClass(size_t hash) {
|
||||
return nextIndex[hash]++;
|
||||
}
|
||||
|
||||
public:
|
||||
static void fieldConstructor(void* offsetField) {
|
||||
|
@ -82,7 +87,7 @@ namespace geode::modifier {
|
|||
reinterpret_cast<Parent*>(reinterpret_cast<std::byte*>(this) - sizeof(Base));
|
||||
static_assert(sizeof(Base) == offsetof(Parent, m_fields), "offsetof not correct");
|
||||
auto container = FieldContainer::from(node);
|
||||
static size_t index = Loader::get()->getFieldIndexForClass(typeid(Base).hash_code());
|
||||
static size_t index = getFieldIndexForClass(typeid(Base).hash_code());
|
||||
// this pointer is offset
|
||||
auto offsetField = container->getField(index);
|
||||
if (!offsetField) {
|
||||
|
|
|
@ -55,7 +55,7 @@ namespace geode {
|
|||
if (isErr()) {
|
||||
return Result(fmt::format(str, std::forward<Args>(args)...));
|
||||
} else {
|
||||
return this;
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,59 @@ namespace geode::utils::file {
|
|||
GEODE_DLL Result<std::vector<std::string>> listFiles(std::string const& path);
|
||||
GEODE_DLL Result<std::vector<std::string>> listFilesRecursively(std::string const& path);
|
||||
|
||||
class UnzipImpl;
|
||||
|
||||
class GEODE_DLL Unzip final {
|
||||
private:
|
||||
UnzipImpl* m_impl;
|
||||
|
||||
public:
|
||||
Unzip() = delete;
|
||||
Unzip(Unzip const&) = delete;
|
||||
Unzip(Unzip&& other);
|
||||
Unzip(UnzipImpl* impl);
|
||||
~Unzip();
|
||||
|
||||
using Path = ghc::filesystem::path;
|
||||
|
||||
/**
|
||||
* Create unzipper for file
|
||||
*/
|
||||
static Result<Unzip> create(Path const& file);
|
||||
|
||||
/**
|
||||
* Path to the opened zip
|
||||
*/
|
||||
Path getPath() const;
|
||||
|
||||
/**
|
||||
* Get all entries in zip
|
||||
*/
|
||||
std::vector<Path> getEntries() const;
|
||||
/**
|
||||
* Check if zip has entry
|
||||
* @param name Entry path in zip
|
||||
*/
|
||||
bool hasEntry(Path const& name);
|
||||
|
||||
/**
|
||||
* Extract entry to memory
|
||||
* @param name Entry path in zip
|
||||
*/
|
||||
Result<byte_array> extract(Path const& name);
|
||||
/**
|
||||
* Extract entry to file
|
||||
* @param name Entry path in zip
|
||||
* @param path Target file path
|
||||
*/
|
||||
Result<> extractTo(Path const& name, Path const& path);
|
||||
/**
|
||||
* Extract all entries to directory
|
||||
* @param dir Directory to unzip the contents to
|
||||
*/
|
||||
Result<> extractAllTo(Path const& dir);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unzip file to directory
|
||||
* @param from File to unzip
|
||||
|
|
|
@ -8,9 +8,19 @@
|
|||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fs/filesystem.hpp>
|
||||
|
||||
#undef snprintf
|
||||
|
||||
// for some reason std::filesystem::path doesn't have std::hash defined in C++17
|
||||
// and ghc seems to have inherited this limitation
|
||||
template<>
|
||||
struct std::hash<ghc::filesystem::path> {
|
||||
std::size_t operator()(ghc::filesystem::path const& path) const noexcept {
|
||||
return ghc::filesystem::hash_value(path);
|
||||
}
|
||||
};
|
||||
|
||||
namespace geode {
|
||||
using byte_array = std::vector<uint8_t>;
|
||||
|
||||
|
|
96
loader/src/cocos2d-ext/CCFileUtils.cpp
Normal file
96
loader/src/cocos2d-ext/CCFileUtils.cpp
Normal file
|
@ -0,0 +1,96 @@
|
|||
|
||||
#include <cocos2d.h>
|
||||
#include <Geode/modify/CCFileUtils.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
static std::vector<CCTexturePack> PACKS;
|
||||
static std::vector<std::string> PATHS;
|
||||
static bool DONT_ADD_PATHS = false;
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4273)
|
||||
|
||||
void CCFileUtils::addTexturePack(CCTexturePack const& pack) {
|
||||
// remove pack if it has already been added
|
||||
ranges::remove(PACKS, [pack](CCTexturePack const& other) {
|
||||
return pack.m_id == other.m_id;
|
||||
});
|
||||
// add pack to start
|
||||
PACKS.insert(PACKS.begin(), pack);
|
||||
this->updatePaths();
|
||||
}
|
||||
|
||||
void CCFileUtils::removeTexturePack(std::string const& id) {
|
||||
ranges::remove(PACKS, [id](CCTexturePack const& pack) {
|
||||
return pack.m_id == id;
|
||||
});
|
||||
this->updatePaths();
|
||||
}
|
||||
|
||||
void CCFileUtils::addPriorityPath(const char* path) {
|
||||
PATHS.insert(PATHS.begin(), path);
|
||||
this->updatePaths();
|
||||
}
|
||||
|
||||
void CCFileUtils::updatePaths() {
|
||||
// add search paths that aren't in PATHS or PACKS to PATHS
|
||||
for (auto& path : m_searchPathArray) {
|
||||
bool isKnown = false;
|
||||
for (auto& pack : PACKS) {
|
||||
for (auto& packPath : pack.m_paths) {
|
||||
if (ghc::filesystem::path(path.c_str()) == packPath) {
|
||||
isKnown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isKnown) break;
|
||||
}
|
||||
if (isKnown) break;
|
||||
for (auto& p : PATHS) {
|
||||
if (ghc::filesystem::path(path.c_str()) == p) {
|
||||
isKnown = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isKnown) {
|
||||
PATHS.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
// clear old paths
|
||||
m_searchPathArray.clear();
|
||||
|
||||
// make sure addSearchPath doesn't add to PACKS or PATHS
|
||||
DONT_ADD_PATHS = true;
|
||||
// add texture packs first
|
||||
for (auto& pack : PACKS) {
|
||||
for (auto& path : pack.m_paths) {
|
||||
this->addSearchPath(path.c_str());
|
||||
}
|
||||
}
|
||||
// add other paths after
|
||||
for (auto& path : PATHS) {
|
||||
this->addSearchPath(path.c_str());
|
||||
}
|
||||
DONT_ADD_PATHS = false;
|
||||
|
||||
log::debug("Search Paths: {}", m_searchPathArray.size());
|
||||
for (auto& path : m_searchPathArray) {
|
||||
log::debug("Path: {}", path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
||||
|
||||
class $modify(CCFileUtils) {
|
||||
static CCFileUtils* sharedFileUtils() {
|
||||
auto doAddPaths = !s_sharedFileUtils;
|
||||
auto ret = CCFileUtils::sharedFileUtils();
|
||||
if (doAddPaths) {
|
||||
s_sharedFileUtils->updatePaths();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
|
@ -17,4 +17,4 @@ struct SaveLoader : Modify<SaveLoader, AppDelegate> {
|
|||
|
||||
return AppDelegate::trySaveGame();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,4 +9,4 @@ struct FunctionQueue : Modify<FunctionQueue, CCScheduler> {
|
|||
InternalLoader::get()->executeGDThreadQueue();
|
||||
return CCScheduler::update(dt);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ struct ResourcesUpdate : Modify<ResourcesUpdate, LoadingLayer> {
|
|||
void loadAssets() {
|
||||
LoadingLayer::loadAssets();
|
||||
// this is in case the user refreshes texture quality at runtime
|
||||
if (this->m_loadStep == 10) {
|
||||
if (m_loadStep == 10) {
|
||||
Loader::get()->updateResources();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -580,7 +580,7 @@ void InstallItems::finish(bool replaceFiles) {
|
|||
}
|
||||
|
||||
// load mods
|
||||
Loader::get()->refreshMods();
|
||||
(void)Loader::get()->refreshModsList();
|
||||
|
||||
// finished
|
||||
this->post(UpdateStatus::Finished, "", 100);
|
||||
|
|
|
@ -45,10 +45,7 @@ InternalMod::InternalMod() : Mod(getInternalModInfo()) {
|
|||
|
||||
ghc::filesystem::create_directories(m_saveDirPath);
|
||||
|
||||
// make sure spritesheets get added
|
||||
m_addResourcesToSearchPath = true;
|
||||
|
||||
auto sett = this->loadSettings();
|
||||
auto sett = this->loadData();
|
||||
if (!sett) {
|
||||
log::log(Severity::Error, this, "{}", sett.unwrapErr());
|
||||
}
|
||||
|
|
|
@ -1,546 +0,0 @@
|
|||
#include <Geode/loader/Hook.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 <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <about.hpp>
|
||||
#include <crashlog.hpp>
|
||||
#include <mutex>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
std::atomic_bool Loader::s_unloading = false;
|
||||
std::mutex g_unloadMutex;
|
||||
|
||||
VersionInfo Loader::getVersion() {
|
||||
return LOADER_VERSION;
|
||||
}
|
||||
|
||||
std::string Loader::getVersionType() {
|
||||
return LOADER_VERSION_TYPE;
|
||||
}
|
||||
|
||||
Loader* Loader::get() {
|
||||
return InternalLoader::get();
|
||||
}
|
||||
|
||||
void Loader::createDirectories() {
|
||||
auto modDir = this->getGeodeDirectory() / GEODE_MOD_DIRECTORY;
|
||||
auto logDir = this->getGeodeDirectory() / GEODE_LOG_DIRECTORY;
|
||||
auto resDir = this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY;
|
||||
auto tempDir = this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY;
|
||||
auto confDir = this->getGeodeDirectory() / GEODE_CONFIG_DIRECTORY;
|
||||
|
||||
#ifdef GEODE_IS_MACOS
|
||||
ghc::filesystem::create_directory(this->getSaveDirectory());
|
||||
#endif
|
||||
|
||||
ghc::filesystem::create_directories(resDir);
|
||||
ghc::filesystem::create_directory(confDir);
|
||||
ghc::filesystem::create_directory(modDir);
|
||||
ghc::filesystem::create_directory(logDir);
|
||||
ghc::filesystem::create_directory(tempDir);
|
||||
|
||||
if (!ranges::contains(m_modDirectories, modDir)) {
|
||||
m_modDirectories.push_back(modDir);
|
||||
}
|
||||
|
||||
// files too
|
||||
m_logStream = std::ofstream(logDir / log::generateLogName());
|
||||
}
|
||||
|
||||
void Loader::updateResourcePaths() {
|
||||
log::debug("Setting resource paths");
|
||||
|
||||
// reset search paths
|
||||
CCFileUtils::get()->removeAllPaths();
|
||||
|
||||
// add custom texture paths first (priority)
|
||||
for (auto const& path : m_texturePaths) {
|
||||
CCFileUtils::get()->addSearchPath(path.string().c_str());
|
||||
}
|
||||
|
||||
// add own paths next
|
||||
CCFileUtils::get()->addSearchPath(
|
||||
(this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY).string().c_str()
|
||||
);
|
||||
CCFileUtils::get()->addSearchPath(
|
||||
(this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY).string().c_str()
|
||||
);
|
||||
|
||||
// add mods' search paths
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
auto searchPath =
|
||||
this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY / mod->getID() / "resources";
|
||||
|
||||
// add search path
|
||||
CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
|
||||
}
|
||||
|
||||
// add GD's search path
|
||||
CCFileUtils::get()->addSearchPath("Resources");
|
||||
}
|
||||
|
||||
void Loader::updateModResources(Mod* mod) {
|
||||
if (!mod->m_addResourcesToSearchPath) {
|
||||
log::debug("Mod {} doesn't have resources, skipping", mod->getID());
|
||||
return;
|
||||
}
|
||||
|
||||
auto searchPath = this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY / mod->getID() / "resources";
|
||||
|
||||
log::debug("Adding resources for {}", mod->getID());
|
||||
|
||||
// add spritesheets
|
||||
for (auto const& sheet : mod->m_info.m_spritesheets) {
|
||||
auto png = sheet + ".png";
|
||||
auto plist = sheet + ".plist";
|
||||
auto ccfu = CCFileUtils::sharedFileUtils();
|
||||
|
||||
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.m_id, sheet
|
||||
);
|
||||
}
|
||||
else {
|
||||
CCTextureCache::sharedTextureCache()->addImage(png.c_str(), false);
|
||||
CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(plist.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::updateResources() {
|
||||
log::debug("Adding resources");
|
||||
|
||||
this->updateResourcePaths();
|
||||
|
||||
// add own spritesheets
|
||||
this->updateModResources(InternalMod::get());
|
||||
|
||||
// add mods' spritesheets
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
this->updateModResources(mod);
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::addTexturePath(ghc::filesystem::path const& path) {
|
||||
// remove path if it already exists
|
||||
this->removeTexturePath(path);
|
||||
m_texturePaths.push_back(path);
|
||||
}
|
||||
|
||||
void Loader::removeTexturePath(ghc::filesystem::path const& path) {
|
||||
ranges::remove(m_texturePaths, path);
|
||||
}
|
||||
|
||||
std::vector<ghc::filesystem::path> Loader::getTexturePaths() const {
|
||||
return m_texturePaths;
|
||||
}
|
||||
|
||||
size_t Loader::loadModsFromDirectory(
|
||||
ghc::filesystem::path const& dir, bool recursive
|
||||
) {
|
||||
log::debug("Searching {}", dir);
|
||||
|
||||
size_t loadedCount = 0;
|
||||
for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
|
||||
// recursively search directories
|
||||
if (ghc::filesystem::is_directory(entry) && recursive) {
|
||||
loadedCount += 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 (utils::map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool {
|
||||
return p->m_info.m_path == entry.path();
|
||||
}))
|
||||
continue;
|
||||
|
||||
// load mod
|
||||
|
||||
log::debug("Loading {}", entry.path().string());
|
||||
|
||||
auto res = this->loadModFromFile(entry.path().string());
|
||||
if (res) {
|
||||
// succesfully loaded
|
||||
loadedCount++;
|
||||
|
||||
// check for dependencies
|
||||
if (!res.unwrap()->hasUnresolvedDependencies()) {
|
||||
log::debug("Successfully loaded {}", res.unwrap());
|
||||
}
|
||||
else {
|
||||
log::error("{} has unresolved dependencies", res.unwrap());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// something went wrong
|
||||
log::error("{}", res.unwrapErr());
|
||||
m_erroredMods.push_back({ entry.path().string(), res.unwrapErr() });
|
||||
}
|
||||
}
|
||||
|
||||
return loadedCount;
|
||||
}
|
||||
|
||||
size_t Loader::refreshMods() {
|
||||
log::debug("Loading mods...");
|
||||
|
||||
// load early load mods
|
||||
m_modLoadMutex.lock();
|
||||
for (auto const& path : m_loadedSettings.m_earlyLoadMods) {
|
||||
if (!ranges::contains(m_mods, [path](auto mod) {
|
||||
return mod.second->m_info.m_path == path;
|
||||
})) {
|
||||
auto res = this->loadModFromFile(path.string());
|
||||
if (!res) {
|
||||
// something went wrong
|
||||
log::error("{}", res.unwrapErr());
|
||||
m_erroredMods.push_back({ path.string(), res.unwrapErr() });
|
||||
}
|
||||
}
|
||||
}
|
||||
m_modLoadMutex.unlock();
|
||||
|
||||
// clear errored mods since that list will be
|
||||
// reconstructed from scratch
|
||||
m_erroredMods.clear();
|
||||
|
||||
// make sure mod directory exists
|
||||
this->createDirectories();
|
||||
|
||||
size_t loadedCount = 0;
|
||||
|
||||
// find all mods in search directories
|
||||
for (auto const& dir : m_modDirectories) {
|
||||
// find all mods in this search directory
|
||||
loadedCount += loadModsFromDirectory(dir, true);
|
||||
}
|
||||
|
||||
log::debug("Loaded {} mods", loadedCount);
|
||||
return loadedCount;
|
||||
}
|
||||
|
||||
Result<> Loader::saveData() {
|
||||
auto res = this->saveSettings();
|
||||
if (!res) return res;
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
auto r = mod->saveData();
|
||||
if (!r) {
|
||||
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Loader::loadData() {
|
||||
auto res = this->loadSettings();
|
||||
if (!res) return res;
|
||||
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();
|
||||
}
|
||||
|
||||
Result<> Loader::saveSettings() {
|
||||
auto json = nlohmann::json::object();
|
||||
|
||||
// save mod enabled / disabled states
|
||||
json["mods"] = nlohmann::json::object();
|
||||
for (auto [id, mod] : m_mods) {
|
||||
if (mod->isUninstalled()) continue;
|
||||
auto value = nlohmann::json::object();
|
||||
value["enabled"] = mod->m_enabled;
|
||||
json["mods"][id] = value;
|
||||
}
|
||||
json["early-load"] = m_loadedSettings.m_earlyLoadMods;
|
||||
|
||||
// save loader settings
|
||||
GEODE_UNWRAP(InternalMod::get()->saveSettings());
|
||||
|
||||
// save info alerts
|
||||
InternalLoader::get()->saveInfoAlerts(json);
|
||||
|
||||
// mark the game as not having crashed
|
||||
json["succesfully-closed"] = true;
|
||||
|
||||
return utils::file::writeString(this->getGeodeSaveDirectory() / "mods.json", json.dump(4));
|
||||
}
|
||||
|
||||
Result<> Loader::loadSettings() {
|
||||
auto path = this->getGeodeSaveDirectory() / "mods.json";
|
||||
if (!ghc::filesystem::exists(path)) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// read mods.json
|
||||
GEODE_UNWRAP_INTO(auto read, utils::file::readString(path));
|
||||
try {
|
||||
auto json = nlohmann::json::parse(read);
|
||||
auto checker = JsonChecker(json);
|
||||
auto root = checker.root("[loader settings]").obj();
|
||||
root.has("early-load").into(m_loadedSettings.m_earlyLoadMods);
|
||||
for (auto [key, val] : root.has("mods").items()) {
|
||||
auto obj = val.obj();
|
||||
LoaderSettings::ModSettings mod;
|
||||
obj.has("enabled").into(mod.m_enabled);
|
||||
m_loadedSettings.m_mods.insert({ key, mod });
|
||||
}
|
||||
if (checker.isError()) {
|
||||
log::error("Error loading global mod settings: {}", checker.getError());
|
||||
}
|
||||
InternalLoader::get()->loadInfoAlerts(json);
|
||||
return Ok();
|
||||
}
|
||||
catch (std::exception const& e) {
|
||||
return Err(e.what());
|
||||
}
|
||||
}
|
||||
|
||||
bool Loader::didLastLaunchCrash() const {
|
||||
return crashlog::didLastLaunchCrash();
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getCrashLogDirectory() const {
|
||||
return crashlog::getCrashLogDirectory();
|
||||
}
|
||||
|
||||
bool Loader::shouldLoadMod(std::string const& id) const {
|
||||
if (m_loadedSettings.m_mods.count(id)) {
|
||||
return m_loadedSettings.m_mods.at(id).m_enabled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<Mod*> Loader::getAllMods() const {
|
||||
return utils::map::getValues(m_mods);
|
||||
}
|
||||
|
||||
std::vector<Loader::FailedModInfo> Loader::getFailedMods() const {
|
||||
return m_erroredMods;
|
||||
}
|
||||
|
||||
void Loader::updateAllDependencies() {
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
mod->updateDependencyStates();
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::unloadMod(Mod* mod) {
|
||||
m_mods.erase(mod->m_info.m_id);
|
||||
// ~Mod will call FreeLibrary
|
||||
// automatically
|
||||
delete mod;
|
||||
}
|
||||
|
||||
bool Loader::setup() {
|
||||
if (m_isSetup) return true;
|
||||
|
||||
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->refreshMods();
|
||||
|
||||
m_isSetup = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Loader::~Loader() {
|
||||
g_unloadMutex.lock();
|
||||
s_unloading = true;
|
||||
g_unloadMutex.unlock();
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
delete mod;
|
||||
}
|
||||
m_mods.clear();
|
||||
m_logs.clear();
|
||||
|
||||
auto tempDir = this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY;
|
||||
ghc::filesystem::remove_all(tempDir);
|
||||
}
|
||||
|
||||
void Loader::pushLog(log::Log&& log) {
|
||||
std::string logStr = log.toString(true);
|
||||
|
||||
InternalLoader::get()->logConsoleMessage(logStr);
|
||||
m_logStream << logStr << std::endl;
|
||||
|
||||
m_logs.push_back(std::move(log));
|
||||
}
|
||||
|
||||
void Loader::popLog(log::Log* log) {
|
||||
ranges::remove(m_logs, *log);
|
||||
}
|
||||
|
||||
std::vector<log::Log*> Loader::getLogs(std::initializer_list<Severity> severityFilter) {
|
||||
std::vector<log::Log*> logs;
|
||||
|
||||
for (auto& log : m_logs) {
|
||||
if (ranges::contains(severityFilter, log.getSeverity()) || !severityFilter.size()) {
|
||||
logs.push_back(&log);
|
||||
}
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
|
||||
void Loader::clearLogs() {
|
||||
m_logs.clear();
|
||||
}
|
||||
|
||||
void Loader::queueInGDThread(ScheduledFunction func) {
|
||||
InternalLoader::get()->queueInGDThread(func);
|
||||
}
|
||||
|
||||
Mod* Loader::getInternalMod() {
|
||||
return InternalMod::get();
|
||||
}
|
||||
|
||||
bool Loader::isUnloading() {
|
||||
return Loader::s_unloading;
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getGameDirectory() const {
|
||||
return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath2().c_str());
|
||||
}
|
||||
|
||||
#ifdef GEODE_IS_WINDOWS
|
||||
#include <filesystem>
|
||||
#endif
|
||||
|
||||
ghc::filesystem::path Loader::getSaveDirectory() const {
|
||||
// not using ~/Library/Caches
|
||||
#ifdef GEODE_IS_MACOS
|
||||
return ghc::filesystem::path("/Users/Shared/Geode");
|
||||
#elif defined(GEODE_IS_WINDOWS)
|
||||
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 Loader::getGeodeDirectory() const {
|
||||
return geode::utils::file::geodeRoot() / GEODE_DIRECTORY;
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getGeodeSaveDirectory() const {
|
||||
return this->getSaveDirectory() / GEODE_DIRECTORY;
|
||||
}
|
||||
|
||||
size_t Loader::getFieldIndexForClass(size_t hash) {
|
||||
static std::unordered_map<size_t, size_t> nextIndex;
|
||||
return nextIndex[hash]++;
|
||||
}
|
||||
|
||||
VersionInfo Loader::minModVersion() {
|
||||
// Remember to update when deleting features!
|
||||
return VersionInfo { 0, 4, 0 };
|
||||
}
|
||||
|
||||
VersionInfo Loader::maxModVersion() {
|
||||
// patches are always backwards-compatible. if not, we have failed
|
||||
return VersionInfo {
|
||||
Loader::getVersion().getMajor(),
|
||||
Loader::getVersion().getMinor(),
|
||||
99999999,
|
||||
};
|
||||
}
|
||||
|
||||
bool Loader::supportedModVersion(VersionInfo const& version) {
|
||||
return version >= Loader::minModVersion() && version <= Loader::maxModVersion();
|
||||
}
|
||||
|
||||
void Loader::openPlatformConsole() {
|
||||
InternalLoader::get()->openPlatformConsole();
|
||||
}
|
||||
|
||||
void Loader::closePlatfromConsole() {
|
||||
InternalLoader::get()->closePlatformConsole();
|
||||
}
|
||||
|
||||
void Loader::scheduleOnModLoad(Mod* m, ScheduledFunction func) {
|
||||
if (m) return func();
|
||||
m_scheduledFunctions.push_back(func);
|
||||
}
|
||||
|
||||
void Loader::releaseScheduledFunctions(Mod* mod) {
|
||||
for (auto& func : m_scheduledFunctions) {
|
||||
func();
|
||||
}
|
||||
m_scheduledFunctions.clear();
|
||||
}
|
||||
|
||||
void Loader::waitForModsToBeLoaded() {
|
||||
std::lock_guard _(m_modLoadMutex);
|
||||
}
|
||||
|
||||
void Loader::setEarlyLoadMod(Mod* mod, bool enabled) {
|
||||
if (enabled) {
|
||||
m_loadedSettings.m_earlyLoadMods.insert(mod->getPackagePath());
|
||||
} else {
|
||||
m_loadedSettings.m_earlyLoadMods.erase(mod->getPackagePath());
|
||||
}
|
||||
}
|
||||
|
||||
bool Loader::shouldEarlyLoadMod(Mod* mod) const {
|
||||
return m_loadedSettings.m_earlyLoadMods.count(mod->getPackagePath());
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#undef snprintf
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
using namespace std::string_literals;
|
||||
|
||||
bool Mod::validateID(std::string const& id) {
|
||||
// ids may not be empty
|
||||
if (!id.size()) return false;
|
||||
for (auto const& c : id) {
|
||||
if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') ||
|
||||
(c == '-') || (c == '_') || (c == '.')))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<Mod*> Loader::loadModFromFile(std::string const& path) {
|
||||
// load mod.json
|
||||
GEODE_UNWRAP_INTO(auto json, ModInfo::createFromGeodeFile(path));
|
||||
|
||||
// check that a duplicate has not been loaded
|
||||
if (m_mods.count(json.m_id)) {
|
||||
return Err("Mod with ID \"" + json.m_id + "\" has already been loaded!");
|
||||
}
|
||||
|
||||
// create and set up Mod instance
|
||||
auto mod = new Mod(json);
|
||||
mod->m_saveDirPath =
|
||||
Loader::get()->getGeodeSaveDirectory() / GEODE_MOD_DIRECTORY / json.m_id;
|
||||
ghc::filesystem::create_directories(mod->m_saveDirPath);
|
||||
|
||||
auto sett = mod->loadSettings();
|
||||
if (!sett) {
|
||||
log::log(Severity::Error, mod, "{}", sett.unwrapErr());
|
||||
}
|
||||
|
||||
// enable mod if needed
|
||||
mod->m_enabled = Loader::get()->shouldLoadMod(mod->m_info.m_id);
|
||||
m_mods.insert({ json.m_id, mod });
|
||||
mod->updateDependencyStates();
|
||||
|
||||
// add mod resources
|
||||
this->queueInGDThread([this, mod]() {
|
||||
this->updateResourcePaths();
|
||||
this->updateModResources(mod);
|
||||
});
|
||||
|
||||
return Ok(mod);
|
||||
}
|
419
loader/src/loader/Loader.cpp
Normal file
419
loader/src/loader/Loader.cpp
Normal file
|
@ -0,0 +1,419 @@
|
|||
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <InternalLoader.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <about.hpp>
|
||||
#include <Geode/utils/ranges.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <crashlog.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
Loader* Loader::get() {
|
||||
return InternalLoader::get();
|
||||
}
|
||||
|
||||
Loader::~Loader() {
|
||||
for (auto& [_, mod] : m_mods) {
|
||||
delete mod;
|
||||
}
|
||||
m_mods.clear();
|
||||
log::Logs::clear();
|
||||
ghc::filesystem::remove_all(
|
||||
this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY
|
||||
);
|
||||
}
|
||||
|
||||
VersionInfo Loader::getVersion() {
|
||||
return LOADER_VERSION;
|
||||
}
|
||||
|
||||
VersionInfo Loader::minModVersion() {
|
||||
return VersionInfo { 0, 7, 0 };
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
void Loader::createDirectories() {
|
||||
auto modDir = this->getGeodeDirectory() / GEODE_MOD_DIRECTORY;
|
||||
auto logDir = this->getGeodeDirectory() / GEODE_LOG_DIRECTORY;
|
||||
auto resDir = this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY;
|
||||
auto tempDir = this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY;
|
||||
auto confDir = this->getGeodeDirectory() / GEODE_CONFIG_DIRECTORY;
|
||||
|
||||
#ifdef GEODE_IS_MACOS
|
||||
ghc::filesystem::create_directory(this->getSaveDirectory());
|
||||
#endif
|
||||
|
||||
ghc::filesystem::create_directories(resDir);
|
||||
ghc::filesystem::create_directory(confDir);
|
||||
ghc::filesystem::create_directory(modDir);
|
||||
ghc::filesystem::create_directory(logDir);
|
||||
ghc::filesystem::create_directory(tempDir);
|
||||
|
||||
if (!ranges::contains(m_modSearchDirectories, modDir)) {
|
||||
m_modSearchDirectories.push_back(modDir);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
GEODE_UNWRAP(this->refreshModsList());
|
||||
|
||||
this->queueInGDThread([]() {
|
||||
Loader::get()->addSearchPaths();
|
||||
});
|
||||
|
||||
m_isSetup = true;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) {
|
||||
if (m_mods.count(info.m_id)) {
|
||||
return Err(fmt::format("Mod with ID '{}' already loaded", info.m_id));
|
||||
}
|
||||
|
||||
// create Mod instance
|
||||
auto mod = new Mod(info);
|
||||
m_mods.insert({ info.m_id, mod });
|
||||
mod->m_enabled = InternalMod::get()->getSavedValue<bool>(
|
||||
"should-load-" + info.m_id, true
|
||||
);
|
||||
// this loads the mod if its dependencies are resolved
|
||||
mod->updateDependencyStates();
|
||||
|
||||
// add mod resources
|
||||
this->queueInGDThread([this, mod]() {
|
||||
auto searchPath = this->getGeodeDirectory() /
|
||||
GEODE_TEMP_DIRECTORY / mod->getID() / "resources";
|
||||
|
||||
CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
|
||||
this->updateModResources(mod);
|
||||
});
|
||||
|
||||
return Ok(mod);
|
||||
}
|
||||
|
||||
Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) {
|
||||
auto res = ModInfo::createFromGeodeFile(file);
|
||||
if (!res) {
|
||||
m_invalidMods.push_back(InvalidGeodeFile {
|
||||
.m_path = file,
|
||||
.m_reason = res.unwrapErr(),
|
||||
});
|
||||
return Err(res.unwrapErr());
|
||||
}
|
||||
return this->loadModFromInfo(res.unwrap());
|
||||
}
|
||||
|
||||
Result<> 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) {
|
||||
GEODE_UNWRAP(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.m_path == entry.path();
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if mods should be loaded immediately, do that
|
||||
if (m_earlyLoadFinished) {
|
||||
GEODE_UNWRAP(this->loadModFromFile(entry));
|
||||
}
|
||||
// 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 {
|
||||
.m_path = entry.path(),
|
||||
.m_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);
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Loader::refreshModsList() {
|
||||
log::debug("Loading mods...");
|
||||
|
||||
// find mods
|
||||
for (auto& dir : m_modSearchDirectories) {
|
||||
GEODE_UNWRAP(this->loadModsFromDirectory(dir));
|
||||
}
|
||||
|
||||
// load early-load mods first
|
||||
for (auto& mod : m_modsToLoad) {
|
||||
if (mod.m_needsEarlyLoad) {
|
||||
GEODE_UNWRAP(this->loadModFromInfo(mod));
|
||||
}
|
||||
}
|
||||
|
||||
// UI can be loaded now
|
||||
m_earlyLoadFinished = true;
|
||||
|
||||
// load the rest of the mods
|
||||
for (auto& mod : m_modsToLoad) {
|
||||
if (!mod.m_needsEarlyLoad) {
|
||||
GEODE_UNWRAP(this->loadModFromInfo(mod));
|
||||
}
|
||||
}
|
||||
m_modsToLoad.clear();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::vector<Mod*> Loader::getAllMods() {
|
||||
return map::getValues(m_mods);
|
||||
}
|
||||
|
||||
Mod* Loader::getInternalMod() {
|
||||
return InternalMod::get();
|
||||
}
|
||||
|
||||
std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
|
||||
return m_invalidMods;
|
||||
}
|
||||
|
||||
void Loader::updateAllDependencies() {
|
||||
for (auto const& [_, mod] : m_mods) {
|
||||
mod->updateDependencyStates();
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::waitForModsToBeLoaded() {
|
||||
while (!m_earlyLoadFinished) {}
|
||||
}
|
||||
|
||||
void Loader::dispatchScheduledFunctions(Mod* mod) {
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
for (auto& func : m_scheduledFunctions) {
|
||||
func();
|
||||
}
|
||||
m_scheduledFunctions.clear();
|
||||
}
|
||||
|
||||
void Loader::queueInGDThread(ScheduledFunction func) {
|
||||
InternalLoader::get()->queueInGDThread(func);
|
||||
}
|
||||
|
||||
void Loader::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
|
||||
std::lock_guard _(m_scheduledFunctionsMutex);
|
||||
if (mod) {
|
||||
return func();
|
||||
}
|
||||
m_scheduledFunctions.push_back(func);
|
||||
}
|
||||
|
||||
bool Loader::didLastLaunchCrash() const {
|
||||
return crashlog::didLastLaunchCrash();
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getCrashLogDirectory() const {
|
||||
return crashlog::getCrashLogDirectory();
|
||||
}
|
||||
|
||||
void Loader::openPlatformConsole() {
|
||||
InternalLoader::get()->openPlatformConsole();
|
||||
}
|
||||
|
||||
void Loader::closePlatfromConsole() {
|
||||
InternalLoader::get()->closePlatformConsole();
|
||||
}
|
||||
|
||||
void Loader::updateModResources(Mod* mod) {
|
||||
if (!mod->m_info.m_spritesheets.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto searchPath = this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY / mod->getID() / "resources";
|
||||
|
||||
log::debug("Adding resources for {}", mod->getID());
|
||||
|
||||
// add spritesheets
|
||||
for (auto const& sheet : mod->m_info.m_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.m_id, sheet
|
||||
);
|
||||
}
|
||||
else {
|
||||
CCTextureCache::get()->addImage(png.c_str(), false);
|
||||
CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Loader::addSearchPaths() {
|
||||
CCFileUtils::get()->addPriorityPath(
|
||||
(this->getGeodeDirectory() / GEODE_RESOURCE_DIRECTORY).string().c_str()
|
||||
);
|
||||
CCFileUtils::get()->addPriorityPath(
|
||||
(this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY).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);
|
||||
}
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getGameDirectory() const {
|
||||
return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath2().c_str());
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getSaveDirectory() const {
|
||||
#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
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getGeodeDirectory() const {
|
||||
return geode::utils::file::geodeRoot() / GEODE_DIRECTORY;
|
||||
}
|
||||
|
||||
ghc::filesystem::path Loader::getGeodeSaveDirectory() const {
|
||||
return this->getSaveDirectory() / GEODE_DIRECTORY;
|
||||
}
|
|
@ -89,7 +89,10 @@ 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;
|
||||
|
@ -111,8 +114,60 @@ std::string Log::toString(bool logTime) const {
|
|||
return res;
|
||||
}
|
||||
|
||||
void Log::pushToLoader() {
|
||||
Loader::get()->pushLog(std::move(*this));
|
||||
void Logs::setup() {
|
||||
s_logStream = std::ofstream(
|
||||
Loader::get()->getGeodeDirectory() /
|
||||
GEODE_LOG_DIRECTORY /
|
||||
log::generateLogName()
|
||||
);
|
||||
}
|
||||
|
||||
void Logs::push(Log&& log) {
|
||||
std::string logStr = log.toString(true);
|
||||
|
||||
InternalLoader::get()->logConsoleMessage(logStr);
|
||||
s_logStream << logStr << std::endl;
|
||||
|
||||
s_logs.emplace_back(std::forward<Log>(log));
|
||||
}
|
||||
|
||||
void Logs::pop(Log* log) {
|
||||
ranges::remove(s_logs, *log);
|
||||
}
|
||||
|
||||
std::vector<Log*> Logs::list() {
|
||||
std::vector<Log*> logs;
|
||||
logs.reserve(s_logs.size());
|
||||
for (auto& log : s_logs) {
|
||||
logs.push_back(&log);
|
||||
}
|
||||
return logs;
|
||||
}
|
||||
|
||||
void Logs::clear() {
|
||||
s_logs.clear();
|
||||
}
|
||||
|
||||
std::vector<ComponentTrait*>& Log::getComponents() {
|
||||
return m_components;
|
||||
}
|
||||
|
||||
log_clock::time_point Log::getTime() const {
|
||||
return m_time;
|
||||
}
|
||||
|
||||
Mod* Log::getSender() const {
|
||||
return m_sender;
|
||||
}
|
||||
|
||||
Severity Log::getSeverity() const {
|
||||
return m_severity;
|
||||
}
|
||||
|
||||
Log::~Log() {
|
||||
for (auto comp : m_components) {
|
||||
delete comp;
|
||||
}
|
||||
}
|
||||
|
||||
std::string geode::log::generateLogName() {
|
||||
|
@ -176,5 +231,5 @@ void geode::log::vlogImpl(
|
|||
|
||||
// (l.getComponents().push_back(new ComponentBase(args)), ...);
|
||||
|
||||
log.pushToLoader();
|
||||
Logs::push(std::move(log));
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
#include <Geode/cocos/support/zip_support/ZipUtils.h>
|
||||
#include <Geode/loader/Hook.hpp>
|
||||
#include <Geode/loader/Loader.hpp>
|
||||
|
@ -11,20 +12,25 @@
|
|||
#include <Geode/utils/string.hpp>
|
||||
#include <InternalMod.hpp>
|
||||
#include <about.hpp>
|
||||
#include <zlib.h>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
Mod::Mod(ModInfo const& info) {
|
||||
m_info = info;
|
||||
m_saveDirPath = Loader::get()->getGeodeSaveDirectory() /
|
||||
GEODE_MOD_DIRECTORY / info.m_id;
|
||||
ghc::filesystem::create_directories(m_saveDirPath);
|
||||
}
|
||||
|
||||
Mod::~Mod() {
|
||||
(void)this->unload();
|
||||
(void)this->unloadBinary();
|
||||
}
|
||||
|
||||
Result<> Mod::loadSettings() {
|
||||
// settings
|
||||
Result<> Mod::loadData() {
|
||||
ModStateEvent(this, ModEventType::DataLoaded).post();
|
||||
|
||||
// settings
|
||||
// Check if settings exist
|
||||
auto settPath = m_saveDirPath / "settings.json";
|
||||
if (ghc::filesystem::exists(settPath)) {
|
||||
|
@ -72,7 +78,9 @@ Result<> Mod::loadSettings() {
|
|||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::saveSettings() {
|
||||
Result<> Mod::saveData() {
|
||||
ModStateEvent(this, ModEventType::DataSaved).post();
|
||||
|
||||
// settings
|
||||
auto settPath = m_saveDirPath / "settings.json";
|
||||
auto json = nlohmann::json::object();
|
||||
|
@ -98,133 +106,73 @@ Result<> Mod::saveSettings() {
|
|||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::loadData() {
|
||||
if (m_loadDataFunc) {
|
||||
if (!m_loadDataFunc(m_saveDirPath.string().c_str())) {
|
||||
log::log(Severity::Error, this, "Mod load data function returned false");
|
||||
}
|
||||
}
|
||||
ModStateEvent(this, ModEventType::DataLoaded).post();
|
||||
|
||||
return this->loadSettings();
|
||||
}
|
||||
|
||||
Result<> Mod::saveData() {
|
||||
if (m_saveDataFunc) {
|
||||
if (!m_saveDataFunc(m_saveDirPath.string().c_str())) {
|
||||
log::log(Severity::Error, this, "Mod save data function returned false");
|
||||
}
|
||||
}
|
||||
ModStateEvent(this, ModEventType::DataSaved).post();
|
||||
|
||||
return this->saveSettings();
|
||||
}
|
||||
|
||||
Result<> Mod::createTempDir() {
|
||||
ZipFile unzip(m_info.m_path.string());
|
||||
|
||||
if (!unzip.isLoaded()) {
|
||||
return Err("Unable to unzip " + m_info.m_path.string());
|
||||
}
|
||||
|
||||
if (!unzip.fileExists(m_info.m_binaryName)) {
|
||||
return Err(
|
||||
"Unable to find platform binary under the name \"" + m_info.m_binaryName + "\""
|
||||
);
|
||||
// Check if temp dir already exists
|
||||
if (m_tempDirName.string().size()) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// Create geode/temp
|
||||
auto tempDir = Loader::get()->getGeodeDirectory() / GEODE_TEMP_DIRECTORY;
|
||||
if (!ghc::filesystem::exists(tempDir)) {
|
||||
if (!ghc::filesystem::create_directory(tempDir)) {
|
||||
return Err("Unable to create temp directory for mods!");
|
||||
}
|
||||
if (!file::createDirectoryAll(tempDir)) {
|
||||
return Err("Unable to create Geode temp directory");
|
||||
}
|
||||
|
||||
auto tempPath = ghc::filesystem::path(tempDir) / m_info.m_id;
|
||||
if (!ghc::filesystem::exists(tempPath) && !ghc::filesystem::create_directories(tempPath)) {
|
||||
return Err("Unable to create temp directory");
|
||||
// Create geode/temp/mod.id
|
||||
auto tempPath = tempDir / m_info.m_id;
|
||||
if (!file::createDirectoryAll(tempPath)) {
|
||||
return Err("Unable to create mod temp directory");
|
||||
}
|
||||
|
||||
// Unzip .geode file into temp dir
|
||||
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_info.m_path));
|
||||
if (!unzip.hasEntry(m_info.m_binaryName)) {
|
||||
return Err(fmt::format(
|
||||
"Unable to find platform binary under the name \"{}\"",
|
||||
m_info.m_binaryName
|
||||
));
|
||||
}
|
||||
GEODE_UNWRAP(unzip.extractAllTo(tempPath));
|
||||
|
||||
// Mark temp dir creation as succesful
|
||||
m_tempDirName = tempPath;
|
||||
|
||||
for (auto file : unzip.getAllFiles()) {
|
||||
auto path = ghc::filesystem::path(file);
|
||||
if (path.has_parent_path()) {
|
||||
if (!ghc::filesystem::exists(tempPath / path.parent_path()) &&
|
||||
!ghc::filesystem::create_directories(tempPath / path.parent_path())) {
|
||||
return Err(
|
||||
"Unable to create directories \"" + path.parent_path().string() + "\""
|
||||
);
|
||||
}
|
||||
}
|
||||
unsigned long size;
|
||||
auto data = unzip.getFileData(file, &size);
|
||||
if (!data || !size) {
|
||||
return Err("Unable to read \"" + std::string(file) + "\"");
|
||||
}
|
||||
auto wrt = utils::file::writeBinary(tempPath / file, byte_array(data, data + size));
|
||||
if (!wrt) return Err("Unable to write \"" + file + "\": " + wrt.unwrapErr());
|
||||
}
|
||||
|
||||
m_addResourcesToSearchPath = true;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::load() {
|
||||
if (m_loaded) {
|
||||
Result<> Mod::loadBinary() {
|
||||
if (m_binaryLoaded) {
|
||||
return Ok();
|
||||
}
|
||||
#define RETURN_LOAD_ERR(str) \
|
||||
{ \
|
||||
m_loadErrorInfo = str; \
|
||||
return Err(m_loadErrorInfo); \
|
||||
}
|
||||
|
||||
if (!m_tempDirName.string().size()) {
|
||||
auto err = this->createTempDir();
|
||||
if (!err) RETURN_LOAD_ERR("Unable to create temp directory: " + err.unwrapErr());
|
||||
}
|
||||
GEODE_UNWRAP(this->createTempDir().expect("Unable to create temp directory"));
|
||||
|
||||
if (this->hasUnresolvedDependencies()) {
|
||||
RETURN_LOAD_ERR("Mod has unresolved dependencies");
|
||||
}
|
||||
auto err = this->loadPlatformBinary();
|
||||
if (!err) RETURN_LOAD_ERR(err.unwrapErr());
|
||||
if (m_implicitLoadFunc) {
|
||||
auto r = m_implicitLoadFunc(this);
|
||||
if (!r) {
|
||||
(void)this->unloadPlatformBinary();
|
||||
RETURN_LOAD_ERR("Implicit mod entry point returned an error");
|
||||
}
|
||||
}
|
||||
if (m_loadFunc) {
|
||||
auto r = m_loadFunc(this);
|
||||
if (!r) {
|
||||
(void)this->unloadPlatformBinary();
|
||||
RETURN_LOAD_ERR("Mod entry point returned an error");
|
||||
}
|
||||
}
|
||||
m_loaded = true;
|
||||
if (m_loadDataFunc) {
|
||||
if (!m_loadDataFunc(m_saveDirPath.string().c_str())) {
|
||||
log::log(Severity::Error, this, "Mod load data function returned false");
|
||||
}
|
||||
return Err("Mod has unresolved dependencies");
|
||||
}
|
||||
GEODE_UNWRAP(this->loadPlatformBinary());
|
||||
|
||||
// Call implicit entry point to place hooks etc.
|
||||
m_implicitLoadFunc(this);
|
||||
|
||||
m_binaryLoaded = true;
|
||||
ModStateEvent(this, ModEventType::Loaded).post();
|
||||
|
||||
auto loadRes = this->loadData();
|
||||
if (!loadRes) {
|
||||
log::warn("Unable to load data for \"{}\": {}", m_info.m_id, loadRes.unwrapErr());
|
||||
}
|
||||
m_loadErrorInfo = "";
|
||||
Loader::get()->updateAllDependencies();
|
||||
|
||||
GEODE_UNWRAP(this->enable());
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::unload() {
|
||||
if (!m_loaded) {
|
||||
Result<> Mod::unloadBinary() {
|
||||
if (!m_binaryLoaded) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
if (!m_info.m_supportsUnloading) {
|
||||
return Err("Mod does not support unloading");
|
||||
}
|
||||
|
@ -234,22 +182,16 @@ Result<> Mod::unload() {
|
|||
return saveRes;
|
||||
}
|
||||
|
||||
if (m_unloadFunc) {
|
||||
m_unloadFunc();
|
||||
}
|
||||
GEODE_UNWRAP(this->disable());
|
||||
ModStateEvent(this, ModEventType::Unloaded).post();
|
||||
|
||||
// Disabling unhooks and unpatches already
|
||||
for (auto const& hook : m_hooks) {
|
||||
auto d = this->disableHook(hook);
|
||||
if (!d) return d;
|
||||
delete hook;
|
||||
}
|
||||
m_hooks.clear();
|
||||
|
||||
for (auto const& patch : m_patches) {
|
||||
if (!patch->restore()) {
|
||||
return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
|
||||
}
|
||||
delete patch;
|
||||
}
|
||||
m_patches.clear();
|
||||
|
@ -258,24 +200,18 @@ Result<> Mod::unload() {
|
|||
if (!res) {
|
||||
return res;
|
||||
}
|
||||
m_loaded = false;
|
||||
m_binaryLoaded = false;
|
||||
|
||||
Loader::get()->updateAllDependencies();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::enable() {
|
||||
if (!m_loaded) {
|
||||
return Err("Mod is not loaded");
|
||||
if (!m_binaryLoaded) {
|
||||
return this->loadBinary();
|
||||
}
|
||||
|
||||
if (m_enableFunc) {
|
||||
if (!m_enableFunc()) {
|
||||
return Err("Mod enable function returned false");
|
||||
}
|
||||
}
|
||||
|
||||
ModStateEvent(this, ModEventType::Enabled).post();
|
||||
|
||||
for (auto const& hook : m_hooks) {
|
||||
auto d = this->enableHook(hook);
|
||||
if (!d) return d;
|
||||
|
@ -287,22 +223,21 @@ Result<> Mod::enable() {
|
|||
}
|
||||
}
|
||||
|
||||
ModStateEvent(this, ModEventType::Enabled).post();
|
||||
|
||||
m_enabled = true;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Mod::disable() {
|
||||
if (!m_enabled) {
|
||||
return Ok();
|
||||
}
|
||||
if (!m_info.m_supportsDisabling) {
|
||||
return Err("Mod does not support disabling");
|
||||
}
|
||||
|
||||
if (m_disableFunc) {
|
||||
if (!m_disableFunc()) {
|
||||
return Err("Mod disable function returned false");
|
||||
}
|
||||
}
|
||||
|
||||
ModStateEvent(this, ModEventType::Disabled).post();
|
||||
|
||||
for (auto const& hook : m_hooks) {
|
||||
|
@ -323,11 +258,9 @@ Result<> Mod::disable() {
|
|||
|
||||
Result<> Mod::uninstall() {
|
||||
if (m_info.m_supportsDisabling) {
|
||||
auto r = this->disable();
|
||||
if (!r) return r;
|
||||
GEODE_UNWRAP(this->disable());
|
||||
if (m_info.m_supportsUnloading) {
|
||||
auto ur = this->unload();
|
||||
if (!ur) return ur;
|
||||
GEODE_UNWRAP(this->unloadBinary());
|
||||
}
|
||||
}
|
||||
if (!ghc::filesystem::remove(m_info.m_path)) {
|
||||
|
@ -366,18 +299,11 @@ bool Mod::updateDependencyStates() {
|
|||
if (!dep.m_mod->m_resolved) {
|
||||
dep.m_mod->m_resolved = true;
|
||||
dep.m_state = ModResolveState::Resolved;
|
||||
auto r = dep.m_mod->load();
|
||||
auto r = dep.m_mod->loadBinary();
|
||||
if (!r) {
|
||||
dep.m_state = ModResolveState::Unloaded;
|
||||
log::log(Severity::Error, dep.m_mod, "{}", r.unwrapErr());
|
||||
}
|
||||
else {
|
||||
auto r = dep.m_mod->enable();
|
||||
if (!r) {
|
||||
dep.m_state = ModResolveState::Disabled;
|
||||
log::log(Severity::Error, dep.m_mod, "{}", r.unwrapErr());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (dep.m_mod->isEnabled()) {
|
||||
|
@ -394,7 +320,7 @@ bool Mod::updateDependencyStates() {
|
|||
}
|
||||
if (dep.isUnresolved()) {
|
||||
m_resolved = false;
|
||||
(void)this->unload();
|
||||
(void)this->unloadBinary();
|
||||
hasUnresolved = true;
|
||||
}
|
||||
}
|
||||
|
@ -403,16 +329,10 @@ bool Mod::updateDependencyStates() {
|
|||
m_resolved = true;
|
||||
if (m_enabled) {
|
||||
log::debug("Resolved & loading {}", m_info.m_id);
|
||||
auto r = this->load();
|
||||
auto r = this->loadBinary();
|
||||
if (!r) {
|
||||
log::error("{} Error loading: {}", this, r.unwrapErr());
|
||||
}
|
||||
else {
|
||||
auto r = this->enable();
|
||||
if (!r) {
|
||||
log::error("{} Error enabling: {}", this, r.unwrapErr());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
log::debug("Resolved {}, however not loading it as it is disabled", m_info.m_id);
|
||||
|
@ -444,23 +364,23 @@ ghc::filesystem::path Mod::getSaveDir() const {
|
|||
return m_saveDirPath;
|
||||
}
|
||||
|
||||
decltype(ModInfo::m_id) Mod::getID() const {
|
||||
std::string Mod::getID() const {
|
||||
return m_info.m_id;
|
||||
}
|
||||
|
||||
decltype(ModInfo::m_name) Mod::getName() const {
|
||||
std::string Mod::getName() const {
|
||||
return m_info.m_name;
|
||||
}
|
||||
|
||||
decltype(ModInfo::m_developer) Mod::getDeveloper() const {
|
||||
std::string Mod::getDeveloper() const {
|
||||
return m_info.m_developer;
|
||||
}
|
||||
|
||||
decltype(ModInfo::m_description) Mod::getDescription() const {
|
||||
std::optional<std::string> Mod::getDescription() const {
|
||||
return m_info.m_description;
|
||||
}
|
||||
|
||||
decltype(ModInfo::m_details) Mod::getDetails() const {
|
||||
std::optional<std::string> Mod::getDetails() const {
|
||||
return m_info.m_details;
|
||||
}
|
||||
|
||||
|
@ -476,10 +396,6 @@ ghc::filesystem::path Mod::getBinaryPath() const {
|
|||
return m_tempDirName / m_info.m_binaryName;
|
||||
}
|
||||
|
||||
std::string Mod::getPath() const {
|
||||
return m_info.m_path.string();
|
||||
}
|
||||
|
||||
ghc::filesystem::path Mod::getPackagePath() const {
|
||||
return m_info.m_path;
|
||||
}
|
||||
|
@ -501,7 +417,7 @@ bool Mod::isEnabled() const {
|
|||
}
|
||||
|
||||
bool Mod::isLoaded() const {
|
||||
return m_loaded;
|
||||
return m_binaryLoaded;
|
||||
}
|
||||
|
||||
bool Mod::supportsDisabling() const {
|
||||
|
@ -526,8 +442,8 @@ bool Mod::depends(std::string const& id) const {
|
|||
});
|
||||
}
|
||||
|
||||
char const* Mod::expandSpriteName(char const* name) {
|
||||
static std::unordered_map<std::string, char const*> expanded = {};
|
||||
const char* Mod::expandSpriteName(const char* name) {
|
||||
static std::unordered_map<std::string, const char*> expanded = {};
|
||||
if (expanded.count(name)) {
|
||||
return expanded[name];
|
||||
}
|
||||
|
@ -564,10 +480,6 @@ bool Mod::hasSetting(std::string const& key) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
std::string Mod::getLoadErrorInfo() const {
|
||||
return m_loadErrorInfo;
|
||||
}
|
||||
|
||||
ModJson Mod::getRuntimeInfo() const {
|
||||
auto json = m_info.toJSON();
|
||||
|
||||
|
@ -581,7 +493,7 @@ ModJson Mod::getRuntimeInfo() const {
|
|||
obj["patches"].push_back(ModJson(patch->getRuntimeInfo()));
|
||||
}
|
||||
obj["enabled"] = m_enabled;
|
||||
obj["loaded"] = m_loaded;
|
||||
obj["loaded"] = m_binaryLoaded;
|
||||
obj["temp-dir"] = this->getTempDir();
|
||||
obj["save-dir"] = this->getSaveDir();
|
||||
obj["config-dir"] = this->getConfigDir(false);
|
|
@ -11,6 +11,17 @@ static std::string sanitizeDetailsData(std::string const& str) {
|
|||
return utils::string::replace(str, "\r", "");
|
||||
}
|
||||
|
||||
bool ModInfo::validateID(std::string const& id) {
|
||||
// ids may not be empty
|
||||
if (!id.size()) return false;
|
||||
for (auto const& c : id) {
|
||||
if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') ||
|
||||
(c == '-') || (c == '_') || (c == '.')))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
||||
ModInfo info;
|
||||
|
||||
|
@ -25,7 +36,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
|
||||
using nlohmann::detail::value_t;
|
||||
|
||||
root.needs("id").validate(&Mod::validateID).into(info.m_id);
|
||||
root.needs("id").validate(&ModInfo::validateID).into(info.m_id);
|
||||
root.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(info.m_version);
|
||||
root.needs("name").into(info.m_name);
|
||||
root.needs("developer").into(info.m_developer);
|
||||
|
@ -33,12 +44,13 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
root.has("repository").into(info.m_repository);
|
||||
root.has("toggleable").into(info.m_supportsDisabling);
|
||||
root.has("unloadable").into(info.m_supportsUnloading);
|
||||
root.has("early-load").into(info.m_needsEarlyLoad);
|
||||
|
||||
for (auto& dep : root.has("dependencies").iterate()) {
|
||||
auto obj = dep.obj();
|
||||
|
||||
auto depobj = Dependency {};
|
||||
obj.needs("id").validate(&Mod::validateID).into(depobj.m_id);
|
||||
obj.needs("id").validate(&ModInfo::validateID).into(depobj.m_id);
|
||||
obj.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(depobj.m_version);
|
||||
obj.has("required").into(depobj.m_required);
|
||||
obj.checkUnknownKeys();
|
||||
|
@ -143,83 +155,61 @@ Result<ModInfo> ModInfo::create(ModJson const& json) {
|
|||
}
|
||||
|
||||
Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) {
|
||||
GEODE_UNWRAP_INTO(auto read, utils::file::readString(path));
|
||||
try {
|
||||
GEODE_UNWRAP_INTO(auto read, utils::file::readString(path));
|
||||
try {
|
||||
GEODE_UNWRAP_INTO(auto info, ModInfo::create(ModJson::parse(read)));
|
||||
info.m_path = path;
|
||||
if (path.has_parent_path()) {
|
||||
GEODE_UNWRAP(info.addSpecialFiles(path.parent_path()));
|
||||
}
|
||||
return Ok(info);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
return Err("Unable to parse mod.json: " + std::string(e.what()));
|
||||
GEODE_UNWRAP_INTO(auto info, ModInfo::create(ModJson::parse(read)));
|
||||
info.m_path = path;
|
||||
if (path.has_parent_path()) {
|
||||
GEODE_UNWRAP(info.addSpecialFiles(path.parent_path()));
|
||||
}
|
||||
return Ok(info);
|
||||
}
|
||||
catch (std::exception const& e) {
|
||||
return Err(e.what());
|
||||
catch (std::exception& e) {
|
||||
return Err("Unable to parse mod.json: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
Result<ModInfo> ModInfo::createFromGeodeFile(ghc::filesystem::path const& path) {
|
||||
ZipFile unzip(path.string());
|
||||
if (!unzip.isLoaded()) {
|
||||
return Err("\"" + path.string() + "\": Unable to unzip");
|
||||
}
|
||||
GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(path));
|
||||
return ModInfo::createFromGeodeZip(unzip);
|
||||
}
|
||||
|
||||
Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) {
|
||||
// Check if mod.json exists in zip
|
||||
if (!unzip.fileExists("mod.json")) {
|
||||
return Err("\"" + path.string() + "\" is missing mod.json");
|
||||
if (!unzip.hasEntry("mod.json")) {
|
||||
return Err("\"" + unzip.getPath().string() + "\" is missing mod.json");
|
||||
}
|
||||
|
||||
// Read mod.json & parse if possible
|
||||
unsigned long readSize = 0;
|
||||
auto read = unzip.getFileData("mod.json", &readSize);
|
||||
if (!read || !readSize) {
|
||||
return Err("\"" + path.string() + "\": Unable to read mod.json");
|
||||
}
|
||||
GEODE_UNWRAP_INTO(auto jsonData, unzip.extract("mod.json"));
|
||||
ModJson json;
|
||||
try {
|
||||
json = ModJson::parse(std::string(read, read + readSize));
|
||||
json = ModJson::parse(std::string(jsonData.begin(), jsonData.end()));
|
||||
}
|
||||
catch (std::exception const& e) {
|
||||
delete[] read;
|
||||
return Err(e.what());
|
||||
}
|
||||
|
||||
delete[] read;
|
||||
|
||||
if (!json.is_object()) {
|
||||
return Err(
|
||||
"\"" + path.string() +
|
||||
"/mod.json\" does not have an "
|
||||
"object at root despite expected"
|
||||
);
|
||||
}
|
||||
|
||||
auto res = ModInfo::create(json);
|
||||
if (!res) {
|
||||
return Err("\"" + path.string() + "\" - " + res.unwrapErr());
|
||||
return Err("\"" + unzip.getPath().string() + "\" - " + res.unwrapErr());
|
||||
}
|
||||
auto info = res.unwrap();
|
||||
info.m_path = path;
|
||||
info.m_path = unzip.getPath();
|
||||
|
||||
GEODE_UNWRAP(info.addSpecialFiles(unzip));
|
||||
|
||||
return Ok(info);
|
||||
}
|
||||
|
||||
Result<> ModInfo::addSpecialFiles(ZipFile& unzip) {
|
||||
Result<> ModInfo::addSpecialFiles(file::Unzip& unzip) {
|
||||
// unzip known MD files
|
||||
for (auto& [file, target] : getSpecialFiles()) {
|
||||
if (unzip.fileExists(file)) {
|
||||
unsigned long readSize = 0;
|
||||
auto fileData = unzip.getFileData(file, &readSize);
|
||||
if (!fileData || !readSize) {
|
||||
return Err("Unable to read \"" + file + "\"");
|
||||
}
|
||||
else {
|
||||
*target = sanitizeDetailsData(std::string(fileData, fileData + readSize));
|
||||
}
|
||||
if (unzip.hasEntry(file)) {
|
||||
GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect(
|
||||
"Unable to extract \"{}\"", file
|
||||
));
|
||||
*target = sanitizeDetailsData(std::string(data.begin(), data.end()));
|
||||
}
|
||||
}
|
||||
return Ok();
|
||||
|
@ -258,6 +248,10 @@ ModJson ModInfo::getRawJSON() const {
|
|||
return m_rawJSON;
|
||||
}
|
||||
|
||||
bool ModInfo::operator==(ModInfo const& other) const {
|
||||
return m_id == other.m_id;
|
||||
}
|
||||
|
||||
void geode::to_json(nlohmann::json& json, ModInfo const& info) {
|
||||
json = info.toJSON();
|
||||
}
|
|
@ -21,27 +21,10 @@ Result<> Mod::loadPlatformBinary() {
|
|||
if (dylib) {
|
||||
this->m_implicitLoadFunc =
|
||||
findSymbolOrMangled<geode_load>(dylib, "geode_implicit_load", "_geode_implicit_load");
|
||||
this->m_loadFunc = findSymbolOrMangled<geode_load>(dylib, "geode_load", "_geode_load");
|
||||
this->m_unloadFunc =
|
||||
findSymbolOrMangled<geode_unload>(dylib, "geode_unload", "_geode_unload");
|
||||
this->m_enableFunc =
|
||||
findSymbolOrMangled<geode_enable>(dylib, "geode_enable", "_geode_enable");
|
||||
this->m_disableFunc =
|
||||
findSymbolOrMangled<geode_disable>(dylib, "geode_disable", "_geode_disable");
|
||||
this->m_saveDataFunc =
|
||||
findSymbolOrMangled<geode_save_data>(dylib, "geode_save_data", "_geode_save_data");
|
||||
this->m_loadDataFunc =
|
||||
findSymbolOrMangled<geode_load_data>(dylib, "geode_load_data", "_geode_load_data");
|
||||
this->m_settingUpdatedFunc = findSymbolOrMangled<geode_setting_updated>(
|
||||
dylib, "geode_setting_updated", "_geode_setting_updated"
|
||||
);
|
||||
|
||||
if (!this->m_implicitLoadFunc && !this->m_loadFunc) {
|
||||
return Err(
|
||||
"Unable to find mod entry point (lacking both implicit & explicit definition)"
|
||||
);
|
||||
if (!this->m_implicitLoadFunc) {
|
||||
return Err("Unable to find mod entry point");
|
||||
}
|
||||
|
||||
if (this->m_platformInfo) {
|
||||
delete this->m_platformInfo;
|
||||
}
|
||||
|
@ -58,14 +41,7 @@ Result<> Mod::unloadPlatformBinary() {
|
|||
delete this->m_platformInfo;
|
||||
this->m_platformInfo = nullptr;
|
||||
if (dlclose(dylib) == 0) {
|
||||
this->m_unloadFunc = nullptr;
|
||||
this->m_loadFunc = nullptr;
|
||||
this->m_implicitLoadFunc = nullptr;
|
||||
this->m_enableFunc = nullptr;
|
||||
this->m_disableFunc = nullptr;
|
||||
this->m_saveDataFunc = nullptr;
|
||||
this->m_loadDataFunc = nullptr;
|
||||
this->m_settingUpdatedFunc = nullptr;
|
||||
return Ok();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -22,25 +22,9 @@ Result<> Mod::loadPlatformBinary() {
|
|||
if (dylib) {
|
||||
this->m_implicitLoadFunc =
|
||||
findSymbolOrMangled<geode_load>(dylib, "geode_implicit_load", "_geode_implicit_load");
|
||||
this->m_loadFunc = findSymbolOrMangled<geode_load>(dylib, "geode_load", "_geode_load");
|
||||
this->m_unloadFunc =
|
||||
findSymbolOrMangled<geode_unload>(dylib, "geode_unload", "_geode_unload");
|
||||
this->m_enableFunc =
|
||||
findSymbolOrMangled<geode_enable>(dylib, "geode_enable", "_geode_enable");
|
||||
this->m_disableFunc =
|
||||
findSymbolOrMangled<geode_disable>(dylib, "geode_disable", "_geode_disable");
|
||||
this->m_saveDataFunc =
|
||||
findSymbolOrMangled<geode_save_data>(dylib, "geode_save_data", "_geode_save_data");
|
||||
this->m_loadDataFunc =
|
||||
findSymbolOrMangled<geode_load_data>(dylib, "geode_load_data", "_geode_load_data");
|
||||
this->m_settingUpdatedFunc = findSymbolOrMangled<geode_setting_updated>(
|
||||
dylib, "geode_setting_updated", "_geode_setting_updated"
|
||||
);
|
||||
|
||||
if (!this->m_implicitLoadFunc && !this->m_loadFunc) {
|
||||
return Err(
|
||||
"Unable to find mod entry point (lacking both implicit & explicit definition)"
|
||||
);
|
||||
if (!this->m_implicitLoadFunc) {
|
||||
return Err("Unable to find mod entry point");
|
||||
}
|
||||
|
||||
if (this->m_platformInfo) {
|
||||
|
@ -59,14 +43,7 @@ Result<> Mod::unloadPlatformBinary() {
|
|||
delete this->m_platformInfo;
|
||||
this->m_platformInfo = nullptr;
|
||||
if (dlclose(dylib) == 0) {
|
||||
this->m_unloadFunc = nullptr;
|
||||
this->m_loadFunc = nullptr;
|
||||
this->m_implicitLoadFunc = nullptr;
|
||||
this->m_enableFunc = nullptr;
|
||||
this->m_disableFunc = nullptr;
|
||||
this->m_saveDataFunc = nullptr;
|
||||
this->m_loadDataFunc = nullptr;
|
||||
this->m_settingUpdatedFunc = nullptr;
|
||||
return Ok();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -24,7 +24,7 @@ void InternalLoader::openPlatformConsole() {
|
|||
|
||||
m_platformConsoleOpen = true;
|
||||
|
||||
for (auto const& log : Loader::get()->getLogs()) {
|
||||
for (auto const& log : log::Logs::list()) {
|
||||
std::cout << log->toString(true) << "\n";
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ ipc_done:
|
|||
|
||||
void InternalLoader::setupIPC() {
|
||||
std::thread([]() {
|
||||
while (!Loader::get()->isUnloading()) {
|
||||
while (true) {
|
||||
auto pipe = CreateNamedPipeA(
|
||||
IPC_PIPE_NAME,
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
#ifdef GEODE_IS_WINDOWS
|
||||
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
#include <Geode/loader/Mod.hpp>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
|
||||
template <typename T>
|
||||
|
@ -71,48 +72,27 @@ std::string getLastWinError() {
|
|||
}
|
||||
|
||||
Result<> Mod::loadPlatformBinary() {
|
||||
auto load = LoadLibraryW((this->m_tempDirName / this->m_info.m_binaryName).wstring().c_str());
|
||||
auto load = LoadLibraryW((m_tempDirName / m_info.m_binaryName).wstring().c_str());
|
||||
if (load) {
|
||||
this->m_implicitLoadFunc =
|
||||
findSymbolOrMangled<geode_load>(load, "geode_implicit_load", "_geode_implicit_load@4");
|
||||
this->m_loadFunc = findSymbolOrMangled<geode_load>(load, "geode_load", "_geode_load@4");
|
||||
this->m_unloadFunc =
|
||||
findSymbolOrMangled<geode_unload>(load, "geode_unload", "_geode_unload@0");
|
||||
this->m_enableFunc =
|
||||
findSymbolOrMangled<geode_enable>(load, "geode_enable", "_geode_enable@0");
|
||||
this->m_disableFunc =
|
||||
findSymbolOrMangled<geode_disable>(load, "geode_disable", "_geode_disable@0");
|
||||
this->m_saveDataFunc =
|
||||
findSymbolOrMangled<geode_save_data>(load, "geode_save_data", "_geode_save_data@4");
|
||||
this->m_loadDataFunc =
|
||||
findSymbolOrMangled<geode_load_data>(load, "geode_load_data", "_geode_load_data@4");
|
||||
this->m_settingUpdatedFunc = findSymbolOrMangled<geode_setting_updated>(
|
||||
load, "geode_setting_updated", "_geode_setting_updated@8"
|
||||
);
|
||||
|
||||
if (!this->m_implicitLoadFunc && !this->m_loadFunc) {
|
||||
return Err(
|
||||
"Unable to find mod entry point (lacking both implicit & explicit definition)"
|
||||
);
|
||||
if (!(m_implicitLoadFunc = findSymbolOrMangled<decltype(geode_implicit_load)*>(
|
||||
load, "geode_implicit_load", "_geode_implicit_load@4"
|
||||
))) {
|
||||
return Err("Unable to find mod entry point");
|
||||
}
|
||||
|
||||
if (this->m_platformInfo) {
|
||||
delete this->m_platformInfo;
|
||||
if (m_platformInfo) {
|
||||
delete m_platformInfo;
|
||||
}
|
||||
this->m_platformInfo = new PlatformInfo { load };
|
||||
|
||||
m_platformInfo = new PlatformInfo { load };
|
||||
return Ok();
|
||||
}
|
||||
return Err("Unable to load the DLL: " + getLastWinError());
|
||||
}
|
||||
|
||||
Result<> Mod::unloadPlatformBinary() {
|
||||
auto hmod = this->m_platformInfo->m_hmod;
|
||||
delete this->m_platformInfo;
|
||||
auto hmod = m_platformInfo->m_hmod;
|
||||
delete m_platformInfo;
|
||||
if (FreeLibrary(hmod)) {
|
||||
this->m_implicitLoadFunc = nullptr;
|
||||
this->m_unloadFunc = nullptr;
|
||||
this->m_loadFunc = nullptr;
|
||||
m_implicitLoadFunc = nullptr;
|
||||
return Ok();
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -199,8 +199,7 @@ static void printInfo(std::ostream& stream, LPEXCEPTION_POINTERS info, Mod* faul
|
|||
}
|
||||
|
||||
static void printGeodeInfo(std::ostream& stream) {
|
||||
stream << "Loader Version: " << Loader::get()->getVersion().toString() << " "
|
||||
<< Loader::get()->getVersionType() << "\n"
|
||||
stream << "Loader Version: " << Loader::get()->getVersion().toString() << "\n"
|
||||
<< "Installed mods: " << Loader::get()->getAllMods().size() << "\n"
|
||||
<< "Failed mods: " << Loader::get()->getFailedMods().size() << "\n";
|
||||
}
|
||||
|
|
|
@ -445,23 +445,21 @@ void ModInfoLayer::onEnableMod(CCObject* pSender) {
|
|||
return;
|
||||
}
|
||||
if (as<CCMenuItemToggler*>(pSender)->isToggled()) {
|
||||
auto res = m_mod->load();
|
||||
auto res = m_mod->loadBinary();
|
||||
if (!res) {
|
||||
FLAlertLayer::create(nullptr, "Error Loading Mod", res.unwrapErr(), "OK", nullptr)->show();
|
||||
}
|
||||
else {
|
||||
auto res = m_mod->enable();
|
||||
if (!res) {
|
||||
FLAlertLayer::create(nullptr, "Error Enabling 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_list) m_list->updateAllStates(nullptr);
|
||||
|
|
|
@ -368,7 +368,7 @@ void ModListLayer::onExit(CCObject*) {
|
|||
}
|
||||
|
||||
void ModListLayer::onReload(CCObject*) {
|
||||
Loader::get()->refreshMods();
|
||||
(void)Loader::get()->refreshModsList();
|
||||
this->reloadList();
|
||||
}
|
||||
|
||||
|
|
|
@ -33,39 +33,37 @@ void ModCell::draw() {
|
|||
void ModCell::onFailedInfo(CCObject*) {
|
||||
FLAlertLayer::create(
|
||||
this, "Error Info",
|
||||
m_obj->m_info.m_reason.size() ? m_obj->m_info.m_reason : m_obj->m_mod->getLoadErrorInfo(),
|
||||
m_obj->m_info.m_reason.size() ?
|
||||
m_obj->m_info.m_reason :
|
||||
"Unable to load mod",
|
||||
"OK", "Remove file", 360.f
|
||||
)
|
||||
->show();
|
||||
)->show();
|
||||
}
|
||||
|
||||
void ModCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
|
||||
if (btn2) {
|
||||
try {
|
||||
if (ghc::filesystem::remove(m_obj->m_info.m_file)) {
|
||||
if (ghc::filesystem::remove(m_obj->m_info.m_path)) {
|
||||
FLAlertLayer::create(
|
||||
"File removed", "Removed <cy>" + m_obj->m_info.m_file + "</c>", "OK"
|
||||
)
|
||||
->show();
|
||||
"File removed", "Removed <cy>" + m_obj->m_info.m_path.string() + "</c>", "OK"
|
||||
)->show();
|
||||
}
|
||||
else {
|
||||
FLAlertLayer::create(
|
||||
"Unable to remove file",
|
||||
"Unable to remove <cy>" + m_obj->m_info.m_file + "</c>", "OK"
|
||||
)
|
||||
->show();
|
||||
"Unable to remove <cy>" + m_obj->m_info.m_path.string() + "</c>", "OK"
|
||||
)->show();
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
FLAlertLayer::create(
|
||||
"Unable to remove file",
|
||||
"Unable to remove <cy>" + m_obj->m_info.m_file + "</c>: <cr>" +
|
||||
"Unable to remove <cy>" + m_obj->m_info.m_path.string() + "</c>: <cr>" +
|
||||
std::string(e.what()) + "</c>",
|
||||
"OK"
|
||||
)
|
||||
->show();
|
||||
)->show();
|
||||
}
|
||||
Loader::get()->refreshMods();
|
||||
(void)Loader::get()->refreshModsList();
|
||||
m_list->refreshList();
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +81,10 @@ void ModCell::setupUnloaded() {
|
|||
titleLabel->setPosition(m_height / 2, m_height / 2 + 7.f);
|
||||
m_mainLayer->addChild(titleLabel);
|
||||
|
||||
auto pathLabel = CCLabelBMFont::create(m_obj->m_info.m_file.c_str(), "chatFont.fnt");
|
||||
auto pathLabel = CCLabelBMFont::create(
|
||||
m_obj->m_info.m_path.string().c_str(),
|
||||
"chatFont.fnt"
|
||||
);
|
||||
pathLabel->setAnchorPoint({ .0f, .5f });
|
||||
pathLabel->setScale(.43f);
|
||||
pathLabel->setPosition(m_height / 2, m_height / 2 - 7.f);
|
||||
|
@ -297,15 +298,12 @@ void ModCell::onEnable(CCObject* pSender) {
|
|||
"still see some effects of the mod left however, and you may "
|
||||
"need to <cg>restart</c> the game to have it fully unloaded.",
|
||||
"OK"
|
||||
)
|
||||
->show();
|
||||
)->show();
|
||||
m_list->updateAllStates(this);
|
||||
return;
|
||||
}
|
||||
if (!as<CCMenuItemToggler*>(pSender)->isToggled()) {
|
||||
if (tryOrAlert(m_obj->m_mod->load(), "Error loading mod")) {
|
||||
tryOrAlert(m_obj->m_mod->enable(), "Error enabling mod");
|
||||
}
|
||||
tryOrAlert(m_obj->m_mod->enable(), "Error enabling mod");
|
||||
}
|
||||
else {
|
||||
tryOrAlert(m_obj->m_mod->disable(), "Error disabling mod");
|
||||
|
|
|
@ -28,14 +28,14 @@ class ModListLayer;
|
|||
struct ModObject : public CCObject {
|
||||
ModObjectType m_type;
|
||||
Mod* m_mod;
|
||||
Loader::FailedModInfo m_info;
|
||||
InvalidGeodeFile m_info;
|
||||
IndexItem m_index;
|
||||
|
||||
inline ModObject(Mod* mod) : m_mod(mod), m_type(ModObjectType::Mod) {
|
||||
this->autorelease();
|
||||
};
|
||||
|
||||
inline ModObject(Loader::FailedModInfo const& info) :
|
||||
inline ModObject(InvalidGeodeFile const& info) :
|
||||
m_info(info), m_type(ModObjectType::Unloaded) {
|
||||
this->autorelease();
|
||||
};
|
||||
|
|
|
@ -8,27 +8,9 @@ bool AdvancedSettingsPopup::setup(Mod* mod) {
|
|||
|
||||
this->setTitle("Advanced Settings for " + mod->getName());
|
||||
|
||||
auto enableBtn = CCMenuItemToggler::createWithStandardSprites(
|
||||
this, menu_selector(AdvancedSettingsPopup::onEnableEarlyLoad), .75f
|
||||
);
|
||||
enableBtn->setPosition(-100.f, 0.f);
|
||||
m_buttonMenu->addChild(enableBtn);
|
||||
|
||||
auto enableText = CCLabelBMFont::create("Enable Early Load", "bigFont.fnt");
|
||||
enableText->setPosition(-70.f, 0.f);
|
||||
enableText->setAnchorPoint({ .0f, .5f });
|
||||
enableText->setScale(.65f);
|
||||
m_buttonMenu->addChild(enableText);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdvancedSettingsPopup::onEnableEarlyLoad(CCObject* sender) {
|
||||
Loader::get()->setEarlyLoadMod(
|
||||
m_mod, !static_cast<CCMenuItemToggler*>(sender)->isToggled()
|
||||
);
|
||||
}
|
||||
|
||||
AdvancedSettingsPopup* AdvancedSettingsPopup::create(Mod* mod) {
|
||||
auto ret = new AdvancedSettingsPopup;
|
||||
if (ret && ret->init(356.f, 220.f, mod)) {
|
||||
|
|
|
@ -11,8 +11,6 @@ protected:
|
|||
|
||||
bool setup(Mod* mod) override;
|
||||
|
||||
void onEnableEarlyLoad(CCObject*);
|
||||
|
||||
public:
|
||||
static AdvancedSettingsPopup* create(Mod* mod);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ USE_GEODE_NAMESPACE();
|
|||
|
||||
bool BasedButtonSprite::init(CCNode* ontop, int type, int size, int color) {
|
||||
if (!CCSprite::initWithSpriteFrameName(Mod::get()->expandSpriteName(
|
||||
fmt::format("GEODE_blank%02d_%02d_%02d.png", type, size, color).c_str()
|
||||
fmt::format("GEODE_blank{:02}_{:02}_{:02}.png", type, size, color).c_str()
|
||||
)))
|
||||
return false;
|
||||
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
#include <Geode/utils/file.hpp>
|
||||
#include <Geode/utils/string.hpp>
|
||||
#include <Geode/utils/map.hpp>
|
||||
#include <fstream>
|
||||
#include <../support/zip_support/ZipUtils.h>
|
||||
#include <../support/zip_support/ioapi.h>
|
||||
#include <../support/zip_support/unzip.h>
|
||||
|
||||
USE_GEODE_NAMESPACE();
|
||||
using namespace geode::utils::file;
|
||||
|
||||
Result<std::string> utils::file::readString(ghc::filesystem::path const& path) {
|
||||
#if _WIN32
|
||||
|
@ -112,46 +116,153 @@ Result<std::vector<std::string>> utils::file::listFilesRecursively(std::string c
|
|||
}
|
||||
|
||||
Result<> utils::file::unzipTo(ghc::filesystem::path const& from, ghc::filesystem::path const& to) {
|
||||
// unzip downloaded
|
||||
auto unzip = ZipFile(from.string());
|
||||
if (!unzip.isLoaded()) {
|
||||
return Err("Unable to unzip index.zip");
|
||||
}
|
||||
GEODE_UNWRAP_INTO(auto unzip, Unzip::create(from));
|
||||
return unzip.extractAllTo(to);
|
||||
}
|
||||
|
||||
if (!ghc::filesystem::exists(to) && !ghc::filesystem::create_directories(to)) {
|
||||
return Err("Unable to create directories \"" + to.string() + "\"");
|
||||
}
|
||||
static constexpr auto MAX_ENTRY_PATH_LEN = 256;
|
||||
|
||||
for (auto file : unzip.getAllFiles()) {
|
||||
// this is a very bad check for seeing
|
||||
// if file is a directory. it seems to
|
||||
// work on windows at least. idk why
|
||||
// getAllFiles returns the directories
|
||||
// aswell now
|
||||
if (utils::string::endsWith(file, "\\") || utils::string::endsWith(file, "/")) continue;
|
||||
struct ZipEntry {
|
||||
unz_file_pos m_pos;
|
||||
ZPOS64_T m_compressedSize;
|
||||
ZPOS64_T m_uncompressedSize;
|
||||
};
|
||||
|
||||
auto zipPath = file;
|
||||
class file::UnzipImpl final {
|
||||
public:
|
||||
using Path = Unzip::Path;
|
||||
|
||||
// dont include the github repo folder
|
||||
file = file.substr(file.find_first_of("/") + 1);
|
||||
private:
|
||||
unzFile m_zip;
|
||||
Path m_zipPath;
|
||||
std::unordered_map<Path, ZipEntry> m_entries;
|
||||
|
||||
auto path = ghc::filesystem::path(file);
|
||||
if (path.has_parent_path()) {
|
||||
auto dir = to / path.parent_path();
|
||||
if (!ghc::filesystem::exists(dir) && !ghc::filesystem::create_directories(dir)) {
|
||||
return Err("Unable to create directories \"" + dir.string() + "\"");
|
||||
public:
|
||||
bool loadEntries() {
|
||||
// Clear old entries
|
||||
m_entries.clear();
|
||||
|
||||
char fileName[MAX_ENTRY_PATH_LEN + 1];
|
||||
unz_file_info64 fileInfo;
|
||||
|
||||
// Read first file
|
||||
if (unzGoToFirstFile64(
|
||||
m_zip, &fileInfo, fileName, sizeof(fileName) - 1
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
// Loop over all files
|
||||
while (true) {
|
||||
// Read file and add to entries
|
||||
unz_file_pos pos;
|
||||
if (unzGetFilePos(m_zip, &pos)) {
|
||||
m_entries.insert({
|
||||
fileName, ZipEntry {
|
||||
.m_pos = pos,
|
||||
.m_compressedSize = fileInfo.compressed_size,
|
||||
.m_uncompressedSize = fileInfo.uncompressed_size,
|
||||
}
|
||||
});
|
||||
}
|
||||
// Read next file, or break on error
|
||||
if (unzGoToNextFile64(
|
||||
m_zip, &fileInfo, fileName, sizeof(fileName) - 1)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
unsigned long size;
|
||||
auto data = unzip.getFileData(zipPath, &size);
|
||||
if (!data || !size) {
|
||||
return Err("Unable to read \"" + std::string(zipPath) + "\"");
|
||||
}
|
||||
auto wrt = utils::file::writeBinary(to / file, byte_array(data, data + size));
|
||||
if (!wrt) {
|
||||
return Err("Unable to write \"" + (to / file).string() + "\": " + wrt.unwrapErr());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<byte_array> extract(Path const& name) {
|
||||
if (!m_entries.count(name)) {
|
||||
return Err("Entry not found");
|
||||
}
|
||||
|
||||
auto entry = m_entries.at(name);
|
||||
|
||||
if (!unzGoToFilePos(m_zip, &entry.m_pos)) {
|
||||
return Err("Unable to navigate to entry");
|
||||
}
|
||||
if (!unzOpenCurrentFile(m_zip)) {
|
||||
return Err("Unable to open entry");
|
||||
}
|
||||
byte_array res;
|
||||
res.reserve(entry.m_uncompressedSize);
|
||||
auto size = unzReadCurrentFile(m_zip, res.data(), entry.m_uncompressedSize);
|
||||
if (size == 0 || size == entry.m_uncompressedSize) {
|
||||
return Err("Unable to extract entry");
|
||||
}
|
||||
unzCloseCurrentFile(m_zip);
|
||||
|
||||
return Ok(res);
|
||||
}
|
||||
|
||||
std::unordered_map<Path, ZipEntry>& entries() {
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
Path& path() {
|
||||
return m_zipPath;
|
||||
}
|
||||
|
||||
UnzipImpl(unzFile zip, Path const& path) : m_zip(zip), m_zipPath(path) {}
|
||||
~UnzipImpl() {
|
||||
unzClose(m_zip);
|
||||
}
|
||||
};
|
||||
|
||||
Unzip::Unzip(UnzipImpl* impl) : m_impl(impl) {}
|
||||
|
||||
Unzip::~Unzip() {
|
||||
if (m_impl) {
|
||||
delete m_impl;
|
||||
}
|
||||
}
|
||||
|
||||
Unzip::Unzip(Unzip&& other) : m_impl(other.m_impl) {
|
||||
other.m_impl = nullptr;
|
||||
}
|
||||
|
||||
Result<Unzip> Unzip::create(Path const& file) {
|
||||
// todo: make sure unicode paths work
|
||||
auto zip = unzOpen(file.generic_string().c_str());
|
||||
if (!zip) {
|
||||
return Err("Unable to open zip file");
|
||||
}
|
||||
auto impl = new UnzipImpl(zip, file);
|
||||
if (!impl->loadEntries()) {
|
||||
return Err("Unable to read zip file");
|
||||
}
|
||||
return Ok(Unzip(impl));
|
||||
}
|
||||
|
||||
ghc::filesystem::path Unzip::getPath() const {
|
||||
return m_impl->path();
|
||||
}
|
||||
|
||||
std::vector<ghc::filesystem::path> Unzip::getEntries() const {
|
||||
return map::getKeys(m_impl->entries());
|
||||
}
|
||||
|
||||
bool Unzip::hasEntry(Path const& name) {
|
||||
return m_impl->entries().count(name);
|
||||
}
|
||||
|
||||
Result<byte_array> Unzip::extract(Path const& name) {
|
||||
return m_impl->extract(name);
|
||||
}
|
||||
|
||||
Result<> Unzip::extractTo(Path const& name, Path const& path) {
|
||||
GEODE_UNWRAP_INTO(auto bytes, m_impl->extract(name));
|
||||
GEODE_UNWRAP(file::writeBinary(path, bytes));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
Result<> Unzip::extractAllTo(Path const& dir) {
|
||||
GEODE_UNWRAP(file::createDirectoryAll(dir));
|
||||
for (auto& [entry, _] : m_impl->entries()) {
|
||||
GEODE_UNWRAP(this->extractTo(entry, dir / entry));
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue