diff --git a/CMakeLists.txt b/CMakeLists.txt
index e3602d8bc5..1346b6da95 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -969,6 +969,8 @@ include_directories(Externals/picojson)
add_subdirectory(Externals/rangeset)
+add_subdirectory(Externals/FatFs)
+
########################################
# Pre-build events: Define configuration variables and write SCM info header
#
diff --git a/Externals/ExternalsReferenceAll.props b/Externals/ExternalsReferenceAll.props
index 2566a4a4a0..89ea6a4680 100644
--- a/Externals/ExternalsReferenceAll.props
+++ b/Externals/ExternalsReferenceAll.props
@@ -34,6 +34,9 @@
{cbc76802-c128-4b17-bf6c-23b08c313e5e}
+
+ {3F17D282-A77D-4931-B844-903AD0809A5E}
+
{4BC5A148-0AB3-440F-A980-A29B4B999190}
diff --git a/Externals/FatFs/CMakeLists.txt b/Externals/FatFs/CMakeLists.txt
new file mode 100644
index 0000000000..5777bf2a3a
--- /dev/null
+++ b/Externals/FatFs/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_library(FatFs STATIC
+ ff.c
+ ffunicode.c
+ diskio.h
+ ff.h
+ ffconf.h
+)
+
+target_include_directories(FatFs
+PUBLIC
+ ${CMAKE_CURRENT_SOURCE_DIR}
+)
diff --git a/Externals/FatFs/FatFs.vcxproj b/Externals/FatFs/FatFs.vcxproj
new file mode 100644
index 0000000000..659f66083b
--- /dev/null
+++ b/Externals/FatFs/FatFs.vcxproj
@@ -0,0 +1,33 @@
+
+
+
+
+
+ {3F17D282-A77D-4931-B844-903AD0809A5E}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Externals/licenses.md b/Externals/licenses.md
index 716b794fb0..36377a4801 100644
--- a/Externals/licenses.md
+++ b/Externals/licenses.md
@@ -14,6 +14,8 @@ Dolphin includes or links code of the following third-party software projects:
[MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE)
- [ENet](http://enet.bespin.org/):
[MIT](http://enet.bespin.org/License.html)
+- [FatFs](http://elm-chan.org/fsw/ff/00index_e.html):
+ [BSD-1-Clause](http://elm-chan.org/fsw/ff/doc/appnote.html#license)
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):
GPLv2+
- [gettext](https://www.gnu.org/software/gettext/):
diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt
index 729d62c441..1edb201350 100644
--- a/Source/Core/Common/CMakeLists.txt
+++ b/Source/Core/Common/CMakeLists.txt
@@ -44,6 +44,7 @@ add_library(common
EnumFormatter.h
EnumMap.h
Event.h
+ FatFsUtil.cpp
FileSearch.cpp
FileSearch.h
FileUtil.cpp
@@ -144,6 +145,7 @@ PUBLIC
PRIVATE
${CURL_LIBRARIES}
+ FatFs
${ICONV_LIBRARIES}
png
${VTUNE_LIBRARIES}
diff --git a/Source/Core/Common/FatFsUtil.cpp b/Source/Core/Common/FatFsUtil.cpp
new file mode 100644
index 0000000000..4d8b450529
--- /dev/null
+++ b/Source/Core/Common/FatFsUtil.cpp
@@ -0,0 +1,78 @@
+// Copyright 2022 Dolphin Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include
+#include
+
+// Does not compile if diskio.h is included first.
+// clang-format off
+#include "ff.h"
+#include "diskio.h"
+// clang-format on
+
+// For now this is just mostly dummy functions so FatFs actually links.
+
+extern "C" DSTATUS disk_status(BYTE pdrv)
+{
+ return STA_NOINIT;
+}
+
+extern "C" DSTATUS disk_initialize(BYTE pdrv)
+{
+ return STA_NOINIT;
+}
+
+extern "C" DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count)
+{
+ return RES_PARERR;
+}
+
+extern "C" DRESULT disk_write(BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count)
+{
+ return RES_PARERR;
+}
+
+extern "C" DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff)
+{
+ return RES_PARERR;
+}
+
+extern "C" DWORD get_fattime(void)
+{
+ return 0;
+}
+
+extern "C" void* ff_memalloc(UINT msize)
+{
+ return std::malloc(msize);
+}
+
+extern "C" void ff_memfree(void* mblock)
+{
+ return std::free(mblock);
+}
+
+extern "C" int ff_cre_syncobj(BYTE vol, FF_SYNC_t* sobj)
+{
+ *sobj = new std::recursive_mutex();
+ return *sobj != nullptr;
+}
+
+extern "C" int ff_req_grant(FF_SYNC_t sobj)
+{
+ std::recursive_mutex* m = reinterpret_cast(sobj);
+ m->lock();
+ return 1;
+}
+
+extern "C" void ff_rel_grant(FF_SYNC_t sobj)
+{
+ std::recursive_mutex* m = reinterpret_cast(sobj);
+ m->unlock();
+}
+
+extern "C" int ff_del_syncobj(FF_SYNC_t sobj)
+{
+ delete reinterpret_cast(sobj);
+ return 1;
+}
diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index 2e7e546c78..17c2aa4345 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -725,6 +725,7 @@
+
diff --git a/Source/VSProps/Base.props b/Source/VSProps/Base.props
index ae821339cc..9e08b233a9 100644
--- a/Source/VSProps/Base.props
+++ b/Source/VSProps/Base.props
@@ -35,6 +35,7 @@
$(ExternalsDir)ed25519;%(AdditionalIncludeDirectories)
$(ExternalsDir)enet\include;%(AdditionalIncludeDirectories)
$(ExternalsDir)FFmpeg-bin\$(Platform)\include;%(AdditionalIncludeDirectories)
+ $(ExternalsDir)FatFs;%(AdditionalIncludeDirectories)
$(ExternalsDir)fmt\include;%(AdditionalIncludeDirectories)
$(ExternalsDir)GL;%(AdditionalIncludeDirectories)
$(ExternalsDir)glslang;$(ExternalsDir)glslang\StandAlone;$(ExternalsDir)glslang\glslang\Public;$(ExternalsDir)glslang\SPIRV;%(AdditionalIncludeDirectories)
diff --git a/Source/dolphin-emu.sln b/Source/dolphin-emu.sln
index b289f27548..c5b81be381 100644
--- a/Source/dolphin-emu.sln
+++ b/Source/dolphin-emu.sln
@@ -83,6 +83,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spirv_cross", "..\Externals
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL2", "..\Externals\SDL\SDL2.vcxproj", "{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FatFs", "..\Externals\FatFs\FatFs.vcxproj", "{3F17D282-A77D-4931-B844-903AD0809A5E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -399,6 +401,14 @@ Global
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|ARM64.Build.0 = Release|ARM64
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.ActiveCfg = Release|x64
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}.Release|x64.Build.0 = Release|x64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Debug|ARM64.Build.0 = Debug|ARM64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Debug|x64.ActiveCfg = Debug|x64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Debug|x64.Build.0 = Debug|x64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Release|ARM64.ActiveCfg = Release|ARM64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Release|ARM64.Build.0 = Release|ARM64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Release|x64.ActiveCfg = Release|x64
+ {3F17D282-A77D-4931-B844-903AD0809A5E}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -433,6 +443,7 @@ Global
{4BC5A148-0AB3-440F-A980-A29B4B999190} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{3D780617-EC8C-4721-B9FD-DFC9BB658C7C} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
+ {3F17D282-A77D-4931-B844-903AD0809A5E} = {87ADDFF9-5768-4DA2-A33B-2477593D6677}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {64B0A343-3B94-4522-9C24-6937FE5EFB22}