diff --git a/code/modules/mining/fulton.dm b/code/modules/mining/fulton.dm
new file mode 100644
index 0000000000..dd9c068a17
--- /dev/null
+++ b/code/modules/mining/fulton.dm
@@ -0,0 +1,191 @@
+var/global/list/total_extraction_beacons = list()
+
+/obj/item/extraction_pack
+ name = "fulton extraction pack"
+ desc = "A balloon that can be used to extract equipment or personnel to a Fulton Recovery Beacon. Anything not bolted down can be moved. Link the pack to a beacon by using the pack in hand."
+ icon = 'icons/obj/fulton.dmi'
+ icon_state = "extraction_pack"
+ w_class = ITEMSIZE_NORMAL
+ var/obj/structure/extraction_point/beacon
+ var/list/beacon_networks = list("station")
+ var/uses_left = 3
+ var/can_use_indoors
+ var/safe_for_living_creatures = 1
+
+/obj/item/extraction_pack/examine()
+ . = ..()
+ usr.show_message("It has [uses_left] use\s remaining.", 1)
+
+/obj/item/extraction_pack/attack_self(mob/user)
+ var/list/possible_beacons = list()
+ for(var/B in global.total_extraction_beacons)
+ var/obj/structure/extraction_point/EP = B
+ if(EP.beacon_network in beacon_networks)
+ possible_beacons += EP
+
+ if(!possible_beacons.len)
+ to_chat(user, "There are no extraction beacons in existence!")
+ return
+
+ else
+ var/A
+
+ A = input("Select a beacon to connect to", "Balloon Extraction Pack", A) as null|anything in possible_beacons
+
+ if(!A)
+ return
+ beacon = A
+ to_chat(user, "You link the extraction pack to the beacon system.")
+
+/obj/item/extraction_pack/afterattack(atom/movable/A, mob/living/carbon/human/user, flag, params)
+ if(!beacon)
+ to_chat(user, "[src] is not linked to a beacon, and cannot be used.")
+ return
+ if(!can_use_indoors)
+ var/turf/T = get_turf(A)
+ if(T && !T.outdoors)
+ to_chat(user, "[src] can only be used on things that are outdoors!")
+ return
+ if(!flag)
+ return
+ if(!istype(A))
+ return
+ else
+ if(!safe_for_living_creatures && check_for_living_mobs(A))
+ to_chat(user, "[src] is not safe for use with living creatures, they wouldn't survive the trip back!")
+ return
+ if(!isturf(A.loc)) // no extracting stuff inside other stuff
+ return
+ if(A.anchored)
+ return
+ to_chat(user, "You start attaching the pack to [A]...")
+ if(do_after(user,50,target=A))
+ to_chat(user, "You attach the pack to [A] and activate it.")
+ /* No components, sorry. No convienence for you!
+ if(loc == user && istype(user.back, /obj/item/storage/backpack))
+ var/obj/item/storage/backpack/B = user.back
+ B.SendSignal(COMSIG_TRY_STORAGE_INSERT, src, user, FALSE, FALSE)
+ */
+ uses_left--
+ if(uses_left <= 0)
+ user.drop_from_inventory(src, A)
+ var/mutable_appearance/balloon
+ var/mutable_appearance/balloon2
+ var/mutable_appearance/balloon3
+ if(isliving(A))
+ var/mob/living/M = A
+ M.AdjustStunned(20) // Keep them from moving during the duration of the extraction
+ if(M.buckled)
+ M.buckled.unbuckle_mob(M)
+ else
+ A.anchored = TRUE
+ A.density = FALSE
+ var/obj/effect/extraction_holder/holder_obj = new(A.loc)
+ holder_obj.appearance = A.appearance
+ A.forceMove(holder_obj)
+ balloon2 = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_expand")
+ balloon2.pixel_y = 10
+ balloon2.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.add_overlay(balloon2)
+ sleep(4)
+ balloon = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_balloon")
+ balloon.pixel_y = 10
+ balloon.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.cut_overlay(balloon2)
+ holder_obj.add_overlay(balloon)
+ playsound(holder_obj.loc, 'sound/items/fulext_deploy.wav', 50, 1, -3)
+ animate(holder_obj, pixel_z = 10, time = 20)
+ sleep(20)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ playsound(holder_obj.loc, 'sound/items/fultext_launch.wav', 50, 1, -3)
+ animate(holder_obj, pixel_z = 1000, time = 30)
+ if(ishuman(A))
+ var/mob/living/carbon/human/L = A
+ L.AdjustStunned(20)
+ L.drowsyness = 0
+ sleep(30)
+ var/list/flooring_near_beacon = list()
+ for(var/turf/simulated/floor/floor in orange(1, beacon))
+ flooring_near_beacon += floor
+ holder_obj.forceMove(pick(flooring_near_beacon))
+ animate(holder_obj, pixel_z = 10, time = 50)
+ sleep(50)
+ animate(holder_obj, pixel_z = 15, time = 10)
+ sleep(10)
+ animate(holder_obj, pixel_z = 10, time = 10)
+ sleep(10)
+ balloon3 = mutable_appearance('icons/obj/fulton_balloon.dmi', "fulton_retract")
+ balloon3.pixel_y = 10
+ balloon3.appearance_flags = RESET_COLOR | RESET_ALPHA | RESET_TRANSFORM
+ holder_obj.cut_overlay(balloon)
+ holder_obj.add_overlay(balloon3)
+ sleep(4)
+ holder_obj.cut_overlay(balloon3)
+ A.anchored = FALSE // An item has to be unanchored to be extracted in the first place.
+ A.density = initial(A.density)
+ animate(holder_obj, pixel_z = 0, time = 5)
+ sleep(5)
+ A.forceMove(holder_obj.loc)
+ qdel(holder_obj)
+ if(uses_left <= 0)
+ qdel(src)
+
+
+/obj/item/fulton_core
+ name = "extraction beacon signaller"
+ desc = "Emits a signal which fulton recovery devices can lock onto. Activate in hand to create a beacon."
+ icon = 'icons/obj/stock_parts.dmi'
+ icon_state = "subspace_amplifier"
+
+/obj/item/fulton_core/attack_self(mob/user)
+ if(do_after(user,15,target = user) && !QDELETED(src))
+ new /obj/structure/extraction_point(get_turf(user))
+ qdel(src)
+
+/obj/structure/extraction_point
+ name = "fulton recovery beacon"
+ desc = "A beacon for the fulton recovery system. Activate a pack in your hand to link it to a beacon."
+ icon = 'icons/obj/fulton.dmi'
+ icon_state = "extraction_point"
+ anchored = TRUE
+ density = FALSE
+ var/beacon_network = "station"
+
+/obj/structure/extraction_point/initialize()
+ . = ..()
+ name += " ([rand(100,999)]) ([get_area_name(src, TRUE)])"
+ global.total_extraction_beacons += src
+
+/obj/structure/extraction_point/Destroy()
+ global.total_extraction_beacons -= src
+ ..()
+
+/obj/effect/extraction_holder
+ name = "extraction holder"
+ desc = "you shouldnt see this"
+ var/atom/movable/stored_obj
+
+/obj/item/extraction_pack/proc/check_for_living_mobs(atom/A)
+ if(isliving(A))
+ var/mob/living/L = A
+ if(L.stat != DEAD)
+ return 1
+ for(var/thing in A.GetAllContents())
+ if(isliving(A))
+ var/mob/living/L = A
+ if(L.stat != DEAD)
+ return 1
+ return 0
+
+/obj/effect/extraction_holder/singularity_pull()
+ return
+
+/obj/effect/extraction_holder/singularity_pull()
+ return
diff --git a/icons/obj/fulton.dmi b/icons/obj/fulton.dmi
new file mode 100644
index 0000000000..c56c275b3b
Binary files /dev/null and b/icons/obj/fulton.dmi differ
diff --git a/icons/obj/fulton_balloon.dmi b/icons/obj/fulton_balloon.dmi
new file mode 100644
index 0000000000..5a1fa98e14
Binary files /dev/null and b/icons/obj/fulton_balloon.dmi differ
diff --git a/sound/items/fulext_deploy.wav b/sound/items/fulext_deploy.wav
new file mode 100644
index 0000000000..9fbcb7e3f8
Binary files /dev/null and b/sound/items/fulext_deploy.wav differ
diff --git a/sound/items/fultext_launch.wav b/sound/items/fultext_launch.wav
new file mode 100644
index 0000000000..a623a89ada
Binary files /dev/null and b/sound/items/fultext_launch.wav differ
diff --git a/vorestation.dme b/vorestation.dme
index 07d743c1cc..c94ef9490b 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -1839,6 +1839,7 @@
#include "code\modules\mining\abandonedcrates_vr.dm"
#include "code\modules\mining\alloys.dm"
#include "code\modules\mining\coins.dm"
+#include "code\modules\mining\fulton.dm"
#include "code\modules\mining\machine_input_output_plates.dm"
#include "code\modules\mining\machine_processing.dm"
#include "code\modules\mining\machine_stacking.dm"