summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-x.travis-deps.sh4
-rwxr-xr-x.travis-upload.sh2
-rw-r--r--.travis.yml1
-rw-r--r--CMakeLists.txt2
-rw-r--r--README.md4
-rw-r--r--dist/citra.xml24
-rw-r--r--src/audio_core/sdl2_sink.cpp4
-rw-r--r--src/citra/config.cpp1
-rw-r--r--src/citra/default_ini.h4
-rw-r--r--src/citra_qt/config.cpp2
-rw-r--r--src/common/common_paths.h2
-rw-r--r--src/common/file_util.cpp16
-rw-r--r--src/common/file_util.h1
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp5
-rw-r--r--src/core/file_sys/archive_backend.h28
-rw-r--r--src/core/file_sys/archive_extsavedata.cpp115
-rw-r--r--src/core/file_sys/archive_ncch.cpp (renamed from src/core/file_sys/archive_savedatacheck.cpp)22
-rw-r--r--src/core/file_sys/archive_ncch.h (renamed from src/core/file_sys/archive_savedatacheck.h)8
-rw-r--r--src/core/file_sys/archive_savedata.cpp4
-rw-r--r--src/core/file_sys/archive_sdmc.cpp279
-rw-r--r--src/core/file_sys/archive_sdmc.h26
-rw-r--r--src/core/file_sys/archive_sdmcwriteonly.cpp70
-rw-r--r--src/core/file_sys/archive_sdmcwriteonly.h57
-rw-r--r--src/core/file_sys/archive_systemsavedata.cpp4
-rw-r--r--src/core/file_sys/directory_backend.h6
-rw-r--r--src/core/file_sys/disk_archive.cpp150
-rw-r--r--src/core/file_sys/disk_archive.h43
-rw-r--r--src/core/file_sys/errors.h40
-rw-r--r--src/core/file_sys/file_backend.h6
-rw-r--r--src/core/file_sys/ivfc_archive.cpp31
-rw-r--r--src/core/file_sys/ivfc_archive.h20
-rw-r--r--src/core/file_sys/path_parser.cpp98
-rw-r--r--src/core/file_sys/path_parser.h61
-rw-r--r--src/core/file_sys/savedata_archive.cpp283
-rw-r--r--src/core/file_sys/savedata_archive.h43
-rw-r--r--src/core/hle/applets/erreula.cpp6
-rw-r--r--src/core/hle/applets/mii_selector.cpp6
-rw-r--r--src/core/hle/applets/swkbd.cpp6
-rw-r--r--src/core/hle/kernel/event.cpp5
-rw-r--r--src/core/hle/kernel/timer.cpp5
-rw-r--r--src/core/hle/result.h9
-rw-r--r--src/core/hle/service/apt/apt.cpp11
-rw-r--r--src/core/hle/service/apt/apt.h34
-rw-r--r--src/core/hle/service/apt/apt_a.cpp2
-rw-r--r--src/core/hle/service/apt/apt_u.cpp2
-rw-r--r--src/core/hle/service/cfg/cfg.cpp19
-rw-r--r--src/core/hle/service/err_f.cpp316
-rw-r--r--src/core/hle/service/fs/archive.cpp58
-rw-r--r--src/core/hle/service/fs/archive.h2
-rw-r--r--src/core/hle/service/mic_u.cpp322
-rw-r--r--src/core/hle/service/mic_u.h1
-rw-r--r--src/core/hle/service/ptm/ptm.cpp2
-rw-r--r--src/core/hw/gpu.cpp21
-rw-r--r--src/core/hw/gpu.h1
-rw-r--r--src/core/memory.cpp14
-rw-r--r--src/core/memory.h9
-rw-r--r--src/core/settings.h1
-rw-r--r--src/tests/CMakeLists.txt2
-rw-r--r--src/tests/core/file_sys/path_parser.cpp38
-rw-r--r--src/tests/glad.cpp14
-rw-r--r--src/video_core/command_processor.cpp28
-rw-r--r--src/video_core/pica.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp6
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp4
67 files changed, 1884 insertions, 540 deletions
diff --git a/.travis-deps.sh b/.travis-deps.sh
index ab3a32382..9fd21cc57 100755
--- a/.travis-deps.sh
+++ b/.travis-deps.sh
@@ -19,8 +19,8 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
if [ ! -e $HOME/.local/lib/libSDL2.la ]; then
echo "SDL2 not found in cache, get and build it..."
- wget http://libsdl.org/release/SDL2-2.0.4.tar.gz -O - | tar xz
- cd SDL2-2.0.4
+ wget http://libsdl.org/release/SDL2-2.0.5.tar.gz -O - | tar xz
+ cd SDL2-2.0.5
./configure --prefix=$HOME/.local
make -j4 && make install
else
diff --git a/.travis-upload.sh b/.travis-upload.sh
index 1bec74b3d..2eeda4c50 100755
--- a/.travis-upload.sh
+++ b/.travis-upload.sh
@@ -14,7 +14,6 @@ if [ "$TRAVIS_EVENT_TYPE" = "push" ]&&[ "$TRAVIS_BRANCH" = "master" ]; then
UPLOAD_DIR="/citra/nightly/osx-amd64"
mkdir "$REV_NAME"
- brew install lftp
cp build/src/citra/Release/citra "$REV_NAME"
cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME"
@@ -122,5 +121,4 @@ EOL
ARCHIVE_NAME="${REV_NAME}.tar.xz"
tar -cJvf "$ARCHIVE_NAME" "$REV_NAME"
- lftp -c "open -u citra-builds,$BUILD_PASSWORD sftp://builds.citra-emu.org; set sftp:auto-confirm yes; put -O '$UPLOAD_DIR' '$ARCHIVE_NAME'"
fi
diff --git a/.travis.yml b/.travis.yml
index ea99be2c4..a9e7aadd2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,7 +25,6 @@ addons:
- libqt5opengl5-dev
- xorg-dev
- lib32stdc++6 # For CMake
- - lftp # To upload builds
- clang-format-4.0
cache:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 56503f1ad..5c9b7f86a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -156,7 +156,7 @@ if (ENABLE_SDL2)
if (CITRA_USE_BUNDLED_SDL2)
# Detect toolchain and platform
if (MSVC14 AND ARCHITECTURE_x86_64)
- set(SDL2_VER "SDL2-2.0.4")
+ set(SDL2_VER "SDL2-2.0.5")
else()
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable CITRA_USE_BUNDLED_SDL2 and provide your own.")
endif()
diff --git a/README.md b/README.md
index 92e2d04a5..7d1e1de0d 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Citra Emulator
[![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg?branch=master)](https://travis-ci.org/citra-emu/citra)
[![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra)
-Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward.
+Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward.
Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project.
@@ -23,7 +23,7 @@ If you want to contribute please take a look at the [Contributor's Guide](CONTRI
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows)
* __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Building-For-Linux)
-* __OSX__: [OS X Build](https://github.com/citra-emu/citra/wiki/Building-For-OS-X)
+* __macOS__: [macOS Build](https://github.com/citra-emu/citra/wiki/Building-for-macOS)
### Support
diff --git a/dist/citra.xml b/dist/citra.xml
index bcb9acd87..6d47c8760 100644
--- a/dist/citra.xml
+++ b/dist/citra.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-ctr-3dsx">
- <comment>3DS homebrew executable</comment>
- <comment xml:lang="fr">Exécutable 3DS homebrew</comment>
+ <comment>Nintendo 3DS homebrew executable</comment>
+ <comment xml:lang="fr">Exécutable non-officiel pour Nintendo 3DS </comment>
<acronym>3DSX</acronym>
<icon name="citra"/>
<glob pattern="*.3dsx"/>
@@ -10,8 +10,8 @@
</mime-type>
<mime-type type="application/x-ctr-cci">
- <comment>3DS cartridge image</comment>
- <comment xml:lang="fr">Image de cartouche 3DS</comment>
+ <comment>Nintendo 3DS cartridge image</comment>
+ <comment xml:lang="fr">Image de cartouche Nintendo 3DS</comment>
<acronym>CCI</acronym>
<expanded-acronym>CTR Cart Image</expanded-acronym>
<icon name="citra"/>
@@ -21,8 +21,8 @@
</mime-type>
<mime-type type="application/x-ctr-cxi">
- <comment>3DS executable</comment>
- <comment xml:lang="fr">Exécutable 3DS</comment>
+ <comment>Nintendo 3DS executable</comment>
+ <comment xml:lang="fr">Exécutable Nintendo 3DS</comment>
<acronym>CXI</acronym>
<expanded-acronym>CTR eXecutable Image</expanded-acronym>
<icon name="citra"/>
@@ -31,8 +31,8 @@
</mime-type>
<mime-type type="application/x-ctr-cia">
- <comment>3DS importable archive</comment>
- <comment xml:lang="fr">Archive importable 3DS</comment>
+ <comment>Nintendo 3DS importable archive</comment>
+ <comment xml:lang="fr">Archive installable Nintendo 3DS</comment>
<acronym>CIA</acronym>
<expanded-acronym>CTR Importable Archive</expanded-acronym>
<icon name="citra"/>
@@ -40,8 +40,8 @@
</mime-type>
<mime-type type="application/x-ctr-smdh">
- <comment>3DS icon</comment>
- <comment xml:lang="fr">Icône 3DS</comment>
+ <comment>Nintendo 3DS icon and metadata</comment>
+ <comment xml:lang="fr">Icône et métadonnées Nintendo 3DS</comment>
<acronym>SMDH</acronym>
<expanded-acronym>System Menu Data Header</expanded-acronym>
<glob pattern="*.smdh"/>
@@ -49,8 +49,8 @@
</mime-type>
<mime-type type="application/x-ctr-cbmd">
- <comment>3DS banner</comment>
- <comment xml:lang="fr">Bannière 3DS</comment>
+ <comment>Nintendo 3DS banner</comment>
+ <comment xml:lang="fr">Bannière Nintendo 3DS</comment>
<acronym>CBMD</acronym>
<expanded-acronym>CTR Banner Model Data</expanded-acronym>
<glob pattern="*.cbmd"/>
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
index 75cc0d6dd..4b66cd826 100644
--- a/src/audio_core/sdl2_sink.cpp
+++ b/src/audio_core/sdl2_sink.cpp
@@ -25,7 +25,7 @@ struct SDL2Sink::Impl {
SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
- LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed");
+ LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed with: %s", SDL_GetError());
impl->audio_device_id = 0;
return;
}
@@ -45,7 +45,7 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
impl->audio_device_id =
SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
if (impl->audio_device_id <= 0) {
- LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed");
+ LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with: %s", SDL_GetError());
return;
}
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index fd30bfc85..fe74fcd72 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -59,7 +59,6 @@ void Config::ReadValues() {
// Core
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
- Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0);
// Renderer
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index b22627a2f..b98dc4d83 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -42,10 +42,6 @@ pad_circle_modifier_scale =
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_cpu_jit =
-# The applied frameskip amount. Must be a power of two.
-# 0 (default): No frameskip, 1: x2 frameskip, 2: x4 frameskip, 3: x8 frameskip, etc.
-frame_skip =
-
[Renderer]
# Whether to use software or hardware rendering.
# 0: Software, 1 (default): Hardware
diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp
index 3d2312619..3cdfe6443 100644
--- a/src/citra_qt/config.cpp
+++ b/src/citra_qt/config.cpp
@@ -39,7 +39,6 @@ void Config::ReadValues() {
qt_config->beginGroup("Core");
Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
- Settings::values.frame_skip = qt_config->value("frame_skip", 0).toInt();
qt_config->endGroup();
qt_config->beginGroup("Renderer");
@@ -146,7 +145,6 @@ void Config::SaveValues() {
qt_config->beginGroup("Core");
qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
- qt_config->setValue("frame_skip", Settings::values.frame_skip);
qt_config->endGroup();
qt_config->beginGroup("Renderer");
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index a5342a610..37304d236 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -19,7 +19,7 @@
#define EMU_DATA_DIR USER_DIR
#else
#ifdef _WIN32
-#define EMU_DATA_DIR "Citra Emulator"
+#define EMU_DATA_DIR "Citra"
#else
#define EMU_DATA_DIR "citra-emu"
#endif
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 407ed047a..413a8e7e5 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -26,6 +26,9 @@
#define stat _stat64
#define fstat _fstat64
#define fileno _fileno
+// Windows version, at least Vista is required to obtain AppData Path
+#define WINVER 0x0600
+#define _WIN32_WINNT 0x0600
#else
#ifdef __APPLE__
#include <sys/param.h>
@@ -594,6 +597,15 @@ std::string& GetExeDirectory() {
}
return exe_path;
}
+
+std::string AppDataRoamingDirectory() {
+ PWSTR pw_local_path = nullptr;
+ // Only supported by Windows Vista or later
+ SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pw_local_path);
+ std::string local_path = Common::UTF16ToUTF8(pw_local_path);
+ CoTaskMemFree(pw_local_path);
+ return local_path;
+}
#else
/**
* @return The user’s home directory on POSIX systems
@@ -671,6 +683,10 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string& new
if (paths[D_USER_IDX].empty()) {
#ifdef _WIN32
paths[D_USER_IDX] = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP;
+ if (!FileUtil::IsDirectory(paths[D_USER_IDX])) {
+ paths[D_USER_IDX] = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP;
+ }
+
paths[D_CONFIG_IDX] = paths[D_USER_IDX] + CONFIG_DIR DIR_SEP;
paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP;
#else
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 204b06f14..ac58607c5 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -154,6 +154,7 @@ std::string GetBundleDirectory();
#ifdef _WIN32
std::string& GetExeDirectory();
+std::string AppDataRoamingDirectory();
#endif
size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename);
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 88209081d..7fd397fe5 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -43,6 +43,7 @@ namespace Log {
SUB(Service, AM) \
SUB(Service, PTM) \
SUB(Service, LDR) \
+ SUB(Service, MIC) \
SUB(Service, NDM) \
SUB(Service, NIM) \
SUB(Service, NWM) \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 8d3a2d03e..8011534b8 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -60,6 +60,7 @@ enum class Class : ClassType {
Service_AM, ///< The AM (Application manager) service
Service_PTM, ///< The PTM (Power status & misc.) service
Service_LDR, ///< The LDR (3ds dll loader) service
+ Service_MIC, ///< The MIC (microphone) service
Service_NDM, ///< The NDM (Network daemon manager) service
Service_NIM, ///< The NIM (Network interface manager) service
Service_NWM, ///< The NWM (Network wlan manager) service
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4a9c6fd2f..299f1f261 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -17,13 +17,16 @@ set(SRCS
core_timing.cpp
file_sys/archive_backend.cpp
file_sys/archive_extsavedata.cpp
+ file_sys/archive_ncch.cpp
file_sys/archive_romfs.cpp
file_sys/archive_savedata.cpp
- file_sys/archive_savedatacheck.cpp
file_sys/archive_sdmc.cpp
+ file_sys/archive_sdmcwriteonly.cpp
file_sys/archive_systemsavedata.cpp
file_sys/disk_archive.cpp
file_sys/ivfc_archive.cpp
+ file_sys/path_parser.cpp
+ file_sys/savedata_archive.cpp
gdbstub/gdbstub.cpp
hle/config_mem.cpp
hle/hle.cpp
@@ -159,15 +162,18 @@ set(HEADERS
core_timing.h
file_sys/archive_backend.h
file_sys/archive_extsavedata.h
+ file_sys/archive_ncch.h
file_sys/archive_romfs.h
file_sys/archive_savedata.h
- file_sys/archive_savedatacheck.h
file_sys/archive_sdmc.h
+ file_sys/archive_sdmcwriteonly.h
file_sys/archive_systemsavedata.h
file_sys/directory_backend.h
file_sys/disk_archive.h
file_sys/file_backend.h
file_sys/ivfc_archive.h
+ file_sys/path_parser.h
+ file_sys/savedata_archive.h
gdbstub/gdbstub.h
hle/config_mem.h
hle/function_wrappers.h
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index b4444c869..ca8a94ee9 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -52,6 +52,7 @@ static Dynarmic::UserCallbacks GetUserCallbacks(ARMul_State* interpeter_state) {
user_callbacks.MemoryWrite16 = &Memory::Write16;
user_callbacks.MemoryWrite32 = &Memory::Write32;
user_callbacks.MemoryWrite64 = &Memory::Write64;
+ user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
return user_callbacks;
}
@@ -130,9 +131,9 @@ MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
MICROPROFILE_SCOPE(ARM_Jit);
- jit->Run(static_cast<unsigned>(num_instructions));
+ unsigned ticks_executed = jit->Run(static_cast<unsigned>(num_instructions));
- AddTicks(num_instructions);
+ AddTicks(ticks_executed);
}
void ARM_Dynarmic::SaveContext(Core::ThreadContext& ctx) {
diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h
index 06b8f2ed7..58f6c150c 100644
--- a/src/core/file_sys/archive_backend.h
+++ b/src/core/file_sys/archive_backend.h
@@ -87,7 +87,7 @@ public:
* @return Opened file, or error code
*/
virtual ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
- const Mode mode) const = 0;
+ const Mode& mode) const = 0;
/**
* Delete a file specified by its path
@@ -100,53 +100,53 @@ public:
* Rename a File specified by its path
* @param src_path Source path relative to the archive
* @param dest_path Destination path relative to the archive
- * @return Whether rename succeeded
+ * @return Result of the operation
*/
- virtual bool RenameFile(const Path& src_path, const Path& dest_path) const = 0;
+ virtual ResultCode RenameFile(const Path& src_path, const Path& dest_path) const = 0;
/**
* Delete a directory specified by its path
* @param path Path relative to the archive
- * @return Whether the directory could be deleted
+ * @return Result of the operation
*/
- virtual bool DeleteDirectory(const Path& path) const = 0;
+ virtual ResultCode DeleteDirectory(const Path& path) const = 0;
/**
* Delete a directory specified by its path and anything under it
* @param path Path relative to the archive
- * @return Whether the directory could be deleted
+ * @return Result of the operation
*/
- virtual bool DeleteDirectoryRecursively(const Path& path) const = 0;
+ virtual ResultCode DeleteDirectoryRecursively(const Path& path) const = 0;
/**
* Create a file specified by its path
* @param path Path relative to the Archive
* @param size The size of the new file, filled with zeroes
- * @return File creation result code
+ * @return Result of the operation
*/
virtual ResultCode CreateFile(const Path& path, u64 size) const = 0;
/**
* Create a directory specified by its path
* @param path Path relative to the archive
- * @return Whether the directory could be created
+ * @return Result of the operation
*/
- virtual bool CreateDirectory(const Path& path) const = 0;
+ virtual ResultCode CreateDirectory(const Path& path) const = 0;
/**
* Rename a Directory specified by its path
* @param src_path Source path relative to the archive
* @param dest_path Destination path relative to the archive
- * @return Whether rename succeeded
+ * @return Result of the operation
*/
- virtual bool RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
+ virtual ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const = 0;
/**
* Open a directory specified by its path
* @param path Path relative to the archive
- * @return Opened directory, or nullptr
+ * @return Opened directory, or error code
*/
- virtual std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const = 0;
+ virtual ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const = 0;
/**
* Get the free space
diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp
index e1d29efd3..e1c4931ec 100644
--- a/src/core/file_sys/archive_extsavedata.cpp
+++ b/src/core/file_sys/archive_extsavedata.cpp
@@ -11,6 +11,9 @@
#include "common/string_util.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/path_parser.h"
+#include "core/file_sys/savedata_archive.h"
#include "core/hle/service/fs/archive.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -18,6 +21,116 @@
namespace FileSys {
+/**
+ * A modified version of DiskFile for fixed-size file used by ExtSaveData
+ * The file size can't be changed by SetSize or Write.
+ */
+class FixSizeDiskFile : public DiskFile {
+public:
+ FixSizeDiskFile(FileUtil::IOFile&& file, const Mode& mode) : DiskFile(std::move(file), mode) {
+ size = GetSize();
+ }
+
+ bool SetSize(u64 size) const override {
+ return false;
+ }
+
+ ResultVal<size_t> Write(u64 offset, size_t length, bool flush,
+ const u8* buffer) const override {
+ if (offset > size) {
+ return ResultCode(ErrorDescription::FS_WriteBeyondEnd, ErrorModule::FS,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+ } else if (offset == size) {
+ return MakeResult<size_t>(0);
+ }
+
+ if (offset + length > size) {
+ length = size - offset;
+ }
+
+ return DiskFile::Write(offset, length, flush, buffer);
+ }
+
+private:
+ u64 size{};
+};
+
+/**
+ * Archive backend for general extsave data archive type.
+ * The behaviour of ExtSaveDataArchive is almost the same as SaveDataArchive, except for
+ * - file size can't be changed once created (thus creating zero-size file and openning with create
+ * flag are prohibited);
+ * - always open a file with read+write permission.
+ */
+class ExtSaveDataArchive : public SaveDataArchive {
+public:
+ ExtSaveDataArchive(const std::string& mount_point) : SaveDataArchive(mount_point) {}
+
+ std::string GetName() const override {
+ return "ExtSaveDataArchive: " + mount_point;
+ }
+
+ ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+ const Mode& mode) const override {
+ LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
+
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ if (mode.hex == 0) {
+ LOG_ERROR(Service_FS, "Empty open mode");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ if (mode.create_flag) {
+ LOG_ERROR(Service_FS, "Create flag is not supported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ case PathParser::PathNotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::FileInPath:
+ case PathParser::DirectoryFound:
+ LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::NotFound:
+ LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ }
+
+ FileUtil::IOFile file(full_path, "r+b");
+ if (!file.IsOpen()) {
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ }
+
+ Mode rwmode;
+ rwmode.write_flag.Assign(1);
+ rwmode.read_flag.Assign(1);
+ auto disk_file = std::make_unique<FixSizeDiskFile>(std::move(file), rwmode);
+ return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
+ }
+
+ ResultCode CreateFile(const Path& path, u64 size) const override {
+ if (size == 0) {
+ LOG_ERROR(Service_FS, "Zero-size file is not supported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+ return SaveDataArchive::CreateFile(path, size);
+ }
+};
+
std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path) {
std::vector<u8> vec_data = path.AsBinary();
const u32* data = reinterpret_cast<const u32*>(vec_data.data());
@@ -84,7 +197,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(cons
ErrorSummary::InvalidState, ErrorLevel::Status);
}
}
- auto archive = std::make_unique<DiskArchive>(fullpath);
+ auto archive = std::make_unique<ExtSaveDataArchive>(fullpath);
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
}
diff --git a/src/core/file_sys/archive_savedatacheck.cpp b/src/core/file_sys/archive_ncch.cpp
index 6c4542b7d..6f1aadfc3 100644
--- a/src/core/file_sys/archive_savedatacheck.cpp
+++ b/src/core/file_sys/archive_ncch.cpp
@@ -9,7 +9,7 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
-#include "core/file_sys/archive_savedatacheck.h"
+#include "core/file_sys/archive_ncch.h"
#include "core/file_sys/ivfc_archive.h"
#include "core/hle/service/fs/archive.h"
@@ -18,22 +18,22 @@
namespace FileSys {
-static std::string GetSaveDataCheckContainerPath(const std::string& nand_directory) {
+static std::string GetNCCHContainerPath(const std::string& nand_directory) {
return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID.c_str());
}
-static std::string GetSaveDataCheckPath(const std::string& mount_point, u32 high, u32 low) {
+static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) {
return Common::StringFromFormat("%s%08x/%08x/content/00000000.app.romfs", mount_point.c_str(),
high, low);
}
-ArchiveFactory_SaveDataCheck::ArchiveFactory_SaveDataCheck(const std::string& nand_directory)
- : mount_point(GetSaveDataCheckContainerPath(nand_directory)) {}
+ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory)
+ : mount_point(GetNCCHContainerPath(nand_directory)) {}
-ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(const Path& path) {
+ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) {
auto vec = path.AsBinary();
const u32* data = reinterpret_cast<u32*>(vec.data());
- std::string file_path = GetSaveDataCheckPath(mount_point, data[1], data[0]);
+ std::string file_path = GetNCCHPath(mount_point, data[1], data[0]);
auto file = std::make_shared<FileUtil::IOFile>(file_path, "rb");
if (!file->IsOpen()) {
@@ -45,15 +45,15 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveDataCheck::Open(co
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
}
-ResultCode ArchiveFactory_SaveDataCheck::Format(const Path& path,
- const FileSys::ArchiveFormatInfo& format_info) {
- LOG_ERROR(Service_FS, "Attempted to format a SaveDataCheck archive.");
+ResultCode ArchiveFactory_NCCH::Format(const Path& path,
+ const FileSys::ArchiveFormatInfo& format_info) {
+ LOG_ERROR(Service_FS, "Attempted to format a NCCH archive.");
// TODO: Verify error code
return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::FS, ErrorSummary::NotSupported,
ErrorLevel::Permanent);
}
-ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveDataCheck::GetFormatInfo(const Path& path) const {
+ResultVal<ArchiveFormatInfo> ArchiveFactory_NCCH::GetFormatInfo(const Path& path) const {
// TODO(Subv): Implement
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
return ResultCode(-1);
diff --git a/src/core/file_sys/archive_savedatacheck.h b/src/core/file_sys/archive_ncch.h
index e9cafbed9..66b8ce75d 100644
--- a/src/core/file_sys/archive_savedatacheck.h
+++ b/src/core/file_sys/archive_ncch.h
@@ -14,13 +14,13 @@
namespace FileSys {
-/// File system interface to the SaveDataCheck archive
-class ArchiveFactory_SaveDataCheck final : public ArchiveFactory {
+/// File system interface to the NCCH archive
+class ArchiveFactory_NCCH final : public ArchiveFactory {
public:
- ArchiveFactory_SaveDataCheck(const std::string& mount_point);
+ ArchiveFactory_NCCH(const std::string& mount_point);
std::string GetName() const override {
- return "SaveDataCheck";
+ return "NCCH";
}
ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp
index 6711035ec..ecb44a215 100644
--- a/src/core/file_sys/archive_savedata.cpp
+++ b/src/core/file_sys/archive_savedata.cpp
@@ -9,7 +9,7 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/file_sys/archive_savedata.h"
-#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/savedata_archive.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/fs/archive.h"
@@ -54,7 +54,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const P
ErrorSummary::InvalidState, ErrorLevel::Status);
}
- auto archive = std::make_unique<DiskArchive>(std::move(concrete_mount_point));
+ auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point));
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
}
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp
index bcb03ed36..333dfb92e 100644
--- a/src/core/file_sys/archive_sdmc.cpp
+++ b/src/core/file_sys/archive_sdmc.cpp
@@ -8,6 +8,8 @@
#include "common/logging/log.h"
#include "core/file_sys/archive_sdmc.h"
#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/path_parser.h"
#include "core/settings.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -15,6 +17,281 @@
namespace FileSys {
+ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path,
+ const Mode& mode) const {
+ Mode modified_mode;
+ modified_mode.hex = mode.hex;
+
+ // SDMC archive always opens a file with at least read permission
+ modified_mode.read_flag.Assign(1);
+
+ return OpenFileBase(path, modified_mode);
+}
+
+ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& path,
+ const Mode& mode) const {
+ LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
+
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ if (mode.hex == 0) {
+ LOG_ERROR(Service_FS, "Empty open mode");
+ return ERROR_INVALID_OPEN_FLAGS;
+ }
+
+ if (mode.create_flag && !mode.write_flag) {
+ LOG_ERROR(Service_FS, "Create flag set but write flag not set");
+ return ERROR_INVALID_OPEN_FLAGS;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::FileInPath:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::DirectoryFound:
+ LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::NotFound:
+ if (!mode.create_flag) {
+ LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
+ full_path.c_str());
+ return ERROR_NOT_FOUND;
+ } else {
+ // Create the file
+ FileUtil::CreateEmptyFile(full_path);
+ }
+ break;
+ }
+
+ FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
+ if (!file.IsOpen()) {
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
+ return ERROR_NOT_FOUND;
+ }
+
+ auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
+ return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
+}
+
+ResultCode SDMCArchive::DeleteFile(const Path& path) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::FileInPath:
+ case PathParser::NotFound:
+ LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::DirectoryFound:
+ LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ }
+
+ if (FileUtil::Delete(full_path)) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
+ return ERROR_NOT_FOUND;
+}
+
+ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
+ if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
+ return RESULT_SUCCESS;
+ }
+
+ // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+ // exist or similar. Verify.
+ return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+ ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+template <typename T>
+static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
+ T deleter) {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ if (path_parser.IsRootDirectory())
+ return ERROR_NOT_FOUND;
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::NotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::FileInPath:
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ }
+
+ if (deleter(full_path)) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+}
+
+ResultCode SDMCArchive::DeleteDirectory(const Path& path) const {
+ return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
+}
+
+ResultCode SDMCArchive::DeleteDirectoryRecursively(const Path& path) const {
+ return DeleteDirectoryHelper(
+ path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
+}
+
+ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::FileInPath:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::DirectoryFound:
+ LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+ return ERROR_ALREADY_EXISTS;
+ }
+
+ if (size == 0) {
+ FileUtil::CreateEmptyFile(full_path);
+ return RESULT_SUCCESS;
+ }
+
+ FileUtil::IOFile file(full_path, "wb");
+ // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
+ // We do this by seeking to the right size, then writing a single null byte.
+ if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_ERROR(Service_FS, "Too large file");
+ return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
+ ErrorLevel::Info);
+}
+
+ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::FileInPath:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::DirectoryFound:
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+ return ERROR_ALREADY_EXISTS;
+ }
+
+ if (FileUtil::CreateDir(mount_point + path.AsString())) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
+ return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
+ ErrorLevel::Status);
+}
+
+ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+ if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
+ return RESULT_SUCCESS;
+
+ // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+ // exist or similar. Verify.
+ return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+ ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Path& path) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::NotFound:
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
+ return ERROR_NOT_FOUND;
+ case PathParser::FileInPath:
+ LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ }
+
+ auto directory = std::make_unique<DiskDirectory>(full_path);
+ return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
+}
+
+u64 SDMCArchive::GetFreeBytes() const {
+ // TODO: Stubbed to return 1GiB
+ return 1024 * 1024 * 1024;
+}
+
ArchiveFactory_SDMC::ArchiveFactory_SDMC(const std::string& sdmc_directory)
: sdmc_directory(sdmc_directory) {
LOG_INFO(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str());
@@ -35,7 +312,7 @@ bool ArchiveFactory_SDMC::Initialize() {
}
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMC::Open(const Path& path) {
- auto archive = std::make_unique<DiskArchive>(sdmc_directory);
+ auto archive = std::make_unique<SDMCArchive>(sdmc_directory);
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
}
diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h
index 88e855351..9d99b110c 100644
--- a/src/core/file_sys/archive_sdmc.h
+++ b/src/core/file_sys/archive_sdmc.h
@@ -14,6 +14,32 @@
namespace FileSys {
+/// Archive backend for SDMC archive
+class SDMCArchive : public ArchiveBackend {
+public:
+ SDMCArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
+
+ std::string GetName() const override {
+ return "SDMCArchive: " + mount_point;
+ }
+
+ ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+ const Mode& mode) const override;
+ ResultCode DeleteFile(const Path& path) const override;
+ ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+ ResultCode DeleteDirectory(const Path& path) const override;
+ ResultCode DeleteDirectoryRecursively(const Path& path) const override;
+ ResultCode CreateFile(const Path& path, u64 size) const override;
+ ResultCode CreateDirectory(const Path& path) const override;
+ ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+ u64 GetFreeBytes() const override;
+
+protected:
+ ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const;
+ std::string mount_point;
+};
+
/// File system interface to the SDMC archive
class ArchiveFactory_SDMC final : public ArchiveFactory {
public:
diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp
new file mode 100644
index 000000000..2aafc9b1d
--- /dev/null
+++ b/src/core/file_sys/archive_sdmcwriteonly.cpp
@@ -0,0 +1,70 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include "common/file_util.h"
+#include "core/file_sys/archive_sdmcwriteonly.h"
+#include "core/file_sys/directory_backend.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/file_backend.h"
+#include "core/settings.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
+ const Mode& mode) const {
+ if (mode.read_flag) {
+ LOG_ERROR(Service_FS, "Read flag is not supported");
+ return ERROR_INVALID_READ_FLAG;
+ }
+ return SDMCArchive::OpenFileBase(path, mode);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> SDMCWriteOnlyArchive::OpenDirectory(
+ const Path& path) const {
+ LOG_ERROR(Service_FS, "Not supported");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+}
+
+ArchiveFactory_SDMCWriteOnly::ArchiveFactory_SDMCWriteOnly(const std::string& mount_point)
+ : sdmc_directory(mount_point) {
+ LOG_INFO(Service_FS, "Directory %s set as SDMCWriteOnly.", sdmc_directory.c_str());
+}
+
+bool ArchiveFactory_SDMCWriteOnly::Initialize() {
+ if (!Settings::values.use_virtual_sd) {
+ LOG_WARNING(Service_FS, "SDMC disabled by config.");
+ return false;
+ }
+
+ if (!FileUtil::CreateFullPath(sdmc_directory)) {
+ LOG_ERROR(Service_FS, "Unable to create SDMC path.");
+ return false;
+ }
+
+ return true;
+}
+
+ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SDMCWriteOnly::Open(const Path& path) {
+ auto archive = std::make_unique<SDMCWriteOnlyArchive>(sdmc_directory);
+ return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
+}
+
+ResultCode ArchiveFactory_SDMCWriteOnly::Format(const Path& path,
+ const FileSys::ArchiveFormatInfo& format_info) {
+ // TODO(wwylele): hwtest this
+ LOG_ERROR(Service_FS, "Attempted to format a SDMC write-only archive.");
+ return ResultCode(-1);
+}
+
+ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const Path& path) const {
+ // TODO(Subv): Implement
+ LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str());
+ return ResultCode(-1);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/archive_sdmcwriteonly.h b/src/core/file_sys/archive_sdmcwriteonly.h
new file mode 100644
index 000000000..ed977485a
--- /dev/null
+++ b/src/core/file_sys/archive_sdmcwriteonly.h
@@ -0,0 +1,57 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/archive_sdmc.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+/**
+ * Archive backend for SDMC write-only archive.
+ * The behaviour of SDMCWriteOnlyArchive is almost the same as SDMCArchive, except for
+ * - OpenDirectory is unsupported;
+ * - OpenFile with read flag is unsupported.
+ */
+class SDMCWriteOnlyArchive : public SDMCArchive {
+public:
+ SDMCWriteOnlyArchive(const std::string& mount_point) : SDMCArchive(mount_point) {}
+
+ std::string GetName() const override {
+ return "SDMCWriteOnlyArchive: " + mount_point;
+ }
+
+ ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+ const Mode& mode) const override;
+
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+};
+
+/// File system interface to the SDMC write-only archive
+class ArchiveFactory_SDMCWriteOnly final : public ArchiveFactory {
+public:
+ ArchiveFactory_SDMCWriteOnly(const std::string& mount_point);
+
+ /**
+ * Initialize the archive.
+ * @return true if it initialized successfully
+ */
+ bool Initialize();
+
+ std::string GetName() const override {
+ return "SDMCWriteOnly";
+ }
+
+ ResultVal<std::unique_ptr<ArchiveBackend>> Open(const Path& path) override;
+ ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info) override;
+ ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override;
+
+private:
+ std::string sdmc_directory;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp
index 48ebc0ed4..54e7793e0 100644
--- a/src/core/file_sys/archive_systemsavedata.cpp
+++ b/src/core/file_sys/archive_systemsavedata.cpp
@@ -9,7 +9,7 @@
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/file_sys/archive_systemsavedata.h"
-#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/savedata_archive.h"
#include "core/hle/service/fs/archive.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -56,7 +56,7 @@ ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(c
return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS,
ErrorSummary::InvalidState, ErrorLevel::Status);
}
- auto archive = std::make_unique<DiskArchive>(fullpath);
+ auto archive = std::make_unique<SaveDataArchive>(fullpath);
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
}
diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h
index b55e382ef..0c93f2074 100644
--- a/src/core/file_sys/directory_backend.h
+++ b/src/core/file_sys/directory_backend.h
@@ -41,12 +41,6 @@ public:
virtual ~DirectoryBackend() {}
/**
- * Open the directory
- * @return true if the directory opened correctly
- */
- virtual bool Open() = 0;
-
- /**
* List files contained in the directory
* @param count Number of entries to return at once in entries
* @param entries Buffer to read data into
diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp
index 2f05af361..a243d9a13 100644
--- a/src/core/file_sys/disk_archive.cpp
+++ b/src/core/file_sys/disk_archive.cpp
@@ -15,144 +15,8 @@
namespace FileSys {
-ResultVal<std::unique_ptr<FileBackend>> DiskArchive::OpenFile(const Path& path,
- const Mode mode) const {
- LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
- auto file = std::make_unique<DiskFile>(*this, path, mode);
- ResultCode result = file->Open();
- if (result.IsError())
- return result;
- return MakeResult<std::unique_ptr<FileBackend>>(std::move(file));
-}
-
-ResultCode DiskArchive::DeleteFile(const Path& path) const {
- std::string file_path = mount_point + path.AsString();
-
- if (FileUtil::IsDirectory(file_path))
- return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
- ErrorLevel::Status);
-
- if (!FileUtil::Exists(file_path))
- return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
- ErrorLevel::Status);
-
- if (FileUtil::Delete(file_path))
- return RESULT_SUCCESS;
-
- return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
- ErrorLevel::Status);
-}
-
-bool DiskArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
- return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
-}
-
-bool DiskArchive::DeleteDirectory(const Path& path) const {
- return FileUtil::DeleteDir(mount_point + path.AsString());
-}
-
-bool DiskArchive::DeleteDirectoryRecursively(const Path& path) const {
- return FileUtil::DeleteDirRecursively(mount_point + path.AsString());
-}
-
-ResultCode DiskArchive::CreateFile(const FileSys::Path& path, u64 size) const {
- std::string full_path = mount_point + path.AsString();
-
- if (FileUtil::IsDirectory(full_path))
- return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
- ErrorLevel::Status);
-
- if (FileUtil::Exists(full_path))
- return ResultCode(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
- ErrorSummary::NothingHappened, ErrorLevel::Status);
-
- if (size == 0) {
- FileUtil::CreateEmptyFile(full_path);
- return RESULT_SUCCESS;
- }
-
- FileUtil::IOFile file(full_path, "wb");
- // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
- // We do this by seeking to the right size, then writing a single null byte.
- if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1)
- return RESULT_SUCCESS;
-
- return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
- ErrorLevel::Info);
-}
-
-bool DiskArchive::CreateDirectory(const Path& path) const {
- return FileUtil::CreateDir(mount_point + path.AsString());
-}
-
-bool DiskArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
- return FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString());
-}
-
-std::unique_ptr<DirectoryBackend> DiskArchive::OpenDirectory(const Path& path) const {
- LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str());
- auto directory = std::make_unique<DiskDirectory>(*this, path);
- if (!directory->Open())
- return nullptr;
- return std::move(directory);
-}
-
-u64 DiskArchive::GetFreeBytes() const {
- // TODO: Stubbed to return 1GiB
- return 1024 * 1024 * 1024;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-DiskFile::DiskFile(const DiskArchive& archive, const Path& path, const Mode mode) {
- // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
- // the root directory we set while opening the archive.
- // For example, opening /../../etc/passwd can give the emulated program your users list.
- this->path = archive.mount_point + path.AsString();
- this->mode.hex = mode.hex;
-}
-
-ResultCode DiskFile::Open() {
- if (FileUtil::IsDirectory(path))
- return ResultCode(ErrorDescription::FS_NotAFile, ErrorModule::FS, ErrorSummary::Canceled,
- ErrorLevel::Status);
-
- // Specifying only the Create flag is invalid
- if (mode.create_flag && !mode.read_flag && !mode.write_flag) {
- return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
- ErrorSummary::Canceled, ErrorLevel::Status);
- }
-
- if (!FileUtil::Exists(path)) {
- if (!mode.create_flag) {
- LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
- path.c_str());
- return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS,
- ErrorSummary::NotFound, ErrorLevel::Status);
- } else {
- // Create the file
- FileUtil::CreateEmptyFile(path);
- }
- }
-
- std::string mode_string = "";
- if (mode.write_flag)
- mode_string += "r+"; // Files opened with Write access can be read from
- else if (mode.read_flag)
- mode_string += "r";
-
- // Open the file in binary mode, to avoid problems with CR/LF on Windows systems
- mode_string += "b";
-
- file = std::make_unique<FileUtil::IOFile>(path, mode_string.c_str());
- if (file->IsOpen())
- return RESULT_SUCCESS;
- return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
- ErrorLevel::Status);
-}
-
ResultVal<size_t> DiskFile::Read(const u64 offset, const size_t length, u8* buffer) const {
- if (!mode.read_flag && !mode.write_flag)
+ if (!mode.read_flag)
return ResultCode(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
ErrorSummary::Canceled, ErrorLevel::Status);
@@ -189,21 +53,11 @@ bool DiskFile::Close() const {
////////////////////////////////////////////////////////////////////////////////////////////////////
-DiskDirectory::DiskDirectory(const DiskArchive& archive, const Path& path) : directory() {
- // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass
- // the root directory we set while opening the archive.
- // For example, opening /../../usr/bin can give the emulated program your installed programs.
- this->path = archive.mount_point + path.AsString();
-}
-
-bool DiskDirectory::Open() {
- if (!FileUtil::IsDirectory(path))
- return false;
+DiskDirectory::DiskDirectory(const std::string& path) : directory() {
unsigned size = FileUtil::ScanDirectoryTree(path, directory);
directory.size = size;
directory.isDirectory = true;
children_iterator = directory.children.begin();
- return true;
}
u32 DiskDirectory::Read(const u32 count, Entry* entries) {
diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h
index 59ebb2002..eb9166df6 100644
--- a/src/core/file_sys/disk_archive.h
+++ b/src/core/file_sys/disk_archive.h
@@ -20,43 +20,13 @@
namespace FileSys {
-/**
- * Helper which implements a backend accessing the host machine's filesystem.
- * This should be subclassed by concrete archive types, which will provide the
- * base directory on the host filesystem and override any required functionality.
- */
-class DiskArchive : public ArchiveBackend {
-public:
- DiskArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
-
- virtual std::string GetName() const override {
- return "DiskArchive: " + mount_point;
- }
-
- ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
- const Mode mode) const override;
- ResultCode DeleteFile(const Path& path) const override;
- bool RenameFile(const Path& src_path, const Path& dest_path) const override;
- bool DeleteDirectory(const Path& path) const override;
- bool DeleteDirectoryRecursively(const Path& path) const override;
- ResultCode CreateFile(const Path& path, u64 size) const override;
- bool CreateDirectory(const Path& path) const override;
- bool RenameDirectory(const Path& src_path, const Path& dest_path) const override;
- std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override;
- u64 GetFreeBytes() const override;
-
-protected:
- friend class DiskFile;
- friend class DiskDirectory;
-
- std::string mount_point;
-};
-
class DiskFile : public FileBackend {
public:
- DiskFile(const DiskArchive& archive, const Path& path, const Mode mode);
+ DiskFile(FileUtil::IOFile&& file_, const Mode& mode_)
+ : file(new FileUtil::IOFile(std::move(file_))) {
+ mode.hex = mode_.hex;
+ }
- ResultCode Open() override;
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
u64 GetSize() const override;
@@ -68,20 +38,18 @@ public:
}
protected:
- std::string path;
Mode mode;
std::unique_ptr<FileUtil::IOFile> file;
};
class DiskDirectory : public DirectoryBackend {
public:
- DiskDirectory(const DiskArchive& archive, const Path& path);
+ DiskDirectory(const std::string& path);
~DiskDirectory() override {
Close();
}
- bool Open() override;
u32 Read(const u32 count, Entry* entries) override;
bool Close() const override {
@@ -89,7 +57,6 @@ public:
}
protected:
- std::string path;
u32 total_entries_in_directory;
FileUtil::FSTEntry directory;
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
new file mode 100644
index 000000000..fd1b07df0
--- /dev/null
+++ b/src/core/file_sys/errors.h
@@ -0,0 +1,40 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/result.h"
+
+namespace FileSys {
+
+const ResultCode ERROR_INVALID_PATH(ErrorDescription::FS_InvalidPath, ErrorModule::FS,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+const ResultCode ERROR_UNSUPPORTED_OPEN_FLAGS(ErrorDescription::FS_UnsupportedOpenFlags,
+ ErrorModule::FS, ErrorSummary::NotSupported,
+ ErrorLevel::Usage);
+const ResultCode ERROR_INVALID_OPEN_FLAGS(ErrorDescription::FS_InvalidOpenFlags, ErrorModule::FS,
+ ErrorSummary::Canceled, ErrorLevel::Status);
+const ResultCode ERROR_INVALID_READ_FLAG(ErrorDescription::FS_InvalidReadFlag, ErrorModule::FS,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+const ResultCode ERROR_FILE_NOT_FOUND(ErrorDescription::FS_FileNotFound, ErrorModule::FS,
+ ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_PATH_NOT_FOUND(ErrorDescription::FS_PathNotFound, ErrorModule::FS,
+ ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_NOT_FOUND(ErrorDescription::FS_NotFound, ErrorModule::FS,
+ ErrorSummary::NotFound, ErrorLevel::Status);
+const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY(ErrorDescription::FS_UnexpectedFileOrDirectory,
+ ErrorModule::FS, ErrorSummary::NotSupported,
+ ErrorLevel::Usage);
+const ResultCode ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC(ErrorDescription::FS_NotAFile,
+ ErrorModule::FS, ErrorSummary::Canceled,
+ ErrorLevel::Status);
+const ResultCode ERROR_DIRECTORY_ALREADY_EXISTS(ErrorDescription::FS_DirectoryAlreadyExists,
+ ErrorModule::FS, ErrorSummary::NothingHappened,
+ ErrorLevel::Status);
+const ResultCode ERROR_FILE_ALREADY_EXISTS(ErrorDescription::FS_FileAlreadyExists, ErrorModule::FS,
+ ErrorSummary::NothingHappened, ErrorLevel::Status);
+const ResultCode ERROR_ALREADY_EXISTS(ErrorDescription::FS_AlreadyExists, ErrorModule::FS,
+ ErrorSummary::NothingHappened, ErrorLevel::Status);
+const ResultCode ERROR_DIRECTORY_NOT_EMPTY(ErrorDescription::FS_DirectoryNotEmpty, ErrorModule::FS,
+ ErrorSummary::Canceled, ErrorLevel::Status);
+
+} // namespace FileSys
diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h
index ed997537f..5e7c2bab4 100644
--- a/src/core/file_sys/file_backend.h
+++ b/src/core/file_sys/file_backend.h
@@ -19,12 +19,6 @@ public:
virtual ~FileBackend() {}
/**
- * Open the file
- * @return Result of the file operation
- */
- virtual ResultCode Open() = 0;
-
- /**
* Read data from the file
* @param offset Offset in bytes to start reading data from
* @param length Length in bytes of data to read from file
diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp
index af59d296d..2735d2e3c 100644
--- a/src/core/file_sys/ivfc_archive.cpp
+++ b/src/core/file_sys/ivfc_archive.cpp
@@ -18,7 +18,7 @@ std::string IVFCArchive::GetName() const {
}
ResultVal<std::unique_ptr<FileBackend>> IVFCArchive::OpenFile(const Path& path,
- const Mode mode) const {
+ const Mode& mode) const {
return MakeResult<std::unique_ptr<FileBackend>>(
std::make_unique<IVFCFile>(romfs_file, data_offset, data_size));
}
@@ -31,22 +31,25 @@ ResultCode IVFCArchive::DeleteFile(const Path& path) const {
ErrorLevel::Status);
}
-bool IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
+ResultCode IVFCArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
GetName().c_str());
- return false;
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
}
-bool IVFCArchive::DeleteDirectory(const Path& path) const {
+ResultCode IVFCArchive::DeleteDirectory(const Path& path) const {
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
GetName().c_str());
- return false;
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
}
-bool IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
+ResultCode IVFCArchive::DeleteDirectoryRecursively(const Path& path) const {
LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an IVFC archive (%s).",
GetName().c_str());
- return false;
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
}
ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
@@ -57,20 +60,22 @@ ResultCode IVFCArchive::CreateFile(const Path& path, u64 size) const {
ErrorLevel::Permanent);
}
-bool IVFCArchive::CreateDirectory(const Path& path) const {
+ResultCode IVFCArchive::CreateDirectory(const Path& path) const {
LOG_CRITICAL(Service_FS, "Attempted to create a directory in an IVFC archive (%s).",
GetName().c_str());
- return false;
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
}
-bool IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+ResultCode IVFCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
LOG_CRITICAL(Service_FS, "Attempted to rename a file within an IVFC archive (%s).",
GetName().c_str());
- return false;
+ // TODO(wwylele): Use correct error code
+ return ResultCode(-1);
}
-std::unique_ptr<DirectoryBackend> IVFCArchive::OpenDirectory(const Path& path) const {
- return std::make_unique<IVFCDirectory>();
+ResultVal<std::unique_ptr<DirectoryBackend>> IVFCArchive::OpenDirectory(const Path& path) const {
+ return MakeResult<std::unique_ptr<DirectoryBackend>>(std::make_unique<IVFCDirectory>());
}
u64 IVFCArchive::GetFreeBytes() const {
diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h
index 2fbb3a568..e6fbdfb1f 100644
--- a/src/core/file_sys/ivfc_archive.h
+++ b/src/core/file_sys/ivfc_archive.h
@@ -33,15 +33,15 @@ public:
std::string GetName() const override;
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
- const Mode mode) const override;
+ const Mode& mode) const override;
ResultCode DeleteFile(const Path& path) const override;
- bool RenameFile(const Path& src_path, const Path& dest_path) const override;
- bool DeleteDirectory(const Path& path) const override;
- bool DeleteDirectoryRecursively(const Path& path) const override;
+ ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+ ResultCode DeleteDirectory(const Path& path) const override;
+ ResultCode DeleteDirectoryRecursively(const Path& path) const override;
ResultCode CreateFile(const Path& path, u64 size) const override;
- bool CreateDirectory(const Path& path) const override;
- bool RenameDirectory(const Path& src_path, const Path& dest_path) const override;
- std::unique_ptr<DirectoryBackend> OpenDirectory(const Path& path) const override;
+ ResultCode CreateDirectory(const Path& path) const override;
+ ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
u64 GetFreeBytes() const override;
protected:
@@ -55,9 +55,6 @@ public:
IVFCFile(std::shared_ptr<FileUtil::IOFile> file, u64 offset, u64 size)
: romfs_file(file), data_offset(offset), data_size(size) {}
- ResultCode Open() override {
- return RESULT_SUCCESS;
- }
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override;
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, const u8* buffer) const override;
u64 GetSize() const override;
@@ -75,9 +72,6 @@ private:
class IVFCDirectory : public DirectoryBackend {
public:
- bool Open() override {
- return false;
- }
u32 Read(const u32 count, Entry* entries) override {
return 0;
}
diff --git a/src/core/file_sys/path_parser.cpp b/src/core/file_sys/path_parser.cpp
new file mode 100644
index 000000000..5a89b02b8
--- /dev/null
+++ b/src/core/file_sys/path_parser.cpp
@@ -0,0 +1,98 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <set>
+#include "common/file_util.h"
+#include "common/string_util.h"
+#include "core/file_sys/path_parser.h"
+
+namespace FileSys {
+
+PathParser::PathParser(const Path& path) {
+ if (path.GetType() != LowPathType::Char && path.GetType() != LowPathType::Wchar) {
+ is_valid = false;
+ return;
+ }
+
+ auto path_string = path.AsString();
+ if (path_string.size() == 0 || path_string[0] != '/') {
+ is_valid = false;
+ return;
+ }
+
+ // Filter out invalid characters for the host system.
+ // Although some of these characters are valid on 3DS, they are unlikely to be used by games.
+ if (std::find_if(path_string.begin(), path_string.end(), [](char c) {
+ static const std::set<char> invalid_chars{'<', '>', '\\', '|', ':', '\"', '*', '?'};
+ return invalid_chars.find(c) != invalid_chars.end();
+ }) != path_string.end()) {
+ is_valid = false;
+ return;
+ }
+
+ Common::SplitString(path_string, '/', path_sequence);
+
+ auto begin = path_sequence.begin();
+ auto end = path_sequence.end();
+ end = std::remove_if(begin, end, [](std::string& str) { return str == "" || str == "."; });
+ path_sequence = std::vector<std::string>(begin, end);
+
+ // checks if the path is out of bounds.
+ int level = 0;
+ for (auto& node : path_sequence) {
+ if (node == "..") {
+ --level;
+ if (level < 0) {
+ is_valid = false;
+ return;
+ }
+ } else {
+ ++level;
+ }
+ }
+
+ is_valid = true;
+ is_root = level == 0;
+}
+
+PathParser::HostStatus PathParser::GetHostStatus(const std::string& mount_point) const {
+ auto path = mount_point;
+ if (!FileUtil::IsDirectory(path))
+ return InvalidMountPoint;
+ if (path_sequence.empty()) {
+ return DirectoryFound;
+ }
+
+ for (auto iter = path_sequence.begin(); iter != path_sequence.end() - 1; iter++) {
+ if (path.back() != '/')
+ path += '/';
+ path += *iter;
+
+ if (!FileUtil::Exists(path))
+ return PathNotFound;
+ if (FileUtil::IsDirectory(path))
+ continue;
+ return FileInPath;
+ }
+
+ path += "/" + path_sequence.back();
+ if (!FileUtil::Exists(path))
+ return NotFound;
+ if (FileUtil::IsDirectory(path))
+ return DirectoryFound;
+ return FileFound;
+}
+
+std::string PathParser::BuildHostPath(const std::string& mount_point) const {
+ std::string path = mount_point;
+ for (auto& node : path_sequence) {
+ if (path.back() != '/')
+ path += '/';
+ path += node;
+ }
+ return path;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/path_parser.h b/src/core/file_sys/path_parser.h
new file mode 100644
index 000000000..990802579
--- /dev/null
+++ b/src/core/file_sys/path_parser.h
@@ -0,0 +1,61 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "core/file_sys/archive_backend.h"
+
+namespace FileSys {
+
+/**
+ * A helper class parsing and verifying a string-type Path.
+ * Every archives with a sub file system should use this class to parse the path argument and check
+ * the status of the file / directory in question on the host file system.
+ */
+class PathParser {
+public:
+ PathParser(const Path& path);
+
+ /**
+ * Checks if the Path is valid.
+ * This function should be called once a PathParser is constructed.
+ * A Path is valid if:
+ * - it is a string path (with type LowPathType::Char or LowPathType::Wchar),
+ * - it starts with "/" (this seems a hard requirement in real 3DS),
+ * - it doesn't contain invalid characters, and
+ * - it doesn't go out of the root directory using "..".
+ */
+ bool IsValid() const {
+ return is_valid;
+ }
+
+ /// Checks if the Path represents the root directory.
+ bool IsRootDirectory() const {
+ return is_root;
+ }
+
+ enum HostStatus {
+ InvalidMountPoint,
+ PathNotFound, // "/a/b/c" when "a" doesn't exist
+ FileInPath, // "/a/b/c" when "a" is a file
+ FileFound, // "/a/b/c" when "c" is a file
+ DirectoryFound, // "/a/b/c" when "c" is a directory
+ NotFound // "/a/b/c" when "a/b/" exists but "c" doesn't exist
+ };
+
+ /// Checks the status of the specified file / directory by the Path on the host file system.
+ HostStatus GetHostStatus(const std::string& mount_point) const;
+
+ /// Builds a full path on the host file system.
+ std::string BuildHostPath(const std::string& mount_point) const;
+
+private:
+ std::vector<std::string> path_sequence;
+ bool is_valid{};
+ bool is_root{};
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
new file mode 100644
index 000000000..f2e6a06bc
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.cpp
@@ -0,0 +1,283 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/file_util.h"
+#include "core/file_sys/disk_archive.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/path_parser.h"
+#include "core/file_sys/savedata_archive.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
+ const Mode& mode) const {
+ LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex);
+
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ if (mode.hex == 0) {
+ LOG_ERROR(Service_FS, "Empty open mode");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ if (mode.create_flag && !mode.write_flag) {
+ LOG_ERROR(Service_FS, "Create flag set but write flag not set");
+ return ERROR_UNSUPPORTED_OPEN_FLAGS;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ case PathParser::PathNotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::FileInPath:
+ case PathParser::DirectoryFound:
+ LOG_ERROR(Service_FS, "Unexpected file or directory in %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::NotFound:
+ if (!mode.create_flag) {
+ LOG_ERROR(Service_FS, "Non-existing file %s can't be open without mode create.",
+ full_path.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ } else {
+ // Create the file
+ FileUtil::CreateEmptyFile(full_path);
+ }
+ break;
+ }
+
+ FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
+ if (!file.IsOpen()) {
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error opening %s", full_path.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ }
+
+ auto disk_file = std::make_unique<DiskFile>(std::move(file), mode);
+ return MakeResult<std::unique_ptr<FileBackend>>(std::move(disk_file));
+}
+
+ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ case PathParser::PathNotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::FileInPath:
+ case PathParser::DirectoryFound:
+ case PathParser::NotFound:
+ LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ }
+
+ if (FileUtil::Delete(full_path)) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error deleting %s", full_path.c_str());
+ return ERROR_FILE_NOT_FOUND;
+}
+
+ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const {
+ if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) {
+ return RESULT_SUCCESS;
+ }
+
+ // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+ // exist or similar. Verify.
+ return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+ ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+template <typename T>
+static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mount_point,
+ T deleter) {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ if (path_parser.IsRootDirectory())
+ return ERROR_DIRECTORY_NOT_EMPTY;
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::NotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::FileInPath:
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ }
+
+ if (deleter(full_path)) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_ERROR(Service_FS, "Directory not empty %s", full_path.c_str());
+ return ERROR_DIRECTORY_NOT_EMPTY;
+}
+
+ResultCode SaveDataArchive::DeleteDirectory(const Path& path) const {
+ return DeleteDirectoryHelper(path, mount_point, FileUtil::DeleteDir);
+}
+
+ResultCode SaveDataArchive::DeleteDirectoryRecursively(const Path& path) const {
+ return DeleteDirectoryHelper(
+ path, mount_point, [](const std::string& p) { return FileUtil::DeleteDirRecursively(p); });
+}
+
+ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ case PathParser::PathNotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::FileInPath:
+ LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::DirectoryFound:
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+ return ERROR_FILE_ALREADY_EXISTS;
+ }
+
+ if (size == 0) {
+ FileUtil::CreateEmptyFile(full_path);
+ return RESULT_SUCCESS;
+ }
+
+ FileUtil::IOFile file(full_path, "wb");
+ // Creates a sparse file (or a normal file on filesystems without the concept of sparse files)
+ // We do this by seeking to the right size, then writing a single null byte.
+ if (file.Seek(size - 1, SEEK_SET) && file.WriteBytes("", 1) == 1) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_ERROR(Service_FS, "Too large file");
+ return ResultCode(ErrorDescription::TooLarge, ErrorModule::FS, ErrorSummary::OutOfResource,
+ ErrorLevel::Info);
+}
+
+ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ case PathParser::PathNotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::FileInPath:
+ LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::DirectoryFound:
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
+ return ERROR_DIRECTORY_ALREADY_EXISTS;
+ }
+
+ if (FileUtil::CreateDir(mount_point + path.AsString())) {
+ return RESULT_SUCCESS;
+ }
+
+ LOG_CRITICAL(Service_FS, "(unreachable) Unknown error creating %s", mount_point.c_str());
+ return ResultCode(ErrorDescription::NoData, ErrorModule::FS, ErrorSummary::Canceled,
+ ErrorLevel::Status);
+}
+
+ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const {
+ if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString()))
+ return RESULT_SUCCESS;
+
+ // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
+ // exist or similar. Verify.
+ return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
+ ErrorSummary::NothingHappened, ErrorLevel::Status);
+}
+
+ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
+ const Path& path) const {
+ const PathParser path_parser(path);
+
+ if (!path_parser.IsValid()) {
+ LOG_ERROR(Service_FS, "Invalid path %s", path.DebugStr().c_str());
+ return ERROR_INVALID_PATH;
+ }
+
+ const auto full_path = path_parser.BuildHostPath(mount_point);
+
+ switch (path_parser.GetHostStatus(mount_point)) {
+ case PathParser::InvalidMountPoint:
+ LOG_CRITICAL(Service_FS, "(unreachable) Invalid mount point %s", mount_point.c_str());
+ return ERROR_FILE_NOT_FOUND;
+ case PathParser::PathNotFound:
+ case PathParser::NotFound:
+ LOG_ERROR(Service_FS, "Path not found %s", full_path.c_str());
+ return ERROR_PATH_NOT_FOUND;
+ case PathParser::FileInPath:
+ case PathParser::FileFound:
+ LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
+ return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ }
+
+ auto directory = std::make_unique<DiskDirectory>(full_path);
+ return MakeResult<std::unique_ptr<DirectoryBackend>>(std::move(directory));
+}
+
+u64 SaveDataArchive::GetFreeBytes() const {
+ // TODO: Stubbed to return 1GiB
+ return 1024 * 1024 * 1024;
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h
new file mode 100644
index 000000000..2fb6c452a
--- /dev/null
+++ b/src/core/file_sys/savedata_archive.h
@@ -0,0 +1,43 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "core/file_sys/archive_backend.h"
+#include "core/file_sys/directory_backend.h"
+#include "core/file_sys/file_backend.h"
+#include "core/hle/result.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FileSys namespace
+
+namespace FileSys {
+
+/// Archive backend for general save data archive type (SaveData and SystemSaveData)
+class SaveDataArchive : public ArchiveBackend {
+public:
+ SaveDataArchive(const std::string& mount_point_) : mount_point(mount_point_) {}
+
+ std::string GetName() const override {
+ return "SaveDataArchive: " + mount_point;
+ }
+
+ ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path,
+ const Mode& mode) const override;
+ ResultCode DeleteFile(const Path& path) const override;
+ ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override;
+ ResultCode DeleteDirectory(const Path& path) const override;
+ ResultCode DeleteDirectoryRecursively(const Path& path) const override;
+ ResultCode CreateFile(const Path& path, u64 size) const override;
+ ResultCode CreateDirectory(const Path& path) const override;
+ ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override;
+ ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+ u64 GetFreeBytes() const override;
+
+protected:
+ std::string mount_point;
+};
+
+} // namespace FileSys
diff --git a/src/core/hle/applets/erreula.cpp b/src/core/hle/applets/erreula.cpp
index 14964427b..e1379ac4d 100644
--- a/src/core/hle/applets/erreula.cpp
+++ b/src/core/hle/applets/erreula.cpp
@@ -10,7 +10,7 @@ namespace HLE {
namespace Applets {
ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
- if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
@@ -36,7 +36,7 @@ ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& param
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
@@ -57,7 +57,7 @@ ResultCode ErrEula::StartImpl(const Service::APT::AppletStartupParameter& parame
Service::APT::MessageParameter message;
message.buffer.resize(parameter.buffer.size());
std::fill(message.buffer.begin(), message.buffer.end(), 0);
- message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);
Service::APT::SendParameter(message);
diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp
index 53a8683a4..3455b9201 100644
--- a/src/core/hle/applets/mii_selector.cpp
+++ b/src/core/hle/applets/mii_selector.cpp
@@ -19,7 +19,7 @@ namespace HLE {
namespace Applets {
ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
- if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
@@ -44,7 +44,7 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
@@ -73,7 +73,7 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
Service::APT::MessageParameter message;
message.buffer.resize(sizeof(MiiResult));
std::memcpy(message.buffer.data(), &result, message.buffer.size());
- message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);
Service::APT::SendParameter(message);
diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp
index 06ddf538b..1e21337f5 100644
--- a/src/core/hle/applets/swkbd.cpp
+++ b/src/core/hle/applets/swkbd.cpp
@@ -22,7 +22,7 @@ namespace HLE {
namespace Applets {
ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) {
- if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
@@ -47,7 +47,7 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
@@ -108,7 +108,7 @@ void SoftwareKeyboard::Finalize() {
Service::APT::MessageParameter message;
message.buffer.resize(sizeof(SoftwareKeyboardConfig));
std::memcpy(message.buffer.data(), &config, message.buffer.size());
- message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);
Service::APT::SendParameter(message);
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp
index 1489c7002..3e116e3df 100644
--- a/src/core/hle/kernel/event.cpp
+++ b/src/core/hle/kernel/event.cpp
@@ -22,6 +22,11 @@ SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) {
evt->reset_type = reset_type;
evt->name = std::move(name);
+ if (reset_type == ResetType::Pulse) {
+ LOG_ERROR(Kernel, "Unimplemented event reset type Pulse");
+ UNIMPLEMENTED();
+ }
+
return evt;
}
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index a9f98223c..eac181f4e 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -31,6 +31,11 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) {
timer->interval_delay = 0;
timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom();
+ if (reset_type == ResetType::Pulse) {
+ LOG_ERROR(Kernel, "Unimplemented timer reset type Pulse");
+ UNIMPLEMENTED();
+ }
+
return timer;
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 7f8d8e00d..f7356f9d8 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -20,15 +20,24 @@ enum class ErrorDescription : u32 {
OS_InvalidBufferDescriptor = 48,
WrongAddress = 53,
FS_ArchiveNotMounted = 101,
+ FS_FileNotFound = 112,
+ FS_PathNotFound = 113,
FS_NotFound = 120,
+ FS_FileAlreadyExists = 180,
+ FS_DirectoryAlreadyExists = 185,
FS_AlreadyExists = 190,
FS_InvalidOpenFlags = 230,
+ FS_DirectoryNotEmpty = 240,
FS_NotAFile = 250,
FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
OutofRangeOrMisalignedAddress =
513, // TODO(purpasmart): Check if this name fits its actual usage
GPU_FirstInitialization = 519,
+ FS_InvalidReadFlag = 700,
FS_InvalidPath = 702,
+ FS_WriteBeyondEnd = 705,
+ FS_UnsupportedOpenFlags = 760,
+ FS_UnexpectedFileOrDirectory = 770,
InvalidSection = 1000,
TooLarge = 1001,
NotAuthorized = 1002,
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index c4bd65986..31e5e07b2 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -396,6 +396,15 @@ void StartLibraryApplet(Service::Interface* self) {
cmd_buff[1] = applet->Start(parameter).raw;
}
+void CancelLibraryApplet(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ u32 exiting = cmd_buff[1] & 0xFF;
+
+ cmd_buff[1] = 1; // TODO: Find the return code meaning
+
+ LOG_WARNING(Service_APT, "(STUBBED) called exiting=%u", exiting);
+}
+
void SetScreenCapPostPermission(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
@@ -523,7 +532,7 @@ void Init() {
notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");
parameter_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Start");
- next_parameter.signal = static_cast<u32>(SignalType::AppJustStarted);
+ next_parameter.signal = static_cast<u32>(SignalType::Wakeup);
next_parameter.destination_id = 0x300;
}
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index a118cda1f..44dbd8757 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -46,12 +46,23 @@ static_assert(sizeof(CaptureBufferInfo) == 0x20, "CaptureBufferInfo struct has i
/// Signals used by APT functions
enum class SignalType : u32 {
None = 0x0,
- AppJustStarted = 0x1,
- LibAppJustStarted = 0x2,
- LibAppFinished = 0x3,
- LibAppClosed = 0xA,
- ReturningToApp = 0xB,
- ExitingApp = 0xC,
+ Wakeup = 0x1,
+ Request = 0x2,
+ Response = 0x3,
+ Exit = 0x4,
+ Message = 0x5,
+ HomeButtonSingle = 0x6,
+ HomeButtonDouble = 0x7,
+ DspSleep = 0x8,
+ DspWakeup = 0x9,
+ WakeupByExit = 0xA,
+ WakeupByPause = 0xB,
+ WakeupByCancel = 0xC,
+ WakeupByCancelAll = 0xD,
+ WakeupByPowerButtonClick = 0xE,
+ WakeupToJumpHome = 0xF,
+ RequestForSysApplet = 0x10,
+ WakeupToLaunchApplication = 0x11,
};
/// App Id's used by APT functions
@@ -381,6 +392,17 @@ void PreloadLibraryApplet(Service::Interface* self);
void StartLibraryApplet(Service::Interface* self);
/**
+ * APT::CancelLibraryApplet service function
+ * Inputs:
+ * 0 : Command header [0x003B0040]
+ * 1 : u8, Application exiting (0 = not exiting, 1 = exiting)
+ * Outputs:
+ * 0 : Header code
+ * 1 : Result code
+ */
+void CancelLibraryApplet(Service::Interface* self);
+
+/**
* APT::GetStartupArgument service function
* Inputs:
* 1 : Parameter Size (capped to 0x300)
diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp
index f27ad91b7..a7a0c8a41 100644
--- a/src/core/hle/service/apt/apt_a.cpp
+++ b/src/core/hle/service/apt/apt_a.cpp
@@ -25,7 +25,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
{0x001E0084, StartLibraryApplet, "StartLibraryApplet"},
- {0x003B0040, nullptr, "CancelLibraryApplet?"},
+ {0x003B0040, CancelLibraryApplet, "CancelLibraryApplet"},
{0x003E0080, nullptr, "ReplySleepQuery"},
{0x00430040, NotifyToWait, "NotifyToWait?"},
{0x00440000, GetSharedFont, "GetSharedFont?"},
diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp
index d6ad42e21..a731c39f6 100644
--- a/src/core/hle/service/apt/apt_u.cpp
+++ b/src/core/hle/service/apt/apt_u.cpp
@@ -67,7 +67,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00380040, nullptr, "PreloadResidentApplet"},
{0x00390040, nullptr, "PrepareToStartResidentApplet"},
{0x003A0044, nullptr, "StartResidentApplet"},
- {0x003B0040, nullptr, "CancelLibraryApplet"},
+ {0x003B0040, CancelLibraryApplet, "CancelLibraryApplet"},
{0x003C0042, nullptr, "SendDspSleep"},
{0x003D0042, nullptr, "SendDspWakeUp"},
{0x003E0080, nullptr, "ReplySleepQuery"},
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 24eee6903..d554c3f54 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -45,7 +45,8 @@ static_assert(sizeof(SaveFileConfig) == 0x455C,
enum ConfigBlockID {
StereoCameraSettingsBlockID = 0x00050005,
SoundOutputModeBlockID = 0x00070001,
- ConsoleUniqueIDBlockID = 0x00090001,
+ ConsoleUniqueID1BlockID = 0x00090000,
+ ConsoleUniqueID2BlockID = 0x00090001,
UsernameBlockID = 0x000A0000,
BirthdayBlockID = 0x000A0001,
LanguageBlockID = 0x000A0002,
@@ -359,7 +360,7 @@ ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* da
}
ResultCode DeleteConfigNANDSaveFile() {
- FileSys::Path path("config");
+ FileSys::Path path("/config");
return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path);
}
@@ -368,7 +369,7 @@ ResultCode UpdateConfigNANDSavegame() {
mode.write_flag.Assign(1);
mode.create_flag.Assign(1);
- FileSys::Path path("config");
+ FileSys::Path path("/config");
auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
ASSERT_MSG(config_result.Succeeded(), "could not open file");
@@ -382,8 +383,9 @@ ResultCode UpdateConfigNANDSavegame() {
ResultCode FormatConfig() {
ResultCode res = DeleteConfigNANDSaveFile();
// The delete command fails if the file doesn't exist, so we have to check that too
- if (!res.IsSuccess() && res.description != ErrorDescription::FS_NotFound)
+ if (!res.IsSuccess() && res.description != ErrorDescription::FS_FileNotFound) {
return res;
+ }
// Delete the old data
cfg_config_file_buffer.fill(0);
// Create the header
@@ -409,7 +411,12 @@ ResultCode FormatConfig() {
if (!res.IsSuccess())
return res;
- res = CreateConfigInfoBlk(ConsoleUniqueIDBlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE,
+ res = CreateConfigInfoBlk(ConsoleUniqueID1BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE,
+ &CONSOLE_UNIQUE_ID);
+ if (!res.IsSuccess())
+ return res;
+
+ res = CreateConfigInfoBlk(ConsoleUniqueID2BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE,
&CONSOLE_UNIQUE_ID);
if (!res.IsSuccess())
return res;
@@ -504,7 +511,7 @@ ResultCode LoadConfigNANDSaveFile() {
cfg_system_save_data_archive = *archive_result;
- FileSys::Path config_path("config");
+ FileSys::Path config_path("/config");
FileSys::Mode open_mode = {};
open_mode.read_flag.Assign(1);
diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp
index 3ca4f98de..9905757c7 100644
--- a/src/core/hle/service/err_f.cpp
+++ b/src/core/hle/service/err_f.cpp
@@ -2,9 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <chrono>
+#include <iomanip>
+#include <sstream>
+
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "core/hle/result.h"
#include "core/hle/service/err_f.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -12,12 +18,45 @@
namespace ERR_F {
-enum {
- ErrSpecifier0 = 0,
- ErrSpecifier1 = 1,
- ErrSpecifier3 = 3,
- ErrSpecifier4 = 4,
+enum class FatalErrType : u32 {
+ Generic = 0,
+ Corrupted = 1,
+ CardRemoved = 2,
+ Exception = 3,
+ ResultFailure = 4,
+ Logged = 5,
+};
+
+enum class ExceptionType : u32 {
+ PrefetchAbort = 0,
+ DataAbort = 1,
+ Undefined = 2,
+ VectorFP = 3,
+};
+
+struct ExceptionInfo {
+ u8 exception_type;
+ INSERT_PADDING_BYTES(3);
+ u32 sr;
+ u32 ar;
+ u32 fpexc;
+ u32 fpinst;
+ u32 fpinst2;
+};
+static_assert(sizeof(ExceptionInfo) == 0x18, "ExceptionInfo struct has incorrect size");
+
+struct ExceptionContext final {
+ std::array<u32, 16> arm_regs;
+ u32 cpsr;
};
+static_assert(sizeof(ExceptionContext) == 0x44, "ExceptionContext struct has incorrect size");
+
+struct ExceptionData {
+ ExceptionInfo exception_info;
+ ExceptionContext exception_context;
+ INSERT_PADDING_WORDS(1);
+};
+static_assert(sizeof(ExceptionData) == 0x60, "ExceptionData struct has incorrect size");
// This is used instead of ResultCode from result.h
// because we can't have non-trivial data members in unions.
@@ -30,150 +69,191 @@ union RSL {
BitField<27, 5, u32> level;
};
-union ErrInfo {
- u8 specifier;
-
- struct {
- u8 specifier; // 0x0
- u8 rev_high; // 0x1
- u16 rev_low; // 0x2
- RSL result_code; // 0x4
- u32 address; // 0x8
- INSERT_PADDING_BYTES(4); // 0xC
- u32 pid_low; // 0x10
- u32 pid_high; // 0x14
- u32 aid_low; // 0x18
- u32 aid_high; // 0x1C
- } errtype1;
-
- struct {
- u8 specifier; // 0x0
- u8 rev_high; // 0x1
- u16 rev_low; // 0x2
- INSERT_PADDING_BYTES(0xC); // 0x4
- u32 pid_low; // 0x10
- u32 pid_high; // 0x14
- u32 aid_low; // 0x18
- u32 aid_high; // 0x1C
- u8 error_type; // 0x20
- INSERT_PADDING_BYTES(3); // 0x21
- u32 fault_status_reg; // 0x24
- u32 fault_addr; // 0x28
- u32 fpexc; // 0x2C
- u32 finst; // 0x30
- u32 finst2; // 0x34
- INSERT_PADDING_BYTES(0x34); // 0x38
- u32 sp; // 0x6C
- u32 pc; // 0x70
- u32 lr; // 0x74
- u32 cpsr; // 0x78
- } errtype3;
-
- struct {
- u8 specifier; // 0x0
- u8 rev_high; // 0x1
- u16 rev_low; // 0x2
- RSL result_code; // 0x4
- INSERT_PADDING_BYTES(8); // 0x8
- u32 pid_low; // 0x10
- u32 pid_high; // 0x14
- u32 aid_low; // 0x18
- u32 aid_high; // 0x1C
- char debug_string1[0x2E]; // 0x20
- char debug_string2[0x2E]; // 0x4E
- } errtype4;
+struct ErrInfo {
+ struct ErrInfoCommon {
+ u8 specifier; // 0x0
+ u8 rev_high; // 0x1
+ u16 rev_low; // 0x2
+ RSL result_code; // 0x4
+ u32 pc_address; // 0x8
+ u32 pid; // 0xC
+ u32 title_id_low; // 0x10
+ u32 title_id_high; // 0x14
+ u32 app_title_id_low; // 0x18
+ u32 app_title_id_high; // 0x1C
+ } errinfo_common;
+ static_assert(sizeof(ErrInfoCommon) == 0x20, "ErrInfoCommon struct has incorrect size");
+
+ union {
+ struct {
+ char data[0x60]; // 0x20
+ } generic;
+
+ struct {
+ ExceptionData exception_data; // 0x20
+ } exception;
+
+ struct {
+ char message[0x60]; // 0x20
+ } result_failure;
+ };
};
-enum { PrefetchAbort = 0, DataAbort = 1, UndefInstr = 2, VectorFP = 3 };
+static std::string GetErrType(u8 type_code) {
+ switch (static_cast<FatalErrType>(type_code)) {
+ case FatalErrType::Generic:
+ return "Generic";
+ case FatalErrType::Corrupted:
+ return "Corrupted";
+ case FatalErrType::CardRemoved:
+ return "CardRemoved";
+ case FatalErrType::Exception:
+ return "Exception";
+ case FatalErrType::ResultFailure:
+ return "ResultFailure";
+ case FatalErrType::Logged:
+ return "Logged";
+ default:
+ return "Unknown Error Type";
+ }
+}
-static std::string GetErrInfo3Type(u8 type_code) {
- switch (type_code) {
- case PrefetchAbort:
+static std::string GetExceptionType(u8 type_code) {
+ switch (static_cast<ExceptionType>(type_code)) {
+ case ExceptionType::PrefetchAbort:
return "Prefetch Abort";
- case DataAbort:
+ case ExceptionType::DataAbort:
return "Data Abort";
- case UndefInstr:
- return "Undefined Instruction";
- case VectorFP:
- return "Vector Floating Point";
+ case ExceptionType::Undefined:
+ return "Undefined Exception";
+ case ExceptionType::VectorFP:
+ return "Vector Floating Point Exception";
default:
- return "unknown";
+ return "Unknown Exception Type";
}
}
+static std::string GetCurrentSystemTime() {
+ auto now = std::chrono::system_clock::now();
+ auto time = std::chrono::system_clock::to_time_t(now);
+
+ std::stringstream time_stream;
+ time_stream << std::put_time(std::localtime(&time), "%Y/%m/%d %H:%M:%S");
+ return time_stream.str();
+}
+
+static void LogGenericInfo(const ErrInfo::ErrInfoCommon& errinfo_common) {
+ LOG_CRITICAL(Service_ERR, "PID: 0x%08X", errinfo_common.pid);
+ LOG_CRITICAL(Service_ERR, "REV: 0x%08X_0x%08X", errinfo_common.rev_high,
+ errinfo_common.rev_low);
+ LOG_CRITICAL(Service_ERR, "TID: 0x%08X_0x%08X", errinfo_common.title_id_high,
+ errinfo_common.title_id_low);
+ LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errinfo_common.app_title_id_high,
+ errinfo_common.app_title_id_low);
+ LOG_CRITICAL(Service_ERR, "ADR: 0x%08X", errinfo_common.pc_address);
+
+ LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errinfo_common.result_code.raw);
+ LOG_CRITICAL(Service_ERR, " Level: %u", errinfo_common.result_code.level.Value());
+ LOG_CRITICAL(Service_ERR, " Summary: %u", errinfo_common.result_code.summary.Value());
+ LOG_CRITICAL(Service_ERR, " Module: %u", errinfo_common.result_code.module.Value());
+ LOG_CRITICAL(Service_ERR, " Desc: %u", errinfo_common.result_code.description.Value());
+}
+
+/* ThrowFatalError function
+ * Inputs:
+ * 0 : Header code [0x00010800]
+ * 1-32 : FatalErrInfo
+ * Outputs:
+ * 0 : Header code
+ * 1 : Result code
+ */
static void ThrowFatalError(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- LOG_CRITICAL(Service_ERR, "Fatal error!");
+ LOG_CRITICAL(Service_ERR, "Fatal error");
const ErrInfo* errinfo = reinterpret_cast<ErrInfo*>(&cmd_buff[1]);
+ LOG_CRITICAL(Service_ERR, "Fatal error type: %s",
+ GetErrType(errinfo->errinfo_common.specifier).c_str());
- switch (errinfo->specifier) {
- case ErrSpecifier0:
- case ErrSpecifier1: {
- const auto& errtype = errinfo->errtype1;
- LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high);
- LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16));
- LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high);
- LOG_CRITICAL(Service_ERR, "ADR: 0x%08X", errtype.address);
-
- LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errtype.result_code.raw);
- LOG_CRITICAL(Service_ERR, " Level: %u", errtype.result_code.level.Value());
- LOG_CRITICAL(Service_ERR, " Summary: %u", errtype.result_code.summary.Value());
- LOG_CRITICAL(Service_ERR, " Module: %u", errtype.result_code.module.Value());
- LOG_CRITICAL(Service_ERR, " Desc: %u", errtype.result_code.description.Value());
+ // Generic Info
+ LogGenericInfo(errinfo->errinfo_common);
+
+ switch (static_cast<FatalErrType>(errinfo->errinfo_common.specifier)) {
+ case FatalErrType::Generic:
+ case FatalErrType::Corrupted:
+ case FatalErrType::CardRemoved:
+ case FatalErrType::Logged: {
+ LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
}
+ case FatalErrType::Exception: {
+ const auto& errtype = errinfo->exception;
+
+ // Register Info
+ LOG_CRITICAL(Service_ERR, "ARM Registers:");
+ for (u32 index = 0; index < errtype.exception_data.exception_context.arm_regs.size();
+ ++index) {
+ if (index < 13) {
+ LOG_DEBUG(Service_ERR, "r%u=0x%08X", index,
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ } else if (index == 13) {
+ LOG_CRITICAL(Service_ERR, "SP=0x%08X",
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ } else if (index == 14) {
+ LOG_CRITICAL(Service_ERR, "LR=0x%08X",
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ } else if (index == 15) {
+ LOG_CRITICAL(Service_ERR, "PC=0x%08X",
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ }
+ }
+ LOG_CRITICAL(Service_ERR, "CPSR=0x%08X", errtype.exception_data.exception_context.cpsr);
- case ErrSpecifier3: {
- const auto& errtype = errinfo->errtype3;
- LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high);
- LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16));
- LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high);
- LOG_CRITICAL(Service_ERR, "TYPE: %s", GetErrInfo3Type(errtype.error_type).c_str());
-
- LOG_CRITICAL(Service_ERR, "PC: 0x%08X", errtype.pc);
- LOG_CRITICAL(Service_ERR, "LR: 0x%08X", errtype.lr);
- LOG_CRITICAL(Service_ERR, "SP: 0x%08X", errtype.sp);
- LOG_CRITICAL(Service_ERR, "CPSR: 0x%08X", errtype.cpsr);
-
- switch (errtype.error_type) {
- case PrefetchAbort:
- case DataAbort:
- LOG_CRITICAL(Service_ERR, "Fault Address: 0x%08X", errtype.fault_addr);
- LOG_CRITICAL(Service_ERR, "Fault Status Register: 0x%08X", errtype.fault_status_reg);
+ // Exception Info
+ LOG_CRITICAL(
+ Service_ERR, "EXCEPTION TYPE: %s",
+ GetExceptionType(errtype.exception_data.exception_info.exception_type).c_str());
+ switch (static_cast<ExceptionType>(errtype.exception_data.exception_info.exception_type)) {
+ case ExceptionType::PrefetchAbort:
+ LOG_CRITICAL(Service_ERR, "IFSR: 0x%08X", errtype.exception_data.exception_info.sr);
+ LOG_CRITICAL(Service_ERR, "r15: 0x%08X", errtype.exception_data.exception_info.ar);
+ case ExceptionType::DataAbort:
+ LOG_CRITICAL(Service_ERR, "DFSR: 0x%08X", errtype.exception_data.exception_info.sr);
+ LOG_CRITICAL(Service_ERR, "DFAR: 0x%08X", errtype.exception_data.exception_info.ar);
break;
- case VectorFP:
- LOG_CRITICAL(Service_ERR, "FPEXC: 0x%08X", errtype.fpexc);
- LOG_CRITICAL(Service_ERR, "FINST: 0x%08X", errtype.finst);
- LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X", errtype.finst2);
+ case ExceptionType::VectorFP:
+ LOG_CRITICAL(Service_ERR, "FPEXC: 0x%08X",
+ errtype.exception_data.exception_info.fpinst);
+ LOG_CRITICAL(Service_ERR, "FINST: 0x%08X",
+ errtype.exception_data.exception_info.fpinst);
+ LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X",
+ errtype.exception_data.exception_info.fpinst2);
break;
}
+ LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
}
- case ErrSpecifier4: {
- const auto& errtype = errinfo->errtype4;
- LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high);
- LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16));
- LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high);
+ case FatalErrType::ResultFailure: {
+ const auto& errtype = errinfo->result_failure;
- LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errtype.result_code.raw);
- LOG_CRITICAL(Service_ERR, " Level: %u", errtype.result_code.level.Value());
- LOG_CRITICAL(Service_ERR, " Summary: %u", errtype.result_code.summary.Value());
- LOG_CRITICAL(Service_ERR, " Module: %u", errtype.result_code.module.Value());
- LOG_CRITICAL(Service_ERR, " Desc: %u", errtype.result_code.description.Value());
-
- LOG_CRITICAL(Service_ERR, "%s", errtype.debug_string1);
- LOG_CRITICAL(Service_ERR, "%s", errtype.debug_string2);
+ // Failure Message
+ LOG_CRITICAL(Service_ERR, "Failure Message: %s", errtype.message);
+ LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
}
- }
- cmd_buff[1] = 0; // No error
+ } // switch FatalErrType
+
+ cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
}
const Interface::FunctionInfo FunctionTable[] = {
+ // clang-format off
{0x00010800, ThrowFatalError, "ThrowFatalError"},
+ {0x00020042, nullptr, "SetUserString"},
+ // clang-format on
};
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 7f9696bfb..4c29784e8 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -15,9 +15,10 @@
#include "common/logging/log.h"
#include "core/file_sys/archive_backend.h"
#include "core/file_sys/archive_extsavedata.h"
+#include "core/file_sys/archive_ncch.h"
#include "core/file_sys/archive_savedata.h"
-#include "core/file_sys/archive_savedatacheck.h"
#include "core/file_sys/archive_sdmc.h"
+#include "core/file_sys/archive_sdmcwriteonly.h"
#include "core/file_sys/archive_systemsavedata.h"
#include "core/file_sys/directory_backend.h"
#include "core/file_sys/file_backend.h"
@@ -338,17 +339,11 @@ ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle,
return ERR_INVALID_ARCHIVE_HANDLE;
if (src_archive == dest_archive) {
- if (src_archive->RenameFile(src_path, dest_path))
- return RESULT_SUCCESS;
+ return src_archive->RenameFile(src_path, dest_path);
} else {
// TODO: Implement renaming across archives
return UnimplementedFunction(ErrorModule::FS);
}
-
- // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
- // exist or similar. Verify.
- return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
- ErrorSummary::NothingHappened, ErrorLevel::Status);
}
ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) {
@@ -356,10 +351,7 @@ ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
if (archive == nullptr)
return ERR_INVALID_ARCHIVE_HANDLE;
- if (archive->DeleteDirectory(path))
- return RESULT_SUCCESS;
- return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
- ErrorSummary::Canceled, ErrorLevel::Status);
+ return archive->DeleteDirectory(path);
}
ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
@@ -368,10 +360,7 @@ ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle,
if (archive == nullptr)
return ERR_INVALID_ARCHIVE_HANDLE;
- if (archive->DeleteDirectoryRecursively(path))
- return RESULT_SUCCESS;
- return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
- ErrorSummary::Canceled, ErrorLevel::Status);
+ return archive->DeleteDirectoryRecursively(path);
}
ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path,
@@ -388,10 +377,7 @@ ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSy
if (archive == nullptr)
return ERR_INVALID_ARCHIVE_HANDLE;
- if (archive->CreateDirectory(path))
- return RESULT_SUCCESS;
- return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
- ErrorSummary::Canceled, ErrorLevel::Status);
+ return archive->CreateDirectory(path);
}
ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
@@ -404,17 +390,11 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle,
return ERR_INVALID_ARCHIVE_HANDLE;
if (src_archive == dest_archive) {
- if (src_archive->RenameDirectory(src_path, dest_path))
- return RESULT_SUCCESS;
+ return src_archive->RenameDirectory(src_path, dest_path);
} else {
// TODO: Implement renaming across archives
return UnimplementedFunction(ErrorModule::FS);
}
-
- // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't
- // exist or similar. Verify.
- return ResultCode(ErrorDescription::NoData, ErrorModule::FS, // TODO: verify description
- ErrorSummary::NothingHappened, ErrorLevel::Status);
}
ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle,
@@ -423,13 +403,11 @@ ResultVal<Kernel::SharedPtr<Directory>> OpenDirectoryFromArchive(ArchiveHandle a
if (archive == nullptr)
return ERR_INVALID_ARCHIVE_HANDLE;
- std::unique_ptr<FileSys::DirectoryBackend> backend = archive->OpenDirectory(path);
- if (backend == nullptr) {
- return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, ErrorSummary::NotFound,
- ErrorLevel::Permanent);
- }
+ auto backend = archive->OpenDirectory(path);
+ if (backend.Failed())
+ return backend.Code();
- auto directory = Kernel::SharedPtr<Directory>(new Directory(std::move(backend), path));
+ auto directory = Kernel::SharedPtr<Directory>(new Directory(backend.MoveFrom(), path));
return MakeResult<Kernel::SharedPtr<Directory>>(std::move(directory));
}
@@ -549,6 +527,13 @@ void RegisterArchiveTypes() {
LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s",
sdmc_directory.c_str());
+ auto sdmcwo_factory = std::make_unique<FileSys::ArchiveFactory_SDMCWriteOnly>(sdmc_directory);
+ if (sdmcwo_factory->Initialize())
+ RegisterArchiveType(std::move(sdmcwo_factory), ArchiveIdCode::SDMCWriteOnly);
+ else
+ LOG_ERROR(Service_FS, "Can't instantiate SDMCWriteOnly archive with path %s",
+ sdmc_directory.c_str());
+
// Create the SaveData archive
auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sdmc_directory);
RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData);
@@ -569,10 +554,9 @@ void RegisterArchiveTypes() {
LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s",
sharedextsavedata_factory->GetMountPoint().c_str());
- // Create the SaveDataCheck archive, basically a small variation of the RomFS archive
- auto savedatacheck_factory =
- std::make_unique<FileSys::ArchiveFactory_SaveDataCheck>(nand_directory);
- RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::SaveDataCheck);
+ // Create the NCCH archive, basically a small variation of the RomFS archive
+ auto savedatacheck_factory = std::make_unique<FileSys::ArchiveFactory_NCCH>(nand_directory);
+ RegisterArchiveType(std::move(savedatacheck_factory), ArchiveIdCode::NCCH);
auto systemsavedata_factory =
std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory);
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 41a76285c..21ed9717b 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -33,7 +33,7 @@ enum class ArchiveIdCode : u32 {
SystemSaveData = 0x00000008,
SDMC = 0x00000009,
SDMCWriteOnly = 0x0000000A,
- SaveDataCheck = 0x2345678A,
+ NCCH = 0x2345678A,
};
/// Media types for the archives
diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp
index edd1ea97b..563341504 100644
--- a/src/core/hle/service/mic_u.cpp
+++ b/src/core/hle/service/mic_u.cpp
@@ -2,6 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/mic_u.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -9,23 +12,298 @@
namespace MIC_U {
+enum class Encoding : u8 {
+ PCM8 = 0,
+ PCM16 = 1,
+ PCM8Signed = 2,
+ PCM16Signed = 3,
+};
+
+enum class SampleRate : u8 {
+ SampleRate32730 = 0,
+ SampleRate16360 = 1,
+ SampleRate10910 = 2,
+ SampleRate8180 = 3
+};
+
+static Kernel::SharedPtr<Kernel::Event> buffer_full_event;
+static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
+static u8 mic_gain = 0;
+static bool mic_power = false;
+static bool is_sampling = false;
+static bool allow_shell_closed;
+static bool clamp = false;
+static Encoding encoding;
+static SampleRate sample_rate;
+static s32 audio_buffer_offset;
+static u32 audio_buffer_size;
+static bool audio_buffer_loop;
+
+/**
+ * MIC::MapSharedMem service function
+ * Inputs:
+ * 0 : Header Code[0x00010042]
+ * 1 : Shared-mem size
+ * 2 : CopyHandleDesc
+ * 3 : Shared-mem handle
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void MapSharedMem(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ u32 size = cmd_buff[1];
+ Handle mem_handle = cmd_buff[3];
+ shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(mem_handle);
+ if (shared_memory) {
+ shared_memory->name = "MIC_U:shared_memory";
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "called, size=0x%X, mem_handle=0x%08X", size, mem_handle);
+}
+
+/**
+ * MIC::UnmapSharedMem service function
+ * Inputs:
+ * 0 : Header Code[0x00020000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void UnmapSharedMem(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "called");
+}
+
+/**
+ * MIC::StartSampling service function
+ * Inputs:
+ * 0 : Header Code[0x00030140]
+ * 1 : Encoding
+ * 2 : SampleRate
+ * 3 : Base offset for audio data in sharedmem
+ * 4 : Size of the audio data in sharedmem
+ * 5 : Loop at end of buffer
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void StartSampling(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ encoding = static_cast<Encoding>(cmd_buff[1] & 0xFF);
+ sample_rate = static_cast<SampleRate>(cmd_buff[2] & 0xFF);
+ audio_buffer_offset = cmd_buff[3];
+ audio_buffer_size = cmd_buff[4];
+ audio_buffer_loop = static_cast<bool>(cmd_buff[5] & 0xFF);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ is_sampling = true;
+ LOG_WARNING(Service_MIC, "(STUBBED) called, encoding=%u, sample_rate=%u, "
+ "audio_buffer_offset=%d, audio_buffer_size=%u, audio_buffer_loop=%u",
+ encoding, sample_rate, audio_buffer_offset, audio_buffer_size, audio_buffer_loop);
+}
+
+/**
+ * MIC::AdjustSampling service function
+ * Inputs:
+ * 0 : Header Code[0x00040040]
+ * 1 : SampleRate
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void AdjustSampling(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ sample_rate = static_cast<SampleRate>(cmd_buff[1] & 0xFF);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", sample_rate);
+}
+
+/**
+ * MIC::StopSampling service function
+ * Inputs:
+ * 0 : Header Code[0x00050000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void StopSampling(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ is_sampling = false;
+ LOG_WARNING(Service_MIC, "(STUBBED) called");
+}
+
+/**
+ * MIC::IsSampling service function
+ * Inputs:
+ * 0 : Header Code[0x00060000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : 0 = sampling, non-zero = sampling
+ */
+static void IsSampling(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = is_sampling;
+ LOG_WARNING(Service_MIC, "(STUBBED) called");
+}
+
+/**
+ * MIC::GetBufferFullEvent service function
+ * Inputs:
+ * 0 : Header Code[0x00070000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 3 : Event handle
+ */
+static void GetBufferFullEvent(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[3] = Kernel::g_handle_table.Create(buffer_full_event).MoveFrom();
+ LOG_WARNING(Service_MIC, "(STUBBED) called");
+}
+
+/**
+ * MIC::SetGain service function
+ * Inputs:
+ * 0 : Header Code[0x00080040]
+ * 1 : Gain
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SetGain(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ mic_gain = cmd_buff[1] & 0xFF;
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "(STUBBED) called, mic_gain=%u", mic_gain);
+}
+
+/**
+ * MIC::GetGain service function
+ * Inputs:
+ * 0 : Header Code[0x00090000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Gain
+ */
+static void GetGain(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = mic_gain;
+ LOG_WARNING(Service_MIC, "(STUBBED) called");
+}
+
+/**
+ * MIC::SetPower service function
+ * Inputs:
+ * 0 : Header Code[0x000A0040]
+ * 1 : Power (0 = off, 1 = on)
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SetPower(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ mic_power = static_cast<bool>(cmd_buff[1] & 0xFF);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "(STUBBED) called, mic_power=%u", mic_power);
+}
+
+/**
+ * MIC::GetPower service function
+ * Inputs:
+ * 0 : Header Code[0x000B0000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Power
+ */
+static void GetPower(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = mic_power;
+ LOG_WARNING(Service_MIC, "(STUBBED) called");
+}
+
+/**
+ * MIC::SetIirFilterMic service function
+ * Inputs:
+ * 0 : Header Code[0x000C0042]
+ * 1 : Size
+ * 2 : (Size << 4) | 0xA
+ * 3 : Pointer to IIR Filter Data
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SetIirFilterMic(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ u32 size = cmd_buff[1];
+ VAddr buffer = cmd_buff[3];
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "(STUBBED) called, size=0x%X, buffer=0x%08X", size, buffer);
+}
+
+/**
+ * MIC::SetClamp service function
+ * Inputs:
+ * 0 : Header Code[0x000D0040]
+ * 1 : Clamp (0 = don't clamp, non-zero = clamp)
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SetClamp(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ clamp = static_cast<bool>(cmd_buff[1] & 0xFF);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "(STUBBED) called, clamp=%u", clamp);
+}
+
+/**
+ * MIC::GetClamp service function
+ * Inputs:
+ * 0 : Header Code[0x000E0000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Clamp (0 = don't clamp, non-zero = clamp)
+ */
+static void GetClamp(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = clamp;
+ LOG_WARNING(Service_MIC, "(STUBBED) called");
+}
+
+/**
+ * MIC::SetAllowShellClosed service function
+ * Inputs:
+ * 0 : Header Code[0x000D0040]
+ * 1 : Sampling allowed while shell closed (0 = disallow, non-zero = allow)
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SetAllowShellClosed(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ allow_shell_closed = static_cast<bool>(cmd_buff[1] & 0xFF);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_MIC, "(STUBBED) called, allow_shell_closed=%u", allow_shell_closed);
+}
+
const Interface::FunctionInfo FunctionTable[] = {
- {0x00010042, nullptr, "MapSharedMem"},
- {0x00020000, nullptr, "UnmapSharedMem"},
- {0x00030140, nullptr, "Initialize"},
- {0x00040040, nullptr, "AdjustSampling"},
- {0x00050000, nullptr, "StopSampling"},
- {0x00060000, nullptr, "IsSampling"},
- {0x00070000, nullptr, "GetEventHandle"},
- {0x00080040, nullptr, "SetGain"},
- {0x00090000, nullptr, "GetGain"},
- {0x000A0040, nullptr, "SetPower"},
- {0x000B0000, nullptr, "GetPower"},
- {0x000C0042, nullptr, "size"},
- {0x000D0040, nullptr, "SetClamp"},
- {0x000E0000, nullptr, "GetClamp"},
- {0x000F0040, nullptr, "SetAllowShellClosed"},
- {0x00100040, nullptr, "unknown_input2"},
+ {0x00010042, MapSharedMem, "MapSharedMem"},
+ {0x00020000, UnmapSharedMem, "UnmapSharedMem"},
+ {0x00030140, StartSampling, "StartSampling"},
+ {0x00040040, AdjustSampling, "AdjustSampling"},
+ {0x00050000, StopSampling, "StopSampling"},
+ {0x00060000, IsSampling, "IsSampling"},
+ {0x00070000, GetBufferFullEvent, "GetBufferFullEvent"},
+ {0x00080040, SetGain, "SetGain"},
+ {0x00090000, GetGain, "GetGain"},
+ {0x000A0040, SetPower, "SetPower"},
+ {0x000B0000, GetPower, "GetPower"},
+ {0x000C0042, SetIirFilterMic, "SetIirFilterMic"},
+ {0x000D0040, SetClamp, "SetClamp"},
+ {0x000E0000, GetClamp, "GetClamp"},
+ {0x000F0040, SetAllowShellClosed, "SetAllowShellClosed"},
+ {0x00100040, nullptr, "SetClientSDKVersion"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -33,6 +311,18 @@ const Interface::FunctionInfo FunctionTable[] = {
Interface::Interface() {
Register(FunctionTable);
+ shared_memory = nullptr;
+ buffer_full_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "MIC_U::buffer_full_event");
+ mic_gain = 0;
+ mic_power = false;
+ is_sampling = false;
+ clamp = false;
+}
+
+Interface::~Interface() {
+ shared_memory = nullptr;
+ buffer_full_event = nullptr;
}
} // namespace
diff --git a/src/core/hle/service/mic_u.h b/src/core/hle/service/mic_u.h
index dc795d14c..1cff7390e 100644
--- a/src/core/hle/service/mic_u.h
+++ b/src/core/hle/service/mic_u.h
@@ -16,6 +16,7 @@ namespace MIC_U {
class Interface : public Service::Interface {
public:
Interface();
+ ~Interface();
std::string GetPortName() const override {
return "mic:u";
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 6e6b63329..cc859c14c 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -128,7 +128,7 @@ void Init() {
Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SharedExtSaveData, archive_path);
ASSERT_MSG(archive_result.Succeeded(), "Could not open the PTM SharedExtSaveData archive!");
- FileSys::Path gamecoin_path("gamecoin.dat");
+ FileSys::Path gamecoin_path("/gamecoin.dat");
FileSys::Mode open_mode = {};
open_mode.write_flag.Assign(1);
open_mode.create_flag.Assign(1);
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 28cb97d8e..45dedea68 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -29,16 +29,12 @@ namespace GPU {
Regs g_regs;
-/// True if the current frame was skipped
-bool g_skip_frame;
/// 268MHz CPU clocks / 60Hz frames per second
const u64 frame_ticks = 268123480ull / 60;
/// Event id for CoreTiming
static int vblank_event;
/// Total number of frames drawn
static u64 frame_count;
-/// True if the last frame was skipped
-static bool last_skip_frame;
template <typename T>
inline void Read(T& var, const u32 raw_addr) {
@@ -519,20 +515,7 @@ template void Write<u8>(u32 addr, const u8 data);
/// Update hardware
static void VBlankCallback(u64 userdata, int cycles_late) {
frame_count++;
- last_skip_frame = g_skip_frame;
- g_skip_frame = (frame_count & Settings::values.frame_skip) != 0;
-
- // Swap buffers based on the frameskip mode, which is a little bit tricky. When
- // a frame is being skipped, nothing is being rendered to the internal framebuffer(s).
- // So, we should only swap frames if the last frame was rendered. The rules are:
- // - If frameskip == 0 (disabled), always swap buffers
- // - If frameskip == 1, swap buffers every other frame (starting from the first frame)
- // - If frameskip > 1, swap buffers every frameskip^n frames (starting from the second frame)
- if ((((Settings::values.frame_skip != 1) ^ last_skip_frame) &&
- last_skip_frame != g_skip_frame) ||
- Settings::values.frame_skip == 0) {
- VideoCore::g_renderer->SwapBuffers();
- }
+ VideoCore::g_renderer->SwapBuffers();
// Signal to GSP that GPU interrupt has occurred
// TODO(yuriks): hwtest to determine if PDC0 is for the Top screen and PDC1 for the Sub
@@ -579,8 +562,6 @@ void Init() {
framebuffer_sub.color_format.Assign(Regs::PixelFormat::RGB8);
framebuffer_sub.active_fb = 0;
- last_skip_frame = false;
- g_skip_frame = false;
frame_count = 0;
vblank_event = CoreTiming::RegisterEvent("GPU::VBlankCallback", VBlankCallback);
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index 32ddc5697..d53381216 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -316,7 +316,6 @@ ASSERT_REG_POSITION(command_processor_config, 0x00638);
static_assert(sizeof(Regs) == 0x1000 * sizeof(u32), "Invalid total size of register set");
extern Regs g_regs;
-extern bool g_skip_frame;
template <typename T>
void Read(T& var, const u32 addr);
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 64c388374..65e4bba85 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -45,13 +45,11 @@ struct SpecialRegion {
* requires an indexed fetch and a check for NULL.
*/
struct PageTable {
- static const size_t NUM_ENTRIES = 1 << (32 - PAGE_BITS);
-
/**
* Array of memory pointers backing each page. An entry can only be non-null if the
* corresponding entry in the `attributes` array is of type `Memory`.
*/
- std::array<u8*, NUM_ENTRIES> pointers;
+ std::array<u8*, PAGE_TABLE_NUM_ENTRIES> pointers;
/**
* Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of
@@ -63,13 +61,13 @@ struct PageTable {
* Array of fine grained page attributes. If it is set to any value other than `Memory`, then
* the corresponding entry in `pointers` MUST be set to null.
*/
- std::array<PageType, NUM_ENTRIES> attributes;
+ std::array<PageType, PAGE_TABLE_NUM_ENTRIES> attributes;
/**
* Indicates the number of externally cached resources touching a page that should be
* flushed before the memory is accessed
*/
- std::array<u8, NUM_ENTRIES> cached_res_count;
+ std::array<u8, PAGE_TABLE_NUM_ENTRIES> cached_res_count;
};
/// Singular page table used for the singleton process
@@ -77,6 +75,10 @@ static PageTable main_page_table;
/// Currently active page table
static PageTable* current_page_table = &main_page_table;
+std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers() {
+ return &current_page_table->pointers;
+}
+
static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
@@ -84,7 +86,7 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
u32 end = base + size;
while (base != end) {
- ASSERT_MSG(base < PageTable::NUM_ENTRIES, "out of range mapping at %08X", base);
+ ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base);
// Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
// null here
diff --git a/src/core/memory.h b/src/core/memory.h
index 8fd3080ff..903b58a22 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include <cstddef>
#include <string>
#include "common/common_types.h"
@@ -17,6 +18,7 @@ namespace Memory {
const u32 PAGE_SIZE = 0x1000;
const u32 PAGE_MASK = PAGE_SIZE - 1;
const int PAGE_BITS = 12;
+const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS);
/// Physical memory regions as seen from the ARM11
enum : PAddr {
@@ -166,4 +168,11 @@ void RasterizerFlushRegion(PAddr start, u32 size);
* Flushes and invalidates any externally cached rasterizer resources touching the given region.
*/
void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size);
+
+/**
+ * Dynarmic has an optimization to memory accesses when the pointer to the page exists that
+ * can be used by setting up the current page table as a callback. This function is used to
+ * retrieve the current page table for that purpose.
+ */
+std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers();
}
diff --git a/src/core/settings.h b/src/core/settings.h
index e931953d7..7470fdbeb 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -78,7 +78,6 @@ struct Values {
// Core
bool use_cpu_jit;
- int frame_skip;
// Data Storage
bool use_virtual_sd;
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 457c55571..89237e477 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,5 +1,7 @@
set(SRCS
+ glad.cpp
tests.cpp
+ core/file_sys/path_parser.cpp
)
set(HEADERS
diff --git a/src/tests/core/file_sys/path_parser.cpp b/src/tests/core/file_sys/path_parser.cpp
new file mode 100644
index 000000000..2b543e438
--- /dev/null
+++ b/src/tests/core/file_sys/path_parser.cpp
@@ -0,0 +1,38 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <catch.hpp>
+#include "common/file_util.h"
+#include "core/file_sys/path_parser.h"
+
+namespace FileSys {
+
+TEST_CASE("PathParser", "[core][file_sys]") {
+ REQUIRE(!PathParser(Path(std::vector<u8>{})).IsValid());
+ REQUIRE(!PathParser(Path("a")).IsValid());
+ REQUIRE(!PathParser(Path("/|")).IsValid());
+ REQUIRE(PathParser(Path("/a")).IsValid());
+ REQUIRE(!PathParser(Path("/a/b/../../c/../../d")).IsValid());
+ REQUIRE(PathParser(Path("/a/b/../c/../../d")).IsValid());
+ REQUIRE(PathParser(Path("/")).IsRootDirectory());
+ REQUIRE(!PathParser(Path("/a")).IsRootDirectory());
+ REQUIRE(PathParser(Path("/a/..")).IsRootDirectory());
+}
+
+TEST_CASE("PathParser - Host file system", "[core][file_sys]") {
+ std::string test_dir = "./test";
+ FileUtil::CreateDir(test_dir);
+ FileUtil::CreateDir(test_dir + "/z");
+ FileUtil::CreateEmptyFile(test_dir + "/a");
+
+ REQUIRE(PathParser(Path("/a")).GetHostStatus(test_dir) == PathParser::FileFound);
+ REQUIRE(PathParser(Path("/b")).GetHostStatus(test_dir) == PathParser::NotFound);
+ REQUIRE(PathParser(Path("/z")).GetHostStatus(test_dir) == PathParser::DirectoryFound);
+ REQUIRE(PathParser(Path("/a/c")).GetHostStatus(test_dir) == PathParser::FileInPath);
+ REQUIRE(PathParser(Path("/b/c")).GetHostStatus(test_dir) == PathParser::PathNotFound);
+
+ FileUtil::DeleteDirRecursively(test_dir);
+}
+
+} // namespace FileSys
diff --git a/src/tests/glad.cpp b/src/tests/glad.cpp
new file mode 100644
index 000000000..b0b016440
--- /dev/null
+++ b/src/tests/glad.cpp
@@ -0,0 +1,14 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <catch.hpp>
+#include <glad/glad.h>
+
+// This is not an actual test, but a work-around for issue #2183.
+// If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS
+// will error about undefined references from video_core to glad. So we explicitly use a glad
+// function here to shut up the linker.
+TEST_CASE("glad fake test", "[dummy]") {
+ REQUIRE(&gladLoadGL != nullptr);
+}
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index fda91e29c..c80c96762 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -52,10 +52,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
if (id >= regs.NumIds())
return;
- // If we're skipping this frame, only allow trigger IRQ
- if (GPU::g_skip_frame && id != PICA_REG_INDEX(trigger_irq))
- return;
-
// TODO: Figure out how register masking acts on e.g. vs.uniform_setup.set_value
u32 old_value = regs[id];
@@ -215,18 +211,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
PrimitiveAssembler<Shader::OutputVertex>& primitive_assembler = g_state.primitive_assembler;
- if (g_debug_context) {
+ if (g_debug_context && g_debug_context->recorder) {
for (int i = 0; i < 3; ++i) {
const auto texture = regs.GetTextures()[i];
if (!texture.enabled)
continue;
u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress());
- if (g_debug_context && Pica::g_debug_context->recorder)
- g_debug_context->recorder->MemoryAccessed(
- texture_data, Pica::Regs::NibblesPerPixel(texture.format) *
- texture.config.width / 2 * texture.config.height,
- texture.config.GetPhysicalAddress());
+ g_debug_context->recorder->MemoryAccessed(
+ texture_data, Pica::Regs::NibblesPerPixel(texture.format) *
+ texture.config.width / 2 * texture.config.height,
+ texture.config.GetPhysicalAddress());
}
}
@@ -236,7 +231,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// The size has been tuned for optimal balance between hit-rate and the cost of lookup
const size_t VERTEX_CACHE_SIZE = 32;
std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids;
- std::array<Shader::OutputRegisters, VERTEX_CACHE_SIZE> vertex_cache;
+ std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache;
+ Shader::OutputVertex output_vertex;
unsigned int vertex_cache_pos = 0;
vertex_cache_ids.fill(-1);
@@ -266,7 +262,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) {
if (vertex == vertex_cache_ids[i]) {
- output_registers = vertex_cache[i];
+ output_vertex = vertex_cache[i];
vertex_cache_hit = true;
break;
}
@@ -285,16 +281,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
output_registers = shader_unit.output_registers;
+ // Retrieve vertex from register data
+ output_vertex = output_registers.ToVertex(regs.vs);
+
if (is_indexed) {
- vertex_cache[vertex_cache_pos] = output_registers;
+ vertex_cache[vertex_cache_pos] = output_vertex;
vertex_cache_ids[vertex_cache_pos] = vertex;
vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE;
}
}
- // Retrieve vertex from register data
- Shader::OutputVertex output_vertex = output_registers.ToVertex(regs.vs);
-
// Send to renderer
using Pica::Shader::OutputVertex;
auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index b2db609ec..99bd59a69 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -40,7 +40,7 @@ namespace Pica {
// field offset. Otherwise, the compiler will fail to compile this code.
#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \
((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name), \
- size_t>::type)PICA_REG_INDEX(field_name))
+ size_t>::type) PICA_REG_INDEX(field_name))
#endif // _MSC_VER
struct Regs {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 7cc3b407a..d4d5903ce 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -893,7 +893,7 @@ bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config)
value_float = config.value_32bit / 16777215.0f; // 2^24 - 1
}
- cur_state.depth.write_mask = true;
+ cur_state.depth.write_mask = GL_TRUE;
cur_state.Apply();
glClearBufferfv(GL_DEPTH, 0, &value_float);
} else if (dst_type == SurfaceType::DepthStencil) {
@@ -908,8 +908,8 @@ bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config)
GLfloat value_float = (config.value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1
GLint value_int = (config.value_32bit >> 24);
- cur_state.depth.write_mask = true;
- cur_state.stencil.write_mask = true;
+ cur_state.depth.write_mask = GL_TRUE;
+ cur_state.stencil.write_mask = 0xFF;
cur_state.Apply();
glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int);
}
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index ed84cadea..2a731f483 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -27,8 +27,8 @@ OpenGLState::OpenGLState() {
stencil.test_enabled = false;
stencil.test_func = GL_ALWAYS;
stencil.test_ref = 0;
- stencil.test_mask = -1;
- stencil.write_mask = -1;
+ stencil.test_mask = 0xFF;
+ stencil.write_mask = 0xFF;
stencil.action_depth_fail = GL_KEEP;
stencil.action_depth_pass = GL_KEEP;
stencil.action_stencil_fail = GL_KEEP;