diff --git a/aurorastation.dme b/aurorastation.dme
index b1dd88d38c5..d2e4c19a49e 100644
--- a/aurorastation.dme
+++ b/aurorastation.dme
@@ -15,6 +15,7 @@
#include "code\names.dm"
#include "code\stylesheet.dm"
#include "code\world.dm"
+#include "code\__defines\_common.dm"
#include "code\__defines\_compile_options.dm"
#include "code\__defines\_layers.dm"
#include "code\__defines\_macros.dm"
@@ -27,6 +28,7 @@
#include "code\__defines\damage_organs.dm"
#include "code\__defines\dna.dm"
#include "code\__defines\dview.dm"
+#include "code\__defines\extools.dm"
#include "code\__defines\flags.dm"
#include "code\__defines\gamemode.dm"
#include "code\__defines\guns.dm"
diff --git a/byond-extools.dll b/byond-extools.dll
new file mode 100644
index 00000000000..e6d9b553490
Binary files /dev/null and b/byond-extools.dll differ
diff --git a/code/__defines/_common.dm b/code/__defines/_common.dm
new file mode 100644
index 00000000000..21ce39444ca
--- /dev/null
+++ b/code/__defines/_common.dm
@@ -0,0 +1 @@
+#define GLOBAL_PROC "some_magic_bullshit"
diff --git a/code/__defines/callback.dm b/code/__defines/callback.dm
index 18059a60839..8649e131ded 100644
--- a/code/__defines/callback.dm
+++ b/code/__defines/callback.dm
@@ -1,4 +1,2 @@
-#define GLOBAL_PROC "some_magic_bullshit"
-
#define CALLBACK new /datum/callback
#define INVOKE_ASYNC ImmediateInvokeAsync
diff --git a/code/__defines/extools.dm b/code/__defines/extools.dm
new file mode 100644
index 00000000000..2fc3a4cef54
--- /dev/null
+++ b/code/__defines/extools.dm
@@ -0,0 +1,178 @@
+#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "byond-extools")
+#define EXTOOLS_SUCCESS "SUCCESS"
+#define EXTOOLS_FAILED "FAIL"
+
+/*
+ Core - Provides necessary functionality for other modules.
+
+ Initializing any other modules also initializes this so it shouldn't be necessary to call this.
+
+*/
+
+/proc/extools_initialize()
+ return call(EXTOOLS, "core_initialize")() == EXTOOLS_SUCCESS
+
+/*
+ TFFI - Threaded FFI
+
+ All DLL calls are automatically threaded off.
+ Black magic is used to suspend (sleep) the currently executing proc, allowing non-blocking FFI.
+
+ You may call a DLL function and sleep until it returns, pass a callback to be called with the result,
+ or call resolve() on the /datum/promise to receive the return value at any time.
+
+ Example:
+
+
+ var/x = call_wait("sample.dll", "do_work", "arg1", "arg2", "arg3")
+
+ - Calls the do_work function from sample.dll with 3 arguments. The proc sleeps until do_work returns.
+
+
+
+ var/datum/promise/P = call_async("sample.dll", "do_work", "arg1")
+ ... do something else ...
+ var/result = P.resolve()
+
+ - Calls do_work with 1 argument. Returns a promise object. Runs some other code before calling P.resolve() to obtain the result.
+
+
+
+ /proc/print_result(result)
+ world << result
+
+ call_cb("sample.dll", "do_work", /proc/print_result, "arg1", "arg2")
+
+ - Calls do_work with 2 arguments. The callback is invoked with the result as the single argument. Execution resumes immediately.
+
+
+*/
+
+#if 0
+
+/proc/tffi_initialize()
+ return call(EXTOOLS, "tffi_initialize")() == EXTOOLS_SUCCESS
+
+var/fallback_alerted = FALSE
+var/next_promise_id = 0
+
+/datum/promise
+ var/completed = FALSE
+ var/result = ""
+ var/callback_context = GLOBAL_PROC
+ var/callback_proc = null
+ var/__id = 0
+
+/datum/promise/New()
+ __id = next_promise_id++ //please don't create more than 10^38 promises in a single tick
+
+//This proc's bytecode is overwritten to allow suspending and resuming on demand.
+//None of the code here should run.
+/datum/promise/proc/__internal_resolve(ref, id)
+ if(!fallback_alerted && world.system_type != UNIX) // the rewriting is currently broken on Linux.
+ world << "TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly."
+ world.log << "TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly."
+ fallback_alerted = TRUE
+ while(!completed)
+ sleep(1)
+ //It might be better to just fail and notify the user that something went wrong.
+
+/datum/promise/proc/__resolve_callback()
+ __internal_resolve("\ref[src]", __id)
+ if(callback_context == GLOBAL_PROC)
+ call(callback_proc)(result)
+ else
+ call(callback_context, callback_proc)(result)
+
+/datum/promise/proc/resolve()
+ __internal_resolve("\ref[src]", __id)
+ return result
+
+/proc/call_async()
+ var/list/arguments = args.Copy()
+ var/datum/promise/P = new
+ arguments.Insert(1, "\ref[P]")
+ call(EXTOOLS, "call_async")(arglist(arguments))
+ return P
+
+/proc/call_cb()
+ var/list/arguments = args.Copy()
+ var/context = arguments[3]
+ var/callback = arguments[4]
+ arguments.Cut(3, 5)
+ var/datum/promise/P = new
+ P.callback_context = context
+ P.callback_proc = callback
+ arguments.Insert(1, "\ref[P]")
+ call(EXTOOLS, "call_async")(arglist(arguments))
+ spawn(0)
+ P.__resolve_callback()
+
+/proc/call_wait()
+ return call_async(arglist(args)).resolve()
+
+#endif
+
+/*
+ Extended Profiling - High precision in-depth performance profiling.
+
+ Turning on extended profiling for a proc will cause each execution of it to generate a file in the ./profiles directory
+ containing a breakdown of time spent executing the proc and each sub-proc it calls. Import the file into https://www.speedscope.app/ to
+ view a good visual representation.
+
+ Be aware that sleeping counts as stopping and restarting the execution of the proc, which will generate multiple files, one between each sleep.
+
+ For large procs the profiles may become unusably large. Optimizations pending.
+
+ Example:
+
+ start_profiling(/datum/explosion/New)
+
+ - Enables profiling for /datum/explosion/New(), which will produce a detailed breakdown of each explosion that occurs afterwards.
+
+ stop_profiling(/datum/explosion/New)
+
+ - Disables profiling for explosions. Any currently running profiles will stop when the proc finishes executing or enters a sleep.
+
+*/
+
+/proc/profiling_initialize()
+ return call(EXTOOLS, "extended_profiling_initialize")() == EXTOOLS_SUCCESS
+
+/proc/start_profiling(procpath)
+ call(EXTOOLS, "enable_extended_profiling")("[procpath]")
+
+/proc/stop_profiling(procpath)
+ call(EXTOOLS, "disable_extended_profiling")("[procpath]")
+
+/*
+
+ Debug Server - High and low level debugging of DM code.
+
+ Calling debugger_initialize will start a debug server that allows connections from frontends,
+ such as SpaceManiac's VSCode extension for line-by-line debugging (and more), or Steamport's
+ Somnium for bytecode inspection.
+
+ Call with pause = TRUE to wait until the debugger connected and immediately break on the next instruction after the call.
+
+*/
+
+/proc/debugger_initialize(pause = FALSE)
+ return call(EXTOOLS, "debug_initialize")(pause ? "pause" : "") == EXTOOLS_SUCCESS
+
+/*
+
+ Misc
+
+*/
+
+//Programatically enable and disable the built-in byond profiler. Useful if you want to, for example, profile subsystem initializations.
+/proc/enable_profiling()
+ return call(EXTOOLS, "enable_profiling")() == EXTOOLS_SUCCESS
+
+/proc/disable_profiling()
+ return call(EXTOOLS, "disable_profiling")() == EXTOOLS_SUCCESS
+
+// Will dump the server's in-depth memory profile into the file specified.
+/proc/dump_memory_profile(file_name)
+ return call(EXTOOLS, "dump_memory_usage")(file_name) == EXTOOLS_SUCCESS
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index c6322365a2a..10a75164874 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -176,7 +176,8 @@ var/list/admin_verbs_server = list(
/client/proc/restart_controller,
/client/proc/cmd_ss_panic,
/client/proc/configure_access_control,
- /datum/admins/proc/togglehubvisibility //toggles visibility on the BYOND Hub
+ /datum/admins/proc/togglehubvisibility, //toggles visibility on the BYOND Hub
+ /client/proc/dump_memory_usage
)
var/list/admin_verbs_debug = list(
/client/proc/getruntimelog, // allows us to access runtime logs to somebody,
@@ -1283,3 +1284,29 @@ var/list/admin_verbs_cciaa = list(
to_chat(usr, "The sunlight system is disabled.")
#endif
+
+/client/proc/dump_memory_usage()
+ set name = "Dump Server Memory Usage"
+ set category = "Server"
+
+ if (!check_rights(R_SERVER))
+ return
+
+ if (alert(usr, "This will momentarily block the server. Proceed?", "Alert", "Yes", "No") != "Yes")
+ return
+
+ var/fname = "[game_id]-[time2text(world.timeofday, "MM-DD-hhmm")].json"
+
+ to_world(SPAN_DANGER("The server will momentarily freeze in 2 seconds!"))
+ log_and_message_admins("has initiated a memory dump into \"[fname]\".", usr)
+
+ sleep(20)
+
+ if (!dump_memory_profile("data/logs/memory/[fname]"))
+ to_chat(usr, SPAN_WARNING("Dumping memory failed at dll call."))
+ return
+
+ if (!fexists("data/logs/memory/[fname]"))
+ to_chat(usr, SPAN_WARNING("File creation failed. Please check to see if the data/logs/memory folder actually exists."))
+ else
+ to_chat(usr, SPAN_NOTICE("Memory dump completed."))
diff --git a/html/changelogs/skull132_memory-dumps.yml b/html/changelogs/skull132_memory-dumps.yml
new file mode 100644
index 00000000000..e0485fe0937
--- /dev/null
+++ b/html/changelogs/skull132_memory-dumps.yml
@@ -0,0 +1,5 @@
+author: Skull132
+delete-after: True
+
+changes:
+ - backend: "Added the ability for admins to dump process memory."