Merge remote-tracking branch 'Adubbz/ncm' into ncm

# Conflicts:
#	libraries/libvapours/include/vapours/util.hpp
This commit is contained in:
Adubbz 2020-02-24 16:03:50 +11:00
commit 453c2f3309
52 changed files with 6468 additions and 1 deletions

View file

@ -40,7 +40,9 @@
#include "stratosphere/hos.hpp"
#include "stratosphere/kvdb.hpp"
#include "stratosphere/ldr.hpp"
#include "stratosphere/lr.hpp"
#include "stratosphere/map.hpp"
#include "stratosphere/ncm.hpp"
#include "stratosphere/patcher.hpp"
#include "stratosphere/pm.hpp"
#include "stratosphere/reg.hpp"

View file

@ -0,0 +1,20 @@
/*
* Copyright (c) 2018-2019 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 "lr/lr_types.hpp"

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 <vapours.hpp>
#include "../fs/fs_directory.hpp"
#include "../sf/sf_buffer_tags.hpp"
namespace ams::lr {
struct alignas(4) Path : ams::sf::LargeData {
char str[fs::EntryNameLengthMax];
static constexpr Path Encode(const char *p) {
Path path = {};
for (size_t i = 0; i < sizeof(path) - 1; i++) {
path.str[i] = p[i];
if (p[i] == '\x00') {
break;
}
}
return path;
}
constexpr inline size_t GetLength() const {
size_t len = 0;
for (size_t i = 0; i < sizeof(this->str) - 1 && this->str[i] != '\x00'; i++) {
len++;
}
return len;
}
constexpr inline bool IsValid() const {
for (size_t i = 0; i < sizeof(this->str); i++) {
if (this->str[i] == '\x00') {
return true;
}
}
return false;
}
};
static_assert(std::is_pod<Path>::value && sizeof(Path) == fs::EntryNameLengthMax);
}

View file

@ -19,6 +19,104 @@
namespace ams::ncm {
enum class ContentMetaType : u8 {
Unknown = 0x0,
SystemProgram = 0x1,
SystemData = 0x2,
SystemUpdate = 0x3,
BootImagePackage = 0x4,
BootImagePackageSafe = 0x5,
Application = 0x80,
Patch = 0x81,
AddOnContent = 0x82,
Delta = 0x83,
};
enum class ContentType : u8 {
Meta = 0,
Program = 1,
Data = 2,
Control = 3,
HtmlDocument = 4,
LegalInformation = 5,
DeltaFragment = 6,
};
enum class ContentMetaAttribute : u8 {
None = 0,
IncludesExFatDriver = 1,
Rebootless = 2,
};
enum class ContentInstallType : u8 {
Full = 0,
FragmentOnly = 1,
Unknown = 7,
};
struct MountName {
char name[0x10];
};
struct PlaceHolderId {
util::Uuid uuid;
bool operator==(const PlaceHolderId& other) const {
return this->uuid == other.uuid;
}
bool operator!=(const PlaceHolderId& other) const {
return this->uuid != other.uuid;
}
bool operator==(const util::Uuid& other) const {
return this->uuid == other;
}
bool operator!=(const util::Uuid& other) const {
return this->uuid != other;
}
} __attribute__((aligned(8)));
static_assert(__alignof__(PlaceHolderId) == 8, "PlaceHolderId definition!");
struct ContentId {
util::Uuid uuid;
bool operator==(const ContentId& other) const {
return this->uuid == other.uuid;
}
bool operator!=(const ContentId& other) const {
return this->uuid != other.uuid;
}
bool operator==(const util::Uuid& other) const {
return this->uuid == other;
}
bool operator!=(const util::Uuid& other) const {
return this->uuid != other;
}
} __attribute__((aligned(4)));
static_assert(__alignof__(ContentId) == 4, "ContentId definition!");
static constexpr PlaceHolderId InvalidPlaceHolderId = { util::InvalidUuid };
static constexpr ContentId InvalidContentId = { util::InvalidUuid };
struct ContentInfo {
ContentId content_id;
u8 size[6];
ContentType content_type;
u8 id_offset;
};
static_assert(sizeof(ContentInfo) == 0x18, "ContentInfo definition!");
typedef void (*MakeContentPathFunc)(char* out, ContentId content_id, const char* root);
typedef void (*MakePlaceHolderPathFunc)(char* out, PlaceHolderId placeholder_id, const char* root);
/* Storage IDs. */
enum class StorageId : u8 {
#define DEFINE_ENUM_MEMBER(nm) nm = NcmStorageId_##nm
@ -444,4 +542,65 @@ namespace ams::ncm {
static_assert(sizeof(ProgramLocation) == 0x10 && std::is_pod<ProgramLocation>::value, "ProgramLocation definition!");
static_assert(sizeof(ProgramLocation) == sizeof(::NcmProgramLocation) && alignof(ProgramLocation) == alignof(::NcmProgramLocation), "ProgramLocation Libnx Compatibility");
struct ContentMetaKey {
ProgramId id;
u32 version;
ContentMetaType type;
ContentInstallType install_type;
u8 padding[2];
bool operator<(const ContentMetaKey& other) const {
if (this->id < other.id) {
return true;
} else if (this->id != other.id) {
return false;
}
if (this->version < other.version) {
return true;
} else if (this->version != other.version) {
return false;
}
if (this->type < other.type) {
return true;
} else if (this->type != other.type) {
return false;
}
return this->install_type < other.install_type;
}
bool operator==(const ContentMetaKey& other) const {
return this->id == other.id &&
this->version == other.version &&
this->type == other.type &&
this->install_type == other.install_type;
}
bool operator!=(const ContentMetaKey& other) const {
return !(*this == other);
}
static constexpr ContentMetaKey Make(ProgramId program_id, u32 version, ContentMetaType type) {
return { .id = program_id, .version = version, .type = type };
}
static constexpr ContentMetaKey Make(ProgramId program_id, u32 version, ContentMetaType type, ContentInstallType install_type) {
return { .id = program_id, .version = version, .type = type, .install_type = install_type };
}
};
static_assert(sizeof(ContentMetaKey) == 0x10, "ContentMetaKey definition!");
/* Used by system updates. They share the exact same struct as ContentMetaKey */
typedef ContentMetaKey ContentMetaInfo;
struct ApplicationContentMetaKey {
ContentMetaKey key;
ProgramId application_program_id;
};
static_assert(sizeof(ApplicationContentMetaKey) == 0x18, "ApplicationContentMetaKey definition!");
}

View file

@ -28,6 +28,7 @@ namespace ams::lr {
R_DEFINE_ERROR_RESULT(AddOnContentNotFound, 7);
R_DEFINE_ERROR_RESULT(ControlNotFound, 8);
R_DEFINE_ERROR_RESULT(LegalInformationNotFound, 9);
R_DEFINE_ERROR_RESULT(DebugProgramNotFound, 10);
R_DEFINE_ERROR_RESULT(TooManyRegisteredPaths, 90);

View file

@ -21,6 +21,7 @@ namespace ams::ncm {
R_DEFINE_NAMESPACE_RESULT_MODULE(5);
R_DEFINE_ERROR_RESULT(StoragePathNotFound, 1);
R_DEFINE_ERROR_RESULT(PlaceHolderAlreadyExists, 2);
R_DEFINE_ERROR_RESULT(PlaceHolderNotFound, 3);
R_DEFINE_ERROR_RESULT(ContentAlreadyExists, 4);
@ -32,9 +33,13 @@ namespace ams::ncm {
R_DEFINE_ERROR_RESULT(InvalidContentStorage, 100);
R_DEFINE_ERROR_RESULT(InvalidContentMetaDatabase, 110);
R_DEFINE_ERROR_RESULT(InvalidPlaceHolderDirectoryEntry, 170);
R_DEFINE_ERROR_RESULT(BufferInsufficient, 180);
R_DEFINE_ERROR_RESULT(InvalidContentStorageOperation, 190);
R_DEFINE_ERROR_RESULT(InvalidContentMetaKey, 240);
R_DEFINE_ERROR_RESULT(StorageRootNotFound, 310);
R_DEFINE_ERROR_RANGE(ContentStorageNotActive, 250, 258);
R_DEFINE_ERROR_RESULT(GameCardContentStorageNotActive, 251);
R_DEFINE_ERROR_RESULT(NandSystemContentStorageNotActive, 252);

View file

@ -31,3 +31,4 @@
#include <vapours/util/util_intrusive_red_black_tree.hpp>
#include <vapours/util/util_tinymt.hpp>
#include <vapours/util/util_bitutil.hpp>
#include <vapours/util/util_uuid.hpp>

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2018-2019 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 <cstring>
#include <switch.h>
namespace ams::util {
struct Uuid {
u8 uuid[0x10];
bool operator==(const Uuid& other) const {
return memcmp(this->uuid, other.uuid, sizeof(Uuid)) == 0;
}
bool operator!=(const Uuid& other) const {
return !(*this == other);
}
u8& operator[](size_t i) {
return uuid[i];
}
};
static_assert(sizeof(Uuid) == 0x10, "Uuid definition!");
static constexpr Uuid InvalidUuid = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } };
}

View file

@ -1,4 +1,4 @@
MODULES := loader pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2
MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2
SUBFOLDERS := $(MODULES)

122
stratosphere/ncm/Makefile Normal file
View file

@ -0,0 +1,122 @@
#---------------------------------------------------------------------------------
# pull in common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.c)) $(notdir $(wildcard $(dir)/*.board.*.c)) $(notdir $(wildcard $(dir)/*.os.*.c)), \
$(notdir $(wildcard $(dir)/*.c))))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).c)))
CPPFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.cpp)) $(notdir $(wildcard $(dir)/*.board.*.cpp)) $(notdir $(wildcard $(dir)/*.os.*.cpp)), \
$(notdir $(wildcard $(dir)/*.cpp))))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).cpp)))
SFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.s)) $(notdir $(wildcard $(dir)/*.board.*.s)) $(notdir $(wildcard $(dir)/*.os.*.s)), \
$(notdir $(wildcard $(dir)/*.s))))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).kip $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).kip
$(OUTPUT).kip : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

67
stratosphere/ncm/ncm.json Normal file
View file

@ -0,0 +1,67 @@
{
"name": "NCM",
"title_id": "0x0100000000000002",
"main_thread_stack_size": "0x00004000",
"main_thread_priority": 49,
"default_cpu_id": 3,
"process_category": 1,
"kernel_capabilities": [
{
"type": "handle_table_size",
"value": 128
}, {
"type": "syscalls",
"value": {
"svcSetHeapSize" : "0x01",
"svcSetMemoryPermission" : "0x02",
"svcSetMemoryAttribute" : "0x03",
"svcMapMemory" : "0x04",
"svcUnmapMemory" : "0x05",
"svcQueryMemory" : "0x06",
"svcExitProcess" : "0x07",
"svcCreateThread" : "0x08",
"svcStartThread" : "0x09",
"svcExitThread" : "0x0A",
"svcSleepThread" : "0x0B",
"svcGetThreadPriority" : "0x0C",
"svcSetThreadPriority" : "0x0D",
"svcGetThreadCoreMask" : "0x0E",
"svcSetThreadCoreMask" : "0x0F",
"svcGetCurrentProcessorNumber" : "0x10",
"svcSignalEvent" : "0x11",
"svcClearEvent" : "0x12",
"svcMapSharedMemory" : "0x13",
"svcUnmapSharedMemory" : "0x14",
"svcCreateTransferMemory" : "0x15",
"svcCloseHandle" : "0x16",
"svcResetSignal" : "0x17",
"svcWaitSynchronization" : "0x18",
"svcCancelSynchronization" : "0x19",
"svcArbitrateLock" : "0x1A",
"svcArbitrateUnlock" : "0x1B",
"svcWaitProcessWideKeyAtomic" : "0x1C",
"svcSignalProcessWideKey" : "0x1D",
"svcGetSystemTick" : "0x1E",
"svcConnectToNamedPort" : "0x1F",
"svcSendSyncRequestLight" : "0x20",
"svcSendSyncRequest" : "0x21",
"svcSendSyncRequestWithUserBuffer" : "0x22",
"svcSendAsyncRequestWithUserBuffer" : "0x23",
"svcGetProcessId" : "0x24",
"svcGetThreadId" : "0x25",
"svcBreak" : "0x26",
"svcOutputDebugString" : "0x27",
"svcReturnFromException" : "0x28",
"svcGetInfo" : "0x29",
"svcWaitForAddress" : "0x34",
"svcSignalToAddress" : "0x35",
"svcCreateSession" : "0x40",
"svcAcceptSession" : "0x41",
"svcReplyAndReceiveLight" : "0x42",
"svcReplyAndReceive" : "0x43",
"svcReplyAndReceiveWithUserBuffer" : "0x44",
"svcCallSecureMonitor" : "0x7F"
}
}
]
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "../lr_contentlocationresolver.hpp"
#include "../lr_redirectonlylocationresolver.hpp"
#include "lr_manager.hpp"
#include "ncm_bounded_map.hpp"
namespace ams::lr::impl {
namespace {
ncm::impl::BoundedMap<ncm::StorageId, std::shared_ptr<ILocationResolver>, 5> g_location_resolvers;
std::shared_ptr<RegisteredLocationResolverInterface> g_registered_location_resolver = nullptr;
std::shared_ptr<AddOnContentLocationResolverInterface> g_add_on_content_location_resolver = nullptr;
os::Mutex g_mutex;
}
Result OpenLocationResolver(sf::Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id) {
std::scoped_lock lk(g_mutex);
auto resolver = g_location_resolvers.Find(storage_id);
if (!resolver) {
if (storage_id == ncm::StorageId::Host) {
g_location_resolvers[storage_id] = std::make_shared<RedirectOnlyLocationResolverInterface>();
} else {
auto content_resolver = std::make_shared<ContentLocationResolverInterface>(storage_id);
R_TRY(content_resolver->Refresh());
g_location_resolvers[storage_id] = std::move(content_resolver);
}
resolver = g_location_resolvers.Find(storage_id);
}
std::shared_ptr<ILocationResolver> new_intf = *resolver;
out.SetValue(std::move(new_intf));
return ResultSuccess();
}
Result OpenRegisteredLocationResolver(sf::Out<std::shared_ptr<RegisteredLocationResolverInterface>> out) {
std::scoped_lock lk(g_mutex);
if (!g_registered_location_resolver) {
g_registered_location_resolver = std::make_shared<RegisteredLocationResolverInterface>();
}
std::shared_ptr<RegisteredLocationResolverInterface> new_intf = g_registered_location_resolver;
out.SetValue(std::move(new_intf));
return ResultSuccess();
}
Result RefreshLocationResolver(ncm::StorageId storage_id) {
std::scoped_lock lk(g_mutex);
auto resolver = g_location_resolvers.Find(storage_id);
if (!resolver) {
return ResultUnknownStorageId();
}
if (storage_id != ncm::StorageId::Host) {
(*resolver)->Refresh();
}
return ResultSuccess();
}
Result OpenAddOnContentLocationResolver(sf::Out<std::shared_ptr<AddOnContentLocationResolverInterface>> out) {
std::scoped_lock lk(g_mutex);
if (!g_add_on_content_location_resolver) {
g_add_on_content_location_resolver = std::make_shared<AddOnContentLocationResolverInterface>();
}
std::shared_ptr<AddOnContentLocationResolverInterface> new_intf = g_add_on_content_location_resolver;
out.SetValue(std::move(new_intf));
return ResultSuccess();
}
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "../lr_addoncontentlocationresolver.hpp"
#include "../lr_ilocationresolver.hpp"
#include "../lr_registeredlocationresolver.hpp"
namespace ams::lr::impl {
/* Location Resolver API. */
Result OpenLocationResolver(sf::Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id);
Result OpenRegisteredLocationResolver(sf::Out<std::shared_ptr<RegisteredLocationResolverInterface>> out);
Result RefreshLocationResolver(ncm::StorageId storage_id);
Result OpenAddOnContentLocationResolver(sf::Out<std::shared_ptr<AddOnContentLocationResolverInterface>> out);
}

View file

@ -0,0 +1,131 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "lr_redirection.hpp"
namespace ams::lr::impl {
class LocationRedirection : public util::IntrusiveListBaseNode<LocationRedirection> {
NON_COPYABLE(LocationRedirection);
NON_MOVEABLE(LocationRedirection);
private:
ncm::ProgramId program_id;
ncm::ProgramId owner_id;
Path path;
u32 flags;
public:
LocationRedirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path& path, u32 flags) :
program_id(program_id), owner_id(owner_id), path(path), flags(flags) { /* ... */ }
ncm::ProgramId GetProgramId() const {
return this->program_id;
}
ncm::ProgramId GetOwnerProgramId() const {
return this->owner_id;
}
void GetPath(Path *out) const {
*out = this->path;
}
u32 GetFlags() const {
return this->flags;
}
void SetFlags(u32 flags) {
this->flags = flags;
}
};
bool LocationRedirector::FindRedirection(Path *out, ncm::ProgramId program_id) {
if (this->redirection_list.empty()) {
return false;
}
for (const auto &redirection : this->redirection_list) {
if (redirection.GetProgramId() == program_id) {
redirection.GetPath(out);
return true;
}
}
return false;
}
void LocationRedirector::SetRedirection(ncm::ProgramId program_id, const Path &path, u32 flags) {
this->SetRedirection(program_id, path, flags);
}
void LocationRedirector::SetRedirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, u32 flags) {
this->EraseRedirection(program_id);
this->redirection_list.push_back(*(new LocationRedirection(program_id, owner_id, path, flags)));
}
void LocationRedirector::SetRedirectionFlags(ncm::ProgramId program_id, u32 flags) {
for (auto &redirection : this->redirection_list) {
if (redirection.GetProgramId() == program_id) {
redirection.SetFlags(flags);
break;
}
}
}
void LocationRedirector::EraseRedirection(ncm::ProgramId program_id) {
for (auto &redirection : this->redirection_list) {
if (redirection.GetProgramId() == program_id) {
this->redirection_list.erase(this->redirection_list.iterator_to(redirection));
delete &redirection;
break;
}
}
}
void LocationRedirector::ClearRedirections(u32 flags) {
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end();) {
if ((it->GetFlags() & flags) == flags) {
auto old = it;
it = this->redirection_list.erase(it);
delete &(*old);
} else {
it++;
}
}
}
void LocationRedirector::ClearRedirections(const ncm::ProgramId* excluding_ids, size_t num_ids) {
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end();) {
bool skip = false;
for (size_t i = 0; i < num_ids; i++) {
ncm::ProgramId id = excluding_ids[i];
if (it->GetOwnerProgramId() == id) {
skip = true;
break;
}
}
if (skip) {
continue;
}
auto old = it;
it = this->redirection_list.erase(it);
delete &(*old);
it++;
}
}
}

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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>
namespace ams::lr::impl {
enum RedirectionFlags {
RedirectionFlags_None = (0 << 0),
RedirectionFlags_Application = (1 << 0),
};
class LocationRedirection;
class LocationRedirector {
NON_COPYABLE(LocationRedirector);
NON_MOVEABLE(LocationRedirector);
private:
ams::util::IntrusiveListBaseTraits<LocationRedirection>::ListType redirection_list;
public:
LocationRedirector() { /* ... */ }
bool FindRedirection(Path *out, ncm::ProgramId program_id);
void SetRedirection(ncm::ProgramId program_id, const Path &path, u32 flags = RedirectionFlags_None);
void SetRedirection(ncm::ProgramId program_id, ncm::ProgramId owner_id, const Path &path, u32 flags = RedirectionFlags_None);
void SetRedirectionFlags(ncm::ProgramId program_id, u32 flags);
void EraseRedirection(ncm::ProgramId program_id);
void ClearRedirections(u32 flags = RedirectionFlags_None);
void ClearRedirections(const ncm::ProgramId* excluding_ids, size_t num_ids);
};
}

View file

@ -0,0 +1,133 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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>
namespace ams::lr::impl {
template<typename Key, typename Value, size_t NumEntries>
class RegisteredData {
NON_COPYABLE(RegisteredData);
NON_MOVEABLE(RegisteredData);
private:
struct Entry {
Value value;
ncm::ProgramId owner_id;
Key key;
bool is_valid;
};
private:
Entry entries[NumEntries];
size_t soft_entry_limit;
public:
RegisteredData(size_t soft_entry_limit = NumEntries) : soft_entry_limit(soft_entry_limit) {
this->Clear();
}
bool Register(const Key &key, const Value &value, const ncm::ProgramId owner_id) {
/* Try to find an existing value. */
for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
Entry& entry = this->entries[i];
if (entry.is_valid && entry.key == key) {
entry.value = value;
return true;
}
}
for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
Entry& entry = this->entries[i];
if (!entry.is_valid) {
entry.key = key;
entry.value = value;
entry.owner_id = owner_id;
entry.is_valid = true;
return true;
}
}
return false;
}
void Unregister(const Key &key) {
for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
Entry& entry = this->entries[i];
if (entry.is_valid && entry.key == key) {
entry.is_valid = false;
}
}
}
void UnregisterOwnerProgram(ncm::ProgramId owner_id) {
for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
Entry& entry = this->entries[i];
if (entry.owner_id == owner_id) {
entry.is_valid = false;
}
}
}
bool Find(Value *out, const Key &key) {
for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
Entry& entry = this->entries[i];
if (entry.is_valid && entry.key == key) {
*out = entry.value;
return true;
}
}
return false;
}
void Clear() {
for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
this->entries[i].is_valid = false;
}
}
void ClearExcluding(const ncm::ProgramId* ids, size_t num_ids) {
for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
Entry& entry = this->entries[i];
bool found = false;
for (size_t j = 0; j < num_ids; j++) {
ncm::ProgramId id = ids[j];
if (entry.owner_id == id) {
found = true;
break;
}
}
if (!found) {
entry.is_valid = false;
}
}
}
size_t GetSoftEntryLimit() const {
return this->soft_entry_limit;
}
};
template<typename Key, size_t NumEntries>
using RegisteredLocations = RegisteredData<Key, lr::Path, NumEntries>;
template<typename Key, size_t NumEntries>
using RegisteredStorages = RegisteredData<Key, ncm::StorageId, NumEntries>;
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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>
namespace ams::ncm::impl {
template<class Key, class Value, size_t N>
class BoundedMap {
private:
std::array<std::optional<Key>, N> keys;
std::array<Value, N> values;
public:
Value *Find(const Key &key) {
for (size_t i = 0; i < N; i++) {
if (this->keys[i] && this->keys[i].value() == key) {
return &this->values[i];
}
}
return nullptr;
}
void Remove(const Key &key) {
for (size_t i = 0; i < N; i++) {
if (this->keys[i] && this->keys[i].value() == key) {
this->keys[i].reset();
}
}
}
void RemoveAll() {
for (size_t i = 0; i < N; i++) {
this->keys[i].reset();
}
}
bool IsFull() {
for (size_t i = 0; i < N; i++) {
if (!this->keys[i]) {
return false;
}
}
return true;
}
Value &operator[](const Key &key) {
/* Try to find an existing value. */
{
Value *value = this->Find(key);
if (value) {
return *value;
}
}
/* Reference a new value. */
for (size_t i = 0; i < N; i++) {
if (!this->keys[i]) {
this->keys[i] = key;
return this->values[i];
}
}
/* We ran out of space in the map. */
std::abort();
}
};
}

View file

@ -0,0 +1,717 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 <switch.h>
#include <stratosphere.hpp>
#include <stratosphere/kvdb/kvdb_memory_key_value_store.hpp>
#include <optional>
#include "../ncm_contentmetadatabase.hpp"
#include "../ncm_contentstorage.hpp"
#include "../ncm_fs.hpp"
#include "../ncm_make_path.hpp"
#include "../ncm_readonlycontentstorage.hpp"
#include "ncm_content_manager.hpp"
#include "ncm_rights_cache.hpp"
namespace ams::ncm::impl {
namespace {
struct ContentStorageEntry {
NON_COPYABLE(ContentStorageEntry);
NON_MOVEABLE(ContentStorageEntry);
char mount_point[16];
char root_path[128];
StorageId storage_id;
FsContentStorageId content_storage_id;
std::shared_ptr<IContentStorage> content_storage;
inline ContentStorageEntry() : storage_id(StorageId::None),
content_storage_id(FsContentStorageId_System), content_storage(nullptr) {
mount_point[0] = '\0';
root_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 = fs::CreateUniqueMountName();
strcpy(this->mount_point, mount_name.name);
snprintf(this->root_path, 0x80, "%s:/", this->mount_point);
}
};
struct SaveDataMeta {
u64 id;
u64 size;
u64 journal_size;
u32 flags;
FsSaveDataSpaceId space_id;
};
static_assert(sizeof(SaveDataMeta) == 0x20, "SaveDataMeta definition!");
struct ContentMetaDBEntry {
NON_COPYABLE(ContentMetaDBEntry);
NON_MOVEABLE(ContentMetaDBEntry);
char mount_point[16];
char meta_path[128];
StorageId storage_id;
SaveDataMeta save_meta;
std::shared_ptr<IContentMetaDatabase> content_meta_database;
std::optional<kvdb::MemoryKeyValueStore<ContentMetaKey>> kvs;
u32 max_content_metas;
inline ContentMetaDBEntry() : 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, SaveDataMeta& 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.reset();
MountName mount_name = 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.reset();
return ResultSuccess();
}
};
constexpr size_t MaxContentStorageEntries = 8;
constexpr size_t MaxContentMetaDBEntries = 8;
os::Mutex g_mutex;
bool g_initialized = false;
ContentStorageEntry g_content_storage_entries[MaxContentStorageEntries];
ContentMetaDBEntry g_content_meta_entries[MaxContentMetaDBEntries];
u32 g_num_content_storage_entries;
u32 g_num_content_meta_entries;
RightsIdCache g_rights_id_cache;
ContentStorageEntry* FindContentStorageEntry(StorageId storage_id) {
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageEntry* entry = &g_content_storage_entries[i];
if (entry->storage_id == storage_id) {
return entry;
}
}
return nullptr;
}
ContentMetaDBEntry* FindContentMetaDBEntry(StorageId storage_id) {
for (size_t i = 0; i < MaxContentMetaDBEntries; i++) {
ContentMetaDBEntry* entry = &g_content_meta_entries[i];
if (entry->storage_id == storage_id) {
return entry;
}
}
return nullptr;
}
}
Result InitializeContentManager() {
std::scoped_lock<os::Mutex> lk(g_mutex);
/* Already initialized. */
if (g_initialized) {
return ResultSuccess();
}
size_t cur_storage_index = g_num_content_storage_entries;
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageEntry* entry = &g_content_storage_entries[i];
entry->storage_id = StorageId::None;
}
for (size_t i = 0; i < MaxContentMetaDBEntries; i++) {
ContentMetaDBEntry* entry = &g_content_meta_entries[i];
entry->storage_id = StorageId::None;
}
g_num_content_storage_entries++;
auto storage_entry = &g_content_storage_entries[cur_storage_index];
/* First, setup the NandSystem storage entry. */
storage_entry->Initialize(StorageId::BuiltInSystem, FsContentStorageId_System);
if (R_FAILED(VerifyContentStorage(StorageId::BuiltInSystem))) {
R_TRY(CreateContentStorage(StorageId::BuiltInSystem));
}
R_TRY(ActivateContentStorage(StorageId::BuiltInSystem));
/* Next, the NandSystem content meta entry. */
SaveDataMeta nand_system_save_meta;
nand_system_save_meta.id = 0x8000000000000120;
nand_system_save_meta.size = 0x6c000;
nand_system_save_meta.journal_size = 0x6c000;
nand_system_save_meta.flags = FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment;
nand_system_save_meta.space_id = FsSaveDataSpaceId_System;
size_t cur_meta_index = g_num_content_meta_entries;
g_num_content_meta_entries++;
auto content_meta_entry = &g_content_meta_entries[cur_meta_index];
R_TRY(content_meta_entry->Initialize(StorageId::BuiltInSystem, nand_system_save_meta, 0x800));
if (R_FAILED(VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
R_TRY(CreateContentMetaDatabase(StorageId::BuiltInSystem));
/* TODO: N supports a number of unused modes here, we don't bother implementing them currently. */
}
u32 current_flags = 0;
if (hos::GetVersion() >= hos::Version_200 && R_SUCCEEDED(fs::GetSaveDataFlags(&current_flags, 0x8000000000000120)) && current_flags != (FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment)) {
fs::SetSaveDataFlags(0x8000000000000120, FsSaveDataSpaceId_System, FsSaveDataFlags_KeepAfterResettingSystemSaveData | FsSaveDataFlags_KeepAfterRefurbishment);
}
R_TRY(ActivateContentMetaDatabase(StorageId::BuiltInSystem));
/* Now for NandUser's content storage entry. */
cur_storage_index = g_num_content_storage_entries;
g_num_content_storage_entries++;
storage_entry = &g_content_storage_entries[cur_storage_index];
storage_entry->Initialize(StorageId::BuiltInUser, FsContentStorageId_User);
/* And NandUser's content meta entry. */
SaveDataMeta nand_user_save_meta;
nand_user_save_meta.id = 0x8000000000000121;
nand_user_save_meta.size = 0x29e000;
nand_user_save_meta.journal_size = 0x29e000;
nand_user_save_meta.flags = 0;
nand_user_save_meta.space_id = FsSaveDataSpaceId_System;
cur_meta_index = g_num_content_meta_entries;
g_num_content_meta_entries++;
content_meta_entry = &g_content_meta_entries[cur_meta_index];
R_TRY(content_meta_entry->Initialize(StorageId::BuiltInUser, nand_user_save_meta, 0x2000));
/*
Beyond this point N no longer appears to bother
incrementing the count for content storage entries or content meta entries.
*/
/* Next SdCard's content storage entry. */
g_content_storage_entries[2].Initialize(StorageId::SdCard, FsContentStorageId_SdCard);
/* And SdCard's content meta entry. */
SaveDataMeta sd_card_save_meta;
sd_card_save_meta.id = 0x8000000000000124;
sd_card_save_meta.size = 0xa08000;
sd_card_save_meta.journal_size = 0xa08000;
sd_card_save_meta.flags = 0;
sd_card_save_meta.space_id = FsSaveDataSpaceId_SdSystem;
content_meta_entry = &g_content_meta_entries[2];
R_TRY(content_meta_entry->Initialize(StorageId::SdCard, sd_card_save_meta, 0x2000));
/* GameCard's content storage entry. */
/* N doesn't set a content storage id for game cards, so we'll just use 0 (NandSystem). */
g_content_storage_entries[3].Initialize(StorageId::GameCard, FsContentStorageId_System);
/* Lasty, GameCard's content meta entry. */
content_meta_entry = &g_content_meta_entries[3];
R_TRY(content_meta_entry->InitializeGameCard(0x800));
g_initialized = true;
return ResultSuccess();
}
void FinalizeContentManager() {
{
std::scoped_lock<os::Mutex> lk(g_mutex);
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageEntry* entry = &g_content_storage_entries[i];
InactivateContentStorage(entry->storage_id);
}
for (size_t i = 0; i < MaxContentMetaDBEntries; i++) {
ContentMetaDBEntry* entry = &g_content_meta_entries[i];
InactivateContentMetaDatabase(entry->storage_id);
}
}
for (size_t i = 0; i < MaxContentMetaDBEntries; i++) {
ContentMetaDBEntry* entry = &g_content_meta_entries[i];
entry->kvs.reset();
}
for (size_t i = 0; i < MaxContentStorageEntries; i++) {
ContentStorageEntry* entry = &g_content_storage_entries[i];
entry->content_storage = nullptr;
}
}
Result CreateContentStorage(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
R_TRY(fs::MountContentStorage(entry->mount_point, entry->content_storage_id));
ON_SCOPE_EXIT {
fs::Unmount(entry->mount_point);
};
R_TRY(fs::EnsureDirectoryRecursively(entry->root_path));
R_TRY(fs::EnsureContentAndPlaceHolderRoot(entry->root_path));
return ResultSuccess();
}
Result VerifyContentStorage(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
MountName mount_name = fs::CreateUniqueMountName();
char mount_root[128] = {0};
strcpy(mount_root, mount_name.name);
strcat(mount_root, strchr(entry->root_path, ':'));
R_TRY(fs::MountContentStorage(mount_name.name, entry->content_storage_id));
ON_SCOPE_EXIT {
fs::Unmount(mount_name.name);
};
R_TRY(fs::CheckContentStorageDirectoriesExist(mount_root));
return ResultSuccess();
}
Result OpenContentStorage(std::shared_ptr<IContentStorage>* out, StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
auto content_storage = entry->content_storage;
if (!content_storage) {
/* 1.0.0 activates content storages as soon as they are opened. */
if (hos::GetVersion() == hos::Version_100) {
R_TRY(ActivateContentStorage(storage_id));
content_storage = entry->content_storage;
} else {
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();
}
}
}
*out = std::move(content_storage);
return ResultSuccess();
}
Result CloseContentStorageForcibly(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None) {
return ResultUnknownStorage();
}
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
if (!entry->content_storage) {
return ResultSuccess();
}
/* N doesn't bother checking the result of this */
entry->content_storage->DisableForcibly();
fs::Unmount(entry->mount_point);
entry->content_storage = nullptr;
return ResultSuccess();
}
Result ActivateContentStorage(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
/* Already activated. */
if (entry->content_storage != nullptr) {
return ResultSuccess();
}
if (storage_id == StorageId::GameCard) {
FsGameCardHandle gc_hnd;
R_TRY(fs::GetGameCardHandle(&gc_hnd));
R_TRY(fs::MountGameCardPartition(entry->mount_point, gc_hnd, FsGameCardPartition_Secure));
auto mount_guard = SCOPE_GUARD { fs::Unmount(entry->mount_point); };
auto content_storage = std::make_shared<ReadOnlyContentStorageInterface>();
R_TRY(content_storage->Initialize(entry->root_path, path::MakeContentPathFlat));
entry->content_storage = std::move(content_storage);
mount_guard.Cancel();
} else {
R_TRY(fs::MountContentStorage(entry->mount_point, entry->content_storage_id));
auto mount_guard = SCOPE_GUARD { fs::Unmount(entry->mount_point); };
MakeContentPathFunc content_path_func = nullptr;
MakePlaceHolderPathFunc placeholder_path_func = nullptr;
bool delay_flush = false;
auto content_storage = std::make_shared<ContentStorageInterface>();
switch (storage_id) {
case StorageId::BuiltInSystem:
content_path_func = path::MakeContentPathFlat;
placeholder_path_func = path::MakePlaceHolderPathFlat;
break;
case StorageId::SdCard:
delay_flush = true;
default:
content_path_func = path::MakeContentPathHashByteLayered;
placeholder_path_func = path::MakePlaceHolderPathHashByteLayered;
break;
}
R_TRY(content_storage->Initialize(entry->root_path, content_path_func, placeholder_path_func, delay_flush, &g_rights_id_cache));
entry->content_storage = std::move(content_storage);
mount_guard.Cancel();
}
return ResultSuccess();
}
Result InactivateContentStorage(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
/* Already inactivated. */
if (entry->content_storage == nullptr) {
return ResultSuccess();
}
entry->content_storage->DisableForcibly();
entry->content_storage = nullptr;
fs::Unmount(entry->mount_point);
return ResultSuccess();
}
Result CreateContentMetaDatabase(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || storage_id == StorageId::GameCard || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
/* N doesn't bother checking the result of this. */
fsDisableAutoSaveDataCreation();
R_TRY_CATCH(fs::MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id)) {
R_CATCH(ams::fs::ResultTargetNotFound) {
R_TRY(fsCreate_SystemSaveData(entry->save_meta.space_id, entry->save_meta.id, entry->save_meta.size, entry->save_meta.journal_size, entry->save_meta.flags));
R_TRY(fs::MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id));
}
} R_END_TRY_CATCH;
ON_SCOPE_EXIT {
fs::Unmount(entry->mount_point);
};
R_TRY(fs::EnsureDirectoryRecursively(entry->meta_path));
R_TRY(fsdevCommitDevice(entry->mount_point));
return ResultSuccess();
}
Result VerifyContentMetaDatabase(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::GameCard) {
return ResultSuccess();
}
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
bool mounted_save_data = false;
if (!entry->content_meta_database) {
R_TRY(fs::MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id));
mounted_save_data = true;
}
ON_SCOPE_EXIT {
if (mounted_save_data) {
fs::Unmount(entry->mount_point);
}
};
bool has_meta_path = false;
R_TRY(fs::HasDirectory(&has_meta_path, entry->meta_path));
if (!has_meta_path) {
return ResultInvalidContentMetaDatabase();
}
return ResultSuccess();
}
Result OpenContentMetaDatabase(std::shared_ptr<IContentMetaDatabase>* out, StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
std::shared_ptr<IContentMetaDatabase> content_meta_db = entry->content_meta_database;
if (!content_meta_db) {
/* 1.0.0 activates content meta dbs as soon as they are opened. */
if (hos::GetVersion() == hos::Version_100) {
R_TRY(ActivateContentMetaDatabase(storage_id));
content_meta_db = entry->content_meta_database;
} else {
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();
}
}
}
*out = std::move(content_meta_db);
return ResultSuccess();
}
Result CloseContentMetaDatabaseForcibly(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None) {
return ResultUnknownStorage();
}
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
std::shared_ptr<IContentMetaDatabase> content_meta_db = entry->content_meta_database;
if (!content_meta_db) {
return ResultSuccess();
}
/* 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.reset();
return ResultSuccess();
}
Result CleanupContentMetaDatabase(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
if (!entry) {
return ResultUnknownStorage();
}
R_TRY(fsDeleteSaveDataFileSystemBySaveDataSpaceId(entry->save_meta.space_id, entry->save_meta.id));
return ResultSuccess();
}
Result ActivateContentMetaDatabase(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
/* Already activated. */
if (entry->content_meta_database != nullptr) {
return ResultSuccess();
}
/* Make a brand new kvs. N doesn't quite do this, but we will for cleanliness. */
entry->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());
auto content_meta_database = std::make_shared<ContentMetaDatabaseInterface>(&*entry->kvs, entry->mount_point);
entry->content_meta_database = std::move(content_meta_database);
mount_guard.Cancel();
} else {
R_TRY(entry->kvs->Initialize(entry->max_content_metas));
R_TRY(entry->kvs->Load());
auto content_meta_database = std::make_shared<OnMemoryContentMetaDatabaseInterface>(&*entry->kvs);
entry->content_meta_database = std::move(content_meta_database);
}
return ResultSuccess();
}
Result InactivateContentMetaDatabase(StorageId storage_id) {
std::scoped_lock<os::Mutex> lk(g_mutex);
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
return ResultUnknownStorage();
}
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
/* Already inactivated. */
if (entry->content_meta_database == nullptr) {
return ResultSuccess();
}
entry->content_meta_database->DisableForcibly();
entry->content_meta_database = nullptr;
/* This should lead to Index's destructor performing cleanup for us. */
entry->kvs.reset();
if (storage_id != StorageId::GameCard) {
fs::Unmount(entry->mount_point);
}
return ResultSuccess();
}
Result InvalidateRightsIdCache() {
g_rights_id_cache.Invalidate();
return ResultSuccess();
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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_icontentmetadatabase.hpp"
#include "../ncm_icontentstorage.hpp"
namespace ams::ncm::impl {
/* Initialization/Finalization. */
Result InitializeContentManager();
void FinalizeContentManager();
/* Content Storage Management. */
Result CreateContentStorage(StorageId storage_id);
Result VerifyContentStorage(StorageId storage_id);
Result OpenContentStorage(std::shared_ptr<IContentStorage>* out, StorageId storage_id);
Result CloseContentStorageForcibly(StorageId storage_id);
Result ActivateContentStorage(StorageId storage_id);
Result InactivateContentStorage(StorageId storage_id);
/* Content Meta Database Management. */
Result CreateContentMetaDatabase(StorageId storage_id);
Result VerifyContentMetaDatabase(StorageId storage_id);
Result OpenContentMetaDatabase(std::shared_ptr<IContentMetaDatabase>* out, StorageId storage_id);
Result CloseContentMetaDatabaseForcibly(StorageId storage_id);
Result CleanupContentMetaDatabase(StorageId storage_id);
Result ActivateContentMetaDatabase(StorageId storage_id);
Result InactivateContentMetaDatabase(StorageId storage_id);
Result InvalidateRightsIdCache();
}

View file

@ -0,0 +1,238 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 {
Result PlaceHolderAccessor::Open(FILE** out_handle, PlaceHolderId placeholder_id) {
if (this->LoadFromCache(out_handle, placeholder_id)) {
return ResultSuccess();
}
char placeholder_path[FS_MAX_PATH] = {0};
this->MakePath(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. */
CacheEntry* entry = this->FindInCache(InvalidPlaceHolderId);
if (entry) {
return entry;
}
/* Get the oldest entry. */
{
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(char* root, MakePlaceHolderPathFunc path_func, bool delay_flush) {
this->root_path = 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;
}
std::abort();
}
void PlaceHolderAccessor::GetPath(char* placeholder_path_out, PlaceHolderId placeholder_id) {
std::scoped_lock lock(this->cache_mutex);
CacheEntry* entry = this->FindInCache(placeholder_id);
this->Invalidate(entry);
this->MakePath(placeholder_path_out, placeholder_id);
}
Result PlaceHolderAccessor::Create(PlaceHolderId placeholder_id, size_t size) {
char placeholder_path[FS_MAX_PATH] = {0};
this->EnsureRecursively(placeholder_id);
this->GetPath(placeholder_path, placeholder_id);
R_TRY_CATCH(fsdevCreateFile(placeholder_path, size, FsCreateOption_BigFile)) {
R_CATCH(ams::fs::ResultPathAlreadyExists) {
return ResultPlaceHolderAlreadyExists();
}
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result PlaceHolderAccessor::Delete(PlaceHolderId placeholder_id) {
char placeholder_path[FS_MAX_PATH] = {0};
this->GetPath(placeholder_path, placeholder_id);
if (std::remove(placeholder_path) != 0) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathNotFound) {
return ResultPlaceHolderNotFound();
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result PlaceHolderAccessor::Write(PlaceHolderId placeholder_id, size_t offset, const void* buffer, size_t size) {
FILE* f = nullptr;
R_TRY_CATCH(this->Open(&f, placeholder_id)) {
R_CATCH(ams::fs::ResultPathNotFound) {
return ResultPlaceHolderNotFound();
}
} R_END_TRY_CATCH;
ON_SCOPE_EXIT {
this->StoreToCache(f, placeholder_id);
};
R_TRY(fs::WriteFile(f, offset, buffer, size, !this->delay_flush));
return ResultSuccess();
}
Result PlaceHolderAccessor::SetSize(PlaceHolderId placeholder_id, size_t size) {
char placeholder_path[FS_MAX_PATH] = {0};
this->MakePath(placeholder_path, placeholder_id);
if (truncate(placeholder_path, size) == -1) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathNotFound) {
return ResultPlaceHolderNotFound();
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result PlaceHolderAccessor::GetSize(bool* found_in_cache, size_t* out_size, PlaceHolderId placeholder_id) {
FILE* f = NULL;
/* Set the scope for the scoped_lock. */
{
std::scoped_lock lock(this->cache_mutex);
if (placeholder_id == InvalidPlaceHolderId) {
*found_in_cache = false;
return ResultSuccess();
}
CacheEntry* cache_entry = this->FindInCache(placeholder_id);
if (cache_entry == nullptr) {
*found_in_cache = false;
return ResultSuccess();
}
cache_entry->id = InvalidPlaceHolderId;
f = cache_entry->handle;
}
this->StoreToCache(f, placeholder_id);
if (fseek(f, 0L, SEEK_END) != 0) {
return fsdevGetLastResult();
}
size_t size = ftell(f);
if (fseek(f, 0L, SEEK_SET) != 0) {
return fsdevGetLastResult();
}
*found_in_cache = true;
*out_size = size;
return ResultSuccess();
}
Result PlaceHolderAccessor::EnsureRecursively(PlaceHolderId placeholder_id) {
char placeholder_path[FS_MAX_PATH] = {0};
this->MakePath(placeholder_path, placeholder_id);
R_TRY(fs::EnsureParentDirectoryRecursively(placeholder_path));
return ResultSuccess();
}
void PlaceHolderAccessor::InvalidateAll() {
for (auto &entry : this->caches) {
if (entry.id != InvalidPlaceHolderId) {
this->Invalidate(&entry);
}
}
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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;
char* 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(char* out_placeholder_root) {
path::GetPlaceHolderRootPath(out_placeholder_root, this->root_path);
}
inline void MakePath(char* out_placeholder_path, PlaceHolderId placeholder_id) {
char placeholder_root_path[FS_MAX_PATH] = {0};
this->MakeRootPath(placeholder_root_path);
this->make_placeholder_path_func(out_placeholder_path, placeholder_id, placeholder_root_path);
}
void Initialize(char* root, MakePlaceHolderPathFunc path_func, bool delay_flush);
unsigned int GetDirectoryDepth();
void GetPath(char* out_placeholder_path, PlaceHolderId placeholder_id);
Result Create(PlaceHolderId placeholder_id, size_t size);
Result Delete(PlaceHolderId placeholder_id);
Result Write(PlaceHolderId placeholder_id, size_t offset, const void* buffer, size_t size);
Result SetSize(PlaceHolderId placeholder_id, size_t size);
Result GetSize(bool* found_in_cache, size_t* out_size, PlaceHolderId placeholder_id);
Result EnsureRecursively(PlaceHolderId placeholder_id);
void InvalidateAll();
};
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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>
namespace ams::ncm::impl {
class RightsIdCache {
public:
static constexpr size_t MaxEntries = 0x80;
public:
struct Entry {
public:
util::Uuid uuid;
FsRightsId rights_id;
u64 key_generation;
u64 last_accessed;
};
Entry entries[MaxEntries];
u64 counter;
os::Mutex mutex;
RightsIdCache() {
this->Invalidate();
}
void Invalidate() {
this->counter = 2;
for (size_t i = 0; i < MaxEntries; i++) {
this->entries[i].last_accessed = 1;
}
}
};
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/ncm_content_manager.hpp"
#include "lr_addoncontentlocationresolver.hpp"
namespace ams::lr {
Result AddOnContentLocationResolverInterface::ResolveAddOnContentPath(sf::Out<Path> out, ncm::ProgramId id) {
ncm::StorageId storage_id = ncm::StorageId::None;
if (!this->registered_storages.Find(&storage_id, id)) {
return ResultAddOnContentNotFound();
}
std::shared_ptr<ncm::IContentMetaDatabase> content_meta_database;
ncm::ContentId data_content_id;
R_TRY(ncm::impl::OpenContentMetaDatabase(&content_meta_database, storage_id));
R_TRY(content_meta_database->GetLatestData(&data_content_id, id));
std::shared_ptr<ncm::IContentStorage> content_storage;
R_TRY(ncm::impl::OpenContentStorage(&content_storage, storage_id));
R_ASSERT(content_storage->GetPath(out.GetPointer(), data_content_id));
return ResultSuccess();
}
Result AddOnContentLocationResolverInterface::RegisterAddOnContentStorageDeprecated(ncm::StorageId storage_id, ncm::ProgramId id) {
if (!this->registered_storages.Register(id, storage_id, ncm::ProgramId::Invalid)) {
return ResultTooManyRegisteredPaths();
}
return ResultSuccess();
}
Result AddOnContentLocationResolverInterface::RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::ProgramId id, ncm::ProgramId application_id) {
if (!this->registered_storages.Register(id, storage_id, application_id)) {
return ResultTooManyRegisteredPaths();
}
return ResultSuccess();
}
Result AddOnContentLocationResolverInterface::UnregisterAllAddOnContentPath() {
this->registered_storages.Clear();
return ResultSuccess();
}
Result AddOnContentLocationResolverInterface::RefreshApplicationAddOnContent(const sf::InArray<ncm::ProgramId> &ids) {
if (ids.GetSize() == 0) {
this->registered_storages.Clear();
return ResultSuccess();
}
this->registered_storages.ClearExcluding(ids.GetPointer(), ids.GetSize());
return ResultSuccess();
}
Result AddOnContentLocationResolverInterface::UnregisterApplicationAddOnContent(ncm::ProgramId id) {
this->registered_storages.UnregisterOwnerProgram(id);
return ResultSuccess();
}
}

View file

@ -0,0 +1,57 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/lr_registered_data.hpp"
namespace ams::lr {
class AddOnContentLocationResolverInterface : public sf::IServiceObject {
protected:
enum class CommandId {
ResolveAddOnContentPath = 0,
RegisterAddOnContentStorageDeprecated = 1,
RegisterAddOnContentStorage = 1,
UnregisterAllAddOnContentPath = 2,
RefreshApplicationAddOnContent = 3,
UnregisterApplicationAddOnContent = 4,
};
private:
impl::RegisteredStorages<ncm::ProgramId, 0x800> registered_storages;
public:
AddOnContentLocationResolverInterface() : registered_storages(hos::GetVersion() < hos::Version_900 ? 0x800 : 0x2) { /* ... */ }
virtual Result ResolveAddOnContentPath(sf::Out<Path> out, ncm::ProgramId id);
virtual Result RegisterAddOnContentStorageDeprecated(ncm::StorageId storage_id, ncm::ProgramId id);
virtual Result RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::ProgramId id, ncm::ProgramId application_id);
virtual Result UnregisterAllAddOnContentPath();
virtual Result RefreshApplicationAddOnContent(const sf::InArray<ncm::ProgramId> &ids);
virtual Result UnregisterApplicationAddOnContent(ncm::ProgramId id);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(ResolveAddOnContentPath, hos::Version_200),
MAKE_SERVICE_COMMAND_META(RegisterAddOnContentStorageDeprecated, hos::Version_200, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RegisterAddOnContentStorage, hos::Version_900),
MAKE_SERVICE_COMMAND_META(UnregisterAllAddOnContentPath, hos::Version_200),
MAKE_SERVICE_COMMAND_META(RefreshApplicationAddOnContent, hos::Version_900),
MAKE_SERVICE_COMMAND_META(UnregisterApplicationAddOnContent, hos::Version_900),
};
};
}

View file

@ -0,0 +1,201 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/ncm_content_manager.hpp"
#include "lr_contentlocationresolver.hpp"
namespace ams::lr {
ContentLocationResolverInterface::~ContentLocationResolverInterface() {
this->ClearRedirections();
}
void ContentLocationResolverInterface::GetContentStoragePath(Path* out, ncm::ContentId content_id) {
R_ASSERT(this->content_storage->GetPath(out, content_id));
}
Result ContentLocationResolverInterface::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->program_redirector, id)) {
return ResultSuccess();
}
ncm::ContentId program_content_id;
R_TRY_CATCH(this->content_meta_database->GetLatestProgram(&program_content_id, id)) {
R_CATCH(ncm::ResultContentMetaNotFound) {
return ResultProgramNotFound();
}
} R_END_TRY_CATCH;
this->GetContentStoragePath(out.GetPointer(), program_content_id);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectProgramPath(const Path &path, ncm::ProgramId id) {
this->program_redirector.SetRedirection(id, path);
return ResultSuccess();
}
Result ContentLocationResolverInterface::ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->app_control_redirector, id)) {
return ResultSuccess();
}
return ResultControlNotFound();
}
Result ContentLocationResolverInterface::ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->html_docs_redirector, id)) {
return ResultSuccess();
}
return ResultHtmlDocumentNotFound();
}
Result ContentLocationResolverInterface::ResolveDataPath(sf::Out<Path> out, ncm::ProgramId id) {
ncm::ContentId data_content_id;
R_TRY(this->content_meta_database->GetLatestData(&data_content_id, id));
this->GetContentStoragePath(out.GetPointer(), data_content_id);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) {
this->app_control_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->app_control_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
this->html_docs_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->html_docs_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->legal_info_redirector, id)) {
return ResultSuccess();
}
return ResultLegalInformationNotFound();
}
Result ContentLocationResolverInterface::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) {
this->legal_info_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->legal_info_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::Refresh() {
std::shared_ptr<ncm::IContentMetaDatabase> content_meta_database;
std::shared_ptr<ncm::IContentStorage> content_storage;
R_TRY(ncm::impl::OpenContentMetaDatabase(&content_meta_database, this->storage_id));
R_TRY(ncm::impl::OpenContentStorage(&content_storage, this->storage_id));
this->content_meta_database = std::move(content_meta_database);
this->content_storage = std::move(content_storage);
this->ClearRedirections();
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
this->program_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->program_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::ClearApplicationRedirectionDeprecated() {
this->ClearRedirections(impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) {
this->ClearRedirections(excluding_ids.GetPointer(), excluding_ids.GetSize());
return ResultSuccess();
}
Result ContentLocationResolverInterface::EraseProgramRedirection(ncm::ProgramId id) {
this->program_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result ContentLocationResolverInterface::EraseApplicationControlRedirection(ncm::ProgramId id) {
this->app_control_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result ContentLocationResolverInterface::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) {
this->html_docs_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result ContentLocationResolverInterface::EraseApplicationLegalInformationRedirection(ncm::ProgramId id) {
this->legal_info_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result ContentLocationResolverInterface::ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->debug_program_redirector, id)) {
return ResultSuccess();
}
R_TRY_CATCH(this->ResolveProgramPath(out.GetPointer(), id)) {
R_CATCH(ResultProgramNotFound) {
return ResultDebugProgramNotFound();
}
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) {
this->debug_program_redirector.SetRedirection(id, path);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) {
this->debug_program_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->debug_program_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result ContentLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::ProgramId id) {
this->debug_program_redirector.EraseRedirection(id);
return ResultSuccess();
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/lr_manager.hpp"
#include "lr_ilocationresolver.hpp"
#include "ncm_icontentmetadatabase.hpp"
#include "ncm_icontentstorage.hpp"
namespace ams::lr {
class ContentLocationResolverInterface : public ILocationResolver {
private:
ncm::StorageId storage_id;
std::shared_ptr<ncm::IContentMetaDatabase> content_meta_database;
std::shared_ptr<ncm::IContentStorage> content_storage;
public:
ContentLocationResolverInterface(ncm::StorageId storage_id) : storage_id(storage_id) { /* ... */ }
~ContentLocationResolverInterface();
private:
void GetContentStoragePath(Path* out, ncm::ContentId content_id);
public:
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id) override;
virtual Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result ResolveDataPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result Refresh() override;
virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result ClearApplicationRedirectionDeprecated() override;
virtual Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) override;
virtual Result EraseProgramRedirection(ncm::ProgramId id) override;
virtual Result EraseApplicationControlRedirection(ncm::ProgramId id) override;
virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) override;
virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) override;
virtual Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId id) override;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveProgramPath),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectProgramPath),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveApplicationControlPath),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveApplicationHtmlDocumentPath),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveDataPath),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationControlPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationControlPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationHtmlDocumentPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationHtmlDocumentPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveApplicationLegalInformationPath),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationLegalInformationPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationLegalInformationPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, Refresh),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPathDeprecated, hos::Version_500, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ClearApplicationRedirectionDeprecated, hos::Version_500, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ClearApplicationRedirection, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseProgramRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseApplicationControlRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseApplicationHtmlDocumentRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseApplicationLegalInformationRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveProgramPathForDebug, hos::Version_700),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectProgramPathForDebug, hos::Version_700),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPathForDebugDeprecated, hos::Version_700, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPathForDebug, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseProgramRedirectionForDebug, hos::Version_700),
};
};
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/lr_redirection.hpp"
namespace ams::lr {
class ILocationResolver : public sf::IServiceObject {
protected:
enum class CommandId {
ResolveProgramPath = 0,
RedirectProgramPath = 1,
ResolveApplicationControlPath = 2,
ResolveApplicationHtmlDocumentPath = 3,
ResolveDataPath = 4,
RedirectApplicationControlPathDeprecated = 5,
RedirectApplicationControlPath = 5,
RedirectApplicationHtmlDocumentPathDeprecated = 6,
RedirectApplicationHtmlDocumentPath = 6,
ResolveApplicationLegalInformationPath = 7,
RedirectApplicationLegalInformationPathDeprecated = 8,
RedirectApplicationLegalInformationPath = 8,
Refresh = 9,
RedirectApplicationProgramPathDeprecated = 10,
RedirectApplicationProgramPath = 10,
ClearApplicationRedirectionDeprecated = 11,
ClearApplicationRedirection = 11,
EraseProgramRedirection = 12,
EraseApplicationControlRedirection = 13,
EraseApplicationHtmlDocumentRedirection = 14,
EraseApplicationLegalInformationRedirection = 15,
ResolveProgramPathForDebug = 16,
RedirectProgramPathForDebug = 17,
RedirectApplicationProgramPathForDebugDeprecated = 18,
RedirectApplicationProgramPathForDebug = 18,
EraseProgramRedirectionForDebug = 19,
};
protected:
impl::LocationRedirector program_redirector;
impl::LocationRedirector debug_program_redirector;
impl::LocationRedirector app_control_redirector;
impl::LocationRedirector html_docs_redirector;
impl::LocationRedirector legal_info_redirector;
protected:
bool GetRedirectedPath(Path* out, impl::LocationRedirector* redirector, ncm::ProgramId id) {
return redirector->FindRedirection(out, id);
}
void ClearRedirections(u32 flags = impl::RedirectionFlags_None) {
this->program_redirector.ClearRedirections(flags);
this->debug_program_redirector.ClearRedirections(flags);
this->app_control_redirector.ClearRedirections(flags);
this->html_docs_redirector.ClearRedirections(flags);
this->legal_info_redirector.ClearRedirections(flags);
}
void ClearRedirections(const ncm::ProgramId* excluding_ids, size_t num_ids) {
this->program_redirector.ClearRedirections(excluding_ids, num_ids);
this->debug_program_redirector.ClearRedirections(excluding_ids, num_ids);
this->app_control_redirector.ClearRedirections(excluding_ids, num_ids);
this->html_docs_redirector.ClearRedirections(excluding_ids, num_ids);
this->legal_info_redirector.ClearRedirections(excluding_ids, num_ids);
}
public:
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) = 0;
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id) = 0;
virtual Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) = 0;
virtual Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) = 0;
virtual Result ResolveDataPath(sf::Out<Path> out, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) = 0;
virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) = 0;
virtual Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) = 0;
virtual Result Refresh() = 0;
virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) = 0;
virtual Result ClearApplicationRedirectionDeprecated() = 0;
virtual Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) = 0;
virtual Result EraseProgramRedirection(ncm::ProgramId id) = 0;
virtual Result EraseApplicationControlRedirection(ncm::ProgramId id) = 0;
virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) = 0;
virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) = 0;
virtual Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) = 0;
virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) = 0;
virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) = 0;
virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId id) = 0;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(ResolveProgramPath),
MAKE_SERVICE_COMMAND_META(RedirectProgramPath),
MAKE_SERVICE_COMMAND_META(ResolveApplicationControlPath),
MAKE_SERVICE_COMMAND_META(ResolveApplicationHtmlDocumentPath),
MAKE_SERVICE_COMMAND_META(ResolveDataPath),
MAKE_SERVICE_COMMAND_META(RedirectApplicationControlPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationControlPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(RedirectApplicationHtmlDocumentPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationHtmlDocumentPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ResolveApplicationLegalInformationPath),
MAKE_SERVICE_COMMAND_META(RedirectApplicationLegalInformationPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationLegalInformationPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(Refresh),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPathDeprecated, hos::Version_500, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ClearApplicationRedirectionDeprecated, hos::Version_500, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ClearApplicationRedirection, hos::Version_900),
MAKE_SERVICE_COMMAND_META(EraseProgramRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(EraseApplicationControlRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(EraseApplicationHtmlDocumentRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(EraseApplicationLegalInformationRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(ResolveProgramPathForDebug, hos::Version_700),
MAKE_SERVICE_COMMAND_META(RedirectProgramPathForDebug, hos::Version_700),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPathForDebugDeprecated, hos::Version_700, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPathForDebug, hos::Version_900),
MAKE_SERVICE_COMMAND_META(EraseProgramRedirectionForDebug, hos::Version_700),
};
};
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 <inttypes.h>
#include "impl/lr_manager.hpp"
#include "lr_manager_service.hpp"
namespace ams::lr {
Result LocationResolverManagerService::OpenLocationResolver(sf::Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id) {
return impl::OpenLocationResolver(out, storage_id);
}
Result LocationResolverManagerService::OpenRegisteredLocationResolver(sf::Out<std::shared_ptr<RegisteredLocationResolverInterface>> out) {
return impl::OpenRegisteredLocationResolver(out);
}
Result LocationResolverManagerService::RefreshLocationResolver(ncm::StorageId storage_id) {
return impl::RefreshLocationResolver(storage_id);
}
Result LocationResolverManagerService::OpenAddOnContentLocationResolver(sf::Out<std::shared_ptr<AddOnContentLocationResolverInterface>> out) {
return impl::OpenAddOnContentLocationResolver(out);
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "lr_addoncontentlocationresolver.hpp"
#include "lr_ilocationresolver.hpp"
#include "lr_registeredlocationresolver.hpp"
namespace ams::lr {
class LocationResolverManagerService final : public sf::IServiceObject {
protected:
enum class CommandId {
OpenLocationResolver = 0,
OpenRegisteredLocationResolver = 1,
RefreshLocationResolver = 2,
OpenAddOnContentLocationResolver = 3,
};
public:
/* Actual commands. */
virtual Result OpenLocationResolver(sf::Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id);
virtual Result OpenRegisteredLocationResolver(sf::Out<std::shared_ptr<RegisteredLocationResolverInterface>> out);
virtual Result RefreshLocationResolver(ncm::StorageId storage_id);
virtual Result OpenAddOnContentLocationResolver(sf::Out<std::shared_ptr<AddOnContentLocationResolverInterface>> out);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(OpenLocationResolver),
MAKE_SERVICE_COMMAND_META(OpenRegisteredLocationResolver),
MAKE_SERVICE_COMMAND_META(RefreshLocationResolver),
MAKE_SERVICE_COMMAND_META(OpenAddOnContentLocationResolver, hos::Version_200),
};
};
}

View file

@ -0,0 +1,188 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/ncm_content_manager.hpp"
#include "lr_redirectonlylocationresolver.hpp"
namespace ams::lr {
RedirectOnlyLocationResolverInterface::~RedirectOnlyLocationResolverInterface() {
this->program_redirector.ClearRedirections();
this->debug_program_redirector.ClearRedirections();
this->app_control_redirector.ClearRedirections();
this->html_docs_redirector.ClearRedirections();
this->legal_info_redirector.ClearRedirections();
}
Result RedirectOnlyLocationResolverInterface::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->program_redirector, id)) {
return ResultSuccess();
}
return ResultProgramNotFound();
}
Result RedirectOnlyLocationResolverInterface::RedirectProgramPath(const Path &path, ncm::ProgramId id) {
this->program_redirector.SetRedirection(id, path);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->app_control_redirector, id)) {
return ResultSuccess();
}
return ResultControlNotFound();
}
Result RedirectOnlyLocationResolverInterface::ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->html_docs_redirector, id)) {
return ResultSuccess();
}
return ResultHtmlDocumentNotFound();
}
Result RedirectOnlyLocationResolverInterface::ResolveDataPath(sf::Out<Path> out, ncm::ProgramId id) {
return ResultDataNotFound();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) {
this->app_control_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->app_control_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
this->html_docs_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->html_docs_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->legal_info_redirector, id)) {
return ResultSuccess();
}
return ResultLegalInformationNotFound();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) {
this->legal_info_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->legal_info_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::Refresh() {
this->program_redirector.ClearRedirections();
this->debug_program_redirector.ClearRedirections();
this->app_control_redirector.ClearRedirections();
this->html_docs_redirector.ClearRedirections();
this->legal_info_redirector.ClearRedirections();
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
this->program_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->program_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::ClearApplicationRedirectionDeprecated() {
this->program_redirector.ClearRedirections(impl::RedirectionFlags_Application);
this->debug_program_redirector.ClearRedirections(impl::RedirectionFlags_Application);
this->app_control_redirector.ClearRedirections(impl::RedirectionFlags_Application);
this->html_docs_redirector.ClearRedirections(impl::RedirectionFlags_Application);
this->legal_info_redirector.ClearRedirections(impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) {
this->ClearRedirections(excluding_ids.GetPointer(), excluding_ids.GetSize());
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::EraseProgramRedirection(ncm::ProgramId id) {
this->program_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::EraseApplicationControlRedirection(ncm::ProgramId id) {
this->app_control_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) {
this->html_docs_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::EraseApplicationLegalInformationRedirection(ncm::ProgramId id) {
this->legal_info_redirector.EraseRedirection(id);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) {
if (this->GetRedirectedPath(out.GetPointer(), &this->debug_program_redirector, id)) {
return ResultSuccess();
}
R_TRY_CATCH(this->ResolveProgramPath(out.GetPointer(), id)) {
R_CATCH(ResultProgramNotFound) {
return ResultDebugProgramNotFound();
}
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) {
this->debug_program_redirector.SetRedirection(id, path);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) {
this->debug_program_redirector.SetRedirection(id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->debug_program_redirector.SetRedirection(id, owner_id, path, impl::RedirectionFlags_Application);
return ResultSuccess();
}
Result RedirectOnlyLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::ProgramId id) {
this->debug_program_redirector.EraseRedirection(id);
return ResultSuccess();
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "lr_contentlocationresolver.hpp"
namespace ams::lr {
class RedirectOnlyLocationResolverInterface : public ILocationResolver {
public:
~RedirectOnlyLocationResolverInterface();
public:
virtual Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId id) override;
virtual Result ResolveApplicationControlPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result ResolveApplicationHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result ResolveDataPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result ResolveApplicationLegalInformationPath(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result Refresh() override;
virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result ClearApplicationRedirectionDeprecated() override;
virtual Result ClearApplicationRedirection(const sf::InArray<ncm::ProgramId> &excluding_ids) override;
virtual Result EraseProgramRedirection(ncm::ProgramId id) override;
virtual Result EraseApplicationControlRedirection(ncm::ProgramId id) override;
virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId id) override;
virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId id) override;
virtual Result ResolveProgramPathForDebug(sf::Out<Path> out, ncm::ProgramId id) override;
virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId id) override;
virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) override;
virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId id) override;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(ResolveProgramPath),
MAKE_SERVICE_COMMAND_META(RedirectProgramPath),
MAKE_SERVICE_COMMAND_META(ResolveApplicationControlPath),
MAKE_SERVICE_COMMAND_META(ResolveApplicationHtmlDocumentPath),
MAKE_SERVICE_COMMAND_META(ResolveDataPath),
MAKE_SERVICE_COMMAND_META(RedirectApplicationControlPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationControlPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(RedirectApplicationHtmlDocumentPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationHtmlDocumentPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ResolveApplicationLegalInformationPath),
MAKE_SERVICE_COMMAND_META(RedirectApplicationLegalInformationPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationLegalInformationPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(Refresh),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPathDeprecated, hos::Version_500, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ClearApplicationRedirectionDeprecated, hos::Version_500, hos::Version_810),
MAKE_SERVICE_COMMAND_META(ClearApplicationRedirection, hos::Version_900),
MAKE_SERVICE_COMMAND_META(EraseProgramRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(EraseApplicationControlRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(EraseApplicationHtmlDocumentRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(EraseApplicationLegalInformationRedirection, hos::Version_500),
MAKE_SERVICE_COMMAND_META(ResolveProgramPathForDebug, hos::Version_700),
MAKE_SERVICE_COMMAND_META(RedirectProgramPathForDebug, hos::Version_700),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPathForDebugDeprecated, hos::Version_700, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectApplicationProgramPathForDebug, hos::Version_900),
MAKE_SERVICE_COMMAND_META(EraseProgramRedirectionForDebug, hos::Version_700),
};
};
}

View file

@ -0,0 +1,139 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "lr_registeredlocationresolver.hpp"
namespace ams::lr {
RegisteredLocationResolverInterface::~RegisteredLocationResolverInterface() {
/* Ensure entries are deallocated */
this->ClearRedirections();
}
void RegisteredLocationResolverInterface::ClearRedirections(u32 flags) {
this->html_docs_redirector.ClearRedirections();
this->program_redirector.ClearRedirections();
}
void RegisteredLocationResolverInterface::RegisterPath(const Path& path, impl::RegisteredLocations<ncm::ProgramId, RegisteredLocationResolverInterface::MaxRegisteredLocations>* locations, ncm::ProgramId id, ncm::ProgramId owner_id) {
if (!locations->Register(id, path, owner_id)) {
locations->Clear();
locations->Register(id, path, owner_id);
}
}
bool RegisteredLocationResolverInterface::ResolvePath(Path* out, impl::LocationRedirector* redirector, impl::RegisteredLocations<ncm::ProgramId, RegisteredLocationResolverInterface::MaxRegisteredLocations>* locations, ncm::ProgramId id) {
if (!redirector->FindRedirection(out, id)) {
if (!locations->Find(out, id)) {
return false;
}
}
return true;
}
Result RegisteredLocationResolverInterface::RefreshImpl(const ncm::ProgramId* excluding_ids, size_t num_ids) {
if (hos::GetVersion() < hos::Version_900) {
this->ClearRedirections();
return ResultSuccess();
}
if (num_ids == 0) {
this->ClearRedirections();
} else {
this->registered_program_locations.ClearExcluding(excluding_ids, num_ids);
this->registered_html_docs_locations.ClearExcluding(excluding_ids, num_ids);
}
this->program_redirector.ClearRedirections(excluding_ids, num_ids);
this->html_docs_redirector.ClearRedirections(excluding_ids, num_ids);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id) {
if (!this->ResolvePath(out.GetPointer(), &this->program_redirector, &this->registered_program_locations, id)) {
return ResultProgramNotFound();
}
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
this->RegisterPath(path, &this->registered_program_locations, id, ncm::ProgramId::Invalid);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->RegisterPath(path, &this->registered_program_locations, id, owner_id);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::UnregisterProgramPath(ncm::ProgramId id) {
this->registered_program_locations.Unregister(id);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id) {
this->program_redirector.SetRedirection(id, path);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->program_redirector.SetRedirection(id, owner_id, path);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id) {
if (!this->ResolvePath(out.GetPointer(), &this->html_docs_redirector, &this->registered_html_docs_locations, id)) {
return ResultHtmlDocumentNotFound();
}
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
this->RegisterPath(path, &this->registered_html_docs_locations, id, ncm::ProgramId::Invalid);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->RegisterPath(path, &this->registered_html_docs_locations, id, owner_id);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::UnregisterHtmlDocumentPath(ncm::ProgramId id) {
this->registered_html_docs_locations.Unregister(id);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id) {
this->html_docs_redirector.SetRedirection(id, path);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id) {
this->html_docs_redirector.SetRedirection(id, owner_id, path);
return ResultSuccess();
}
Result RegisteredLocationResolverInterface::Refresh() {
return this->RefreshImpl(nullptr, 0);
}
Result RegisteredLocationResolverInterface::RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids) {
return this->RefreshImpl(ids.GetPointer(), ids.GetSize());
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/lr_redirection.hpp"
#include "impl/lr_registered_data.hpp"
namespace ams::lr {
class RegisteredLocationResolverInterface final : public sf::IServiceObject {
private:
static constexpr size_t MaxRegisteredLocations = 0x20;
protected:
enum class CommandId {
ResolveProgramPath = 0,
RegisterProgramPathDeprecated = 1,
RegisterProgramPath = 1,
UnregisterProgramPath = 2,
RedirectProgramPathDeprecated = 3,
RedirectProgramPath = 3,
ResolveHtmlDocumentPath = 4,
RegisterHtmlDocumentPathDeprecated = 5,
RegisterHtmlDocumentPath = 5,
UnregisterHtmlDocumentPath = 6,
RedirectHtmlDocumentPathDeprecated = 7,
RedirectHtmlDocumentPath = 7,
Refresh = 8,
RefreshExcluding = 9,
};
private:
impl::LocationRedirector program_redirector;
impl::RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations> registered_program_locations;
impl::LocationRedirector html_docs_redirector;
impl::RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations> registered_html_docs_locations;
private:
void ClearRedirections(u32 flags = impl::RedirectionFlags_None);
void RegisterPath(const Path& path, impl::RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations>* locations, ncm::ProgramId id, ncm::ProgramId owner_id);
bool ResolvePath(Path* out, impl::LocationRedirector* redirector, impl::RegisteredLocations<ncm::ProgramId, MaxRegisteredLocations>* locations, ncm::ProgramId id);
Result RefreshImpl(const ncm::ProgramId* excluding_ids, size_t num_ids);
public:
RegisteredLocationResolverInterface() : registered_program_locations(hos::GetVersion() < hos::Version_900 ? 0x10 : MaxRegisteredLocations), registered_html_docs_locations(hos::GetVersion() < hos::Version_900 ? 0x10 : MaxRegisteredLocations) { /* ... */ }
~RegisteredLocationResolverInterface();
Result ResolveProgramPath(sf::Out<Path> out, ncm::ProgramId id);
Result RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId id);
Result RegisterProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id);
Result UnregisterProgramPath(ncm::ProgramId id);
Result RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId id);
Result RedirectProgramPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id);
Result ResolveHtmlDocumentPath(sf::Out<Path> out, ncm::ProgramId id);
Result RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id);
Result RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id);
Result UnregisterHtmlDocumentPath(ncm::ProgramId id);
Result RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId id);
Result RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId id, ncm::ProgramId owner_id);
Result Refresh();
Result RefreshExcluding(const sf::InArray<ncm::ProgramId> &ids);
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(ResolveProgramPath),
MAKE_SERVICE_COMMAND_META(RegisterProgramPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RegisterProgramPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(UnregisterProgramPath),
MAKE_SERVICE_COMMAND_META(RedirectProgramPathDeprecated, hos::Version_100, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectProgramPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(ResolveHtmlDocumentPath, hos::Version_200),
MAKE_SERVICE_COMMAND_META(RegisterHtmlDocumentPathDeprecated, hos::Version_200, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RegisterHtmlDocumentPath, hos::Version_900),
MAKE_SERVICE_COMMAND_META(UnregisterHtmlDocumentPath, hos::Version_200),
MAKE_SERVICE_COMMAND_META(RedirectHtmlDocumentPathDeprecated, hos::Version_200, hos::Version_810),
MAKE_SERVICE_COMMAND_META(RedirectHtmlDocumentPathDeprecated, hos::Version_900),
MAKE_SERVICE_COMMAND_META(Refresh, hos::Version_700),
MAKE_SERVICE_COMMAND_META(RefreshExcluding, hos::Version_900),
};
};
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/ncm_content_manager.hpp"
#include "ncm_content_manager_service.hpp"
namespace ams::ncm {
Result ContentManagerService::CreateContentStorage(StorageId storage_id) {
return impl::CreateContentStorage(storage_id);
}
Result ContentManagerService::CreateContentMetaDatabase(StorageId storage_id) {
return impl::CreateContentMetaDatabase(storage_id);
}
Result ContentManagerService::VerifyContentStorage(StorageId storage_id) {
return impl::VerifyContentStorage(storage_id);
}
Result ContentManagerService::VerifyContentMetaDatabase(StorageId storage_id) {
return impl::VerifyContentMetaDatabase(storage_id);
}
Result ContentManagerService::OpenContentStorage(sf::Out<std::shared_ptr<IContentStorage>> out, StorageId storage_id) {
std::shared_ptr<IContentStorage> content_storage;
R_TRY(impl::OpenContentStorage(&content_storage, storage_id));
out.SetValue(std::move(content_storage));
return ResultSuccess();
}
Result ContentManagerService::OpenContentMetaDatabase(sf::Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id) {
std::shared_ptr<IContentMetaDatabase> content_meta_database;
R_TRY(impl::OpenContentMetaDatabase(&content_meta_database, storage_id));
out.SetValue(std::move(content_meta_database));
return ResultSuccess();
}
Result ContentManagerService::CloseContentStorageForcibly(StorageId storage_id) {
return impl::CloseContentStorageForcibly(storage_id);
}
Result ContentManagerService::CloseContentMetaDatabaseForcibly(StorageId storage_id) {
return impl::CloseContentMetaDatabaseForcibly(storage_id);
}
Result ContentManagerService::CleanupContentMetaDatabase(StorageId storage_id) {
return impl::CleanupContentMetaDatabase(storage_id);
}
Result ContentManagerService::ActivateContentStorage(StorageId storage_id) {
return impl::ActivateContentStorage(storage_id);
}
Result ContentManagerService::InactivateContentStorage(StorageId storage_id) {
return impl::InactivateContentStorage(storage_id);
}
Result ContentManagerService::ActivateContentMetaDatabase(StorageId storage_id) {
return impl::ActivateContentMetaDatabase(storage_id);
}
Result ContentManagerService::InactivateContentMetaDatabase(StorageId storage_id) {
return impl::InactivateContentMetaDatabase(storage_id);
}
Result ContentManagerService::InvalidateRightsIdCache() {
return impl::InvalidateRightsIdCache();
}
}

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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_icontentmetadatabase.hpp"
#include "ncm_icontentstorage.hpp"
namespace ams::ncm {
class ContentManagerService final : public sf::IServiceObject {
protected:
enum class CommandId {
CreateContentStorage = 0,
CreateContentMetaDatabase = 1,
VerifyContentStorage = 2,
VerifyContentMetaDatabase = 3,
OpenContentStorage = 4,
OpenContentMetaDatabase = 5,
CloseContentStorageForcibly = 6,
CloseContentMetaDatabaseForcibly = 7,
CleanupContentMetaDatabase = 8,
ActivateContentStorage = 9,
InactivateContentStorage = 10,
ActivateContentMetaDatabase = 11,
InactivateContentMetaDatabase = 12,
InvalidateRightsIdCache = 13,
};
public:
virtual Result CreateContentStorage(StorageId storage_id);
virtual Result CreateContentMetaDatabase(StorageId storage_id);
virtual Result VerifyContentStorage(StorageId storage_id);
virtual Result VerifyContentMetaDatabase(StorageId storage_id);
virtual Result OpenContentStorage(sf::Out<std::shared_ptr<IContentStorage>> out, StorageId storage_id);
virtual Result OpenContentMetaDatabase(sf::Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id);
virtual Result CloseContentStorageForcibly(StorageId storage_id);
virtual Result CloseContentMetaDatabaseForcibly(StorageId storage_id);
virtual Result CleanupContentMetaDatabase(StorageId storage_id);
virtual Result ActivateContentStorage(StorageId storage_id);
virtual Result InactivateContentStorage(StorageId storage_id);
virtual Result ActivateContentMetaDatabase(StorageId storage_id);
virtual Result InactivateContentMetaDatabase(StorageId storage_id);
virtual Result InvalidateRightsIdCache();
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(CreateContentStorage),
MAKE_SERVICE_COMMAND_META(CreateContentMetaDatabase),
MAKE_SERVICE_COMMAND_META(VerifyContentStorage),
MAKE_SERVICE_COMMAND_META(VerifyContentMetaDatabase),
MAKE_SERVICE_COMMAND_META(OpenContentStorage),
MAKE_SERVICE_COMMAND_META(OpenContentMetaDatabase),
MAKE_SERVICE_COMMAND_META(CloseContentStorageForcibly, hos::Version_100, hos::Version_100),
MAKE_SERVICE_COMMAND_META(CloseContentMetaDatabaseForcibly, hos::Version_100, hos::Version_100),
MAKE_SERVICE_COMMAND_META(CleanupContentMetaDatabase),
MAKE_SERVICE_COMMAND_META(ActivateContentStorage, hos::Version_200),
MAKE_SERVICE_COMMAND_META(InactivateContentStorage, hos::Version_200),
MAKE_SERVICE_COMMAND_META(ActivateContentMetaDatabase, hos::Version_200),
MAKE_SERVICE_COMMAND_META(InactivateContentMetaDatabase, hos::Version_200),
MAKE_SERVICE_COMMAND_META(InvalidateRightsIdCache, hos::Version_900),
};
};
}

View file

@ -0,0 +1,620 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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_contentmetadatabase.hpp"
#include "ncm_utils.hpp"
namespace ams::ncm {
namespace {
struct ContentMetaHeader {
u16 extended_header_size;
u16 content_count;
u16 content_meta_count;
ContentMetaAttribute attributes;
StorageId storage_id;
};
static_assert(sizeof(ContentMetaHeader) == 0x8, "ContentMetaHeader definition!");
struct ApplicationMetaExtendedHeader {
ProgramId patch_id;
u32 required_system_version;
u32 required_application_version;
};
struct PatchMetaExtendedHeader {
ProgramId application_id;
u32 required_system_version;
u32 extended_data_size;
u8 reserved[0x8];
};
struct AddOnContentMetaExtendedHeader {
ProgramId application_id;
u32 required_application_version;
u32 padding;
};
struct SystemUpdateMetaExtendedHeader {
u32 extended_data_size;
};
inline const ContentMetaHeader* GetValueHeader(const void* value) {
return reinterpret_cast<const ContentMetaHeader*>(value);
}
template<typename ExtendedHeaderType>
inline const ExtendedHeaderType* GetValueExtendedHeader(const void* value) {
return reinterpret_cast<const ExtendedHeaderType*>(reinterpret_cast<const u8*>(value) + sizeof(ContentMetaHeader));
}
inline const ContentInfo* GetValueContentInfos(const void* value) {
return reinterpret_cast<const ContentInfo*>(reinterpret_cast<const u8*>(value) + sizeof(ContentMetaHeader) + GetValueHeader(value)->extended_header_size);
}
inline const ContentMetaInfo* GetValueContentMetaInfos(const void* value) {
auto header = GetValueHeader(value);
return reinterpret_cast<const ContentMetaInfo*>(reinterpret_cast<const u8*>(GetValueContentInfos(value)) + sizeof(ContentInfo) * header->content_count);
}
Result GetContentMetaSize(size_t *out, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) {
R_TRY_CATCH(kvs->GetValueSize(out, key)) {
R_CATCH(kvdb::ResultKeyNotFound) {
return ResultContentMetaNotFound();
}
} R_END_TRY_CATCH;
return ResultSuccess();
}
Result GetContentMetaValuePointer(const void **out_value_ptr, size_t *out_size, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) {
R_TRY(GetContentMetaSize(out_size, key, kvs));
R_TRY(kvs->GetValuePointer(out_value_ptr, key));
return ResultSuccess();
}
}
Result ContentMetaDatabaseInterface::GetContentIdByTypeImpl(ContentId* out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) {
R_TRY(this->EnsureEnabled());
const auto it = this->kvs->lower_bound(key);
if (it == this->kvs->end() || it->GetKey().id != key.id) {
return ResultContentMetaNotFound();
}
const auto stored_key = it->GetKey();
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, stored_key, this->kvs));
const auto header = GetValueHeader(value);
if (header->content_count == 0) {
return ResultContentNotFound();
}
const ContentInfo* content_infos = GetValueContentInfos(value);
const ContentInfo* found_content_info = nullptr;
if (id_offset) {
for (size_t i = 0; i < header->content_count; i++) {
const ContentInfo* content_info = &content_infos[i];
if (content_info->content_type == type && content_info->id_offset == *id_offset) {
found_content_info = content_info;
break;
}
}
} else {
const ContentInfo* lowest_id_offset_info = nullptr;
for (size_t i = 0; i < header->content_count; i++) {
const ContentInfo* content_info = &content_infos[i];
if (content_info->content_type == type && (!lowest_id_offset_info || lowest_id_offset_info->id_offset > content_info->id_offset)) {
lowest_id_offset_info = content_info;
}
}
found_content_info = lowest_id_offset_info;
}
if (!found_content_info) {
return ResultContentNotFound();
}
*out = found_content_info->content_id;
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetLatestContentMetaKeyImpl(ContentMetaKey* out_key, ProgramId id) {
R_TRY(this->EnsureEnabled());
ContentMetaKey key = {0};
key.id = id;
bool found_key = false;
for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) {
if (entry->GetKey().id != key.id) {
break;
}
if (entry->GetKey().install_type == ContentInstallType::Full) {
key = entry->GetKey();
found_key = true;
}
}
if (!found_key) {
return ResultContentMetaNotFound();
}
*out_key = key;
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::Set(ContentMetaKey key, sf::InBuffer value) {
R_TRY(this->EnsureEnabled());
R_TRY(this->kvs->Set(key, value.GetPointer(), value.GetSize()));
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::Get(sf::Out<u64> out_size, ContentMetaKey key, sf::OutBuffer out_value) {
R_TRY(this->EnsureEnabled());
R_TRY(this->kvs->Get(out_size.GetPointer(), out_value.GetPointer(), out_value.GetSize(), key));
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::Remove(ContentMetaKey key) {
R_TRY(this->EnsureEnabled());
R_TRY(this->kvs->Remove(key));
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetContentIdByType(sf::Out<ContentId> out_content_id, ContentMetaKey key, ContentType type) {
ContentId content_id;
R_TRY(this->GetContentIdByTypeImpl(&content_id, key, type, std::nullopt));
out_content_id.SetValue(content_id);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::ListContentInfo(sf::Out<u32> out_count, const sf::OutArray<ContentInfo> &out_info, ContentMetaKey key, u32 offset) {
if (offset >> 0x1f != 0) {
return ResultInvalidOffset();
}
R_TRY(this->EnsureEnabled());
const void *value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const auto header = GetValueHeader(value);
const auto content_infos = GetValueContentInfos(value);
size_t count;
for (count = 0; offset + count < header->content_count && count < out_info.GetSize(); count++) {
out_info[count] = content_infos[offset + count];
}
out_count.SetValue(count);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::List(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType type, ProgramId application_program_id, ProgramId program_id_min, ProgramId program_id_max, ContentInstallType install_type) {
R_TRY(this->EnsureEnabled());
size_t entries_total = 0;
size_t entries_written = 0;
/* If there are no entries then we've already successfully listed them all. */
if (this->kvs->GetCount() == 0) {
out_entries_total.SetValue(entries_total);
out_entries_written.SetValue(entries_written);
return ResultSuccess();
}
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
ContentMetaKey key = entry->GetKey();
/* Check if this entry matches the given filters. */
if (!((static_cast<u8>(type) == 0 || key.type == type) && (program_id_min <= key.id && key.id <= program_id_max) && (key.install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
continue;
}
if (static_cast<u64>(application_program_id) != 0) {
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
/* Each of these types are owned by an application. We need to check if their owner application matches the filter. */
if (key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent || key.type == ContentMetaType::Delta) {
ProgramId entry_application_id = key.id;
switch (key.type) {
case ContentMetaType::Application:
break;
default:
/* The first u64 of all non-application extended headers is the application program id. */
entry_application_id = *GetValueExtendedHeader<ProgramId>(value);
}
/* Application id doesn't match filter, skip this entry. */
if (entry_application_id != application_program_id) {
continue;
}
}
}
/* Write the entry to the output buffer. */
if (entries_written < out_info.GetSize()) {
out_info[entries_written] = key;
entries_written++;
}
entries_total++;
}
out_entries_total.SetValue(entries_total);
out_entries_written.SetValue(entries_written);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId program_id) {
R_TRY(this->EnsureEnabled());
ContentMetaKey key;
R_TRY(this->GetLatestContentMetaKeyImpl(&key, program_id));
out_key.SetValue(key);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::ListApplication(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType type) {
R_TRY(this->EnsureEnabled());
size_t entries_total = 0;
size_t entries_written = 0;
/* If there are no entries then we've already successfully listed them all. */
if (this->kvs->GetCount() == 0) {
out_entries_total.SetValue(entries_total);
out_entries_written.SetValue(entries_written);
return ResultSuccess();
}
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
ContentMetaKey key = entry->GetKey();
/* Check if this entry matches the given filters. */
if (!((static_cast<u8>(type) == 0 || key.type == type))) {
continue;
}
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
if (key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent || key.type == ContentMetaType::Delta) {
ProgramId application_id = key.id;
switch (key.type) {
case ContentMetaType::Application:
break;
default:
/* The first u64 of all non-application extended headers is the application program id. */
application_id = *GetValueExtendedHeader<ProgramId>(value);
}
/* Write the entry to the output buffer. */
if (entries_written < out_keys.GetSize()) {
ApplicationContentMetaKey* out_app_key = &out_keys[entries_written];
out_app_key->application_program_id = application_id;
out_app_key->key = key;
entries_written++;
}
entries_total++;
}
}
out_entries_total.SetValue(entries_total);
out_entries_written.SetValue(entries_written);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::Has(sf::Out<bool> out, ContentMetaKey key) {
R_TRY(this->EnsureEnabled());
if (this->kvs->GetCount() == 0) {
out.SetValue(false);
return ResultSuccess();
}
bool has = false;
const auto it = this->kvs->lower_bound(key);
if (it != this->kvs->end()) {
has = it->GetKey() == key;
}
out.SetValue(has);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) {
R_TRY(this->EnsureEnabled());
bool has = true;
for (size_t i = 0; i < keys.GetSize() && has; i++) {
R_TRY(this->Has(&has, keys[i]));
}
out.SetValue(has);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetSize(sf::Out<u64> out_size, ContentMetaKey key) {
R_TRY(this->EnsureEnabled());
if (this->kvs->GetCount() == 0) {
return ResultContentMetaNotFound();
}
const auto it = this->kvs->lower_bound(key);
if (it == this->kvs->end() || it->GetKey() != key) {
return ResultContentMetaNotFound();
}
out_size.SetValue(it->GetValueSize());
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetRequiredSystemVersion(sf::Out<u32> out_version, ContentMetaKey key) {
R_TRY(this->EnsureEnabled());
if (key.type != ContentMetaType::Application && key.type != ContentMetaType::Patch) {
return ResultInvalidContentMetaKey();
}
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
/* Required system version is at the same offset for the patch and application extended header.
We use the application header for convenience. */
const auto ext_header = GetValueExtendedHeader<ApplicationMetaExtendedHeader>(value);
out_version.SetValue(ext_header->required_system_version);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetPatchId(sf::Out<ProgramId> out_patch_id, ContentMetaKey key) {
R_TRY(this->EnsureEnabled());
if (key.type != ContentMetaType::Application) {
return ResultInvalidContentMetaKey();
}
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const auto ext_header = GetValueExtendedHeader<ApplicationMetaExtendedHeader>(value);
out_patch_id.SetValue(ext_header->patch_id);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::DisableForcibly() {
this->disabled = true;
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) {
R_TRY(this->EnsureEnabled());
if (out_orphaned.GetSize() < content_ids.GetSize()) {
return ResultBufferInsufficient();
}
/* Default to orphaned for all content ids. */
if (out_orphaned.GetSize() > 0) {
std::fill_n(out_orphaned.GetPointer(), out_orphaned.GetSize(), true);
}
if (this->kvs->GetCount() == 0) {
return ResultSuccess();
}
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
const auto value = entry->GetValuePointer();
const auto header = GetValueHeader(value);
if (header->content_count == 0) {
continue;
}
if (content_ids.GetSize() == 0) {
continue;
}
const ContentInfo* content_infos = GetValueContentInfos(value);
for (size_t i = 0; i < header->content_count; i++) {
const ContentInfo* content_info = &content_infos[i];
/* Check if any of this entry's content infos matches one of the provided content ids.
If they do, then the content id isn't orphaned. */
for (size_t j = 0; j < content_ids.GetSize(); j++) {
const ContentId content_id = content_ids[j];
if (content_id == content_info->content_id) {
out_orphaned[j] = false;
break;
}
}
}
}
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::Commit() {
R_TRY(this->EnsureEnabled());
R_TRY(this->kvs->Save());
R_TRY(fsdevCommitDevice(this->mount_name));
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::HasContent(sf::Out<bool> out, ContentMetaKey key, ContentId content_id) {
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const auto header = GetValueHeader(value);
const ContentInfo* content_infos = GetValueContentInfos(value);
if (header->content_count > 0) {
for (size_t i = 0; i < header->content_count; i++) {
const ContentInfo* content_info = &content_infos[i];
if (content_id == content_info->content_id) {
out.SetValue(false);
return ResultSuccess();
}
}
}
out.SetValue(false);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::ListContentMetaInfo(sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, ContentMetaKey key, u32 start_index) {
if (start_index >> 0x1f != 0) {
return ResultInvalidOffset();
}
R_TRY(this->EnsureEnabled());
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const auto header = GetValueHeader(value);
const auto content_meta_infos = GetValueContentMetaInfos(value);
size_t entries_written = 0;
if (out_meta_info.GetSize() == 0) {
out_entries_written.SetValue(0);
return ResultSuccess();
}
for (size_t i = start_index; i < out_meta_info.GetSize(); i++) {
/* We have no more entries we can read out. */
if (header->content_meta_count <= start_index + i) {
break;
}
out_meta_info[i] = content_meta_infos[i];
entries_written = i + 1;
}
out_entries_written.SetValue(entries_written);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetAttributes(sf::Out<ContentMetaAttribute> out_attributes, ContentMetaKey key) {
R_TRY(this->EnsureEnabled());
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
const auto header = GetValueHeader(value);
out_attributes.SetValue(header->attributes);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetRequiredApplicationVersion(sf::Out<u32> out_version, ContentMetaKey key) {
R_TRY(this->EnsureEnabled());
const void* value = nullptr;
size_t value_size = 0;
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
/* As of 9.0.0, applications can be dependent on a specific base application version. */
if (hos::GetVersion() >= hos::Version_900 && key.type == ContentMetaType::Application) {
const auto ext_header = GetValueExtendedHeader<ApplicationMetaExtendedHeader>(value);
out_version.SetValue(ext_header->required_application_version);
return ResultSuccess();
}
if (key.type != ContentMetaType::AddOnContent) {
return ResultInvalidContentMetaKey();
}
const auto ext_header = GetValueExtendedHeader<AddOnContentMetaExtendedHeader>(value);
out_version.SetValue(ext_header->required_application_version);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) {
ContentId content_id;
R_TRY(this->GetContentIdByTypeImpl(&content_id, key, type, std::optional(id_offset)));
out_content_id.SetValue(content_id);
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetLatestProgram(ContentId* out_content_id, ProgramId program_id) {
ContentMetaKey key;
R_TRY(this->GetLatestContentMetaKey(&key, program_id));
R_TRY(this->GetContentIdByType(out_content_id, key, ContentType::Program));
return ResultSuccess();
}
Result ContentMetaDatabaseInterface::GetLatestData(ContentId* out_content_id, ProgramId program_id) {
ContentMetaKey key;
R_TRY(this->GetLatestContentMetaKey(&key, program_id));
R_TRY(this->GetContentIdByType(out_content_id, key, ContentType::Data));
return ResultSuccess();
}
Result OnMemoryContentMetaDatabaseInterface::GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId program_id) {
R_TRY(this->EnsureEnabled());
const ContentMetaKey key = ContentMetaKey::Make(program_id, 0, ContentMetaType::Unknown);
std::optional<ContentMetaKey> found_key;
for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) {
if (entry->GetKey().id != program_id) {
break;
}
found_key = entry->GetKey();
}
if (!found_key) {
return ResultContentMetaNotFound();
}
*out_key = *found_key;
return ResultSuccess();
}
Result OnMemoryContentMetaDatabaseInterface::LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) {
return ResultInvalidContentMetaDatabase();
}
Result OnMemoryContentMetaDatabaseInterface::Commit() {
R_TRY(this->EnsureEnabled());
return ResultSuccess();
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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_icontentmetadatabase.hpp"
namespace ams::ncm {
class ContentMetaDatabaseInterface : public IContentMetaDatabase {
public:
ContentMetaDatabaseInterface(ams::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs, const char* mount_name) : IContentMetaDatabase(kvs, mount_name) {
}
ContentMetaDatabaseInterface(ams::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs) : IContentMetaDatabase(kvs) {
}
private:
Result GetContentIdByTypeImpl(ContentId* out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset);
Result GetLatestContentMetaKeyImpl(ContentMetaKey* out_key, ProgramId id);
public:
virtual Result Set(ContentMetaKey key, sf::InBuffer value) override;
virtual Result Get(sf::Out<u64> out_size, ContentMetaKey key, sf::OutBuffer out_value) override;
virtual Result Remove(ContentMetaKey key) override;
virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, ContentMetaKey key, ContentType type) override;
virtual Result ListContentInfo(sf::Out<u32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, ContentMetaKey key, u32 start_index) override;
virtual Result List(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType type, ProgramId application_program_id, ProgramId program_id_min, ProgramId program_id_max, ContentInstallType install_type) override;
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId id) override;
virtual Result ListApplication(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType type) override;
virtual Result Has(sf::Out<bool> out, ContentMetaKey key) override;
virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) override;
virtual Result GetSize(sf::Out<u64> out_size, ContentMetaKey key) override;
virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, ContentMetaKey key) override;
virtual Result GetPatchId(sf::Out<ProgramId> out_patch_id, ContentMetaKey key) override;
virtual Result DisableForcibly() override;
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override;
virtual Result Commit() override;
virtual Result HasContent(sf::Out<bool> out, ContentMetaKey key, ContentId content_id) override;
virtual Result ListContentMetaInfo(sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, ContentMetaKey key, u32 start_index) override;
virtual Result GetAttributes(sf::Out<ContentMetaAttribute> out_attributes, ContentMetaKey key) override;
virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, ContentMetaKey key) override;
virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) override;
/* APIs. */
virtual Result GetLatestProgram(ContentId* out_content_id, ProgramId program_id) override;
virtual Result GetLatestData(ContentId* out_content_id, ProgramId program_id) override;
};
class OnMemoryContentMetaDatabaseInterface : public ContentMetaDatabaseInterface {
public:
OnMemoryContentMetaDatabaseInterface(ams::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs) : ContentMetaDatabaseInterface(kvs) {
}
public:
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId id) override;
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) override;
virtual Result Commit() override;
};
}

View file

@ -0,0 +1,654 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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_contentstorage.hpp"
#include "ncm_fs.hpp"
#include "ncm_make_path.hpp"
#include "ncm_utils.hpp"
namespace ams::ncm {
ContentStorageInterface::~ContentStorageInterface() {
this->Finalize();
}
Result ContentStorageInterface::Initialize(const char* root_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(root_path));
const size_t root_path_len = strnlen(root_path, FS_MAX_PATH-1);
if (root_path_len >= FS_MAX_PATH-1) {
std::abort();
}
strncpy(this->root_path, root_path, FS_MAX_PATH-2);
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 ContentStorageInterface::Finalize() {
this->ClearContentCache();
this->placeholder_accessor.InvalidateAll();
}
void ContentStorageInterface::ClearContentCache() {
if (this->cached_content_id != InvalidContentId) {
fclose(this->content_cache_file_handle);
this->cached_content_id = InvalidContentId;
}
}
unsigned int ContentStorageInterface::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;
}
std::abort();
}
Result ContentStorageInterface::OpenCachedContentFile(ContentId content_id) {
if (this->cached_content_id == content_id) {
return ResultSuccess();
}
this->ClearContentCache();
char content_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
R_TRY_CATCH(fs::OpenFile(&this->content_cache_file_handle, content_path, FsOpenMode_Read)) {
R_CATCH(ams::fs::ResultPathNotFound) {
return ResultContentNotFound();
}
} R_END_TRY_CATCH;
this->cached_content_id = content_id;
return ResultSuccess();
}
Result ContentStorageInterface::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
R_TRY(this->EnsureEnabled());
ams::rnd::GenerateRandomBytes(out.GetPointer(), sizeof(PlaceHolderId));
char placeholder_str[FS_MAX_PATH] = {0};
GetStringFromPlaceHolderId(placeholder_str, *out.GetPointer());
return ResultSuccess();
}
Result ContentStorageInterface::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) {
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
R_TRY(fs::EnsureParentDirectoryRecursively(content_path));
R_TRY(this->placeholder_accessor.Create(placeholder_id, size));
return ResultSuccess();
}
Result ContentStorageInterface::DeletePlaceHolder(PlaceHolderId placeholder_id) {
R_TRY(this->EnsureEnabled());
return this->placeholder_accessor.Delete(placeholder_id);
}
Result ContentStorageInterface::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
R_TRY(this->EnsureEnabled());
char placeholder_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.MakePath(placeholder_path, placeholder_id);
bool has = false;
R_TRY(fs::HasFile(&has, placeholder_path));
out.SetValue(has);
return ResultSuccess();
}
Result ContentStorageInterface::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, sf::InBuffer data) {
/* Offset is too large */
if (offset >> 0x3f != 0) {
return ResultInvalidOffset();
}
R_TRY(this->EnsureEnabled());
R_TRY(this->placeholder_accessor.Write(placeholder_id, offset, data.GetPointer(), data.GetSize()));
return ResultSuccess();
}
Result ContentStorageInterface::Register(PlaceHolderId placeholder_id, ContentId content_id) {
this->ClearContentCache();
R_TRY(this->EnsureEnabled());
char placeholder_path[FS_MAX_PATH] = {0};
char content_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.GetPath(placeholder_path, placeholder_id);
this->GetContentPath(content_path, content_id);
if (rename(placeholder_path, content_path) != 0) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathNotFound) {
return ResultPlaceHolderNotFound();
}
R_CATCH(ams::fs::ResultPathAlreadyExists) {
return ResultContentAlreadyExists();
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result ContentStorageInterface::Delete(ContentId content_id) {
R_TRY(this->EnsureEnabled());
this->ClearContentCache();
char content_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
if (std::remove(content_path) != 0) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathNotFound) {
return ResultContentNotFound();
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result ContentStorageInterface::Has(sf::Out<bool> out, ContentId content_id) {
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
bool has = false;
R_TRY(fs::HasFile(&has, content_path));
out.SetValue(has);
return ResultSuccess();
}
Result ContentStorageInterface::GetPath(sf::Out<lr::Path> out, ContentId content_id) {
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
char common_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
out.SetValue(lr::Path::Encode(common_path));
return ResultSuccess();
}
Result ContentStorageInterface::GetPlaceHolderPath(sf::Out<lr::Path> out, PlaceHolderId placeholder_id) {
R_TRY(this->EnsureEnabled());
char placeholder_path[FS_MAX_PATH] = {0};
char common_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.GetPath(placeholder_path, placeholder_id);
R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path));
out.SetValue(lr::Path::Encode(common_path));
return ResultSuccess();
}
Result ContentStorageInterface::CleanupAllPlaceHolder() {
R_TRY(this->EnsureEnabled());
char placeholder_root_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.InvalidateAll();
this->placeholder_accessor.MakeRootPath(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));
if (mkdir(placeholder_root_path, S_IRWXU) == -1) {
return fsdevGetLastResult();
}
return ResultSuccess();
}
Result ContentStorageInterface::ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
R_TRY(this->EnsureEnabled());
char placeholder_root_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.MakeRootPath(placeholder_root_path);
const unsigned int dir_depth = this->placeholder_accessor.GetDirectoryDepth();
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 {
*should_continue = true;
*should_retry_dir_read = false;
if (dir_entry->d_type == DT_REG) {
if (entry_count > out_buf.GetSize()) {
return ResultBufferInsufficient();
}
PlaceHolderId cur_entry_placeholder_id = {0};
R_TRY(GetPlaceHolderIdFromDirEntry(&cur_entry_placeholder_id, dir_entry));
out_buf[entry_count++] = cur_entry_placeholder_id;
}
return ResultSuccess();
}));
out_count.SetValue(static_cast<u32>(entry_count));
return ResultSuccess();
}
Result ContentStorageInterface::GetContentCount(sf::Out<u32> out_count) {
R_TRY(this->EnsureEnabled());
char content_root_path[FS_MAX_PATH] = {0};
this->GetContentRootPath(content_root_path);
const unsigned int dir_depth = this->GetContentDirectoryDepth();
u32 content_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 {
*should_continue = true;
*should_retry_dir_read = false;
if (dir_entry->d_type == DT_REG) {
content_count++;
}
return ResultSuccess();
}));
out_count.SetValue(content_count);
return ResultSuccess();
}
Result ContentStorageInterface::ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 start_offset) {
if (start_offset >> 0x1f != 0) {
return ResultInvalidOffset();
}
R_TRY(this->EnsureEnabled());
char content_root_path[FS_MAX_PATH] = {0};
this->GetContentRootPath(content_root_path);
const unsigned int dir_depth = this->GetContentDirectoryDepth();
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) {
*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 don't necessarily expect to be able to completely fill the output buffer. */
if (entry_count > out_buf.GetSize()) {
*should_continue = false;
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();
}
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();
}
Result ContentStorageInterface::GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) {
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
struct stat st;
if (stat(content_path, &st) == -1) {
return fsdevGetLastResult();
}
out_size.SetValue(st.st_size);
return ResultSuccess();
}
Result ContentStorageInterface::DisableForcibly() {
this->disabled = true;
this->ClearContentCache();
this->placeholder_accessor.InvalidateAll();
return ResultSuccess();
}
Result ContentStorageInterface::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
R_TRY(this->EnsureEnabled());
char old_content_path[FS_MAX_PATH] = {0};
char new_content_path[FS_MAX_PATH] = {0};
char placeholder_path[FS_MAX_PATH] = {0};
this->ClearContentCache();
/* Ensure the new content path is ready. */
this->GetContentPath(new_content_path, new_content_id);
R_TRY(fs::EnsureParentDirectoryRecursively(new_content_path));
R_TRY(this->placeholder_accessor.EnsureRecursively(placeholder_id));
this->placeholder_accessor.GetPath(placeholder_path, placeholder_id);
if (rename(old_content_path, placeholder_path) != 0) {
R_TRY_CATCH(fsdevGetLastResult()) {
R_CATCH(ams::fs::ResultPathNotFound) {
return ResultPlaceHolderNotFound();
}
R_CATCH(ams::fs::ResultPathAlreadyExists) {
return ResultPlaceHolderAlreadyExists();
}
} R_END_TRY_CATCH;
}
return ResultSuccess();
}
Result ContentStorageInterface::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) {
R_TRY(this->EnsureEnabled());
R_TRY(this->placeholder_accessor.SetSize(placeholder_id, size));
return ResultSuccess();
}
Result ContentStorageInterface::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) {
/* Offset is too large */
if (offset >> 0x3f != 0) {
return ResultInvalidOffset();
}
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
R_TRY(this->OpenCachedContentFile(content_id));
R_TRY(fs::ReadFile(this->content_cache_file_handle, offset, buf.GetPointer(), buf.GetSize()));
return ResultSuccess();
}
Result ContentStorageInterface::GetRightsIdFromPlaceHolderId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id) {
R_TRY(this->EnsureEnabled());
FsRightsId rights_id = {0};
u8 key_generation = 0;
char placeholder_path[FS_MAX_PATH] = {0};
char common_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.GetPath(placeholder_path, placeholder_id);
R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path));
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
out_rights_id.SetValue(rights_id);
out_key_generation.SetValue(static_cast<u64>(key_generation));
return ResultSuccess();
}
Result ContentStorageInterface::GetRightsIdFromContentId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, ContentId content_id) {
R_TRY(this->EnsureEnabled());
{
std::scoped_lock<os::Mutex> lk(this->rights_id_cache->mutex);
/* Attempt to locate the content id in the cache. */
for (size_t i = 0; i < impl::RightsIdCache::MaxEntries; i++) {
impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i];
if (entry->last_accessed != 1 && content_id == entry->uuid) {
entry->last_accessed = this->rights_id_cache->counter;
this->rights_id_cache->counter++;
out_rights_id.SetValue(entry->rights_id);
out_key_generation.SetValue(entry->key_generation);
return ResultSuccess();
}
}
}
FsRightsId rights_id = {0};
u8 key_generation = 0;
char content_path[FS_MAX_PATH] = {0};
char common_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
{
std::scoped_lock<os::Mutex> lk(this->rights_id_cache->mutex);
impl::RightsIdCache::Entry* eviction_candidate = &this->rights_id_cache->entries[0];
/* Find a suitable existing entry to store our new one at. */
for (size_t i = 1; i < impl::RightsIdCache::MaxEntries; i++) {
impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i];
/* Change eviction candidates if the uuid already matches ours, or if the uuid doesn't already match and the last_accessed count is lower */
if (content_id == entry->uuid || (content_id != eviction_candidate->uuid && entry->last_accessed < eviction_candidate->last_accessed)) {
eviction_candidate = entry;
}
}
/* Update the cache. */
eviction_candidate->uuid = content_id.uuid;
eviction_candidate->rights_id = rights_id;
eviction_candidate->key_generation = key_generation;
eviction_candidate->last_accessed = this->rights_id_cache->counter;
this->rights_id_cache->counter++;
/* Set output. */
out_rights_id.SetValue(rights_id);
out_key_generation.SetValue(key_generation);
}
return ResultSuccess();
}
Result ContentStorageInterface::WriteContentForDebug(ContentId content_id, u64 offset, sf::InBuffer data) {
/* Offset is too large */
if (offset >> 0x3f != 0) {
return ResultInvalidOffset();
}
R_TRY(this->EnsureEnabled());
bool is_development = false;
if (R_FAILED(splIsDevelopment(&is_development)) || !is_development) {
std::abort();
}
this->ClearContentCache();
char content_path[FS_MAX_PATH] = {0};
this->GetContentPath(content_path, content_id);
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(), FsWriteOption_Flush));
return ResultSuccess();
}
Result ContentStorageInterface::GetFreeSpaceSize(sf::Out<u64> out_size) {
struct statvfs st = {0};
if (statvfs(this->root_path, &st) == -1) {
return fsdevGetLastResult();
}
out_size.SetValue(st.f_bfree);
return ResultSuccess();
}
Result ContentStorageInterface::GetTotalSpaceSize(sf::Out<u64> out_size) {
struct statvfs st = {0};
if (statvfs(this->root_path, &st) == -1) {
return fsdevGetLastResult();
}
out_size.SetValue(st.f_blocks);
return ResultSuccess();
}
Result ContentStorageInterface::FlushPlaceHolder() {
this->placeholder_accessor.InvalidateAll();
return ResultSuccess();
}
Result ContentStorageInterface::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);
return ResultSuccess();
}
char placeholder_path[FS_MAX_PATH] = {0};
struct stat st;
this->placeholder_accessor.GetPath(placeholder_path, placeholder_id);
if (stat(placeholder_path, &st) == -1) {
return fsdevGetLastResult();
}
out_size.SetValue(st.st_size);
return ResultSuccess();
}
Result ContentStorageInterface::RepairInvalidFileAttribute() {
char content_root_path[FS_MAX_PATH] = {0};
this->GetContentRootPath(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) {
*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))) {
*should_retry_dir_read = true;
}
}
}
return ResultSuccess();
};
R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, fix_file_attributes));
char placeholder_root_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.InvalidateAll();
this->placeholder_accessor.MakeRootPath(placeholder_root_path);
dir_depth = this->placeholder_accessor.GetDirectoryDepth();
R_TRY(fs::TraverseDirectory(placeholder_root_path, dir_depth, fix_file_attributes));
return ResultSuccess();
}
Result ContentStorageInterface::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) {
R_TRY(this->EnsureEnabled());
{
std::scoped_lock<os::Mutex> lk(this->rights_id_cache->mutex);
/* Attempt to locate the content id in the cache. */
for (size_t i = 0; i < impl::RightsIdCache::MaxEntries; i++) {
impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i];
if (entry->last_accessed != 1 && cache_content_id == entry->uuid) {
entry->last_accessed = this->rights_id_cache->counter;
this->rights_id_cache->counter++;
out_rights_id.SetValue(entry->rights_id);
out_key_generation.SetValue(entry->key_generation);
return ResultSuccess();
}
}
}
FsRightsId rights_id = {0};
u8 key_generation = 0;
char placeholder_path[FS_MAX_PATH] = {0};
char common_path[FS_MAX_PATH] = {0};
this->placeholder_accessor.GetPath(placeholder_path, placeholder_id);
R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path));
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
{
std::scoped_lock<os::Mutex> lk(this->rights_id_cache->mutex);
impl::RightsIdCache::Entry* eviction_candidate = &this->rights_id_cache->entries[0];
/* Find a suitable existing entry to store our new one at. */
for (size_t i = 1; i < impl::RightsIdCache::MaxEntries; i++) {
impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i];
/* Change eviction candidates if the uuid already matches ours, or if the uuid doesn't already match and the last_accessed count is lower */
if (cache_content_id == entry->uuid || (cache_content_id != eviction_candidate->uuid && entry->last_accessed < eviction_candidate->last_accessed)) {
eviction_candidate = entry;
}
}
/* Update the cache. */
eviction_candidate->uuid = cache_content_id.uuid;
eviction_candidate->rights_id = rights_id;
eviction_candidate->key_generation = key_generation;
eviction_candidate->last_accessed = this->rights_id_cache->counter;
this->rights_id_cache->counter++;
/* Set output. */
out_rights_id.SetValue(rights_id);
out_key_generation.SetValue(key_generation);
}
return ResultSuccess();
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 "impl/ncm_placeholder_accessor.hpp"
#include "impl/ncm_rights_cache.hpp"
#include "ncm_icontentstorage.hpp"
#include "ncm_path_utils.hpp"
namespace ams::ncm {
class ContentStorageInterface : public IContentStorage {
protected:
impl::PlaceHolderAccessor placeholder_accessor;
ContentId cached_content_id;
FILE* content_cache_file_handle;
impl::RightsIdCache* rights_id_cache;
public:
~ContentStorageInterface();
Result Initialize(const char* root_path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush, impl::RightsIdCache* rights_id_cache);
void Finalize();
private:
void ClearContentCache();
unsigned int GetContentDirectoryDepth();
Result OpenCachedContentFile(ContentId content_id);
inline void GetContentRootPath(char* out_content_root) {
path::GetContentRootPath(out_content_root, this->root_path);
}
inline void GetContentPath(char* out_content_path, ContentId content_id) {
char content_root_path[FS_MAX_PATH] = {0};
this->GetContentRootPath(content_root_path);
this->make_content_path_func(out_content_path, content_id, content_root_path);
}
public:
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override;
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, sf::InBuffer data) override;
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
virtual Result Delete(ContentId content_id) override;
virtual Result Has(sf::Out<bool> out, ContentId content_id) override;
virtual Result GetPath(sf::Out<lr::Path> out, ContentId content_id) override;
virtual Result GetPlaceHolderPath(sf::Out<lr::Path> out, PlaceHolderId placeholder_id) override;
virtual Result CleanupAllPlaceHolder() override;
virtual Result ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override;
virtual Result GetContentCount(sf::Out<u32> out_count) override;
virtual Result ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 start_offset) override;
virtual Result GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) override;
virtual Result DisableForcibly() override;
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) override;
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) override;
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id) override;
virtual Result GetRightsIdFromContentId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, ContentId content_id) override;
virtual Result WriteContentForDebug(ContentId content_id, u64 offset, sf::InBuffer data) override;
virtual Result GetFreeSpaceSize(sf::Out<u64> out_size) override;
virtual Result GetTotalSpaceSize(sf::Out<u64> out_size) override;
virtual Result FlushPlaceHolder() override;
virtual Result GetSizeFromPlaceHolderId(sf::Out<u64> out, PlaceHolderId placeholder_id) override;
virtual Result RepairInvalidFileAttribute() override;
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) override;
};
}

View file

@ -0,0 +1,382 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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));
if (!has) {
return 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);
if (f == nullptr) {
return fsdevGetLastResult();
}
*out = f;
return ResultSuccess();
}
Result WriteFile(FILE* f, size_t offset, const void* buffer, size_t size, u32 option) {
if (fseek(f, 0, SEEK_END) != 0) {
return fsdevGetLastResult();
}
size_t existing_size = ftell(f);
if (offset + size > existing_size) {
return ams::fs::ResultFileExtensionWithoutOpenModeAllowAppend();
}
if (fseek(f, offset, SEEK_SET) != 0) {
return fsdevGetLastResult();
}
if (fwrite(buffer, 1, size, f) != size) {
return fsdevGetLastResult();
}
if (option & FsWriteOption_Flush) {
fflush(f);
}
return ResultSuccess();
}
Result ReadFile(FILE* f, size_t offset, void* buffer, size_t size) {
if (fseek(f, offset, SEEK_SET) != 0) {
return fsdevGetLastResult();
}
if (fread(buffer, 1, size, f) != size && ferror(f)) {
return 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* root_path) {
char content_root[FS_MAX_PATH] = {0};
char placeholder_root[FS_MAX_PATH] = {0};
bool has_root = false;
R_TRY(HasDirectory(&has_root, root_path));
if (!has_root) {
return ResultStorageRootNotFound();
}
path::GetContentRootPath(content_root, root_path);
bool has_content_root = false;
R_TRY(HasDirectory(&has_content_root, content_root));
if (!has_content_root) {
return ResultStoragePathNotFound();
}
path::GetPlaceHolderRootPath(placeholder_root, root_path);
bool has_placeholder_root = false;
R_TRY(HasDirectory(&has_placeholder_root, placeholder_root));
if (!has_placeholder_root) {
return ResultStoragePathNotFound();
}
return ResultSuccess();
}
Result EnsureContentAndPlaceHolderRoot(const char* root_path) {
char content_root[FS_MAX_PATH] = {0};
char placeholder_root[FS_MAX_PATH] = {0};
path::GetContentRootPath(content_root, root_path);
R_TRY(EnsureDirectoryRecursively(content_root));
path::GetPlaceHolderRootPath(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) {
if (!path) {
return ams::fs::ResultNullptrArgument();
}
size_t path_len = strlen(path);
char working_path_buf[FS_MAX_PATH] = {0};
if (path_len + 1 < FS_MAX_PATH) {
strncpy(working_path_buf + 1, path, FS_MAX_PATH-1);
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] = '/';
}
}
}
} else {
return ResultAllocationFailed();
}
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<os::Mutex> 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. */
if (!unqual_path || unqual_path > path + 0xf) {
return 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) {
if (!mount_point) {
return 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));
if (fsdevMountDevice(mount_point, fs) == -1) {
std::abort();
}
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) {
if (!mount_point) {
return ams::fs::ResultNullptrArgument();
}
FsFileSystem fs;
R_TRY(fsOpenContentStorageFileSystem(&fs, id));
if (fsdevMountDevice(mount_point, fs) == -1) {
std::abort();
}
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:
std::abort();
};
return ResultSuccess();
}
Result MountGameCardPartition(const char* mount_point, const FsGameCardHandle handle, FsGameCardPartition partition) {
if (partition > 2) {
std::abort();
}
FsFileSystem fs;
R_TRY(fsOpenGameCardFileSystem(&fs, &handle, partition));
if (fsdevMountDevice(mount_point, fs) == -1) {
std::abort();
}
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) {
if (!mount_point) {
return ams::fs::ResultNullptrArgument();
}
/* Erase any content storage mappings which may potentially exist. */
g_mount_content_storage.erase(mount_point);
if (fsdevUnmountDevice(mount_point) == -1) {
std::abort();
}
return ResultSuccess();
}
Result ConvertToFsCommonPath(char* out_common_path, size_t out_len, const char* path) {
if (!out_common_path || !path) {
return ams::fs::ResultNullptrArgument();
}
MountName mount_name = {0};
R_TRY(GetMountNameFromPath(&mount_name, path));
if (!fsdevGetDeviceFileSystem(mount_name.name) || g_mount_content_storage.find(mount_name.name) == g_mount_content_storage.end()) {
return ams::fs::ResultNotMounted();
}
char translated_path[FS_MAX_PATH] = {0};
std::string common_mount_name = g_mount_content_storage[mount_name.name];
if (fsdevTranslatePath(path, NULL, translated_path) == -1) {
std::abort();
}
snprintf(out_common_path, out_len, "%s:%s", common_mount_name.c_str(), translated_path);
return ResultSuccess();
}
Result GetSaveDataFlags(u32* out_flags, u64 save_id) {
FsSaveDataExtraData extra_data;
R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id));
*out_flags = extra_data.flags;
return ResultSuccess();
}
Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags) {
FsSaveDataExtraData extra_data;
R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id));
extra_data.flags = flags;
R_TRY(fsWriteSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), space_id, save_id));
return ResultSuccess();
}
}

View file

@ -0,0 +1,114 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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, u32 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;
if (max_level < 1) {
return ResultSuccess();
}
bool retry_dir_read = true;
while (retry_dir_read) {
retry_dir_read = false;
if ((dir = opendir(root_path)) == nullptr) {
return 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];
if (snprintf(current_path, FS_MAX_PATH-1, "%s/%s", root_path, dir_entry->d_name) < 0) {
std::abort();
}
bool should_continue = true;
bool should_retry_dir_read = false;
R_TRY(f(&should_continue, &should_retry_dir_read, current_path, dir_entry));
/* If the provided function wishes to terminate immediately, we should respect it. */
if (!should_continue) {
*out_should_continue = false;
return ResultSuccess();
}
if (should_retry_dir_read) {
retry_dir_read = true;
break;
}
if (dir_entry->d_type == DT_DIR) {
R_TRY(TraverseDirectory(&should_continue, current_path, max_level-1, f));
if (!should_continue) {
*out_should_continue = false;
return ResultSuccess();
}
}
}
}
return ResultSuccess();
};
template<typename F>
Result TraverseDirectory(const char* root_path, int max_level, F f) {
bool should_continue = false;
return TraverseDirectory(&should_continue, root_path, max_level, f);
}
}

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 <stratosphere/kvdb/kvdb_memory_key_value_store.hpp>
namespace ams::ncm {
class IContentMetaDatabase : public sf::IServiceObject {
protected:
enum class CommandId {
Set = 0,
Get = 1,
Remove = 2,
GetContentIdByType = 3,
ListContentInfo = 4,
List = 5,
GetLatestContentMetaKey = 6,
ListApplication = 7,
Has = 8,
HasAll = 9,
GetSize = 10,
GetRequiredSystemVersion = 11,
GetPatchId = 12,
DisableForcibly = 13,
LookupOrphanContent = 14,
Commit = 15,
HasContent = 16,
ListContentMetaInfo = 17,
GetAttributes = 18,
GetRequiredApplicationVersion = 19,
GetContentIdByTypeAndIdOffset = 20,
};
protected:
ams::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs;
char mount_name[16];
bool disabled;
protected:
Result EnsureEnabled() {
if (this->disabled) {
return ResultInvalidContentMetaDatabase();
}
return ResultSuccess();
}
public:
IContentMetaDatabase(ams::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs) :
kvs(kvs), disabled(false)
{
}
IContentMetaDatabase(ams::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs, const char* mount_name) :
IContentMetaDatabase(kvs)
{
strcpy(this->mount_name, mount_name);
}
public:
/* Actual commands. */
virtual Result Set(ContentMetaKey key, sf::InBuffer value) = 0;
virtual Result Get(sf::Out<u64> out_size, ContentMetaKey key, sf::OutBuffer out_value) = 0;
virtual Result Remove(ContentMetaKey key) = 0;
virtual Result GetContentIdByType(sf::Out<ContentId> out_content_id, ContentMetaKey key, ContentType type) = 0;
virtual Result ListContentInfo(sf::Out<u32> out_entries_written, const sf::OutArray<ContentInfo> &out_info, ContentMetaKey key, u32 start_index) = 0;
virtual Result List(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaKey> &out_info, ContentMetaType meta_type, ProgramId application_program_id, ProgramId program_id_min, ProgramId program_id_max, ContentInstallType install_type) = 0;
virtual Result GetLatestContentMetaKey(sf::Out<ContentMetaKey> out_key, ProgramId id) = 0;
virtual Result ListApplication(sf::Out<u32> out_entries_total, sf::Out<u32> out_entries_written, const sf::OutArray<ApplicationContentMetaKey> &out_keys, ContentMetaType meta_type) = 0;
virtual Result Has(sf::Out<bool> out, ContentMetaKey key) = 0;
virtual Result HasAll(sf::Out<bool> out, const sf::InArray<ContentMetaKey> &keys) = 0;
virtual Result GetSize(sf::Out<u64> out_size, ContentMetaKey key) = 0;
virtual Result GetRequiredSystemVersion(sf::Out<u32> out_version, ContentMetaKey key) = 0;
virtual Result GetPatchId(sf::Out<ProgramId> out_patch_id, ContentMetaKey key) = 0;
virtual Result DisableForcibly() = 0;
virtual Result LookupOrphanContent(const sf::OutArray<bool> &out_orphaned, const sf::InArray<ContentId> &content_ids) = 0;
virtual Result Commit() = 0;
virtual Result HasContent(sf::Out<bool> out, ContentMetaKey key, ContentId content_id) = 0;
virtual Result ListContentMetaInfo(sf::Out<u32> out_entries_written, const sf::OutArray<ContentMetaInfo> &out_meta_info, ContentMetaKey key, u32 start_index) = 0;
virtual Result GetAttributes(sf::Out<ContentMetaAttribute> out_attributes, ContentMetaKey key) = 0;
virtual Result GetRequiredApplicationVersion(sf::Out<u32> out_version, ContentMetaKey key) = 0;
virtual Result GetContentIdByTypeAndIdOffset(sf::Out<ContentId> out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) = 0;
/* APIs. */
virtual Result GetLatestProgram(ContentId* out_content_id, ProgramId program_id) = 0;
virtual Result GetLatestData(ContentId* out_content_id, ProgramId program_id) = 0;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(Set),
MAKE_SERVICE_COMMAND_META(Get),
MAKE_SERVICE_COMMAND_META(Remove),
MAKE_SERVICE_COMMAND_META(GetContentIdByType),
MAKE_SERVICE_COMMAND_META(ListContentInfo),
MAKE_SERVICE_COMMAND_META(List),
MAKE_SERVICE_COMMAND_META(GetLatestContentMetaKey),
MAKE_SERVICE_COMMAND_META(ListApplication),
MAKE_SERVICE_COMMAND_META(Has),
MAKE_SERVICE_COMMAND_META(HasAll),
MAKE_SERVICE_COMMAND_META(GetSize),
MAKE_SERVICE_COMMAND_META(GetRequiredSystemVersion),
MAKE_SERVICE_COMMAND_META(GetPatchId),
MAKE_SERVICE_COMMAND_META(DisableForcibly),
MAKE_SERVICE_COMMAND_META(LookupOrphanContent),
MAKE_SERVICE_COMMAND_META(Commit),
MAKE_SERVICE_COMMAND_META(HasContent),
MAKE_SERVICE_COMMAND_META(ListContentMetaInfo),
MAKE_SERVICE_COMMAND_META(GetAttributes),
MAKE_SERVICE_COMMAND_META(GetRequiredApplicationVersion, hos::Version_200),
MAKE_SERVICE_COMMAND_META(GetContentIdByTypeAndIdOffset, hos::Version_500),
};
};
}

View file

@ -0,0 +1,129 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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>
namespace ams::ncm {
class IContentStorage : public sf::IServiceObject {
protected:
enum class CommandId {
GeneratePlaceHolderId = 0,
CreatePlaceHolder = 1,
DeletePlaceHolder = 2,
HasPlaceHolder = 3,
WritePlaceHolder = 4,
Register = 5,
Delete = 6,
Has = 7,
GetPath = 8,
GetPlaceHolderPath = 9,
CleanupAllPlaceHolder = 10,
ListPlaceHolder = 11,
GetContentCount = 12,
ListContentId = 13,
GetSizeFromContentId = 14,
DisableForcibly = 15,
RevertToPlaceHolder = 16,
SetPlaceHolderSize = 17,
ReadContentIdFile = 18,
GetRightsIdFromPlaceHolderId = 19,
GetRightsIdFromContentId = 20,
WriteContentForDebug = 21,
GetFreeSpaceSize = 22,
GetTotalSpaceSize = 23,
FlushPlaceHolder = 24,
GetSizeFromPlaceHolderId = 25,
RepairInvalidFileAttribute = 26,
GetRightsIdFromPlaceHolderIdWithCache = 27,
};
protected:
char root_path[FS_MAX_PATH-1];
MakeContentPathFunc make_content_path_func;
bool disabled;
protected:
Result EnsureEnabled() {
if (this->disabled) {
return ResultInvalidContentStorage();
}
return ResultSuccess();
}
public:
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) = 0;
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) = 0;
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) = 0;
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) = 0;
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, sf::InBuffer data) = 0;
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) = 0;
virtual Result Delete(ContentId content_id) = 0;
virtual Result Has(sf::Out<bool> out, ContentId content_id) = 0;
virtual Result GetPath(sf::Out<lr::Path> out, ContentId content_id) = 0;
virtual Result GetPlaceHolderPath(sf::Out<lr::Path> out, PlaceHolderId placeholder_id) = 0;
virtual Result CleanupAllPlaceHolder() = 0;
virtual Result ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) = 0;
virtual Result GetContentCount(sf::Out<u32> out_count) = 0;
virtual Result ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 start_offset) = 0;
virtual Result GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) = 0;
virtual Result DisableForcibly() = 0;
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) = 0;
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) = 0;
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) = 0;
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id) = 0;
virtual Result GetRightsIdFromContentId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, ContentId content_id) = 0;
virtual Result WriteContentForDebug(ContentId content_id, u64 offset, sf::InBuffer data) = 0;
virtual Result GetFreeSpaceSize(sf::Out<u64> out_size) = 0;
virtual Result GetTotalSpaceSize(sf::Out<u64> out_size) = 0;
virtual Result FlushPlaceHolder() = 0;
virtual Result GetSizeFromPlaceHolderId(sf::Out<u64> out, PlaceHolderId placeholder_id) = 0;
virtual Result RepairInvalidFileAttribute() = 0;
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) = 0;
public:
DEFINE_SERVICE_DISPATCH_TABLE {
MAKE_SERVICE_COMMAND_META(GeneratePlaceHolderId),
MAKE_SERVICE_COMMAND_META(CreatePlaceHolder),
MAKE_SERVICE_COMMAND_META(DeletePlaceHolder),
MAKE_SERVICE_COMMAND_META(HasPlaceHolder),
MAKE_SERVICE_COMMAND_META(WritePlaceHolder),
MAKE_SERVICE_COMMAND_META(Register),
MAKE_SERVICE_COMMAND_META(Delete),
MAKE_SERVICE_COMMAND_META(Has),
MAKE_SERVICE_COMMAND_META(GetPath),
MAKE_SERVICE_COMMAND_META(GetPlaceHolderPath),
MAKE_SERVICE_COMMAND_META(CleanupAllPlaceHolder),
MAKE_SERVICE_COMMAND_META(ListPlaceHolder),
MAKE_SERVICE_COMMAND_META(GeneratePlaceHolderId),
MAKE_SERVICE_COMMAND_META(GetContentCount),
MAKE_SERVICE_COMMAND_META(ListContentId),
MAKE_SERVICE_COMMAND_META(GetSizeFromContentId),
MAKE_SERVICE_COMMAND_META(DisableForcibly),
MAKE_SERVICE_COMMAND_META(RevertToPlaceHolder, hos::Version_200),
MAKE_SERVICE_COMMAND_META(SetPlaceHolderSize, hos::Version_200),
MAKE_SERVICE_COMMAND_META(ReadContentIdFile, hos::Version_200),
MAKE_SERVICE_COMMAND_META(GetRightsIdFromPlaceHolderId, hos::Version_200),
MAKE_SERVICE_COMMAND_META(GetRightsIdFromContentId, hos::Version_200),
MAKE_SERVICE_COMMAND_META(WriteContentForDebug, hos::Version_200),
MAKE_SERVICE_COMMAND_META(GetFreeSpaceSize, hos::Version_200),
MAKE_SERVICE_COMMAND_META(GetTotalSpaceSize, hos::Version_200),
MAKE_SERVICE_COMMAND_META(FlushPlaceHolder, hos::Version_300),
MAKE_SERVICE_COMMAND_META(GetSizeFromPlaceHolderId, hos::Version_400),
MAKE_SERVICE_COMMAND_META(RepairInvalidFileAttribute, hos::Version_400),
MAKE_SERVICE_COMMAND_META(GetRightsIdFromPlaceHolderIdWithCache, hos::Version_800),
};
};
}

View file

@ -0,0 +1,144 @@
/*
* Copyright (c) 2019 Adubbz
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "impl/ncm_content_manager.hpp"
#include "lr_manager_service.hpp"
#include "ncm_content_manager_service.hpp"
extern "C" {
extern u32 __start__;
u32 __nx_applet_type = AppletType_None;
#define INNER_HEAP_SIZE 0x100000
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_initheap(void);
void __appInit(void);
void __appExit(void);
/* Exception handling. */
alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize];
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
void __libnx_exception_handler(ThreadExceptionDump *ctx);
}
namespace ams {
ncm::ProgramId CurrentProgramId = ncm::ProgramId::Ncm;
namespace result {
bool CallFatalOnResultAssertion = false;
}
}
using namespace ams;
void __libnx_exception_handler(ThreadExceptionDump *ctx) {
ams::CrashHandler(ctx);
}
void __libnx_initheap(void) {
void* addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
/* Newlib */
extern char* fake_heap_start;
extern char* fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void) {
hos::SetVersionForLibnx();
sm::DoWithSession([&]() {
R_ASSERT(fsInitialize());
});
ams::CheckApiVersion();
}
void __appExit(void) {
/* Cleanup services. */
fsdevUnmountAll();
fsExit();
}
namespace {
struct NcmServerOptions {
static constexpr size_t PointerBufferSize = 0x400;
static constexpr size_t MaxDomains = 0;
static constexpr size_t MaxDomainObjects = 0;
};
constexpr sm::ServiceName NcmServiceName = sm::ServiceName::Encode("ncm");
constexpr size_t NcmMaxSessions = 16;
constexpr size_t NcmNumServers = 1;
constexpr sm::ServiceName LrServiceName = sm::ServiceName::Encode("lr");
constexpr size_t LrMaxSessions = 16;
constexpr size_t LrNumServers = 1;
sf::hipc::ServerManager<NcmNumServers, NcmServerOptions, NcmMaxSessions> g_ncm_server_manager;
sf::hipc::ServerManager<LrNumServers, NcmServerOptions, LrMaxSessions> g_lr_server_manager;
}
void ContentManagerServerMain(void* arg) {
/* Create services. */
R_ASSERT(g_ncm_server_manager.RegisterServer<ncm::ContentManagerService>(NcmServiceName, NcmMaxSessions));
/* Loop forever, servicing our services. */
g_ncm_server_manager.LoopProcess();
}
void LocationResolverServerMain(void* arg) {
/* Create services. */
R_ASSERT(g_lr_server_manager.RegisterServer<lr::LocationResolverManagerService>(LrServiceName, LrMaxSessions));
/* Loop forever, servicing our services. */
g_lr_server_manager.LoopProcess();
}
int main(int argc, char **argv)
{
/* Initialize content manager implementation. */
R_ASSERT(ams::ncm::impl::InitializeContentManager());
static os::Thread s_content_manager_thread;
static os::Thread s_location_resolver_thread;
R_ASSERT(s_content_manager_thread.Initialize(&ContentManagerServerMain, nullptr, 0x4000, 0x15));
R_ASSERT(s_content_manager_thread.Start());
R_ASSERT(s_location_resolver_thread.Initialize(&LocationResolverServerMain, nullptr, 0x4000, 0x15));
R_ASSERT(s_location_resolver_thread.Start());
s_content_manager_thread.Join();
s_location_resolver_thread.Join();
ams::ncm::impl::FinalizeContentManager();
return 0;
}

View file

@ -0,0 +1,93 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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_make_path.hpp"
#include "ncm_path_utils.hpp"
namespace ams::ncm::path {
namespace {
u16 Get16BitSha256HashPrefix(util::Uuid uuid) {
u8 hash[SHA256_HASH_SIZE];
sha256CalculateHash(hash, uuid.uuid, sizeof(util::Uuid));
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.uuid, sizeof(util::Uuid));
return hash[0];
}
}
void MakeContentPathFlat(char* path_out, ContentId content_id, const char* root) {
char content_name[FS_MAX_PATH] = {0};
GetContentFileName(content_name, content_id);
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%s", root, content_name) < 0) {
std::abort();
}
}
void MakeContentPathDualLayered(char* path_out, ContentId content_id, const char* root) {
char content_name[FS_MAX_PATH] = {0};
const u16 hash = Get16BitSha256HashPrefix(content_id.uuid);
const u32 hash_lower = (hash >> 4) & 0x3f;
const u32 hash_upper = (hash >> 10) & 0x3f;
GetContentFileName(content_name, content_id);
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%08X/%s", root, hash_upper, hash_lower, content_name) < 0) {
std::abort();
}
}
void MakeContentPath10BitLayered(char* path_out, ContentId content_id, const char* root) {
char content_name[FS_MAX_PATH] = {0};
const u32 hash = (Get16BitSha256HashPrefix(content_id.uuid) >> 6) & 0x3FF;
GetContentFileName(content_name, content_id);
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash, content_name) < 0) {
std::abort();
}
}
void MakeContentPathHashByteLayered(char* path_out, ContentId content_id, const char* root) {
char content_name[FS_MAX_PATH] = {0};
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(content_id.uuid));
GetContentFileName(content_name, content_id);
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash_byte, content_name) < 0) {
std::abort();
}
}
void MakePlaceHolderPathFlat(char* path_out, PlaceHolderId placeholder_id, const char* root) {
char placeholder_name[FS_MAX_PATH] = {0};
GetPlaceHolderFileName(placeholder_name, placeholder_id);
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%s", root, placeholder_name) < 0) {
std::abort();
}
}
void MakePlaceHolderPathHashByteLayered(char* path_out, PlaceHolderId placeholder_id, const char* root) {
char placeholder_name[FS_MAX_PATH] = {0};
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(placeholder_id.uuid));
GetPlaceHolderFileName(placeholder_name, placeholder_id);
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash_byte, placeholder_name) < 0) {
std::abort();
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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>
namespace ams::ncm::path {
void MakeContentPathFlat(char* out_path, ContentId content_id, const char* root);
void MakeContentPathHashByteLayered(char* out_path, ContentId content_id, const char* root);
void MakeContentPath10BitLayered(char* out_path, ContentId content_id, const char* root);
void MakeContentPathDualLayered(char* out_path, ContentId content_id, const char* root);
void MakePlaceHolderPathFlat(char* out_path, PlaceHolderId placeholder_id, const char* root);
void MakePlaceHolderPathHashByteLayered(char* out_path, PlaceHolderId placeholder_id, const char* root);
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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(char* out, ContentId content_id, MakeContentPathFunc path_func, const char* root_path) {
char tmp_path[FS_MAX_PATH-1] = {0};
char content_path[FS_MAX_PATH-1] = {0};
path_func(content_path, content_id, root_path);
const size_t len = strnlen(content_path, FS_MAX_PATH-1);
const size_t len_no_extension = len - 4;
if (len_no_extension > len || len_no_extension >= FS_MAX_PATH-1) {
std::abort();
}
strncpy(tmp_path, content_path, len_no_extension);
memcpy(out, tmp_path, FS_MAX_PATH-1);
const size_t out_len = strnlen(out, FS_MAX_PATH-1);
if (out_len + 9 >= FS_MAX_PATH-1) {
std::abort();
}
strncat(out, ".cnmt.nca", 0x2ff - out_len);
}
void GetContentFileName(char* out, ContentId content_id) {
char content_name[sizeof(ContentId)*2+1] = {0};
GetStringFromContentId(content_name, content_id);
snprintf(out, FS_MAX_PATH-1, "%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, FS_MAX_PATH-1, "%s%s", placeholder_name, ".nca");
}
bool IsNcaPath(const char* path) {
PathView path_view(path);
if (!path_view.HasSuffix(".nca")) {
return false;
}
std::string_view file_name = path_view.GetFileName();
if (file_name.length() != 0x24) {
return false;
}
for (size_t i = 0; i < sizeof(util::Uuid)*2; i++) {
if (!std::isxdigit(file_name.at(i))) {
return false;
}
}
return true;
}
bool PathView::HasPrefix(std::string_view prefix) const {
return this->path.compare(0, prefix.length(), prefix) == 0;
}
bool PathView::HasSuffix(std::string_view suffix) const {
return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0;
}
std::string_view PathView::GetFileName() const {
return this->path.substr(this->path.find_last_of("/") + 1);
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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>
namespace ams::ncm::path {
inline void GetContentRootPath(char* out_content_root, const char* root_path) {
/* TODO: Replace with BoundedString? */
if (snprintf(out_content_root, FS_MAX_PATH-1, "%s%s", root_path, "/registered") < 0) {
std::abort();
}
}
inline void GetPlaceHolderRootPath(char* out_placeholder_root, const char* root_path) {
/* TODO: Replace with BoundedString? */
if (snprintf(out_placeholder_root, FS_MAX_PATH, "%s%s", root_path, "/placehld") < 0) {
std::abort();
}
}
void GetContentMetaPath(char* out, ContentId content_id, MakeContentPathFunc path_func, const char* root_path);
void GetContentFileName(char* out, ContentId content_id);
void GetPlaceHolderFileName(char* out, PlaceHolderId placeholder_id);
bool IsNcaPath(const char* path);
class PathView {
private:
std::string_view path; /* Nintendo uses nn::util::string_view here. */
public:
PathView(std::string_view p) : path(p) { /* ...*/ }
bool HasPrefix(std::string_view prefix) const;
bool HasSuffix(std::string_view suffix) const;
std::string_view GetFileName() const;
};
}

View file

@ -0,0 +1,248 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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.hpp"
#include "ncm_path_utils.hpp"
#include "ncm_readonlycontentstorage.hpp"
namespace ams::ncm {
Result ReadOnlyContentStorageInterface::Initialize(const char* root_path, MakeContentPathFunc content_path_func) {
R_TRY(this->EnsureEnabled());
const size_t root_path_len = strnlen(root_path, FS_MAX_PATH-1);
if (root_path_len >= FS_MAX_PATH-1) {
std::abort();
}
strncpy(this->root_path, root_path, FS_MAX_PATH-2);
this->make_content_path_func = *content_path_func;
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::DeletePlaceHolder(PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, sf::InBuffer data) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::Register(PlaceHolderId placeholder_id, ContentId content_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::Delete(ContentId content_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::Has(sf::Out<bool> out, ContentId content_id) {
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
this->make_content_path_func(content_path, content_id, this->root_path);
bool has = false;
R_TRY(fs::HasFile(&has, content_path));
if (!has) {
path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path);
R_TRY(fs::HasFile(&has, content_path));
}
out.SetValue(has);
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::GetPath(sf::Out<lr::Path> out, ContentId content_id) {
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
char common_path[FS_MAX_PATH] = {0};
bool is_content_meta_file = false;
path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path);
R_TRY(fs::HasFile(&is_content_meta_file, content_path));
if (!is_content_meta_file) {
this->make_content_path_func(content_path, content_id, this->root_path);
}
R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
out.SetValue(lr::Path::Encode(common_path));
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::GetPlaceHolderPath(sf::Out<lr::Path> out, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::CleanupAllPlaceHolder() {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::GetContentCount(sf::Out<u32> out_count) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 start_offset) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) {
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
bool is_content_file = false;
this->make_content_path_func(content_path, content_id, this->root_path);
R_TRY(fs::HasFile(&is_content_file, content_path));
if (!is_content_file) {
path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path);
}
struct stat st;
if (stat(content_path, &st) == -1) {
return fsdevGetLastResult();
}
out_size.SetValue(st.st_size);
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::DisableForcibly() {
this->disabled = true;
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) {
/* Offset is too large */
if (offset >> 0x3f != 0) {
return ResultInvalidOffset();
}
R_TRY(this->EnsureEnabled());
char content_path[FS_MAX_PATH] = {0};
bool is_content_file = false;
this->make_content_path_func(content_path, content_id, this->root_path);
R_TRY(fs::HasFile(&is_content_file, content_path));
if (!is_content_file) {
path::GetContentMetaPath(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 ReadOnlyContentStorageInterface::GetRightsIdFromPlaceHolderId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::GetRightsIdFromContentId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, ContentId content_id) {
R_TRY(this->EnsureEnabled());
FsRightsId rights_id = {0};
u8 key_generation = 0;
char content_path[FS_MAX_PATH] = {0};
char common_path[FS_MAX_PATH] = {0};
bool is_content_meta_file = false;
path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path);
R_TRY(fs::HasFile(&is_content_meta_file, content_path));
if (!is_content_meta_file) {
this->make_content_path_func(content_path, content_id, this->root_path);
}
R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
out_rights_id.SetValue(rights_id);
out_key_generation.SetValue(static_cast<u64>(key_generation));
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::WriteContentForDebug(ContentId content_id, u64 offset, sf::InBuffer data) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::GetFreeSpaceSize(sf::Out<u64> out_size) {
out_size.SetValue(0);
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::GetTotalSpaceSize(sf::Out<u64> out_size) {
out_size.SetValue(0);
return ResultSuccess();
}
Result ReadOnlyContentStorageInterface::FlushPlaceHolder() {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::GetSizeFromPlaceHolderId(sf::Out<u64> out, PlaceHolderId placeholder_id) {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::RepairInvalidFileAttribute() {
return ResultInvalidContentStorageOperation();
}
Result ReadOnlyContentStorageInterface::GetRightsIdFromPlaceHolderIdWithCache(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) {
return ResultInvalidContentStorageOperation();
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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_icontentstorage.hpp"
namespace ams::ncm {
class ReadOnlyContentStorageInterface : public IContentStorage {
public:
Result Initialize(const char* root_path, MakeContentPathFunc content_path_func);
public:
virtual Result GeneratePlaceHolderId(sf::Out<PlaceHolderId> out) override;
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
virtual Result HasPlaceHolder(sf::Out<bool> out, PlaceHolderId placeholder_id) override;
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, sf::InBuffer data) override;
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
virtual Result Delete(ContentId content_id) override;
virtual Result Has(sf::Out<bool> out, ContentId content_id) override;
virtual Result GetPath(sf::Out<lr::Path> out, ContentId content_id) override;
virtual Result GetPlaceHolderPath(sf::Out<lr::Path> out, PlaceHolderId placeholder_id) override;
virtual Result CleanupAllPlaceHolder() override;
virtual Result ListPlaceHolder(sf::Out<u32> out_count, const sf::OutArray<PlaceHolderId> &out_buf) override;
virtual Result GetContentCount(sf::Out<u32> out_count) override;
virtual Result ListContentId(sf::Out<u32> out_count, const sf::OutArray<ContentId> &out_buf, u32 start_offset) override;
virtual Result GetSizeFromContentId(sf::Out<u64> out_size, ContentId content_id) override;
virtual Result DisableForcibly() override;
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) override;
virtual Result ReadContentIdFile(sf::OutBuffer buf, ContentId content_id, u64 offset) override;
virtual Result GetRightsIdFromPlaceHolderId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id) override;
virtual Result GetRightsIdFromContentId(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, ContentId content_id) override;
virtual Result WriteContentForDebug(ContentId content_id, u64 offset, sf::InBuffer data) override;
virtual Result GetFreeSpaceSize(sf::Out<u64> out_size) override;
virtual Result GetTotalSpaceSize(sf::Out<u64> out_size) override;
virtual Result FlushPlaceHolder() override;
virtual Result GetSizeFromPlaceHolderId(sf::Out<u64> out, PlaceHolderId placeholder_id) override;
virtual Result RepairInvalidFileAttribute() override;
virtual Result GetRightsIdFromPlaceHolderIdWithCache(sf::Out<FsRightsId> out_rights_id, sf::Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) override;
};
}

View file

@ -0,0 +1,81 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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) {
if (strnlen(dir_entry->d_name, 0x30) != 0x24 || strncmp(dir_entry->d_name + 0x20, ".nca", 4) != 0) {
return ResultInvalidPlaceHolderDirectoryEntry();
}
PlaceHolderId placeholder_id = {0};
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);
placeholder_id.uuid[i] = (u8)converted_val;
}
*out = placeholder_id;
return ResultSuccess();
}
std::optional<ContentId> GetContentIdFromString(const char* str, size_t len) {
ContentId content_id = {0};
if (len < 0x20) {
return std::nullopt;
}
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);
content_id.uuid[i] = (u8)converted_val;
}
return std::optional<ContentId>(content_id);
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2019 Adubbz
*
* 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 {
void GetStringFromContentId(char* out, ContentId content_id);
void GetStringFromPlaceHolderId(char* out, PlaceHolderId placeholder_id);
Result GetPlaceHolderIdFromDirEntry(PlaceHolderId* out, struct dirent* dir_entry);
std::optional<ContentId> GetContentIdFromString(const char* str, size_t len);
};