Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"

This reverts commit 15b7df8ef1.
This commit is contained in:
souldbminersmwc
2025-11-09 16:14:52 -05:00
parent 22ec140738
commit 21a3f953d7
3804 changed files with 435 additions and 570162 deletions

View File

@@ -1,52 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem::buffers {
namespace {
/* TODO: os::SdkThreadLocalStorage g_buffer_manager_context_tls_slot; */
class ThreadLocalStorageWrapper {
private:
os::TlsSlot m_tls_slot;
public:
ThreadLocalStorageWrapper() { R_ABORT_UNLESS(os::AllocateTlsSlot(std::addressof(m_tls_slot), nullptr)); }
~ThreadLocalStorageWrapper() { os::FreeTlsSlot(m_tls_slot); }
void SetValue(uintptr_t value) { os::SetTlsValue(m_tls_slot, value); }
uintptr_t GetValue() const { return os::GetTlsValue(m_tls_slot); }
os::TlsSlot GetTlsSlot() const { return m_tls_slot; }
} g_buffer_manager_context_tls_slot;
}
void RegisterBufferManagerContext(const BufferManagerContext *context) {
g_buffer_manager_context_tls_slot.SetValue(reinterpret_cast<uintptr_t>(context));
}
BufferManagerContext *GetBufferManagerContext() {
return reinterpret_cast<BufferManagerContext *>(g_buffer_manager_context_tls_slot.GetValue());
}
void EnableBlockingBufferManagerAllocation() {
if (auto context = GetBufferManagerContext(); context != nullptr) {
context->SetNeedBlocking(true);
}
}
}

View File

@@ -1,360 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
FileSystemBuddyHeap::PageEntry *FileSystemBuddyHeap::PageList::PopFront() {
AMS_ASSERT(m_entry_count > 0);
/* Get the first entry. */
auto page_entry = m_first_page_entry;
/* Advance our list. */
m_first_page_entry = page_entry->next;
page_entry->next = nullptr;
/* Decrement our count. */
--m_entry_count;
AMS_ASSERT(m_entry_count >= 0);
/* If this was our last page, clear our last entry. */
if (m_entry_count == 0) {
m_last_page_entry = nullptr;
}
return page_entry;
}
void FileSystemBuddyHeap::PageList::PushBack(PageEntry *page_entry) {
AMS_ASSERT(page_entry != nullptr);
/* If we're empty, we want to set the first page entry. */
if (this->IsEmpty()) {
m_first_page_entry = page_entry;
} else {
/* We're not empty, so push the page to the back. */
AMS_ASSERT(m_last_page_entry != page_entry);
m_last_page_entry->next = page_entry;
}
/* Set our last page entry to be this one, and link it to the list. */
m_last_page_entry = page_entry;
m_last_page_entry->next = nullptr;
/* Increment our entry count. */
++m_entry_count;
AMS_ASSERT(m_entry_count > 0);
}
bool FileSystemBuddyHeap::PageList::Remove(PageEntry *page_entry) {
AMS_ASSERT(page_entry != nullptr);
/* If we're empty, we can't remove the page list. */
if (this->IsEmpty()) {
return false;
}
/* We're going to loop over all pages to find this one, then unlink it. */
PageEntry *prev_entry = nullptr;
PageEntry *cur_entry = m_first_page_entry;
while (true) {
/* Check if we found the page. */
if (cur_entry == page_entry) {
if (cur_entry == m_first_page_entry) {
/* If it's the first page, we just set our first. */
m_first_page_entry = cur_entry->next;
} else if (cur_entry == m_last_page_entry) {
/* If it's the last page, we set our last. */
m_last_page_entry = prev_entry;
m_last_page_entry->next = nullptr;
} else {
/* If it's in the middle, we just unlink. */
prev_entry->next = cur_entry->next;
}
/* Unlink this entry's next. */
cur_entry->next = nullptr;
/* Update our entry count. */
--m_entry_count;
AMS_ASSERT(m_entry_count >= 0);
return true;
}
/* If we have no next page, we can't remove. */
if (cur_entry->next == nullptr) {
return false;
}
/* Advance to the next item in the list. */
prev_entry = cur_entry;
cur_entry = cur_entry->next;
}
}
Result FileSystemBuddyHeap::Initialize(uintptr_t address, size_t size, size_t block_size, s32 order_max) {
/* Ensure our preconditions. */
AMS_ASSERT(m_free_lists == nullptr);
AMS_ASSERT(address != 0);
AMS_ASSERT(util::IsAligned(address, BufferAlignment));
AMS_ASSERT(block_size >= BlockSizeMin);
AMS_ASSERT(util::IsPowerOfTwo(block_size));
AMS_ASSERT(size >= block_size);
AMS_ASSERT(order_max > 0);
AMS_ASSERT(order_max < OrderUpperLimit);
/* Set up our basic member variables */
m_block_size = block_size;
m_order_max = order_max;
m_heap_start = address;
m_heap_size = (size / m_block_size) * m_block_size;
m_total_free_size = 0;
/* Determine page sizes. */
const auto max_page_size = m_block_size << m_order_max;
const auto max_page_count = util::AlignUp(m_heap_size, max_page_size) / max_page_size;
AMS_ASSERT(max_page_count > 0);
/* Setup the free lists. */
if (m_external_free_lists != nullptr) {
AMS_ASSERT(m_internal_free_lists == nullptr);
m_free_lists = m_external_free_lists;
} else {
m_internal_free_lists.reset(new PageList[m_order_max + 1]);
m_free_lists = m_internal_free_lists.get();
R_UNLESS(m_free_lists != nullptr, fs::ResultAllocationMemoryFailedInFileSystemBuddyHeapA());
}
/* All but the last page region should go to the max order. */
for (size_t i = 0; i < max_page_count - 1; i++) {
auto page_entry = this->GetPageEntryFromAddress(m_heap_start + i * max_page_size);
m_free_lists[m_order_max].PushBack(page_entry);
}
m_total_free_size += m_free_lists[m_order_max].GetSize() * this->GetBytesFromOrder(m_order_max);
/* Allocate remaining space to smaller orders as possible. */
{
auto remaining = m_heap_size - (max_page_count - 1) * max_page_size;
auto cur_address = m_heap_start + (max_page_count - 1) * max_page_size;
AMS_ASSERT(util::IsAligned(remaining, m_block_size));
do {
/* Determine what order we can use. */
auto order = GetOrderFromBytes(remaining + 1);
if (order < 0) {
AMS_ASSERT(GetOrderFromBytes(remaining) == m_order_max);
order = m_order_max + 1;
}
AMS_ASSERT(0 < order);
AMS_ASSERT(order <= m_order_max + 1);
/* Add to the correct free list. */
m_free_lists[order - 1].PushBack(GetPageEntryFromAddress(cur_address));
m_total_free_size += GetBytesFromOrder(order - 1);
/* Move on to the next order. */
const auto page_size = GetBytesFromOrder(order - 1);
cur_address += page_size;
remaining -= page_size;
} while (m_block_size <= remaining);
}
R_SUCCEED();
}
void FileSystemBuddyHeap::Finalize() {
AMS_ASSERT(m_free_lists != nullptr);
m_free_lists = nullptr;
m_external_free_lists = nullptr;
m_internal_free_lists.reset();
}
void *FileSystemBuddyHeap::AllocateByOrder(s32 order) {
AMS_ASSERT(m_free_lists != nullptr);
AMS_ASSERT(order >= 0);
AMS_ASSERT(order <= this->GetOrderMax());
/* Get the page entry. */
if (const auto page_entry = this->GetFreePageEntry(order); page_entry != nullptr) {
/* Ensure we're allocating an unlinked page. */
AMS_ASSERT(page_entry->next == nullptr);
/* Return the address for this entry. */
return reinterpret_cast<void *>(this->GetAddressFromPageEntry(*page_entry));
} else {
return nullptr;
}
}
void FileSystemBuddyHeap::Free(void *ptr, s32 order) {
AMS_ASSERT(m_free_lists != nullptr);
AMS_ASSERT(order >= 0);
AMS_ASSERT(order <= this->GetOrderMax());
/* Allow free(nullptr) */
if (ptr == nullptr) {
return;
}
/* Ensure the pointer is block aligned. */
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(ptr) - m_heap_start, this->GetBlockSize()));
/* Get the page entry. */
auto page_entry = this->GetPageEntryFromAddress(reinterpret_cast<uintptr_t>(ptr));
AMS_ASSERT(this->IsAlignedToOrder(page_entry, order));
/* Reinsert into the free lists. */
this->JoinBuddies(page_entry, order);
}
size_t FileSystemBuddyHeap::GetTotalFreeSize() const {
AMS_ASSERT(m_free_lists != nullptr);
return m_total_free_size;
}
size_t FileSystemBuddyHeap::GetAllocatableSizeMax() const {
AMS_ASSERT(m_free_lists != nullptr);
/* The maximum allocatable size is a chunk from the biggest non-empty order. */
for (s32 order = this->GetOrderMax(); order >= 0; --order) {
if (!m_free_lists[order].IsEmpty()) {
return this->GetBytesFromOrder(order);
}
}
/* If all orders are empty, then we can't allocate anything. */
return 0;
}
void FileSystemBuddyHeap::Dump() const {
AMS_ASSERT(m_free_lists != nullptr);
/* TODO: Support logging metrics. */
}
void FileSystemBuddyHeap::DivideBuddies(PageEntry *page_entry, s32 required_order, s32 chosen_order) {
AMS_ASSERT(page_entry != nullptr);
AMS_ASSERT(required_order >= 0);
AMS_ASSERT(chosen_order >= required_order);
AMS_ASSERT(chosen_order <= this->GetOrderMax());
/* Start at the end of the entry. */
auto address = this->GetAddressFromPageEntry(*page_entry) + this->GetBytesFromOrder(chosen_order);
for (auto order = chosen_order; order > required_order; --order) {
/* For each order, subtract that order's size from the address to get the start of a new block. */
address -= this->GetBytesFromOrder(order - 1);
auto divided_entry = this->GetPageEntryFromAddress(address);
/* Push back to the list. */
m_free_lists[order - 1].PushBack(divided_entry);
m_total_free_size += this->GetBytesFromOrder(order - 1);
}
}
void FileSystemBuddyHeap::JoinBuddies(PageEntry *page_entry, s32 order) {
AMS_ASSERT(page_entry != nullptr);
AMS_ASSERT(order >= 0);
AMS_ASSERT(order <= this->GetOrderMax());
auto cur_entry = page_entry;
auto cur_order = order;
while (cur_order < this->GetOrderMax()) {
/* Get the buddy page. */
const auto buddy_entry = this->GetBuddy(cur_entry, cur_order);
/* Check whether the buddy is in the relevant free list. */
if (buddy_entry != nullptr && m_free_lists[cur_order].Remove(buddy_entry)) {
m_total_free_size -= GetBytesFromOrder(cur_order);
/* Ensure we coalesce with the correct buddy when page is aligned */
if (!this->IsAlignedToOrder(cur_entry, cur_order + 1)) {
cur_entry = buddy_entry;
}
++cur_order;
} else {
/* Buddy isn't in the free list, so we can't coalesce. */
break;
}
}
/* Insert the coalesced entry into the free list. */
m_free_lists[cur_order].PushBack(cur_entry);
m_total_free_size += this->GetBytesFromOrder(cur_order);
}
FileSystemBuddyHeap::PageEntry *FileSystemBuddyHeap::GetBuddy(PageEntry *page_entry, s32 order) {
AMS_ASSERT(page_entry != nullptr);
AMS_ASSERT(order >= 0);
AMS_ASSERT(order <= this->GetOrderMax());
const auto address = this->GetAddressFromPageEntry(*page_entry);
const auto offset = this->GetBlockCountFromOrder(order) * this->GetBlockSize();
if (this->IsAlignedToOrder(page_entry, order + 1)) {
/* If the page entry is aligned to the next order, return the buddy block to the right of the current entry. */
return (address + offset < m_heap_start + m_heap_size) ? GetPageEntryFromAddress(address + offset) : nullptr;
} else {
/* If the page entry isn't aligned, return the buddy block to the left of the current entry. */
return (m_heap_start <= address - offset) ? GetPageEntryFromAddress(address - offset) : nullptr;
}
}
FileSystemBuddyHeap::PageEntry *FileSystemBuddyHeap::GetFreePageEntry(s32 order) {
AMS_ASSERT(order >= 0);
AMS_ASSERT(order <= this->GetOrderMax());
/* Try orders from low to high until we find a free page entry. */
for (auto cur_order = order; cur_order <= this->GetOrderMax(); cur_order++) {
if (auto &free_list = m_free_lists[cur_order]; !free_list.IsEmpty()) {
/* The current list isn't empty, so grab an entry from it. */
PageEntry *page_entry = free_list.PopFront();
AMS_ASSERT(page_entry != nullptr);
/* Update size bookkeeping. */
m_total_free_size -= GetBytesFromOrder(cur_order);
/* If we allocated more memory than needed, free the unneeded portion. */
this->DivideBuddies(page_entry, order, cur_order);
AMS_ASSERT(page_entry->next == nullptr);
/* Return the newly-divided entry. */
return page_entry;
}
}
/* We failed to find a free page. */
return nullptr;
}
s32 FileSystemBuddyHeap::GetOrderFromBlockCount(s32 block_count) const {
AMS_ASSERT(block_count >= 0);
/* Return the first order with a big enough block count. */
for (s32 order = 0; order <= this->GetOrderMax(); ++order) {
if (block_count <= this->GetBlockCountFromOrder(order)) {
return order;
}
}
return -1;
}
}

View File

@@ -1,419 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
Result FileSystemBufferManager::CacheHandleTable::Initialize(s32 max_cache_count) {
/* Validate pre-conditions. */
AMS_ASSERT(m_entries == nullptr);
AMS_ASSERT(m_internal_entry_buffer == nullptr);
/* If we don't have an external buffer, try to allocate an internal one. */
if (m_external_entry_buffer == nullptr) {
m_entry_buffer_size = sizeof(Entry) * max_cache_count;
m_internal_entry_buffer = fs::impl::MakeUnique<char[]>(m_entry_buffer_size);
}
/* We need to have at least one entry buffer. */
R_UNLESS(m_internal_entry_buffer != nullptr || m_external_entry_buffer != nullptr, fs::ResultAllocationMemoryFailedInFileSystemBufferManagerA());
/* Set entries. */
m_entries = reinterpret_cast<Entry *>(m_external_entry_buffer != nullptr ? m_external_entry_buffer : m_internal_entry_buffer.get());
m_entry_count = 0;
m_entry_count_max = max_cache_count;
AMS_ASSERT(m_entries != nullptr);
m_cache_count_min = max_cache_count / 16;
m_cache_size_min = m_cache_count_min * 0x100;
R_SUCCEED();
}
void FileSystemBufferManager::CacheHandleTable::Finalize() {
if (m_entries != nullptr) {
AMS_ASSERT(m_entry_count == 0);
if (m_external_attr_info_buffer == nullptr) {
auto it = m_attr_list.begin();
while (it != m_attr_list.end()) {
const auto attr_info = std::addressof(*it);
it = m_attr_list.erase(it);
delete attr_info;
}
}
m_internal_entry_buffer.reset();
m_external_entry_buffer = nullptr;
m_entry_buffer_size = 0;
m_entries = nullptr;
m_total_cache_size = 0;
}
}
bool FileSystemBufferManager::CacheHandleTable::Register(CacheHandle *out, uintptr_t address, size_t size, const BufferAttribute &attr) {
/* Validate pre-conditions. */
AMS_ASSERT(m_entries != nullptr);
AMS_ASSERT(out != nullptr);
/* Get the entry. */
auto entry = this->AcquireEntry(address, size, attr);
/* If we don't have an entry, we can't register. */
if (entry == nullptr) {
return false;
}
/* Get the attr info. If we have one, increment. */
if (const auto attr_info = this->FindAttrInfo(attr); attr_info != nullptr) {
attr_info->IncrementCacheCount();
attr_info->AddCacheSize(size);
} else {
/* Make a new attr info and add it to the list. */
AttrInfo *new_info = nullptr;
if (m_external_attr_info_buffer == nullptr) {
new_info = new AttrInfo(attr.GetLevel(), 1, size);
} else if (0 <= attr.GetLevel() && attr.GetLevel() < m_external_attr_info_count) {
void *buffer = m_external_attr_info_buffer + attr.GetLevel() * sizeof(AttrInfo);
new_info = std::construct_at(reinterpret_cast<AttrInfo *>(buffer), attr.GetLevel(), 1, size);
}
/* If we failed to make a new attr info, we can't register. */
if (new_info == nullptr) {
this->ReleaseEntry(entry);
return false;
}
m_attr_list.push_back(*new_info);
}
m_total_cache_size += size;
*out = entry->GetHandle();
return true;
}
bool FileSystemBufferManager::CacheHandleTable::Unregister(uintptr_t *out_address, size_t *out_size, CacheHandle handle) {
/* Validate pre-conditions. */
AMS_ASSERT(m_entries != nullptr);
AMS_ASSERT(out_address != nullptr);
AMS_ASSERT(out_size != nullptr);
/* Find the lower bound for the entry. */
const auto entry = std::lower_bound(m_entries, m_entries + m_entry_count, handle, [](const Entry &entry, CacheHandle handle) {
return entry.GetHandle() < handle;
});
/* If the entry is a match, unregister it. */
if (entry != m_entries + m_entry_count && entry->GetHandle() == handle) {
this->UnregisterCore(out_address, out_size, entry);
return true;
} else {
return false;
}
}
bool FileSystemBufferManager::CacheHandleTable::UnregisterOldest(uintptr_t *out_address, size_t *out_size, const BufferAttribute &attr, size_t required_size) {
AMS_UNUSED(attr, required_size);
/* Validate pre-conditions. */
AMS_ASSERT(m_entries != nullptr);
AMS_ASSERT(out_address != nullptr);
AMS_ASSERT(out_size != nullptr);
/* If we have no entries, we can't unregister any. */
if (m_entry_count == 0) {
return false;
}
const auto CanUnregister = [this](const Entry &entry) {
const auto attr_info = this->FindAttrInfo(entry.GetBufferAttribute());
AMS_ASSERT(attr_info != nullptr);
const auto ccm = this->GetCacheCountMin(entry.GetBufferAttribute());
const auto csm = this->GetCacheSizeMin(entry.GetBufferAttribute());
return ccm < attr_info->GetCacheCount() && csm + entry.GetSize() <= attr_info->GetCacheSize();
};
/* Find an entry, falling back to the first entry. */
auto entry = std::find_if(m_entries, m_entries + m_entry_count, CanUnregister);
if (entry == m_entries + m_entry_count) {
entry = m_entries;
}
AMS_ASSERT(entry != m_entries + m_entry_count);
this->UnregisterCore(out_address, out_size, entry);
return true;
}
void FileSystemBufferManager::CacheHandleTable::UnregisterCore(uintptr_t *out_address, size_t *out_size, Entry *entry) {
/* Validate pre-conditions. */
AMS_ASSERT(m_entries != nullptr);
AMS_ASSERT(out_address != nullptr);
AMS_ASSERT(out_size != nullptr);
AMS_ASSERT(entry != nullptr);
/* Get the attribute info. */
const auto attr_info = this->FindAttrInfo(entry->GetBufferAttribute());
AMS_ASSERT(attr_info != nullptr);
AMS_ASSERT(attr_info->GetCacheCount() > 0);
AMS_ASSERT(attr_info->GetCacheSize() >= entry->GetSize());
/* Release from the attr info. */
attr_info->DecrementCacheCount();
attr_info->SubtractCacheSize(entry->GetSize());
/* Release from cached size. */
AMS_ASSERT(m_total_cache_size >= entry->GetSize());
m_total_cache_size -= entry->GetSize();
/* Release the entry. */
*out_address = entry->GetAddress();
*out_size = entry->GetSize();
this->ReleaseEntry(entry);
}
FileSystemBufferManager::CacheHandle FileSystemBufferManager::CacheHandleTable::PublishCacheHandle() {
AMS_ASSERT(m_entries != nullptr);
return (++m_current_handle);
}
size_t FileSystemBufferManager::CacheHandleTable::GetTotalCacheSize() const {
return m_total_cache_size;
}
FileSystemBufferManager::CacheHandleTable::Entry *FileSystemBufferManager::CacheHandleTable::AcquireEntry(uintptr_t address, size_t size, const BufferAttribute &attr) {
/* Validate pre-conditions. */
AMS_ASSERT(m_entries != nullptr);
Entry *entry = nullptr;
if (m_entry_count < m_entry_count_max) {
entry = m_entries + m_entry_count;
entry->Initialize(this->PublishCacheHandle(), address, size, attr);
++m_entry_count;
AMS_ASSERT(m_entry_count == 1 || (entry-1)->GetHandle() < entry->GetHandle());
}
return entry;
}
void FileSystemBufferManager::CacheHandleTable::ReleaseEntry(Entry *entry) {
/* Validate pre-conditions. */
AMS_ASSERT(m_entries != nullptr);
AMS_ASSERT(entry != nullptr);
/* Ensure the entry is valid. */
{
const auto entry_buffer = m_external_entry_buffer != nullptr ? m_external_entry_buffer : m_internal_entry_buffer.get();
AMS_ASSERT(static_cast<void *>(entry_buffer) <= static_cast<void *>(entry));
AMS_ASSERT(static_cast<void *>(entry) < static_cast<void *>(entry_buffer + m_entry_buffer_size));
AMS_UNUSED(entry_buffer);
}
/* Copy the entries back by one. */
std::memmove(entry, entry + 1, sizeof(Entry) * (m_entry_count - ((entry + 1) - m_entries)));
/* Decrement our entry count. */
--m_entry_count;
}
FileSystemBufferManager::CacheHandleTable::AttrInfo *FileSystemBufferManager::CacheHandleTable::FindAttrInfo(const BufferAttribute &attr) {
const auto it = std::find_if(m_attr_list.begin(), m_attr_list.end(), [&attr](const AttrInfo &info) {
return attr.GetLevel() == info.GetLevel();
});
return it != m_attr_list.end() ? std::addressof(*it) : nullptr;
}
const fs::IBufferManager::MemoryRange FileSystemBufferManager::AllocateBufferImpl(size_t size, const BufferAttribute &attr) {
/* Get/sanity check the required order. */
fs::IBufferManager::MemoryRange range = {};
const auto order = m_buddy_heap.GetOrderFromBytes(size);
AMS_ASSERT(order >= 0);
while (true) {
/* Try to allocate a buffer at the desired order. */
if (auto address = m_buddy_heap.AllocateByOrder(order); address != 0) {
/* Check that we allocated enough. */
const auto allocated_size = m_buddy_heap.GetBytesFromOrder(order);
AMS_ASSERT(size <= allocated_size);
/* Set up the range extents. */
range.first = reinterpret_cast<uintptr_t>(address);
range.second = allocated_size;
/* Update our peak tracking variables. */
const size_t free_size = m_buddy_heap.GetTotalFreeSize();
m_peak_free_size = std::min(m_peak_free_size, free_size);
const size_t total_allocatable_size = free_size + m_cache_handle_table.GetTotalCacheSize();
m_peak_total_allocatable_size = std::min(m_peak_total_allocatable_size, total_allocatable_size);
break;
}
/* We failed, to we'll need to deallocate something and retry. */
++m_retried_count;
/* Deallocate a buffer. */
uintptr_t deallocate_address = 0;
size_t deallocate_size = 0;
if (m_cache_handle_table.UnregisterOldest(std::addressof(deallocate_address), std::addressof(deallocate_size), attr, size)) {
this->DeallocateBufferImpl(deallocate_address, deallocate_size);
} else {
break;
}
}
/* Return the range we allocated. */
return range;
}
void FileSystemBufferManager::DeallocateBufferImpl(uintptr_t address, size_t size) {
AMS_ASSERT(util::IsPowerOfTwo(size));
m_buddy_heap.Free(reinterpret_cast<void *>(address), m_buddy_heap.GetOrderFromBytes(size));
}
FileSystemBufferManager::CacheHandle FileSystemBufferManager::RegisterCacheImpl(uintptr_t address, size_t size, const BufferAttribute &attr) {
CacheHandle handle = 0;
while (true) {
/* Try to register the handle. */
if (m_cache_handle_table.Register(std::addressof(handle), address, size, attr)) {
break;
}
/* Deallocate a buffer. */
uintptr_t deallocate_address = 0;
size_t deallocate_size = 0;
++m_retried_count;
if (m_cache_handle_table.UnregisterOldest(std::addressof(deallocate_address), std::addressof(deallocate_size), attr)) {
this->DeallocateBufferImpl(deallocate_address, deallocate_size);
} else {
this->DeallocateBufferImpl(address, size);
handle = m_cache_handle_table.PublishCacheHandle();
break;
}
}
return handle;
}
const fs::IBufferManager::MemoryRange FileSystemBufferManager::AcquireCacheImpl(CacheHandle handle) {
fs::IBufferManager::MemoryRange range = {};
if (m_cache_handle_table.Unregister(std::addressof(range.first), std::addressof(range.second), handle)) {
const size_t total_allocatable_size = m_buddy_heap.GetTotalFreeSize() + m_cache_handle_table.GetTotalCacheSize();
m_peak_total_allocatable_size = std::min(m_peak_total_allocatable_size, total_allocatable_size);
} else {
range.first = 0;
range.second = 0;
}
return range;
}
size_t FileSystemBufferManager::GetFreeSizeImpl() const {
return m_buddy_heap.GetTotalFreeSize();
}
size_t FileSystemBufferManager::GetTotalAllocatableSizeImpl() const {
return this->GetFreeSizeImpl() + m_cache_handle_table.GetTotalCacheSize();
}
size_t FileSystemBufferManager::GetFreeSizePeakImpl() const {
return m_peak_free_size;
}
size_t FileSystemBufferManager::GetTotalAllocatableSizePeakImpl() const {
return m_peak_total_allocatable_size;
}
size_t FileSystemBufferManager::GetRetriedCountImpl() const {
return m_retried_count;
}
void FileSystemBufferManager::ClearPeakImpl() {
m_peak_free_size = this->GetFreeSizeImpl();
m_peak_total_allocatable_size = this->GetTotalAllocatableSizeImpl();
m_retried_count = 0;
}
const fs::IBufferManager::MemoryRange FileSystemBufferManager::DoAllocateBuffer(size_t size, const BufferAttribute &attr) {
std::scoped_lock lk(m_mutex);
return this->AllocateBufferImpl(size, attr);
}
void FileSystemBufferManager::DoDeallocateBuffer(uintptr_t address, size_t size) {
std::scoped_lock lk(m_mutex);
return this->DeallocateBufferImpl(address, size);
}
FileSystemBufferManager::CacheHandle FileSystemBufferManager::DoRegisterCache(uintptr_t address, size_t size, const BufferAttribute &attr) {
std::scoped_lock lk(m_mutex);
return this->RegisterCacheImpl(address, size, attr);
}
const fs::IBufferManager::MemoryRange FileSystemBufferManager::DoAcquireCache(CacheHandle handle) {
std::scoped_lock lk(m_mutex);
return this->AcquireCacheImpl(handle);
}
size_t FileSystemBufferManager::DoGetTotalSize() const {
return m_total_size;
}
size_t FileSystemBufferManager::DoGetFreeSize() const {
std::scoped_lock lk(m_mutex);
return this->GetFreeSizeImpl();
}
size_t FileSystemBufferManager::DoGetTotalAllocatableSize() const {
std::scoped_lock lk(m_mutex);
return this->GetTotalAllocatableSizeImpl();
}
size_t FileSystemBufferManager::DoGetFreeSizePeak() const {
std::scoped_lock lk(m_mutex);
return this->GetFreeSizePeakImpl();
}
size_t FileSystemBufferManager::DoGetTotalAllocatableSizePeak() const {
std::scoped_lock lk(m_mutex);
return this->GetTotalAllocatableSizePeakImpl();
}
size_t FileSystemBufferManager::DoGetRetriedCount() const {
std::scoped_lock lk(m_mutex);
return this->GetRetriedCountImpl();
}
void FileSystemBufferManager::DoClearPeak() {
std::scoped_lock lk(m_mutex);
return this->ClearPeakImpl();
}
}

View File

@@ -1,364 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
class SoftwareDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
public:
virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) override final;
virtual bool HasExternalDecryptionKey() const override final { return false; }
};
class ExternalDecryptor final : public AesCtrCounterExtendedStorage::IDecryptor {
public:
static constexpr size_t BlockSize = AesCtrCounterExtendedStorage::BlockSize;
static constexpr size_t KeySize = AesCtrCounterExtendedStorage::KeySize;
static constexpr size_t IvSize = AesCtrCounterExtendedStorage::IvSize;
private:
AesCtrCounterExtendedStorage::DecryptFunction m_decrypt_function;
s32 m_key_index;
s32 m_key_generation;
public:
ExternalDecryptor(AesCtrCounterExtendedStorage::DecryptFunction df, s32 key_idx, s32 key_gen) : m_decrypt_function(df), m_key_index(key_idx), m_key_generation(key_gen) {
AMS_ASSERT(m_decrypt_function != nullptr);
}
public:
virtual void Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) override final;
virtual bool HasExternalDecryptionKey() const override final { return m_key_index < 0; }
};
}
Result AesCtrCounterExtendedStorage::CreateExternalDecryptor(std::unique_ptr<IDecryptor> *out, DecryptFunction func, s32 key_index, s32 key_generation) {
std::unique_ptr<IDecryptor> decryptor = std::make_unique<ExternalDecryptor>(func, key_index, key_generation);
R_UNLESS(decryptor != nullptr, fs::ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA());
*out = std::move(decryptor);
R_SUCCEED();
}
Result AesCtrCounterExtendedStorage::CreateSoftwareDecryptor(std::unique_ptr<IDecryptor> *out) {
std::unique_ptr<IDecryptor> decryptor = std::make_unique<SoftwareDecryptor>();
R_UNLESS(decryptor != nullptr, fs::ResultAllocationMemoryFailedInAesCtrCounterExtendedStorageA());
*out = std::move(decryptor);
R_SUCCEED();
}
Result AesCtrCounterExtendedStorage::Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, fs::SubStorage data_storage, fs::SubStorage table_storage) {
/* Read and verify the bucket tree header. */
BucketTree::Header header;
R_TRY(table_storage.Read(0, std::addressof(header), sizeof(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;
/* Create a software decryptor. */
std::unique_ptr<IDecryptor> sw_decryptor;
R_TRY(CreateSoftwareDecryptor(std::addressof(sw_decryptor)));
/* Initialize. */
R_RETURN(this->Initialize(allocator, key, key_size, secure_value, 0, data_storage, fs::SubStorage(std::addressof(table_storage), node_storage_offset, node_storage_size), fs::SubStorage(std::addressof(table_storage), entry_storage_offset, entry_storage_size), header.entry_count, std::move(sw_decryptor)));
}
Result AesCtrCounterExtendedStorage::Initialize(IAllocator *allocator, const void *key, size_t key_size, u32 secure_value, s64 counter_offset, fs::SubStorage data_storage, fs::SubStorage node_storage, fs::SubStorage entry_storage, s32 entry_count, std::unique_ptr<IDecryptor> &&decryptor) {
/* Validate preconditions. */
AMS_ASSERT(key != nullptr);
AMS_ASSERT(key_size == KeySize);
AMS_ASSERT(counter_offset >= 0);
AMS_ASSERT(decryptor != nullptr);
/* Initialize the bucket tree table. */
if (entry_count > 0) {
R_TRY(m_table.Initialize(allocator, node_storage, entry_storage, NodeSize, sizeof(Entry), entry_count));
} else {
m_table.Initialize(NodeSize, 0);
}
/* Set members. */
m_data_storage = data_storage;
std::memcpy(m_key, key, key_size);
m_secure_value = secure_value;
m_counter_offset = counter_offset;
m_decryptor = std::move(decryptor);
R_SUCCEED();
}
void AesCtrCounterExtendedStorage::Finalize() {
if (this->IsInitialized()) {
m_table.Finalize();
m_data_storage = fs::SubStorage();
}
}
Result AesCtrCounterExtendedStorage::GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size) {
/* Validate pre-conditions. */
AMS_ASSERT(offset >= 0);
AMS_ASSERT(size >= 0);
AMS_ASSERT(this->IsInitialized());
/* Clear the out count. */
R_UNLESS(out_entry_count != nullptr, fs::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, fs::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), fs::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>()->GetOffset();
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset());
}
/* 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.GetOffset() < 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();
}
Result AesCtrCounterExtendedStorage::Read(s64 offset, void *buffer, size_t size) {
/* Validate preconditions. */
AMS_ASSERT(offset >= 0);
AMS_ASSERT(this->IsInitialized());
/* Allow zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidOffset());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidSize());
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange());
/* Read the data. */
R_TRY(m_data_storage.Read(offset, buffer, size));
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* 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>()->GetOffset();
R_UNLESS(util::IsAligned(entry_offset, BlockSize), fs::ResultInvalidAesCtrCounterExtendedEntryOffset());
R_UNLESS(0 <= entry_offset && table_offsets.IsInclude(entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset());
}
/* Prepare to read in chunks. */
u8 *cur_data = static_cast<u8 *>(buffer);
auto cur_offset = offset;
const auto end_offset = offset + static_cast<s64>(size);
while (cur_offset < end_offset) {
/* Get the current entry. */
const auto cur_entry = *visitor.Get<Entry>();
/* Get and validate the entry's offset. */
const auto cur_entry_offset = cur_entry.GetOffset();
R_UNLESS(cur_entry_offset <= cur_offset, fs::ResultInvalidAesCtrCounterExtendedEntryOffset());
/* Get and validate the next entry offset. */
s64 next_entry_offset;
if (visitor.CanMoveNext()) {
R_TRY(visitor.MoveNext());
next_entry_offset = visitor.Get<Entry>()->GetOffset();
R_UNLESS(table_offsets.IsInclude(next_entry_offset), fs::ResultInvalidAesCtrCounterExtendedEntryOffset());
} else {
next_entry_offset = table_offsets.end_offset;
}
R_UNLESS(util::IsAligned(next_entry_offset, BlockSize), fs::ResultInvalidAesCtrCounterExtendedEntryOffset());
R_UNLESS(cur_offset < next_entry_offset, fs::ResultInvalidAesCtrCounterExtendedEntryOffset());
/* Get the offset of the entry in the data we read. */
const auto data_offset = cur_offset - cur_entry_offset;
const auto data_size = (next_entry_offset - cur_entry_offset) - data_offset;
AMS_ASSERT(data_size > 0);
/* Determine how much is left. */
const auto remaining_size = end_offset - cur_offset;
const auto cur_size = static_cast<size_t>(std::min(remaining_size, data_size));
AMS_ASSERT(cur_size <= size);
/* If necessary, perform decryption. */
if (cur_entry.encryption_value == Entry::Encryption::Encrypted) {
/* Make the CTR for the data we're decrypting. */
const auto counter_offset = m_counter_offset + cur_entry_offset + data_offset;
NcaAesCtrUpperIv upper_iv = { .part = { .generation = static_cast<u32>(cur_entry.generation), .secure_value = m_secure_value } };
u8 iv[IvSize];
AesCtrStorageByPointer::MakeIv(iv, IvSize, upper_iv.value, counter_offset);
/* Decrypt. */
m_decryptor->Decrypt(cur_data, cur_size, m_key, KeySize, iv, IvSize);
}
/* Advance. */
cur_data += cur_size;
cur_offset += cur_size;
}
R_SUCCEED();
}
Result AesCtrCounterExtendedStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
switch (op_id) {
case fs::OperationId::Invalidate:
{
/* Validate preconditions. */
AMS_ASSERT(this->IsInitialized());
/* Invalidate our table's cache. */
R_TRY(m_table.InvalidateCache());
/* Operate on our data storage. */
R_TRY(m_data_storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max()));
R_SUCCEED();
}
case fs::OperationId::QueryRange:
{
/* Validate preconditions. */
AMS_ASSERT(offset >= 0);
AMS_ASSERT(this->IsInitialized());
/* Validate that we have an output range info. */
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
/* Succeed if there's nothing to operate on. */
if (size == 0) {
reinterpret_cast<fs::QueryRangeInfo *>(dst)->Clear();
R_SUCCEED();
}
/* Validate arguments. */
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidOffset());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidSize());
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange());
/* Operate on our data storage. */
R_TRY(m_data_storage.OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
/* Add in new flags. */
fs::QueryRangeInfo new_info;
new_info.Clear();
new_info.aes_ctr_key_type = static_cast<s32>(m_decryptor->HasExternalDecryptionKey() ? fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes);
/* Merge in the new info. */
reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(new_info);
R_SUCCEED();
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForAesCtrCounterExtendedStorage());
}
}
void SoftwareDecryptor::Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) {
crypto::DecryptAes128Ctr(buf, buf_size, enc_key, enc_key_size, iv, iv_size, buf, buf_size);
}
void ExternalDecryptor::Decrypt(void *buf, size_t buf_size, const void *enc_key, size_t enc_key_size, void *iv, size_t iv_size) {
/* Validate preconditions. */
AMS_ASSERT(buf != nullptr);
AMS_ASSERT(enc_key != nullptr);
AMS_ASSERT(enc_key_size == KeySize);
AMS_ASSERT(iv != nullptr);
AMS_ASSERT(iv_size == IvSize);
AMS_UNUSED(iv_size);
/* Copy the ctr. */
u8 ctr[IvSize];
std::memcpy(ctr, iv, IvSize);
/* Setup tracking. */
size_t remaining_size = buf_size;
s64 cur_offset = 0;
/* Allocate a pooled buffer for decryption. */
PooledBuffer pooled_buffer;
pooled_buffer.AllocateParticularlyLarge(buf_size, BlockSize);
AMS_ASSERT(pooled_buffer.GetSize() > 0 && util::IsAligned(pooled_buffer.GetSize(), BlockSize));
/* Read and decrypt in chunks. */
while (remaining_size > 0) {
size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size);
u8 *dst = static_cast<u8 *>(buf) + cur_offset;
m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, enc_key, enc_key_size, ctr, IvSize, dst, cur_size);
std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size);
cur_offset += cur_size;
remaining_size -= cur_size;
if (remaining_size > 0) {
AddCounter(ctr, IvSize, cur_size / BlockSize);
}
}
}
}

View File

@@ -1,195 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
template<fs::PointerToStorage BasePointer>
void AesCtrStorage<BasePointer>::MakeIv(void *dst, size_t dst_size, u64 upper, s64 offset) {
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(dst_size == IvSize);
AMS_ASSERT(offset >= 0);
AMS_UNUSED(dst_size);
const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
util::StoreBigEndian(reinterpret_cast<u64 *>(out_addr + 0), upper);
util::StoreBigEndian(reinterpret_cast<s64 *>(out_addr + sizeof(u64)), static_cast<s64>(offset / BlockSize));
}
template<fs::PointerToStorage BasePointer>
AesCtrStorage<BasePointer>::AesCtrStorage(BasePointer base, const void *key, size_t key_size, const void *iv, size_t iv_size) : m_base_storage(std::move(base)) {
AMS_ASSERT(m_base_storage != nullptr);
AMS_ASSERT(key != nullptr);
AMS_ASSERT(iv != nullptr);
AMS_ASSERT(key_size == KeySize);
AMS_ASSERT(iv_size == IvSize);
AMS_UNUSED(key_size, iv_size);
std::memcpy(m_key, key, KeySize);
std::memcpy(m_iv, iv, IvSize);
}
template<fs::PointerToStorage BasePointer>
Result AesCtrStorage<BasePointer>::Read(s64 offset, void *buffer, size_t size) {
/* Allow zero-size reads. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* We can only read at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument());
/* Read the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Prepare to decrypt the data, with temporarily increased priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / BlockSize);
/* Decrypt, ensure we decrypt correctly. */
auto dec_size = crypto::DecryptAes128Ctr(buffer, size, m_key, KeySize, ctr, IvSize, buffer, size);
R_UNLESS(size == dec_size, fs::ResultUnexpectedInAesCtrStorageA());
R_SUCCEED();
}
template<fs::PointerToStorage BasePointer>
Result AesCtrStorage<BasePointer>::Write(s64 offset, const void *buffer, size_t size) {
/* Allow zero-size writes. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* We can only write at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument());
/* Get a pooled buffer. */
PooledBuffer pooled_buffer;
const bool use_work_buffer = !IsDeviceAddress(buffer);
if (use_work_buffer) {
pooled_buffer.Allocate(size, BlockSize);
}
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / BlockSize);
/* Loop until all data is written. */
size_t remaining = size;
s64 cur_offset = 0;
while (remaining > 0) {
/* Determine data we're writing and where. */
const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : const_cast<void *>(buffer);
/* Encrypt the data, with temporarily increased priority. */
{
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
auto enc_size = crypto::EncryptAes128Ctr(write_buf, write_size, m_key, KeySize, ctr, IvSize, reinterpret_cast<const char *>(buffer) + cur_offset, write_size);
R_UNLESS(enc_size == write_size, fs::ResultUnexpectedInAesCtrStorageA());
}
/* Write the encrypted data. */
R_TRY(m_base_storage->Write(offset + cur_offset, write_buf, write_size));
/* Advance. */
cur_offset += write_size;
remaining -= write_size;
if (remaining > 0) {
AddCounter(ctr, IvSize, write_size / BlockSize);
}
}
R_SUCCEED();
}
template<fs::PointerToStorage BasePointer>
Result AesCtrStorage<BasePointer>::Flush() {
R_RETURN(m_base_storage->Flush());
}
template<fs::PointerToStorage BasePointer>
Result AesCtrStorage<BasePointer>::SetSize(s64 size) {
AMS_UNUSED(size);
R_THROW(fs::ResultUnsupportedSetSizeForAesCtrStorage());
}
template<fs::PointerToStorage BasePointer>
Result AesCtrStorage<BasePointer>::GetSize(s64 *out) {
R_RETURN(m_base_storage->GetSize(out));
}
template<fs::PointerToStorage BasePointer>
Result AesCtrStorage<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* If operation isn't invalidate, special case. */
if (op_id != fs::OperationId::Invalidate) {
/* Handle the zero-size case. */
if (size == 0) {
if (op_id == fs::OperationId::QueryRange) {
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
reinterpret_cast<fs::QueryRangeInfo *>(dst)->Clear();
}
R_SUCCEED();
}
/* Ensure alignment. */
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument());
}
switch (op_id) {
case fs::OperationId::QueryRange:
{
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
fs::QueryRangeInfo info;
info.Clear();
info.aes_ctr_key_type = static_cast<s32>(fs::AesCtrKeyTypeFlag::InternalKeyForSoftwareAes);
reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(info);
}
break;
default:
{
R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
break;
}
R_SUCCEED();
}
template class AesCtrStorage<fs::IStorage *>;
template class AesCtrStorage<std::shared_ptr<fs::IStorage>>;
}

View File

@@ -1,130 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
AesCtrStorageExternal::AesCtrStorageExternal(std::shared_ptr<fs::IStorage> bs, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, DecryptAesCtrFunction df, s32 kidx, s32 kgen) : m_base_storage(std::move(bs)), m_decrypt_function(df), m_key_index(kidx), m_key_generation(kgen) {
AMS_ASSERT(m_base_storage != nullptr);
AMS_ASSERT(enc_key_size == KeySize);
AMS_ASSERT(iv != nullptr);
AMS_ASSERT(iv_size == IvSize);
AMS_UNUSED(iv_size);
std::memcpy(m_iv, iv, IvSize);
std::memcpy(m_encrypted_key, enc_key, enc_key_size);
}
Result AesCtrStorageExternal::Read(s64 offset, void *buffer, size_t size) {
/* Allow zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
/* NOTE: For some reason, Nintendo uses InvalidArgument instead of InvalidOffset/InvalidSize here. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
R_UNLESS(util::IsAligned(offset, BlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, BlockSize), fs::ResultInvalidArgument());
/* Read the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Allocate a pooled buffer for decryption. */
PooledBuffer pooled_buffer;
pooled_buffer.AllocateParticularlyLarge(size, BlockSize);
AMS_ASSERT(pooled_buffer.GetSize() >= BlockSize);
/* Setup the counter. */
u8 ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / BlockSize);
/* Setup tracking. */
size_t remaining_size = size;
s64 cur_offset = 0;
while (remaining_size > 0) {
/* Get the current size to process. */
size_t cur_size = std::min(pooled_buffer.GetSize(), remaining_size);
char *dst = static_cast<char *>(buffer) + cur_offset;
/* Decrypt into the temporary buffer */
m_decrypt_function(pooled_buffer.GetBuffer(), cur_size, m_key_index, m_key_generation, m_encrypted_key, KeySize, ctr, IvSize, dst, cur_size);
/* Copy to the destination. */
std::memcpy(dst, pooled_buffer.GetBuffer(), cur_size);
/* Update tracking. */
cur_offset += cur_size;
remaining_size -= cur_size;
if (remaining_size > 0) {
AddCounter(ctr, IvSize, cur_size / BlockSize);
}
}
R_SUCCEED();
}
Result AesCtrStorageExternal::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
switch (op_id) {
case fs::OperationId::QueryRange:
{
/* Validate that we have an output range info. */
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
/* Operate on our base storage. */
R_TRY(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
/* Add in new flags. */
fs::QueryRangeInfo new_info;
new_info.Clear();
new_info.aes_ctr_key_type = static_cast<s32>(m_key_index >= 0 ? fs::AesCtrKeyTypeFlag::InternalKeyForHardwareAes : fs::AesCtrKeyTypeFlag::ExternalKeyForHardwareAes);
/* Merge the new info in. */
reinterpret_cast<fs::QueryRangeInfo *>(dst)->Merge(new_info);
R_SUCCEED();
}
default:
{
/* Operate on our base storage. */
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
}
}
Result AesCtrStorageExternal::GetSize(s64 *out) {
R_RETURN(m_base_storage->GetSize(out));
}
Result AesCtrStorageExternal::Flush() {
R_SUCCEED();
}
Result AesCtrStorageExternal::Write(s64 offset, const void *buffer, size_t size) {
AMS_UNUSED(offset, buffer, size);
R_THROW(fs::ResultUnsupportedWriteForAesCtrStorageExternal());
}
Result AesCtrStorageExternal::SetSize(s64 size) {
AMS_UNUSED(size);
R_THROW(fs::ResultUnsupportedSetSizeForAesCtrStorageExternal());
}
}

View File

@@ -1,247 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
template<fs::PointerToStorage BasePointer>
void AesXtsStorage<BasePointer>::MakeAesXtsIv(void *dst, size_t dst_size, s64 offset, size_t block_size) {
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(dst_size == IvSize);
AMS_ASSERT(offset >= 0);
AMS_UNUSED(dst_size);
const uintptr_t out_addr = reinterpret_cast<uintptr_t>(dst);
util::StoreBigEndian<s64>(reinterpret_cast<s64 *>(out_addr + sizeof(s64)), offset / block_size);
}
template<fs::PointerToStorage BasePointer>
AesXtsStorage<BasePointer>::AesXtsStorage(BasePointer base, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size) : m_base_storage(std::move(base)), m_block_size(block_size), m_mutex() {
AMS_ASSERT(m_base_storage != nullptr);
AMS_ASSERT(key1 != nullptr);
AMS_ASSERT(key2 != nullptr);
AMS_ASSERT(iv != nullptr);
AMS_ASSERT(key_size == KeySize);
AMS_ASSERT(iv_size == IvSize);
AMS_ASSERT(util::IsAligned(m_block_size, AesBlockSize));
AMS_UNUSED(key_size, iv_size);
std::memcpy(m_key[0], key1, KeySize);
std::memcpy(m_key[1], key2, KeySize);
std::memcpy(m_iv, iv, IvSize);
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorage<BasePointer>::Read(s64 offset, void *buffer, size_t size) {
/* Allow zero-size reads. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* We can only read at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
/* Read the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Prepare to decrypt the data, with temporarily increased priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / m_block_size);
/* Handle any unaligned data before the start. */
size_t processed_size = 0;
if ((offset % m_block_size) != 0) {
/* Determine the size of the pre-data read. */
const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size));
const size_t data_size = std::min(size, m_block_size - skip_size);
/* Decrypt into a pooled buffer. */
{
PooledBuffer tmp_buf(m_block_size, m_block_size);
AMS_ASSERT(tmp_buf.GetSize() >= m_block_size);
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
const size_t dec_size = crypto::DecryptAes128Xts(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size);
R_UNLESS(dec_size == m_block_size, fs::ResultUnexpectedInAesXtsStorageA());
std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
}
AddCounter(ctr, IvSize, 1);
processed_size += data_size;
AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size));
}
/* Decrypt aligned chunks. */
char *cur = static_cast<char *>(buffer) + processed_size;
size_t remaining = size - processed_size;
while (remaining > 0) {
const size_t cur_size = std::min(m_block_size, remaining);
const size_t dec_size = crypto::DecryptAes128Xts(cur, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, cur, cur_size);
R_UNLESS(cur_size == dec_size, fs::ResultUnexpectedInAesXtsStorageA());
remaining -= cur_size;
cur += cur_size;
AddCounter(ctr, IvSize, 1);
}
R_SUCCEED();
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorage<BasePointer>::Write(s64 offset, const void *buffer, size_t size) {
/* Allow zero-size writes. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* We can only read at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
/* Get a pooled buffer. */
PooledBuffer pooled_buffer;
const bool use_work_buffer = !IsDeviceAddress(buffer);
if (use_work_buffer) {
pooled_buffer.Allocate(size, m_block_size);
}
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / m_block_size);
/* Handle any unaligned data before the start. */
size_t processed_size = 0;
if ((offset % m_block_size) != 0) {
/* Determine the size of the pre-data read. */
const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size));
const size_t data_size = std::min(size, m_block_size - skip_size);
/* Create an encryptor. */
/* NOTE: This is completely unnecessary, because crypto::EncryptAes128Xts is used below. */
/* However, Nintendo does it, so we will too. */
crypto::Aes128XtsEncryptor xts;
xts.Initialize(m_key[0], m_key[1], KeySize, ctr, IvSize);
/* Encrypt into a pooled buffer. */
{
/* NOTE: Nintendo allocates a second pooled buffer here despite having one already allocated above. */
PooledBuffer tmp_buf(m_block_size, m_block_size);
AMS_ASSERT(tmp_buf.GetSize() >= m_block_size);
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
const size_t enc_size = crypto::EncryptAes128Xts(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size);
R_UNLESS(enc_size == m_block_size, fs::ResultUnexpectedInAesXtsStorageA());
R_TRY(m_base_storage->Write(offset, tmp_buf.GetBuffer() + skip_size, data_size));
}
AddCounter(ctr, IvSize, 1);
processed_size += data_size;
AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size));
}
/* Encrypt aligned chunks. */
size_t remaining = size - processed_size;
s64 cur_offset = offset + processed_size;
while (remaining > 0) {
/* Determine data we're writing and where. */
const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
/* Encrypt the data, with temporarily increased priority. */
{
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
size_t remaining_write = write_size;
size_t encrypt_offset = 0;
while (remaining_write > 0) {
const size_t cur_size = std::min(remaining_write, m_block_size);
const void *src = static_cast<const char *>(buffer) + processed_size + encrypt_offset;
void *dst = use_work_buffer ? pooled_buffer.GetBuffer() + encrypt_offset : const_cast<void *>(src);
const size_t enc_size = crypto::EncryptAes128Xts(dst, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, src, cur_size);
R_UNLESS(enc_size == cur_size, fs::ResultUnexpectedInAesXtsStorageA());
AddCounter(ctr, IvSize, 1);
encrypt_offset += cur_size;
remaining_write -= cur_size;
}
}
/* Write the encrypted data. */
const void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : static_cast<const char *>(buffer) + processed_size;
R_TRY(m_base_storage->Write(cur_offset, write_buf, write_size));
/* Advance. */
cur_offset += write_size;
processed_size += write_size;
remaining -= write_size;
}
R_SUCCEED();
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorage<BasePointer>::Flush() {
R_RETURN(m_base_storage->Flush());
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorage<BasePointer>::SetSize(s64 size) {
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA());
R_RETURN(m_base_storage->SetSize(size));
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorage<BasePointer>::GetSize(s64 *out) {
R_RETURN(m_base_storage->GetSize(out));
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorage<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* Unless invalidating cache, check the arguments. */
if (op_id != fs::OperationId::Invalidate) {
/* Handle the zero size case. */
R_SUCCEED_IF(size == 0);
/* Ensure alignment. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
}
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
template class AesXtsStorage<fs::IStorage *>;
template class AesXtsStorage<std::shared_ptr<fs::IStorage>>;
}

View File

@@ -1,233 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
template<fs::PointerToStorage BasePointer>
AesXtsStorageExternal<BasePointer>::AesXtsStorageExternal(BasePointer bs, const void *key1, const void *key2, size_t key_size, const void *iv, size_t iv_size, size_t block_size, CryptAesXtsFunction ef, CryptAesXtsFunction df) : m_base_storage(std::move(bs)), m_block_size(block_size), m_encrypt_function(ef), m_decrypt_function(df) {
AMS_ASSERT(key_size == KeySize);
AMS_ASSERT(iv_size == IvSize);
AMS_UNUSED(key_size, iv_size);
if (key1 != nullptr) {
std::memcpy(m_key[0], key1, KeySize);
}
if (key2 != nullptr) {
std::memcpy(m_key[1], key2, KeySize);
}
std::memcpy(m_iv, iv, IvSize);
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorageExternal<BasePointer>::Read(s64 offset, void *buffer, size_t size) {
/* Allow zero size. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Ensure we can decrypt. */
R_UNLESS(m_decrypt_function != nullptr, fs::ResultNullptrArgument());
/* We can only read at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
/* Read the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / m_block_size);
/* Handle any unaligned data before the start. */
size_t processed_size = 0;
if ((offset % m_block_size) != 0) {
/* Determine the size of the pre-data read. */
const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size));
const size_t data_size = std::min(size, m_block_size - skip_size);
/* Decrypt into a pooled buffer. */
{
PooledBuffer tmp_buf(m_block_size, m_block_size);
AMS_ASSERT(tmp_buf.GetSize() >= m_block_size);
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
/* Decrypt. */
R_TRY(m_decrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size));
std::memcpy(buffer, tmp_buf.GetBuffer() + skip_size, data_size);
}
AddCounter(ctr, IvSize, 1);
processed_size += data_size;
AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size));
}
/* Decrypt aligned chunks. */
char *cur = static_cast<char *>(buffer) + processed_size;
size_t remaining = size - processed_size;
while (remaining > 0) {
const size_t cur_size = std::min(m_block_size, remaining);
R_TRY(m_decrypt_function(cur, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, cur, cur_size));
remaining -= cur_size;
cur += cur_size;
AddCounter(ctr, IvSize, 1);
}
R_SUCCEED();
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorageExternal<BasePointer>::Write(s64 offset, const void *buffer, size_t size) {
/* Allow zero-size writes. */
R_SUCCEED_IF(size == 0);
/* Ensure buffer is valid. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Ensure we can encrypt. */
R_UNLESS(m_encrypt_function != nullptr, fs::ResultNullptrArgument());
/* We can only write at block aligned offsets. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
/* Get a pooled buffer. */
PooledBuffer pooled_buffer;
const bool use_work_buffer = !IsDeviceAddress(buffer);
if (use_work_buffer) {
pooled_buffer.Allocate(size, m_block_size);
}
/* Setup the counter. */
char ctr[IvSize];
std::memcpy(ctr, m_iv, IvSize);
AddCounter(ctr, IvSize, offset / m_block_size);
/* Handle any unaligned data before the start. */
size_t processed_size = 0;
if ((offset % m_block_size) != 0) {
/* Determine the size of the pre-data read. */
const size_t skip_size = static_cast<size_t>(offset - util::AlignDown(offset, m_block_size));
const size_t data_size = std::min(size, m_block_size - skip_size);
/* Encrypt into a pooled buffer. */
{
/* NOTE: Nintendo allocates a second pooled buffer here despite having one already allocated above. */
PooledBuffer tmp_buf(m_block_size, m_block_size);
AMS_ASSERT(tmp_buf.GetSize() >= m_block_size);
std::memset(tmp_buf.GetBuffer(), 0, skip_size);
std::memcpy(tmp_buf.GetBuffer() + skip_size, buffer, data_size);
R_TRY(m_encrypt_function(tmp_buf.GetBuffer(), m_block_size, m_key[0], m_key[1], KeySize, ctr, IvSize, tmp_buf.GetBuffer(), m_block_size));
R_TRY(m_base_storage->Write(offset, tmp_buf.GetBuffer() + skip_size, data_size));
}
AddCounter(ctr, IvSize, 1);
processed_size += data_size;
AMS_ASSERT(processed_size == std::min(size, m_block_size - skip_size));
}
/* Encrypt aligned chunks. */
size_t remaining = size - processed_size;
s64 cur_offset = offset + processed_size;
while (remaining > 0) {
/* Determine data we're writing and where. */
const size_t write_size = use_work_buffer ? std::min(pooled_buffer.GetSize(), remaining) : remaining;
/* Encrypt the data, with temporarily increased priority. */
{
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
size_t remaining_write = write_size;
size_t encrypt_offset = 0;
while (remaining_write > 0) {
const size_t cur_size = std::min(remaining_write, m_block_size);
const void *src = static_cast<const char *>(buffer) + processed_size + encrypt_offset;
void *dst = use_work_buffer ? pooled_buffer.GetBuffer() + encrypt_offset : const_cast<void *>(src);
R_TRY(m_encrypt_function(dst, cur_size, m_key[0], m_key[1], KeySize, ctr, IvSize, src, cur_size));
AddCounter(ctr, IvSize, 1);
encrypt_offset += cur_size;
remaining_write -= cur_size;
}
}
/* Write the encrypted data. */
const void *write_buf = use_work_buffer ? pooled_buffer.GetBuffer() : static_cast<const char *>(buffer) + processed_size;
R_TRY(m_base_storage->Write(cur_offset, write_buf, write_size));
/* Advance. */
cur_offset += write_size;
processed_size += write_size;
remaining -= write_size;
}
R_SUCCEED();
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorageExternal<BasePointer>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* Unless invalidating cache, check the arguments. */
if (op_id != fs::OperationId::Invalidate) {
/* Handle the zero size case. */
R_SUCCEED_IF(size == 0);
/* Ensure alignment. */
R_UNLESS(util::IsAligned(offset, AesBlockSize), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultInvalidArgument());
}
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorageExternal<BasePointer>::GetSize(s64 *out) {
R_RETURN(m_base_storage->GetSize(out));
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorageExternal<BasePointer>::Flush() {
R_RETURN(m_base_storage->Flush());
}
template<fs::PointerToStorage BasePointer>
Result AesXtsStorageExternal<BasePointer>::SetSize(s64 size) {
R_UNLESS(util::IsAligned(size, AesBlockSize), fs::ResultUnexpectedInAesXtsStorageA());
R_RETURN(m_base_storage->SetSize(size));
}
template class AesXtsStorageExternal<fs::IStorage *>;
template class AesXtsStorageExternal<std::shared_ptr<fs::IStorage>>;
}

View File

@@ -1,261 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
template<typename T>
constexpr ALWAYS_INLINE size_t GetRoundDownDifference(T x, size_t align) {
return static_cast<size_t>(x - util::AlignDown(x, align));
}
template<typename T>
constexpr ALWAYS_INLINE size_t GetRoundUpDifference(T x, size_t align) {
return static_cast<size_t>(util::AlignUp(x, align) - x);
}
template<typename T>
ALWAYS_INLINE size_t GetRoundUpDifference(T *x, size_t align) {
return GetRoundUpDifference(reinterpret_cast<uintptr_t>(x), align);
}
}
Result AlignmentMatchingStorageImpl::Read(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, char *buffer, size_t size) {
/* Check preconditions. */
AMS_ASSERT(work_buf_size >= data_alignment);
AMS_UNUSED(work_buf_size);
/* Succeed if zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Determine extents. */
char *aligned_core_buffer;
s64 core_offset;
size_t core_size;
size_t buffer_gap;
size_t offset_gap;
s64 covered_offset;
const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
if (util::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, buffer_alignment)) {
aligned_core_buffer = buffer + offset_round_up_difference;
core_offset = util::AlignUp(offset, data_alignment);
core_size = (size < offset_round_up_difference) ? 0 : util::AlignDown(size - offset_round_up_difference, data_alignment);
buffer_gap = 0;
offset_gap = 0;
covered_offset = core_size > 0 ? core_offset : offset;
} else {
const size_t buffer_round_up_difference = GetRoundUpDifference(buffer, buffer_alignment);
aligned_core_buffer = buffer + buffer_round_up_difference;
core_offset = util::AlignDown(offset, data_alignment);
core_size = (size < buffer_round_up_difference) ? 0 : util::AlignDown(size - buffer_round_up_difference, data_alignment);
buffer_gap = buffer_round_up_difference;
offset_gap = GetRoundDownDifference(offset, data_alignment);
covered_offset = offset;
}
/* Read the core portion. */
if (core_size > 0) {
R_TRY(base_storage->Read(core_offset, aligned_core_buffer, core_size));
if (offset_gap != 0 || buffer_gap != 0) {
std::memmove(aligned_core_buffer - buffer_gap, aligned_core_buffer + offset_gap, core_size - offset_gap);
core_size -= offset_gap;
}
}
/* Handle the head portion. */
if (offset < covered_offset) {
const s64 head_offset = util::AlignDown(offset, data_alignment);
const size_t head_size = static_cast<size_t>(covered_offset - offset);
AMS_ASSERT(GetRoundDownDifference(offset, data_alignment) + head_size <= work_buf_size);
R_TRY(base_storage->Read(head_offset, work_buf, data_alignment));
std::memcpy(buffer, work_buf + GetRoundDownDifference(offset, data_alignment), head_size);
}
/* Handle the tail portion. */
s64 tail_offset = covered_offset + core_size;
size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
while (remaining_tail_size > 0) {
const auto aligned_tail_offset = util::AlignDown(tail_offset, data_alignment);
const auto cur_size = std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), remaining_tail_size);
R_TRY(base_storage->Read(aligned_tail_offset, work_buf, data_alignment));
AMS_ASSERT((tail_offset - offset) + cur_size <= size);
AMS_ASSERT((tail_offset - aligned_tail_offset) + cur_size <= data_alignment);
std::memcpy(static_cast<char *>(buffer) + (tail_offset - offset), work_buf + (tail_offset - aligned_tail_offset), cur_size);
remaining_tail_size -= cur_size;
tail_offset += cur_size;
}
R_SUCCEED();
}
Result AlignmentMatchingStorageImpl::Write(fs::IStorage *base_storage, char *work_buf, size_t work_buf_size, size_t data_alignment, size_t buffer_alignment, s64 offset, const char *buffer, size_t size) {
/* Check preconditions. */
AMS_ASSERT(work_buf_size >= data_alignment);
AMS_UNUSED(work_buf_size);
/* Succeed if zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Determine extents. */
const char *aligned_core_buffer;
s64 core_offset;
size_t core_size;
s64 covered_offset;
const size_t offset_round_up_difference = GetRoundUpDifference(offset, data_alignment);
if (util::IsAligned(reinterpret_cast<uintptr_t>(buffer) + offset_round_up_difference, buffer_alignment)) {
aligned_core_buffer = buffer + offset_round_up_difference;
core_offset = util::AlignUp(offset, data_alignment);
core_size = (size < offset_round_up_difference) ? 0 : util::AlignDown(size - offset_round_up_difference, data_alignment);
covered_offset = core_size > 0 ? core_offset : offset;
} else {
aligned_core_buffer = nullptr;
core_offset = util::AlignDown(offset, data_alignment);
core_size = 0;
covered_offset = offset;
}
/* Write the core portion. */
if (core_size > 0) {
R_TRY(base_storage->Write(core_offset, aligned_core_buffer, core_size));
}
/* Handle the head portion. */
if (offset < covered_offset) {
const s64 head_offset = util::AlignDown(offset, data_alignment);
const size_t head_size = static_cast<size_t>(covered_offset - offset);
AMS_ASSERT((offset - head_offset) + head_size <= data_alignment);
R_TRY(base_storage->Read(head_offset, work_buf, data_alignment));
std::memcpy(work_buf + (offset - head_offset), buffer, head_size);
R_TRY(base_storage->Write(head_offset, work_buf, data_alignment));
}
/* Handle the tail portion. */
s64 tail_offset = covered_offset + core_size;
size_t remaining_tail_size = static_cast<size_t>((offset + size) - tail_offset);
while (remaining_tail_size > 0) {
AMS_ASSERT(static_cast<size_t>(tail_offset - offset) < size);
const auto aligned_tail_offset = util::AlignDown(tail_offset, data_alignment);
const auto cur_size = std::min(static_cast<size_t>(aligned_tail_offset + data_alignment - tail_offset), remaining_tail_size);
R_TRY(base_storage->Read(aligned_tail_offset, work_buf, data_alignment));
std::memcpy(work_buf + GetRoundDownDifference(tail_offset, data_alignment), buffer + (tail_offset - offset), cur_size);
R_TRY(base_storage->Write(aligned_tail_offset, work_buf, data_alignment));
remaining_tail_size -= cur_size;
tail_offset += cur_size;
}
R_SUCCEED();
}
template<>
Result AlignmentMatchingStorageInBulkRead<1>::Read(s64 offset, void *buffer, size_t size) {
/* Succeed if zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
s64 bs_size = 0;
R_TRY(this->GetSize(std::addressof(bs_size)));
R_TRY(fs::IStorage::CheckAccessRange(offset, size, bs_size));
/* Determine extents. */
const auto offset_end = offset + static_cast<s64>(size);
const auto aligned_offset = util::AlignDown(offset, m_data_align);
const auto aligned_offset_end = util::AlignUp(offset_end, m_data_align);
const auto aligned_size = static_cast<size_t>(aligned_offset_end - aligned_offset);
/* If we aren't aligned, we need to allocate a buffer. */
PooledBuffer pooled_buffer;
if (aligned_offset != offset || aligned_size != size) {
if (aligned_size <= pooled_buffer.GetAllocatableSizeMax()) {
pooled_buffer.Allocate(aligned_size, m_data_align);
if (aligned_size <= pooled_buffer.GetSize()) {
R_TRY(m_base_storage->Read(aligned_offset, pooled_buffer.GetBuffer(), aligned_size));
std::memcpy(buffer, pooled_buffer.GetBuffer() + (offset - aligned_offset), size);
R_SUCCEED();
} else {
pooled_buffer.Shrink(m_data_align);
}
} else {
pooled_buffer.Allocate(m_data_align, m_data_align);
}
AMS_ASSERT(pooled_buffer.GetSize() >= static_cast<size_t>(m_data_align));
}
/* Determine read extents for the aligned portion. */
const auto core_offset = util::AlignUp(offset, m_data_align);
const auto core_offset_end = util::AlignDown(offset_end, m_data_align);
/* Handle any data before the aligned portion. */
if (offset < core_offset) {
const auto head_size = static_cast<size_t>(core_offset - offset);
AMS_ASSERT(head_size < size);
R_TRY(m_base_storage->Read(aligned_offset, pooled_buffer.GetBuffer(), m_data_align));
std::memcpy(buffer, pooled_buffer.GetBuffer() + (offset - aligned_offset), head_size);
}
/* Handle the aligned portion. */
if (core_offset < core_offset_end) {
const auto core_buffer = static_cast<char *>(buffer) + (core_offset - offset);
const auto core_size = static_cast<size_t>(core_offset_end - core_offset);
R_TRY(m_base_storage->Read(core_offset, core_buffer, core_size));
}
/* Handle any data after the aligned portion. */
if (core_offset_end < offset_end) {
const auto tail_buffer = static_cast<char *>(buffer) + (core_offset_end - offset);
const auto tail_size = static_cast<size_t>(offset_end - core_offset_end);
R_TRY(m_base_storage->Read(core_offset_end, pooled_buffer.GetBuffer(), m_data_align));
std::memcpy(tail_buffer, pooled_buffer.GetBuffer(), tail_size);
}
R_SUCCEED();
}
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
constexpr bool UseDefaultAllocators = false;
constinit bool g_used_default_allocator = false;
void *DefaultAllocate(size_t size) {
g_used_default_allocator = true;
return ams::Malloc(size);
}
void DefaultDeallocate(void *ptr, size_t size) {
AMS_UNUSED(size);
ams::Free(ptr);
}
constinit os::SdkMutex g_allocate_mutex;
constinit os::SdkMutex g_allocate_mutex_for_system;
constinit AllocateFunction g_allocate_func = UseDefaultAllocators ? DefaultAllocate : nullptr;
constinit DeallocateFunction g_deallocate_func = UseDefaultAllocators ? DefaultDeallocate : nullptr;
constinit AllocateFunction g_allocate_func_for_system = nullptr;
constinit DeallocateFunction g_deallocate_func_for_system = nullptr;
void *AllocateUnsafe(size_t size) {
/* Check pre-conditions. */
AMS_ASSERT(g_allocate_mutex.IsLockedByCurrentThread());
AMS_ASSERT(g_allocate_func != nullptr);
/* Allocate memory. */
return g_allocate_func(size);
}
void DeallocateUnsafe(void *ptr, size_t size) {
/* Check pre-conditions. */
AMS_ASSERT(g_allocate_mutex.IsLockedByCurrentThread());
AMS_ASSERT(g_deallocate_func != nullptr);
/* Deallocate the pointer. */
g_deallocate_func(ptr, size);
}
}
namespace impl {
/* Normal allocator set. */
template<> void *AllocatorFunctionSet<false>::Allocate(size_t size) { return ::ams::fssystem::Allocate(size); }
template<> void *AllocatorFunctionSet<false>::AllocateUnsafe(size_t size) { return ::ams::fssystem::AllocateUnsafe(size); }
template<> void AllocatorFunctionSet<false>::Deallocate(void *ptr, size_t size) { return ::ams::fssystem::Deallocate(ptr, size); }
template<> void AllocatorFunctionSet<false>::DeallocateUnsafe(void *ptr, size_t size) { return ::ams::fssystem::DeallocateUnsafe(ptr, size); }
template<> void AllocatorFunctionSet<false>::LockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex.Lock(); }
template<> void AllocatorFunctionSet<false>::UnlockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex.Unlock(); }
/* System allocator set. */
template<> void *AllocatorFunctionSet<true>::AllocateUnsafe(size_t size) {
/* Check pre-conditions. */
AMS_ASSERT(::ams::fssystem::g_allocate_mutex_for_system.IsLockedByCurrentThread());
AMS_ASSERT(::ams::fssystem::g_allocate_func_for_system != nullptr);
/* Allocate memory. */
return g_allocate_func_for_system(size);
}
template<> void AllocatorFunctionSet<true>::DeallocateUnsafe(void *ptr, size_t size) {
/* Check pre-conditions. */
AMS_ASSERT(::ams::fssystem::g_allocate_mutex_for_system.IsLockedByCurrentThread());
AMS_ASSERT(::ams::fssystem::g_deallocate_func_for_system != nullptr);
/* Deallocate the pointer. */
::ams::fssystem::g_deallocate_func_for_system(ptr, size);
}
template<> void *AllocatorFunctionSet<true>::Allocate(size_t size) {
std::scoped_lock lk(::ams::fssystem::g_allocate_mutex_for_system);
return ::ams::fssystem::impl::AllocatorFunctionSet<true>::AllocateUnsafe(size);
}
template<> void AllocatorFunctionSet<true>::Deallocate(void *ptr, size_t size) {
std::scoped_lock lk(::ams::fssystem::g_allocate_mutex_for_system);
return ::ams::fssystem::impl::AllocatorFunctionSet<true>::DeallocateUnsafe(ptr, size);
}
template<> void AllocatorFunctionSet<true>::LockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex_for_system.Lock(); }
template<> void AllocatorFunctionSet<true>::UnlockAllocatorMutex() { ::ams::fssystem::g_allocate_mutex_for_system.Unlock(); }
}
void *Allocate(size_t size) {
std::scoped_lock lk(g_allocate_mutex);
return AllocateUnsafe(size);
}
void Deallocate(void *ptr, size_t size) {
std::scoped_lock lk(g_allocate_mutex);
return DeallocateUnsafe(ptr, size);
}
void InitializeAllocator(AllocateFunction allocate_func, DeallocateFunction deallocate_func) {
/* Check pre-conditions. */
AMS_ASSERT(allocate_func != nullptr);
AMS_ASSERT(deallocate_func != nullptr);
/* Check that we can initialize. */
if constexpr (UseDefaultAllocators) {
AMS_ASSERT(g_used_default_allocator == false);
} else {
AMS_ASSERT(g_allocate_func == nullptr);
AMS_ASSERT(g_deallocate_func == nullptr);
}
/* Set the allocator functions. */
g_allocate_func = allocate_func;
g_deallocate_func = deallocate_func;
}
void InitializeAllocatorForSystem(AllocateFunction allocate_func, DeallocateFunction deallocate_func) {
/* Check pre-conditions. */
AMS_ASSERT(allocate_func != nullptr);
AMS_ASSERT(deallocate_func != nullptr);
/* Check that we can initialize. */
AMS_ASSERT(g_allocate_func_for_system == nullptr);
AMS_ASSERT(g_deallocate_func_for_system == nullptr);
/* Set the system allocator functions. */
g_allocate_func_for_system = allocate_func;
g_deallocate_func_for_system = deallocate_func;
}
}

View File

@@ -1,557 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
using Node = impl::BucketTreeNode<const s64 *>;
static_assert(sizeof(Node) == sizeof(BucketTree::NodeHeader));
static_assert(util::is_pod<Node>::value);
constexpr inline s32 NodeHeaderSize = sizeof(BucketTree::NodeHeader);
class StorageNode {
private:
class Offset {
public:
using difference_type = s64;
private:
s64 m_offset;
s32 m_stride;
public:
constexpr Offset(s64 offset, s32 stride) : m_offset(offset), m_stride(stride) { /* ... */ }
constexpr Offset &operator++() { m_offset += m_stride; return *this; }
constexpr Offset operator++(int) { Offset ret(*this); m_offset += m_stride; return ret; }
constexpr Offset &operator--() { m_offset -= m_stride; return *this; }
constexpr Offset operator--(int) { Offset ret(*this); m_offset -= m_stride; return ret; }
constexpr difference_type operator-(const Offset &rhs) const { return (m_offset - rhs.m_offset) / m_stride; }
constexpr Offset operator+(difference_type ofs) const { return Offset(m_offset + ofs * m_stride, m_stride); }
constexpr Offset operator-(difference_type ofs) const { return Offset(m_offset - ofs * m_stride, m_stride); }
constexpr Offset &operator+=(difference_type ofs) { m_offset += ofs * m_stride; return *this; }
constexpr Offset &operator-=(difference_type ofs) { m_offset -= ofs * m_stride; return *this; }
constexpr bool operator==(const Offset &rhs) const { return m_offset == rhs.m_offset; }
constexpr bool operator!=(const Offset &rhs) const { return m_offset != rhs.m_offset; }
constexpr s64 Get() const { return m_offset; }
};
private:
const Offset m_start;
const s32 m_count;
s32 m_index;
public:
StorageNode(size_t size, s32 count) : m_start(NodeHeaderSize, static_cast<s32>(size)), m_count(count), m_index(-1) { /* ... */ }
StorageNode(s64 ofs, size_t size, s32 count) : m_start(NodeHeaderSize + ofs, static_cast<s32>(size)), m_count(count), m_index(-1) { /* ... */ }
s32 GetIndex() const { return m_index; }
void Find(const char *buffer, s64 virtual_address) {
s32 end = m_count;
auto pos = m_start;
while (end > 0) {
auto half = end / 2;
auto mid = pos + half;
s64 offset = 0;
std::memcpy(std::addressof(offset), buffer + mid.Get(), sizeof(s64));
if (offset <= virtual_address) {
pos = mid + 1;
end -= half + 1;
} else {
end = half;
}
}
m_index = static_cast<s32>(pos - m_start) - 1;
}
Result Find(fs::SubStorage &storage, s64 virtual_address) {
s32 end = m_count;
auto pos = m_start;
while (end > 0) {
auto half = end / 2;
auto mid = pos + half;
s64 offset = 0;
R_TRY(storage.Read(mid.Get(), std::addressof(offset), sizeof(s64)));
if (offset <= virtual_address) {
pos = mid + 1;
end -= half + 1;
} else {
end = half;
}
}
m_index = static_cast<s32>(pos - m_start) - 1;
R_SUCCEED();
}
};
}
void BucketTree::Header::Format(s32 entry_count) {
AMS_ASSERT(entry_count >= 0);
this->magic = Magic;
this->version = Version;
this->entry_count = entry_count;
this->reserved = 0;
}
Result BucketTree::Header::Verify() const {
R_UNLESS(this->magic == Magic, fs::ResultInvalidBucketTreeSignature());
R_UNLESS(this->entry_count >= 0, fs::ResultInvalidBucketTreeEntryCount());
R_UNLESS(this->version <= Version, fs::ResultUnsupportedVersion());
R_SUCCEED();
}
Result BucketTree::NodeHeader::Verify(s32 node_index, size_t node_size, size_t entry_size) const {
R_UNLESS(this->index == node_index, fs::ResultInvalidBucketTreeNodeIndex());
R_UNLESS(entry_size != 0 && node_size >= entry_size + NodeHeaderSize, fs::ResultInvalidSize());
const size_t max_entry_count = (node_size - NodeHeaderSize) / entry_size;
R_UNLESS(this->count > 0 && static_cast<size_t>(this->count) <= max_entry_count, fs::ResultInvalidBucketTreeNodeEntryCount());
R_UNLESS(this->offset >= 0, fs::ResultInvalidBucketTreeNodeOffset());
R_SUCCEED();
}
Result BucketTree::Initialize(IAllocator *allocator, fs::SubStorage node_storage, fs::SubStorage entry_storage, size_t node_size, size_t entry_size, s32 entry_count) {
/* Validate preconditions. */
AMS_ASSERT(allocator != nullptr);
AMS_ASSERT(entry_size >= sizeof(s64));
AMS_ASSERT(node_size >= entry_size + sizeof(NodeHeader));
AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
AMS_ASSERT(util::IsPowerOfTwo(node_size));
AMS_ASSERT(!this->IsInitialized());
/* Ensure valid entry count. */
R_UNLESS(entry_count > 0, fs::ResultInvalidArgument());
/* Allocate node. */
R_UNLESS(m_node_l1.Allocate(allocator, node_size), fs::ResultBufferAllocationFailed());
ON_RESULT_FAILURE { m_node_l1.Free(node_size); };
/* Read node. */
R_TRY(node_storage.Read(0, m_node_l1.Get(), node_size));
/* Verify node. */
R_TRY(m_node_l1->Verify(0, node_size, sizeof(s64)));
/* Validate offsets. */
const auto offset_count = GetOffsetCount(node_size);
const auto entry_set_count = GetEntrySetCount(node_size, entry_size, entry_count);
const auto * const node = m_node_l1.Get<Node>();
s64 start_offset;
if (offset_count < entry_set_count && node->GetCount() < offset_count) {
start_offset = *node->GetEnd();
} else {
start_offset = *node->GetBegin();
}
const auto end_offset = node->GetEndOffset();
R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), fs::ResultInvalidBucketTreeEntryOffset());
R_UNLESS(start_offset < end_offset, fs::ResultInvalidBucketTreeEntryOffset());
/* Set member variables. */
m_node_storage = node_storage;
m_entry_storage = entry_storage;
m_node_size = node_size;
m_entry_size = entry_size;
m_entry_count = entry_count;
m_offset_count = offset_count;
m_entry_set_count = entry_set_count;
m_offset_cache.offsets.start_offset = start_offset;
m_offset_cache.offsets.end_offset = end_offset;
m_offset_cache.is_initialized = true;
/* We succeeded. */
R_SUCCEED();
}
void BucketTree::Initialize(size_t node_size, s64 end_offset) {
AMS_ASSERT(NodeSizeMin <= node_size && node_size <= NodeSizeMax);
AMS_ASSERT(util::IsPowerOfTwo(node_size));
AMS_ASSERT(end_offset > 0);
AMS_ASSERT(!this->IsInitialized());
m_node_size = node_size;
m_offset_cache.offsets.start_offset = 0;
m_offset_cache.offsets.end_offset = end_offset;
m_offset_cache.is_initialized = true;
}
void BucketTree::Finalize() {
if (this->IsInitialized()) {
m_node_storage = fs::SubStorage();
m_entry_storage = fs::SubStorage();
m_node_l1.Free(m_node_size);
m_node_size = 0;
m_entry_size = 0;
m_entry_count = 0;
m_offset_count = 0;
m_entry_set_count = 0;
m_offset_cache.offsets.start_offset = 0;
m_offset_cache.offsets.end_offset = 0;
m_offset_cache.is_initialized = false;
}
}
Result BucketTree::Find(Visitor *visitor, s64 virtual_address) {
AMS_ASSERT(visitor != nullptr);
AMS_ASSERT(this->IsInitialized());
R_UNLESS(virtual_address >= 0, fs::ResultInvalidOffset());
R_UNLESS(!this->IsEmpty(), fs::ResultOutOfRange());
BucketTree::Offsets offsets;
R_TRY(this->GetOffsets(std::addressof(offsets)));
R_TRY(visitor->Initialize(this, offsets));
R_RETURN(visitor->Find(virtual_address));
}
Result BucketTree::InvalidateCache() {
/* Invalidate the node storage cache. */
R_TRY(m_node_storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max()));
/* Invalidate the entry storage cache. */
R_TRY(m_entry_storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max()));
/* Reset our offsets. */
m_offset_cache.is_initialized = false;
R_SUCCEED();
}
Result BucketTree::EnsureOffsetCache() {
/* If we already have an offset cache, we're good. */
R_SUCCEED_IF(m_offset_cache.is_initialized);
/* Acquire exclusive right to edit the offset cache. */
std::scoped_lock lk(m_offset_cache.mutex);
/* Check again, to be sure. */
R_SUCCEED_IF(m_offset_cache.is_initialized);
/* Read/verify L1. */
R_TRY(m_node_storage.Read(0, m_node_l1.Get(), m_node_size));
R_TRY(m_node_l1->Verify(0, m_node_size, sizeof(s64)));
/* Get the node. */
auto * const node = m_node_l1.Get<Node>();
s64 start_offset;
if (m_offset_count < m_entry_set_count && node->GetCount() < m_offset_count) {
start_offset = *node->GetEnd();
} else {
start_offset = *node->GetBegin();
}
const auto end_offset = node->GetEndOffset();
R_UNLESS(0 <= start_offset && start_offset <= node->GetBeginOffset(), fs::ResultInvalidBucketTreeEntryOffset());
R_UNLESS(start_offset < end_offset, fs::ResultInvalidBucketTreeEntryOffset());
m_offset_cache.offsets.start_offset = start_offset;
m_offset_cache.offsets.end_offset = end_offset;
m_offset_cache.is_initialized = true;
R_SUCCEED();
}
Result BucketTree::Visitor::Initialize(const BucketTree *tree, const BucketTree::Offsets &offsets) {
AMS_ASSERT(tree != nullptr);
AMS_ASSERT(m_tree == nullptr || m_tree == tree);
if (m_entry == nullptr) {
m_entry = tree->GetAllocator()->Allocate(tree->m_entry_size);
R_UNLESS(m_entry != nullptr, fs::ResultBufferAllocationFailed());
m_tree = tree;
m_offsets = offsets;
}
R_SUCCEED();
}
Result BucketTree::Visitor::MoveNext() {
R_UNLESS(this->IsValid(), fs::ResultOutOfRange());
/* Invalidate our index, and read the header for the next index. */
auto entry_index = m_entry_index + 1;
if (entry_index == m_entry_set.info.count) {
const auto entry_set_index = m_entry_set.info.index + 1;
R_UNLESS(entry_set_index < m_entry_set_count, fs::ResultOutOfRange());
m_entry_index = -1;
const auto end = m_entry_set.info.end;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
R_TRY(m_tree->m_entry_storage.Read(entry_set_offset, std::addressof(m_entry_set), sizeof(EntrySetHeader)));
R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
R_UNLESS(m_entry_set.info.start == end && m_entry_set.info.start < m_entry_set.info.end, fs::ResultInvalidBucketTreeEntrySetOffset());
entry_index = 0;
} else {
m_entry_index = -1;
}
/* Read the new entry. */
const auto entry_size = m_tree->m_entry_size;
const auto entry_offset = impl::GetBucketTreeEntryOffset(m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
R_TRY(m_tree->m_entry_storage.Read(entry_offset, m_entry, entry_size));
/* Note that we changed index. */
m_entry_index = entry_index;
R_SUCCEED();
}
Result BucketTree::Visitor::MovePrevious() {
R_UNLESS(this->IsValid(), fs::ResultOutOfRange());
/* Invalidate our index, and read the heasder for the previous index. */
auto entry_index = m_entry_index;
if (entry_index == 0) {
R_UNLESS(m_entry_set.info.index > 0, fs::ResultOutOfRange());
m_entry_index = -1;
const auto start = m_entry_set.info.start;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_index = m_entry_set.info.index - 1;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
R_TRY(m_tree->m_entry_storage.Read(entry_set_offset, std::addressof(m_entry_set), sizeof(EntrySetHeader)));
R_TRY(m_entry_set.header.Verify(entry_set_index, entry_set_size, m_tree->m_entry_size));
R_UNLESS(m_entry_set.info.end == start && m_entry_set.info.start < m_entry_set.info.end, fs::ResultInvalidBucketTreeEntrySetOffset());
entry_index = m_entry_set.info.count;
} else {
m_entry_index = -1;
}
--entry_index;
/* Read the new entry. */
const auto entry_size = m_tree->m_entry_size;
const auto entry_offset = impl::GetBucketTreeEntryOffset(m_entry_set.info.index, m_tree->m_node_size, entry_size, entry_index);
R_TRY(m_tree->m_entry_storage.Read(entry_offset, m_entry, entry_size));
/* Note that we changed index. */
m_entry_index = entry_index;
R_SUCCEED();
}
Result BucketTree::Visitor::Find(s64 virtual_address) {
AMS_ASSERT(m_tree != nullptr);
/* Get the node. */
const auto * const node = m_tree->m_node_l1.Get<Node>();
R_UNLESS(virtual_address < node->GetEndOffset(), fs::ResultOutOfRange());
/* Get the entry set index. */
s32 entry_set_index = -1;
if (m_tree->IsExistOffsetL2OnL1() && virtual_address < node->GetBeginOffset()) {
const auto start = node->GetEnd();
const auto end = node->GetBegin() + m_tree->m_offset_count;
auto pos = std::upper_bound(start, end, virtual_address);
R_UNLESS(start < pos, fs::ResultOutOfRange());
--pos;
entry_set_index = static_cast<s32>(pos - start);
} else {
const auto start = node->GetBegin();
const auto end = node->GetEnd();
auto pos = std::upper_bound(start, end, virtual_address);
R_UNLESS(start < pos, fs::ResultOutOfRange());
--pos;
if (m_tree->IsExistL2()) {
const auto node_index = static_cast<s32>(pos - start);
R_UNLESS(0 <= node_index && node_index < m_tree->m_offset_count, fs::ResultInvalidBucketTreeNodeOffset());
R_TRY(this->FindEntrySet(std::addressof(entry_set_index), virtual_address, node_index));
} else {
entry_set_index = static_cast<s32>(pos - start);
}
}
/* Validate the entry set index. */
R_UNLESS(0 <= entry_set_index && entry_set_index < m_tree->m_entry_set_count, fs::ResultInvalidBucketTreeNodeOffset());
/* Find the entry. */
R_TRY(this->FindEntry(virtual_address, entry_set_index));
/* Set count. */
m_entry_set_count = m_tree->m_entry_set_count;
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntrySet(s32 *out_index, s64 virtual_address, s32 node_index) {
const auto node_size = m_tree->m_node_size;
PooledBuffer pool(node_size, 1);
if (node_size <= pool.GetSize()) {
R_RETURN(this->FindEntrySetWithBuffer(out_index, virtual_address, node_index, pool.GetBuffer()));
} else {
pool.Deallocate();
R_RETURN(this->FindEntrySetWithoutBuffer(out_index, virtual_address, node_index));
}
}
Result BucketTree::Visitor::FindEntrySetWithBuffer(s32 *out_index, s64 virtual_address, s32 node_index, char *buffer) {
/* Calculate node extents. */
const auto node_size = m_tree->m_node_size;
const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
fs::SubStorage &storage = m_tree->m_node_storage;
/* Read the node. */
R_TRY(storage.Read(node_offset, buffer, node_size));
/* Validate the header. */
NodeHeader header;
std::memcpy(std::addressof(header), buffer, NodeHeaderSize);
R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
/* Create the node, and find. */
StorageNode node(sizeof(s64), header.count);
node.Find(buffer, virtual_address);
R_UNLESS(node.GetIndex() >= 0, fs::ResultInvalidBucketTreeVirtualOffset());
/* Return the index. */
*out_index = m_tree->GetEntrySetIndex(header.index, node.GetIndex());
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntrySetWithoutBuffer(s32 *out_index, s64 virtual_address, s32 node_index) {
/* Calculate node extents. */
const auto node_size = m_tree->m_node_size;
const auto node_offset = (node_index + 1) * static_cast<s64>(node_size);
fs::SubStorage &storage = m_tree->m_node_storage;
/* Read and validate the header. */
NodeHeader header;
R_TRY(storage.Read(node_offset, std::addressof(header), NodeHeaderSize));
R_TRY(header.Verify(node_index, node_size, sizeof(s64)));
/* Create the node, and find. */
StorageNode node(node_offset, sizeof(s64), header.count);
R_TRY(node.Find(storage, virtual_address));
R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange());
/* Return the index. */
*out_index = m_tree->GetEntrySetIndex(header.index, node.GetIndex());
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntry(s64 virtual_address, s32 entry_set_index) {
const auto entry_set_size = m_tree->m_node_size;
PooledBuffer pool(entry_set_size, 1);
if (entry_set_size <= pool.GetSize()) {
R_RETURN(this->FindEntryWithBuffer(virtual_address, entry_set_index, pool.GetBuffer()));
} else {
pool.Deallocate();
R_RETURN(this->FindEntryWithoutBuffer(virtual_address, entry_set_index));
}
}
Result BucketTree::Visitor::FindEntryWithBuffer(s64 virtual_address, s32 entry_set_index, char *buffer) {
/* Calculate entry set extents. */
const auto entry_size = m_tree->m_entry_size;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
fs::SubStorage &storage = m_tree->m_entry_storage;
/* Read the entry set. */
R_TRY(storage.Read(entry_set_offset, buffer, entry_set_size));
/* Validate the entry_set. */
EntrySetHeader entry_set;
std::memcpy(std::addressof(entry_set), buffer, sizeof(EntrySetHeader));
R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
/* Create the node, and find. */
StorageNode node(entry_size, entry_set.info.count);
node.Find(buffer, virtual_address);
R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange());
/* Copy the data into entry. */
const auto entry_index = node.GetIndex();
const auto entry_offset = impl::GetBucketTreeEntryOffset(0, entry_size, entry_index);
std::memcpy(m_entry, buffer + entry_offset, entry_size);
/* Set our entry set/index. */
m_entry_set = entry_set;
m_entry_index = entry_index;
R_SUCCEED();
}
Result BucketTree::Visitor::FindEntryWithoutBuffer(s64 virtual_address, s32 entry_set_index) {
/* Calculate entry set extents. */
const auto entry_size = m_tree->m_entry_size;
const auto entry_set_size = m_tree->m_node_size;
const auto entry_set_offset = entry_set_index * static_cast<s64>(entry_set_size);
fs::SubStorage &storage = m_tree->m_entry_storage;
/* Read and validate the entry_set. */
EntrySetHeader entry_set;
R_TRY(storage.Read(entry_set_offset, std::addressof(entry_set), sizeof(EntrySetHeader)));
R_TRY(entry_set.header.Verify(entry_set_index, entry_set_size, entry_size));
/* Create the node, and find. */
StorageNode node(entry_set_offset, entry_size, entry_set.info.count);
R_TRY(node.Find(storage, virtual_address));
R_UNLESS(node.GetIndex() >= 0, fs::ResultOutOfRange());
/* Copy the data into entry. */
const auto entry_index = node.GetIndex();
const auto entry_offset = impl::GetBucketTreeEntryOffset(entry_set_offset, entry_size, entry_index);
R_TRY(storage.Read(entry_offset, m_entry, entry_size));
/* Set our entry set/index. */
m_entry_set = entry_set;
m_entry_index = entry_index;
R_SUCCEED();
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fssystem_key_slot_cache.hpp"
namespace ams::fssystem {
namespace {
Result DecompressLz4(void *dst, size_t dst_size, const void *src, size_t src_size) {
R_UNLESS(util::DecompressLZ4(dst, dst_size, src, src_size) == static_cast<int>(dst_size), fs::ResultUnexpectedInCompressedStorageC());
R_SUCCEED();
}
constexpr DecompressorFunction GetNcaDecompressorFunction(CompressionType type) {
switch (type) {
case CompressionType_Lz4:
return DecompressLz4;
default:
return nullptr;
}
}
constexpr NcaCompressionConfiguration g_nca_compression_configuration {
.get_decompressor = GetNcaDecompressorFunction,
};
}
const ::ams::fssystem::NcaCompressionConfiguration *GetNcaCompressionConfiguration() {
return std::addressof(g_nca_compression_configuration);
}
}

View File

@@ -1,336 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fssystem_key_slot_cache.hpp"
namespace ams::fssystem {
namespace {
constexpr inline const size_t KeySize = crypto::AesDecryptor128::KeySize;
constexpr inline const size_t NxAcidSignatureKeyGenerationMax = 1;
constexpr inline const size_t NxAcidSignatureKeyModulusSize = NcaCryptoConfiguration::Rsa2048KeyModulusSize;
constexpr inline const u8 NxAcidSignatureKeyModulusDev[NxAcidSignatureKeyGenerationMax + 1][NxAcidSignatureKeyModulusSize] = {
{
0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89,
0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87,
0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C,
0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B,
0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5,
0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32,
0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53,
0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4,
0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA,
0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B,
0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F,
0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33,
0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C,
0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3,
0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0,
0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69
},
{
0xBC, 0xA5, 0x6A, 0x7E, 0xEA, 0x38, 0x34, 0x62, 0xA6, 0x10, 0x18, 0x3C, 0xE1, 0x63, 0x7B, 0xF0,
0xD3, 0x08, 0x8C, 0xF5, 0xC5, 0xC4, 0xC7, 0x93, 0xE9, 0xD9, 0xE6, 0x32, 0xF3, 0xA0, 0xF6, 0x6E,
0x8A, 0x98, 0x76, 0x47, 0x33, 0x47, 0x65, 0x02, 0x70, 0xDC, 0x86, 0x5F, 0x3D, 0x61, 0x5A, 0x70,
0xBC, 0x5A, 0xCA, 0xCA, 0x50, 0xAD, 0x61, 0x7E, 0xC9, 0xEC, 0x27, 0xFF, 0xE8, 0x64, 0x42, 0x9A,
0xEE, 0xBE, 0xC3, 0xD1, 0x0B, 0xC0, 0xE9, 0xBF, 0x83, 0x8D, 0xC0, 0x0C, 0xD8, 0x00, 0x5B, 0x76,
0x90, 0xD2, 0x4B, 0x30, 0x84, 0x35, 0x8B, 0x1E, 0x20, 0xB7, 0xE4, 0xDC, 0x63, 0xE5, 0xDF, 0xCD,
0x00, 0x5F, 0x81, 0x5F, 0x67, 0xC5, 0x8B, 0xDF, 0xFC, 0xE1, 0x37, 0x5F, 0x07, 0xD9, 0xDE, 0x4F,
0xE6, 0x7B, 0xF1, 0xFB, 0xA1, 0x5A, 0x71, 0x40, 0xFE, 0xBA, 0x1E, 0xAE, 0x13, 0x22, 0xD2, 0xFE,
0x37, 0xA2, 0xB6, 0x8B, 0xAB, 0xEB, 0x84, 0x81, 0x4E, 0x7C, 0x1E, 0x02, 0xD1, 0xFB, 0xD7, 0x5D,
0x11, 0x84, 0x64, 0xD2, 0x4D, 0xBB, 0x50, 0x00, 0x67, 0x54, 0xE2, 0x77, 0x89, 0xBA, 0x0B, 0xE7,
0x05, 0x57, 0x9A, 0x22, 0x5A, 0xEC, 0x76, 0x1C, 0xFD, 0xE8, 0xA8, 0x18, 0x16, 0x41, 0x65, 0x03,
0xFA, 0xC4, 0xA6, 0x31, 0x5C, 0x1A, 0x7F, 0xAB, 0x11, 0xC8, 0x4A, 0x99, 0xB9, 0xE6, 0xCF, 0x62,
0x21, 0xA6, 0x72, 0x47, 0xDB, 0xBA, 0x96, 0x26, 0x4E, 0x2E, 0xD4, 0x8C, 0x46, 0xD6, 0xA7, 0x1A,
0x6C, 0x32, 0xA7, 0xDF, 0x85, 0x1C, 0x03, 0xC3, 0x6D, 0xA9, 0xE9, 0x68, 0xF4, 0x17, 0x1E, 0xB2,
0x70, 0x2A, 0xA1, 0xE5, 0xE1, 0xF3, 0x8F, 0x6F, 0x63, 0xAC, 0xEB, 0x72, 0x0B, 0x4C, 0x4A, 0x36,
0x3C, 0x60, 0x91, 0x9F, 0x6E, 0x1C, 0x71, 0xEA, 0xD0, 0x78, 0x78, 0xA0, 0x2E, 0xC6, 0x32, 0x6B
}
};
constexpr inline const u8 NxAcidSignatureKeyModulusProd[NxAcidSignatureKeyGenerationMax + 1][NxAcidSignatureKeyModulusSize] = {
{
0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D,
0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50,
0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57,
0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20,
0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21,
0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2,
0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4,
0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE,
0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10,
0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53,
0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD,
0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08,
0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A,
0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA,
0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B,
0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD
},
{
0xE7, 0xAA, 0x25, 0xC8, 0x01, 0xA5, 0x14, 0x6B, 0x01, 0x60, 0x3E, 0xD9, 0x96, 0x5A, 0xBF, 0x90,
0xAC, 0xA7, 0xFD, 0x9B, 0x5B, 0xBD, 0x8A, 0x26, 0xB0, 0xCB, 0x20, 0x28, 0x9A, 0x72, 0x12, 0xF5,
0x20, 0x65, 0xB3, 0xB9, 0x84, 0x58, 0x1F, 0x27, 0xBC, 0x7C, 0xA2, 0xC9, 0x9E, 0x18, 0x95, 0xCF,
0xC2, 0x73, 0x2E, 0x74, 0x8C, 0x66, 0xE5, 0x9E, 0x79, 0x2B, 0xB8, 0x07, 0x0C, 0xB0, 0x4E, 0x8E,
0xAB, 0x85, 0x21, 0x42, 0xC4, 0xC5, 0x6D, 0x88, 0x9C, 0xDB, 0x15, 0x95, 0x3F, 0x80, 0xDB, 0x7A,
0x9A, 0x7D, 0x41, 0x56, 0x25, 0x17, 0x18, 0x42, 0x4D, 0x8C, 0xAC, 0xA5, 0x7B, 0xDB, 0x42, 0x5D,
0x59, 0x35, 0x45, 0x5D, 0x8A, 0x02, 0xB5, 0x70, 0xC0, 0x72, 0x35, 0x46, 0xD0, 0x1D, 0x60, 0x01,
0x4A, 0xCC, 0x1C, 0x46, 0xD3, 0xD6, 0x35, 0x52, 0xD6, 0xE1, 0xF8, 0x3B, 0x5D, 0xEA, 0xDD, 0xB8,
0xFE, 0x7D, 0x50, 0xCB, 0x35, 0x23, 0x67, 0x8B, 0xB6, 0xE4, 0x74, 0xD2, 0x60, 0xFC, 0xFD, 0x43,
0xBF, 0x91, 0x08, 0x81, 0xC5, 0x4F, 0x5D, 0x16, 0x9A, 0xC4, 0x9A, 0xC6, 0xF6, 0xF3, 0xE1, 0xF6,
0x5C, 0x07, 0xAA, 0x71, 0x6C, 0x13, 0xA4, 0xB1, 0xB3, 0x66, 0xBF, 0x90, 0x4C, 0x3D, 0xA2, 0xC4,
0x0B, 0xB8, 0x3D, 0x7A, 0x8C, 0x19, 0xFA, 0xFF, 0x6B, 0xB9, 0x1F, 0x02, 0xCC, 0xB6, 0xD3, 0x0C,
0x7D, 0x19, 0x1F, 0x47, 0xF9, 0xC7, 0x40, 0x01, 0xFA, 0x46, 0xEA, 0x0B, 0xD4, 0x02, 0xE0, 0x3D,
0x30, 0x9A, 0x1A, 0x0F, 0xEA, 0xA7, 0x66, 0x55, 0xF7, 0xCB, 0x28, 0xE2, 0xBB, 0x99, 0xE4, 0x83,
0xC3, 0x43, 0x03, 0xEE, 0xDC, 0x1F, 0x02, 0x23, 0xDD, 0xD1, 0x2D, 0x39, 0xA4, 0x65, 0x75, 0x03,
0xEF, 0x37, 0x9C, 0x06, 0xD6, 0xFA, 0xA1, 0x15, 0xF0, 0xDB, 0x17, 0x47, 0x26, 0x4F, 0x49, 0x03
}
};
static_assert(sizeof(NxAcidSignatureKeyModulusProd) == sizeof(NxAcidSignatureKeyModulusDev));
constexpr inline const u8 AcidSignatureKeyPublicExponent[] = {
0x01, 0x00, 0x01
};
NcaCryptoConfiguration g_nca_crypto_configuration_dev;
NcaCryptoConfiguration g_nca_crypto_configuration_prod;
constexpr inline s32 KeySlotCacheEntryCount = 3;
constinit KeySlotCache g_key_slot_cache;
constinit util::optional<KeySlotCacheEntry> g_key_slot_cache_entry[KeySlotCacheEntryCount];
spl::AccessKey &GetNcaKekAccessKey(s32 key_type) {
AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_nca_kek_access_key_array[KeyAreaEncryptionKeyCount]);
AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_nca_header_kek_access_key);
AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(spl::AccessKey, s_invalid_nca_kek_access_key);
if (key_type > static_cast<s32>(KeyType::NcaHeaderKey2) || IsInvalidKeyTypeValue(key_type)) {
return s_invalid_nca_kek_access_key;
} else if (key_type == static_cast<s32>(KeyType::NcaHeaderKey1) || key_type == static_cast<s32>(KeyType::NcaHeaderKey2)) {
return s_nca_header_kek_access_key;
} else {
return s_nca_kek_access_key_array[key_type];
}
}
void GenerateNcaKey(void *dst, size_t dst_size, const void *src, size_t src_size, s32 key_type) {
R_ABORT_UNLESS(spl::GenerateAesKey(dst, dst_size, GetNcaKekAccessKey(key_type), src, src_size));
}
void ComputeCtr(void *dst, size_t dst_size, int key_slot_idx, const void *src, size_t src_size, const void *iv, size_t iv_size) {
if (dst == src) {
/* If the destination and source are the same, we'll use an intermediate buffer. */
constexpr size_t MinimumSizeToRequireLocking = 256_KB;
constexpr size_t MinimumWorkBufferSize = 16_KB;
/* If the request is large enough, acquire a lock to prevent too many large requests in flight simultaneously. */
static constinit os::SdkMutex s_large_work_buffer_mutex;
util::optional<std::scoped_lock<os::SdkMutex>> lk = util::nullopt;
if (dst_size >= MinimumSizeToRequireLocking) {
lk.emplace(s_large_work_buffer_mutex);
}
/* Allocate a pooled buffer. */
PooledBuffer pooled_buffer;
pooled_buffer.AllocateParticularlyLarge(dst_size, MinimumWorkBufferSize);
/* Copy the iv locally. */
AMS_ASSERT(iv_size == crypto::Aes128CtrEncryptor::IvSize);
u8 work_iv[crypto::Aes128CtrEncryptor::IvSize];
std::memcpy(work_iv, iv, sizeof(work_iv));
/* Process all data. */
size_t processed = 0;
while (processed < dst_size) {
/* Determine the currently processable size. */
const size_t cur_size = std::min<size_t>(dst_size - processed, pooled_buffer.GetSize());
/* Process. */
R_ABORT_UNLESS(spl::ComputeCtr(pooled_buffer.GetBuffer(), cur_size, key_slot_idx, static_cast<const u8 *>(src) + processed, cur_size, work_iv, sizeof(work_iv)));
/* Copy to dst. */
std::memcpy(static_cast<u8 *>(dst) + processed, pooled_buffer.GetBuffer(), cur_size);
/* Advance. */
processed += cur_size;
/* Increment the counter. */
fssystem::AddCounter(work_iv, sizeof(work_iv), cur_size / crypto::Aes128CtrEncryptor::BlockSize);
}
} else {
/* If the destination and source are different, we can just call ComputeCtr directly. */
R_ABORT_UNLESS(spl::ComputeCtr(dst, dst_size, key_slot_idx, src, src_size, iv, iv_size));
}
}
void DecryptAesCtr(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) {
std::unique_ptr<KeySlotCacheAccessor> accessor;
const s32 key_type = GetKeyTypeValue(key_index, key_generation);
R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) {
R_CATCH(fs::ResultTargetNotFound) {
R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type));
R_ABORT_UNLESS(spl::LoadAesKey(accessor->GetKeySlotIndex(), GetNcaKekAccessKey(key_type), enc_key, enc_key_size));
}
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size);
}
void DecryptAesCtrForPreparedKey(void *dst, size_t dst_size, u8 key_index, u8 key_generation, const void *enc_key, size_t enc_key_size, const void *iv, size_t iv_size, const void *src, size_t src_size) {
std::unique_ptr<KeySlotCacheAccessor> accessor;
AMS_UNUSED(key_index, key_generation);
const s32 key_type = static_cast<s32>(KeyType::NcaExternalKey);
R_TRY_CATCH(g_key_slot_cache.Find(std::addressof(accessor), enc_key, enc_key_size, key_type)) {
R_CATCH(fs::ResultTargetNotFound) {
R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), enc_key, enc_key_size, key_type));
spl::AccessKey access_key;
AMS_ABORT_UNLESS(enc_key_size == sizeof(access_key));
std::memcpy(std::addressof(access_key), enc_key, sizeof(access_key));
R_ABORT_UNLESS(spl::LoadPreparedAesKey(accessor->GetKeySlotIndex(), access_key));
}
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
ComputeCtr(dst, dst_size, accessor->GetKeySlotIndex(), src, src_size, iv, iv_size);
}
bool VerifySign1Prod(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation) {
const u8 *mod = g_nca_crypto_configuration_prod.header_1_sign_key_moduli[generation];
const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize;
const u8 *exp = g_nca_crypto_configuration_prod.header_1_sign_key_public_exponent;
const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize;
return crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, data, data_size);
}
bool VerifySign1Dev(const void *sig, size_t sig_size, const void *data, size_t data_size, u8 generation) {
const u8 *mod = g_nca_crypto_configuration_dev.header_1_sign_key_moduli[generation];
const size_t mod_size = NcaCryptoConfiguration::Rsa2048KeyModulusSize;
const u8 *exp = g_nca_crypto_configuration_dev.header_1_sign_key_public_exponent;
const size_t exp_size = NcaCryptoConfiguration::Rsa2048KeyPublicExponentSize;
return crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, data, data_size);
}
}
const ::ams::fssystem::NcaCryptoConfiguration *GetNcaCryptoConfiguration(bool prod) {
/* Decide which configuration to use. */
NcaCryptoConfiguration * const cfg = prod ? std::addressof(g_nca_crypto_configuration_prod) : std::addressof(g_nca_crypto_configuration_dev);
std::memcpy(cfg, fssrv::GetDefaultNcaCryptoConfiguration(prod), sizeof(NcaCryptoConfiguration));
/* Set the key generation functions. */
cfg->generate_key = GenerateNcaKey;
cfg->decrypt_aes_xts_external = nullptr;
cfg->encrypt_aes_xts_external = nullptr;
cfg->decrypt_aes_ctr = DecryptAesCtr;
cfg->decrypt_aes_ctr_external = DecryptAesCtrForPreparedKey;
cfg->verify_sign1 = prod ? VerifySign1Prod : VerifySign1Dev;
cfg->is_plaintext_header_available = !prod;
cfg->is_available_sw_key = true;
/* TODO: Should this default to false for host tools with api to set explicitly? */
#if !defined(ATMOSPHERE_BOARD_NINTENDO_NX)
cfg->is_unsigned_header_available_for_host_tool = true;
#endif
return cfg;
}
void SetUpKekAccessKeys(bool prod) {
/* Get the crypto configuration. */
const NcaCryptoConfiguration *nca_crypto_cfg = GetNcaCryptoConfiguration(prod);
/* Setup the nca keys. */
{
constexpr s32 Option = 0;
/* Setup the key area encryption keys. */
for (u8 i = 0; i < NcaCryptoConfiguration::KeyGenerationMax; ++i) {
spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(0, i))), nca_crypto_cfg->key_area_encryption_key_source[0], KeySize, i, Option);
spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(1, i))), nca_crypto_cfg->key_area_encryption_key_source[1], KeySize, i, Option);
spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(GetKeyTypeValue(2, i))), nca_crypto_cfg->key_area_encryption_key_source[2], KeySize, i, Option);
}
/* Setup the header encryption key. */
R_ABORT_UNLESS(spl::GenerateAesKek(std::addressof(GetNcaKekAccessKey(static_cast<s32>(KeyType::NcaHeaderKey1))), nca_crypto_cfg->header_encryption_key_source, KeySize, 0, Option));
}
/* TODO FS-REIMPL: Save stuff. */
/* Setup the keyslot cache. */
for (s32 i = 0; i < KeySlotCacheEntryCount; i++) {
s32 slot_index = -1;
R_ABORT_UNLESS(spl::AllocateAesKeySlot(std::addressof(slot_index)));
g_key_slot_cache_entry[i].emplace(slot_index);
g_key_slot_cache.AddEntry(std::addressof(g_key_slot_cache_entry[i].value()));
}
}
void InvalidateHardwareAesKey() {
constexpr u8 InvalidKey[KeySize] = {};
for (s32 i = 0; i < KeySlotCacheEntryCount; ++i) {
std::unique_ptr<KeySlotCacheAccessor> accessor;
R_ABORT_UNLESS(g_key_slot_cache.AllocateHighPriority(std::addressof(accessor), InvalidKey, KeySize, -1 - i));
}
}
bool IsValidSignatureKeyGeneration(ncm::ContentMetaPlatform platform, size_t key_generation) {
switch (platform) {
case ncm::ContentMetaPlatform::Nx:
return key_generation <= NxAcidSignatureKeyGenerationMax;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
const u8 *GetAcidSignatureKeyModulus(ncm::ContentMetaPlatform platform, bool prod, size_t key_generation, bool unk_unused) {
AMS_ASSERT(IsValidSignatureKeyGeneration(platform, key_generation));
AMS_UNUSED(unk_unused);
switch (platform) {
case ncm::ContentMetaPlatform::Nx:
{
const size_t used_keygen = (key_generation % (NxAcidSignatureKeyGenerationMax + 1));
return prod ? NxAcidSignatureKeyModulusProd[used_keygen] : NxAcidSignatureKeyModulusDev[used_keygen];
}
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
size_t GetAcidSignatureKeyModulusSize(ncm::ContentMetaPlatform platform, bool unk_unused) {
AMS_UNUSED(unk_unused);
switch (platform) {
case ncm::ContentMetaPlatform::Nx:
return NxAcidSignatureKeyModulusSize;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
const u8 *GetAcidSignatureKeyPublicExponent() {
return AcidSignatureKeyPublicExponent;
}
}

View File

@@ -1,366 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
constexpr size_t IdealWorkBufferSize = 1_MB;
constexpr size_t MinimumWorkBufferSize = 1_KB;
constexpr const fs::Path CommittedDirectoryPath = fs::MakeConstantPath("/0");
constexpr const fs::Path WorkingDirectoryPath = fs::MakeConstantPath("/1");
constexpr const fs::Path SynchronizingDirectoryPath = fs::MakeConstantPath("/_");
constexpr const fs::Path LockFilePath = fs::MakeConstantPath("/.lock");
class DirectorySaveDataFile : public fs::fsa::IFile, public fs::impl::Newable {
private:
std::unique_ptr<fs::fsa::IFile> m_base_file;
DirectorySaveDataFileSystem *m_parent_fs;
fs::OpenMode m_open_mode;
public:
DirectorySaveDataFile(std::unique_ptr<fs::fsa::IFile> f, DirectorySaveDataFileSystem *p, fs::OpenMode m) : m_base_file(std::move(f)), m_parent_fs(p), m_open_mode(m) {
/* ... */
}
virtual ~DirectorySaveDataFile() {
/* Observe closing of writable file. */
if (m_open_mode & fs::OpenMode_Write) {
m_parent_fs->DecrementWriteOpenFileCount();
}
}
public:
virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override {
R_RETURN(m_base_file->Read(out, offset, buffer, size, option));
}
virtual Result DoGetSize(s64 *out) override {
R_RETURN(m_base_file->GetSize(out));
}
virtual Result DoFlush() override {
R_RETURN(m_base_file->Flush());
}
virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override {
R_RETURN(m_base_file->Write(offset, buffer, size, option));
}
virtual Result DoSetSize(s64 size) override {
R_RETURN(m_base_file->SetSize(size));
}
virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
R_RETURN(m_base_file->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
public:
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
return m_base_file->GetDomainObjectId();
}
};
}
Result DirectorySaveDataFileSystem::Initialize(bool journaling_supported, bool multi_commit_supported, bool journaling_enabled) {
/* Configure ourselves. */
m_is_journaling_supported = journaling_supported;
m_is_multi_commit_supported = multi_commit_supported;
m_is_journaling_enabled = journaling_enabled;
/* Ensure that we can initialize further by acquiring a lock on the filesystem. */
R_TRY(this->AcquireLockFile());
fs::DirectoryEntryType type;
/* Check that the working directory exists. */
R_TRY_CATCH(m_base_fs->GetEntryType(std::addressof(type), WorkingDirectoryPath)) {
/* If path isn't found, create working directory and committed directory. */
R_CATCH(fs::ResultPathNotFound) {
R_TRY(m_base_fs->CreateDirectory(WorkingDirectoryPath));
if (m_is_journaling_supported) {
R_TRY(m_base_fs->CreateDirectory(CommittedDirectoryPath));
}
}
} R_END_TRY_CATCH;
/* If we support journaling, we need to set up the committed directory. */
if (m_is_journaling_supported) {
/* Now check for the committed directory. */
R_TRY_CATCH(m_base_fs->GetEntryType(std::addressof(type), CommittedDirectoryPath)) {
/* Committed doesn't exist, so synchronize and rename. */
R_CATCH(fs::ResultPathNotFound) {
R_TRY(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath));
R_TRY(m_base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath));
R_SUCCEED();
}
} R_END_TRY_CATCH;
/* The committed directory exists, so if we should, synchronize it to the working directory. */
if (m_is_journaling_enabled) {
R_TRY(this->SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath));
}
}
R_SUCCEED();
}
Result DirectorySaveDataFileSystem::SynchronizeDirectory(const fs::Path &dst, const fs::Path &src) {
/* Delete destination dir and recreate it. */
R_TRY_CATCH(m_base_fs->DeleteDirectoryRecursively(dst)) {
R_CATCH(fs::ResultPathNotFound) { /* Nintendo returns error unconditionally, but I think that's a bug in their code. */}
} R_END_TRY_CATCH;
R_TRY(m_base_fs->CreateDirectory(dst));
/* Get a work buffer to work with. */
fssystem::PooledBuffer buffer;
buffer.AllocateParticularlyLarge(IdealWorkBufferSize, MinimumWorkBufferSize);
/* Copy the directory recursively. */
fs::DirectoryEntry dir_entry_buffer = {};
R_RETURN(fssystem::CopyDirectoryRecursively(m_base_fs, dst, src, std::addressof(dir_entry_buffer), buffer.GetBuffer(), buffer.GetSize()));
}
Result DirectorySaveDataFileSystem::ResolvePath(fs::Path *out, const fs::Path &path) {
const fs::Path &directory = (m_is_journaling_supported && !m_is_journaling_enabled) ? CommittedDirectoryPath : WorkingDirectoryPath;
R_RETURN(out->Combine(directory, path));
}
Result DirectorySaveDataFileSystem::AcquireLockFile() {
/* If we already have a lock file, we don't need to lock again. */
R_SUCCEED_IF(m_lock_file != nullptr);
/* Open the lock file. */
std::unique_ptr<fs::fsa::IFile> file;
R_TRY_CATCH(m_base_fs->OpenFile(std::addressof(file), LockFilePath, fs::OpenMode_ReadWrite)) {
/* If the lock file doesn't yet exist, we may need to create it. */
R_CATCH(fs::ResultPathNotFound) {
R_TRY(m_base_fs->CreateFile(LockFilePath, 0));
R_TRY(m_base_fs->OpenFile(std::addressof(file), LockFilePath, fs::OpenMode_ReadWrite));
}
} R_END_TRY_CATCH;
/* Set our lock file. */
m_lock_file = std::move(file);
R_SUCCEED();
}
void DirectorySaveDataFileSystem::DecrementWriteOpenFileCount() {
std::scoped_lock lk(m_accessor_mutex);
--m_open_writable_files;
}
Result DirectorySaveDataFileSystem::DoCreateFile(const fs::Path &path, s64 size, int option) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->CreateFile(resolved, size, option));
}
Result DirectorySaveDataFileSystem::DoDeleteFile(const fs::Path &path) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->DeleteFile(resolved));
}
Result DirectorySaveDataFileSystem::DoCreateDirectory(const fs::Path &path) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->CreateDirectory(resolved));
}
Result DirectorySaveDataFileSystem::DoDeleteDirectory(const fs::Path &path) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->DeleteDirectory(resolved));
}
Result DirectorySaveDataFileSystem::DoDeleteDirectoryRecursively(const fs::Path &path) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->DeleteDirectoryRecursively(resolved));
}
Result DirectorySaveDataFileSystem::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) {
/* Resolve the final paths. */
fs::Path old_resolved;
fs::Path new_resolved;
R_TRY(this->ResolvePath(std::addressof(old_resolved), old_path));
R_TRY(this->ResolvePath(std::addressof(new_resolved), new_path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->RenameFile(old_resolved, new_resolved));
}
Result DirectorySaveDataFileSystem::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) {
/* Resolve the final paths. */
fs::Path old_resolved;
fs::Path new_resolved;
R_TRY(this->ResolvePath(std::addressof(old_resolved), old_path));
R_TRY(this->ResolvePath(std::addressof(new_resolved), new_path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->RenameDirectory(old_resolved, new_resolved));
}
Result DirectorySaveDataFileSystem::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->GetEntryType(out, resolved));
}
Result DirectorySaveDataFileSystem::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
/* Open base file. */
std::unique_ptr<fs::fsa::IFile> base_file;
R_TRY(m_base_fs->OpenFile(std::addressof(base_file), resolved, mode));
/* Make DirectorySaveDataFile. */
std::unique_ptr<fs::fsa::IFile> file = std::make_unique<DirectorySaveDataFile>(std::move(base_file), this, mode);
R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInDirectorySaveDataFileSystemA());
/* Increment our open writable files, if the file is writable. */
if (mode & fs::OpenMode_Write) {
++m_open_writable_files;
}
/* Set the output. */
*out_file = std::move(file);
R_SUCCEED();
}
Result DirectorySaveDataFileSystem::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->OpenDirectory(out_dir, resolved, mode));
}
Result DirectorySaveDataFileSystem::DoCommit() {
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
/* If we aren't journaling, we don't need to do anything. */
R_SUCCEED_IF(!m_is_journaling_enabled);
R_SUCCEED_IF(!m_is_journaling_supported);
/* Check that there are no open files blocking the commit. */
R_UNLESS(m_open_writable_files == 0, fs::ResultWriteModeFileNotClosed());
/* Remove the previous commit by renaming the folder. */
R_TRY(fssystem::RetryFinitelyForTargetLocked([&] () ALWAYS_INLINE_LAMBDA { R_RETURN(m_base_fs->RenameDirectory(CommittedDirectoryPath, SynchronizingDirectoryPath)); }));
/* Synchronize the working directory to the synchronizing directory. */
R_TRY(fssystem::RetryFinitelyForTargetLocked([&] () ALWAYS_INLINE_LAMBDA { R_RETURN(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath)); }));
/* Rename the synchronized directory to commit it. */
R_TRY(fssystem::RetryFinitelyForTargetLocked([&] () ALWAYS_INLINE_LAMBDA { R_RETURN(m_base_fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath)); }));
R_SUCCEED();
}
Result DirectorySaveDataFileSystem::DoGetFreeSpaceSize(s64 *out, const fs::Path &path) {
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
/* Get the free space size in our working directory. */
AMS_UNUSED(path);
R_RETURN(m_base_fs->GetFreeSpaceSize(out, WorkingDirectoryPath));
}
Result DirectorySaveDataFileSystem::DoGetTotalSpaceSize(s64 *out, const fs::Path &path) {
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
/* Get the free space size in our working directory. */
AMS_UNUSED(path);
R_RETURN(m_base_fs->GetTotalSpaceSize(out, WorkingDirectoryPath));
}
Result DirectorySaveDataFileSystem::DoCleanDirectoryRecursively(const fs::Path &path) {
/* Resolve the final path. */
fs::Path resolved;
R_TRY(this->ResolvePath(std::addressof(resolved), path));
/* Lock ourselves. */
std::scoped_lock lk(m_accessor_mutex);
R_RETURN(m_base_fs->CleanDirectoryRecursively(resolved));
}
Result DirectorySaveDataFileSystem::DoCommitProvisionally(s64 counter) {
/* Check that we support multi-commit. */
R_UNLESS(m_is_multi_commit_supported, fs::ResultUnsupportedCommitProvisionallyForDirectorySaveDataFileSystem());
/* Do nothing. */
AMS_UNUSED(counter);
R_SUCCEED();
}
Result DirectorySaveDataFileSystem::DoRollback() {
/* On non-journaled savedata, there's nothing to roll back to. */
R_SUCCEED_IF(!m_is_journaling_supported);
/* Perform a re-initialize. */
R_RETURN(this->Initialize(m_is_journaling_supported, m_is_multi_commit_supported, m_is_journaling_enabled));
}
}

View File

@@ -1,93 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
#if defined(ATMOSPHERE_BOARD_NINTENDO_NX)
namespace {
constexpr inline size_t MaxExternalCodeFileSystem = 0x10;
util::BoundedMap<ncm::ProgramId, fs::RemoteFileSystem, MaxExternalCodeFileSystem> g_ecs_map;
util::BoundedMap<ncm::ProgramId, os::NativeHandle, MaxExternalCodeFileSystem> g_hnd_map;
}
fs::fsa::IFileSystem *GetExternalCodeFileSystem(ncm::ProgramId program_id) {
/* Return a fs from the map if one exists. */
if (auto *fs = g_ecs_map.Find(program_id); fs != nullptr) {
return fs;
}
/* Otherwise, we may have a handle. */
if (auto *hnd = g_hnd_map.Find(program_id); hnd != nullptr) {
/* Create a service using libnx bindings. */
Service srv;
serviceCreate(std::addressof(srv), *hnd);
g_hnd_map.Remove(program_id);
/* Create a remote filesystem. */
const FsFileSystem fs = { srv };
g_ecs_map.Emplace(program_id, fs);
/* Return the created filesystem. */
return g_ecs_map.Find(program_id);
}
/* Otherwise, we have no filesystem. */
return nullptr;
}
Result CreateExternalCode(os::NativeHandle *out, ncm::ProgramId program_id) {
/* Create a handle pair. */
os::NativeHandle server, client;
R_TRY(svc::CreateSession(std::addressof(server), std::addressof(client), false, 0));
/* Insert the handle into the map. */
g_hnd_map.Emplace(program_id, client);
*out = server;
R_SUCCEED();
}
void DestroyExternalCode(ncm::ProgramId program_id) {
g_ecs_map.Remove(program_id);
if (auto *hnd = g_hnd_map.Find(program_id); hnd != nullptr) {
os::CloseNativeHandle(*hnd);
g_hnd_map.Remove(program_id);
}
}
#else
fs::fsa::IFileSystem *GetExternalCodeFileSystem(ncm::ProgramId program_id) {
AMS_UNUSED(program_id);
return nullptr;
}
Result CreateExternalCode(os::NativeHandle *out, ncm::ProgramId program_id) {
AMS_UNUSED(out, program_id);
R_THROW(fs::ResultNotImplemented());
}
void DestroyExternalCode(ncm::ProgramId program_id) {
AMS_UNUSED(program_id);
}
#endif
}

View File

@@ -1,287 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "../fssrv/impl/fssrv_program_registry_manager.hpp"
namespace ams::fssystem {
/* TODO: All of this should really be inside fs process, but ams.mitm wants it to. */
/* How should we handle this? */
namespace {
constexpr inline auto FileSystemProxyServerThreadCount = fssrv::FileSystemProxyServerActiveSessionCount;
/* TODO: Heap sizes need to match FS, when this is FS in master rather than ams.mitm. */
/* Official FS has a 4.5 MB exp heap, a 6 MB buffer pool, an 8 MB device buffer manager heap, and a 14 MB buffer manager heap. */
/* We don't need so much memory for ams.mitm (as we're servicing a much more limited context). */
/* We'll give ourselves a 1.5 MB exp heap, a 1 MB buffer pool, a 1 MB device buffer manager heap, and a 1 MB buffer manager heap. */
/* These numbers are 1 MB less than signed-system-partition safe FS in all pools. */
constexpr size_t ExpHeapSize = 1_MB + 512_KB;
constexpr size_t BufferPoolSize = 1_MB;
constexpr size_t DeviceBufferSize = 1_MB;
constexpr size_t BufferManagerHeapSize = 1_MB;
constexpr size_t MaxCacheCount = 1024;
constexpr size_t BlockSize = 16_KB;
alignas(os::MemoryPageSize) constinit u8 g_exp_heap_buffer[ExpHeapSize];
constinit lmem::HeapHandle g_exp_heap_handle = nullptr;
constinit fssrv::PeakCheckableMemoryResourceFromExpHeap g_exp_allocator(ExpHeapSize);
void InitializeExpHeap() {
if (g_exp_heap_handle == nullptr) {
g_exp_heap_handle = lmem::CreateExpHeap(g_exp_heap_buffer, ExpHeapSize, lmem::CreateOption_ThreadSafe);
AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr);
g_exp_allocator.SetHeapHandle(g_exp_heap_handle);
}
}
void *AllocateForFileSystemProxy(size_t size) {
AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr);
auto scoped_lock = g_exp_allocator.GetScopedLock();
void *p = lmem::AllocateFromExpHeap(g_exp_heap_handle, size);
g_exp_allocator.OnAllocate(p, size);
return p;
}
void DeallocateForFileSystemProxy(void *p, size_t size) {
AMS_ABORT_UNLESS(g_exp_heap_handle != nullptr);
auto scoped_lock = g_exp_allocator.GetScopedLock();
g_exp_allocator.OnDeallocate(p, size);
lmem::FreeToExpHeap(g_exp_heap_handle, p);
}
alignas(os::MemoryPageSize) constinit u8 g_device_buffer[DeviceBufferSize] = {};
alignas(os::MemoryPageSize) constinit u8 g_buffer_pool[BufferPoolSize] = {};
constinit util::TypedStorage<mem::StandardAllocator> g_buffer_allocator = {};
constinit util::TypedStorage<fssrv::MemoryResourceFromStandardAllocator> g_allocator = {};
/* TODO: Nintendo uses os::SetMemoryHeapSize (svc::SetHeapSize) and os::AllocateMemoryBlock for the BufferManager heap. */
/* It's unclear how we should handle this in ams.mitm (especially hoping to reuse some logic for fs reimpl). */
/* Should we be doing the same(?) */
constinit util::TypedStorage<fssystem::FileSystemBufferManager> g_buffer_manager = {};
alignas(os::MemoryPageSize) constinit u8 g_buffer_manager_heap[BufferManagerHeapSize] = {};
/* FileSystem creators. */
constinit util::TypedStorage<fssrv::fscreator::RomFileSystemCreator> g_rom_fs_creator = {};
constinit util::TypedStorage<fssrv::fscreator::PartitionFileSystemCreator> g_partition_fs_creator = {};
constinit util::TypedStorage<fssrv::fscreator::StorageOnNcaCreator> g_storage_on_nca_creator = {};
constinit fssrv::fscreator::FileSystemCreatorInterfaces g_fs_creator_interfaces = {};
}
void InitializeForFileSystemProxy() {
/* TODO FS-REIMPL: Setup MainThreadStackUsageReporter. */
/* Register service context for main thread. */
fssystem::ServiceContext context;
fssystem::RegisterServiceContext(std::addressof(context));
/* Initialize spl library. */
spl::InitializeForFs();
/* TODO FS-REIMPL: spl::SetIsAvailableAccessKeyHandler(fssrv::IsAvailableAccessKey) */
/* Determine whether we're prod or dev. */
bool is_prod = !spl::IsDevelopment();
bool is_development_function_enabled = spl::IsDevelopmentFunctionEnabled();
/* Set debug flags. */
fssrv::SetDebugFlagEnabled(is_development_function_enabled);
/* Setup our crypto configuration. */
SetUpKekAccessKeys(is_prod);
/* Setup our heap. */
InitializeExpHeap();
/* Initialize buffer allocator. */
util::ConstructAt(g_buffer_allocator, g_buffer_pool, BufferPoolSize);
util::ConstructAt(g_allocator, GetPointer(g_buffer_allocator));
/* Set allocators. */
/* TODO FS-REIMPL: sf::SetGlobalDefaultMemoryResource() */
fs::SetAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy);
fssystem::InitializeAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy);
fssystem::InitializeAllocatorForSystem(AllocateForFileSystemProxy, DeallocateForFileSystemProxy);
/* Initialize the buffer manager. */
/* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */
util::ConstructAt(g_buffer_manager);
GetReference(g_buffer_manager).Initialize(MaxCacheCount, reinterpret_cast<uintptr_t>(g_buffer_manager_heap), BufferManagerHeapSize, BlockSize);
/* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */
/* TODO FS-REIMPL: fssrv::storage::CreateDeviceAddressSpace(...); */
fssystem::InitializeBufferPool(reinterpret_cast<char *>(g_device_buffer), DeviceBufferSize);
/* TODO FS-REIMPL: Create Pooled Threads/Stack Usage Reporter, fssystem::RegisterThreadPool. */
/* TODO FS-REIMPL: fssrv::GetFileSystemProxyServices(), some service creation. */
/* Initialize fs creators. */
/* TODO FS-REIMPL: Revise for accuracy. */
util::ConstructAt(g_rom_fs_creator, GetPointer(g_allocator));
util::ConstructAt(g_partition_fs_creator);
util::ConstructAt(g_storage_on_nca_creator, GetPointer(g_allocator), *GetNcaCryptoConfiguration(is_prod), *GetNcaCompressionConfiguration(), GetPointer(g_buffer_manager), fs::impl::GetNcaHashGeneratorFactorySelector());
/* TODO FS-REIMPL: Initialize other creators. */
g_fs_creator_interfaces = {
.rom_fs_creator = GetPointer(g_rom_fs_creator),
.partition_fs_creator = GetPointer(g_partition_fs_creator),
.storage_on_nca_creator = GetPointer(g_storage_on_nca_creator),
};
/* TODO FS-REIMPL: Revise above for latest firmware, all the new Services creation. */
fssrv::ProgramRegistryServiceImpl program_registry_service(fssrv::ProgramRegistryServiceImpl::Configuration{});
fssrv::ProgramRegistryImpl::Initialize(std::addressof(program_registry_service));
/* TODO FS-REIMPL: Memory Report Creators, fssrv::SetMemoryReportCreator */
/* TODO FS-REIMPL: Sd Card detection, speed emulation. */
/* Initialize fssrv. TODO FS-REIMPL: More arguments, more actions taken. */
const fssrv::FileSystemProxyConfiguration config = {
.m_fs_creator_interfaces = std::addressof(g_fs_creator_interfaces),
.m_base_storage_service_impl = nullptr /* TODO */,
.m_base_file_system_service_impl = nullptr /* TODO */,
.m_nca_file_system_service_impl = nullptr /* TODO */,
.m_save_data_file_system_service_impl = nullptr /* TODO */,
.m_access_failure_management_service_impl = nullptr /* TODO */,
.m_time_service_impl = nullptr /* TODO */,
.m_status_report_service_impl = nullptr /* TODO */,
.m_program_registry_service_impl = std::addressof(program_registry_service),
.m_access_log_service_impl = nullptr /* TODO */,
.m_debug_configuration_service_impl = nullptr /* TODO */,
};
fssrv::InitializeForFileSystemProxy(config);
/* TODO FS-REIMPL: GetFileSystemProxyServiceObject(), set current process, initialize global service object. */
/* Disable auto-abort in fs library code. */
fs::SetEnabledAutoAbort(false);
/* Initialize fsp server. */
fssrv::InitializeFileSystemProxyServer(FileSystemProxyServerThreadCount);
/* TODO FS-REIMPL: Cleanup calls. */
/* TODO FS-REIMPL: Spawn worker threads. */
/* TODO FS-REIMPL: Set mmc devices ready. */
/* TODO FS-REIMPL: fssrv::LoopPmEventServer(...); */
/* TODO FS-REIMPL: Wait/destroy threads. */
/* TODO FS-REIMPL: spl::Finalize(); */
}
void InitializeForAtmosphereMitm() {
/* Initialize spl library. */
spl::InitializeForFs();
/* TODO FS-REIMPL: spl::SetIsAvailableAccessKeyHandler(fssrv::IsAvailableAccessKey) */
/* Determine whether we're prod or dev. */
bool is_prod = !spl::IsDevelopment();
bool is_development_function_enabled = spl::IsDevelopmentFunctionEnabled();
/* Set debug flags. */
fssrv::SetDebugFlagEnabled(is_development_function_enabled);
/* Setup our crypto configuration. */
SetUpKekAccessKeys(is_prod);
/* Setup our heap. */
InitializeExpHeap();
/* Initialize buffer allocator. */
util::ConstructAt(g_buffer_allocator, g_buffer_pool, BufferPoolSize);
util::ConstructAt(g_allocator, GetPointer(g_buffer_allocator));
/* Set allocators. */
/* TODO FS-REIMPL: sf::SetGlobalDefaultMemoryResource() */
fs::SetAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy);
fssystem::InitializeAllocator(AllocateForFileSystemProxy, DeallocateForFileSystemProxy);
fssystem::InitializeAllocatorForSystem(AllocateForFileSystemProxy, DeallocateForFileSystemProxy);
/* Initialize the buffer manager. */
/* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */
util::ConstructAt(g_buffer_manager);
GetReference(g_buffer_manager).Initialize(MaxCacheCount, reinterpret_cast<uintptr_t>(g_buffer_manager_heap), BufferManagerHeapSize, BlockSize);
/* TODO FS-REIMPL: os::AllocateMemoryBlock(...); */
/* TODO FS-REIMPL: fssrv::storage::CreateDeviceAddressSpace(...); */
fssystem::InitializeBufferPool(reinterpret_cast<char *>(g_device_buffer), DeviceBufferSize);
/* TODO FS-REIMPL: Create Pooled Threads/Stack Usage Reporter, fssystem::RegisterThreadPool. */
/* TODO FS-REIMPL: fssrv::GetFileSystemProxyServices(), some service creation. */
/* Initialize fs creators. */
/* TODO FS-REIMPL: Revise for accuracy. */
util::ConstructAt(g_rom_fs_creator, GetPointer(g_allocator));
util::ConstructAt(g_partition_fs_creator);
util::ConstructAt(g_storage_on_nca_creator, GetPointer(g_allocator), *GetNcaCryptoConfiguration(is_prod), *GetNcaCompressionConfiguration(), GetPointer(g_buffer_manager), fs::impl::GetNcaHashGeneratorFactorySelector());
/* TODO FS-REIMPL: Initialize other creators. */
g_fs_creator_interfaces = {
.rom_fs_creator = GetPointer(g_rom_fs_creator),
.partition_fs_creator = GetPointer(g_partition_fs_creator),
.storage_on_nca_creator = GetPointer(g_storage_on_nca_creator),
};
/* Initialize fssrv. TODO FS-REIMPL: More arguments, more actions taken. */
const fssrv::FileSystemProxyConfiguration config = {
.m_fs_creator_interfaces = std::addressof(g_fs_creator_interfaces),
.m_base_storage_service_impl = nullptr /* TODO */,
.m_base_file_system_service_impl = nullptr /* TODO */,
.m_nca_file_system_service_impl = nullptr /* TODO */,
.m_save_data_file_system_service_impl = nullptr /* TODO */,
.m_access_failure_management_service_impl = nullptr /* TODO */,
.m_time_service_impl = nullptr /* TODO */,
.m_status_report_service_impl = nullptr /* TODO */,
.m_program_registry_service_impl = nullptr /* TODO */,
.m_access_log_service_impl = nullptr /* TODO */,
.m_debug_configuration_service_impl = nullptr /* TODO */,
};
fssrv::InitializeForFileSystemProxy(config);
/* Disable auto-abort in fs library code. */
fs::SetEnabledAutoAbort(false);
/* Quick sanity check, before we leave. */
#if defined(ATMOSPHERE_OS_HORIZON)
AMS_ABORT_UNLESS(os::GetCurrentProgramId() == ncm::AtmosphereProgramId::Mitm);
#endif
}
const ::ams::fssrv::fscreator::FileSystemCreatorInterfaces *GetFileSystemCreatorInterfaces() {
return std::addressof(g_fs_creator_interfaces);
}
}

View File

@@ -1,354 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
constexpr inline u32 IntegrityVerificationStorageMagic = util::FourCC<'I','V','F','C'>::Code;
constexpr inline u32 IntegrityVerificationStorageVersion = 0x00020000;
constexpr inline u32 IntegrityVerificationStorageVersionMask = 0xFFFF0000;
constexpr inline auto AccessCountMax = 5;
constexpr inline auto AccessTimeout = TimeSpan::FromMilliSeconds(10);
os::Semaphore g_read_semaphore(AccessCountMax, AccessCountMax);
os::Semaphore g_write_semaphore(AccessCountMax, AccessCountMax);
constexpr inline const char MasterKey[] = "HierarchicalIntegrityVerificationStorage::Master";
constexpr inline const char L1Key[] = "HierarchicalIntegrityVerificationStorage::L1";
constexpr inline const char L2Key[] = "HierarchicalIntegrityVerificationStorage::L2";
constexpr inline const char L3Key[] = "HierarchicalIntegrityVerificationStorage::L3";
constexpr inline const char L4Key[] = "HierarchicalIntegrityVerificationStorage::L4";
constexpr inline const char L5Key[] = "HierarchicalIntegrityVerificationStorage::L5";
constexpr inline const struct {
const char *key;
size_t size;
} KeyArray[] = {
{ MasterKey, sizeof(MasterKey) },
{ L1Key, sizeof(L1Key) },
{ L2Key, sizeof(L2Key) },
{ L3Key, sizeof(L3Key) },
{ L4Key, sizeof(L4Key) },
{ L5Key, sizeof(L5Key) },
};
}
/* Instantiate the global random generation function. */
constinit HierarchicalIntegrityVerificationStorage::GenerateRandomFunction HierarchicalIntegrityVerificationStorage::s_generate_random = nullptr;
Result HierarchicalIntegrityVerificationStorageControlArea::QuerySize(HierarchicalIntegrityVerificationSizeSet *out, const InputParam &input_param, s32 layer_count, s64 data_size) {
/* Validate preconditions. */
AMS_ASSERT(out != nullptr);
AMS_ASSERT((static_cast<s32>(IntegrityMinLayerCount) <= layer_count) && (layer_count <= static_cast<s32>(IntegrityMaxLayerCount)));
for (s32 level = 0; level < (layer_count - 1); ++level) {
AMS_ASSERT(input_param.level_block_size[level] > 0);
AMS_ASSERT(IsPowerOfTwo(static_cast<s32>(input_param.level_block_size[level])));
}
/* Set the control size. */
out->control_size = sizeof(HierarchicalIntegrityVerificationMetaInformation);
/* Determine the level sizes. */
s64 level_size[IntegrityMaxLayerCount];
s32 level = layer_count - 1;
level_size[level] = util::AlignUp(data_size, input_param.level_block_size[level - 1]);
--level;
for (/* ... */; level > 0; --level) {
level_size[level] = util::AlignUp(level_size[level + 1] / input_param.level_block_size[level] * HashSize, input_param.level_block_size[level - 1]);
}
/* Determine the master size. */
level_size[0] = level_size[1] / input_param.level_block_size[0] * HashSize;
/* Set the master size. */
out->master_hash_size = level_size[0];
/* Set the level sizes. */
for (level = 1; level < layer_count - 1; ++level) {
out->layered_hash_sizes[level - 1] = level_size[level];
}
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorageControlArea::Expand(fs::SubStorage meta_storage, const HierarchicalIntegrityVerificationMetaInformation &meta) {
/* Check the meta size. */
{
s64 meta_size = 0;
R_TRY(meta_storage.GetSize(std::addressof(meta_size)));
R_UNLESS(meta_size >= static_cast<s64>(sizeof(meta)), fs::ResultInvalidSize());
}
/* Validate both the previous and new metas. */
{
/* Read the previous meta. */
HierarchicalIntegrityVerificationMetaInformation prev_meta = {};
R_TRY(meta_storage.Read(0, std::addressof(prev_meta), sizeof(prev_meta)));
/* Validate both magics. */
R_UNLESS(prev_meta.magic == IntegrityVerificationStorageMagic, fs::ResultIncorrectIntegrityVerificationMagic());
R_UNLESS(prev_meta.magic == meta.magic, fs::ResultIncorrectIntegrityVerificationMagic());
/* Validate both versions. */
R_UNLESS(prev_meta.version == IntegrityVerificationStorageVersion, fs::ResultUnsupportedVersion());
R_UNLESS(prev_meta.version == meta.version, fs::ResultUnsupportedVersion());
}
/* Write the new meta. */
R_TRY(meta_storage.Write(0, std::addressof(meta), sizeof(meta)));
R_TRY(meta_storage.Flush());
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorageControlArea::Initialize(fs::SubStorage meta_storage) {
/* Check the meta size. */
{
s64 meta_size = 0;
R_TRY(meta_storage.GetSize(std::addressof(meta_size)));
R_UNLESS(meta_size >= static_cast<s64>(sizeof(m_meta)), fs::ResultInvalidSize());
}
/* Set the storage and read the meta. */
m_storage = meta_storage;
R_TRY(m_storage.Read(0, std::addressof(m_meta), sizeof(m_meta)));
/* Validate the meta magic. */
R_UNLESS(m_meta.magic == IntegrityVerificationStorageMagic, fs::ResultIncorrectIntegrityVerificationMagic());
/* Validate the meta version. */
R_UNLESS((m_meta.version & IntegrityVerificationStorageVersionMask) == (IntegrityVerificationStorageVersion & IntegrityVerificationStorageVersionMask), fs::ResultUnsupportedVersion());
R_SUCCEED();
}
void HierarchicalIntegrityVerificationStorageControlArea::Finalize() {
m_storage = fs::SubStorage();
}
Result HierarchicalIntegrityVerificationStorage::Initialize(const HierarchicalIntegrityVerificationInformation &info, HierarchicalStorageInformation storage, FileSystemBufferManagerSet *bufs, IHash256GeneratorFactory *hgf, bool hash_salt_enabled, os::SdkRecursiveMutex *mtx, os::Semaphore *read_sema, os::Semaphore *write_sema, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, bool is_writable, bool allow_cleared_blocks) {
/* Validate preconditions. */
AMS_ASSERT(bufs != nullptr);
AMS_ASSERT(IntegrityMinLayerCount <= info.max_layers && info.max_layers <= IntegrityMaxLayerCount);
/* Set member variables. */
m_max_layers = info.max_layers;
m_buffers = bufs;
m_mutex = mtx;
m_read_semaphore = read_sema;
m_write_semaphore = write_sema;
/* If hash salt is enabled, generate it. */
util::optional<fs::HashSalt> hash_salt = util::nullopt;
if (hash_salt_enabled) {
hash_salt.emplace();
crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[0].key, KeyArray[0].size);
}
/* Initialize the top level verification storage. */
m_verify_storages[0].Initialize(storage[HierarchicalStorageInformation::MasterStorage], storage[HierarchicalStorageInformation::Layer1Storage], static_cast<s64>(1) << info.info[0].block_order, HashSize, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks);
/* Ensure we don't leak state if further initialization goes wrong. */
ON_RESULT_FAILURE {
m_verify_storages[0].Finalize();
m_data_size = -1;
m_buffers = nullptr;
m_mutex = nullptr;
};
/* Initialize the top level buffer storage. */
R_TRY(m_buffer_storages[0].Initialize(m_buffers->buffers[0], m_mutex, std::addressof(m_verify_storages[0]), info.info[0].size, static_cast<s64>(1) << info.info[0].block_order, max_hash_cache_entries, false, 0x10, false, is_writable));
ON_RESULT_FAILURE_2 { m_buffer_storages[0].Finalize(); };
/* Prepare to initialize the level storages. */
s32 level = 0;
/* Ensure we don't leak state if further initialization goes wrong. */
ON_RESULT_FAILURE_2 {
m_verify_storages[level + 1].Finalize();
for (/* ... */; level > 0; --level) {
m_buffer_storages[level].Finalize();
m_verify_storages[level].Finalize();
}
};
/* Initialize the level storages. */
for (/* ... */; level < m_max_layers - 3; ++level) {
/* If hash salt is enabled, generate it. */
util::optional<fs::HashSalt> hash_salt = util::nullopt;
if (hash_salt_enabled) {
hash_salt.emplace();
crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size);
}
/* Initialize the verification storage. */
fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size);
m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, false, is_writable, allow_cleared_blocks);
/* Initialize the buffer storage. */
R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_hash_cache_entries, false, 0x11 + static_cast<s8>(level), false, is_writable));
}
/* Initialize the final level storage. */
{
/* If hash salt is enabled, generate it. */
util::optional<fs::HashSalt> hash_salt = util::nullopt;
if (hash_salt_enabled) {
hash_salt.emplace();
crypto::GenerateHmacSha256(hash_salt->value, sizeof(hash_salt->value), info.seed.value, sizeof(info.seed), KeyArray[level + 1].key, KeyArray[level + 1].size);
}
/* Initialize the verification storage. */
fs::SubStorage buffer_storage(std::addressof(m_buffer_storages[level]), 0, info.info[level].size);
m_verify_storages[level + 1].Initialize(buffer_storage, storage[level + 2], static_cast<s64>(1) << info.info[level + 1].block_order, static_cast<s64>(1) << info.info[level].block_order, m_buffers->buffers[m_max_layers - 2], hgf, hash_salt, true, is_writable, allow_cleared_blocks);
/* Initialize the buffer storage. */
R_TRY(m_buffer_storages[level + 1].Initialize(m_buffers->buffers[level + 1], m_mutex, std::addressof(m_verify_storages[level + 1]), info.info[level + 1].size, static_cast<s64>(1) << info.info[level + 1].block_order, max_data_cache_entries, true, buffer_level, true, is_writable));
}
/* Set the data size. */
m_data_size = info.info[level + 1].size;
/* We succeeded. */
R_SUCCEED();
}
void HierarchicalIntegrityVerificationStorage::Finalize() {
if (m_data_size >= 0) {
m_data_size = 0;
m_buffers = nullptr;
m_mutex = nullptr;
for (s32 level = m_max_layers - 2; level >= 0; --level) {
m_buffer_storages[level].Finalize();
m_verify_storages[level].Finalize();
}
m_data_size = -1;
}
}
Result HierarchicalIntegrityVerificationStorage::Read(s64 offset, void *buffer, size_t size) {
/* Validate preconditions. */
AMS_ASSERT(m_data_size >= 0);
/* Succeed if zero-size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* If we have a read semaphore, acquire it. */
if (m_read_semaphore != nullptr) { m_read_semaphore->Acquire(); }
ON_SCOPE_EXIT { if (m_read_semaphore != nullptr) { m_read_semaphore->Release(); } };
/* Acquire access to the global read semaphore. */
if (!g_read_semaphore.TimedAcquire(AccessTimeout)) {
for (auto level = m_max_layers - 2; level >= 0; --level) {
R_TRY(m_buffer_storages[level].Flush());
}
g_read_semaphore.Acquire();
}
/* Ensure that we release the semaphore when done. */
ON_SCOPE_EXIT { g_read_semaphore.Release(); };
/* Read the data. */
R_RETURN(m_buffer_storages[m_max_layers - 2].Read(offset, buffer, size));
}
Result HierarchicalIntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) {
/* Validate preconditions. */
AMS_ASSERT(m_data_size >= 0);
/* Succeed if zero-size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* If we have a write semaphore, acquire it. */
if (m_write_semaphore != nullptr) { m_write_semaphore->Acquire(); }
ON_SCOPE_EXIT { if (m_write_semaphore != nullptr) { m_write_semaphore->Release(); } };
/* Acquire access to the write semaphore. */
if (!g_write_semaphore.TimedAcquire(AccessTimeout)) {
for (auto level = m_max_layers - 2; level >= 0; --level) {
R_TRY(m_buffer_storages[level].Flush());
}
g_write_semaphore.Acquire();
}
/* Ensure that we release the semaphore when done. */
ON_SCOPE_EXIT { g_write_semaphore.Release(); };
/* Write the data. */
R_RETURN(m_buffer_storages[m_max_layers - 2].Write(offset, buffer, size));
}
Result HierarchicalIntegrityVerificationStorage::GetSize(s64 *out) {
AMS_ASSERT(out != nullptr);
AMS_ASSERT(m_data_size >= 0);
*out = m_data_size;
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorage::Flush() {
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
switch (op_id) {
case fs::OperationId::FillZero:
case fs::OperationId::DestroySignature:
{
R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_SUCCEED();
}
case fs::OperationId::Invalidate:
case fs::OperationId::QueryRange:
{
R_TRY(m_buffer_storages[m_max_layers - 2].OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
R_SUCCEED();
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForHierarchicalIntegrityVerificationStorage());
}
}
Result HierarchicalIntegrityVerificationStorage::Commit() {
for (s32 level = m_max_layers - 2; level >= 0; --level) {
R_TRY(m_buffer_storages[level].Commit());
}
R_SUCCEED();
}
Result HierarchicalIntegrityVerificationStorage::OnRollback() {
for (s32 level = m_max_layers - 2; level >= 0; --level) {
R_TRY(m_buffer_storages[level].OnRollback());
}
R_SUCCEED();
}
}

View File

@@ -1,197 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fssystem_hierarchical_sha256_storage.hpp"
namespace ams::fssystem {
namespace {
s32 Log2(s32 value) {
AMS_ASSERT(value > 0);
AMS_ASSERT(util::IsPowerOfTwo(value));
s32 log = 0;
while ((value >>= 1) > 0) {
++log;
}
return log;
}
}
template<typename BaseStorageType>
Result HierarchicalSha256Storage<BaseStorageType>::Initialize(BaseStorageType *base_storages, s32 layer_count, size_t htbs, void *hash_buf, size_t hash_buf_size, fssystem::IHash256GeneratorFactory *hgf) {
/* Validate preconditions. */
AMS_ASSERT(layer_count == LayerCount);
AMS_ASSERT(util::IsPowerOfTwo(htbs));
AMS_ASSERT(hash_buf != nullptr);
AMS_ASSERT(hgf != nullptr);
AMS_UNUSED(layer_count);
/* Set size tracking members. */
m_hash_target_block_size = htbs;
m_log_size_ratio = Log2(m_hash_target_block_size / HashSize);
m_hash_generator_factory = hgf;
/* Get the base storage size. */
R_TRY(base_storages[2]->GetSize(std::addressof(m_base_storage_size)));
{
auto size_guard = SCOPE_GUARD { m_base_storage_size = 0; };
R_UNLESS(m_base_storage_size <= static_cast<s64>(HashSize) << m_log_size_ratio << m_log_size_ratio, fs::ResultHierarchicalSha256BaseStorageTooLarge());
size_guard.Cancel();
}
/* Set hash buffer tracking members. */
m_base_storage = base_storages[2];
m_hash_buffer = static_cast<char *>(hash_buf);
m_hash_buffer_size = hash_buf_size;
/* Read the master hash. */
u8 master_hash[HashSize];
R_TRY(base_storages[0]->Read(0, master_hash, HashSize));
/* Read and validate the data being hashed. */
s64 hash_storage_size;
R_TRY(base_storages[1]->GetSize(std::addressof(hash_storage_size)));
AMS_ASSERT(util::IsAligned(hash_storage_size, HashSize));
AMS_ASSERT(hash_storage_size <= m_hash_target_block_size);
AMS_ASSERT(hash_storage_size <= static_cast<s64>(m_hash_buffer_size));
R_TRY(base_storages[1]->Read(0, m_hash_buffer, static_cast<size_t>(hash_storage_size)));
/* Calculate and verify the master hash. */
u8 calc_hash[HashSize];
m_hash_generator_factory->GenerateHash(calc_hash, sizeof(calc_hash), m_hash_buffer, static_cast<size_t>(hash_storage_size));
R_UNLESS(crypto::IsSameBytes(master_hash, calc_hash, HashSize), fs::ResultHierarchicalSha256HashVerificationFailed());
R_SUCCEED();
}
template<typename BaseStorageType>
Result HierarchicalSha256Storage<BaseStorageType>::Read(s64 offset, void *buffer, size_t size) {
/* Succeed if zero-size. */
R_SUCCEED_IF(size == 0);
/* Validate that we have a buffer to read into. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Validate preconditions. */
R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument());
/* Read the data. */
const size_t reduced_size = static_cast<size_t>(std::min<s64>(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset);
R_TRY(m_base_storage->Read(offset, buffer, reduced_size));
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Setup tracking variables. */
auto cur_offset = offset;
auto remaining_size = reduced_size;
while (remaining_size > 0) {
/* Generate the hash of the region we're validating. */
u8 hash[HashSize];
const auto cur_size = static_cast<size_t>(std::min<s64>(m_hash_target_block_size, remaining_size));
m_hash_generator_factory->GenerateHash(hash, sizeof(hash), static_cast<u8 *>(buffer) + (cur_offset - offset), cur_size);
AMS_ASSERT(static_cast<size_t>(cur_offset >> m_log_size_ratio) < m_hash_buffer_size);
/* Check the hash. */
{
std::scoped_lock lk(m_mutex);
auto clear_guard = SCOPE_GUARD { std::memset(buffer, 0, size); };
R_UNLESS(crypto::IsSameBytes(hash, std::addressof(m_hash_buffer[cur_offset >> m_log_size_ratio]), HashSize), fs::ResultHierarchicalSha256HashVerificationFailed());
clear_guard.Cancel();
}
/* Advance. */
cur_offset += cur_size;
remaining_size -= cur_size;
}
R_SUCCEED();
}
template<typename BaseStorageType>
Result HierarchicalSha256Storage<BaseStorageType>::Write(s64 offset, const void *buffer, size_t size) {
/* Succeed if zero-size. */
R_SUCCEED_IF(size == 0);
/* Validate that we have a buffer to read into. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Validate preconditions. */
R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument());
/* Setup tracking variables. */
const size_t reduced_size = static_cast<size_t>(std::min<s64>(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset);
auto cur_offset = offset;
auto remaining_size = reduced_size;
while (remaining_size > 0) {
/* Generate the hash of the region we're validating. */
u8 hash[HashSize];
const auto cur_size = static_cast<size_t>(std::min<s64>(m_hash_target_block_size, remaining_size));
{
/* Temporarily increase our thread priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
m_hash_generator_factory->GenerateHash(hash, sizeof(hash), static_cast<const u8 *>(buffer) + (cur_offset - offset), cur_size);
}
/* Write the data. */
R_TRY(m_base_storage->Write(cur_offset, static_cast<const u8 *>(buffer) + (cur_offset - offset), cur_size));
/* Write the hash. */
{
std::scoped_lock lk(m_mutex);
std::memcpy(std::addressof(m_hash_buffer[cur_offset >> m_log_size_ratio]), hash, HashSize);
}
/* Advance. */
cur_offset += cur_size;
remaining_size -= cur_size;
}
R_SUCCEED();
}
template<typename BaseStorageType>
Result HierarchicalSha256Storage<BaseStorageType>::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
if (op_id == fs::OperationId::Invalidate) {
R_RETURN(m_base_storage->OperateRange(fs::OperationId::Invalidate, offset, size));
} else {
/* Succeed if zero-size. */
R_SUCCEED_IF(size == 0);
/* Validate preconditions. */
R_UNLESS(util::IsAligned(offset, m_hash_target_block_size), fs::ResultInvalidArgument());
R_UNLESS(util::IsAligned(size, m_hash_target_block_size), fs::ResultInvalidArgument());
/* Determine size to use. */
const auto reduced_size = std::min<s64>(m_base_storage_size, util::AlignUp(offset + size, m_hash_target_block_size)) - offset;
/* Operate on the base storage. */
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, reduced_size, src, src_size));
}
}
template class HierarchicalSha256Storage<fs::SubStorage>;
}

View File

@@ -1,60 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::fssystem {
template<typename BaseStorageType>
class HierarchicalSha256Storage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(HierarchicalSha256Storage);
NON_MOVEABLE(HierarchicalSha256Storage);
public:
static constexpr s32 LayerCount = 3;
static constexpr size_t HashSize = crypto::Sha256Generator::HashSize;
private:
BaseStorageType m_base_storage;
s64 m_base_storage_size;
char *m_hash_buffer;
size_t m_hash_buffer_size;
s32 m_hash_target_block_size;
s32 m_log_size_ratio;
fssystem::IHash256GeneratorFactory *m_hash_generator_factory;
os::SdkMutex m_mutex;
public:
HierarchicalSha256Storage() : m_mutex() { /* ... */ }
Result Initialize(BaseStorageType *base_storages, s32 layer_count, size_t htbs, void *hash_buf, size_t hash_buf_size, fssystem::IHash256GeneratorFactory *hgf);
virtual Result Read(s64 offset, void *buffer, size_t size) override;
virtual Result Write(s64 offset, const void *buffer, size_t size) override;
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override;
virtual Result GetSize(s64 *out) override {
R_RETURN(m_base_storage->GetSize(out));
}
virtual Result Flush() override {
R_RETURN(m_base_storage->Flush());
}
virtual Result SetSize(s64 size) override {
AMS_UNUSED(size);
R_THROW(fs::ResultUnsupportedSetSizeForHierarchicalSha256Storage());
}
};
}

View File

@@ -1,185 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
Result IndirectStorage::Initialize(IAllocator *allocator, fs::SubStorage table_storage) {
/* Read and verify the bucket tree header. */
BucketTree::Header header;
R_TRY(table_storage.Read(0, std::addressof(header), sizeof(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(allocator, fs::SubStorage(std::addressof(table_storage), node_storage_offset, node_storage_size), fs::SubStorage(std::addressof(table_storage), entry_storage_offset, entry_storage_size), header.entry_count));
}
void IndirectStorage::Finalize() {
if (this->IsInitialized()) {
m_table.Finalize();
for (auto i = 0; i < StorageCount; i++) {
m_data_storage[i] = fs::SubStorage();
}
}
}
Result IndirectStorage::GetEntryList(Entry *out_entries, s32 *out_entry_count, s32 entry_count, s64 offset, s64 size) {
/* Validate pre-conditions. */
AMS_ASSERT(offset >= 0);
AMS_ASSERT(size >= 0);
AMS_ASSERT(this->IsInitialized());
/* Clear the out count. */
R_UNLESS(out_entry_count != nullptr, fs::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, fs::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), fs::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), fs::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();
}
Result IndirectStorage::Read(s64 offset, void *buffer, size_t size) {
/* Validate pre-conditions. */
AMS_ASSERT(offset >= 0);
AMS_ASSERT(this->IsInitialized());
/* Succeed if there's nothing to read. */
R_SUCCEED_IF(size == 0);
/* Ensure that we have a buffer to read to. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
R_TRY((this->OperatePerEntry<true, true>(offset, size, [=](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
R_TRY(storage->Read(data_offset, reinterpret_cast<u8 *>(buffer) + (cur_offset - offset), static_cast<size_t>(cur_size)));
R_SUCCEED();
})));
R_SUCCEED();
}
Result IndirectStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* Validate pre-conditions. */
AMS_ASSERT(offset >= 0);
AMS_ASSERT(size >= 0);
AMS_ASSERT(this->IsInitialized());
switch (op_id) {
case fs::OperationId::Invalidate:
{
if (!m_table.IsEmpty()) {
/* Invalidate our table's cache. */
R_TRY(m_table.InvalidateCache());
/* Invalidate our storages. */
for (auto &storage : m_data_storage) {
R_TRY(storage.OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max()));
}
}
R_SUCCEED();
}
case fs::OperationId::QueryRange:
{
/* Validate that we have an output range info. */
R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
R_UNLESS(dst_size == sizeof(fs::QueryRangeInfo), fs::ResultInvalidSize());
if (size > 0) {
/* Validate arguments. */
BucketTree::Offsets table_offsets;
R_TRY(m_table.GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange());
if (!m_table.IsEmpty()) {
/* Create a new info. */
fs::QueryRangeInfo merged_info;
merged_info.Clear();
/* Operate on our entries. */
R_TRY((this->OperatePerEntry<false, true>(offset, size, [=, &merged_info](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
AMS_UNUSED(cur_offset);
fs::QueryRangeInfo cur_info;
R_TRY(storage->OperateRange(std::addressof(cur_info), sizeof(cur_info), op_id, data_offset, cur_size, src, src_size));
merged_info.Merge(cur_info);
R_SUCCEED();
})));
/* Write the merged info. */
*reinterpret_cast<fs::QueryRangeInfo *>(dst) = merged_info;
}
}
R_SUCCEED();
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForIndirectStorage());
}
R_SUCCEED();
}
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
Result IntegrityRomFsStorage::Initialize(HierarchicalIntegrityVerificationInformation level_hash_info, Hash master_hash, HierarchicalIntegrityVerificationStorage::HierarchicalStorageInformation storage_info, fs::IBufferManager *bm, int max_data_cache_entries, int max_hash_cache_entries, s8 buffer_level, IHash256GeneratorFactory *hgf) {
/* Validate preconditions. */
AMS_ASSERT(bm != nullptr);
/* Set master hash. */
m_master_hash = master_hash;
m_master_hash_storage = std::make_unique<fs::MemoryStorage>(std::addressof(m_master_hash), sizeof(Hash));
R_UNLESS(m_master_hash_storage != nullptr, fs::ResultAllocationMemoryFailedInIntegrityRomFsStorageA());
/* Set the master hash storage. */
storage_info[0] = fs::SubStorage(m_master_hash_storage.get(), 0, sizeof(Hash));
/* Set buffers. */
for (size_t i = 0; i < util::size(m_buffers.buffers); ++i) {
m_buffers.buffers[i] = bm;
}
/* Initialize our integrity storage. */
R_RETURN(m_integrity_storage.Initialize(level_hash_info, storage_info, std::addressof(m_buffers), hgf, false, std::addressof(m_mutex), max_data_cache_entries, max_hash_cache_entries, buffer_level, false, false));
}
void IntegrityRomFsStorage::Finalize() {
m_integrity_storage.Finalize();
}
}

View File

@@ -1,500 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
void IntegrityVerificationStorage::Initialize(fs::SubStorage hs, fs::SubStorage ds, s64 verif_block_size, s64 upper_layer_verif_block_size, fs::IBufferManager *bm, fssystem::IHash256GeneratorFactory *hgf, const util::optional<fs::HashSalt> &salt, bool is_real_data, bool is_writable, bool allow_cleared_blocks) {
/* Validate preconditions. */
AMS_ASSERT(verif_block_size >= HashSize);
AMS_ASSERT(bm != nullptr);
AMS_ASSERT(hgf != nullptr);
/* Set storages. */
m_hash_storage = hs;
m_data_storage = ds;
/* Set hash generator factory. */
m_hash_generator_factory = hgf;
/* Set verification block sizes. */
m_verification_block_size = verif_block_size;
m_verification_block_order = ILog2(static_cast<u32>(verif_block_size));
AMS_ASSERT(m_verification_block_size == (1l << m_verification_block_order));
/* Set buffer manager. */
m_buffer_manager = bm;
/* Set upper layer block sizes. */
upper_layer_verif_block_size = std::max(upper_layer_verif_block_size, HashSize);
m_upper_layer_verification_block_size = upper_layer_verif_block_size;
m_upper_layer_verification_block_order = ILog2(static_cast<u32>(upper_layer_verif_block_size));
AMS_ASSERT(m_upper_layer_verification_block_size == (1l << m_upper_layer_verification_block_order));
/* Validate sizes. */
{
s64 hash_size = 0;
s64 data_size = 0;
AMS_ASSERT(R_SUCCEEDED(m_hash_storage.GetSize(std::addressof(hash_size))));
AMS_ASSERT(R_SUCCEEDED(m_data_storage.GetSize(std::addressof(data_size))));
AMS_ASSERT(((hash_size / HashSize) * m_verification_block_size) >= data_size);
AMS_UNUSED(hash_size, data_size);
}
/* Set salt. */
m_salt = salt;
/* Set data, writable, and allow cleared. */
m_is_real_data = is_real_data;
m_is_writable = is_writable;
m_allow_cleared_blocks = allow_cleared_blocks;
}
void IntegrityVerificationStorage::Finalize() {
if (m_buffer_manager != nullptr) {
m_hash_storage = fs::SubStorage();
m_data_storage = fs::SubStorage();
m_buffer_manager = nullptr;
}
}
Result IntegrityVerificationStorage::Read(s64 offset, void *buffer, size_t size) {
/* Although we support zero-size reads, we expect non-zero sizes. */
AMS_ASSERT(size != 0);
/* Validate other preconditions. */
AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size)));
AMS_ASSERT(util::IsAligned(size, static_cast<size_t>(m_verification_block_size)));
/* Succeed if zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Validate the offset. */
s64 data_size;
R_TRY(m_data_storage.GetSize(std::addressof(data_size)));
R_UNLESS(offset <= data_size, fs::ResultInvalidOffset());
/* Validate the access range. */
R_TRY(IStorage::CheckAccessRange(offset, size, util::AlignUp(data_size, static_cast<size_t>(m_verification_block_size))));
/* Determine the read extents. */
size_t read_size = size;
if (static_cast<s64>(offset + read_size) > data_size) {
/* Determine the padding sizes. */
s64 padding_offset = data_size - offset;
size_t padding_size = static_cast<size_t>(m_verification_block_size - (padding_offset & (m_verification_block_size - 1)));
AMS_ASSERT(static_cast<s64>(padding_size) < m_verification_block_size);
/* Clear the padding. */
std::memset(static_cast<u8 *>(buffer) + padding_offset, 0, padding_size);
/* Set the new in-bounds size. */
read_size = static_cast<size_t>(data_size - offset);
}
/* Perform the read. */
{
auto clear_guard = SCOPE_GUARD { std::memset(buffer, 0, size); };
R_TRY(m_data_storage.Read(offset, buffer, read_size));
clear_guard.Cancel();
}
/* Verify the signatures. */
Result verify_hash_result = ResultSuccess();
/* Create hash generator. */
std::unique_ptr<IHash256Generator> generator = nullptr;
R_TRY(m_hash_generator_factory->Create(std::addressof(generator)));
/* Prepare to validate the signatures. */
const auto signature_count = size >> m_verification_block_order;
PooledBuffer signature_buffer(signature_count * sizeof(BlockHash), sizeof(BlockHash));
const auto buffer_count = std::min(signature_count, signature_buffer.GetSize() / sizeof(BlockHash));
size_t verified_count = 0;
while (verified_count < signature_count) {
/* Read the current signatures. */
const auto cur_count = std::min(buffer_count, signature_count - verified_count);
auto cur_result = this->ReadBlockSignature(signature_buffer.GetBuffer(), signature_buffer.GetSize(), offset + (verified_count << m_verification_block_order), cur_count << m_verification_block_order);
/* Temporarily increase our priority. */
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
/* Loop over each signature we read. */
for (size_t i = 0; i < cur_count && R_SUCCEEDED(cur_result); ++i) {
const auto verified_size = (verified_count + i) << m_verification_block_order;
u8 *cur_buf = static_cast<u8 *>(buffer) + verified_size;
cur_result = this->VerifyHash(cur_buf, reinterpret_cast<BlockHash *>(signature_buffer.GetBuffer()) + i, generator);
/* If the data is corrupted, clear the corrupted parts. */
if (fs::ResultIntegrityVerificationStorageCorrupted::Includes(cur_result)) {
std::memset(cur_buf, 0, m_verification_block_size);
/* Set the result if we should. */
if (!fs::ResultClearedRealDataVerificationFailed::Includes(cur_result) && !m_allow_cleared_blocks) {
verify_hash_result = cur_result;
}
cur_result = ResultSuccess();
}
}
/* If we failed, clear and return. */
if (R_FAILED(cur_result)) {
std::memset(buffer, 0, size);
R_THROW(cur_result);
}
/* Advance. */
verified_count += cur_count;
}
R_RETURN(verify_hash_result);
}
Result IntegrityVerificationStorage::Write(s64 offset, const void *buffer, size_t size) {
/* Succeed if zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
/* Check the offset/size. */
R_TRY(IStorage::CheckOffsetAndSize(offset, size));
/* Validate the offset. */
s64 data_size;
R_TRY(m_data_storage.GetSize(std::addressof(data_size)));
R_UNLESS(offset < data_size, fs::ResultInvalidOffset());
/* Validate the access range. */
R_TRY(IStorage::CheckAccessRange(offset, size, util::AlignUp(data_size, static_cast<size_t>(m_verification_block_size))));
/* Validate preconditions. */
AMS_ASSERT(util::IsAligned(offset, m_verification_block_size));
AMS_ASSERT(util::IsAligned(size, m_verification_block_size));
AMS_ASSERT(offset <= data_size);
AMS_ASSERT(static_cast<s64>(offset + size) < data_size + m_verification_block_size);
/* Validate that if writing past the end, all extra data is zero padding. */
if (static_cast<s64>(offset + size) > data_size) {
const u8 *padding_cur = static_cast<const u8 *>(buffer) + data_size - offset;
const u8 *padding_end = padding_cur + (offset + size - data_size);
while (padding_cur < padding_end) {
AMS_ASSERT((*padding_cur) == 0);
++padding_cur;
}
}
/* Determine the unpadded size to write. */
auto write_size = size;
if (static_cast<s64>(offset + write_size) > data_size) {
write_size = static_cast<size_t>(data_size - offset);
R_SUCCEED_IF(write_size == 0);
}
/* Determine the size we're writing in blocks. */
const auto aligned_write_size = util::AlignUp(write_size, m_verification_block_size);
/* Write the updated block signatures. */
Result update_result = ResultSuccess();
size_t updated_count = 0;
{
const auto signature_count = aligned_write_size >> m_verification_block_order;
PooledBuffer signature_buffer(signature_count * sizeof(BlockHash), sizeof(BlockHash));
const auto buffer_count = std::min(signature_count, signature_buffer.GetSize() / sizeof(BlockHash));
/* Create hash generator. */
std::unique_ptr<IHash256Generator> generator = nullptr;
R_TRY(m_hash_generator_factory->Create(std::addressof(generator)));
while (updated_count < signature_count) {
const auto cur_count = std::min(buffer_count, signature_count - updated_count);
/* Calculate the hash with temporarily increased priority. */
{
ScopedThreadPriorityChanger cp(+1, ScopedThreadPriorityChanger::Mode::Relative);
for (size_t i = 0; i < cur_count; ++i) {
const auto updated_size = (updated_count + i) << m_verification_block_order;
this->CalcBlockHash(reinterpret_cast<BlockHash *>(signature_buffer.GetBuffer()) + i, reinterpret_cast<const u8 *>(buffer) + updated_size, generator);
}
}
/* Write the new block signatures. */
if (R_FAILED((update_result = this->WriteBlockSignature(signature_buffer.GetBuffer(), signature_buffer.GetSize(), offset + (updated_count << m_verification_block_order), cur_count << m_verification_block_order)))) {
break;
}
/* Advance. */
updated_count += cur_count;
}
}
/* Write the data. */
R_TRY(m_data_storage.Write(offset, buffer, std::min(write_size, updated_count << m_verification_block_order)));
R_RETURN(update_result);
}
Result IntegrityVerificationStorage::GetSize(s64 *out) {
R_RETURN(m_data_storage.GetSize(out));
}
Result IntegrityVerificationStorage::Flush() {
/* Flush both storages. */
R_TRY(m_hash_storage.Flush());
R_TRY(m_data_storage.Flush());
R_SUCCEED();
}
Result IntegrityVerificationStorage::OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) {
/* Validate preconditions. */
if (op_id != fs::OperationId::Invalidate) {
AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size)));
AMS_ASSERT(util::IsAligned(size, static_cast<size_t>(m_verification_block_size)));
}
switch (op_id) {
case fs::OperationId::FillZero:
{
/* FillZero should only be called for writable storages. */
AMS_ASSERT(m_is_writable);
/* Validate the range. */
s64 data_size = 0;
R_TRY(m_data_storage.GetSize(std::addressof(data_size)));
R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset());
/* Determine the extents to clear. */
const auto sign_offset = (offset >> m_verification_block_order) * HashSize;
const auto sign_size = (std::min(size, data_size - offset) >> m_verification_block_order) * HashSize;
/* Allocate a work buffer. */
const auto buf_size = static_cast<size_t>(std::min(sign_size, static_cast<s64>(1) << (m_upper_layer_verification_block_order + 2)));
std::unique_ptr<char[], fs::impl::Deleter> buf = fs::impl::MakeUnique<char[]>(buf_size);
R_UNLESS(buf != nullptr, fs::ResultAllocationMemoryFailedInIntegrityVerificationStorageA());
/* Clear the work buffer. */
std::memset(buf.get(), 0, buf_size);
/* Clear in chunks. */
auto remaining_size = sign_size;
while (remaining_size > 0) {
const auto cur_size = static_cast<size_t>(std::min(remaining_size, static_cast<s64>(buf_size)));
R_TRY(m_hash_storage.Write(sign_offset + sign_size - remaining_size, buf.get(), cur_size));
remaining_size -= cur_size;
}
R_SUCCEED();
}
case fs::OperationId::DestroySignature:
{
/* DestroySignature should only be called for save data. */
AMS_ASSERT(m_is_writable);
/* Validate the range. */
s64 data_size = 0;
R_TRY(m_data_storage.GetSize(std::addressof(data_size)));
R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset());
/* Determine the extents to clear the signature for. */
const auto sign_offset = (offset >> m_verification_block_order) * HashSize;
const auto sign_size = (std::min(size, data_size - offset) >> m_verification_block_order) * HashSize;
/* Allocate a work buffer. */
std::unique_ptr<char[], fs::impl::Deleter> buf = fs::impl::MakeUnique<char[]>(sign_size);
R_UNLESS(buf != nullptr, fs::ResultAllocationMemoryFailedInIntegrityVerificationStorageB());
/* Read the existing signature. */
R_TRY(m_hash_storage.Read(sign_offset, buf.get(), sign_size));
/* Clear the signature. */
/* This flips all bits other than the verification bit. */
for (auto i = 0; i < sign_size; ++i) {
buf[i] ^= ((i + 1) % HashSize == 0 ? 0x7F : 0xFF);
}
/* Write the cleared signature. */
R_RETURN(m_hash_storage.Write(sign_offset, buf.get(), sign_size));
}
case fs::OperationId::Invalidate:
{
/* Only allow cache invalidation read-only storages. */
R_UNLESS(!m_is_writable, fs::ResultUnsupportedOperateRangeForWritableIntegrityVerificationStorage());
/* Operate on our storages. */
R_TRY(m_hash_storage.OperateRange(op_id, 0, std::numeric_limits<s64>::max()));
R_TRY(m_data_storage.OperateRange(op_id, offset, size));
R_SUCCEED();
}
case fs::OperationId::QueryRange:
{
/* Validate the range. */
s64 data_size = 0;
R_TRY(m_data_storage.GetSize(std::addressof(data_size)));
R_UNLESS(0 <= offset && offset <= data_size, fs::ResultInvalidOffset());
/* Determine the real size to query. */
const auto actual_size = std::min(size, data_size - offset);
/* Query the data storage. */
R_RETURN(m_data_storage.OperateRange(dst, dst_size, op_id, offset, actual_size, src, src_size));
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForIntegrityVerificationStorage());
}
}
void IntegrityVerificationStorage::CalcBlockHash(BlockHash *out, const void *buffer, size_t block_size, std::unique_ptr<fssystem::IHash256Generator> &generator) const {
/* Hash procedure depends on whether or not we're writable. */
if (m_is_writable) {
/* Compute the hash with or without the hash salt, if we have one. */
if (m_salt.has_value()) {
/* Initialize the generator. */
generator->Initialize();
/* Hash the salt. */
generator->Update(m_salt->value, sizeof(m_salt->value));
/* Update with the buffer and get the hash. */
generator->Update(buffer, block_size);
generator->GetHash(out, sizeof(*out));
} else {
/* If we have no hash salt, just calculate the hash. */
m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size);
}
/* Set the validation bit. */
SetValidationBit(out);
} else {
/* If we're not writable, just calculate the hash. */
m_hash_generator_factory->GenerateHash(out, sizeof(*out), buffer, block_size);
}
}
Result IntegrityVerificationStorage::ReadBlockSignature(void *dst, size_t dst_size, s64 offset, size_t size) {
/* Validate preconditions. */
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size)));
AMS_ASSERT(util::IsAligned(size, static_cast<size_t>(m_verification_block_size)));
/* Determine where to read the signature. */
const s64 sign_offset = (offset >> m_verification_block_order) * HashSize;
const auto sign_size = static_cast<size_t>((size >> m_verification_block_order) * HashSize);
AMS_ASSERT(dst_size >= sign_size);
AMS_UNUSED(dst_size);
/* Create a guard in the event of failure. */
auto clear_guard = SCOPE_GUARD { std::memset(dst, 0, sign_size); };
/* Validate that we can read the signature. */
s64 hash_size;
R_TRY(m_hash_storage.GetSize(std::addressof(hash_size)));
const bool range_valid = static_cast<s64>(sign_offset + sign_size) <= hash_size;
AMS_ASSERT(range_valid);
R_UNLESS(range_valid, fs::ResultOutOfRange());
/* Read the signature. */
R_TRY(m_hash_storage.Read(sign_offset, dst, sign_size));
/* We succeeded. */
clear_guard.Cancel();
R_SUCCEED();
}
Result IntegrityVerificationStorage::WriteBlockSignature(const void *src, size_t src_size, s64 offset, size_t size) {
/* Validate preconditions. */
AMS_ASSERT(src != nullptr);
AMS_ASSERT(util::IsAligned(offset, static_cast<size_t>(m_verification_block_size)));
/* Determine where to write the signature. */
const s64 sign_offset = (offset >> m_verification_block_order) * HashSize;
const auto sign_size = static_cast<size_t>((size >> m_verification_block_order) * HashSize);
AMS_ASSERT(src_size >= sign_size);
AMS_UNUSED(src_size);
/* Write the signature. */
R_TRY(m_hash_storage.Write(sign_offset, src, sign_size));
/* We succeeded. */
R_SUCCEED();
}
Result IntegrityVerificationStorage::VerifyHash(const void *buf, BlockHash *hash, std::unique_ptr<fssystem::IHash256Generator> &generator) {
/* Validate preconditions. */
AMS_ASSERT(buf != nullptr);
AMS_ASSERT(hash != nullptr);
/* Get the comparison hash. */
auto &cmp_hash = *hash;
/* If writable, check if the data is uninitialized. */
if (m_is_writable) {
bool is_cleared = false;
R_TRY(this->IsCleared(std::addressof(is_cleared), cmp_hash));
R_UNLESS(!is_cleared, fs::ResultClearedRealDataVerificationFailed());
}
/* Get the calculated hash. */
BlockHash calc_hash;
this->CalcBlockHash(std::addressof(calc_hash), buf, generator);
/* Check that the signatures are equal. */
if (!crypto::IsSameBytes(std::addressof(cmp_hash), std::addressof(calc_hash), sizeof(BlockHash))) {
/* Clear the comparison hash. */
std::memset(std::addressof(cmp_hash), 0, sizeof(cmp_hash));
/* Return the appropriate result. */
if (m_is_real_data) {
R_THROW(fs::ResultUnclearedRealDataVerificationFailed());
} else {
R_THROW(fs::ResultNonRealDataVerificationFailed());
}
}
R_SUCCEED();
}
Result IntegrityVerificationStorage::IsCleared(bool *is_cleared, const BlockHash &hash) {
/* Validate preconditions. */
AMS_ASSERT(is_cleared != nullptr);
AMS_ASSERT(m_is_writable);
/* Default to uncleared. */
*is_cleared = false;
/* Succeed if the validation bit is set. */
R_SUCCEED_IF(IsValidationBit(std::addressof(hash)));
/* Otherwise, we expect the hash to be all zero. */
for (size_t i = 0; i < sizeof(hash.hash); ++i) {
R_UNLESS(hash.hash[i] == 0, fs::ResultInvalidZeroHash());
}
/* Set cleared. */
*is_cleared = true;
R_SUCCEED();
}
}

View File

@@ -1,135 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::fssystem {
class KeySlotCacheAccessor : public ::ams::fs::impl::Newable {
NON_COPYABLE(KeySlotCacheAccessor);
NON_MOVEABLE(KeySlotCacheAccessor);
private:
util::unique_lock<os::SdkMutex> m_lk;
const s32 m_slot_index;
public:
KeySlotCacheAccessor(s32 idx, util::unique_lock<os::SdkMutex> &&l) : m_lk(std::move(l)), m_slot_index(idx) { /* ... */ }
s32 GetKeySlotIndex() const { return m_slot_index; }
};
class KeySlotCacheEntry : public util::IntrusiveListBaseNode<KeySlotCacheEntry> {
NON_COPYABLE(KeySlotCacheEntry);
NON_MOVEABLE(KeySlotCacheEntry);
public:
static constexpr size_t KeySize = crypto::AesDecryptor128::KeySize;
private:
const s32 m_slot_index;
u8 m_key1[KeySize];
s32 m_key2;
public:
explicit KeySlotCacheEntry(s32 idx) : m_slot_index(idx), m_key2(-1) {
std::memset(m_key1, 0, sizeof(m_key1));
}
bool Contains(const void *key, size_t key_size, s32 key2) const {
AMS_ASSERT(key_size == KeySize);
AMS_UNUSED(key_size);
return key2 == m_key2 && std::memcmp(m_key1, key, KeySize) == 0;
}
s32 GetKeySlotIndex() const { return m_slot_index; }
void SetKey(const void *key, size_t key_size, s32 key2) {
AMS_ASSERT(key_size == KeySize);
std::memcpy(m_key1, key, key_size);
m_key2 = key2;
}
};
class KeySlotCache {
NON_COPYABLE(KeySlotCache);
NON_MOVEABLE(KeySlotCache);
private:
using KeySlotCacheEntryList = util::IntrusiveListBaseTraits<KeySlotCacheEntry>::ListType;
private:
os::SdkMutex m_mutex;
KeySlotCacheEntryList m_high_priority_mru_list;
KeySlotCacheEntryList m_low_priority_mru_list;
public:
constexpr KeySlotCache() : m_mutex(), m_high_priority_mru_list(), m_low_priority_mru_list() { /* ... */ }
Result AllocateHighPriority(std::unique_ptr<KeySlotCacheAccessor> *out, const void *key, size_t key_size, s32 key2) {
R_RETURN(this->AllocateFromLru(out, m_high_priority_mru_list, key, key_size, key2));
}
Result AllocateLowPriority(std::unique_ptr<KeySlotCacheAccessor> *out, const void *key, size_t key_size, s32 key2) {
R_RETURN(this->AllocateFromLru(out, m_high_priority_mru_list, key, key_size, key2));
}
Result Find(std::unique_ptr<KeySlotCacheAccessor> *out, const void *key, size_t key_size, s32 key2) {
util::unique_lock lk(m_mutex);
KeySlotCacheEntryList *lists[2] = { std::addressof(m_high_priority_mru_list), std::addressof(m_low_priority_mru_list) };
for (auto list : lists) {
for (auto it = list->begin(); it != list->end(); ++it) {
if (it->Contains(key, key_size, key2)) {
std::unique_ptr accessor = std::make_unique<KeySlotCacheAccessor>(it->GetKeySlotIndex(), std::move(lk));
R_UNLESS(accessor != nullptr, fs::ResultAllocationMemoryFailed());
*out = std::move(accessor);
this->UpdateMru(list, it);
R_SUCCEED();
}
}
}
R_THROW(fs::ResultTargetNotFound());
}
void AddEntry(KeySlotCacheEntry *entry) {
util::unique_lock lk(m_mutex);
m_low_priority_mru_list.push_front(*entry);
}
private:
Result AllocateFromLru(std::unique_ptr<KeySlotCacheAccessor> *out, KeySlotCacheEntryList &dst_list, const void *key, size_t key_size, s32 key2) {
util::unique_lock lk(m_mutex);
KeySlotCacheEntryList &src_list = m_low_priority_mru_list.empty() ? m_high_priority_mru_list : m_low_priority_mru_list;
AMS_ASSERT(!src_list.empty());
auto it = src_list.rbegin();
std::unique_ptr accessor = std::make_unique<KeySlotCacheAccessor>(it->GetKeySlotIndex(), std::move(lk));
*out = std::move(accessor);
it->SetKey(key, key_size, key2);
auto *entry = std::addressof(*it);
src_list.pop_back();
dst_list.push_front(*entry);
R_SUCCEED();
}
void UpdateMru(KeySlotCacheEntryList *list, KeySlotCacheEntryList::iterator it) {
auto *entry = std::addressof(*it);
list->erase(it);
list->push_front(*entry);
}
};
}

View File

@@ -1,89 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::fssystem {
template<typename Key, typename Value>
class LruListCache {
NON_COPYABLE(LruListCache);
NON_MOVEABLE(LruListCache);
public:
class Node : public ::ams::fs::impl::Newable {
NON_COPYABLE(Node);
NON_MOVEABLE(Node);
public:
Key m_key;
Value m_value;
util::IntrusiveListNode m_mru_list_node;
public:
explicit Node(const Value &value) : m_value(value) { /* ... */ }
};
private:
using MruList = typename util::IntrusiveListMemberTraits<&Node::m_mru_list_node>::ListType;
private:
MruList m_mru_list;
public:
constexpr LruListCache() : m_mru_list() { /* ... */ }
bool FindValueAndUpdateMru(Value *out, const Key &key) {
for (auto it = m_mru_list.begin(); it != m_mru_list.end(); ++it) {
if (it->m_key == key) {
*out = it->m_value;
m_mru_list.erase(it);
m_mru_list.push_front(*it);
return true;
}
}
return false;
}
std::unique_ptr<Node> PopLruNode() {
AMS_ABORT_UNLESS(!m_mru_list.empty());
Node *lru = std::addressof(*m_mru_list.rbegin());
m_mru_list.pop_back();
return std::unique_ptr<Node>(lru);
}
void PushMruNode(std::unique_ptr<Node> &&node, const Key &key) {
node->m_key = key;
m_mru_list.push_front(*node);
node.release();
}
void DeleteAllNodes() {
while (!m_mru_list.empty()) {
Node *lru = std::addressof(*m_mru_list.rbegin());
m_mru_list.erase(m_mru_list.iterator_to(*lru));
delete lru;
}
}
size_t GetSize() const {
return m_mru_list.size();
}
bool IsEmpty() const {
return m_mru_list.empty();
}
};
}

View File

@@ -1,87 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::fssystem {
class MemoryResourceBufferHoldStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(MemoryResourceBufferHoldStorage);
NON_MOVEABLE(MemoryResourceBufferHoldStorage);
private:
std::shared_ptr<fs::IStorage> m_storage;
MemoryResource *m_memory_resource;
void *m_buffer;
size_t m_buffer_size;
public:
MemoryResourceBufferHoldStorage(std::shared_ptr<fs::IStorage> storage, MemoryResource *mr, size_t buffer_size) : m_storage(std::move(storage)), m_memory_resource(mr), m_buffer(m_memory_resource->Allocate(buffer_size)), m_buffer_size(buffer_size) {
/* ... */
}
virtual ~MemoryResourceBufferHoldStorage() {
/* If we have a buffer, deallocate it. */
if (m_buffer != nullptr) {
m_memory_resource->Deallocate(m_buffer, m_buffer_size);
}
}
ALWAYS_INLINE bool IsValid() const { return m_buffer != nullptr; }
ALWAYS_INLINE void *GetBuffer() const { return m_buffer; }
public:
virtual Result Read(s64 offset, void *buffer, size_t size) override {
/* Check pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
R_RETURN(m_storage->Read(offset, buffer, size));
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
/* Check pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
R_RETURN(m_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
virtual Result GetSize(s64 *out) override {
/* Check pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
R_RETURN(m_storage->GetSize(out));
}
virtual Result Flush() override {
/* Check pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
R_RETURN(m_storage->Flush());
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
/* Check pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
R_RETURN(m_storage->Write(offset, buffer, size));
}
virtual Result SetSize(s64 size) override {
/* Check pre-conditions. */
AMS_ASSERT(m_storage != nullptr);
R_RETURN(m_storage->SetSize(size));
}
};
}

View File

@@ -1,32 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
u8 NcaHeader::GetProperKeyGeneration() const {
return std::max(this->key_generation, this->key_generation_2);
}
bool NcaPatchInfo::HasIndirectTable() const {
return this->indirect_size != 0;
}
bool NcaPatchInfo::HasAesCtrExTable() const {
return this->aes_ctr_ex_size != 0;
}
}

View File

@@ -1,582 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
constexpr inline u32 SdkAddonVersionMin = 0x000B0000;
constexpr Result CheckNcaMagic(u32 magic) {
/* Verify the magic is not a deprecated one. */
R_UNLESS(magic != NcaHeader::Magic0, fs::ResultUnsupportedSdkVersion());
R_UNLESS(magic != NcaHeader::Magic1, fs::ResultUnsupportedSdkVersion());
R_UNLESS(magic != NcaHeader::Magic2, fs::ResultUnsupportedSdkVersion());
/* Verify the magic is the current one. */
R_UNLESS(magic == NcaHeader::Magic3, fs::ResultInvalidNcaSignature());
R_SUCCEED();
}
}
NcaReader::NcaReader() : m_body_storage(), m_header_storage(), m_decrypt_aes_ctr(), m_decrypt_aes_ctr_external(), m_is_software_aes_prioritized(false), m_is_available_sw_key(false), m_header_encryption_type(NcaHeader::EncryptionType::Auto), m_get_decompressor(), m_hash_generator_factory_selector() {
std::memset(std::addressof(m_header), 0, sizeof(m_header));
std::memset(std::addressof(m_decryption_keys), 0, sizeof(m_decryption_keys));
std::memset(std::addressof(m_external_decryption_key), 0, sizeof(m_external_decryption_key));
}
NcaReader::~NcaReader() {
/* ... */
}
Result NcaReader::Initialize(std::shared_ptr<fs::IStorage> base_storage, const NcaCryptoConfiguration &crypto_cfg, const NcaCompressionConfiguration &compression_cfg, IHash256GeneratorFactorySelector *hgf_selector) {
/* Validate preconditions. */
AMS_ASSERT(base_storage != nullptr);
AMS_ASSERT(hgf_selector != nullptr);
AMS_ASSERT(m_body_storage == nullptr);
/* Check that the crypto config is valid. */
R_UNLESS(crypto_cfg.verify_sign1 != nullptr, fs::ResultInvalidArgument());
/* Create the work header storage storage. */
std::unique_ptr<fs::IStorage> work_header_storage;
if (crypto_cfg.is_available_sw_key) {
/* If software key is available, we need to be able to generate keys. */
R_UNLESS(crypto_cfg.generate_key != nullptr, fs::ResultInvalidArgument());
/* Generate keys for header. */
using AesXtsStorageForNcaHeader = AesXtsStorageBySharedPointer;
constexpr const s32 HeaderKeyTypeValues[NcaCryptoConfiguration::HeaderEncryptionKeyCount] = {
static_cast<s32>(KeyType::NcaHeaderKey1),
static_cast<s32>(KeyType::NcaHeaderKey2),
};
u8 header_decryption_keys[NcaCryptoConfiguration::HeaderEncryptionKeyCount][NcaCryptoConfiguration::Aes128KeySize];
for (size_t i = 0; i < NcaCryptoConfiguration::HeaderEncryptionKeyCount; i++) {
crypto_cfg.generate_key(header_decryption_keys[i], AesXtsStorageForNcaHeader::KeySize, crypto_cfg.header_encrypted_encryption_keys[i], AesXtsStorageForNcaHeader::KeySize, HeaderKeyTypeValues[i]);
}
/* Create the header storage. */
const u8 header_iv[AesXtsStorageForNcaHeader::IvSize] = {};
work_header_storage = std::make_unique<AesXtsStorageForNcaHeader>(base_storage, header_decryption_keys[0], header_decryption_keys[1], AesXtsStorageForNcaHeader::KeySize, header_iv, AesXtsStorageForNcaHeader::IvSize, NcaHeader::XtsBlockSize);
} else {
/* Software key isn't available, so we need to be able to decrypt externally. */
R_UNLESS(crypto_cfg.decrypt_aes_xts_external, fs::ResultInvalidArgument());
/* Create the header storage. */
using AesXtsStorageExternalForNcaHeader = AesXtsStorageExternalByPointer;
const u8 header_iv[AesXtsStorageExternalForNcaHeader::IvSize] = {};
work_header_storage = std::make_unique<AesXtsStorageExternalForNcaHeader>(base_storage.get(), nullptr, nullptr, AesXtsStorageExternalForNcaHeader::KeySize, header_iv, AesXtsStorageExternalForNcaHeader::IvSize, NcaHeader::XtsBlockSize, crypto_cfg.encrypt_aes_xts_external, crypto_cfg.decrypt_aes_xts_external);
}
/* Check that we successfully created the storage. */
R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaReaderA());
/* Read the header. */
R_TRY(work_header_storage->Read(0, std::addressof(m_header), sizeof(m_header)));
/* Validate the magic. */
if (const Result magic_result = CheckNcaMagic(m_header.magic); R_FAILED(magic_result)) {
/* If we're not allowed to use plaintext headers, stop here. */
R_UNLESS(crypto_cfg.is_plaintext_header_available, magic_result);
/* Try to use a plaintext header. */
R_TRY(base_storage->Read(0, std::addressof(m_header), sizeof(m_header)));
R_UNLESS(R_SUCCEEDED(CheckNcaMagic(m_header.magic)), magic_result);
/* Configure to use the plaintext header. */
s64 base_storage_size;
R_TRY(base_storage->GetSize(std::addressof(base_storage_size)));
work_header_storage.reset(new fs::SubStorage(base_storage, 0, base_storage_size));
R_UNLESS(work_header_storage != nullptr, fs::ResultAllocationMemoryFailedInNcaReaderA());
/* Set encryption type as plaintext. */
m_header_encryption_type = NcaHeader::EncryptionType::None;
}
/* Validate the fixed key signature. */
R_UNLESS(m_header.header1_signature_key_generation <= NcaCryptoConfiguration::Header1SignatureKeyGenerationMax, fs::ResultInvalidNcaHeader1SignatureKeyGeneration());
/* Verify the header sign1. */
{
const u8 *sig = m_header.header_sign_1;
const size_t sig_size = NcaHeader::HeaderSignSize;
const u8 *msg = static_cast<const u8 *>(static_cast<const void *>(std::addressof(m_header.magic)));
const size_t msg_size = NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount;
m_is_header_sign1_signature_valid = crypto_cfg.verify_sign1(sig, sig_size, msg, msg_size, m_header.header1_signature_key_generation);
#if defined(ATMOSPHERE_BOARD_NINTENDO_NX)
R_UNLESS(m_is_header_sign1_signature_valid, fs::ResultNcaHeaderSignature1VerificationFailed());
#else
R_UNLESS(m_is_header_sign1_signature_valid || crypto_cfg.is_unsigned_header_available_for_host_tool, fs::ResultNcaHeaderSignature1VerificationFailed());
#endif
}
/* Validate the sdk version. */
R_UNLESS(m_header.sdk_addon_version >= SdkAddonVersionMin, fs::ResultUnsupportedSdkVersion());
/* Validate the key index. */
R_UNLESS(m_header.key_index < NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexCount || m_header.key_index == NcaCryptoConfiguration::KeyAreaEncryptionKeyIndexZeroKey, fs::ResultInvalidNcaKeyIndex());
/* Set our hash generator factory selector. */
m_hash_generator_factory_selector = hgf_selector;
/* Check if we have a rights id. */
constexpr const u8 ZeroRightsId[NcaHeader::RightsIdSize] = {};
if (crypto::IsSameBytes(ZeroRightsId, m_header.rights_id, NcaHeader::RightsIdSize)) {
/* If we don't, then we don't have an external key, so we need to generate decryption keys if software keys are available. */
if (crypto_cfg.is_available_sw_key) {
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtr], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtr * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
/* If we're building for non-nx board (i.e., a host tool), generate all keys for debug. */
#if !defined(ATMOSPHERE_BOARD_NINTENDO_NX)
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts1], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts1 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesXts2], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesXts2 * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
crypto_cfg.generate_key(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrEx], crypto::AesDecryptor128::KeySize, m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrEx * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize, GetKeyTypeValue(m_header.key_index, m_header.GetProperKeyGeneration()));
#endif
}
/* Copy the hardware speed emulation key. */
std::memcpy(m_decryption_keys[NcaHeader::DecryptionKey_AesCtrHw], m_header.encrypted_key_area + NcaHeader::DecryptionKey_AesCtrHw * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize);
}
/* Clear the external decryption key. */
std::memset(m_external_decryption_key, 0, sizeof(m_external_decryption_key));
/* Set software key availability. */
m_is_available_sw_key = crypto_cfg.is_available_sw_key;
/* Set our decryptor functions. */
m_decrypt_aes_ctr = crypto_cfg.decrypt_aes_ctr;
m_decrypt_aes_ctr_external = crypto_cfg.decrypt_aes_ctr_external;
/* Set our decompressor function getter. */
m_get_decompressor = compression_cfg.get_decompressor;
/* Set our storages. */
m_header_storage = std::move(work_header_storage);
m_body_storage = std::move(base_storage);
R_SUCCEED();
}
std::shared_ptr<fs::IStorage> NcaReader::GetSharedBodyStorage() {
AMS_ASSERT(m_body_storage != nullptr);
return m_body_storage;
}
u32 NcaReader::GetMagic() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.magic;
}
NcaHeader::DistributionType NcaReader::GetDistributionType() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.distribution_type;
}
NcaHeader::ContentType NcaReader::GetContentType() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.content_type;
}
u8 NcaReader::GetHeaderSign1KeyGeneration() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.header1_signature_key_generation;
}
u8 NcaReader::GetKeyGeneration() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.GetProperKeyGeneration();
}
u8 NcaReader::GetKeyIndex() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.key_index;
}
u64 NcaReader::GetContentSize() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.content_size;
}
u64 NcaReader::GetProgramId() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.program_id;
}
u32 NcaReader::GetContentIndex() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.content_index;
}
u32 NcaReader::GetSdkAddonVersion() const {
AMS_ASSERT(m_body_storage != nullptr);
return m_header.sdk_addon_version;
}
void NcaReader::GetRightsId(u8 *dst, size_t dst_size) const {
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(dst_size >= NcaHeader::RightsIdSize);
AMS_UNUSED(dst_size);
std::memcpy(dst, m_header.rights_id, NcaHeader::RightsIdSize);
}
bool NcaReader::HasFsInfo(s32 index) const {
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
return m_header.fs_info[index].start_sector != 0 || m_header.fs_info[index].end_sector != 0;
}
s32 NcaReader::GetFsCount() const {
AMS_ASSERT(m_body_storage != nullptr);
for (s32 i = 0; i < NcaHeader::FsCountMax; i++) {
if (!this->HasFsInfo(i)) {
return i;
}
}
return NcaHeader::FsCountMax;
}
const Hash &NcaReader::GetFsHeaderHash(s32 index) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
return m_header.fs_header_hash[index];
}
void NcaReader::GetFsHeaderHash(Hash *dst, s32 index) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
AMS_ASSERT(dst != nullptr);
std::memcpy(dst, std::addressof(m_header.fs_header_hash[index]), sizeof(*dst));
}
void NcaReader::GetFsInfo(NcaHeader::FsInfo *dst, s32 index) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
AMS_ASSERT(dst != nullptr);
std::memcpy(dst, std::addressof(m_header.fs_info[index]), sizeof(*dst));
}
u64 NcaReader::GetFsOffset(s32 index) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
return NcaHeader::SectorToByte(m_header.fs_info[index].start_sector);
}
u64 NcaReader::GetFsEndOffset(s32 index) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector);
}
u64 NcaReader::GetFsSize(s32 index) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
return NcaHeader::SectorToByte(m_header.fs_info[index].end_sector - m_header.fs_info[index].start_sector);
}
void NcaReader::GetEncryptedKey(void *dst, size_t size) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(size >= NcaHeader::EncryptedKeyAreaSize);
AMS_UNUSED(size);
std::memcpy(dst, m_header.encrypted_key_area, NcaHeader::EncryptedKeyAreaSize);
}
const void *NcaReader::GetDecryptionKey(s32 index) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::DecryptionKey_Count);
return m_decryption_keys[index];
}
bool NcaReader::HasValidInternalKey() const {
constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {};
for (s32 i = 0; i < NcaHeader::DecryptionKey_Count; i++) {
if (!crypto::IsSameBytes(ZeroKey, m_header.encrypted_key_area + i * crypto::AesDecryptor128::KeySize, crypto::AesDecryptor128::KeySize)) {
return true;
}
}
return false;
}
bool NcaReader::HasInternalDecryptionKeyForAesHw() const {
constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {};
return !crypto::IsSameBytes(ZeroKey, this->GetDecryptionKey(NcaHeader::DecryptionKey_AesCtrHw), crypto::AesDecryptor128::KeySize);
}
bool NcaReader::IsSoftwareAesPrioritized() const {
return m_is_software_aes_prioritized;
}
void NcaReader::PrioritizeSoftwareAes() {
m_is_software_aes_prioritized = true;
}
bool NcaReader::IsAvailableSwKey() const {
return m_is_available_sw_key;
}
bool NcaReader::HasExternalDecryptionKey() const {
constexpr const u8 ZeroKey[crypto::AesDecryptor128::KeySize] = {};
return !crypto::IsSameBytes(ZeroKey, this->GetExternalDecryptionKey(), crypto::AesDecryptor128::KeySize);
}
const void *NcaReader::GetExternalDecryptionKey() const {
return m_external_decryption_key;
}
void NcaReader::SetExternalDecryptionKey(const void *src, size_t size) {
AMS_ASSERT(src != nullptr);
AMS_ASSERT(size == sizeof(m_external_decryption_key));
AMS_UNUSED(size);
std::memcpy(m_external_decryption_key, src, sizeof(m_external_decryption_key));
}
void NcaReader::GetRawData(void *dst, size_t dst_size) const {
AMS_ASSERT(m_body_storage != nullptr);
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(dst_size >= sizeof(NcaHeader));
AMS_UNUSED(dst_size);
std::memcpy(dst, std::addressof(m_header), sizeof(NcaHeader));
}
DecryptAesCtrFunction NcaReader::GetExternalDecryptAesCtrFunction() const {
AMS_ASSERT(m_decrypt_aes_ctr != nullptr);
return m_decrypt_aes_ctr;
}
DecryptAesCtrFunction NcaReader::GetExternalDecryptAesCtrFunctionForExternalKey() const {
AMS_ASSERT(m_decrypt_aes_ctr_external != nullptr);
return m_decrypt_aes_ctr_external;
}
GetDecompressorFunction NcaReader::GetDecompressor() const {
AMS_ASSERT(m_get_decompressor != nullptr);
return m_get_decompressor;
}
IHash256GeneratorFactorySelector *NcaReader::GetHashGeneratorFactorySelector() const {
AMS_ASSERT(m_hash_generator_factory_selector != nullptr);
return m_hash_generator_factory_selector;
}
NcaHeader::EncryptionType NcaReader::GetEncryptionType() const {
return m_header_encryption_type;
}
Result NcaReader::ReadHeader(NcaFsHeader *dst, s32 index) const {
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(0 <= index && index < NcaHeader::FsCountMax);
const s64 offset = sizeof(NcaHeader) + sizeof(NcaFsHeader) * index;
R_RETURN(m_header_storage->Read(offset, dst, sizeof(NcaFsHeader)));
}
bool NcaReader::GetHeaderSign1Valid() const {
#if defined(ATMOSPHERE_BOARD_NINTENDO_NX)
AMS_ABORT_UNLESS(m_is_header_sign1_signature_valid);
#endif
return m_is_header_sign1_signature_valid;
}
void NcaReader::GetHeaderSign2(void *dst, size_t size) const {
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(size == NcaHeader::HeaderSignSize);
std::memcpy(dst, m_header.header_sign_2, size);
}
void NcaReader::GetHeaderSign2TargetHash(void *dst, size_t size) const {
AMS_ASSERT(m_hash_generator_factory_selector!= nullptr);
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(size == IHash256Generator::HashSize);
auto * const factory = m_hash_generator_factory_selector->GetFactory(fssystem::HashAlgorithmType_Sha2);
return factory->GenerateHash(dst, size, static_cast<const void *>(std::addressof(m_header.magic)), NcaHeader::Size - NcaHeader::HeaderSignSize * NcaHeader::HeaderSignCount);
}
Result NcaFsHeaderReader::Initialize(const NcaReader &reader, s32 index) {
/* Reset ourselves to uninitialized. */
m_fs_index = -1;
/* Read the header. */
R_TRY(reader.ReadHeader(std::addressof(m_data), index));
/* Generate the hash. */
Hash hash;
crypto::GenerateSha256(std::addressof(hash), sizeof(hash), std::addressof(m_data), sizeof(NcaFsHeader));
/* Validate the hash. */
R_UNLESS(crypto::IsSameBytes(std::addressof(reader.GetFsHeaderHash(index)), std::addressof(hash), sizeof(Hash)), fs::ResultNcaFsHeaderHashVerificationFailed());
/* Set our index. */
m_fs_index = index;
R_SUCCEED();
}
void NcaFsHeaderReader::GetRawData(void *dst, size_t dst_size) const {
AMS_ASSERT(this->IsInitialized());
AMS_ASSERT(dst != nullptr);
AMS_ASSERT(dst_size >= sizeof(NcaFsHeader));
AMS_UNUSED(dst_size);
std::memcpy(dst, std::addressof(m_data), sizeof(NcaFsHeader));
}
NcaFsHeader::HashData &NcaFsHeaderReader::GetHashData() {
AMS_ASSERT(this->IsInitialized());
return m_data.hash_data;
}
const NcaFsHeader::HashData &NcaFsHeaderReader::GetHashData() const {
AMS_ASSERT(this->IsInitialized());
return m_data.hash_data;
}
u16 NcaFsHeaderReader::GetVersion() const {
AMS_ASSERT(this->IsInitialized());
return m_data.version;
}
s32 NcaFsHeaderReader::GetFsIndex() const {
AMS_ASSERT(this->IsInitialized());
return m_fs_index;
}
NcaFsHeader::FsType NcaFsHeaderReader::GetFsType() const {
AMS_ASSERT(this->IsInitialized());
return m_data.fs_type;
}
NcaFsHeader::HashType NcaFsHeaderReader::GetHashType() const {
AMS_ASSERT(this->IsInitialized());
return m_data.hash_type;
}
NcaFsHeader::EncryptionType NcaFsHeaderReader::GetEncryptionType() const {
AMS_ASSERT(this->IsInitialized());
return m_data.encryption_type;
}
NcaPatchInfo &NcaFsHeaderReader::GetPatchInfo() {
AMS_ASSERT(this->IsInitialized());
return m_data.patch_info;
}
const NcaPatchInfo &NcaFsHeaderReader::GetPatchInfo() const {
AMS_ASSERT(this->IsInitialized());
return m_data.patch_info;
}
const NcaAesCtrUpperIv NcaFsHeaderReader::GetAesCtrUpperIv() const {
AMS_ASSERT(this->IsInitialized());
return m_data.aes_ctr_upper_iv;
}
bool NcaFsHeaderReader::IsSkipLayerHashEncryption() const {
AMS_ASSERT(this->IsInitialized());
return m_data.IsSkipLayerHashEncryption();
}
Result NcaFsHeaderReader::GetHashTargetOffset(s64 *out) const {
AMS_ASSERT(out != nullptr);
AMS_ASSERT(this->IsInitialized());
R_RETURN(m_data.GetHashTargetOffset(out));
}
bool NcaFsHeaderReader::ExistsSparseLayer() const {
AMS_ASSERT(this->IsInitialized());
return m_data.sparse_info.generation != 0;
}
NcaSparseInfo &NcaFsHeaderReader::GetSparseInfo() {
AMS_ASSERT(this->IsInitialized());
return m_data.sparse_info;
}
const NcaSparseInfo &NcaFsHeaderReader::GetSparseInfo() const {
AMS_ASSERT(this->IsInitialized());
return m_data.sparse_info;
}
bool NcaFsHeaderReader::ExistsCompressionLayer() const {
AMS_ASSERT(this->IsInitialized());
return m_data.compression_info.bucket.offset != 0 && m_data.compression_info.bucket.size != 0;
}
NcaCompressionInfo &NcaFsHeaderReader::GetCompressionInfo() {
AMS_ASSERT(this->IsInitialized());
return m_data.compression_info;
}
const NcaCompressionInfo &NcaFsHeaderReader::GetCompressionInfo() const {
AMS_ASSERT(this->IsInitialized());
return m_data.compression_info;
}
bool NcaFsHeaderReader::ExistsPatchMetaHashLayer() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info.size != 0 && this->GetPatchInfo().HasIndirectTable();
}
NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetPatchMetaDataHashDataInfo() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetPatchMetaHashType() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_type;
}
bool NcaFsHeaderReader::ExistsSparseMetaHashLayer() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info.size != 0 && this->ExistsSparseLayer();
}
NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
const NcaMetaDataHashDataInfo &NcaFsHeaderReader::GetSparseMetaDataHashDataInfo() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_data_info;
}
NcaFsHeader::MetaDataHashType NcaFsHeaderReader::GetSparseMetaHashType() const {
AMS_ASSERT(this->IsInitialized());
return m_data.meta_data_hash_type;
}
}

View File

@@ -1,474 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
constexpr const char RootPath[] = "/";
class PartitionFileSystemDefaultAllocator : public MemoryResource {
private:
virtual void *AllocateImpl(size_t size, size_t alignment) override {
AMS_UNUSED(alignment);
return ::ams::fs::impl::Allocate(size);
}
virtual void DeallocateImpl(void *buffer, size_t size, size_t alignment) override {
AMS_UNUSED(alignment);
::ams::fs::impl::Deallocate(buffer, size);
}
virtual bool IsEqualImpl(const MemoryResource &rhs) const override {
return this == std::addressof(rhs);
}
};
PartitionFileSystemDefaultAllocator g_partition_filesystem_default_allocator;
}
template <typename MetaType>
class PartitionFileSystemCore<MetaType>::PartitionFile : public fs::fsa::IFile, public fs::impl::Newable {
private:
const typename MetaType::PartitionEntry *m_partition_entry;
const PartitionFileSystemCore<MetaType> *m_parent;
const fs::OpenMode m_mode;
public:
PartitionFile(PartitionFileSystemCore<MetaType> *parent, const typename MetaType::PartitionEntry *partition_entry, fs::OpenMode mode) : m_partition_entry(partition_entry), m_parent(parent), m_mode(mode) { /* ... */ }
private:
virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override final;
virtual Result DoGetSize(s64 *out) override final {
*out = m_partition_entry->size;
R_SUCCEED();
}
virtual Result DoFlush() override final {
/* Nothing to do if writing disallowed. */
R_SUCCEED_IF((m_mode & fs::OpenMode_Write) == 0);
/* Flush base storage. */
R_RETURN(m_parent->m_base_storage->Flush());
}
virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override final {
/* Ensure appending is not required. */
bool needs_append;
R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, m_mode));
R_UNLESS(!needs_append, fs::ResultUnsupportedWriteForPartitionFile());
/* Appending is prohibited. */
AMS_ASSERT((m_mode & fs::OpenMode_AllowAppend) == 0);
/* Validate offset and size. */
R_UNLESS(offset <= static_cast<s64>(m_partition_entry->size), fs::ResultOutOfRange());
R_UNLESS(static_cast<s64>(offset + size) <= static_cast<s64>(m_partition_entry->size), fs::ResultInvalidSize());
/* Write to the base storage. */
R_RETURN(m_parent->m_base_storage->Write(m_parent->m_meta_data_size + m_partition_entry->offset + offset, buffer, size));
}
virtual Result DoSetSize(s64 size) override final {
R_TRY(this->DrySetSize(size, m_mode));
R_RETURN(fs::ResultUnsupportedWriteForPartitionFile());
}
virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override final {
/* Validate preconditions for operation. */
s64 operate_offset;
s64 operate_size;
switch (op_id) {
case fs::OperationId::Invalidate:
R_UNLESS((m_mode & fs::OpenMode_Read) != 0, fs::ResultReadNotPermitted());
R_UNLESS((m_mode & fs::OpenMode_Write) == 0, fs::ResultUnsupportedOperateRangeForPartitionFile());
/* Set offset/size. */
operate_offset = 0;
operate_size = std::numeric_limits<s64>::max();
break;
case fs::OperationId::QueryRange:
/* Validate offset and size. */
R_UNLESS(offset >= 0, fs::ResultOutOfRange());
R_UNLESS(offset <= static_cast<s64>(m_partition_entry->size), fs::ResultOutOfRange());
R_UNLESS(static_cast<s64>(offset + size) <= static_cast<s64>(m_partition_entry->size), fs::ResultInvalidSize());
R_UNLESS(static_cast<s64>(offset + size) >= offset, fs::ResultInvalidSize());
/* Set offset/size. */
operate_offset = m_parent->m_meta_data_size + m_partition_entry->offset + offset;
operate_size = size;
break;
default:
R_THROW(fs::ResultUnsupportedOperateRangeForPartitionFile());
}
R_RETURN(m_parent->m_base_storage->OperateRange(dst, dst_size, op_id, operate_offset, operate_size, src, src_size));
}
public:
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
/* TODO: How should this be handled? */
return sf::cmif::InvalidDomainObjectId;
}
};
template<>
Result PartitionFileSystemCore<PartitionFileSystemMeta>::PartitionFile::DoRead(size_t *out, s64 offset, void *dst, size_t dst_size, const fs::ReadOption &option) {
/* Perform a dry read. */
size_t read_size = 0;
R_TRY(this->DryRead(std::addressof(read_size), offset, dst_size, option, m_mode));
/* Read from the base storage. */
R_TRY(m_parent->m_base_storage->Read(m_parent->m_meta_data_size + m_partition_entry->offset + offset, dst, read_size));
/* Set output size. */
*out = read_size;
R_SUCCEED();
}
template<>
Result PartitionFileSystemCore<Sha256PartitionFileSystemMeta>::PartitionFile::DoRead(size_t *out, s64 offset, void *dst, size_t dst_size, const fs::ReadOption &option) {
/* Perform a dry read. */
size_t read_size = 0;
R_TRY(this->DryRead(std::addressof(read_size), offset, dst_size, option, m_mode));
const s64 entry_start = m_parent->m_meta_data_size + m_partition_entry->offset;
const s64 read_end = static_cast<s64>(offset + read_size);
const s64 hash_start = static_cast<s64>(m_partition_entry->hash_target_offset);
const s64 hash_end = hash_start + m_partition_entry->hash_target_size;
if (read_end <= hash_start || hash_end <= offset) {
/* We aren't reading hashed data, so we can just read from the base storage. */
R_TRY(m_parent->m_base_storage->Read(entry_start + offset, dst, read_size));
} else {
/* Only hash target offset == 0 is supported. */
R_UNLESS(hash_start == 0, fs::ResultInvalidSha256PartitionHashTarget());
/* Ensure that the hash region is valid. */
R_UNLESS(m_partition_entry->hash_target_offset + m_partition_entry->hash_target_size <= m_partition_entry->size, fs::ResultInvalidSha256PartitionHashTarget());
/* Validate our read offset. */
const s64 read_offset = entry_start + offset;
R_UNLESS(read_offset >= offset, fs::ResultOutOfRange());
/* Prepare a buffer for our calculated hash. */
char hash[crypto::Sha256Generator::HashSize];
crypto::Sha256Generator generator;
/* Ensure we can perform our read. */
const bool hash_in_read = offset <= hash_start && hash_end <= read_end;
const bool read_in_hash = hash_start <= offset && read_end <= hash_end;
R_UNLESS(hash_in_read || read_in_hash, fs::ResultInvalidSha256PartitionHashTarget());
/* Initialize the generator. */
generator.Initialize();
if (hash_in_read) {
/* Easy case: hash region is contained within the bounds. */
R_TRY(m_parent->m_base_storage->Read(entry_start + offset, dst, read_size));
generator.Update(static_cast<u8 *>(dst) + hash_start - offset, m_partition_entry->hash_target_size);
} else /* if (read_in_hash) */ {
/* We're reading a portion of what's hashed. */
s64 remaining_hash_size = m_partition_entry->hash_target_size;
s64 hash_offset = entry_start + hash_start;
s64 remaining_size = read_size;
s64 copy_offset = 0;
while (remaining_hash_size > 0) {
/* Read some portion of data into the buffer. */
constexpr size_t HashBufferSize = 0x200;
char hash_buffer[HashBufferSize];
size_t cur_size = static_cast<size_t>(std::min(static_cast<s64>(HashBufferSize), remaining_hash_size));
R_TRY(m_parent->m_base_storage->Read(hash_offset, hash_buffer, cur_size));
/* Update the hash. */
generator.Update(hash_buffer, cur_size);
/* If we need to copy, do so. */
if (read_offset <= (hash_offset + static_cast<s64>(cur_size)) && remaining_size > 0) {
const s64 hash_buffer_offset = std::max<s64>(read_offset - hash_offset, 0);
const size_t copy_size = static_cast<size_t>(std::min<s64>(cur_size - hash_buffer_offset, remaining_size));
std::memcpy(static_cast<u8 *>(dst) + copy_offset, hash_buffer + hash_buffer_offset, copy_size);
remaining_size -= copy_size;
copy_offset += copy_size;
}
/* Update offsets. */
remaining_hash_size -= cur_size;
hash_offset += cur_size;
}
}
/* Get the hash. */
generator.GetHash(hash, sizeof(hash));
/* Validate the hash. */
auto hash_guard = SCOPE_GUARD { std::memset(dst, 0, read_size); };
R_UNLESS(crypto::IsSameBytes(m_partition_entry->hash, hash, sizeof(hash)), fs::ResultSha256PartitionHashVerificationFailed());
/* We successfully completed our read. */
hash_guard.Cancel();
}
/* Set output size. */
*out = read_size;
R_SUCCEED();
}
template <typename MetaType>
class PartitionFileSystemCore<MetaType>::PartitionDirectory : public fs::fsa::IDirectory, public fs::impl::Newable {
private:
u32 m_cur_index;
const PartitionFileSystemCore<MetaType> *m_parent;
const fs::OpenDirectoryMode m_mode;
public:
PartitionDirectory(PartitionFileSystemCore<MetaType> *parent, fs::OpenDirectoryMode mode) : m_cur_index(0), m_parent(parent), m_mode(mode) { /* ... */ }
public:
virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override final {
/* There are no subdirectories. */
if ((m_mode & fs::OpenDirectoryMode_File) == 0) {
*out_count = 0;
R_SUCCEED();
}
/* Calculate number of entries. */
const s64 entry_count = std::min(max_entries, static_cast<s64>(m_parent->m_meta_data->GetEntryCount() - m_cur_index));
/* Populate output directory entries. */
for (auto i = 0; i < entry_count; i++, m_cur_index++) {
fs::DirectoryEntry &dir_entry = out_entries[i];
/* Setup the output directory entry. */
dir_entry.type = fs::DirectoryEntryType_File;
dir_entry.file_size = m_parent->m_meta_data->GetEntry(m_cur_index)->size;
std::strncpy(dir_entry.name, m_parent->m_meta_data->GetEntryName(m_cur_index), sizeof(dir_entry.name) - 1);
dir_entry.name[sizeof(dir_entry.name) - 1] = fs::StringTraits::NullTerminator;
}
*out_count = entry_count;
R_SUCCEED();
}
virtual Result DoGetEntryCount(s64 *out) override final {
/* Output the parent meta data entry count for files, otherwise 0. */
if (m_mode & fs::OpenDirectoryMode_File) {
*out = m_parent->m_meta_data->GetEntryCount();
} else {
*out = 0;
}
R_SUCCEED();
}
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
/* TODO: How should this be handled? */
return sf::cmif::InvalidDomainObjectId;
}
};
template <typename MetaType>
PartitionFileSystemCore<MetaType>::PartitionFileSystemCore() : m_initialized(false) {
/* ... */
}
template <typename MetaType>
PartitionFileSystemCore<MetaType>::~PartitionFileSystemCore() {
/* ... */
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::Initialize(fs::IStorage *base_storage, MemoryResource *allocator) {
/* Validate preconditions. */
R_UNLESS(!m_initialized, fs::ResultPreconditionViolation());
/* Allocate meta data. */
m_unique_meta_data = std::make_unique<MetaType>();
R_UNLESS(m_unique_meta_data != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemA());
/* Initialize meta data. */
R_TRY(m_unique_meta_data->Initialize(base_storage, allocator));
/* Initialize members. */
m_meta_data = m_unique_meta_data.get();
m_base_storage = base_storage;
m_meta_data_size = m_meta_data->GetMetaDataSize();
m_initialized = true;
R_SUCCEED();
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::Initialize(std::unique_ptr<MetaType> &&meta_data, std::shared_ptr<fs::IStorage> base_storage) {
m_unique_meta_data = std::move(meta_data);
R_RETURN(this->Initialize(m_unique_meta_data.get(), base_storage));
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::Initialize(MetaType *meta_data, std::shared_ptr<fs::IStorage> base_storage) {
/* Validate preconditions. */
R_UNLESS(!m_initialized, fs::ResultPreconditionViolation());
/* Initialize members. */
m_shared_storage = std::move(base_storage);
m_base_storage = m_shared_storage.get();
m_meta_data = meta_data;
m_meta_data_size = m_meta_data->GetMetaDataSize();
m_initialized = true;
R_SUCCEED();
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::Initialize(fs::IStorage *base_storage) {
R_RETURN(this->Initialize(base_storage, std::addressof(g_partition_filesystem_default_allocator)));
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::Initialize(std::shared_ptr<fs::IStorage> base_storage) {
m_shared_storage = std::move(base_storage);
R_RETURN(this->Initialize(m_shared_storage.get()));
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::Initialize(std::shared_ptr<fs::IStorage> base_storage, MemoryResource *allocator) {
m_shared_storage = std::move(base_storage);
R_RETURN(this->Initialize(m_shared_storage.get(), allocator));
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::GetFileBaseOffset(s64 *out_offset, const char *path) {
/* Validate preconditions. */
R_UNLESS(m_initialized, fs::ResultPreconditionViolation());
/* Obtain and validate the entry index. */
const s32 entry_index = m_meta_data->GetEntryIndex(path + 1);
R_UNLESS(entry_index >= 0, fs::ResultPathNotFound());
/* Output offset. */
*out_offset = m_meta_data_size + m_meta_data->GetEntry(entry_index)->offset;
R_SUCCEED();
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) {
/* Validate preconditions. */
R_UNLESS(m_initialized, fs::ResultPreconditionViolation());
const char * const p = path.GetString();
R_UNLESS(p[0] == RootPath[0], fs::ResultInvalidPathFormat());
/* Check if the path is for a directory. */
if (util::Strncmp(p, RootPath, sizeof(RootPath)) == 0) {
*out = fs::DirectoryEntryType_Directory;
R_SUCCEED();
}
/* Ensure that path is for a file. */
R_UNLESS(m_meta_data->GetEntryIndex(p + 1) >= 0, fs::ResultPathNotFound());
*out = fs::DirectoryEntryType_File;
R_SUCCEED();
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) {
/* Validate preconditions. */
R_UNLESS(m_initialized, fs::ResultPreconditionViolation());
/* Obtain and validate the entry index. */
const s32 entry_index = m_meta_data->GetEntryIndex(path.GetString() + 1);
R_UNLESS(entry_index >= 0, fs::ResultPathNotFound());
/* Create and output the file directory. */
std::unique_ptr file = std::make_unique<PartitionFile>(this, m_meta_data->GetEntry(entry_index), mode);
R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemB());
*out_file = std::move(file);
R_SUCCEED();
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) {
/* Validate preconditions. */
R_UNLESS(m_initialized, fs::ResultPreconditionViolation());
R_UNLESS(path == RootPath, fs::ResultPathNotFound());
/* Create and output the partition directory. */
std::unique_ptr directory = std::make_unique<PartitionDirectory>(this, mode);
R_UNLESS(directory != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemC());
*out_dir = std::move(directory);
R_SUCCEED();
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoCommit() {
R_SUCCEED();
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoCleanDirectoryRecursively(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoCreateDirectory(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoCreateFile(const fs::Path &path, s64 size, int option) {
AMS_UNUSED(path, size, option);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoDeleteDirectory(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoDeleteDirectoryRecursively(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoDeleteFile(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) {
AMS_UNUSED(old_path, new_path);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) {
AMS_UNUSED(old_path, new_path);
R_THROW(fs::ResultUnsupportedWriteForPartitionFileSystem());
}
template <typename MetaType>
Result PartitionFileSystemCore<MetaType>::DoCommitProvisionally(s64 counter) {
AMS_UNUSED(counter);
R_THROW(fs::ResultUnsupportedCommitProvisionallyForPartitionFileSystem());
}
template class PartitionFileSystemCore<PartitionFileSystemMeta>;
template class PartitionFileSystemCore<Sha256PartitionFileSystemMeta>;
}

View File

@@ -1,219 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
template <typename Format>
struct PartitionFileSystemMetaCore<Format>::PartitionFileSystemHeader {
char signature[sizeof(Format::VersionSignature)];
s32 entry_count;
u32 name_table_size;
u32 reserved;
};
static_assert(util::is_pod<PartitionFileSystemMeta::PartitionFileSystemHeader>::value);
static_assert(sizeof(PartitionFileSystemMeta::PartitionFileSystemHeader) == 0x10);
template <typename Format>
PartitionFileSystemMetaCore<Format>::~PartitionFileSystemMetaCore() {
this->DeallocateBuffer();
}
template <typename Format>
Result PartitionFileSystemMetaCore<Format>::Initialize(fs::IStorage *storage, MemoryResource *allocator) {
/* Validate preconditions. */
AMS_ASSERT(allocator != nullptr);
/* Determine the meta data size. */
R_TRY(this->QueryMetaDataSize(std::addressof(m_meta_data_size), storage));
/* Deallocate any old meta buffer and allocate a new one. */
this->DeallocateBuffer();
m_allocator = allocator;
m_buffer = static_cast<char *>(m_allocator->Allocate(m_meta_data_size));
R_UNLESS(m_buffer != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemMetaA());
/* Perform regular initialization. */
R_RETURN(this->Initialize(storage, m_buffer, m_meta_data_size));
}
template <typename Format>
Result PartitionFileSystemMetaCore<Format>::Initialize(fs::IStorage *storage, void *meta, size_t meta_size) {
/* Validate size for header. */
R_UNLESS(meta_size >= sizeof(PartitionFileSystemHeader), fs::ResultInvalidSize());
/* Read the header. */
R_TRY(storage->Read(0, meta, sizeof(PartitionFileSystemHeader)));
/* Set and validate the header. */
m_header = reinterpret_cast<PartitionFileSystemHeader *>(meta);
R_UNLESS(crypto::IsSameBytes(m_header->signature, Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed());
/* Setup entries and name table. */
const size_t entries_size = m_header->entry_count * sizeof(typename Format::PartitionEntry);
m_entries = reinterpret_cast<PartitionEntry *>(static_cast<u8 *>(meta) + sizeof(PartitionFileSystemHeader));
m_name_table = static_cast<char *>(meta) + sizeof(PartitionFileSystemHeader) + entries_size;
/* Validate size for header + entries + name table. */
R_UNLESS(meta_size >= sizeof(PartitionFileSystemHeader) + entries_size + m_header->name_table_size, fs::ResultInvalidSize());
/* Read entries and name table. */
R_TRY(storage->Read(sizeof(PartitionFileSystemHeader), m_entries, entries_size + m_header->name_table_size));
/* Mark as initialized. */
m_initialized = true;
R_SUCCEED();
}
template <typename Format>
void PartitionFileSystemMetaCore<Format>::DeallocateBuffer() {
if (m_buffer != nullptr) {
AMS_ABORT_UNLESS(m_allocator != nullptr);
m_allocator->Deallocate(m_buffer, m_meta_data_size);
m_buffer = nullptr;
}
}
template <typename Format>
const typename Format::PartitionEntry *PartitionFileSystemMetaCore<Format>::GetEntry(s32 index) const {
if (m_initialized && 0 <= index && index < static_cast<s32>(m_header->entry_count)) {
return std::addressof(m_entries[index]);
}
return nullptr;
}
template <typename Format>
s32 PartitionFileSystemMetaCore<Format>::GetEntryCount() const {
if (m_initialized) {
return m_header->entry_count;
}
return 0;
}
template <typename Format>
s32 PartitionFileSystemMetaCore<Format>::GetEntryIndex(const char *name) const {
/* Fail if not initialized. */
if (!m_initialized) {
return 0;
}
for (s32 i = 0; i < static_cast<s32>(m_header->entry_count); i++) {
const auto &entry = m_entries[i];
/* Name offset is invalid. */
if (entry.name_offset >= m_header->name_table_size) {
return 0;
}
/* Compare to input name. */
const s32 max_name_len = m_header->name_table_size - entry.name_offset;
if (std::strncmp(std::addressof(m_name_table[entry.name_offset]), name, max_name_len) == 0) {
return i;
}
}
/* Not found. */
return -1;
}
template <typename Format>
const char *PartitionFileSystemMetaCore<Format>::GetEntryName(s32 index) const {
if (m_initialized && index < static_cast<s32>(m_header->entry_count)) {
return std::addressof(m_name_table[this->GetEntry(index)->name_offset]);
}
return nullptr;
}
template <typename Format>
size_t PartitionFileSystemMetaCore<Format>::GetHeaderSize() const {
return sizeof(PartitionFileSystemHeader);
}
template <typename Format>
size_t PartitionFileSystemMetaCore<Format>::GetMetaDataSize() const {
return m_meta_data_size;
}
template <typename Format>
Result PartitionFileSystemMetaCore<Format>::QueryMetaDataSize(size_t *out_size, fs::IStorage *storage) {
/* Read and validate the header. */
PartitionFileSystemHeader header;
R_TRY(storage->Read(0, std::addressof(header), sizeof(PartitionFileSystemHeader)));
R_UNLESS(crypto::IsSameBytes(std::addressof(header), Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed());
/* Output size. */
*out_size = sizeof(PartitionFileSystemHeader) + header.entry_count * sizeof(typename Format::PartitionEntry) + header.name_table_size;
R_SUCCEED();
}
template class PartitionFileSystemMetaCore<impl::PartitionFileSystemFormat>;
template class PartitionFileSystemMetaCore<impl::Sha256PartitionFileSystemFormat>;
Result Sha256PartitionFileSystemMeta::Initialize(fs::IStorage *base_storage, MemoryResource *allocator, const void *hash, size_t hash_size, util::optional<u8> suffix) {
/* Ensure preconditions. */
R_UNLESS(hash_size == crypto::Sha256Generator::HashSize, fs::ResultPreconditionViolation());
/* Get metadata size. */
R_TRY(QueryMetaDataSize(std::addressof(m_meta_data_size), base_storage));
/* Ensure we have no buffer. */
this->DeallocateBuffer();
/* Set allocator and allocate buffer. */
m_allocator = allocator;
m_buffer = static_cast<char *>(m_allocator->Allocate(m_meta_data_size));
R_UNLESS(m_buffer != nullptr, fs::ResultAllocationMemoryFailedInPartitionFileSystemMetaB());
/* Read metadata. */
R_TRY(base_storage->Read(0, m_buffer, m_meta_data_size));
/* Calculate hash. */
char calc_hash[crypto::Sha256Generator::HashSize];
{
crypto::Sha256Generator generator;
generator.Initialize();
generator.Update(m_buffer, m_meta_data_size);
if (suffix) {
u8 suffix_val = *suffix;
generator.Update(std::addressof(suffix_val), 1);
}
generator.GetHash(calc_hash, sizeof(calc_hash));
}
/* Ensure hash is valid. */
R_UNLESS(crypto::IsSameBytes(hash, calc_hash, sizeof(calc_hash)), fs::ResultSha256PartitionHashVerificationFailed());
/* Give access to Format */
using Format = impl::Sha256PartitionFileSystemFormat;
/* Set header. */
m_header = reinterpret_cast<PartitionFileSystemHeader *>(m_buffer);
R_UNLESS(crypto::IsSameBytes(m_header->signature, Format::VersionSignature, sizeof(Format::VersionSignature)), typename Format::ResultSignatureVerificationFailed());
/* Validate size for entries and name table. */
const size_t entries_size = m_header->entry_count * sizeof(typename Format::PartitionEntry);
R_UNLESS(m_meta_data_size >= sizeof(PartitionFileSystemHeader) + entries_size + m_header->name_table_size, fs::ResultInvalidSha256PartitionMetaDataSize());
/* Set entries and name table. */
m_entries = reinterpret_cast<PartitionEntry *>(m_buffer + sizeof(PartitionFileSystemHeader));
m_name_table = m_buffer + sizeof(PartitionFileSystemHeader) + entries_size;
/* We initialized. */
m_initialized = true;
R_SUCCEED();
}
}

View File

@@ -1,269 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
class AdditionalDeviceAddressEntry {
private:
os::SdkMutex m_mutex;
bool m_is_registered;
uintptr_t m_address;
size_t m_size;
public:
constexpr AdditionalDeviceAddressEntry() : m_mutex(), m_is_registered(), m_address(), m_size() { /* ... */ }
void Register(uintptr_t addr, size_t sz) {
std::scoped_lock lk(m_mutex);
AMS_ASSERT(!m_is_registered);
if (!m_is_registered) {
m_is_registered = true;
m_address = addr;
m_size = sz;
}
}
void Unregister(uintptr_t addr) {
std::scoped_lock lk(m_mutex);
if (m_is_registered && m_address == addr) {
m_is_registered = false;
m_address = 0;
m_size = 0;
}
}
bool Includes(const void *ptr) {
std::scoped_lock lk(m_mutex);
if (m_is_registered) {
const uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
return m_address <= addr && addr < m_address + m_size;
} else {
return false;
}
}
};
constexpr auto RetryWait = TimeSpan::FromMilliSeconds(10);
constexpr size_t HeapBlockSize = BufferPoolAlignment;
static_assert(HeapBlockSize == 4_KB);
/* A heap block is 4KB. An order is a power of two. */
/* This gives blocks of the order 32KB, 512KB, 4MB. */
constexpr s32 HeapOrderTrim = 3;
constexpr s32 HeapOrderMax = 7;
constexpr s32 HeapOrderMaxForLarge = HeapOrderMax + 3;
constexpr size_t HeapAllocatableSizeTrim = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderTrim);
constexpr size_t HeapAllocatableSizeMax = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMax);
constexpr size_t HeapAllocatableSizeMaxForLarge = HeapBlockSize * (static_cast<size_t>(1) << HeapOrderMaxForLarge);
constinit os::SdkMutex g_heap_mutex;
constinit FileSystemBuddyHeap g_heap;
constinit std::atomic<size_t> g_retry_count;
constinit std::atomic<size_t> g_reduce_allocation_count;
constinit void *g_heap_buffer;
constinit size_t g_heap_size;
constinit size_t g_heap_free_size_peak;
constinit AdditionalDeviceAddressEntry g_additional_device_address_entry;
}
size_t PooledBuffer::GetAllocatableSizeMaxCore(bool large) {
return large ? HeapAllocatableSizeMaxForLarge : HeapAllocatableSizeMax;
}
void PooledBuffer::AllocateCore(size_t ideal_size, size_t required_size, bool large) {
/* Ensure preconditions. */
AMS_ASSERT(g_heap_buffer != nullptr);
AMS_ASSERT(m_buffer == nullptr);
AMS_ASSERT(g_heap.GetBlockSize() == HeapBlockSize);
/* Check that we can allocate this size. */
AMS_ASSERT(required_size <= GetAllocatableSizeMaxCore(large));
const size_t target_size = std::min(std::max(ideal_size, required_size), GetAllocatableSizeMaxCore(large));
/* Loop until we allocate. */
while (true) {
/* Lock the heap and try to allocate. */
{
std::scoped_lock lk(g_heap_mutex);
/* Determine how much we can allocate, and don't allocate more than half the heap. */
size_t allocatable_size = g_heap.GetAllocatableSizeMax();
if (allocatable_size > HeapBlockSize) {
allocatable_size >>= 1;
}
/* Check if this allocation is acceptable. */
if (allocatable_size >= required_size) {
/* Get the order. */
const auto order = g_heap.GetOrderFromBytes(std::min(target_size, allocatable_size));
/* Allocate and get the size. */
m_buffer = reinterpret_cast<char *>(g_heap.AllocateByOrder(order));
m_size = g_heap.GetBytesFromOrder(order);
}
}
/* Check if we allocated. */
if (m_buffer != nullptr) {
/* If we need to trim the end, do so. */
if (this->GetSize() >= target_size + HeapAllocatableSizeTrim) {
this->Shrink(util::AlignUp(target_size, HeapAllocatableSizeTrim));
}
AMS_ASSERT(this->GetSize() >= required_size);
/* If we reduced, note so. */
if (this->GetSize() < std::min(target_size, HeapAllocatableSizeMax)) {
g_reduce_allocation_count++;
}
break;
} else {
/* Sleep. */
os::SleepThread(RetryWait);
g_retry_count++;
}
}
/* Update metrics. */
{
std::scoped_lock lk(g_heap_mutex);
const size_t free_size = g_heap.GetTotalFreeSize();
if (free_size < g_heap_free_size_peak) {
g_heap_free_size_peak = free_size;
}
}
}
void PooledBuffer::Shrink(size_t ideal_size) {
AMS_ASSERT(ideal_size <= GetAllocatableSizeMaxCore(true));
/* Check if we actually need to shrink. */
if (m_size > ideal_size) {
/* If we do, we need to have a buffer allocated from the heap. */
AMS_ASSERT(m_buffer != nullptr);
AMS_ASSERT(g_heap.GetBlockSize() == HeapBlockSize);
const size_t new_size = util::AlignUp(ideal_size, HeapBlockSize);
/* Repeatedly free the tail of our buffer until we're done. */
{
std::scoped_lock lk(g_heap_mutex);
while (new_size < m_size) {
/* Determine the size and order to free. */
const size_t tail_align = util::LeastSignificantOneBit(m_size);
const size_t free_size = std::min(util::FloorPowerOfTwo(m_size - new_size), tail_align);
const s32 free_order = g_heap.GetOrderFromBytes(free_size);
/* Ensure we determined size correctly. */
AMS_ASSERT(util::IsAligned(free_size, HeapBlockSize));
AMS_ASSERT(free_size == g_heap.GetBytesFromOrder(free_order));
/* Actually free the memory. */
g_heap.Free(m_buffer + m_size - free_size, free_order);
m_size -= free_size;
}
}
/* Shrinking to zero means that we have no buffer. */
if (m_size == 0) {
m_buffer = nullptr;
}
}
}
Result InitializeBufferPool(char *buffer, size_t size) {
AMS_ASSERT(g_heap_buffer == nullptr);
AMS_ASSERT(buffer != nullptr);
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), BufferPoolAlignment));
/* Initialize the heap. */
R_TRY(g_heap.Initialize(reinterpret_cast<uintptr_t>(buffer), size, HeapBlockSize, HeapOrderMaxForLarge + 1));
/* Initialize metrics. */
g_heap_buffer = buffer;
g_heap_size = size;
g_heap_free_size_peak = size;
R_SUCCEED();
}
Result InitializeBufferPool(char *buffer, size_t size, char *work, size_t work_size) {
AMS_ASSERT(g_heap_buffer == nullptr);
AMS_ASSERT(buffer != nullptr);
AMS_ASSERT(util::IsAligned(reinterpret_cast<uintptr_t>(buffer), BufferPoolAlignment));
AMS_ASSERT(work_size >= BufferPoolWorkSize);
/* Initialize the heap. */
R_TRY(g_heap.Initialize(reinterpret_cast<uintptr_t>(buffer), size, HeapBlockSize, HeapOrderMaxForLarge + 1, work, work_size));
/* Initialize metrics. */
g_heap_buffer = buffer;
g_heap_size = size;
g_heap_free_size_peak = size;
R_SUCCEED();
}
bool IsPooledBuffer(const void *buffer) {
AMS_ASSERT(buffer != nullptr);
return g_heap_buffer <= buffer && buffer < reinterpret_cast<char *>(g_heap_buffer) + g_heap_size;
}
size_t GetPooledBufferRetriedCount() {
return g_retry_count;
}
size_t GetPooledBufferReduceAllocationCount() {
return g_reduce_allocation_count;
}
size_t GetPooledBufferFreeSizePeak() {
return g_heap_free_size_peak;
}
void ClearPooledBufferPeak() {
std::scoped_lock lk(g_heap_mutex);
g_heap_free_size_peak = g_heap.GetTotalFreeSize();
g_retry_count = 0;
g_reduce_allocation_count = 0;
}
void RegisterAdditionalDeviceAddress(uintptr_t address, size_t size) {
g_additional_device_address_entry.Register(address, size);
}
void UnregisterAdditionalDeviceAddress(uintptr_t address) {
g_additional_device_address_entry.Unregister(address);
}
bool IsAdditionalDeviceAddress(const void *ptr) {
return g_additional_device_address_entry.Includes(ptr);
}
}

View File

@@ -1,131 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "fssystem_lru_list_cache.hpp"
namespace ams::fssystem {
class ReadOnlyBlockCacheStorage : public ::ams::fs::IStorage, public ::ams::fs::impl::Newable {
NON_COPYABLE(ReadOnlyBlockCacheStorage);
NON_MOVEABLE(ReadOnlyBlockCacheStorage);
private:
using BlockCache = LruListCache<s64, char *>;
private:
os::SdkMutex m_mutex;
BlockCache m_block_cache;
std::shared_ptr<fs::IStorage> m_base_storage;
s32 m_block_size;
public:
ReadOnlyBlockCacheStorage(std::shared_ptr<fs::IStorage> bs, s32 bsz, char *buf, size_t buf_size, s32 cache_block_count) : m_mutex(), m_block_cache(), m_base_storage(std::move(bs)), m_block_size(bsz) {
/* Validate preconditions. */
AMS_ASSERT(buf_size >= static_cast<size_t>(m_block_size));
AMS_ASSERT(util::IsPowerOfTwo(m_block_size));
AMS_ASSERT(cache_block_count > 0);
AMS_ASSERT(buf_size >= static_cast<size_t>(m_block_size * cache_block_count));
AMS_UNUSED(buf_size);
/* Create a node for each cache block. */
for (auto i = 0; i < cache_block_count; i++) {
std::unique_ptr node = std::make_unique<BlockCache::Node>(buf + m_block_size * i);
AMS_ASSERT(node != nullptr);
if (node != nullptr) {
m_block_cache.PushMruNode(std::move(node), -1);
}
}
}
~ReadOnlyBlockCacheStorage() {
m_block_cache.DeleteAllNodes();
}
virtual Result Read(s64 offset, void *buffer, size_t size) override {
/* Validate preconditions. */
AMS_ASSERT(util::IsAligned(offset, m_block_size));
AMS_ASSERT(util::IsAligned(size, m_block_size));
if (size == static_cast<size_t>(m_block_size)) {
char *cached_buffer = nullptr;
/* Try to find a cached copy of the data. */
{
std::scoped_lock lk(m_mutex);
bool found = m_block_cache.FindValueAndUpdateMru(std::addressof(cached_buffer), offset / m_block_size);
if (found) {
std::memcpy(buffer, cached_buffer, size);
R_SUCCEED();
}
}
/* We failed to get a cache hit, so read in the data. */
R_TRY(m_base_storage->Read(offset, buffer, size));
/* Add the block to the cache. */
{
std::scoped_lock lk(m_mutex);
auto lru = m_block_cache.PopLruNode();
std::memcpy(lru->m_value, buffer, m_block_size);
m_block_cache.PushMruNode(std::move(lru), offset / m_block_size);
}
R_SUCCEED();
} else {
R_RETURN(m_base_storage->Read(offset, buffer, size));
}
}
virtual Result OperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
/* If invalidating cache, invalidate our blocks. */
if (op_id == fs::OperationId::Invalidate) {
std::scoped_lock lk(m_mutex);
const size_t cache_block_count = m_block_cache.GetSize();
for (size_t i = 0; i < cache_block_count; ++i) {
auto lru = m_block_cache.PopLruNode();
m_block_cache.PushMruNode(std::move(lru), -1);
}
R_SUCCEED();
} else {
/* Validate preconditions. */
AMS_ASSERT(util::IsAligned(offset, m_block_size));
AMS_ASSERT(util::IsAligned(size, m_block_size));
}
/* Operate on the base storage. */
R_RETURN(m_base_storage->OperateRange(dst, dst_size, op_id, offset, size, src, src_size));
}
virtual Result GetSize(s64 *out) override {
R_RETURN(m_base_storage->GetSize(out));
}
virtual Result Flush() override {
R_SUCCEED();
}
virtual Result Write(s64 offset, const void *buffer, size_t size) override {
AMS_UNUSED(offset, buffer, size);
R_THROW(fs::ResultUnsupportedWriteForReadOnlyBlockCacheStorage());
}
virtual Result SetSize(s64 size) override {
AMS_UNUSED(size);
R_THROW(fs::ResultUnsupportedSetSizeForReadOnlyBlockCacheStorage());
}
};
}

View File

@@ -1,418 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
constexpr size_t CalculateRequiredWorkingMemorySize(const fs::RomFileSystemInformation &header) {
return header.directory_bucket_size + header.directory_entry_size + header.file_bucket_size + header.file_entry_size;
}
class RomFsFile : public ams::fs::fsa::IFile, public ams::fs::impl::Newable {
private:
RomFsFileSystem *m_parent;
s64 m_start;
s64 m_end;
private:
s64 GetSize() const {
return m_end - m_start;
}
public:
RomFsFile(RomFsFileSystem *p, s64 s, s64 e) : m_parent(p), m_start(s), m_end(e) { /* ... */ }
virtual ~RomFsFile() { /* ... */ }
public:
virtual Result DoRead(size_t *out, s64 offset, void *buffer, size_t size, const fs::ReadOption &option) override {
R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
size_t read_size = 0;
R_TRY(this->DryRead(std::addressof(read_size), offset, size, option, fs::OpenMode_Read));
R_TRY(m_parent->GetBaseStorage()->Read(offset + m_start, buffer, read_size));
*out = read_size;
R_SUCCEED();
}, AMS_CURRENT_FUNCTION_NAME));
R_SUCCEED();
}
virtual Result DoGetSize(s64 *out) override {
*out = this->GetSize();
R_SUCCEED();
}
virtual Result DoFlush() override {
R_SUCCEED();
}
virtual Result DoWrite(s64 offset, const void *buffer, size_t size, const fs::WriteOption &option) override {
AMS_UNUSED(buffer);
bool needs_append;
R_TRY(this->DryWrite(std::addressof(needs_append), offset, size, option, fs::OpenMode_Read));
AMS_ASSERT(needs_append == false);
R_THROW(fs::ResultUnsupportedWriteForRomFsFile());
}
virtual Result DoSetSize(s64 size) override {
R_TRY(this->DrySetSize(size, fs::OpenMode_Read));
R_THROW(fs::ResultUnsupportedWriteForRomFsFile());
}
virtual Result DoOperateRange(void *dst, size_t dst_size, fs::OperationId op_id, s64 offset, s64 size, const void *src, size_t src_size) override {
switch (op_id) {
case fs::OperationId::Invalidate:
{
R_RETURN(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
R_RETURN(m_parent->GetBaseStorage()->OperateRange(fs::OperationId::Invalidate, 0, std::numeric_limits<s64>::max()));
}, AMS_CURRENT_FUNCTION_NAME));
}
case fs::OperationId::QueryRange:
{
R_UNLESS(offset >= 0, fs::ResultInvalidOffset());
R_UNLESS(this->GetSize() >= offset, fs::ResultOutOfRange());
auto operate_size = size;
if (offset + operate_size > this->GetSize() || offset + operate_size < offset) {
operate_size = this->GetSize() - offset;
}
R_RETURN(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
R_RETURN(m_parent->GetBaseStorage()->OperateRange(dst, dst_size, op_id, m_start + offset, operate_size, src, src_size));
}, AMS_CURRENT_FUNCTION_NAME));
}
default:
R_THROW(fs::ResultUnsupportedOperateRangeForRomFsFile());
}
}
public:
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
AMS_ABORT();
}
};
class RomFsDirectory : public ams::fs::fsa::IDirectory, public ams::fs::impl::Newable {
private:
using FindPosition = RomFsFileSystem::RomFileTable::FindPosition;
private:
RomFsFileSystem *m_parent;
FindPosition m_current_find;
FindPosition m_first_find;
fs::OpenDirectoryMode m_mode;
public:
RomFsDirectory(RomFsFileSystem *p, const FindPosition &f, fs::OpenDirectoryMode m) : m_parent(p), m_current_find(f), m_first_find(f), m_mode(m) { /* ... */ }
virtual ~RomFsDirectory() override { /* ... */ }
public:
virtual Result DoRead(s64 *out_count, fs::DirectoryEntry *out_entries, s64 max_entries) override {
R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
R_RETURN(this->ReadInternal(out_count, std::addressof(m_current_find), out_entries, max_entries));
}, AMS_CURRENT_FUNCTION_NAME));
R_SUCCEED();
}
virtual Result DoGetEntryCount(s64 *out) override {
FindPosition find = m_first_find;
R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
R_TRY(this->ReadInternal(out, std::addressof(find), nullptr, 0));
R_SUCCEED();
}, AMS_CURRENT_FUNCTION_NAME));
R_SUCCEED();
}
private:
Result ReadInternal(s64 *out_count, FindPosition *find, fs::DirectoryEntry *out_entries, s64 max_entries) {
constexpr size_t NameBufferSize = fs::EntryNameLengthMax + 1;
fs::RomPathChar name[NameBufferSize];
s32 i = 0;
if (m_mode & fs::OpenDirectoryMode_Directory) {
while (i < max_entries || out_entries == nullptr) {
R_TRY_CATCH(m_parent->GetRomFileTable()->FindNextDirectory(name, find, NameBufferSize)) {
R_CATCH(fs::ResultDbmFindFinished) { break; }
} R_END_TRY_CATCH;
if (out_entries) {
R_UNLESS(strnlen(name, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath());
strncpy(out_entries[i].name, name, fs::EntryNameLengthMax);
out_entries[i].name[fs::EntryNameLengthMax] = '\x00';
out_entries[i].type = fs::DirectoryEntryType_Directory;
out_entries[i].file_size = 0;
}
i++;
}
}
if (m_mode & fs::OpenDirectoryMode_File) {
while (i < max_entries || out_entries == nullptr) {
auto file_pos = find->next_file;
R_TRY_CATCH(m_parent->GetRomFileTable()->FindNextFile(name, find, NameBufferSize)) {
R_CATCH(fs::ResultDbmFindFinished) { break; }
} R_END_TRY_CATCH;
if (out_entries) {
R_UNLESS(strnlen(name, NameBufferSize) < NameBufferSize, fs::ResultTooLongPath());
strncpy(out_entries[i].name, name, fs::EntryNameLengthMax);
out_entries[i].name[fs::EntryNameLengthMax] = '\x00';
out_entries[i].type = fs::DirectoryEntryType_File;
RomFsFileSystem::RomFileTable::FileInfo file_info;
R_TRY(m_parent->GetRomFileTable()->OpenFile(std::addressof(file_info), m_parent->GetRomFileTable()->PositionToFileId(file_pos)));
out_entries[i].file_size = file_info.size.Get();
}
i++;
}
}
*out_count = i;
R_SUCCEED();
}
public:
virtual sf::cmif::DomainObjectId GetDomainObjectId() const override {
AMS_ABORT();
}
};
}
RomFsFileSystem::RomFsFileSystem() : m_base_storage() {
/* ... */
}
RomFsFileSystem::~RomFsFileSystem() {
/* ... */
}
fs::IStorage *RomFsFileSystem::GetBaseStorage() {
return m_base_storage;
}
RomFsFileSystem::RomFileTable *RomFsFileSystem::GetRomFileTable() {
return std::addressof(m_rom_file_table);
}
Result RomFsFileSystem::GetRequiredWorkingMemorySize(size_t *out, fs::IStorage *storage) {
fs::RomFileSystemInformation header;
R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
R_TRY(storage->Read(0, std::addressof(header), sizeof(header)));
R_SUCCEED();
}, AMS_CURRENT_FUNCTION_NAME));
*out = CalculateRequiredWorkingMemorySize(header);
R_SUCCEED();
}
Result RomFsFileSystem::Initialize(fs::IStorage *base, void *work, size_t work_size, bool use_cache) {
AMS_ABORT_UNLESS(!use_cache || work != nullptr);
AMS_ABORT_UNLESS(base != nullptr);
/* Register blocking context for the scope. */
buffers::ScopedBufferManagerContextRegistration _sr;
buffers::EnableBlockingBufferManagerAllocation();
/* Read the header. */
fs::RomFileSystemInformation header;
R_TRY(base->Read(0, std::addressof(header), sizeof(header)));
/* Set up our storages. */
if (use_cache) {
const size_t needed_size = CalculateRequiredWorkingMemorySize(header);
R_UNLESS(work_size >= needed_size, fs::ResultAllocationMemoryFailedInRomFsFileSystemA());
u8 *buf = static_cast<u8 *>(work);
auto dir_bucket_buf = buf; buf += header.directory_bucket_size;
auto dir_entry_buf = buf; buf += header.directory_entry_size;
auto file_bucket_buf = buf; buf += header.file_bucket_size;
auto file_entry_buf = buf; buf += header.file_entry_size;
R_TRY(base->Read(header.directory_bucket_offset, dir_bucket_buf, static_cast<size_t>(header.directory_bucket_size)));
R_TRY(base->Read(header.directory_entry_offset, dir_entry_buf, static_cast<size_t>(header.directory_entry_size)));
R_TRY(base->Read(header.file_bucket_offset, file_bucket_buf, static_cast<size_t>(header.file_bucket_size)));
R_TRY(base->Read(header.file_entry_offset, file_entry_buf, static_cast<size_t>(header.file_entry_size)));
m_dir_bucket_storage.reset(new fs::MemoryStorage(dir_bucket_buf, header.directory_bucket_size));
m_dir_entry_storage.reset(new fs::MemoryStorage(dir_entry_buf, header.directory_entry_size));
m_file_bucket_storage.reset(new fs::MemoryStorage(file_bucket_buf, header.file_bucket_size));
m_file_entry_storage.reset(new fs::MemoryStorage(file_entry_buf, header.file_entry_size));
} else {
m_dir_bucket_storage.reset(new fs::SubStorage(base, header.directory_bucket_offset, header.directory_bucket_size));
m_dir_entry_storage.reset(new fs::SubStorage(base, header.directory_entry_offset, header.directory_entry_size));
m_file_bucket_storage.reset(new fs::SubStorage(base, header.file_bucket_offset, header.file_bucket_size));
m_file_entry_storage.reset(new fs::SubStorage(base, header.file_entry_offset, header.file_entry_size));
}
/* Ensure we allocated storages successfully. */
R_UNLESS(m_dir_bucket_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB());
R_UNLESS(m_dir_entry_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB());
R_UNLESS(m_file_bucket_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB());
R_UNLESS(m_file_entry_storage != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemB());
/* Initialize the rom table. */
R_TRY(m_rom_file_table.Initialize(fs::SubStorage(m_dir_bucket_storage.get(), 0, static_cast<u32>(header.directory_bucket_size)),
fs::SubStorage(m_dir_entry_storage.get(), 0, static_cast<u32>(header.directory_entry_size)),
fs::SubStorage(m_file_bucket_storage.get(), 0, static_cast<u32>(header.file_bucket_size)),
fs::SubStorage(m_file_entry_storage.get(), 0, static_cast<u32>(header.file_entry_size))));
/* Set members. */
m_entry_size = header.body_offset;
m_base_storage = base;
R_SUCCEED();
}
Result RomFsFileSystem::Initialize(std::shared_ptr<fs::IStorage> base, void *work, size_t work_size, bool use_cache) {
m_shared_storage = std::move(base);
R_RETURN(this->Initialize(m_shared_storage.get(), work, work_size, use_cache));
}
Result RomFsFileSystem::GetFileInfo(RomFileTable::FileInfo *out, const char *path) {
R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
R_TRY_CATCH(m_rom_file_table.OpenFile(out, path)) {
R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound());
} R_END_TRY_CATCH;
R_SUCCEED();
}, AMS_CURRENT_FUNCTION_NAME));
R_SUCCEED();
}
Result RomFsFileSystem::GetFileBaseOffset(s64 *out, const fs::Path &path) {
R_TRY(this->CheckPathFormat(path));
RomFileTable::FileInfo info;
R_TRY(this->GetFileInfo(std::addressof(info), path));
*out = m_entry_size + info.offset.Get();
R_SUCCEED();
}
Result RomFsFileSystem::DoCreateFile(const fs::Path &path, s64 size, int flags) {
AMS_UNUSED(path, size, flags);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoDeleteFile(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoCreateDirectory(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoDeleteDirectory(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoDeleteDirectoryRecursively(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoRenameFile(const fs::Path &old_path, const fs::Path &new_path) {
AMS_UNUSED(old_path, new_path);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoRenameDirectory(const fs::Path &old_path, const fs::Path &new_path) {
AMS_UNUSED(old_path, new_path);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoGetEntryType(fs::DirectoryEntryType *out, const fs::Path &path) {
R_TRY(this->CheckPathFormat(path));
R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
fs::HierarchicalRomFileTable::FindPosition find_pos;
R_TRY_CATCH(m_rom_file_table.FindOpen(std::addressof(find_pos), path.GetString())) {
R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound())
R_CATCH(fs::ResultDbmInvalidOperation) {
*out = fs::DirectoryEntryType_File;
R_SUCCEED();
}
} R_END_TRY_CATCH;
*out = fs::DirectoryEntryType_Directory;
R_SUCCEED();
}, AMS_CURRENT_FUNCTION_NAME));
R_SUCCEED();
}
Result RomFsFileSystem::DoOpenFile(std::unique_ptr<fs::fsa::IFile> *out_file, const fs::Path &path, fs::OpenMode mode) {
R_UNLESS(mode == fs::OpenMode_Read, fs::ResultInvalidOpenMode());
R_TRY(this->CheckPathFormat(path));
RomFileTable::FileInfo file_info;
R_TRY(this->GetFileInfo(std::addressof(file_info), path));
auto file = std::make_unique<RomFsFile>(this, m_entry_size + file_info.offset.Get(), m_entry_size + file_info.offset.Get() + file_info.size.Get());
R_UNLESS(file != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemC());
*out_file = std::move(file);
R_SUCCEED();
}
Result RomFsFileSystem::DoOpenDirectory(std::unique_ptr<fs::fsa::IDirectory> *out_dir, const fs::Path &path, fs::OpenDirectoryMode mode) {
R_TRY(this->CheckPathFormat(path));
RomFileTable::FindPosition find;
R_TRY(buffers::DoContinuouslyUntilBufferIsAllocated([&]() -> Result {
R_TRY_CATCH(m_rom_file_table.FindOpen(std::addressof(find), path.GetString())) {
R_CONVERT(fs::ResultDbmNotFound, fs::ResultPathNotFound())
} R_END_TRY_CATCH;
R_SUCCEED();
}, AMS_CURRENT_FUNCTION_NAME));
auto dir = std::make_unique<RomFsDirectory>(this, find, mode);
R_UNLESS(dir != nullptr, fs::ResultAllocationMemoryFailedInRomFsFileSystemD());
*out_dir = std::move(dir);
R_SUCCEED();
}
Result RomFsFileSystem::DoCommit() {
R_SUCCEED();
}
Result RomFsFileSystem::DoGetFreeSpaceSize(s64 *out, const fs::Path &path) {
AMS_UNUSED(path);
*out = 0;
R_SUCCEED();
}
Result RomFsFileSystem::DoCleanDirectoryRecursively(const fs::Path &path) {
AMS_UNUSED(path);
R_THROW(fs::ResultUnsupportedWriteForRomFsFileSystem());
}
Result RomFsFileSystem::DoCommitProvisionally(s64 counter) {
AMS_UNUSED(counter);
R_THROW(fs::ResultUnsupportedCommitProvisionallyForRomFsFileSystem());
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
os::SdkThreadLocalStorage g_tls_service_context;
}
void RegisterServiceContext(ServiceContext *context) {
/* Check pre-conditions. */
AMS_ASSERT(context != nullptr);
AMS_ASSERT(g_tls_service_context.GetValue() == 0);
/* Register context. */
g_tls_service_context.SetValue(reinterpret_cast<uintptr_t>(context));
}
void UnregisterServiceContext() {
/* Unregister context. */
g_tls_service_context.SetValue(0);
}
ServiceContext *GetServiceContext() {
/* Get context. */
auto * const context = reinterpret_cast<ServiceContext *>(g_tls_service_context.GetValue());
AMS_ASSERT(context != nullptr);
return context;
}
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
Result SparseStorage::Read(s64 offset, void *buffer, size_t size) {
/* Validate preconditions. */
AMS_ASSERT(offset >= 0);
AMS_ASSERT(this->IsInitialized());
/* Allow zero size. */
R_SUCCEED_IF(size == 0);
/* Validate arguments. */
R_UNLESS(buffer != nullptr, fs::ResultNullptrArgument());
if (this->GetEntryTable().IsEmpty()) {
BucketTree::Offsets table_offsets;
R_TRY(this->GetEntryTable().GetOffsets(std::addressof(table_offsets)));
R_UNLESS(table_offsets.IsInclude(offset, size), fs::ResultOutOfRange());
std::memset(buffer, 0, size);
} else {
R_TRY((this->OperatePerEntry<false, true>(offset, size, [=](fs::IStorage *storage, s64 data_offset, s64 cur_offset, s64 cur_size) -> Result {
R_TRY(storage->Read(data_offset, reinterpret_cast<u8 *>(buffer) + (cur_offset - offset), static_cast<size_t>(cur_size)));
R_SUCCEED();
})));
}
R_SUCCEED();
}
}

View File

@@ -1,34 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
std::atomic<::ams::fs::SpeedEmulationMode> g_speed_emulation_mode = ::ams::fs::SpeedEmulationMode::None;
}
void SpeedEmulationConfiguration::SetSpeedEmulationMode(::ams::fs::SpeedEmulationMode mode) {
g_speed_emulation_mode = mode;
}
::ams::fs::SpeedEmulationMode SpeedEmulationConfiguration::GetSpeedEmulationMode() {
return g_speed_emulation_mode;
}
}

View File

@@ -1,26 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
s32 ScopedThreadPriorityChangerByAccessPriority::GetThreadPriorityByAccessPriority(AccessMode mode) {
/* TODO: Actually implement this for real. */
AMS_UNUSED(mode);
return os::GetThreadPriority(os::GetCurrentThread());
}
}

View File

@@ -1,192 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
namespace ams::fssystem {
namespace {
Result EnsureDirectoryImpl(fs::fsa::IFileSystem *fs, const fs::Path &path) {
/* Create work path. */
fs::Path work_path;
R_TRY(work_path.Initialize(path));
/* Create a directory path parser. */
fs::DirectoryPathParser parser;
R_TRY(parser.Initialize(std::addressof(work_path)));
bool is_finished;
do {
/* Get the current path. */
const fs::Path &cur_path = parser.GetCurrentPath();
/* Get the entry type for the current path. */
fs::DirectoryEntryType type;
R_TRY_CATCH(fs->GetEntryType(std::addressof(type), cur_path)) {
R_CATCH(fs::ResultPathNotFound) {
/* The path doesn't exist. We should create it. */
R_TRY(fs->CreateDirectory(cur_path));
/* Get the updated entry type. */
R_TRY(fs->GetEntryType(std::addressof(type), cur_path));
}
} R_END_TRY_CATCH;
/* Verify that the current entry isn't a file. */
R_UNLESS(type != fs::DirectoryEntryType_File, fs::ResultPathAlreadyExists());
/* Advance to the next part of the path. */
R_TRY(parser.ReadNext(std::addressof(is_finished)));
} while (!is_finished);
R_SUCCEED();
}
Result HasEntry(bool *out, fs::fsa::IFileSystem *fsa, const fs::Path &path, fs::DirectoryEntryType type) {
/* Set out to false initially. */
*out = false;
/* Try to get the entry type. */
fs::DirectoryEntryType entry_type;
R_TRY_CATCH(fsa->GetEntryType(std::addressof(entry_type), path)) {
/* If the path doesn't exist, nothing has gone wrong. */
R_CONVERT(fs::ResultPathNotFound, ResultSuccess());
} R_END_TRY_CATCH;
/* We succeeded. */
*out = entry_type == type;
R_SUCCEED();
}
}
Result CopyFile(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const fs::Path &dst_path, const fs::Path &src_path, void *work_buf, size_t work_buf_size) {
/* Open source file. */
std::unique_ptr<fs::fsa::IFile> src_file;
R_TRY(src_fs->OpenFile(std::addressof(src_file), src_path, fs::OpenMode_Read));
/* Get the file size. */
s64 file_size;
R_TRY(src_file->GetSize(std::addressof(file_size)));
/* Open dst file. */
std::unique_ptr<fs::fsa::IFile> dst_file;
R_TRY(dst_fs->CreateFile(dst_path, file_size));
R_TRY(dst_fs->OpenFile(std::addressof(dst_file), dst_path, fs::OpenMode_Write));
/* Read/Write file in work buffer sized chunks. */
s64 remaining = file_size;
s64 offset = 0;
while (remaining > 0) {
size_t read_size;
R_TRY(src_file->Read(std::addressof(read_size), offset, work_buf, work_buf_size, fs::ReadOption()));
R_TRY(dst_file->Write(offset, work_buf, read_size, fs::WriteOption()));
remaining -= read_size;
offset += read_size;
}
R_SUCCEED();
}
Result CopyDirectoryRecursively(fs::fsa::IFileSystem *dst_fs, fs::fsa::IFileSystem *src_fs, const fs::Path &dst_path, const fs::Path &src_path, fs::DirectoryEntry *entry, void *work_buf, size_t work_buf_size) {
/* Set up the destination work path to point at the target directory. */
fs::Path dst_work_path;
R_TRY(dst_work_path.Initialize(dst_path));
/* Iterate, copying files. */
R_RETURN(IterateDirectoryRecursively(src_fs, src_path, entry,
[&](const fs::Path &path, const fs::DirectoryEntry &entry) -> Result { /* On Enter Directory */
AMS_UNUSED(path, entry);
/* Append the current entry to the dst work path. */
R_TRY(dst_work_path.AppendChild(entry.name));
/* Create the directory. */
R_RETURN(dst_fs->CreateDirectory(dst_work_path));
},
[&](const fs::Path &path, const fs::DirectoryEntry &entry) -> Result { /* On Exit Directory */
AMS_UNUSED(path, entry);
/* Remove the directory we're leaving from the dst work path. */
R_RETURN(dst_work_path.RemoveChild());
},
[&](const fs::Path &path, const fs::DirectoryEntry &entry) -> Result { /* On File */
/* Append the current entry to the dst work path. */
R_TRY(dst_work_path.AppendChild(entry.name));
/* Copy the file. */
R_TRY(fssystem::CopyFile(dst_fs, src_fs, dst_work_path, path, work_buf, work_buf_size));
/* Remove the current entry from the dst work path. */
R_RETURN(dst_work_path.RemoveChild());
}
));
}
Result HasFile(bool *out, fs::fsa::IFileSystem *fs, const fs::Path &path) {
R_RETURN(HasEntry(out, fs, path, fs::DirectoryEntryType_File));
}
Result HasDirectory(bool *out, fs::fsa::IFileSystem *fs, const fs::Path &path) {
R_RETURN(HasEntry(out, fs, path, fs::DirectoryEntryType_Directory));
}
Result EnsureDirectory(fs::fsa::IFileSystem *fs, const fs::Path &path) {
/* First, check if the directory already exists. If it does, we're good to go. */
fs::DirectoryEntryType type;
R_TRY_CATCH(fs->GetEntryType(std::addressof(type), path)) {
/* If the directory doesn't already exist, we should create it. */
R_CATCH(fs::ResultPathNotFound) {
R_TRY(EnsureDirectoryImpl(fs, path));
}
} R_END_TRY_CATCH;
R_SUCCEED();
}
Result TryAcquireCountSemaphore(util::unique_lock<SemaphoreAdaptor> *out, SemaphoreAdaptor *adaptor) {
/* Create deferred unique lock. */
util::unique_lock<SemaphoreAdaptor> lock(*adaptor, std::defer_lock);
/* Try to lock. */
R_UNLESS(lock.try_lock(), fs::ResultOpenCountLimit());
/* Set the output lock. */
*out = std::move(lock);
R_SUCCEED();
}
void AddCounter(void *_counter, size_t counter_size, u64 value) {
u8 *counter = static_cast<u8 *>(_counter);
u64 remaining = value;
u8 carry = 0;
for (size_t i = 0; i < counter_size; i++) {
auto sum = counter[counter_size - 1 - i] + (remaining & 0xFF) + carry;
carry = static_cast<u8>(sum >> BITSIZEOF(u8));
auto sum8 = static_cast<u8>(sum & 0xFF);
counter[counter_size - 1 - i] = sum8;
remaining >>= BITSIZEOF(u8);
if (carry == 0 && remaining == 0) {
break;
}
}
}
}