diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 42e1a29c1..a86889756 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -40,6 +40,7 @@ #define MAPS_DIR "maps" #define CACHE_DIR "cache" #define SDMC_DIR "sdmc" +#define SAVEDATA_DIR "savedata" #define SYSDATA_DIR "sysdata" #define SHADERCACHE_DIR "shader_cache" #define STATESAVES_DIR "state_saves" diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 88c46c117..42cdf3262 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -676,6 +676,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new paths[D_MAPS_IDX] = paths[D_USER_IDX] + MAPS_DIR DIR_SEP; paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; + paths[D_SAVEDATA_IDX] = paths[D_USER_IDX] + SAVEDATA_DIR DIR_SEP; paths[D_SYSDATA_IDX] = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP; paths[D_SHADERCACHE_IDX] = paths[D_USER_IDX] + SHADERCACHE_DIR DIR_SEP; paths[D_SHADERS_IDX] = paths[D_USER_IDX] + SHADERS_DIR DIR_SEP; @@ -718,6 +719,7 @@ const std::string& GetUserPath(const unsigned int DirIDX, const std::string &new paths[D_MAPS_IDX] = paths[D_USER_IDX] + MAPS_DIR DIR_SEP; paths[D_CACHE_IDX] = paths[D_USER_IDX] + CACHE_DIR DIR_SEP; paths[D_SDMC_IDX] = paths[D_USER_IDX] + SDMC_DIR DIR_SEP; + paths[D_SAVEDATA_IDX] = paths[D_USER_IDX] + SAVEDATA_DIR DIR_SEP; paths[D_SHADERCACHE_IDX] = paths[D_USER_IDX] + SHADERCACHE_DIR DIR_SEP; paths[D_SHADERS_IDX] = paths[D_USER_IDX] + SHADERS_DIR DIR_SEP; paths[D_STATESAVES_IDX] = paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP; diff --git a/src/common/file_util.h b/src/common/file_util.h index a9d48cfe8..e691b6139 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -27,6 +27,7 @@ enum { D_STATESAVES_IDX, D_SCREENSHOTS_IDX, D_SDMC_IDX, + D_SAVEDATA_IDX, D_SYSDATA_IDX, D_HIRESTEXTURES_IDX, D_DUMP_IDX, diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 198e4afd3..f71232c1a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -18,11 +18,11 @@ set(SRCS arm/skyeye_common/vfp/vfpinstr.cpp arm/skyeye_common/vfp/vfpsingle.cpp file_sys/archive_romfs.cpp + file_sys/archive_savedata.cpp file_sys/archive_sdmc.cpp + file_sys/disk_archive.cpp file_sys/file_romfs.cpp - file_sys/file_sdmc.cpp file_sys/directory_romfs.cpp - file_sys/directory_sdmc.cpp hle/kernel/address_arbiter.cpp hle/kernel/event.cpp hle/kernel/kernel.cpp @@ -99,13 +99,13 @@ set(HEADERS arm/arm_interface.h file_sys/archive_backend.h file_sys/archive_romfs.h + file_sys/archive_savedata.h file_sys/archive_sdmc.h + file_sys/disk_archive.h file_sys/file_backend.h file_sys/file_romfs.h - file_sys/file_sdmc.h file_sys/directory_backend.h file_sys/directory_romfs.h - file_sys/directory_sdmc.h hle/kernel/address_arbiter.h hle/kernel/event.h hle/kernel/kernel.h diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp new file mode 100644 index 000000000..2414564e4 --- /dev/null +++ b/src/core/file_sys/archive_savedata.cpp @@ -0,0 +1,33 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include + +#include "common/common_types.h" +#include "common/file_util.h" + +#include "core/file_sys/archive_savedata.h" +#include "core/file_sys/disk_archive.h" +#include "core/settings.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +Archive_SaveData::Archive_SaveData(const std::string& mount_point, u64 program_id) + : DiskArchive(mount_point + Common::StringFromFormat("%016X", program_id) + DIR_SEP) { + LOG_INFO(Service_FS, "Directory %s set as SaveData.", this->mount_point.c_str()); +} + +bool Archive_SaveData::Initialize() { + if (!FileUtil::CreateFullPath(mount_point)) { + LOG_ERROR(Service_FS, "Unable to create SaveData path."); + return false; + } + + return true; +} + +} // namespace FileSys diff --git a/src/core/file_sys/archive_savedata.h b/src/core/file_sys/archive_savedata.h new file mode 100644 index 000000000..b3e561130 --- /dev/null +++ b/src/core/file_sys/archive_savedata.h @@ -0,0 +1,32 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/file_sys/disk_archive.h" +#include "core/loader/loader.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +/// File system interface to the SaveData archive +class Archive_SaveData final : public DiskArchive { +public: + Archive_SaveData(const std::string& mount_point, u64 program_id); + + /** + * Initialize the archive. + * @return CreateSaveDataResult AlreadyExists if the SaveData folder already exists, + * Success if it was created properly and Failure if there was any error + */ + bool Initialize(); + + std::string GetName() const override { return "SaveData"; } +}; + +} // namespace FileSys diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 9d58668e0..dccdf7f67 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -8,8 +8,7 @@ #include "common/file_util.h" #include "core/file_sys/archive_sdmc.h" -#include "core/file_sys/directory_sdmc.h" -#include "core/file_sys/file_sdmc.h" +#include "core/file_sys/disk_archive.h" #include "core/settings.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -17,18 +16,10 @@ namespace FileSys { -Archive_SDMC::Archive_SDMC(const std::string& mount_point) { - this->mount_point = mount_point; +Archive_SDMC::Archive_SDMC(const std::string& mount_point) : DiskArchive(mount_point) { LOG_INFO(Service_FS, "Directory %s set as SDMC.", mount_point.c_str()); } -Archive_SDMC::~Archive_SDMC() { -} - -/** - * Initialize the archive. - * @return true if it initialized successfully - */ bool Archive_SDMC::Initialize() { if (!Settings::values.use_virtual_sd) { LOG_WARNING(Service_FS, "SDMC disabled by config."); @@ -43,74 +34,4 @@ bool Archive_SDMC::Initialize() { return true; } -/** - * Open a file specified by its path, using the specified mode - * @param path Path relative to the archive - * @param mode Mode to open the file with - * @return Opened file, or nullptr - */ -std::unique_ptr Archive_SDMC::OpenFile(const Path& path, const Mode mode) const { - LOG_DEBUG(Service_FS, "called path=%s mode=%u", path.DebugStr().c_str(), mode.hex); - File_SDMC* file = new File_SDMC(this, path, mode); - if (!file->Open()) - return nullptr; - return std::unique_ptr(file); -} - -/** - * Delete a file specified by its path - * @param path Path relative to the archive - * @return Whether the file could be deleted - */ -bool Archive_SDMC::DeleteFile(const FileSys::Path& path) const { - return FileUtil::Delete(GetMountPoint() + path.AsString()); -} - -bool Archive_SDMC::RenameFile(const FileSys::Path& src_path, const FileSys::Path& dest_path) const { - return FileUtil::Rename(GetMountPoint() + src_path.AsString(), GetMountPoint() + dest_path.AsString()); -} - -/** - * Delete a directory specified by its path - * @param path Path relative to the archive - * @return Whether the directory could be deleted - */ -bool Archive_SDMC::DeleteDirectory(const FileSys::Path& path) const { - return FileUtil::DeleteDir(GetMountPoint() + path.AsString()); -} - -/** - * Create a directory specified by its path - * @param path Path relative to the archive - * @return Whether the directory could be created - */ -bool Archive_SDMC::CreateDirectory(const Path& path) const { - return FileUtil::CreateDir(GetMountPoint() + path.AsString()); -} - -bool Archive_SDMC::RenameDirectory(const FileSys::Path& src_path, const FileSys::Path& dest_path) const { - return FileUtil::Rename(GetMountPoint() + src_path.AsString(), GetMountPoint() + dest_path.AsString()); -} - -/** - * Open a directory specified by its path - * @param path Path relative to the archive - * @return Opened directory, or nullptr - */ -std::unique_ptr Archive_SDMC::OpenDirectory(const Path& path) const { - LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); - Directory_SDMC* directory = new Directory_SDMC(this, path); - if (!directory->Open()) - return nullptr; - return std::unique_ptr(directory); -} - -/** - * Getter for the path used for this Archive - * @return Mount point of that passthrough archive - */ -std::string Archive_SDMC::GetMountPoint() const { - return mount_point; -} - } // namespace FileSys diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h index 059045245..c84c6948e 100644 --- a/src/core/file_sys/archive_sdmc.h +++ b/src/core/file_sys/archive_sdmc.h @@ -6,7 +6,7 @@ #include "common/common_types.h" -#include "core/file_sys/archive_backend.h" +#include "core/file_sys/disk_archive.h" #include "core/loader/loader.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -15,10 +15,9 @@ namespace FileSys { /// File system interface to the SDMC archive -class Archive_SDMC final : public ArchiveBackend { +class Archive_SDMC final : public DiskArchive { public: Archive_SDMC(const std::string& mount_point); - ~Archive_SDMC() override; /** * Initialize the archive. @@ -27,67 +26,6 @@ public: bool Initialize(); std::string GetName() const override { return "SDMC"; } - - /** - * Open a file specified by its path, using the specified mode - * @param path Path relative to the archive - * @param mode Mode to open the file with - * @return Opened file, or nullptr - */ - std::unique_ptr OpenFile(const Path& path, const Mode mode) const override; - - /** - * Delete a file specified by its path - * @param path Path relative to the archive - * @return Whether the file could be deleted - */ - bool DeleteFile(const FileSys::Path& path) const override; - - /** - * 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 - */ - bool RenameFile(const FileSys::Path& src_path, const FileSys::Path& dest_path) const override; - - /** - * Delete a directory specified by its path - * @param path Path relative to the archive - * @return Whether the directory could be deleted - */ - bool DeleteDirectory(const FileSys::Path& path) const override; - - /** - * Create a directory specified by its path - * @param path Path relative to the archive - * @return Whether the directory could be created - */ - bool CreateDirectory(const Path& path) const override; - - /** - * 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 - */ - bool RenameDirectory(const FileSys::Path& src_path, const FileSys::Path& dest_path) const override; - - /** - * Open a directory specified by its path - * @param path Path relative to the archive - * @return Opened directory, or nullptr - */ - std::unique_ptr OpenDirectory(const Path& path) const override; - - /** - * Getter for the path used for this Archive - * @return Mount point of that passthrough archive - */ - std::string GetMountPoint() const; - -private: - std::string mount_point; }; } // namespace FileSys diff --git a/src/core/file_sys/directory_sdmc.cpp b/src/core/file_sys/directory_sdmc.cpp deleted file mode 100644 index 519787641..000000000 --- a/src/core/file_sys/directory_sdmc.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#include - -#include "common/common_types.h" -#include "common/file_util.h" - -#include "core/file_sys/directory_sdmc.h" -#include "core/file_sys/archive_sdmc.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -Directory_SDMC::Directory_SDMC(const Archive_SDMC* archive, const Path& path) { - // 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->GetMountPoint() + path.AsString(); - -} - -Directory_SDMC::~Directory_SDMC() { - Close(); -} - -bool Directory_SDMC::Open() { - if (!FileUtil::IsDirectory(path)) - return false; - FileUtil::ScanDirectoryTree(path, directory); - children_iterator = directory.children.begin(); - return true; -} - -/** - * List files contained in the directory - * @param count Number of entries to return at once in entries - * @param entries Buffer to read data into - * @return Number of entries listed - */ -u32 Directory_SDMC::Read(const u32 count, Entry* entries) { - u32 entries_read = 0; - - while (entries_read < count && children_iterator != directory.children.cend()) { - const FileUtil::FSTEntry& file = *children_iterator; - const std::string& filename = file.virtualName; - Entry& entry = entries[entries_read]; - - LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size, file.isDirectory); - - // TODO(Link Mauve): use a proper conversion to UTF-16. - for (size_t j = 0; j < FILENAME_LENGTH; ++j) { - entry.filename[j] = filename[j]; - if (!filename[j]) - break; - } - - FileUtil::SplitFilename83(filename, entry.short_name, entry.extension); - - entry.is_directory = file.isDirectory; - entry.is_hidden = (filename[0] == '.'); - entry.is_read_only = 0; - entry.file_size = file.size; - - // We emulate a SD card where the archive bit has never been cleared, as it would be on - // most user SD cards. - // Some homebrews (blargSNES for instance) are known to mistakenly use the archive bit as a - // file bit. - entry.is_archive = !file.isDirectory; - - ++entries_read; - ++children_iterator; - } - return entries_read; -} - -/** - * Close the directory - * @return true if the directory closed correctly - */ -bool Directory_SDMC::Close() const { - return true; -} - -} // namespace FileSys diff --git a/src/core/file_sys/directory_sdmc.h b/src/core/file_sys/directory_sdmc.h deleted file mode 100644 index 407a256ef..000000000 --- a/src/core/file_sys/directory_sdmc.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" -#include "common/file_util.h" - -#include "core/file_sys/directory_backend.h" -#include "core/file_sys/archive_sdmc.h" -#include "core/loader/loader.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -class Directory_SDMC final : public DirectoryBackend { -public: - Directory_SDMC(); - Directory_SDMC(const Archive_SDMC* archive, const Path& path); - ~Directory_SDMC() override; - - /** - * Open the directory - * @return true if the directory opened correctly - */ - bool Open() override; - - /** - * List files contained in the directory - * @param count Number of entries to return at once in entries - * @param entries Buffer to read data into - * @return Number of entries listed - */ - u32 Read(const u32 count, Entry* entries) override; - - /** - * Close the directory - * @return true if the directory closed correctly - */ - bool Close() const override; - -private: - std::string path; - u32 total_entries_in_directory; - FileUtil::FSTEntry directory; - - // We need to remember the last entry we returned, so a subsequent call to Read will continue - // from the next one. This iterator will always point to the next unread entry. - std::vector::iterator children_iterator; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp new file mode 100644 index 000000000..eabf58057 --- /dev/null +++ b/src/core/file_sys/disk_archive.cpp @@ -0,0 +1,167 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include + +#include "common/common_types.h" +#include "common/file_util.h" + +#include "core/file_sys/disk_archive.h" +#include "core/settings.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +std::unique_ptr DiskArchive::OpenFile(const Path& path, const Mode mode) const { + LOG_DEBUG(Service_FS, "called path=%s mode=%01X", path.DebugStr().c_str(), mode.hex); + DiskFile* file = new DiskFile(this, path, mode); + if (!file->Open()) + return nullptr; + return std::unique_ptr(file); +} + +bool DiskArchive::DeleteFile(const FileSys::Path& path) const { + return FileUtil::Delete(GetMountPoint() + path.AsString()); +} + +bool DiskArchive::RenameFile(const FileSys::Path& src_path, const FileSys::Path& dest_path) const { + return FileUtil::Rename(GetMountPoint() + src_path.AsString(), GetMountPoint() + dest_path.AsString()); +} + +bool DiskArchive::DeleteDirectory(const FileSys::Path& path) const { + return FileUtil::DeleteDir(GetMountPoint() + path.AsString()); +} + +bool DiskArchive::CreateDirectory(const Path& path) const { + return FileUtil::CreateDir(GetMountPoint() + path.AsString()); +} + +bool DiskArchive::RenameDirectory(const FileSys::Path& src_path, const FileSys::Path& dest_path) const { + return FileUtil::Rename(GetMountPoint() + src_path.AsString(), GetMountPoint() + dest_path.AsString()); +} + +std::unique_ptr DiskArchive::OpenDirectory(const Path& path) const { + LOG_DEBUG(Service_FS, "called path=%s", path.DebugStr().c_str()); + DiskDirectory* directory = new DiskDirectory(this, path); + if (!directory->Open()) + return nullptr; + return std::unique_ptr(directory); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +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->GetMountPoint() + path.AsString(); + this->mode.hex = mode.hex; + this->archive = archive; +} + +bool DiskFile::Open() { + if (!mode.create_flag && !FileUtil::Exists(path)) { + LOG_ERROR(Service_FS, "Non-existing file %s can’t be open without mode create.", path.c_str()); + return false; + } + + std::string mode_string; + if (mode.create_flag) + mode_string = "w+"; + else 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 = new FileUtil::IOFile(path, mode_string.c_str()); + return true; +} + +size_t DiskFile::Read(const u64 offset, const u32 length, u8* buffer) const { + file->Seek(offset, SEEK_SET); + return file->ReadBytes(buffer, length); +} + +size_t DiskFile::Write(const u64 offset, const u32 length, const u32 flush, const u8* buffer) const { + file->Seek(offset, SEEK_SET); + size_t written = file->WriteBytes(buffer, length); + if (flush) + file->Flush(); + return written; +} + +size_t DiskFile::GetSize() const { + return static_cast(file->GetSize()); +} + +bool DiskFile::SetSize(const u64 size) const { + file->Resize(size); + file->Flush(); + return true; +} + +bool DiskFile::Close() const { + return file->Close(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +DiskDirectory::DiskDirectory(const DiskArchive* archive, const Path& path) { + // 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->GetMountPoint() + path.AsString(); + this->archive = archive; +} + +bool DiskDirectory::Open() { + if (!FileUtil::IsDirectory(path)) + return false; + FileUtil::ScanDirectoryTree(path, directory); + children_iterator = directory.children.begin(); + return true; +} + +u32 DiskDirectory::Read(const u32 count, Entry* entries) { + u32 entries_read = 0; + + while (entries_read < count && children_iterator != directory.children.cend()) { + const FileUtil::FSTEntry& file = *children_iterator; + const std::string& filename = file.virtualName; + Entry& entry = entries[entries_read]; + + LOG_TRACE(Service_FS, "File %s: size=%llu dir=%d", filename.c_str(), file.size, file.isDirectory); + + // TODO(Link Mauve): use a proper conversion to UTF-16. + for (size_t j = 0; j < FILENAME_LENGTH; ++j) { + entry.filename[j] = filename[j]; + if (!filename[j]) + break; + } + + FileUtil::SplitFilename83(filename, entry.short_name, entry.extension); + + entry.is_directory = file.isDirectory; + entry.is_hidden = (filename[0] == '.'); + entry.is_read_only = 0; + entry.file_size = file.size; + + // We emulate a SD card where the archive bit has never been cleared, as it would be on + // most user SD cards. + // Some homebrews (blargSNES for instance) are known to mistakenly use the archive bit as a + // file bit. + entry.is_archive = !file.isDirectory; + + ++entries_read; + ++children_iterator; + } + return entries_read; +} + +} // namespace FileSys diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h new file mode 100644 index 000000000..778c83953 --- /dev/null +++ b/src/core/file_sys/disk_archive.h @@ -0,0 +1,101 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/file_sys/archive_backend.h" +#include "core/loader/loader.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +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 = 0; + std::unique_ptr OpenFile(const Path& path, const Mode mode) const override; + bool DeleteFile(const FileSys::Path& path) const override; + bool RenameFile(const FileSys::Path& src_path, const FileSys::Path& dest_path) const override; + bool DeleteDirectory(const FileSys::Path& path) const override; + bool CreateDirectory(const Path& path) const override; + bool RenameDirectory(const FileSys::Path& src_path, const FileSys::Path& dest_path) const override; + std::unique_ptr OpenDirectory(const Path& path) const override; + + /** + * Getter for the path used for this Archive + * @return Mount point of that passthrough archive + */ + const std::string& GetMountPoint() const { + return mount_point; + } + +protected: + std::string mount_point; +}; + +class DiskFile : public FileBackend { +public: + DiskFile(); + DiskFile(const DiskArchive* archive, const Path& path, const Mode mode); + + ~DiskFile() override { + Close(); + } + + bool Open() override; + size_t Read(const u64 offset, const u32 length, u8* buffer) const override; + size_t Write(const u64 offset, const u32 length, const u32 flush, const u8* buffer) const override; + size_t GetSize() const override; + bool SetSize(const u64 size) const override; + bool Close() const override; + + void Flush() const override { + file->Flush(); + } + +protected: + const DiskArchive* archive; + std::string path; + Mode mode; + FileUtil::IOFile* file; +}; + +class DiskDirectory : public DirectoryBackend { +public: + DiskDirectory(); + DiskDirectory(const DiskArchive* archive, const Path& path); + + ~DiskDirectory() override { + Close(); + } + + bool Open() override; + u32 Read(const u32 count, Entry* entries) override; + + bool Close() const override { + return true; + } + +protected: + const DiskArchive* archive; + std::string path; + u32 total_entries_in_directory; + FileUtil::FSTEntry directory; + + // We need to remember the last entry we returned, so a subsequent call to Read will continue + // from the next one. This iterator will always point to the next unread entry. + std::vector::iterator children_iterator; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h index 1b81d5fe9..539ec7314 100644 --- a/src/core/file_sys/file_backend.h +++ b/src/core/file_sys/file_backend.h @@ -61,6 +61,11 @@ public: * @return true if the file closed correctly */ virtual bool Close() const = 0; + + /** + * Flushes the file + */ + virtual void Flush() const = 0; }; } // namespace FileSys diff --git a/src/core/file_sys/file_romfs.h b/src/core/file_sys/file_romfs.h index 09fa2e7e3..32fa6b6d3 100644 --- a/src/core/file_sys/file_romfs.h +++ b/src/core/file_sys/file_romfs.h @@ -64,6 +64,8 @@ public: */ bool Close() const override; + void Flush() const override { } + private: const Archive_RomFS* archive; }; diff --git a/src/core/file_sys/file_sdmc.cpp b/src/core/file_sys/file_sdmc.cpp deleted file mode 100644 index 46c29900b..000000000 --- a/src/core/file_sys/file_sdmc.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#include - -#include "common/common_types.h" -#include "common/file_util.h" - -#include "core/file_sys/file_sdmc.h" -#include "core/file_sys/archive_sdmc.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -File_SDMC::File_SDMC(const Archive_SDMC* 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->GetMountPoint() + path.AsString(); - this->mode.hex = mode.hex; -} - -File_SDMC::~File_SDMC() { - Close(); -} - -/** - * Open the file - * @return true if the file opened correctly - */ -bool File_SDMC::Open() { - if (!mode.create_flag && !FileUtil::Exists(path)) { - LOG_ERROR(Service_FS, "Non-existing file %s can’t be open without mode create.", path.c_str()); - return false; - } - - std::string mode_string; - if (mode.create_flag) - mode_string = "w+"; - else 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 = new FileUtil::IOFile(path, mode_string.c_str()); - return true; -} - -/** - * 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 - * @param buffer Buffer to read data into - * @return Number of bytes read - */ -size_t File_SDMC::Read(const u64 offset, const u32 length, u8* buffer) const { - file->Seek(offset, SEEK_SET); - return file->ReadBytes(buffer, length); -} - -/** - * Write data to the file - * @param offset Offset in bytes to start writing data to - * @param length Length in bytes of data to write to file - * @param flush The flush parameters (0 == do not flush) - * @param buffer Buffer to read data from - * @return Number of bytes written - */ -size_t File_SDMC::Write(const u64 offset, const u32 length, const u32 flush, const u8* buffer) const { - file->Seek(offset, SEEK_SET); - size_t written = file->WriteBytes(buffer, length); - if (flush) - file->Flush(); - return written; -} - -/** - * Get the size of the file in bytes - * @return Size of the file in bytes - */ -size_t File_SDMC::GetSize() const { - return static_cast(file->GetSize()); -} - -/** - * Set the size of the file in bytes - * @param size New size of the file - * @return true if successful - */ -bool File_SDMC::SetSize(const u64 size) const { - file->Resize(size); - file->Flush(); - return true; -} - -/** - * Close the file - * @return true if the file closed correctly - */ -bool File_SDMC::Close() const { - return file->Close(); -} - -} // namespace FileSys diff --git a/src/core/file_sys/file_sdmc.h b/src/core/file_sys/file_sdmc.h deleted file mode 100644 index e01548598..000000000 --- a/src/core/file_sys/file_sdmc.h +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" -#include "common/file_util.h" - -#include "core/file_sys/file_backend.h" -#include "core/file_sys/archive_sdmc.h" -#include "core/loader/loader.h" - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// FileSys namespace - -namespace FileSys { - -class File_SDMC final : public FileBackend { -public: - File_SDMC(); - File_SDMC(const Archive_SDMC* archive, const Path& path, const Mode mode); - ~File_SDMC() override; - - /** - * Open the file - * @return true if the file opened correctly - */ - bool Open() override; - - /** - * 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 - * @param buffer Buffer to read data into - * @return Number of bytes read - */ - size_t Read(const u64 offset, const u32 length, u8* buffer) const override; - - /** - * Write data to the file - * @param offset Offset in bytes to start writing data to - * @param length Length in bytes of data to write to file - * @param flush The flush parameters (0 == do not flush) - * @param buffer Buffer to read data from - * @return Number of bytes written - */ - size_t Write(const u64 offset, const u32 length, const u32 flush, const u8* buffer) const override; - - /** - * Get the size of the file in bytes - * @return Size of the file in bytes - */ - size_t GetSize() const override; - - /** - * Set the size of the file in bytes - * @param size New size of the file - * @return true if successful - */ - bool SetSize(const u64 size) const override; - - /** - * Close the file - * @return true if the file closed correctly - */ - bool Close() const override; - -private: - std::string path; - Mode mode; - FileUtil::IOFile* file; -}; - -} // namespace FileSys diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 929422b36..6a690e915 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -14,6 +14,7 @@ namespace Kernel { Handle g_main_thread = 0; ObjectPool g_object_pool; +u64 g_program_id = 0; ObjectPool::ObjectPool() { next_id = INITIAL_NEXT_ID; diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 7e0f15c84..7123485be 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -151,6 +151,12 @@ private: extern ObjectPool g_object_pool; extern Handle g_main_thread; +/// The ID code of the currently running game +/// TODO(Subv): This variable should not be here, +/// we need a way to store information about the currently loaded application +/// for later query during runtime, maybe using the LDR service? +extern u64 g_program_id; + /// Initialize the kernel void Init(); diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 15c4a2677..14d2be4a2 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -17,6 +17,8 @@ /// Detailed description of the error. This listing is likely incomplete. enum class ErrorDescription : u32 { Success = 0, + FS_NotFound = 100, + FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive InvalidSection = 1000, TooLarge = 1001, NotAuthorized = 1002, diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index caf82d556..9c3834733 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -9,6 +9,7 @@ #include "common/file_util.h" #include "common/math_util.h" +#include "core/file_sys/archive_savedata.h" #include "core/file_sys/archive_backend.h" #include "core/file_sys/archive_sdmc.h" #include "core/file_sys/directory_backend.h" @@ -135,6 +136,13 @@ public: break; } + case FileCommand::Flush: + { + LOG_TRACE(Service_FS, "Flush"); + backend->Flush(); + break; + } + // Unknown command... default: LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd); @@ -220,9 +228,18 @@ ResultVal OpenArchive(ArchiveIdCode id_code) { auto itr = id_code_map.find(id_code); if (itr == id_code_map.end()) { + if (id_code == ArchiveIdCode::SaveData) { + // When a SaveData archive is created for the first time, it is not yet formatted + // and the save file/directory structure expected by the game has not yet been initialized. + // Returning the NotFormatted error code will signal the game to provision the SaveData archive + // with the files and folders that it expects. + // The FormatSaveData service call will create the SaveData archive when it is called. + return ResultCode(ErrorDescription::FS_NotFormatted, ErrorModule::FS, + ErrorSummary::InvalidState, ErrorLevel::Status); + } // TODO: Verify error against hardware return ResultCode(ErrorDescription::NotFound, ErrorModule::FS, - ErrorSummary::NotFound, ErrorLevel::Permanent); + ErrorSummary::NotFound, ErrorLevel::Permanent); } // This should never even happen in the first place with 64-bit handles, @@ -260,8 +277,8 @@ ResultVal OpenFileFromArchive(ArchiveHandle archive_handle, const FileSy std::unique_ptr backend = archive->backend->OpenFile(path, mode); if (backend == nullptr) { - return ResultCode(ErrorDescription::NotFound, ErrorModule::FS, - ErrorSummary::NotFound, ErrorLevel::Permanent); + return ResultCode(ErrorDescription::FS_NotFound, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Status); } auto file = std::make_unique(std::move(backend), path); @@ -366,6 +383,28 @@ ResultVal OpenDirectoryFromArchive(ArchiveHandle archive_handle, const F return MakeResult(handle); } +ResultCode FormatSaveData() { + // TODO(Subv): Actually wipe the savedata folder after creating or opening it + + // Do not create the archive again if it already exists + if (id_code_map.find(ArchiveIdCode::SaveData) != id_code_map.end()) + return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the correct error code + + // Create the SaveData archive + std::string savedata_directory = FileUtil::GetUserPath(D_SAVEDATA_IDX); + auto savedata_archive = std::make_unique(savedata_directory, + Kernel::g_program_id); + + if (savedata_archive->Initialize()) { + CreateArchive(std::move(savedata_archive), ArchiveIdCode::SaveData); + return RESULT_SUCCESS; + } else { + LOG_ERROR(Service_FS, "Can't instantiate SaveData archive with path %s", + savedata_archive->GetMountPoint().c_str()); + return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the proper error code + } +} + /// Initialize archives void ArchiveInit() { next_handle = 1; @@ -375,9 +414,9 @@ void ArchiveInit() { // archive type is SDMC, so it is the only one getting exposed. std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX); - auto archive = std::make_unique(sdmc_directory); - if (archive->Initialize()) - CreateArchive(std::move(archive), ArchiveIdCode::SDMC); + auto sdmc_archive = std::make_unique(sdmc_directory); + if (sdmc_archive->Initialize()) + CreateArchive(std::move(sdmc_archive), ArchiveIdCode::SDMC); else LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str()); } diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index a38de92e3..a128276b6 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -109,6 +109,12 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, cons */ ResultVal OpenDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path); +/** + * Creates a blank SaveData archive. + * @return ResultCode 0 on success or the corresponding code on error + */ +ResultCode FormatSaveData(); + /// Initialize archives void ArchiveInit(); diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp index 0f75d5e3a..f99d84b2f 100644 --- a/src/core/hle/service/fs/fs_user.cpp +++ b/src/core/hle/service/fs/fs_user.cpp @@ -3,11 +3,11 @@ // Refer to the license.txt file included. #include "common/common.h" +#include "common/file_util.h" #include "common/scope_exit.h" - #include "common/string_util.h" -#include "core/hle/service/fs/archive.h" #include "core/hle/result.h" +#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/fs_user.h" #include "core/settings.h" @@ -50,9 +50,7 @@ static void Initialize(Service::Interface* self) { static void OpenFile(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); - // TODO(Link Mauve): cmd_buff[2], aka archive handle lower word, isn't used according to - // 3dmoo's or ctrulib's implementations. Triple check if it's really the case. - Handle archive_handle = static_cast(cmd_buff[3]); + ArchiveHandle archive_handle = MakeArchiveHandle(cmd_buff[2], cmd_buff[3]); auto filename_type = static_cast(cmd_buff[4]); u32 filename_size = cmd_buff[5]; FileSys::Mode mode; mode.hex = cmd_buff[6]; @@ -398,6 +396,36 @@ static void IsSdmcDetected(Service::Interface* self) { LOG_DEBUG(Service_FS, "called"); } +/** + * FS_User::FormatSaveData service function + * Inputs: + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void FormatSaveData(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_DEBUG(Service_FS, "(STUBBED)"); + + // TODO(Subv): Find out what the inputs and outputs of this function are + + cmd_buff[1] = FormatSaveData().raw; +} + +/** + * FS_User::FormatThisUserSaveData service function + * Inputs: + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +static void FormatThisUserSaveData(Service::Interface* self) { + u32* cmd_buff = Kernel::GetCommandBuffer(); + LOG_DEBUG(Service_FS, "(STUBBED)"); + + // TODO(Subv): Find out what the inputs and outputs of this function are + + cmd_buff[1] = FormatSaveData().raw; +} + const FSUserInterface::FunctionInfo FunctionTable[] = { {0x000100C6, nullptr, "Dummy1"}, {0x040100C4, nullptr, "Control"}, @@ -415,7 +443,7 @@ const FSUserInterface::FunctionInfo FunctionTable[] = { {0x080C00C2, OpenArchive, "OpenArchive"}, {0x080D0144, nullptr, "ControlArchive"}, {0x080E0080, CloseArchive, "CloseArchive"}, - {0x080F0180, nullptr, "FormatThisUserSaveData"}, + {0x080F0180, FormatThisUserSaveData,"FormatThisUserSaveData"}, {0x08100200, nullptr, "CreateSystemSaveData"}, {0x08110040, nullptr, "DeleteSystemSaveData"}, {0x08120080, nullptr, "GetFreeBytes"}, @@ -476,7 +504,7 @@ const FSUserInterface::FunctionInfo FunctionTable[] = { {0x08490040, nullptr, "GetArchiveResource"}, {0x084A0002, nullptr, "ExportIntegrityVerificationSeed"}, {0x084B0002, nullptr, "ImportIntegrityVerificationSeed"}, - {0x084C0242, nullptr, "FormatSaveData"}, + {0x084C0242, FormatSaveData, "FormatSaveData"}, {0x084D0102, nullptr, "GetLegacySubBannerData"}, {0x084E0342, nullptr, "UpdateSha256Context"}, {0x084F0102, nullptr, "ReadSpecialFile"}, diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 463dacca3..480274d23 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -74,6 +74,7 @@ ResultStatus LoadFile(const std::string& filename) { // Load application and RomFS if (ResultStatus::Success == app_loader.Load()) { + Kernel::g_program_id = app_loader.GetProgramId(); Service::FS::CreateArchive(std::make_unique(app_loader), Service::FS::ArchiveIdCode::RomFS); return ResultStatus::Success; } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index ba9ba00c0..4d23656ec 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -315,4 +315,8 @@ ResultStatus AppLoader_NCCH::ReadRomFS(std::vector& buffer) const { return ResultStatus::Error; } +u64 AppLoader_NCCH::GetProgramId() const { + return *reinterpret_cast(&ncch_header.program_id[0]); +} + } // namespace Loader diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 03116add8..2fe2a7d82 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -191,6 +191,12 @@ public: */ ResultStatus ReadRomFS(std::vector& buffer) const override; + /* + * Gets the program id from the NCCH header + * @return u64 Program id + */ + u64 GetProgramId() const; + private: /**