Просмотр исходного кода

Merge pull request #288 from Subv/savedata_stuff

FS_U: Implemented the SaveData archive
bunnei 11 лет назад
Родитель
Сommit
94a103a000

+ 1 - 0
src/common/common_paths.h

@@ -40,6 +40,7 @@
 #define MAPS_DIR          "maps"
 #define MAPS_DIR          "maps"
 #define CACHE_DIR         "cache"
 #define CACHE_DIR         "cache"
 #define SDMC_DIR          "sdmc"
 #define SDMC_DIR          "sdmc"
+#define SAVEDATA_DIR      "savedata"
 #define SYSDATA_DIR       "sysdata"
 #define SYSDATA_DIR       "sysdata"
 #define SHADERCACHE_DIR   "shader_cache"
 #define SHADERCACHE_DIR   "shader_cache"
 #define STATESAVES_DIR    "state_saves"
 #define STATESAVES_DIR    "state_saves"

+ 2 - 0
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_MAPS_IDX]           = paths[D_USER_IDX] + MAPS_DIR DIR_SEP;
         paths[D_CACHE_IDX]          = paths[D_USER_IDX] + CACHE_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_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_SYSDATA_IDX]        = paths[D_USER_IDX] + SYSDATA_DIR DIR_SEP;
         paths[D_SHADERCACHE_IDX]    = paths[D_USER_IDX] + SHADERCACHE_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_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_MAPS_IDX]           = paths[D_USER_IDX] + MAPS_DIR DIR_SEP;
             paths[D_CACHE_IDX]          = paths[D_USER_IDX] + CACHE_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_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_SHADERCACHE_IDX]    = paths[D_USER_IDX] + SHADERCACHE_DIR DIR_SEP;
             paths[D_SHADERS_IDX]        = paths[D_USER_IDX] + SHADERS_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;
             paths[D_STATESAVES_IDX]     = paths[D_USER_IDX] + STATESAVES_DIR DIR_SEP;

+ 1 - 0
src/common/file_util.h

@@ -27,6 +27,7 @@ enum {
     D_STATESAVES_IDX,
     D_STATESAVES_IDX,
     D_SCREENSHOTS_IDX,
     D_SCREENSHOTS_IDX,
     D_SDMC_IDX,
     D_SDMC_IDX,
+    D_SAVEDATA_IDX,
     D_SYSDATA_IDX,
     D_SYSDATA_IDX,
     D_HIRESTEXTURES_IDX,
     D_HIRESTEXTURES_IDX,
     D_DUMP_IDX,
     D_DUMP_IDX,

+ 4 - 4
src/core/CMakeLists.txt

@@ -18,11 +18,11 @@ set(SRCS
             arm/skyeye_common/vfp/vfpinstr.cpp
             arm/skyeye_common/vfp/vfpinstr.cpp
             arm/skyeye_common/vfp/vfpsingle.cpp
             arm/skyeye_common/vfp/vfpsingle.cpp
             file_sys/archive_romfs.cpp
             file_sys/archive_romfs.cpp
+            file_sys/archive_savedata.cpp
             file_sys/archive_sdmc.cpp
             file_sys/archive_sdmc.cpp
+            file_sys/disk_archive.cpp
             file_sys/file_romfs.cpp
             file_sys/file_romfs.cpp
-            file_sys/file_sdmc.cpp
             file_sys/directory_romfs.cpp
             file_sys/directory_romfs.cpp
-            file_sys/directory_sdmc.cpp
             hle/kernel/address_arbiter.cpp
             hle/kernel/address_arbiter.cpp
             hle/kernel/event.cpp
             hle/kernel/event.cpp
             hle/kernel/kernel.cpp
             hle/kernel/kernel.cpp
@@ -99,13 +99,13 @@ set(HEADERS
             arm/arm_interface.h
             arm/arm_interface.h
             file_sys/archive_backend.h
             file_sys/archive_backend.h
             file_sys/archive_romfs.h
             file_sys/archive_romfs.h
+            file_sys/archive_savedata.h
             file_sys/archive_sdmc.h
             file_sys/archive_sdmc.h
+            file_sys/disk_archive.h
             file_sys/file_backend.h
             file_sys/file_backend.h
             file_sys/file_romfs.h
             file_sys/file_romfs.h
-            file_sys/file_sdmc.h
             file_sys/directory_backend.h
             file_sys/directory_backend.h
             file_sys/directory_romfs.h
             file_sys/directory_romfs.h
-            file_sys/directory_sdmc.h
             hle/kernel/address_arbiter.h
             hle/kernel/address_arbiter.h
             hle/kernel/event.h
             hle/kernel/event.h
             hle/kernel/kernel.h
             hle/kernel/kernel.h

+ 33 - 0
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 <sys/stat.h>
+
+#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

+ 32 - 0
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

+ 2 - 81
src/core/file_sys/archive_sdmc.cpp

@@ -8,8 +8,7 @@
 #include "common/file_util.h"
 #include "common/file_util.h"
 
 
 #include "core/file_sys/archive_sdmc.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"
 #include "core/settings.h"
 
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -17,18 +16,10 @@
 
 
 namespace FileSys {
 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());
     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() {
 bool Archive_SDMC::Initialize() {
     if (!Settings::values.use_virtual_sd) {
     if (!Settings::values.use_virtual_sd) {
         LOG_WARNING(Service_FS, "SDMC disabled by config.");
         LOG_WARNING(Service_FS, "SDMC disabled by config.");
@@ -43,74 +34,4 @@ bool Archive_SDMC::Initialize() {
     return true;
     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<FileBackend> 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<FileBackend>(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<DirectoryBackend> 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<DirectoryBackend>(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
 } // namespace FileSys

+ 2 - 64
src/core/file_sys/archive_sdmc.h

@@ -6,7 +6,7 @@
 
 
 #include "common/common_types.h"
 #include "common/common_types.h"
 
 
-#include "core/file_sys/archive_backend.h"
+#include "core/file_sys/disk_archive.h"
 #include "core/loader/loader.h"
 #include "core/loader/loader.h"
 
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -15,10 +15,9 @@
 namespace FileSys {
 namespace FileSys {
 
 
 /// File system interface to the SDMC archive
 /// File system interface to the SDMC archive
-class Archive_SDMC final : public ArchiveBackend {
+class Archive_SDMC final : public DiskArchive {
 public:
 public:
     Archive_SDMC(const std::string& mount_point);
     Archive_SDMC(const std::string& mount_point);
-    ~Archive_SDMC() override;
 
 
     /**
     /**
      * Initialize the archive.
      * Initialize the archive.
@@ -27,67 +26,6 @@ public:
     bool Initialize();
     bool Initialize();
 
 
     std::string GetName() const override { return "SDMC"; }
     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<FileBackend> 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<DirectoryBackend> 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
 } // namespace FileSys

+ 0 - 88
src/core/file_sys/directory_sdmc.cpp

@@ -1,88 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
-// Refer to the license.txt file included.
-
-#include <sys/stat.h>
-
-#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

+ 0 - 55
src/core/file_sys/directory_sdmc.h

@@ -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<FileUtil::FSTEntry>::iterator children_iterator;
-};
-
-} // namespace FileSys

+ 167 - 0
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 <sys/stat.h>
+
+#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<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);
+    DiskFile* file = new DiskFile(this, path, mode);
+    if (!file->Open())
+        return nullptr;
+    return std::unique_ptr<FileBackend>(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<DirectoryBackend> 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<DirectoryBackend>(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<size_t>(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

+ 101 - 0
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<FileBackend> 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<DirectoryBackend> 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<FileUtil::FSTEntry>::iterator children_iterator;
+};
+
+} // namespace FileSys

+ 5 - 0
src/core/file_sys/file_backend.h

@@ -61,6 +61,11 @@ public:
      * @return true if the file closed correctly
      * @return true if the file closed correctly
      */
      */
     virtual bool Close() const = 0;
     virtual bool Close() const = 0;
+
+    /**
+     * Flushes the file
+     */
+    virtual void Flush() const = 0;
 };
 };
 
 
 } // namespace FileSys
 } // namespace FileSys

+ 2 - 0
src/core/file_sys/file_romfs.h

@@ -64,6 +64,8 @@ public:
      */
      */
     bool Close() const override;
     bool Close() const override;
 
 
+    void Flush() const override { }
+
 private:
 private:
     const Archive_RomFS* archive;
     const Archive_RomFS* archive;
 };
 };

+ 0 - 110
src/core/file_sys/file_sdmc.cpp

@@ -1,110 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2
-// Refer to the license.txt file included.
-
-#include <sys/stat.h>
-
-#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<size_t>(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

+ 0 - 75
src/core/file_sys/file_sdmc.h

@@ -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

+ 1 - 0
src/core/hle/kernel/kernel.cpp

@@ -14,6 +14,7 @@ namespace Kernel {
 
 
 Handle g_main_thread = 0;
 Handle g_main_thread = 0;
 ObjectPool g_object_pool;
 ObjectPool g_object_pool;
+u64 g_program_id = 0;
 
 
 ObjectPool::ObjectPool() {
 ObjectPool::ObjectPool() {
     next_id = INITIAL_NEXT_ID;
     next_id = INITIAL_NEXT_ID;

+ 6 - 0
src/core/hle/kernel/kernel.h

@@ -151,6 +151,12 @@ private:
 extern ObjectPool g_object_pool;
 extern ObjectPool g_object_pool;
 extern Handle g_main_thread;
 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
 /// Initialize the kernel
 void Init();
 void Init();
 
 

+ 2 - 0
src/core/hle/result.h

@@ -17,6 +17,8 @@
 /// Detailed description of the error. This listing is likely incomplete.
 /// Detailed description of the error. This listing is likely incomplete.
 enum class ErrorDescription : u32 {
 enum class ErrorDescription : u32 {
     Success = 0,
     Success = 0,
+    FS_NotFound = 100,
+    FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
     InvalidSection = 1000,
     InvalidSection = 1000,
     TooLarge = 1001,
     TooLarge = 1001,
     NotAuthorized = 1002,
     NotAuthorized = 1002,

+ 45 - 6
src/core/hle/service/fs/archive.cpp

@@ -9,6 +9,7 @@
 #include "common/file_util.h"
 #include "common/file_util.h"
 #include "common/math_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_backend.h"
 #include "core/file_sys/archive_sdmc.h"
 #include "core/file_sys/archive_sdmc.h"
 #include "core/file_sys/directory_backend.h"
 #include "core/file_sys/directory_backend.h"
@@ -135,6 +136,13 @@ public:
             break;
             break;
         }
         }
 
 
+        case FileCommand::Flush:
+        {
+            LOG_TRACE(Service_FS, "Flush");
+            backend->Flush();
+            break;
+        }
+
         // Unknown command...
         // Unknown command...
         default:
         default:
             LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd);
             LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd);
@@ -220,9 +228,18 @@ ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code) {
 
 
     auto itr = id_code_map.find(id_code);
     auto itr = id_code_map.find(id_code);
     if (itr == id_code_map.end()) {
     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
         // TODO: Verify error against hardware
         return ResultCode(ErrorDescription::NotFound, ErrorModule::FS,
         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, 
     // This should never even happen in the first place with 64-bit handles, 
@@ -260,8 +277,8 @@ ResultVal<Handle> OpenFileFromArchive(ArchiveHandle archive_handle, const FileSy
 
 
     std::unique_ptr<FileSys::FileBackend> backend = archive->backend->OpenFile(path, mode);
     std::unique_ptr<FileSys::FileBackend> backend = archive->backend->OpenFile(path, mode);
     if (backend == nullptr) {
     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<File>(std::move(backend), path);
     auto file = std::make_unique<File>(std::move(backend), path);
@@ -366,6 +383,28 @@ ResultVal<Handle> OpenDirectoryFromArchive(ArchiveHandle archive_handle, const F
     return MakeResult<Handle>(handle);
     return MakeResult<Handle>(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<FileSys::Archive_SaveData>(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
 /// Initialize archives
 void ArchiveInit() {
 void ArchiveInit() {
     next_handle = 1;
     next_handle = 1;
@@ -375,9 +414,9 @@ void ArchiveInit() {
     // archive type is SDMC, so it is the only one getting exposed.
     // archive type is SDMC, so it is the only one getting exposed.
 
 
     std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX);
     std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX);
-    auto archive = std::make_unique<FileSys::Archive_SDMC>(sdmc_directory);
-    if (archive->Initialize())
-        CreateArchive(std::move(archive), ArchiveIdCode::SDMC);
+    auto sdmc_archive = std::make_unique<FileSys::Archive_SDMC>(sdmc_directory);
+    if (sdmc_archive->Initialize())
+        CreateArchive(std::move(sdmc_archive), ArchiveIdCode::SDMC);
     else
     else
         LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str());
         LOG_ERROR(Service_FS, "Can't instantiate SDMC archive with path %s", sdmc_directory.c_str());
 }
 }

+ 6 - 0
src/core/hle/service/fs/archive.h

@@ -109,6 +109,12 @@ ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, cons
  */
  */
 ResultVal<Handle> OpenDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path);
 ResultVal<Handle> 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
 /// Initialize archives
 void ArchiveInit();
 void ArchiveInit();
 
 

+ 35 - 7
src/core/hle/service/fs/fs_user.cpp

@@ -3,11 +3,11 @@
 // Refer to the license.txt file included.
 // Refer to the license.txt file included.
 
 
 #include "common/common.h"
 #include "common/common.h"
+#include "common/file_util.h"
 #include "common/scope_exit.h"
 #include "common/scope_exit.h"
-
 #include "common/string_util.h"
 #include "common/string_util.h"
-#include "core/hle/service/fs/archive.h"
 #include "core/hle/result.h"
 #include "core/hle/result.h"
+#include "core/hle/service/fs/archive.h"
 #include "core/hle/service/fs/fs_user.h"
 #include "core/hle/service/fs/fs_user.h"
 #include "core/settings.h"
 #include "core/settings.h"
 
 
@@ -50,9 +50,7 @@ static void Initialize(Service::Interface* self) {
 static void OpenFile(Service::Interface* self) {
 static void OpenFile(Service::Interface* self) {
     u32* cmd_buff = Kernel::GetCommandBuffer();
     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<Handle>(cmd_buff[3]);
+    ArchiveHandle archive_handle = MakeArchiveHandle(cmd_buff[2], cmd_buff[3]);
     auto filename_type    = static_cast<FileSys::LowPathType>(cmd_buff[4]);
     auto filename_type    = static_cast<FileSys::LowPathType>(cmd_buff[4]);
     u32 filename_size     = cmd_buff[5];
     u32 filename_size     = cmd_buff[5];
     FileSys::Mode mode; mode.hex = cmd_buff[6];
     FileSys::Mode mode; mode.hex = cmd_buff[6];
@@ -398,6 +396,36 @@ static void IsSdmcDetected(Service::Interface* self) {
     LOG_DEBUG(Service_FS, "called");
     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[] = {
 const FSUserInterface::FunctionInfo FunctionTable[] = {
     {0x000100C6, nullptr,               "Dummy1"},
     {0x000100C6, nullptr,               "Dummy1"},
     {0x040100C4, nullptr,               "Control"},
     {0x040100C4, nullptr,               "Control"},
@@ -415,7 +443,7 @@ const FSUserInterface::FunctionInfo FunctionTable[] = {
     {0x080C00C2, OpenArchive,           "OpenArchive"},
     {0x080C00C2, OpenArchive,           "OpenArchive"},
     {0x080D0144, nullptr,               "ControlArchive"},
     {0x080D0144, nullptr,               "ControlArchive"},
     {0x080E0080, CloseArchive,          "CloseArchive"},
     {0x080E0080, CloseArchive,          "CloseArchive"},
-    {0x080F0180, nullptr,               "FormatThisUserSaveData"},
+    {0x080F0180, FormatThisUserSaveData,"FormatThisUserSaveData"},
     {0x08100200, nullptr,               "CreateSystemSaveData"},
     {0x08100200, nullptr,               "CreateSystemSaveData"},
     {0x08110040, nullptr,               "DeleteSystemSaveData"},
     {0x08110040, nullptr,               "DeleteSystemSaveData"},
     {0x08120080, nullptr,               "GetFreeBytes"},
     {0x08120080, nullptr,               "GetFreeBytes"},
@@ -476,7 +504,7 @@ const FSUserInterface::FunctionInfo FunctionTable[] = {
     {0x08490040, nullptr,               "GetArchiveResource"},
     {0x08490040, nullptr,               "GetArchiveResource"},
     {0x084A0002, nullptr,               "ExportIntegrityVerificationSeed"},
     {0x084A0002, nullptr,               "ExportIntegrityVerificationSeed"},
     {0x084B0002, nullptr,               "ImportIntegrityVerificationSeed"},
     {0x084B0002, nullptr,               "ImportIntegrityVerificationSeed"},
-    {0x084C0242, nullptr,               "FormatSaveData"},
+    {0x084C0242, FormatSaveData,        "FormatSaveData"},
     {0x084D0102, nullptr,               "GetLegacySubBannerData"},
     {0x084D0102, nullptr,               "GetLegacySubBannerData"},
     {0x084E0342, nullptr,               "UpdateSha256Context"},
     {0x084E0342, nullptr,               "UpdateSha256Context"},
     {0x084F0102, nullptr,               "ReadSpecialFile"},
     {0x084F0102, nullptr,               "ReadSpecialFile"},

+ 1 - 0
src/core/loader/loader.cpp

@@ -74,6 +74,7 @@ ResultStatus LoadFile(const std::string& filename) {
 
 
         // Load application and RomFS
         // Load application and RomFS
         if (ResultStatus::Success == app_loader.Load()) {
         if (ResultStatus::Success == app_loader.Load()) {
+            Kernel::g_program_id = app_loader.GetProgramId();
             Service::FS::CreateArchive(std::make_unique<FileSys::Archive_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS);
             Service::FS::CreateArchive(std::make_unique<FileSys::Archive_RomFS>(app_loader), Service::FS::ArchiveIdCode::RomFS);
             return ResultStatus::Success;
             return ResultStatus::Success;
         }
         }

+ 4 - 0
src/core/loader/ncch.cpp

@@ -315,4 +315,8 @@ ResultStatus AppLoader_NCCH::ReadRomFS(std::vector<u8>& buffer) const {
     return ResultStatus::Error;
     return ResultStatus::Error;
 }
 }
 
 
+u64 AppLoader_NCCH::GetProgramId() const {
+    return *reinterpret_cast<u64 const*>(&ncch_header.program_id[0]);
+}
+
 } // namespace Loader
 } // namespace Loader

+ 6 - 0
src/core/loader/ncch.h

@@ -191,6 +191,12 @@ public:
      */
      */
     ResultStatus ReadRomFS(std::vector<u8>& buffer) const override;
     ResultStatus ReadRomFS(std::vector<u8>& buffer) const override;
 
 
+    /*
+     * Gets the program id from the NCCH header
+     * @return u64 Program id
+     */
+    u64 GetProgramId() const;
+
 private:
 private:
 
 
     /**
     /**