Merge branch 'main' into layout

This commit is contained in:
HJfod 2022-11-12 12:03:15 +02:00
commit 61c0f1b274
41 changed files with 714 additions and 588 deletions

View file

@ -79,11 +79,12 @@ IncludeCategories:
IndentAccessModifiers: false
AccessModifierOffset: -4
IndentCaseBlocks: true
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExternBlock: Indent
IndentGotoLabels: true
IndentPPDirectives: BeforeHash
IndentRequiresClause: false
IndentWrappedFunctionNames: false
# InsertBraces: true
InsertTrailingCommas: None
@ -107,6 +108,7 @@ PenaltyIndentedWhitespace: 0
QualifierAlignment: Right
RequiresClausePosition: OwnLine
ReflowComments: true
SeparateDefinitionBlocks: Always

View file

@ -39,7 +39,6 @@ target_sources(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/entry.cpp)
add_subdirectory(codegen)
message(STATUS ${GEODE_CODEGEN_PATH}/Geode/GeneratedSource.cpp)
add_custom_command(
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings/GeometryDash.bro
${CMAKE_CURRENT_SOURCE_DIR}/bindings/Cocos2d.bro
@ -106,7 +105,7 @@ elseif(EXISTS ${GEODE_PLATFORM_BIN_PATH})
)
else()
message(FATAL_ERROR
"No valid loader binary to link to! Install prebuilts with `geode sdk install-prebuilts` "
"or build Geode from source."
"No valid loader binary to link to! Install prebuilts with `geode sdk install-prebuilts`, "
"or build Geode from source and add `set(GEODE_LINK_NIGHTLY On)` to your CMakeLists.txt."
)
endif()

View file

@ -140,6 +140,8 @@ class cocos2d::CCDrawNode {
static cocos2d::CCDrawNode* create() = mac 0x378d00;
auto drawPolygon(cocos2d::CCPoint*, unsigned int, cocos2d::_ccColor4F const&, float, cocos2d::_ccColor4F const&) = mac 0x3797f0;
auto drawSegment(cocos2d::CCPoint const&, cocos2d::CCPoint const&, float, cocos2d::_ccColor4F const&) = mac 0x3792d0;
auto drawDot(cocos2d::CCPoint const&, float, cocos2d::_ccColor4F const&) = mac 0x379100;
auto getBlendFunc() const = mac 0x379ea0;
auto init() = mac 0x378e00;
auto setBlendFunc(cocos2d::_ccBlendFunc const&) = mac 0x379eb0;
@ -262,9 +264,8 @@ class cocos2d::CCLabelBMFont {
static cocos2d::CCLabelBMFont* create(char const*, char const*) = mac 0x347660;
auto limitLabelWidth(float, float, float) = mac 0x34a6e0, ios 0x21b740;
virtual ~CCLabelBMFont() = mac 0x347e80, ios 0x219afc;
virtual auto init() = mac 0x347b10, ios 0x2198e0;
bool initWithString(const char* str, const char* fnt, float width, cocos2d::CCTextAlignment align, cocos2d::CCPoint offset);
virtual auto setScaleX(float) = mac 0x34a5b0, ios 0x21b6e8;
virtual auto setScaleY(float) = mac 0x34a5d0, ios 0x21b714;
virtual auto setScale(float) = mac 0x34a590, ios 0x21b6bc;
@ -292,6 +293,7 @@ class cocos2d::CCLabelBMFont {
virtual auto isCascadeColorEnabled() = mac 0x3493c0, ios 0x21aa3c;
virtual auto setCascadeColorEnabled(bool) = mac 0x3493e0, ios 0x21aa4c;
virtual auto setString(unsigned short*, bool) = mac 0x348a60, ios 0x21a4b4;
virtual ~CCLabelBMFont() = mac 0x347e80;
}
class cocos2d::CCLabelTTF {
@ -657,6 +659,7 @@ class cocos2d::CCRenderTexture {
auto end() = mac 0x35d2c0;
static cocos2d::CCRenderTexture* create(int, int, cocos2d::CCTexture2DPixelFormat) = mac 0x35c720;
auto newCCImage(bool) = mac 0x35d7d0;
auto saveToFile(char const*) = mac 0x35dab0;
}
class cocos2d::CCRepeat {

View file

@ -1,4 +1,5 @@
// geode additions to make stl containers easier
// clang-format off
class GDString {
void winDtor() = win 0xf6e0;
char const* winCStr() = win 0xf710;
@ -16,7 +17,6 @@ class GDString {
void macDestroy() = mac 0x489f78;
}
class AchievementBar : cocos2d::CCNodeRGBA {
static AchievementBar* create(const char* title, const char* desc, const char* icon, bool quest) = mac 0x379f80, win 0x3b120, ios 0x1a4784;
@ -1104,6 +1104,17 @@ class EditorOptionsLayer {
}
class EditorPauseLayer : CCBlockLayer, FLAlertLayerProtocol {
static EditorPauseLayer* get() {
if (!EditorUI::get()) return nullptr;
auto editor = LevelEditorLayer::get();
for (auto i = 0; i < editor->getChildrenCount(); ++i) {
if (auto layer = cast::safe_cast<EditorPauseLayer*>(editor->getChildren()->objectAtIndex(i))) {
return layer;
}
}
return nullptr;
}
static EditorPauseLayer* create(LevelEditorLayer* editor) {
auto pRet = new EditorPauseLayer();
if (pRet && pRet->init(editor)) {
@ -2105,6 +2116,14 @@ class GJGameLevel : cocos2d::CCNode {
GJDifficulty getAverageDifficulty() = win 0xbd9b0;
gd::string getUnpackedLevelDescription() = win 0xbf890;
static GJGameLevel* getCurrent() {
auto playLayer = PlayLayer::get();
if (playLayer) return playLayer->m_level;
auto editorLayer = LevelEditorLayer::get();
if (editorLayer) return editorLayer->m_level;
return nullptr;
}
cocos2d::CCDictionary* m_lastBuildSave;
int m_levelIDRand;
int m_levelIDSeed;
@ -3550,10 +3569,10 @@ class LevelCell : TableViewCell {
}
class LevelCommentDelegate {
virtual void loadCommentsFinished(cocos2d::CCArray *, const char*) {}
virtual void loadCommentsFailed(const char*) {}
virtual void loadCommentsFinished(cocos2d::CCArray*, char const*) {}
virtual void loadCommentsFailed(char const*) {}
virtual void updateUserScoreFinished() {}
virtual void setupPageInfo(gd::string, const char*) {}
virtual void setupPageInfo(gd::string, char const*) {}
}
class LevelDeleteDelegate {
@ -3855,6 +3874,12 @@ class LevelSettingsObject : cocos2d::CCNode {
static LevelSettingsObject* objectFromString(gd::string) = mac 0x945a0, win 0x16f440;
void setupColorsFromLegacyMode(cocos2d::CCDictionary*) = mac 0xa6a30, win 0x170050;
static LevelSettingsObject* get() {
auto baseLayer = GJBaseGameLayer::get();
if (baseLayer) return baseLayer->m_levelSettings;
return nullptr;
}
gd::string getSaveString() = mac 0x979c0, win 0x16ebf0;
GJEffectManager* m_effectManager;
@ -4035,7 +4060,7 @@ class MusicDownloadManager : cocos2d::CCNode, PlatformDownloadDelegate {
cocos2d::CCDictionary* m_unknownDict;
cocos2d::CCArray* m_handlers;
cocos2d::CCDictionary* m_songsDict;
int m_unknown;
int m_priority;
}
class NumberInputDelegate {
@ -5190,6 +5215,19 @@ class TableView : CCScrollLayerExt, CCScrollLayerExtDelegate {
static TableView* create(TableViewDelegate*, TableViewDataSource*, cocos2d::CCRect) = mac 0x37eb30, win 0x30ed0;
void reloadData() = mac 0x37f970, win 0x317e0;
virtual void onEnter() = mac 0x37ff30, ios 0x21dcac;
virtual void onExit() = mac 0x37ff40, ios 0x21dcb0;
virtual bool ccTouchBegan(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x380120, ios 0x21de24, win 0x31de0;
virtual void ccTouchMoved(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x380be0, ios 0x21e5e8;
virtual void ccTouchEnded(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x3809a0, ios 0x21e46c;
virtual void ccTouchCancelled(cocos2d::CCTouch*, cocos2d::CCEvent*) = mac 0x380b20, ios 0x21e580;
virtual void registerWithTouchDispatcher() = mac 0x37ff50, ios 0x21dcb4;
virtual void scrollWheel(float, float) = mac 0x380cd0, ios 0x21e6b4;
virtual void scrllViewWillBeginDecelerating(CCScrollLayerExt*) = mac 0x3818a0, ios 0x21efd4;
virtual void scrollViewDidEndDecelerating(CCScrollLayerExt*) = mac 0x3818c0, ios 0x21efdc;
virtual void scrollViewTouchMoving(CCScrollLayerExt*) = mac 0x3818e0, ios 0x21efe4;
virtual void scrollViewDidEndMoving(CCScrollLayerExt*) = mac 0x381900, ios 0x21efec;
bool m_touchOutOfBoundary;
cocos2d::CCTouch* m_touchStart;
cocos2d::CCPoint m_touchStartPosition2;
@ -5261,7 +5299,7 @@ class TeleportPortalObject : GameObject {
bool m_teleportEase;
}
class TextAlertPopup {
class TextAlertPopup : cocos2d::CCNode {
static TextAlertPopup* create(gd::string const& text, float time, float scale) = win 0x1450b0;
}
@ -5372,4 +5410,4 @@ class VideoOptionsLayer : FLAlertLayer {
class LevelTools {
static gd::string base64DecodeString(gd::string) = mac 0x294510, win 0x18b3b0;
}
// clang-format on

View file

@ -1,25 +1,27 @@
#include "Shared.hpp"
#include "TypeOpt.hpp"
#include <iostream>
#include <set>
namespace { namespace format_strings {
char const* wrap_start = R"GEN(
namespace {
namespace format_strings {
char const* wrap_start = R"GEN(
namespace wrap {
)GEN";
char const* wrap_declare_identifier = R"GEN(
char const* wrap_declare_identifier = R"GEN(
#ifndef GEODE_WRAP_{function_name}
#define GEODE_WRAP_{function_name}
GEODE_WRAPPER_FOR_IDENTIFIER({function_name})
#endif
)GEN";
char const* wrap_end = R"GEN(
char const* wrap_end = R"GEN(
}
)GEN";
// requires: class_name
char const* modify_start = R"GEN(#pragma once
// requires: class_name
char const* modify_start = R"GEN(#pragma once
#include <Geode/modify/Modify.hpp>
#include <Geode/modify/Field.hpp>
#include <Geode/modify/InternalMacros.hpp>
@ -28,100 +30,89 @@ using namespace geode::modifier;
namespace geode::modifier {{
{wrap}
template<class Derived>
struct Modify<Derived, {class_name}> : ModifyBase<Modify<Derived, {class_name}>> {{
using ModifyBase<Modify<Derived, {class_name}>>::ModifyBase;
struct ModifyDerive<Derived, {class_name}> : ModifyBase<ModifyDerive<Derived, {class_name}>> {{
using ModifyBase<ModifyDerive<Derived, {class_name}>>::ModifyBase;
using Base = {class_name};
static void apply() {{
using namespace geode::core::meta;
)GEN";
// requires: index, class_name, arg_types, function_name, raw_arg_types, non_virtual
char const* apply_function = R"GEN(
// requires: index, class_name, arg_types, function_name, raw_arg_types, non_virtual
char const* apply_function = R"GEN(
GEODE_APPLY_MODIFY_FOR_FUNCTION({addr_index}, {pure_index}, {function_convention}, {class_name}, {function_name}))GEN";
char const* modify_end = R"GEN(
char const* modify_end = R"GEN(
}
};
}
)GEN";
char const* modify_include = R"GEN(#include "modify/{file_name}"
char const* modify_include = R"GEN(#include "modify/{file_name}"
)GEN";
}}
}
}
std::string generateModifyHeader(Root& root, ghc::filesystem::path const& singleFolder) {
std::string output;
std::string output;
TypeBank bank;
bank.loadFrom(root);
TypeBank bank;
bank.loadFrom(root);
for (auto c : root.classes) {
if (c.name == "cocos2d")
continue;
for (auto c : root.classes) {
if (c.name == "cocos2d") continue;
std::string filename = (codegen::getUnqualifiedClassName(c.name) + ".hpp");
output += fmt::format(format_strings::modify_include,
fmt::arg("file_name", filename)
);
std::string filename = (codegen::getUnqualifiedClassName(c.name) + ".hpp");
output += fmt::format(format_strings::modify_include, fmt::arg("file_name", filename));
std::string single_output;
std::string wrap;
std::string single_output;
std::string wrap;
// wrap
wrap += format_strings::wrap_start;
std::set<std::string> used;
for (auto& f : c.fields) {
if (auto fn = f.get_fn()) {
if (fn->type == FunctionType::Normal && !used.count(fn->name)) {
used.insert(fn->name);
wrap += fmt::format(format_strings::wrap_declare_identifier,
fmt::arg("function_name", fn->name)
);
}
}
}
wrap += format_strings::wrap_end;
// wrap
wrap += format_strings::wrap_start;
std::set<std::string> used;
for (auto& f : c.fields) {
if (auto fn = f.get_fn()) {
if (fn->type == FunctionType::Normal && !used.count(fn->name)) {
used.insert(fn->name);
wrap += fmt::format(
format_strings::wrap_declare_identifier, fmt::arg("function_name", fn->name)
);
}
}
}
wrap += format_strings::wrap_end;
single_output += fmt::format(format_strings::modify_start,
fmt::arg("class_name", c.name),
fmt::arg("wrap", wrap)
);
single_output += fmt::format(
format_strings::modify_start, fmt::arg("class_name", c.name), fmt::arg("wrap", wrap)
);
// modify
for (auto& f : c.fields) {
if (codegen::getStatus(f) != BindStatus::Unbindable) {
auto begin = f.get_fn();
// modify
for (auto& f : c.fields) {
if (codegen::getStatus(f) != BindStatus::Unbindable) {
auto begin = f.get_fn();
std::string function_name;
std::string function_name;
switch (begin->type) {
case FunctionType::Normal:
function_name = begin->name;
break;
case FunctionType::Ctor:
function_name = "constructor";
break;
case FunctionType::Dtor:
function_name = "destructor";
break;
}
switch (begin->type) {
case FunctionType::Normal: function_name = begin->name; break;
case FunctionType::Ctor: function_name = "constructor"; break;
case FunctionType::Dtor: function_name = "destructor"; break;
}
single_output += fmt::format(format_strings::apply_function,
fmt::arg("addr_index", f.field_id),
fmt::arg("pure_index", bank.getPure(*begin, c.name)),
fmt::arg("class_name", c.name),
fmt::arg("function_name", function_name),
fmt::arg("function_convention", codegen::getConvention(f))
);
}
}
single_output += fmt::format(
format_strings::apply_function, fmt::arg("addr_index", f.field_id),
fmt::arg("pure_index", bank.getPure(*begin, c.name)),
fmt::arg("class_name", c.name), fmt::arg("function_name", function_name),
fmt::arg("function_convention", codegen::getConvention(f))
);
}
}
single_output += format_strings::modify_end;
single_output += format_strings::modify_end;
writeFile(singleFolder / filename, single_output);
}
writeFile(singleFolder / filename, single_output);
}
return output;
return output;
}

View file

@ -10,30 +10,84 @@ namespace geode {
class Mod;
class Event;
enum class PassThrough : bool {
enum class ListenerResult {
Propagate,
Stop,
Stop
};
struct GEODE_DLL BasicEventHandler {
virtual PassThrough passThrough(Event*) = 0;
struct GEODE_DLL EventListenerProtocol {
virtual void enable();
virtual void disable();
virtual ListenerResult passThrough(Event*) = 0;
};
virtual ~BasicEventHandler();
void listen();
void unlisten();
template <typename C, typename T>
struct to_member;
template <typename C, typename R, typename ...Args>
struct to_member<C, R(Args...)> {
using value = R(C::*)(Args...);
};
template <typename T>
concept is_event = std::is_base_of_v<Event, T>;
template <is_event T>
class EventFilter {
public:
using Callback = ListenerResult(T*);
using Event = T;
ListenerResult handle(std::function<Callback> fn, T* e) {
return fn(e);
}
};
template <typename T>
concept is_filter =
std::is_base_of_v<EventFilter<typename T::Event>, T> &&
requires(T a) {
a.handle(std::declval<typename T::Callback>(), std::declval<typename T::Event*>());
};
template <is_filter T>
class EventListener : public EventListenerProtocol {
public:
using Callback = typename T::Callback;
template <typename C> requires std::is_class_v<C>
using MemberFn = typename to_member<C, Callback>::value;
ListenerResult passThrough(Event* e) override {
if (auto myev = dynamic_cast<typename T::Event*>(e)) {
return m_filter.handle(m_callback, myev);
}
return ListenerResult::Propagate;
}
EventListener(T filter = T()) {}
EventListener(std::function<Callback> fn, T filter = T()) : m_callback(fn), m_filter(filter) {}
EventListener(Callback* fnptr, T filter = T()) : m_callback(fnptr), m_filter(filter) {}
template <class C>
EventListener(C* cls, MemberFn<C> fn, T filter = T()) : EventListener(std::bind(fn, cls, std::placeholders::_1), filter) {}
void bind(std::function<Callback> fn) {
m_callback = fn;
}
template <typename C>
void bind(C* cls, MemberFn<C> fn) {
m_callback = std::bind(fn, cls, std::placeholders::_1);
}
protected:
std::function<Callback> m_callback;
T m_filter;
};
class GEODE_DLL Event {
static std::unordered_set<BasicEventHandler*> s_handlers;
friend BasicEventHandler;
static std::unordered_set<EventListenerProtocol*> s_listeners;
Mod* m_sender;
friend EventListenerProtocol;
public:
static std::unordered_set<BasicEventHandler*> const& getHandlers();
void postFrom(Mod* sender);
inline void post() {
@ -44,21 +98,4 @@ namespace geode {
virtual ~Event();
};
template <typename T>
class EventHandler : public BasicEventHandler {
public:
virtual PassThrough handle(T*) = 0;
PassThrough passThrough(Event* ev) override {
if (auto myev = dynamic_cast<T*>(ev)) {
return handle(myev);
}
return PassThrough::Propagate;
}
EventHandler() {
listen();
}
};
}

View file

@ -34,6 +34,7 @@ namespace geode {
static constexpr std::string_view GEODE_CONFIG_DIRECTORY = "config";
static constexpr std::string_view GEODE_TEMP_DIRECTORY = "temp";
static constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
static constexpr std::string_view GEODE_INDEX_DIRECTORY = "index";
class Mod;
class Hook;
@ -41,7 +42,7 @@ namespace geode {
class VersionInfo;
namespace modifier {
template <class, class, class>
template <class, class>
class FieldIntermediate;
}
}
@ -101,7 +102,7 @@ namespace geode {
size_t getFieldIndexForClass(size_t hash);
template <class, class, class>
template <class, class>
friend class modifier::FieldIntermediate;
void updateModResources(Mod* mod);

View file

@ -468,11 +468,19 @@ namespace geode {
} \
}
// clang-format off
template <class T>
T getBuiltInSettingValue(const std::shared_ptr<Setting> setting) {
GEODE_INT_BUILTIN_SETTING_IF(Bool, getValue(), std::is_same_v<T, bool>)
else GEODE_INT_BUILTIN_SETTING_IF(Float, getValue(), std::is_floating_point_v<T>) else GEODE_INT_BUILTIN_SETTING_IF(Int, getValue(), std::is_integral_v<T>) else GEODE_INT_BUILTIN_SETTING_IF(String, getValue(), std::is_same_v<T, std::string>) else {
static_assert(!std::is_same_v<T, T>, "todo: implement");
else GEODE_INT_BUILTIN_SETTING_IF(Float, getValue(), std::is_floating_point_v<T>)
else GEODE_INT_BUILTIN_SETTING_IF(Int, getValue(), std::is_integral_v<T>)
else GEODE_INT_BUILTIN_SETTING_IF(String, getValue(), std::is_same_v<T, std::string>)
else GEODE_INT_BUILTIN_SETTING_IF(File, getValue(), std::is_same_v<T, ghc::filesystem::path>)
else GEODE_INT_BUILTIN_SETTING_IF(Color, getValue(), std::is_same_v<T, cocos2d::ccColor3B>)
else GEODE_INT_BUILTIN_SETTING_IF(ColorAlpha, getValue(), std::is_same_v<T, cocos2d::ccColor4B>)
else {
static_assert(!std::is_same_v<T, T>, "Unsupported type for getting setting value!");
}
return T();
}
@ -480,10 +488,17 @@ namespace geode {
template <class T>
void setBuiltInSettingValue(const std::shared_ptr<Setting> setting, T const& value) {
GEODE_INT_BUILTIN_SETTING_IF(Bool, setValue(value), std::is_same_v<T, bool>)
else GEODE_INT_BUILTIN_SETTING_IF(Float, setValue(value), std::is_floating_point_v<T>) else GEODE_INT_BUILTIN_SETTING_IF(Int, setValue(value), std::is_integral_v<T>) else GEODE_INT_BUILTIN_SETTING_IF(String, setValue(value), std::is_same_v<T, std::string>) else {
static_assert(!std::is_same_v<T, T>, "todo: implement");
else GEODE_INT_BUILTIN_SETTING_IF(Float, setValue(value), std::is_floating_point_v<T>)
else GEODE_INT_BUILTIN_SETTING_IF(Int, setValue(value), std::is_integral_v<T>)
else GEODE_INT_BUILTIN_SETTING_IF(String, setValue(value), std::is_same_v<T, std::string>)
else GEODE_INT_BUILTIN_SETTING_IF(File, setValue(value), std::is_same_v<T, ghc::filesystem::path>)
else GEODE_INT_BUILTIN_SETTING_IF(Color, setValue(value), std::is_same_v<T, cocos2d::ccColor3B>)
else GEODE_INT_BUILTIN_SETTING_IF(ColorAlpha, setValue(value), std::is_same_v<T, cocos2d::ccColor4B>)
else {
static_assert(!std::is_same_v<T, T>, "Unsupported type for getting setting value!");
}
}
// clang-format on
}
#pragma warning(pop)

View file

@ -18,41 +18,37 @@ namespace geode {
std::shared_ptr<Setting> getSetting() const;
};
template <class T>
class SettingChangedEventHandler : public EventHandler<SettingChangedEvent> {
template <typename T = Setting, typename = std::enable_if_t<std::is_base_of_v<Setting, T>>>
class SettingChangedFilter : public EventFilter<SettingChangedEvent> {
public:
using Consumer = void (*)(std::shared_ptr<T>);
using Callback = void(std::shared_ptr<T>);
using Event = SettingChangedEvent;
static_assert(std::is_base_of_v<Setting, T>, "Setting must inherit from the Setting class");
protected:
Consumer m_consumer;
std::string m_modID;
std::optional<std::string> m_targetKey;
public:
PassThrough handle(SettingChangedEvent* event) override {
ListenerResult handle(std::function<Callback> fn, SettingChangedEvent* event) {
if (m_modID == event->getModID() &&
(!m_targetKey || m_targetKey.value() == event->getSetting()->getKey())) {
m_consumer(std::static_pointer_cast<T>(event->getSetting()));
fn(std::static_pointer_cast<T>(event->getSetting()));
}
return PassThrough::Propagate;
return ListenerResult::Propagate;
}
/**
* Listen to changes on a specific setting
*/
SettingChangedEventHandler(
std::string const& modID, std::string const& settingID, Consumer handler
SettingChangedFilter(
std::string const& modID, std::string const& settingID
) :
m_modID(modID),
m_targetKey(settingID), m_consumer(handler) {}
m_targetKey(settingID) {}
/**
* Listen to changes on all of a mods' settings
*/
SettingChangedEventHandler(std::string const& modID, Consumer handler) :
m_modID(modID), m_targetKey(std::nullopt), m_consumer(handler) {}
SettingChangedFilter(std::string const& modID) :
m_modID(modID), m_targetKey(std::nullopt) {}
protected:
std::string m_modID;
std::optional<std::string> m_targetKey;
};
template <class T>
@ -61,14 +57,14 @@ namespace geode {
std::string const& settingID, void (*callback)(std::shared_ptr<T>)
) {
Loader::get()->scheduleOnModLoad(getMod(), [=]() {
static SettingChangedEventHandler<T> _(getMod()->getID(), settingID, callback);
static auto _ = EventListener(callback, SettingChangedFilter<T>(getMod()->getID(), settingID));
});
return std::monostate();
}
static std::monostate listenForAllSettingChanges(void (*callback)(std::shared_ptr<Setting>)) {
Loader::get()->scheduleOnModLoad(getMod(), [=]() {
static SettingChangedEventHandler<Setting> _(getMod()->getID(), callback);
static auto _ = EventListener(callback, SettingChangedFilter(getMod()->getID()));
});
return std::monostate();
}

View file

@ -42,7 +42,7 @@ namespace geode::modifier {
}
};
template <class Base, class Intermediate, class Parent>
template <class Parent, class Base>
class FieldIntermediate {
// Padding used for guaranteeing any member of parents
// will be in between sizeof(Intermediate) and sizeof(Parent)
@ -54,22 +54,22 @@ namespace geode::modifier {
auto parent = new (parentContainer.data()) Parent();
parent->Intermediate::~Intermediate();
parent->Base::~Base();
std::memcpy(
offsetField, std::launder(&parentContainer[sizeof(Intermediate)]),
sizeof(Parent) - sizeof(Intermediate)
offsetField, std::launder(&parentContainer[sizeof(Base)]),
sizeof(Parent) - sizeof(Base)
);
}
static void fieldDestructor(void* offsetField) {
std::array<std::byte, sizeof(Parent)> parentContainer;
auto parent = new (parentContainer.data()) Intermediate();
auto parent = new (parentContainer.data()) Base();
std::memcpy(
std::launder(&parentContainer[sizeof(Intermediate)]), offsetField,
sizeof(Parent) - sizeof(Intermediate)
std::launder(&parentContainer[sizeof(Base)]), offsetField,
sizeof(Parent) - sizeof(Base)
);
static_cast<Parent*>(parent)->Parent::~Parent();
@ -87,15 +87,14 @@ namespace geode::modifier {
auto offsetField = container->getField(index);
if (!offsetField) {
offsetField = container->setField(
index, sizeof(Parent) - sizeof(Intermediate),
&FieldIntermediate::fieldDestructor
index, sizeof(Parent) - sizeof(Base), &FieldIntermediate::fieldDestructor
);
FieldIntermediate::fieldConstructor(offsetField);
}
return reinterpret_cast<Parent*>(
reinterpret_cast<std::byte*>(offsetField) - sizeof(Intermediate)
reinterpret_cast<std::byte*>(offsetField) - sizeof(Base)
);
}
};

View file

@ -8,15 +8,9 @@
* struct hook0 {};
* namespace {
* struct hook0Parent {};
* Modify<hook0<hook0Parent>, MenuLayer> hook0Apply;
* struct GEODE_HIDDEN hook0Intermediate: public MenuLayer {
* geode::modifier::FieldIntermediate<MenuLayer,
* hook0<hook0Intermediate>, hook0<hook0Parent>
* > m_fields;
* };
* }
* template<>
* struct GEODE_HIDDEN hook0<hook0Parent>: hook0Intermediate {
* struct GEODE_HIDDEN hook0<hook0Parent> : Modify<hook0<hook0Parent>, MenuLayer> {
* // code stuff idk
* };
*
@ -24,33 +18,19 @@
* I am bad at this stuff
*/
#define GEODE_MODIFY_DECLARE_ANONYMOUS(base, derived) \
derived##Dummy; \
template <class> \
struct derived {}; \
namespace { \
struct derived##Parent {}; \
Modify<derived<derived##Parent>, base> derived##Apply; \
struct GEODE_HIDDEN derived##Intermediate : base { \
mutable geode::modifier::FieldIntermediate< \
base, derived##Intermediate, derived<derived##Parent>> \
m_fields; \
}; \
} \
template <> \
struct GEODE_HIDDEN derived<derived##Parent> : derived##Intermediate
#define GEODE_MODIFY_DECLARE_ANONYMOUS(base, derived) \
derived##Dummy; \
template <class> \
struct derived {}; \
namespace { \
struct derived##Parent {}; \
} \
template <> \
struct GEODE_HIDDEN derived<derived##Parent> : geode::Modify<derived<derived##Parent>, base>
#define GEODE_MODIFY_DECLARE(base, derived) \
derived##Dummy; \
struct derived; \
namespace { \
Modify<derived, base> derived##Apply; \
struct GEODE_HIDDEN derived##Intermediate : base { \
mutable geode::modifier::FieldIntermediate<base, derived##Intermediate, derived> \
m_fields; \
}; \
} \
struct GEODE_HIDDEN derived : derived##Intermediate
#define GEODE_MODIFY_DECLARE(base, derived) \
derived##Dummy; \
struct GEODE_HIDDEN derived : geode::Modify<derived, base>
#define GEODE_MODIFY_REDIRECT4(base, derived) GEODE_MODIFY_DECLARE(base, derived)
#define GEODE_MODIFY_REDIRECT3(base, derived) GEODE_MODIFY_DECLARE_ANONYMOUS(base, derived)

View file

@ -1,6 +1,7 @@
#pragma once
#include "../meta/meta.hpp"
#include "Addresses.hpp"
#include "Field.hpp"
#include "Types.hpp"
#include "Wrapper.hpp"
@ -22,7 +23,7 @@
namespace geode::modifier {
template <class Derived, class Base>
class Modify;
class ModifyDerive;
template <class Derived>
class ModifyBase {
@ -34,15 +35,29 @@ namespace geode::modifier {
});
}
template <class, class>
friend class Modify;
friend class ModifyDerive;
// explicit Modify(Property property) idea
};
template <class Derived, class Base>
class Modify {
class ModifyDerive {
public:
Modify() {
ModifyDerive() {
static_assert(core::meta::always_false<Derived>, "Custom Modify not implemented.");
}
};
}
namespace geode {
template <class Derived, class Base>
class Modify : public Base {
private:
static inline modifier::ModifyDerive<Derived, Base> s_apply;
// because for some reason we need it
static inline auto s_applyRef = &Modify::s_apply;
public:
modifier::FieldIntermediate<Derived, Base> m_fields;
};
}

View file

@ -101,9 +101,32 @@ namespace geode {
bool operator==(T* other) const {
return m_obj == other;
}
bool operator==(Ref<T> const& other) const {
return m_obj == other.m_obj;
}
bool operator!=(T* other) const {
return m_obj != other;
}
bool operator!=(Ref<T> const& other) const {
return m_obj != other.m_obj;
}
// for containers
bool operator<(Ref<T> const& other) const {
return m_obj < other.m_obj;
}
bool operator>(Ref<T> const& other) const {
return m_obj > other.m_obj;
}
};
}
namespace std {
template<class T>
struct hash<geode::Ref<T>> {
size_t operator()(geode::Ref<T> const& ref) const {
return std::hash<T*>()(ref.data());
}
};
}

View file

@ -60,4 +60,9 @@ namespace geode {
std::string toString() const;
};
inline std::ostream& operator<<(std::ostream& stream, VersionInfo const& version) {
stream << version.toString();
return stream;
}
}

View file

@ -9,7 +9,7 @@ namespace geode::cast {
/**
* Alias for static_cast
*/
template <typename T, typename F>
template <class T, class F>
static constexpr T as(F const v) {
return static_cast<T>(v);
}
@ -18,8 +18,8 @@ namespace geode::cast {
* Cast from anything to anything else,
* provided they are the same size
*/
template <typename T, typename F>
static constexpr T union_cast(F v) {
template <class T, class F>
static constexpr T union_cast(F const v) {
static_assert(sizeof(F) == sizeof(T), "union_cast: R and T don't match in size!");
union {
@ -36,27 +36,40 @@ namespace geode::cast {
* cast but uses reference syntactic sugar to
* look cleaner.
*/
template <typename T, typename F>
template <class T, class F>
static constexpr T reference_cast(F v) {
return reinterpret_cast<T&>(v);
}
/**
* Cast an adjusted this pointer to it's base pointer
* Cast based on RTTI. Casts an adjusted this pointer
* to it's non offset form.
*/
template <typename T, typename F>
static constexpr T base_cast(F obj) {
return reinterpret_cast<T>(dynamic_cast<void*>(obj));
template <class T, class F>
static constexpr T base_cast(F const obj) {
return static_cast<T>(dynamic_cast<void*>(obj));
}
/**
* Cast based on RTTI. This is a replacement for
* dynamic_cast, since it doesn't work for gd.
* Cast based on RTTI. This is used to check
* if an object is exactly the class needed. Returns
* nullptr on failure.
*/
template <typename T, typename F>
static T typeid_cast(F obj) {
if (std::string(typeid(*obj).name()) == typeid(std::remove_pointer_t<T>).name())
return reinterpret_cast<T>(obj);
else return nullptr;
template <class T, class F>
static T exact_cast(F const obj) {
if (std::strcmp(typeid(*obj).name(), typeid(std::remove_pointer_t<T>).name()) == 0) {
return base_cast<T>(obj);
}
return nullptr;
}
/**
* Cast based on RTTI. This behaves as a replacement
* of dynamic_cast for cocos and gd classes,
* and must be used for expected results.
*/
template <class T, class F>
static T safe_cast(F const obj) {
return typeinfo_cast<T>(obj);
}
}

View file

@ -86,6 +86,7 @@ namespace geode::utils::web {
mutable std::mutex m_mutex;
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target =
std::monostate();
std::vector<std::string> m_httpHeaders;
template <class T>
friend class AsyncWebResult;
@ -134,6 +135,7 @@ namespace geode::utils::web {
AsyncCancelled m_cancelled = nullptr;
bool m_sent = false;
std::variant<std::monostate, std::ostream*, ghc::filesystem::path> m_target;
std::vector<std::string> m_httpHeaders;
template <class T>
friend class AsyncWebResult;
@ -159,6 +161,12 @@ namespace geode::utils::web {
* @returns Same AsyncWebRequest
*/
AsyncWebRequest& join(std::string const& requestID);
/**
* In order to specify a http header to the request, give it here.
* Can be called more than once.
*/
AsyncWebRequest& header(std::string const& header);
/**
* URL to fetch from the internet asynchronously
* @param url URL of the data to download. Redirects will be

View file

@ -28,6 +28,15 @@ namespace geode {
return rect;
}
static cocos2d::CCRect operator*(cocos2d::CCRect const& rect, float mul) {
return {
rect.origin.x * mul,
rect.origin.y * mul,
rect.size.width * mul,
rect.size.height * mul,
};
}
static cocos2d::CCPoint operator/=(cocos2d::CCPoint& pos, float div) {
pos.x /= div;
pos.y /= div;

View file

@ -4,6 +4,9 @@
#include <algorithm>
#include <string>
#undef min
#undef max
namespace geode::utils::ranges {
template <class C>
concept ValidConstContainer = requires(C const& c) {
@ -205,4 +208,57 @@ namespace geode::utils::ranges {
std::transform(from.begin(), from.end(), res.end(), mapper);
return res;
}
template <ValidConstContainer C>
typename C::value_type min(C const& container) {
auto it = std::min_element(container.begin(), container.end());
if (it == container.end()) {
return C::value_type();
}
return *it;
}
template <class T, ValidConstContainer C, ValidIntoConverter<typename C::value_type, T> Member>
requires requires(T a, T b) {
a < b;
}
T min(C const& container, Member member) {
auto it = std::min_element(
container.begin(), container.end(),
[member](auto const& a, auto const& b) -> bool {
return member(a) < member(b);
}
);
if (it == container.end()) {
return T();
}
return member(*it);
}
template <ValidConstContainer C>
typename C::value_type max(C const& container) {
auto it = std::max_element(container.begin(), container.end());
if (it == container.end()) {
return C::value_type();
}
return *it;
}
template <class T, ValidConstContainer C, ValidIntoConverter<typename C::value_type, T> Member>
requires requires(T a, T b) {
a < b;
T();
}
T max(C const& container, Member member) {
auto it = std::max_element(
container.begin(), container.end(),
[member](auto const& a, auto const& b) -> bool {
return member(a) < member(b);
}
);
if (it == container.end()) {
return T();
}
return member(*it);
}
}

View file

@ -1,7 +1,7 @@
{
"geode": "v@PROJECT_VERSION@",
"geode": "@PROJECT_VERSION@",
"id": "geode.loader",
"version": "v@PROJECT_VERSION@",
"version": "@PROJECT_VERSION@",
"name": "Geode",
"developer": "Geode Team",
"description": "The Geode mod loader",

View file

@ -68,7 +68,7 @@ public:
// proxy forwards
// clang-format off
#include <Geode/modify/CCNode.hpp>
class $modify(ProxyCCNode, CCNode) {
struct ProxyCCNode : Modify<ProxyCCNode, CCNode> {
virtual CCObject* getUserObject() {
return GeodeNodeMetadata::set(this)->m_userObject;
}

View file

@ -9,9 +9,10 @@
USE_GEODE_NAMESPACE();
#pragma warning(disable : 4217)
template <class T = CCNode>
requires std::is_base_of_v<CCNode, T> T* setIDSafe(CCNode* node, int index, char const* id) {
template <class T = CCNode>
requires std::is_base_of_v<CCNode, T>
T* setIDSafe(CCNode* node, int index, char const* id) {
if constexpr (std::is_same_v<CCNode, T>) {
if (auto child = getChild(node, index)) {
child->setID(id);
@ -29,7 +30,7 @@ requires std::is_base_of_v<CCNode, T> T* setIDSafe(CCNode* node, int index, char
// clang-format off
#include <Geode/modify/LevelSearchLayer.hpp>
class $modify(LevelSearchLayer) {
struct LevelSearchLayerIDs : Modify<LevelSearchLayerIDs, LevelSearchLayer> {
bool init() {
if (!LevelSearchLayer::init())
return false;
@ -99,4 +100,5 @@ class $modify(LevelSearchLayer) {
return true;
}
};
// clang-format on

View file

@ -3,26 +3,23 @@
USE_GEODE_NAMESPACE();
// clang-format off
#include <Geode/modify/LoadingLayer.hpp>
class $modify(CustomLoadingLayer, LoadingLayer) {
struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
bool m_updatingResources;
CustomLoadingLayer() : m_updatingResources(false) {}
bool init(bool fromReload) {
if (!LoadingLayer::init(fromReload))
return false;
if (!LoadingLayer::init(fromReload)) return false;
auto winSize = CCDirector::sharedDirector()->getWinSize();
auto count = Loader::get()->getAllMods().size();
auto label = CCLabelBMFont::create(
CCString::createWithFormat(
"Geode: Loaded %lu mods",
static_cast<unsigned long>(count)
)->getCString(),
CCString::createWithFormat("Geode: Loaded %lu mods", static_cast<unsigned long>(count))
->getCString(),
"goldFont.fnt"
);
label->setPosition(winSize.width / 2, 30.f);
@ -31,13 +28,10 @@ class $modify(CustomLoadingLayer, LoadingLayer) {
this->addChild(label);
// verify loader resources
if (!InternalLoader::get()->verifyLoaderResources(
std::bind(
&CustomLoadingLayer::updateResourcesProgress, this,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3
)
)) {
if (!InternalLoader::get()->verifyLoaderResources(std::bind(
&CustomLoadingLayer::updateResourcesProgress, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3
))) {
// auto bg = CCScale9Sprite::create(
// "square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }
// );
@ -73,17 +67,10 @@ class $modify(CustomLoadingLayer, LoadingLayer) {
// );
}
void updateResourcesProgress(
UpdateStatus status,
std::string const& info,
uint8_t progress
) {
void updateResourcesProgress(UpdateStatus status, std::string const& info, uint8_t progress) {
switch (status) {
case UpdateStatus::Progress: {
this->setUpdateText(
"Downloading Resources: " +
std::to_string(progress) + "%"
);
this->setUpdateText("Downloading Resources: " + std::to_string(progress) + "%");
} break;
case UpdateStatus::Finished: {
@ -95,9 +82,10 @@ class $modify(CustomLoadingLayer, LoadingLayer) {
case UpdateStatus::Failed: {
InternalLoader::platformMessageBox(
"Error updating resources",
"Unable to update Geode resources: " + info + ".\n"
"The game will be loaded as normal, but please be aware "
"that it may very likely crash."
"Unable to update Geode resources: " + info +
".\n"
"The game will be loaded as normal, but please be aware "
"that it may very likely crash."
);
this->setUpdateText("Resource Download Failed");
m_fields->m_updatingResources = false;
@ -106,12 +94,10 @@ class $modify(CustomLoadingLayer, LoadingLayer) {
}
}
void loadAssets() {
if (m_fields->m_updatingResources) {
void loadAssets() {
if (m_fields->m_updatingResources) {
return;
}
LoadingLayer::loadAssets();
}
}
LoadingLayer::loadAssets();
}
};
// clang-format on

View file

@ -64,7 +64,7 @@ static void updateIndexProgress(UpdateStatus status, std::string const& info, ui
}
#include <Geode/modify/MenuLayer.hpp>
class $modify(CustomMenuLayer, MenuLayer) {
class CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
void destructor() {
g_geodeButton = nullptr;
MenuLayer::~MenuLayer();
@ -160,4 +160,3 @@ class $modify(CustomMenuLayer, MenuLayer) {
ModListLayer::scene();
}
};
// clang-format on

View file

@ -1,15 +1,15 @@
USE_GEODE_NAMESPACE();
// clang-format off
#include <Geode/modify/CCTouchDispatcher.hpp>
class $modify(CCTouchDispatcher) {
void addTargetedDelegate(CCTouchDelegate *delegate, int priority, bool swallowsTouches) {
struct ForcePrioRevert : Modify<ForcePrioRevert, CCTouchDispatcher> {
void addTargetedDelegate(CCTouchDelegate* delegate, int priority, bool swallowsTouches) {
m_bForcePrio = false;
if (m_pTargetedHandlers->count() > 0) {
auto handler = static_cast<CCTouchHandler*>(m_pTargetedHandlers->objectAtIndex(0));
priority = handler->getPriority() - 2;
}
CCTouchDispatcher::addTargetedDelegate(delegate, priority, swallowsTouches);
}
@ -21,4 +21,3 @@ class $modify(CCTouchDispatcher) {
m_bForcePrio = false;
}
};
// clang-format on

View file

@ -1,14 +1,12 @@
// this is the fix for the dynamic_cast problems
// TODO: completely replace dynamic_cast on macos
using namespace cocos2d;
using namespace geode::modifier;
#include <Geode/DefaultInclude.hpp>
#if defined(GEODE_IS_IOS) || defined(GEODE_IS_MACOS)
using namespace geode::cast;
USE_GEODE_NAMESPACE();
#define HandlerFixFor(CCUtility) \
class $modify(CCUtility##HandlerTypeinfoFix, CCUtility##Handler) { \
struct CCUtility##HandlerFix : Modify<CCUtility##HandlerFix, CCUtility##Handler> { \
void destructor() { \
if (m_pDelegate) { \
cocos2d::CCObject* pObject = base_cast<cocos2d::CCObject*>(m_pDelegate); \
@ -55,7 +53,7 @@ HandlerFixFor(CCKeyboard);
HandlerFixFor(CCMouse);
#include <Geode/modify/CCTargetedTouchHandler.hpp>
class $modify(CCTargetedTouchHandlerTypeinfoFix, CCTargetedTouchHandler) {
struct CCTargetedTouchHandlerFix : Modify<CCTargetedTouchHandlerFix, CCTargetedTouchHandler> {
void destructor() {
if (m_pDelegate) {
cocos2d::CCObject* pObject = base_cast<cocos2d::CCObject*>(m_pDelegate);
@ -101,7 +99,7 @@ class $modify(CCTargetedTouchHandlerTypeinfoFix, CCTargetedTouchHandler) {
};
#include <Geode/modify/CCStandardTouchHandler.hpp>
class $modify(CCStandardTouchHandlerTypeinfoFix, CCStandardTouchHandler) {
struct CCStandardTouchHandlerFix : Modify<CCStandardTouchHandlerFix, CCStandardTouchHandler> {
void destructor() {
if (m_pDelegate) {
cocos2d::CCObject* pObject = base_cast<cocos2d::CCObject*>(m_pDelegate);
@ -140,6 +138,7 @@ class $modify(CCStandardTouchHandlerTypeinfoFix, CCStandardTouchHandler) {
return pHandler;
}
};
// clang-format on
#endif

View file

@ -2,12 +2,11 @@
USE_GEODE_NAMESPACE();
// clang-format off
#include <Geode/modify/AchievementNotifier.hpp>
class $modify(AchievementNotifier) {
struct SceneSwitch : Modify<SceneSwitch, AchievementNotifier> {
void willSwitchToScene(CCScene* scene) {
AchievementNotifier::willSwitchToScene(scene);
SceneManager::get()->willSwitchToScene(scene);
}
};
// clang-format on

View file

@ -2,9 +2,9 @@
USE_GEODE_NAMESPACE();
// clang-format off
#include <Geode/modify/AppDelegate.hpp>
class $modify(AppDelegate) {
struct SaveLoader : Modify<SaveLoader, AppDelegate> {
void trySaveGame() {
log::log(Severity::Info, Loader::getInternalMod(), "Saving...");
@ -14,8 +14,7 @@ class $modify(AppDelegate) {
}
log::log(Severity::Info, Loader::getInternalMod(), "Saved");
return AppDelegate::trySaveGame();
}
};
// clang-format on
};

View file

@ -1,12 +1,12 @@
#include <InternalLoader.hpp>
USE_GEODE_NAMESPACE();
// clang-format off
#include <Geode/modify/CCScheduler.hpp>
class $modify(CCScheduler) {
struct FunctionQueue : Modify<FunctionQueue, CCScheduler> {
void update(float dt) {
InternalLoader::get()->executeGDThreadQueue();
return CCScheduler::update(dt);
}
};
// clang-format on
};

View file

@ -4,21 +4,12 @@
USE_GEODE_NAMESPACE();
class $modify(GameManager) {
void reloadAllStep2() {
GameManager::reloadAllStep2();
Loader::get()->updateResourcePaths();
}
struct ResourcesUpdate : Modify<ResourcesUpdate, LoadingLayer> {
void loadAssets() {
LoadingLayer::loadAssets();
// this is in case the user refreshes texture quality at runtime
if (this->m_loadStep == 10) {
Loader::get()->updateResources();
}
}
};
class $modify(LoadingLayer) {
bool init(bool fromReload) {
// this is in case the user refreshes texture quality at runtime
// resources are loaded first so things like texture packs override
// the GD textures
Loader::get()->waitForModsToBeLoaded();
Loader::get()->updateResources();
return LoadingLayer::init(fromReload);
}
};
// clang-format on

View file

@ -12,6 +12,7 @@
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/string.hpp>
#include <Geode/utils/vector.hpp>
#include <fmt/format.h>
#include <hash.hpp>
#include <thread>
@ -86,7 +87,7 @@ void Index::updateIndex(IndexUpdateCallback callback, bool force) {
// create directory for the local clone of
// the index
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
auto indexDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY;
ghc::filesystem::create_directories(indexDir);
#if GITHUB_DONT_RATE_LIMIT_ME_PLS == 1
@ -104,39 +105,32 @@ void Index::updateIndex(IndexUpdateCallback callback, bool force) {
#endif
// read sha of currently installed commit
std::string currentCommitSHA = "";
if (ghc::filesystem::exists(indexDir / "current")) {
auto data = utils::file::readString(indexDir / "current");
if (data) {
currentCommitSHA = data.value();
}
}
web::AsyncWebRequest()
.join("index-update")
.fetch("https://api.github.com/repos/geode-sdk/mods/commits")
.json()
.then([this, force, callback](nlohmann::json const& json) {
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
.header(fmt::format("If-None-Match: \"{}\"", currentCommitSHA))
.header("Accept: application/vnd.github.sha")
.fetch("https://api.github.com/repos/geode-sdk/mods/commits/main")
.text()
.then([this, force, callback, currentCommitSHA](std::string const& upcomingCommitSHA) {
auto indexDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY;
// check if rate-limited (returns object)
JsonChecker checkerObj(json);
auto obj = checkerObj.root("[geode-sdk/mods/commits]").obj();
if (obj.has("documentation_url") && obj.has("message")) {
RETURN_ERROR(obj.has("message").get<std::string>());
}
// gee i sure hope no one does 60 commits to the mod index an hour and download every
// single one of them
if (upcomingCommitSHA == "") {
m_upToDate = true;
m_updating = false;
// get sha of latest commit
JsonChecker checker(json);
auto root = checker.root("[geode-sdk/mods/commits]").array();
std::string upcomingCommitSHA;
if (auto first = root.at(0).obj().needs("sha")) {
upcomingCommitSHA = first.get<std::string>();
}
else {
RETURN_ERROR("Unable to get hash from latest commit: " + checker.getError());
}
// read sha of currently installed commit
std::string currentCommitSHA = "";
if (ghc::filesystem::exists(indexDir / "current")) {
auto data = utils::file::readString(indexDir / "current");
if (data) {
currentCommitSHA = data.value();
}
if (callback) callback(UpdateStatus::Finished, "", 100);
return;
}
// update if forced or latest commit has
@ -224,16 +218,34 @@ void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
return;
}
auto info = ModInfo::createFromFile(dir / "mod.json");
if (!info) {
log::warn("{}: {}, skipping", dir, info.error());
auto infoRes = ModInfo::createFromFile(dir / "mod.json");
if (!infoRes) {
log::warn("{}: {}, skipping", dir, infoRes.error());
return;
}
auto info = infoRes.value();
// make sure only latest version is present in index
auto old = std::find_if(m_items.begin(), m_items.end(), [info](IndexItem const& item) {
return item.m_info.m_id == info.m_id;
});
if (old != m_items.end()) {
// this one is newer
if (old->m_info.m_version < info.m_version) {
m_items.erase(old);
} else {
log::warn(
"Found older version of ({} < {}) of {}, skipping",
info.m_version, old->m_info.m_version, info.m_id
);
return;
}
}
IndexItem item;
item.m_path = dir;
item.m_info = info.value();
item.m_info = info;
if (!json.contains("download") || !json["download"].is_object()) {
log::warn("[index.json].download is not an object, skipping");
@ -284,7 +296,7 @@ void Index::addIndexItemFromFolder(ghc::filesystem::path const& dir) {
Result<> Index::updateIndexFromLocalCache() {
m_items.clear();
auto baseIndexDir = Loader::get()->getGeodeDirectory() / "index";
auto baseIndexDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY;
// load geode.json (index settings)
if (auto baseIndexJson = readJSON(baseIndexDir / "geode.json")) {
@ -529,7 +541,7 @@ void InstallItems::error(std::string const& info) {
void InstallItems::finish(bool replaceFiles) {
// move files from temp dir to geode directory
auto tempDir = Loader::get()->getGeodeDirectory() / "index" / "temp";
auto tempDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY / "temp";
for (auto& file : ghc::filesystem::directory_iterator(tempDir)) {
try {
auto modDir = Loader::get()->getGeodeDirectory() / "mods";
@ -605,7 +617,7 @@ InstallItems::CallbackID InstallItems::start(ItemInstallCallback callback, bool
// by virtue of running this function we know item must be valid
auto item = Index::get()->getKnownItem(inst);
auto indexDir = Loader::get()->getGeodeDirectory() / "index";
auto indexDir = Loader::get()->getGeodeSaveDirectory() / GEODE_INDEX_DIRECTORY;
(void)file::createDirectoryAll(indexDir / "temp");
auto tempFile = indexDir / "temp" / item.m_download.m_filename;

View file

@ -7,6 +7,7 @@
#include <Geode/loader/Log.hpp>
#include <Geode/utils/fetch.hpp>
#include <Geode/utils/file.hpp>
#include <fmt/format.h>
#include <hash.hpp>
#include <iostream>
#include <sstream>
@ -95,67 +96,38 @@ void InternalLoader::downloadLoaderResources(IndexUpdateCallback callback) {
web::AsyncWebRequest()
.join("update-geode-loader-resources")
.fetch("https://api.github.com/repos/geode-sdk/geode/releases/tags/" + version)
.json()
.then([tempResourcesZip, resourcesDir, callback](nlohmann::json const& json) {
auto checker = JsonChecker(json);
auto root = checker.root("[matching geode release]").obj();
// find resources.zip and download it
for (auto asset : root.needs("assets").iterate()) {
auto obj = asset.obj();
if (obj.needs("name").get<std::string>() == "resources.zip") {
auto url = obj.needs("browser_download_url").get<std::string>();
if (url.size()) {
web::AsyncWebRequest()
.fetch(url)
.into(tempResourcesZip)
.then([tempResourcesZip, resourcesDir, callback](auto) {
// unzip resources zip
auto unzip = file::unzipTo(tempResourcesZip, resourcesDir);
if (!unzip) {
if (callback)
callback(
UpdateStatus::Failed,
"Unable to unzip new resources: " + unzip.error(), 0
);
return;
}
// delete resources zip
try {
ghc::filesystem::remove(tempResourcesZip);
}
catch (...) {
}
Loader::get()->updateResources();
if (callback) callback(
UpdateStatus::Finished,
"Resources updated",
100
);
})
.expect([callback](std::string const& info) {
if (callback) callback(UpdateStatus::Failed, info, 0);
})
.progress([callback](auto&, double now, double total) {
if (callback)
callback(
UpdateStatus::Progress, "Downloading resources",
static_cast<uint8_t>(now / total * 100.0)
);
});
}
}
.fetch(fmt::format(
"https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version
))
.into(tempResourcesZip)
.then([tempResourcesZip, resourcesDir, callback](auto) {
// unzip resources zip
auto unzip = file::unzipTo(tempResourcesZip, resourcesDir);
if (!unzip) {
if (callback)
callback(
UpdateStatus::Failed, "Unable to unzip new resources: " + unzip.error(), 0
);
return;
}
// delete resources zip
try {
ghc::filesystem::remove(tempResourcesZip);
}
catch (...) {
}
if (checker.isError()) {
if (callback) callback(UpdateStatus::Failed, checker.getError(), 0);
}
if (callback) callback(UpdateStatus::Finished, "Resources updated", 100);
})
.expect([callback](std::string const& info) {
if (callback) callback(UpdateStatus::Failed, info, 0);
})
.progress([callback](auto&, double now, double total) {
if (callback)
callback(
UpdateStatus::Progress, "Downloading resources",
static_cast<uint8_t>(now / total * 100.0)
);
});
}

View file

@ -3,14 +3,14 @@
USE_GEODE_NAMESPACE();
std::unordered_set<BasicEventHandler*> Event::s_handlers = {};
std::unordered_set<EventListenerProtocol*> Event::s_listeners = {};
void BasicEventHandler::listen() {
Event::s_handlers.insert(this);
void EventListenerProtocol::enable() {
Event::s_listeners.insert(this);
}
void BasicEventHandler::unlisten() {
Event::s_handlers.erase(this);
void EventListenerProtocol::disable() {
Event::s_listeners.erase(this);
}
BasicEventHandler::~BasicEventHandler() {
@ -22,8 +22,8 @@ Event::~Event() {}
void Event::postFrom(Mod* m) {
if (m) m_sender = m;
for (auto h : Event::s_handlers) {
if (h->passThrough(this) == PassThrough::Stop) {
for (auto h : Event::s_listeners) {
if (h->passThrough(this) == ListenerResult::Stop) {
break;
}
}
@ -32,7 +32,3 @@ void Event::postFrom(Mod* m) {
Mod* Event::getSender() {
return m_sender;
}
std::unordered_set<BasicEventHandler*> const& Event::getHandlers() {
return Event::s_handlers;
}

View file

@ -37,6 +37,10 @@ void Loader::createDirectories() {
auto tempDir = this->getGeodeDirectory() / GEODE_TEMP_DIRECTORY;
auto confDir = this->getGeodeDirectory() / GEODE_CONFIG_DIRECTORY;
#ifdef GEODE_IS_MACOS
ghc::filesystem::create_directory(this->getSaveDirectory());
#endif
ghc::filesystem::create_directories(resDir);
ghc::filesystem::create_directory(confDir);
ghc::filesystem::create_directory(modDir);
@ -465,7 +469,12 @@ ghc::filesystem::path Loader::getGameDirectory() const {
}
ghc::filesystem::path Loader::getSaveDirectory() const {
// not using ~/Library/Caches
#ifdef GEODE_IS_MACOS
return ghc::filesystem::path("/Users/Shared/Geode");
#else
return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath().c_str());
#endif
}
ghc::filesystem::path Loader::getGeodeDirectory() const {

View file

@ -507,45 +507,33 @@ float TextRenderer::adjustLineAlignment() {
auto anchor = node->getAnchorPoint().y;
switch (this->getCurrentVerticalAlign()) {
case TextAlignment::Begin:
default:
{
node->setPositionY(m_cursor.y - height * (1.f - anchor));
}
break;
default: {
node->setPositionY(m_cursor.y - height * (1.f - anchor));
} break;
case TextAlignment::Center:
{
node->setPositionY(m_cursor.y - maxHeight / 2 + height * (.5f - anchor));
}
break;
case TextAlignment::Center: {
node->setPositionY(m_cursor.y - maxHeight / 2 + height * (.5f - anchor));
} break;
case TextAlignment::End:
{
node->setPositionY(m_cursor.y - maxHeight + height * anchor);
}
break;
case TextAlignment::End: {
node->setPositionY(m_cursor.y - maxHeight + height * anchor);
} break;
}
switch (this->getCurrentHorizontalAlign()) {
case TextAlignment::Begin:
default:
{
// already correct
}
break;
default: {
// already correct
} break;
case TextAlignment::Center:
{
node->setPositionX(node->getPositionX() + (m_size.width - maxWidth) / 2);
}
break;
case TextAlignment::Center: {
node->setPositionX(node->getPositionX() + (m_size.width - maxWidth) / 2);
} break;
case TextAlignment::End:
{
node->setPositionX(
node->getPositionX() + m_size.width - maxWidth - this->getCurrentIndent()
);
}
break;
case TextAlignment::End: {
node->setPositionX(
node->getPositionX() + m_size.width - maxWidth - this->getCurrentIndent()
);
} break;
}
}
return maxHeight;

View file

@ -126,37 +126,29 @@ Result<ccColor3B> geode::cocos::cc3bFromHexString(std::string const& hexValue) {
return Err("Invalid hex value");
}
switch (hexValue.size()) {
case 6:
{
auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
auto b = static_cast<uint8_t>((numValue & 0x0000FF));
return Ok(ccc3(r, g, b));
}
break;
case 6: {
auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
auto b = static_cast<uint8_t>((numValue & 0x0000FF));
return Ok(ccc3(r, g, b));
} break;
case 3:
{
auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
return Ok(ccc3(r, g, b));
}
break;
case 3: {
auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
return Ok(ccc3(r, g, b));
} break;
case 2:
{
auto num = static_cast<uint8_t>(numValue);
return Ok(ccc3(num, num, num));
}
break;
case 2: {
auto num = static_cast<uint8_t>(numValue);
return Ok(ccc3(num, num, num));
} break;
case 1:
{
auto num = static_cast<uint8_t>(numValue) * 17;
return Ok(ccc3(num, num, num));
}
break;
case 1: {
auto num = static_cast<uint8_t>(numValue) * 17;
return Ok(ccc3(num, num, num));
} break;
default: return Err("Invalid hex size, expected 1, 2, 3, or 6");
}
@ -177,57 +169,45 @@ Result<ccColor4B> geode::cocos::cc4bFromHexString(std::string const& hexValue) {
return Err("Invalid hex value");
}
switch (hexValue.size()) {
case 8:
{
auto r = static_cast<uint8_t>((numValue & 0xFF000000) >> 24);
auto g = static_cast<uint8_t>((numValue & 0x00FF0000) >> 16);
auto b = static_cast<uint8_t>((numValue & 0x0000FF00) >> 8);
auto a = static_cast<uint8_t>((numValue & 0x000000FF));
return Ok(ccc4(r, g, b, a));
}
break;
case 8: {
auto r = static_cast<uint8_t>((numValue & 0xFF000000) >> 24);
auto g = static_cast<uint8_t>((numValue & 0x00FF0000) >> 16);
auto b = static_cast<uint8_t>((numValue & 0x0000FF00) >> 8);
auto a = static_cast<uint8_t>((numValue & 0x000000FF));
return Ok(ccc4(r, g, b, a));
} break;
case 6:
{
auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
auto b = static_cast<uint8_t>((numValue & 0x0000FF));
return Ok(ccc4(r, g, b, 255));
}
break;
case 6: {
auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
auto b = static_cast<uint8_t>((numValue & 0x0000FF));
return Ok(ccc4(r, g, b, 255));
} break;
case 4:
{
auto r = static_cast<uint8_t>(((numValue & 0xF000) >> 12) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F00) >> 8) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F0) >> 4) * 17);
auto a = static_cast<uint8_t>(((numValue & 0x000F)) * 17);
return Ok(ccc4(r, g, b, a));
}
break;
case 4: {
auto r = static_cast<uint8_t>(((numValue & 0xF000) >> 12) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F00) >> 8) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F0) >> 4) * 17);
auto a = static_cast<uint8_t>(((numValue & 0x000F)) * 17);
return Ok(ccc4(r, g, b, a));
} break;
case 3:
{
auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
return Ok(ccc4(r, g, b, 255));
}
break;
case 3: {
auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
return Ok(ccc4(r, g, b, 255));
} break;
case 2:
{
auto num = static_cast<uint8_t>(numValue);
return Ok(ccc4(num, num, num, 255));
}
break;
case 2: {
auto num = static_cast<uint8_t>(numValue);
return Ok(ccc4(num, num, num, 255));
} break;
case 1:
{
auto num = static_cast<uint8_t>(numValue) * 17;
return Ok(ccc4(num, num, num, 255));
}
break;
case 1: {
auto num = static_cast<uint8_t>(numValue) * 17;
return Ok(ccc4(num, num, num, 255));
} break;
default: return Err("Invalid hex size, expected 1, 2, 3, 4, 6, or 8");
}

View file

@ -127,7 +127,7 @@ static std::unordered_map<std::string, SentAsyncWebRequestHandle> RUNNING_REQUES
static std::mutex RUNNING_REQUESTS_MUTEX;
SentAsyncWebRequest::SentAsyncWebRequest(AsyncWebRequest const& req, std::string const& id) :
m_id(id), m_url(req.m_url), m_target(req.m_target) {
m_id(id), m_url(req.m_url), m_target(req.m_target), m_httpHeaders(req.m_httpHeaders) {
#define AWAIT_RESUME() \
while (m_paused) {} \
if (m_cancelled) { \
@ -178,6 +178,12 @@ SentAsyncWebRequest::SentAsyncWebRequest(AsyncWebRequest const& req, std::string
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_slist* headers = nullptr;
for (auto& header : m_httpHeaders) {
headers = curl_slist_append(headers, header.c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
struct ProgressData {
SentAsyncWebRequest* self;
std::ofstream* file;
@ -293,6 +299,11 @@ AsyncWebRequest& AsyncWebRequest::join(std::string const& requestID) {
return *this;
}
AsyncWebRequest& AsyncWebRequest::header(std::string const& header) {
m_httpHeaders.push_back(header);
return *this;
}
AsyncWebResponse AsyncWebRequest::fetch(std::string const& url) {
m_url = url;
return AsyncWebResponse(*this);

View file

@ -15,16 +15,15 @@ USE_GEODE_NAMESPACE();
#endif
VersionInfo::VersionInfo(char const* versionString) {
if (!THE_SSCANF(versionString, "v%d.%d.%d", &this->m_major, &this->m_minor, &this->m_patch))
if (!THE_SSCANF(versionString, "%d.%d.%d", &this->m_major, &this->m_minor, &this->m_patch))
THE_SSCANF(versionString, "%d %d %d", &this->m_major, &this->m_minor, &this->m_patch);
if (!THE_SSCANF(versionString, "v%d.%d.%d", &this->m_major, &this->m_minor, &this->m_patch)) {
THE_SSCANF(versionString, "%d.%d.%d", &this->m_major, &this->m_minor, &this->m_patch);
}
}
bool VersionInfo::validate(std::string const& string) {
int buf0, buf1, buf2;
if (THE_SSCANF(string.c_str(), "v%d.%d.%d", &buf0, &buf1, &buf2)) return true;
if (THE_SSCANF(string.c_str(), "%d.%d.%d", &buf0, &buf1, &buf2)) return true;
if (THE_SSCANF(string.c_str(), "%d %d %d", &buf0, &buf1, &buf2)) return true;
return false;
}

View file

@ -3,31 +3,26 @@
USE_GEODE_NAMESPACE();
#include <Geode/modify/MenuLayer.hpp>
class $modify(MenuLayer) {
void onMoreGames(CCObject*) {
if (Mod::get()->getSettingValue<bool>("its-raining-after-all")) {
FLAlertLayer::create(
"Damn",
":(",
"OK"
)->show();
} else {
FLAlertLayer::create(
"Yay",
"The weather report said it wouldn't rain today :)",
"OK"
)->show();
}
}
struct MyMenuLayer : Modify<MyMenuLayer, MenuLayer> {
void onMoreGames(CCObject*) {
if (Mod::get()->getSettingValue<bool>("its-raining-after-all")) {
FLAlertLayer::create("Damn", ":(", "OK")->show();
}
else {
FLAlertLayer::create("Yay", "The weather report said it wouldn't rain today :)", "OK")
->show();
}
}
};
GEODE_API bool GEODE_CALL geode_load(Mod*) {
// Dispatcher::get()->addFunction<void(GJGarageLayer*)>("test-garage-open", [](GJGarageLayer* gl) {
// auto label = CCLabelBMFont::create("Dispatcher works!", "bigFont.fnt");
// label->setPosition(100, 80);
// label->setScale(.4f);
// label->setZOrder(99999);
// gl->addChild(label);
// });
return true;
// Dispatcher::get()->addFunction<void(GJGarageLayer*)>("test-garage-open", [](GJGarageLayer*
// gl) { auto label = CCLabelBMFont::create("Dispatcher works!", "bigFont.fnt");
// label->setPosition(100, 80);
// label->setScale(.4f);
// label->setZOrder(99999);
// gl->addChild(label);
// });
return true;
}

View file

@ -1,6 +1,6 @@
{
"geode": "v0.4.1",
"version": "v1.0.0",
"geode": "0.4.1",
"version": "1.0.0",
"id": "geode.testdep",
"name": "Geode Test Dependency",
"developer": "Geode Team",

View file

@ -2,90 +2,91 @@
USE_GEODE_NAMESPACE();
auto test = [](){
auto test = []() {
log::info("Static logged");
return 0;
};
// Exported functions
GEODE_API bool GEODE_CALL geode_enable() {
log::info("Enabled");
return true;
log::info("Enabled");
return true;
}
GEODE_API bool GEODE_CALL geode_disable() {
log::info("Disabled");
return true;
log::info("Disabled");
return true;
}
GEODE_API bool GEODE_CALL geode_load(Mod*) {
log::info("Loaded");
return true;
log::info("Loaded");
return true;
}
GEODE_API bool GEODE_CALL geode_unload() {
log::info("Unloaded");
return true;
log::info("Unloaded");
return true;
}
// Modify
#include <Geode/modify/GJGarageLayer.hpp>
class $modify(GJGarageLayerTest, GJGarageLayer) {
GJGarageLayerTest() :
myValue(1907) {}
int myValue;
std::string myString = "yeah have fun finding a better thing for this";
bool init() {
if (!GJGarageLayer::init()) return false;
struct GJGarageLayerTest : Modify<GJGarageLayerTest, GJGarageLayer> {
GJGarageLayerTest() : myValue(1907) {}
auto label = CCLabelBMFont::create("Modify works!", "bigFont.fnt");
label->setPosition(100, 110);
label->setScale(.4f);
label->setZOrder(99999);
addChild(label);
int myValue;
std::string myString = "yeah have fun finding a better thing for this";
if (m_fields->myValue == 1907 && m_fields->myString != "") {
auto label = CCLabelBMFont::create("Field default works!", "bigFont.fnt");
label->setPosition(100, 100);
label->setScale(.4f);
label->setZOrder(99999);
addChild(label);
}
bool init() {
if (!GJGarageLayer::init()) return false;
// Data Store
auto ds = Mod::get()->getDataStore();
int out = ds["times-opened"];
ds["times-opened"] = out + 1;
auto label = CCLabelBMFont::create("Modify works!", "bigFont.fnt");
label->setPosition(100, 110);
label->setScale(.4f);
label->setZOrder(99999);
addChild(label);
std::string text = std::string("Times opened: ") + std::to_string(out);
if (m_fields->myValue == 1907 && m_fields->myString != "") {
auto label = CCLabelBMFont::create("Field default works!", "bigFont.fnt");
label->setPosition(100, 100);
label->setScale(.4f);
label->setZOrder(99999);
addChild(label);
}
auto label2 = CCLabelBMFont::create(text.c_str(), "bigFont.fnt");
label2->setPosition(100, 90);
label2->setScale(.4f);
label2->setZOrder(99999);
addChild(label2);
// Data Store
auto ds = Mod::get()->getDataStore();
int out = ds["times-opened"];
ds["times-opened"] = out + 1;
// Dispatch system pt. 1
// auto fn = Dispatcher::get()->getFunction<void(GJGarageLayer*)>("test-garage-open");
// fn(this);
std::string text = std::string("Times opened: ") + std::to_string(out);
return true;
}
auto label2 = CCLabelBMFont::create(text.c_str(), "bigFont.fnt");
label2->setPosition(100, 90);
label2->setScale(.4f);
label2->setZOrder(99999);
addChild(label2);
// Dispatch system pt. 1
// auto fn = Dispatcher::get()->getFunction<void(GJGarageLayer*)>("test-garage-open");
// fn(this);
return true;
}
};
/*// Event system pt. 2
int a = (0, []() {
Dispatcher::get()->addSelector("test-garage-open", [](GJGarageLayer* gl) {
auto label = CCLabelBMFont::create("EventCenter works!", "bigFont.fnt");
label->setPosition(100, 80);
label->setScale(.4f);
label->setZOrder(99999);
gl->addChild(label);
Dispatcher::get()->addSelector("test-garage-open", [](GJGarageLayer* gl) {
auto label = CCLabelBMFont::create("EventCenter works!", "bigFont.fnt");
label->setPosition(100, 80);
label->setScale(.4f);
label->setZOrder(99999);
gl->addChild(label);
TestDependency::depTest(gl);
});
TestDependency::depTest(gl);
});
// Event system pt. 2
// $observe("test-garage-open", GJGarageLayer*, evt) {
@ -99,6 +100,5 @@ int a = (0, []() {
// // API pt. 2
// TestDependency::depTest(gl);
// }
return 0;
return 0;
}());*/

View file

@ -1,6 +1,6 @@
{
"geode": "v0.4.1",
"version": "v1.0.0",
"geode": "0.4.1",
"version": "1.0.0",
"id": "geode.test",
"name": "Geode Test",
"developer": "Geode Team",
@ -11,7 +11,7 @@
"dependencies": [
{
"id": "geode.testdep",
"version": "v1.0.*",
"version": "1.0.*",
"required": true
}
]