Merge branch 'main' into copy-mods

This commit is contained in:
Justin 2024-11-18 19:28:08 -05:00 committed by GitHub
commit 00ab705154
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
184 changed files with 3674 additions and 11624 deletions

View file

@ -1,5 +1,56 @@
# Geode Changelog
## v4.0.0-beta.1
* Button to manually install mods from files (e881dc5)
* Add `ModRequestedAction::Update` (e881dc5)
* Add `ModMetadata::checkGeodeVersion` and `ModMetadata::checkTargetVersions` (e881dc5)
* Add `geode::createModLogo` for creating a logo from a `.geode` package (e881dc5)
* Tags now use names provided by the server (893b03e)
* Add web support for multiple request headers with same name (#1150)
* Fix `Task::chain` using the wrong type in the impl (22a11b9)
* Fix installing mods not checking the current version (#1148)
* Fix searching for mods ignoring geode and gd version (#1153)
* Fix crash when checking tags (01807fe)
* Fix 'Outdated' label being visible while updating (6679a69)
* Fix log nesting issue (0e8d4c6)
* Remove forward compat message box as it confuses users (5592ef6)
* Fix crash on opening mod changelogs (9834cb2)
* Make `ColorPickPopup` pimpl (1a201e1)
* Fix lag issue in `ColorPickPopup` (3081164)
* Change return type of `ModSettingsManager::save` (da92090)
* Fix every misspelling of successfully (#1151)
* Allow building geode itself in debug mode (5645399)
## v4.0.0-alpha.1
* Support for the 2.2074 update
* Developers, see [this page for a migration guide](https://docs.geode-sdk.org/tutorials/migrate-v4)
* Major API breaks:
* Remove everything previously marked deprecated
* `utils::MiniFunction` removed
* Rewritten `geode::Result` class
* Rewritten matjson library
* Settings V2 completely removed, use V3 now
* `JsonChecker` removed
* Add new system for ordered hook priority, [see docs](https://docs.geode-sdk.org/tutorials/hookpriority) (673317d, 6db3084)
* C++20 coroutine support for `geode::Task`, [see docs](https://docs.geode-sdk.org/tutorials/tasks#coroutines) (e61b2c0, ab196b9)
* Add `Task::chain`, [see docs](https://docs.geode-sdk.org/tutorials/tasks#chaining-tasks) (3248831)
* Single page local mods list (efb1fbf)
* Split mod problems into load and outdated (12e8bbb, 09fa872, df2528c)
* This means mods made for outdated gd or geode versions no longer count as actual errors, resulting in less clutter in the ui
* Fix safe mode popup on windows showing up when not supposed to (038788b)
* WebRequest::ignoreContentLength (#1126)
* Lots of smaller fixes to the geode ui (c9afa75, f5f3365, 2d66279, 02845d9, 9b95301, 6d13f78, 123b3ab, 0b2fc66, f96ea5e, cad670f)
* Fix CCArrayExt::pop_back() return type (#1130)
* Add missing spanish translations to installer (#1145)
* Add hashtag symbol to CommonFilter::Any (#1131)
* Disable forward compat on android (c9e97af)
## v3.9.2
* Fix searching for mods returning unavailable mods (#1149)
## v3.9.1
* Fix mod downloads not checking version (f575187)
## v3.9.0
* Many changes to the settings ui (#1108)
* Fuzzy search is now more reasonable

View file

@ -189,8 +189,8 @@ include(cmake/Platform.cmake)
include(cmake/GeodeFile.cmake)
if (NOT DEFINED GEODE_GD_VERSION)
set(GEODE_GD_VERSION 2.206)
set(GEODE_COMP_GD_VERSION 22060)
set(GEODE_GD_VERSION 2.2074)
set(GEODE_COMP_GD_VERSION 22074)
endif()
target_compile_definitions(
@ -236,8 +236,9 @@ if (ANDROID)
endif()
set(MAT_JSON_AS_INTERFACE ON)
CPMAddPackage("gh:geode-sdk/json#cda9807")
CPMAddPackage("gh:fmtlib/fmt#10.2.1")
CPMAddPackage("gh:geode-sdk/result@1.2.1")
CPMAddPackage("gh:geode-sdk/json@3.1.0")
CPMAddPackage("gh:fmtlib/fmt#11.0.2")
target_compile_definitions(${PROJECT_NAME} INTERFACE MAT_JSON_DYNAMIC=1)
@ -266,7 +267,7 @@ if (DEFINED GEODE_TULIPHOOK_REPO_PATH)
message(STATUS "Using ${GEODE_TULIPHOOK_REPO_PATH} for TulipHook")
add_subdirectory(${GEODE_TULIPHOOK_REPO_PATH} ${GEODE_TULIPHOOK_REPO_PATH}/build)
else()
CPMAddPackage("gh:geode-sdk/TulipHook#d1d9559")
CPMAddPackage("gh:geode-sdk/TulipHook@2.4.1")
endif()
set(CMAKE_WARN_DEPRECATED ON CACHE BOOL "" FORCE)
@ -296,7 +297,7 @@ target_include_directories(GeodeBindings PUBLIC
${GEODE_LOADER_PATH}/include/Geode/fmod
)
target_link_directories(GeodeBindings PUBLIC ${GEODE_LOADER_PATH}/include/link)
target_link_libraries(GeodeBindings PUBLIC fmt TulipHookInclude mat-json)
target_link_libraries(GeodeBindings PUBLIC fmt TulipHookInclude mat-json GeodeResult)
target_link_libraries(${PROJECT_NAME} INTERFACE GeodeBindings)
if (NOT EXISTS ${GEODE_BIN_PATH})

View file

@ -1 +1 @@
3.9.0
4.0.0-beta.1

View file

@ -2,8 +2,8 @@
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
set(CPM_DOWNLOAD_VERSION 0.39.0)
set(CPM_HASH_SUM "66639bcac9dd2907b2918de466783554c1334446b9874e90d38e3778d404c2ef")
set(CPM_DOWNLOAD_VERSION 0.40.2)
set(CPM_HASH_SUM "c8cdc32c03816538ce22781ed72964dc864b2a34a310d3b7104812a5ca2d835d")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")

View file

@ -72,19 +72,27 @@ elseif (GEODE_TARGET_PLATFORM STREQUAL "Win64")
target_link_libraries(${PROJECT_NAME} INTERFACE
${GEODE_LOADER_PATH}/include/link/win64/libcocos2d.lib
${GEODE_LOADER_PATH}/include/link/win64/libExtensions.lib
${GEODE_LOADER_PATH}/include/link/win64/ssl.lib
${GEODE_LOADER_PATH}/include/link/win64/crypto.lib
${GEODE_LOADER_PATH}/include/link/win64/nghttp2.lib
${GEODE_LOADER_PATH}/include/link/win64/ngtcp2.lib
${GEODE_LOADER_PATH}/include/link/win64/nghttp3.lib
${GEODE_LOADER_PATH}/include/link/win64/ngtcp2_crypto_boringssl.lib
${GEODE_LOADER_PATH}/include/link/win64/libcurl.lib
${GEODE_LOADER_PATH}/include/link/win64/glew32.lib
${GEODE_LOADER_PATH}/include/link/win64/gdstring.lib
${GEODE_LOADER_PATH}/include/link/win64/fmod.lib
opengl32
)
if (PROJECT_IS_TOP_LEVEL AND CMAKE_BUILD_TYPE STREQUAL "Debug")
target_link_libraries(${PROJECT_NAME} INTERFACE
${GEODE_LOADER_PATH}/include/link/win64/gd-libcurl.lib
)
else()
target_link_libraries(${PROJECT_NAME} INTERFACE
${GEODE_LOADER_PATH}/include/link/win64/ssl.lib
${GEODE_LOADER_PATH}/include/link/win64/crypto.lib
${GEODE_LOADER_PATH}/include/link/win64/nghttp2.lib
${GEODE_LOADER_PATH}/include/link/win64/ngtcp2.lib
${GEODE_LOADER_PATH}/include/link/win64/nghttp3.lib
${GEODE_LOADER_PATH}/include/link/win64/ngtcp2_crypto_boringssl.lib
${GEODE_LOADER_PATH}/include/link/win64/libcurl.lib
)
endif()
# Windows links against .lib and not .dll
set(GEODE_OUTPUT_NAME "Geode")
set(GEODE_PLATFORM_BINARY "Geode.lib")

View file

@ -0,0 +1,16 @@
!insertmacro LANGFILE_EXT Portuguese
!pragma warning disable 6030
${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "O instalador irá guiá-lo através da instalação de $(^NameDA).$\r$\n$\r$\nAntes de iniciar a instalação, certifique-se de que o Geometry Dash não está aberto.$\r$\n$\r$\n$_CLICK"
${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "O instalador irá guiá-lo através da desinstalação de $(^NameDA).$\r$\n$\r$\nAntes de iniciar a desinstalação, certifique-se de que o Geometry Dash não está aberto.$\r$\n$\r$\n$_CLICK"
!pragma warning default 6030
; installer
${LangFileString} GEODE_TEXT_GD_MISSING "$\r$\n$\r$\nEsse caminho não tem o Geometry Dash instalado!"
${LangFileString} GEODE_TEXT_GD_OLD "$\r$\n$\r$\nA sua versão do Geometry Dash é demasiado antiga para esta versão do Geode!"
${LangFileString} GEODE_TEXT_MOD_LOADER_ALREADY_INSTALLED "Esse caminho já tem outros mods instalados!$\r$\nEles serão substituídos pelo Geode. (the dll trademark)"
; uninstaller
${LangFileString} GEODE_UNTEXT_GEODE_MISSING "Esse caminho não tem o Geode instalado!"

View file

@ -1,15 +1,15 @@
!insertmacro LANGFILE_EXT SpanishInternational
!pragma warning disable 6030
${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Este asistente le guiará durante la instalación de $(^NameDA) en su computadora.$\r$\n$\r$\nAntes de iniciar la instalación, asegúrese de que Geometry Dash no se está ejecutando$\r$\n$\r$\n$_CLICK"
${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Este asistente le guiará durante la desinstalación de $(^NameDA).$\r$\n$\r$\nAntes de iniciar la desinstalación, asegúrese de que Geometry Dash no se está ejecutando.$\r$\n$\r$\n$_CLICK"
${LangFileString} MUI_TEXT_WELCOME_INFO_TEXT "Este asistente le guiará durante la instalación de Geode en su computadora.$\r$\n$\r$\nAntes de iniciar la instalación, asegúrese de que Geometry Dash no se está ejecutando$\r$\n$\r$\n$_CLICK"
${LangFileString} MUI_UNTEXT_WELCOME_INFO_TEXT "Este asistente le guiará durante la desinstalación de Geode.$\r$\n$\r$\nAntes de iniciar la desinstalación, asegúrese de que Geometry Dash no se está ejecutando.$\r$\n$\r$\n$_CLICK"
!pragma warning default 6030
; installer
${LangFileString} GEODE_TEXT_GD_MISSING "$\r$\n$\r$\n¡Geometry Dash no está instalado en esta ruta!"
${LangFileString} GEODE_TEXT_GD_OLD "$\r$\n$\r$\nYour version of Geometry Dash is too old for this version of Geode!"
${LangFileString} GEODE_TEXT_MOD_LOADER_ALREADY_INSTALLED "This path has other mods installed!$\r$\nThey will be overwritten by Geode. (the dll trademark)"
${LangFileString} GEODE_TEXT_GD_OLD "$\r$\n$\r$\n¡Su versión de Geometry Dash es demasiado antigua para esta versión de Geode!"
${LangFileString} GEODE_TEXT_MOD_LOADER_ALREADY_INSTALLED "¡Esta ruta ya tiene otros mods instalados!$\r$\nVan a ser sobreescritos por Geode. (the dll trademark)"
; uninstaller

View file

@ -58,6 +58,7 @@
!insertmacro GEODE_LANGUAGE "Polish"
!insertmacro GEODE_LANGUAGE "Russian"
!insertmacro GEODE_LANGUAGE "PortugueseBR"
!insertmacro GEODE_LANGUAGE "Portuguese"
!insertmacro GEODE_LANGUAGE "Ukrainian"
!insertmacro GEODE_LANGUAGE "Czech"
!insertmacro GEODE_LANGUAGE "Turkish"

View file

@ -5,21 +5,10 @@
#include <Geode/platform/platform.hpp>
#include <variant>
#define GEODE_STATIC_PTR(type, name) \
static type* s_##name; \
inline type* name() { \
if (!s_##name) s_##name = new type(); \
return s_##name; \
}
#define GEODE_STATIC_VAR(type, name) \
inline type& name() { \
static type s_##name; \
return s_##name; \
}
#define GEODE_WRAPPER_CONCAT(x, y) x##y
#define GEODE_CONCAT(x, y) GEODE_WRAPPER_CONCAT(x, y)
#if !defined(GEODE_CONCAT)
#define GEODE_WRAPPER_CONCAT(x, y) x##y
#define GEODE_CONCAT(x, y) GEODE_WRAPPER_CONCAT(x, y)
#endif
#define GEODE_WRAPPER_STR(...) #__VA_ARGS__
#define GEODE_STR(...) GEODE_WRAPPER_STR(__VA_ARGS__)

View file

@ -6,7 +6,8 @@
#include "ui/EnterLayerEvent.hpp"
#include "ui/BasedButtonSprite.hpp"
#include "ui/IconButtonSprite.hpp"
#include "ui/InputNode.hpp"
#include "ui/Layout.hpp"
#include "ui/SpacerNode.hpp"
#include "ui/General.hpp"
#include "ui/ListView.hpp"
#include "ui/MDPopup.hpp"

View file

@ -1,7 +1,6 @@
#pragma once
#include "DefaultInclude.hpp"
#include "utils/Result.hpp"
#include "utils/VersionInfo.hpp"
#include "utils/ranges.hpp"
#include "utils/casts.hpp"
@ -12,5 +11,4 @@
#include "utils/permission.hpp"
#include "utils/general.hpp"
#include "utils/timer.hpp"
#include "utils/MiniFunction.hpp"
#include "utils/ObjcHook.hpp"

View file

@ -93,7 +93,7 @@ namespace gd {
bool empty() const;
bool operator==(string const& other) const;
bool operator==(std::string_view const other) const;
bool operator==(std::string_view other) const;
bool operator==(char const* other) const {
return *this == std::string_view(other);
}
@ -101,7 +101,7 @@ namespace gd {
return *this == std::string_view(other);
}
std::strong_ordering operator<=>(string const& other) const;
std::strong_ordering operator<=>(std::string_view const other) const;
std::strong_ordering operator<=>(std::string_view other) const;
std::strong_ordering operator<=>(char const* other) const {
return *this <=> std::string_view(other);
}

View file

@ -37,7 +37,6 @@
#include "../kazmath/include/kazmath/kazmath.h"
#include "../script_support/CCScriptSupport.h"
#include "../include/CCProtocols.h"
#include "Layout.hpp"
#include "../../loader/Event.hpp"
#include <Geode/utils/casts.hpp>
@ -45,6 +44,12 @@
#include <matjson.hpp>
#endif
namespace geode {
class Layout;
class LayoutOptions;
enum class Anchor;
}
NS_CC_BEGIN
class CCCamera;
@ -871,31 +876,19 @@ public:
private:
friend class geode::modifier::FieldContainer;
[[deprecated("Will be removed, it's an ABI break")]]
GEODE_DLL geode::modifier::FieldContainer* getFieldContainer();
GEODE_DLL geode::modifier::FieldContainer* getFieldContainer(char const* forClass);
GEODE_DLL void addEventListenerInternal(
std::string const& id,
geode::EventListenerProtocol* protocol
);
#ifdef GEODE_EXPORTING
[[deprecated("Will be removed, it's an ABI break")]]
GEODE_DLL std::optional<matjson::Value> getAttributeInternal(std::string const& attribute);
#endif
public:
#ifdef GEODE_EXPORTING
[[deprecated("Will be removed, it's an ABI break")]]
GEODE_DLL void setAttribute(std::string const& attribute, matjson::Value const& value);
#endif
public:
/**
* Get the string ID of this node
* @returns The ID, or an empty string if the node has no ID.
* @note Geode addition
*/
GEODE_DLL std::string getID();
GEODE_DLL const std::string& getID();
/**
* Set the string ID of this node. String IDs are a Geode addition
* that are much safer to use to get nodes than absolute indexes
@ -906,13 +899,23 @@ public:
*/
GEODE_DLL void setID(std::string const& id);
/**
* Set the string ID of this node. String IDs are a Geode addition
* that are much safer to use to get nodes than absolute indexes
* @param id The ID of the node, recommended to be in kebab case
* without any spaces or uppercase letters. If the node is added
* by a mod, use the _spr literal to append the mod ID to it
* @note Geode addition
*/
GEODE_DLL void setID(std::string&& id);
/**
* Get a child by its string ID
* @param id ID of the child
* @returns The child, or nullptr if none was found
* @note Geode addition
*/
GEODE_DLL CCNode* getChildByID(std::string const& id);
GEODE_DLL CCNode* getChildByID(std::string_view id);
/**
* Get a child by its string ID. Recursively searches all the children
@ -920,7 +923,7 @@ public:
* @returns The child, or nullptr if none was found
* @note Geode addition
*/
GEODE_DLL CCNode* getChildByIDRecursive(std::string const& id);
GEODE_DLL CCNode* getChildByIDRecursive(std::string_view id);
/**
* Get a child based on a query. Searches the child tree for a matching
@ -936,14 +939,14 @@ public:
* ->getChildByID("mod.id/epic-button")`
* @returns The first matching node, or nullptr if none was found
*/
GEODE_DLL CCNode* querySelector(std::string const& query);
GEODE_DLL CCNode* querySelector(std::string_view query);
/**
* Removes a child from the container by its ID.
* @param id The ID of the node
* @note Geode addition
*/
GEODE_DLL void removeChildByID(std::string const& id);
GEODE_DLL void removeChildByID(std::string_view id);
/**
* Add a child before a specified existing child
@ -990,13 +993,13 @@ public:
* CCLayers / CCMenus, this will change where the children are located
* @note Geode addition
*/
GEODE_DLL void setLayout(Layout* layout, bool apply = true, bool respectAnchor = true);
GEODE_DLL void setLayout(geode::Layout* layout, bool apply = true, bool respectAnchor = true);
/**
* Get the Layout for this node
* @returns The current layout, or nullptr if no layout is set
* @note Geode addition
*/
GEODE_DLL Layout* getLayout();
GEODE_DLL geode::Layout* getLayout();
/**
* Update the layout of this node using the current Layout. If no layout is
* set, nothing happens
@ -1011,13 +1014,13 @@ public:
* @param apply Whether to update the layout of the parent node
* @note Geode addition
*/
GEODE_DLL void setLayoutOptions(LayoutOptions* options, bool apply = true);
GEODE_DLL void setLayoutOptions(geode::LayoutOptions* options, bool apply = true);
/**
* Get the layout options for this node
* @returns The current layout options, or nullptr if no options are set
* @note Geode addition
*/
GEODE_DLL LayoutOptions* getLayoutOptions();
GEODE_DLL geode::LayoutOptions* getLayoutOptions();
/**
* Adds a child at an anchored position with an offset. The node is placed
* in its parent where the anchor specifies, and then the offset is used to
@ -1029,7 +1032,7 @@ public:
* if no other layout is already specified
* @note Geode addition
*/
GEODE_DLL void addChildAtPosition(CCNode* child, Anchor anchor, CCPoint const& offset = CCPointZero, bool useAnchorLayout = true);
GEODE_DLL void addChildAtPosition(CCNode* child, geode::Anchor anchor, CCPoint const& offset = CCPointZero, bool useAnchorLayout = true);
/**
* Adds a child at an anchored position with an offset. The node is placed
* in its parent where the anchor specifies, and then the offset is used to
@ -1044,7 +1047,7 @@ public:
*/
GEODE_DLL void addChildAtPosition(
CCNode* child,
Anchor anchor,
geode::Anchor anchor,
CCPoint const& offset,
CCPoint const& nodeAnchor,
bool useAnchorLayout = true
@ -1057,7 +1060,7 @@ public:
* @param offset Where to place the child relative to the anchor
* @note Geode addition
*/
GEODE_DLL void updateAnchoredPosition(Anchor anchor, CCPoint const& offset = CCPointZero);
GEODE_DLL void updateAnchoredPosition(geode::Anchor anchor, CCPoint const& offset = CCPointZero);
/**
* Updates the anchored position of a child. Requires the child to already
* have a parent; if the child already has AnchorLayoutOptions set, those
@ -1068,7 +1071,7 @@ public:
* @note Geode addition
*/
GEODE_DLL void updateAnchoredPosition(
Anchor anchor,
geode::Anchor anchor,
CCPoint const& offset,
CCPoint const& nodeAnchor
);
@ -1102,7 +1105,7 @@ public:
template <class Filter, class... Args>
geode::EventListenerProtocol* addEventListener(
std::string const& id,
geode::utils::MiniFunction<typename Filter::Callback> callback,
std::function<typename Filter::Callback> callback,
Args&&... args
) {
auto listener = new geode::EventListener<Filter>(
@ -1113,7 +1116,7 @@ public:
}
template <class Filter, class... Args>
geode::EventListenerProtocol* addEventListener(
geode::utils::MiniFunction<typename Filter::Callback> callback,
std::function<typename Filter::Callback> callback,
Args&&... args
) {
return this->addEventListener<Filter, Args...>(
@ -1923,7 +1926,7 @@ namespace geode {
std::string m_targetID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, UserObjectSetEvent* event);
ListenerResult handle(std::function<Callback> fn, UserObjectSetEvent* event);
AttributeSetFilter(std::string const& id);
};

View file

@ -28,6 +28,8 @@
#include "../../include/cocos2d.h"
#include "../ExtensionMacros.h"
enum class GJHttpType;
NS_CC_EXT_BEGIN
class CC_DLL CCHttpClient;
@ -216,6 +218,50 @@ public:
return _headers;
}
inline int getType() {
return _type;
}
inline void setType(int type) {
_type = type;
}
// @note Geode addition
inline void setType(GJHttpType type) {
_type = static_cast<int>(type);
}
inline bool getShouldCancel() {
return _shouldCancel;
}
inline void setShouldCancel(bool shouldCancel) {
_shouldCancel = shouldCancel;
}
inline int getDownloadProgress() {
return _downloadProgress;
}
inline void setDownloadProgress(int downloadProgress) {
_downloadProgress = downloadProgress;
}
inline int getReadTimeout() {
return _readTimeout;
}
inline void setReadTimeout(int readTimeout) {
_readTimeout = readTimeout;
}
inline int getConnectTimeout() {
return _connectTimeout;
}
inline void setConnectTimeout(int connectTimeout) {
_connectTimeout = connectTimeout;
}
protected:
// properties
@ -229,13 +275,15 @@ protected:
gd::vector<gd::string> _headers; /// custom http headers
// @note RobTop Addition
int _requestTypeGJ;
int _type;
// @note RobTop Addition
bool _shouldCancel;
// @note RobTop Addition
int _downloadProgress;
// @note RobTop Addition
int _readTimeout;
// @note RobTop Addition
int _connectTimeout;
};
NS_CC_EXT_END

View file

@ -59,7 +59,6 @@ THE SOFTWARE.
// base_nodes
#include "../base_nodes/CCNode.h"
#include "../base_nodes/CCAtlasNode.h"
#include "../base_nodes/SpacerNode.hpp"
// cocoa
#include "../cocoa/CCAffineTransform.h"

View file

@ -142,7 +142,7 @@ public:
/**
* @note RobTop addition
*/
void toggleFullScreen(bool fullscreen, bool borderless);
void toggleFullScreen(bool fullscreen, bool borderless, bool fix);
/**
* @note RobTop addition
@ -185,6 +185,8 @@ public:
protected:
static CCEGLView* s_pEglView;
// @note unknown members here
uint8_t m_unkPad[8];
bool m_bCaptured;
// Robtop Removal
// HWND m_hWnd;
@ -203,6 +205,8 @@ protected:
int m_nRetinaFactor;
// @note RobTop Addition
bool m_bCursorHidden;
// @note may be before m_bCursorHidden
int m_unkSize4;
// Robtop Removal
// LPCWSTR m_menu;
// Robtop Removal
@ -225,6 +229,8 @@ public:
// @note RobTop Addition
bool m_bIsBorderless;
// @note RobTop Addition
bool m_bIsFix;
// @note RobTop Addition
bool m_bShouldHideCursor;
// @note RobTop Addition
bool m_bCursorLocked;

View file

@ -208,9 +208,9 @@ namespace cocos2d
/**
* Custom function added for geode; returns if the
* zip file was succesfully decoded.
* zip file was successfully decoded.
*
* @return true if the zip was succesfully loaded,
* @return true if the zip was successfully loaded,
* false otherwise.
*
* @since geode v1.0.0

File diff suppressed because it is too large Load diff

View file

@ -53,7 +53,7 @@ namespace geode {
return dispatchPools()[m_id];
}
ListenerResult handle(utils::MiniFunction<Callback> fn, Ev* event) {
ListenerResult handle(std::function<Callback> fn, Ev* event) {
if (event->getID() == m_id) {
return std::apply(fn, event->getArgs());
}

View file

@ -1,7 +1,6 @@
#pragma once
#include "../utils/casts.hpp"
#include "../utils/MiniFunction.hpp"
#include <Geode/DefaultInclude.hpp>
#include <type_traits>
@ -52,6 +51,7 @@ namespace geode {
private:
static DefaultEventListenerPool* create();
DefaultEventListenerPool();
public:
bool add(EventListenerProtocol* listener) override;
@ -64,10 +64,7 @@ namespace geode {
friend class DispatchEvent;
template <class... Args>
friend class DispatchFilter;
// todo: make this private in Geode 4.0.0
DefaultEventListenerPool();
friend class DispatchFilter;
};
class GEODE_DLL EventListenerProtocol {
@ -103,7 +100,7 @@ namespace geode {
using Callback = ListenerResult(T*);
using Event = T;
ListenerResult handle(utils::MiniFunction<Callback> fn, T* e) {
ListenerResult handle(std::function<Callback> fn, T* e) {
return fn(e);
}
@ -156,7 +153,7 @@ namespace geode {
this->enable();
}
EventListener(utils::MiniFunction<Callback> fn, T filter = T())
EventListener(std::function<Callback> fn, T filter = T())
: m_callback(fn), m_filter(filter)
{
m_filter.setListener(this);
@ -193,10 +190,7 @@ namespace geode {
this->enable();
}
void bind(utils::MiniFunction<Callback> const& fn) {
m_callback = fn;
}
void bind(utils::MiniFunction<Callback>&& fn) {
void bind(std::function<Callback> fn) {
m_callback = fn;
}
@ -218,12 +212,12 @@ namespace geode {
return m_filter;
}
utils::MiniFunction<Callback>& getCallback() {
std::function<Callback>& getCallback() {
return m_callback;
}
protected:
utils::MiniFunction<Callback> m_callback = nullptr;
std::function<Callback> m_callback = nullptr;
T m_filter;
};

View file

@ -57,7 +57,7 @@ namespace geode::ipc {
std::string m_messageID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, IPCEvent* event);
ListenerResult handle(std::function<Callback> fn, IPCEvent* event);
IPCFilter(
std::string const& modID,
std::string const& messageID

View file

@ -1,8 +1,7 @@
#pragma once
#include <filesystem>
#include "../utils/Result.hpp"
#include "../utils/MiniFunction.hpp"
#include <Geode/Result.hpp>
#include "Log.hpp"
#include "ModEvent.hpp"
#include "ModMetadata.hpp"
@ -15,7 +14,7 @@
#include <string_view>
namespace geode {
using ScheduledFunction = utils::MiniFunction<void()>;
using ScheduledFunction = std::function<void()>;
struct InvalidGeodeFile {
std::filesystem::path path;
@ -47,6 +46,21 @@ namespace geode {
Type type;
std::variant<std::filesystem::path, ModMetadata, Mod*> cause;
std::string message;
bool isSuggestion() const {
return
type == LoadProblem::Type::Recommendation ||
type == LoadProblem::Type::Suggestion;
}
bool isOutdated() const {
return
type == LoadProblem::Type::UnsupportedVersion ||
type == LoadProblem::Type::NeedsNewerGeodeVersion ||
type == LoadProblem::Type::UnsupportedGeodeVersion;
}
bool isProblem() const {
return !isSuggestion() && !isOutdated();
}
};
class LoaderImpl;
@ -92,7 +106,8 @@ namespace geode {
Mod* getLoadedMod(std::string const& id) const;
std::vector<Mod*> getAllMods();
std::vector<LoadProblem> getAllProblems() const;
std::vector<LoadProblem> getProblems() const;
std::vector<LoadProblem> getLoadProblems() const;
std::vector<LoadProblem> getOutdated() const;
std::vector<LoadProblem> getRecommendations() const;
/**
@ -103,41 +118,36 @@ namespace geode {
* Returns whether the specified launch argument was passed in via the command line.
* @param name The argument name
*/
bool hasLaunchArgument(std::string_view const name) const;
bool hasLaunchArgument(std::string_view name) const;
/**
* Get a launch argument. These are passed into the game as command-line arguments
* with the format `--geode:arg-name=value`.
* @param name The argument name
* @return The value, if present
*/
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view name) const;
/**
* Get a launch argument flag. Returns whether the argument is present and its
* value is exactly `true`.
* @param name The argument name
*/
bool getLaunchFlag(std::string_view const name) const;
bool getLaunchFlag(std::string_view name) const;
/**
* Get and parse a launch argument value using the setting value system.
* @param name The argument name
*/
template <class T>
std::optional<T> parseLaunchArgument(std::string_view const name) const {
Result<T> parseLaunchArgument(std::string_view name) const {
auto str = this->getLaunchArgument(name);
if (!str.has_value()) {
return std::nullopt;
return Err(fmt::format("Launch argument '{}' not found", name));
}
std::string parseError;
auto jsonOpt = matjson::parse(str.value(), parseError);
if (!jsonOpt.has_value()) {
log::debug("Parsing launch argument '{}' failed: {}", name, parseError);
return std::nullopt;
auto jsonOpt = matjson::Value::parse(str.value());
if (jsonOpt.isErr()) {
return Err(fmt::format("Parsing launch argument '{}' failed: {}", name, jsonOpt.unwrapErr()));
}
auto value = jsonOpt.value();
if (!value.is<T>()) {
return std::nullopt;
}
return value.as<T>();
auto value = jsonOpt.unwrap();
return value.template as<T>();
}
void queueInMainThread(ScheduledFunction&& func);

View file

@ -65,12 +65,6 @@ namespace std::filesystem {
}
}
namespace matjson {
GEODE_INLINE GEODE_HIDDEN std::string format_as(matjson::Value const& value) {
return value.dump(matjson::NO_INDENTATION);
}
}
namespace geode {
class Mod;
@ -120,6 +114,37 @@ namespace geode {
popNest(getMod());
}
struct NestScope {
private:
bool m_active = true;
public:
NestScope() {
pushNest();
}
NestScope(NestScope const&) {
pushNest();
}
NestScope(NestScope&& other) {
other.m_active = false;
}
NestScope& operator=(NestScope const&) {
pushNest();
return *this;
}
NestScope& operator=(NestScope&& other) {
other.m_active = false;
return *this;
}
~NestScope() {
if (m_active) popNest();
}
};
class Nest final {
private:
class Impl;

View file

@ -2,7 +2,7 @@
#include "../DefaultInclude.hpp"
#include "../cocos/support/zip_support/ZipUtils.h"
#include "../utils/Result.hpp"
#include <Geode/Result.hpp>
#include "../utils/VersionInfo.hpp"
#include "../utils/general.hpp"
@ -10,7 +10,6 @@
#include "Hook.hpp"
#include "ModMetadata.hpp"
#include "Setting.hpp"
#include "SettingV3.hpp"
#include "Types.hpp"
#include "Loader.hpp"
@ -23,9 +22,7 @@
#include <unordered_map>
#include <vector>
namespace geode {
class SettingV3;
namespace geode {
template <class T>
struct HandleToSaved : public T {
Mod* m_mod;
@ -44,7 +41,8 @@ namespace geode {
Enable,
Disable,
Uninstall,
UninstallWithSaveData
UninstallWithSaveData,
Update
};
static constexpr bool modRequestedActionIsToggle(ModRequestedAction action) {
@ -54,22 +52,6 @@ namespace geode {
return action == ModRequestedAction::Uninstall || action == ModRequestedAction::UninstallWithSaveData;
}
template <class T>
static consteval bool typeImplementsIsJSON() {
using namespace matjson;
if constexpr (requires(const Value& json) { Serialize<std::decay_t<T>>::is_json(json); })
return true;
if constexpr (std::is_same_v<T, Value>) return true;
if constexpr (std::is_same_v<T, Array>) return true;
if constexpr (std::is_same_v<T, Object>) return true;
if constexpr (std::is_constructible_v<std::string, T>) return true;
if constexpr (std::is_integral_v<T> || std::is_floating_point_v<T>) return true;
if constexpr (std::is_same_v<T, bool>) return true;
if constexpr (std::is_same_v<T, std::nullptr_t>) return true;
return false;
}
GEODE_HIDDEN Mod* takeNextLoaderMod();
class ModImpl;
@ -108,8 +90,6 @@ namespace geode {
std::string getID() const;
std::string getName() const;
[[deprecated("Use Mod::getDevelopers instead")]]
std::string getDeveloper() const;
std::vector<std::string> getDevelopers() const;
std::optional<std::string> getDescription() const;
std::optional<std::string> getDetails() const;
@ -137,17 +117,6 @@ namespace geode {
std::vector<Mod*> getDependants() const;
#endif
/**
* Check if this Mod has updates available on the mods index. If
* you're using this for automatic update checking, use
* `openInfoPopup` or `openIndexPopup` from the `ui/GeodeUI.hpp`
* header to open the Mod's page to let the user install the update
* @returns The latest available version on the index if there are
* updates for this mod
*/
[[deprecated("Use Mod::checkUpdates instead; this function always returns nullopt")]]
std::optional<VersionInfo> hasAvailableUpdate() const;
using CheckUpdatesTask = Task<Result<std::optional<VersionInfo>, std::string>>;
/**
* Check if this Mod has updates available on the mods index. If
@ -187,52 +156,15 @@ namespace geode {
* declared in `mod.json`)
*/
std::vector<std::string> getSettingKeys() const;
bool hasSetting(std::string_view const key) const;
bool hasSetting(std::string_view key) const;
// todo in v4: remove these
[[deprecated("Use Mod::getSettingV3")]]
std::optional<Setting> getSettingDefinition(std::string_view const key) const;
[[deprecated("Use Mod::getSettingV3")]]
SettingValue* getSetting(std::string_view const key) const;
// todo in v4: possibly rename this to getSetting?
/**
* Get the definition of a setting, or null if the setting was not found,
* or if it's a custom setting that has not yet been registered using
* `Mod::registerCustomSettingType`
* @param key The key of the setting as defined in `mod.json`
*/
std::shared_ptr<SettingV3> getSettingV3(std::string_view const key) const;
/**
* Register a custom setting's value class. See Mod::addCustomSetting
* for a convenience wrapper that creates the value in-place to avoid
* code duplication. Also see
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for
* more information about custom settings
* @param key The setting's key
* @param value The SettingValue class that shall handle this setting
* @see addCustomSetting
*/
[[deprecated("Use Mod::registerCustomSettingType")]]
void registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value);
/**
* Register a custom setting's value class. The new SettingValue class
* will be created in-place using `std::make_unique`. See
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for
* more information about custom settings
* @param key The setting's key
* @param value The value of the custom setting
* @example
* $on_mod(Loaded) {
* Mod::get()->addCustomSetting<MySettingValue>("setting-key", DEFAULT_VALUE);
* }
*/
template <class T, class V>
[[deprecated("Use Mod::registerCustomSettingType")]]
void addCustomSetting(std::string_view const key, V const& value) {
this->registerCustomSetting(key, std::make_unique<T>(std::string(key), this->getID(), value));
}
std::shared_ptr<Setting> getSetting(std::string_view key) const;
/**
* Register a custom setting type. See
@ -248,7 +180,7 @@ namespace geode {
* Returns a prefixed launch argument name. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
*/
std::string getLaunchArgumentName(std::string_view const name) const;
std::string getLaunchArgumentName(std::string_view name) const;
/**
* Returns the names of the available mod-specific launch arguments.
*/
@ -258,7 +190,7 @@ namespace geode {
* for details about mod-specific launch arguments.
* @param name The argument name
*/
bool hasLaunchArgument(std::string_view const name) const;
bool hasLaunchArgument(std::string_view name) const;
/**
* Get a mod-specific launch argument. This is equivalent to `Loader::getLaunchArgument`
* with the argument name prefixed by the mod ID. For example, a launch argument named
@ -266,20 +198,20 @@ namespace geode {
* @param name The argument name
* @return The value, if present
*/
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view name) const;
/**
* Equivalent to a prefixed `Loader::getLaunchFlag` call. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
* @param name The argument name
*/
bool getLaunchFlag(std::string_view const name) const;
bool getLaunchFlag(std::string_view name) const;
/**
* Equivalent to a prefixed `Loader::parseLaunchArgument` call. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments.
* @param name The argument name
*/
template <class T>
std::optional<T> parseLaunchArgument(std::string_view const name) const {
std::optional<T> parseLaunchArgument(std::string_view name) const {
return Loader::get()->parseLaunchArgument<T>(this->getLaunchArgumentName(name));
}
@ -293,18 +225,18 @@ namespace geode {
* setting type has a `getValue` function which returns the value
*/
template <class T>
T getSettingValue(std::string_view const key) const {
T getSettingValue(std::string_view key) const {
using S = typename SettingTypeForValueType<T>::SettingType;
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSettingV3(key))) {
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSetting(key))) {
return sett->getValue();
}
return T();
}
template <class T>
T setSettingValue(std::string_view const key, T const& value) {
T setSettingValue(std::string_view key, T const& value) {
using S = typename SettingTypeForValueType<T>::SettingType;
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSettingV3(key))) {
if (auto sett = cast::typeinfo_pointer_cast<S>(this->getSetting(key))) {
auto old = sett->getValue();
sett->setValue(value);
return old;
@ -312,30 +244,28 @@ namespace geode {
return T();
}
bool hasSavedValue(std::string_view const key);
bool hasSavedValue(std::string_view key);
template <class T>
T getSavedValue(std::string_view const key) {
static_assert(geode::typeImplementsIsJSON<T>(), "T must implement is_json in matjson::Serialize<T>, otherwise this always returns default value.");
T getSavedValue(std::string_view key) {
auto& saved = this->getSaveContainer();
if (saved.contains(key)) {
if (auto value = saved.try_get<T>(key)) {
return *value;
}
if (auto res = saved.get(key).andThen([](auto&& v) {
return v.template as<T>();
}); res.isOk()) {
return res.unwrap();
}
return T();
}
template <class T>
T getSavedValue(std::string_view const key, T const& defaultValue) {
static_assert(geode::typeImplementsIsJSON<T>(), "T must implement is_json in matjson::Serialize<T>, otherwise this always returns default value.");
T getSavedValue(std::string_view key, T const& defaultValue) {
auto& saved = this->getSaveContainer();
if (saved.contains(key)) {
if (auto value = saved.try_get<T>(key)) {
return *value;
}
if (auto res = saved.get(key).andThen([](auto&& v) {
return v.template as<T>();
}); res.isOk()) {
return res.unwrap();
}
saved[key] = defaultValue;
saved[key] = matjson::Value(defaultValue);
return defaultValue;
}
@ -347,7 +277,7 @@ namespace geode {
* @returns The old value
*/
template <class T>
T setSavedValue(std::string_view const key, T const& value) {
T setSavedValue(std::string_view key, T const& value) {
auto& saved = this->getSaveContainer();
auto old = this->getSavedValue<T>(key);
saved[key] = value;
@ -488,7 +418,7 @@ namespace geode {
* Check whether or not this Mod
* depends on another mod
*/
bool depends(std::string_view const id) const;
bool depends(std::string_view id) const;
/**
* Check whether all the required
@ -517,7 +447,17 @@ namespace geode {
bool isLoggingEnabled() const;
void setLoggingEnabled(bool enabled);
bool hasProblems() const;
/**
* If this mod is built for an outdated GD or Geode version, returns the
* `LoadProblem` describing the situation. Otherwise `nullopt` if the
* mod is made for the correct version of the game and Geode
*/
std::optional<LoadProblem> targetsOutdatedVersion() const;
/**
* @note Make sure to also call `targetsOutdatedVersion` if you want to
* make sure the mod is actually loadable
*/
bool hasLoadProblems() const;
std::vector<LoadProblem> getAllProblems() const;
std::vector<LoadProblem> getProblems() const;
std::vector<LoadProblem> getRecommendations() const;

View file

@ -10,9 +10,6 @@ namespace geode {
enum class ModEventType {
Loaded,
Unloaded,
Enabled,
Disabled,
DataLoaded,
DataSaved,
};
@ -43,7 +40,7 @@ namespace geode {
Mod* m_mod;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, ModStateEvent* event);
ListenerResult handle(std::function<Callback> fn, ModStateEvent* event);
/**
* Create a mod state listener

View file

@ -1,8 +1,7 @@
#pragma once
#include "../utils/Result.hpp"
#include <Geode/Result.hpp>
#include "../utils/VersionInfo.hpp"
#include "Setting.hpp"
#include "Types.hpp"
#include <matjson.hpp>
@ -123,13 +122,6 @@ namespace geode {
* character set.
*/
[[nodiscard]] std::string getName() const;
/**
* The name of the head developer.
* If the mod has multiple * developers, this will return the first
* developer in the list.
*/
[[nodiscard, deprecated("Use ModMetadata::getDevelopers() instead")]]
std::string getDeveloper() const;
/**
* The developers of this mod
*/
@ -155,11 +147,6 @@ namespace geode {
* (see MDTextArea for more info)
*/
[[nodiscard]] std::optional<std::string> getSupportInfo() const;
/**
* Git Repository of the mod
*/
[[nodiscard, deprecated("Use ModMetadata::getLinks instead")]]
std::optional<std::string> getRepository() const;
/**
* Get the links (related websites / servers / etc.) for this mod
*/
@ -184,12 +171,7 @@ namespace geode {
* Mod settings
* @note Not a map because insertion order must be preserved
*/
[[nodiscard, deprecated("Use getSettingsV3")]] std::vector<std::pair<std::string, Setting>> getSettings() const;
/**
* Mod settings
* @note Not a map because insertion order must be preserved
*/
[[nodiscard]] std::vector<std::pair<std::string, matjson::Value>> getSettingsV3() const;
[[nodiscard]] std::vector<std::pair<std::string, matjson::Value>> getSettings() const;
/**
* Get the tags for this mod
*/
@ -216,9 +198,19 @@ namespace geode {
/**
* Checks if mod can be installed on the current GD version.
* Returns Ok() if it can, Err otherwise.
* Returns Ok() if it can, Err explaining why not otherwise.
*/
Result<> checkGameVersion() const;
/**
* Checks if mod can be installed on the current Geode version.
* Returns Ok() if it can, Err explaining why not otherwise.
*/
Result<> checkGeodeVersion() const;
/**
* Checks if mod can be installed on the current GD & Geode version.
* Returns Ok() if it can, Err explaining why not otherwise.
*/
Result<> checkTargetVersions() const;
#if defined(GEODE_EXPOSE_SECRET_INTERNALS_IN_HEADERS_DO_NOT_DEFINE_PLEASE)
void setPath(std::filesystem::path const& value);
@ -237,8 +229,6 @@ namespace geode {
void setDependencies(std::vector<Dependency> const& value);
void setIncompatibilities(std::vector<Incompatibility> const& value);
void setSpritesheets(std::vector<std::string> const& value);
[[deprecated("This function does NOTHING")]]
void setSettings(std::vector<std::pair<std::string, Setting>> const& value);
void setSettings(std::vector<std::pair<std::string, matjson::Value>> const& value);
void setTags(std::unordered_set<std::string> const& value);
void setNeedsEarlyLoad(bool const& value);
@ -310,7 +300,8 @@ namespace geode {
template <>
struct matjson::Serialize<geode::ModMetadata> {
static matjson::Value to_json(geode::ModMetadata const& info) {
return info.toJSON();
static Value toJson(geode::ModMetadata const& value)
{
return Value(value.toJSON());
}
};

View file

@ -1,7 +1,7 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include "SettingV3.hpp"
#include "Setting.hpp"
namespace geode {
class Mod;
@ -40,12 +40,8 @@ namespace geode {
* The format of the savedata will be an object with the keys being
* setting IDs and then the values the values of the saved settings
* @note If saving a setting fails, it will log a warning to the console
* @warning This will overwrite the whole `json` parameter - be sure to
* pass the full settings savedata to `load()` so you can be sure that
* unregistered custom settings' saved values don't disappear!
* @todo in v4: make this return the value instead lol
*/
void save(matjson::Value& json);
matjson::Value save();
/**
* Get the savedata for settings, aka the JSON object that contains all
@ -57,12 +53,8 @@ namespace geode {
matjson::Value& getSaveData();
Result<> registerCustomSettingType(std::string_view type, SettingGenerator generator);
// todo in v4: remove this
Result<> registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr);
std::shared_ptr<SettingV3> get(std::string_view key);
std::shared_ptr<SettingValue> getLegacy(std::string_view key);
std::optional<Setting> getLegacyDefinition(std::string_view key);
std::shared_ptr<Setting> get(std::string_view key);
/**
* Returns true if any setting with the `"restart-required"` attribute

View file

@ -1,324 +1,46 @@
#pragma once
#include "Types.hpp"
#include "../DefaultInclude.hpp"
#include "../utils/Result.hpp"
#include "../utils/file.hpp"
#include <matjson.hpp>
#include <optional>
#include <unordered_set>
#include <cocos2d.h>
#pragma warning(push)
#pragma warning(disable : 4275)
namespace geode {
class SettingNode;
class SettingValue;
struct JsonMaybeObject;
struct JsonMaybeValue;
/**
* A Setting for a boolean value. Represented in-game as a simple toggle
*/
struct GEODE_DLL BoolSetting final {
using ValueType = bool;
std::optional<std::string> name;
std::optional<std::string> description;
bool defaultValue;
static Result<BoolSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for an integer value. The value can be limited using the min
* and max options
*/
struct GEODE_DLL IntSetting final {
using ValueType = int64_t;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
std::optional<ValueType> min;
std::optional<ValueType> max;
struct {
bool arrows = true;
bool bigArrows = false;
size_t arrowStep = 1;
size_t bigArrowStep = 5;
bool slider = true;
std::optional<ValueType> sliderStep = std::nullopt;
bool input = true;
} controls;
static Result<IntSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for a float value. The value can be limited using the min
* and max options
*/
struct GEODE_DLL FloatSetting final {
using ValueType = double;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
std::optional<ValueType> min;
std::optional<ValueType> max;
struct {
bool arrows = true;
bool bigArrows = false;
size_t arrowStep = 1;
size_t bigArrowStep = 5;
bool slider = true;
std::optional<ValueType> sliderStep = std::nullopt;
bool input = true;
} controls;
static Result<FloatSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for a string value
*/
struct GEODE_DLL StringSetting final {
using ValueType = std::string;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
struct Data {
/**
* A regex the string must successfully match against
*/
std::optional<std::string> match;
/**
* The CCTextInputNode's allowed character filter
*/
std::optional<std::string> filter;
/**
* A list of options the user can choose from
*/
std::optional<std::vector<std::string>> options;
};
std::unique_ptr<Data> controls;
std::array<uint8_t, sizeof(Data::match) + sizeof(Data::filter) - sizeof(controls)> m_padding;
StringSetting();
StringSetting(StringSetting const& other);
StringSetting(StringSetting&& other) noexcept;
StringSetting& operator=(StringSetting const& other);
StringSetting& operator=(StringSetting&& other) noexcept;
~StringSetting();
static Result<StringSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for a file input. Lets the user select a file from their
* local device
*/
struct GEODE_DLL FileSetting final {
using ValueType = std::filesystem::path;
using Filter = utils::file::FilePickOptions::Filter;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
struct {
std::vector<Filter> filters;
} controls;
static Result<FileSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for an RGB color. See ColorAlphaSetting for a setting that
* also allows customizing alpha
*/
struct GEODE_DLL ColorSetting final {
using ValueType = cocos2d::ccColor3B;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
static Result<ColorSetting> parse(JsonMaybeObject& obj);
};
/**
* A Setting for an RGBA color. See ColorSetting for a setting that doesn't
* have alpha
*/
struct GEODE_DLL ColorAlphaSetting final {
using ValueType = cocos2d::ccColor4B;
std::optional<std::string> name;
std::optional<std::string> description;
ValueType defaultValue;
static Result<ColorAlphaSetting> parse(JsonMaybeObject& obj);
};
/**
* A custom setting, defined by the mod. See
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for more
* information about how to create custom settings
*/
struct GEODE_DLL CustomSetting final {
std::shared_ptr<ModJson> json;
};
using SettingKind = std::variant<
BoolSetting,
IntSetting,
FloatSetting,
StringSetting,
FileSetting,
ColorSetting,
ColorAlphaSetting,
CustomSetting
>;
/**
* Represents a saved value for a mod that can be customized by the user
* through an in-game UI. This class is for modeling the setting's
* definition - what values are accepted, its name etc.
* See [the tutorial page](https://docs.geode-sdk.org/mods/settings)
* for more information about how settings work
* @see SettingValue
* @see SettingNode
*/
struct GEODE_DLL Setting final {
private:
std::string m_key;
std::string m_modID;
SettingKind m_kind;
Setting() = default;
public:
static Result<Setting> parse(
std::string const& key,
std::string const& mod,
JsonMaybeValue& obj
);
Setting(
std::string const& key,
std::string const& mod,
SettingKind const& kind
);
template<class T>
std::optional<T> get() {
if (std::holds_alternative<T>(m_kind)) {
return std::get<T>(m_kind);
}
return std::nullopt;
}
std::unique_ptr<SettingValue> createDefaultValue() const;
bool isCustom() const;
std::string getDisplayName() const;
std::optional<std::string> getDescription() const;
std::string getModID() const;
};
/**
* Stores the actual, current value of a Setting. See
* [the tutorial page](https://docs.geode-sdk.org/mods/settings) for more
* information, and how to create custom settings
*/
class GEODE_DLL SettingValue {
protected:
std::string m_key;
std::string m_modID;
SettingValue(std::string const& key, std::string const& mod);
void valueChanged();
public:
virtual ~SettingValue() = default;
virtual bool load(matjson::Value const& json) = 0;
virtual bool save(matjson::Value& json) const = 0;
virtual SettingNode* createNode(float width) = 0;
std::string getKey() const;
std::string getModID() const;
};
template<class T>
class GeodeSettingValue final : public SettingValue {
public:
using ValueType = typename T::ValueType;
protected:
ValueType m_value;
T m_definition;
using Valid = std::pair<ValueType, std::optional<std::string>>;
GEODE_DLL Valid toValid(ValueType const& value) const;
public:
GeodeSettingValue(std::string const& key, std::string const& modID, T const& definition)
: SettingValue(key, modID),
m_definition(definition),
m_value(definition.defaultValue) {}
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const;
GEODE_DLL SettingNode* createNode(float width) override;
T castDefinition() const {
return m_definition;
}
Setting getDefinition() const {
return Setting(m_key, m_modID, m_definition);
}
GEODE_DLL ValueType getValue() const;
GEODE_DLL void setValue(ValueType const& value);
GEODE_DLL Result<> validate(ValueType const& value) const;
};
using BoolSettingValue = GeodeSettingValue<BoolSetting>;
using IntSettingValue = GeodeSettingValue<IntSetting>;
using FloatSettingValue = GeodeSettingValue<FloatSetting>;
using StringSettingValue = GeodeSettingValue<StringSetting>;
using FileSettingValue = GeodeSettingValue<FileSetting>;
using ColorSettingValue = GeodeSettingValue<ColorSetting>;
using ColorAlphaSettingValue = GeodeSettingValue<ColorAlphaSetting>;
// todo: remove in v3
template<class T>
struct [[deprecated("Use SettingTypeForValueType from SettingV3 instead")]] GEODE_DLL SettingValueSetter {
static T get(SettingValue* setting);
static void set(SettingValue* setting, T const& value);
};
template<class T>
bool GeodeSettingValue<T>::load(matjson::Value const& json) {
if (!json.is<ValueType>()) return false;
m_value = json.as<ValueType>();
return true;
}
template<class T>
bool GeodeSettingValue<T>::save(matjson::Value& json) const {
json = m_value;
return true;
}
}
#pragma warning(pop)
#pragma once
#include "SettingV3.hpp"
namespace geode {
using Setting = SettingV3;
using SettingGenerator = SettingGeneratorV3;
template <class T, class V = T>
using SettingBaseValue = SettingBaseValueV3<T, V>;
using TitleSetting = TitleSettingV3;
using BoolSetting = BoolSettingV3;
using IntSetting = IntSettingV3;
using FloatSetting = FloatSettingV3;
using StringSetting = StringSettingV3;
using FileSetting = FileSettingV3;
using Color3BSetting = Color3BSettingV3;
using Color4BSetting = Color4BSettingV3;
using SettingNode = SettingNodeV3;
template <class S>
using SettingValueNode = SettingValueNodeV3<S>;
using SettingChangedEvent = SettingChangedEventV3;
using SettingChangedFilter = SettingChangedFilterV3;
using SettingNodeSizeChangeEvent = SettingNodeSizeChangeEventV3;
using SettingNodeValueChangeEvent = SettingNodeValueChangeEventV3;
template <class T, class Lambda>
EventListener<SettingChangedFilter>* listenForSettingChanges(std::string_view settingKey, Lambda&& callback, Mod* mod = getMod()) {
return listenForSettingChangesV3<T>(settingKey, std::forward<Lambda>(callback), mod);
}
template <class Lambda>
EventListener<SettingChangedFilter>* listenForSettingChanges(std::string_view settingKey, Lambda&& callback, Mod* mod = getMod()) {
return listenForSettingChangesV3(settingKey, std::forward<Lambda>(callback), mod);
}
inline EventListener<SettingChangedFilter>* listenForAllSettingChanges(
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
Mod* mod = getMod()
) {
return listenForAllSettingChangesV3(callback, mod);
}
}

View file

@ -1,63 +0,0 @@
#pragma once
#include "Event.hpp"
#include "Loader.hpp"
#include "Setting.hpp"
#include "Mod.hpp"
#include "SettingV3.hpp"
#include <optional>
namespace geode {
struct GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedEvent : public Event {
Mod* mod;
SettingValue* value;
SettingChangedEvent(Mod* mod, SettingValue* value);
};
class GEODE_DLL [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] SettingChangedFilter : public EventFilter<SettingChangedEvent> {
protected:
std::string m_modID;
std::optional<std::string> m_targetKey;
public:
using Callback = void(SettingValue*);
ListenerResult handle(utils::MiniFunction<Callback> fn, SettingChangedEvent* event);
/**
* Listen to changes on a setting, or all settings
* @param modID Mod whose settings to listen to
* @param settingID Setting to listen to, or all settings if nullopt
*/
SettingChangedFilter(
std::string const& modID,
std::optional<std::string> const& settingKey
);
SettingChangedFilter(SettingChangedFilter const&) = default;
};
/**
* Listen for built-in setting changes
*/
template<class T>
class [[deprecated("Use SettingChangedEventV3 from SettingV3.hpp instead")]] GeodeSettingChangedFilter : public SettingChangedFilter {
public:
using Callback = void(T);
ListenerResult handle(utils::MiniFunction<Callback> fn, SettingChangedEvent* event) {
if (
m_modID == event->mod->getID() &&
(!m_targetKey || m_targetKey.value() == event->value->getKey())
) {
fn(SettingValueSetter<T>::get(event->value));
}
return ListenerResult::Propagate;
}
GeodeSettingChangedFilter(
std::string const& modID,
std::string const& settingID
) : SettingChangedFilter(modID, settingID) {}
GeodeSettingChangedFilter(GeodeSettingChangedFilter const&) = default;
};
}

View file

@ -1,32 +0,0 @@
#pragma once
#include "Setting.hpp"
#include <cocos2d.h>
namespace geode {
class SettingNode;
struct SettingNodeDelegate {
virtual void settingValueChanged(SettingNode* node) {}
virtual void settingValueCommitted(SettingNode* node) {}
};
class GEODE_DLL SettingNode : public cocos2d::CCNode {
protected:
SettingValue* m_value;
SettingNodeDelegate* m_delegate = nullptr;
bool init(SettingValue* value);
void dispatchChanged();
void dispatchCommitted();
public:
void setDelegate(SettingNodeDelegate* delegate);
virtual void commit() = 0;
virtual bool hasUncommittedChanges() = 0;
virtual bool hasNonDefaultValue() = 0;
virtual void resetToDefault() = 0;
};
}

View file

@ -3,22 +3,17 @@
#include "../DefaultInclude.hpp"
#include <optional>
#include <cocos2d.h>
// todo: remove this header in 4.0.0
#include "Setting.hpp"
#include "../utils/cocos.hpp"
#include "../utils/file.hpp"
// this unfortunately has to be included because of C++ templates
#include "../utils/JsonValidation.hpp"
#include "../utils/function.hpp"
// todo in v4: this can be removed as well as the friend decl in LegacyCustomSettingV3
class LegacyCustomSettingToV3Node;
class ModSettingsPopup;
namespace geode {
class ModSettingsManager;
class SettingNodeV3;
// todo in v4: remove this
class SettingValue;
class GEODE_DLL SettingV3 : public std::enable_shared_from_this<SettingV3> {
private:
@ -125,8 +120,6 @@ namespace geode {
*/
void markChanged();
friend class ::geode::SettingValue;
public:
SettingV3();
virtual ~SettingV3();
@ -183,20 +176,9 @@ namespace geode {
* Reset this setting's value back to its original value
*/
virtual void reset() = 0;
[[deprecated(
"This function will be removed alongside legacy settings in 4.0.0! "
"You should NOT be implementing it for your own custom setting classes"
)]]
virtual std::optional<Setting> convertToLegacy() const;
[[deprecated(
"This function will be removed alongside legacy settings in 4.0.0! "
"You should NOT be implementing it for your own custom setting classes"
)]]
virtual std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const;
};
using SettingGenerator = std::function<Result<std::shared_ptr<SettingV3>>(
using SettingGeneratorV3 = std::function<Result<std::shared_ptr<SettingV3>>(
std::string const& key,
std::string const& modID,
matjson::Value const& json
@ -324,14 +306,15 @@ namespace geode {
}
bool load(matjson::Value const& json) override {
if (json.is<T>()) {
m_impl->value = json.as<T>();
return true;
auto res = json.as<T>();
if (res.isErr()) {
return false;
}
return false;
m_impl->value = res.unwrap();
return true;
}
bool save(matjson::Value& json) const override {
json = m_impl->value;
json = matjson::Value(m_impl->value);
return true;
}
};
@ -357,37 +340,6 @@ namespace geode {
void reset() override;
};
// todo in v4: remove this class completely
class GEODE_DLL LegacyCustomSettingV3 final : public SettingV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
friend class ::geode::ModSettingsManager;
friend class ::LegacyCustomSettingToV3Node;
private:
class PrivateMarker {};
friend class SettingV3;
public:
LegacyCustomSettingV3(PrivateMarker);
static Result<std::shared_ptr<LegacyCustomSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
std::shared_ptr<SettingValue> getValue() const;
void setValue(std::shared_ptr<SettingValue> value);
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL BoolSettingV3 final : public SettingBaseValueV3<bool> {
private:
class Impl;
@ -404,9 +356,6 @@ namespace geode {
Result<> isValid(bool value) const override;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL IntSettingV3 final : public SettingBaseValueV3<int64_t> {
@ -436,9 +385,6 @@ namespace geode {
bool isInputEnabled() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FloatSettingV3 final : public SettingBaseValueV3<double> {
@ -468,9 +414,6 @@ namespace geode {
bool isInputEnabled() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL StringSettingV3 final : public SettingBaseValueV3<std::string, std::string_view> {
@ -493,9 +436,6 @@ namespace geode {
std::optional<std::vector<std::string>> getEnumOptions() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FileSettingV3 final : public SettingBaseValueV3<std::filesystem::path, std::filesystem::path const&> {
@ -519,9 +459,6 @@ namespace geode {
std::optional<std::vector<utils::file::FilePickOptions::Filter>> getFilters() const;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color3BSettingV3 final : public SettingBaseValueV3<cocos2d::ccColor3B> {
@ -540,9 +477,6 @@ namespace geode {
Result<> isValid(cocos2d::ccColor3B value) const override;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color4BSettingV3 final : public SettingBaseValueV3<cocos2d::ccColor4B> {
@ -561,9 +495,6 @@ namespace geode {
Result<> isValid(cocos2d::ccColor4B value) const override;
SettingNodeV3* createNode(float width) override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL SettingNodeV3 : public cocos2d::CCNode {
@ -724,7 +655,7 @@ namespace geode {
public:
using Callback = void(std::shared_ptr<SettingV3>);
ListenerResult handle(utils::MiniFunction<Callback> fn, SettingChangedEventV3* event);
ListenerResult handle(std::function<Callback> fn, SettingChangedEventV3* event);
/**
* Listen to changes on a setting, or all settings
* @param modID Mod whose settings to listen to
@ -800,7 +731,7 @@ namespace geode {
};
template <class T>
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
EventListener<SettingChangedFilterV3>* listenForSettingChangesV3(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
using Ty = typename SettingTypeForValueType<T>::SettingType;
return new EventListener(
[callback = std::move(callback)](std::shared_ptr<SettingV3> setting) {
@ -811,11 +742,11 @@ namespace geode {
SettingChangedFilterV3(mod, std::string(settingKey))
);
}
EventListener<SettingChangedFilterV3>* listenForSettingChanges(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
EventListener<SettingChangedFilterV3>* listenForSettingChangesV3(std::string_view settingKey, auto&& callback, Mod* mod = getMod()) {
using T = std::remove_cvref_t<utils::function::Arg<0, decltype(callback)>>;
return listenForSettingChanges<T>(settingKey, std::move(callback), mod);
return listenForSettingChangesV3<T>(settingKey, std::move(callback), mod);
}
GEODE_DLL EventListener<SettingChangedFilterV3>* listenForAllSettingChanges(
GEODE_DLL EventListener<SettingChangedFilterV3>* listenForAllSettingChangesV3(
std::function<void(std::shared_ptr<SettingV3>)> const& callback,
Mod* mod = getMod()
);

View file

@ -2,12 +2,13 @@
#include <Geode/platform/platform.hpp>
#include <tulip/TulipHook.hpp>
#include "../Prelude.hpp"
namespace geode::hook {
/**
* Create a calling convention wrapper for a function.
*/
GEODE_DLL tulip::hook::Result<void*> createWrapper(
GEODE_DLL Result<void*> createWrapper(
void* address,
tulip::hook::WrapperMetadata const& metadata
) noexcept;

View file

@ -89,14 +89,11 @@ namespace geode {
constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
class Mod;
class Setting;
class Loader;
class Hook;
class VersionInfo;
class Unknown;
using unknownmemfn_t = void (Unknown::*)();
using unknownfn_t = void (*)();
namespace modifier {
template <class, class>

View file

@ -1,6 +1,5 @@
#pragma once
#include "../utils/MiniFunction.hpp"
#include "Traits.hpp"
#include <Geode/loader/Loader.hpp>
@ -20,7 +19,7 @@ namespace geode::modifier {
class FieldContainer {
private:
std::vector<void*> m_containedFields;
std::vector<utils::MiniFunction<void(void*)>> m_destructorFunctions;
std::vector<std::function<void(void*)>> m_destructorFunctions;
public:
~FieldContainer() {
@ -40,9 +39,9 @@ namespace geode::modifier {
return m_containedFields.at(index);
}
void* setField(size_t index, size_t size, utils::MiniFunction<void(void*)> destructor) {
void* setField(size_t index, size_t size, std::function<void(void*)> destructor) {
m_containedFields.at(index) = operator new(size);
m_destructorFunctions.at(index) = destructor;
m_destructorFunctions.at(index) = std::move(destructor);
return m_containedFields.at(index);
}

View file

@ -104,8 +104,36 @@
} \
} while (0);
namespace geode::modifier {
namespace geode {
class Priority {
public:
static inline constexpr int32_t First = -3000;
static inline constexpr int32_t VeryEarly = -2000;
static inline constexpr int32_t Early = -1000;
static inline constexpr int32_t Normal = 0;
static inline constexpr int32_t Late = 1000;
static inline constexpr int32_t VeryLate = 2000;
static inline constexpr int32_t Last = 3000;
static inline constexpr int32_t FirstPre = First;
static inline constexpr int32_t VeryEarlyPre = VeryEarly;
static inline constexpr int32_t EarlyPre = Early;
static inline constexpr int32_t NormalPre = Normal;
static inline constexpr int32_t LatePre = Late;
static inline constexpr int32_t VeryLatePre = VeryLate;
static inline constexpr int32_t LastPre = Last;
static inline constexpr int32_t FirstPost = Last;
static inline constexpr int32_t VeryEarlyPost = VeryLate;
static inline constexpr int32_t EarlyPost = Late;
static inline constexpr int32_t NormalPost = Normal;
static inline constexpr int32_t LatePost = Early;
static inline constexpr int32_t VeryLatePost = VeryEarly;
static inline constexpr int32_t LastPost = First;
};
}
namespace geode::modifier {
template <class Derived, class Base>
class ModifyDerive;
@ -114,22 +142,179 @@ namespace geode::modifier {
public:
std::map<std::string, std::shared_ptr<Hook>> m_hooks;
Result<Hook*> getHook(std::string const& name) {
if (m_hooks.find(name) == m_hooks.end()) {
/// @brief Get a hook by name
/// @param name The name of the hook to get
/// @returns Ok if the hook was found, Err if the hook was not found
Result<Hook*> getHook(std::string_view name) {
auto key = std::string(name);
if (m_hooks.find(key) == m_hooks.end()) {
return Err("Hook not in this modify");
}
return Ok(m_hooks[name].get());
return Ok(m_hooks[key].get());
}
Result<> setHookPriority(std::string const& name, int32_t priority) {
auto res = this->getHook(name);
if (!res) {
return Err(res.unwrapErr());
}
res.unwrap()->setPriority(priority);
/// @brief Set the priority of a hook
/// @param name The name of the hook to set the priority of
/// @param priority The priority to set the hook to
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriority(std::string_view name, int32_t priority = Priority::Normal) {
GEODE_UNWRAP_INTO(auto hook, this->getHook(name));
hook->setPriority(priority);
return Ok();
}
/// @brief Set the priority of a hook
/// @param name The name of the hook to set the priority of
/// @param priority The priority to set the hook to
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityPre(std::string_view name, int32_t priority = Priority::Normal) {
return this->setHookPriority(name, priority);
}
/// @brief Set the priority of a hook
/// @param name The name of the hook to set the priority of
/// @param priority The priority to set the hook to
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityPost(std::string_view name, int32_t priority = Priority::Normal) {
return this->setHookPriority(name, -priority);
}
/// @brief Set the priority of a hook to be after another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param after The mod to set the priority after
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityAfter(std::string_view name, Mod* mod) {
GEODE_UNWRAP_INTO(auto hook, this->getHook(name));
auto func = [=](ModStateEvent* event){
auto hooks = mod->getHooks();
for (auto modHook : hooks) {
if (modHook->getAddress() != hook->getAddress()) continue;
auto priority = modHook->getPriority();
if (hook->getPriority() <= priority) {
hook->setPriority(priority + 1);
}
}
return ListenerResult::Propagate;
};
if (mod->isEnabled()) {
func(nullptr);
}
else {
new EventListener(func, ModStateFilter(mod, ModEventType::Loaded));
}
return Ok();
}
/// @brief Set the priority of a hook to be after another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param after The mod id of the mod to set the priority after
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityAfter(std::string_view name, std::string_view after) {
auto mod = Loader::get()->getInstalledMod(std::string(after));
if (!mod) return Err("Mod not found");
return this->setHookPriorityAfter(name, mod);
}
/// @brief Set the priority of a hook to be before another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod to set the priority before
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityBefore(std::string_view name, Mod* mod) {
GEODE_UNWRAP_INTO(auto hook, this->getHook(name));
auto func = [=](ModStateEvent* event){
auto hooks = mod->getHooks();
for (auto modHook : hooks) {
if (modHook->getAddress() != hook->getAddress()) continue;
auto priority = modHook->getPriority();
if (hook->getPriority() >= priority) {
hook->setPriority(priority - 1);
}
}
return ListenerResult::Propagate;
};
if (mod->isEnabled()) {
func(nullptr);
}
else {
new EventListener(func, ModStateFilter(mod, ModEventType::Loaded));
}
return Ok();
}
/// @brief Set the priority of a hook to be before another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod id of the mod to set the priority before
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityBefore(std::string_view name, std::string_view before) {
auto mod = Loader::get()->getInstalledMod(std::string(before));
if (!mod) return Err("Mod not found");
return this->setHookPriorityBefore(name, mod);
}
/// @brief Set the priority of a hook to be after another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param after The mod id of the mod to set the priority after
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityAfterPre(std::string_view name, std::string_view after) {
return this->setHookPriorityAfter(name, after);
}
/// @brief Set the priority of a hook to be after another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod to set the priority after
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityAfterPre(std::string_view name, Mod* mod) {
return this->setHookPriorityAfter(name, mod);
}
/// @brief Set the priority of a hook to be before another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod id of the mod to set the priority before
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityBeforePre(std::string_view name, std::string_view before) {
return this->setHookPriorityBefore(name, before);
}
/// @brief Set the priority of a hook to be before another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod to set the priority before
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityBeforePre(std::string_view name, Mod* mod) {
return this->setHookPriorityBefore(name, mod);
}
/// @brief Set the priority of a hook to be after another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param after The mod id of the mod to set the priority after
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityAfterPost(std::string_view name, std::string_view after) {
return this->setHookPriorityBefore(name, after);
}
/// @brief Set the priority of a hook to be after another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod to set the priority after
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityAfterPost(std::string_view name, Mod* mod) {
return this->setHookPriorityBefore(name, mod);
}
/// @brief Set the priority of a hook to be before another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod id of the mod to set the priority before
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityBeforePost(std::string_view name, std::string_view before) {
return this->setHookPriorityAfter(name, before);
}
/// @brief Set the priority of a hook to be before another hook in different mods
/// @param name The name of the hook to set the priority of
/// @param before The mod to set the priority before
/// @returns Ok if the hook was found and the priority was set, Err if the hook was not found
Result<> setHookPriorityBeforePost(std::string_view name, Mod* mod) {
return this->setHookPriorityAfter(name, mod);
}
// unordered_map<handles> idea
ModifyBase() {
struct EboCheck : ModifyDerived::Base {
@ -158,7 +343,7 @@ namespace geode::modifier {
for (auto& [uuid, hook] : m_hooks) {
auto res = Mod::get()->claimHook(hook);
if (!res) {
log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.error());
log::error("Failed to claim hook {}: {}", hook->getDisplayName(), res.unwrapErr());
}
else {
added.push_back(uuid);

View file

@ -11,6 +11,8 @@
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) || defined(WIN64) || defined(_WIN64) || defined(__WIN64) && !defined(__CYGWIN__)
#define GEODE_WINDOWS(...) __VA_ARGS__
#define GEODE_DESKTOP(...) __VA_ARGS__
#define GEODE_MOBILE(...)
#define GEODE_IS_WINDOWS
#define GEODE_IS_DESKTOP
#define GEODE_PLATFORM_NAME "Windows"
@ -43,6 +45,8 @@
#define GEODE_INTEL_MAC(...)
#define GEODE_ARM_MAC(...)
#define GEODE_IOS(...) __VA_ARGS__
#define GEODE_DESKTOP(...)
#define GEODE_MOBILE(...) __VA_ARGS__
#define GEODE_IS_IOS
#define GEODE_IS_MOBILE
#define GEODE_PLATFORM_NAME "iOS"
@ -53,6 +57,8 @@
#else
#define GEODE_IOS(...)
#define GEODE_MACOS(...) __VA_ARGS__
#define GEODE_DESKTOP(...) __VA_ARGS__
#define GEODE_MOBILE(...)
#define GEODE_IS_MACOS
#define GEODE_IS_DESKTOP
#define GEODE_PLATFORM_EXTENSION ".dylib"
@ -84,6 +90,8 @@
// Android
#if defined(__ANDROID__)
#define GEODE_ANDROID(...) __VA_ARGS__
#define GEODE_MOBILE(...) __VA_ARGS__
#define GEODE_DESKTOP(...)
#define GEODE_IS_ANDROID
#define GEODE_IS_MOBILE
#define GEODE_CALL

View file

@ -116,14 +116,22 @@ namespace geode {
public:
// todo in v4: make these flags and add archless Mac and Android as well as Desktop and Mobile and remove Linux
enum {
Unknown = -1,
Windows = 0,
MacIntel = 1,
MacArm = 2,
iOS = 3,
Android32 = 4,
Android64 = 5,
Linux = 6,
Unknown = 0b000000,
Windows = 0b000001,
Android32 = 0b000010,
Android64 = 0b000100,
MacIntel = 0b001000,
MacArm = 0b010000,
iOS = 0b100000,
Android = Android32 | Android64,
Mac = MacIntel | MacArm,
Apple = Mac | iOS,
X64 = MacIntel | Windows,
X86 = Unknown,
ArmV7 = Android32,
ArmV8 = Android64 | MacArm | iOS,
Desktop = Windows | Mac,
Mobile = Android | iOS,
};
using Type = decltype(Unknown);
@ -190,7 +198,6 @@ namespace geode {
case iOS: return "iOS";
case Android32: return "Android32";
case Android64: return "Android64";
case Linux: return "Linux";
default: break;
}
return "Undefined";
@ -206,7 +213,6 @@ namespace geode {
case iOS: return "ios";
case Android32: return ignoreArch ? "android" : "android32";
case Android64: return ignoreArch ? "android" : "android64";
case Linux: return "linux";
default: break;
}
return "undefined";

View file

@ -13,7 +13,7 @@ namespace geode {
virtual void updateColor(cocos2d::ccColor4B const& color) {}
};
// todo in v4: make this pimpl and maybe use events over the delegate?
// todo in v4: maybe use events over the delegate?
// thing with events is that if you just filter via ColorPickPopup* it
// won't work unless you automatically detach the filter when closing the
// popup (otherwise opening another popup really quickly will just be
@ -24,18 +24,8 @@ namespace geode {
public cocos2d::extension::ColorPickerDelegate,
public TextInputDelegate {
protected:
cocos2d::ccColor4B m_color;
cocos2d::ccColor4B m_originalColor;
cocos2d::extension::CCControlColourPicker* m_picker;
Slider* m_opacitySlider = nullptr;
TextInput* m_rInput;
TextInput* m_gInput;
TextInput* m_bInput;
TextInput* m_hexInput;
TextInput* m_opacityInput = nullptr;
ColorPickPopupDelegate* m_delegate = nullptr;
cocos2d::CCSprite* m_newColorSpr;
CCMenuItemSpriteExtra* m_resetBtn;
class Impl;
std::unique_ptr<Impl> m_impl;
static constexpr auto TAG_OPACITY_INPUT = 0;
static constexpr auto TAG_R_INPUT = 1;
@ -43,10 +33,13 @@ namespace geode {
static constexpr auto TAG_B_INPUT = 3;
static constexpr auto TAG_HEX_INPUT = 4;
ColorPickPopup();
~ColorPickPopup();
bool setup(cocos2d::ccColor4B const& color, bool isRGBA) override;
void onOpacitySlider(cocos2d::CCObject* sender);
void onReset(cocos2d::CCObject* sender);
void onClose(cocos2d::CCObject* sender) override;
void textChanged(CCTextInputNode* input) override;
void colorValueChanged(cocos2d::ccColor3B color) override;

View file

@ -30,7 +30,7 @@ namespace geode {
std::optional<std::string> m_targetID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, AEnterLayerEvent* event);
ListenerResult handle(std::function<Callback> fn, AEnterLayerEvent* event);
AEnterLayerFilter(
std::optional<std::string> const& id
@ -63,7 +63,7 @@ namespace geode {
std::optional<std::string> m_targetID;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, EnterLayerEvent<N>* event) {
ListenerResult handle(std::function<Callback> fn, EnterLayerEvent<N>* event) {
if (m_targetID == event->getID()) {
fn(static_cast<T*>(event));
}

View file

@ -133,11 +133,6 @@ namespace geode {
*/
GEODE_DLL void openSupportPopup(Mod* mod);
GEODE_DLL void openSupportPopup(ModMetadata const& metadata);
/**
* Open the store page for a mod (if it exists)
*/
[[deprecated("Will be removed, use openInfoPopup instead")]]
GEODE_DLL void openIndexPopup(Mod* mod);
/**
* Open the settings popup for a mod (if it has any settings)
*/
@ -160,6 +155,10 @@ namespace geode {
* Create a logo sprite for a mod
*/
GEODE_DLL cocos2d::CCNode* createModLogo(Mod* mod);
/**
* Create a logo sprite for a mod from a .geode file
*/
GEODE_DLL cocos2d::CCNode* createModLogo(std::filesystem::path const& geodePackage);
/**
* Create a logo sprite for a mod downloaded from the Geode servers. The
* logo is initially a loading circle, with the actual sprite downloaded

View file

@ -1,41 +0,0 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <Geode/binding/CCTextInputNode.hpp>
#include <cocos2d.h>
namespace geode {
class GEODE_DLL InputNode : public cocos2d::CCMenuItem {
protected:
cocos2d::extension::CCScale9Sprite* m_bgSprite;
CCTextInputNode* m_input;
bool init(float, float, char const*, char const*, std::string const&, int);
bool init(float, char const*, char const*, std::string const&, int);
public:
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(
float width, char const* placeholder, char const* fontFile, std::string const& filter,
int limit
);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(
float width, char const* placeholder, std::string const& filter, int limit
);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(float width, char const* placeholder, std::string const& filter);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(float width, char const* placeholder, char const* fontFile);
[[deprecated("Use geode::TextInput from the ui/TextInput.hpp header instead")]]
static InputNode* create(float width, char const* placeholder);
CCTextInputNode* getInput() const;
cocos2d::extension::CCScale9Sprite* getBG() const;
void setEnabled(bool enabled) override;
void setString(std::string const&);
std::string getString();
};
}

View file

@ -1,448 +1,430 @@
#pragma once
#include "../include/ccMacros.h"
#include "../cocoa/CCAffineTransform.h"
#include "../cocoa/CCArray.h"
#include <Geode/platform/platform.hpp>
#include <optional>
#include <memory>
NS_CC_BEGIN
class CCNode;
#pragma warning(push)
#pragma warning(disable: 4275)
/**
* Layouts automatically handle the positioning of nodes. Use CCNode::setLayout
* to apply a layout to a node, and then use CCNode::updateLayout to apply
* the layout's positioning. Geode comes with a few default layouts like
* RowLayout, ColumnLayout, and GridLayout, but if you need a different kind
* of layout you can inherit from the Layout class.
*/
class GEODE_DLL Layout : public CCObject {
protected:
CCArray* getNodesToPosition(CCNode* forNode) const;
bool m_ignoreInvisibleChildren = false;
public:
/**
* Automatically apply the layout's positioning on a set of nodes
* @param on Node to apply the layout on. Position's the node's children
* according to the layout. The content size of the node should be
* respected as a boundary the layout shouldn't overflow. The node may be
* rescaled to better fit its contents
*/
virtual void apply(CCNode* on) = 0;
/**
* Get how much space this layout would like to take up for a given target
*/
virtual CCSize getSizeHint(CCNode* on) const = 0;
void ignoreInvisibleChildren(bool ignore);
bool isIgnoreInvisibleChildren() const;
virtual ~Layout() = default;
};
class GEODE_DLL LayoutOptions : public CCObject {
public:
virtual ~LayoutOptions() = default;
};
/**
* The direction of an AxisLayout
*/
enum class Axis {
Row,
Column,
};
/**
* Specifies the alignment of something in an AxisLayout
*/
enum class AxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
Center,
// Align items to the end
// |......ooo|
End,
// Each item gets the same portion from the layout (disregards gap)
// |.o..o..o.|
Even,
// Space between each item is the same (disregards gap)
// |o...o...o|
Between,
};
constexpr float AXISLAYOUT_DEFAULT_MIN_SCALE = 0.65f;
constexpr int AXISLAYOUT_DEFAULT_PRIORITY = 0;
/**
* Options for controlling the behaviour of individual nodes in an AxisLayout
* @example
* auto node = CCNode::create();
* // this node will have 10 units of spacing between it and the next one
* node->setLayoutOptions(
* AxisLayoutOptions::create()
* ->setNextGap(10.f)
* );
* someNodeWithALayout->addChild(node);
*/
class GEODE_DLL AxisLayoutOptions : public LayoutOptions {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayoutOptions();
public:
static AxisLayoutOptions* create();
virtual ~AxisLayoutOptions();
std::optional<bool> getAutoScale() const;
// @note Use hasExplicitMaxScale to know if the default scale has been overwritten
float getMaxScale() const;
// @note Use hasExplicitMinScale to know if the default scale has been overwritten
float getMinScale() const;
bool hasExplicitMaxScale() const;
bool hasExplicitMinScale() const;
float getRelativeScale() const;
std::optional<float> getLength() const;
std::optional<float> getPrevGap() const;
std::optional<float> getNextGap() const;
bool getBreakLine() const;
bool getSameLine() const;
int getScalePriority() const;
std::optional<AxisAlignment> getCrossAxisAlignment() const;
/**
* Set the maximum scale this node can be if it's contained in an
* auto-scaled layout. Default is 1
*/
[[deprecated("Use AxisLayoutOptions::setScaleLimits")]]
AxisLayoutOptions* setMaxScale(float scale);
/**
* Set the minimum scale this node can be if it's contained in an
* auto-scaled layout. Default is AXISLAYOUT_DEFAULT_MIN_SCALE
*/
[[deprecated("Use AxisLayoutOptions::setScaleLimits")]]
AxisLayoutOptions* setMinScale(float scale);
/**
* Set the limits to what the node can be scaled to. Passing `std::nullopt`
* uses the parent layout's default min / max scales
*/
AxisLayoutOptions* setScaleLimits(std::optional<float> min, std::optional<float> max);
/**
* Set the relative scale of this node compared to other nodes if it's
* contained in an auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setRelativeScale(float scale);
/**
* Set auto-scaling for this node, overriding the layout's auto-scale
* setting. If nullopt, the layout's auto-scale options will be used
*/
AxisLayoutOptions* setAutoScale(std::optional<bool> enabled);
/**
* Set an absolute length for this node. If nullopt, the length will be
* dynamically calculated based on content size
*/
AxisLayoutOptions* setLength(std::optional<float> length);
/**
* Override the default gap in the layout between this node and the
* previous one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setPrevGap(std::optional<float> gap);
/**
* Override the default gap in the layout between this node and the next
* one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setNextGap(std::optional<float> gap);
/**
* If enabled, the node will always cause a growable axis layout to break
* into a new line even if the current line could've fit the next node
*/
AxisLayoutOptions* setBreakLine(bool enable);
/**
* If enabled, the node will be forced to be on the same line as the
* previous node even if doing this would overflow
*/
AxisLayoutOptions* setSameLine(bool enable);
/**
* Set the scale priority of this node. Nodes with higher priority will be
* scaled down first before nodes with lower priority when an auto-scaled
* layout attempts to fit its contents. Default is
* AXISLAYOUT_DEFAULT_PRIORITY
* @note For optimal performance, the priorities should all be close to
* each other with no gaps
*/
AxisLayoutOptions* setScalePriority(int priority);
/**
* Override the cross axis alignment for this node in the layout
*/
AxisLayoutOptions* setCrossAxisAlignment(std::optional<AxisAlignment> alignment);
};
/**
* A multi-purpose dynamic layout for arranging nodes along an axis. Can be
* used to arrange nodes in a single line, a grid, or a flex layout. The
* RowLayout and ColumnLayout classes function as simple thin wrappers over
* AxisLayout. The positioning of individual nodes in the layout can be
* further controlled using AxisLayoutOptions
* @warning Calculating layouts can get increasingly expensive for large
* amounts of child nodes being fit into a small space - while this should
* never prove a real performance concern as most layouts only have a few
* hundred children at the very most, be aware that you probably shouldn't
* call CCNode::updateLayout every frame for a menu with thousands of children
* @example
* auto menu = CCMenu::create();
* // The menu's children will be arranged horizontally, unless they overflow
* // the content size width in which case a new line will be inserted and
* // aligned to the left. The menu automatically will automatically grow in
* // height to fit all the rows
* menu->setLayout(
* RowLayout::create()
* ->setGap(10.f)
* ->setGrowCrossAxis(true)
* ->setAxisAlignment(AxisAlignment::Start)
* );
* menu->setContentSize({ 200.f, 0.f });
* menu->addChild(...);
* menu->updateLayout();
*/
class GEODE_DLL AxisLayout : public Layout {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayout(Axis);
public:
/**
* Create a new AxisLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on AxisLayout for
* what options you can customize for the layout
* @param axis The direction of the layout
* @note For convenience, you can use the RowLayout and ColumnLayout
* classes, which are just thin wrappers over AxisLayout
* @returns Created AxisLayout
*/
static AxisLayout* create(Axis axis = Axis::Row);
virtual ~AxisLayout();
void apply(CCNode* on) override;
CCSize getSizeHint(CCNode* on) const override;
Axis getAxis() const;
AxisAlignment getAxisAlignment() const;
AxisAlignment getCrossAxisAlignment() const;
AxisAlignment getCrossAxisLineAlignment() const;
float getGap() const;
bool getAxisReverse() const;
bool getCrossAxisReverse() const;
bool getAutoScale() const;
bool getGrowCrossAxis() const;
bool getCrossAxisOverflow() const;
std::optional<float> getAutoGrowAxis() const;
float getDefaultMinScale() const;
float getDefaultMaxScale() const;
AxisLayout* setAxis(Axis axis);
/**
* Sets where to align the target node's children on the main axis (X-axis
* for Row, Y-axis for Column)
*/
AxisLayout* setAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis (Y-axis
* for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis for
* each row (Y-axis for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisLineAlignment(AxisAlignment align);
/**
* The spacing between the children of the node this layout applies to.
* Measured as the space between their edges, not centres. Does not apply
* on the main / cross axis if their alignment is AxisAlignment::Even
*/
AxisLayout* setGap(float gap);
/**
* Whether to reverse the direction of the children in this layout or not
*/
AxisLayout* setAxisReverse(bool reverse);
/**
* Whether to reverse the direction of the rows on the cross-axis or not
*/
AxisLayout* setCrossAxisReverse(bool reverse);
/**
* If enabled, then the layout may scale the target's children if they are
* about to overflow. Assumes that all the childrens' intended scale is 1
*/
AxisLayout* setAutoScale(bool enable);
/**
* If true, if the main axis overflows extra nodes will be placed on new
* rows/columns on the cross-axis
*/
AxisLayout* setGrowCrossAxis(bool expand);
/**
* If true, the cross-axis content size of the target node will be
* automatically adjusted to fit the children
*/
AxisLayout* setCrossAxisOverflow(bool allow);
/**
* If not `std::nullopt`, then the axis will be automatically extended to
* fit all items in a single row whose minimum length is the specified.
* Useful for scrollable list layer contents
*/
AxisLayout* setAutoGrowAxis(std::optional<float> allowAndMinLength);
/**
* Set the default minimum/maximum scales for nodes in the layout
*/
AxisLayout* setDefaultScaleLimits(float min, float max);
};
/**
* Simple layout for arranging nodes in a row (horizontal line)
*/
class GEODE_DLL RowLayout : public AxisLayout {
protected:
RowLayout();
public:
/**
* Create a new RowLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created RowLayout
*/
static RowLayout* create();
};
/**
* Simple layout for arranging nodes in a column (vertical line)
*/
class GEODE_DLL ColumnLayout : public AxisLayout {
protected:
ColumnLayout();
public:
/**
* Create a new ColumnLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created ColumnLayout
*/
static ColumnLayout* create();
};
/**
* The relative position of a node to its parent in an AnchorLayout
*/
enum class Anchor {
Center,
TopLeft,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
};
/**
* Options for customizing a node's position in an AnchorLayout
*/
class GEODE_DLL AnchorLayoutOptions : public LayoutOptions {
protected:
Anchor m_anchor = Anchor::Center;
CCPoint m_offset = CCPointZero;
public:
static AnchorLayoutOptions* create();
Anchor getAnchor() const;
CCPoint getOffset() const;
AnchorLayoutOptions* setAnchor(Anchor anchor);
AnchorLayoutOptions* setOffset(CCPoint const& offset);
};
/**
* A layout for positioning nodes at specific positions relative to their
* parent's content size. See `Anchor` for available anchoring options. Useful
* for example for popups, where a popup using `AnchorLayout` can be
* automatically resized without needing to manually shuffle nodes around
*/
class GEODE_DLL AnchorLayout : public Layout {
public:
static AnchorLayout* create();
void apply(CCNode* on) override;
CCSize getSizeHint(CCNode* on) const override;
/**
* Get a position according to anchoring rules, with the same algorithm as
* `AnchorLayout` uses to position its nodes
* @param in The node whose content size to use as a reference
* @param anchor The anchor position
* @param offset Offset from the anchor
* @returns A position in `in` for the anchored and offsetted location
*/
static CCPoint getAnchoredPosition(CCNode* in, Anchor anchor, CCPoint const& offset);
};
/**
* A layout for automatically copying the content size of a node to other nodes.
* Basically main use case is for FLAlertLayers (setting the size of the
* background and `m_buttonMenu` based on `m_mainLayer`)
*/
class GEODE_DLL CopySizeLayout : public cocos2d::AnchorLayout {
protected:
cocos2d::CCArray* m_targets;
public:
static CopySizeLayout* create();
virtual ~CopySizeLayout();
/**
* Add a target to be automatically resized. Any targets' layouts will
* also be updated when this layout is updated
*/
CopySizeLayout* add(cocos2d::CCNode* target);
/**
* Remove a target from being automatically resized
*/
CopySizeLayout* remove(cocos2d::CCNode* target);
void apply(cocos2d::CCNode* in) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* in) const override;
};
#pragma warning(pop)
NS_CC_END
#pragma once
#include <cocos2d.h>
#include <Geode/platform/platform.hpp>
#include <optional>
#include <memory>
namespace geode {
#pragma warning(push)
#pragma warning(disable: 4275)
/**
* Layouts automatically handle the positioning of nodes. Use CCNode::setLayout
* to apply a layout to a node, and then use CCNode::updateLayout to apply
* the layout's positioning. Geode comes with a few default layouts like
* RowLayout, ColumnLayout, and GridLayout, but if you need a different kind
* of layout you can inherit from the Layout class.
*/
class GEODE_DLL Layout : public cocos2d::CCObject {
protected:
cocos2d::CCArray* getNodesToPosition(cocos2d::CCNode* forNode) const;
bool m_ignoreInvisibleChildren = false;
public:
/**
* Automatically apply the layout's positioning on a set of nodes
* @param on Node to apply the layout on. Position's the node's children
* according to the layout. The content size of the node should be
* respected as a boundary the layout shouldn't overflow. The node may be
* rescaled to better fit its contents
*/
virtual void apply(cocos2d::CCNode* on) = 0;
/**
* Get how much space this layout would like to take up for a given target
*/
virtual cocos2d::CCSize getSizeHint(cocos2d::CCNode* on) const = 0;
void ignoreInvisibleChildren(bool ignore);
bool isIgnoreInvisibleChildren() const;
virtual ~Layout() = default;
};
class GEODE_DLL LayoutOptions : public cocos2d::CCObject {
public:
virtual ~LayoutOptions() = default;
};
/**
* The direction of an AxisLayout
*/
enum class Axis {
Row,
Column,
};
/**
* Specifies the alignment of something in an AxisLayout
*/
enum class AxisAlignment {
// Align items to the start
// |ooo......|
Start,
// All items are centered
// |...ooo...|
Center,
// Align items to the end
// |......ooo|
End,
// Each item gets the same portion from the layout (disregards gap)
// |.o..o..o.|
Even,
// Space between each item is the same (disregards gap)
// |o...o...o|
Between,
};
constexpr float AXISLAYOUT_DEFAULT_MIN_SCALE = 0.65f;
constexpr int AXISLAYOUT_DEFAULT_PRIORITY = 0;
/**
* Options for controlling the behaviour of individual nodes in an AxisLayout
* @example
* auto node = CCNode::create();
* // this node will have 10 units of spacing between it and the next one
* node->setLayoutOptions(
* AxisLayoutOptions::create()
* ->setNextGap(10.f)
* );
* someNodeWithALayout->addChild(node);
*/
class GEODE_DLL AxisLayoutOptions : public LayoutOptions {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayoutOptions();
public:
static AxisLayoutOptions* create();
virtual ~AxisLayoutOptions();
std::optional<bool> getAutoScale() const;
// @note Use hasExplicitMaxScale to know if the default scale has been overwritten
float getMaxScale() const;
// @note Use hasExplicitMinScale to know if the default scale has been overwritten
float getMinScale() const;
bool hasExplicitMaxScale() const;
bool hasExplicitMinScale() const;
float getRelativeScale() const;
std::optional<float> getLength() const;
std::optional<float> getPrevGap() const;
std::optional<float> getNextGap() const;
bool getBreakLine() const;
bool getSameLine() const;
int getScalePriority() const;
std::optional<AxisAlignment> getCrossAxisAlignment() const;
/**
* Set the limits to what the node can be scaled to. Passing `std::nullopt`
* uses the parent layout's default min / max scales
*/
AxisLayoutOptions* setScaleLimits(std::optional<float> min, std::optional<float> max);
/**
* Set the relative scale of this node compared to other nodes if it's
* contained in an auto-scaled layout. Default is 1
*/
AxisLayoutOptions* setRelativeScale(float scale);
/**
* Set auto-scaling for this node, overriding the layout's auto-scale
* setting. If nullopt, the layout's auto-scale options will be used
*/
AxisLayoutOptions* setAutoScale(std::optional<bool> enabled);
/**
* Set an absolute length for this node. If nullopt, the length will be
* dynamically calculated based on content size
*/
AxisLayoutOptions* setLength(std::optional<float> length);
/**
* Override the default gap in the layout between this node and the
* previous one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setPrevGap(std::optional<float> gap);
/**
* Override the default gap in the layout between this node and the next
* one. If nullopt, the default gap of the layout will be used
*/
AxisLayoutOptions* setNextGap(std::optional<float> gap);
/**
* If enabled, the node will always cause a growable axis layout to break
* into a new line even if the current line could've fit the next node
*/
AxisLayoutOptions* setBreakLine(bool enable);
/**
* If enabled, the node will be forced to be on the same line as the
* previous node even if doing this would overflow
*/
AxisLayoutOptions* setSameLine(bool enable);
/**
* Set the scale priority of this node. Nodes with higher priority will be
* scaled down first before nodes with lower priority when an auto-scaled
* layout attempts to fit its contents. Default is
* AXISLAYOUT_DEFAULT_PRIORITY
* @note For optimal performance, the priorities should all be close to
* each other with no gaps
*/
AxisLayoutOptions* setScalePriority(int priority);
/**
* Override the cross axis alignment for this node in the layout
*/
AxisLayoutOptions* setCrossAxisAlignment(std::optional<AxisAlignment> alignment);
};
/**
* A multi-purpose dynamic layout for arranging nodes along an axis. Can be
* used to arrange nodes in a single line, a grid, or a flex layout. The
* RowLayout and ColumnLayout classes function as simple thin wrappers over
* AxisLayout. The positioning of individual nodes in the layout can be
* further controlled using AxisLayoutOptions
* @warning Calculating layouts can get increasingly expensive for large
* amounts of child nodes being fit into a small space - while this should
* never prove a real performance concern as most layouts only have a few
* hundred children at the very most, be aware that you probably shouldn't
* call CCNode::updateLayout every frame for a menu with thousands of children
* @example
* auto menu = CCMenu::create();
* // The menu's children will be arranged horizontally, unless they overflow
* // the content size width in which case a new line will be inserted and
* // aligned to the left. The menu automatically will automatically grow in
* // height to fit all the rows
* menu->setLayout(
* RowLayout::create()
* ->setGap(10.f)
* ->setGrowCrossAxis(true)
* ->setAxisAlignment(AxisAlignment::Start)
* );
* menu->setContentSize({ 200.f, 0.f });
* menu->addChild(...);
* menu->updateLayout();
*/
class GEODE_DLL AxisLayout : public Layout {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
AxisLayout(Axis);
public:
/**
* Create a new AxisLayout. Note that this class is not automatically
* managed by default, so you must assign it to a CCNode or manually
* manage the memory yourself. See the chainable setters on AxisLayout for
* what options you can customize for the layout
* @param axis The direction of the layout
* @note For convenience, you can use the RowLayout and ColumnLayout
* classes, which are just thin wrappers over AxisLayout
* @returns Created AxisLayout
*/
static AxisLayout* create(Axis axis = Axis::Row);
virtual ~AxisLayout();
void apply(cocos2d::CCNode* on) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* on) const override;
Axis getAxis() const;
AxisAlignment getAxisAlignment() const;
AxisAlignment getCrossAxisAlignment() const;
AxisAlignment getCrossAxisLineAlignment() const;
float getGap() const;
bool getAxisReverse() const;
bool getCrossAxisReverse() const;
bool getAutoScale() const;
bool getGrowCrossAxis() const;
bool getCrossAxisOverflow() const;
std::optional<float> getAutoGrowAxis() const;
float getDefaultMinScale() const;
float getDefaultMaxScale() const;
AxisLayout* setAxis(Axis axis);
/**
* Sets where to align the target node's children on the main axis (X-axis
* for Row, Y-axis for Column)
*/
AxisLayout* setAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis (Y-axis
* for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisAlignment(AxisAlignment align);
/**
* Sets where to align the target node's children on the cross-axis for
* each row (Y-axis for Row, X-axis for Column)
*/
AxisLayout* setCrossAxisLineAlignment(AxisAlignment align);
/**
* The spacing between the children of the node this layout applies to.
* Measured as the space between their edges, not centres. Does not apply
* on the main / cross axis if their alignment is AxisAlignment::Even
*/
AxisLayout* setGap(float gap);
/**
* Whether to reverse the direction of the children in this layout or not
*/
AxisLayout* setAxisReverse(bool reverse);
/**
* Whether to reverse the direction of the rows on the cross-axis or not
*/
AxisLayout* setCrossAxisReverse(bool reverse);
/**
* If enabled, then the layout may scale the target's children if they are
* about to overflow. Assumes that all the childrens' intended scale is 1
*/
AxisLayout* setAutoScale(bool enable);
/**
* If true, if the main axis overflows extra nodes will be placed on new
* rows/columns on the cross-axis
*/
AxisLayout* setGrowCrossAxis(bool expand);
/**
* If true, the cross-axis content size of the target node will be
* automatically adjusted to fit the children
*/
AxisLayout* setCrossAxisOverflow(bool allow);
/**
* If not `std::nullopt`, then the axis will be automatically extended to
* fit all items in a single row whose minimum length is the specified.
* Useful for scrollable list layer contents
*/
AxisLayout* setAutoGrowAxis(std::optional<float> allowAndMinLength);
/**
* Set the default minimum/maximum scales for nodes in the layout
*/
AxisLayout* setDefaultScaleLimits(float min, float max);
};
/**
* Simple layout for arranging nodes in a row (horizontal line)
*/
class GEODE_DLL RowLayout : public AxisLayout {
protected:
RowLayout();
public:
/**
* Create a new RowLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created RowLayout
*/
static RowLayout* create();
};
/**
* Simple layout for arranging nodes in a column (vertical line)
*/
class GEODE_DLL ColumnLayout : public AxisLayout {
protected:
ColumnLayout();
public:
/**
* Create a new ColumnLayout. See the chainable setters on RowLayout for
* what options you can customize for the layout
* @returns Created ColumnLayout
*/
static ColumnLayout* create();
};
/**
* The relative position of a node to its parent in an AnchorLayout
*/
enum class Anchor {
Center,
TopLeft,
Top,
TopRight,
Right,
BottomRight,
Bottom,
BottomLeft,
Left,
};
/**
* Options for customizing a node's position in an AnchorLayout
*/
class GEODE_DLL AnchorLayoutOptions : public LayoutOptions {
protected:
Anchor m_anchor = Anchor::Center;
cocos2d::CCPoint m_offset = cocos2d::CCPointZero;
public:
static AnchorLayoutOptions* create();
Anchor getAnchor() const;
cocos2d::CCPoint getOffset() const;
AnchorLayoutOptions* setAnchor(Anchor anchor);
AnchorLayoutOptions* setOffset(cocos2d::CCPoint const& offset);
};
/**
* A layout for positioning nodes at specific positions relative to their
* parent's content size. See `Anchor` for available anchoring options. Useful
* for example for popups, where a popup using `AnchorLayout` can be
* automatically resized without needing to manually shuffle nodes around
*/
class GEODE_DLL AnchorLayout : public Layout {
public:
static AnchorLayout* create();
void apply(cocos2d::CCNode* on) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* on) const override;
/**
* Get a position according to anchoring rules, with the same algorithm as
* `AnchorLayout` uses to position its nodes
* @param in The node whose content size to use as a reference
* @param anchor The anchor position
* @param offset Offset from the anchor
* @returns A position in `in` for the anchored and offsetted location
*/
static cocos2d::CCPoint getAnchoredPosition(cocos2d::CCNode* in, Anchor anchor, cocos2d::CCPoint const& offset);
};
/**
* A layout for automatically copying the content size of a node to other nodes.
* Basically main use case is for FLAlertLayers (setting the size of the
* background and `m_buttonMenu` based on `m_mainLayer`)
*/
class GEODE_DLL CopySizeLayout : public AnchorLayout {
protected:
cocos2d::CCArray* m_targets;
public:
static CopySizeLayout* create();
virtual ~CopySizeLayout();
/**
* Add a target to be automatically resized. Any targets' layouts will
* also be updated when this layout is updated
*/
CopySizeLayout* add(cocos2d::CCNode* target);
/**
* Remove a target from being automatically resized
*/
CopySizeLayout* remove(cocos2d::CCNode* target);
void apply(cocos2d::CCNode* in) override;
cocos2d::CCSize getSizeHint(cocos2d::CCNode* in) const override;
};
#pragma warning(pop)
}

View file

@ -11,13 +11,13 @@ namespace geode {
class GEODE_DLL MDPopup :
public Popup<
std::string const&, std::string const&, char const*, char const*,
utils::MiniFunction<void(bool)>> {
std::function<void(bool)>> {
protected:
utils::MiniFunction<void(bool)> m_onClick = nullptr;
std::function<void(bool)> m_onClick = nullptr;
bool setup(
std::string const& title, std::string const& info, char const* btn1, char const* btn2,
utils::MiniFunction<void(bool)> onClick
std::function<void(bool)> onClick
) override;
void onBtn(CCObject*);
@ -27,7 +27,7 @@ namespace geode {
public:
static MDPopup* create(
std::string const& title, std::string const& content, char const* btn1,
char const* btn2 = nullptr, utils::MiniFunction<void(bool)> onClick = nullptr
char const* btn2 = nullptr, std::function<void(bool)> onClick = nullptr
);
};
}

View file

@ -2,8 +2,8 @@
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
#include <Geode/binding/FLAlertLayer.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/utils/cocos.hpp>
#include <Geode/ui/Layout.hpp>
namespace geode {
template <class... InitArgs>
@ -51,7 +51,7 @@ namespace geode {
}
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, CloseEvent* event) {
ListenerResult handle(std::function<Callback> fn, CloseEvent* event) {
if (event->getPopup() == m_impl->popup) {
fn(event);
}
@ -104,7 +104,7 @@ namespace geode {
m_mainLayer->setPosition(winSize / 2);
m_mainLayer->setContentSize(m_size);
m_mainLayer->setLayout(
cocos2d::CopySizeLayout::create()
geode::CopySizeLayout::create()
->add(m_buttonMenu)
->add(m_bgSprite)
);
@ -119,7 +119,7 @@ namespace geode {
closeSpr, this, (cocos2d::SEL_MenuHandler)(&Popup::onClose)
);
if (dynamic) {
m_buttonMenu->addChildAtPosition(m_closeBtn, cocos2d::Anchor::TopLeft, { 3.f, -3.f });
m_buttonMenu->addChildAtPosition(m_closeBtn, geode::Anchor::TopLeft, { 3.f, -3.f });
}
else {
m_closeBtn->setPosition(-m_size.width / 2 + 3.f, m_size.height / 2 - 3.f);
@ -137,14 +137,6 @@ namespace geode {
}
protected:
[[deprecated("Use Popup::initAnchored instead, as it has more reasonable menu and layer content sizes")]]
bool init(
float width, float height, InitArgs... args, char const* bg = "GJ_square01.png",
cocos2d::CCRect bgRect = { 0, 0, 80, 80 }
) {
return this->initBase(width, height, std::forward<InitArgs>(args)..., bg, bgRect, false);
}
/**
* Init with AnchorLayout and the content size of `m_buttonMenu` and
* `m_bgSprite` being tied to the size of `m_mainLayer` (rather than
@ -185,7 +177,7 @@ namespace geode {
m_title = cocos2d::CCLabelBMFont::create(title.c_str(), font);
m_title->setZOrder(2);
if (m_dynamic) {
m_mainLayer->addChildAtPosition(m_title, cocos2d::Anchor::Top, ccp(0, -offset));
m_mainLayer->addChildAtPosition(m_title, geode::Anchor::Top, ccp(0, -offset));
}
else {
auto winSize = cocos2d::CCDirector::get()->getWinSize();
@ -221,21 +213,21 @@ namespace geode {
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow = true
std::function<void(FLAlertLayer*, bool)> selected, bool doShow = true
);
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
float width, utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow = true
float width, std::function<void(FLAlertLayer*, bool)> selected, bool doShow = true
);
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
std::function<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
);
GEODE_DLL FLAlertLayer* createQuickPopup(
char const* title, std::string const& content, char const* btn1, char const* btn2,
float width, utils::MiniFunction<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
float width, std::function<void(FLAlertLayer*, bool)> selected, bool doShow, bool cancelledByEscape
);
}

View file

@ -8,6 +8,8 @@
#include <Geode/utils/cocos.hpp>
namespace geode {
struct SceneSwitch;
class GEODE_DLL SceneManager final {
protected:
std::vector<Ref<cocos2d::CCNode>> m_persistedNodes;
@ -15,6 +17,10 @@ namespace geode {
virtual ~SceneManager();
void willSwitchToScene(cocos2d::CCScene* scene);
friend struct SceneSwitch;
public:
static SceneManager* get();
@ -34,9 +40,5 @@ namespace geode {
* Gets a span of the persisted nodes. To add new nodes to the list, use keepAcrossScenes.
*/
std::span<Ref<cocos2d::CCNode> const> getPersistedNodes();
// This method should only be called by geode itself
// TODO(v4): hide this
void willSwitchToScene(cocos2d::CCScene* scene);
};
}

View file

@ -14,13 +14,13 @@ namespace geode {
protected:
std::vector<T> m_list;
size_t m_index = 0;
utils::MiniFunction<void(T const&, size_t)> m_onChange;
std::function<void(T const&, size_t)> m_onChange;
cocos2d::CCLabelBMFont* m_label;
CCMenuItemSpriteExtra* m_prevBtn;
CCMenuItemSpriteExtra* m_nextBtn;
bool init(
float width, std::vector<T> const& list, utils::MiniFunction<void(T const&, size_t)> onChange
float width, std::vector<T> const& list, std::function<void(T const&, size_t)> onChange
) {
if (!cocos2d::CCMenu::init()) return false;
@ -94,7 +94,7 @@ namespace geode {
public:
static SelectList* create(
float width, std::vector<T> const& list, utils::MiniFunction<void(T const&, size_t)> onChange
float width, std::vector<T> const& list, std::function<void(T const&, size_t)> onChange
) {
auto ret = new SelectList();
if (ret->init(width, list, onChange)) {

View file

@ -1,8 +1,8 @@
#pragma once
#include "CCNode.h"
#include <cocos2d.h>
NS_CC_BEGIN
namespace geode {
#pragma warning(push)
#pragma warning(disable: 4275)
@ -22,7 +22,7 @@ NS_CC_BEGIN
* @note If you want to specify a minimum width for a SpacerNode, add
* AxisLayoutOptions for it and use setLength
*/
class GEODE_DLL SpacerNode : public CCNode {
class GEODE_DLL SpacerNode : public cocos2d::CCNode {
protected:
size_t m_grow;
@ -61,9 +61,9 @@ public:
*/
class GEODE_DLL SpacerNodeChild : public SpacerNode {
protected:
CCNode* m_child = nullptr;
cocos2d::CCNode* m_child = nullptr;
bool init(CCNode* child, size_t grow);
bool init(cocos2d::CCNode* child, size_t grow);
public:
/**
@ -73,11 +73,11 @@ public:
* factors (akin to CSS flew grow)
* @param grow The grow factor for this node. Default is 1
*/
static SpacerNodeChild* create(CCNode* child, size_t grow = 1);
static SpacerNodeChild* create(cocos2d::CCNode* child, size_t grow = 1);
void setContentSize(CCSize const& size) override;
void setContentSize(cocos2d::CCSize const& size) override;
};
#pragma warning(pop)
NS_CC_END
}

View file

@ -24,34 +24,34 @@ namespace geode {
*/
class GEODE_DLL SimpleTextArea : public cocos2d::CCNode {
public:
static SimpleTextArea* create(const std::string& text, const std::string& font = "chatFont.fnt", const float scale = 1);
static SimpleTextArea* create(const std::string& text, const std::string& font, const float scale, const float width);
static SimpleTextArea* create(const std::string& text, const std::string& font = "chatFont.fnt", float scale = 1.0f);
static SimpleTextArea* create(const std::string& text, const std::string& font, float scale, float width);
void setFont(const std::string& font);
std::string getFont();
void setColor(const cocos2d::ccColor4B& color);
cocos2d::ccColor4B getColor();
void setAlignment(const cocos2d::CCTextAlignment alignment);
void setAlignment(cocos2d::CCTextAlignment alignment);
cocos2d::CCTextAlignment getAlignment();
void setWrappingMode(const WrappingMode mode);
void setWrappingMode(WrappingMode mode);
WrappingMode getWrappingMode();
void setText(const std::string& text);
std::string getText();
void setMaxLines(const size_t maxLines);
void setMaxLines(size_t maxLines);
size_t getMaxLines();
void setWidth(const float width);
void setWidth(float width);
float getWidth();
void setScale(const float scale) override;
void setScale(float scale) override;
float getScale() override;
void setLinePadding(const float padding);
void setLinePadding(float padding);
float getLinePadding();
std::vector<cocos2d::CCLabelBMFont*> getLines();
float getHeight();
float getLineHeight();
private:
static SimpleTextArea* create(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
static SimpleTextArea* create(const std::string& font, const std::string& text, float scale, float width, const bool artificialWidth);
bool init(const std::string& font, const std::string& text, const float scale, const float width, const bool artificialWidth);
bool init(const std::string& font, const std::string& text, float scale, float width, const bool artificialWidth);
bool m_shouldUpdate = false;
bool m_artificialWidth = false;
@ -67,9 +67,9 @@ namespace geode {
float m_lineHeight = 0.f;
float m_linePadding = 0.f;
cocos2d::CCLabelBMFont* createLabel(const std::string& text, const float top);
cocos2d::CCLabelBMFont* createLabel(const std::string& text, float top);
float calculateOffset(cocos2d::CCLabelBMFont* label);
void charIteration(const std::function<cocos2d::CCLabelBMFont*(cocos2d::CCLabelBMFont* line, const char c, const float top)>& overflowHandling);
void charIteration(const std::function<cocos2d::CCLabelBMFont*(cocos2d::CCLabelBMFont* line, char c, float top)>& overflowHandling);
void updateLinesNoWrap();
void updateLinesWordWrap(bool spaceWrap);
void updateLinesCutoffWrap();

View file

@ -122,7 +122,7 @@ namespace geode {
* to distinguish between bold, italic and
* regular text.
*/
using Font = utils::MiniFunction<Label(int)>;
using Font = std::function<Label(int)>;
protected:
cocos2d::CCPoint m_origin = cocos2d::CCPointZero;

View file

@ -21,7 +21,7 @@ namespace geode {
std::string m_id;
public:
ListenerResult handle(utils::MiniFunction<Callback> fn, ColorProvidedEvent* event);
ListenerResult handle(std::function<Callback> fn, ColorProvidedEvent* event);
ColorProvidedFilter(std::string const& id);
};

View file

@ -4,8 +4,7 @@
#include "../loader/Log.hpp"
#include <set>
#include <variant>
#include <Geode/utils/MiniFunction.hpp>
#include <Geode/utils/Result.hpp>
#include <Geode/Result.hpp>
namespace geode {
struct JsonChecker;
@ -73,231 +72,11 @@ namespace geode {
}
template <class T>
using JsonValueValidator = utils::MiniFunction<bool(T const&)>;
using JsonValueValidator = std::function<bool(T const&)>;
struct JsonMaybeObject;
struct JsonMaybeValue;
struct GEODE_DLL
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
JsonMaybeSomething {
protected:
JsonChecker& m_checker;
matjson::Value& m_json;
std::string m_hierarchy;
bool m_hasValue;
friend struct JsonMaybeObject;
friend struct JsonMaybeValue;
void setError(std::string const& error);
public:
matjson::Value& json();
JsonMaybeSomething(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
);
bool isError() const;
std::string getError() const;
operator bool() const;
};
struct GEODE_DLL
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
JsonMaybeValue : public JsonMaybeSomething {
bool m_inferType = true;
JsonMaybeValue(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
);
JsonMaybeSomething& self();
template <matjson::Type T>
JsonMaybeValue& as() {
if (this->isError()) return *this;
if (!jsonConvertibleTo(self().m_json.type(), T)) {
this->setError(
self().m_hierarchy + ": Invalid type \"" + jsonValueTypeToString(self().m_json.type()) +
"\", expected \"" + jsonValueTypeToString(T) + "\""
);
}
m_inferType = false;
return *this;
}
JsonMaybeValue& array();
template <matjson::Type... T>
JsonMaybeValue& asOneOf() {
if (this->isError()) return *this;
bool isOneOf = (... || jsonConvertibleTo(self().m_json.type(), T));
if (!isOneOf) {
this->setError(
self().m_hierarchy + ": Invalid type \"" + jsonValueTypeToString(self().m_json.type()) +
"\", expected one of \"" + (jsonValueTypeToString(T), ...) + "\""
);
}
m_inferType = false;
return *this;
}
template <class T>
bool is() {
if (this->isError()) return false;
return self().m_json.is<T>();
}
template <class T>
JsonMaybeValue& validate(JsonValueValidator<T> validator) {
if (this->isError()) return *this;
if (self().m_json.is<T>()) {
if (!validator(self().m_json.as<T>())) {
this->setError(self().m_hierarchy + ": Invalid value format");
}
}
else {
this->setError(
self().m_hierarchy + ": Invalid type \"" +
std::string(jsonValueTypeToString(self().m_json.type())) + "\""
);
}
return *this;
}
template <class T>
JsonMaybeValue& inferType() {
if (this->isError() || !m_inferType) return *this;
return this->as<getJsonType<T>()>();
}
template <class T>
JsonMaybeValue& intoRaw(T& target) {
if (this->isError()) return *this;
target = self().m_json;
return *this;
}
template <class T>
JsonMaybeValue& into(T& target) {
return this->intoAs<T, T>(target);
}
template <class T>
JsonMaybeValue& into(std::optional<T>& target) {
return this->intoAs<T, std::optional<T>>(target);
}
template <class A, class T>
JsonMaybeValue& intoAs(T& target) {
this->inferType<A>();
if (this->isError()) return *this;
if (self().m_json.is<A>()) {
try {
target = self().m_json.as<A>();
}
catch(matjson::JsonException const& e) {
this->setError(
self().m_hierarchy + ": Error parsing JSON: " + std::string(e.what())
);
}
}
else {
this->setError(
self().m_hierarchy + ": Invalid type \"" +
std::string(jsonValueTypeToString(self().m_json.type())) + "\""
);
}
return *this;
}
template <class T>
T get() {
this->inferType<T>();
if (this->isError()) return T();
if (self().m_json.is<T>()) {
return self().m_json.as<T>();
}
return T();
}
JsonMaybeObject obj();
template <class T>
struct Iterator {
std::vector<T> m_values;
using iterator = typename std::vector<T>::iterator;
using const_iterator = typename std::vector<T>::const_iterator;
iterator begin() {
return m_values.begin();
}
iterator end() {
return m_values.end();
}
const_iterator begin() const {
return m_values.begin();
}
const_iterator end() const {
return m_values.end();
}
};
JsonMaybeValue at(size_t i);
Iterator<JsonMaybeValue> iterate();
Iterator<std::pair<std::string, JsonMaybeValue>> items();
};
struct
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
std::set<std::string> m_knownKeys;
JsonMaybeObject(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
);
JsonMaybeSomething& self();
void addKnownKey(std::string const& key);
matjson::Value& json();
JsonMaybeValue emptyValue();
JsonMaybeValue has(std::string const& key);
JsonMaybeValue needs(std::string const& key);
void checkUnknownKeys();
};
struct
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
GEODE_DLL JsonChecker {
std::variant<std::monostate, std::string> m_result;
matjson::Value& m_json;
JsonChecker(matjson::Value& json);
bool isError() const;
std::string getError() const;
JsonMaybeValue root(std::string const& hierarchy);
};
class GEODE_DLL JsonExpectedValue final {
protected:
class Impl;
@ -325,21 +104,14 @@ namespace geode {
return this->getJSONRef();
}
else {
try {
if (this->getJSONRef().is<T>()) {
return this->getJSONRef().as<T>();
}
else {
this->setError(
"unexpected type {}",
this->matJsonTypeToString(this->getJSONRef().type())
);
}
}
// matjson can throw variant exceptions too so you need to do this
catch(std::exception const& e) {
this->setError("unable to parse json: {}", e);
auto res = this->getJSONRef().as<T>();
if (res) {
return res.unwrap();
}
this->setError(
"unexpected type {}",
this->matJsonTypeToString(this->getJSONRef().type())
);
}
return std::nullopt;
}
@ -451,6 +223,13 @@ namespace geode {
* @returns The key, which is a no-op value if it didn't exist
*/
JsonExpectedValue has(std::string_view key);
/**
* Check if this object has an optional key. Asserts that this JSON
* value is an object. If the key doesn't exist, or the value is null, returns a
* `JsonExpectValue` that does nothing
* @returns The key, which is a no-op value if it didn't exist, or was null
*/
JsonExpectedValue hasNullable(std::string_view key);
/**
* Check if this object has an optional key. Asserts that this JSON
* value is an object. If the key doesn't exist, sets an error and

View file

@ -1,141 +0,0 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <memory>
#include <concepts>
#include "terminate.hpp"
namespace geode::utils {
template <class FunctionType>
class MiniFunction;
template <class Ret, class... Args>
class MiniFunctionStateBase {
public:
virtual ~MiniFunctionStateBase() = default;
virtual Ret call(Args... args) const = 0;
virtual MiniFunctionStateBase* clone() const = 0;
};
template <class Type, class Ret, class... Args>
class MiniFunctionState final : public MiniFunctionStateBase<Ret, Args...> {
public:
Type m_func;
explicit MiniFunctionState(Type func) : m_func(func) {}
Ret call(Args... args) const override {
return const_cast<Type&>(m_func)(std::forward<Args>(args)...);
}
MiniFunctionStateBase<Ret, Args...>* clone() const override {
return new MiniFunctionState(*this);
}
};
template <class Type, class Ret, class... Args>
class MiniFunctionStatePointer final : public MiniFunctionStateBase<Ret, Args...> {
public:
Type m_func;
explicit MiniFunctionStatePointer(Type func) : m_func(func) {}
Ret call(Args... args) const override {
return const_cast<Type&>(*m_func)(std::forward<Args>(args)...);
}
MiniFunctionStateBase<Ret, Args...>* clone() const override {
return new MiniFunctionStatePointer(*this);
}
};
template <class Type, class Ret, class Class, class... Args>
class MiniFunctionStateMemberPointer final : public MiniFunctionStateBase<Ret, Class, Args...> {
public:
Type m_func;
explicit MiniFunctionStateMemberPointer(Type func) : m_func(func) {}
Ret call(Class self, Args... args) const override {
return const_cast<Type&>(self->*m_func)(std::forward<Args>(args)...);
}
MiniFunctionStateBase<Ret, Class, Args...>* clone() const override {
return new MiniFunctionStateMemberPointer(*this);
}
};
template <class Callable, class Ret, class... Args>
concept MiniFunctionCallable = requires(Callable&& func, Args... args) {
{ func(std::forward<Args>(args)...) } -> std::same_as<Ret>;
};
template <class Ret, class... Args>
class MiniFunction<Ret(Args...)> {
public:
using FunctionType = Ret(Args...);
using StateType = MiniFunctionStateBase<Ret, Args...>;
private:
StateType* m_state;
public:
MiniFunction() : m_state(nullptr) {}
MiniFunction(std::nullptr_t) : MiniFunction() {}
MiniFunction(MiniFunction const& other) :
m_state(other.m_state ? other.m_state->clone() : nullptr) {}
MiniFunction(MiniFunction&& other) : m_state(other.m_state) {
other.m_state = nullptr;
}
~MiniFunction() {
if (m_state) delete m_state;
}
template <class Callable>
requires(MiniFunctionCallable<Callable, Ret, Args...> && !std::is_same_v<std::decay_t<Callable>, MiniFunction<FunctionType>>)
MiniFunction(Callable&& func) :
m_state(new MiniFunctionState<std::decay_t<Callable>, Ret, Args...>(std::forward<Callable>(func))) {}
template <class FunctionPointer>
requires(!MiniFunctionCallable<FunctionPointer, Ret, Args...> && std::is_pointer_v<FunctionPointer> && std::is_function_v<std::remove_pointer_t<FunctionPointer>>)
MiniFunction(FunctionPointer func) :
m_state(new MiniFunctionStatePointer<FunctionPointer, Ret, Args...>(func)) {}
template <class MemberFunctionPointer>
requires(std::is_member_function_pointer_v<MemberFunctionPointer>)
MiniFunction(MemberFunctionPointer func) :
m_state(new MiniFunctionStateMemberPointer<MemberFunctionPointer, Ret, Args...>(func)) {}
MiniFunction& operator=(MiniFunction const& other) {
if (m_state) delete m_state;
m_state = other.m_state ? other.m_state->clone() : nullptr;
return *this;
}
MiniFunction& operator=(MiniFunction&& other) {
if (m_state) delete m_state;
m_state = other.m_state;
other.m_state = nullptr;
return *this;
}
Ret operator()(Args... args) const {
if (!m_state) {
utils::terminate(
"Attempted to call a MiniFunction that was never assigned "
"any function, or one that has been moved"
);
}
return m_state->call(std::forward<Args>(args)...);
}
explicit operator bool() const {
return m_state;
}
};
}

View file

@ -1,9 +1,11 @@
#pragma once
#include <Geode/modify/Modify.hpp>
#include "cocos.hpp"
namespace geode::node_ids {
using namespace cocos2d;
static constexpr int32_t GEODE_ID_PRIORITY = 0x100000;
static constexpr int32_t GEODE_ID_PRIORITY = Priority::VeryEarlyPost;
template <class T = CCNode>
requires std::is_base_of_v<CCNode, T>

View file

@ -1,7 +1,7 @@
#pragma once
#include "../loader/Hook.hpp"
#include "Result.hpp"
#include <Geode/Result.hpp>
namespace geode {
namespace hook {

View file

@ -1,305 +0,0 @@
#pragma once
#include "../DefaultInclude.hpp"
#include "../external/result/result.hpp"
#include <fmt/format.h>
#include <string>
#include <string_view>
#include <type_traits>
#include <variant>
#include <optional>
namespace geode {
namespace impl {
using DefaultValue = std::monostate;
using DefaultError = std::string;
template <class T>
using WrappedResult = std::conditional_t<
std::is_lvalue_reference_v<T>, std::reference_wrapper<std::remove_reference_t<T>>,
std::remove_const_t<T>>;
template <class E = impl::DefaultError>
class [[nodiscard]] Failure {
public:
WrappedResult<E> m_error;
Failure() = default;
template <class E2>
requires(std::is_constructible_v<E, E2 const&>)
explicit constexpr Failure(E2 const& e) : m_error(e) {}
template <class E2>
requires(std::is_constructible_v<E, E2 &&>)
explicit constexpr Failure(E2&& e) : m_error(std::move(e)) {}
E& error() & noexcept {
return m_error;
}
E const& error() const& noexcept {
return m_error;
}
E&& error() && noexcept {
return static_cast<E&&>(m_error);
}
E const&& error() const&& noexcept {
return static_cast<E&&>(m_error);
}
};
template <class T = impl::DefaultValue>
class [[nodiscard]] Success {
public:
WrappedResult<T> m_value;
Success() = default;
template <class T2>
requires(std::is_constructible_v<T, T2 const&>)
explicit constexpr Success(T2 const& v) : m_value(v) {}
template <class T2>
requires(std::is_constructible_v<T, T2 &&>)
explicit constexpr Success(T2&& v) : m_value(std::forward<T2>(v)) {}
T& value() & noexcept {
return m_value;
}
T const& value() const& noexcept {
return m_value;
}
T&& value() && noexcept {
return static_cast<T&&>(m_value);
}
T const&& value() const&& noexcept {
return static_cast<T&&>(m_value);
}
};
}
template <class T = impl::DefaultValue, class E = impl::DefaultError>
class [[nodiscard]] Result : public cpp::result<T, E> {
public:
using Base = cpp::result<T, E>;
using ValueType = typename Base::value_type;
using ErrorType = typename Base::error_type;
using Base::result;
template <class U>
requires(cpp::detail::result_is_implicit_value_convertible<T, U>::value)
constexpr Result(U&& value) = delete;
template <class E2>
requires(std::is_constructible_v<E, E2 const&>)
constexpr Result(impl::Failure<E2> const& e) : Base(cpp::failure<E>(e.error())) {}
template <class E2>
requires(std::is_constructible_v<E, E2 &&>)
constexpr Result(impl::Failure<E2>&& e) : Base(cpp::failure<E>(std::move(e.error()))) {}
template <class T2>
requires(std::is_constructible_v<T, T2 const&>)
constexpr Result(impl::Success<T2> const& s) : Base(s.value()) {}
template <class T2>
requires(std::is_constructible_v<T, T2 &&>)
constexpr Result(impl::Success<T2>&& s) : Base(std::move(s.value())) {}
[[nodiscard]] constexpr explicit operator bool() const noexcept {
return this->Base::operator bool();
}
[[nodiscard]] constexpr bool isOk() const noexcept {
return this->Base::has_value();
}
[[nodiscard]] constexpr bool isErr() const noexcept {
return this->Base::has_error();
}
[[nodiscard]] constexpr decltype(auto) unwrap() & {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrap() const& {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrap() && {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrap() const&& {
return this->Base::value();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() & {
return this->Base::error();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() const& {
return this->Base::error();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() && {
return this->Base::error();
}
[[nodiscard]] constexpr decltype(auto) unwrapErr() const&& {
return this->Base::error();
}
template <class... Args>
requires(std::is_constructible_v<T, T &&>)
[[nodiscard]] Result<T, std::string> expect(std::string const& format, Args&&... args) {
if (this->isErr()) {
return impl::Failure<std::string>(fmt::format(
fmt::runtime(format), std::forward<Args>(args)...,
fmt::arg("error", this->unwrapErr())
));
}
else {
return std::move(*this);
}
}
template <class... Args>
requires(std::is_constructible_v<T, T const&>)
[[nodiscard]] Result<T, std::string> expect(std::string const& format, Args&&... args)
const {
if (this->isErr()) {
return impl::Failure<std::string>(fmt::format(
fmt::runtime(format), std::forward<Args>(args)...,
fmt::arg("error", this->unwrapErr())
));
}
else {
return *this;
}
}
template <class U>
[[nodiscard]] constexpr decltype(auto) unwrapOr(U&& val) && {
return this->Base::value_or(std::forward<U>(val));
}
template <class U>
[[nodiscard]] constexpr decltype(auto) unwrapOr(U&& val) const& {
return this->Base::value_or(std::forward<U>(val));
}
[[nodiscard]] constexpr decltype(auto) unwrapOrDefault() && requires std::is_default_constructible_v<T> {
return this->Base::value_or(T());
}
[[nodiscard]] constexpr decltype(auto) unwrapOrDefault() const& requires std::is_default_constructible_v<T> {
return this->Base::value_or(T());
}
template <class U>
[[nodiscard]] constexpr decltype(auto) errorOr(U&& val) && {
return this->Base::error_or(std::forward<U>(val));
}
template <class U>
[[nodiscard]] constexpr decltype(auto) errorOr(U&& val) const& {
return this->Base::error_or(std::forward<U>(val));
}
/**
* Convert the result into an optional containing the value if Ok, and
* nullopt if Err
*/
[[nodiscard]] constexpr std::optional<T> ok() const& {
if (this->isOk()) {
return this->unwrap();
}
return std::nullopt;
}
/**
* Convert the result into an optional containing the value if Ok, and
* nullopt if Err
*/
[[nodiscard]] constexpr std::optional<T> ok() && {
if (this->isOk()) {
return this->unwrap();
}
return std::nullopt;
}
/**
* Convert the result into an optional containing the error if Err, and
* nullopt if Ok
*/
[[nodiscard]] constexpr std::optional<E> err() const& {
if (this->isErr()) {
return this->unwrapErr();
}
return std::nullopt;
}
/**
* Convert the result into an optional containing the error if Err, and
* nullopt if Ok
*/
[[nodiscard]] constexpr std::optional<E> err() && {
if (this->isErr()) {
return this->unwrapErr();
}
return std::nullopt;
}
/**
* Completely disregard the result. Only recommended if the result is
* inconsequential
*/
constexpr void disregard() && {}
};
template <class T = impl::DefaultValue>
constexpr impl::Success<T> Ok() {
return impl::Success<T>();
}
template <class T>
constexpr impl::Success<T> Ok(T value) {
// DO NOT MAKE THE PARAMETER T&&!!!! THAT WILL CAUSE C++ TO DO UNEXPECTED
// IMPLICIT MOVES FOR EXAMPLE WHEN DOING `Ok(unordered_map.at(value))`
return impl::Success<T>(std::forward<T>(value));
}
template <class E>
constexpr impl::Failure<E> Err(E error) {
return impl::Failure<E>(std::forward<E>(error));
}
template <class... Args>
inline impl::Failure<std::string> Err(std::string const& format, Args&&... args) {
return impl::Failure<std::string>(
fmt::format(fmt::runtime(format), std::forward<Args>(args)...)
);
}
#define GEODE_UNWRAP_INTO(into, ...) \
auto GEODE_CONCAT(unwrap_res_, __LINE__) = (__VA_ARGS__); \
if (GEODE_CONCAT(unwrap_res_, __LINE__).isErr()) { \
return geode::Err(std::move(GEODE_CONCAT(unwrap_res_, __LINE__).unwrapErr())); \
} \
into = std::move(GEODE_CONCAT(unwrap_res_, __LINE__).unwrap())
#define GEODE_UNWRAP(...) \
do { \
auto GEODE_CONCAT(unwrap_res_, __LINE__) = (__VA_ARGS__); \
if (GEODE_CONCAT(unwrap_res_, __LINE__).isErr()) { \
return geode::Err(std::move(GEODE_CONCAT(unwrap_res_, __LINE__).unwrapErr())); \
} \
} while(false)
}

View file

@ -1,13 +1,21 @@
#pragma once
#include "general.hpp"
#include "MiniFunction.hpp"
#include "../loader/Event.hpp"
#include "../loader/Loader.hpp"
#include <mutex>
#include <string_view>
#include <coroutine>
namespace geode {
namespace geode_internal {
template <class T, class P>
struct TaskPromise;
template <class T, class P>
struct TaskAwaiter;
}
/**
* Tasks represent an asynchronous operation that will be finished at some
* unknown point in the future. Tasks can report their progress, and will
@ -87,7 +95,7 @@ namespace geode {
enum class Status {
/// The task is still running or waiting to start
Pending,
/// The task has succesfully finished
/// The task has successfully finished
Finished,
/// The task has been cancelled
Cancelled,
@ -141,7 +149,7 @@ namespace geode {
class PrivateMarker final {};
static std::shared_ptr<Handle> create(std::string_view const name) {
static std::shared_ptr<Handle> create(std::string_view name) {
return std::make_shared<Handle>(PrivateMarker(), name);
}
@ -153,8 +161,14 @@ namespace geode {
template <std::move_constructible T2, std::move_constructible P2>
friend class Task;
template <class, class>
friend struct geode_internal::TaskPromise;
template <class, class>
friend struct geode_internal::TaskAwaiter;
public:
Handle(PrivateMarker, std::string_view const name) : m_name(name) {}
Handle(PrivateMarker, std::string_view name) : m_name(name) {}
~Handle() {
// If this Task was still pending when the Handle was destroyed,
// it can no longer be listened to so just cancel and cleanup
@ -249,11 +263,11 @@ namespace geode {
using Value = T;
using Progress = P;
using PostResult = utils::MiniFunction<void(Result)>;
using PostProgress = utils::MiniFunction<void(P)>;
using HasBeenCancelled = utils::MiniFunction<bool()>;
using Run = utils::MiniFunction<Result(PostProgress, HasBeenCancelled)>;
using RunWithCallback = utils::MiniFunction<void(PostResult, PostProgress, HasBeenCancelled)>;
using PostResult = std::function<void(Result&&)>;
using PostProgress = std::function<void(P)>;
using HasBeenCancelled = std::function<bool()>;
using Run = std::function<Result(PostProgress, HasBeenCancelled)>;
using RunWithCallback = std::function<void(PostResult, PostProgress, HasBeenCancelled)>;
using Callback = void(Event*);
@ -308,6 +322,12 @@ namespace geode {
template <std::move_constructible T2, std::move_constructible P2>
friend class Task;
template <class, class>
friend struct geode_internal::TaskPromise;
template <class, class>
friend struct geode_internal::TaskAwaiter;
public:
// Allow default-construction
Task() : m_handle(nullptr) {}
@ -394,7 +414,7 @@ namespace geode {
* Create a new Task that is immediately cancelled
* @param name The name of the Task; used for debugging
*/
static Task cancelled(std::string_view const name = "<Cancelled Task>") {
static Task cancelled(std::string_view name = "<Cancelled Task>") {
auto task = Task(Handle::create(name));
Task::cancel(task.m_handle);
return task;
@ -405,7 +425,7 @@ namespace geode {
* @param value The value the Task shall be finished with
* @param name The name of the Task; used for debugging
*/
static Task immediate(T value, std::string_view const name = "<Immediate Task>") {
static Task immediate(T value, std::string_view name = "<Immediate Task>") {
auto task = Task(Handle::create(name));
Task::finish(task.m_handle, std::move(value));
return task;
@ -417,7 +437,7 @@ namespace geode {
* function MUST be synchronous - Task creates the thread for you!
* @param name The name of the Task; used for debugging
*/
static Task run(Run&& body, std::string_view const name = "<Task>") {
static Task run(Run&& body, std::string_view name = "<Task>") {
auto task = Task(Handle::create(name));
std::thread([handle = std::weak_ptr(task.m_handle), name = std::string(name), body = std::move(body)] {
utils::thread::setName(fmt::format("Task '{}'", name));
@ -450,7 +470,7 @@ namespace geode {
* calls will always be ignored
* @param name The name of the Task; used for debugging
*/
static Task runWithCallback(RunWithCallback&& body, std::string_view const name = "<Callback Task>") {
static Task runWithCallback(RunWithCallback&& body, std::string_view name = "<Callback Task>") {
auto task = Task(Handle::create(name));
std::thread([handle = std::weak_ptr(task.m_handle), name = std::string(name), body = std::move(body)] {
utils::thread::setName(fmt::format("Task '{}'", name));
@ -485,7 +505,7 @@ namespace geode {
* were cancelled!
*/
template <std::move_constructible NP>
static Task<std::vector<T*>, std::monostate> all(std::vector<Task<T, NP>>&& tasks, std::string_view const name = "<Multiple Tasks>") {
static Task<std::vector<T*>, std::monostate> all(std::vector<Task<T, NP>>&& tasks, std::string_view name = "<Multiple Tasks>") {
using AllTask = Task<std::vector<T*>, std::monostate>;
// If there are no tasks, return an immediate task that does nothing
@ -582,7 +602,7 @@ namespace geode {
* the mapped task is appended to the end
*/
template <class ResultMapper, class ProgressMapper, class OnCancelled>
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, OnCancelled&& onCancelled, std::string_view const name = "<Mapping Task>") const {
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, OnCancelled&& onCancelled, std::string_view name = "<Mapping Task>") const {
using T2 = decltype(resultMapper(std::declval<T*>()));
using P2 = decltype(progressMapper(std::declval<P*>()));
@ -652,7 +672,7 @@ namespace geode {
* @param name The name of the Task; used for debugging. The name of
* the mapped task is appended to the end
*/ template <class ResultMapper, class ProgressMapper>
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, std::string_view const name = "<Mapping Task>") const {
auto map(ResultMapper&& resultMapper, ProgressMapper&& progressMapper, std::string_view name = "<Mapping Task>") const {
return this->map(std::move(resultMapper), std::move(progressMapper), +[]() {}, name);
}
@ -670,7 +690,7 @@ namespace geode {
* the mapped task is appended to the end
*/ template <class ResultMapper>
requires std::copy_constructible<P>
auto map(ResultMapper&& resultMapper, std::string_view const name = "<Mapping Task>") const {
auto map(ResultMapper&& resultMapper, std::string_view name = "<Mapping Task>") const {
return this->map(std::move(resultMapper), +[](P* p) -> P { return *p; }, name);
}
@ -752,7 +772,94 @@ namespace geode {
this->listen(std::move(onResult), [](auto const&) {}, [] {});
}
ListenerResult handle(utils::MiniFunction<Callback> fn, Event* e) {
/**
* Create a new Task that listens to this Task and maps the values using
* the provided function. The new Task will only start when this Task finishes.
* @param mapper Function that makes a new task given the finished value of this task.
* The function signature should be `Task<NewType, NewProgress>(T*)`, and it will be executed
* on the main thread.
* @param name The name of the Task; used for debugging.
* @return The new Task that will run when this Task finishes.
* @note Progress from this task is not sent through, only progress from the new task is.
*/
template <std::invocable<T*> Mapper>
auto chain(Mapper mapper, std::string_view name = "<Chained Task>") const -> decltype(mapper(std::declval<T*>())) {
using NewTask = decltype(mapper(std::declval<T*>()));
using NewType = typename NewTask::Value;
using NewProgress = typename NewTask::Progress;
std::unique_lock<std::recursive_mutex> lock(m_handle->m_mutex);
if (m_handle->m_status == Status::Cancelled) {
// if the current task has been cancelled already, make an immediate cancelled task
return NewTask::cancelled();
}
else if (m_handle->m_status == Status::Finished) {
// if the current task is already done, we can just call the mapper directly
return mapper(&*m_handle->m_resultValue);
}
// otherwise, make a wrapper task that waits for the current task to finish,
// and then runs the mapper on the result. this new task will also wait for the task
// created by the mapper to finish, and will just forward the values through.
// do this because we cant really change the handle of the task we already returned
NewTask task = NewTask::Handle::create(fmt::format("{} <- {}", name, m_handle->m_name));
task.m_handle->m_extraData = std::make_unique<typename NewTask::Handle::ExtraData>(
// make the first event listener that waits for the current task
static_cast<void*>(new EventListener<Task>(
[handle = std::weak_ptr(task.m_handle), mapper = std::move(mapper)](Event* event) mutable {
if (auto v = event->getValue()) {
auto newInnerTask = mapper(v);
// this is scary.. but it doesn't seem to crash lol
handle.lock()->m_extraData = std::make_unique<typename NewTask::Handle::ExtraData>(
// make the second event listener that waits for the mapper's task
// and just forwards everything through
static_cast<void*>(new EventListener<NewTask>(
[handle](typename NewTask::Event* event) mutable {
if (auto v = event->getValue()) {
NewTask::finish(handle.lock(), std::move(*v));
}
else if (auto p = event->getProgress()) {
NewTask::progress(handle.lock(), std::move(*p));
}
else if (event->isCancelled()) {
NewTask::cancel(handle.lock());
}
},
std::move(newInnerTask)
)),
+[](void* ptr) {
delete static_cast<EventListener<NewTask>*>(ptr);
},
+[](void* ptr) {
static_cast<EventListener<NewTask>*>(ptr)->getFilter().cancel();
}
);
}
else if (auto p = event->getProgress()) {
// no guarantee P and NewProgress are compatible
// nor does it seem like the intended behavior?
// TODO: maybe add a mapper for progress?
}
else if (event->isCancelled()) {
NewTask::cancel(handle.lock());
}
},
*this
)),
+[](void* ptr) {
delete static_cast<EventListener<Task>*>(ptr);
},
+[](void* ptr) {
static_cast<EventListener<Task>*>(ptr)->getFilter().cancel();
}
);
return task;
}
ListenerResult handle(std::function<Callback> fn, Event* e) {
if (e->m_handle == m_handle && (!e->m_for || e->m_for == m_listener)) {
fn(e);
}
@ -797,3 +904,132 @@ namespace geode {
static_assert(is_filter<Task<int>>, "The Task class must be a valid event filter!");
}
// - C++20 coroutine support for Task - //
// Example usage (function must return a Task):
// ```
// Task<int> someTask() {
// auto response = co_await web::WebRequest().get("https://example.com");
// co_return response.code();
// }
// ```
// This will create a Task that will finish with the response code of the
// web request.
//
// Note: If the Task the coroutine is waiting on is cancelled, the coroutine
// will be destroyed and the Task will be cancelled as well. If the Task returned
// by the coroutine is cancelled, the coroutine will be destroyed as well and execution
// stops as soon as possible.
//
// The body of the coroutine is ran in whatever thread it got called in.
// TODO: maybe guarantee main thread?
//
// The coroutine can also yield progress values using `co_yield`:
// ```
// Task<std::string, int> someTask() {
// for (int i = 0; i < 10; i++) {
// co_yield i;
// }
// co_return "done!";
// }
// ```
namespace geode {
namespace geode_internal {
template <class T, class P>
struct TaskPromise {
using MyTask = Task<T, P>;
std::weak_ptr<typename MyTask::Handle> m_handle;
~TaskPromise() {
// does nothing if its not pending
MyTask::cancel(m_handle.lock());
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
// TODO: do something here?
void unhandled_exception() {}
MyTask get_return_object() {
auto handle = MyTask::Handle::create("<Coroutine Task>");
m_handle = handle;
return handle;
}
void return_value(T x) {
MyTask::finish(m_handle.lock(), std::move(x));
}
std::suspend_never yield_value(P value) {
MyTask::progress(m_handle.lock(), std::move(value));
return {};
}
bool isCancelled() {
if (auto p = m_handle.lock()) {
return p->is(MyTask::Status::Cancelled);
}
return true;
}
};
template <class T, class P>
struct TaskAwaiter {
Task<T, P> task;
bool await_ready() {
return task.isFinished();
}
template <class U, class V>
void await_suspend(std::coroutine_handle<TaskPromise<U, V>> handle) {
if (handle.promise().isCancelled()) {
handle.destroy();
return;
}
// this should be fine because the parent task can only have
// one pending task at a time
auto parentHandle = handle.promise().m_handle.lock();
if (!parentHandle) {
handle.destroy();
return;
}
parentHandle->m_extraData = std::make_unique<typename Task<U, V>::Handle::ExtraData>(
static_cast<void*>(new EventListener<Task<T, P>>(
[handle](auto* event) {
if (event->getValue()) {
handle.resume();
}
if (event->isCancelled()) {
handle.destroy();
}
},
task
)),
+[](void* ptr) {
delete static_cast<EventListener<Task<T, P>>*>(ptr);
},
+[](void* ptr) {
static_cast<EventListener<Task<T, P>>*>(ptr)->getFilter().cancel();
}
);
}
T await_resume() {
return std::move(*task.getFinishedValue());
}
};
}
}
template <class T, class P>
auto operator co_await(geode::Task<T, P> task) {
return geode::geode_internal::TaskAwaiter<T, P>{task};
}
template <class T, class P, class... Args>
struct std::coroutine_traits<geode::Task<T, P>, Args...> {
using promise_type = geode::geode_internal::TaskPromise<T, P>;
};

View file

@ -4,7 +4,8 @@
#include <string_view>
#include <matjson.hpp>
#include <tuple>
#include "../utils/Result.hpp"
#include <Geode/Result.hpp>
#include <fmt/format.h>
namespace geode {
enum class VersionCompare {
@ -186,8 +187,6 @@ namespace geode {
std::tie(other.m_major, other.m_minor, other.m_patch, other.m_tag);
}
[[deprecated("Use toNonVString or toVString instead")]]
std::string toString(bool includeTag = true) const;
std::string toVString(bool includeTag = true) const;
std::string toNonVString(bool includeTag = true) const;
@ -258,25 +257,15 @@ namespace geode {
template <class V>
requires std::is_same_v<V, geode::VersionInfo> || std::is_same_v<V, geode::ComparableVersionInfo>
struct matjson::Serialize<V> {
static matjson::Value to_json(V const& info) {
return info.toString();
static geode::Result<V, std::string> fromJson(Value const& value) {
GEODE_UNWRAP_INTO(auto str, value.asString());
GEODE_UNWRAP_INTO(auto version, V::parse(str).mapErr([](auto&& err) {
return fmt::format("Invalid version format: {}", err);
}));
return geode::Ok(version);
}
static bool is_json(matjson::Value const& json) {
if (json.is_string()) {
auto ver = V::parse(json.as_string());
return !ver.isErr();
}
return false;
}
static V from_json(matjson::Value const& json) {
auto ver = V::parse(json.as_string());
if (!ver) {
throw matjson::JsonException(
"Invalid version format: " + ver.unwrapErr()
);
}
return ver.unwrap();
static Value toJson(V const& value) {
return Value(value.toNonVString());
}
};

View file

@ -10,22 +10,21 @@
#include "../loader/Event.hpp"
#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
#include <Geode/binding/CCMenuItemToggler.hpp>
#include "MiniFunction.hpp"
#include "../ui/Layout.hpp"
#include "../ui/SpacerNode.hpp"
// support converting ccColor3B / ccColor4B to / from json
template <>
struct matjson::Serialize<cocos2d::ccColor3B> {
static matjson::Value GEODE_DLL to_json(cocos2d::ccColor3B const& color);
static cocos2d::ccColor3B GEODE_DLL from_json(matjson::Value const& color);
static bool GEODE_DLL is_json(matjson::Value const& json);
static geode::Result<cocos2d::ccColor3B> GEODE_DLL fromJson(Value const& value);
static Value GEODE_DLL toJson(cocos2d::ccColor3B const& value);
};
template <>
struct matjson::Serialize<cocos2d::ccColor4B> {
static matjson::Value GEODE_DLL to_json(cocos2d::ccColor4B const& color);
static cocos2d::ccColor4B GEODE_DLL from_json(matjson::Value const& color);
static bool GEODE_DLL is_json(matjson::Value const& json);
static geode::Result<cocos2d::ccColor4B> GEODE_DLL fromJson(Value const& value);
static Value GEODE_DLL toJson(cocos2d::ccColor4B const& value);
};
// operators for CC geometry
@ -615,18 +614,6 @@ namespace geode::cocos {
return static_cast<T*>(x->getChildren()->objectAtIndex(i));
}
/**
* Get nth child that is a given type. Checks bounds.
* @returns Child at index cast to the given type,
* or nullptr if index exceeds bounds
*/
template <class Type = cocos2d::CCNode>
[[deprecated("Use CCNode::getChildByType instead")]]
static Type* getChildOfType(cocos2d::CCNode* node, int index) {
return node->getChildByType<Type>(index);
}
/**
* Return a node, or create a default one if it's
* nullptr. Syntactic sugar function
@ -670,7 +657,7 @@ namespace geode::cocos {
*/
GEODE_DLL cocos2d::CCScene* switchToScene(cocos2d::CCLayer* layer);
using CreateLayerFunc = utils::MiniFunction<cocos2d::CCLayer*()>;
using CreateLayerFunc = std::function<cocos2d::CCLayer*()>;
/**
* Reload textures, overwriting the scene to return to after the loading
@ -738,7 +725,7 @@ namespace geode::cocos {
* there is none
*/
template <class Type = cocos2d::CCNode>
Type* findFirstChildRecursive(cocos2d::CCNode* node, utils::MiniFunction<bool(Type*)> predicate) {
Type* findFirstChildRecursive(cocos2d::CCNode* node, std::function<bool(Type*)> predicate) {
if (cast::typeinfo_cast<Type*>(node) && predicate(static_cast<Type*>(node)))
return static_cast<Type*>(node);
@ -856,30 +843,6 @@ namespace geode::cocos {
return {color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f};
}
[[deprecated("This function may have unintended behavior, use cc3bFromHexString or manually expand the color instead")]]
constexpr cocos2d::ccColor3B cc3x(int hexValue) {
if (hexValue <= 0xf)
return cocos2d::ccColor3B{
static_cast<GLubyte>(hexValue * 17),
static_cast<GLubyte>(hexValue * 17),
static_cast<GLubyte>(hexValue * 17)};
if (hexValue <= 0xff)
return cocos2d::ccColor3B{
static_cast<GLubyte>(hexValue),
static_cast<GLubyte>(hexValue),
static_cast<GLubyte>(hexValue)};
if (hexValue <= 0xfff)
return cocos2d::ccColor3B{
static_cast<GLubyte>((hexValue >> 8 & 0xf) * 17),
static_cast<GLubyte>((hexValue >> 4 & 0xf) * 17),
static_cast<GLubyte>((hexValue >> 0 & 0xf) * 17)};
else
return cocos2d::ccColor3B{
static_cast<GLubyte>(hexValue >> 16 & 0xff),
static_cast<GLubyte>(hexValue >> 8 & 0xff),
static_cast<GLubyte>(hexValue >> 0 & 0xff)};
}
/**
* Parse a ccColor3B from a hexadecimal string. The string may contain
* a leading '#'
@ -887,7 +850,7 @@ namespace geode::cocos {
* @param permissive If true, strings like "f" are considered valid
* representations of the color white. Useful for UIs that allow entering
* a hex color. Empty strings evaluate to pure white
* @returns A ccColor3B if it could be succesfully parsed, or an error
* @returns A ccColor3B if it could be successfully parsed, or an error
* indicating the failure reason
*/
GEODE_DLL Result<cocos2d::ccColor3B> cc3bFromHexString(std::string const& hexValue, bool permissive = false);
@ -900,7 +863,7 @@ namespace geode::cocos {
* @param permissive If true, strings like "f" are considered valid
* representations of the color white. Useful for UIs that allow entering
* a hex color. Empty strings evaluate to pure white
* @returns A ccColor4B if it could be succesfully parsed, or an error
* @returns A ccColor4B if it could be successfully parsed, or an error
* indicating the failure reason
*/
GEODE_DLL Result<cocos2d::ccColor4B> cc4bFromHexString(std::string const& hexValue, bool requireAlpha = false, bool permissive = false);
@ -916,7 +879,7 @@ namespace geode::cocos {
}
template <typename T, typename C, typename = std::enable_if_t<std::is_pointer_v<C>>>
static cocos2d::CCArray* vectorToCCArray(std::vector<T> const& vec, utils::MiniFunction<C(T)> convFunc) {
static cocos2d::CCArray* vectorToCCArray(std::vector<T> const& vec, std::function<C(T)> convFunc) {
auto res = cocos2d::CCArray::createWithCapacity(vec.size());
for (auto const& item : vec)
res->addObject(convFunc(item));
@ -943,7 +906,7 @@ namespace geode::cocos {
template <
typename K, typename V, typename C,
typename = std::enable_if_t<std::is_same_v<C, std::string> || std::is_same_v<C, intptr_t>>>
static cocos2d::CCDictionary* mapToCCDict(std::map<K, V> const& map, utils::MiniFunction<C(K)> convFunc) {
static cocos2d::CCDictionary* mapToCCDict(std::map<K, V> const& map, std::function<C(K)> convFunc) {
auto res = cocos2d::CCDictionary::create();
for (auto const& [key, value] : map)
res->setObject(value, convFunc(key));
@ -969,10 +932,10 @@ namespace std {
};
template <typename T>
struct std::hash<geode::WeakRef<T>> {
struct hash<geode::WeakRef<T>> {
size_t operator()(geode::WeakRef<T> const& ref) const {
// the explicit template argument is needed here because it would otherwise cast to WeakRef and recurse
return hash<std::shared_ptr<geode::WeakRefController>>{}(ref.m_controller);
return std::hash<std::shared_ptr<geode::WeakRefController>>{}(ref.m_controller);
}
};
}
@ -1216,11 +1179,11 @@ namespace geode::cocos {
template <class Node>
class LambdaCallback : public cocos2d::CCObject {
public:
utils::MiniFunction<void(Node*)> m_callback;
std::function<void(Node*)> m_callback;
static LambdaCallback* create(utils::MiniFunction<void(Node*)>&& callback) {
static LambdaCallback* create(std::function<void(Node*)> callback) {
auto ret = new (std::nothrow) LambdaCallback();
if (ret->init(std::forward<std::remove_reference_t<decltype(callback)>>(callback))) {
if (ret->init(std::move(callback))) {
ret->autorelease();
return ret;
}
@ -1228,8 +1191,8 @@ namespace geode::cocos {
return nullptr;
}
bool init(utils::MiniFunction<void(Node*)>&& callback) {
m_callback = std::forward<std::remove_reference_t<decltype(callback)>>(callback);
bool init(std::function<void(Node*)> callback) {
m_callback = std::move(callback);
return true;
}
@ -1240,20 +1203,20 @@ namespace geode::cocos {
public:
static cocos2d::CCMenuItem* create(
utils::MiniFunction<void(cocos2d::CCMenuItem*)>&& callback
std::function<void(cocos2d::CCMenuItem*)> callback
) {
auto item = cocos2d::CCMenuItem::create();
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static cocos2d::CCMenuItemSprite* createSprite(
cocos2d::CCNode* normalSprite,
cocos2d::CCNode* selectedSprite,
utils::MiniFunction<void(cocos2d::CCMenuItemSprite*)>&& callback
std::function<void(cocos2d::CCMenuItemSprite*)> callback
) {
auto item = cocos2d::CCMenuItemSprite::create(normalSprite, selectedSprite);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
@ -1261,57 +1224,57 @@ namespace geode::cocos {
cocos2d::CCNode* normalSprite,
cocos2d::CCNode* selectedSprite,
cocos2d::CCNode* disabledSprite,
utils::MiniFunction<void(cocos2d::CCMenuItemSprite*)>&& callback
std::function<void(cocos2d::CCMenuItemSprite*)> callback
) {
auto item = cocos2d::CCMenuItemSprite::create(normalSprite, selectedSprite, disabledSprite);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static CCMenuItemSpriteExtra* createSpriteExtra(
cocos2d::CCNode* normalSprite,
utils::MiniFunction<void(CCMenuItemSpriteExtra*)>&& callback
std::function<void(CCMenuItemSpriteExtra*)> callback
) {
auto item = CCMenuItemSpriteExtra::create(normalSprite, nullptr, nullptr);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static CCMenuItemSpriteExtra* createSpriteExtraWithFilename(
std::string_view normalSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemSpriteExtra*)>&& callback
std::function<void(CCMenuItemSpriteExtra*)> callback
) {
auto sprite = cocos2d::CCSprite::create(normalSpriteName.data());
sprite->setScale(scale);
return createSpriteExtra(sprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createSpriteExtra(sprite, std::move(callback));
}
static CCMenuItemSpriteExtra* createSpriteExtraWithFrameName(
std::string_view normalSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemSpriteExtra*)>&& callback
std::function<void(CCMenuItemSpriteExtra*)> callback
) {
auto sprite = cocos2d::CCSprite::createWithSpriteFrameName(normalSpriteName.data());
sprite->setScale(scale);
return createSpriteExtra(sprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createSpriteExtra(sprite, std::move(callback));
}
static CCMenuItemToggler* createToggler(
cocos2d::CCNode* onSprite,
cocos2d::CCNode* offSprite,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto item = CCMenuItemToggler::create(offSprite, onSprite, nullptr, nullptr);
assignCallback(item, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
assignCallback(item, std::move(callback));
return item;
}
static CCMenuItemToggler* createTogglerWithStandardSprites(
float scale,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto offSprite = cocos2d::CCSprite::createWithSpriteFrameName("GJ_checkOff_001.png");
auto onSprite = cocos2d::CCSprite::createWithSpriteFrameName("GJ_checkOn_001.png");
@ -1319,14 +1282,14 @@ namespace geode::cocos {
offSprite->setScale(scale);
onSprite->setScale(scale);
return createToggler(onSprite, offSprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createToggler(onSprite, offSprite, std::move(callback));
}
static CCMenuItemToggler* createTogglerWithFilename(
std::string_view onSpriteName,
std::string_view offSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto offSprite = cocos2d::CCSprite::create(offSpriteName.data());
auto onSprite = cocos2d::CCSprite::create(onSpriteName.data());
@ -1334,14 +1297,14 @@ namespace geode::cocos {
offSprite->setScale(scale);
onSprite->setScale(scale);
return createToggler(onSprite, offSprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createToggler(onSprite, offSprite, std::move(callback));
}
static CCMenuItemToggler* createTogglerWithFrameName(
std::string_view onSpriteName,
std::string_view offSpriteName,
float scale,
utils::MiniFunction<void(CCMenuItemToggler*)>&& callback
std::function<void(CCMenuItemToggler*)> callback
) {
auto offSprite = cocos2d::CCSprite::createWithSpriteFrameName(offSpriteName.data());
auto onSprite = cocos2d::CCSprite::createWithSpriteFrameName(onSpriteName.data());
@ -1349,15 +1312,15 @@ namespace geode::cocos {
offSprite->setScale(scale);
onSprite->setScale(scale);
return createToggler(onSprite, offSprite, std::forward<std::remove_reference_t<decltype(callback)>>(callback));
return createToggler(onSprite, offSprite, std::move(callback));
}
template <class Node>
static void assignCallback(
cocos2d::CCMenuItem* item,
utils::MiniFunction<void(Node*)>&& callback
std::function<void(Node*)> callback
) {
auto lambda = LambdaCallback<Node>::create(std::forward<std::remove_reference_t<decltype(callback)>>(callback));
auto lambda = LambdaCallback<Node>::create(std::move(callback));
item->setTarget(lambda, menu_selector(LambdaCallback<Node>::execute));
item->setUserObject("lambda-callback", lambda);
}
@ -1368,15 +1331,13 @@ namespace geode::cocos {
class CallFuncExtImpl : public cocos2d::CCActionInstant {
public:
static CallFuncExtImpl* create(const F& func) {
auto ret = new CallFuncExtImpl;
ret->m_func = func;
auto ret = new CallFuncExtImpl(func);
ret->autorelease();
return ret;
}
static CallFuncExtImpl* create(F&& func) {
auto ret = new CallFuncExtImpl;
ret->m_func = std::move(func);
auto ret = new CallFuncExtImpl(std::move(func));
ret->autorelease();
return ret;
}
@ -1384,8 +1345,17 @@ namespace geode::cocos {
private:
F m_func;
// F may not be default-constructible
CallFuncExtImpl(F&& func) : m_func(std::move(func)) {}
CallFuncExtImpl(F const& func) : m_func(func) {}
void update(float) override {
if (m_func) this->m_func();
// Make sure any `std::function`s are valid
if constexpr (requires { static_cast<bool>(m_func); }) {
if (m_func) m_func();
} else {
m_func();
}
}
};

View file

@ -1,6 +1,6 @@
#pragma once
#include "Result.hpp"
#include <Geode/Result.hpp>
#include "general.hpp"
#include "../loader/Event.hpp"
#include "Task.hpp"
@ -13,14 +13,15 @@
template <>
struct matjson::Serialize<std::filesystem::path> {
static matjson::Value to_json(std::filesystem::path const& path) {
return path.string();
static geode::Result<std::filesystem::path, std::string> fromJson(Value const& value)
{
GEODE_UNWRAP_INTO(auto str, value.asString());
return geode::Ok(std::filesystem::path(str).make_preferred());
}
static std::filesystem::path from_json(matjson::Value const& value) {
return std::filesystem::path(value.as_string()).make_preferred();
}
static bool is_json(matjson::Value const& value) {
return value.is_string();
static Value toJson(std::filesystem::path const& value)
{
return Value(value.string());
}
};
@ -32,10 +33,7 @@ namespace geode::utils::file {
template <class T>
Result<T> readFromJson(std::filesystem::path const& file) {
GEODE_UNWRAP_INTO(auto json, readJson(file));
if (!json.is<T>()) {
return Err("JSON is not of type {}", typeid(T).name());
}
return Ok(json.as<T>());
return json.as<T>();
}
GEODE_DLL Result<> writeString(std::filesystem::path const& path, std::string const& data);
@ -162,7 +160,7 @@ namespace geode::utils::file {
* @param callback Callback to call with the progress of the unzip operation
*/
void setProgressCallback(
utils::MiniFunction<void(uint32_t, uint32_t)> callback
std::function<void(uint32_t, uint32_t)> callback
);
/**
@ -213,7 +211,7 @@ namespace geode::utils::file {
);
static Result<> intoDir(
utils::MiniFunction<void(uint32_t, uint32_t)> progressCallback,
std::function<void(uint32_t, uint32_t)> progressCallback,
Path const& from,
Path const& to,
bool deleteZipAfter = false
@ -281,7 +279,7 @@ namespace geode::utils::file {
public:
using Callback = void(FileWatchEvent*);
ListenerResult handle(utils::MiniFunction<Callback> callback, FileWatchEvent* event);
ListenerResult handle(std::function<Callback> callback, FileWatchEvent* event);
FileWatchFilter(std::filesystem::path const& path);
};

View file

@ -1,11 +1,10 @@
#pragma once
#include "Result.hpp"
#include <Geode/Result.hpp>
#include "../DefaultInclude.hpp"
#include <chrono>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
#include <filesystem>
@ -13,19 +12,11 @@
#include <charconv>
#include <clocale>
#include <type_traits>
#include <fmt/format.h>
namespace geode {
using ByteVector = std::vector<uint8_t>;
// todo in v4: remove this
template <typename T>
[[deprecated("Use geode::toBytes instead")]]
ByteVector toByteArray(T const& a) {
ByteVector out;
out.resize(sizeof(T));
std::memcpy(out.data(), &a, sizeof(T));
return out;
}
template <typename T>
ByteVector toBytes(T const& a) {
ByteVector out;
@ -72,9 +63,7 @@ namespace geode {
template <typename T>
std::string intToHex(T i) {
std::stringstream stream;
stream << std::showbase << std::setbase(16) << (uint64_t)i;
return stream.str();
return fmt::format("{:#x}", i);
}
/**
@ -83,15 +72,16 @@ namespace geode {
* @param num Number to convert to string
* @param precision Precision of the converted number
* @returns Number as string
* @note Precision has no effect on integers
*/
template <class Num>
std::string numToString(Num num, size_t precision = 0) {
std::stringstream ss;
if (precision) {
ss << std::fixed << std::setprecision(precision);
if constexpr (std::is_floating_point_v<Num>) {
if (precision) {
return fmt::format("{:.{}f}", num, precision);
}
}
ss << num;
return ss.str();
return fmt::to_string(num);
}
/**
@ -125,7 +115,7 @@ namespace geode {
* @returns String as number, or Err if the string couldn't be converted
*/
template <class Num>
Result<Num> numFromString(std::string_view const str, int base = 10) {
Result<Num> numFromString(std::string_view str, int base = 10) {
if constexpr (std::is_floating_point_v<Num>
#if defined(__cpp_lib_to_chars)
&& false
@ -168,12 +158,18 @@ namespace geode {
*/
GEODE_DLL float getDisplayFactor();
}
template <class... Args>
requires (sizeof...(Args) > 0)
constexpr auto Err(fmt::format_string<Args...> fmt, Args&&... args) {
return Err(fmt::format(fmt, std::forward<Args>(args)...));
}
}
template<>
struct matjson::Serialize<geode::ByteVector> {
static matjson::Value to_json(geode::ByteVector const& bytes) {
return matjson::Array(bytes.begin(), bytes.end());
static Value toJson(geode::ByteVector const& bytes) {
return std::vector<matjson::Value>(bytes.begin(), bytes.end());
}
};

View file

@ -1,6 +1,6 @@
#pragma once
#include "Result.hpp"
#include <Geode/Result.hpp>
#include <Geode/DefaultInclude.hpp>
#include <functional>
@ -20,7 +20,7 @@ namespace geode::utils::map {
* false if not.
*/
template <typename T, typename R, typename H>
bool contains(std::unordered_map<T, R, H> const& map, utils::MiniFunction<bool(R)> containFunc) {
bool contains(std::unordered_map<T, R, H> const& map, std::function<bool(R)> containFunc) {
for (auto const& [_, r] : map) {
if (containFunc(r)) return true;
}
@ -39,7 +39,7 @@ namespace geode::utils::map {
* a pointer.
*/
template <class T, class R, class H>
R select(std::unordered_map<T, R, H> const& map, utils::MiniFunction<bool(R)> selectFunc) {
R select(std::unordered_map<T, R, H> const& map, std::function<bool(R)> selectFunc) {
for (auto const& [_, r] : map) {
if (selectFunc(r)) return r;
}
@ -59,7 +59,7 @@ namespace geode::utils::map {
*/
template <class T, class R, class H>
std::vector<R> selectAll(
std::unordered_map<T, R, H> const& map, utils::MiniFunction<bool(R)> selectFunc
std::unordered_map<T, R, H> const& map, std::function<bool(R)> selectFunc
) {
std::vector<R> res;
for (auto const& [_, r] : map) {
@ -111,7 +111,7 @@ namespace geode::utils::map {
template <class T1, class V1, class H1, class T2, class V2, class H2>
std::unordered_map<T2, V2, H2> remap(
std::unordered_map<T1, V1, H1> const& map,
utils::MiniFunction<std::pair<T2, V2>(std::pair<T1, V1>)> remapFunc
std::function<std::pair<T2, V2>(std::pair<T1, V1>)> remapFunc
) {
std::unordered_map<T2, V2, H2> res;
for (auto const& [t, v] : map) {

View file

@ -1,7 +1,6 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include "MiniFunction.hpp"
#include <string_view>
namespace geode::utils::permission {
@ -21,5 +20,5 @@ namespace geode::utils::permission {
* @param permission The permission
* @param callback The callback, passed value is 'true' if permission was granted and 'false' otherwise.
*/
void GEODE_DLL requestPermission(Permission permission, utils::MiniFunction<void(bool)> callback);
void GEODE_DLL requestPermission(Permission permission, std::function<void(bool)> callback);
}

View file

@ -4,6 +4,7 @@
#include <string>
#include <vector>
#include <compare>
#include "../DefaultInclude.hpp"
namespace geode::utils::string {
/**

View file

@ -71,6 +71,7 @@ namespace geode::utils {
std::string m_msg;
Timer<Clock> m_timer;
// @geode-ignore(geode-alternative)
LogPerformance(std::string const& msg = "", std::ostream& out = std::cout) :
m_msg(msg), m_output(out) {
m_timer = Timer<Clock>();

View file

@ -2,7 +2,7 @@
#include <Geode/loader/Loader.hpp> // another great circular dependency fix
#include <matjson.hpp>
#include "Result.hpp"
#include <Geode/Result.hpp>
#include "Task.hpp"
#include <chrono>
#include <optional>
@ -83,6 +83,15 @@ namespace geode::utils::web {
std::vector<std::string> headers() const;
std::optional<std::string> header(std::string_view name) const;
/**
* Retrieves a list of all headers from the response with a given name - there can be
* multiple headers with the same name, such as Set-Cookie, with each cookie in a separate
* header
* @param name name of the header
* @return std::optional<std::vector<std::string>>
*/
std::optional<std::vector<std::string>> getAllHeadersNamed(std::string_view name) const;
};
class GEODE_DLL WebProgress final {
@ -203,6 +212,15 @@ namespace geode::utils::web {
*/
WebRequest& followRedirects(bool enabled);
/**
* Enables or disables ignoring the content length header.
* The default is false.
*
* @param enabled
* @return WebRequest&
*/
WebRequest& ignoreContentLength(bool enabled);
/**
* Sets the Certificate Authority (CA) bundle content.
* Defaults to not sending a CA bundle.
@ -277,9 +295,9 @@ namespace geode::utils::web {
/**
* Gets the request headers
*
* @return std::unordered_map<std::string, std::string>
* @return std::unordered_map<std::string, std::vector<std::string>>
*/
std::unordered_map<std::string, std::string> getHeaders() const;
std::unordered_map<std::string, std::vector<std::string>> getHeaders() const;
/**
* Gets the parameters inside the URL

Binary file not shown.

View file

@ -6,6 +6,7 @@
struct XINPUT_STATE;
struct XINPUT_CAPABILITIES;
struct XINPUT_VIBRATION;
constexpr static auto MAX_PATH_CHARS = 32768u;
@ -41,6 +42,17 @@ extern "C" DWORD XInputGetState(DWORD dwUserIndex, XINPUT_STATE *pState) {
return ERROR_DEVICE_NOT_CONNECTED;
}
#pragma comment(linker, "/export:XInputSetState,@3")
extern "C" DWORD XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration) {
static auto fp = getFP("XInputSetState");
if (fp) {
using FPType = decltype(&XInputSetState);
return reinterpret_cast<FPType>(fp)(dwUserIndex, pVibration);
}
return ERROR_DEVICE_NOT_CONNECTED;
}
#pragma comment(linker, "/export:XInputGetCapabilities,@4")
extern "C" DWORD XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES *pCapabilities) {
static auto fp = getFP("XInputGetCapabilities");

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -85,6 +85,12 @@
"name": "Enable Geode-Themed Colors",
"description": "When enabled, the Geode menu has a <ca>Geode-themed color scheme</c>. <cy>This does not affect any other menus!</c>"
},
"infinite-local-mods-list": {
"type": "bool",
"default": false,
"name": "Expand Installed Mods List",
"description": "Make the installed mods list a single infinite scrollable list instead of having pages"
},
"copy-mods": {
"type": "custom:copy-mods",
"name": ""

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -13,7 +13,7 @@ namespace geode::stl {
void free();
char* getStorage();
void setStorage(const std::string_view);
void setStorage(std::string_view);
size_t getSize();
void setSize(size_t);

View file

@ -100,11 +100,11 @@ namespace gd {
bool string::operator==(string const& other) const {
return std::string_view(*this) == std::string_view(other);
}
bool string::operator==(std::string_view const other) const {
bool string::operator==(std::string_view other) const {
return std::string_view(*this) == other;
}
std::strong_ordering string::operator<=>(std::string_view const other) const {
std::strong_ordering string::operator<=>(std::string_view other) const {
return static_cast<std::strong_ordering>(std::string_view(*this).compare(other) <=> 0);
}

View file

@ -1026,14 +1026,6 @@ std::optional<AxisAlignment> AxisLayoutOptions::getCrossAxisAlignment() const {
return m_impl->m_crossAxisAlignment;
}
AxisLayoutOptions* AxisLayoutOptions::setMaxScale(float scale) {
m_impl->m_scaleLimits.second = scale;
return this;
}
AxisLayoutOptions* AxisLayoutOptions::setMinScale(float scale) {
m_impl->m_scaleLimits.first = scale;
return this;
}
AxisLayoutOptions* AxisLayoutOptions::setScaleLimits(std::optional<float> min, std::optional<float> max) {
m_impl->m_scaleLimits = { min, max };
return this;

View file

@ -8,11 +8,15 @@ $execute {
// this is needed because the transitions in cocos uses dynamic cast to check
// layers, which fail on user layers due to typeinfo not matching
#if defined(GEODE_IS_MAC) && GEODE_COMP_GD_VERSION != 22074
#error "Unsupported version for macOS dynamic cast fix, please update the addresses"
#endif
#if defined(GEODE_IS_INTEL_MAC)
void* dynamicCastAddr = reinterpret_cast<void*>(base::get() + 0x7dd5e7);
void* dynamicCastAddr = reinterpret_cast<void*>(base::get() + 0x7ba1d8);
(void) Mod::get()->hook(dynamicCastAddr, &cast::typeinfoCastInternal, "__dynamic_cast");
#elif defined(GEODE_IS_ARM_MAC)
void* dynamicCastAddr = reinterpret_cast<void*>(base::get() + 0x6dfb10);
void* dynamicCastAddr = reinterpret_cast<void*>(base::get() + 0x6c8bcc);
(void)Mod::get()->hook(dynamicCastAddr, &cast::typeinfoCastInternal, "__dynamic_cast");
#elif defined(GEODE_IS_ANDROID)
void* handle = dlopen("libcocos2dcpp.so", RTLD_LAZY | RTLD_NOLOAD);

View file

@ -62,10 +62,6 @@ public:
return meta;
}
FieldContainer* getFieldContainer() {
return nullptr;
}
FieldContainer* getFieldContainer(char const* forClass) {
if (!m_classFieldContainers.count(forClass)) {
m_classFieldContainers[forClass] = new FieldContainer();
@ -104,16 +100,11 @@ size_t modifier::getFieldIndexForClass(char const* name) {
return s_nextIndex[name]++;
}
// not const because might modify contents
FieldContainer* CCNode::getFieldContainer() {
return GeodeNodeMetadata::set(this)->getFieldContainer();
}
FieldContainer* CCNode::getFieldContainer(char const* forClass) {
return GeodeNodeMetadata::set(this)->getFieldContainer(forClass);
}
std::string CCNode::getID() {
const std::string& CCNode::getID() {
return GeodeNodeMetadata::set(this)->m_id;
}
@ -121,7 +112,11 @@ void CCNode::setID(std::string const& id) {
GeodeNodeMetadata::set(this)->m_id = id;
}
CCNode* CCNode::getChildByID(std::string const& id) {
void CCNode::setID(std::string&& id) {
GeodeNodeMetadata::set(this)->m_id = std::move(id);
}
CCNode* CCNode::getChildByID(std::string_view id) {
for (auto child : CCArrayExt<CCNode*>(this->getChildren())) {
if (child->getID() == id) {
return child;
@ -130,7 +125,7 @@ CCNode* CCNode::getChildByID(std::string const& id) {
return nullptr;
}
CCNode* CCNode::getChildByIDRecursive(std::string const& id) {
CCNode* CCNode::getChildByIDRecursive(std::string_view id) {
if (auto child = this->getChildByID(id)) {
return child;
}
@ -189,7 +184,7 @@ private:
std::unique_ptr<NodeQuery> m_next = nullptr;
public:
static Result<std::unique_ptr<NodeQuery>> parse(std::string const& query) {
static Result<std::unique_ptr<NodeQuery>> parse(std::string_view query) {
if (query.empty()) {
return Err("Query may not be empty");
}
@ -287,7 +282,7 @@ public:
}
};
CCNode* CCNode::querySelector(std::string const& queryStr) {
CCNode* CCNode::querySelector(std::string_view queryStr) {
auto res = NodeQuery::parse(queryStr);
if (!res) {
log::error("Invalid CCNode::querySelector query '{}': {}", queryStr, res.unwrapErr());
@ -298,7 +293,7 @@ CCNode* CCNode::querySelector(std::string const& queryStr) {
return query->match(this);
}
void CCNode::removeChildByID(std::string const& id) {
void CCNode::removeChildByID(std::string_view id) {
if (auto child = this->getChildByID(id)) {
this->removeChild(child);
}
@ -344,7 +339,7 @@ void CCNode::updateLayout(bool updateChildOrder) {
UserObjectSetEvent::UserObjectSetEvent(CCNode* node, std::string const& id, CCObject* value)
: node(node), id(id), value(value) {}
ListenerResult AttributeSetFilter::handle(MiniFunction<Callback> fn, UserObjectSetEvent* event) {
ListenerResult AttributeSetFilter::handle(std::function<Callback> fn, UserObjectSetEvent* event) {
if (event->id == m_targetID) {
fn(event);
}
@ -455,13 +450,4 @@ void CCNode::updateAnchoredPosition(Anchor anchor, CCPoint const& offset, CCPoin
}
}
#ifdef GEODE_EXPORTING
void CCNode::setAttribute(std::string const& attr, matjson::Value const& value) {}
std::optional<matjson::Value> CCNode::getAttributeInternal(std::string const& attr) {
return std::nullopt;
}
#endif
#pragma warning(pop)

View file

@ -75,6 +75,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
this->fixSocialMenu();
//this code doesnt run have fun figuring out why idc enough
if (auto node = this->getChildByID("settings-gamepad-icon")) {
node->setPositionX(
bottomMenu->getChildByID("settings-button")->getPositionX() + winSize.width / 2
@ -83,13 +84,12 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
}
// show if some mods failed to load
if (Loader::get()->getProblems().size()) {
if (Loader::get()->getLoadProblems().size()) {
static bool shownProblemPopup = false;
if (!shownProblemPopup) {
shownProblemPopup = true;
Notification::create("There were errors - see Geode page!", NotificationIcon::Error)->show();
}
if (m_fields->m_geodeButton) {
m_fields->m_exclamation = CCSprite::createWithSpriteFrameName("exMark_001.png");
m_fields->m_exclamation->setPosition(m_fields->m_geodeButton->getContentSize() - ccp(10, 10));

View file

@ -41,9 +41,9 @@ $execute {
// hook them to call our own handler
if (LoaderImpl::get()->isForwardCompatMode()) return;
#if GEODE_COMP_GD_VERSION == 22060
const uintptr_t offset1 = 0x75d00; // member function in CCEGLView
const uintptr_t offset2 = 0x75d60; // static function
#if GEODE_COMP_GD_VERSION == 22074
const uintptr_t offset1 = 0x75D90; // member function in CCEGLView
const uintptr_t offset2 = 0x75DF0; // static function
(void) Mod::get()->hook(
reinterpret_cast<void*>(geode::base::getCocos() + offset1),

View file

@ -37,15 +37,15 @@ static void patchCall(uintptr_t addr, uintptr_t newCall) {
$execute {
if (LoaderImpl::get()->isForwardCompatMode()) return;
#if GEODE_COMP_GD_VERSION == 22040
// BitmapDC::~BitmapDC
#if 0 // TODO: mat GEODE_COMP_GD_VERSION == 22040
patchCall(0xC9A56, (uintptr_t)&RemoveFontResourceWHook);
// BitmapDC::setFont
patchCall(0xCB5BC, (uintptr_t)&RemoveFontResourceWHook);
patchCall(0xCB642, (uintptr_t)&AddFontResourceWHook);
#else
// #pragma message("Unsupported GD version!")
#pragma message("Unsupported GD version!")
#endif
};

View file

@ -2,10 +2,15 @@
using namespace geode::prelude;
#ifdef GEODE_IS_WINDOWS
#include <Geode/modify/AppDelegate.hpp>
#else
#include <Geode/modify/AchievementNotifier.hpp>
#endif
namespace geode {
#ifdef GEODE_IS_WINDOWS
#include <Geode/modify/AppDelegate.hpp>
struct SceneSwitch : Modify<SceneSwitch, AppDelegate> {
GEODE_FORWARD_COMPAT_DISABLE_HOOKS("persist disabled")
void willSwitchToScene(CCScene* scene) {
@ -15,8 +20,6 @@ struct SceneSwitch : Modify<SceneSwitch, AppDelegate> {
};
#else
#include <Geode/modify/AchievementNotifier.hpp>
struct SceneSwitch : Modify<SceneSwitch, AchievementNotifier> {
GEODE_FORWARD_COMPAT_DISABLE_HOOKS("persist disabled")
void willSwitchToScene(CCScene* scene) {
@ -26,3 +29,5 @@ struct SceneSwitch : Modify<SceneSwitch, AchievementNotifier> {
};
#endif
}

View file

@ -8,7 +8,7 @@ using namespace geode::prelude;
namespace {
void saveModData() {
log::info("Saving mod data...");
log::pushNest();
log::NestScope nest;
auto begin = std::chrono::high_resolution_clock::now();
@ -17,8 +17,6 @@ namespace {
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
log::info("Took {}s", static_cast<float>(time) / 1000.f);
log::popNest();
}
}

View file

@ -46,6 +46,11 @@ $register_ids(MenuLayer) {
}
setIDSafe<CCLabelBMFont>(this, labelOffset++, "player-username");
if(auto node = this->getChildByID("settings-gamepad-icon")) {
// hide it until someone figures out how to bind the positioning to the actual button
node->setVisible(false);
}
// main menu
if (auto menu = this->getChildByType<CCMenu>(0)) {

View file

@ -2,15 +2,14 @@
#include <Geode/DefaultInclude.hpp>
//#include <Geode/utils/general.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include <filesystem>
#include <functional>
#include <string>
class FileWatcher {
public:
using FileWatchCallback = geode::utils::MiniFunction<void(std::filesystem::path)>;
using ErrorCallback = geode::utils::MiniFunction<void(std::string)>;
using FileWatchCallback = std::function<void(std::filesystem::path)>;
using ErrorCallback = std::function<void(std::string)>;
protected:
std::filesystem::path m_file;

View file

@ -20,7 +20,7 @@ protected:
std::string content;
std::string optionA;
std::string optionB;
MiniFunction<void(bool)> after;
std::function<void(bool)> after;
};
EventListener<server::ModDownloadFilter> m_download;
@ -79,7 +79,7 @@ protected:
public:
void start() {
for (auto problem : Loader::get()->getProblems()) {
for (auto problem : Loader::get()->getAllProblems()) {
switch (problem.type) {
// Errors where the correct solution is to just delete the invalid .geode package
case LoadProblem::Type::InvalidFile:

View file

@ -24,7 +24,8 @@ void crashlog::printGeodeInfo(std::stringstream& stream) {
<< "Loader Commit: " << about::getLoaderCommitHash() << "\n"
<< "Bindings Commit: " << about::getBindingsCommitHash() << "\n"
<< "Installed mods: " << Loader::get()->getAllMods().size() << "\n"
<< "Problems: " << Loader::get()->getProblems().size() << "\n";
<< "Outdated mods: " << Loader::get()->getOutdated().size() << "\n"
<< "Problems: " << Loader::get()->getLoadProblems().size() << "\n";
}
void crashlog::printMods(std::stringstream& stream) {
@ -44,7 +45,8 @@ void crashlog::printMods(std::stringstream& stream) {
stream << fmt::format("{} | [{}] {}\n",
mod->isCurrentlyLoading() ? "o"sv :
mod->isEnabled() ? "x"sv :
mod->hasProblems() ? "!"sv : // thank you for this bug report
mod->hasLoadProblems() ? "!"sv : // thank you for this bug report
mod->targetsOutdatedVersion() ? "*"sv : // thank you very much for this bug report
mod->shouldLoad() ? "~"sv :
" "sv,
mod->getVersion().toVString(), mod->getID()

View file

@ -7,7 +7,6 @@
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/SettingEvent.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <loader/LogImpl.hpp>
@ -30,8 +29,7 @@ $on_mod(Loaded) {
std::vector<matjson::Value> res;
auto args = *event->messageData;
JsonChecker checker(args);
auto root = checker.root("[ipc/list-mods]").obj();
auto root = checkJson(args, "[ipc/list-mods]");
auto includeRunTimeInfo = root.has("include-runtime-info").get<bool>();
auto dontIncludeLoader = root.has("dont-include-loader").get<bool>();
@ -69,13 +67,13 @@ void tryShowForwardCompat() {
return;
// TODO: change text later
console::messageBox(
"Forward Compatibility Warning",
"Geode is running in a newer version of GD than Geode targets.\n"
"UI is going to be disabled, platform console is forced on and crashes can be more common.\n"
"However, if your game crashes, it is probably caused by an outdated mod and not Geode itself.",
Severity::Warning
);
// console::messageBox(
// "Forward Compatibility Warning",
// "Geode is running in a newer version of GD than Geode targets.\n"
// "UI is going to be disabled, platform console is forced on and crashes can be more common.\n"
// "However, if your game crashes, it is probably caused by an outdated mod and not Geode itself.",
// Severity::Warning
// );
Mod::get()->setSavedValue<std::string>(
"last-forward-compat-warn-popup-ver",
@ -86,7 +84,7 @@ void tryShowForwardCompat() {
#ifdef GEODE_IS_WINDOWS
bool safeModeCheck() {
// yes this is quite funny
if (GetAsyncKeyState(VK_SHIFT) == 0) {
if (!(GetAsyncKeyState(VK_SHIFT) & (1 << 15))) {
return false;
}
@ -140,17 +138,18 @@ int geodeEntry(void* platformData) {
// set up internal mod, settings and data
log::info("Setting up internal mod");
log::pushNest();
auto internalSetupRes = LoaderImpl::get()->setupInternalMod();
log::popNest();
if (!internalSetupRes) {
console::messageBox(
"Unable to Load Geode!",
"There was a fatal error setting up "
"the internal mod and Geode can not be loaded: " + internalSetupRes.unwrapErr()
);
LoaderImpl::get()->forceReset();
return 1;
{
log::NestScope nest;
auto internalSetupRes = LoaderImpl::get()->setupInternalMod();
if (!internalSetupRes) {
console::messageBox(
"Unable to Load Geode!",
"There was a fatal error setting up "
"the internal mod and Geode can not be loaded: " + internalSetupRes.unwrapErr()
);
LoaderImpl::get()->forceReset();
return 1;
}
}
tryShowForwardCompat();
@ -163,26 +162,28 @@ int geodeEntry(void* platformData) {
// set up loader, load mods, etc.
log::info("Setting up loader");
log::pushNest();
auto setupRes = LoaderImpl::get()->setup();
log::popNest();
if (!setupRes) {
console::messageBox(
"Unable to Load Geode!",
"There was an unknown fatal error setting up "
"the loader and Geode can not be loaded. "
"(" + setupRes.unwrapErr() + ")"
);
LoaderImpl::get()->forceReset();
return 1;
{
log::NestScope nest;
auto setupRes = LoaderImpl::get()->setup();
if (!setupRes) {
console::messageBox(
"Unable to Load Geode!",
"There was an unknown fatal error setting up "
"the loader and Geode can not be loaded. "
"(" + setupRes.unwrapErr() + ")"
);
LoaderImpl::get()->forceReset();
return 1;
}
}
crashlog::setupPlatformHandlerPost();
log::debug("Setting up IPC");
log::pushNest();
ipc::setup();
log::popNest();
{
log::NestScope nest;
ipc::setup();
}
// download and install new loader update in the background

View file

@ -98,7 +98,7 @@ std::string_view Hook::Impl::getDisplayName() const {
}
matjson::Value Hook::Impl::getRuntimeInfo() const {
auto json = matjson::Object();
matjson::Value json;
json["address"] = std::to_string(reinterpret_cast<uintptr_t>(m_address));
json["detour"] = std::to_string(reinterpret_cast<uintptr_t>(m_detour));
json["name"] = m_displayName;

View file

@ -19,7 +19,7 @@ ipc::IPCEvent::IPCEvent(
ipc::IPCEvent::~IPCEvent() {}
ListenerResult ipc::IPCFilter::handle(utils::MiniFunction<Callback> fn, IPCEvent* event) {
ListenerResult ipc::IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) {
if (event->targetModID == m_modID && event->messageID == m_messageID) {
event->replyData = fn(event);
return ListenerResult::Stop;
@ -33,19 +33,18 @@ ipc::IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID
matjson::Value ipc::processRaw(void* rawHandle, std::string const& buffer) {
matjson::Value reply;
std::string error;
auto res = matjson::parse(buffer, error);
if (error.size() > 0) {
log::warn("Received IPC message that isn't valid JSON: {}", error);
auto res = matjson::Value::parse(buffer);
if (!res) {
log::warn("Received IPC message that isn't valid JSON: {}", res.unwrapErr());
return reply;
}
matjson::Value json = res.value();
matjson::Value json = res.unwrap();
if (!json.contains("mod") || !json["mod"].is_string()) {
if (!json.contains("mod") || !json["mod"].isString()) {
log::warn("Received IPC message without 'mod' field");
return reply;
}
if (!json.contains("message") || !json["message"].is_string()) {
if (!json.contains("message") || !json["message"].isString()) {
log::warn("Received IPC message without 'message' field");
return reply;
}
@ -55,6 +54,6 @@ matjson::Value ipc::processRaw(void* rawHandle, std::string const& buffer) {
}
// log::debug("Posting IPC event");
// ! warning: if the event system is ever made asynchronous this will break!
IPCEvent(rawHandle, json["mod"].as_string(), json["message"].as_string(), data, reply).post();
IPCEvent(rawHandle, json["mod"].asString().unwrap(), json["message"].asString().unwrap(), data, reply).post();
return reply;
}

View file

@ -68,13 +68,19 @@ std::vector<Mod*> Loader::getAllMods() {
std::vector<LoadProblem> Loader::getAllProblems() const {
return m_impl->getProblems();
}
std::vector<LoadProblem> Loader::getProblems() const {
std::vector<LoadProblem> Loader::getLoadProblems() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type != LoadProblem::Type::Recommendation &&
problem.type != LoadProblem::Type::Suggestion
) {
if (problem.isProblem()) {
result.push_back(problem);
}
}
return result;
}
std::vector<LoadProblem> Loader::getOutdated() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (problem.isOutdated()) {
result.push_back(problem);
}
}
@ -83,10 +89,7 @@ std::vector<LoadProblem> Loader::getProblems() const {
std::vector<LoadProblem> Loader::getRecommendations() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type == LoadProblem::Type::Recommendation ||
problem.type == LoadProblem::Type::Suggestion
) {
if (problem.isSuggestion()) {
result.push_back(problem);
}
}
@ -109,14 +112,14 @@ std::vector<std::string> Loader::getLaunchArgumentNames() const {
return m_impl->getLaunchArgumentNames();
}
bool Loader::hasLaunchArgument(std::string_view const name) const {
bool Loader::hasLaunchArgument(std::string_view name) const {
return m_impl->hasLaunchArgument(name);
}
std::optional<std::string> Loader::getLaunchArgument(std::string_view const name) const {
std::optional<std::string> Loader::getLaunchArgument(std::string_view name) const {
return m_impl->getLaunchArgument(name);
}
bool Loader::getLaunchFlag(std::string_view const name) const {
bool Loader::getLaunchFlag(std::string_view name) const {
return m_impl->getLaunchFlag(name);
}

View file

@ -29,6 +29,9 @@
#include <string_view>
#include <vector>
#include <server/DownloadManager.hpp>
#include <Geode/ui/Popup.hpp>
using namespace geode::prelude;
Loader::Impl* LoaderImpl::get() {
@ -42,6 +45,11 @@ Loader::Impl::~Impl() = default;
// Initialization
bool Loader::Impl::isForwardCompatMode() {
#ifdef GEODE_IS_ANDROID
// forward compat mode doesn't really make sense on android
return false;
#endif
if (!m_forwardCompatMode.has_value()) {
m_forwardCompatMode = !this->getGameVersion().empty() &&
this->getGameVersion() != GEODE_STR(GEODE_GD_VERSION);
@ -73,35 +81,32 @@ Result<> Loader::Impl::setup() {
if (this->supportsLaunchArguments()) {
log::debug("Loading launch arguments");
log::pushNest();
log::NestScope nest;
this->initLaunchArguments();
log::popNest();
}
// on some platforms, using the crash handler overrides more convenient native handlers
if (!this->getLaunchFlag("disable-crash-handler")) {
log::debug("Setting up crash handler");
log::pushNest();
log::NestScope nest;
if (!crashlog::setupPlatformHandler()) {
log::debug("Failed to set up crash handler");
}
log::popNest();
} else {
log::debug("Crash handler setup skipped");
}
log::debug("Loading hooks");
log::pushNest();
if (!this->loadHooks()) {
if (log::NestScope nest; !this->loadHooks()) {
return Err("There were errors loading some hooks, see console for details");
}
log::popNest();
log::debug("Setting up directories");
log::pushNest();
this->createDirectories();
this->addSearchPaths();
log::popNest();
{
log::NestScope nest;
this->createDirectories();
this->addSearchPaths();
}
// Trigger on_mod(Loaded) for the internal mod
// this function is already on the gd thread, so this should be fine
@ -121,7 +126,7 @@ void Loader::Impl::addSearchPaths() {
void Loader::Impl::updateResources(bool forceReload) {
log::debug("Adding resources");
log::pushNest();
log::NestScope nest;
for (auto const& [_, mod] : m_mods) {
if (!forceReload && ModImpl::getImpl(mod)->m_resourcesLoaded)
continue;
@ -132,7 +137,6 @@ void Loader::Impl::updateResources(bool forceReload) {
// we have to call it in both places since setup is only called once ever, but updateResources is called
// on every texture reload
CCFileUtils::get()->updatePaths();
log::popNest();
}
std::vector<Mod*> Loader::Impl::getAllMods() {
@ -170,24 +174,22 @@ bool Loader::Impl::isModVersionSupported(VersionInfo const& target) {
void Loader::Impl::saveData() {
for (auto& [id, mod] : m_mods) {
log::debug("{}", mod->getID());
log::pushNest();
log::NestScope nest;
auto r = mod->saveData();
if (!r) {
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
}
log::popNest();
}
}
void Loader::Impl::loadData() {
for (auto& [_, mod] : m_mods) {
log::debug("{}", mod->getID());
log::pushNest();
log::NestScope nest;
auto r = mod->loadData();
if (!r) {
log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
}
log::popNest();
}
}
@ -230,7 +232,7 @@ void Loader::Impl::updateModResources(Mod* mod) {
return;
log::debug("{}", mod->getID());
log::pushNest();
log::NestScope nest;
for (auto const& sheet : mod->getMetadata().getSpritesheets()) {
log::debug("Adding sheet {}", sheet);
@ -250,8 +252,6 @@ void Loader::Impl::updateModResources(Mod* mod) {
CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
}
}
log::popNest();
}
void Loader::Impl::addProblem(LoadProblem const& problem) {
@ -267,14 +267,14 @@ void Loader::Impl::addProblem(LoadProblem const& problem) {
void Loader::Impl::queueMods(std::vector<ModMetadata>& modQueue) {
for (auto const& dir : m_modSearchDirectories) {
log::debug("Searching {}", dir);
log::pushNest();
log::NestScope nest;
for (auto const& entry : std::filesystem::directory_iterator(dir)) {
if (!std::filesystem::is_regular_file(entry) ||
entry.path().extension() != GEODE_MOD_EXTENSION)
continue;
log::debug("Found {}", entry.path().filename());
log::pushNest();
log::NestScope nest;
auto res = ModMetadata::createFromGeodeFile(entry.path());
if (!res) {
@ -284,7 +284,6 @@ void Loader::Impl::queueMods(std::vector<ModMetadata>& modQueue) {
res.unwrapErr()
});
log::error("Failed to queue: {}", res.unwrapErr());
log::popNest();
continue;
}
auto modMetadata = res.unwrap();
@ -302,14 +301,11 @@ void Loader::Impl::queueMods(std::vector<ModMetadata>& modQueue) {
"A mod with the same ID is already present."
});
log::error("Failed to queue: a mod with the same ID is already queued");
log::popNest();
continue;
}
modQueue.push_back(modMetadata);
log::popNest();
}
log::popNest();
}
}
@ -327,7 +323,7 @@ void Loader::Impl::populateModList(std::vector<ModMetadata>& modQueue) {
for (auto const& metadata : modQueue) {
log::debug("{} {}", metadata.getID(), metadata.getVersion());
log::pushNest();
log::NestScope nest;
auto mod = new Mod(metadata);
@ -339,20 +335,17 @@ void Loader::Impl::populateModList(std::vector<ModMetadata>& modQueue) {
res.unwrapErr()
});
log::error("Failed to set up: {}", res.unwrapErr());
log::popNest();
continue;
}
m_mods.insert({metadata.getID(), mod});
log::popNest();
}
}
void Loader::Impl::buildModGraph() {
for (auto const& [id, mod] : m_mods) {
log::debug("{}", mod->getID());
log::pushNest();
log::NestScope nest;
for (auto& dependency : mod->m_impl->m_metadata.m_impl->m_dependencies) {
log::debug("{}", dependency.id);
if (!m_mods.contains(dependency.id)) {
@ -379,11 +372,38 @@ void Loader::Impl::buildModGraph() {
incompatibility.mod =
m_mods.contains(incompatibility.id) ? m_mods[incompatibility.id] : nullptr;
}
log::popNest();
}
}
void Loader::Impl::loadModGraph(Mod* node, bool early) {
// Check version first, as it's not worth trying to load a mod with an
// invalid target version
// Also this makes it so that when GD updates, outdated mods get shown as
// "Outdated" in the UI instead of "Missing Dependencies"
auto res = node->getMetadata().checkGameVersion();
if (!res) {
this->addProblem({
LoadProblem::Type::UnsupportedVersion,
node,
res.unwrapErr()
});
log::error("{}", res.unwrapErr());
return;
}
auto geodeVerRes = node->getMetadata().checkGeodeVersion();
if (!geodeVerRes) {
this->addProblem({
node->getMetadata().getGeodeVersion() > this->getVersion() ?
LoadProblem::Type::NeedsNewerGeodeVersion :
LoadProblem::Type::UnsupportedGeodeVersion,
node,
geodeVerRes.unwrapErr()
});
log::error("{}", geodeVerRes.unwrapErr());
return;
}
if (node->hasUnresolvedDependencies()) {
log::debug("{} {} has unresolved dependencies", node->getID(), node->getVersion());
return;
@ -393,12 +413,10 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
return;
}
log::debug("{} {}", node->getID(), node->getVersion());
log::pushNest();
log::NestScope nest;
if (node->isEnabled()) {
log::warn("Mod {} already loaded, this should never happen", node->getID());
log::popNest();
return;
}
@ -409,6 +427,7 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
auto unzipFunction = [this, node]() {
log::debug("Unzip");
log::NestScope nest;
auto res = node->m_impl->unzipGeodeFile(node->getMetadata());
return res;
};
@ -416,6 +435,7 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
auto loadFunction = [this, node, early]() {
if (node->shouldLoad()) {
log::debug("Load");
log::NestScope nest;
auto res = node->m_impl->loadBinary();
if (!res) {
this->addProblem({
@ -441,36 +461,6 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
});
log::error("{}", reason.value());
m_refreshingModCount -= 1;
log::popNest();
return;
}
auto res = node->getMetadata().checkGameVersion();
if (!res) {
this->addProblem({
LoadProblem::Type::UnsupportedVersion,
node,
res.unwrapErr()
});
log::error("{}", res.unwrapErr());
m_refreshingModCount -= 1;
log::popNest();
return;
}
if (!this->isModVersionSupported(node->getMetadata().getGeodeVersion())) {
this->addProblem({
node->getMetadata().getGeodeVersion() > this->getVersion() ? LoadProblem::Type::NeedsNewerGeodeVersion : LoadProblem::Type::UnsupportedGeodeVersion,
node,
fmt::format(
"Geode version {}\nis required to run this mod\n(installed: {})",
node->getMetadata().getGeodeVersion().toVString(),
this->getVersion().toVString()
)
});
log::error("Unsupported Geode version: {}", node->getMetadata().getGeodeVersion());
m_refreshingModCount -= 1;
log::popNest();
return;
}
}
@ -485,7 +475,6 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
});
log::error("Failed to unzip: {}", res.unwrapErr());
m_refreshingModCount -= 1;
log::popNest();
return;
}
loadFunction();
@ -496,7 +485,7 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
thread::setName("Mod Unzip");
log::loadNest(nest);
auto res = unzipFunction();
this->queueInMainThread([=, this]() {
this->queueInMainThread([=, this, res = std::move(res)]() {
auto prevNest = log::saveNest();
log::loadNest(nest);
if (!res) {
@ -515,7 +504,6 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
});
}).detach();
}
log::popNest();
}
void Loader::Impl::findProblems() {
@ -524,8 +512,12 @@ void Loader::Impl::findProblems() {
log::debug("{} is not enabled", id);
continue;
}
if (mod->targetsOutdatedVersion()) {
log::debug("{} is outdated", id);
continue;
}
log::debug("{}", id);
log::pushNest();
log::NestScope nest;
for (auto const& dep : mod->getMetadata().getDependencies()) {
if (dep.mod && dep.mod->isEnabled() && dep.version.compare(dep.mod->getVersion()))
@ -652,18 +644,15 @@ void Loader::Impl::findProblems() {
});
log::error("{} failed to load for an unknown reason", id);
}
log::popNest();
}
}
void Loader::Impl::refreshModGraph() {
log::info("Refreshing mod graph");
log::pushNest();
log::NestScope nest;
if (m_isSetup) {
log::error("Cannot refresh mod graph after startup");
log::popNest();
return;
}
@ -673,48 +662,51 @@ void Loader::Impl::refreshModGraph() {
m_loadingState = LoadingState::Queue;
log::debug("Queueing mods");
log::pushNest();
std::vector<ModMetadata> modQueue;
this->queueMods(modQueue);
log::popNest();
{
log::NestScope nest;
this->queueMods(modQueue);
}
m_loadingState = LoadingState::List;
log::debug("Populating mod list");
log::pushNest();
this->populateModList(modQueue);
modQueue.clear();
log::popNest();
{
log::NestScope nest;
this->populateModList(modQueue);
modQueue.clear();
}
m_loadingState = LoadingState::Graph;
log::debug("Building mod graph");
log::pushNest();
this->buildModGraph();
log::popNest();
{
log::NestScope nest;
this->buildModGraph();
}
log::debug("Ordering mod stack");
log::pushNest();
this->orderModStack();
log::popNest();
{
log::NestScope nest;
this->orderModStack();
}
m_loadingState = LoadingState::EarlyMods;
log::debug("Loading early mods");
log::pushNest();
while (!m_modsToLoad.empty() && m_modsToLoad.front()->needsEarlyLoad()) {
auto mod = m_modsToLoad.front();
m_modsToLoad.pop_front();
this->loadModGraph(mod, true);
{
log::NestScope nest;
while (!m_modsToLoad.empty() && m_modsToLoad.front()->needsEarlyLoad()) {
auto mod = m_modsToLoad.front();
m_modsToLoad.pop_front();
this->loadModGraph(mod, true);
}
}
log::popNest();
auto end = std::chrono::high_resolution_clock::now();
auto time = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin).count();
log::info("Took {}s. Continuing next frame...", static_cast<float>(time) / 1000.f);
log::popNest();
m_loadingState = LoadingState::Mods;
queueInMainThread([&]() {
queueInMainThread([this]() {
this->continueRefreshModGraph();
});
}
@ -768,7 +760,7 @@ void Loader::Impl::orderModStack() {
void Loader::Impl::continueRefreshModGraph() {
if (m_refreshingModCount != 0) {
queueInMainThread([&]() {
queueInMainThread([this]() {
this->continueRefreshModGraph();
});
return;
@ -781,28 +773,27 @@ void Loader::Impl::continueRefreshModGraph() {
}
log::info("Continuing mod graph refresh...");
log::pushNest();
log::NestScope nest;
m_timerBegin = std::chrono::high_resolution_clock::now();
switch (m_loadingState) {
case LoadingState::Mods:
if (!m_modsToLoad.empty()) {
log::debug("Loading mods");
log::pushNest();
auto mod = m_modsToLoad.front();
m_modsToLoad.pop_front();
log::debug("Loading mod {} {}", mod->getID(), mod->getVersion());
this->loadModGraph(mod, false);
log::popNest();
break;
}
m_loadingState = LoadingState::Problems;
[[fallthrough]];
case LoadingState::Problems:
log::debug("Finding problems");
log::pushNest();
this->findProblems();
log::popNest();
{
log::NestScope nest;
this->findProblems();
}
m_loadingState = LoadingState::Done;
{
auto end = std::chrono::high_resolution_clock::now();
@ -818,12 +809,10 @@ void Loader::Impl::continueRefreshModGraph() {
}
if (m_loadingState != LoadingState::Done) {
queueInMainThread([&]() {
queueInMainThread([this]() {
this->continueRefreshModGraph();
});
}
log::popNest();
}
std::vector<LoadProblem> Loader::Impl::getProblems() const {
@ -927,11 +916,11 @@ std::vector<std::string> Loader::Impl::getLaunchArgumentNames() const {
return map::keys(m_launchArgs);
}
bool Loader::Impl::hasLaunchArgument(std::string_view const name) const {
bool Loader::Impl::hasLaunchArgument(std::string_view name) const {
return m_launchArgs.find(std::string(name)) != m_launchArgs.end();
}
std::optional<std::string> Loader::Impl::getLaunchArgument(std::string_view const name) const {
std::optional<std::string> Loader::Impl::getLaunchArgument(std::string_view name) const {
auto value = m_launchArgs.find(std::string(name));
if (value == m_launchArgs.end()) {
return std::nullopt;
@ -939,7 +928,7 @@ std::optional<std::string> Loader::Impl::getLaunchArgument(std::string_view cons
return std::optional(value->second);
}
bool Loader::Impl::getLaunchFlag(std::string_view const name) const {
bool Loader::Impl::getLaunchFlag(std::string_view name) const {
auto arg = this->getLaunchArgument(name);
return arg.has_value() && arg.value() == "true";
}
@ -966,4 +955,171 @@ bool Loader::Impl::isSafeMode() const {
void Loader::Impl::forceSafeMode() {
m_forceSafeMode = true;
}
}
void Loader::Impl::installModManuallyFromFile(std::filesystem::path const& path, std::function<void()> after) {
auto res = ModMetadata::createFromGeodeFile(path);
if (!res) {
FLAlertLayer::create(
"Invalid File",
fmt::format(
"The path <cy>'{}'</c> is not a valid Geode mod: {}",
path.string(),
res.unwrapErr()
),
"OK"
)->show();
return;
}
auto meta = res.unwrap();
auto check = meta.checkTargetVersions();
if (!check) {
FLAlertLayer::create(
"Invalid Mod Version",
fmt::format(
"The mod <cy>{}</c> can not be installed: {}",
meta.getID(),
check.unwrapErr()
),
"OK"
)->show();
}
auto doInstallModFromFile = [this, path, meta, after]() {
std::error_code ec;
static size_t MAX_ATTEMPTS = 10;
// Figure out a free path to install to
auto installTo = dirs::getModsDir() / fmt::format("{}.geode", meta.getID());
size_t counter = 0;
while (std::filesystem::exists(installTo, ec) && counter < MAX_ATTEMPTS) {
installTo = dirs::getModsDir() / fmt::format("{}-{}.geode", meta.getID(), counter);
counter += 1;
}
// This is incredibly unlikely but theoretically possible
if (counter >= MAX_ATTEMPTS) {
FLAlertLayer::create(
"Unable to Install",
fmt::format(
"Unable to install mod <co>{}</c>: Can't find a free filename!",
meta.getID()
),
"OK"
)->show();
return;
}
// Actually copy the file over to the install directory
std::filesystem::copy_file(path, installTo, ec);
if (ec) {
FLAlertLayer::create(
"Unable to Install",
fmt::format(
"Unable to install mod <co>{}</c>: {} (Error code <cr>{}</c>)",
meta.getID(), ec.message(), ec.value()
),
"OK"
)->show();
return;
}
// Mark an updated mod as updated or add to the mods list
if (m_mods.contains(meta.getID())) {
m_mods.at(meta.getID())->m_impl->m_requestedAction = ModRequestedAction::Update;
}
// Otherwise add a new Mod
// This should be safe as all of the scary stuff in setup() is only relevant
// for mods that are actually running
else {
auto mod = new Mod(meta);
auto res = mod->m_impl->setup();
if (!res) {
log::error("Unable to set up manually installed mod: {}", res.unwrapErr());
}
(void)mod->enable();
m_mods.insert({ meta.getID(), mod });
}
if (after) after();
// No need for the user to go and manually clean up the file
createQuickPopup(
"Mod Installed",
fmt::format(
"Mod <co>{}</c> has been successfully installed from file! "
"<cy>Do you want to delete the original file?</c>",
meta.getName()
),
"OK", "Delete File",
[path](auto, bool btn2) {
if (btn2) {
std::error_code ec;
std::filesystem::remove(path, ec);
if (ec) {
FLAlertLayer::create(
"Unable to Delete",
fmt::format(
"Unable to delete <cy>{}</c>: {} (Error code <cr>{}</c>)",
path, ec.message(), ec.value()
),
"OK"
)->show();
}
// No need to show a confirmation popup if successful since that's
// to be assumed via pressing the button on the previous popup
}
}
);
};
if (auto existing = Loader::get()->getInstalledMod(meta.getID())) {
createQuickPopup(
"Already Installed",
fmt::format(
"The mod <cy>{}</c> <cj>{}</c> has already been installed "
"as version <cl>{}</c>. Do you want to <co>replace the "
"installed version with the file</c>?",
meta.getID(), meta.getVersion(),
existing->getVersion()
),
"Cancel", "Replace",
[doInstallModFromFile, path, existing, meta](auto, bool btn2) mutable {
std::error_code ec;
std::filesystem::remove(existing->getPackagePath(), ec);
if (ec) {
FLAlertLayer::create(
"Unable to Uninstall",
fmt::format(
"Unable to uninstall <cy>{}</c>: {} (Error code <cr>{}</c>)",
existing->getID(), ec.message(), ec.value()
),
"OK"
)->show();
return;
}
doInstallModFromFile();
}
);
return;
}
doInstallModFromFile();
}
bool Loader::Impl::isRestartRequired() const {
for (auto mod : Loader::get()->getAllMods()) {
if (mod->getRequestedAction() != ModRequestedAction::None) {
return true;
}
if (ModSettingsManager::from(mod)->restartRequired()) {
return true;
}
}
if (server::ModDownloadManager::get()->wantsRestart()) {
return true;
}
return false;
}

View file

@ -7,10 +7,9 @@
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/utils/Result.hpp>
#include <Geode/Result.hpp>
#include <Geode/utils/map.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/MiniFunction.hpp>
#include "ModImpl.hpp"
#include <crashlog.hpp>
#include <mutex>
@ -41,7 +40,7 @@ namespace geode {
LoadingState m_loadingState = LoadingState::None;
std::vector<utils::MiniFunction<void(void)>> m_mainThreadQueue;
std::vector<std::function<void(void)>> m_mainThreadQueue;
mutable std::mutex m_mainThreadMutex;
std::vector<std::pair<Hook*, Mod*>> m_uninitializedHooks;
bool m_readyToHook = false;
@ -116,9 +115,9 @@ namespace geode {
std::string getLaunchCommand() const;
void initLaunchArguments();
std::vector<std::string> getLaunchArgumentNames() const;
bool hasLaunchArgument(std::string_view const name) const;
std::optional<std::string> getLaunchArgument(std::string_view const name) const;
bool getLaunchFlag(std::string_view const name) const;
bool hasLaunchArgument(std::string_view name) const;
std::optional<std::string> getLaunchArgument(std::string_view name) const;
bool getLaunchFlag(std::string_view name) const;
void updateResources(bool forceReload);
@ -139,6 +138,12 @@ namespace geode {
bool isSafeMode() const;
// enables safe mode, even if the launch arg wasnt provided
void forceSafeMode();
// This will potentially start a whole sequence of popups that guide the
// user through installing the specific .geode file
void installModManuallyFromFile(std::filesystem::path const& path, std::function<void()> after);
bool isRestartRequired() const;
};
class LoaderImpl : public Loader::Impl {

View file

@ -20,10 +20,6 @@ std::string Mod::getName() const {
return m_impl->getName();
}
std::string Mod::getDeveloper() const {
return m_impl->getDevelopers().empty() ? "" : m_impl->getDevelopers().front();
}
std::vector<std::string> Mod::getDevelopers() const {
return m_impl->getDevelopers();
}
@ -101,9 +97,6 @@ std::vector<Mod*> Mod::getDependants() const {
}
#endif
std::optional<VersionInfo> Mod::hasAvailableUpdate() const {
return std::nullopt;
}
Mod::CheckUpdatesTask Mod::checkUpdates() const {
return server::checkUpdates(this).map(
[](auto* result) -> Mod::CheckUpdatesTask::Value {
@ -155,28 +148,14 @@ std::vector<std::string> Mod::getSettingKeys() const {
return m_impl->getSettingKeys();
}
bool Mod::hasSetting(std::string_view const key) const {
bool Mod::hasSetting(std::string_view key) const {
return m_impl->hasSetting(key);
}
std::optional<Setting> Mod::getSettingDefinition(std::string_view const key) const {
return m_impl->m_settings->getLegacyDefinition(std::string(key));
}
SettingValue* Mod::getSetting(std::string_view const key) const {
return m_impl->m_settings->getLegacy(std::string(key)).get();
}
std::shared_ptr<SettingV3> Mod::getSettingV3(std::string_view const key) const {
std::shared_ptr<Setting> Mod::getSetting(std::string_view key) const {
return m_impl->m_settings->get(std::string(key));
}
void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value) {
auto reg = m_impl->m_settings->registerLegacyCustomSetting(key, std::move(value));
if (!reg) {
log::error("Unable to register custom setting: {}", reg.unwrapErr());
}
}
Result<> Mod::registerCustomSettingType(std::string_view type, SettingGenerator generator) {
return m_impl->m_settings->registerCustomSettingType(type, generator);
}
@ -185,15 +164,15 @@ std::vector<std::string> Mod::getLaunchArgumentNames() const {
return m_impl->getLaunchArgumentNames();
}
bool Mod::hasLaunchArgument(std::string_view const name) const {
bool Mod::hasLaunchArgument(std::string_view name) const {
return m_impl->hasLaunchArgument(name);
}
std::optional<std::string> Mod::getLaunchArgument(std::string_view const name) const {
std::optional<std::string> Mod::getLaunchArgument(std::string_view name) const {
return m_impl->getLaunchArgument(name);
}
bool Mod::getLaunchFlag(std::string_view const name) const {
bool Mod::getLaunchFlag(std::string_view name) const {
return m_impl->getLaunchFlag(name);
}
@ -241,7 +220,7 @@ ModRequestedAction Mod::getRequestedAction() const {
return m_impl->getRequestedAction();
}
bool Mod::depends(std::string_view const id) const {
bool Mod::depends(std::string_view id) const {
return m_impl->depends(id);
}
@ -269,12 +248,20 @@ void Mod::setLoggingEnabled(bool enabled) {
m_impl->setLoggingEnabled(enabled);
}
bool Mod::hasSavedValue(std::string_view const key) {
bool Mod::hasSavedValue(std::string_view key) {
return this->getSaveContainer().contains(key);
}
bool Mod::hasProblems() const {
return m_impl->hasProblems();
bool Mod::hasLoadProblems() const {
return m_impl->hasLoadProblems();
}
std::optional<LoadProblem> Mod::targetsOutdatedVersion() const {
for (auto problem : this->getAllProblems()) {
if (problem.isOutdated()) {
return problem;
}
}
return std::nullopt;
}
std::vector<LoadProblem> Mod::getAllProblems() const {
return m_impl->getProblems();
@ -282,10 +269,7 @@ std::vector<LoadProblem> Mod::getAllProblems() const {
std::vector<LoadProblem> Mod::getProblems() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type != LoadProblem::Type::Recommendation &&
problem.type != LoadProblem::Type::Suggestion
) {
if (problem.isProblem()) {
result.push_back(problem);
}
}
@ -294,10 +278,7 @@ std::vector<LoadProblem> Mod::getProblems() const {
std::vector<LoadProblem> Mod::getRecommendations() const {
std::vector<LoadProblem> result;
for (auto problem : this->getAllProblems()) {
if (
problem.type == LoadProblem::Type::Recommendation ||
problem.type == LoadProblem::Type::Suggestion
) {
if (problem.isSuggestion()) {
result.push_back(problem);
}
}

Some files were not shown because too many files have changed in this diff Show more