diff --git a/thermosphere/src/exception_vectors.s b/thermosphere/src/exception_vectors.s
index abde290e7..fa706d017 100644
--- a/thermosphere/src/exception_vectors.s
+++ b/thermosphere/src/exception_vectors.s
@@ -152,11 +152,8 @@ vector_entry _synchSp0
eret
_handleSafecpy:
- // Set Z flag
- mrs x18, spsr_el2
- orr x18, x18, #(1 << 30)
- msr spsr_el2, x18
- mov x18, #0
+ // Set x16 to 1
+ mov x16, #1
eret
check_vector_size _synchSp0
diff --git a/thermosphere/src/hvisor_safe_io_copy.hpp b/thermosphere/src/hvisor_safe_io_copy.hpp
new file mode 100644
index 000000000..b55f3a045
--- /dev/null
+++ b/thermosphere/src/hvisor_safe_io_copy.hpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2019-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "defines.hpp"
+
+namespace ams::hvisor {
+
+ // Caller needs to disable interrupts
+ size_t SafeIoCopy(void *dst, const void *src, size_t size);
+
+}
diff --git a/thermosphere/src/hvisor_safe_io_copy.s b/thermosphere/src/hvisor_safe_io_copy.s
new file mode 100644
index 000000000..fe7fafbdc
--- /dev/null
+++ b/thermosphere/src/hvisor_safe_io_copy.s
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "asm_macros.s"
+
+// ams::hvisor::SafeIoCopy(void*, void const*, unsigned long)
+FUNCTION _ZN3ams6hvisor10SafeIoCopyEPvPKvm
+ // Caller needs to mask interrupts
+ // See _synchSp0 exception handler
+ msr spsel, #0
+ mov x9, x18
+ mov x18, #0
+ mov x16, #0
+
+ // x0-x2 parameters
+ // x3: remainder
+ // x4: offset
+
+ cbz x2, 2f
+ mov x3, x2
+ mov x4, #0
+
+ mov x5, #0
+ // while (remainder > 0) { offset += increment; test alignment etc. } return offset;
+ 1:
+ // Dispatcher
+ add x5, x1, x4
+ add x6, x0, x4
+ orr x7, x5, x6
+ tst x7, #3
+ // ((src + off)|(dst + off)) & 3 == 0 ? remainder > 3 : eq
+ ccmp x3, #3, #0, eq
+ bhi 3f
+ // same thing but for 2-byte alignment
+ cmp x3, #1
+ cset w8, hi
+ bics wzr, w8, w5
+ bne 4f
+
+ // 8-bit load, if the load and/or store crashes, x16 = 1 (same thing for the other load/stores)
+ ldrb w5, [x5]
+ strb w5, [x6]
+ cbnz x16, 2f
+ add x4, x4, #1
+ subs x3, x3, #1
+ bne 1b
+ 2:
+ // Return
+ msr spsel, #1
+ mov x18, x9
+ mov x0, x4
+ ret
+ 3:
+ // 32-bit load
+ ldr w5, [x5]
+ str w5, [x6]
+ cbnz x16, 2b
+ add x4, x4, #4
+ subs x3, x3, #4
+ bne 1b
+ b 2b
+ 4:
+ // 16-bit load
+ ldrh w5, [x5]
+ strh w5, [x6]
+ cbnz x16, 2b
+ add x4, x4, #2
+ subs x3, x3, #2
+ bne 1b
+ b 2b
+END_FUNCTION