From c9ddd5b0aed813a495cc13882b72da366f881845 Mon Sep 17 00:00:00 2001
From: Subv <subv2112@gmail.com>
Date: Thu, 7 Dec 2017 14:38:19 -0500
Subject: [PATCH] HLE/FS: Implemented FSFile::OpenSubFile.

The File class now holds a list of connected sessions along with data unique to each session.

A subfile is a window into an existing file. They have a few limitations compared to normal files:

* They can't be written to.
* They can't be flushed.
* Their size can not be changed.
* New subfiles can't be created from another subfile.
---
 src/core/hle/kernel/hle_ipc.h       |   2 +-
 src/core/hle/service/am/am.cpp      |   6 +-
 src/core/hle/service/fs/archive.cpp | 165 ++++++++++++++++++++++++++--
 src/core/hle/service/fs/archive.h   |  27 ++++-
 src/core/hle/service/fs/fs_user.cpp |  12 +-
 5 files changed, 184 insertions(+), 28 deletions(-)

diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 2a246fa27..338f3c7d1 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -54,7 +54,7 @@ public:
      * associated ServerSession.
      * @param server_session ServerSession associated with the connection.
      */
-    void ClientDisconnected(SharedPtr<ServerSession> server_session);
+    virtual void ClientDisconnected(SharedPtr<ServerSession> server_session);
 
 protected:
     /// List of sessions that are connected to this handler.
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 9b87bb6da..ebaa2fc6e 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -909,16 +909,12 @@ void BeginImportProgram(Service::Interface* self) {
     const FileSys::Path cia_path = {};
     auto file =
         std::make_shared<Service::FS::File>(std::make_unique<CIAFile>(media_type), cia_path);
-    auto sessions = Kernel::ServerSession::CreateSessionPair(file->GetName());
-    file->ClientConnected(std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions));
 
     cia_installing = true;
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     rb.Push(RESULT_SUCCESS); // No error
-    rb.PushCopyHandles(
-        Kernel::g_handle_table.Create(std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions))
-            .Unwrap());
+    rb.PushCopyHandles(Kernel::g_handle_table.Create(file->Connect()).Unwrap());
 
     LOG_WARNING(Service_AM, "(STUBBED) media_type=%u", static_cast<u32>(media_type));
 }
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index be6086c68..92eb4ef04 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <algorithm>
 #include <cinttypes>
 #include <cstddef>
 #include <memory>
@@ -67,8 +68,9 @@ enum class DirectoryCommand : u32 {
 };
 
 File::File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path)
-    : ServiceFramework("", 1), path(path), priority(0), backend(std::move(backend)) {
+    : 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"},
@@ -90,6 +92,16 @@ void File::Read(Kernel::HLERequestContext& ctx) {
     LOG_TRACE(Service_FS, "Read %s: offset=0x%" PRIx64 " length=0x%08X", GetName().c_str(), offset,
               length);
 
+    const SessionSlot& file = GetSessionSlot(ctx.Session());
+
+    if (file.subfile && length > file.size) {
+        LOG_WARNING(Service_FS, "Trying to read beyond the subfile size, truncating");
+        length = 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%" PRIx64
                               " length=0x%08X file_size=0x%" PRIx64,
@@ -122,6 +134,16 @@ void File::Write(Kernel::HLERequestContext& ctx) {
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
 
+    const SessionSlot& file = GetSessionSlot(ctx.Session());
+
+    // Subfiles can not be written to
+    if (file.subfile) {
+        rb.Push(FileSys::ERROR_UNSUPPORTED_OPEN_FLAGS);
+        rb.Push<u32>(0);
+        rb.PushMappedBuffer(buffer);
+        return;
+    }
+
     std::vector<u8> data(length);
     buffer.Read(data.data(), 0, data.size());
     ResultVal<size_t> written = backend->Write(offset, data.size(), flush != 0, data.data());
@@ -137,20 +159,41 @@ void File::Write(Kernel::HLERequestContext& ctx) {
 
 void File::GetSize(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x0804, 0, 0);
+
+    const SessionSlot& file = GetSessionSlot(ctx.Session());
+
     IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
     rb.Push(RESULT_SUCCESS);
-    rb.Push(backend->GetSize());
+    rb.Push<u64>(file.size);
 }
 
 void File::SetSize(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x0805, 2, 0);
-    backend->SetSize(rp.Pop<u64>());
+    u64 size = rp.Pop<u64>();
+
+    SessionSlot& file = GetSessionSlot(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 (session_slots.size() > 1)
+        LOG_WARNING(Service_FS, "Closing File backend but %zu clients still connected",
+                    session_slots.size());
+
     backend->Close();
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     rb.Push(RESULT_SUCCESS);
@@ -158,23 +201,38 @@ void File::Close(Kernel::HLERequestContext& ctx) {
 
 void File::Flush(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x0809, 0, 0);
-    backend->Flush();
+
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+    const SessionSlot& file = GetSessionSlot(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);
-    priority = rp.Pop<u32>();
+
+    SessionSlot& file = GetSessionSlot(ctx.Session());
+    file.priority = rp.Pop<u32>();
+
     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 SessionSlot& file = GetSessionSlot(ctx.Session());
+
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
-    rb.Push(priority);
+    rb.Push(file.priority);
 }
 
 void File::OpenLinkFile(Kernel::HLERequestContext& ctx) {
@@ -185,13 +243,104 @@ void File::OpenLinkFile(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x080C, 0, 0);
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     auto sessions = ServerSession::CreateSessionPair(GetName());
-    ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
+    auto server = std::get<SharedPtr<ServerSession>>(sessions);
+    ClientConnected(server);
+
+    const SessionSlot& original_file = GetSessionSlot(ctx.Session());
+
+    SessionSlot slot{};
+    slot.priority = original_file.priority;
+    slot.offset = 0;
+    slot.size = backend->GetSize();
+    slot.session = server;
+    slot.subfile = false;
+
+    session_slots.emplace_back(std::move(slot));
 
     rb.Push(RESULT_SUCCESS);
     rb.PushMoveObjects(std::get<SharedPtr<ClientSession>>(sessions));
 }
 
-File::~File() {}
+void File::OpenSubFile(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0801, 4, 0);
+    s64 offset = rp.PopRaw<s64>();
+    s64 size = rp.PopRaw<s64>();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+
+    const SessionSlot& original_file = GetSessionSlot(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;
+    }
+
+    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<SharedPtr<ServerSession>>(sessions);
+    ClientConnected(server);
+
+    SessionSlot slot{};
+    slot.priority = original_file.priority;
+    slot.offset = offset;
+    slot.size = size;
+    slot.session = server;
+    slot.subfile = true;
+
+    session_slots.emplace_back(std::move(slot));
+
+    rb.Push(RESULT_SUCCESS);
+    rb.PushMoveObjects(std::get<SharedPtr<ClientSession>>(sessions));
+}
+
+File::SessionSlot& File::GetSessionSlot(Kernel::SharedPtr<Kernel::ServerSession> session) {
+    auto itr = std::find_if(session_slots.begin(), session_slots.end(),
+                            [&](const SessionSlot& slot) { return slot.session == session; });
+    ASSERT(itr != session_slots.end());
+    return *itr;
+}
+
+void File::ClientDisconnected(Kernel::SharedPtr<Kernel::ServerSession> server_session) {
+    session_slots.erase(
+        std::remove_if(session_slots.begin(), session_slots.end(),
+                       [&](const SessionSlot& slot) { return slot.session == server_session; }),
+        session_slots.end());
+    SessionRequestHandler::ClientDisconnected(server_session);
+}
+
+Kernel::SharedPtr<Kernel::ClientSession> File::Connect() {
+    auto sessions = Kernel::ServerSession::CreateSessionPair(GetName());
+    auto server = std::get<Kernel::SharedPtr<Kernel::ServerSession>>(sessions);
+    ClientConnected(server);
+
+    SessionSlot slot{};
+    slot.priority = 0;
+    slot.offset = 0;
+    slot.size = backend->GetSize();
+    slot.session = server;
+    slot.subfile = false;
+
+    session_slots.emplace_back(std::move(slot));
+
+    return std::get<Kernel::SharedPtr<Kernel::ClientSession>>(sessions);
+}
 
 Directory::Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend,
                      const FileSys::Path& path)
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index b9a9637e1..01d67c3d0 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -6,16 +6,18 @@
 
 #include <memory>
 #include <string>
+#include <vector>
 #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
 
 /// The unique system identifier hash, also known as ID0
 static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"};
@@ -53,16 +55,20 @@ typedef u64 ArchiveHandle;
 class File final : public ServiceFramework<File> {
 public:
     File(std::unique_ptr<FileSys::FileBackend>&& backend, const FileSys::Path& path);
-    ~File();
+    ~File() = default;
 
     std::string GetName() const {
         return "Path: " + path.DebugStr();
     }
 
-    FileSys::Path path; ///< Path of the file
-    u32 priority;       ///< Priority of the file. TODO(Subv): Find out what this means
+    FileSys::Path path;                            ///< Path of the file
     std::unique_ptr<FileSys::FileBackend> backend; ///< File backend interface
 
+    /// Creates a new session to this File and returns the ClientSession part of the connection.
+    Kernel::SharedPtr<Kernel::ClientSession> Connect();
+
+    void ClientDisconnected(Kernel::SharedPtr<Kernel::ServerSession> server_session) override;
+
 private:
     void Read(Kernel::HLERequestContext& ctx);
     void Write(Kernel::HLERequestContext& ctx);
@@ -73,6 +79,19 @@ private:
     void SetPriority(Kernel::HLERequestContext& ctx);
     void GetPriority(Kernel::HLERequestContext& ctx);
     void OpenLinkFile(Kernel::HLERequestContext& ctx);
+    void OpenSubFile(Kernel::HLERequestContext& ctx);
+
+    struct SessionSlot {
+        Kernel::SharedPtr<Kernel::ServerSession> session; ///< The session that this slot refers to.
+        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.
+    };
+
+    std::vector<SessionSlot> session_slots;
+
+    SessionSlot& GetSessionSlot(Kernel::SharedPtr<Kernel::ServerSession> session);
 };
 
 class Directory final : public Kernel::SessionRequestHandler {
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index bff4c14f1..4e774394b 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -86,11 +86,7 @@ static void OpenFile(Service::Interface* self) {
     rb.Push(file_res.Code());
     if (file_res.Succeeded()) {
         std::shared_ptr<File> file = *file_res;
-        auto sessions = ServerSession::CreateSessionPair(file->GetName());
-        file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
-
-        rb.PushMoveHandles(
-            Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap());
+        rb.PushMoveHandles(Kernel::g_handle_table.Create(file->Connect()).Unwrap());
     } else {
         rb.PushMoveHandles(0);
         LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str());
@@ -152,11 +148,7 @@ static void OpenFileDirectly(Service::Interface* self) {
     cmd_buff[1] = file_res.Code().raw;
     if (file_res.Succeeded()) {
         std::shared_ptr<File> file = *file_res;
-        auto sessions = ServerSession::CreateSessionPair(file->GetName());
-        file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
-
-        cmd_buff[3] =
-            Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap();
+        cmd_buff[3] = Kernel::g_handle_table.Create(file->Connect()).Unwrap();
     } else {
         cmd_buff[3] = 0;
         LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u",