diff --git a/VERSION b/VERSION
index 524cb552..867e5243 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.1.1
+1.2.0
\ No newline at end of file
diff --git a/loader/include/Geode/Utils.hpp b/loader/include/Geode/Utils.hpp
index 5aa821cf..0ccd436e 100644
--- a/loader/include/Geode/Utils.hpp
+++ b/loader/include/Geode/Utils.hpp
@@ -11,3 +11,4 @@
 #include "utils/general.hpp"
 #include "utils/timer.hpp"
 #include "utils/MiniFunction.hpp"
+#include "utils/ObjcHook.hpp"
diff --git a/loader/include/Geode/utils/ObjcHook.hpp b/loader/include/Geode/utils/ObjcHook.hpp
new file mode 100644
index 00000000..7e156777
--- /dev/null
+++ b/loader/include/Geode/utils/ObjcHook.hpp
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "../loader/Hook.hpp"
+#include "Result.hpp"
+
+namespace geode {
+    namespace hook {
+        /**
+         * Add a new Objective-C method to a class. This method will be created
+         * using the imp provided. If the method already exists, it won't do
+         * anything.
+         * @param className The name of the class to add the method to
+         * @param selectorName The name of the method to add
+         * @param imp The implementation of the method
+         * @returns Ok() if the method was added successfully, or an error.
+         */
+        Result<> addObjcMethod(std::string const& className, std::string const& selectorName, void* imp);
+
+        /**
+         * Get the implementation of an Objective-C method. 
+         * @param className The name of the class whose method to get
+         * @param selectorName The name of the method to get
+         * @returns The implementation of the method, or an error.
+         */
+        Result<void*> getObjcMethodImp(std::string const& className, std::string const& selectorName);
+    }
+
+    class ObjcHook {
+    public:
+        /**
+         * Create a hook for an Objective-C method
+         * @param className The name of the class whose method to hook
+         * @param selectorName The name of the method to hook
+         * @param function The detour to run when the method is called
+         * @returns The created hook, or an error. 
+         */
+        template <class Func>
+        static Result<Hook*> create(std::string const& className, std::string const& selectorName, Func function, tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) {
+            GEODE_UNWRAP_INTO(auto imp, geode::hook::getObjcMethodImp(className, selectorName));
+
+            return Ok(Hook::create(
+                getMod(),
+                imp,
+                function,
+                className + "::" + selectorName,
+                tulip::hook::TulipConvention::Default,
+                metadata
+            ));
+        }
+
+        /**
+         * Create a hook for a new Objective-C method. This method will be 
+         * created with a dummy implementation that does nothing. 
+         * @param className The name of the class whose method to hook
+         * @param selectorName The name of the method to hook
+         * @param function The detour to run when the method is called
+         * @param empty A function that takes no arguments and returns nothing. 
+         * This is used to create a dummy method that can be hooked.
+         * @returns The created hook, or an error. 
+         */
+        template <class Func>
+        static Result<Hook*> create(std::string const& className, std::string const& selectorName, Func function, void(*empty)(), tulip::hook::HookMetadata const& metadata = tulip::hook::HookMetadata()) {
+            GEODE_UNWRAP(geode::hook::addObjcMethod(className, selectorName, (void*)empty));
+
+            return ObjcHook::create(className, selectorName, function, metadata);
+        }
+    };
+}
\ No newline at end of file
diff --git a/loader/src/platform/mac/util.mm b/loader/src/platform/mac/util.mm
index cd2c83c0..dbe564cd 100644
--- a/loader/src/platform/mac/util.mm
+++ b/loader/src/platform/mac/util.mm
@@ -7,9 +7,7 @@ using namespace geode::prelude;
 
 #include <Geode/loader/Dirs.hpp>
 #import <AppKit/AppKit.h>
-#include <Geode/utils/web.hpp>
-#include <Geode/utils/file.hpp>
-#include <Geode/utils/cocos.hpp>
+#include <Geode/Utils.hpp>
 #include <Geode/binding/GameManager.hpp>
 
 bool utils::clipboard::write(std::string const& data) {
@@ -235,4 +233,29 @@ void geode::utils::game::restart() {
     ), CCDirector::get()->getRunningScene(), false);
 }
 
+Result<> geode::hook::addObjcMethod(std::string const& className, std::string const& selectorName, void* imp) {
+    auto cls = objc_getClass(className.c_str());
+    if (!cls)
+        return Err("Class not found");
+    
+    auto sel = sel_registerName(selectorName.c_str());
+    
+    class_addMethod(cls, sel, (IMP)imp, "v@:");
+
+    return Ok();
+}
+Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::string const& selectorName) {
+    auto cls = objc_getClass(className.c_str());
+    if (!cls)
+        return Err("Class not found");
+    
+    auto sel = sel_registerName(selectorName.c_str());
+    
+    auto method = class_getInstanceMethod(cls, sel);
+    if (!method)
+        return Err("Method not found");
+
+    return Ok((void*)method_getImplementation(method));
+}
+
 #endif
diff --git a/loader/src/platform/windows/util.cpp b/loader/src/platform/windows/util.cpp
index 7ecb7322..74291a38 100644
--- a/loader/src/platform/windows/util.cpp
+++ b/loader/src/platform/windows/util.cpp
@@ -184,4 +184,11 @@ void geode::utils::game::restart() {
         exit(0);
 }
 
+Result<> geode::hook::addObjcMethod(std::string const& className, std::string const& selectorName, void* imp) {
+    return Err("Wrong platform");
+}
+Result<void*> geode::hook::getObjcMethodImp(std::string const& className, std::string const& selectorName) {
+    return Err("Wrong platform");
+}
+
 #endif