diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5bce64207..2a3a91b42 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -247,6 +247,10 @@ add_library(core STATIC hle/service/frd/frd_u.h hle/service/fs/archive.cpp hle/service/fs/archive.h + hle/service/fs/directory.cpp + hle/service/fs/directory.h + hle/service/fs/file.cpp + hle/service/fs/file.h hle/service/fs/fs_user.cpp hle/service/fs/fs_user.h hle/service/gsp/gsp.cpp diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 6d72b4c53..e76ec1e13 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -27,339 +27,11 @@ #include "core/file_sys/directory_backend.h" #include "core/file_sys/errors.h" #include "core/file_sys/file_backend.h" -#include "core/hle/ipc.h" -#include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/client_port.h" -#include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/event.h" -#include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/server_session.h" #include "core/hle/result.h" #include "core/hle/service/fs/archive.h" -#include "core/hle/service/fs/fs_user.h" -#include "core/hle/service/service.h" -#include "core/memory.h" namespace Service::FS { -// Command to access directory -enum class DirectoryCommand : u32 { - Dummy1 = 0x000100C6, - Control = 0x040100C4, - Read = 0x08010042, - Close = 0x08020000, -}; - -File::File(std::unique_ptr&& backend, const FileSys::Path& path) - : ServiceFramework("", 1), path(path), backend(std::move(backend)) { - static const FunctionInfo functions[] = { - {0x08010100, &File::OpenSubFile, "OpenSubFile"}, - {0x080200C2, &File::Read, "Read"}, - {0x08030102, &File::Write, "Write"}, - {0x08040000, &File::GetSize, "GetSize"}, - {0x08050080, &File::SetSize, "SetSize"}, - {0x08080000, &File::Close, "Close"}, - {0x08090000, &File::Flush, "Flush"}, - {0x080A0040, &File::SetPriority, "SetPriority"}, - {0x080B0000, &File::GetPriority, "GetPriority"}, - {0x080C0000, &File::OpenLinkFile, "OpenLinkFile"}, - }; - RegisterHandlers(functions); -} - -void File::Read(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0802, 3, 2); - u64 offset = rp.Pop(); - u32 length = rp.Pop(); - auto& buffer = rp.PopMappedBuffer(); - LOG_TRACE(Service_FS, "Read {}: offset=0x{:x} length=0x{:08X}", GetName(), offset, length); - - const FileSessionSlot* file = GetSessionData(ctx.Session()); - - if (file->subfile && length > file->size) { - LOG_WARNING(Service_FS, "Trying to read beyond the subfile size, truncating"); - length = static_cast(file->size); - } - - // This file session might have a specific offset from where to start reading, apply it. - offset += file->offset; - - if (offset + length > backend->GetSize()) { - LOG_ERROR(Service_FS, - "Reading from out of bounds offset=0x{:x} length=0x{:08X} file_size=0x{:x}", - offset, length, backend->GetSize()); - } - - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - - std::vector data(length); - ResultVal read = backend->Read(offset, data.size(), data.data()); - if (read.Failed()) { - rb.Push(read.Code()); - rb.Push(0); - } else { - buffer.Write(data.data(), 0, *read); - rb.Push(RESULT_SUCCESS); - rb.Push(static_cast(*read)); - } - rb.PushMappedBuffer(buffer); - - std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)}; - ctx.SleepClientThread(Kernel::GetCurrentThread(), "file::read", read_timeout_ns, - [](Kernel::SharedPtr thread, - Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { - // Nothing to do here - }); -} - -void File::Write(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0803, 4, 2); - u64 offset = rp.Pop(); - u32 length = rp.Pop(); - u32 flush = rp.Pop(); - auto& buffer = rp.PopMappedBuffer(); - LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flush=0x{:x}", GetName(), offset, - length, flush); - - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - - const FileSessionSlot* file = GetSessionData(ctx.Session()); - - // Subfiles can not be written to - if (file->subfile) { - rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); - rb.Push(0); - rb.PushMappedBuffer(buffer); - return; - } - - std::vector data(length); - buffer.Read(data.data(), 0, data.size()); - ResultVal written = backend->Write(offset, data.size(), flush != 0, data.data()); - if (written.Failed()) { - rb.Push(written.Code()); - rb.Push(0); - } else { - rb.Push(RESULT_SUCCESS); - rb.Push(static_cast(*written)); - } - rb.PushMappedBuffer(buffer); -} - -void File::GetSize(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0804, 0, 0); - - const FileSessionSlot* file = GetSessionData(ctx.Session()); - - IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); - rb.Push(RESULT_SUCCESS); - rb.Push(file->size); -} - -void File::SetSize(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0805, 2, 0); - u64 size = rp.Pop(); - - FileSessionSlot* file = GetSessionData(ctx.Session()); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - - // SetSize can not be called on subfiles. - if (file->subfile) { - rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); - return; - } - - file->size = size; - backend->SetSize(size); - rb.Push(RESULT_SUCCESS); -} - -void File::Close(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0808, 0, 0); - - // TODO(Subv): Only close the backend if this client is the only one left. - if (connected_sessions.size() > 1) - LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected", - connected_sessions.size()); - - backend->Close(); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(RESULT_SUCCESS); -} - -void File::Flush(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0809, 0, 0); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - - const FileSessionSlot* file = GetSessionData(ctx.Session()); - - // Subfiles can not be flushed. - if (file->subfile) { - rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); - return; - } - - backend->Flush(); - rb.Push(RESULT_SUCCESS); -} - -void File::SetPriority(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x080A, 1, 0); - - FileSessionSlot* file = GetSessionData(ctx.Session()); - file->priority = rp.Pop(); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(RESULT_SUCCESS); -} - -void File::GetPriority(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x080B, 0, 0); - const FileSessionSlot* file = GetSessionData(ctx.Session()); - - IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); - rb.Push(RESULT_SUCCESS); - rb.Push(file->priority); -} - -void File::OpenLinkFile(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_FS, "(STUBBED) File command OpenLinkFile {}", GetName()); - using Kernel::ClientSession; - using Kernel::ServerSession; - using Kernel::SharedPtr; - IPC::RequestParser rp(ctx, 0x080C, 0, 0); - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - auto sessions = ServerSession::CreateSessionPair(GetName()); - auto server = std::get>(sessions); - ClientConnected(server); - - FileSessionSlot* slot = GetSessionData(server); - const FileSessionSlot* original_file = GetSessionData(ctx.Session()); - - slot->priority = original_file->priority; - slot->offset = 0; - slot->size = backend->GetSize(); - slot->subfile = false; - - rb.Push(RESULT_SUCCESS); - rb.PushMoveObjects(std::get>(sessions)); -} - -void File::OpenSubFile(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0801, 4, 0); - s64 offset = rp.PopRaw(); - s64 size = rp.PopRaw(); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - - const FileSessionSlot* original_file = GetSessionData(ctx.Session()); - - if (original_file->subfile) { - // OpenSubFile can not be called on a file which is already as subfile - rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); - return; - } - - if (offset < 0 || size < 0) { - rb.Push(FileSys::ERR_WRITE_BEYOND_END); - return; - } - - std::size_t end = offset + size; - - // TODO(Subv): Check for overflow and return ERR_WRITE_BEYOND_END - - if (end > original_file->size) { - rb.Push(FileSys::ERR_WRITE_BEYOND_END); - return; - } - - using Kernel::ClientSession; - using Kernel::ServerSession; - using Kernel::SharedPtr; - auto sessions = ServerSession::CreateSessionPair(GetName()); - auto server = std::get>(sessions); - ClientConnected(server); - - FileSessionSlot* slot = GetSessionData(server); - slot->priority = original_file->priority; - slot->offset = offset; - slot->size = size; - slot->subfile = true; - - rb.Push(RESULT_SUCCESS); - rb.PushMoveObjects(std::get>(sessions)); -} - -Kernel::SharedPtr File::Connect() { - auto sessions = Kernel::ServerSession::CreateSessionPair(GetName()); - auto server = std::get>(sessions); - ClientConnected(server); - - FileSessionSlot* slot = GetSessionData(server); - slot->priority = 0; - slot->offset = 0; - slot->size = backend->GetSize(); - slot->subfile = false; - - return std::get>(sessions); -} - -std::size_t File::GetSessionFileOffset(Kernel::SharedPtr session) { - const FileSessionSlot* slot = GetSessionData(session); - ASSERT(slot); - return slot->offset; -} - -std::size_t File::GetSessionFileSize(Kernel::SharedPtr session) { - const FileSessionSlot* slot = GetSessionData(session); - ASSERT(slot); - return slot->size; -} - -Directory::Directory(std::unique_ptr&& backend, - const FileSys::Path& path) - : ServiceFramework("", 1), path(path), backend(std::move(backend)) { - static const FunctionInfo functions[] = { - // clang-format off - {0x08010042, &Directory::Read, "Read"}, - {0x08020000, &Directory::Close, "Close"}, - // clang-format on - }; - RegisterHandlers(functions); -} - -Directory::~Directory() {} - -void Directory::Read(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0801, 1, 2); - u32 count = rp.Pop(); - auto& buffer = rp.PopMappedBuffer(); - std::vector entries(count); - LOG_TRACE(Service_FS, "Read {}: count={}", GetName(), count); - // Number of entries actually read - u32 read = backend->Read(static_cast(entries.size()), entries.data()); - buffer.Write(entries.data(), 0, read * sizeof(FileSys::Entry)); - - IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - rb.Push(RESULT_SUCCESS); - rb.Push(read); - rb.PushMappedBuffer(buffer); -} - -void Directory::Close(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx, 0x0802, 0, 0); - LOG_TRACE(Service_FS, "Close {}", GetName()); - backend->Close(); - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(RESULT_SUCCESS); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// - using FileSys::ArchiveBackend; using FileSys::ArchiveFactory; diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index d6ceb9229..4c568097c 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h @@ -9,15 +9,9 @@ #include #include "common/common_types.h" #include "core/file_sys/archive_backend.h" -#include "core/hle/kernel/hle_ipc.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/result.h" -#include "core/hle/service/service.h" - -namespace FileSys { -class DirectoryBackend; -class FileBackend; -} // namespace FileSys +#include "core/hle/service/fs/directory.h" +#include "core/hle/service/fs/file.h" /// The unique system identifier hash, also known as ID0 static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"}; @@ -49,68 +43,6 @@ enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 }; typedef u64 ArchiveHandle; -struct FileSessionSlot : public Kernel::SessionRequestHandler::SessionDataBase { - u32 priority; ///< Priority of the file. TODO(Subv): Find out what this means - u64 offset; ///< Offset that this session will start reading from. - u64 size; ///< Max size of the file that this session is allowed to access - bool subfile; ///< Whether this file was opened via OpenSubFile or not. -}; - -// TODO: File is not a real service, but it can still utilize ServiceFramework::RegisterHandlers. -// Consider splitting ServiceFramework interface. -class File final : public ServiceFramework { -public: - File(std::unique_ptr&& backend, const FileSys::Path& path); - ~File() = default; - - std::string GetName() const { - return "Path: " + path.DebugStr(); - } - - FileSys::Path path; ///< Path of the file - std::unique_ptr backend; ///< File backend interface - - /// Creates a new session to this File and returns the ClientSession part of the connection. - Kernel::SharedPtr Connect(); - - // Returns the start offset of an open file represented by the input session, opened with - // OpenSubFile. - std::size_t GetSessionFileOffset(Kernel::SharedPtr session); - - // Returns the size of an open file represented by the input session, opened with - // OpenSubFile. - std::size_t GetSessionFileSize(Kernel::SharedPtr session); - -private: - void Read(Kernel::HLERequestContext& ctx); - void Write(Kernel::HLERequestContext& ctx); - void GetSize(Kernel::HLERequestContext& ctx); - void SetSize(Kernel::HLERequestContext& ctx); - void Close(Kernel::HLERequestContext& ctx); - void Flush(Kernel::HLERequestContext& ctx); - void SetPriority(Kernel::HLERequestContext& ctx); - void GetPriority(Kernel::HLERequestContext& ctx); - void OpenLinkFile(Kernel::HLERequestContext& ctx); - void OpenSubFile(Kernel::HLERequestContext& ctx); -}; - -class Directory final : public ServiceFramework { -public: - Directory(std::unique_ptr&& backend, const FileSys::Path& path); - ~Directory(); - - std::string GetName() const { - return "Directory: " + path.DebugStr(); - } - - FileSys::Path path; ///< Path of the directory - std::unique_ptr backend; ///< File backend interface - -protected: - void Read(Kernel::HLERequestContext& ctx); - void Close(Kernel::HLERequestContext& ctx); -}; - /** * Opens an archive * @param id_code IdCode of the archive to open diff --git a/src/core/hle/service/fs/directory.cpp b/src/core/hle/service/fs/directory.cpp new file mode 100644 index 000000000..a72a9307a --- /dev/null +++ b/src/core/hle/service/fs/directory.cpp @@ -0,0 +1,51 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/file_sys/directory_backend.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/fs/directory.h" + +namespace Service::FS { + +Directory::Directory(std::unique_ptr&& backend, + const FileSys::Path& path) + : ServiceFramework("", 1), path(path), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + // clang-format off + {0x08010042, &Directory::Read, "Read"}, + {0x08020000, &Directory::Close, "Close"}, + // clang-format on + }; + RegisterHandlers(functions); +} + +Directory::~Directory() {} + +void Directory::Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0801, 1, 2); + u32 count = rp.Pop(); + auto& buffer = rp.PopMappedBuffer(); + std::vector entries(count); + LOG_TRACE(Service_FS, "Read {}: count={}", GetName(), count); + // Number of entries actually read + u32 read = backend->Read(static_cast(entries.size()), entries.data()); + buffer.Write(entries.data(), 0, read * sizeof(FileSys::Entry)); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + rb.Push(RESULT_SUCCESS); + rb.Push(read); + rb.PushMappedBuffer(buffer); +} + +void Directory::Close(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0802, 0, 0); + LOG_TRACE(Service_FS, "Close {}", GetName()); + backend->Close(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +} // namespace Service::FS diff --git a/src/core/hle/service/fs/directory.h b/src/core/hle/service/fs/directory.h new file mode 100644 index 000000000..2f4529ca0 --- /dev/null +++ b/src/core/hle/service/fs/directory.h @@ -0,0 +1,30 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/archive_backend.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/service.h" + +namespace Service::FS { + +class Directory final : public ServiceFramework { +public: + Directory(std::unique_ptr&& backend, const FileSys::Path& path); + ~Directory(); + + std::string GetName() const { + return "Directory: " + path.DebugStr(); + } + + FileSys::Path path; ///< Path of the directory + std::unique_ptr backend; ///< File backend interface + +protected: + void Read(Kernel::HLERequestContext& ctx); + void Close(Kernel::HLERequestContext& ctx); +}; + +} // namespace Service::FS diff --git a/src/core/hle/service/fs/file.cpp b/src/core/hle/service/fs/file.cpp new file mode 100644 index 000000000..d2f1f2a85 --- /dev/null +++ b/src/core/hle/service/fs/file.cpp @@ -0,0 +1,286 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/file_sys/errors.h" +#include "core/file_sys/file_backend.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/client_session.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/server_session.h" +#include "core/hle/service/fs/file.h" + +namespace Service::FS { + +File::File(std::unique_ptr&& backend, const FileSys::Path& path) + : ServiceFramework("", 1), path(path), backend(std::move(backend)) { + static const FunctionInfo functions[] = { + {0x08010100, &File::OpenSubFile, "OpenSubFile"}, + {0x080200C2, &File::Read, "Read"}, + {0x08030102, &File::Write, "Write"}, + {0x08040000, &File::GetSize, "GetSize"}, + {0x08050080, &File::SetSize, "SetSize"}, + {0x08080000, &File::Close, "Close"}, + {0x08090000, &File::Flush, "Flush"}, + {0x080A0040, &File::SetPriority, "SetPriority"}, + {0x080B0000, &File::GetPriority, "GetPriority"}, + {0x080C0000, &File::OpenLinkFile, "OpenLinkFile"}, + }; + RegisterHandlers(functions); +} + +void File::Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0802, 3, 2); + u64 offset = rp.Pop(); + u32 length = rp.Pop(); + auto& buffer = rp.PopMappedBuffer(); + LOG_TRACE(Service_FS, "Read {}: offset=0x{:x} length=0x{:08X}", GetName(), offset, length); + + const FileSessionSlot* file = GetSessionData(ctx.Session()); + + if (file->subfile && length > file->size) { + LOG_WARNING(Service_FS, "Trying to read beyond the subfile size, truncating"); + length = static_cast(file->size); + } + + // This file session might have a specific offset from where to start reading, apply it. + offset += file->offset; + + if (offset + length > backend->GetSize()) { + LOG_ERROR(Service_FS, + "Reading from out of bounds offset=0x{:x} length=0x{:08X} file_size=0x{:x}", + offset, length, backend->GetSize()); + } + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + + std::vector data(length); + ResultVal read = backend->Read(offset, data.size(), data.data()); + if (read.Failed()) { + rb.Push(read.Code()); + rb.Push(0); + } else { + buffer.Write(data.data(), 0, *read); + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast(*read)); + } + rb.PushMappedBuffer(buffer); + + std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)}; + ctx.SleepClientThread(Kernel::GetCurrentThread(), "file::read", read_timeout_ns, + [](Kernel::SharedPtr thread, + Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) { + // Nothing to do here + }); +} + +void File::Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0803, 4, 2); + u64 offset = rp.Pop(); + u32 length = rp.Pop(); + u32 flush = rp.Pop(); + auto& buffer = rp.PopMappedBuffer(); + LOG_TRACE(Service_FS, "Write {}: offset=0x{:x} length={}, flush=0x{:x}", GetName(), offset, + length, flush); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); + + const FileSessionSlot* file = GetSessionData(ctx.Session()); + + // Subfiles can not be written to + if (file->subfile) { + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + rb.Push(0); + rb.PushMappedBuffer(buffer); + return; + } + + std::vector data(length); + buffer.Read(data.data(), 0, data.size()); + ResultVal written = backend->Write(offset, data.size(), flush != 0, data.data()); + if (written.Failed()) { + rb.Push(written.Code()); + rb.Push(0); + } else { + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast(*written)); + } + rb.PushMappedBuffer(buffer); +} + +void File::GetSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0804, 0, 0); + + const FileSessionSlot* file = GetSessionData(ctx.Session()); + + IPC::RequestBuilder rb = rp.MakeBuilder(3, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(file->size); +} + +void File::SetSize(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0805, 2, 0); + u64 size = rp.Pop(); + + FileSessionSlot* file = GetSessionData(ctx.Session()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + // SetSize can not be called on subfiles. + if (file->subfile) { + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + return; + } + + file->size = size; + backend->SetSize(size); + rb.Push(RESULT_SUCCESS); +} + +void File::Close(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0808, 0, 0); + + // TODO(Subv): Only close the backend if this client is the only one left. + if (connected_sessions.size() > 1) + LOG_WARNING(Service_FS, "Closing File backend but {} clients still connected", + connected_sessions.size()); + + backend->Close(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void File::Flush(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0809, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + + const FileSessionSlot* file = GetSessionData(ctx.Session()); + + // Subfiles can not be flushed. + if (file->subfile) { + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + return; + } + + backend->Flush(); + rb.Push(RESULT_SUCCESS); +} + +void File::SetPriority(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x080A, 1, 0); + + FileSessionSlot* file = GetSessionData(ctx.Session()); + file->priority = rp.Pop(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); +} + +void File::GetPriority(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x080B, 0, 0); + const FileSessionSlot* file = GetSessionData(ctx.Session()); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(file->priority); +} + +void File::OpenLinkFile(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) File command OpenLinkFile {}", GetName()); + using Kernel::ClientSession; + using Kernel::ServerSession; + using Kernel::SharedPtr; + IPC::RequestParser rp(ctx, 0x080C, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + auto sessions = ServerSession::CreateSessionPair(GetName()); + auto server = std::get>(sessions); + ClientConnected(server); + + FileSessionSlot* slot = GetSessionData(server); + const FileSessionSlot* original_file = GetSessionData(ctx.Session()); + + slot->priority = original_file->priority; + slot->offset = 0; + slot->size = backend->GetSize(); + slot->subfile = false; + + rb.Push(RESULT_SUCCESS); + rb.PushMoveObjects(std::get>(sessions)); +} + +void File::OpenSubFile(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx, 0x0801, 4, 0); + s64 offset = rp.PopRaw(); + s64 size = rp.PopRaw(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + + const FileSessionSlot* original_file = GetSessionData(ctx.Session()); + + if (original_file->subfile) { + // OpenSubFile can not be called on a file which is already as subfile + rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS); + return; + } + + if (offset < 0 || size < 0) { + rb.Push(FileSys::ERR_WRITE_BEYOND_END); + return; + } + + std::size_t end = offset + size; + + // TODO(Subv): Check for overflow and return ERR_WRITE_BEYOND_END + + if (end > original_file->size) { + rb.Push(FileSys::ERR_WRITE_BEYOND_END); + return; + } + + using Kernel::ClientSession; + using Kernel::ServerSession; + using Kernel::SharedPtr; + auto sessions = ServerSession::CreateSessionPair(GetName()); + auto server = std::get>(sessions); + ClientConnected(server); + + FileSessionSlot* slot = GetSessionData(server); + slot->priority = original_file->priority; + slot->offset = offset; + slot->size = size; + slot->subfile = true; + + rb.Push(RESULT_SUCCESS); + rb.PushMoveObjects(std::get>(sessions)); +} + +Kernel::SharedPtr File::Connect() { + auto sessions = Kernel::ServerSession::CreateSessionPair(GetName()); + auto server = std::get>(sessions); + ClientConnected(server); + + FileSessionSlot* slot = GetSessionData(server); + slot->priority = 0; + slot->offset = 0; + slot->size = backend->GetSize(); + slot->subfile = false; + + return std::get>(sessions); +} + +std::size_t File::GetSessionFileOffset(Kernel::SharedPtr session) { + const FileSessionSlot* slot = GetSessionData(session); + ASSERT(slot); + return slot->offset; +} + +std::size_t File::GetSessionFileSize(Kernel::SharedPtr session) { + const FileSessionSlot* slot = GetSessionData(session); + ASSERT(slot); + return slot->size; +} + +} // namespace Service::FS diff --git a/src/core/hle/service/fs/file.h b/src/core/hle/service/fs/file.h new file mode 100644 index 000000000..2de9add98 --- /dev/null +++ b/src/core/hle/service/fs/file.h @@ -0,0 +1,58 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/file_sys/archive_backend.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/service/service.h" + +namespace Service::FS { + +struct FileSessionSlot : public Kernel::SessionRequestHandler::SessionDataBase { + u32 priority; ///< Priority of the file. TODO(Subv): Find out what this means + u64 offset; ///< Offset that this session will start reading from. + u64 size; ///< Max size of the file that this session is allowed to access + bool subfile; ///< Whether this file was opened via OpenSubFile or not. +}; + +// TODO: File is not a real service, but it can still utilize ServiceFramework::RegisterHandlers. +// Consider splitting ServiceFramework interface. +class File final : public ServiceFramework { +public: + File(std::unique_ptr&& backend, const FileSys::Path& path); + ~File() = default; + + std::string GetName() const { + return "Path: " + path.DebugStr(); + } + + FileSys::Path path; ///< Path of the file + std::unique_ptr backend; ///< File backend interface + + /// Creates a new session to this File and returns the ClientSession part of the connection. + Kernel::SharedPtr Connect(); + + // Returns the start offset of an open file represented by the input session, opened with + // OpenSubFile. + std::size_t GetSessionFileOffset(Kernel::SharedPtr session); + + // Returns the size of an open file represented by the input session, opened with + // OpenSubFile. + std::size_t GetSessionFileSize(Kernel::SharedPtr session); + +private: + void Read(Kernel::HLERequestContext& ctx); + void Write(Kernel::HLERequestContext& ctx); + void GetSize(Kernel::HLERequestContext& ctx); + void SetSize(Kernel::HLERequestContext& ctx); + void Close(Kernel::HLERequestContext& ctx); + void Flush(Kernel::HLERequestContext& ctx); + void SetPriority(Kernel::HLERequestContext& ctx); + void GetPriority(Kernel::HLERequestContext& ctx); + void OpenLinkFile(Kernel::HLERequestContext& ctx); + void OpenSubFile(Kernel::HLERequestContext& ctx); +}; + +} // namespace Service::FS