diff --git a/loader/include/Geode/cocos/base_nodes/CCNode.h b/loader/include/Geode/cocos/base_nodes/CCNode.h
index 8917d30c..bc811c9c 100644
--- a/loader/include/Geode/cocos/base_nodes/CCNode.h
+++ b/loader/include/Geode/cocos/base_nodes/CCNode.h
@@ -38,6 +38,7 @@
 #include "../script_support/CCScriptSupport.h"
 #include "../include/CCProtocols.h"
 #include "Layout.hpp"
+#include <any>
 
 NS_CC_BEGIN
 
@@ -848,6 +849,7 @@ public:
         friend class geode::modifier::FieldContainer;
 
         geode::modifier::FieldContainer* getFieldContainer();
+        std::optional<std::any> getAttributeInternal(std::string const& attribute);
     
     public:
         /**
@@ -878,6 +880,19 @@ public:
          */
         CCNode* getChildByIDRecursive(std::string const& id);
 
+        void setAttribute(std::string const& attribute, std::any value);
+        template<class T>
+        std::optional<T> getAttribute(std::string const& attribute) {
+            if (auto value = this->getAttribute(attribute)) {
+                try {
+                    return std::any_cast<T>(value.value());
+                } catch(...) {
+                    return std::nullopt;
+                }
+            }
+            return std::nullopt;
+        }
+
         void setLayout(Layout* layout, bool apply = true);
         Layout* getLayout();
         void updateLayout();
diff --git a/loader/src/hooks/GeodeNodeMetadata.cpp b/loader/src/hooks/GeodeNodeMetadata.cpp
index 12c77c07..311245c4 100644
--- a/loader/src/hooks/GeodeNodeMetadata.cpp
+++ b/loader/src/hooks/GeodeNodeMetadata.cpp
@@ -22,6 +22,7 @@ private:
     std::string m_id = "";
     std::unique_ptr<Layout> m_layout = nullptr;
     PositionHint m_positionHint = PositionHint::Default;
+    std::unordered_map<std::string, std::any> m_attributes;
 
     friend class ProxyCCNode;
     friend class cocos2d::CCNode;
@@ -139,4 +140,16 @@ PositionHint CCNode::getPositionHint() {
     return GeodeNodeMetadata::set(this)->m_positionHint;
 }
 
+void CCNode::setAttribute(std::string const& attr, std::any value) {
+    GeodeNodeMetadata::set(this)->m_attributes[attr] = value;
+}
+
+std::optional<std::any> CCNode::getAttributeInternal(std::string const& attr) {
+    auto meta = GeodeNodeMetadata::set(this);
+    if (meta->m_attributes.count(attr)) {
+        return meta->m_attributes.at(attr);
+    }
+    return std::nullopt;
+}
+
 #pragma warning(pop)
diff --git a/loader/src/load/Loader.cpp b/loader/src/load/Loader.cpp
index 140628f6..8759849b 100644
--- a/loader/src/load/Loader.cpp
+++ b/loader/src/load/Loader.cpp
@@ -148,7 +148,7 @@ size_t Loader::loadModsFromDirectory(
     ghc::filesystem::path const& dir, bool recursive
 ) {
     log::debug("Searching {}", dir);
-        
+
     size_t loadedCount = 0;
     for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
         // recursively search directories
@@ -196,6 +196,7 @@ size_t Loader::loadModsFromDirectory(
             m_erroredMods.push_back({ entry.path().string(), res.error() });
         }
     }
+
     return loadedCount;
 }
 
diff --git a/loader/src/load/Mod.cpp b/loader/src/load/Mod.cpp
index 48f4214b..5d6429e8 100644
--- a/loader/src/load/Mod.cpp
+++ b/loader/src/load/Mod.cpp
@@ -115,6 +115,7 @@ Result<> Mod::loadSettings() {
             return Err(std::string("Unable to parse datastore: ") + e.what());
         }
     }
+
     return Ok();
 }
 
diff --git a/loader/src/load/load.cpp b/loader/src/load/load.cpp
index 7bf03cd7..fefb96f7 100644
--- a/loader/src/load/load.cpp
+++ b/loader/src/load/load.cpp
@@ -1,6 +1,7 @@
 #include <Geode/DefaultInclude.hpp>
 #include <Geode/loader/Loader.hpp>
 #include <Geode/loader/Mod.hpp>
+#include <Geode/utils/timer.hpp>
 #undef snprintf
 
 USE_GEODE_NAMESPACE();
@@ -23,12 +24,16 @@ bool Mod::validateID(std::string const& id) {
 }
 
 Result<Mod*> Loader::loadModFromFile(std::string const& path) {
+    Timer timer;
+    
     // load mod.json
     auto res = ModInfo::createFromGeodeFile(path);
     if (!res) {
         return Err(res.error());
     }
 
+    log::debug("ModInfo::createFromGeodeFile took {}", timer.elapsedAsString());
+
     // check that a duplicate has not been loaded
     if (m_mods.count(res.value().m_id)) {
         return Err("Mod with ID \"" + res.value().m_id + "\" has already been loaded!");
@@ -55,5 +60,7 @@ Result<Mod*> Loader::loadModFromFile(std::string const& path) {
         this->updateModResources(mod);
     });
 
+    log::debug("Loader::loadModFromFile took {}", timer.elapsedAsString());
+
     return Ok(mod);
 }