diff --git a/byond-extools.dll b/byond-extools.dll
index bd6b34c48e..cd27969ce1 100644
Binary files a/byond-extools.dll and b/byond-extools.dll differ
diff --git a/code/__DEFINES/_extools.dm b/code/__DEFINES/_extools.dm
new file mode 100644
index 0000000000..4513243aae
--- /dev/null
+++ b/code/__DEFINES/_extools.dm
@@ -0,0 +1 @@
+#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so")
diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm
index 621af2c811..dad2a38afd 100644
--- a/code/__DEFINES/atmospherics.dm
+++ b/code/__DEFINES/atmospherics.dm
@@ -281,8 +281,6 @@ GLOBAL_LIST_INIT(atmos_adjacent_savings, list(0,0))
#define CALCULATE_ADJACENT_TURFS(T) SSadjacent_air.queue[T] = 1
#endif
-#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "libbyond-extools.so")
-
GLOBAL_VAR(atmos_extools_initialized) // this must be an uninitialized (null) one or init_monstermos will be called twice because reasons
#define ATMOS_EXTOOLS_CHECK if(!GLOB.atmos_extools_initialized){\
GLOB.atmos_extools_initialized=TRUE;\
diff --git a/code/__HELPERS/_extools_api.dm b/code/__HELPERS/_extools_api.dm
new file mode 100644
index 0000000000..8c00ec643e
--- /dev/null
+++ b/code/__HELPERS/_extools_api.dm
@@ -0,0 +1,89 @@
+/*
+ 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.
+*/
+
+/proc/tffi_initialize()
+ return call(EXTOOLS, "tffi_initialize")()
+
+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()
+
+/*
+ Logging -- same exact interface as rust_g logging, but threaded and doesn't go through call()().
+*/
+
+#define EXTOOLS_LOGGING // rust_g is used as a fallback if this is undefined
+
+/proc/extools_log_write()
+
+/proc/extools_finalize_logging()
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index 62e6c4daf9..888c20003f 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -4,10 +4,15 @@
#define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound)
#define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text)
#define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text)
+#ifdef EXTOOLS_LOGGING
+// proc hooked, so we can just put in standard TRUE and FALSE
+#define WRITE_LOG(log, text) extools_log_write(log,text,TRUE)
+#define WRITE_LOG_NO_FORMAT(log, text) extools_log_write(log,text,FALSE)
+#else
//This is an external call, "true" and "false" are how rust parses out booleans
#define WRITE_LOG(log, text) rustg_log_write(log, text, "true")
#define WRITE_LOG_NO_FORMAT(log, text) rustg_log_write(log, text, "false")
-
+#endif
//print a warning message to world.log
#define WARNING(MSG) warning("[MSG] in [__FILE__] at line [__LINE__] src: [UNLINT(src)] usr: [usr].")
/proc/warning(msg)
@@ -216,7 +221,11 @@
/* Close open log handles. This should be called as late as possible, and no logging should hapen after. */
/proc/shutdown_logging()
+#ifdef EXTOOLS_LOGGING
+ extools_finalize_logging()
+#else
rustg_log_close_all()
+#endif
/* Helper procs for building detailed log lines */
diff --git a/code/game/world.dm b/code/game/world.dm
index 2d174c86e5..0306b42275 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -11,6 +11,11 @@ GLOBAL_LIST(topic_status_cache)
/world/New()
if (fexists(EXTOOLS))
call(EXTOOLS, "maptick_initialize")()
+ #ifdef EXTOOLS_LOGGING
+ call(EXTOOLS, "init_logging")()
+ else
+ CRASH("[EXTOOLS] does not exist!")
+ #endif
enable_debugger()
#ifdef REFERENCE_TRACKING
enable_reference_tracking()
diff --git a/libbyond-extools.so b/libbyond-extools.so
index 8e17f952f2..184babef2c 100644
Binary files a/libbyond-extools.so and b/libbyond-extools.so differ
diff --git a/tgstation.dme b/tgstation.dme
index e742498527..f0ca748803 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -17,6 +17,7 @@
#include "_maps\_basemap.dm"
#include "code\_compile_options.dm"
#include "code\world.dm"
+#include "code\__DEFINES\_extools.dm"
#include "code\__DEFINES\_globals.dm"
#include "code\__DEFINES\_protect.dm"
#include "code\__DEFINES\_tick.dm"
@@ -147,6 +148,7 @@
#include "code\__DEFINES\storage\_storage.dm"
#include "code\__DEFINES\storage\volumetrics.dm"
#include "code\__HELPERS\_cit_helpers.dm"
+#include "code\__HELPERS\_extools_api.dm"
#include "code\__HELPERS\_lists.dm"
#include "code\__HELPERS\_logging.dm"
#include "code\__HELPERS\_string_lists.dm"