diff --git a/include/lua.hpp b/include/lua.hpp
index f45a838d..ceb91cae 100644
--- a/include/lua.hpp
+++ b/include/lua.hpp
@@ -1,5 +1,6 @@
 #pragma once
 #include "helpers.hpp"
+#include "memory.hpp"
 
 #ifdef PANDA3DS_ENABLE_LUA
 extern "C" {
@@ -15,8 +16,15 @@ class LuaManager {
 	bool initialized = false;
 
   public:
+	// For Lua we must have some global pointers to our emulator objects to use them in script code via thunks. See the thunks in lua.cpp as an
+	// example
+	static Memory* g_memory;
+
+	LuaManager(Memory& mem) { g_memory = &mem; }
+
 	void close();
 	void initialize();
+	void initializeThunks();
 	void loadFile(const char* path);
 	void reset();
 };
@@ -24,6 +32,8 @@ class LuaManager {
 #elif  // Lua not enabled, Lua manager does nothing
 class LuaManager {
   public:
+	LuaManager(Memory& mem) {}
+
 	void close() {}
 	void initialize() {}
 	void loadFile(const char* path) {}
diff --git a/src/emulator.cpp b/src/emulator.cpp
index c5acce76..c21e86b9 100644
--- a/src/emulator.cpp
+++ b/src/emulator.cpp
@@ -13,7 +13,7 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
 
 Emulator::Emulator()
 	: config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu, config), cpu(memory, kernel), gpu(memory, config),
-	  memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), running(false), programRunning(false)
+	  memory(cpu.getTicksRef(), config), cheats(memory, kernel.getServiceManager().getHID()), lua(memory), running(false), programRunning(false)
 #ifdef PANDA3DS_ENABLE_HTTP_SERVER
 	  , httpServer(this)
 #endif
diff --git a/src/lua.cpp b/src/lua.cpp
index a01cc0b5..1cdcc689 100644
--- a/src/lua.cpp
+++ b/src/lua.cpp
@@ -3,14 +3,16 @@
 
 void LuaManager::initialize() {
 	L = luaL_newstate();  // Open Lua
-	
+
 	if (!L) {
 		printf("Lua initialization failed, continuing without Lua");
-		initialized = false; 
+		initialized = false;
 		return;
 	}
 
 	luaL_openlibs(L);
+	initializeThunks();
+
 	initialized = true;
 }
 
@@ -34,4 +36,41 @@ void LuaManager::loadFile(const char* path) {
 void LuaManager::reset() {
 	// Reset scripts
 }
+
+// Initialize C++ thunks for Lua code to call here
+// All code beyond this point is terrible and full of global state, don't judge
+
+Memory* LuaManager::g_memory = nullptr;
+
+#define MAKE_MEMORY_FUNCTIONS(size)                                 \
+	static int read##size##Thunk(lua_State* L) {                    \
+		const u32 vaddr = (u32)lua_tonumber(L, 1);                  \
+		lua_pushnumber(L, LuaManager::g_memory->read##size(vaddr)); \
+		return 1;                                                   \
+	}                                                               \
+	static int write##size##Thunk(lua_State* L) {                   \
+		const u32 vaddr = (u32)lua_tonumber(L, 1);                  \
+		const u##size value = (u##size)lua_tonumber(L, 2);          \
+		LuaManager::g_memory->write##size(vaddr, value);            \
+		return 0;                                                   \
+	}
+
+
+MAKE_MEMORY_FUNCTIONS(8)
+MAKE_MEMORY_FUNCTIONS(16)
+MAKE_MEMORY_FUNCTIONS(32)
+MAKE_MEMORY_FUNCTIONS(64)
+#undef MAKE_MEMORY_FUNCTIONS
+
+void LuaManager::initializeThunks() {
+	lua_register(L, "read8", read8Thunk);
+	lua_register(L, "read16", read16Thunk);
+	lua_register(L, "read32", read32Thunk);
+	lua_register(L, "read64", read64Thunk);
+	lua_register(L, "write8", write8Thunk);
+	lua_register(L, "write16", write16Thunk);
+	lua_register(L, "write32", write32Thunk);
+	lua_register(L, "write64", write64Thunk);
+}
+
 #endif
\ No newline at end of file