aboutsummaryrefslogtreecommitdiff
path: root/src/core/file_sys/fssystem/fssystem_indirect_storage.cpp
blob: e66ce49d9ac845ddc24aacc9726b0d0be56f9ddf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/logging.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/fssystem/fssystem_indirect_storage.h"

namespace FileSys {

Result IndirectStorage::Initialize(VirtualFile table_storage) {
    // Read and verify the bucket tree header.
    BucketTree::Header header;
    table_storage->ReadObject(std::addressof(header));
    R_TRY(header.Verify());

    // Determine extents.
    const auto node_storage_size = QueryNodeStorageSize(header.entry_count);
    const auto entry_storage_size = QueryEntryStorageSize(header.entry_count);
    const auto node_storage_offset = QueryHeaderStorageSize();
    const auto entry_storage_offset = node_storage_offset + node_storage_size;

    // Initialize.
    R_RETURN(this->Initialize(
        std::make_shared<OffsetVfsFile>(table_storage, node_storage_size, node_storage_offset),
        std::make_shared<OffsetVfsFile>(table_storage, entry_storage_size, entry_storage_offset),
        header.entry_count));
}

void IndirectStorage::Finalize() {
    if (this->IsInitialized()) {
        m_table.Finalize();
        for (auto i = 0; i < StorageCount; i++) {
            m_data_storage[i] = VirtualFile();
        }
    }
}

Result IndirectStorage::GetEntryList(Entry* out_entries, s32* out_entry_count, s32 entry_count,
                                     s64 offset, s64 size) {
    // Validate pre-conditions.
    ASSERT(offset >= 0);
    ASSERT(size >= 0);
    ASSERT(this->IsInitialized());

    // Clear the out count.
    R_UNLESS(out_entry_count != nullptr, ResultNullptrArgument);
    *out_entry_count = 0;

    // Succeed if there's no range.
    R_SUCCEED_IF(size == 0);

    // If we have an output array, we need it to be non-null.
    R_UNLESS(out_entries != nullptr || entry_count == 0, ResultNullptrArgument);

    // Check that our range is valid.
    BucketTree::Offsets table_offsets{};
    R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));

    R_UNLESS(table_offsets.IsInclude(offset, size), ResultOutOfRange);

    // Find the offset in our tree.
    BucketTree::Visitor visitor;
    R_TRY(m_table.Find(std::addressof(visitor), offset));
    {
        const auto entry_offset = visitor.Get<Entry>()->GetVirtualOffset();
        R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset),
                 ResultInvalidIndirectEntryOffset);
    }

    // Prepare to loop over entries.
    const auto end_offset = offset + static_cast<s64>(size);
    s32 count = 0;

    auto cur_entry = *visitor.Get<Entry>();
    while (cur_entry.GetVirtualOffset() < end_offset) {
        // Try to write the entry to the out list.
        if (entry_count != 0) {
            if (count >= entry_count) {
                break;
            }
            std::memcpy(out_entries + count, std::addressof(cur_entry), sizeof(Entry));
        }

        count++;

        // Advance.
        if (visitor.CanMoveNext()) {
            R_TRY(visitor.MoveNext());
            cur_entry = *visitor.Get<Entry>();
        } else {
            break;
        }
    }

    // Write the output count.
    *out_entry_count = count;
    R_SUCCEED();
}

void IndirectStorage::ReportMissingOriginal(s64 offset, s64 size) {
    if (m_reported_missing_original) {
        return;
    }
    m_reported_missing_original = true;

    LOG_ERROR(Common_Filesystem,
              "Patch storage requests {:#x} bytes at {:#x} from a base storage that is not "
              "present; this data will read as garbage",
              size, offset);
}

size_t IndirectStorage::Read(u8* buffer, size_t size, size_t offset) const {
    // Validate pre-conditions.
    ASSERT(this->IsInitialized());
    ASSERT(buffer != nullptr);

    // Succeed if there's nothing to read.
    if (size == 0) {
        return 0;
    }

    const_cast<IndirectStorage*>(this)->OperatePerEntry<true, true>(
        offset, size,
        [=](VirtualFile storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
            storage->Read(reinterpret_cast<u8*>(buffer) + (cur_offset - offset),
                          static_cast<size_t>(cur_size), data_offset);
            R_SUCCEED();
        });

    return size;
}
} // namespace FileSys