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