50 changed files with 8 additions and 6976 deletions
-
94src/citra_qt/configuration/configure_system.cpp
-
14src/citra_qt/main.cpp
-
245src/core/file_sys/archive_extsavedata.cpp
-
89src/core/file_sys/archive_extsavedata.h
-
114src/core/file_sys/archive_ncch.cpp
-
34src/core/file_sys/archive_ncch.h
-
145src/core/file_sys/archive_other_savedata.cpp
-
52src/core/file_sys/archive_other_savedata.h
-
33src/core/file_sys/archive_savedata.cpp
-
33src/core/file_sys/archive_savedata.h
-
379src/core/file_sys/archive_sdmc.cpp
-
66src/core/file_sys/archive_sdmc.h
-
70src/core/file_sys/archive_sdmcwriteonly.cpp
-
57src/core/file_sys/archive_sdmcwriteonly.h
-
297src/core/file_sys/archive_selfncch.cpp
-
54src/core/file_sys/archive_selfncch.h
-
97src/core/file_sys/archive_source_sd_savedata.cpp
-
32src/core/file_sys/archive_source_sd_savedata.h
-
77src/core/file_sys/archive_systemsavedata.cpp
-
61src/core/file_sys/archive_systemsavedata.h
-
716src/core/hle/service/cfg/cfg.cpp
-
369src/core/hle/service/cfg/cfg.h
-
64src/core/hle/service/cfg/cfg_i.cpp
-
22src/core/hle/service/cfg/cfg_i.h
-
23src/core/hle/service/cfg/cfg_nor.cpp
-
22src/core/hle/service/cfg/cfg_nor.h
-
41src/core/hle/service/cfg/cfg_s.cpp
-
22src/core/hle/service/cfg/cfg_s.h
-
31src/core/hle/service/cfg/cfg_u.cpp
-
22src/core/hle/service/cfg/cfg_u.h
-
605src/core/hle/service/fs/archive.cpp
-
276src/core/hle/service/fs/archive.h
-
1045src/core/hle/service/fs/fs_user.cpp
-
23src/core/hle/service/fs/fs_user.h
-
425src/core/hle/service/hid/hid.cpp
-
266src/core/hle/service/hid/hid.h
-
29src/core/hle/service/hid/hid_spvr.cpp
-
22src/core/hle/service/hid/hid_spvr.h
-
29src/core/hle/service/hid/hid_user.cpp
-
28src/core/hle/service/hid/hid_user.h
-
16src/core/hle/service/ns/ns.cpp
-
16src/core/hle/service/ns/ns.h
-
34src/core/hle/service/ns/ns_s.cpp
-
21src/core/hle/service/ns/ns_s.h
-
350src/core/loader/3dsx.cpp
-
46src/core/loader/3dsx.h
-
31src/core/loader/loader.cpp
-
4src/core/loader/loader.h
-
263src/core/loader/ncch.cpp
-
80src/core/loader/ncch.h
@ -1,245 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <memory>
|
|
||||
#include <vector>
|
|
||||
#include "common/common_types.h"
|
|
||||
#include "common/file_util.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
#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"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
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 ERR_WRITE_BEYOND_END; |
|
||||
} 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: |
|
||||
explicit 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; |
|
||||
case PathParser::FileFound: |
|
||||
break; // Expected 'success' case
|
|
||||
} |
|
||||
|
|
||||
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()); |
|
||||
u32 save_low = data[1]; |
|
||||
u32 save_high = data[2]; |
|
||||
return Common::StringFromFormat("%s%08X/%08X/", mount_point.c_str(), save_high, save_low); |
|
||||
} |
|
||||
|
|
||||
std::string GetExtDataContainerPath(const std::string& mount_point, bool shared) { |
|
||||
if (shared) |
|
||||
return Common::StringFromFormat("%sdata/%s/extdata/", mount_point.c_str(), SYSTEM_ID); |
|
||||
|
|
||||
return Common::StringFromFormat("%sNintendo 3DS/%s/%s/extdata/", mount_point.c_str(), SYSTEM_ID, |
|
||||
SDCARD_ID); |
|
||||
} |
|
||||
|
|
||||
Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low) { |
|
||||
std::vector<u8> binary_path; |
|
||||
binary_path.reserve(12); |
|
||||
|
|
||||
// Append each word byte by byte
|
|
||||
|
|
||||
// The first word is the media type
|
|
||||
for (unsigned i = 0; i < 4; ++i) |
|
||||
binary_path.push_back((media_type >> (8 * i)) & 0xFF); |
|
||||
|
|
||||
// Next is the low word
|
|
||||
for (unsigned i = 0; i < 4; ++i) |
|
||||
binary_path.push_back((low >> (8 * i)) & 0xFF); |
|
||||
|
|
||||
// Next is the high word
|
|
||||
for (unsigned i = 0; i < 4; ++i) |
|
||||
binary_path.push_back((high >> (8 * i)) & 0xFF); |
|
||||
|
|
||||
return {binary_path}; |
|
||||
} |
|
||||
|
|
||||
ArchiveFactory_ExtSaveData::ArchiveFactory_ExtSaveData(const std::string& mount_location, |
|
||||
bool shared) |
|
||||
: shared(shared), mount_point(GetExtDataContainerPath(mount_location, shared)) { |
|
||||
LOG_DEBUG(Service_FS, "Directory %s set as base for ExtSaveData.", mount_point.c_str()); |
|
||||
} |
|
||||
|
|
||||
bool ArchiveFactory_ExtSaveData::Initialize() { |
|
||||
if (!FileUtil::CreateFullPath(mount_point)) { |
|
||||
LOG_ERROR(Service_FS, "Unable to create ExtSaveData base path."); |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_ExtSaveData::Open(const Path& path) { |
|
||||
std::string fullpath = GetExtSaveDataPath(mount_point, path) + "user/"; |
|
||||
if (!FileUtil::Exists(fullpath)) { |
|
||||
// TODO(Subv): Verify the archive behavior of SharedExtSaveData compared to ExtSaveData.
|
|
||||
// ExtSaveData seems to return FS_NotFound (120) when the archive doesn't exist.
|
|
||||
if (!shared) { |
|
||||
return ERR_NOT_FOUND_INVALID_STATE; |
|
||||
} else { |
|
||||
return ERR_NOT_FORMATTED; |
|
||||
} |
|
||||
} |
|
||||
auto archive = std::make_unique<ExtSaveDataArchive>(fullpath); |
|
||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveFactory_ExtSaveData::Format(const Path& path, |
|
||||
const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
// These folders are always created with the ExtSaveData
|
|
||||
std::string user_path = GetExtSaveDataPath(mount_point, path) + "user/"; |
|
||||
std::string boss_path = GetExtSaveDataPath(mount_point, path) + "boss/"; |
|
||||
FileUtil::CreateFullPath(user_path); |
|
||||
FileUtil::CreateFullPath(boss_path); |
|
||||
|
|
||||
// Write the format metadata
|
|
||||
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata"; |
|
||||
FileUtil::IOFile file(metadata_path, "wb"); |
|
||||
|
|
||||
if (!file.IsOpen()) { |
|
||||
// TODO(Subv): Find the correct error code
|
|
||||
return ResultCode(-1); |
|
||||
} |
|
||||
|
|
||||
file.WriteBytes(&format_info, sizeof(format_info)); |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_ExtSaveData::GetFormatInfo(const Path& path) const { |
|
||||
std::string metadata_path = GetExtSaveDataPath(mount_point, path) + "metadata"; |
|
||||
FileUtil::IOFile file(metadata_path, "rb"); |
|
||||
|
|
||||
if (!file.IsOpen()) { |
|
||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive"); |
|
||||
// TODO(Subv): Verify error code
|
|
||||
return ERR_NOT_FORMATTED; |
|
||||
} |
|
||||
|
|
||||
ArchiveFormatInfo info = {}; |
|
||||
file.ReadBytes(&info, sizeof(info)); |
|
||||
return MakeResult<ArchiveFormatInfo>(info); |
|
||||
} |
|
||||
|
|
||||
void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, const u8* icon_data, |
|
||||
size_t icon_size) { |
|
||||
std::string game_path = FileSys::GetExtSaveDataPath(GetMountPoint(), path); |
|
||||
FileUtil::IOFile icon_file(game_path + "icon", "wb"); |
|
||||
icon_file.WriteBytes(icon_data, icon_size); |
|
||||
} |
|
||||
|
|
||||
} // namespace FileSys
|
|
||||
@ -1,89 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include "common/common_types.h" |
|
||||
#include "core/file_sys/archive_backend.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
/// File system interface to the ExtSaveData archive |
|
||||
class ArchiveFactory_ExtSaveData final : public ArchiveFactory { |
|
||||
public: |
|
||||
ArchiveFactory_ExtSaveData(const std::string& mount_point, bool shared); |
|
||||
|
|
||||
/** |
|
||||
* Initialize the archive. |
|
||||
* @return true if it initialized successfully |
|
||||
*/ |
|
||||
bool Initialize(); |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "ExtSaveData"; |
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
|
|
||||
const std::string& GetMountPoint() const { |
|
||||
return mount_point; |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* Writes the SMDH icon of the ExtSaveData to file |
|
||||
* @param path Path of this ExtSaveData |
|
||||
* @param icon_data Binary data of the icon |
|
||||
* @param icon_size Size of the icon data |
|
||||
*/ |
|
||||
void WriteIcon(const Path& path, const u8* icon_data, size_t icon_size); |
|
||||
|
|
||||
private: |
|
||||
bool shared; ///< Whether this archive represents an ExtSaveData archive or a SharedExtSaveData |
|
||||
/// archive |
|
||||
|
|
||||
/** |
|
||||
* This holds the full directory path for this archive, it is only set after a successful call |
|
||||
* to Open, this is formed as `<base extsavedatapath>/<type>/<high>/<low>`. |
|
||||
* See GetExtSaveDataPath for the code that extracts this data from an archive path. |
|
||||
*/ |
|
||||
std::string mount_point; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Constructs a path to the concrete ExtData archive in the host filesystem based on the |
|
||||
* input Path and base mount point. |
|
||||
* @param mount_point The base mount point of the ExtSaveData archives. |
|
||||
* @param path The path that identifies the requested concrete ExtSaveData archive. |
|
||||
* @returns The complete path to the specified extdata archive in the host filesystem |
|
||||
*/ |
|
||||
std::string GetExtSaveDataPath(const std::string& mount_point, const Path& path); |
|
||||
|
|
||||
/** |
|
||||
* Constructs a path to the base folder to hold concrete ExtSaveData archives in the host file |
|
||||
* system. |
|
||||
* @param mount_point The base folder where this folder resides, ie. SDMC or NAND. |
|
||||
* @param shared Whether this ExtSaveData container is for SharedExtSaveDatas or not. |
|
||||
* @returns The path to the base ExtSaveData archives' folder in the host file system |
|
||||
*/ |
|
||||
std::string GetExtDataContainerPath(const std::string& mount_point, bool shared); |
|
||||
|
|
||||
/** |
|
||||
* Constructs a FileSys::Path object that refers to the ExtData archive identified by |
|
||||
* the specified media type, high save id and low save id. |
|
||||
* @param media_type The media type where the archive is located (NAND / SDMC) |
|
||||
* @param high The high word of the save id for the archive |
|
||||
* @param low The low word of the save id for the archive |
|
||||
* @returns A FileSys::Path to the wanted archive |
|
||||
*/ |
|
||||
Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low); |
|
||||
|
|
||||
} // namespace FileSys |
|
||||
@ -1,114 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <memory>
|
|
||||
#include <vector>
|
|
||||
#include "common/common_types.h"
|
|
||||
#include "common/file_util.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "common/string_util.h"
|
|
||||
#include "core/core.h"
|
|
||||
#include "core/file_sys/archive_ncch.h"
|
|
||||
#include "core/file_sys/errors.h"
|
|
||||
#include "core/file_sys/ivfc_archive.h"
|
|
||||
#include "core/file_sys/ncch_container.h"
|
|
||||
#include "core/file_sys/title_metadata.h"
|
|
||||
#include "core/hle/service/fs/archive.h"
|
|
||||
#include "core/loader/loader.h"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
static std::string GetNCCHContainerPath(const std::string& nand_directory) { |
|
||||
return Common::StringFromFormat("%s%s/title/", nand_directory.c_str(), SYSTEM_ID); |
|
||||
} |
|
||||
|
|
||||
static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) { |
|
||||
u32 content_id = 0; |
|
||||
|
|
||||
// TODO(shinyquagsire23): Title database should be doing this path lookup
|
|
||||
std::string content_path = |
|
||||
Common::StringFromFormat("%s%08x/%08x/content/", mount_point.c_str(), high, low); |
|
||||
std::string tmd_path = content_path + "00000000.tmd"; |
|
||||
TitleMetadata tmd(tmd_path); |
|
||||
if (tmd.Load() == Loader::ResultStatus::Success) { |
|
||||
content_id = tmd.GetBootContentID(); |
|
||||
} |
|
||||
|
|
||||
return Common::StringFromFormat("%s%08x.app", content_path.c_str(), content_id); |
|
||||
} |
|
||||
|
|
||||
ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory) |
|
||||
: mount_point(GetNCCHContainerPath(nand_directory)) {} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_NCCH::Open(const Path& path) { |
|
||||
auto vec = path.AsBinary(); |
|
||||
const u32* data = reinterpret_cast<u32*>(vec.data()); |
|
||||
u32 high = data[1]; |
|
||||
u32 low = data[0]; |
|
||||
std::string file_path = GetNCCHPath(mount_point, high, low); |
|
||||
|
|
||||
std::shared_ptr<FileUtil::IOFile> romfs_file; |
|
||||
u64 romfs_offset = 0; |
|
||||
u64 romfs_size = 0; |
|
||||
auto ncch_container = NCCHContainer(file_path); |
|
||||
|
|
||||
if (ncch_container.ReadRomFS(romfs_file, romfs_offset, romfs_size) != |
|
||||
Loader::ResultStatus::Success) { |
|
||||
// High Title ID of the archive: The category (https://3dbrew.org/wiki/Title_list).
|
|
||||
constexpr u32 shared_data_archive = 0x0004009B; |
|
||||
constexpr u32 system_data_archive = 0x000400DB; |
|
||||
|
|
||||
// Low Title IDs.
|
|
||||
constexpr u32 mii_data = 0x00010202; |
|
||||
constexpr u32 region_manifest = 0x00010402; |
|
||||
constexpr u32 ng_word_list = 0x00010302; |
|
||||
|
|
||||
LOG_DEBUG(Service_FS, "Full Path: %s. Category: 0x%X. Path: 0x%X.", path.DebugStr().c_str(), |
|
||||
high, low); |
|
||||
|
|
||||
if (high == shared_data_archive) { |
|
||||
if (low == mii_data) { |
|
||||
LOG_ERROR(Service_FS, "Failed to get a handle for shared data archive: Mii data. "); |
|
||||
Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSystemFiles, |
|
||||
"Mii data"); |
|
||||
} else if (low == region_manifest) { |
|
||||
LOG_ERROR(Service_FS, |
|
||||
"Failed to get a handle for shared data archive: region manifest."); |
|
||||
Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSystemFiles, |
|
||||
"Region manifest"); |
|
||||
} |
|
||||
} else if (high == system_data_archive) { |
|
||||
if (low == ng_word_list) { |
|
||||
LOG_ERROR(Service_FS, |
|
||||
"Failed to get a handle for system data archive: NG bad word list."); |
|
||||
Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSystemFiles, |
|
||||
"NG bad word list"); |
|
||||
} |
|
||||
} |
|
||||
return ERROR_NOT_FOUND; |
|
||||
} |
|
||||
|
|
||||
auto archive = std::make_unique<IVFCArchive>(romfs_file, romfs_offset, romfs_size); |
|
||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(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_NCCH::GetFormatInfo(const Path& path) const { |
|
||||
// TODO(Subv): Implement
|
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); |
|
||||
return ResultCode(-1); |
|
||||
} |
|
||||
|
|
||||
} // namespace FileSys
|
|
||||
@ -1,34 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include "core/file_sys/archive_backend.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
/// File system interface to the NCCH archive |
|
||||
class ArchiveFactory_NCCH final : public ArchiveFactory { |
|
||||
public: |
|
||||
explicit ArchiveFactory_NCCH(const std::string& mount_point); |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "NCCH"; |
|
||||
} |
|
||||
|
|
||||
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 mount_point; |
|
||||
}; |
|
||||
|
|
||||
} // namespace FileSys |
|
||||
@ -1,145 +0,0 @@ |
|||||
// Copyright 2016 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <tuple>
|
|
||||
#include "core/file_sys/archive_other_savedata.h"
|
|
||||
#include "core/file_sys/errors.h"
|
|
||||
#include "core/hle/kernel/process.h"
|
|
||||
#include "core/hle/service/fs/archive.h"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
// TODO(wwylele): The storage info in exheader should be checked before accessing these archives
|
|
||||
|
|
||||
using Service::FS::MediaType; |
|
||||
|
|
||||
namespace { |
|
||||
|
|
||||
template <typename T> |
|
||||
ResultVal<std::tuple<MediaType, u64>> ParsePath(const Path& path, T program_id_reader) { |
|
||||
if (path.GetType() != Binary) { |
|
||||
LOG_ERROR(Service_FS, "Wrong path type %d", static_cast<int>(path.GetType())); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
std::vector<u8> vec_data = path.AsBinary(); |
|
||||
|
|
||||
if (vec_data.size() != 12) { |
|
||||
LOG_ERROR(Service_FS, "Wrong path length %zu", vec_data.size()); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
const u32* data = reinterpret_cast<const u32*>(vec_data.data()); |
|
||||
auto media_type = static_cast<MediaType>(data[0]); |
|
||||
|
|
||||
if (media_type != MediaType::SDMC && media_type != MediaType::GameCard) { |
|
||||
LOG_ERROR(Service_FS, "Unsupported media type %u", static_cast<u32>(media_type)); |
|
||||
|
|
||||
// Note: this is strange, but the error code was verified with a real 3DS
|
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
return MakeResult<std::tuple<MediaType, u64>>(media_type, program_id_reader(data)); |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::tuple<MediaType, u64>> ParsePathPermitted(const Path& path) { |
|
||||
return ParsePath(path, |
|
||||
[](const u32* data) -> u64 { return (data[1] << 8) | 0x0004000000000000ULL; }); |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::tuple<MediaType, u64>> ParsePathGeneral(const Path& path) { |
|
||||
return ParsePath( |
|
||||
path, [](const u32* data) -> u64 { return data[1] | (static_cast<u64>(data[2]) << 32); }); |
|
||||
} |
|
||||
|
|
||||
} // namespace
|
|
||||
|
|
||||
ArchiveFactory_OtherSaveDataPermitted::ArchiveFactory_OtherSaveDataPermitted( |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata) |
|
||||
: sd_savedata_source(sd_savedata) {} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataPermitted::Open( |
|
||||
const Path& path) { |
|
||||
MediaType media_type; |
|
||||
u64 program_id; |
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathPermitted(path)); |
|
||||
|
|
||||
if (media_type == MediaType::GameCard) { |
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard"); |
|
||||
return ERROR_GAMECARD_NOT_INSERTED; |
|
||||
} |
|
||||
|
|
||||
return sd_savedata_source->Open(program_id); |
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveFactory_OtherSaveDataPermitted::Format( |
|
||||
const Path& path, const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
LOG_ERROR(Service_FS, "Attempted to format a OtherSaveDataPermitted archive."); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataPermitted::GetFormatInfo( |
|
||||
const Path& path) const { |
|
||||
MediaType media_type; |
|
||||
u64 program_id; |
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathPermitted(path)); |
|
||||
|
|
||||
if (media_type == MediaType::GameCard) { |
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard"); |
|
||||
return ERROR_GAMECARD_NOT_INSERTED; |
|
||||
} |
|
||||
|
|
||||
return sd_savedata_source->GetFormatInfo(program_id); |
|
||||
} |
|
||||
|
|
||||
ArchiveFactory_OtherSaveDataGeneral::ArchiveFactory_OtherSaveDataGeneral( |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata) |
|
||||
: sd_savedata_source(sd_savedata) {} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_OtherSaveDataGeneral::Open( |
|
||||
const Path& path) { |
|
||||
MediaType media_type; |
|
||||
u64 program_id; |
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path)); |
|
||||
|
|
||||
if (media_type == MediaType::GameCard) { |
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard"); |
|
||||
return ERROR_GAMECARD_NOT_INSERTED; |
|
||||
} |
|
||||
|
|
||||
return sd_savedata_source->Open(program_id); |
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveFactory_OtherSaveDataGeneral::Format( |
|
||||
const Path& path, const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
MediaType media_type; |
|
||||
u64 program_id; |
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path)); |
|
||||
|
|
||||
if (media_type == MediaType::GameCard) { |
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard"); |
|
||||
return ERROR_GAMECARD_NOT_INSERTED; |
|
||||
} |
|
||||
|
|
||||
return sd_savedata_source->Format(program_id, format_info); |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_OtherSaveDataGeneral::GetFormatInfo( |
|
||||
const Path& path) const { |
|
||||
MediaType media_type; |
|
||||
u64 program_id; |
|
||||
CASCADE_RESULT(std::tie(media_type, program_id), ParsePathGeneral(path)); |
|
||||
|
|
||||
if (media_type == MediaType::GameCard) { |
|
||||
LOG_WARNING(Service_FS, "(stubbed) Unimplemented media type GameCard"); |
|
||||
return ERROR_GAMECARD_NOT_INSERTED; |
|
||||
} |
|
||||
|
|
||||
return sd_savedata_source->GetFormatInfo(program_id); |
|
||||
} |
|
||||
|
|
||||
} // namespace FileSys
|
|
||||
@ -1,52 +0,0 @@ |
|||||
// 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_source_sd_savedata.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
/// File system interface to the OtherSaveDataPermitted archive |
|
||||
class ArchiveFactory_OtherSaveDataPermitted final : public ArchiveFactory { |
|
||||
public: |
|
||||
explicit ArchiveFactory_OtherSaveDataPermitted( |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source); |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "OtherSaveDataPermitted"; |
|
||||
} |
|
||||
|
|
||||
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 mount_point; |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source; |
|
||||
}; |
|
||||
|
|
||||
/// File system interface to the OtherSaveDataGeneral archive |
|
||||
class ArchiveFactory_OtherSaveDataGeneral final : public ArchiveFactory { |
|
||||
public: |
|
||||
explicit ArchiveFactory_OtherSaveDataGeneral( |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source); |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "OtherSaveDataGeneral"; |
|
||||
} |
|
||||
|
|
||||
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 mount_point; |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source; |
|
||||
}; |
|
||||
|
|
||||
} // namespace FileSys |
|
||||
@ -1,33 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/file_sys/archive_savedata.h"
|
|
||||
#include "core/hle/kernel/process.h"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
ArchiveFactory_SaveData::ArchiveFactory_SaveData( |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata) |
|
||||
: sd_savedata_source(sd_savedata) {} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SaveData::Open(const Path& path) { |
|
||||
UNIMPLEMENTED(); |
|
||||
return {}; //sd_savedata_source->Open(Kernel::g_current_process->codeset->program_id);
|
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveFactory_SaveData::Format(const Path& path, |
|
||||
const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
UNIMPLEMENTED(); |
|
||||
return RESULT_SUCCESS; //sd_savedata_source->Format(Kernel::g_current_process->codeset->program_id, format_info);
|
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SaveData::GetFormatInfo(const Path& path) const { |
|
||||
UNIMPLEMENTED(); |
|
||||
return {}; //sd_savedata_source->GetFormatInfo(Kernel::g_current_process->codeset->program_id);
|
|
||||
} |
|
||||
|
|
||||
} // namespace FileSys
|
|
||||
@ -1,33 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/file_sys/archive_source_sd_savedata.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
/// File system interface to the SaveData archive |
|
||||
class ArchiveFactory_SaveData final : public ArchiveFactory { |
|
||||
public: |
|
||||
explicit ArchiveFactory_SaveData(std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source); |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "SaveData"; |
|
||||
} |
|
||||
|
|
||||
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 mount_point; |
|
||||
std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source; |
|
||||
}; |
|
||||
|
|
||||
} // namespace FileSys |
|
||||
@ -1,379 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <memory>
|
|
||||
#include "common/file_util.h"
|
|
||||
#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"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
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; |
|
||||
case PathParser::FileFound: |
|
||||
break; // Expected 'success' case
|
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
case PathParser::FileFound: |
|
||||
break; // Expected 'success' case
|
|
||||
} |
|
||||
|
|
||||
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 { |
|
||||
const PathParser path_parser_src(src_path); |
|
||||
|
|
||||
// TODO: Verify these return codes with HW
|
|
||||
if (!path_parser_src.IsValid()) { |
|
||||
LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
const PathParser path_parser_dest(dest_path); |
|
||||
|
|
||||
if (!path_parser_dest.IsValid()) { |
|
||||
LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point); |
|
||||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); |
|
||||
|
|
||||
if (FileUtil::Rename(src_path_full, dest_path_full)) { |
|
||||
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; |
|
||||
case PathParser::DirectoryFound: |
|
||||
break; // Expected 'success' case
|
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
case PathParser::NotFound: |
|
||||
break; // Expected 'success' case
|
|
||||
} |
|
||||
|
|
||||
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; |
|
||||
case PathParser::NotFound: |
|
||||
break; // Expected 'success' case
|
|
||||
} |
|
||||
|
|
||||
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 { |
|
||||
const PathParser path_parser_src(src_path); |
|
||||
|
|
||||
// TODO: Verify these return codes with HW
|
|
||||
if (!path_parser_src.IsValid()) { |
|
||||
LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
const PathParser path_parser_dest(dest_path); |
|
||||
|
|
||||
if (!path_parser_dest.IsValid()) { |
|
||||
LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
const auto src_path_full = path_parser_src.BuildHostPath(mount_point); |
|
||||
const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); |
|
||||
|
|
||||
if (FileUtil::Rename(src_path_full, dest_path_full)) { |
|
||||
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; |
|
||||
case PathParser::DirectoryFound: |
|
||||
break; // Expected 'success' case
|
|
||||
} |
|
||||
|
|
||||
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_DEBUG(Service_FS, "Directory %s set as SDMC.", sdmc_directory.c_str()); |
|
||||
} |
|
||||
|
|
||||
bool ArchiveFactory_SDMC::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_SDMC::Open(const Path& path) { |
|
||||
auto archive = std::make_unique<SDMCArchive>(sdmc_directory); |
|
||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveFactory_SDMC::Format(const Path& path, |
|
||||
const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
// This is kind of an undesirable operation, so let's just ignore it. :)
|
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMC::GetFormatInfo(const Path& path) const { |
|
||||
// TODO(Subv): Implement
|
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); |
|
||||
return ResultCode(-1); |
|
||||
} |
|
||||
} // namespace FileSys
|
|
||||
@ -1,66 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include "core/file_sys/archive_backend.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
/// Archive backend for SDMC archive |
|
||||
class SDMCArchive : public ArchiveBackend { |
|
||||
public: |
|
||||
explicit 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: |
|
||||
explicit ArchiveFactory_SDMC(const std::string& mount_point); |
|
||||
|
|
||||
/** |
|
||||
* Initialize the archive. |
|
||||
* @return true if it initialized successfully |
|
||||
*/ |
|
||||
bool Initialize(); |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "SDMC"; |
|
||||
} |
|
||||
|
|
||||
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 |
|
||||
@ -1,70 +0,0 @@ |
|||||
// 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_DEBUG(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
|
|
||||
@ -1,57 +0,0 @@ |
|||||
// 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: |
|
||||
explicit 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: |
|
||||
explicit 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 |
|
||||
@ -1,297 +0,0 @@ |
|||||
// Copyright 2017 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <array>
|
|
||||
#include <cinttypes>
|
|
||||
#include "common/common_types.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "common/swap.h"
|
|
||||
#include "core/file_sys/archive_selfncch.h"
|
|
||||
#include "core/file_sys/errors.h"
|
|
||||
#include "core/file_sys/ivfc_archive.h"
|
|
||||
#include "core/hle/kernel/process.h"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
enum class SelfNCCHFilePathType : u32 { |
|
||||
RomFS = 0, |
|
||||
Code = 1, // This is not supported by SelfNCCHArchive but by archive 0x2345678E
|
|
||||
ExeFS = 2, |
|
||||
UpdateRomFS = 5, // This is presumably for accessing the RomFS of the update patch.
|
|
||||
}; |
|
||||
|
|
||||
struct SelfNCCHFilePath { |
|
||||
u32_le type; |
|
||||
std::array<char, 8> exefs_filename; |
|
||||
}; |
|
||||
static_assert(sizeof(SelfNCCHFilePath) == 12, "NCCHFilePath has wrong size!"); |
|
||||
|
|
||||
// A read-only file created from a block of data. It only allows you to read the entire file at
|
|
||||
// once, in a single read operation.
|
|
||||
class ExeFSSectionFile final : public FileBackend { |
|
||||
public: |
|
||||
explicit ExeFSSectionFile(std::shared_ptr<std::vector<u8>> data_) : data(std::move(data_)) {} |
|
||||
|
|
||||
ResultVal<size_t> Read(u64 offset, size_t length, u8* buffer) const override { |
|
||||
if (offset != 0) { |
|
||||
LOG_ERROR(Service_FS, "offset must be zero!"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
if (length != data->size()) { |
|
||||
LOG_ERROR(Service_FS, "size must match the file size!"); |
|
||||
return ERROR_INCORRECT_EXEFS_READ_SIZE; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(buffer, data->data(), data->size()); |
|
||||
return MakeResult<size_t>(data->size()); |
|
||||
} |
|
||||
|
|
||||
ResultVal<size_t> Write(u64 offset, size_t length, bool flush, |
|
||||
const u8* buffer) const override { |
|
||||
LOG_ERROR(Service_FS, "The file is read-only!"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
u64 GetSize() const override { |
|
||||
return data->size(); |
|
||||
} |
|
||||
|
|
||||
bool SetSize(u64 size) const override { |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
bool Close() const override { |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
void Flush() const override {} |
|
||||
|
|
||||
private: |
|
||||
std::shared_ptr<std::vector<u8>> data; |
|
||||
}; |
|
||||
|
|
||||
// SelfNCCHArchive represents the running application itself. From this archive the application can
|
|
||||
// open RomFS and ExeFS, excluding the .code section.
|
|
||||
class SelfNCCHArchive final : public ArchiveBackend { |
|
||||
public: |
|
||||
explicit SelfNCCHArchive(const NCCHData& ncch_data_) : ncch_data(ncch_data_) {} |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "SelfNCCHArchive"; |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenFile(const Path& path, const Mode&) const override { |
|
||||
// Note: SelfNCCHArchive doesn't check the open mode.
|
|
||||
|
|
||||
if (path.GetType() != LowPathType::Binary) { |
|
||||
LOG_ERROR(Service_FS, "Path need to be Binary"); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
std::vector<u8> binary = path.AsBinary(); |
|
||||
if (binary.size() != sizeof(SelfNCCHFilePath)) { |
|
||||
LOG_ERROR(Service_FS, "Wrong path size %zu", binary.size()); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
SelfNCCHFilePath file_path; |
|
||||
std::memcpy(&file_path, binary.data(), sizeof(SelfNCCHFilePath)); |
|
||||
|
|
||||
switch (static_cast<SelfNCCHFilePathType>(file_path.type)) { |
|
||||
case SelfNCCHFilePathType::UpdateRomFS: |
|
||||
return OpenUpdateRomFS(); |
|
||||
|
|
||||
case SelfNCCHFilePathType::RomFS: |
|
||||
return OpenRomFS(); |
|
||||
|
|
||||
case SelfNCCHFilePathType::Code: |
|
||||
LOG_ERROR(Service_FS, "Reading the code section is not supported!"); |
|
||||
return ERROR_COMMAND_NOT_ALLOWED; |
|
||||
|
|
||||
case SelfNCCHFilePathType::ExeFS: { |
|
||||
const auto& raw = file_path.exefs_filename; |
|
||||
auto end = std::find(raw.begin(), raw.end(), '\0'); |
|
||||
std::string filename(raw.begin(), end); |
|
||||
return OpenExeFS(filename); |
|
||||
} |
|
||||
default: |
|
||||
LOG_ERROR(Service_FS, "Unknown file type %u!", static_cast<u32>(file_path.type)); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteFile(const Path& path) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
ResultCode RenameFile(const Path& src_path, const Path& dest_path) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteDirectory(const Path& path) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteDirectoryRecursively(const Path& path) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
ResultCode CreateFile(const Path& path, u64 size) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
ResultCode CreateDirectory(const Path& path) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override { |
|
||||
LOG_ERROR(Service_FS, "Unsupported"); |
|
||||
return ERROR_UNSUPPORTED_OPEN_FLAGS; |
|
||||
} |
|
||||
|
|
||||
u64 GetFreeBytes() const override { |
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenRomFS() const { |
|
||||
if (ncch_data.romfs_file) { |
|
||||
return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>( |
|
||||
ncch_data.romfs_file, ncch_data.romfs_offset, ncch_data.romfs_size)); |
|
||||
} else { |
|
||||
LOG_INFO(Service_FS, "Unable to read RomFS"); |
|
||||
return ERROR_ROMFS_NOT_FOUND; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenUpdateRomFS() const { |
|
||||
if (ncch_data.update_romfs_file) { |
|
||||
return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>( |
|
||||
ncch_data.update_romfs_file, ncch_data.update_romfs_offset, |
|
||||
ncch_data.update_romfs_size)); |
|
||||
} else { |
|
||||
LOG_INFO(Service_FS, "Unable to read update RomFS"); |
|
||||
return ERROR_ROMFS_NOT_FOUND; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const { |
|
||||
if (filename == "icon") { |
|
||||
if (ncch_data.icon) { |
|
||||
return MakeResult<std::unique_ptr<FileBackend>>( |
|
||||
std::make_unique<ExeFSSectionFile>(ncch_data.icon)); |
|
||||
} |
|
||||
|
|
||||
LOG_WARNING(Service_FS, "Unable to read icon"); |
|
||||
return ERROR_EXEFS_SECTION_NOT_FOUND; |
|
||||
} |
|
||||
|
|
||||
if (filename == "logo") { |
|
||||
if (ncch_data.logo) { |
|
||||
return MakeResult<std::unique_ptr<FileBackend>>( |
|
||||
std::make_unique<ExeFSSectionFile>(ncch_data.logo)); |
|
||||
} |
|
||||
|
|
||||
LOG_WARNING(Service_FS, "Unable to read logo"); |
|
||||
return ERROR_EXEFS_SECTION_NOT_FOUND; |
|
||||
} |
|
||||
|
|
||||
if (filename == "banner") { |
|
||||
if (ncch_data.banner) { |
|
||||
return MakeResult<std::unique_ptr<FileBackend>>( |
|
||||
std::make_unique<ExeFSSectionFile>(ncch_data.banner)); |
|
||||
} |
|
||||
|
|
||||
LOG_WARNING(Service_FS, "Unable to read banner"); |
|
||||
return ERROR_EXEFS_SECTION_NOT_FOUND; |
|
||||
} |
|
||||
|
|
||||
LOG_ERROR(Service_FS, "Unknown ExeFS section %s!", filename.c_str()); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
NCCHData ncch_data; |
|
||||
}; |
|
||||
|
|
||||
void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) { |
|
||||
u64 program_id = 0; |
|
||||
if (app_loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) { |
|
||||
LOG_WARNING( |
|
||||
Service_FS, |
|
||||
"Could not read program id when registering with SelfNCCH, this might be a 3dsx file"); |
|
||||
} |
|
||||
|
|
||||
LOG_DEBUG(Service_FS, "Registering program %016" PRIX64 " with the SelfNCCH archive factory", |
|
||||
program_id); |
|
||||
|
|
||||
if (ncch_data.find(program_id) != ncch_data.end()) { |
|
||||
LOG_WARNING(Service_FS, "Registering program %016" PRIX64 |
|
||||
" with SelfNCCH will override existing mapping", |
|
||||
program_id); |
|
||||
} |
|
||||
|
|
||||
NCCHData& data = ncch_data[program_id]; |
|
||||
|
|
||||
std::shared_ptr<FileUtil::IOFile> romfs_file_; |
|
||||
if (Loader::ResultStatus::Success == |
|
||||
app_loader.ReadRomFS(romfs_file_, data.romfs_offset, data.romfs_size)) { |
|
||||
|
|
||||
data.romfs_file = std::move(romfs_file_); |
|
||||
} |
|
||||
|
|
||||
std::shared_ptr<FileUtil::IOFile> update_romfs_file; |
|
||||
if (Loader::ResultStatus::Success == |
|
||||
app_loader.ReadUpdateRomFS(update_romfs_file, data.update_romfs_offset, |
|
||||
data.update_romfs_size)) { |
|
||||
|
|
||||
data.update_romfs_file = std::move(update_romfs_file); |
|
||||
} |
|
||||
|
|
||||
std::vector<u8> buffer; |
|
||||
|
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer)) |
|
||||
data.icon = std::make_shared<std::vector<u8>>(std::move(buffer)); |
|
||||
|
|
||||
buffer.clear(); |
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer)) |
|
||||
data.logo = std::make_shared<std::vector<u8>>(std::move(buffer)); |
|
||||
|
|
||||
buffer.clear(); |
|
||||
if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer)) |
|
||||
data.banner = std::make_shared<std::vector<u8>>(std::move(buffer)); |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) { |
|
||||
//auto archive = std::make_unique<SelfNCCHArchive>(
|
|
||||
// ncch_data[Kernel::g_current_process->codeset->program_id]);
|
|
||||
//return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive));
|
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveFactory_SelfNCCH::Format(const Path&, const FileSys::ArchiveFormatInfo&) { |
|
||||
LOG_ERROR(Service_FS, "Attempted to format a SelfNCCH archive."); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SelfNCCH::GetFormatInfo(const Path&) const { |
|
||||
LOG_ERROR(Service_FS, "Attempted to get format info of a SelfNCCH archive"); |
|
||||
return ERROR_INVALID_PATH; |
|
||||
} |
|
||||
|
|
||||
} // namespace FileSys
|
|
||||
@ -1,54 +0,0 @@ |
|||||
// Copyright 2017 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include <unordered_map> |
|
||||
#include <vector> |
|
||||
#include "common/common_types.h" |
|
||||
#include "core/file_sys/archive_backend.h" |
|
||||
#include "core/hle/result.h" |
|
||||
#include "core/loader/loader.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
struct NCCHData { |
|
||||
std::shared_ptr<std::vector<u8>> icon; |
|
||||
std::shared_ptr<std::vector<u8>> logo; |
|
||||
std::shared_ptr<std::vector<u8>> banner; |
|
||||
std::shared_ptr<FileUtil::IOFile> romfs_file; |
|
||||
u64 romfs_offset = 0; |
|
||||
u64 romfs_size = 0; |
|
||||
|
|
||||
std::shared_ptr<FileUtil::IOFile> update_romfs_file; |
|
||||
u64 update_romfs_offset = 0; |
|
||||
u64 update_romfs_size = 0; |
|
||||
}; |
|
||||
|
|
||||
/// File system interface to the SelfNCCH archive |
|
||||
class ArchiveFactory_SelfNCCH final : public ArchiveFactory { |
|
||||
public: |
|
||||
ArchiveFactory_SelfNCCH() = default; |
|
||||
|
|
||||
/// Registers a loaded application so that we can open its SelfNCCH archive when requested. |
|
||||
void Register(Loader::AppLoader& app_loader); |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "SelfNCCH"; |
|
||||
} |
|
||||
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: |
|
||||
/// Mapping of ProgramId -> NCCHData |
|
||||
std::unordered_map<u64, NCCHData> ncch_data; |
|
||||
}; |
|
||||
|
|
||||
} // namespace FileSys |
|
||||
@ -1,97 +0,0 @@ |
|||||
// 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 "common/logging/log.h"
|
|
||||
#include "common/string_util.h"
|
|
||||
#include "core/file_sys/archive_source_sd_savedata.h"
|
|
||||
#include "core/file_sys/errors.h"
|
|
||||
#include "core/file_sys/savedata_archive.h"
|
|
||||
#include "core/hle/service/fs/archive.h"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
namespace { |
|
||||
|
|
||||
std::string GetSaveDataContainerPath(const std::string& sdmc_directory) { |
|
||||
return Common::StringFromFormat("%sNintendo 3DS/%s/%s/title/", sdmc_directory.c_str(), |
|
||||
SYSTEM_ID, SDCARD_ID); |
|
||||
} |
|
||||
|
|
||||
std::string GetSaveDataPath(const std::string& mount_location, u64 program_id) { |
|
||||
u32 high = static_cast<u32>(program_id >> 32); |
|
||||
u32 low = static_cast<u32>(program_id & 0xFFFFFFFF); |
|
||||
return Common::StringFromFormat("%s%08x/%08x/data/00000001/", mount_location.c_str(), high, |
|
||||
low); |
|
||||
} |
|
||||
|
|
||||
std::string GetSaveDataMetadataPath(const std::string& mount_location, u64 program_id) { |
|
||||
u32 high = static_cast<u32>(program_id >> 32); |
|
||||
u32 low = static_cast<u32>(program_id & 0xFFFFFFFF); |
|
||||
return Common::StringFromFormat("%s%08x/%08x/data/00000001.metadata", mount_location.c_str(), |
|
||||
high, low); |
|
||||
} |
|
||||
|
|
||||
} // namespace
|
|
||||
|
|
||||
ArchiveSource_SDSaveData::ArchiveSource_SDSaveData(const std::string& sdmc_directory) |
|
||||
: mount_point(GetSaveDataContainerPath(sdmc_directory)) { |
|
||||
LOG_DEBUG(Service_FS, "Directory %s set as SaveData.", mount_point.c_str()); |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveSource_SDSaveData::Open(u64 program_id) { |
|
||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); |
|
||||
if (!FileUtil::Exists(concrete_mount_point)) { |
|
||||
// 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.
|
|
||||
return ERR_NOT_FORMATTED; |
|
||||
} |
|
||||
|
|
||||
auto archive = std::make_unique<SaveDataArchive>(std::move(concrete_mount_point)); |
|
||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveSource_SDSaveData::Format(u64 program_id, |
|
||||
const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
std::string concrete_mount_point = GetSaveDataPath(mount_point, program_id); |
|
||||
FileUtil::DeleteDirRecursively(concrete_mount_point); |
|
||||
FileUtil::CreateFullPath(concrete_mount_point); |
|
||||
|
|
||||
// Write the format metadata
|
|
||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); |
|
||||
FileUtil::IOFile file(metadata_path, "wb"); |
|
||||
|
|
||||
if (file.IsOpen()) { |
|
||||
file.WriteBytes(&format_info, sizeof(format_info)); |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveSource_SDSaveData::GetFormatInfo(u64 program_id) const { |
|
||||
std::string metadata_path = GetSaveDataMetadataPath(mount_point, program_id); |
|
||||
FileUtil::IOFile file(metadata_path, "rb"); |
|
||||
|
|
||||
if (!file.IsOpen()) { |
|
||||
LOG_ERROR(Service_FS, "Could not open metadata information for archive"); |
|
||||
// TODO(Subv): Verify error code
|
|
||||
return ERR_NOT_FORMATTED; |
|
||||
} |
|
||||
|
|
||||
ArchiveFormatInfo info = {}; |
|
||||
file.ReadBytes(&info, sizeof(info)); |
|
||||
return MakeResult<ArchiveFormatInfo>(info); |
|
||||
} |
|
||||
|
|
||||
std::string ArchiveSource_SDSaveData::GetSaveDataPathFor(const std::string& mount_point, |
|
||||
u64 program_id) { |
|
||||
return GetSaveDataPath(GetSaveDataContainerPath(mount_point), program_id); |
|
||||
} |
|
||||
|
|
||||
} // namespace FileSys
|
|
||||
@ -1,32 +0,0 @@ |
|||||
// Copyright 2016 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include "core/file_sys/archive_backend.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
/// A common source of SD save data archive |
|
||||
class ArchiveSource_SDSaveData { |
|
||||
public: |
|
||||
explicit ArchiveSource_SDSaveData(const std::string& mount_point); |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> Open(u64 program_id); |
|
||||
ResultCode Format(u64 program_id, const FileSys::ArchiveFormatInfo& format_info); |
|
||||
ResultVal<ArchiveFormatInfo> GetFormatInfo(u64 program_id) const; |
|
||||
|
|
||||
static std::string GetSaveDataPathFor(const std::string& mount_point, u64 program_id); |
|
||||
|
|
||||
private: |
|
||||
std::string mount_point; |
|
||||
}; |
|
||||
|
|
||||
} // namespace FileSys |
|
||||
@ -1,77 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <memory>
|
|
||||
#include <vector>
|
|
||||
#include "common/common_types.h"
|
|
||||
#include "common/file_util.h"
|
|
||||
#include "common/string_util.h"
|
|
||||
#include "core/file_sys/archive_systemsavedata.h"
|
|
||||
#include "core/file_sys/errors.h"
|
|
||||
#include "core/file_sys/savedata_archive.h"
|
|
||||
#include "core/hle/service/fs/archive.h"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// FileSys namespace
|
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
std::string GetSystemSaveDataPath(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()); |
|
||||
u32 save_low = data[1]; |
|
||||
u32 save_high = data[0]; |
|
||||
return Common::StringFromFormat("%s%08X/%08X/", mount_point.c_str(), save_low, save_high); |
|
||||
} |
|
||||
|
|
||||
std::string GetSystemSaveDataContainerPath(const std::string& mount_point) { |
|
||||
return Common::StringFromFormat("%sdata/%s/sysdata/", mount_point.c_str(), SYSTEM_ID); |
|
||||
} |
|
||||
|
|
||||
Path ConstructSystemSaveDataBinaryPath(u32 high, u32 low) { |
|
||||
std::vector<u8> binary_path; |
|
||||
binary_path.reserve(8); |
|
||||
|
|
||||
// Append each word byte by byte
|
|
||||
|
|
||||
// First is the high word
|
|
||||
for (unsigned i = 0; i < 4; ++i) |
|
||||
binary_path.push_back((high >> (8 * i)) & 0xFF); |
|
||||
|
|
||||
// Next is the low word
|
|
||||
for (unsigned i = 0; i < 4; ++i) |
|
||||
binary_path.push_back((low >> (8 * i)) & 0xFF); |
|
||||
|
|
||||
return {binary_path}; |
|
||||
} |
|
||||
|
|
||||
ArchiveFactory_SystemSaveData::ArchiveFactory_SystemSaveData(const std::string& nand_path) |
|
||||
: base_path(GetSystemSaveDataContainerPath(nand_path)) {} |
|
||||
|
|
||||
ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SystemSaveData::Open(const Path& path) { |
|
||||
std::string fullpath = GetSystemSaveDataPath(base_path, path); |
|
||||
if (!FileUtil::Exists(fullpath)) { |
|
||||
// TODO(Subv): Check error code, this one is probably wrong
|
|
||||
return ERR_NOT_FORMATTED; |
|
||||
} |
|
||||
auto archive = std::make_unique<SaveDataArchive>(fullpath); |
|
||||
return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
|
||||
} |
|
||||
|
|
||||
ResultCode ArchiveFactory_SystemSaveData::Format(const Path& path, |
|
||||
const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
std::string fullpath = GetSystemSaveDataPath(base_path, path); |
|
||||
FileUtil::DeleteDirRecursively(fullpath); |
|
||||
FileUtil::CreateFullPath(fullpath); |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveFormatInfo> ArchiveFactory_SystemSaveData::GetFormatInfo(const Path& path) const { |
|
||||
// TODO(Subv): Implement
|
|
||||
LOG_ERROR(Service_FS, "Unimplemented GetFormatInfo archive %s", GetName().c_str()); |
|
||||
return ResultCode(-1); |
|
||||
} |
|
||||
|
|
||||
} // namespace FileSys
|
|
||||
@ -1,61 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include "common/common_types.h" |
|
||||
#include "core/file_sys/archive_backend.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// FileSys namespace |
|
||||
|
|
||||
namespace FileSys { |
|
||||
|
|
||||
/// File system interface to the SystemSaveData archive |
|
||||
class ArchiveFactory_SystemSaveData final : public ArchiveFactory { |
|
||||
public: |
|
||||
explicit ArchiveFactory_SystemSaveData(const std::string& mount_point); |
|
||||
|
|
||||
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; |
|
||||
|
|
||||
std::string GetName() const override { |
|
||||
return "SystemSaveData"; |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
std::string base_path; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Constructs a path to the concrete SystemSaveData archive in the host filesystem based on the |
|
||||
* input Path and base mount point. |
|
||||
* @param mount_point The base mount point of the SystemSaveData archives. |
|
||||
* @param path The path that identifies the requested concrete SystemSaveData archive. |
|
||||
* @returns The complete path to the specified SystemSaveData archive in the host filesystem |
|
||||
*/ |
|
||||
std::string GetSystemSaveDataPath(const std::string& mount_point, const Path& path); |
|
||||
|
|
||||
/** |
|
||||
* Constructs a path to the base folder to hold concrete SystemSaveData archives in the host file |
|
||||
* system. |
|
||||
* @param mount_point The base folder where this folder resides, ie. SDMC or NAND. |
|
||||
* @returns The path to the base SystemSaveData archives' folder in the host file system |
|
||||
*/ |
|
||||
std::string GetSystemSaveDataContainerPath(const std::string& mount_point); |
|
||||
|
|
||||
/** |
|
||||
* Constructs a FileSys::Path object that refers to the SystemSaveData archive identified by |
|
||||
* the specified high save id and low save id. |
|
||||
* @param high The high word of the save id for the archive |
|
||||
* @param low The low word of the save id for the archive |
|
||||
* @returns A FileSys::Path to the wanted archive |
|
||||
*/ |
|
||||
Path ConstructSystemSaveDataBinaryPath(u32 high, u32 low); |
|
||||
|
|
||||
} // namespace FileSys |
|
||||
@ -1,716 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <array>
|
|
||||
#include <cryptopp/osrng.h>
|
|
||||
#include <cryptopp/sha.h>
|
|
||||
#include "common/file_util.h"
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "common/string_util.h"
|
|
||||
#include "common/swap.h"
|
|
||||
#include "core/file_sys/archive_systemsavedata.h"
|
|
||||
#include "core/file_sys/errors.h"
|
|
||||
#include "core/file_sys/file_backend.h"
|
|
||||
#include "core/hle/ipc.h"
|
|
||||
#include "core/hle/ipc_helpers.h"
|
|
||||
#include "core/hle/result.h"
|
|
||||
#include "core/hle/service/cfg/cfg.h"
|
|
||||
#include "core/hle/service/cfg/cfg_i.h"
|
|
||||
#include "core/hle/service/cfg/cfg_nor.h"
|
|
||||
#include "core/hle/service/cfg/cfg_s.h"
|
|
||||
#include "core/hle/service/cfg/cfg_u.h"
|
|
||||
#include "core/hle/service/fs/archive.h"
|
|
||||
#include "core/hle/service/service.h"
|
|
||||
#include "core/memory.h"
|
|
||||
#include "core/settings.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
/// The maximum number of block entries that can exist in the config file
|
|
||||
static const u32 CONFIG_FILE_MAX_BLOCK_ENTRIES = 1479; |
|
||||
|
|
||||
namespace { |
|
||||
|
|
||||
/**
|
|
||||
* The header of the config savedata file, |
|
||||
* contains information about the blocks in the file |
|
||||
*/ |
|
||||
struct SaveFileConfig { |
|
||||
u16 total_entries; ///< The total number of set entries in the config file
|
|
||||
u16 data_entries_offset; ///< The offset where the data for the blocks start, this is hardcoded
|
|
||||
/// to 0x455C as per hardware
|
|
||||
SaveConfigBlockEntry block_entries[CONFIG_FILE_MAX_BLOCK_ENTRIES]; ///< The block headers, the
|
|
||||
/// maximum possible value is
|
|
||||
/// 1479 as per hardware
|
|
||||
u32 unknown; ///< This field is unknown, possibly padding, 0 has been observed in hardware
|
|
||||
}; |
|
||||
static_assert(sizeof(SaveFileConfig) == 0x455C, |
|
||||
"SaveFileConfig header must be exactly 0x455C bytes"); |
|
||||
|
|
||||
enum ConfigBlockID { |
|
||||
StereoCameraSettingsBlockID = 0x00050005, |
|
||||
SoundOutputModeBlockID = 0x00070001, |
|
||||
ConsoleUniqueID1BlockID = 0x00090000, |
|
||||
ConsoleUniqueID2BlockID = 0x00090001, |
|
||||
ConsoleUniqueID3BlockID = 0x00090002, |
|
||||
UsernameBlockID = 0x000A0000, |
|
||||
BirthdayBlockID = 0x000A0001, |
|
||||
LanguageBlockID = 0x000A0002, |
|
||||
CountryInfoBlockID = 0x000B0000, |
|
||||
CountryNameBlockID = 0x000B0001, |
|
||||
StateNameBlockID = 0x000B0002, |
|
||||
EULAVersionBlockID = 0x000D0000, |
|
||||
ConsoleModelBlockID = 0x000F0004, |
|
||||
}; |
|
||||
|
|
||||
struct UsernameBlock { |
|
||||
char16_t username[10]; ///< Exactly 20 bytes long, padded with zeros at the end if necessary
|
|
||||
u32 zero; |
|
||||
u32 ng_word; |
|
||||
}; |
|
||||
static_assert(sizeof(UsernameBlock) == 0x1C, "UsernameBlock must be exactly 0x1C bytes"); |
|
||||
|
|
||||
struct BirthdayBlock { |
|
||||
u8 month; ///< The month of the birthday
|
|
||||
u8 day; ///< The day of the birthday
|
|
||||
}; |
|
||||
static_assert(sizeof(BirthdayBlock) == 2, "BirthdayBlock must be exactly 2 bytes"); |
|
||||
|
|
||||
struct ConsoleModelInfo { |
|
||||
u8 model; ///< The console model (3DS, 2DS, etc)
|
|
||||
u8 unknown[3]; ///< Unknown data
|
|
||||
}; |
|
||||
static_assert(sizeof(ConsoleModelInfo) == 4, "ConsoleModelInfo must be exactly 4 bytes"); |
|
||||
|
|
||||
struct ConsoleCountryInfo { |
|
||||
u8 unknown[3]; ///< Unknown data
|
|
||||
u8 country_code; ///< The country code of the console
|
|
||||
}; |
|
||||
static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes"); |
|
||||
} |
|
||||
|
|
||||
static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}}; |
|
||||
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; |
|
||||
static const UsernameBlock CONSOLE_USERNAME_BLOCK = {u"CITRA", 0, 0}; |
|
||||
static const BirthdayBlock PROFILE_BIRTHDAY = {3, 25}; // March 25th, 2014
|
|
||||
static const u8 SOUND_OUTPUT_MODE = SOUND_SURROUND; |
|
||||
static const u8 UNITED_STATES_COUNTRY_ID = 49; |
|
||||
/// TODO(Subv): Find what the other bytes are
|
|
||||
static const ConsoleCountryInfo COUNTRY_INFO = {{0, 0, 0}, UNITED_STATES_COUNTRY_ID}; |
|
||||
|
|
||||
/**
|
|
||||
* TODO(Subv): Find out what this actually is, these values fix some NaN uniforms in some games, |
|
||||
* for example Nintendo Zone |
|
||||
* Thanks Normmatt for providing this information |
|
||||
*/ |
|
||||
static const std::array<float, 8> STEREO_CAMERA_SETTINGS = { |
|
||||
62.0f, 289.0f, 76.80000305175781f, 46.08000183105469f, |
|
||||
10.0f, 5.0f, 55.58000183105469f, 21.56999969482422f, |
|
||||
}; |
|
||||
static_assert(sizeof(STEREO_CAMERA_SETTINGS) == 0x20, |
|
||||
"STEREO_CAMERA_SETTINGS must be exactly 0x20 bytes"); |
|
||||
|
|
||||
static const u32 CONFIG_SAVEFILE_SIZE = 0x8000; |
|
||||
static std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer; |
|
||||
|
|
||||
static Service::FS::ArchiveHandle cfg_system_save_data_archive; |
|
||||
static const std::vector<u8> cfg_system_savedata_id = { |
|
||||
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, |
|
||||
}; |
|
||||
|
|
||||
static u32 preferred_region_code = 0; |
|
||||
|
|
||||
void GetCountryCodeString(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
u32 country_code_id = cmd_buff[1]; |
|
||||
|
|
||||
if (country_code_id >= country_codes.size() || 0 == country_codes[country_code_id]) { |
|
||||
LOG_ERROR(Service_CFG, "requested country code id=%d is invalid", country_code_id); |
|
||||
cmd_buff[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, |
|
||||
ErrorSummary::WrongArgument, ErrorLevel::Permanent) |
|
||||
.raw; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
cmd_buff[1] = 0; |
|
||||
cmd_buff[2] = country_codes[country_code_id]; |
|
||||
} |
|
||||
|
|
||||
void GetCountryCodeID(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
u16 country_code = static_cast<u16>(cmd_buff[1]); |
|
||||
u16 country_code_id = 0; |
|
||||
|
|
||||
// The following algorithm will fail if the first country code isn't 0.
|
|
||||
DEBUG_ASSERT(country_codes[0] == 0); |
|
||||
|
|
||||
for (u16 id = 0; id < country_codes.size(); ++id) { |
|
||||
if (country_codes[id] == country_code) { |
|
||||
country_code_id = id; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (0 == country_code_id) { |
|
||||
LOG_ERROR(Service_CFG, "requested country code name=%c%c is invalid", country_code & 0xff, |
|
||||
country_code >> 8); |
|
||||
cmd_buff[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, |
|
||||
ErrorSummary::WrongArgument, ErrorLevel::Permanent) |
|
||||
.raw; |
|
||||
cmd_buff[2] = 0xFFFF; |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
cmd_buff[1] = 0; |
|
||||
cmd_buff[2] = country_code_id; |
|
||||
} |
|
||||
|
|
||||
u32 GetRegionValue() { |
|
||||
if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) |
|
||||
return preferred_region_code; |
|
||||
|
|
||||
return Settings::values.region_value; |
|
||||
} |
|
||||
|
|
||||
void SecureInfoGetRegion(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
|
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; |
|
||||
cmd_buff[2] = GetRegionValue(); |
|
||||
} |
|
||||
|
|
||||
void GenHashConsoleUnique(Service::Interface* self) { |
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); |
|
||||
const u32 app_id_salt = rp.Pop<u32>() & 0x000FFFFF; |
|
||||
|
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); |
|
||||
|
|
||||
std::array<u8, 12> buffer; |
|
||||
const ResultCode result = GetConfigInfoBlock(ConsoleUniqueID2BlockID, 8, 2, buffer.data()); |
|
||||
rb.Push(result); |
|
||||
if (result.IsSuccess()) { |
|
||||
std::memcpy(&buffer[8], &app_id_salt, sizeof(u32)); |
|
||||
std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash; |
|
||||
CryptoPP::SHA256().CalculateDigest(hash.data(), buffer.data(), sizeof(buffer)); |
|
||||
u32 low, high; |
|
||||
memcpy(&low, &hash[hash.size() - 8], sizeof(u32)); |
|
||||
memcpy(&high, &hash[hash.size() - 4], sizeof(u32)); |
|
||||
rb.Push(low); |
|
||||
rb.Push(high); |
|
||||
} else { |
|
||||
rb.Push<u32>(0); |
|
||||
rb.Push<u32>(0); |
|
||||
} |
|
||||
|
|
||||
LOG_DEBUG(Service_CFG, "called app_id_salt=0x%X", app_id_salt); |
|
||||
} |
|
||||
|
|
||||
void GetRegionCanadaUSA(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
|
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; |
|
||||
|
|
||||
u8 canada_or_usa = 1; |
|
||||
if (canada_or_usa == GetRegionValue()) { |
|
||||
cmd_buff[2] = 1; |
|
||||
} else { |
|
||||
cmd_buff[2] = 0; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void GetSystemModel(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
u32 data; |
|
||||
|
|
||||
// TODO(Subv): Find out the correct error codes
|
|
||||
cmd_buff[1] = |
|
||||
Service::CFG::GetConfigInfoBlock(0x000F0004, 4, 0x8, reinterpret_cast<u8*>(&data)).raw; |
|
||||
cmd_buff[2] = data & 0xFF; |
|
||||
} |
|
||||
|
|
||||
void GetModelNintendo2DS(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
u32 data; |
|
||||
|
|
||||
// TODO(Subv): Find out the correct error codes
|
|
||||
cmd_buff[1] = |
|
||||
Service::CFG::GetConfigInfoBlock(0x000F0004, 4, 0x8, reinterpret_cast<u8*>(&data)).raw; |
|
||||
|
|
||||
u8 model = data & 0xFF; |
|
||||
if (model == Service::CFG::NINTENDO_2DS) |
|
||||
cmd_buff[2] = 0; |
|
||||
else |
|
||||
cmd_buff[2] = 1; |
|
||||
} |
|
||||
|
|
||||
void GetConfigInfoBlk2(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
u32 size = cmd_buff[1]; |
|
||||
u32 block_id = cmd_buff[2]; |
|
||||
VAddr data_pointer = cmd_buff[4]; |
|
||||
|
|
||||
if (!Memory::IsValidVirtualAddress(data_pointer)) { |
|
||||
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
std::vector<u8> data(size); |
|
||||
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data.data()).raw; |
|
||||
Memory::WriteBlock(data_pointer, data.data(), data.size()); |
|
||||
} |
|
||||
|
|
||||
void GetConfigInfoBlk8(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
u32 size = cmd_buff[1]; |
|
||||
u32 block_id = cmd_buff[2]; |
|
||||
VAddr data_pointer = cmd_buff[4]; |
|
||||
|
|
||||
if (!Memory::IsValidVirtualAddress(data_pointer)) { |
|
||||
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
std::vector<u8> data(size); |
|
||||
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data.data()).raw; |
|
||||
Memory::WriteBlock(data_pointer, data.data(), data.size()); |
|
||||
} |
|
||||
|
|
||||
void SetConfigInfoBlk4(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
u32 block_id = cmd_buff[1]; |
|
||||
u32 size = cmd_buff[2]; |
|
||||
VAddr data_pointer = cmd_buff[4]; |
|
||||
|
|
||||
if (!Memory::IsValidVirtualAddress(data_pointer)) { |
|
||||
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
|
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
std::vector<u8> data(size); |
|
||||
Memory::ReadBlock(data_pointer, data.data(), data.size()); |
|
||||
cmd_buff[1] = Service::CFG::SetConfigInfoBlock(block_id, size, 0x4, data.data()).raw; |
|
||||
} |
|
||||
|
|
||||
void UpdateConfigNANDSavegame(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
cmd_buff[1] = Service::CFG::UpdateConfigNANDSavegame().raw; |
|
||||
} |
|
||||
|
|
||||
void FormatConfig(Service::Interface* self) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
cmd_buff[1] = Service::CFG::FormatConfig().raw; |
|
||||
} |
|
||||
|
|
||||
static ResultVal<void*> GetConfigInfoBlockPointer(u32 block_id, u32 size, u32 flag) { |
|
||||
// Read the header
|
|
||||
SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); |
|
||||
|
|
||||
auto itr = |
|
||||
std::find_if(std::begin(config->block_entries), std::end(config->block_entries), |
|
||||
[&](const SaveConfigBlockEntry& entry) { return entry.block_id == block_id; }); |
|
||||
|
|
||||
if (itr == std::end(config->block_entries)) { |
|
||||
LOG_ERROR(Service_CFG, "Config block 0x%X with flags %u and size %u was not found", |
|
||||
block_id, flag, size); |
|
||||
return ResultCode(ErrorDescription::NotFound, ErrorModule::Config, |
|
||||
ErrorSummary::WrongArgument, ErrorLevel::Permanent); |
|
||||
} |
|
||||
|
|
||||
if ((itr->flags & flag) == 0) { |
|
||||
LOG_ERROR(Service_CFG, "Invalid flag %u for config block 0x%X with size %u", flag, block_id, |
|
||||
size); |
|
||||
return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::Config, |
|
||||
ErrorSummary::WrongArgument, ErrorLevel::Permanent); |
|
||||
} |
|
||||
|
|
||||
if (itr->size != size) { |
|
||||
LOG_ERROR(Service_CFG, "Invalid size %u for config block 0x%X with flags %u", size, |
|
||||
block_id, flag); |
|
||||
return ResultCode(ErrorDescription::InvalidSize, ErrorModule::Config, |
|
||||
ErrorSummary::WrongArgument, ErrorLevel::Permanent); |
|
||||
} |
|
||||
|
|
||||
void* pointer; |
|
||||
|
|
||||
// The data is located in the block header itself if the size is less than 4 bytes
|
|
||||
if (itr->size <= 4) |
|
||||
pointer = &itr->offset_or_data; |
|
||||
else |
|
||||
pointer = &cfg_config_file_buffer[itr->offset_or_data]; |
|
||||
|
|
||||
return MakeResult<void*>(pointer); |
|
||||
} |
|
||||
|
|
||||
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) { |
|
||||
void* pointer; |
|
||||
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag)); |
|
||||
memcpy(output, pointer, size); |
|
||||
|
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultCode SetConfigInfoBlock(u32 block_id, u32 size, u32 flag, const void* input) { |
|
||||
void* pointer; |
|
||||
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag)); |
|
||||
memcpy(pointer, input, size); |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* data) { |
|
||||
SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); |
|
||||
if (config->total_entries >= CONFIG_FILE_MAX_BLOCK_ENTRIES) |
|
||||
return ResultCode(-1); // TODO(Subv): Find the right error code
|
|
||||
|
|
||||
// Insert the block header with offset 0 for now
|
|
||||
config->block_entries[config->total_entries] = {block_id, 0, size, flags}; |
|
||||
if (size > 4) { |
|
||||
u32 offset = config->data_entries_offset; |
|
||||
// Perform a search to locate the next offset for the new data
|
|
||||
// use the offset and size of the previous block to determine the new position
|
|
||||
for (int i = config->total_entries - 1; i >= 0; --i) { |
|
||||
// Ignore the blocks that don't have a separate data offset
|
|
||||
if (config->block_entries[i].size > 4) { |
|
||||
offset = config->block_entries[i].offset_or_data + config->block_entries[i].size; |
|
||||
break; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
config->block_entries[config->total_entries].offset_or_data = offset; |
|
||||
|
|
||||
// Write the data at the new offset
|
|
||||
memcpy(&cfg_config_file_buffer[offset], data, size); |
|
||||
} else { |
|
||||
// The offset_or_data field in the header contains the data itself if it's 4 bytes or less
|
|
||||
memcpy(&config->block_entries[config->total_entries].offset_or_data, data, size); |
|
||||
} |
|
||||
|
|
||||
++config->total_entries; |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteConfigNANDSaveFile() { |
|
||||
FileSys::Path path("/config"); |
|
||||
return Service::FS::DeleteFileFromArchive(cfg_system_save_data_archive, path); |
|
||||
} |
|
||||
|
|
||||
ResultCode UpdateConfigNANDSavegame() { |
|
||||
FileSys::Mode mode = {}; |
|
||||
mode.write_flag.Assign(1); |
|
||||
mode.create_flag.Assign(1); |
|
||||
|
|
||||
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"); |
|
||||
|
|
||||
auto config = std::move(config_result).Unwrap(); |
|
||||
config->backend->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data()); |
|
||||
|
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
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 != FileSys::ERROR_FILE_NOT_FOUND) { |
|
||||
return res; |
|
||||
} |
|
||||
// Delete the old data
|
|
||||
cfg_config_file_buffer.fill(0); |
|
||||
// Create the header
|
|
||||
SaveFileConfig* config = reinterpret_cast<SaveFileConfig*>(cfg_config_file_buffer.data()); |
|
||||
// This value is hardcoded, taken from 3dbrew, verified by hardware, it's always the same value
|
|
||||
config->data_entries_offset = 0x455C; |
|
||||
|
|
||||
// Insert the default blocks
|
|
||||
u8 zero_buffer[0xC0] = {}; |
|
||||
|
|
||||
// 0x00030001 - Unknown
|
|
||||
res = CreateConfigInfoBlk(0x00030001, 0x8, 0xE, zero_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(StereoCameraSettingsBlockID, sizeof(STEREO_CAMERA_SETTINGS), 0xE, |
|
||||
STEREO_CAMERA_SETTINGS.data()); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(SoundOutputModeBlockID, sizeof(SOUND_OUTPUT_MODE), 0xE, |
|
||||
&SOUND_OUTPUT_MODE); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
u32 random_number; |
|
||||
u64 console_id; |
|
||||
GenerateConsoleUniqueId(random_number, console_id); |
|
||||
|
|
||||
u64_le console_id_le = console_id; |
|
||||
res = CreateConfigInfoBlk(ConsoleUniqueID1BlockID, sizeof(console_id_le), 0xE, &console_id_le); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
u32_le random_number_le = random_number; |
|
||||
res = CreateConfigInfoBlk(ConsoleUniqueID3BlockID, sizeof(random_number_le), 0xE, |
|
||||
&random_number_le); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(UsernameBlockID, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, |
|
||||
&CONSOLE_USERNAME_BLOCK); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(BirthdayBlockID, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(LanguageBlockID, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(CountryInfoBlockID, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
u16_le country_name_buffer[16][0x40] = {}; |
|
||||
std::u16string region_name = Common::UTF8ToUTF16("Gensokyo"); |
|
||||
for (size_t i = 0; i < 16; ++i) { |
|
||||
std::copy(region_name.cbegin(), region_name.cend(), country_name_buffer[i]); |
|
||||
} |
|
||||
// 0x000B0001 - Localized names for the profile Country
|
|
||||
res = CreateConfigInfoBlk(CountryNameBlockID, sizeof(country_name_buffer), 0xE, |
|
||||
country_name_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
// 0x000B0002 - Localized names for the profile State/Province
|
|
||||
res = CreateConfigInfoBlk(StateNameBlockID, sizeof(country_name_buffer), 0xE, |
|
||||
country_name_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
// 0x000B0003 - Unknown, related to country/address (zip code?)
|
|
||||
res = CreateConfigInfoBlk(0x000B0003, 0x4, 0xE, zero_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
// 0x000C0000 - Unknown
|
|
||||
res = CreateConfigInfoBlk(0x000C0000, 0xC0, 0xE, zero_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
// 0x000C0001 - Unknown
|
|
||||
res = CreateConfigInfoBlk(0x000C0001, 0x14, 0xE, zero_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
// 0x000D0000 - Accepted EULA version
|
|
||||
res = CreateConfigInfoBlk(EULAVersionBlockID, 0x4, 0xE, zero_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = CreateConfigInfoBlk(ConsoleModelBlockID, sizeof(CONSOLE_MODEL), 0xC, &CONSOLE_MODEL); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
// 0x00170000 - Unknown
|
|
||||
res = CreateConfigInfoBlk(0x00170000, 0x4, 0xE, zero_buffer); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
// Save the buffer to the file
|
|
||||
res = UpdateConfigNANDSavegame(); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultCode LoadConfigNANDSaveFile() { |
|
||||
// Open the SystemSaveData archive 0x00010017
|
|
||||
FileSys::Path archive_path(cfg_system_savedata_id); |
|
||||
auto archive_result = |
|
||||
Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); |
|
||||
|
|
||||
// If the archive didn't exist, create the files inside
|
|
||||
if (archive_result.Code() == FileSys::ERR_NOT_FORMATTED) { |
|
||||
// Format the archive to create the directories
|
|
||||
Service::FS::FormatArchive(Service::FS::ArchiveIdCode::SystemSaveData, |
|
||||
FileSys::ArchiveFormatInfo(), archive_path); |
|
||||
|
|
||||
// Open it again to get a valid archive now that the folder exists
|
|
||||
archive_result = |
|
||||
Service::FS::OpenArchive(Service::FS::ArchiveIdCode::SystemSaveData, archive_path); |
|
||||
} |
|
||||
|
|
||||
ASSERT_MSG(archive_result.Succeeded(), "Could not open the CFG SystemSaveData archive!"); |
|
||||
|
|
||||
cfg_system_save_data_archive = *archive_result; |
|
||||
|
|
||||
FileSys::Path config_path("/config"); |
|
||||
FileSys::Mode open_mode = {}; |
|
||||
open_mode.read_flag.Assign(1); |
|
||||
|
|
||||
auto config_result = Service::FS::OpenFileFromArchive(*archive_result, config_path, open_mode); |
|
||||
|
|
||||
// Read the file if it already exists
|
|
||||
if (config_result.Succeeded()) { |
|
||||
auto config = std::move(config_result).Unwrap(); |
|
||||
config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data()); |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
return FormatConfig(); |
|
||||
} |
|
||||
|
|
||||
void Init() { |
|
||||
AddService(new CFG_I); |
|
||||
AddService(new CFG_NOR); |
|
||||
AddService(new CFG_S); |
|
||||
AddService(new CFG_U); |
|
||||
|
|
||||
LoadConfigNANDSaveFile(); |
|
||||
|
|
||||
preferred_region_code = 0; |
|
||||
} |
|
||||
|
|
||||
void Shutdown() {} |
|
||||
|
|
||||
/// Checks if the language is available in the chosen region, and returns a proper one
|
|
||||
static SystemLanguage AdjustLanguageInfoBlock(u32 region, SystemLanguage language) { |
|
||||
static const std::array<std::vector<SystemLanguage>, 7> region_languages{{ |
|
||||
// JPN
|
|
||||
{LANGUAGE_JP}, |
|
||||
// USA
|
|
||||
{LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT}, |
|
||||
// EUR
|
|
||||
{LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, |
|
||||
LANGUAGE_RU}, |
|
||||
// AUS
|
|
||||
{LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT, |
|
||||
LANGUAGE_RU}, |
|
||||
// CHN
|
|
||||
{LANGUAGE_ZH}, |
|
||||
// KOR
|
|
||||
{LANGUAGE_KO}, |
|
||||
// TWN
|
|
||||
{LANGUAGE_TW}, |
|
||||
}}; |
|
||||
const auto& available = region_languages[region]; |
|
||||
if (std::find(available.begin(), available.end(), language) == available.end()) { |
|
||||
return available[0]; |
|
||||
} |
|
||||
return language; |
|
||||
} |
|
||||
|
|
||||
void SetPreferredRegionCode(u32 region_code) { |
|
||||
preferred_region_code = region_code; |
|
||||
LOG_INFO(Service_CFG, "Preferred region code set to %u", preferred_region_code); |
|
||||
|
|
||||
if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) { |
|
||||
const SystemLanguage current_language = GetSystemLanguage(); |
|
||||
const SystemLanguage adjusted_language = |
|
||||
AdjustLanguageInfoBlock(region_code, current_language); |
|
||||
if (current_language != adjusted_language) { |
|
||||
LOG_WARNING(Service_CFG, "System language %d does not fit the region. Adjusted to %d", |
|
||||
static_cast<int>(current_language), static_cast<int>(adjusted_language)); |
|
||||
SetSystemLanguage(adjusted_language); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void SetUsername(const std::u16string& name) { |
|
||||
ASSERT(name.size() <= 10); |
|
||||
UsernameBlock block{}; |
|
||||
name.copy(block.username, name.size()); |
|
||||
SetConfigInfoBlock(UsernameBlockID, sizeof(block), 4, &block); |
|
||||
} |
|
||||
|
|
||||
std::u16string GetUsername() { |
|
||||
UsernameBlock block; |
|
||||
GetConfigInfoBlock(UsernameBlockID, sizeof(block), 8, &block); |
|
||||
|
|
||||
// the username string in the block isn't null-terminated,
|
|
||||
// so we need to find the end manually.
|
|
||||
std::u16string username(block.username, ARRAY_SIZE(block.username)); |
|
||||
const size_t pos = username.find(u'\0'); |
|
||||
if (pos != std::u16string::npos) |
|
||||
username.erase(pos); |
|
||||
return username; |
|
||||
} |
|
||||
|
|
||||
void SetBirthday(u8 month, u8 day) { |
|
||||
BirthdayBlock block = {month, day}; |
|
||||
SetConfigInfoBlock(BirthdayBlockID, sizeof(block), 4, &block); |
|
||||
} |
|
||||
|
|
||||
std::tuple<u8, u8> GetBirthday() { |
|
||||
BirthdayBlock block; |
|
||||
GetConfigInfoBlock(BirthdayBlockID, sizeof(block), 8, &block); |
|
||||
return std::make_tuple(block.month, block.day); |
|
||||
} |
|
||||
|
|
||||
void SetSystemLanguage(SystemLanguage language) { |
|
||||
u8 block = language; |
|
||||
SetConfigInfoBlock(LanguageBlockID, sizeof(block), 4, &block); |
|
||||
} |
|
||||
|
|
||||
SystemLanguage GetSystemLanguage() { |
|
||||
u8 block; |
|
||||
GetConfigInfoBlock(LanguageBlockID, sizeof(block), 8, &block); |
|
||||
return static_cast<SystemLanguage>(block); |
|
||||
} |
|
||||
|
|
||||
void SetSoundOutputMode(SoundOutputMode mode) { |
|
||||
u8 block = mode; |
|
||||
SetConfigInfoBlock(SoundOutputModeBlockID, sizeof(block), 4, &block); |
|
||||
} |
|
||||
|
|
||||
SoundOutputMode GetSoundOutputMode() { |
|
||||
u8 block; |
|
||||
GetConfigInfoBlock(SoundOutputModeBlockID, sizeof(block), 8, &block); |
|
||||
return static_cast<SoundOutputMode>(block); |
|
||||
} |
|
||||
|
|
||||
void GenerateConsoleUniqueId(u32& random_number, u64& console_id) { |
|
||||
CryptoPP::AutoSeededRandomPool rng; |
|
||||
random_number = rng.GenerateWord32(0, 0xFFFF); |
|
||||
u64_le local_friend_code_seed; |
|
||||
rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&local_friend_code_seed), |
|
||||
sizeof(local_friend_code_seed)); |
|
||||
console_id = (local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48); |
|
||||
} |
|
||||
|
|
||||
ResultCode SetConsoleUniqueId(u32 random_number, u64 console_id) { |
|
||||
u64_le console_id_le = console_id; |
|
||||
ResultCode res = |
|
||||
SetConfigInfoBlock(ConsoleUniqueID1BlockID, sizeof(console_id_le), 0xE, &console_id_le); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
res = SetConfigInfoBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
u32_le random_number_le = random_number; |
|
||||
res = SetConfigInfoBlock(ConsoleUniqueID3BlockID, sizeof(random_number_le), 0xE, |
|
||||
&random_number_le); |
|
||||
if (!res.IsSuccess()) |
|
||||
return res; |
|
||||
|
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
u64 GetConsoleUniqueId() { |
|
||||
u64_le console_id_le; |
|
||||
GetConfigInfoBlock(ConsoleUniqueID2BlockID, sizeof(console_id_le), 0xE, &console_id_le); |
|
||||
return console_id_le; |
|
||||
} |
|
||||
|
|
||||
} // namespace CFG
|
|
||||
} // namespace Service
|
|
||||
@ -1,369 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <array> |
|
||||
#include <string> |
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
union ResultCode; |
|
||||
|
|
||||
namespace Service { |
|
||||
|
|
||||
class Interface; |
|
||||
|
|
||||
namespace CFG { |
|
||||
|
|
||||
enum SystemModel { |
|
||||
NINTENDO_3DS = 0, |
|
||||
NINTENDO_3DS_XL = 1, |
|
||||
NEW_NINTENDO_3DS = 2, |
|
||||
NINTENDO_2DS = 3, |
|
||||
NEW_NINTENDO_3DS_XL = 4 |
|
||||
}; |
|
||||
|
|
||||
enum SystemLanguage { |
|
||||
LANGUAGE_JP = 0, |
|
||||
LANGUAGE_EN = 1, |
|
||||
LANGUAGE_FR = 2, |
|
||||
LANGUAGE_DE = 3, |
|
||||
LANGUAGE_IT = 4, |
|
||||
LANGUAGE_ES = 5, |
|
||||
LANGUAGE_ZH = 6, |
|
||||
LANGUAGE_KO = 7, |
|
||||
LANGUAGE_NL = 8, |
|
||||
LANGUAGE_PT = 9, |
|
||||
LANGUAGE_RU = 10, |
|
||||
LANGUAGE_TW = 11 |
|
||||
}; |
|
||||
|
|
||||
enum SoundOutputMode { SOUND_MONO = 0, SOUND_STEREO = 1, SOUND_SURROUND = 2 }; |
|
||||
|
|
||||
/// Block header in the config savedata file |
|
||||
struct SaveConfigBlockEntry { |
|
||||
u32 block_id; ///< The id of the current block |
|
||||
u32 offset_or_data; ///< This is the absolute offset to the block data if the size is greater |
|
||||
/// than 4 bytes, otherwise it contains the data itself |
|
||||
u16 size; ///< The size of the block |
|
||||
u16 flags; ///< The flags of the block, possibly used for access control |
|
||||
}; |
|
||||
|
|
||||
static constexpr u16 C(const char code[2]) { |
|
||||
return code[0] | (code[1] << 8); |
|
||||
} |
|
||||
|
|
||||
static const std::array<u16, 187> country_codes = {{ |
|
||||
0, C("JP"), 0, 0, 0, 0, 0, 0, // 0-7 |
|
||||
C("AI"), C("AG"), C("AR"), C("AW"), C("BS"), C("BB"), C("BZ"), C("BO"), // 8-15 |
|
||||
C("BR"), C("VG"), C("CA"), C("KY"), C("CL"), C("CO"), C("CR"), C("DM"), // 16-23 |
|
||||
C("DO"), C("EC"), C("SV"), C("GF"), C("GD"), C("GP"), C("GT"), C("GY"), // 24-31 |
|
||||
C("HT"), C("HN"), C("JM"), C("MQ"), C("MX"), C("MS"), C("AN"), C("NI"), // 32-39 |
|
||||
C("PA"), C("PY"), C("PE"), C("KN"), C("LC"), C("VC"), C("SR"), C("TT"), // 40-47 |
|
||||
C("TC"), C("US"), C("UY"), C("VI"), C("VE"), 0, 0, 0, // 48-55 |
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 56-63 |
|
||||
C("AL"), C("AU"), C("AT"), C("BE"), C("BA"), C("BW"), C("BG"), C("HR"), // 64-71 |
|
||||
C("CY"), C("CZ"), C("DK"), C("EE"), C("FI"), C("FR"), C("DE"), C("GR"), // 72-79 |
|
||||
C("HU"), C("IS"), C("IE"), C("IT"), C("LV"), C("LS"), C("LI"), C("LT"), // 80-87 |
|
||||
C("LU"), C("MK"), C("MT"), C("ME"), C("MZ"), C("NA"), C("NL"), C("NZ"), // 88-95 |
|
||||
C("NO"), C("PL"), C("PT"), C("RO"), C("RU"), C("RS"), C("SK"), C("SI"), // 96-103 |
|
||||
C("ZA"), C("ES"), C("SZ"), C("SE"), C("CH"), C("TR"), C("GB"), C("ZM"), // 104-111 |
|
||||
C("ZW"), C("AZ"), C("MR"), C("ML"), C("NE"), C("TD"), C("SD"), C("ER"), // 112-119 |
|
||||
C("DJ"), C("SO"), C("AD"), C("GI"), C("GG"), C("IM"), C("JE"), C("MC"), // 120-127 |
|
||||
C("TW"), 0, 0, 0, 0, 0, 0, 0, // 128-135 |
|
||||
C("KR"), 0, 0, 0, 0, 0, 0, 0, // 136-143 |
|
||||
C("HK"), C("MO"), 0, 0, 0, 0, 0, 0, // 144-151 |
|
||||
C("ID"), C("SG"), C("TH"), C("PH"), C("MY"), 0, 0, 0, // 152-159 |
|
||||
C("CN"), 0, 0, 0, 0, 0, 0, 0, // 160-167 |
|
||||
C("AE"), C("IN"), C("EG"), C("OM"), C("QA"), C("KW"), C("SA"), C("SY"), // 168-175 |
|
||||
C("BH"), C("JO"), 0, 0, 0, 0, 0, 0, // 176-183 |
|
||||
C("SM"), C("VA"), C("BM"), // 184-186 |
|
||||
}}; |
|
||||
|
|
||||
/** |
|
||||
* CFG::GetCountryCodeString service function |
|
||||
* Inputs: |
|
||||
* 1 : Country Code ID |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* 2 : Country's 2-char string |
|
||||
*/ |
|
||||
void GetCountryCodeString(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::GetCountryCodeID service function |
|
||||
* Inputs: |
|
||||
* 1 : Country Code 2-char string |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* 2 : Country Code ID |
|
||||
*/ |
|
||||
void GetCountryCodeID(Service::Interface* self); |
|
||||
|
|
||||
u32 GetRegionValue(); |
|
||||
|
|
||||
/** |
|
||||
* CFG::SecureInfoGetRegion service function |
|
||||
* Inputs: |
|
||||
* 1 : None |
|
||||
* Outputs: |
|
||||
* 0 : Result Header code |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* 2 : Region value loaded from SecureInfo offset 0x100 |
|
||||
*/ |
|
||||
void SecureInfoGetRegion(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::GenHashConsoleUnique service function |
|
||||
* Inputs: |
|
||||
* 1 : 20 bit application ID salt |
|
||||
* Outputs: |
|
||||
* 0 : Result Header code |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* 2 : Hash/"ID" lower word |
|
||||
* 3 : Hash/"ID" upper word |
|
||||
*/ |
|
||||
void GenHashConsoleUnique(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::GetRegionCanadaUSA service function |
|
||||
* Inputs: |
|
||||
* 1 : None |
|
||||
* Outputs: |
|
||||
* 0 : Result Header code |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* 2 : 1 if the system is a Canada or USA model, 0 otherwise |
|
||||
*/ |
|
||||
void GetRegionCanadaUSA(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::GetSystemModel service function |
|
||||
* Inputs: |
|
||||
* 0 : 0x00050000 |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* 2 : Model of the console |
|
||||
*/ |
|
||||
void GetSystemModel(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::GetModelNintendo2DS service function |
|
||||
* Inputs: |
|
||||
* 0 : 0x00060000 |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* 2 : 0 if the system is a Nintendo 2DS, 1 otherwise |
|
||||
*/ |
|
||||
void GetModelNintendo2DS(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::GetConfigInfoBlk2 service function |
|
||||
* Inputs: |
|
||||
* 0 : 0x00010082 |
|
||||
* 1 : Size |
|
||||
* 2 : Block ID |
|
||||
* 3 : Descriptor for the output buffer |
|
||||
* 4 : Output buffer pointer |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
*/ |
|
||||
void GetConfigInfoBlk2(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::GetConfigInfoBlk8 service function |
|
||||
* Inputs: |
|
||||
* 0 : 0x04010082 / 0x08010082 |
|
||||
* 1 : Size |
|
||||
* 2 : Block ID |
|
||||
* 3 : Descriptor for the output buffer |
|
||||
* 4 : Output buffer pointer |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
*/ |
|
||||
void GetConfigInfoBlk8(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::SetConfigInfoBlk4 service function |
|
||||
* Inputs: |
|
||||
* 0 : 0x04020082 / 0x08020082 |
|
||||
* 1 : Block ID |
|
||||
* 2 : Size |
|
||||
* 3 : Descriptor for the output buffer |
|
||||
* 4 : Output buffer pointer |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
* Note: |
|
||||
* The parameters order is different from GetConfigInfoBlk2/8's, |
|
||||
* where Block ID and Size are switched. |
|
||||
*/ |
|
||||
void SetConfigInfoBlk4(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::UpdateConfigNANDSavegame service function |
|
||||
* Inputs: |
|
||||
* 0 : 0x04030000 / 0x08030000 |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
*/ |
|
||||
void UpdateConfigNANDSavegame(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* CFG::FormatConfig service function |
|
||||
* Inputs: |
|
||||
* 0 : 0x08060000 |
|
||||
* Outputs: |
|
||||
* 1 : Result of function, 0 on success, otherwise error code |
|
||||
*/ |
|
||||
void FormatConfig(Service::Interface* self); |
|
||||
|
|
||||
/** |
|
||||
* Reads a block with the specified id and flag from the Config savegame buffer |
|
||||
* and writes the output to output. The input size must match exactly the size of the requested |
|
||||
* block. |
|
||||
* |
|
||||
* @param block_id The id of the block we want to read |
|
||||
* @param size The size of the block we want to read |
|
||||
* @param flag The requested block must have this flag set |
|
||||
* @param output A pointer where we will write the read data |
|
||||
* @returns ResultCode indicating the result of the operation, 0 on success |
|
||||
*/ |
|
||||
ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output); |
|
||||
|
|
||||
/** |
|
||||
* Reads data from input and writes to a block with the specified id and flag |
|
||||
* in the Config savegame buffer. The input size must match exactly the size of the target block. |
|
||||
* |
|
||||
* @param block_id The id of the block we want to write |
|
||||
* @param size The size of the block we want to write |
|
||||
* @param flag The target block must have this flag set |
|
||||
* @param input A pointer where we will read data and write to Config savegame buffer |
|
||||
* @returns ResultCode indicating the result of the operation, 0 on success |
|
||||
*/ |
|
||||
ResultCode SetConfigInfoBlock(u32 block_id, u32 size, u32 flag, const void* input); |
|
||||
|
|
||||
/** |
|
||||
* Creates a block with the specified id and writes the input data to the cfg savegame buffer in |
|
||||
* memory. The config savegame file in the filesystem is not updated. |
|
||||
* |
|
||||
* @param block_id The id of the block we want to create |
|
||||
* @param size The size of the block we want to create |
|
||||
* @param flags The flags of the new block |
|
||||
* @param data A pointer containing the data we will write to the new block |
|
||||
* @returns ResultCode indicating the result of the operation, 0 on success |
|
||||
*/ |
|
||||
ResultCode CreateConfigInfoBlk(u32 block_id, u16 size, u16 flags, const void* data); |
|
||||
|
|
||||
/** |
|
||||
* Deletes the config savegame file from the filesystem, the buffer in memory is not affected |
|
||||
* @returns ResultCode indicating the result of the operation, 0 on success |
|
||||
*/ |
|
||||
ResultCode DeleteConfigNANDSaveFile(); |
|
||||
|
|
||||
/** |
|
||||
* Writes the config savegame memory buffer to the config savegame file in the filesystem |
|
||||
* @returns ResultCode indicating the result of the operation, 0 on success |
|
||||
*/ |
|
||||
ResultCode UpdateConfigNANDSavegame(); |
|
||||
|
|
||||
/** |
|
||||
* Re-creates the config savegame file in memory and the filesystem with the default blocks |
|
||||
* @returns ResultCode indicating the result of the operation, 0 on success |
|
||||
*/ |
|
||||
ResultCode FormatConfig(); |
|
||||
|
|
||||
/** |
|
||||
* Open the config savegame file and load it to the memory buffer |
|
||||
* @returns ResultCode indicating the result of the operation, 0 on success |
|
||||
*/ |
|
||||
ResultCode LoadConfigNANDSaveFile(); |
|
||||
|
|
||||
/// Initialize the config service |
|
||||
void Init(); |
|
||||
|
|
||||
/// Shutdown the config service |
|
||||
void Shutdown(); |
|
||||
|
|
||||
/** |
|
||||
* Set the region code preferred by the game so that CFG will adjust to it when the region setting |
|
||||
* is auto. |
|
||||
* @param region_code the preferred region code to set |
|
||||
*/ |
|
||||
void SetPreferredRegionCode(u32 region_code); |
|
||||
|
|
||||
// Utilities for frontend to set config data. |
|
||||
// Note: before calling these functions, LoadConfigNANDSaveFile should be called, |
|
||||
// and UpdateConfigNANDSavegame should be called after making changes to config data. |
|
||||
|
|
||||
/** |
|
||||
* Sets the username in config savegame. |
|
||||
* @param name the username to set. The maximum size is 10 in char16_t. |
|
||||
*/ |
|
||||
void SetUsername(const std::u16string& name); |
|
||||
|
|
||||
/** |
|
||||
* Gets the username from config savegame. |
|
||||
* @returns the username |
|
||||
*/ |
|
||||
std::u16string GetUsername(); |
|
||||
|
|
||||
/** |
|
||||
* Sets the profile birthday in config savegame. |
|
||||
* @param month the month of birthday. |
|
||||
* @param day the day of the birthday. |
|
||||
*/ |
|
||||
void SetBirthday(u8 month, u8 day); |
|
||||
|
|
||||
/** |
|
||||
* Gets the profile birthday from the config savegame. |
|
||||
* @returns a tuple of (month, day) of birthday |
|
||||
*/ |
|
||||
std::tuple<u8, u8> GetBirthday(); |
|
||||
|
|
||||
/** |
|
||||
* Sets the system language in config savegame. |
|
||||
* @param language the system language to set. |
|
||||
*/ |
|
||||
void SetSystemLanguage(SystemLanguage language); |
|
||||
|
|
||||
/** |
|
||||
* Gets the system language from config savegame. |
|
||||
* @returns the system language |
|
||||
*/ |
|
||||
SystemLanguage GetSystemLanguage(); |
|
||||
|
|
||||
/** |
|
||||
* Sets the sound output mode in config savegame. |
|
||||
* @param mode the sound output mode to set |
|
||||
*/ |
|
||||
void SetSoundOutputMode(SoundOutputMode mode); |
|
||||
|
|
||||
/** |
|
||||
* Gets the sound output mode from config savegame. |
|
||||
* @returns the sound output mode |
|
||||
*/ |
|
||||
SoundOutputMode GetSoundOutputMode(); |
|
||||
|
|
||||
/** |
|
||||
* Generates a new random console unique id. |
|
||||
* @param random_number a random generated 16bit number stored at 0x90002, used for generating the |
|
||||
* console_id |
|
||||
* @param console_id the randomly created console id |
|
||||
*/ |
|
||||
void GenerateConsoleUniqueId(u32& random_number, u64& console_id); |
|
||||
|
|
||||
/** |
|
||||
* Sets the random_number and the console unique id in the config savegame. |
|
||||
* @param random_number the random_number to set |
|
||||
* @param console_id the console id to set |
|
||||
*/ |
|
||||
ResultCode SetConsoleUniqueId(u32 random_number, u64 console_id); |
|
||||
|
|
||||
/** |
|
||||
* Gets the console unique id from config savegame. |
|
||||
* @returns the console unique id |
|
||||
*/ |
|
||||
u64 GetConsoleUniqueId(); |
|
||||
|
|
||||
} // namespace CFG |
|
||||
} // namespace Service |
|
||||
@ -1,64 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/cfg/cfg.h"
|
|
||||
#include "core/hle/service/cfg/cfg_i.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
const Interface::FunctionInfo FunctionTable[] = { |
|
||||
// cfg common
|
|
||||
{0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, |
|
||||
{0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, |
|
||||
{0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, |
|
||||
{0x00040000, GetRegionCanadaUSA, "GetRegionCanadaUSA"}, |
|
||||
{0x00050000, GetSystemModel, "GetSystemModel"}, |
|
||||
{0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, |
|
||||
{0x00070040, nullptr, "WriteToFirstByteCfgSavegame"}, |
|
||||
{0x00080080, nullptr, "GoThroughTable"}, |
|
||||
{0x00090040, GetCountryCodeString, "GetCountryCodeString"}, |
|
||||
{0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, |
|
||||
{0x000B0000, nullptr, "IsFangateSupported"}, |
|
||||
// cfg:i
|
|
||||
{0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, |
|
||||
{0x04020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"}, |
|
||||
{0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, |
|
||||
{0x04040042, nullptr, "GetLocalFriendCodeSeedData"}, |
|
||||
{0x04050000, nullptr, "GetLocalFriendCodeSeed"}, |
|
||||
{0x04060000, SecureInfoGetRegion, "SecureInfoGetRegion"}, |
|
||||
{0x04070000, nullptr, "SecureInfoGetByte101"}, |
|
||||
{0x04080042, nullptr, "SecureInfoGetSerialNo"}, |
|
||||
{0x04090000, nullptr, "UpdateConfigBlk00040003"}, |
|
||||
{0x08010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, |
|
||||
{0x08020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"}, |
|
||||
{0x08030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, |
|
||||
{0x080400C2, nullptr, "CreateConfigInfoBlk"}, |
|
||||
{0x08050000, nullptr, "DeleteConfigNANDSavefile"}, |
|
||||
{0x08060000, FormatConfig, "FormatConfig"}, |
|
||||
{0x08080000, nullptr, "UpdateConfigBlk1"}, |
|
||||
{0x08090000, nullptr, "UpdateConfigBlk2"}, |
|
||||
{0x080A0000, nullptr, "UpdateConfigBlk3"}, |
|
||||
{0x080B0082, nullptr, "SetGetLocalFriendCodeSeedData"}, |
|
||||
{0x080C0042, nullptr, "SetLocalFriendCodeSeedSignature"}, |
|
||||
{0x080D0000, nullptr, "DeleteCreateNANDLocalFriendCodeSeed"}, |
|
||||
{0x080E0000, nullptr, "VerifySigLocalFriendCodeSeed"}, |
|
||||
{0x080F0042, nullptr, "GetLocalFriendCodeSeedData"}, |
|
||||
{0x08100000, nullptr, "GetLocalFriendCodeSeed"}, |
|
||||
{0x08110084, nullptr, "SetSecureInfo"}, |
|
||||
{0x08120000, nullptr, "DeleteCreateNANDSecureInfo"}, |
|
||||
{0x08130000, nullptr, "VerifySigSecureInfo"}, |
|
||||
{0x08140042, nullptr, "SecureInfoGetData"}, |
|
||||
{0x08150042, nullptr, "SecureInfoGetSignature"}, |
|
||||
{0x08160000, SecureInfoGetRegion, "SecureInfoGetRegion"}, |
|
||||
{0x08170000, nullptr, "SecureInfoGetByte101"}, |
|
||||
{0x08180042, nullptr, "SecureInfoGetSerialNo"}, |
|
||||
}; |
|
||||
|
|
||||
CFG_I::CFG_I() { |
|
||||
Register(FunctionTable); |
|
||||
} |
|
||||
|
|
||||
} // namespace CFG
|
|
||||
} // namespace Service
|
|
||||
@ -1,22 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
class CFG_I final : public Interface { |
|
||||
public: |
|
||||
CFG_I(); |
|
||||
|
|
||||
std::string GetPortName() const override { |
|
||||
return "cfg:i"; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} // namespace CFG |
|
||||
} // namespace Service |
|
||||
@ -1,23 +0,0 @@ |
|||||
// Copyright 2016 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/cfg/cfg.h"
|
|
||||
#include "core/hle/service/cfg/cfg_nor.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
const Interface::FunctionInfo FunctionTable[] = { |
|
||||
{0x00010040, nullptr, "Initialize"}, |
|
||||
{0x00020000, nullptr, "Shutdown"}, |
|
||||
{0x00050082, nullptr, "ReadData"}, |
|
||||
{0x00060082, nullptr, "WriteData"}, |
|
||||
}; |
|
||||
|
|
||||
CFG_NOR::CFG_NOR() { |
|
||||
Register(FunctionTable); |
|
||||
} |
|
||||
|
|
||||
} // namespace CFG
|
|
||||
} // namespace Service
|
|
||||
@ -1,22 +0,0 @@ |
|||||
// Copyright 2016 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
class CFG_NOR final : public Interface { |
|
||||
public: |
|
||||
CFG_NOR(); |
|
||||
|
|
||||
std::string GetPortName() const override { |
|
||||
return "cfg:nor"; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} // namespace CFG |
|
||||
} // namespace Service |
|
||||
@ -1,41 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/cfg/cfg.h"
|
|
||||
#include "core/hle/service/cfg/cfg_s.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
const Interface::FunctionInfo FunctionTable[] = { |
|
||||
// cfg common
|
|
||||
{0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, |
|
||||
{0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, |
|
||||
{0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, |
|
||||
{0x00040000, GetRegionCanadaUSA, "GetRegionCanadaUSA"}, |
|
||||
{0x00050000, GetSystemModel, "GetSystemModel"}, |
|
||||
{0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, |
|
||||
{0x00070040, nullptr, "WriteToFirstByteCfgSavegame"}, |
|
||||
{0x00080080, nullptr, "GoThroughTable"}, |
|
||||
{0x00090040, GetCountryCodeString, "GetCountryCodeString"}, |
|
||||
{0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, |
|
||||
{0x000B0000, nullptr, "IsFangateSupported"}, |
|
||||
// cfg:s
|
|
||||
{0x04010082, GetConfigInfoBlk8, "GetConfigInfoBlk8"}, |
|
||||
{0x04020082, SetConfigInfoBlk4, "SetConfigInfoBlk4"}, |
|
||||
{0x04030000, UpdateConfigNANDSavegame, "UpdateConfigNANDSavegame"}, |
|
||||
{0x04040042, nullptr, "GetLocalFriendCodeSeedData"}, |
|
||||
{0x04050000, nullptr, "GetLocalFriendCodeSeed"}, |
|
||||
{0x04060000, nullptr, "SecureInfoGetRegion"}, |
|
||||
{0x04070000, nullptr, "SecureInfoGetByte101"}, |
|
||||
{0x04080042, nullptr, "SecureInfoGetSerialNo"}, |
|
||||
{0x04090000, nullptr, "UpdateConfigBlk00040003"}, |
|
||||
}; |
|
||||
|
|
||||
CFG_S::CFG_S() { |
|
||||
Register(FunctionTable); |
|
||||
} |
|
||||
|
|
||||
} // namespace CFG
|
|
||||
} // namespace Service
|
|
||||
@ -1,22 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
class CFG_S final : public Interface { |
|
||||
public: |
|
||||
CFG_S(); |
|
||||
|
|
||||
std::string GetPortName() const override { |
|
||||
return "cfg:s"; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} // namespace CFG |
|
||||
} // namespace Service |
|
||||
@ -1,31 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/cfg/cfg.h"
|
|
||||
#include "core/hle/service/cfg/cfg_u.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
const Interface::FunctionInfo FunctionTable[] = { |
|
||||
// cfg common
|
|
||||
{0x00010082, GetConfigInfoBlk2, "GetConfigInfoBlk2"}, |
|
||||
{0x00020000, SecureInfoGetRegion, "SecureInfoGetRegion"}, |
|
||||
{0x00030040, GenHashConsoleUnique, "GenHashConsoleUnique"}, |
|
||||
{0x00040000, GetRegionCanadaUSA, "GetRegionCanadaUSA"}, |
|
||||
{0x00050000, GetSystemModel, "GetSystemModel"}, |
|
||||
{0x00060000, GetModelNintendo2DS, "GetModelNintendo2DS"}, |
|
||||
{0x00070040, nullptr, "WriteToFirstByteCfgSavegame"}, |
|
||||
{0x00080080, nullptr, "GoThroughTable"}, |
|
||||
{0x00090040, GetCountryCodeString, "GetCountryCodeString"}, |
|
||||
{0x000A0040, GetCountryCodeID, "GetCountryCodeID"}, |
|
||||
{0x000B0000, nullptr, "IsFangateSupported"}, |
|
||||
}; |
|
||||
|
|
||||
CFG_U::CFG_U() { |
|
||||
Register(FunctionTable); |
|
||||
} |
|
||||
|
|
||||
} // namespace CFG
|
|
||||
} // namespace Service
|
|
||||
@ -1,22 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace CFG { |
|
||||
|
|
||||
class CFG_U final : public Interface { |
|
||||
public: |
|
||||
CFG_U(); |
|
||||
|
|
||||
std::string GetPortName() const override { |
|
||||
return "cfg:u"; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} // namespace CFG |
|
||||
} // namespace Service |
|
||||
@ -1,605 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <cstddef>
|
|
||||
#include <memory>
|
|
||||
#include <system_error>
|
|
||||
#include <type_traits>
|
|
||||
#include <unordered_map>
|
|
||||
#include <utility>
|
|
||||
#include <boost/container/flat_map.hpp>
|
|
||||
#include "common/assert.h"
|
|
||||
#include "common/common_types.h"
|
|
||||
#include "common/file_util.h"
|
|
||||
#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_other_savedata.h"
|
|
||||
#include "core/file_sys/archive_savedata.h"
|
|
||||
#include "core/file_sys/archive_sdmc.h"
|
|
||||
#include "core/file_sys/archive_sdmcwriteonly.h"
|
|
||||
#include "core/file_sys/archive_selfncch.h"
|
|
||||
#include "core/file_sys/archive_systemsavedata.h"
|
|
||||
#include "core/file_sys/directory_backend.h"
|
|
||||
#include "core/file_sys/errors.h"
|
|
||||
#include "core/file_sys/file_backend.h"
|
|
||||
#include "core/hle/ipc.h"
|
|
||||
#include "core/hle/kernel/client_port.h"
|
|
||||
#include "core/hle/kernel/client_session.h"
|
|
||||
#include "core/hle/kernel/handle_table.h"
|
|
||||
#include "core/hle/kernel/server_session.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/service.h"
|
|
||||
#include "core/memory.h"
|
|
||||
|
|
||||
// Specializes std::hash for ArchiveIdCode, so that we can use it in std::unordered_map.
|
|
||||
// Workaroung for libstdc++ bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60970
|
|
||||
namespace std { |
|
||||
template <> |
|
||||
struct hash<Service::FS::ArchiveIdCode> { |
|
||||
typedef Service::FS::ArchiveIdCode argument_type; |
|
||||
typedef std::size_t result_type; |
|
||||
|
|
||||
result_type operator()(const argument_type& id_code) const { |
|
||||
typedef std::underlying_type<argument_type>::type Type; |
|
||||
return std::hash<Type>()(static_cast<Type>(id_code)); |
|
||||
} |
|
||||
}; |
|
||||
} // namespace std
|
|
||||
|
|
||||
static constexpr Kernel::Handle INVALID_HANDLE{}; |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace FS { |
|
||||
|
|
||||
// Command to access archive file
|
|
||||
enum class FileCommand : u32 { |
|
||||
Dummy1 = 0x000100C6, |
|
||||
Control = 0x040100C4, |
|
||||
OpenSubFile = 0x08010100, |
|
||||
Read = 0x080200C2, |
|
||||
Write = 0x08030102, |
|
||||
GetSize = 0x08040000, |
|
||||
SetSize = 0x08050080, |
|
||||
GetAttributes = 0x08060000, |
|
||||
SetAttributes = 0x08070040, |
|
||||
Close = 0x08080000, |
|
||||
Flush = 0x08090000, |
|
||||
SetPriority = 0x080A0040, |
|
||||
GetPriority = 0x080B0000, |
|
||||
OpenLinkFile = 0x080C0000, |
|
||||
}; |
|
||||
|
|
||||
// Command to access directory
|
|
||||
enum class DirectoryCommand : u32 { |
|
||||
Dummy1 = 0x000100C6, |
|
||||
Control = 0x040100C4, |
|
||||
Read = 0x08010042, |
|
||||
Close = 0x08020000, |
|
||||
}; |
|
||||
|
|
||||
File::File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path) |
|
||||
: path(path), priority(0), backend(std::move(backend)) {} |
|
||||
|
|
||||
File::~File() {} |
|
||||
|
|
||||
void File::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { |
|
||||
using Kernel::ClientSession; |
|
||||
using Kernel::ServerSession; |
|
||||
using Kernel::SharedPtr; |
|
||||
|
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
FileCommand cmd = static_cast<FileCommand>(cmd_buff[0]); |
|
||||
switch (cmd) { |
|
||||
|
|
||||
// Read from file...
|
|
||||
case FileCommand::Read: { |
|
||||
u64 offset = cmd_buff[1] | ((u64)cmd_buff[2]) << 32; |
|
||||
u32 length = cmd_buff[3]; |
|
||||
u32 address = cmd_buff[5]; |
|
||||
LOG_TRACE(Service_FS, "Read %s: offset=0x%llx length=%d address=0x%x", GetName().c_str(), |
|
||||
offset, length, address); |
|
||||
|
|
||||
if (offset + length > backend->GetSize()) { |
|
||||
LOG_ERROR(Service_FS, |
|
||||
"Reading from out of bounds offset=0x%llX length=0x%08X file_size=0x%llX", |
|
||||
offset, length, backend->GetSize()); |
|
||||
} |
|
||||
|
|
||||
std::vector<u8> data(length); |
|
||||
ResultVal<size_t> read = backend->Read(offset, data.size(), data.data()); |
|
||||
if (read.Failed()) { |
|
||||
cmd_buff[1] = read.Code().raw; |
|
||||
return; |
|
||||
} |
|
||||
Memory::WriteBlock(address, data.data(), *read); |
|
||||
cmd_buff[2] = static_cast<u32>(*read); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
// Write to file...
|
|
||||
case FileCommand::Write: { |
|
||||
u64 offset = cmd_buff[1] | ((u64)cmd_buff[2]) << 32; |
|
||||
u32 length = cmd_buff[3]; |
|
||||
u32 flush = cmd_buff[4]; |
|
||||
u32 address = cmd_buff[6]; |
|
||||
LOG_TRACE(Service_FS, "Write %s: offset=0x%llx length=%d address=0x%x, flush=0x%x", |
|
||||
GetName().c_str(), offset, length, address, flush); |
|
||||
|
|
||||
std::vector<u8> data(length); |
|
||||
Memory::ReadBlock(address, data.data(), data.size()); |
|
||||
ResultVal<size_t> written = backend->Write(offset, data.size(), flush != 0, data.data()); |
|
||||
if (written.Failed()) { |
|
||||
cmd_buff[1] = written.Code().raw; |
|
||||
return; |
|
||||
} |
|
||||
cmd_buff[2] = static_cast<u32>(*written); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case FileCommand::GetSize: { |
|
||||
LOG_TRACE(Service_FS, "GetSize %s", GetName().c_str()); |
|
||||
u64 size = backend->GetSize(); |
|
||||
cmd_buff[2] = (u32)size; |
|
||||
cmd_buff[3] = size >> 32; |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case FileCommand::SetSize: { |
|
||||
u64 size = cmd_buff[1] | ((u64)cmd_buff[2] << 32); |
|
||||
LOG_TRACE(Service_FS, "SetSize %s size=%llu", GetName().c_str(), size); |
|
||||
backend->SetSize(size); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case FileCommand::Close: { |
|
||||
LOG_TRACE(Service_FS, "Close %s", GetName().c_str()); |
|
||||
backend->Close(); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case FileCommand::Flush: { |
|
||||
LOG_TRACE(Service_FS, "Flush"); |
|
||||
backend->Flush(); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case FileCommand::OpenLinkFile: { |
|
||||
LOG_WARNING(Service_FS, "(STUBBED) File command OpenLinkFile %s", GetName().c_str()); |
|
||||
auto sessions = ServerSession::CreateSessionPair(GetName()); |
|
||||
ClientConnected(std::get<SharedPtr<ServerSession>>(sessions)); |
|
||||
cmd_buff[3] = Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)) |
|
||||
.ValueOr(INVALID_HANDLE); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case FileCommand::SetPriority: { |
|
||||
priority = cmd_buff[1]; |
|
||||
LOG_TRACE(Service_FS, "SetPriority %u", priority); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case FileCommand::GetPriority: { |
|
||||
cmd_buff[2] = priority; |
|
||||
LOG_TRACE(Service_FS, "GetPriority"); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
// Unknown command...
|
|
||||
default: |
|
||||
LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd); |
|
||||
ResultCode error = UnimplementedFunction(ErrorModule::FS); |
|
||||
cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that.
|
|
||||
return; |
|
||||
} |
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
|
||||
} |
|
||||
|
|
||||
Directory::Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, |
|
||||
const FileSys::Path& path) |
|
||||
: path(path), backend(std::move(backend)) {} |
|
||||
|
|
||||
Directory::~Directory() {} |
|
||||
|
|
||||
void Directory::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) { |
|
||||
u32* cmd_buff = Kernel::GetCommandBuffer(); |
|
||||
DirectoryCommand cmd = static_cast<DirectoryCommand>(cmd_buff[0]); |
|
||||
switch (cmd) { |
|
||||
// Read from directory...
|
|
||||
case DirectoryCommand::Read: { |
|
||||
u32 count = cmd_buff[1]; |
|
||||
u32 address = cmd_buff[3]; |
|
||||
std::vector<FileSys::Entry> entries(count); |
|
||||
LOG_TRACE(Service_FS, "Read %s: count=%d", GetName().c_str(), count); |
|
||||
|
|
||||
// Number of entries actually read
|
|
||||
u32 read = backend->Read(static_cast<u32>(entries.size()), entries.data()); |
|
||||
cmd_buff[2] = read; |
|
||||
Memory::WriteBlock(address, entries.data(), read * sizeof(FileSys::Entry)); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
case DirectoryCommand::Close: { |
|
||||
LOG_TRACE(Service_FS, "Close %s", GetName().c_str()); |
|
||||
backend->Close(); |
|
||||
break; |
|
||||
} |
|
||||
|
|
||||
// Unknown command...
|
|
||||
default: |
|
||||
LOG_ERROR(Service_FS, "Unknown command=0x%08X!", cmd); |
|
||||
ResultCode error = UnimplementedFunction(ErrorModule::FS); |
|
||||
cmd_buff[1] = error.raw; // TODO(Link Mauve): use the correct error code for that.
|
|
||||
return; |
|
||||
} |
|
||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
|
||||
} |
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
|
|
||||
using FileSys::ArchiveBackend; |
|
||||
using FileSys::ArchiveFactory; |
|
||||
|
|
||||
/**
|
|
||||
* Map of registered archives, identified by id code. Once an archive is registered here, it is |
|
||||
* never removed until UnregisterArchiveTypes is called. |
|
||||
*/ |
|
||||
static boost::container::flat_map<ArchiveIdCode, std::unique_ptr<ArchiveFactory>> id_code_map; |
|
||||
|
|
||||
/**
|
|
||||
* Map of active archive handles. Values are pointers to the archives in `idcode_map`. |
|
||||
*/ |
|
||||
static std::unordered_map<ArchiveHandle, std::unique_ptr<ArchiveBackend>> handle_map; |
|
||||
static ArchiveHandle next_handle; |
|
||||
|
|
||||
static ArchiveBackend* GetArchive(ArchiveHandle handle) { |
|
||||
auto itr = handle_map.find(handle); |
|
||||
return (itr == handle_map.end()) ? nullptr : itr->second.get(); |
|
||||
} |
|
||||
|
|
||||
ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code, FileSys::Path& archive_path) { |
|
||||
LOG_TRACE(Service_FS, "Opening archive with id code 0x%08X", id_code); |
|
||||
|
|
||||
auto itr = id_code_map.find(id_code); |
|
||||
if (itr == id_code_map.end()) { |
|
||||
return FileSys::ERROR_NOT_FOUND; |
|
||||
} |
|
||||
|
|
||||
CASCADE_RESULT(std::unique_ptr<ArchiveBackend> res, itr->second->Open(archive_path)); |
|
||||
|
|
||||
// This should never even happen in the first place with 64-bit handles,
|
|
||||
while (handle_map.count(next_handle) != 0) { |
|
||||
++next_handle; |
|
||||
} |
|
||||
handle_map.emplace(next_handle, std::move(res)); |
|
||||
return MakeResult<ArchiveHandle>(next_handle++); |
|
||||
} |
|
||||
|
|
||||
ResultCode CloseArchive(ArchiveHandle handle) { |
|
||||
if (handle_map.erase(handle) == 0) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
else |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
// TODO(yuriks): This might be what the fs:REG service is for. See the Register/Unregister calls in
|
|
||||
// http://3dbrew.org/wiki/Filesystem_services#ProgramRegistry_service_.22fs:REG.22
|
|
||||
ResultCode RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFactory>&& factory, |
|
||||
ArchiveIdCode id_code) { |
|
||||
auto result = id_code_map.emplace(id_code, std::move(factory)); |
|
||||
|
|
||||
bool inserted = result.second; |
|
||||
ASSERT_MSG(inserted, "Tried to register more than one archive with same id code"); |
|
||||
|
|
||||
auto& archive = result.first->second; |
|
||||
LOG_DEBUG(Service_FS, "Registered archive %s with id code 0x%08X", archive->GetName().c_str(), |
|
||||
id_code); |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, |
|
||||
const FileSys::Path& path, |
|
||||
const FileSys::Mode mode) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
auto backend = archive->OpenFile(path, mode); |
|
||||
if (backend.Failed()) |
|
||||
return backend.Code(); |
|
||||
|
|
||||
auto file = std::shared_ptr<File>(new File(std::move(backend).Unwrap(), path)); |
|
||||
return MakeResult<std::shared_ptr<File>>(std::move(file)); |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
return archive->DeleteFile(path); |
|
||||
} |
|
||||
|
|
||||
ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, |
|
||||
const FileSys::Path& src_path, |
|
||||
ArchiveHandle dest_archive_handle, |
|
||||
const FileSys::Path& dest_path) { |
|
||||
ArchiveBackend* src_archive = GetArchive(src_archive_handle); |
|
||||
ArchiveBackend* dest_archive = GetArchive(dest_archive_handle); |
|
||||
if (src_archive == nullptr || dest_archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
if (src_archive == dest_archive) { |
|
||||
return src_archive->RenameFile(src_path, dest_path); |
|
||||
} else { |
|
||||
// TODO: Implement renaming across archives
|
|
||||
return UnimplementedFunction(ErrorModule::FS); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
return archive->DeleteDirectory(path); |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, |
|
||||
const FileSys::Path& path) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
return archive->DeleteDirectoryRecursively(path); |
|
||||
} |
|
||||
|
|
||||
ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, |
|
||||
u64 file_size) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
return archive->CreateFile(path, file_size); |
|
||||
} |
|
||||
|
|
||||
ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
return archive->CreateDirectory(path); |
|
||||
} |
|
||||
|
|
||||
ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, |
|
||||
const FileSys::Path& src_path, |
|
||||
ArchiveHandle dest_archive_handle, |
|
||||
const FileSys::Path& dest_path) { |
|
||||
ArchiveBackend* src_archive = GetArchive(src_archive_handle); |
|
||||
ArchiveBackend* dest_archive = GetArchive(dest_archive_handle); |
|
||||
if (src_archive == nullptr || dest_archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
if (src_archive == dest_archive) { |
|
||||
return src_archive->RenameDirectory(src_path, dest_path); |
|
||||
} else { |
|
||||
// TODO: Implement renaming across archives
|
|
||||
return UnimplementedFunction(ErrorModule::FS); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, |
|
||||
const FileSys::Path& path) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
|
|
||||
auto backend = archive->OpenDirectory(path); |
|
||||
if (backend.Failed()) |
|
||||
return backend.Code(); |
|
||||
|
|
||||
auto directory = std::shared_ptr<Directory>(new Directory(std::move(backend).Unwrap(), path)); |
|
||||
return MakeResult<std::shared_ptr<Directory>>(std::move(directory)); |
|
||||
} |
|
||||
|
|
||||
ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle) { |
|
||||
ArchiveBackend* archive = GetArchive(archive_handle); |
|
||||
if (archive == nullptr) |
|
||||
return FileSys::ERR_INVALID_ARCHIVE_HANDLE; |
|
||||
return MakeResult<u64>(archive->GetFreeBytes()); |
|
||||
} |
|
||||
|
|
||||
ResultCode FormatArchive(ArchiveIdCode id_code, const FileSys::ArchiveFormatInfo& format_info, |
|
||||
const FileSys::Path& path) { |
|
||||
auto archive_itr = id_code_map.find(id_code); |
|
||||
if (archive_itr == id_code_map.end()) { |
|
||||
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
|
|
||||
} |
|
||||
|
|
||||
return archive_itr->second->Format(path, format_info); |
|
||||
} |
|
||||
|
|
||||
ResultVal<FileSys::ArchiveFormatInfo> GetArchiveFormatInfo(ArchiveIdCode id_code, |
|
||||
FileSys::Path& archive_path) { |
|
||||
auto archive = id_code_map.find(id_code); |
|
||||
if (archive == id_code_map.end()) { |
|
||||
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
|
|
||||
} |
|
||||
|
|
||||
return archive->second->GetFormatInfo(archive_path); |
|
||||
} |
|
||||
|
|
||||
ResultCode CreateExtSaveData(MediaType media_type, u32 high, u32 low, VAddr icon_buffer, |
|
||||
u32 icon_size, const FileSys::ArchiveFormatInfo& format_info) { |
|
||||
// Construct the binary path to the archive first
|
|
||||
FileSys::Path path = |
|
||||
FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low); |
|
||||
|
|
||||
auto archive = id_code_map.find(media_type == MediaType::NAND ? ArchiveIdCode::SharedExtSaveData |
|
||||
: ArchiveIdCode::ExtSaveData); |
|
||||
|
|
||||
if (archive == id_code_map.end()) { |
|
||||
return UnimplementedFunction(ErrorModule::FS); // TODO(Subv): Find the right error
|
|
||||
} |
|
||||
|
|
||||
auto ext_savedata = static_cast<FileSys::ArchiveFactory_ExtSaveData*>(archive->second.get()); |
|
||||
|
|
||||
ResultCode result = ext_savedata->Format(path, format_info); |
|
||||
if (result.IsError()) |
|
||||
return result; |
|
||||
|
|
||||
if (!Memory::IsValidVirtualAddress(icon_buffer)) |
|
||||
return ResultCode(-1); // TODO(Subv): Find the right error code
|
|
||||
|
|
||||
std::vector<u8> smdh_icon(icon_size); |
|
||||
Memory::ReadBlock(icon_buffer, smdh_icon.data(), smdh_icon.size()); |
|
||||
ext_savedata->WriteIcon(path, smdh_icon.data(), smdh_icon.size()); |
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteExtSaveData(MediaType media_type, u32 high, u32 low) { |
|
||||
// Construct the binary path to the archive first
|
|
||||
FileSys::Path path = |
|
||||
FileSys::ConstructExtDataBinaryPath(static_cast<u32>(media_type), high, low); |
|
||||
|
|
||||
std::string media_type_directory; |
|
||||
if (media_type == MediaType::NAND) { |
|
||||
media_type_directory = FileUtil::GetUserPath(D_NAND_IDX); |
|
||||
} else if (media_type == MediaType::SDMC) { |
|
||||
media_type_directory = FileUtil::GetUserPath(D_SDMC_IDX); |
|
||||
} else { |
|
||||
LOG_ERROR(Service_FS, "Unsupported media type %u", media_type); |
|
||||
return ResultCode(-1); // TODO(Subv): Find the right error code
|
|
||||
} |
|
||||
|
|
||||
// Delete all directories (/user, /boss) and the icon file.
|
|
||||
std::string base_path = |
|
||||
FileSys::GetExtDataContainerPath(media_type_directory, media_type == MediaType::NAND); |
|
||||
std::string extsavedata_path = FileSys::GetExtSaveDataPath(base_path, path); |
|
||||
if (FileUtil::Exists(extsavedata_path) && !FileUtil::DeleteDirRecursively(extsavedata_path)) |
|
||||
return ResultCode(-1); // TODO(Subv): Find the right error code
|
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultCode DeleteSystemSaveData(u32 high, u32 low) { |
|
||||
// Construct the binary path to the archive first
|
|
||||
FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); |
|
||||
|
|
||||
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); |
|
||||
std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory); |
|
||||
std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path); |
|
||||
if (!FileUtil::DeleteDirRecursively(systemsavedata_path)) |
|
||||
return ResultCode(-1); // TODO(Subv): Find the right error code
|
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
ResultCode CreateSystemSaveData(u32 high, u32 low) { |
|
||||
// Construct the binary path to the archive first
|
|
||||
FileSys::Path path = FileSys::ConstructSystemSaveDataBinaryPath(high, low); |
|
||||
|
|
||||
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); |
|
||||
std::string base_path = FileSys::GetSystemSaveDataContainerPath(nand_directory); |
|
||||
std::string systemsavedata_path = FileSys::GetSystemSaveDataPath(base_path, path); |
|
||||
if (!FileUtil::CreateFullPath(systemsavedata_path)) |
|
||||
return ResultCode(-1); // TODO(Subv): Find the right error code
|
|
||||
return RESULT_SUCCESS; |
|
||||
} |
|
||||
|
|
||||
void RegisterArchiveTypes() { |
|
||||
// TODO(Subv): Add the other archive types (see here for the known types:
|
|
||||
// http://3dbrew.org/wiki/FS:OpenArchive#Archive_idcodes).
|
|
||||
|
|
||||
std::string sdmc_directory = FileUtil::GetUserPath(D_SDMC_IDX); |
|
||||
std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); |
|
||||
auto sdmc_factory = std::make_unique<FileSys::ArchiveFactory_SDMC>(sdmc_directory); |
|
||||
if (sdmc_factory->Initialize()) |
|
||||
RegisterArchiveType(std::move(sdmc_factory), ArchiveIdCode::SDMC); |
|
||||
else |
|
||||
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 sd_savedata_source = std::make_shared<FileSys::ArchiveSource_SDSaveData>(sdmc_directory); |
|
||||
auto savedata_factory = std::make_unique<FileSys::ArchiveFactory_SaveData>(sd_savedata_source); |
|
||||
RegisterArchiveType(std::move(savedata_factory), ArchiveIdCode::SaveData); |
|
||||
auto other_savedata_permitted_factory = |
|
||||
std::make_unique<FileSys::ArchiveFactory_OtherSaveDataPermitted>(sd_savedata_source); |
|
||||
RegisterArchiveType(std::move(other_savedata_permitted_factory), |
|
||||
ArchiveIdCode::OtherSaveDataPermitted); |
|
||||
auto other_savedata_general_factory = |
|
||||
std::make_unique<FileSys::ArchiveFactory_OtherSaveDataGeneral>(sd_savedata_source); |
|
||||
RegisterArchiveType(std::move(other_savedata_general_factory), |
|
||||
ArchiveIdCode::OtherSaveDataGeneral); |
|
||||
|
|
||||
auto extsavedata_factory = |
|
||||
std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(sdmc_directory, false); |
|
||||
if (extsavedata_factory->Initialize()) |
|
||||
RegisterArchiveType(std::move(extsavedata_factory), ArchiveIdCode::ExtSaveData); |
|
||||
else |
|
||||
LOG_ERROR(Service_FS, "Can't instantiate ExtSaveData archive with path %s", |
|
||||
extsavedata_factory->GetMountPoint().c_str()); |
|
||||
|
|
||||
auto sharedextsavedata_factory = |
|
||||
std::make_unique<FileSys::ArchiveFactory_ExtSaveData>(nand_directory, true); |
|
||||
if (sharedextsavedata_factory->Initialize()) |
|
||||
RegisterArchiveType(std::move(sharedextsavedata_factory), ArchiveIdCode::SharedExtSaveData); |
|
||||
else |
|
||||
LOG_ERROR(Service_FS, "Can't instantiate SharedExtSaveData archive with path %s", |
|
||||
sharedextsavedata_factory->GetMountPoint().c_str()); |
|
||||
|
|
||||
// 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); |
|
||||
RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData); |
|
||||
|
|
||||
auto selfncch_factory = std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(); |
|
||||
RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH); |
|
||||
} |
|
||||
|
|
||||
void RegisterSelfNCCH(Loader::AppLoader& app_loader) { |
|
||||
auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH); |
|
||||
if (itr == id_code_map.end()) { |
|
||||
LOG_ERROR(Service_FS, |
|
||||
"Could not register a new NCCH because the SelfNCCH archive hasn't been created"); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
auto* factory = static_cast<FileSys::ArchiveFactory_SelfNCCH*>(itr->second.get()); |
|
||||
factory->Register(app_loader); |
|
||||
} |
|
||||
|
|
||||
void UnregisterArchiveTypes() { |
|
||||
id_code_map.clear(); |
|
||||
} |
|
||||
|
|
||||
/// Initialize archives
|
|
||||
void ArchiveInit() { |
|
||||
next_handle = 1; |
|
||||
|
|
||||
AddService(new FS::Interface); |
|
||||
|
|
||||
RegisterArchiveTypes(); |
|
||||
} |
|
||||
|
|
||||
/// Shutdown archives
|
|
||||
void ArchiveShutdown() { |
|
||||
handle_map.clear(); |
|
||||
UnregisterArchiveTypes(); |
|
||||
} |
|
||||
|
|
||||
} // namespace FS
|
|
||||
} // namespace Service
|
|
||||
@ -1,276 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include <string> |
|
||||
#include "common/common_types.h" |
|
||||
#include "core/file_sys/archive_backend.h" |
|
||||
#include "core/hle/kernel/hle_ipc.h" |
|
||||
#include "core/hle/result.h" |
|
||||
|
|
||||
namespace FileSys { |
|
||||
class DirectoryBackend; |
|
||||
class FileBackend; |
|
||||
} |
|
||||
|
|
||||
/// The unique system identifier hash, also known as ID0 |
|
||||
static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"}; |
|
||||
/// The scrambled SD card CID, also known as ID1 |
|
||||
static constexpr char SDCARD_ID[]{"00000000000000000000000000000000"}; |
|
||||
|
|
||||
namespace Loader { |
|
||||
class AppLoader; |
|
||||
} |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace FS { |
|
||||
|
|
||||
/// Supported archive types |
|
||||
enum class ArchiveIdCode : u32 { |
|
||||
SelfNCCH = 0x00000003, |
|
||||
SaveData = 0x00000004, |
|
||||
ExtSaveData = 0x00000006, |
|
||||
SharedExtSaveData = 0x00000007, |
|
||||
SystemSaveData = 0x00000008, |
|
||||
SDMC = 0x00000009, |
|
||||
SDMCWriteOnly = 0x0000000A, |
|
||||
NCCH = 0x2345678A, |
|
||||
OtherSaveDataGeneral = 0x567890B2, |
|
||||
OtherSaveDataPermitted = 0x567890B4, |
|
||||
}; |
|
||||
|
|
||||
/// Media types for the archives |
|
||||
enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 }; |
|
||||
|
|
||||
typedef u64 ArchiveHandle; |
|
||||
|
|
||||
class File final : public Kernel::SessionRequestHandler { |
|
||||
public: |
|
||||
File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path); |
|
||||
~File(); |
|
||||
|
|
||||
std::string GetName() const { |
|
||||
return "Path: " + path.DebugStr(); |
|
||||
} |
|
||||
|
|
||||
FileSys::Path path; ///< Path of the file |
|
||||
u32 priority; ///< Priority of the file. TODO(Subv): Find out what this means |
|
||||
std::unique_ptr<FileSys::FileBackend> backend; ///< File backend interface |
|
||||
|
|
||||
protected: |
|
||||
void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; |
|
||||
}; |
|
||||
|
|
||||
class Directory final : public Kernel::SessionRequestHandler { |
|
||||
public: |
|
||||
Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend, const FileSys::Path& path); |
|
||||
~Directory(); |
|
||||
|
|
||||
std::string GetName() const { |
|
||||
return "Directory: " + path.DebugStr(); |
|
||||
} |
|
||||
|
|
||||
FileSys::Path path; ///< Path of the directory |
|
||||
std::unique_ptr<FileSys::DirectoryBackend> backend; ///< File backend interface |
|
||||
|
|
||||
protected: |
|
||||
void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override; |
|
||||
}; |
|
||||
|
|
||||
/** |
|
||||
* Opens an archive |
|
||||
* @param id_code IdCode of the archive to open |
|
||||
* @param archive_path Path to the archive, used with Binary paths |
|
||||
* @return Handle to the opened archive |
|
||||
*/ |
|
||||
ResultVal<ArchiveHandle> OpenArchive(ArchiveIdCode id_code, FileSys::Path& archive_path); |
|
||||
|
|
||||
/** |
|
||||
* Closes an archive |
|
||||
* @param handle Handle to the archive to close |
|
||||
*/ |
|
||||
ResultCode CloseArchive(ArchiveHandle handle); |
|
||||
|
|
||||
/** |
|
||||
* Registers an Archive type, instances of which can later be opened using its IdCode. |
|
||||
* @param factory File system backend interface to the archive |
|
||||
* @param id_code Id code used to access this type of archive |
|
||||
*/ |
|
||||
ResultCode RegisterArchiveType(std::unique_ptr<FileSys::ArchiveFactory>&& factory, |
|
||||
ArchiveIdCode id_code); |
|
||||
|
|
||||
/** |
|
||||
* Open a File from an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @param path Path to the File inside of the Archive |
|
||||
* @param mode Mode under which to open the File |
|
||||
* @return The opened File object |
|
||||
*/ |
|
||||
ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handle, |
|
||||
const FileSys::Path& path, |
|
||||
const FileSys::Mode mode); |
|
||||
|
|
||||
/** |
|
||||
* Delete a File from an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @param path Path to the File inside of the Archive |
|
||||
* @return Whether deletion succeeded |
|
||||
*/ |
|
||||
ResultCode DeleteFileFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path); |
|
||||
|
|
||||
/** |
|
||||
* Rename a File between two Archives |
|
||||
* @param src_archive_handle Handle to the source Archive object |
|
||||
* @param src_path Path to the File inside of the source Archive |
|
||||
* @param dest_archive_handle Handle to the destination Archive object |
|
||||
* @param dest_path Path to the File inside of the destination Archive |
|
||||
* @return Whether rename succeeded |
|
||||
*/ |
|
||||
ResultCode RenameFileBetweenArchives(ArchiveHandle src_archive_handle, |
|
||||
const FileSys::Path& src_path, |
|
||||
ArchiveHandle dest_archive_handle, |
|
||||
const FileSys::Path& dest_path); |
|
||||
|
|
||||
/** |
|
||||
* Delete a Directory from an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @param path Path to the Directory inside of the Archive |
|
||||
* @return Whether deletion succeeded |
|
||||
*/ |
|
||||
ResultCode DeleteDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path); |
|
||||
|
|
||||
/** |
|
||||
* Delete a Directory and anything under it from an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @param path Path to the Directory inside of the Archive |
|
||||
* @return Whether deletion succeeded |
|
||||
*/ |
|
||||
ResultCode DeleteDirectoryRecursivelyFromArchive(ArchiveHandle archive_handle, |
|
||||
const FileSys::Path& path); |
|
||||
|
|
||||
/** |
|
||||
* Create a File in an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @param path Path to the File inside of the Archive |
|
||||
* @param file_size The size of the new file, filled with zeroes |
|
||||
* @return File creation result code |
|
||||
*/ |
|
||||
ResultCode CreateFileInArchive(ArchiveHandle archive_handle, const FileSys::Path& path, |
|
||||
u64 file_size); |
|
||||
|
|
||||
/** |
|
||||
* Create a Directory from an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @param path Path to the Directory inside of the Archive |
|
||||
* @return Whether creation of directory succeeded |
|
||||
*/ |
|
||||
ResultCode CreateDirectoryFromArchive(ArchiveHandle archive_handle, const FileSys::Path& path); |
|
||||
|
|
||||
/** |
|
||||
* Rename a Directory between two Archives |
|
||||
* @param src_archive_handle Handle to the source Archive object |
|
||||
* @param src_path Path to the Directory inside of the source Archive |
|
||||
* @param dest_archive_handle Handle to the destination Archive object |
|
||||
* @param dest_path Path to the Directory inside of the destination Archive |
|
||||
* @return Whether rename succeeded |
|
||||
*/ |
|
||||
ResultCode RenameDirectoryBetweenArchives(ArchiveHandle src_archive_handle, |
|
||||
const FileSys::Path& src_path, |
|
||||
ArchiveHandle dest_archive_handle, |
|
||||
const FileSys::Path& dest_path); |
|
||||
|
|
||||
/** |
|
||||
* Open a Directory from an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @param path Path to the Directory inside of the Archive |
|
||||
* @return The opened Directory object |
|
||||
*/ |
|
||||
ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle archive_handle, |
|
||||
const FileSys::Path& path); |
|
||||
|
|
||||
/** |
|
||||
* Get the free space in an Archive |
|
||||
* @param archive_handle Handle to an open Archive object |
|
||||
* @return The number of free bytes in the archive |
|
||||
*/ |
|
||||
ResultVal<u64> GetFreeBytesInArchive(ArchiveHandle archive_handle); |
|
||||
|
|
||||
/** |
|
||||
* Erases the contents of the physical folder that contains the archive |
|
||||
* identified by the specified id code and path |
|
||||
* @param id_code The id of the archive to format |
|
||||
* @param format_info Format information about the new archive |
|
||||
* @param path The path to the archive, if relevant. |
|
||||
* @return ResultCode 0 on success or the corresponding code on error |
|
||||
*/ |
|
||||
ResultCode FormatArchive(ArchiveIdCode id_code, const FileSys::ArchiveFormatInfo& format_info, |
|
||||
const FileSys::Path& path = FileSys::Path()); |
|
||||
|
|
||||
/** |
|
||||
* Retrieves the format info about the archive of the specified type and path. |
|
||||
* The format info is supplied by the client code when creating archives. |
|
||||
* @param id_code The id of the archive |
|
||||
* @param archive_path The path of the archive, if relevant |
|
||||
* @return The format info of the archive, or the corresponding error code if failed. |
|
||||
*/ |
|
||||
ResultVal<FileSys::ArchiveFormatInfo> GetArchiveFormatInfo(ArchiveIdCode id_code, |
|
||||
FileSys::Path& archive_path); |
|
||||
|
|
||||
/** |
|
||||
* Creates a blank SharedExtSaveData archive for the specified extdata ID |
|
||||
* @param media_type The media type of the archive to create (NAND / SDMC) |
|
||||
* @param high The high word of the extdata id to create |
|
||||
* @param low The low word of the extdata id to create |
|
||||
* @param icon_buffer VAddr of the SMDH icon for this ExtSaveData |
|
||||
* @param icon_size Size of the SMDH icon |
|
||||
* @param format_info Format information about the new archive |
|
||||
* @return ResultCode 0 on success or the corresponding code on error |
|
||||
*/ |
|
||||
ResultCode CreateExtSaveData(MediaType media_type, u32 high, u32 low, VAddr icon_buffer, |
|
||||
u32 icon_size, const FileSys::ArchiveFormatInfo& format_info); |
|
||||
|
|
||||
/** |
|
||||
* Deletes the SharedExtSaveData archive for the specified extdata ID |
|
||||
* @param media_type The media type of the archive to delete (NAND / SDMC) |
|
||||
* @param high The high word of the extdata id to delete |
|
||||
* @param low The low word of the extdata id to delete |
|
||||
* @return ResultCode 0 on success or the corresponding code on error |
|
||||
*/ |
|
||||
ResultCode DeleteExtSaveData(MediaType media_type, u32 high, u32 low); |
|
||||
|
|
||||
/** |
|
||||
* Deletes the SystemSaveData archive folder for the specified save data id |
|
||||
* @param high The high word of the SystemSaveData archive to delete |
|
||||
* @param low The low word of the SystemSaveData archive to delete |
|
||||
* @return ResultCode 0 on success or the corresponding code on error |
|
||||
*/ |
|
||||
ResultCode DeleteSystemSaveData(u32 high, u32 low); |
|
||||
|
|
||||
/** |
|
||||
* Creates the SystemSaveData archive folder for the specified save data id |
|
||||
* @param high The high word of the SystemSaveData archive to create |
|
||||
* @param low The low word of the SystemSaveData archive to create |
|
||||
* @return ResultCode 0 on success or the corresponding code on error |
|
||||
*/ |
|
||||
ResultCode CreateSystemSaveData(u32 high, u32 low); |
|
||||
|
|
||||
/// Initialize archives |
|
||||
void ArchiveInit(); |
|
||||
|
|
||||
/// Shutdown archives |
|
||||
void ArchiveShutdown(); |
|
||||
|
|
||||
/// Registers a new NCCH file with the SelfNCCH archive factory |
|
||||
void RegisterSelfNCCH(Loader::AppLoader& app_loader); |
|
||||
|
|
||||
/// Register all archive types |
|
||||
void RegisterArchiveTypes(); |
|
||||
|
|
||||
/// Unregister all archive types |
|
||||
void UnregisterArchiveTypes(); |
|
||||
|
|
||||
} // namespace FS |
|
||||
} // namespace Service |
|
||||
1045
src/core/hle/service/fs/fs_user.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,23 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace FS { |
|
||||
|
|
||||
/// Interface to "fs:USER" service |
|
||||
class Interface : public Service::Interface { |
|
||||
public: |
|
||||
Interface(); |
|
||||
|
|
||||
std::string GetPortName() const override { |
|
||||
return "fs:USER"; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} // namespace FS |
|
||||
} // namespace Service |
|
||||
@ -1,29 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/hid/hid.h"
|
|
||||
#include "core/hle/service/hid/hid_spvr.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace HID { |
|
||||
|
|
||||
const Interface::FunctionInfo FunctionTable[] = { |
|
||||
{0x000A0000, GetIPCHandles, "GetIPCHandles"}, |
|
||||
{0x000B0000, nullptr, "StartAnalogStickCalibration"}, |
|
||||
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, |
|
||||
{0x00110000, EnableAccelerometer, "EnableAccelerometer"}, |
|
||||
{0x00120000, DisableAccelerometer, "DisableAccelerometer"}, |
|
||||
{0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, |
|
||||
{0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, |
|
||||
{0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, |
|
||||
{0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, |
|
||||
{0x00170000, GetSoundVolume, "GetSoundVolume"}, |
|
||||
}; |
|
||||
|
|
||||
HID_SPVR_Interface::HID_SPVR_Interface() { |
|
||||
Register(FunctionTable); |
|
||||
} |
|
||||
|
|
||||
} // namespace HID
|
|
||||
} // namespace Service
|
|
||||
@ -1,22 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace HID { |
|
||||
|
|
||||
class HID_SPVR_Interface : public Service::Interface { |
|
||||
public: |
|
||||
HID_SPVR_Interface(); |
|
||||
|
|
||||
std::string GetPortName() const override { |
|
||||
return "hid:SPVR"; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} // namespace HID |
|
||||
} // namespace Service |
|
||||
@ -1,29 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/hid/hid.h"
|
|
||||
#include "core/hle/service/hid/hid_user.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace HID { |
|
||||
|
|
||||
const Interface::FunctionInfo FunctionTable[] = { |
|
||||
{0x000A0000, GetIPCHandles, "GetIPCHandles"}, |
|
||||
{0x000B0000, nullptr, "StartAnalogStickCalibration"}, |
|
||||
{0x000E0000, nullptr, "GetAnalogStickCalibrateParam"}, |
|
||||
{0x00110000, EnableAccelerometer, "EnableAccelerometer"}, |
|
||||
{0x00120000, DisableAccelerometer, "DisableAccelerometer"}, |
|
||||
{0x00130000, EnableGyroscopeLow, "EnableGyroscopeLow"}, |
|
||||
{0x00140000, DisableGyroscopeLow, "DisableGyroscopeLow"}, |
|
||||
{0x00150000, GetGyroscopeLowRawToDpsCoefficient, "GetGyroscopeLowRawToDpsCoefficient"}, |
|
||||
{0x00160000, GetGyroscopeLowCalibrateParam, "GetGyroscopeLowCalibrateParam"}, |
|
||||
{0x00170000, GetSoundVolume, "GetSoundVolume"}, |
|
||||
}; |
|
||||
|
|
||||
HID_U_Interface::HID_U_Interface() { |
|
||||
Register(FunctionTable); |
|
||||
} |
|
||||
|
|
||||
} // namespace HID
|
|
||||
} // namespace Service
|
|
||||
@ -1,28 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
// This service is used for interfacing to physical user controls. |
|
||||
// Uses include game pad controls, touchscreen, accelerometers, gyroscopes, and debug pad. |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace HID { |
|
||||
|
|
||||
/** |
|
||||
* HID service interface. |
|
||||
*/ |
|
||||
class HID_U_Interface : public Service::Interface { |
|
||||
public: |
|
||||
HID_U_Interface(); |
|
||||
|
|
||||
std::string GetPortName() const override { |
|
||||
return "hid:USER"; |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
} // namespace HID |
|
||||
} // namespace Service |
|
||||
@ -1,16 +0,0 @@ |
|||||
// Copyright 2017 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/ns/ns.h"
|
|
||||
#include "core/hle/service/ns/ns_s.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace NS { |
|
||||
|
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) { |
|
||||
std::make_shared<NS_S>()->InstallAsService(service_manager); |
|
||||
} |
|
||||
|
|
||||
} // namespace NS
|
|
||||
} // namespace Service
|
|
||||
@ -1,16 +0,0 @@ |
|||||
// Copyright 2017 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace NS { |
|
||||
|
|
||||
/// Registers all NS services with the specified service manager. |
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager); |
|
||||
|
|
||||
} // namespace NS |
|
||||
} // namespace Service |
|
||||
@ -1,34 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include "core/hle/service/ns/ns_s.h"
|
|
||||
|
|
||||
namespace Service { |
|
||||
namespace NS { |
|
||||
|
|
||||
NS_S::NS_S() : ServiceFramework("ns:s", 2) { |
|
||||
static const FunctionInfo functions[] = { |
|
||||
{0x000100C0, nullptr, "LaunchFIRM"}, |
|
||||
{0x000200C0, nullptr, "LaunchTitle"}, |
|
||||
{0x00030000, nullptr, "TerminateApplication"}, |
|
||||
{0x00040040, nullptr, "TerminateProcess"}, |
|
||||
{0x000500C0, nullptr, "LaunchApplicationFIRM"}, |
|
||||
{0x00060042, nullptr, "SetFIRMParams4A0"}, |
|
||||
{0x00070042, nullptr, "CardUpdateInitialize"}, |
|
||||
{0x00080000, nullptr, "CardUpdateShutdown"}, |
|
||||
{0x000D0140, nullptr, "SetTWLBannerHMAC"}, |
|
||||
{0x000E0000, nullptr, "ShutdownAsync"}, |
|
||||
{0x00100180, nullptr, "RebootSystem"}, |
|
||||
{0x00110100, nullptr, "TerminateTitle"}, |
|
||||
{0x001200C0, nullptr, "SetApplicationCpuTimeLimit"}, |
|
||||
{0x00150140, nullptr, "LaunchApplication"}, |
|
||||
{0x00160000, nullptr, "RebootSystemClean"}, |
|
||||
}; |
|
||||
RegisterHandlers(functions); |
|
||||
} |
|
||||
|
|
||||
NS_S::~NS_S() = default; |
|
||||
|
|
||||
} // namespace NS
|
|
||||
} // namespace Service
|
|
||||
@ -1,21 +0,0 @@ |
|||||
// Copyright 2015 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "core/hle/kernel/kernel.h" |
|
||||
#include "core/hle/service/service.h" |
|
||||
|
|
||||
namespace Service { |
|
||||
namespace NS { |
|
||||
|
|
||||
/// Interface to "ns:s" service |
|
||||
class NS_S final : public ServiceFramework<NS_S> { |
|
||||
public: |
|
||||
NS_S(); |
|
||||
~NS_S(); |
|
||||
}; |
|
||||
|
|
||||
} // namespace NS |
|
||||
} // namespace Service |
|
||||
@ -1,350 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <vector>
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "core/file_sys/archive_selfncch.h"
|
|
||||
#include "core/hle/kernel/process.h"
|
|
||||
#include "core/hle/kernel/resource_limit.h"
|
|
||||
#include "core/hle/service/fs/archive.h"
|
|
||||
#include "core/loader/3dsx.h"
|
|
||||
#include "core/memory.h"
|
|
||||
|
|
||||
namespace Loader { |
|
||||
|
|
||||
/*
|
|
||||
* File layout: |
|
||||
* - File header |
|
||||
* - Code, rodata and data relocation table headers |
|
||||
* - Code segment |
|
||||
* - Rodata segment |
|
||||
* - Loadable (non-BSS) part of the data segment |
|
||||
* - Code relocation table |
|
||||
* - Rodata relocation table |
|
||||
* - Data relocation table |
|
||||
* |
|
||||
* Memory layout before relocations are applied: |
|
||||
* [0..codeSegSize) -> code segment |
|
||||
* [codeSegSize..rodataSegSize) -> rodata segment |
|
||||
* [rodataSegSize..dataSegSize) -> data segment |
|
||||
* |
|
||||
* Memory layout after relocations are applied: well, however the loader sets it up :) |
|
||||
* The entrypoint is always the start of the code segment. |
|
||||
* The BSS section must be cleared manually by the application. |
|
||||
*/ |
|
||||
|
|
||||
enum THREEDSX_Error { ERROR_NONE = 0, ERROR_READ = 1, ERROR_FILE = 2, ERROR_ALLOC = 3 }; |
|
||||
|
|
||||
static const u32 RELOCBUFSIZE = 512; |
|
||||
static const unsigned int NUM_SEGMENTS = 3; |
|
||||
|
|
||||
// File header
|
|
||||
#pragma pack(1)
|
|
||||
struct THREEDSX_Header { |
|
||||
u32 magic; |
|
||||
u16 header_size, reloc_hdr_size; |
|
||||
u32 format_ver; |
|
||||
u32 flags; |
|
||||
|
|
||||
// Sizes of the code, rodata and data segments +
|
|
||||
// size of the BSS section (uninitialized latter half of the data segment)
|
|
||||
u32 code_seg_size, rodata_seg_size, data_seg_size, bss_size; |
|
||||
// offset and size of smdh
|
|
||||
u32 smdh_offset, smdh_size; |
|
||||
// offset to filesystem
|
|
||||
u32 fs_offset; |
|
||||
}; |
|
||||
|
|
||||
// Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts.
|
|
||||
struct THREEDSX_RelocHdr { |
|
||||
// # of absolute relocations (that is, fix address to post-relocation memory layout)
|
|
||||
u32 cross_segment_absolute; |
|
||||
// # of cross-segment relative relocations (that is, 32bit signed offsets that need to be
|
|
||||
// patched)
|
|
||||
u32 cross_segment_relative; |
|
||||
// more?
|
|
||||
|
|
||||
// Relocations are written in this order:
|
|
||||
// - Absolute relocations
|
|
||||
// - Relative relocations
|
|
||||
}; |
|
||||
|
|
||||
// Relocation entry: from the current pointer, skip X words and patch Y words
|
|
||||
struct THREEDSX_Reloc { |
|
||||
u16 skip, patch; |
|
||||
}; |
|
||||
#pragma pack()
|
|
||||
|
|
||||
struct THREEloadinfo { |
|
||||
u8* seg_ptrs[3]; // code, rodata & data
|
|
||||
u32 seg_addrs[3]; |
|
||||
u32 seg_sizes[3]; |
|
||||
}; |
|
||||
|
|
||||
static u32 TranslateAddr(u32 addr, const THREEloadinfo* loadinfo, u32* offsets) { |
|
||||
if (addr < offsets[0]) |
|
||||
return loadinfo->seg_addrs[0] + addr; |
|
||||
if (addr < offsets[1]) |
|
||||
return loadinfo->seg_addrs[1] + addr - offsets[0]; |
|
||||
return loadinfo->seg_addrs[2] + addr - offsets[1]; |
|
||||
} |
|
||||
|
|
||||
using Kernel::CodeSet; |
|
||||
using Kernel::SharedPtr; |
|
||||
|
|
||||
static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, |
|
||||
SharedPtr<CodeSet>* out_codeset) { |
|
||||
if (!file.IsOpen()) |
|
||||
return ERROR_FILE; |
|
||||
|
|
||||
// Reset read pointer in case this file has been read before.
|
|
||||
file.Seek(0, SEEK_SET); |
|
||||
|
|
||||
THREEDSX_Header hdr; |
|
||||
if (file.ReadBytes(&hdr, sizeof(hdr)) != sizeof(hdr)) |
|
||||
return ERROR_READ; |
|
||||
|
|
||||
THREEloadinfo loadinfo; |
|
||||
// loadinfo segments must be a multiple of 0x1000
|
|
||||
loadinfo.seg_sizes[0] = (hdr.code_seg_size + 0xFFF) & ~0xFFF; |
|
||||
loadinfo.seg_sizes[1] = (hdr.rodata_seg_size + 0xFFF) & ~0xFFF; |
|
||||
loadinfo.seg_sizes[2] = (hdr.data_seg_size + 0xFFF) & ~0xFFF; |
|
||||
u32 offsets[2] = {loadinfo.seg_sizes[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1]}; |
|
||||
u32 n_reloc_tables = hdr.reloc_hdr_size / sizeof(u32); |
|
||||
std::vector<u8> program_image(loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] + |
|
||||
loadinfo.seg_sizes[2]); |
|
||||
|
|
||||
loadinfo.seg_addrs[0] = base_addr; |
|
||||
loadinfo.seg_addrs[1] = loadinfo.seg_addrs[0] + loadinfo.seg_sizes[0]; |
|
||||
loadinfo.seg_addrs[2] = loadinfo.seg_addrs[1] + loadinfo.seg_sizes[1]; |
|
||||
loadinfo.seg_ptrs[0] = program_image.data(); |
|
||||
loadinfo.seg_ptrs[1] = loadinfo.seg_ptrs[0] + loadinfo.seg_sizes[0]; |
|
||||
loadinfo.seg_ptrs[2] = loadinfo.seg_ptrs[1] + loadinfo.seg_sizes[1]; |
|
||||
|
|
||||
// Skip header for future compatibility
|
|
||||
file.Seek(hdr.header_size, SEEK_SET); |
|
||||
|
|
||||
// Read the relocation headers
|
|
||||
std::vector<u32> relocs(n_reloc_tables * NUM_SEGMENTS); |
|
||||
for (unsigned int current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) { |
|
||||
size_t size = n_reloc_tables * sizeof(u32); |
|
||||
if (file.ReadBytes(&relocs[current_segment * n_reloc_tables], size) != size) |
|
||||
return ERROR_READ; |
|
||||
} |
|
||||
|
|
||||
// Read the segments
|
|
||||
if (file.ReadBytes(loadinfo.seg_ptrs[0], hdr.code_seg_size) != hdr.code_seg_size) |
|
||||
return ERROR_READ; |
|
||||
if (file.ReadBytes(loadinfo.seg_ptrs[1], hdr.rodata_seg_size) != hdr.rodata_seg_size) |
|
||||
return ERROR_READ; |
|
||||
if (file.ReadBytes(loadinfo.seg_ptrs[2], hdr.data_seg_size - hdr.bss_size) != |
|
||||
hdr.data_seg_size - hdr.bss_size) |
|
||||
return ERROR_READ; |
|
||||
|
|
||||
// BSS clear
|
|
||||
memset((char*)loadinfo.seg_ptrs[2] + hdr.data_seg_size - hdr.bss_size, 0, hdr.bss_size); |
|
||||
|
|
||||
// Relocate the segments
|
|
||||
for (unsigned int current_segment = 0; current_segment < NUM_SEGMENTS; ++current_segment) { |
|
||||
for (unsigned current_segment_reloc_table = 0; current_segment_reloc_table < n_reloc_tables; |
|
||||
current_segment_reloc_table++) { |
|
||||
u32 n_relocs = relocs[current_segment * n_reloc_tables + current_segment_reloc_table]; |
|
||||
if (current_segment_reloc_table >= 2) { |
|
||||
// We are not using this table - ignore it because we don't know what it dose
|
|
||||
file.Seek(n_relocs * sizeof(THREEDSX_Reloc), SEEK_CUR); |
|
||||
continue; |
|
||||
} |
|
||||
THREEDSX_Reloc reloc_table[RELOCBUFSIZE]; |
|
||||
|
|
||||
u32* pos = (u32*)loadinfo.seg_ptrs[current_segment]; |
|
||||
const u32* end_pos = pos + (loadinfo.seg_sizes[current_segment] / 4); |
|
||||
|
|
||||
while (n_relocs) { |
|
||||
u32 remaining = std::min(RELOCBUFSIZE, n_relocs); |
|
||||
n_relocs -= remaining; |
|
||||
|
|
||||
if (file.ReadBytes(reloc_table, remaining * sizeof(THREEDSX_Reloc)) != |
|
||||
remaining * sizeof(THREEDSX_Reloc)) |
|
||||
return ERROR_READ; |
|
||||
|
|
||||
for (unsigned current_inprogress = 0; |
|
||||
current_inprogress < remaining && pos < end_pos; current_inprogress++) { |
|
||||
const auto& table = reloc_table[current_inprogress]; |
|
||||
LOG_TRACE(Loader, "(t=%d,skip=%u,patch=%u)", current_segment_reloc_table, |
|
||||
static_cast<u32>(table.skip), static_cast<u32>(table.patch)); |
|
||||
pos += table.skip; |
|
||||
s32 num_patches = table.patch; |
|
||||
while (0 < num_patches && pos < end_pos) { |
|
||||
u32 in_addr = base_addr + static_cast<u32>(reinterpret_cast<u8*>(pos) - |
|
||||
program_image.data()); |
|
||||
u32 orig_data = *pos; |
|
||||
u32 sub_type = orig_data >> (32 - 4); |
|
||||
u32 addr = TranslateAddr(orig_data & ~0xF0000000, &loadinfo, offsets); |
|
||||
LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", in_addr, addr, |
|
||||
current_segment_reloc_table, *pos); |
|
||||
switch (current_segment_reloc_table) { |
|
||||
case 0: { |
|
||||
if (sub_type != 0) |
|
||||
return ERROR_READ; |
|
||||
*pos = addr; |
|
||||
break; |
|
||||
} |
|
||||
case 1: { |
|
||||
u32 data = addr - in_addr; |
|
||||
switch (sub_type) { |
|
||||
case 0: // 32-bit signed offset
|
|
||||
*pos = data; |
|
||||
break; |
|
||||
case 1: // 31-bit signed offset
|
|
||||
*pos = data & ~(1U << 31); |
|
||||
break; |
|
||||
default: |
|
||||
return ERROR_READ; |
|
||||
} |
|
||||
break; |
|
||||
} |
|
||||
default: |
|
||||
break; // this should never happen
|
|
||||
} |
|
||||
pos++; |
|
||||
num_patches--; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Create the CodeSet
|
|
||||
SharedPtr<CodeSet> code_set = CodeSet::Create("", 0); |
|
||||
|
|
||||
code_set->code.offset = loadinfo.seg_ptrs[0] - program_image.data(); |
|
||||
code_set->code.addr = loadinfo.seg_addrs[0]; |
|
||||
code_set->code.size = loadinfo.seg_sizes[0]; |
|
||||
|
|
||||
code_set->rodata.offset = loadinfo.seg_ptrs[1] - program_image.data(); |
|
||||
code_set->rodata.addr = loadinfo.seg_addrs[1]; |
|
||||
code_set->rodata.size = loadinfo.seg_sizes[1]; |
|
||||
|
|
||||
code_set->data.offset = loadinfo.seg_ptrs[2] - program_image.data(); |
|
||||
code_set->data.addr = loadinfo.seg_addrs[2]; |
|
||||
code_set->data.size = loadinfo.seg_sizes[2]; |
|
||||
|
|
||||
code_set->entrypoint = code_set->code.addr; |
|
||||
code_set->memory = std::make_shared<std::vector<u8>>(std::move(program_image)); |
|
||||
|
|
||||
LOG_DEBUG(Loader, "code size: 0x%X", loadinfo.seg_sizes[0]); |
|
||||
LOG_DEBUG(Loader, "rodata size: 0x%X", loadinfo.seg_sizes[1]); |
|
||||
LOG_DEBUG(Loader, "data size: 0x%X (including 0x%X of bss)", loadinfo.seg_sizes[2], |
|
||||
hdr.bss_size); |
|
||||
|
|
||||
*out_codeset = code_set; |
|
||||
return ERROR_NONE; |
|
||||
} |
|
||||
|
|
||||
FileType AppLoader_THREEDSX::IdentifyType(FileUtil::IOFile& file) { |
|
||||
u32 magic; |
|
||||
file.Seek(0, SEEK_SET); |
|
||||
if (1 != file.ReadArray<u32>(&magic, 1)) |
|
||||
return FileType::Error; |
|
||||
|
|
||||
if (MakeMagic('3', 'D', 'S', 'X') == magic) |
|
||||
return FileType::THREEDSX; |
|
||||
|
|
||||
return FileType::Error; |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_THREEDSX::Load(Kernel::SharedPtr<Kernel::Process>& process) { |
|
||||
if (is_loaded) |
|
||||
return ResultStatus::ErrorAlreadyLoaded; |
|
||||
|
|
||||
if (!file.IsOpen()) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
SharedPtr<CodeSet> codeset; |
|
||||
if (Load3DSXFile(file, Memory::PROCESS_IMAGE_VADDR, &codeset) != ERROR_NONE) |
|
||||
return ResultStatus::Error; |
|
||||
codeset->name = filename; |
|
||||
|
|
||||
process = Kernel::Process::Create("main"); |
|
||||
process->LoadModule(codeset, codeset->entrypoint); |
|
||||
process->svc_access_mask.set(); |
|
||||
process->address_mappings = default_address_mappings; |
|
||||
|
|
||||
// Attach the default resource limit (APPLICATION) to the process
|
|
||||
process->resource_limit = |
|
||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); |
|
||||
process->Run(codeset->entrypoint, 48, Kernel::DEFAULT_STACK_SIZE); |
|
||||
|
|
||||
Service::FS::RegisterSelfNCCH(*this); |
|
||||
|
|
||||
is_loaded = true; |
|
||||
return ResultStatus::Success; |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_THREEDSX::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, |
|
||||
u64& offset, u64& size) { |
|
||||
if (!file.IsOpen()) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
// Reset read pointer in case this file has been read before.
|
|
||||
file.Seek(0, SEEK_SET); |
|
||||
|
|
||||
THREEDSX_Header hdr; |
|
||||
if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
if (hdr.header_size != sizeof(THREEDSX_Header)) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
// Check if the 3DSX has a RomFS...
|
|
||||
if (hdr.fs_offset != 0) { |
|
||||
u32 romfs_offset = hdr.fs_offset; |
|
||||
u32 romfs_size = static_cast<u32>(file.GetSize()) - hdr.fs_offset; |
|
||||
|
|
||||
LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset); |
|
||||
LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size); |
|
||||
|
|
||||
// We reopen the file, to allow its position to be independent from file's
|
|
||||
romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); |
|
||||
if (!romfs_file->IsOpen()) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
offset = romfs_offset; |
|
||||
size = romfs_size; |
|
||||
|
|
||||
return ResultStatus::Success; |
|
||||
} |
|
||||
LOG_DEBUG(Loader, "3DSX has no RomFS"); |
|
||||
return ResultStatus::ErrorNotUsed; |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_THREEDSX::ReadIcon(std::vector<u8>& buffer) { |
|
||||
if (!file.IsOpen()) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
// Reset read pointer in case this file has been read before.
|
|
||||
file.Seek(0, SEEK_SET); |
|
||||
|
|
||||
THREEDSX_Header hdr; |
|
||||
if (file.ReadBytes(&hdr, sizeof(THREEDSX_Header)) != sizeof(THREEDSX_Header)) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
if (hdr.header_size != sizeof(THREEDSX_Header)) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
// Check if the 3DSX has a SMDH...
|
|
||||
if (hdr.smdh_offset != 0) { |
|
||||
file.Seek(hdr.smdh_offset, SEEK_SET); |
|
||||
buffer.resize(hdr.smdh_size); |
|
||||
|
|
||||
if (file.ReadBytes(&buffer[0], hdr.smdh_size) != hdr.smdh_size) |
|
||||
return ResultStatus::Error; |
|
||||
|
|
||||
return ResultStatus::Success; |
|
||||
} |
|
||||
return ResultStatus::ErrorNotUsed; |
|
||||
} |
|
||||
|
|
||||
} // namespace Loader
|
|
||||
@ -1,46 +0,0 @@ |
|||||
// Copyright 2014 Dolphin Emulator Project / Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <string> |
|
||||
#include "common/common_types.h" |
|
||||
#include "core/loader/loader.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// Loader namespace |
|
||||
|
|
||||
namespace Loader { |
|
||||
|
|
||||
/// Loads an 3DSX file |
|
||||
class AppLoader_THREEDSX final : public AppLoader { |
|
||||
public: |
|
||||
AppLoader_THREEDSX(FileUtil::IOFile&& file, const std::string& filename, |
|
||||
const std::string& filepath) |
|
||||
: AppLoader(std::move(file)), filename(std::move(filename)), filepath(filepath) {} |
|
||||
|
|
||||
/** |
|
||||
* Returns the type of the file |
|
||||
* @param file FileUtil::IOFile open file |
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it |
|
||||
*/ |
|
||||
static FileType IdentifyType(FileUtil::IOFile& file); |
|
||||
|
|
||||
FileType GetFileType() override { |
|
||||
return IdentifyType(file); |
|
||||
} |
|
||||
|
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |
|
||||
|
|
||||
ResultStatus ReadIcon(std::vector<u8>& buffer) override; |
|
||||
|
|
||||
ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |
|
||||
u64& size) override; |
|
||||
|
|
||||
private: |
|
||||
std::string filename; |
|
||||
std::string filepath; |
|
||||
}; |
|
||||
|
|
||||
} // namespace Loader |
|
||||
@ -1,263 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project
|
|
||||
// Licensed under GPLv2 or any later version
|
|
||||
// Refer to the license.txt file included.
|
|
||||
|
|
||||
#include <algorithm>
|
|
||||
#include <cinttypes>
|
|
||||
#include <codecvt>
|
|
||||
#include <cstring>
|
|
||||
#include <locale>
|
|
||||
#include <memory>
|
|
||||
#include "common/logging/log.h"
|
|
||||
#include "common/string_util.h"
|
|
||||
#include "common/swap.h"
|
|
||||
#include "core/core.h"
|
|
||||
#include "core/file_sys/archive_selfncch.h"
|
|
||||
#include "core/file_sys/ncch_container.h"
|
|
||||
#include "core/file_sys/title_metadata.h"
|
|
||||
#include "core/hle/kernel/process.h"
|
|
||||
#include "core/hle/kernel/resource_limit.h"
|
|
||||
#include "core/hle/service/cfg/cfg.h"
|
|
||||
#include "core/hle/service/fs/archive.h"
|
|
||||
#include "core/loader/ncch.h"
|
|
||||
#include "core/loader/smdh.h"
|
|
||||
#include "core/memory.h"
|
|
||||
#include "network/network.h"
|
|
||||
|
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||
// Loader namespace
|
|
||||
|
|
||||
namespace Loader { |
|
||||
|
|
||||
static const u64 UPDATE_MASK = 0x0000000e00000000; |
|
||||
|
|
||||
FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { |
|
||||
u32 magic; |
|
||||
file.Seek(0x100, SEEK_SET); |
|
||||
if (1 != file.ReadArray<u32>(&magic, 1)) |
|
||||
return FileType::Error; |
|
||||
|
|
||||
if (MakeMagic('N', 'C', 'S', 'D') == magic) |
|
||||
return FileType::CCI; |
|
||||
|
|
||||
if (MakeMagic('N', 'C', 'C', 'H') == magic) |
|
||||
return FileType::CXI; |
|
||||
|
|
||||
return FileType::Error; |
|
||||
} |
|
||||
|
|
||||
static std::string GetUpdateNCCHPath(u64_le program_id) { |
|
||||
u32 high = static_cast<u32>((program_id | UPDATE_MASK) >> 32); |
|
||||
u32 low = static_cast<u32>((program_id | UPDATE_MASK) & 0xFFFFFFFF); |
|
||||
|
|
||||
// TODO(shinyquagsire23): Title database should be doing this path lookup
|
|
||||
std::string content_path = Common::StringFromFormat( |
|
||||
"%sNintendo 3DS/%s/%s/title/%08x/%08x/content/", FileUtil::GetUserPath(D_SDMC_IDX).c_str(), |
|
||||
SYSTEM_ID, SDCARD_ID, high, low); |
|
||||
std::string tmd_path = content_path + "00000000.tmd"; |
|
||||
|
|
||||
u32 content_id = 0; |
|
||||
FileSys::TitleMetadata tmd(tmd_path); |
|
||||
if (tmd.Load() == ResultStatus::Success) { |
|
||||
content_id = tmd.GetBootContentID(); |
|
||||
} |
|
||||
|
|
||||
return Common::StringFromFormat("%s%08x.app", content_path.c_str(), content_id); |
|
||||
} |
|
||||
|
|
||||
std::pair<boost::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() { |
|
||||
if (!is_loaded) { |
|
||||
ResultStatus res = base_ncch.Load(); |
|
||||
if (res != ResultStatus::Success) { |
|
||||
return std::make_pair(boost::none, res); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Set the system mode as the one from the exheader.
|
|
||||
return std::make_pair(overlay_ncch->exheader_header.arm11_system_local_caps.system_mode.Value(), |
|
||||
ResultStatus::Success); |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::LoadExec(Kernel::SharedPtr<Kernel::Process>& process) { |
|
||||
using Kernel::CodeSet; |
|
||||
using Kernel::SharedPtr; |
|
||||
|
|
||||
if (!is_loaded) |
|
||||
return ResultStatus::ErrorNotLoaded; |
|
||||
|
|
||||
std::vector<u8> code; |
|
||||
u64_le program_id; |
|
||||
if (ResultStatus::Success == ReadCode(code) && |
|
||||
ResultStatus::Success == ReadProgramId(program_id)) { |
|
||||
std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( |
|
||||
(const char*)overlay_ncch->exheader_header.codeset_info.name, 8); |
|
||||
|
|
||||
SharedPtr<CodeSet> codeset = CodeSet::Create(process_name, program_id); |
|
||||
|
|
||||
codeset->code.offset = 0; |
|
||||
codeset->code.addr = overlay_ncch->exheader_header.codeset_info.text.address; |
|
||||
codeset->code.size = |
|
||||
overlay_ncch->exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; |
|
||||
|
|
||||
codeset->rodata.offset = codeset->code.offset + codeset->code.size; |
|
||||
codeset->rodata.addr = overlay_ncch->exheader_header.codeset_info.ro.address; |
|
||||
codeset->rodata.size = |
|
||||
overlay_ncch->exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; |
|
||||
|
|
||||
// TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just
|
|
||||
// to the regular size. Playing it safe for now.
|
|
||||
u32 bss_page_size = (overlay_ncch->exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; |
|
||||
code.resize(code.size() + bss_page_size, 0); |
|
||||
|
|
||||
codeset->data.offset = codeset->rodata.offset + codeset->rodata.size; |
|
||||
codeset->data.addr = overlay_ncch->exheader_header.codeset_info.data.address; |
|
||||
codeset->data.size = |
|
||||
overlay_ncch->exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + |
|
||||
bss_page_size; |
|
||||
|
|
||||
codeset->entrypoint = codeset->code.addr; |
|
||||
codeset->memory = std::make_shared<std::vector<u8>>(std::move(code)); |
|
||||
|
|
||||
process = Kernel::Process::Create("main"); |
|
||||
process->LoadModule(codeset, codeset->entrypoint); |
|
||||
|
|
||||
// Attach a resource limit to the process based on the resource limit category
|
|
||||
process->resource_limit = |
|
||||
Kernel::ResourceLimit::GetForCategory(static_cast<Kernel::ResourceLimitCategory>( |
|
||||
overlay_ncch->exheader_header.arm11_system_local_caps.resource_limit_category)); |
|
||||
|
|
||||
// Set the default CPU core for this process
|
|
||||
process->ideal_processor = |
|
||||
overlay_ncch->exheader_header.arm11_system_local_caps.ideal_processor; |
|
||||
|
|
||||
// Copy data while converting endianness
|
|
||||
std::array<u32, ARRAY_SIZE(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors)> |
|
||||
kernel_caps; |
|
||||
std::copy_n(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), |
|
||||
begin(kernel_caps)); |
|
||||
process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); |
|
||||
|
|
||||
s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; |
|
||||
u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; |
|
||||
process->Run(codeset->entrypoint, priority, stack_size); |
|
||||
return ResultStatus::Success; |
|
||||
} |
|
||||
return ResultStatus::Error; |
|
||||
} |
|
||||
|
|
||||
void AppLoader_NCCH::ParseRegionLockoutInfo() { |
|
||||
std::vector<u8> smdh_buffer; |
|
||||
if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { |
|
||||
SMDH smdh; |
|
||||
memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH)); |
|
||||
u32 region_lockout = smdh.region_lockout; |
|
||||
constexpr u32 REGION_COUNT = 7; |
|
||||
for (u32 region = 0; region < REGION_COUNT; ++region) { |
|
||||
if (region_lockout & 1) { |
|
||||
Service::CFG::SetPreferredRegionCode(region); |
|
||||
break; |
|
||||
} |
|
||||
region_lockout >>= 1; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::Load(Kernel::SharedPtr<Kernel::Process>& process) { |
|
||||
u64_le ncch_program_id; |
|
||||
|
|
||||
if (is_loaded) |
|
||||
return ResultStatus::ErrorAlreadyLoaded; |
|
||||
|
|
||||
ResultStatus result = base_ncch.Load(); |
|
||||
if (result != ResultStatus::Success) |
|
||||
return result; |
|
||||
|
|
||||
ReadProgramId(ncch_program_id); |
|
||||
std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_program_id)}; |
|
||||
|
|
||||
LOG_INFO(Loader, "Program ID: %s", program_id.c_str()); |
|
||||
|
|
||||
update_ncch.OpenFile(GetUpdateNCCHPath(ncch_program_id)); |
|
||||
result = update_ncch.Load(); |
|
||||
if (result == ResultStatus::Success) { |
|
||||
overlay_ncch = &update_ncch; |
|
||||
} |
|
||||
|
|
||||
Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); |
|
||||
|
|
||||
if (auto room_member = Network::GetRoomMember().lock()) { |
|
||||
Network::GameInfo game_info; |
|
||||
ReadTitle(game_info.name); |
|
||||
game_info.id = ncch_program_id; |
|
||||
room_member->SendGameInfo(game_info); |
|
||||
} |
|
||||
|
|
||||
is_loaded = true; // Set state to loaded
|
|
||||
|
|
||||
result = LoadExec(process); // Load the executable into memory for booting
|
|
||||
if (ResultStatus::Success != result) |
|
||||
return result; |
|
||||
|
|
||||
Service::FS::RegisterSelfNCCH(*this); |
|
||||
|
|
||||
ParseRegionLockoutInfo(); |
|
||||
|
|
||||
return ResultStatus::Success; |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadCode(std::vector<u8>& buffer) { |
|
||||
return overlay_ncch->LoadSectionExeFS(".code", buffer); |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadIcon(std::vector<u8>& buffer) { |
|
||||
return overlay_ncch->LoadSectionExeFS("icon", buffer); |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadBanner(std::vector<u8>& buffer) { |
|
||||
return overlay_ncch->LoadSectionExeFS("banner", buffer); |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadLogo(std::vector<u8>& buffer) { |
|
||||
return overlay_ncch->LoadSectionExeFS("logo", buffer); |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { |
|
||||
ResultStatus result = base_ncch.ReadProgramId(out_program_id); |
|
||||
if (result != ResultStatus::Success) |
|
||||
return result; |
|
||||
|
|
||||
return ResultStatus::Success; |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |
|
||||
u64& size) { |
|
||||
return base_ncch.ReadRomFS(romfs_file, offset, size); |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, |
|
||||
u64& offset, u64& size) { |
|
||||
ResultStatus result = update_ncch.ReadRomFS(romfs_file, offset, size); |
|
||||
|
|
||||
if (result != ResultStatus::Success) |
|
||||
return base_ncch.ReadRomFS(romfs_file, offset, size); |
|
||||
} |
|
||||
|
|
||||
ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { |
|
||||
std::vector<u8> data; |
|
||||
Loader::SMDH smdh; |
|
||||
ReadIcon(data); |
|
||||
|
|
||||
if (!Loader::IsValidSMDH(data)) { |
|
||||
return ResultStatus::ErrorInvalidFormat; |
|
||||
} |
|
||||
|
|
||||
memcpy(&smdh, data.data(), sizeof(Loader::SMDH)); |
|
||||
|
|
||||
const auto& short_title = smdh.GetShortTitle(SMDH::TitleLanguage::English); |
|
||||
auto title_end = std::find(short_title.begin(), short_title.end(), u'\0'); |
|
||||
title = Common::UTF16ToUTF8(std::u16string{short_title.begin(), title_end}); |
|
||||
|
|
||||
return ResultStatus::Success; |
|
||||
} |
|
||||
|
|
||||
} // namespace Loader
|
|
||||
@ -1,80 +0,0 @@ |
|||||
// Copyright 2014 Citra Emulator Project |
|
||||
// Licensed under GPLv2 or any later version |
|
||||
// Refer to the license.txt file included. |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <memory> |
|
||||
#include "common/common_types.h" |
|
||||
#include "common/swap.h" |
|
||||
#include "core/file_sys/ncch_container.h" |
|
||||
#include "core/loader/loader.h" |
|
||||
|
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|
||||
// Loader namespace |
|
||||
|
|
||||
namespace Loader { |
|
||||
|
|
||||
/// Loads an NCCH file (e.g. from a CCI, or the first NCCH in a CXI) |
|
||||
class AppLoader_NCCH final : public AppLoader { |
|
||||
public: |
|
||||
AppLoader_NCCH(FileUtil::IOFile&& file, const std::string& filepath) |
|
||||
: AppLoader(std::move(file)), filepath(filepath), base_ncch(filepath), |
|
||||
overlay_ncch(&base_ncch) {} |
|
||||
|
|
||||
/** |
|
||||
* Returns the type of the file |
|
||||
* @param file FileUtil::IOFile open file |
|
||||
* @return FileType found, or FileType::Error if this loader doesn't know it |
|
||||
*/ |
|
||||
static FileType IdentifyType(FileUtil::IOFile& file); |
|
||||
|
|
||||
FileType GetFileType() override { |
|
||||
return IdentifyType(file); |
|
||||
} |
|
||||
|
|
||||
ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |
|
||||
|
|
||||
/** |
|
||||
* Loads the Exheader and returns the system mode for this application. |
|
||||
* @returns A pair with the optional system mode, and and the status. |
|
||||
*/ |
|
||||
std::pair<boost::optional<u32>, ResultStatus> LoadKernelSystemMode() override; |
|
||||
|
|
||||
ResultStatus ReadCode(std::vector<u8>& buffer) override; |
|
||||
|
|
||||
ResultStatus ReadIcon(std::vector<u8>& buffer) override; |
|
||||
|
|
||||
ResultStatus ReadBanner(std::vector<u8>& buffer) override; |
|
||||
|
|
||||
ResultStatus ReadLogo(std::vector<u8>& buffer) override; |
|
||||
|
|
||||
ResultStatus ReadProgramId(u64& out_program_id) override; |
|
||||
|
|
||||
ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |
|
||||
u64& size) override; |
|
||||
|
|
||||
ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |
|
||||
u64& size) override; |
|
||||
|
|
||||
ResultStatus ReadTitle(std::string& title) override; |
|
||||
|
|
||||
private: |
|
||||
/** |
|
||||
* Loads .code section into memory for booting |
|
||||
* @param process The newly created process |
|
||||
* @return ResultStatus result of function |
|
||||
*/ |
|
||||
ResultStatus LoadExec(Kernel::SharedPtr<Kernel::Process>& process); |
|
||||
|
|
||||
/// Reads the region lockout info in the SMDH and send it to CFG service |
|
||||
void ParseRegionLockoutInfo(); |
|
||||
|
|
||||
FileSys::NCCHContainer base_ncch; |
|
||||
FileSys::NCCHContainer update_ncch; |
|
||||
FileSys::NCCHContainer* overlay_ncch; |
|
||||
|
|
||||
std::string filepath; |
|
||||
}; |
|
||||
|
|
||||
} // namespace Loader |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue