// 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));
}
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