mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-04-22 04:24:48 +00:00
ncm: use fs bindings, other refactoring
This commit is contained in:
parent
0fe4e2950e
commit
f4af4f8be0
36 changed files with 1470 additions and 1596 deletions
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
};
|
||||
|
||||
}
|
72
stratosphere/ncm/source/ncm_content_id_utils.cpp
Normal file
72
stratosphere/ncm/source/ncm_content_id_utils.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
};
|
||||
}
|
|
@ -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(¤t_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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
#include "ncm_content_meta_database_impl.hpp"
|
||||
#include "ncm_utils.hpp"
|
||||
#include "ncm_content_id_utils.hpp"
|
||||
|
||||
namespace ams::ncm {
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
175
stratosphere/ncm/source/ncm_fs_utils.cpp
Normal file
175
stratosphere/ncm/source/ncm_fs_utils.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
232
stratosphere/ncm/source/ncm_placeholder_accessor.cpp
Normal file
232
stratosphere/ncm/source/ncm_placeholder_accessor.cpp
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
83
stratosphere/ncm/source/ncm_placeholder_accessor.hpp
Normal file
83
stratosphere/ncm/source/ncm_placeholder_accessor.hpp
Normal 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); }
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::ncm::impl {
|
||||
namespace ams::ncm {
|
||||
|
||||
class RightsIdCache {
|
||||
NON_COPYABLE(RightsIdCache);
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue