diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f49a316127..4660ed2a58 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -60,6 +60,7 @@ add_library(common STATIC scm_rev.cpp scm_rev.h scope_exit.h + std_filesystem.h string_util.cpp string_util.h swap.h diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 2d0b81c6ee..5cd8aea558 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -388,7 +388,7 @@ u64 GetSize(FILE* f) { bool CreateEmptyFile(const std::string& filename) { NGLOG_TRACE(Common_Filesystem, "{}", filename); - if (!FileUtil::IOFile(filename, "wb")) { + if (!FileUtil::IOFile(filename, "wb").IsOpen()) { NGLOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); return false; } @@ -750,7 +750,7 @@ size_t WriteStringToFile(bool text_file, const std::string& str, const char* fil size_t ReadFileToString(bool text_file, const char* filename, std::string& str) { IOFile file(filename, text_file ? "r" : "rb"); - if (!file) + if (!file.IsOpen()) return false; str.resize(static_cast(file.GetSize())); @@ -820,7 +820,6 @@ IOFile& IOFile::operator=(IOFile&& other) noexcept { void IOFile::Swap(IOFile& other) noexcept { std::swap(m_file, other.m_file); - std::swap(m_good, other.m_good); } bool IOFile::Open(const std::string& filename, const char openmode[]) { @@ -832,16 +831,15 @@ bool IOFile::Open(const std::string& filename, const char openmode[]) { m_file = fopen(filename.c_str(), openmode); #endif - m_good = IsOpen(); - return m_good; + return IsOpen(); } bool IOFile::Close() { if (!IsOpen() || 0 != std::fclose(m_file)) - m_good = false; + return false; m_file = nullptr; - return m_good; + return true; } u64 IOFile::GetSize() const { @@ -851,11 +849,8 @@ u64 IOFile::GetSize() const { return 0; } -bool IOFile::Seek(s64 off, int origin) { - if (!IsOpen() || 0 != fseeko(m_file, off, origin)) - m_good = false; - - return m_good; +bool IOFile::Seek(s64 off, int origin) const { + return IsOpen() && 0 == fseeko(m_file, off, origin); } u64 IOFile::Tell() const { @@ -866,26 +861,20 @@ u64 IOFile::Tell() const { } bool IOFile::Flush() { - if (!IsOpen() || 0 != std::fflush(m_file)) - m_good = false; - - return m_good; + return IsOpen() && 0 == std::fflush(m_file); } bool IOFile::Resize(u64 size) { - if (!IsOpen() || 0 != + return IsOpen() && 0 == #ifdef _WIN32 - // ector: _chsize sucks, not 64-bit safe - // F|RES: changed to _chsize_s. i think it is 64-bit safe - _chsize_s(_fileno(m_file), size) + // ector: _chsize sucks, not 64-bit safe + // F|RES: changed to _chsize_s. i think it is 64-bit safe + _chsize_s(_fileno(m_file), size) #else - // TODO: handle 64bit and growing - ftruncate(fileno(m_file), size) + // TODO: handle 64bit and growing + ftruncate(fileno(m_file), size) #endif - ) - m_good = false; - - return m_good; + ; } } // namespace FileUtil diff --git a/src/common/file_util.h b/src/common/file_util.h index fc6b3ea466..38631b8438 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -169,41 +169,27 @@ public: bool Close(); template - size_t ReadArray(T* data, size_t length) { + size_t ReadArray(T* data, size_t length) const { static_assert(std::is_trivially_copyable(), "Given array does not consist of trivially copyable objects"); - if (!IsOpen()) { - m_good = false; + if (!IsOpen()) return -1; - } - size_t items_read = std::fread(data, sizeof(T), length, m_file); - if (items_read != length) - m_good = false; - - return items_read; + return std::fread(data, sizeof(T), length, m_file); } template size_t WriteArray(const T* data, size_t length) { static_assert(std::is_trivially_copyable(), "Given array does not consist of trivially copyable objects"); - - if (!IsOpen()) { - m_good = false; + if (!IsOpen()) return -1; - } - - size_t items_written = std::fwrite(data, sizeof(T), length, m_file); - if (items_written != length) - m_good = false; - - return items_written; + return std::fwrite(data, sizeof(T), length, m_file); } template - size_t ReadBytes(T* data, size_t length) { + size_t ReadBytes(T* data, size_t length) const { static_assert(std::is_trivially_copyable(), "T must be trivially copyable"); return ReadArray(reinterpret_cast(data), length); } @@ -224,15 +210,7 @@ public: return nullptr != m_file; } - // m_good is set to false when a read, write or other function fails - bool IsGood() const { - return m_good; - } - explicit operator bool() const { - return IsGood(); - } - - bool Seek(s64 off, int origin); + bool Seek(s64 off, int origin) const; u64 Tell() const; u64 GetSize() const; bool Resize(u64 size); @@ -240,13 +218,11 @@ public: // clear error state void Clear() { - m_good = true; std::clearerr(m_file); } private: std::FILE* m_file = nullptr; - bool m_good = true; }; } // namespace FileUtil diff --git a/src/common/std_filesystem.h b/src/common/std_filesystem.h new file mode 100644 index 0000000000..39ac2237d1 --- /dev/null +++ b/src/common/std_filesystem.h @@ -0,0 +1,13 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#ifdef _MSC_VER +#include +namespace filesystem = std::filesystem; +#else +#include +namespace filesystem = std::experimental::filesystem; +#endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 51e4088d24..15235cd7e6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -29,6 +29,12 @@ add_library(core STATIC file_sys/sdmc_factory.cpp file_sys/sdmc_factory.h file_sys/storage.h + file_sys/vfs.cpp + file_sys/vfs.h + file_sys/vfs_offset.cpp + file_sys/vfs_offset.h + file_sys/vfs_real.cpp + file_sys/vfs_real.h frontend/emu_window.cpp frontend/emu_window.h frontend/framebuffer_layout.cpp diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp new file mode 100644 index 0000000000..d602873e07 --- /dev/null +++ b/src/core/file_sys/vfs.cpp @@ -0,0 +1,111 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "core/file_sys/vfs.h" + +namespace FileSys { + +VfsFile::~VfsFile() = default; + +VfsDirectory::~VfsDirectory() = default; + +boost::optional VfsFile::ReadByte(size_t offset) const { + u8 out{}; + size_t size = Read(&out, 1, offset); + if (size == 1) + return out; + + return boost::none; +} + +std::vector VfsFile::ReadBytes(size_t size, size_t offset) const { + std::vector out(size); + size_t read_size = Read(out.data(), size, offset); + out.resize(read_size); + return out; +} + +std::vector VfsFile::ReadAllBytes() const { + return ReadBytes(GetSize()); +} + +bool VfsFile::WriteByte(u8 data, size_t offset) { + return 1 == Write(&data, 1, offset); +} + +size_t VfsFile::WriteBytes(std::vector data, size_t offset) { + return Write(data.data(), data.size(), offset); +} + +std::shared_ptr VfsDirectory::GetFileRelative(const filesystem::path& path) const { + if (path.parent_path() == path.root_path()) + return GetFile(path.filename().string()); + + auto parent = path.parent_path().string(); + parent.replace(path.root_path().string().begin(), path.root_path().string().end(), ""); + const auto index = parent.find_first_of('\\'); + const auto first_dir = parent.substr(0, index), rest = parent.substr(index + 1); + const auto sub = GetSubdirectory(first_dir); + if (sub == nullptr) + return nullptr; + return sub->GetFileRelative(path.root_path().string() + rest); +} + +std::shared_ptr VfsDirectory::GetFileAbsolute(const filesystem::path& path) const { + if (IsRoot()) + return GetFileRelative(path); + + return GetParentDirectory()->GetFileAbsolute(path); +} + +std::shared_ptr VfsDirectory::GetFile(const std::string& name) const { + const auto& files = GetFiles(); + const auto& iter = std::find_if(files.begin(), files.end(), [&name](const auto& file1) { + return name == file1->GetName(); + }); + return iter == files.end() ? nullptr : *iter; +} + +std::shared_ptr VfsDirectory::GetSubdirectory(const std::string& name) const { + const auto& subs = GetSubdirectories(); + const auto& iter = std::find_if( + subs.begin(), subs.end(), [&name](const auto& file1) { return name == file1->GetName(); }); + return iter == subs.end() ? nullptr : std::move(*iter); +} + +bool VfsDirectory::IsRoot() const { + return GetParentDirectory() == nullptr; +} + +size_t VfsDirectory::GetSize() const { + const auto& files = GetFiles(); + const auto file_total = + std::accumulate(files.begin(), files.end(), 0ull, + [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); }); + + const auto& sub_dir = GetSubdirectories(); + const auto subdir_total = + std::accumulate(sub_dir.begin(), sub_dir.end(), 0ull, + [](const auto& f1, const auto& f2) { return f1 + f2->GetSize(); }); + + return file_total + subdir_total; +} + +bool VfsDirectory::Copy(const std::string& src, const std::string& dest) { + const auto f1 = CreateFile(src); + auto f2 = CreateFile(dest); + if (f1 == nullptr || f2 == nullptr) + return false; + + if (!f2->Resize(f1->GetSize())) { + DeleteFile(dest); + return false; + } + + return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize(); +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h new file mode 100644 index 0000000000..e45bc8af19 --- /dev/null +++ b/src/core/file_sys/vfs.h @@ -0,0 +1,117 @@ +// 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 "boost/optional.hpp" +#include "common/common_types.h" +#include "common/file_util.h" +#include "common/std_filesystem.h" + +namespace FileSys { +struct VfsDirectory; + +struct VfsFile : NonCopyable { + virtual ~VfsFile(); + + virtual std::string GetName() const = 0; + virtual size_t GetSize() const = 0; + virtual bool Resize(size_t new_size) = 0; + virtual std::shared_ptr GetContainingDirectory() const = 0; + + virtual bool IsWritable() const = 0; + virtual bool IsReadable() const = 0; + + virtual size_t Read(u8* data, size_t length, size_t offset = 0) const = 0; + virtual size_t Write(const u8* data, size_t length, size_t offset = 0) = 0; + + virtual boost::optional ReadByte(size_t offset = 0) const; + virtual std::vector ReadBytes(size_t size, size_t offset = 0) const; + virtual std::vector ReadAllBytes() const; + + template + size_t ReadArray(T* data, size_t number_elements, size_t offset = 0) const { + static_assert(std::is_trivially_copyable::value, + "Data type must be trivially copyable."); + + return Read(data, number_elements * sizeof(T), offset); + } + + template + size_t ReadBytes(T* data, size_t size, size_t offset = 0) const { + static_assert(std::is_trivially_copyable::value, + "Data type must be trivially copyable."); + return Read(reinterpret_cast(data), size, offset); + } + + template + size_t ReadObject(T& data, size_t offset = 0) const { + static_assert(std::is_trivially_copyable::value, + "Data type must be trivially copyable."); + return Read(&data, sizeof(T), offset); + } + + virtual bool WriteByte(u8 data, size_t offset = 0); + virtual size_t WriteBytes(std::vector data, size_t offset = 0); + + template + size_t WriteArray(T* data, size_t number_elements, size_t offset = 0) { + static_assert(std::is_trivially_copyable::value, + "Data type must be trivially copyable."); + + return Write(data, number_elements * sizeof(T), offset); + } + + template + size_t WriteBytes(T* data, size_t size, size_t offset = 0) { + static_assert(std::is_trivially_copyable::value, + "Data type must be trivially copyable."); + return Write(reinterpret_cast(data), size, offset); + } + + template + size_t WriteObject(T& data, size_t offset = 0) { + static_assert(std::is_trivially_copyable::value, + "Data type must be trivially copyable."); + return Write(&data, sizeof(T), offset); + } + + virtual bool Rename(const std::string& name) = 0; +}; + +struct VfsDirectory : NonCopyable { + virtual ~VfsDirectory(); + + virtual std::shared_ptr GetFileRelative(const filesystem::path& path) const; + virtual std::shared_ptr GetFileAbsolute(const filesystem::path& path) const; + + virtual std::vector> GetFiles() const = 0; + virtual std::shared_ptr GetFile(const std::string& name) const; + + virtual std::vector> GetSubdirectories() const = 0; + virtual std::shared_ptr GetSubdirectory(const std::string& name) const; + + virtual bool IsWritable() const = 0; + virtual bool IsReadable() const = 0; + + virtual bool IsRoot() const; + + virtual std::string GetName() const = 0; + virtual size_t GetSize() const; + virtual std::shared_ptr GetParentDirectory() const = 0; + + virtual std::shared_ptr CreateSubdirectory(const std::string& name) = 0; + virtual std::shared_ptr CreateFile(const std::string& name) = 0; + + virtual bool DeleteSubdirectory(const std::string& name) = 0; + virtual bool DeleteFile(const std::string& name) = 0; + + virtual bool Rename(const std::string& name) = 0; + + virtual bool Copy(const std::string& src, const std::string& dest); +}; +} // namespace FileSys diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp new file mode 100644 index 0000000000..bc3b16eb5b --- /dev/null +++ b/src/core/file_sys/vfs_offset.cpp @@ -0,0 +1,83 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/file_sys/vfs_offset.h" + +namespace FileSys { + +OffsetVfsFile::OffsetVfsFile(std::unique_ptr&& file_, u64 offset_, u64 size_) + : file(std::move(file_)), offset(offset_), size(size_) {} + +std::string OffsetVfsFile::GetName() const { + return file->GetName(); +} + +u64 OffsetVfsFile::GetSize() const { + return size; +} + +bool OffsetVfsFile::Resize(u64 new_size) { + if (offset + new_size < file->GetSize()) { + size = new_size; + return true; + } + + return false; +} + +std::shared_ptr OffsetVfsFile::GetContainingDirectory() const { + return file->GetContainingDirectory(); +} + +bool OffsetVfsFile::IsWritable() const { + return file->IsWritable(); +} + +bool OffsetVfsFile::IsReadable() const { + return file->IsReadable(); +} + +size_t OffsetVfsFile::Read(u8* data, size_t length, size_t r_offset) const { + return file->Read(data, TrimToFit(length, r_offset), offset + r_offset); +} + +size_t OffsetVfsFile::Write(const u8* data, size_t length, size_t r_offset) { + return file->Write(data, TrimToFit(length, r_offset), offset + r_offset); +} + +boost::optional OffsetVfsFile::ReadByte(size_t r_offset) const { + if (r_offset < size) + return file->ReadByte(offset + r_offset); + + return boost::none; +} + +std::vector OffsetVfsFile::ReadBytes(size_t r_size, size_t r_offset) const { + return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset); +} + +std::vector OffsetVfsFile::ReadAllBytes() const { + return file->ReadBytes(size, offset); +} + +bool OffsetVfsFile::WriteByte(u8 data, size_t r_offset) { + if (r_offset < size) + return file->WriteByte(data, offset + r_offset); + + return false; +} + +size_t OffsetVfsFile::WriteBytes(std::vector data, size_t r_offset) { + return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset); +} + +bool OffsetVfsFile::Rename(const std::string& name) { + return file->Rename(name); +} + +size_t OffsetVfsFile::TrimToFit(size_t r_size, size_t r_offset) const { + return std::max(std::min(size - r_offset, r_size), 0ull); +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h new file mode 100644 index 0000000000..58d59448ea --- /dev/null +++ b/src/core/file_sys/vfs_offset.h @@ -0,0 +1,38 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/vfs.h" + +namespace FileSys { + +struct OffsetVfsFile : public VfsFile { + OffsetVfsFile(std::unique_ptr&& file, size_t offset, size_t size); + + std::string GetName() const override; + size_t GetSize() const override; + bool Resize(size_t new_size) override; + std::shared_ptr GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + size_t Read(u8* data, size_t length, size_t offset) const override; + size_t Write(const u8* data, size_t length, size_t offset) override; + boost::optional ReadByte(size_t offset) const override; + std::vector ReadBytes(size_t size, size_t offset) const override; + std::vector ReadAllBytes() const override; + bool WriteByte(u8 data, size_t offset) override; + size_t WriteBytes(std::vector data, size_t offset) override; + + bool Rename(const std::string& name) override; + +private: + size_t TrimToFit(size_t r_size, size_t r_offset) const; + + std::unique_ptr file; + size_t offset; + size_t size; +}; + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp new file mode 100644 index 0000000000..853cb3cdaf --- /dev/null +++ b/src/core/file_sys/vfs_real.cpp @@ -0,0 +1,128 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/file_sys/vfs_real.h" + +namespace FileSys { + +static const char* PermissionsToCharArray(filesystem::perms perms) { + std::string out; + if ((perms & filesystem::perms::owner_read) != filesystem::perms::none) + out += "r"; + if ((perms & filesystem::perms::owner_write) != filesystem::perms::none) + out += "w"; + return out.c_str(); +} + +RealVfsFile::RealVfsFile(const filesystem::path& path_, filesystem::perms perms_) + : backing(path_.string(), PermissionsToCharArray(perms_)), path(path_), perms(perms_) {} + +std::string RealVfsFile::GetName() const { + return path.filename().string(); +} + +size_t RealVfsFile::GetSize() const { + return backing.GetSize(); +} + +bool RealVfsFile::Resize(size_t new_size) { + return backing.Resize(new_size); +} + +std::shared_ptr RealVfsFile::GetContainingDirectory() const { + return std::make_shared(path.parent_path(), perms); +} + +bool RealVfsFile::IsWritable() const { + return (perms & filesystem::perms::owner_write) != filesystem::perms::none; +} + +bool RealVfsFile::IsReadable() const { + return (perms & filesystem::perms::owner_read) != filesystem::perms::none; +} + +size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const { + if (!backing.Seek(offset, SEEK_SET)) + return 0; + return backing.ReadBytes(data, length); +} + +size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) { + if (!backing.Seek(offset, SEEK_SET)) + return 0; + return backing.WriteBytes(data, length); +} + +bool RealVfsFile::Rename(const std::string& name) { + const auto out = FileUtil::Rename(GetName(), name); + path = path.parent_path() / name; + backing = FileUtil::IOFile(path.string(), PermissionsToCharArray(perms)); + return out; +} + +RealVfsDirectory::RealVfsDirectory(const filesystem::path& path_, filesystem::perms perms_) + : path(path_), perms(perms_) { + for (const auto& entry : filesystem::directory_iterator(path)) { + if (filesystem::is_directory(entry.path())) + subdirectories.emplace_back(std::make_shared(entry.path(), perms)); + else if (filesystem::is_regular_file(entry.path())) + files.emplace_back(std::make_shared(entry.path(), perms)); + } +} + +std::vector> RealVfsDirectory::GetFiles() const { + return std::vector>(files); +} + +std::vector> RealVfsDirectory::GetSubdirectories() const { + return std::vector>(subdirectories); +} + +bool RealVfsDirectory::IsWritable() const { + return (perms & filesystem::perms::owner_write) != filesystem::perms::none; +} + +bool RealVfsDirectory::IsReadable() const { + return (perms & filesystem::perms::owner_read) != filesystem::perms::none; +} + +std::string RealVfsDirectory::GetName() const { + return path.filename().string(); +} + +std::shared_ptr RealVfsDirectory::GetParentDirectory() const { + if (path.parent_path() == path.root_path()) + return nullptr; + + return std::make_shared(path.parent_path(), perms); +} + +std::shared_ptr RealVfsDirectory::CreateSubdirectory(const std::string& name) { + if (!FileUtil::CreateDir((path / name).string())) + return nullptr; + subdirectories.emplace_back(std::make_shared(path / name, perms)); + return subdirectories.back(); +} + +std::shared_ptr RealVfsDirectory::CreateFile(const std::string& name) { + if (!FileUtil::CreateEmptyFile((path / name).string())) + return nullptr; + files.emplace_back(std::make_shared(path / name, perms)); + return files.back(); +} + +bool RealVfsDirectory::DeleteSubdirectory(const std::string& name) { + return FileUtil::DeleteDirRecursively((path / name).string()); +} + +bool RealVfsDirectory::DeleteFile(const std::string& name) { + return FileUtil::Delete((path / name).string()); +} + +bool RealVfsDirectory::Rename(const std::string& name) { + return FileUtil::Rename(path.string(), (path / name).string()); +} + +} // namespace FileSys diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h new file mode 100644 index 0000000000..48aa8837f3 --- /dev/null +++ b/src/core/file_sys/vfs_real.h @@ -0,0 +1,55 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/file_util.h" +#include "core/file_sys/vfs.h" + +namespace FileSys { + +struct RealVfsFile : public VfsFile { + RealVfsFile(const filesystem::path& name, filesystem::perms perms); + + std::string GetName() const override; + size_t GetSize() const override; + bool Resize(size_t new_size) override; + std::shared_ptr GetContainingDirectory() const override; + bool IsWritable() const override; + bool IsReadable() const override; + size_t Read(u8* data, size_t length, size_t offset) const override; + size_t Write(const u8* data, size_t length, size_t offset) override; + bool Rename(const std::string& name) override; + +private: + FileUtil::IOFile backing; + filesystem::path path; + filesystem::perms perms; +}; + +struct RealVfsDirectory : public VfsDirectory { + RealVfsDirectory(const filesystem::path& path, filesystem::perms perms); + + std::vector> GetFiles() const override; + 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; + std::shared_ptr CreateFile(const std::string& name) override; + bool DeleteSubdirectory(const std::string& name) override; + bool DeleteFile(const std::string& name) override; + bool Rename(const std::string& name) override; + +private: + filesystem::path path; + filesystem::perms perms; + std::vector> files; + std::vector> subdirectories; +}; + +} // namespace FileSys