diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index 4155cb14a9..58f7ee1d9d 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -4,8 +4,8 @@ #include "common/logging/log.h" #include "core/file_sys/content_archive.h" +#include "core/file_sys/vfs_offset.h" #include "core/loader/loader.h" -#include "vfs_offset.h" // Media offsets in headers are stored divided by 512. Mult. by this to get real offset. constexpr u64 MEDIA_OFFSET_MULTIPLIER = 0x200; @@ -14,18 +14,18 @@ constexpr u64 SECTION_HEADER_SIZE = 0x200; constexpr u64 SECTION_HEADER_OFFSET = 0x400; namespace FileSys { -enum class NcaSectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 }; +enum class NCASectionFilesystemType : u8 { PFS0 = 0x2, ROMFS = 0x3 }; -struct NcaSectionHeaderBlock { +struct NCASectionHeaderBlock { INSERT_PADDING_BYTES(3); - NcaSectionFilesystemType filesystem_type; + NCASectionFilesystemType filesystem_type; u8 crypto_type; INSERT_PADDING_BYTES(3); }; -static_assert(sizeof(NcaSectionHeaderBlock) == 0x8, "NcaSectionHeaderBlock has incorrect size."); +static_assert(sizeof(NCASectionHeaderBlock) == 0x8, "NCASectionHeaderBlock has incorrect size."); -struct Pfs0Superblock { - NcaSectionHeaderBlock header_block; +struct PFS0Superblock { + NCASectionHeaderBlock header_block; std::array hash; u32_le size; INSERT_PADDING_BYTES(4); @@ -35,40 +35,39 @@ struct Pfs0Superblock { u64_le pfs0_size; INSERT_PADDING_BYTES(432); }; -static_assert(sizeof(Pfs0Superblock) == 0x200, "Pfs0Superblock has incorrect size."); +static_assert(sizeof(PFS0Superblock) == 0x200, "PFS0Superblock has incorrect size."); -Loader::ResultStatus Nca::Load(v_file file_) { - file = std::move(file_); - - if (sizeof(NcaHeader) != file->ReadObject(&header)) +NCA::NCA(v_file file_) : file(file_) { + if (sizeof(NCAHeader) != file->ReadObject(&header)) NGLOG_CRITICAL(Loader, "File reader errored out during header read."); - if (!IsValidNca(header)) - return Loader::ResultStatus::ErrorInvalidFormat; + if (!IsValidNCA(header)) { + status = Loader::ResultStatus::ErrorInvalidFormat; + return; + } int number_sections = std::count_if(std::begin(header.section_tables), std::end(header.section_tables), - [](NcaSectionTableEntry entry) { return entry.media_offset > 0; }); + [](NCASectionTableEntry entry) { return entry.media_offset > 0; }); for (int i = 0; i < number_sections; ++i) { // Seek to beginning of this section. - NcaSectionHeaderBlock block{}; - if (sizeof(NcaSectionHeaderBlock) != + NCASectionHeaderBlock block{}; + if (sizeof(NCASectionHeaderBlock) != file->ReadObject(&block, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) NGLOG_CRITICAL(Loader, "File reader errored out during header read."); - if (block.filesystem_type == NcaSectionFilesystemType::ROMFS) { + if (block.filesystem_type == NCASectionFilesystemType::ROMFS) { const size_t romfs_offset = header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER; const size_t romfs_size = header.section_tables[i].media_end_offset * MEDIA_OFFSET_MULTIPLIER - romfs_offset; - entries.emplace_back(std::make_shared( - std::make_shared(file, romfs_size, romfs_offset))); - romfs = entries.back(); - } else if (block.filesystem_type == NcaSectionFilesystemType::PFS0) { - Pfs0Superblock sb{}; + files.emplace_back(std::make_shared(file, romfs_size, romfs_offset)); + romfs = files.back(); + } else if (block.filesystem_type == NCASectionFilesystemType::PFS0) { + PFS0Superblock sb{}; // Seek back to beginning of this section. - if (sizeof(Pfs0Superblock) != + if (sizeof(PFS0Superblock) != file->ReadObject(&sb, SECTION_HEADER_OFFSET + i * SECTION_HEADER_SIZE)) NGLOG_CRITICAL(Loader, "File reader errored out during header read."); @@ -77,50 +76,63 @@ Loader::ResultStatus Nca::Load(v_file file_) { sb.pfs0_header_offset; u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset - header.section_tables[i].media_offset); - FileSys::PartitionFilesystem npfs{}; - Loader::ResultStatus status = - npfs.Load(std::make_shared(file, size, offset)); + auto npfs = std::make_shared( + std::make_shared(file, size, offset)); - if (status == Loader::ResultStatus::Success) { - entries.emplace_back(npfs); - if (IsDirectoryExeFs(entries.back())) - exefs = entries.back(); + if (npfs->GetStatus() == Loader::ResultStatus::Success) { + dirs.emplace_back(npfs); + if (IsDirectoryExeFS(dirs.back())) + exefs = dirs.back(); } } } - return Loader::ResultStatus::Success; + status = Loader::ResultStatus::Success; } -std::vector> Nca::GetFiles() const { - return {}; +Loader::ResultStatus NCA::GetStatus() const { + return status; } -std::vector> Nca::GetSubdirectories() const { - return entries; +std::vector> NCA::GetFiles() const { + if (status != Loader::ResultStatus::Success) + return {}; + return files; } -std::string Nca::GetName() const { +std::vector> NCA::GetSubdirectories() const { + if (status != Loader::ResultStatus::Success) + return {}; + return dirs; +} + +std::string NCA::GetName() const { return file->GetName(); } -std::shared_ptr Nca::GetParentDirectory() const { +std::shared_ptr NCA::GetParentDirectory() const { return file->GetContainingDirectory(); } -NcaContentType Nca::GetType() const { +NCAContentType NCA::GetType() const { return header.content_type; } -u64 Nca::GetTitleId() const { +u64 NCA::GetTitleId() const { + if (status != Loader::ResultStatus::Success) + return {}; return header.title_id; } -v_dir Nca::GetRomFs() const { +v_file NCA::GetRomFS() const { return romfs; } -v_dir Nca::GetExeFs() const { +v_dir NCA::GetExeFS() const { return exefs; } + +bool NCA::ReplaceFileWithSubdirectory(v_file file, v_dir dir) { + return false; +} } // namespace FileSys diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index 42dd843e14..63a81d7482 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -11,21 +11,21 @@ namespace FileSys { -enum class NcaContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 }; +enum class NCAContentType : u8 { Program = 0, Meta = 1, Control = 2, Manual = 3, Data = 4 }; -struct NcaSectionTableEntry { +struct NCASectionTableEntry { u32_le media_offset; u32_le media_end_offset; INSERT_PADDING_BYTES(0x8); }; -static_assert(sizeof(NcaSectionTableEntry) == 0x10, "NcaSectionTableEntry has incorrect size."); +static_assert(sizeof(NCASectionTableEntry) == 0x10, "NCASectionTableEntry has incorrect size."); -struct NcaHeader { +struct NCAHeader { std::array rsa_signature_1; std::array rsa_signature_2; u32_le magic; u8 is_system; - NcaContentType content_type; + NCAContentType content_type; u8 crypto_type; u8 key_index; u64_le size; @@ -35,46 +35,52 @@ struct NcaHeader { u8 crypto_type_2; INSERT_PADDING_BYTES(15); std::array rights_id; - std::array section_tables; + std::array section_tables; std::array, 0x4> hash_tables; std::array, 0x4> key_area; INSERT_PADDING_BYTES(0xC0); }; -static_assert(sizeof(NcaHeader) == 0x400, "NcaHeader has incorrect size."); +static_assert(sizeof(NCAHeader) == 0x400, "NCAHeader has incorrect size."); -static bool IsDirectoryExeFs(std::shared_ptr pfs) { +static bool IsDirectoryExeFS(std::shared_ptr pfs) { // According to switchbrew, an exefs must only contain these two files: return pfs->GetFile("main") != nullptr && pfs->GetFile("main.ndpm") != nullptr; } -static bool IsValidNca(const NcaHeader& header) { +static bool IsValidNCA(const NCAHeader& header) { return header.magic == Common::MakeMagic('N', 'C', 'A', '2') || header.magic == Common::MakeMagic('N', 'C', 'A', '3'); } -class Nca : public ReadOnlyVfsDirectory { - std::vector entries; - std::vector entry_offset; +class NCA : public ReadOnlyVfsDirectory { + std::vector dirs{}; + std::vector files{}; - v_dir romfs = nullptr; + v_file romfs = nullptr; v_dir exefs = nullptr; v_file file; - NcaHeader header{}; + NCAHeader header{}; + + Loader::ResultStatus status{}; public: - Loader::ResultStatus Load(v_file file); + explicit NCA(v_file file); + Loader::ResultStatus GetStatus() const; std::vector> GetFiles() const override; std::vector> GetSubdirectories() const override; std::string GetName() const override; std::shared_ptr GetParentDirectory() const override; - NcaContentType GetType() const; + NCAContentType GetType() const; u64 GetTitleId() const; - v_dir GetRomFs() const; - v_dir GetExeFs() const; + v_file GetRomFS() const; + v_dir GetExeFS() const; + +protected: + bool ReplaceFileWithSubdirectory(v_file file, v_dir dir) override; }; } // namespace FileSys diff --git a/src/core/file_sys/filesystem.h b/src/core/file_sys/filesystem.h index bf25045e8a..2d925d9e47 100644 --- a/src/core/file_sys/filesystem.h +++ b/src/core/file_sys/filesystem.h @@ -66,44 +66,4 @@ private: std::u16string u16str; }; -/// Parameters of the archive, as specified in the Create or Format call. -struct ArchiveFormatInfo { - u32_le total_size; ///< The pre-defined size of the archive. - u32_le number_directories; ///< The pre-defined number of directories in the archive. - u32_le number_files; ///< The pre-defined number of files in the archive. - u8 duplicate_data; ///< Whether the archive should duplicate the data. -}; -static_assert(std::is_pod::value, "ArchiveFormatInfo is not POD"); - -class FileSystemFactory : NonCopyable { -public: - virtual ~FileSystemFactory() {} - - /** - * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) - */ - virtual std::string GetName() const = 0; - - /** - * Tries to open the archive of this type with the specified path - * @param path Path to the archive - * @return An ArchiveBackend corresponding operating specified archive path. - */ - virtual ResultVal> Open(const Path& path) = 0; - - /** - * Deletes the archive contents and then re-creates the base folder - * @param path Path to the archive - * @return ResultCode of the operation, 0 on success - */ - virtual ResultCode Format(const Path& path) = 0; - - /** - * Retrieves the format info about the archive with the specified path - * @param path Path to the archive - * @return Format information about the archive or error code - */ - virtual ResultVal GetFormatInfo(const Path& path) const = 0; -}; - } // namespace FileSys diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index 8a713ea81d..c405e9844a 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -11,20 +11,25 @@ namespace FileSys { -Loader::ResultStatus PartitionFilesystem::Load(std::shared_ptr file) { +PartitionFilesystem::PartitionFilesystem(std::shared_ptr file) { // At least be as large as the header - if (file->GetSize() < sizeof(Header)) - return Loader::ResultStatus::Error; + if (file->GetSize() < sizeof(Header)) { + status = Loader::ResultStatus::Error; + return; + } // For cartridges, HFSs can get very large, so we need to calculate the size up to // the actual content itself instead of just blindly reading in the entire file. Header pfs_header; - if (!file->ReadObject(&pfs_header)) - return Loader::ResultStatus::Error; + if (!file->ReadObject(&pfs_header)) { + status = Loader::ResultStatus::Error; + return; + } if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { - return Loader::ResultStatus::ErrorInvalidFormat; + status = Loader::ResultStatus::ErrorInvalidFormat; + return; } bool is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); @@ -36,17 +41,22 @@ Loader::ResultStatus PartitionFilesystem::Load(std::shared_ptr file) { // Actually read in now... std::vector file_data = file->ReadBytes(metadata_size); - if (file_data.size() != metadata_size) - return Loader::ResultStatus::Error; + if (file_data.size() != metadata_size) { + status = Loader::ResultStatus::Error; + return; + } size_t total_size = file_data.size(); - if (total_size < sizeof(Header)) - return Loader::ResultStatus::Error; + if (total_size < sizeof(Header)) { + status = Loader::ResultStatus::Error; + return; + } memcpy(&pfs_header, &file_data, sizeof(Header)); if (pfs_header.magic != Common::MakeMagic('H', 'F', 'S', '0') && pfs_header.magic != Common::MakeMagic('P', 'F', 'S', '0')) { - return Loader::ResultStatus::ErrorInvalidFormat; + status = Loader::ResultStatus::ErrorInvalidFormat; + return; } is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); @@ -65,7 +75,11 @@ Loader::ResultStatus PartitionFilesystem::Load(std::shared_ptr file) { std::make_shared(file, entry.size, content_offset + entry.offset, name)); } - return Loader::ResultStatus::Success; + status = Loader::ResultStatus::Success; +} + +Loader::ResultStatus PartitionFilesystem::GetStatus() const { + return status; } std::vector> PartitionFilesystem::GetFiles() const { diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h index 19fa08d025..9b70417eb1 100644 --- a/src/core/file_sys/partition_filesystem.h +++ b/src/core/file_sys/partition_filesystem.h @@ -24,7 +24,8 @@ namespace FileSys { */ class PartitionFilesystem : public ReadOnlyVfsDirectory { public: - Loader::ResultStatus Load(std::shared_ptr file); + explicit PartitionFilesystem(std::shared_ptr file); + Loader::ResultStatus GetStatus() const; std::vector> GetFiles() const override; std::vector> GetSubdirectories() const override; @@ -72,6 +73,8 @@ private: #pragma pack(pop) + Loader::ResultStatus status; + Header pfs_header; bool is_hfs; size_t content_offset; diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 9dfa71548e..ee98e69221 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -14,13 +14,19 @@ Loader::ResultStatus ProgramMetadata::Load(v_file file) { if (total_size < sizeof(Header)) return Loader::ResultStatus::Error; - if (sizeof(Header) != file->ReadObject(&npdm_header)) + // TODO(DarkLordZach): Use ReadObject when Header/AcidHeader becomes trivially copyable. + std::vector npdm_header_data = file->ReadBytes(sizeof(Header)); + if (sizeof(Header) != npdm_header_data.size()) return Loader::ResultStatus::Error; + std::memcpy(&npdm_header, npdm_header_data.data(), sizeof(Header)); + + std::vector acid_header_data = file->ReadBytes(sizeof(AcidHeader), npdm_header.acid_offset); + if (sizeof(AcidHeader) != acid_header_data.size()) + return Loader::ResultStatus::Error; + std::memcpy(&acid_header, acid_header_data.data(), sizeof(AcidHeader)); if (sizeof(AciHeader) != file->ReadObject(&aci_header, npdm_header.aci_offset)) return Loader::ResultStatus::Error; - if (sizeof(AcidHeader) != file->ReadObject(&acid_header, npdm_header.acid_offset)) - return Loader::ResultStatus::Error; if (sizeof(FileAccessControl) != file->ReadObject(&acid_file_access, acid_header.fac_offset)) return Loader::ResultStatus::Error; diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 592dc96b79..a00083919a 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -51,6 +51,7 @@ public: void Print() const; private: + // TODO(DarkLordZach): BitField is not trivially copyable. struct Header { std::array magic; std::array reserved; @@ -77,6 +78,7 @@ private: static_assert(sizeof(Header) == 0x80, "NPDM header structure size is wrong"); + // TODO(DarkLordZach): BitField is not trivially copyable. struct AcidHeader { std::array signature; std::array nca_modulus; diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/core/file_sys/romfs_filesystem.cpp b/src/core/file_sys/romfs_filesystem.cpp deleted file mode 100644 index 83162622b4..0000000000 --- a/src/core/file_sys/romfs_filesystem.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include "common/common_types.h" -#include "common/logging/log.h" -#include "core/file_sys/romfs_filesystem.h" - -namespace FileSys { - -std::string RomFS_FileSystem::GetName() const { - return "RomFS"; -} - -ResultVal> RomFS_FileSystem::OpenFile(const std::string& path, - Mode mode) const { - return MakeResult>( - std::make_unique(romfs_file, data_offset, data_size)); -} - -ResultCode RomFS_FileSystem::DeleteFile(const std::string& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a file from an ROMFS archive ({}).", GetName()); - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -ResultCode RomFS_FileSystem::RenameFile(const std::string& src_path, - const std::string& dest_path) const { - LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode RomFS_FileSystem::DeleteDirectory(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).", - GetName()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode RomFS_FileSystem::DeleteDirectoryRecursively(const Path& path) const { - LOG_CRITICAL(Service_FS, "Attempted to delete a directory from an ROMFS archive ({}).", - GetName()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode RomFS_FileSystem::CreateFile(const std::string& path, u64 size) const { - LOG_CRITICAL(Service_FS, "Attempted to create a file in an ROMFS archive ({}).", GetName()); - // TODO(bunnei): Use correct error code - return ResultCode(-1); -} - -ResultCode RomFS_FileSystem::CreateDirectory(const std::string& path) const { - LOG_CRITICAL(Service_FS, "Attempted to create a directory in an ROMFS archive ({}).", - GetName()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultCode RomFS_FileSystem::RenameDirectory(const Path& src_path, const Path& dest_path) const { - LOG_CRITICAL(Service_FS, "Attempted to rename a file within an ROMFS archive ({}).", GetName()); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultVal> RomFS_FileSystem::OpenDirectory( - const std::string& path) const { - LOG_WARNING(Service_FS, "Opening Directory in a ROMFS archive"); - return MakeResult>(std::make_unique()); -} - -u64 RomFS_FileSystem::GetFreeSpaceSize() const { - LOG_WARNING(Service_FS, "Attempted to get the free space in an ROMFS archive"); - return 0; -} - -ResultVal RomFS_FileSystem::GetEntryType(const std::string& path) const { - LOG_CRITICAL(Service_FS, "Called within an ROMFS archive (path {}).", path); - // TODO(wwylele): Use correct error code - return ResultCode(-1); -} - -ResultVal RomFS_Storage::Read(const u64 offset, const size_t length, u8* buffer) const { - LOG_TRACE(Service_FS, "called offset={}, length={}", offset, length); - romfs_file->Seek(data_offset + offset, SEEK_SET); - size_t read_length = (size_t)std::min((u64)length, data_size - offset); - - return MakeResult(romfs_file->ReadBytes(buffer, read_length)); -} - -ResultVal RomFS_Storage::Write(const u64 offset, const size_t length, const bool flush, - const u8* buffer) const { - LOG_ERROR(Service_FS, "Attempted to write to ROMFS file"); - // TODO(Subv): Find error code - return MakeResult(0); -} - -u64 RomFS_Storage::GetSize() const { - return data_size; -} - -bool RomFS_Storage::SetSize(const u64 size) const { - LOG_ERROR(Service_FS, "Attempted to set the size of an ROMFS file"); - return false; -} - -} // namespace FileSys diff --git a/src/core/file_sys/romfs_filesystem.h b/src/core/file_sys/romfs_filesystem.h deleted file mode 100644 index 64be39abe4..0000000000 --- a/src/core/file_sys/romfs_filesystem.h +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include "common/common_types.h" -#include "common/file_util.h" -#include "core/file_sys/directory.h" -#include "core/file_sys/filesystem.h" -#include "core/file_sys/storage.h" -#include "core/hle/result.h" -#include "core/hle/service/filesystem/filesystem.h" - -namespace FileSys { - -/** - * Helper which implements an interface to deal with Switch .istorage ROMFS images used in some - * archives This should be subclassed by concrete archive types, which will provide the input data - * (load the raw ROMFS archive) and override any required methods - */ -class RomFS_FileSystem : public FileSystemBackend { -public: - RomFS_FileSystem(std::shared_ptr file, u64 offset, u64 size) - : romfs_file(file), data_offset(offset), data_size(size) {} - - std::string GetName() const override; - - ResultVal> OpenFile(const std::string& path, - Mode mode) const override; - ResultCode DeleteFile(const std::string& path) const override; - ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const override; - ResultCode DeleteDirectory(const Path& path) const override; - ResultCode DeleteDirectoryRecursively(const Path& path) const override; - ResultCode CreateFile(const std::string& path, u64 size) const override; - ResultCode CreateDirectory(const std::string& path) const override; - ResultCode RenameDirectory(const Path& src_path, const Path& dest_path) const override; - ResultVal> OpenDirectory( - const std::string& path) const override; - u64 GetFreeSpaceSize() const override; - ResultVal GetEntryType(const std::string& path) const override; - -protected: - std::shared_ptr romfs_file; - u64 data_offset; - u64 data_size; -}; - -class RomFS_Storage : public StorageBackend { -public: - RomFS_Storage(std::shared_ptr file, u64 offset, u64 size) - : romfs_file(file), data_offset(offset), data_size(size) {} - - ResultVal Read(u64 offset, size_t length, u8* buffer) const override; - ResultVal Write(u64 offset, size_t length, bool flush, const u8* buffer) const override; - u64 GetSize() const override; - bool SetSize(u64 size) const override; - bool Close() const override { - return false; - } - void Flush() const override {} - -private: - std::shared_ptr romfs_file; - u64 data_offset; - u64 data_size; -}; - -class ROMFSDirectory : public DirectoryBackend { -public: - u64 Read(const u64 count, Entry* entries) override { - return 0; - } - u64 GetEntryCount() const override { - return 0; - } - bool Close() const override { - return false; - } -}; - -} // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 853cb3cdaf..1a4ecf3017 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -125,4 +125,16 @@ bool RealVfsDirectory::Rename(const std::string& name) { return FileUtil::Rename(path.string(), (path / name).string()); } +bool RealVfsDirectory::ReplaceFileWithSubdirectory(v_file file, v_dir dir) { + auto iter = std::find(files.begin(), files.end(), file); + if (iter == files.end()) + return false; + + files[iter - files.begin()] = files.back(); + files.pop_back(); + + subdirectories.emplace_back(dir); + + return true; +} } // namespace FileSys diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 48aa8837f3..a4f8488923 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -36,7 +36,6 @@ struct RealVfsDirectory : public VfsDirectory { std::vector> GetSubdirectories() const override; bool IsWritable() const override; bool IsReadable() const override; - bool IsRoot() const override; std::string GetName() const override; std::shared_ptr GetParentDirectory() const override; std::shared_ptr CreateSubdirectory(const std::string& name) override; @@ -45,6 +44,9 @@ struct RealVfsDirectory : public VfsDirectory { bool DeleteFile(const std::string& name) override; bool Rename(const std::string& name) override; +protected: + bool ReplaceFileWithSubdirectory(v_file file, v_dir dir) override; + private: filesystem::path path; filesystem::perms perms; diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index f58b518b62..b98ac46ae4 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -5,21 +5,26 @@ #include #include "common/file_util.h" #include "core/file_sys/filesystem.h" -#include "core/file_sys/savedata_factory.h" -#include "core/file_sys/sdmc_factory.h" +#include "core/file_sys/vfs.h" +#include "core/file_sys/vfs_real.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_srv.h" namespace Service::FileSystem { +ResultVal OpenRomFS() { + return romfs; +} + /** * Map of registered file systems, identified by type. Once an file system is registered here, it * is never removed until UnregisterFileSystems is called. */ -static boost::container::flat_map> filesystem_map; +static boost::container::flat_map filesystem_map; +static v_file filesystem_romfs; -ResultCode RegisterFileSystem(std::unique_ptr&& factory, Type type) { - auto result = filesystem_map.emplace(type, std::move(factory)); +ResultCode RegisterFileSystem(v_dir factory, Type type) { + auto result = filesystem_map.emplace(type, factory); bool inserted = result.second; ASSERT_MSG(inserted, "Tried to register more than one system with same id code"); @@ -30,9 +35,18 @@ ResultCode RegisterFileSystem(std::unique_ptr&& fact return RESULT_SUCCESS; } -ResultVal> OpenFileSystem(Type type, - FileSys::Path& path) { - LOG_TRACE(Service_FS, "Opening FileSystem with type={}", static_cast(type)); +ResultCode RegisterRomFS(v_file filesystem) { + bool inserted = filesystem_romfs == nullptr; + ASSERT_MSG(inserted, "Tried to register more than one system with same id code"); + + filesystem_romfs = filesystem; + NGLOG_DEBUG(Service_FS, "Registered file system {} with id code 0x{:08X}", + filesystem->GetName(), static_cast(Type::RomFS)); + return RESULT_SUCCESS; +} + +ResultVal OpenFileSystem(Type type) { + NGLOG_TRACE(Service_FS, "Opening FileSystem with type={}", static_cast(type)); auto itr = filesystem_map.find(type); if (itr == filesystem_map.end()) { @@ -40,7 +54,7 @@ ResultVal> OpenFileSystem(Type type, return ResultCode(-1); } - return itr->second->Open(path); + return MakeResult(itr->second); } ResultCode FormatFileSystem(Type type) { @@ -52,8 +66,9 @@ ResultCode FormatFileSystem(Type type) { return ResultCode(-1); } - FileSys::Path unused; - return itr->second->Format(unused); + return itr->second->GetParentDirectory()->DeleteSubdirectory(itr->second->GetName()) + ? RESULT_SUCCESS + : ResultCode(-1); } void RegisterFileSystems() { @@ -62,10 +77,11 @@ void RegisterFileSystems() { std::string nand_directory = FileUtil::GetUserPath(D_NAND_IDX); std::string sd_directory = FileUtil::GetUserPath(D_SDMC_IDX); - auto savedata = std::make_unique(std::move(nand_directory)); + auto savedata = + std::make_unique(nand_directory, filesystem::perms::all); RegisterFileSystem(std::move(savedata), Type::SaveData); - auto sdcard = std::make_unique(std::move(sd_directory)); + auto sdcard = std::make_unique(sd_directory, filesystem::perms::all); RegisterFileSystem(std::move(sdcard), Type::SDMC); } diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 56d26146e9..e3a86a54c9 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -6,6 +6,7 @@ #include #include "common/common_types.h" +#include "core/file_sys/vfs.h" #include "core/hle/result.h" namespace FileSys { @@ -29,12 +30,109 @@ enum class Type { SDMC = 3, }; +class VfsDirectoryServiceWrapper { + v_dir backing; + +public: + VfsDirectoryServiceWrapper(v_dir backing); + + /** + * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) + */ + std::string GetName() const; + + /** + * Create a file specified by its path + * @param path Path relative to the Archive + * @param size The size of the new file, filled with zeroes + * @return Result of the operation + */ + ResultCode CreateFile(const std::string& path, u64 size) const; + + /** + * Delete a file specified by its path + * @param path Path relative to the archive + * @return Result of the operation + */ + ResultCode DeleteFile(const std::string& path) const; + + /** + * Create a directory specified by its path + * @param path Path relative to the archive + * @return Result of the operation + */ + ResultCode CreateDirectory(const std::string& path) const; + + /** + * Delete a directory specified by its path + * @param path Path relative to the archive + * @return Result of the operation + */ + ResultCode DeleteDirectory(const std::string& path) const; + + /** + * Delete a directory specified by its path and anything under it + * @param path Path relative to the archive + * @return Result of the operation + */ + ResultCode DeleteDirectoryRecursively(const std::string& path) const; + + /** + * Rename a File specified by its path + * @param src_path Source path relative to the archive + * @param dest_path Destination path relative to the archive + * @return Result of the operation + */ + ResultCode RenameFile(const std::string& src_path, const std::string& dest_path) const; + + /** + * Rename a Directory specified by its path + * @param src_path Source path relative to the archive + * @param dest_path Destination path relative to the archive + * @return Result of the operation + */ + ResultCode RenameDirectory(const std::string& src_path, const std::string& dest_path) const; + + /** + * Open a file specified by its path, using the specified mode + * @param path Path relative to the archive + * @param mode Mode to open the file with + * @return Opened file, or error code + */ + ResultVal OpenFile(const std::string& path, FileSys::Mode mode) const; + + /** + * Open a directory specified by its path + * @param path Path relative to the archive + * @return Opened directory, or error code + */ + ResultVal OpenDirectory(const std::string& path) const; + + /** + * Get the free space + * @return The number of free bytes in the archive + */ + u64 GetFreeSpaceSize() const; + + /** + * Get the type of the specified path + * @return The type of the specified path or error code + */ + ResultVal GetEntryType(const std::string& path) const; +}; + +class VfsFileServiceWrapper { + v_file backing; +}; + /** * Registers a FileSystem, instances of which can later be opened using its IdCode. * @param factory FileSystem backend interface to use * @param type Type used to access this type of FileSystem */ -ResultCode RegisterFileSystem(std::unique_ptr&& factory, Type type); +ResultCode RegisterFileSystem(v_dir fs, Type type); + +ResultCode RegisterRomFS(v_file fs); /** * Opens a file system @@ -42,8 +140,9 @@ ResultCode RegisterFileSystem(std::unique_ptr&& fact * @param path Path to the file system, used with Binary paths * @return FileSys::FileSystemBackend interface to the file system */ -ResultVal> OpenFileSystem(Type type, - FileSys::Path& path); +ResultVal OpenFileSystem(Type type); + +ResultVal OpenRomFS(); /** * Formats a file system diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 216bfea0a6..df8e200c0e 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -19,8 +19,7 @@ namespace Service::FileSystem { class IStorage final : public ServiceFramework { public: - IStorage(std::unique_ptr&& backend) - : ServiceFramework("IStorage"), backend(std::move(backend)) { + IStorage(v_file backend_) : ServiceFramework("IStorage"), backend(backend_) { static const FunctionInfo functions[] = { {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"}, {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"}, @@ -29,7 +28,7 @@ public: } private: - std::unique_ptr backend; + v_file backend; void Read(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -51,8 +50,8 @@ private: } // Read the data from the Storage backend - std::vector output(length); - ResultVal res = backend->Read(offset, length, output.data()); + std::vector output = backend->ReadBytes(length, offset); + auto res = MakeResult(output.size()); if (res.Failed()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(res.Code()); @@ -69,8 +68,7 @@ private: class IFile final : public ServiceFramework { public: - explicit IFile(std::unique_ptr&& backend) - : ServiceFramework("IFile"), backend(std::move(backend)) { + explicit IFile(v_file backend_) : ServiceFramework("IFile"), backend(backend_) { static const FunctionInfo functions[] = { {0, &IFile::Read, "Read"}, {1, &IFile::Write, "Write"}, {2, &IFile::Flush, "Flush"}, {3, &IFile::SetSize, "SetSize"}, @@ -80,7 +78,7 @@ public: } private: - std::unique_ptr backend; + v_file backend; void Read(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; @@ -103,8 +101,8 @@ private: } // Read the data from the Storage backend - std::vector output(length); - ResultVal res = backend->Read(offset, length, output.data()); + std::vector output = backend->ReadBytes(length, offset); + auto res = MakeResult(output.size()); if (res.Failed()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(res.Code()); @@ -140,8 +138,7 @@ private: } // Write the data to the Storage backend - std::vector data = ctx.ReadBuffer(); - ResultVal res = backend->Write(offset, length, true, data.data()); + auto res = MakeResult(backend->WriteBytes(ctx.ReadBuffer(), length, offset)); if (res.Failed()) { IPC::ResponseBuilder rb{ctx, 2}; rb.Push(res.Code()); @@ -153,8 +150,9 @@ private: } void Flush(Kernel::HLERequestContext& ctx) { - LOG_DEBUG(Service_FS, "called"); - backend->Flush(); + NGLOG_DEBUG(Service_FS, "called"); + + // Exists for SDK compatibiltity -- No need to flush file. IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); @@ -163,7 +161,7 @@ private: void SetSize(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 size = rp.Pop(); - backend->SetSize(size); + backend->Resize(size); LOG_DEBUG(Service_FS, "called, size={}", size); IPC::ResponseBuilder rb{ctx, 2}; @@ -232,7 +230,7 @@ private: class IFileSystem final : public ServiceFramework { public: - explicit IFileSystem(std::unique_ptr&& backend) + explicit IFileSystem(v_dir backend) : ServiceFramework("IFileSystem"), backend(std::move(backend)) { static const FunctionInfo functions[] = { {0, &IFileSystem::CreateFile, "CreateFile"}, @@ -267,7 +265,12 @@ public: LOG_DEBUG(Service_FS, "called file {} mode 0x{:X} size 0x{:08X}", name, mode, size); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(backend->CreateFile(name, size)); + auto b1 = backend->CreateFile(name); + if (b1 == nullptr) { + } + auto b2 = b1->Res + + rb.Push(? RESULT_SUCCESS : ResultCode(-1)); } void DeleteFile(Kernel::HLERequestContext& ctx) { @@ -291,7 +294,8 @@ public: LOG_DEBUG(Service_FS, "called directory {}", name); IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(backend->CreateDirectory(name)); + auto dir = backend->CreateSubdirectory(name); + rb.Push(dir == nullptr ? -1 : 0); } void RenameFile(Kernel::HLERequestContext& ctx) { @@ -389,7 +393,7 @@ public: } private: - std::unique_ptr backend; + v_dir backend; }; FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { @@ -490,10 +494,9 @@ void FSP_SRV::TryLoadRomFS() { if (romfs) { return; } - FileSys::Path unused; - auto res = OpenFileSystem(Type::RomFS, unused); + auto res = OpenRomFS(); if (res.Succeeded()) { - romfs = std::move(res.Unwrap()); + romfs = res.Unwrap(); } } @@ -507,8 +510,7 @@ void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) { void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); - FileSys::Path unused; - auto filesystem = OpenFileSystem(Type::SDMC, unused).Unwrap(); + auto filesystem = OpenFileSystem(Type::SDMC).Unwrap(); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -559,15 +561,6 @@ void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) { return; } - // Attempt to open a StorageBackend interface to the RomFS - auto storage = romfs->OpenFile({}, {}); - if (storage.Failed()) { - LOG_CRITICAL(Service_FS, "no storage interface available!"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(storage.Code()); - return; - } - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface(std::move(storage.Unwrap())); diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index acb78fac12..a0dc0bbbff 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -29,7 +29,7 @@ private: void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); void OpenRomStorage(Kernel::HLERequestContext& ctx); - std::unique_ptr romfs; + v_file romfs; }; } // namespace Service::FileSystem diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 10d0529412..f7430f54cb 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -4,12 +4,10 @@ #include #include "common/common_funcs.h" -#include "common/common_paths.h" #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" #include "core/file_sys/content_archive.h" -#include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/filesystem/filesystem.h" @@ -51,7 +49,7 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(v_file : AppLoader(file) {} FileType AppLoader_DeconstructedRomDirectory::IdentifyType(v_file file) { - if (FileSys::IsDirectoryExeFs(file->GetContainingDirectory())) { + if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) { return FileType::DeconstructedRomDirectory; } @@ -110,26 +108,19 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( }); // TODO(DarkLordZach): Identify RomFS if its a subdirectory. - romfs = std::make_shared((romfs_iter == dir->GetFiles().end()) ? nullptr : *romfs_iter); - - // Register the RomFS if a ".romfs" file was found - if (romfs != nullptr) { - Service::FileSystem::RegisterFileSystem(std::make_unique(*this), - Service::FileSystem::Type::RomFS); - } + romfs = (romfs_iter == dir->GetFiles().end()) ? nullptr : *romfs_iter; is_loaded = true; return ResultStatus::Success; } -ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(v_dir& dir) { - - if (filepath_romfs.empty()) { +ResultStatus AppLoader_DeconstructedRomDirectory::ReadRomFS(v_file& file) { + if (romfs == nullptr) { LOG_DEBUG(Loader, "No RomFS available"); return ResultStatus::ErrorNotUsed; } - dir = romfs; + file = romfs; return ResultStatus::Success; } diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index 45df993de3..f772ee34d9 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -35,10 +35,10 @@ public: ResultStatus Load(Kernel::SharedPtr& process) override; - ResultStatus ReadRomFS(v_dir& dir) override; + ResultStatus ReadRomFS(v_file& file) override; private: - v_dir romfs; + v_file romfs; FileSys::ProgramMetadata metadata; }; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 68bb1d84aa..d645c15b96 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -158,7 +158,7 @@ public: * @param file The file containing the RomFS * @return ResultStatus result of function */ - virtual ResultStatus ReadRomFS(v_dir& dir) { + virtual ResultStatus ReadRomFS(v_file& dir) { return ResultStatus::ErrorNotImplemented; } @@ -168,7 +168,7 @@ public: * @param file The file containing the RomFS * @return ResultStatus result of function */ - virtual ResultStatus ReadUpdateRomFS(v_dir& file) { + virtual ResultStatus ReadUpdateRomFS(v_file& file) { return ResultStatus::ErrorNotImplemented; } diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 2b309d583f..4c9bd806c8 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -4,14 +4,11 @@ #include -#include "common/common_funcs.h" #include "common/file_util.h" #include "common/logging/log.h" -#include "common/swap.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/program_metadata.h" -#include "core/file_sys/romfs_factory.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/filesystem/filesystem.h" @@ -21,15 +18,15 @@ namespace Loader { -AppLoader_NCA::AppLoader_NCA(v_file file) : AppLoader(std::move(file)) {} +AppLoader_NCA::AppLoader_NCA(v_file file) : AppLoader(file) {} FileType AppLoader_NCA::IdentifyType(v_file file) { // TODO(DarkLordZach): Assuming everything is decrypted. Add crypto support. - FileSys::NcaHeader header{}; - if (sizeof(FileSys::NcaHeader) != file->ReadObject(&header)) + FileSys::NCAHeader header{}; + if (sizeof(FileSys::NCAHeader) != file->ReadObject(&header)) return FileType::Error; - if (IsValidNca(header) && header.content_type == FileSys::NcaContentType::Program) + if (IsValidNCA(header) && header.content_type == FileSys::NCAContentType::Program) return FileType::NCA; return FileType::Error; @@ -40,16 +37,16 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr& process) { return ResultStatus::ErrorAlreadyLoaded; } - nca = std::make_unique(); - ResultStatus result = nca->Load(file); + nca = std::make_unique(file); + ResultStatus result = nca->GetStatus(); if (result != ResultStatus::Success) { return result; } - if (nca->GetType() != FileSys::NcaContentType::Program) + if (nca->GetType() != FileSys::NCAContentType::Program) return ResultStatus::ErrorInvalidFormat; - auto exefs = nca->GetExeFs(); + auto exefs = nca->GetExeFS(); if (exefs == nullptr) return ResultStatus::ErrorInvalidFormat; @@ -85,23 +82,19 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr& process) { process->Run(Memory::PROCESS_IMAGE_VADDR, metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize()); - if (nca->GetRomFs() != nullptr) - Service::FileSystem::RegisterFileSystem(std::make_unique(*this), - Service::FileSystem::Type::RomFS); - is_loaded = true; return ResultStatus::Success; } -ResultStatus AppLoader_NCA::ReadRomFS(v_dir& dir) { - const auto romfs = nca->GetRomFs(); +ResultStatus AppLoader_NCA::ReadRomFS(v_file& file) { + const auto romfs = nca->GetRomFS(); if (romfs == nullptr) { NGLOG_DEBUG(Loader, "No RomFS available"); return ResultStatus::ErrorNotUsed; } - dir = romfs; + file = romfs; return ResultStatus::Success; } diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 64b98513d5..682ee16898 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -6,7 +6,7 @@ #include #include "common/common_types.h" -#include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/content_archive.h" #include "core/file_sys/program_metadata.h" #include "core/hle/kernel/kernel.h" #include "core/loader/loader.h" @@ -31,14 +31,14 @@ public: ResultStatus Load(Kernel::SharedPtr& process) override; - ResultStatus ReadRomFS(v_dir& dir) override; + ResultStatus ReadRomFS(v_file& file) override; ~AppLoader_NCA(); private: FileSys::ProgramMetadata metadata; - std::unique_ptr nca; + std::unique_ptr nca; }; } // namespace Loader diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index ba5f44e956..fdf9758757 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -37,6 +37,7 @@ struct NsoHeader { std::array segments_compressed_size; }; static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size."); +static_assert(std::is_trivially_copyable::value, "NsoHeader isn't trivially copyable."); struct ModHeader { u32_le magic; @@ -99,7 +100,7 @@ VAddr AppLoader_NSO::LoadModule(v_file file, VAddr load_base) { return {}; NsoHeader nso_header{}; - if (sizeof(NsoHeader) != file->ReadObject(&file)) + if (sizeof(NsoHeader) != file->ReadObject(&nso_header)) return {}; if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))