diff --git a/include/stratosphere.hpp b/include/stratosphere.hpp
index 885695c0..cc386d67 100644
--- a/include/stratosphere.hpp
+++ b/include/stratosphere.hpp
@@ -17,6 +17,7 @@
#pragma once
#include "stratosphere/utilities.hpp"
+#include "stratosphere/emummc_utilities.hpp"
#include "stratosphere/scope_guard.hpp"
diff --git a/include/stratosphere/emummc_utilities.hpp b/include/stratosphere/emummc_utilities.hpp
new file mode 100644
index 00000000..04bcfd7d
--- /dev/null
+++ b/include/stratosphere/emummc_utilities.hpp
@@ -0,0 +1,28 @@
+/*
+ * 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 .
+ */
+
+#pragma once
+#include
+#include
+
+/* Get whether emummc is active. */
+bool IsEmummc();
+
+/* Get Nintendo redirection path. */
+const char *GetEmummcNintendoDirPath();
+
+/* Get Emummc folderpath, NULL if not file-based. */
+const char *GetEmummcFilePath();
diff --git a/include/stratosphere/utilities.hpp b/include/stratosphere/utilities.hpp
index 51b82c95..98ccdccf 100644
--- a/include/stratosphere/utilities.hpp
+++ b/include/stratosphere/utilities.hpp
@@ -118,23 +118,6 @@ static inline bool ShouldBlankProdInfo() {
return should_blank_prodinfo;
}
-static inline Result GetEmunandConfig(u64 *out) {
- u64 tmp = 0;
- Result rc = SmcGetConfig((SplConfigItem)65100, &tmp);
- if (R_SUCCEEDED(rc)) {
- *out = tmp;
- }
- return rc;
-}
-
-static inline bool IsEmunand() {
- u64 emunand;
- if (R_FAILED(GetEmunandConfig(&emunand))) {
- std::abort();
- }
- return emunand != 0;
-}
-
HosRecursiveMutex &GetSmSessionMutex();
HosRecursiveMutex &GetSmMitmSessionMutex();
diff --git a/source/emummc_utilities.cpp b/source/emummc_utilities.cpp
new file mode 100644
index 00000000..cef3b9af
--- /dev/null
+++ b/source/emummc_utilities.cpp
@@ -0,0 +1,142 @@
+/*
+ * 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
+#include
+
+/* EFS0 */
+static constexpr u32 EmummcStorageMagic = 0x30534645;
+static constexpr size_t EmummcMaxDirLength = 0x7F;
+
+struct EmummcBaseConfig {
+ u32 magic;
+ u32 type;
+ u32 id;
+ u32 fs_version;
+};
+
+struct EmummcPartitionConfig {
+ u64 start_sector;
+};
+
+struct EmummcFileConfig {
+ char path[EmummcMaxDirLength+1];
+};
+
+struct ExoEmummcConfig {
+ EmummcBaseConfig base_cfg;
+ union {
+ EmummcPartitionConfig partition_cfg;
+ EmummcFileConfig file_cfg;
+ };
+ char emu_dir_path[EmummcMaxDirLength+1];
+};
+
+enum EmummcType {
+ EmummcType_Emmc = 0,
+ EmummcType_Sd,
+ EmummcType_SdFile,
+
+ EmummcType_Max,
+};
+
+static bool g_IsEmummc = false;
+static bool g_HasCached = false;
+static Mutex g_Mutex;
+static ExoEmummcConfig g_exo_emummc_config;
+
+static void _CacheValues(void)
+{
+ if (__atomic_load_n(&g_HasCached, __ATOMIC_SEQ_CST))
+ return;
+
+ mutexLock(&g_Mutex);
+
+ if (g_HasCached) {
+ mutexUnlock(&g_Mutex);
+ return;
+ }
+
+ static struct {
+ char file_path[EmummcMaxDirLength+1];
+ char nintendo_path[EmummcMaxDirLength+1];
+ } __attribute__((aligned(0x1000))) paths;
+
+ {
+ SecmonArgs args = {0};
+ args.X[0] = 0xF0000404; /* smcAmsGetEmunandConfig */
+ args.X[1] = 0; /* NAND */
+ args.X[2] = reinterpret_cast(&paths); /* path output */
+ if (R_FAILED(svcCallSecureMonitor(&args)) || args.X[0] != 0) {
+ std::abort();
+ }
+ std::memcpy(&g_exo_emummc_config, &args.X[1], sizeof(args) - sizeof(args.X[0]));
+ }
+
+ const EmummcType emummc_type = static_cast(g_exo_emummc_config.base_cfg.type);
+
+/* Ignore format warnings. */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-truncation"
+ switch (emummc_type) {
+ case EmummcType_SdFile:
+ std::snprintf(g_exo_emummc_config.file_cfg.path, sizeof(g_exo_emummc_config.file_cfg.path), "/%s", paths.file_path);
+ break;
+ default:
+ break;
+ }
+
+ std::snprintf(g_exo_emummc_config.emu_dir_path, sizeof(g_exo_emummc_config.emu_dir_path), "/%s", paths.nintendo_path);
+
+ g_IsEmummc = g_exo_emummc_config.base_cfg.magic == EmummcStorageMagic && emummc_type != EmummcType_Emmc;
+
+ /* Default Nintendo redirection path. */
+ if (g_IsEmummc) {
+ if (std::strcmp(g_exo_emummc_config.emu_dir_path, "/") == 0) {
+ std::snprintf(g_exo_emummc_config.emu_dir_path, sizeof(g_exo_emummc_config.emu_dir_path), "/emummc/Nintendo_%04x", g_exo_emummc_config.base_cfg.id);
+ }
+ }
+#pragma GCC diagnostic pop
+
+ __atomic_store_n(&g_HasCached, true, __ATOMIC_SEQ_CST);
+
+ mutexUnlock(&g_Mutex);
+}
+
+
+/* Get whether emummc is active. */
+bool IsEmummc() {
+ _CacheValues();
+ return g_IsEmummc;
+}
+
+/* Get Nintendo redirection path. */
+const char *GetEmummcNintendoDirPath() {
+ _CacheValues();
+ if (!g_IsEmummc) {
+ return nullptr;
+ }
+ return g_exo_emummc_config.emu_dir_path;
+}
+
+/* Get Emummc folderpath, NULL if not file-based. */
+const char *GetEmummcFilePath() {
+ _CacheValues();
+ if (!g_IsEmummc || g_exo_emummc_config.base_cfg.type != EmummcType_SdFile) {
+ return nullptr;
+ }
+ return g_exo_emummc_config.file_cfg.path;
+}