14 changed files with 554 additions and 130 deletions
-
4src/core/CMakeLists.txt
-
2src/core/file_sys/fs_filesystem.h
-
8src/core/file_sys/fs_memory_management.h
-
2src/core/file_sys/fs_path.h
-
15src/core/file_sys/fs_string_util.h
-
89src/core/file_sys/fsa/fs_i_directory.h
-
167src/core/file_sys/fsa/fs_i_file.h
-
207src/core/file_sys/fsa/fs_i_filesystem.h
-
58src/core/hle/service/filesystem/fsp/fs_i_directory.cpp
-
7src/core/hle/service/filesystem/fsp/fs_i_directory.h
-
74src/core/hle/service/filesystem/fsp/fs_i_file.cpp
-
5src/core/hle/service/filesystem/fsp/fs_i_file.h
-
41src/core/hle/service/filesystem/fsp/fs_i_filesystem.cpp
-
5src/core/hle/service/filesystem/fsp/fs_i_filesystem.h
@ -0,0 +1,89 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
#include "core/file_sys/errors.h" |
|||
#include "core/file_sys/fs_directory.h" |
|||
#include "core/file_sys/fs_file.h" |
|||
#include "core/file_sys/fs_filesystem.h" |
|||
#include "core/file_sys/savedata_factory.h" |
|||
#include "core/file_sys/vfs/vfs.h" |
|||
#include "core/hle/result.h" |
|||
|
|||
namespace FileSys::Fsa { |
|||
|
|||
class IDirectory { |
|||
public: |
|||
IDirectory(VirtualDir backend_, OpenDirectoryMode mode) : backend(std::move(backend_)) { |
|||
// TODO(DarkLordZach): Verify that this is the correct behavior. |
|||
// Build entry index now to save time later. |
|||
if (True(mode & OpenDirectoryMode::Directory)) { |
|||
BuildEntryIndex(entries, backend->GetSubdirectories(), DirectoryEntryType::Directory); |
|||
} |
|||
if (True(mode & OpenDirectoryMode::File)) { |
|||
BuildEntryIndex(entries, backend->GetFiles(), DirectoryEntryType::File); |
|||
} |
|||
} |
|||
virtual ~IDirectory() {} |
|||
|
|||
Result Read(s64* out_count, DirectoryEntry* out_entries, s64 max_entries) { |
|||
R_UNLESS(out_count != nullptr, ResultNullptrArgument); |
|||
if (max_entries == 0) { |
|||
*out_count = 0; |
|||
R_SUCCEED(); |
|||
} |
|||
R_UNLESS(out_entries != nullptr, ResultNullptrArgument); |
|||
R_UNLESS(max_entries > 0, ResultInvalidArgument); |
|||
R_RETURN(this->DoRead(out_count, out_entries, max_entries)); |
|||
} |
|||
|
|||
Result GetEntryCount(s64* out) { |
|||
R_UNLESS(out != nullptr, ResultNullptrArgument); |
|||
R_RETURN(this->DoGetEntryCount(out)); |
|||
} |
|||
|
|||
private: |
|||
virtual Result DoRead(s64* out_count, DirectoryEntry* out_entries, s64 max_entries) { |
|||
const u64 actual_entries = |
|||
std::min(static_cast<u64>(max_entries), entries.size() - next_entry_index); |
|||
auto* begin = reinterpret_cast<u8*>(entries.data() + next_entry_index); |
|||
|
|||
next_entry_index += actual_entries; |
|||
*out_count = actual_entries; |
|||
|
|||
out_entries = reinterpret_cast<DirectoryEntry*>(begin); |
|||
|
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
virtual Result DoGetEntryCount(s64* out) { |
|||
*out = entries.size() - next_entry_index; |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
// TODO: Remove this when VFS is gone |
|||
template <typename T> |
|||
void BuildEntryIndex(std::vector<DirectoryEntry>& entries, const std::vector<T>& new_data, |
|||
DirectoryEntryType type) { |
|||
entries.reserve(entries.size() + new_data.size()); |
|||
|
|||
for (const auto& new_entry : new_data) { |
|||
auto name = new_entry->GetName(); |
|||
|
|||
if (type == DirectoryEntryType::File && name == GetSaveDataSizeFileName()) { |
|||
continue; |
|||
} |
|||
|
|||
entries.emplace_back(name, static_cast<s8>(type), |
|||
type == DirectoryEntryType::Directory ? 0 : new_entry->GetSize()); |
|||
} |
|||
} |
|||
|
|||
VirtualDir backend; |
|||
std::vector<DirectoryEntry> entries; |
|||
u64 next_entry_index = 0; |
|||
}; |
|||
|
|||
} // namespace FileSys::Fsa |
|||
@ -0,0 +1,167 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/overflow.h" |
|||
#include "core/file_sys/errors.h" |
|||
#include "core/file_sys/fs_file.h" |
|||
#include "core/file_sys/fs_filesystem.h" |
|||
#include "core/file_sys/fs_operate_range.h" |
|||
#include "core/file_sys/vfs/vfs.h" |
|||
#include "core/file_sys/vfs/vfs_types.h" |
|||
#include "core/hle/result.h" |
|||
|
|||
namespace FileSys::Fsa { |
|||
|
|||
class IFile { |
|||
public: |
|||
IFile(VirtualFile backend_) : backend(std::move(backend_)) {} |
|||
virtual ~IFile() {} |
|||
|
|||
Result Read(size_t* out, s64 offset, void* buffer, size_t size, const ReadOption& option) { |
|||
// Check that we have an output pointer |
|||
R_UNLESS(out != nullptr, ResultNullptrArgument); |
|||
|
|||
// If we have nothing to read, just succeed |
|||
if (size == 0) { |
|||
*out = 0; |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
// Check that the read is valid |
|||
R_UNLESS(buffer != nullptr, ResultNullptrArgument); |
|||
R_UNLESS(offset >= 0, ResultOutOfRange); |
|||
R_UNLESS(Common::CanAddWithoutOverflow<s64>(offset, size), ResultOutOfRange); |
|||
|
|||
// Do the read |
|||
R_RETURN(this->DoRead(out, offset, buffer, size, option)); |
|||
} |
|||
|
|||
Result Read(size_t* out, s64 offset, void* buffer, size_t size) { |
|||
R_RETURN(this->Read(out, offset, buffer, size, ReadOption::None)); |
|||
} |
|||
|
|||
Result GetSize(s64* out) { |
|||
R_UNLESS(out != nullptr, ResultNullptrArgument); |
|||
R_RETURN(this->DoGetSize(out)); |
|||
} |
|||
|
|||
Result Flush() { |
|||
R_RETURN(this->DoFlush()); |
|||
} |
|||
|
|||
Result Write(s64 offset, const void* buffer, size_t size, const WriteOption& option) { |
|||
// Handle the zero-size case |
|||
if (size == 0) { |
|||
if (option.HasFlushFlag()) { |
|||
R_TRY(this->Flush()); |
|||
} |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
// Check the write is valid |
|||
R_UNLESS(buffer != nullptr, ResultNullptrArgument); |
|||
R_UNLESS(offset >= 0, ResultOutOfRange); |
|||
R_UNLESS(Common::CanAddWithoutOverflow<s64>(offset, size), ResultOutOfRange); |
|||
|
|||
R_RETURN(this->DoWrite(offset, buffer, size, option)); |
|||
} |
|||
|
|||
Result SetSize(s64 size) { |
|||
R_UNLESS(size >= 0, ResultOutOfRange); |
|||
R_RETURN(this->DoSetSize(size)); |
|||
} |
|||
|
|||
Result OperateRange(void* dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, |
|||
const void* src, size_t src_size) { |
|||
R_RETURN(this->DoOperateRange(dst, dst_size, op_id, offset, size, src, src_size)); |
|||
} |
|||
|
|||
Result OperateRange(OperationId op_id, s64 offset, s64 size) { |
|||
R_RETURN(this->DoOperateRange(nullptr, 0, op_id, offset, size, nullptr, 0)); |
|||
} |
|||
|
|||
protected: |
|||
Result DryRead(size_t* out, s64 offset, size_t size, const ReadOption& option, |
|||
OpenMode open_mode) { |
|||
// Check that we can read |
|||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::Read) != 0, ResultReadNotPermitted); |
|||
|
|||
// Get the file size, and validate our offset |
|||
s64 file_size = 0; |
|||
R_TRY(this->DoGetSize(std::addressof(file_size))); |
|||
R_UNLESS(offset <= file_size, ResultOutOfRange); |
|||
|
|||
*out = static_cast<size_t>(std::min(file_size - offset, static_cast<s64>(size))); |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
Result DrySetSize(s64 size, OpenMode open_mode) { |
|||
// Check that we can write |
|||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::Write) != 0, ResultWriteNotPermitted); |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
Result DryWrite(bool* out_append, s64 offset, size_t size, const WriteOption& option, |
|||
OpenMode open_mode) { |
|||
// Check that we can write |
|||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::Write) != 0, ResultWriteNotPermitted); |
|||
|
|||
// Get the file size |
|||
s64 file_size = 0; |
|||
R_TRY(this->DoGetSize(&file_size)); |
|||
|
|||
// Determine if we need to append |
|||
*out_append = false; |
|||
if (file_size < offset + static_cast<s64>(size)) { |
|||
R_UNLESS(static_cast<u32>(open_mode & OpenMode::AllowAppend) != 0, |
|||
ResultFileExtensionWithoutOpenModeAllowAppend); |
|||
*out_append = true; |
|||
} |
|||
|
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
private: |
|||
Result DoRead(size_t* out, s64 offset, void* buffer, size_t size, const ReadOption& option) { |
|||
std::vector<u8> output = backend->ReadBytes(size, offset); |
|||
*out = output.size(); |
|||
buffer = output.data(); |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
Result DoGetSize(s64* out) { |
|||
*out = backend->GetSize(); |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
Result DoFlush() { |
|||
// Exists for SDK compatibiltity -- No need to flush file. |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
Result DoWrite(s64 offset, const void* buffer, size_t size, const WriteOption& option) { |
|||
const std::size_t written = backend->Write(static_cast<const u8*>(buffer), size, offset); |
|||
|
|||
ASSERT_MSG(written == size, |
|||
"Could not write all bytes to file (requested={:016X}, actual={:016X}).", size, |
|||
written); |
|||
|
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
Result DoSetSize(s64 size) { |
|||
backend->Resize(size); |
|||
R_SUCCEED(); |
|||
} |
|||
|
|||
Result DoOperateRange(void* dst, size_t dst_size, OperationId op_id, s64 offset, s64 size, |
|||
const void* src, size_t src_size) { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
VirtualFile backend; |
|||
}; |
|||
|
|||
} // namespace FileSys::Fsa |
|||
@ -0,0 +1,207 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include "core/file_sys/errors.h" |
|||
#include "core/file_sys/fs_filesystem.h" |
|||
#include "core/file_sys/fs_path.h" |
|||
#include "core/file_sys/vfs/vfs_types.h" |
|||
#include "core/hle/result.h" |
|||
#include "core/hle/service/filesystem/filesystem.h" |
|||
|
|||
namespace FileSys::Fsa { |
|||
|
|||
class IFile; |
|||
class IDirectory; |
|||
|
|||
enum class QueryId { |
|||
SetConcatenationFileAttribute = 0, |
|||
UpdateMac = 1, |
|||
IsSignedSystemPartitionOnSdCardValid = 2, |
|||
QueryUnpreparedFileInformation = 3, |
|||
}; |
|||
|
|||
class IFileSystem { |
|||
public: |
|||
IFileSystem(VirtualDir backend_) : backend{std::move(backend_)} {} |
|||
virtual ~IFileSystem() {} |
|||
|
|||
Result CreateFile(const Path& path, s64 size, CreateOption option) { |
|||
R_UNLESS(size >= 0, ResultOutOfRange); |
|||
R_RETURN(this->DoCreateFile(path, size, static_cast<int>(option))); |
|||
} |
|||
|
|||
Result CreateFile(const Path& path, s64 size) { |
|||
R_RETURN(this->CreateFile(path, size, CreateOption::None)); |
|||
} |
|||
|
|||
Result DeleteFile(const Path& path) { |
|||
R_RETURN(this->DoDeleteFile(path)); |
|||
} |
|||
|
|||
Result CreateDirectory(const Path& path) { |
|||
R_RETURN(this->DoCreateDirectory(path)); |
|||
} |
|||
|
|||
Result DeleteDirectory(const Path& path) { |
|||
R_RETURN(this->DoDeleteDirectory(path)); |
|||
} |
|||
|
|||
Result DeleteDirectoryRecursively(const Path& path) { |
|||
R_RETURN(this->DoDeleteDirectoryRecursively(path)); |
|||
} |
|||
|
|||
Result RenameFile(const Path& old_path, const Path& new_path) { |
|||
R_RETURN(this->DoRenameFile(old_path, new_path)); |
|||
} |
|||
|
|||
Result RenameDirectory(const Path& old_path, const Path& new_path) { |
|||
R_RETURN(this->DoRenameDirectory(old_path, new_path)); |
|||
} |
|||
|
|||
Result GetEntryType(DirectoryEntryType* out, const Path& path) { |
|||
R_RETURN(this->DoGetEntryType(out, path)); |
|||
} |
|||
|
|||
Result OpenFile(VirtualFile* out_file, const Path& path, OpenMode mode) { |
|||
R_UNLESS(out_file != nullptr, ResultNullptrArgument); |
|||
R_UNLESS(static_cast<u32>(mode & OpenMode::ReadWrite) != 0, ResultInvalidOpenMode); |
|||
R_UNLESS(static_cast<u32>(mode & ~OpenMode::All) == 0, ResultInvalidOpenMode); |
|||
R_RETURN(this->DoOpenFile(out_file, path, mode)); |
|||
} |
|||
|
|||
Result OpenDirectory(VirtualDir* out_dir, const Path& path, OpenDirectoryMode mode) { |
|||
R_UNLESS(out_dir != nullptr, ResultNullptrArgument); |
|||
R_UNLESS(static_cast<u64>(mode & OpenDirectoryMode::All) != 0, ResultInvalidOpenMode); |
|||
R_UNLESS(static_cast<u64>( |
|||
mode & ~(OpenDirectoryMode::All | OpenDirectoryMode::NotRequireFileSize)) == 0, |
|||
ResultInvalidOpenMode); |
|||
R_RETURN(this->DoOpenDirectory(out_dir, path, mode)); |
|||
} |
|||
|
|||
Result Commit() { |
|||
R_RETURN(this->DoCommit()); |
|||
} |
|||
|
|||
Result GetFreeSpaceSize(s64* out, const Path& path) { |
|||
R_UNLESS(out != nullptr, ResultNullptrArgument); |
|||
R_RETURN(this->DoGetFreeSpaceSize(out, path)); |
|||
} |
|||
|
|||
Result GetTotalSpaceSize(s64* out, const Path& path) { |
|||
R_UNLESS(out != nullptr, ResultNullptrArgument); |
|||
R_RETURN(this->DoGetTotalSpaceSize(out, path)); |
|||
} |
|||
|
|||
Result CleanDirectoryRecursively(const Path& path) { |
|||
R_RETURN(this->DoCleanDirectoryRecursively(path)); |
|||
} |
|||
|
|||
Result GetFileTimeStampRaw(FileTimeStampRaw* out, const Path& path) { |
|||
R_UNLESS(out != nullptr, ResultNullptrArgument); |
|||
R_RETURN(this->DoGetFileTimeStampRaw(out, path)); |
|||
} |
|||
|
|||
Result QueryEntry(char* dst, size_t dst_size, const char* src, size_t src_size, QueryId query, |
|||
const Path& path) { |
|||
R_RETURN(this->DoQueryEntry(dst, dst_size, src, src_size, query, path)); |
|||
} |
|||
|
|||
// These aren't accessible as commands |
|||
Result CommitProvisionally(s64 counter) { |
|||
R_RETURN(this->DoCommitProvisionally(counter)); |
|||
} |
|||
|
|||
Result Rollback() { |
|||
R_RETURN(this->DoRollback()); |
|||
} |
|||
|
|||
Result Flush() { |
|||
R_RETURN(this->DoFlush()); |
|||
} |
|||
|
|||
private: |
|||
Result DoCreateFile(const Path& path, s64 size, int flags) { |
|||
R_RETURN(backend.CreateFile(path.GetString(), size)); |
|||
} |
|||
|
|||
Result DoDeleteFile(const Path& path) { |
|||
R_RETURN(backend.DeleteFile(path.GetString())); |
|||
} |
|||
|
|||
Result DoCreateDirectory(const Path& path) { |
|||
R_RETURN(backend.CreateDirectory(path.GetString())); |
|||
} |
|||
|
|||
Result DoDeleteDirectory(const Path& path) { |
|||
R_RETURN(backend.DeleteDirectory(path.GetString())); |
|||
} |
|||
|
|||
Result DoDeleteDirectoryRecursively(const Path& path) { |
|||
R_RETURN(backend.DeleteDirectoryRecursively(path.GetString())); |
|||
} |
|||
|
|||
Result DoRenameFile(const Path& old_path, const Path& new_path) { |
|||
R_RETURN(backend.RenameFile(old_path.GetString(), new_path.GetString())); |
|||
} |
|||
|
|||
Result DoRenameDirectory(const Path& old_path, const Path& new_path) { |
|||
R_RETURN(backend.RenameDirectory(old_path.GetString(), new_path.GetString())); |
|||
} |
|||
|
|||
Result DoGetEntryType(DirectoryEntryType* out, const Path& path) { |
|||
R_RETURN(backend.GetEntryType(out, path.GetString())); |
|||
} |
|||
|
|||
Result DoOpenFile(VirtualFile* out_file, const Path& path, OpenMode mode) { |
|||
R_RETURN(backend.OpenFile(out_file, path.GetString(), mode)); |
|||
} |
|||
|
|||
Result DoOpenDirectory(VirtualDir* out_directory, const Path& path, |
|||
OpenDirectoryMode mode) { |
|||
R_RETURN(backend.OpenDirectory(out_directory, path.GetString())); |
|||
} |
|||
|
|||
Result DoCommit() { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
Result DoGetFreeSpaceSize(s64* out, const Path& path) { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
Result DoGetTotalSpaceSize(s64* out, const Path& path) { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
Result DoCleanDirectoryRecursively(const Path& path) { |
|||
R_RETURN(backend.CleanDirectoryRecursively(path.GetString())); |
|||
} |
|||
|
|||
Result DoGetFileTimeStampRaw(FileTimeStampRaw* out, const Path& path) { |
|||
R_RETURN(backend.GetFileTimeStampRaw(out, path.GetString())); |
|||
} |
|||
|
|||
Result DoQueryEntry(char* dst, size_t dst_size, const char* src, size_t src_size, QueryId query, |
|||
const Path& path) { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
// These aren't accessible as commands |
|||
Result DoCommitProvisionally(s64 counter) { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
Result DoRollback() { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
Result DoFlush() { |
|||
R_THROW(ResultNotImplemented); |
|||
} |
|||
|
|||
Service::FileSystem::VfsDirectoryServiceWrapper backend; |
|||
}; |
|||
|
|||
} // namespace FileSys::Fsa |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue