ncm: use fs bindings, other refactoring

This commit is contained in:
Michael Scire 2020-03-04 18:39:52 -08:00
parent 0fe4e2950e
commit f4af4f8be0
36 changed files with 1470 additions and 1596 deletions

View file

@ -34,4 +34,5 @@
#include "fs/fs_sd_card.hpp"
#include "fs/fs_save_data_types.hpp"
#include "fs/fs_save_data_management.hpp"
#include "fs/fs_save_data_transaction.hpp"
#include "fs/fs_system_save_data.hpp"

View file

@ -19,6 +19,9 @@
namespace ams::fs {
Result DeleteSaveData(SaveDataId id);
Result DeleteSaveData(SaveDataSpaceId space_id, SaveDataId id);
Result GetSaveDataFlags(u32 *out, SaveDataId id);
Result GetSaveDataFlags(u32 *out, SaveDataSpaceId space_id, SaveDataId id);
Result SetSaveDataFlags(SaveDataId id, SaveDataSpaceId space_id, u32 flags);

View file

@ -0,0 +1,24 @@
/*
* Copyright (c) 2018-2020 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 "fs_common.hpp"
#include "fs_save_data_types.hpp"
namespace ams::fs {
Result CommitSaveData(const char *path);
}

View file

@ -39,7 +39,7 @@ namespace ams::ncm {
using PathString = kvdb::BoundedString<fs::EntryNameLengthMax>;
using MakeContentPathFunc = void (*)(PathString *out, ContentId content_id, const PathString &root);
using MakePlaceHolderPathFunc = void (*)(PathString *out, PlaceHolderId placeholder_id, const PathString &root);
using MakeContentPathFunction = void (*)(PathString *out, ContentId content_id, const char *root_path);
using MakePlaceHolderPathFunction = void (*)(PathString *out, PlaceHolderId placeholder_id,const char *root_path);
}

View file

@ -154,6 +154,10 @@ namespace ams::ncm {
#undef DEFINE_ENUM_MEMBER
};
constexpr inline bool IsUniqueStorage(StorageId id) {
return id != StorageId::None && id != StorageId::Any;
}
/* Program IDs (Formerly: Title IDs). */
struct ProgramId {
svc::ProgramId value;

View file

@ -30,9 +30,7 @@ namespace ams::os {
private:
CondVar cv;
public:
ConditionVariable() {
condvarInit(&cv);
}
constexpr ConditionVariable() : cv() { /* ... */ }
ConditionVariableStatus TimedWait(::Mutex *m, u64 timeout) {
if (timeout > 0) {

View file

@ -28,13 +28,11 @@ namespace ams::os {
private:
::Mutex m;
private:
::Mutex *GetMutex() {
constexpr ::Mutex *GetMutex() {
return &this->m;
}
public:
Mutex() {
mutexInit(GetMutex());
}
constexpr Mutex() : m() { /* ... */ }
void lock() {
mutexLock(GetMutex());
@ -65,13 +63,11 @@ namespace ams::os {
private:
::RMutex m;
private:
::RMutex *GetMutex() {
constexpr ::RMutex *GetMutex() {
return &this->m;
}
public:
RecursiveMutex() {
rmutexInit(GetMutex());
}
constexpr RecursiveMutex() : m() { /* ... */ }
void lock() {
rmutexLock(GetMutex());

View file

@ -76,6 +76,15 @@ namespace ams::fs {
return CreateSystemSaveData(SaveDataSpaceId::System, save_id, user_id, owner_id, size, journal_size, flags);
}
Result DeleteSaveData(SaveDataId id) {
/* TODO: Libnx binding for DeleteSaveDataFileSystem */
AMS_ABORT();
}
Result DeleteSaveData(SaveDataSpaceId space_id, SaveDataId id) {
return fsDeleteSaveDataFileSystemBySaveDataSpaceId(static_cast<::FsSaveDataSpaceId>(space_id), id);
}
Result DeleteSystemSaveData(SaveDataSpaceId space_id, SystemSaveDataId id, UserId user_id) {
const auto attribute = SaveDataAttribute::Make(ncm::InvalidProgramId, SaveDataType::System, user_id, id);

View file

@ -28,7 +28,7 @@ namespace ams::fs::impl {
FileSystemList fs_list;
os::Mutex mutex;
public:
MountTable() : fs_list(), mutex() { /* ... */ }
constexpr MountTable() : fs_list(), mutex() { /* ... */ }
private:
bool CanAcceptMountName(const char *name);
public:

View file

@ -103,7 +103,7 @@ namespace ams::fs::impl {
}
bool IsReservedMountName(const char *name) {
return name[0] != ReservedMountNamePrefixCharacter;
return name[0] == ReservedMountNamePrefixCharacter;
}
Result CheckMountName(const char *name) {

View file

@ -166,5 +166,24 @@ namespace ams::fs {
return ResultSuccess();
}
namespace {
Result CommitImpl(const char *path) {
impl::FileSystemAccessor *accessor;
R_TRY(impl::FindFileSystem(std::addressof(accessor), path));
return accessor->Commit();
}
}
Result Commit(const char *path) {
return CommitImpl(path);
}
Result CommitSaveData(const char *path) {
return CommitImpl(path);
}
}

View file

@ -35,7 +35,7 @@ namespace ams::ncm {
R_DEFINE_ERROR_RESULT(InvalidPlaceHolderFile, 170);
R_DEFINE_ERROR_RESULT(BufferInsufficient, 180);
R_DEFINE_ERROR_RESULT(InvalidContentStorageOperation, 190);
R_DEFINE_ERROR_RESULT(WriteToReadOnlyContentStorage, 190);
R_DEFINE_ERROR_RESULT(InvalidContentMetaKey, 240);
R_DEFINE_ERROR_RESULT(ContentStorageBaseNotFound, 310);

View file

@ -79,10 +79,14 @@ void __appInit(void) {
R_ABORT_UNLESS(gpioInitialize());
});
/* Mount the SD card. */
R_ABORT_UNLESS(fs::MountSdCard("sdmc"));
ams::CheckApiVersion();
}
void __appExit(void) {
fs::Unmount("sdmc");
gpioExit();
setsysExit();
pmshellExit();
@ -93,10 +97,6 @@ void __appExit(void) {
int main(int argc, char **argv)
{
/* Mount the SD card. */
R_ABORT_UNLESS(fs::MountSdCard("sdmc"));
ON_SCOPE_EXIT { fs::Unmount("sdmc"); };
/* Launch all programs off of SYSTEM/the SD. */
boot2::LaunchPostSdCardBootPrograms();
}

View file

@ -1,216 +0,0 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 "ncm_placeholder_accessor.hpp"
#include "../ncm_fs.hpp"
#include "../ncm_utils.hpp"
#include "../ncm_make_path.hpp"
#include "../ncm_path_utils.hpp"
namespace ams::ncm::impl {
namespace {
ALWAYS_INLINE Result ConvertNotFoundResult(Result r) {
R_TRY_CATCH(r) {
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
} R_END_TRY_CATCH;
return ResultSuccess();
}
}
Result PlaceHolderAccessor::Open(FILE** out_handle, PlaceHolderId placeholder_id) {
R_UNLESS(!this->LoadFromCache(out_handle, placeholder_id), ResultSuccess());
PathString placeholder_path;
this->MakePath(std::addressof(placeholder_path), placeholder_id);
FILE *f = nullptr;
R_TRY(fs::OpenFile(&f, placeholder_path, FsOpenMode_Write));
*out_handle = f;
return ResultSuccess();
}
bool PlaceHolderAccessor::LoadFromCache(FILE** out_handle, PlaceHolderId placeholder_id) {
std::scoped_lock lk(this->cache_mutex);
CacheEntry *entry = this->FindInCache(placeholder_id);
if (!entry) {
return false;
}
*out_handle = entry->handle;
entry->id = InvalidPlaceHolderId;
entry->handle = nullptr;
return true;
}
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) {
for (size_t i = 0; i < MaxCaches; i++) {
if (placeholder_id == this->caches[i].id) {
return &this->caches[i];
}
}
return nullptr;
}
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::GetFreeEntry() {
/* Try to find an already free entry. */
if (CacheEntry *entry = this->FindInCache(InvalidPlaceHolderId); entry != nullptr) {
return entry;
}
/* Get the oldest entry. */
CacheEntry *entry = &this->caches[0];
for (size_t i = 1; i < MaxCaches; i++) {
if (entry->counter > this->caches[i].counter) {
entry = &this->caches[i];
}
}
this->Invalidate(entry);
return entry;
}
void PlaceHolderAccessor::StoreToCache(FILE *handle, PlaceHolderId placeholder_id) {
std::scoped_lock lk(this->cache_mutex);
CacheEntry *entry = this->GetFreeEntry();
entry->id = placeholder_id;
entry->handle = handle;
entry->counter = this->cur_counter++;
}
void PlaceHolderAccessor::Invalidate(CacheEntry *entry) {
if (entry != nullptr) {
if (entry->handle != nullptr) {
fflush(entry->handle);
fclose(entry->handle);
entry->handle = nullptr;
}
entry->id = InvalidPlaceHolderId;
}
}
void PlaceHolderAccessor::Initialize(const char *root, MakePlaceHolderPathFunc path_func, bool delay_flush) {
this->root_path = PathString(root);
this->make_placeholder_path_func = path_func;
this->delay_flush = delay_flush;
}
unsigned int PlaceHolderAccessor::GetDirectoryDepth() {
if (this->make_placeholder_path_func == static_cast<MakePlaceHolderPathFunc>(path::MakePlaceHolderPathFlat)) {
return 1;
} else if (this->make_placeholder_path_func == static_cast<MakePlaceHolderPathFunc>(path::MakePlaceHolderPathHashByteLayered)) {
return 2;
} else {
AMS_ABORT();
}
__builtin_unreachable();
}
void PlaceHolderAccessor::GetPath(PathString *placeholder_path, PlaceHolderId placeholder_id) {
std::scoped_lock lock(this->cache_mutex);
CacheEntry *entry = this->FindInCache(placeholder_id);
this->Invalidate(entry);
this->MakePath(placeholder_path, placeholder_id);
}
Result PlaceHolderAccessor::Create(PlaceHolderId placeholder_id, size_t size) {
this->EnsureRecursively(placeholder_id);
PathString placeholder_path;
this->GetPath(std::addressof(placeholder_path), placeholder_id);
R_TRY_CATCH(fsdevCreateFile(placeholder_path, size, FsCreateOption_BigFile)) {
R_CONVERT(ams::fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists())
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result PlaceHolderAccessor::Delete(PlaceHolderId placeholder_id) {
PathString placeholder_path;
this->GetPath(std::addressof(placeholder_path), placeholder_id);
R_UNLESS(std::remove(placeholder_path) == 0, ConvertNotFoundResult(fsdevGetLastResult()));
return ResultSuccess();
}
Result PlaceHolderAccessor::Write(PlaceHolderId placeholder_id, size_t offset, const void *buffer, size_t size) {
FILE *f = nullptr;
R_TRY(ConvertNotFoundResult(this->Open(&f, placeholder_id)));
ON_SCOPE_EXIT { this->StoreToCache(f, placeholder_id); };
R_TRY(fs::WriteFile(f, offset, buffer, size, this->delay_flush ? ams::fs::WriteOption::Flush : ams::fs::WriteOption::None));
return ResultSuccess();
}
Result PlaceHolderAccessor::SetSize(PlaceHolderId placeholder_id, size_t size) {
PathString placeholder_path;
this->MakePath(std::addressof(placeholder_path), placeholder_id);
R_UNLESS(truncate(placeholder_path, size) != -1, ConvertNotFoundResult(fsdevGetLastResult()));
return ResultSuccess();
}
Result PlaceHolderAccessor::GetSize(bool *found_in_cache, size_t *out_size, PlaceHolderId placeholder_id) {
FILE *f = NULL;
*found_in_cache = false;
/* Set the scope for the scoped_lock. */
{
std::scoped_lock lock(this->cache_mutex);
/* If the placeholder id is invalid, return success early. */
R_UNLESS(placeholder_id != InvalidPlaceHolderId, ResultSuccess());
CacheEntry *cache_entry = this->FindInCache(placeholder_id);
/* If there is no entry in the cache, return success early. */
R_UNLESS(cache_entry != nullptr, ResultSuccess());
cache_entry->id = InvalidPlaceHolderId;
f = cache_entry->handle;
}
this->StoreToCache(f, placeholder_id);
R_UNLESS(fseek(f, 0L, SEEK_END) == 0, fsdevGetLastResult());
size_t size = ftell(f);
R_UNLESS(fseek(f, 0L, SEEK_SET) == 0, fsdevGetLastResult());
*found_in_cache = true;
*out_size = size;
return ResultSuccess();
}
Result PlaceHolderAccessor::EnsureRecursively(PlaceHolderId placeholder_id) {
PathString placeholder_path;
this->MakePath(std::addressof(placeholder_path), placeholder_id);
R_TRY(fs::EnsureParentDirectoryRecursively(placeholder_path));
return ResultSuccess();
}
void PlaceHolderAccessor::InvalidateAll() {
for (auto &entry : this->caches) {
if (entry.id != InvalidPlaceHolderId) {
this->Invalidate(&entry);
}
}
}
}

View file

@ -1,78 +0,0 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 <switch.h>
#include <stratosphere.hpp>
#include "../ncm_path_utils.hpp"
namespace ams::ncm::impl {
class PlaceHolderAccessor {
private:
class CacheEntry {
public:
PlaceHolderId id;
FILE *handle;
u64 counter;
};
private:
static constexpr size_t MaxCaches = 0x2;
std::array<CacheEntry, MaxCaches> caches;
PathString root_path;
u64 cur_counter;
os::Mutex cache_mutex;
MakePlaceHolderPathFunc make_placeholder_path_func;
bool delay_flush;
private:
Result Open(FILE** out_handle, PlaceHolderId placeholder_id);
CacheEntry *FindInCache(PlaceHolderId placeholder_id);
bool LoadFromCache(FILE** out_handle, PlaceHolderId placeholder_id);
CacheEntry *GetFreeEntry();
void StoreToCache(FILE *handle, PlaceHolderId placeholder_id);
void Invalidate(CacheEntry *entry);
public:
PlaceHolderAccessor() : cur_counter(0), delay_flush(false) {
for (size_t i = 0; i < MaxCaches; i++) {
caches[i].id = InvalidPlaceHolderId;
}
}
inline void MakeRootPath(PathString *placeholder_root) {
path::GetPlaceHolderRootPath(placeholder_root, this->root_path);
}
inline void MakePath(PathString *placeholder_path, PlaceHolderId placeholder_id) {
PathString root_path;
this->MakeRootPath(std::addressof(root_path));
this->make_placeholder_path_func(placeholder_path, placeholder_id, root_path);
}
void Initialize(const char *root, MakePlaceHolderPathFunc path_func, bool delay_flush);
unsigned int GetDirectoryDepth();
void GetPath(PathString *out_placeholder_path, PlaceHolderId placeholder_id);
Result Create(PlaceHolderId placeholder_id, size_t size);
Result Delete(PlaceHolderId placeholder_id);
Result Write(PlaceHolderId placeholder_id, size_t offset, const void *buffer, size_t size);
Result SetSize(PlaceHolderId placeholder_id, size_t size);
Result GetSize(bool *found_in_cache, size_t *out_size, PlaceHolderId placeholder_id);
Result EnsureRecursively(PlaceHolderId placeholder_id);
void InvalidateAll();
};
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 "ncm_content_id_utils.hpp"
namespace ams::ncm {
namespace {
void GetStringFromBytes(char *dst, const void *src, size_t count) {
for (size_t i = 0; i < count; i++) {
std::snprintf(dst + 2 * i, 3, "%02x", static_cast<const u8 *>(src)[i]);
}
}
bool GetBytesFromString(void *dst, size_t dst_size, const char *src, size_t src_size) {
if (!util::IsAligned(src_size, 2) || (dst_size * 2 < src_size)) {
return false;
}
for (size_t i = 0; i < src_size; i += 2) {
char tmp[3];
strlcpy(tmp, src + i, sizeof(tmp));
char *err = nullptr;
reinterpret_cast<u8 *>(dst)[i / 2] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16));
if (*err != '\x00') {
return false;
}
}
return true;
}
}
ContentIdString GetContentIdString(ContentId id) {
ContentIdString str;
GetStringFromContentId(str.data, sizeof(str), id);
return str;
}
void GetStringFromContentId(char *dst, size_t dst_size, ContentId id) {
AMS_ABORT_UNLESS(dst_size > ContentIdStringLength);
GetStringFromBytes(dst, std::addressof(id), sizeof(id));
}
std::optional<ContentId> GetContentIdFromString(const char *str, size_t len) {
if (len < ContentIdStringLength) {
return std::nullopt;
}
ContentId content_id;
return GetBytesFromString(std::addressof(content_id), sizeof(content_id), str, ContentIdStringLength) ? std::optional<ContentId>(content_id) : std::nullopt;
}
}

View file

@ -15,16 +15,23 @@
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include <sys/dirent.h>
namespace ams::ncm {
void GetStringFromContentId(char *out, ContentId content_id);
void GetStringFromPlaceHolderId(char *out, PlaceHolderId placeholder_id);
constexpr inline size_t ContentIdStringLength = 2 * sizeof(ContentId);
constexpr inline size_t RightsIdStringLength = 2 * sizeof(fs::RightsId);
constexpr inline size_t TicketFileStringLength = RightsIdStringLength + 4;
constexpr inline size_t CertFileStringLength = RightsIdStringLength + 5;
struct ContentIdString {
char data[ContentIdStringLength + 1];
};
ContentIdString GetContentIdString(ContentId id);
void GetStringFromContentId(char *dst, size_t dst_size, ContentId id);
Result GetPlaceHolderIdFromDirEntry(PlaceHolderId *out, struct dirent *dir_entry);
std::optional<ContentId> GetContentIdFromString(const char *str, size_t len);
};
}

View file

@ -25,75 +25,183 @@ namespace ams::ncm {
namespace {
constexpr u64 BuiltInSystemSaveDataId = 0x8000000000000120;
constexpr u64 BuiltInSystemSaveDataSize = 0x6c000;
constexpr u64 BuiltInSystemSaveDataJournalSize = 0x6c000;
constexpr u32 BuiltInSystemSaveDataFlags = FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment;
constexpr fs::SystemSaveDataId BuiltInSystemSaveDataId = 0x8000000000000120;
constexpr u64 BuiltInSystemSaveDataSize = 0x6c000;
constexpr u64 BuiltInSystemSaveDataJournalSize = 0x6c000;
constexpr u32 BuiltInSystemSaveDataFlags = FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment;
constexpr SystemSaveDataInfo BuiltInSystemSystemSaveDataInfo = {
.id = BuiltInSystemSaveDataId,
.size = BuiltInSystemSaveDataSize,
.journal_size = BuiltInSystemSaveDataJournalSize,
.flags = BuiltInSystemSaveDataFlags,
.space_id = FsSaveDataSpaceId_System
.space_id = fs::SaveDataSpaceId::System
};
constexpr u64 BuiltInUserSaveDataId = 0x8000000000000121;
constexpr u64 BuiltInUserSaveDataSize = 0x29e000;
constexpr u64 BuiltInUserSaveDataJournalSize = 0x29e000;
constexpr u32 BuiltInUserSaveDataFlags = 0;
constexpr fs::SystemSaveDataId BuiltInUserSaveDataId = 0x8000000000000121;
constexpr u64 BuiltInUserSaveDataSize = 0x29e000;
constexpr u64 BuiltInUserSaveDataJournalSize = 0x29e000;
constexpr u32 BuiltInUserSaveDataFlags = 0;
constexpr SystemSaveDataInfo BuiltInUserSystemSaveDataInfo = {
.id = BuiltInUserSaveDataId,
.size = BuiltInUserSaveDataSize,
.journal_size = BuiltInUserSaveDataJournalSize,
.flags = BuiltInUserSaveDataFlags,
.space_id = FsSaveDataSpaceId_System
.space_id = fs::SaveDataSpaceId::System
};
constexpr u64 SdCardSaveDataId = 0x8000000000000124;
constexpr u64 SdCardSaveDataSize = 0xa08000;
constexpr u64 SdCardSaveDataJournalSize = 0xa08000;
constexpr u32 SdCardSaveDataFlags = 0;
constexpr fs::SystemSaveDataId SdCardSaveDataId = 0x8000000000000124;
constexpr u64 SdCardSaveDataSize = 0xa08000;
constexpr u64 SdCardSaveDataJournalSize = 0xa08000;
constexpr u32 SdCardSaveDataFlags = 0;
constexpr SystemSaveDataInfo SdCardSystemSaveDataInfo = {
.id = SdCardSaveDataId,
.size = SdCardSaveDataSize,
.journal_size = SdCardSaveDataJournalSize,
.flags = SdCardSaveDataFlags,
.space_id = FsSaveDataSpaceId_SdSystem,
.space_id = fs::SaveDataSpaceId::SdSystem,
};
constexpr size_t MaxBuiltInSystemContentMetaCount = 0x800;
constexpr size_t MaxBuiltInUserContentMetaCount = 0x2000;
constexpr size_t MaxSdCardContentMetaCount = 0x2000;
constexpr size_t MaxGameCardContentMetaCount = 0x800;
using RootPath = kvdb::BoundedString<32>;
inline void ReplaceMountName(char *out_path, const char *mount_name, const char *path) {
std::strcpy(out_path, mount_name);
std::strcat(out_path, std::strchr(path, ':'));
}
Result EnsureBuiltInSystemSaveDataFlags() {
u32 cur_flags = 0;
R_TRY(fs::GetSaveDataFlags(std::addressof(cur_flags), BuiltInSystemSaveDataId));
if (cur_flags != BuiltInSystemSaveDataFlags) {
R_TRY(fs::SetSaveDataFlags(BuiltInSystemSaveDataId, fs::SaveDataSpaceId::System, BuiltInSystemSaveDataFlags));
}
return ResultSuccess();
}
ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) {
switch (storage_id) {
case StorageId::GameCard: return ResultGameCardContentStorageNotActive();
case StorageId::BuiltInSystem: return ResultNandSystemContentStorageNotActive();
case StorageId::BuiltInUser: return ResultNandUserContentStorageNotActive();
case StorageId::SdCard: return ResultSdCardContentStorageNotActive();
default: return ResultUnknownContentStorageNotActive();
}
}
ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) {
switch (storage_id) {
case StorageId::GameCard: return ResultGameCardContentMetaDatabaseNotActive();
case StorageId::BuiltInSystem: return ResultNandSystemContentMetaDatabaseNotActive();
case StorageId::BuiltInUser: return ResultNandUserContentMetaDatabaseNotActive();
case StorageId::SdCard: return ResultSdCardContentMetaDatabaseNotActive();
default: return ResultUnknownContentMetaDatabaseNotActive();
}
}
}
ContentManagerImpl::~ContentManagerImpl() {
{
std::scoped_lock lk(this->mutex);
std::scoped_lock lk(this->mutex);
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageRoot *entry = &this->content_storage_roots[i];
this->InactivateContentStorage(entry->storage_id);
for (auto &root : this->content_storage_roots) {
this->InactivateContentStorage(root.storage_id);
}
for (auto &root : this->content_meta_database_roots) {
this->InactivateContentMetaDatabase(root.storage_id);
}
}
Result ContentManagerImpl::EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &info) const {
constexpr u64 OwnerId = 0;
fs::DisableAutoSaveDataCreation();
R_TRY_CATCH(fs::MountSystemSaveData(mount_name, info.space_id, info.id)) {
R_CATCH(fs::ResultTargetNotFound) {
R_TRY(fs::CreateSystemSaveData(info.space_id, info.id, OwnerId, info.size, info.journal_size, info.flags));
R_TRY(fs::MountSystemSaveData(mount_name, info.space_id, info.id));
}
} R_END_TRY_CATCH;
for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) {
ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i];
this->InactivateContentMetaDatabase(entry->storage_id);
return ResultSuccess();
}
Result ContentManagerImpl::GetContentStorageRoot(ContentStorageRoot **out, StorageId id) {
R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage());
for (auto &root : this->content_storage_roots) {
if (root.storage_id == id) {
*out = std::addressof(root);
return ResultSuccess();
}
}
for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) {
ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i];
entry->kvs.reset();
return ncm::ResultUnknownStorage();
}
Result ContentManagerImpl::GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id) {
R_UNLESS(IsUniqueStorage(id), ncm::ResultUnknownStorage());
for (auto &root : this->content_meta_database_roots) {
if (root.storage_id == id) {
*out = std::addressof(root);
return ResultSuccess();
}
}
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageRoot *entry = &this->content_storage_roots[i];
entry->content_storage = nullptr;
}
return ncm::ResultUnknownStorage();
}
Result ContentManagerImpl::InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, fs::ContentStorageId content_storage_id) {
out->storage_id = storage_id;
out->content_storage_id = content_storage_id;
out->content_storage = nullptr;
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name);
return ResultSuccess();
}
Result ContentManagerImpl::InitializeGameCardContentStorageRoot(ContentStorageRoot *out) {
out->storage_id = StorageId::GameCard;
out->content_storage = nullptr;
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
std::snprintf(out->path, sizeof(out->path), "%s:/", out->mount_name);
return ResultSuccess();
}
Result ContentManagerImpl::InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, const SystemSaveDataInfo &info, size_t max_content_metas) {
out->storage_id = storage_id;
out->info = info;
out->max_content_metas = max_content_metas;
out->content_meta_database = nullptr;
out->kvs = std::nullopt;
std::strcpy(out->mount_name, impl::CreateUniqueMountName().str);
out->mount_name[0] = '#';
std::snprintf(out->path, sizeof(out->path), "%s:/meta", out->mount_name);
return ResultSuccess();
}
Result ContentManagerImpl::InitializeGameCardContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, size_t max_content_metas) {
out->storage_id = StorageId::GameCard;
out->max_content_metas = max_content_metas;
out->content_meta_database = nullptr;
out->kvs = std::nullopt;
return ResultSuccess();
}
Result ContentManagerImpl::Initialize() {
@ -102,56 +210,52 @@ namespace ams::ncm {
/* Already initialized. */
R_UNLESS(!this->initialized, ResultSuccess());
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageRoot *entry = &this->content_storage_roots[i];
entry->storage_id = StorageId::None;
/* Clear storage id for all roots. */
for (auto &root : this->content_storage_roots) {
root.storage_id = StorageId::None;
}
for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) {
ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i];
entry->storage_id = StorageId::None;
for (auto &root : this->content_meta_database_roots) {
root.storage_id = StorageId::None;
}
/* First, setup the BuiltInSystem storage entry. */
this->content_storage_roots[this->num_content_storage_entries++].Initialize(StorageId::BuiltInSystem, FsContentStorageId_System);
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInSystem, fs::ContentStorageId::System));
if (R_FAILED(this->VerifyContentStorage(StorageId::BuiltInSystem))) {
R_TRY(this->CreateContentStorage(StorageId::BuiltInSystem));
}
R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem));
/* Next, the BuiltInSystem content meta entry. */
R_TRY(this->content_meta_entries[this->num_content_meta_entries++].Initialize(StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, MaxBuiltInSystemContentMetaCount));
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInSystem, BuiltInSystemSystemSaveDataInfo, MaxBuiltInSystemContentMetaCount));
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
/* TODO: N supports a number of unused modes here, we don't bother implementing them currently. */
/* TODO: N supports building the database depending on config (unused on retail). */
}
u32 current_flags = 0;
if (hos::GetVersion() >= hos::Version_200 && R_SUCCEEDED(fs::GetSaveDataFlags(&current_flags, BuiltInSystemSaveDataId)) && current_flags != (FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment)) {
fs::SetSaveDataFlags(BuiltInSystemSaveDataId, FsSaveDataSpaceId_System, FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment);
/* Ensure correct flags on the BuiltInSystem save data. */
if (hos::GetVersion() >= hos::Version_200) {
R_TRY(EnsureBuiltInSystemSaveDataFlags());
}
R_TRY(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem));
/* Now for BuiltInUser's content storage and content meta entries. */
this->content_storage_roots[this->num_content_storage_entries++].Initialize(StorageId::BuiltInUser, FsContentStorageId_User);
R_TRY(this->content_meta_entries[this->num_content_meta_entries++].Initialize(StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, MaxBuiltInUserContentMetaCount));
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[this->num_content_storage_entries++], StorageId::BuiltInUser, fs::ContentStorageId::User));
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[this->num_content_meta_entries++], StorageId::BuiltInUser, BuiltInUserSystemSaveDataInfo, MaxBuiltInUserContentMetaCount));
/* Beyond this point N no longer appears to bother */
/* incrementing the count for content storage entries or content meta entries. */
/* Beyond this point, N uses hardcoded indices. */
/* Next SdCard's content storage and content meta entries. */
this->content_storage_roots[2].Initialize(StorageId::SdCard, FsContentStorageId_SdCard);
R_TRY(this->content_meta_entries[2].Initialize(StorageId::SdCard, SdCardSystemSaveDataInfo, MaxSdCardContentMetaCount));
R_TRY(this->InitializeContentStorageRoot(&this->content_storage_roots[2], StorageId::SdCard, fs::ContentStorageId::SdCard));
R_TRY(this->InitializeContentMetaDatabaseRoot(&this->content_meta_database_roots[2], StorageId::SdCard, SdCardSystemSaveDataInfo, MaxSdCardContentMetaCount));
/* GameCard's content storage and content meta entries. */
/* N doesn't set a content storage id for game cards, so we'll just use 0 (System). */
this->content_storage_roots[3].Initialize(StorageId::GameCard, FsContentStorageId_System);
R_TRY(this->content_meta_entries[3].InitializeGameCard(0x800));
R_TRY(this->InitializeGameCardContentStorageRoot(&this->content_storage_roots[3]));
R_TRY(this->InitializeGameCardContentMetaDatabaseRoot(&this->content_meta_database_roots[3], MaxGameCardContentMetaCount));
this->initialized = true;
return ResultSuccess();
@ -161,13 +265,13 @@ namespace ams::ncm {
std::scoped_lock lk(this->mutex);
ContentStorageRoot *root;
R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id));
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
R_TRY(fs::MountContentStorage(root->mount_point, root->content_storage_id));
ON_SCOPE_EXIT { fs::Unmount(root->mount_point); };
R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id));
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
R_TRY(fs::EnsureDirectoryRecursively(root->path));
R_TRY(fs::EnsureContentAndPlaceHolderRoot(root->path));
R_TRY(impl::EnsureDirectoryRecursively(root->path));
R_TRY(ContentStorageImpl::InitializeBase(root->path));
return ResultSuccess();
}
@ -176,17 +280,16 @@ namespace ams::ncm {
std::scoped_lock lk(this->mutex);
R_UNLESS(storage_id != StorageId::GameCard, ncm::ResultUnknownStorage());
ContentMetaDatabaseEntry *entry;
R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id));
/* N doesn't bother checking the result of this. */
fsDisableAutoSaveDataCreation();
ContentMetaDatabaseRoot *root;
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
R_TRY(EnsureAndMountSystemSaveData(entry->mount_point, entry->save_meta));
ON_SCOPE_EXIT { fs::Unmount(entry->mount_point); };
R_TRY(this->EnsureAndMountSystemSaveData(root->mount_name, root->info));
ON_SCOPE_EXIT { fs::Unmount(root->mount_name); };
R_TRY(fs::EnsureDirectoryRecursively(entry->meta_path));
R_TRY(fsdevCommitDevice(entry->mount_point));
R_TRY(impl::EnsureDirectoryRecursively(root->path));
R_TRY(fs::CommitSaveData(root->mount_name));
return ResultSuccess();
}
@ -195,37 +298,38 @@ namespace ams::ncm {
std::scoped_lock lk(this->mutex);
ContentStorageRoot *root;
R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id));
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
char mount_root[0x80] = {};
auto mount_name = ncm::fs::CreateUniqueMountName(); /* should this be fs::? should it be ncm::? ncm::impl? */
ReplaceMountName(mount_root, mount_name.name, root->path);
char path[0x80];
auto mount_name = impl::CreateUniqueMountName(); /* should this be fs::? should it be ncm::? ncm::impl? */
ReplaceMountName(path, mount_name.str, root->path);
R_TRY(fs::MountContentStorage(mount_name.name, root->content_storage_id));
ON_SCOPE_EXIT { fs::Unmount(mount_name.name); };
R_TRY(fs::MountContentStorage(mount_name.str, root->content_storage_id));
ON_SCOPE_EXIT { fs::Unmount(mount_name.str); };
R_TRY(fs::CheckContentStorageDirectoriesExist(mount_root));
R_TRY(ContentStorageImpl::VerifyBase(path));
return ResultSuccess();
}
Result ContentManagerImpl::VerifyContentMetaDatabase(StorageId storage_id) {
R_UNLESS(storage_id != StorageId::GameCard, ResultSuccess());
std::scoped_lock lk(this->mutex);
R_UNLESS(storage_id != StorageId::GameCard, ResultSuccess());
ContentMetaDatabaseEntry *entry;
R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id));
ContentMetaDatabaseRoot *root;
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
auto mount_guard = SCOPE_GUARD { fs::Unmount(entry->mount_point); };
if (!entry->content_meta_database) {
R_TRY(fs::MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id));
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
if (!root->content_meta_database) {
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
} else {
mount_guard.Cancel();
}
bool has_meta_path = false;
R_TRY(fs::HasDirectory(&has_meta_path, entry->meta_path));
R_UNLESS(has_meta_path, ncm::ResultInvalidContentMetaDatabase());
bool has_dir = false;
R_TRY(impl::HasDirectory(&has_dir, root->path));
R_UNLESS(has_dir, ncm::ResultInvalidContentMetaDatabase());
return ResultSuccess();
}
@ -234,20 +338,18 @@ namespace ams::ncm {
std::scoped_lock lk(this->mutex);
ContentStorageRoot *root;
R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id));
auto content_storage = root->content_storage;
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
if (hos::GetVersion() >= hos::Version_200) {
R_UNLESS(content_storage, GetContentStorageNotActiveResult(storage_id));
R_UNLESS(root->content_storage, GetContentStorageNotActiveResult(storage_id));
} else {
/* 1.0.0 activates content storages as soon as they are opened. */
if (!content_storage) {
if (!root->content_storage) {
R_TRY(this->ActivateContentStorage(storage_id));
content_storage = root->content_storage;
}
}
auto content_storage = root->content_storage;
out.SetValue(std::move(content_storage));
return ResultSuccess();
}
@ -255,73 +357,41 @@ namespace ams::ncm {
Result ContentManagerImpl::OpenContentMetaDatabase(sf::Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id) {
std::scoped_lock lk(this->mutex);
ContentMetaDatabaseEntry *entry;
R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id));
ContentMetaDatabaseRoot *root;
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
auto content_meta_db = entry->content_meta_database;
if (hos::GetVersion() >= hos::Version_200) {
R_UNLESS(content_meta_db, GetContentMetaDatabaseNotActiveResult(storage_id));
R_UNLESS(root->content_meta_database, GetContentMetaDatabaseNotActiveResult(storage_id));
} else {
/* 1.0.0 activates content meta databases as soon as they are opened. */
if (!content_meta_db) {
if (!root->content_meta_database) {
R_TRY(this->ActivateContentMetaDatabase(storage_id));
content_meta_db = entry->content_meta_database;
}
}
auto content_meta_db = root->content_meta_database;
out.SetValue(std::move(content_meta_db));
return ResultSuccess();
}
Result ContentManagerImpl::CloseContentStorageForcibly(StorageId storage_id) {
std::scoped_lock lk(this->mutex);
R_UNLESS(storage_id != StorageId::None, ncm::ResultUnknownStorage());
ContentStorageRoot *root;
R_TRY(FindContentStorageRoot(std::addressof(root), storage_id));
if (root->content_storage) {
/* N doesn't bother checking the result of this */
root->content_storage->DisableForcibly();
fs::Unmount(root->mount_point);
root->content_storage = nullptr;
}
return ResultSuccess();
return this->InactivateContentStorage(storage_id);
}
Result ContentManagerImpl::CloseContentMetaDatabaseForcibly(StorageId storage_id) {
std::scoped_lock lk(this->mutex);
R_UNLESS(storage_id != StorageId::None, ncm::ResultUnknownStorage());
ContentMetaDatabaseEntry *entry;
R_TRY(FindContentMetaDatabaseEntry(&entry, storage_id));
auto content_meta_db = entry->content_meta_database;
if (content_meta_db) {
/* N doesn't bother checking the result of this */
content_meta_db->DisableForcibly();
if (storage_id != StorageId::GameCard) {
fs::Unmount(entry->mount_point);
}
entry->content_meta_database = nullptr;
entry->kvs = std::nullopt;
}
return ResultSuccess();
return this->InactivateContentMetaDatabase(storage_id);
}
Result ContentManagerImpl::CleanupContentMetaDatabase(StorageId storage_id) {
std::scoped_lock lk(this->mutex);
ContentMetaDatabaseEntry *entry;
R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id));
R_TRY(this->InactivateContentMetaDatabase(storage_id));
R_TRY(fsDeleteSaveDataFileSystemBySaveDataSpaceId(entry->save_meta.space_id, entry->save_meta.id));
ContentMetaDatabaseRoot *root;
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
R_TRY(fs::DeleteSaveData(root->info.space_id, root->info.id));
return ResultSuccess();
}
@ -329,45 +399,40 @@ namespace ams::ncm {
std::scoped_lock lk(this->mutex);
ContentStorageRoot *root;
R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id));
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
/* Already activated. */
/* Check if the storage is already activated. */
R_UNLESS(root->content_storage == nullptr, ResultSuccess());
if (storage_id == StorageId::GameCard) {
FsGameCardHandle gc_hnd;
R_TRY(fs::GetGameCardHandle(&gc_hnd));
R_TRY(fs::MountGameCardPartition(root->mount_point, gc_hnd, FsGameCardPartition_Secure));
fs::GameCardHandle handle;
R_TRY(fs::GetGameCardHandle(std::addressof(handle)));
R_TRY(fs::MountGameCardPartition(root->mount_name, handle, fs::GameCardPartition::Secure));
} else {
R_TRY(fs::MountContentStorage(root->mount_point, root->content_storage_id));
R_TRY(fs::MountContentStorage(root->mount_name, root->content_storage_id));
}
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_point); };
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
if (storage_id == StorageId::GameCard) {
auto content_storage = std::make_shared<ReadOnlyContentStorageImpl>();
R_TRY(content_storage->Initialize(root->path, path::MakeContentPathFlat));
R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath));
root->content_storage = std::move(content_storage);
} else {
MakeContentPathFunc content_path_func = nullptr;
MakePlaceHolderPathFunc placeholder_path_func = nullptr;
bool delay_flush = false;
auto content_storage = std::make_shared<ContentStorageImpl>();
switch (storage_id) {
case StorageId::BuiltInSystem:
content_path_func = path::MakeContentPathFlat;
placeholder_path_func = path::MakePlaceHolderPathFlat;
R_TRY(content_storage->Initialize(root->path, MakeFlatContentFilePath, MakeFlatPlaceHolderFilePath, false, std::addressof(this->rights_id_cache)));
break;
case StorageId::SdCard:
delay_flush = true;
R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, true, std::addressof(this->rights_id_cache)));
break;
default:
content_path_func = path::MakeContentPathHashByteLayered;
placeholder_path_func = path::MakePlaceHolderPathHashByteLayered;
R_TRY(content_storage->Initialize(root->path, MakeSha256HierarchicalContentFilePath_ForFat16KCluster, MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster, false, std::addressof(this->rights_id_cache)));
break;
}
R_TRY(content_storage->Initialize(root->path, content_path_func, placeholder_path_func, delay_flush, &this->rights_id_cache));
root->content_storage = std::move(content_storage);
}
@ -379,40 +444,42 @@ namespace ams::ncm {
std::scoped_lock lk(this->mutex);
ContentStorageRoot *root;
R_TRY(GetUniqueContentStorageRoot(std::addressof(root), storage_id));
R_TRY(this->GetContentStorageRoot(std::addressof(root), storage_id));
/* Already inactivated. */
R_UNLESS(root->content_storage != nullptr, ResultSuccess());
if (root->content_storage) {
/* N doesn't bother checking the result of this */
root->content_storage->DisableForcibly();
root->content_storage = nullptr;
fs::Unmount(root->mount_name);
}
root->content_storage->DisableForcibly();
root->content_storage = nullptr;
fs::Unmount(root->mount_point);
return ResultSuccess();
}
Result ContentManagerImpl::ActivateContentMetaDatabase(StorageId storage_id) {
std::scoped_lock lk(this->mutex);
ContentMetaDatabaseEntry *entry;
R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id));
ContentMetaDatabaseRoot *root;
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
/* Already activated. */
R_UNLESS(entry->content_meta_database == nullptr, ResultSuccess());
R_UNLESS(root->content_meta_database == nullptr, ResultSuccess());
/* Make a brand new kvs. N doesn't quite do this, but we will for cleanliness. */
entry->kvs.emplace();
/* Make a new kvs. */
root->kvs.emplace();
if (storage_id != StorageId::GameCard) {
R_TRY(fs::MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id));
auto mount_guard = SCOPE_GUARD { fs::Unmount(entry->mount_point); };
R_TRY(entry->kvs->Initialize(entry->meta_path, entry->max_content_metas));
R_TRY(entry->kvs->Load());
entry->content_meta_database = std::make_shared<ContentMetaDatabaseImpl>(std::addressof(*entry->kvs), entry->mount_point);
mount_guard.Cancel();
if (storage_id == StorageId::GameCard) {
R_TRY(root->kvs->Initialize(root->max_content_metas));
root->content_meta_database = std::make_shared<OnMemoryContentMetaDatabaseImpl>(std::addressof(*root->kvs));
} else {
R_TRY(entry->kvs->Initialize(entry->max_content_metas));
R_TRY(entry->kvs->Load());
entry->content_meta_database = std::make_shared<OnMemoryContentMetaDatabaseImpl>(std::addressof(*entry->kvs));
R_TRY(fs::MountSystemSaveData(root->mount_name, root->info.space_id, root->info.id));
auto mount_guard = SCOPE_GUARD { fs::Unmount(root->mount_name); };
R_TRY(root->kvs->Initialize(root->path, root->max_content_metas));
R_TRY(root->kvs->Load());
root->content_meta_database = std::make_shared<ContentMetaDatabaseImpl>(std::addressof(*root->kvs), root->mount_name);
mount_guard.Cancel();
}
return ResultSuccess();
@ -421,18 +488,17 @@ namespace ams::ncm {
Result ContentManagerImpl::InactivateContentMetaDatabase(StorageId storage_id) {
std::scoped_lock lk(this->mutex);
ContentMetaDatabaseEntry *entry;
R_TRY(GetUniqueContentMetaDatabaseEntry(&entry, storage_id));
ContentMetaDatabaseRoot *root;
R_TRY(this->GetContentMetaDatabaseRoot(&root, storage_id));
/* Already inactivated. */
if (entry->content_meta_database != nullptr) {
entry->content_meta_database->DisableForcibly();
entry->content_meta_database = nullptr;
/* This should lead to Index's destructor performing cleanup for us. */
entry->kvs = std::nullopt;
if (root->content_meta_database) {
/* N doesn't bother checking the result of this */
root->content_meta_database->DisableForcibly();
root->content_meta_database = nullptr;
root->kvs = std::nullopt;
if (storage_id != StorageId::GameCard) {
fs::Unmount(entry->mount_point);
fs::Unmount(root->mount_name);
}
}

View file

@ -16,8 +16,8 @@
#pragma once
#include <stratosphere.hpp>
#include "impl/ncm_rights_cache.hpp"
#include "ncm_fs.hpp"
#include "ncm_rights_cache.hpp"
#include "ncm_fs_utils.hpp"
namespace ams::ncm {
@ -26,177 +26,67 @@ namespace ams::ncm {
u64 size;
u64 journal_size;
u32 flags;
FsSaveDataSpaceId space_id;
fs::SaveDataSpaceId space_id;
};
static_assert(sizeof(SystemSaveDataInfo) == 0x20, "SystemSaveDataInfo definition!");
static_assert(std::is_pod<SystemSaveDataInfo>::value);
class ContentManagerImpl final : public IContentManager {
private:
constexpr static size_t MaxContentStorageEntries = 8;
constexpr static size_t MaxContentMetaDatabaseEntries = 8;
constexpr static size_t MaxContentStorageRoots = 8;
constexpr static size_t MaxContentMetaDatabaseRoots = 8;
private:
struct ContentStorageRoot {
NON_COPYABLE(ContentStorageRoot);
NON_MOVEABLE(ContentStorageRoot);
char mount_point[16];
char mount_name[fs::MountNameLengthMax + 1];
char path[128];
StorageId storage_id;
FsContentStorageId content_storage_id;
fs::ContentStorageId content_storage_id;
std::shared_ptr<IContentStorage> content_storage;
inline ContentStorageRoot() : storage_id(StorageId::None),
content_storage_id(FsContentStorageId_System), content_storage(nullptr) {
mount_point[0] = '\0';
path[0] = '\0';
}
inline void Initialize(StorageId storage_id, FsContentStorageId content_storage_id) {
this->storage_id = storage_id;
this->content_storage_id = content_storage_id;
this->content_storage = nullptr;
MountName mount_name = ncm::fs::CreateUniqueMountName();
std::strcpy(this->mount_point, mount_name.name);
snprintf(this->path, 0x80, "%s:/", this->mount_point);
}
ContentStorageRoot() { /* ... */ }
};
struct ContentMetaDatabaseEntry {
NON_COPYABLE(ContentMetaDatabaseEntry);
NON_MOVEABLE(ContentMetaDatabaseEntry);
struct ContentMetaDatabaseRoot {
NON_COPYABLE(ContentMetaDatabaseRoot);
NON_MOVEABLE(ContentMetaDatabaseRoot);
char mount_point[16];
char meta_path[128];
char mount_name[fs::MountNameLengthMax + 1];
char path[128];
StorageId storage_id;
SystemSaveDataInfo save_meta;
SystemSaveDataInfo info;
std::shared_ptr<IContentMetaDatabase> content_meta_database;
std::optional<kvdb::MemoryKeyValueStore<ContentMetaKey>> kvs;
u32 max_content_metas;
inline ContentMetaDatabaseEntry() : storage_id(StorageId::None), save_meta({0}),
content_meta_database(nullptr), kvs(std::nullopt), max_content_metas(0) {
mount_point[0] = '\0';
meta_path[0] = '\0';
}
Result Initialize(StorageId storage_id, const SystemSaveDataInfo& save_meta, size_t max_content_metas) {
this->storage_id = storage_id;
this->max_content_metas = max_content_metas;
this->save_meta = save_meta;
this->content_meta_database = nullptr;
this->kvs = std::nullopt;
MountName mount_name = ncm::fs::CreateUniqueMountName();
strcpy(this->mount_point, mount_name.name);
this->mount_point[0] = '#';
snprintf(this->meta_path, 0x80, "%s:/meta", this->mount_point);
return ResultSuccess();
}
Result InitializeGameCard(size_t max_content_metas) {
this->storage_id = StorageId::GameCard;
this->max_content_metas = max_content_metas;
this->content_meta_database = nullptr;
this->kvs = std::nullopt;
return ResultSuccess();
}
ContentMetaDatabaseRoot() { /* ... */ }
};
private:
os::Mutex mutex;
bool initialized = false;
ContentStorageRoot content_storage_roots[MaxContentStorageEntries];
ContentMetaDatabaseEntry content_meta_entries[MaxContentMetaDatabaseEntries];
ContentStorageRoot content_storage_roots[MaxContentStorageRoots];
ContentMetaDatabaseRoot content_meta_database_roots[MaxContentMetaDatabaseRoots];
u32 num_content_storage_entries;
u32 num_content_meta_entries;
impl::RightsIdCache rights_id_cache;
RightsIdCache rights_id_cache;
public:
ContentManagerImpl() { /* ... */ };
~ContentManagerImpl();
public:
Result Initialize();
private:
constexpr inline bool IsUniqueStorage(StorageId id) {
return id != StorageId::None && id != StorageId::Any;
}
Result GetContentStorageRoot(ContentStorageRoot **out, StorageId id);
Result GetContentMetaDatabaseRoot(ContentMetaDatabaseRoot **out, StorageId id);
Result FindContentStorageRoot(ContentStorageRoot **out, StorageId storage_id) {
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageRoot *root = &this->content_storage_roots[i];
Result InitializeContentStorageRoot(ContentStorageRoot *out, StorageId storage_id, fs::ContentStorageId content_storage_id);
Result InitializeGameCardContentStorageRoot(ContentStorageRoot *out);
if (root->storage_id == storage_id) {
*out = root;
return ResultSuccess();
}
}
return ncm::ResultUnknownStorage();
}
Result InitializeContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, StorageId storage_id, const SystemSaveDataInfo &info, size_t max_content_metas);
Result InitializeGameCardContentMetaDatabaseRoot(ContentMetaDatabaseRoot *out, size_t max_content_metas);
Result GetUniqueContentStorageRoot(ContentStorageRoot **out, StorageId storage_id) {
R_UNLESS(IsUniqueStorage(storage_id), ncm::ResultUnknownStorage());
return FindContentStorageRoot(out, storage_id);
}
Result EnsureAndMountSystemSaveData(const char *mount, const SystemSaveDataInfo &info) const;
Result FindContentMetaDatabaseEntry(ContentMetaDatabaseEntry **out, StorageId storage_id) {
for (size_t i = 0; i < MaxContentMetaDatabaseEntries; i++) {
ContentMetaDatabaseEntry *entry = &this->content_meta_entries[i];
if (entry->storage_id == storage_id) {
*out = entry;
return ResultSuccess();
}
}
return ncm::ResultUnknownStorage();
}
Result GetUniqueContentMetaDatabaseEntry(ContentMetaDatabaseEntry **out, StorageId storage_id) {
R_UNLESS(IsUniqueStorage(storage_id), ncm::ResultUnknownStorage());
return FindContentMetaDatabaseEntry(out, storage_id);
}
ALWAYS_INLINE Result GetContentStorageNotActiveResult(StorageId storage_id) {
switch (storage_id) {
case StorageId::GameCard:
return ResultGameCardContentStorageNotActive();
case StorageId::BuiltInSystem:
return ResultNandSystemContentStorageNotActive();
case StorageId::BuiltInUser:
return ResultNandUserContentStorageNotActive();
case StorageId::SdCard:
return ResultSdCardContentStorageNotActive();
default:
return ResultUnknownContentStorageNotActive();
}
}
ALWAYS_INLINE Result GetContentMetaDatabaseNotActiveResult(StorageId storage_id) {
switch (storage_id) {
case StorageId::GameCard:
return ResultGameCardContentMetaDatabaseNotActive();
case StorageId::BuiltInSystem:
return ResultNandSystemContentMetaDatabaseNotActive();
case StorageId::BuiltInUser:
return ResultNandUserContentMetaDatabaseNotActive();
case StorageId::SdCard:
return ResultSdCardContentMetaDatabaseNotActive();
default:
return ResultUnknownContentMetaDatabaseNotActive();
}
}
Result EnsureAndMountSystemSaveData(const char *mount_name, const SystemSaveDataInfo &save_meta) {
R_TRY_CATCH(fs::MountSystemSaveData(mount_name, save_meta.space_id, save_meta.id)) {
R_CATCH(ams::fs::ResultTargetNotFound) {
R_TRY(fsCreate_SystemSaveData(save_meta.space_id, save_meta.id, save_meta.size, save_meta.journal_size, save_meta.flags));
R_TRY(fs::MountSystemSaveData(mount_name, save_meta.space_id, save_meta.id));
}
} R_END_TRY_CATCH;
return ResultSuccess();
}
inline void ReplaceMountName(char *out_path, const char *mount_name, const char *root_path) {
strcpy(out_path, mount_name);
strcat(out_path, strchr(root_path, ':'));
}
public:
virtual Result CreateContentStorage(StorageId storage_id) override;
virtual Result CreateContentMetaDatabase(StorageId storage_id) override;

View file

@ -15,7 +15,7 @@
*/
#include "ncm_content_meta_database_impl.hpp"
#include "ncm_utils.hpp"
#include "ncm_content_id_utils.hpp"
namespace ams::ncm {

View file

@ -15,62 +15,207 @@
*/
#include "ncm_content_storage_impl.hpp"
#include "ncm_fs.hpp"
#include "ncm_fs_utils.hpp"
#include "ncm_make_path.hpp"
#include "ncm_utils.hpp"
#include "ncm_content_id_utils.hpp"
namespace ams::ncm {
ContentStorageImpl::~ContentStorageImpl() {
this->Finalize();
namespace {
constexpr inline const char * const BaseContentDirectory = "/registered";
void MakeBaseContentDirectoryPath(PathString *out, const char *root_path) {
out->SetFormat("%s%s", root_path, BaseContentDirectory);
}
void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
PathString path;
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
func(out, id, path);
}
Result EnsureContentDirectory(ContentId id, MakeContentPathFunction func, const char *root_path) {
PathString path;
MakeContentPath(std::addressof(path), id, func, root_path);
return impl::EnsureParentDirectoryRecursively(path);
}
Result DeleteContentFile(ContentId id, MakeContentPathFunction func, const char *root_path) {
PathString path;
MakeContentPath(std::addressof(path), id, func, root_path);
R_TRY_CATCH(fs::DeleteFile(path)) {
R_CONVERT(fs::ResultPathNotFound, ncm::ResultContentNotFound())
} R_END_TRY_CATCH;
return ResultSuccess();
}
template<typename F>
Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) {
R_UNLESS(max_level > 0, ResultSuccess());
bool retry_dir_read = true;
while (retry_dir_read) {
retry_dir_read = false;
fs::DirectoryHandle dir;
R_TRY(fs::OpenDirectory(std::addressof(dir), root_path, fs::OpenDirectoryMode_All | fs::OpenDirectoryMode_NotRequireFileSize));
ON_SCOPE_EXIT { fs::CloseDirectory(dir); };
while (true) {
fs::DirectoryEntry entry;
s64 entry_count;
R_TRY(fs::ReadDirectory(std::addressof(entry_count), std::addressof(entry), dir, 1));
if (entry_count == 0) {
break;
}
PathString current_path;
current_path.SetFormat("%s/%s", root_path, entry.name);
bool should_continue = true;
bool should_retry_dir_read = false;
R_TRY(f(&should_continue, &should_retry_dir_read, current_path, entry));
/* If the provided function wishes to terminate immediately, we should respect it. */
if (!should_continue) {
*out_should_continue = false;
return ResultSuccess();
}
if (should_retry_dir_read) {
retry_dir_read = true;
break;
}
/* If the entry is a directory, recurse. */
if (entry.type == fs::DirectoryEntryType_Directory) {
R_TRY(TraverseDirectory(std::addressof(should_continue), current_path, max_level - 1, f));
if (!should_continue) {
*out_should_continue = false;
return ResultSuccess();
}
}
}
}
return ResultSuccess();
}
template<typename F>
Result TraverseDirectory(const char *root_path, int max_level, F f) {
bool should_continue = false;
return TraverseDirectory(std::addressof(should_continue), root_path, max_level, f);
}
bool IsContentPath(const char *path) {
impl::PathView view(path);
if (!view.HasSuffix(".nca")) {
return false;
}
auto file_name = view.GetFileName();
if (file_name.length() != ContentIdStringLength + 4) {
return false;
}
for (size_t i = 0; i < ContentIdStringLength; i++) {
if (!std::isxdigit(static_cast<unsigned char>(file_name[i]))) {
return false;
}
}
return true;
}
bool IsPlaceHolderPath(const char *path) {
return IsContentPath(path);
}
Result CleanDirectoryRecursively(const PathString &path) {
if (hos::GetVersion() >= hos::Version_300) {
R_TRY(fs::CleanDirectoryRecursively(path));
} else {
/* CleanDirectoryRecursively didn't exist on < 3.0.0, so we will polyfill it. */
/* We'll delete the directory, then recreate it. */
R_TRY(fs::DeleteDirectoryRecursively(path));
R_TRY(fs::CreateDirectory(path));
}
return ResultSuccess();
}
}
Result ContentStorageImpl::Initialize(const char *path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush, impl::RightsIdCache *rights_id_cache) {
R_TRY(this->EnsureEnabled());
R_TRY(fs::CheckContentStorageDirectoriesExist(path));
ContentStorageImpl::~ContentStorageImpl() {
this->InvalidateFileCache();
}
Result ContentStorageImpl::InitializeBase(const char *root_path) {
PathString path;
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
R_TRY(impl::EnsureDirectoryRecursively(path));
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
R_TRY(impl::EnsureDirectoryRecursively(path));
this->root_path = PathString(path);
this->make_content_path_func = *content_path_func;
this->placeholder_accessor.Initialize(this->root_path, *placeholder_path_func, delay_flush);
this->rights_id_cache = rights_id_cache;
return ResultSuccess();
}
void ContentStorageImpl::Finalize() {
this->ClearContentCache();
this->placeholder_accessor.InvalidateAll();
Result ContentStorageImpl::CleanupBase(const char *root_path) {
PathString path;
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
R_TRY(CleanDirectoryRecursively(path));
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
R_TRY(CleanDirectoryRecursively(path));
return ResultSuccess();
}
void ContentStorageImpl::ClearContentCache() {
Result ContentStorageImpl::VerifyBase(const char *root_path) {
PathString path;
bool has_dir;
R_TRY(impl::HasDirectory(std::addressof(has_dir), root_path));
R_UNLESS(has_dir, ncm::ResultContentStorageBaseNotFound());
bool has_registered;
MakeBaseContentDirectoryPath(std::addressof(path), root_path);
R_TRY(impl::HasDirectory(std::addressof(has_registered), path));
bool has_placeholder;
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), root_path);
R_TRY(impl::HasDirectory(std::addressof(has_placeholder), path));
R_UNLESS(has_registered || has_placeholder, ncm::ResultContentStorageBaseNotFound());
R_UNLESS(has_registered, ncm::ResultInvalidContentStorageBase());
R_UNLESS(has_placeholder, ncm::ResultInvalidContentStorageBase());
return ResultSuccess();
}
void ContentStorageImpl::InvalidateFileCache() {
if (this->cached_content_id != InvalidContentId) {
fclose(this->content_cache_file_handle);
fs::CloseFile(this->cached_file_handle);
this->cached_content_id = InvalidContentId;
}
}
unsigned int ContentStorageImpl::GetContentDirectoryDepth() {
if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPathFlat)) {
return 1;
} else if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPathHashByteLayered)) {
return 2;
} else if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPath10BitLayered)) {
return 2;
} else if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPathDualLayered)) {
return 3;
}
AMS_ABORT();
}
Result ContentStorageImpl::OpenCachedContentFile(ContentId content_id) {
Result ContentStorageImpl::OpenContentIdFile(ContentId content_id) {
R_UNLESS(this->cached_content_id != content_id, ResultSuccess());
this->ClearContentCache();
this->InvalidateFileCache();
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
PathString path;
MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path);
R_TRY_CATCH(fs::OpenFile(&this->content_cache_file_handle, content_path, FsOpenMode_Read)) {
R_TRY_CATCH(fs::OpenFile(&this->cached_file_handle, path, fs::OpenMode_Read)) {
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound())
} R_END_TRY_CATCH;
@ -78,6 +223,19 @@ namespace ams::ncm {
return ResultSuccess();
}
Result ContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache) {
R_TRY(this->EnsureEnabled());
R_TRY(VerifyBase(path));
this->root_path = PathString(path);
this->make_content_path_func = content_path_func;
this->placeholder_accessor.Initialize(std::addressof(this->root_path), placeholder_path_func, delay_flush);
this->rights_id_cache = rights_id_cache;
return ResultSuccess();
}
Result ContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
R_TRY(this->EnsureEnabled());
out.SetValue({util::GenerateUuid()});
@ -87,18 +245,15 @@ namespace ams::ncm {
Result ContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) {
R_TRY(this->EnsureEnabled());
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
R_TRY(fs::EnsureParentDirectoryRecursively(content_path));
R_TRY(this->placeholder_accessor.Create(placeholder_id, size));
R_TRY(EnsureContentDirectory(content_id, this->make_content_path_func, this->root_path));
R_TRY(this->placeholder_accessor.CreatePlaceHolderFile(placeholder_id, size));
return ResultSuccess();
}
Result ContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) {
R_TRY(this->EnsureEnabled());
return this->placeholder_accessor.Delete(placeholder_id);
return this->placeholder_accessor.DeletePlaceHolderFile(placeholder_id);
}
Result ContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
@ -108,7 +263,7 @@ namespace ams::ncm {
this->placeholder_accessor.MakePath(std::addressof(placeholder_path), placeholder_id);
bool has = false;
R_TRY(fs::HasFile(&has, placeholder_path));
R_TRY(impl::HasFile(&has, placeholder_path));
out.SetValue(has);
return ResultSuccess();
@ -118,25 +273,23 @@ namespace ams::ncm {
/* Offset is too large */
R_UNLESS(offset <= std::numeric_limits<s64>::max(), ncm::ResultInvalidOffset());
R_TRY(this->EnsureEnabled());
R_TRY(this->placeholder_accessor.Write(placeholder_id, offset, data.GetPointer(), data.GetSize()));
return this->placeholder_accessor.WritePlaceHolderFile(placeholder_id, offset, data.GetPointer(), data.GetSize());
return ResultSuccess();
}
Result ContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) {
this->ClearContentCache();
this->InvalidateFileCache();
R_TRY(this->EnsureEnabled());
PathString placeholder_path;
PathString content_path;
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
this->GetContentPath(std::addressof(content_path), content_id);
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
if (rename(placeholder_path, content_path) != 0) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
R_CONVERT(ams::fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists())
} R_END_TRY_CATCH;
}
R_TRY_CATCH(fs::RenameFile(placeholder_path, content_path)) {
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists())
} R_END_TRY_CATCH;
return ResultSuccess();
}
@ -144,28 +297,19 @@ namespace ams::ncm {
Result ContentStorageImpl::Delete(ContentId content_id) {
R_TRY(this->EnsureEnabled());
this->ClearContentCache();
this->InvalidateFileCache();
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
if (std::remove(content_path) != 0) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultContentNotFound())
} R_END_TRY_CATCH;
}
return ResultSuccess();
return DeleteContentFile(content_id, this->make_content_path_func, this->root_path);
}
Result ContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) {
R_TRY(this->EnsureEnabled());
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
bool has = false;
R_TRY(fs::HasFile(&has, content_path));
R_TRY(impl::HasFile(&has, content_path));
out.SetValue(has);
return ResultSuccess();
@ -175,11 +319,12 @@ namespace ams::ncm {
R_TRY(this->EnsureEnabled());
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
Path common_path;
R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path));
out.SetValue(Path::Encode(common_path.str));
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path));
out.SetValue(common_path);
return ResultSuccess();
}
@ -190,43 +335,44 @@ namespace ams::ncm {
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
Path common_path;
R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, placeholder_path));
out.SetValue(Path::Encode(common_path.str));
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path));
out.SetValue(common_path);
return ResultSuccess();
}
Result ContentStorageImpl::CleanupAllPlaceHolder() {
R_TRY(this->EnsureEnabled());
PathString placeholder_root_path;
this->placeholder_accessor.InvalidateAll();
this->placeholder_accessor.MakeRootPath(std::addressof(placeholder_root_path));
/* Nintendo uses CleanDirectoryRecursively which is 3.0.0+.
We'll just delete the directory and recreate it to support all firmwares. */
R_TRY(fsdevDeleteDirectoryRecursively(placeholder_root_path));
R_UNLESS(mkdir(placeholder_root_path, S_IRWXU) != -1, fsdevGetLastResult());
PathString placeholder_dir;
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path);
CleanDirectoryRecursively(placeholder_dir);
return ResultSuccess();
}
Result ContentStorageImpl::ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
R_TRY(this->EnsureEnabled());
PathString placeholder_root_path;
this->placeholder_accessor.MakeRootPath(std::addressof(placeholder_root_path));
const unsigned int dir_depth = this->placeholder_accessor.GetDirectoryDepth();
PathString placeholder_dir;
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(placeholder_dir), this->root_path);
const size_t max_entries = out_buf.GetSize();
size_t entry_count = 0;
R_TRY(fs::TraverseDirectory(placeholder_root_path, dir_depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) -> Result {
R_TRY(TraverseDirectory(placeholder_dir, placeholder_accessor.GetHierarchicalDirectoryDepth(), [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result {
*should_continue = true;
*should_retry_dir_read = false;
if (dir_entry->d_type == DT_REG) {
R_UNLESS(entry_count <= out_buf.GetSize(), ncm::ResultBufferInsufficient());
if (entry.type == fs::DirectoryEntryType_File) {
R_UNLESS(entry_count <= max_entries, ncm::ResultBufferInsufficient());
PlaceHolderId cur_entry_placeholder_id = {0};
R_TRY(GetPlaceHolderIdFromDirEntry(&cur_entry_placeholder_id, dir_entry));
out_buf[entry_count++] = cur_entry_placeholder_id;
PlaceHolderId placeholder_id;
R_TRY(PlaceHolderAccessor::GetPlaceHolderIdFromFileName(std::addressof(placeholder_id), entry.name));
out_buf[entry_count++] = placeholder_id;
}
return ResultSuccess();
@ -239,72 +385,64 @@ namespace ams::ncm {
Result ContentStorageImpl::GetContentCount(sf::Out<u32> out_count) {
R_TRY(this->EnsureEnabled());
PathString content_root_path;
this->GetContentRootPath(std::addressof(content_root_path));
const unsigned int dir_depth = this->GetContentDirectoryDepth();
u32 content_count = 0;
PathString path;
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func);
size_t count = 0;
R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) -> Result {
R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) -> Result {
*should_continue = true;
*should_retry_dir_read = false;
if (dir_entry->d_type == DT_REG) {
content_count++;
if (entry.type == fs::DirectoryEntryType_File) {
count++;
}
return ResultSuccess();
}));
out_count.SetValue(content_count);
out_count.SetValue(static_cast<u32>(count));
return ResultSuccess();
}
Result ContentStorageImpl::ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 start_offset) {
R_UNLESS(start_offset <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
Result ContentStorageImpl::ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 offset) {
R_UNLESS(offset <= std::numeric_limits<s32>::max(), ncm::ResultInvalidOffset());
R_TRY(this->EnsureEnabled());
PathString content_root_path;
this->GetContentRootPath(std::addressof(content_root_path));
const unsigned int dir_depth = this->GetContentDirectoryDepth();
PathString path;
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
const auto depth = GetHierarchicalContentDirectoryDepth(this->make_content_path_func);
size_t entry_count = 0;
R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) {
R_TRY(TraverseDirectory(path, depth, [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) {
*should_retry_dir_read = false;
*should_continue = true;
if (dir_entry->d_type == DT_REG) {
/* Skip entries until we reach the start offset. */
if (start_offset > 0) {
start_offset--;
return ResultSuccess();
}
/* We have nothing to do if not working with a file. */
if (entry.type != fs::DirectoryEntryType_File) {
return ResultSuccess();
}
/* We don't necessarily expect to be able to completely fill the output buffer. */
if (entry_count > out_buf.GetSize()) {
*should_continue = false;
return ResultSuccess();
}
/* Skip entries until we reach the start offset. */
if (offset > 0) {
--offset;
return ResultSuccess();
}
size_t name_len = strlen(dir_entry->d_name);
std::optional<ContentId> content_id = GetContentIdFromString(dir_entry->d_name, name_len);
/* Skip to the next entry if the id was invalid. */
if (!content_id) {
return ResultSuccess();
}
/* We don't necessarily expect to be able to completely fill the output buffer. */
if (entry_count >= out_buf.GetSize()) {
*should_continue = false;
return ResultSuccess();
}
auto content_id = GetContentIdFromString(entry.name, std::strlen(entry.name));
if (content_id) {
out_buf[entry_count++] = *content_id;
}
return ResultSuccess();
}));
for (size_t i = 0; i < entry_count; i++) {
char content_name[sizeof(ContentId)*2+1] = {0};
GetStringFromContentId(content_name, out_buf[i]);
}
out_count.SetValue(static_cast<u32>(entry_count));
return ResultSuccess();
}
@ -313,17 +451,22 @@ namespace ams::ncm {
R_TRY(this->EnsureEnabled());
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
struct stat st;
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
R_UNLESS(stat(content_path, &st) != -1, fsdevGetLastResult());
out_size.SetValue(st.st_size);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), content_path, fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
s64 file_size;
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
out_size.SetValue(file_size);
return ResultSuccess();
}
Result ContentStorageImpl::DisableForcibly() {
this->disabled = true;
this->ClearContentCache();
this->InvalidateFileCache();
this->placeholder_accessor.InvalidateAll();
return ResultSuccess();
}
@ -331,31 +474,27 @@ namespace ams::ncm {
Result ContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
R_TRY(this->EnsureEnabled());
PathString old_content_path;
PathString new_content_path;
this->InvalidateFileCache();
R_TRY(EnsureContentDirectory(new_content_id, this->make_content_path_func, this->root_path));
R_TRY(this->placeholder_accessor.EnsurePlaceHolderDirectory(placeholder_id));
PathString placeholder_path;
this->ClearContentCache();
/* Ensure the new content path is ready. */
this->GetContentPath(std::addressof(new_content_path), new_content_id);
R_TRY(fs::EnsureParentDirectoryRecursively(new_content_path));
R_TRY(this->placeholder_accessor.EnsureRecursively(placeholder_id));
PathString content_path;
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
if (rename(old_content_path, placeholder_path) != 0) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
R_CONVERT(ams::fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists())
} R_END_TRY_CATCH;
}
MakeContentPath(std::addressof(content_path), old_content_id, this->make_content_path_func, this->root_path);
R_TRY_CATCH(fs::RenameFile(content_path, placeholder_path)) {
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultContentAlreadyExists())
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result ContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) {
R_TRY(this->EnsureEnabled());
R_TRY(this->placeholder_accessor.SetSize(placeholder_id, size));
R_TRY(this->placeholder_accessor.SetPlaceHolderFileSize(placeholder_id, size));
return ResultSuccess();
}
@ -365,10 +504,11 @@ namespace ams::ncm {
R_TRY(this->EnsureEnabled());
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
R_TRY(this->OpenCachedContentFile(content_id));
R_TRY(fs::ReadFile(this->content_cache_file_handle, offset, buf.GetPointer(), buf.GetSize()));
R_TRY(this->OpenContentIdFile(content_id));
R_TRY(fs::ReadFile(this->cached_file_handle, offset, buf.GetPointer(), buf.GetSize()));
return ResultSuccess();
}
@ -383,17 +523,10 @@ namespace ams::ncm {
Result ContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
R_TRY(this->EnsureEnabled());
PathString placeholder_path;
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
Path path;
R_TRY(this->GetPlaceHolderPath(std::addressof(path), placeholder_id));
Path common_path;
R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, placeholder_path));
ncm::RightsId rights_id;
R_TRY(GetRightsId(&rights_id, common_path));
out_rights_id.SetValue(rights_id);
return ResultSuccess();
return GetRightsId(out_rights_id.GetPointer(), path);
}
Result ContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) {
@ -410,17 +543,13 @@ namespace ams::ncm {
return ResultSuccess();
}
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
Path common_path;
R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path));
Path path;
R_TRY(this->GetPath(std::addressof(path), content_id));
ncm::RightsId rights_id;
R_TRY(GetRightsId(&rights_id, common_path));
R_TRY(GetRightsId(std::addressof(rights_id), path));
this->rights_id_cache->Store(content_id, rights_id);
/* Set output. */
out_rights_id.SetValue(rights_id);
return ResultSuccess();
}
@ -430,39 +559,33 @@ namespace ams::ncm {
R_UNLESS(offset <= std::numeric_limits<s64>::max(), ncm::ResultInvalidOffset());
R_TRY(this->EnsureEnabled());
bool is_development = false;
AMS_ABORT_UNLESS(spl::IsDevelopmentHardware());
AMS_ABORT_UNLESS(R_SUCCEEDED(splIsDevelopment(&is_development)));
AMS_ABORT_UNLESS(is_development);
this->InvalidateFileCache();
this->ClearContentCache();
PathString path;
MakeContentPath(std::addressof(path), content_id, this->make_content_path_func, this->root_path);
PathString content_path;
this->GetContentPath(std::addressof(content_path), content_id);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), path.Get(), fs::OpenMode_Write));
ON_SCOPE_EXIT { fs::CloseFile(file); };
FILE *f = nullptr;
R_TRY(fs::OpenFile(&f, content_path, FsOpenMode_Write));
ON_SCOPE_EXIT {
fclose(f);
};
R_TRY(fs::WriteFile(f, offset, data.GetPointer(), data.GetSize(), ams::fs::WriteOption::Flush));
R_TRY(fs::WriteFile(file, offset, data.GetPointer(), data.GetSize(), fs::WriteOption::Flush));
return ResultSuccess();
}
Result ContentStorageImpl::GetFreeSpaceSize(sf::Out<u64> out_size) {
struct statvfs st = {0};
R_UNLESS(statvfs(this->root_path, &st) != -1, fsdevGetLastResult());
out_size.SetValue(st.f_bfree);
s64 size;
R_TRY(fs::GetFreeSpaceSize(std::addressof(size), this->root_path));
out_size.SetValue(size);
return ResultSuccess();
}
Result ContentStorageImpl::GetTotalSpaceSize(sf::Out<u64> out_size) {
struct statvfs st = {0};
R_UNLESS(statvfs(this->root_path, &st) != -1, fsdevGetLastResult());
out_size.SetValue(st.f_blocks);
s64 size;
R_TRY(fs::GetTotalSpaceSize(std::addressof(size), this->root_path));
out_size.SetValue(size);
return ResultSuccess();
}
@ -474,37 +597,39 @@ namespace ams::ncm {
Result ContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<u64> out_size, PlaceHolderId placeholder_id) {
R_TRY(this->EnsureEnabled());
bool found_in_cache = false;
size_t size = 0;
R_TRY(this->placeholder_accessor.GetSize(&found_in_cache, &size, placeholder_id));
if (found_in_cache) {
out_size.SetValue(size);
bool found = false;
s64 file_size = 0;
R_TRY(this->placeholder_accessor.TryGetPlaceHolderFileSize(std::addressof(found), std::addressof(file_size), placeholder_id));
if (found) {
out_size.SetValue(file_size);
return ResultSuccess();
}
PathString placeholder_path;
struct stat st;
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
R_UNLESS(stat(placeholder_path, &st) != -1, fsdevGetLastResult());
out_size.SetValue(st.st_size);
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), placeholder_path, fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
out_size.SetValue(file_size);
return ResultSuccess();
}
Result ContentStorageImpl::RepairInvalidFileAttribute() {
PathString content_root_path;
this->GetContentRootPath(std::addressof(content_root_path));
unsigned int dir_depth = this->GetContentDirectoryDepth();
auto fix_file_attributes = [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, struct dirent *dir_entry) {
/* Callback for TraverseDirectory */
using PathChecker = bool (*)(const char *);
PathChecker path_checker = nullptr;
auto fix_file_attributes = [&](bool *should_continue, bool *should_retry_dir_read, const char *current_path, const fs::DirectoryEntry &entry) {
*should_retry_dir_read = false;
*should_continue = true;
if (dir_entry->d_type == DT_DIR) {
if (path::IsNcaPath(current_path)) {
if (R_SUCCEEDED(fsdevSetConcatenationFileAttribute(current_path))) {
if (entry.type == fs::DirectoryEntryType_Directory) {
if (path_checker(current_path)) {
if (R_SUCCEEDED(fs::SetConcatenationFileAttribute(current_path))) {
*should_retry_dir_read = true;
}
}
@ -513,14 +638,24 @@ namespace ams::ncm {
return ResultSuccess();
};
R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, fix_file_attributes));
/* Fix Content */
{
path_checker = IsContentPath;
PathString path;
MakeBaseContentDirectoryPath(std::addressof(path), this->root_path);
PathString placeholder_root_path;
R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes));
}
/* Fix placeholder. */
this->placeholder_accessor.InvalidateAll();
this->placeholder_accessor.MakeRootPath(std::addressof(placeholder_root_path));
dir_depth = this->placeholder_accessor.GetDirectoryDepth();
{
path_checker = IsPlaceHolderPath;
PathString path;
PlaceHolderAccessor::MakeBaseDirectoryPath(std::addressof(path), this->root_path);
R_TRY(fs::TraverseDirectory(placeholder_root_path, dir_depth, fix_file_attributes));
R_TRY(TraverseDirectory(path, GetHierarchicalContentDirectoryDepth(this->make_content_path_func), fix_file_attributes));
}
return ResultSuccess();
}
@ -536,7 +671,7 @@ namespace ams::ncm {
this->placeholder_accessor.GetPath(std::addressof(placeholder_path), placeholder_id);
Path common_path;
R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, placeholder_path));
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), placeholder_path));
ncm::RightsId rights_id;
R_TRY(GetRightsId(&rights_id, common_path));
@ -544,7 +679,6 @@ namespace ams::ncm {
/* Set output. */
out_rights_id.SetValue(rights_id);
return ResultSuccess();
}

View file

@ -18,38 +18,30 @@
#include <switch.h>
#include <stratosphere.hpp>
#include "impl/ncm_placeholder_accessor.hpp"
#include "impl/ncm_rights_cache.hpp"
#include "ncm_placeholder_accessor.hpp"
#include "ncm_rights_cache.hpp"
#include "ncm_content_storage_impl_base.hpp"
#include "ncm_path_utils.hpp"
#include "ncm_fs_utils.hpp"
namespace ams::ncm {
class ContentStorageImpl : public ContentStorageImplBase {
protected:
impl::PlaceHolderAccessor placeholder_accessor;
PlaceHolderAccessor placeholder_accessor;
ContentId cached_content_id;
FILE *content_cache_file_handle;
impl::RightsIdCache *rights_id_cache;
fs::FileHandle cached_file_handle;
RightsIdCache *rights_id_cache;
public:
static Result InitializeBase(const char *root_path);
static Result CleanupBase(const char *root_path);
static Result VerifyBase(const char *root_path);
public:
~ContentStorageImpl();
Result Initialize(const char *root_path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush, impl::RightsIdCache *rights_id_cache);
void Finalize();
Result Initialize(const char *root_path, MakeContentPathFunction content_path_func, MakePlaceHolderPathFunction placeholder_path_func, bool delay_flush, RightsIdCache *rights_id_cache);
private:
void ClearContentCache();
unsigned int GetContentDirectoryDepth();
Result OpenCachedContentFile(ContentId content_id);
inline void GetContentRootPath(PathString *content_root) {
path::GetContentRootPath(content_root, this->root_path);
}
inline void GetContentPath(PathString *content_path, ContentId content_id) {
PathString root_path;
this->GetContentRootPath(std::addressof(root_path));
this->make_content_path_func(content_path, content_id, root_path);
}
Result OpenContentIdFile(ContentId content_id);
void InvalidateFileCache();
public:
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;

View file

@ -24,21 +24,21 @@ namespace ams::ncm {
NON_MOVEABLE(ContentStorageImplBase);
protected:
PathString root_path;
MakeContentPathFunc make_content_path_func;
MakeContentPathFunction make_content_path_func;
bool disabled;
protected:
ContentStorageImplBase() { /* ... */ }
protected:
Result EnsureEnabled() {
Result EnsureEnabled() const {
R_UNLESS(!this->disabled, ncm::ResultInvalidContentStorage());
return ResultSuccess();
}
static Result GetRightsId(ncm::RightsId *out_rights_id, const Path &path) {
if (hos::GetVersion() >= hos::Version_300) {
R_TRY(ams::fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str));
R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), std::addressof(out_rights_id->key_generation), path.str));
} else {
R_TRY(ams::fs::GetRightsId(std::addressof(out_rights_id->id), path.str));
R_TRY(fs::GetRightsId(std::addressof(out_rights_id->id), path.str));
out_rights_id->key_generation = 0;
}
return ResultSuccess();

View file

@ -1,323 +0,0 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 <map>
#include <fcntl.h>
#include <sys/iosupport.h>
#include "ncm_fs.hpp"
#include "ncm_path_utils.hpp"
namespace ams::ncm::fs {
Result OpenFile(FILE** out, const char *path, u32 mode) {
bool has = false;
/* Manually check if the file already exists, so it doesn't get created automatically. */
R_TRY(HasFile(&has, path));
R_UNLESS(has, ams::fs::ResultPathNotFound());
const char *fopen_mode = "";
if (mode & FsOpenMode_Write) {
fopen_mode = "r+b";
} else if (mode & FsOpenMode_Read) {
fopen_mode = "rb";
}
FILE *f = fopen(path, fopen_mode);
R_UNLESS(f != nullptr, fsdevGetLastResult());
*out = f;
return ResultSuccess();
}
Result WriteFile(FILE *f, size_t offset, const void *buffer, size_t size, ams::fs::WriteOption option) {
R_UNLESS(fseek(f, 0, SEEK_END) == 0, fsdevGetLastResult());
size_t existing_size = ftell(f);
R_UNLESS(offset + size <= existing_size, ams::fs::ResultFileExtensionWithoutOpenModeAllowAppend());
R_UNLESS(fseek(f, offset, SEEK_SET) == 0, fsdevGetLastResult());
R_UNLESS(fwrite(buffer, 1, size, f) == size, fsdevGetLastResult());
if (option.HasFlushFlag()) {
fflush(f);
}
return ResultSuccess();
}
Result ReadFile(FILE *f, size_t offset, void *buffer, size_t size) {
R_UNLESS(fseek(f, offset, SEEK_SET) == 0, fsdevGetLastResult());
R_UNLESS(fread(buffer, 1, size, f) == size, fsdevGetLastResult());
R_UNLESS(!ferror(f), fsdevGetLastResult());
return ResultSuccess();
}
Result HasFile(bool *out, const char *path) {
struct stat st;
if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
*out = true;
} else {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathNotFound) {
*out = false;
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result HasDirectory(bool *out, const char *path) {
struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
*out = true;
} else {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathNotFound) {
*out = false;
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result CheckContentStorageDirectoriesExist(const char *path) {
const PathString root_path(path);
bool has_root = false;
R_TRY(HasDirectory(&has_root, root_path));
R_UNLESS(has_root, ncm::ResultContentStorageBaseNotFound());
PathString content_root;
path::GetContentRootPath(std::addressof(content_root), root_path);
bool has_content_root = false;
R_TRY(HasDirectory(&has_content_root, content_root));
R_UNLESS(has_content_root, ncm::ResultInvalidContentStorageBase());
PathString placeholder_root;
path::GetPlaceHolderRootPath(std::addressof(placeholder_root), root_path);
bool has_placeholder_root = false;
R_TRY(HasDirectory(&has_placeholder_root, placeholder_root));
R_UNLESS(has_placeholder_root, ncm::ResultInvalidContentStorageBase());
return ResultSuccess();
}
Result EnsureContentAndPlaceHolderRoot(const char *path) {
const PathString root_path(path);
PathString content_root;
path::GetContentRootPath(std::addressof(content_root), root_path);
R_TRY(EnsureDirectoryRecursively(content_root));
PathString placeholder_root;
path::GetPlaceHolderRootPath(std::addressof(placeholder_root), root_path);
R_TRY(EnsureDirectoryRecursively(placeholder_root));
return ResultSuccess();
}
Result EnsureDirectoryRecursively(const char *dir_path) {
R_TRY(EnsureRecursively(dir_path));
if (mkdir(dir_path, S_IRWXU) == -1) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathAlreadyExists) {
/* If the path already exists, that's okay. Anything else is an error. */
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result EnsureRecursively(const char *path) {
R_UNLESS(path, ams::fs::ResultNullptrArgument());
size_t path_len = strlen(path);
char working_path_buf[FS_MAX_PATH] = {0};
R_UNLESS(path_len + 1 < FS_MAX_PATH, ncm::ResultAllocationFailed());
strncpy(working_path_buf + 1, path, ams::fs::EntryNameLengthMax);
if (path_len != 0) {
for (size_t i = 0; i < path_len; i++) {
if (i != 0 && working_path_buf[i + 1] == '/' && working_path_buf[i] != ':') {
/* Temporarily make the path terminate before the '/' */
working_path_buf[i + 1] = 0;
if (mkdir(working_path_buf + 1, S_IRWXU) == -1) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathAlreadyExists) {
/* If the path already exists, that's okay. Anything else is an error. */
}
} R_END_TRY_CATCH;
}
/* Restore the path to its former state */
working_path_buf[i + 1] = '/';
}
}
}
return ResultSuccess();
}
Result EnsureParentDirectoryRecursively(const char *path) {
return EnsureRecursively(path);
}
Result GetGameCardHandle(FsGameCardHandle *out_handle) {
FsDeviceOperator devop;
R_TRY(fsOpenDeviceOperator(&devop));
/* Ensure we close even on early return. */
ON_SCOPE_EXIT { fsDeviceOperatorClose(&devop); };
R_TRY(fsDeviceOperatorGetGameCardHandle(&devop, out_handle));
return ResultSuccess();
}
static u32 g_mount_index = 0;
static os::Mutex g_mount_index_lock;
MountName CreateUniqueMountName() {
std::scoped_lock lk(g_mount_index_lock);
MountName mount_name;
g_mount_index++;
snprintf(mount_name.name, sizeof(MountName), "@ncm%08x", g_mount_index);
return mount_name;
}
Result GetMountNameFromPath(MountName *mount_name, const char *path) {
const char *unqual_path = strchr(path, ':');
/* We should be given a qualified path. */
R_UNLESS(unqual_path, ams::fs::ResultInvalidMountName());
R_UNLESS(unqual_path <= path + 0xf, ams::fs::ResultInvalidMountName());
strncpy(mount_name->name, path, unqual_path - path);
return ResultSuccess();
}
Result MountSystemSaveData(const char *mount_point, FsSaveDataSpaceId space_id, u64 save_id) {
R_UNLESS(mount_point, ams::fs::ResultNullptrArgument());
FsSaveDataAttribute save = {
.system_save_data_id = save_id,
.save_data_type = FsSaveDataType_System,
};
FsFileSystem fs;
R_TRY(fsOpenSaveDataFileSystemBySystemSaveDataId(&fs, space_id, &save));
AMS_ABORT_UNLESS(fsdevMountDevice(mount_point, fs) != -1);
return ResultSuccess();
}
constexpr const char *SystemContentMountName = "@SystemContent";
constexpr const char *UserContentMountName = "@UserContent";
constexpr const char *SdCardContentMountName = "@SdCardContent";
constexpr const char *GameCardMountNameBase = "@Gc";
constexpr const char *GameCardPartitionLetters[3] = { "U", "N", "S" };
/* Maps mount names to their common mount names. */
std::map<std::string, std::string> g_mount_content_storage;
Result MountContentStorage(const char *mount_point, FsContentStorageId id) {
R_UNLESS(mount_point, ams::fs::ResultNullptrArgument());
FsFileSystem fs;
R_TRY(fsOpenContentStorageFileSystem(&fs, id));
AMS_ABORT_UNLESS(fsdevMountDevice(mount_point, fs) != -1);
switch (id) {
case FsContentStorageId_System:
g_mount_content_storage[mount_point] = SystemContentMountName;
break;
case FsContentStorageId_User:
g_mount_content_storage[mount_point] = UserContentMountName;
break;
case FsContentStorageId_SdCard:
g_mount_content_storage[mount_point] = SdCardContentMountName;
break;
default:
AMS_ABORT();
};
return ResultSuccess();
}
Result MountGameCardPartition(const char *mount_point, const FsGameCardHandle handle, FsGameCardPartition partition) {
AMS_ABORT_UNLESS(partition <= 2);
FsFileSystem fs;
R_TRY(fsOpenGameCardFileSystem(&fs, &handle, partition));
AMS_ABORT_UNLESS(fsdevMountDevice(mount_point, fs) != -1);
MountName mount = {0};
snprintf(mount.name, sizeof(MountName), "%s%s%08x", GameCardMountNameBase, GameCardPartitionLetters[partition], handle.value);
g_mount_content_storage[mount_point] = mount.name;
return ResultSuccess();
}
Result Unmount(const char *mount_point) {
R_UNLESS(mount_point, ams::fs::ResultNullptrArgument());
/* Erase any content storage mappings which may potentially exist. */
g_mount_content_storage.erase(mount_point);
AMS_ABORT_UNLESS(fsdevUnmountDevice(mount_point) != -1);
return ResultSuccess();
}
Result ConvertToFsCommonPath(char *out_common_path, size_t out_len, const char *path) {
R_UNLESS(out_common_path, ams::fs::ResultNullptrArgument());
R_UNLESS(path, ams::fs::ResultNullptrArgument());
MountName mount_name = {0};
R_TRY(GetMountNameFromPath(&mount_name, path));
R_UNLESS(fsdevGetDeviceFileSystem(mount_name.name), ams::fs::ResultNotMounted());
R_UNLESS(g_mount_content_storage.find(mount_name.name) != g_mount_content_storage.end(), ams::fs::ResultNotMounted());
char translated_path[FS_MAX_PATH] = {0};
std::string common_mount_name = g_mount_content_storage[mount_name.name];
AMS_ABORT_UNLESS(fsdevTranslatePath(path, NULL, translated_path) != -1);
snprintf(out_common_path, out_len, "%s:%s", common_mount_name.c_str(), translated_path);
return ResultSuccess();
}
Result GetSaveDataFlags(u32 *out_flags, u64 save_id) {
FsSaveDataExtraData extra_data;
R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id));
*out_flags = extra_data.flags;
return ResultSuccess();
}
Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags) {
FsSaveDataExtraData extra_data;
R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id));
extra_data.flags = flags;
R_TRY(fsWriteSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), space_id, save_id));
return ResultSuccess();
}
}

View file

@ -1,108 +0,0 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 <switch.h>
#include <stratosphere.hpp>
#include <sys/dirent.h>
namespace ams::ncm::fs {
Result OpenFile(FILE** out, const char *path, u32 mode);
Result WriteFile(FILE *f, size_t offset, const void *buffer, size_t size, ams::fs::WriteOption option);
Result ReadFile(FILE *f, size_t offset, void *buffer, size_t size);
Result HasFile(bool *out, const char *path);
Result HasDirectory(bool *out, const char *path);
Result CheckContentStorageDirectoriesExist(const char *root_path);
Result EnsureContentAndPlaceHolderRoot(const char *root_path);
Result EnsureDirectoryRecursively(const char *dir_path);
Result EnsureRecursively(const char *path);
/* Create all parent directories for a file path */
Result EnsureParentDirectoryRecursively(const char *path);
Result GetGameCardHandle(FsGameCardHandle *out_handle);
MountName CreateUniqueMountName();
Result GetMountNameFromPath(MountName *mount_name, const char *path);
Result MountSystemSaveData(const char *mount_point, FsSaveDataSpaceId space_id, u64 save_id);
Result MountContentStorage(const char *mount_point, FsContentStorageId id);
Result MountGameCardPartition(const char *mount_point, const FsGameCardHandle handle, FsGameCardPartition partition);
Result Unmount(const char *mount_point);
Result ConvertToFsCommonPath(char *out_common_path, size_t len, const char *path);
Result GetSaveDataFlags(u32 *out_flags, u64 save_id);
Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags);
template<typename F>
Result TraverseDirectory(bool *out_should_continue, const char *root_path, int max_level, F f) {
DIR *dir;
struct dirent *dir_entry = nullptr;
R_UNLESS(max_level >= 1, ResultSuccess());
bool retry_dir_read = true;
while (retry_dir_read) {
retry_dir_read = false;
R_UNLESS((dir = opendir(root_path)) != nullptr, fsdevGetLastResult());
ON_SCOPE_EXIT { closedir(dir); };
while ((dir_entry = readdir(dir)) != nullptr) {
if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) {
continue;
}
char current_path[FS_MAX_PATH];
AMS_ABORT_UNLESS(snprintf(current_path, ams::fs::EntryNameLengthMax, "%s/%s", root_path, dir_entry->d_name) >= 0);
bool should_continue = true;
bool should_retry_dir_read = false;
R_TRY(f(&should_continue, &should_retry_dir_read, current_path, dir_entry));
/* If the provided function wishes to terminate immediately, we should respect it. */
if (!should_continue) {
*out_should_continue = false;
return ResultSuccess();
}
if (should_retry_dir_read) {
retry_dir_read = true;
break;
}
if (dir_entry->d_type == DT_DIR) {
R_TRY(TraverseDirectory(&should_continue, current_path, max_level-1, f));
if (!should_continue) {
*out_should_continue = false;
return ResultSuccess();
}
}
}
}
return ResultSuccess();
};
template<typename F>
Result TraverseDirectory(const char *root_path, int max_level, F f) {
bool should_continue = false;
return TraverseDirectory(&should_continue, root_path, max_level, f);
}
}

View file

@ -0,0 +1,175 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 "ncm_fs_utils.hpp"
namespace ams::ncm::impl {
namespace {
Result EnsureDirectory(const char *path) {
/* Create the path, and allow it to already exist. */
R_TRY_CATCH(fs::CreateDirectory(path)) {
R_CONVERT(fs::ResultPathAlreadyExists, ResultSuccess())
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result EnsureDirectoryRecursivelyImpl(const char *path, bool create_last) {
char work_buf[fs::EntryNameLengthMax];
/* Ensure the path is not too long. */
const size_t len = std::strlen(path);
R_UNLESS(len + 1 <= sizeof(work_buf), ResultAllocationFailed());
/* Copy in the path. */
std::strncpy(work_buf, path, sizeof(work_buf));
/* Create all but the last directory. */
for (size_t i = 0; i < len; i++) {
if (i > 0 && fs::PathTool::IsSeparator(work_buf[i]) && !fs::PathTool::IsDriveSeparator(work_buf[i-1])) {
work_buf[i] = fs::StringTraits::NullTerminator;
R_TRY(EnsureDirectory(work_buf));
work_buf[i] = fs::StringTraits::DirectorySeparator;
}
}
/* Create the last directory if requested. */
if (create_last) {
R_TRY(EnsureDirectory(path));
}
return ResultSuccess();
}
Result HasEntry(bool *out, const char *path, fs::DirectoryEntryType type) {
/* Set out to false initially. */
*out = false;
/* Try to get the entry type. */
fs::DirectoryEntryType entry_type;
R_TRY_CATCH(fs::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;
return ResultSuccess();
}
std::atomic<u32> g_mount_name_count;
}
Result HasFile(bool *out, const char *path) {
return HasEntry(out, path, fs::DirectoryEntryType_File);
}
Result HasDirectory(bool *out, const char *path) {
return HasEntry(out, path, fs::DirectoryEntryType_Directory);
}
Result EnsureDirectoryRecursively(const char *path) {
return EnsureDirectoryRecursivelyImpl(path, true);
}
Result EnsureParentDirectoryRecursively(const char *path) {
return EnsureDirectoryRecursivelyImpl(path, false);
}
bool PathView::HasPrefix(std::string_view prefix) const {
return this->path.compare(0, prefix.length(), prefix) == 0;
}
bool PathView::HasSuffix(std::string_view suffix) const {
return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0;
}
std::string_view PathView::GetFileName() const {
auto pos = this->path.find_last_of("/");
return pos != std::string_view::npos ? this->path.substr(pos + 1) : this->path;
}
MountName CreateUniqueMountName() {
MountName name = {};
std::snprintf(name.str, sizeof(name.str), "@ncm%08x", g_mount_name_count.fetch_add(1));
return name;
}
RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name) {
RootDirectoryPath path = {};
std::snprintf(path.str, sizeof(path.str), "%s:/", mount_name.str);
return path;
}
Result CopyFile(const char *dst_path, const char *src_path) {
fs::FileHandle src_file, dst_file;
/* Open the source file and get its size. */
R_TRY(fs::OpenFile(std::addressof(src_file), src_path, fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(src_file); };
s64 file_size;
R_TRY(fs::GetFileSize(std::addressof(file_size), src_file));
/* Create the destination file. */
R_TRY(fs::CreateFile(dst_path, file_size));
/* Open the destination file. */
R_TRY(fs::OpenFile(std::addressof(dst_file), dst_path, fs::OpenMode_Write));
ON_SCOPE_EXIT { fs::CloseFile(dst_file); };
/* Allocate a buffer with which to copy. */
constexpr size_t BufferSize = 4_KB;
AutoBuffer buffer;
R_TRY(buffer.Initialize(BufferSize));
/* Repeatedly read until we've copied all the data. */
s64 offset = 0;
while (offset < file_size) {
const size_t read_size = std::min(static_cast<size_t>(file_size - offset), buffer.GetSize());
R_TRY(fs::ReadFile(src_file, offset, buffer.Get(), read_size));
R_TRY(fs::WriteFile(dst_file, offset, buffer.Get(), read_size, fs::WriteOption::None));
offset += read_size;
}
/* Flush the destination file. */
R_TRY(fs::FlushFile(dst_file));
return ResultSuccess();
}
Result GetSaveDataFlags(u32 *out_flags, u64 save_id) {
FsSaveDataExtraData extra_data;
R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id));
*out_flags = extra_data.flags;
return ResultSuccess();
}
Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags) {
FsSaveDataExtraData extra_data;
R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id));
extra_data.flags = flags;
R_TRY(fsWriteSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), space_id, save_id));
return ResultSuccess();
}
}

View file

@ -18,24 +18,19 @@
#include <switch.h>
#include <stratosphere.hpp>
namespace ams::ncm::path {
namespace ams::ncm::impl {
inline void GetContentRootPath(PathString *content_root, const PathString &root_path) {
content_root->SetFormat("%s%s", root_path.Get(), "/registered");
}
Result HasFile(bool *out, const char *path);
Result HasDirectory(bool *out, const char *path);
inline void GetPlaceHolderRootPath(PathString *placeholder_root, const PathString &root_path) {
placeholder_root->SetFormat("%s%s", root_path.Get(), "/placehld");
}
Result EnsureDirectoryRecursively(const char *path);
Result EnsureParentDirectoryRecursively(const char *path);
void GetContentMetaPath(PathString *out, ContentId content_id, MakeContentPathFunc path_func, const PathString &root_path);
void GetContentFileName(char *out, ContentId content_id);
void GetPlaceHolderFileName(char *out, PlaceHolderId placeholder_id);
bool IsNcaPath(const char *path);
Result CopyFile(const char *dst_path, const char *src_path);
class PathView {
private:
std::string_view path; /* Nintendo uses nn::util::string_view here. */
std::string_view path; /* Nintendo uses util::string_view here. */
public:
PathView(std::string_view p) : path(p) { /* ...*/ }
bool HasPrefix(std::string_view prefix) const;
@ -43,4 +38,15 @@ namespace ams::ncm::path {
std::string_view GetFileName() const;
};
struct MountName {
char str[fs::MountNameLengthMax + 1];
};
struct RootDirectoryPath {
char str[fs::MountNameLengthMax + 3]; /* mount name + :/ */
};
MountName CreateUniqueMountName();
RootDirectoryPath GetRootDirectoryPath(const MountName &mount_name);
}

View file

@ -15,70 +15,113 @@
*/
#include "ncm_make_path.hpp"
#include "ncm_path_utils.hpp"
#include "ncm_content_id_utils.hpp"
namespace ams::ncm::path {
namespace ams::ncm {
namespace {
u16 Get16BitSha256HashPrefix(util::Uuid uuid) {
u8 hash[SHA256_HASH_SIZE];
sha256CalculateHash(hash, uuid.data, sizeof(util::Uuid));
void MakeContentName(PathString *out, ContentId id) {
out->SetFormat("%s.nca", GetContentIdString(id).data);
}
void MakePlaceHolderName(PathString *out, PlaceHolderId id) {
auto &bytes = id.uuid.data;
char tmp[3];
for (size_t i = 0; i < sizeof(bytes); i++) {
std::snprintf(tmp, util::size(tmp), "%02x", bytes[i]);
out->Append(tmp);
}
out->Append(".nca");
}
u16 Get16BitSha256HashPrefix(ContentId id) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
return static_cast<u16>(hash[0]) | (static_cast<u16>(hash[1]) << 8);
}
u8 Get8BitSha256HashPrefix(util::Uuid uuid) {
u8 hash[SHA256_HASH_SIZE];
sha256CalculateHash(hash, uuid.data, sizeof(util::Uuid));
u8 Get8BitSha256HashPrefix(ContentId id) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
return hash[0];
}
u8 Get8BitSha256HashPrefix(PlaceHolderId id) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256Hash(hash, sizeof(hash), std::addressof(id), sizeof(id));
return hash[0];
}
}
void MakeContentPathFlat(PathString *out, ContentId content_id, const PathString &root) {
Path content_name;
GetContentFileName(content_name.str, content_id);
out->SetFormat("%s/%s", root.Get(), content_name.str);
void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path) {
PathString content_name;
MakeContentName(std::addressof(content_name), content_id);
out->SetFormat("%s/%s", root_path, content_name.Get());
}
void MakeContentPathDualLayered(PathString *out, ContentId content_id, const PathString &root) {
const u16 hash = Get16BitSha256HashPrefix(content_id.uuid);
const u32 hash_lower = (hash >> 4) & 0x3f;
void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path) {
const u16 hash = Get16BitSha256HashPrefix(content_id);
const u32 hash_upper = (hash >> 10) & 0x3f;
const u32 hash_lower = (hash >> 4) & 0x3f;
Path content_name;
GetContentFileName(content_name.str, content_id);
out->SetFormat("%s/%08X/%08X/%s", root.Get(), hash_upper, hash_lower, content_name.str);
PathString content_name;
MakeContentName(std::addressof(content_name), content_id);
out->SetFormat("%s/%08X/%08X/%s", root_path, hash_upper, hash_lower, content_name.Get());
}
void MakeContentPath10BitLayered(PathString *out, ContentId content_id, const PathString &root) {
const u32 hash = (Get16BitSha256HashPrefix(content_id.uuid) >> 6) & 0x3FF;
void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path) {
const u32 hash = (Get16BitSha256HashPrefix(content_id) >> 6) & 0x3FF;
Path content_name;
GetContentFileName(content_name.str, content_id);
out->SetFormat("%s/%08X/%s", root.Get(), hash, content_name.str);
PathString content_name;
MakeContentName(std::addressof(content_name), content_id);
out->SetFormat("%s/%08X/%s", root_path, hash, content_name.Get());
}
void MakeContentPathHashByteLayered(PathString *out, ContentId content_id, const PathString &root) {
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(content_id.uuid));
void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path) {
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(content_id));
Path content_name;
GetContentFileName(content_name.str, content_id);
out->SetFormat("%s/%08X/%s", root.Get(), hash_byte, content_name.str);
PathString content_name;
MakeContentName(std::addressof(content_name), content_id);
out->SetFormat("%s/%08X/%s", root_path, hash_byte, content_name.Get());
}
void MakePlaceHolderPathFlat(PathString *out, PlaceHolderId placeholder_id, const PathString &root) {
Path placeholder_name;
GetPlaceHolderFileName(placeholder_name.str, placeholder_id);
out->SetFormat("%s/%s", root.Get(), placeholder_name.str);
size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func) {
if (func == MakeFlatContentFilePath) {
return 1;
} else if (func == MakeSha256HierarchicalContentFilePath_ForFat4KCluster) {
return 3;
} else if (func == MakeSha256HierarchicalContentFilePath_ForFat16KCluster ||
func == MakeSha256HierarchicalContentFilePath_ForFat32KCluster) {
return 2;
} else {
AMS_ABORT();
}
}
void MakePlaceHolderPathHashByteLayered(PathString *out, PlaceHolderId placeholder_id, const PathString &root) {
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(placeholder_id.uuid));
void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path) {
PathString placeholder_name;
MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id);
out->SetFormat("%s/%s", root_path, placeholder_name.Get());
}
Path placeholder_name;
GetPlaceHolderFileName(placeholder_name.str, placeholder_id);
out->SetFormat("%s/%08X/%s", root.Get(), hash_byte, placeholder_name.str);
void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_path) {
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(placeholder_id));
PathString placeholder_name;
MakePlaceHolderName(std::addressof(placeholder_name), placeholder_id);
out->SetFormat("%s/%08X/%s", root_path, hash_byte, placeholder_name.Get());
}
size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func) {
if (func == MakeFlatPlaceHolderFilePath) {
return 1;
} else if (func == MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster) {
return 2;
} else {
AMS_ABORT();
}
}
}

View file

@ -18,14 +18,18 @@
#include <switch.h>
#include <stratosphere.hpp>
namespace ams::ncm::path {
namespace ams::ncm {
void MakeContentPathFlat(PathString *out, ContentId content_id, const PathString &root);
void MakeContentPathHashByteLayered(PathString *out, ContentId content_id, const PathString &root);
void MakeContentPath10BitLayered(PathString *out, ContentId content_id, const PathString &root);
void MakeContentPathDualLayered(PathString *out, ContentId content_id, const PathString &root);
void MakeFlatContentFilePath(PathString *out, ContentId content_id, const char *root_path);
void MakeSha256HierarchicalContentFilePath_ForFat4KCluster(PathString *out, ContentId content_id, const char *root_path);
void MakeSha256HierarchicalContentFilePath_ForFat16KCluster(PathString *out, ContentId content_id, const char *root_path);
void MakeSha256HierarchicalContentFilePath_ForFat32KCluster(PathString *out, ContentId content_id, const char *root_path);
void MakePlaceHolderPathFlat(PathString *out, PlaceHolderId placeholder_id, const PathString &root);
void MakePlaceHolderPathHashByteLayered(PathString *out, PlaceHolderId placeholder_id, const PathString &root);
size_t GetHierarchicalContentDirectoryDepth(MakeContentPathFunction func);
void MakeFlatPlaceHolderFilePath(PathString *out, PlaceHolderId placeholder_id, const char *root_path);
void MakeSha256HierarchicalPlaceHolderFilePath_ForFat16KCluster(PathString *out, PlaceHolderId placeholder_id, const char *root_pathroot);
size_t GetHierarchicalPlaceHolderDirectoryDepth(MakePlaceHolderPathFunction func);
}

View file

@ -1,76 +0,0 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 "ncm_path_utils.hpp"
#include "ncm_utils.hpp"
namespace ams::ncm::path {
void GetContentMetaPath(PathString *out, ContentId content_id, MakeContentPathFunc path_func, const PathString &root_path) {
PathString content_path;
path_func(std::addressof(content_path), content_id, root_path);
out->Set(content_path.GetSubstring(0, content_path.GetLength() - 4));
out->Append(".cnmt.nca");
}
void GetContentFileName(char *out, ContentId content_id) {
char content_name[sizeof(ContentId)*2+1] = {0};
GetStringFromContentId(content_name, content_id);
snprintf(out, ams::fs::EntryNameLengthMax, "%s%s", content_name, ".nca");
}
void GetPlaceHolderFileName(char *out, PlaceHolderId placeholder_id) {
char placeholder_name[sizeof(PlaceHolderId)*2+1] = {0};
GetStringFromPlaceHolderId(placeholder_name, placeholder_id);
snprintf(out, ams::fs::EntryNameLengthMax, "%s%s", placeholder_name, ".nca");
}
bool IsNcaPath(const char *path) {
PathView path_view(path);
if (!path_view.HasSuffix(".nca")) {
return false;
}
std::string_view file_name = path_view.GetFileName();
if (file_name.length() != 0x24) {
return false;
}
for (size_t i = 0; i < sizeof(util::Uuid)*2; i++) {
if (!std::isxdigit(file_name.at(i))) {
return false;
}
}
return true;
}
bool PathView::HasPrefix(std::string_view prefix) const {
return this->path.compare(0, prefix.length(), prefix) == 0;
}
bool PathView::HasSuffix(std::string_view suffix) const {
return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0;
}
std::string_view PathView::GetFileName() const {
return this->path.substr(this->path.find_last_of("/") + 1);
}
}

View file

@ -0,0 +1,232 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 "ncm_placeholder_accessor.hpp"
#include "ncm_make_path.hpp"
namespace ams::ncm {
namespace {
constexpr inline const char * const BasePlaceHolderDirectory = "/registered";
constexpr inline const char * const PlaceHolderExtension = ".nca";
constexpr inline size_t PlaceHolderExtensionLength = 4;
constexpr inline size_t PlaceHolderFileNameLengthWithoutExtension = 2 * sizeof(PlaceHolderId);
constexpr inline size_t PlaceHolderFileNameLength = PlaceHolderFileNameLengthWithoutExtension + PlaceHolderExtensionLength;
void MakeBasePlaceHolderDirectoryPath(PathString *out, const char *root_path) {
out->SetFormat("%s%s", root_path, BasePlaceHolderDirectory);
}
void MakePlaceHolderFilePath(PathString *out, PlaceHolderId id, MakePlaceHolderPathFunction func, const char *root_path) {
PathString path;
MakeBasePlaceHolderDirectoryPath(std::addressof(path), root_path);
func(out, id, path);
}
ALWAYS_INLINE Result ConvertNotFoundResult(Result r) {
R_TRY_CATCH(r) {
R_CONVERT(ams::fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
} R_END_TRY_CATCH;
return ResultSuccess();
}
}
void PlaceHolderAccessor::MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const {
MakePlaceHolderFilePath(out_placeholder_path, placeholder_id, this->make_placeholder_path_func, *this->root_path);
}
void PlaceHolderAccessor::MakeBaseDirectoryPath(PathString *out, const char *root_path) {
MakeBasePlaceHolderDirectoryPath(out, root_path);
}
Result PlaceHolderAccessor::EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id) {
PathString path;
this->MakePath(std::addressof(path), placeholder_id);
return impl::EnsureParentDirectoryRecursively(path);
}
Result PlaceHolderAccessor::GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name) {
R_UNLESS(strnlen(name, PlaceHolderFileNameLength) == PlaceHolderFileNameLength, ncm::ResultInvalidPlaceHolderFile());
R_UNLESS(strncmp(name + PlaceHolderFileNameLengthWithoutExtension, PlaceHolderExtension, PlaceHolderExtensionLength) == 0, ncm::ResultInvalidPlaceHolderFile());
PlaceHolderId placeholder_id = {};
for (size_t i = 0; i < sizeof(placeholder_id); i++) {
char tmp[3];
strlcpy(tmp, name + i * 2, sizeof(tmp));
char *err = nullptr;
reinterpret_cast<u8 *>(std::addressof(placeholder_id))[i] = static_cast<u8>(std::strtoul(tmp, std::addressof(err), 16));
R_UNLESS(*err == '\x00', ncm::ResultInvalidPlaceHolderFile());
}
*out = placeholder_id;
return ResultSuccess();
}
Result PlaceHolderAccessor::Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) {
/* Try to load from the cache. */
R_UNLESS(!this->LoadFromCache(out_handle, placeholder_id), ResultSuccess());
PathString placeholder_path;
this->MakePath(std::addressof(placeholder_path), placeholder_id);
return fs::OpenFile(out_handle, placeholder_path, fs::OpenMode_Write);
}
bool PlaceHolderAccessor::LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id) {
std::scoped_lock lk(this->cache_mutex);
CacheEntry *entry = this->FindInCache(placeholder_id);
if (!entry) {
return false;
}
entry->id = InvalidPlaceHolderId;
*out_handle = entry->handle;
return true;
}
void PlaceHolderAccessor::StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle) {
std::scoped_lock lk(this->cache_mutex);
CacheEntry *entry = this->GetFreeEntry();
entry->id = placeholder_id;
entry->handle = handle;
entry->counter = this->cur_counter++;
}
void PlaceHolderAccessor::Invalidate(CacheEntry *entry) {
if (entry != nullptr) {
fs::FlushFile(entry->handle);
fs::CloseFile(entry->handle);
entry->id = InvalidPlaceHolderId;
}
}
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) {
if (placeholder_id == InvalidPlaceHolderId) {
return nullptr;
}
for (size_t i = 0; i < MaxCacheEntries; i++) {
if (placeholder_id == this->caches[i].id) {
return &this->caches[i];
}
}
return nullptr;
}
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::GetFreeEntry() {
/* Try to find an already free entry. */
for (size_t i = 0; i < MaxCacheEntries; i++) {
if (this->caches[i].id == InvalidPlaceHolderId) {
return std::addressof(this->caches[i]);
}
}
/* Get the oldest entry. */
CacheEntry *entry = std::addressof(this->caches[0]);
for (size_t i = 1; i < MaxCacheEntries; i++) {
if (entry->counter < this->caches[i].counter) {
entry = std::addressof(this->caches[i]);
}
}
this->Invalidate(entry);
return entry;
}
void PlaceHolderAccessor::GetPath(PathString *placeholder_path, PlaceHolderId placeholder_id) {
{
std::scoped_lock lock(this->cache_mutex);
this->Invalidate(this->FindInCache(placeholder_id));
}
this->MakePath(placeholder_path, placeholder_id);
}
Result PlaceHolderAccessor::CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size) {
R_TRY(this->EnsurePlaceHolderDirectory(placeholder_id));
PathString placeholder_path;
this->GetPath(std::addressof(placeholder_path), placeholder_id);
R_TRY_CATCH(fs::CreateFile(placeholder_path, size, fs::CreateOption_BigFile)) {
R_CONVERT(fs::ResultPathAlreadyExists, ncm::ResultPlaceHolderAlreadyExists())
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result PlaceHolderAccessor::DeletePlaceHolderFile(PlaceHolderId placeholder_id) {
PathString placeholder_path;
this->GetPath(std::addressof(placeholder_path), placeholder_id);
R_TRY_CATCH(fs::DeleteFile(placeholder_path)) {
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result PlaceHolderAccessor::WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size) {
fs::FileHandle file;
R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) {
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
} R_END_TRY_CATCH;
ON_SCOPE_EXIT { this->StoreToCache(placeholder_id, file); };
R_TRY(fs::WriteFile(file, offset, buffer, size, this->delay_flush ? fs::WriteOption::Flush : fs::WriteOption::None));
return ResultSuccess();
}
Result PlaceHolderAccessor::SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size) {
fs::FileHandle file;
R_TRY_CATCH(this->Open(std::addressof(file), placeholder_id)) {
R_CONVERT(fs::ResultPathNotFound, ncm::ResultPlaceHolderNotFound())
} R_END_TRY_CATCH;
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(fs::SetFileSize(file, size));
return ResultSuccess();
}
Result PlaceHolderAccessor::TryGetPlaceHolderFileSize(bool *found_in_cache, s64 *out_size, PlaceHolderId placeholder_id) {
fs::FileHandle handle;
auto found = this->LoadFromCache(std::addressof(handle), placeholder_id);
if (found) {
this->StoreToCache(placeholder_id, handle);
R_TRY(fs::GetFileSize(out_size, handle));
*found_in_cache = true;
} else {
*found_in_cache = false;
}
return ResultSuccess();
}
void PlaceHolderAccessor::InvalidateAll() {
for (auto &entry : this->caches) {
if (entry.id != InvalidPlaceHolderId) {
this->Invalidate(std::addressof(entry));
}
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 <switch.h>
#include <stratosphere.hpp>
#include "ncm_fs_utils.hpp"
#include "ncm_make_path.hpp"
namespace ams::ncm {
class PlaceHolderAccessor {
private:
class CacheEntry {
public:
PlaceHolderId id;
fs::FileHandle handle;
u64 counter;
};
static constexpr size_t MaxCacheEntries = 0x2;
private:
std::array<CacheEntry, MaxCacheEntries> caches;
PathString *root_path;
u64 cur_counter;
os::Mutex cache_mutex;
MakePlaceHolderPathFunction make_placeholder_path_func;
bool delay_flush;
private:
Result Open(fs::FileHandle *out_handle, PlaceHolderId placeholder_id);
bool LoadFromCache(fs::FileHandle *out_handle, PlaceHolderId placeholder_id);
void StoreToCache(PlaceHolderId placeholder_id, fs::FileHandle handle);
void Invalidate(CacheEntry *entry);
CacheEntry *FindInCache(PlaceHolderId placeholder_id);
CacheEntry *GetFreeEntry();;
public:
PlaceHolderAccessor() : cur_counter(0), delay_flush(false) {
for (size_t i = 0; i < MaxCacheEntries; i++) {
caches[i].id = InvalidPlaceHolderId;
}
}
~PlaceHolderAccessor() { this->InvalidateAll(); }
static void MakeBaseDirectoryPath(PathString *out, const char *root_path);
static Result GetPlaceHolderIdFromFileName(PlaceHolderId *out, const char *name);
public:
void Initialize(PathString *root, MakePlaceHolderPathFunction path_func, bool delay_flush) {
this->root_path = root;
this->make_placeholder_path_func = path_func;
this->delay_flush = delay_flush;
}
Result CreatePlaceHolderFile(PlaceHolderId placeholder_id, s64 size);
Result DeletePlaceHolderFile(PlaceHolderId placeholder_id);
Result WritePlaceHolderFile(PlaceHolderId placeholder_id, s64 offset, const void *buffer, size_t size);
Result SetPlaceHolderFileSize(PlaceHolderId placeholder_id, s64 size);
Result TryGetPlaceHolderFileSize(bool *out_found, s64 *out_size, PlaceHolderId placeholder_id);
void GetPath(PathString *out_placeholder_path, PlaceHolderId placeholder_id);
void MakePath(PathString *out_placeholder_path, PlaceHolderId placeholder_id) const;
void InvalidateAll();
Result EnsurePlaceHolderDirectory(PlaceHolderId placeholder_id);
size_t GetHierarchicalDirectoryDepth() const { return GetHierarchicalPlaceHolderDirectoryDepth(this->make_placeholder_path_func); }
};
}

View file

@ -14,60 +14,88 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ncm_fs.hpp"
#include "ncm_path_utils.hpp"
#include "ncm_fs_utils.hpp"
#include "ncm_read_only_content_storage_impl.hpp"
namespace ams::ncm {
Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunc content_path_func) {
namespace {
void MakeContentPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
return func(out, id, root_path);
}
void MakeGameCardContentMetaPath(PathString *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
PathString path;
func(std::addressof(path), id, root_path);
*out = path.GetSubstring(0, path.GetLength() - 4);
out->Append(".cnmt.nca");
}
Result OpenContentIdFileImpl(fs::FileHandle *out, ContentId id, MakeContentPathFunction func, const char *root_path) {
PathString path;
MakeContentPath(std::addressof(path), id, func, root_path);
R_TRY_CATCH(fs::OpenFile(out, path, fs::OpenMode_Read)) {
R_CATCH(fs::ResultPathNotFound) {
MakeGameCardContentMetaPath(std::addressof(path), id, func, root_path);
R_TRY(fs::OpenFile(out, path, fs::OpenMode_Read));
}
} R_END_TRY_CATCH;
return ResultSuccess();
}
}
Result ReadOnlyContentStorageImpl::Initialize(const char *path, MakeContentPathFunction content_path_func) {
R_TRY(this->EnsureEnabled());
this->root_path = PathString(path);
this->make_content_path_func = *content_path_func;
this->root_path.Set(path);
this->make_content_path_func = content_path_func;
return ResultSuccess();
}
Result ReadOnlyContentStorageImpl::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::DeletePlaceHolder(PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, sf::InBuffer data) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::Register(PlaceHolderId placeholder_id, ContentId content_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::Delete(ContentId content_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::Has(sf::Out<bool> out, ContentId content_id) {
R_TRY(this->EnsureEnabled());
PathString content_path;
this->make_content_path_func(std::addressof(content_path), content_id, this->root_path);
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
bool has = false;
R_TRY(fs::HasFile(&has, content_path));
bool has;
R_TRY(impl::HasFile(std::addressof(has), content_path));
if (!has) {
path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
R_TRY(fs::HasFile(&has, content_path));
MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
R_TRY(impl::HasFile(std::addressof(has), content_path));
}
out.SetValue(has);
@ -78,58 +106,52 @@ namespace ams::ncm {
R_TRY(this->EnsureEnabled());
PathString content_path;
path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
bool is_content_meta_file = false;
R_TRY(fs::HasFile(&is_content_meta_file, content_path));
MakeGameCardContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
if (!is_content_meta_file) {
this->make_content_path_func(std::addressof(content_path), content_id, this->root_path);
bool has_file;
R_TRY(impl::HasFile(std::addressof(has_file), content_path));
if (!has_file) {
MakeContentPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
}
Path common_path;
R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path));
out.SetValue(Path::Encode(common_path.str));
R_TRY(fs::ConvertToFsCommonPath(common_path.str, sizeof(common_path.str), content_path));
out.SetValue(common_path);
return ResultSuccess();
}
Result ReadOnlyContentStorageImpl::GetPlaceHolderPath(sf::Out<Path> out, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::CleanupAllPlaceHolder() {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::GetContentCount(sf::Out<u32> out_count) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 start_offset) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) {
R_TRY(this->EnsureEnabled());
PathString content_path;
this->make_content_path_func(std::addressof(content_path), content_id, this->root_path);
fs::FileHandle file;
R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path));
ON_SCOPE_EXIT { fs::CloseFile(file); };
bool is_content_file = false;
R_TRY(fs::HasFile(&is_content_file, content_path));
s64 file_size;
R_TRY(fs::GetFileSize(std::addressof(file_size), file));
if (!is_content_file) {
path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
}
struct stat st;
R_UNLESS(stat(content_path, &st) != -1, fsdevGetLastResult());
out_size.SetValue(st.st_size);
out_size.SetValue(file_size);
return ResultSuccess();
}
@ -139,11 +161,11 @@ namespace ams::ncm {
}
Result ReadOnlyContentStorageImpl::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) {
@ -151,33 +173,21 @@ namespace ams::ncm {
R_UNLESS(offset <= std::numeric_limits<s64>::max(), ncm::ResultInvalidOffset());
R_TRY(this->EnsureEnabled());
PathString content_path;
this->make_content_path_func(std::addressof(content_path), content_id, this->root_path);
fs::FileHandle file;
R_TRY(OpenContentIdFileImpl(std::addressof(file), content_id, this->make_content_path_func, this->root_path));
ON_SCOPE_EXIT { fs::CloseFile(file); };
bool is_content_file = false;
R_TRY(fs::HasFile(&is_content_file, content_path));
R_TRY(fs::ReadFile(file, offset, buf.GetPointer(), buf.GetSize()));
if (!is_content_file) {
path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
}
FILE *f = nullptr;
R_TRY(fs::OpenFile(&f, content_path, FsOpenMode_Read));
ON_SCOPE_EXIT {
fclose(f);
};
R_TRY(fs::ReadFile(f, offset, buf.GetPointer(), buf.GetSize()));
return ResultSuccess();
}
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderId(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::GetRightsIdFromContentIdDeprecated(sf::Out<ams::fs::RightsId> out_rights_id, ContentId content_id) {
@ -190,28 +200,18 @@ namespace ams::ncm {
Result ReadOnlyContentStorageImpl::GetRightsIdFromContentId(sf::Out<ncm::RightsId> out_rights_id, ContentId content_id) {
R_TRY(this->EnsureEnabled());
PathString content_path;
path::GetContentMetaPath(std::addressof(content_path), content_id, this->make_content_path_func, this->root_path);
bool is_content_meta_file = false;
R_TRY(fs::HasFile(&is_content_meta_file, content_path));
if (!is_content_meta_file) {
this->make_content_path_func(std::addressof(content_path), content_id, this->root_path);
}
Path common_path;
R_TRY(fs::ConvertToFsCommonPath(common_path.str, ams::fs::EntryNameLengthMax, content_path));
Path path;
R_TRY(this->GetPath(std::addressof(path), content_id));
ncm::RightsId rights_id;
R_TRY(GetRightsId(&rights_id, common_path));
R_TRY(GetRightsId(&rights_id, path));
out_rights_id.SetValue(rights_id);
return ResultSuccess();
}
Result ReadOnlyContentStorageImpl::WriteContentForDebug(ContentId content_id, u64 offset, sf::InBuffer data) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::GetFreeSpaceSize(sf::Out<u64> out_size) {
@ -225,19 +225,19 @@ namespace ams::ncm {
}
Result ReadOnlyContentStorageImpl::FlushPlaceHolder() {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::GetSizeFromPlaceHolderId(sf::Out<u64> out, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::RepairInvalidFileAttribute() {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
Result ReadOnlyContentStorageImpl::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<ncm::RightsId> out_rights_id, PlaceHolderId placeholder_id, ContentId cache_content_id) {
return ResultInvalidContentStorageOperation();
return ncm::ResultWriteToReadOnlyContentStorage();
}
}

View file

@ -23,7 +23,7 @@ namespace ams::ncm {
class ReadOnlyContentStorageImpl : public ContentStorageImplBase {
public:
Result Initialize(const char *root_path, MakeContentPathFunc content_path_func);
Result Initialize(const char *root_path, MakeContentPathFunction content_path_func);
public:
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;

View file

@ -18,7 +18,7 @@
#include <switch.h>
#include <stratosphere.hpp>
namespace ams::ncm::impl {
namespace ams::ncm {
class RightsIdCache {
NON_COPYABLE(RightsIdCache);

View file

@ -1,83 +0,0 @@
/*
* Copyright (c) 2019-2020 Adubbz, 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 "ncm_utils.hpp"
namespace ams::ncm {
void GetStringFromContentId(char *out, ContentId content_id) {
for (size_t i = 0; i < sizeof(ContentId); i++) {
snprintf(out+i*2, 3, "%02x", content_id.uuid[i]);
}
}
void GetStringFromPlaceHolderId(char *out, PlaceHolderId placeholder_id) {
for (size_t i = 0; i < sizeof(PlaceHolderId); i++) {
snprintf(out+i*2, 3, "%02x", placeholder_id.uuid[i]);
}
}
Result GetPlaceHolderIdFromDirEntry(PlaceHolderId *out, struct dirent *dir_entry) {
R_UNLESS(strnlen(dir_entry->d_name, 0x30) == 0x24, ncm::ResultInvalidPlaceHolderFile());
R_UNLESS(strncmp(dir_entry->d_name + 0x20, ".nca", 4) == 0, ncm::ResultInvalidPlaceHolderFile());
u8 tmp[sizeof(PlaceHolderId)] = {};
char byte_string[2];
char *end_ptr;
u64 converted_val;
for (size_t i = 0; i < sizeof(PlaceHolderId); i++) {
char *name_char_pair = dir_entry->d_name + i * 2;
byte_string[0] = name_char_pair[0];
byte_string[1] = name_char_pair[1];
converted_val = strtoull(byte_string, &end_ptr, 0x10);
tmp[i] = (u8)converted_val;
}
PlaceHolderId placeholder_id;
std::memcpy(placeholder_id.uuid.data, tmp, sizeof(PlaceHolderId));
*out = placeholder_id;
return ResultSuccess();
}
std::optional<ContentId> GetContentIdFromString(const char *str, size_t len) {
if (len < 0x20) {
return std::nullopt;
}
u8 tmp[sizeof(ContentId)] = {};
char byte_string[2];
char *end_ptr;
u64 converted_val;
for (size_t i = 0; i < sizeof(ContentId); i++) {
const char *char_par = str + i * 2;
byte_string[0] = char_par[0];
byte_string[1] = char_par[1];
converted_val = strtoull(byte_string, &end_ptr, 0x10);
tmp[i] = (u8)converted_val;
}
ContentId content_id;
std::memcpy(content_id.uuid.data, tmp, sizeof(ContentId));
return std::optional<ContentId>(content_id);
}
}