diff --git a/libraries/libstratosphere/include/stratosphere.hpp b/libraries/libstratosphere/include/stratosphere.hpp
index aae1f69cd..724e6277b 100644
--- a/libraries/libstratosphere/include/stratosphere.hpp
+++ b/libraries/libstratosphere/include/stratosphere.hpp
@@ -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"
diff --git a/libraries/libstratosphere/include/stratosphere/lr.hpp b/libraries/libstratosphere/include/stratosphere/lr.hpp
new file mode 100644
index 000000000..51ea02934
--- /dev/null
+++ b/libraries/libstratosphere/include/stratosphere/lr.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+
+#include "lr/lr_types.hpp"
diff --git a/libraries/libstratosphere/include/stratosphere/lr/lr_types.hpp b/libraries/libstratosphere/include/stratosphere/lr/lr_types.hpp
new file mode 100644
index 000000000..3038ebff8
--- /dev/null
+++ b/libraries/libstratosphere/include/stratosphere/lr/lr_types.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#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::value && sizeof(Path) == fs::EntryNameLengthMax);
+
+}
diff --git a/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp b/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp
index 5f8af79b5..0c4348a7d 100644
--- a/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp
+++ b/libraries/libstratosphere/include/stratosphere/ncm/ncm_types.hpp
@@ -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::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 title_id, u32 version, ContentMetaType type) {
+ return { .id = title_id, .version = version, .type = type };
+ }
+
+ static constexpr ContentMetaKey Make(ProgramId title_id, u32 version, ContentMetaType type, ContentInstallType install_type) {
+ return { .id = title_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_title_id;
+ };
+
+ static_assert(sizeof(ApplicationContentMetaKey) == 0x18, "ApplicationContentMetaKey definition!");
+
}
diff --git a/libraries/libvapours/include/vapours/results/lr_results.hpp b/libraries/libvapours/include/vapours/results/lr_results.hpp
index c1983ee07..38afec514 100644
--- a/libraries/libvapours/include/vapours/results/lr_results.hpp
+++ b/libraries/libvapours/include/vapours/results/lr_results.hpp
@@ -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);
diff --git a/libraries/libvapours/include/vapours/results/ncm_results.hpp b/libraries/libvapours/include/vapours/results/ncm_results.hpp
index cdba12c7c..2216a9a3a 100644
--- a/libraries/libvapours/include/vapours/results/ncm_results.hpp
+++ b/libraries/libvapours/include/vapours/results/ncm_results.hpp
@@ -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);
diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp
index 28cea78c4..c9116be02 100644
--- a/libraries/libvapours/include/vapours/util.hpp
+++ b/libraries/libvapours/include/vapours/util.hpp
@@ -23,3 +23,4 @@
#include "util/util_typed_storage.hpp"
#include "util/util_intrusive_list.hpp"
#include "util/util_intrusive_red_black_tree.hpp"
+#include "util/util_uuid.hpp"
diff --git a/libraries/libvapours/include/vapours/util/util_uuid.hpp b/libraries/libvapours/include/vapours/util/util_uuid.hpp
new file mode 100644
index 000000000..60bb724c5
--- /dev/null
+++ b/libraries/libvapours/include/vapours/util/util_uuid.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+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 } };
+
+}
\ No newline at end of file
diff --git a/stratosphere/Makefile b/stratosphere/Makefile
index 645bc1b7f..d70f63a3a 100644
--- a/stratosphere/Makefile
+++ b/stratosphere/Makefile
@@ -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)
diff --git a/stratosphere/ncm/Makefile b/stratosphere/ncm/Makefile
new file mode 100644
index 000000000..a92187ef8
--- /dev/null
+++ b/stratosphere/ncm/Makefile
@@ -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
+#---------------------------------------------------------------------------------------
diff --git a/stratosphere/ncm/ncm.json b/stratosphere/ncm/ncm.json
new file mode 100644
index 000000000..5a3ac657b
--- /dev/null
+++ b/stratosphere/ncm/ncm.json
@@ -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"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/stratosphere/ncm/source/impl/lr_manager.cpp b/stratosphere/ncm/source/impl/lr_manager.cpp
new file mode 100644
index 000000000..7b293dd37
--- /dev/null
+++ b/stratosphere/ncm/source/impl/lr_manager.cpp
@@ -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 .
+ */
+
+#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, 5> g_location_resolvers;
+ std::shared_ptr g_registered_location_resolver = nullptr;
+ std::shared_ptr g_add_on_content_location_resolver = nullptr;
+ os::Mutex g_mutex;
+
+ }
+
+ Result OpenLocationResolver(sf::Out> 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();
+ } else {
+ auto content_resolver = std::make_shared(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 new_intf = *resolver;
+ out.SetValue(std::move(new_intf));
+ return ResultSuccess();
+ }
+
+ Result OpenRegisteredLocationResolver(sf::Out> out) {
+ std::scoped_lock lk(g_mutex);
+
+ if (!g_registered_location_resolver) {
+ g_registered_location_resolver = std::make_shared();
+ }
+
+ std::shared_ptr 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> out) {
+ std::scoped_lock lk(g_mutex);
+
+ if (!g_add_on_content_location_resolver) {
+ g_add_on_content_location_resolver = std::make_shared();
+ }
+
+ std::shared_ptr new_intf = g_add_on_content_location_resolver;
+ out.SetValue(std::move(new_intf));
+ return ResultSuccess();
+ }
+
+}
diff --git a/stratosphere/ncm/source/impl/lr_manager.hpp b/stratosphere/ncm/source/impl/lr_manager.hpp
new file mode 100644
index 000000000..0032642c0
--- /dev/null
+++ b/stratosphere/ncm/source/impl/lr_manager.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#include "../lr_addoncontentlocationresolver.hpp"
+#include "../lr_ilocationresolver.hpp"
+#include "../lr_registeredlocationresolver.hpp"
+
+namespace ams::lr::impl {
+
+ /* Location Resolver API. */
+ Result OpenLocationResolver(sf::Out> out, ncm::StorageId storage_id);
+ Result OpenRegisteredLocationResolver(sf::Out> out);
+ Result RefreshLocationResolver(ncm::StorageId storage_id);
+ Result OpenAddOnContentLocationResolver(sf::Out> out);
+
+}
diff --git a/stratosphere/ncm/source/impl/lr_redirection.cpp b/stratosphere/ncm/source/impl/lr_redirection.cpp
new file mode 100644
index 000000000..d39a88bf9
--- /dev/null
+++ b/stratosphere/ncm/source/impl/lr_redirection.cpp
@@ -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 .
+ */
+
+#include "lr_redirection.hpp"
+
+namespace ams::lr::impl {
+
+ class LocationRedirection : public util::IntrusiveListBaseNode {
+ NON_COPYABLE(LocationRedirection);
+ NON_MOVEABLE(LocationRedirection);
+ private:
+ ncm::ProgramId title_id;
+ ncm::ProgramId owner_tid;
+ Path path;
+ u32 flags;
+ public:
+ LocationRedirection(ncm::ProgramId title_id, ncm::ProgramId owner_tid, const Path& path, u32 flags) :
+ title_id(title_id), owner_tid(owner_tid), path(path), flags(flags) { /* ... */ }
+
+ ncm::ProgramId GetTitleId() const {
+ return this->title_id;
+ }
+
+ ncm::ProgramId GetOwnerTitleId() const {
+ return this->owner_tid;
+ }
+
+ 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 title_id) {
+ if (this->redirection_list.empty()) {
+ return false;
+ }
+
+ for (const auto &redirection : this->redirection_list) {
+ if (redirection.GetTitleId() == title_id) {
+ redirection.GetPath(out);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void LocationRedirector::SetRedirection(ncm::ProgramId title_id, const Path &path, u32 flags) {
+ this->SetRedirection(title_id, path, flags);
+ }
+
+ void LocationRedirector::SetRedirection(ncm::ProgramId title_id, ncm::ProgramId owner_tid, const Path &path, u32 flags) {
+ this->EraseRedirection(title_id);
+ this->redirection_list.push_back(*(new LocationRedirection(title_id, owner_tid, path, flags)));
+ }
+
+ void LocationRedirector::SetRedirectionFlags(ncm::ProgramId title_id, u32 flags) {
+ for (auto &redirection : this->redirection_list) {
+ if (redirection.GetTitleId() == title_id) {
+ redirection.SetFlags(flags);
+ break;
+ }
+ }
+ }
+
+ void LocationRedirector::EraseRedirection(ncm::ProgramId title_id) {
+ for (auto &redirection : this->redirection_list) {
+ if (redirection.GetTitleId() == title_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_tids, size_t num_tids) {
+ for (auto it = this->redirection_list.begin(); it != this->redirection_list.end();) {
+ bool skip = false;
+ for (size_t i = 0; i < num_tids; i++) {
+ ncm::ProgramId tid = excluding_tids[i];
+
+ if (it->GetOwnerTitleId() == tid) {
+ skip = true;
+ break;
+ }
+ }
+
+ if (skip) {
+ continue;
+ }
+
+ auto old = it;
+ it = this->redirection_list.erase(it);
+ delete &(*old);
+ it++;
+ }
+ }
+
+}
diff --git a/stratosphere/ncm/source/impl/lr_redirection.hpp b/stratosphere/ncm/source/impl/lr_redirection.hpp
new file mode 100644
index 000000000..1085a3660
--- /dev/null
+++ b/stratosphere/ncm/source/impl/lr_redirection.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+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::ListType redirection_list;
+ public:
+ LocationRedirector() { /* ... */ }
+
+ bool FindRedirection(Path *out, ncm::ProgramId title_id);
+ void SetRedirection(ncm::ProgramId title_id, const Path &path, u32 flags = RedirectionFlags_None);
+ void SetRedirection(ncm::ProgramId title_id, ncm::ProgramId owner_tid, const Path &path, u32 flags = RedirectionFlags_None);
+ void SetRedirectionFlags(ncm::ProgramId title_id, u32 flags);
+ void EraseRedirection(ncm::ProgramId title_id);
+ void ClearRedirections(u32 flags = RedirectionFlags_None);
+ void ClearRedirections(const ncm::ProgramId* excluding_tids, size_t num_tids);
+ };
+
+}
diff --git a/stratosphere/ncm/source/impl/lr_registered_data.hpp b/stratosphere/ncm/source/impl/lr_registered_data.hpp
new file mode 100644
index 000000000..744d699d4
--- /dev/null
+++ b/stratosphere/ncm/source/impl/lr_registered_data.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+namespace ams::lr::impl {
+
+ template
+ class RegisteredData {
+ NON_COPYABLE(RegisteredData);
+ NON_MOVEABLE(RegisteredData);
+ private:
+ struct Entry {
+ Value value;
+ ncm::ProgramId owner_tid;
+ 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_tid) {
+ /* 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_tid = owner_tid;
+ 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 UnregisterOwnerTitle(ncm::ProgramId owner_tid) {
+ for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
+ Entry& entry = this->entries[i];
+ if (entry.owner_tid == owner_tid) {
+ 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* tids, size_t num_tids) {
+ for (size_t i = 0; i < this->GetSoftEntryLimit(); i++) {
+ Entry& entry = this->entries[i];
+ bool found = false;
+
+ for (size_t j = 0; j < num_tids; j++) {
+ ncm::ProgramId tid = tids[j];
+
+ if (entry.owner_tid == tid) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ entry.is_valid = false;
+ }
+ }
+ }
+
+ size_t GetSoftEntryLimit() const {
+ return this->soft_entry_limit;
+ }
+ };
+
+ template
+ using RegisteredLocations = RegisteredData;
+
+ template
+ using RegisteredStorages = RegisteredData;
+
+}
diff --git a/stratosphere/ncm/source/impl/ncm_bounded_map.hpp b/stratosphere/ncm/source/impl/ncm_bounded_map.hpp
new file mode 100644
index 000000000..39daa41ae
--- /dev/null
+++ b/stratosphere/ncm/source/impl/ncm_bounded_map.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+namespace ams::ncm::impl {
+
+ template
+ class BoundedMap {
+ private:
+ std::array, N> keys;
+ std::array 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();
+ }
+ };
+
+}
diff --git a/stratosphere/ncm/source/impl/ncm_content_manager.cpp b/stratosphere/ncm/source/impl/ncm_content_manager.cpp
new file mode 100644
index 000000000..46c2ef0ba
--- /dev/null
+++ b/stratosphere/ncm/source/impl/ncm_content_manager.cpp
@@ -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 .
+ */
+
+#include
+#include
+#include
+#include
+
+#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 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 content_meta_database;
+ std::optional> 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 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(¤t_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 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 lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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 lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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* out, StorageId storage_id) {
+ std::scoped_lock lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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 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 lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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();
+
+ 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();
+
+ 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 lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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 lk(g_mutex);
+
+ if (storage_id == StorageId::None || storage_id == StorageId::GameCard || static_cast(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 lk(g_mutex);
+
+ if (storage_id == StorageId::GameCard) {
+ return ResultSuccess();
+ }
+
+ if (storage_id == StorageId::None || static_cast(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* out, StorageId storage_id) {
+ std::scoped_lock lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(storage_id) == 6) {
+ return ResultUnknownStorage();
+ }
+
+ ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
+
+ if (!entry) {
+ return ResultUnknownStorage();
+ }
+
+ std::shared_ptr 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 lk(g_mutex);
+
+ if (storage_id == StorageId::None) {
+ return ResultUnknownStorage();
+ }
+
+ ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
+
+ if (!entry) {
+ return ResultUnknownStorage();
+ }
+
+ std::shared_ptr 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 lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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 lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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(&*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(&*entry->kvs);
+ entry->content_meta_database = std::move(content_meta_database);
+ }
+
+ return ResultSuccess();
+ }
+
+ Result InactivateContentMetaDatabase(StorageId storage_id) {
+ std::scoped_lock lk(g_mutex);
+
+ if (storage_id == StorageId::None || static_cast(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();
+ }
+
+}
diff --git a/stratosphere/ncm/source/impl/ncm_content_manager.hpp b/stratosphere/ncm/source/impl/ncm_content_manager.hpp
new file mode 100644
index 000000000..1ab9298f1
--- /dev/null
+++ b/stratosphere/ncm/source/impl/ncm_content_manager.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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* 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* 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();
+
+}
diff --git a/stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp b/stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp
new file mode 100644
index 000000000..7b3ee44be
--- /dev/null
+++ b/stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp
@@ -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 .
+ */
+
+#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(path::MakePlaceHolderPathFlat)) {
+ return 1;
+ } else if (this->make_placeholder_path_func == static_cast(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);
+ }
+ }
+ }
+
+}
diff --git a/stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp b/stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp
new file mode 100644
index 000000000..763398d05
--- /dev/null
+++ b/stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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 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();
+ };
+
+}
diff --git a/stratosphere/ncm/source/impl/ncm_rights_cache.hpp b/stratosphere/ncm/source/impl/ncm_rights_cache.hpp
new file mode 100644
index 000000000..a1715d04d
--- /dev/null
+++ b/stratosphere/ncm/source/impl/ncm_rights_cache.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+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;
+ }
+ }
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_addoncontentlocationresolver.cpp b/stratosphere/ncm/source/lr_addoncontentlocationresolver.cpp
new file mode 100644
index 000000000..7094dc920
--- /dev/null
+++ b/stratosphere/ncm/source/lr_addoncontentlocationresolver.cpp
@@ -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 .
+ */
+
+#include "impl/ncm_content_manager.hpp"
+#include "lr_addoncontentlocationresolver.hpp"
+
+namespace ams::lr {
+
+ Result AddOnContentLocationResolverInterface::ResolveAddOnContentPath(sf::Out out, ncm::ProgramId tid) {
+ ncm::StorageId storage_id = ncm::StorageId::None;
+
+ if (!this->registered_storages.Find(&storage_id, tid)) {
+ return ResultAddOnContentNotFound();
+ }
+
+ std::shared_ptr 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, tid));
+
+ std::shared_ptr 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 tid) {
+ if (!this->registered_storages.Register(tid, storage_id, ncm::ProgramId::Invalid)) {
+ return ResultTooManyRegisteredPaths();
+ }
+
+ return ResultSuccess();
+ }
+
+ Result AddOnContentLocationResolverInterface::RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::ProgramId tid, ncm::ProgramId application_tid) {
+ if (!this->registered_storages.Register(tid, storage_id, application_tid)) {
+ return ResultTooManyRegisteredPaths();
+ }
+
+ return ResultSuccess();
+ }
+
+ Result AddOnContentLocationResolverInterface::UnregisterAllAddOnContentPath() {
+ this->registered_storages.Clear();
+ return ResultSuccess();
+ }
+
+ Result AddOnContentLocationResolverInterface::RefreshApplicationAddOnContent(const sf::InArray &tids) {
+ if (tids.GetSize() == 0) {
+ this->registered_storages.Clear();
+ return ResultSuccess();
+ }
+
+ this->registered_storages.ClearExcluding(tids.GetPointer(), tids.GetSize());
+ return ResultSuccess();
+ }
+
+ Result AddOnContentLocationResolverInterface::UnregisterApplicationAddOnContent(ncm::ProgramId tid) {
+ this->registered_storages.UnregisterOwnerTitle(tid);
+ return ResultSuccess();
+ }
+
+}
diff --git a/stratosphere/ncm/source/lr_addoncontentlocationresolver.hpp b/stratosphere/ncm/source/lr_addoncontentlocationresolver.hpp
new file mode 100644
index 000000000..111ee4dea
--- /dev/null
+++ b/stratosphere/ncm/source/lr_addoncontentlocationresolver.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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 registered_storages;
+ public:
+ AddOnContentLocationResolverInterface() : registered_storages(hos::GetVersion() < hos::Version_900 ? 0x800 : 0x2) { /* ... */ }
+
+ virtual Result ResolveAddOnContentPath(sf::Out out, ncm::ProgramId tid);
+ virtual Result RegisterAddOnContentStorageDeprecated(ncm::StorageId storage_id, ncm::ProgramId tid);
+ virtual Result RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::ProgramId tid, ncm::ProgramId application_tid);
+ virtual Result UnregisterAllAddOnContentPath();
+ virtual Result RefreshApplicationAddOnContent(const sf::InArray &tids);
+ virtual Result UnregisterApplicationAddOnContent(ncm::ProgramId tid);
+ 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),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_contentlocationresolver.cpp b/stratosphere/ncm/source/lr_contentlocationresolver.cpp
new file mode 100644
index 000000000..f4efd4521
--- /dev/null
+++ b/stratosphere/ncm/source/lr_contentlocationresolver.cpp
@@ -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 .
+ */
+
+#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 out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->program_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ ncm::ContentId program_content_id;
+
+ R_TRY_CATCH(this->content_meta_database->GetLatestProgram(&program_content_id, tid)) {
+ 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 tid) {
+ this->program_redirector.SetRedirection(tid, path);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::ResolveApplicationControlPath(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->app_control_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ return ResultControlNotFound();
+ }
+
+ Result ContentLocationResolverInterface::ResolveApplicationHtmlDocumentPath(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->html_docs_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ return ResultHtmlDocumentNotFound();
+ }
+
+ Result ContentLocationResolverInterface::ResolveDataPath(sf::Out out, ncm::ProgramId tid) {
+ ncm::ContentId data_content_id;
+
+ R_TRY(this->content_meta_database->GetLatestData(&data_content_id, tid));
+ this->GetContentStoragePath(out.GetPointer(), data_content_id);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->app_control_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationControlPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->app_control_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->html_docs_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->html_docs_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::ResolveApplicationLegalInformationPath(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->legal_info_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ return ResultLegalInformationNotFound();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->legal_info_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->legal_info_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::Refresh() {
+ std::shared_ptr content_meta_database;
+ std::shared_ptr 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 tid) {
+ this->program_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->program_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::ClearApplicationRedirectionDeprecated() {
+ this->ClearRedirections(impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::ClearApplicationRedirection(const sf::InArray &excluding_tids) {
+ this->ClearRedirections(excluding_tids.GetPointer(), excluding_tids.GetSize());
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::EraseProgramRedirection(ncm::ProgramId tid) {
+ this->program_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::EraseApplicationControlRedirection(ncm::ProgramId tid) {
+ this->app_control_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId tid) {
+ this->html_docs_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::EraseApplicationLegalInformationRedirection(ncm::ProgramId tid) {
+ this->legal_info_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::ResolveProgramPathForDebug(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->debug_program_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ R_TRY_CATCH(this->ResolveProgramPath(out.GetPointer(), tid)) {
+ R_CATCH(ResultProgramNotFound) {
+ return ResultDebugProgramNotFound();
+ }
+ } R_END_TRY_CATCH;
+
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId tid) {
+ this->debug_program_redirector.SetRedirection(tid, path);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->debug_program_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->debug_program_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result ContentLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::ProgramId tid) {
+ this->debug_program_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+}
diff --git a/stratosphere/ncm/source/lr_contentlocationresolver.hpp b/stratosphere/ncm/source/lr_contentlocationresolver.hpp
new file mode 100644
index 000000000..32dc16078
--- /dev/null
+++ b/stratosphere/ncm/source/lr_contentlocationresolver.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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 content_meta_database;
+ std::shared_ptr 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 out, ncm::ProgramId tid) override;
+ virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId tid) override;
+ virtual Result ResolveApplicationControlPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result ResolveApplicationHtmlDocumentPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result ResolveDataPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result ResolveApplicationLegalInformationPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result Refresh() override;
+ virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result ClearApplicationRedirectionDeprecated() override;
+ virtual Result ClearApplicationRedirection(const sf::InArray &excluding_tids) override;
+ virtual Result EraseProgramRedirection(ncm::ProgramId tid) override;
+ virtual Result EraseApplicationControlRedirection(ncm::ProgramId tid) override;
+ virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId tid) override;
+ virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId tid) override;
+ virtual Result ResolveProgramPathForDebug(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId tid) 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),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_ilocationresolver.hpp b/stratosphere/ncm/source/lr_ilocationresolver.hpp
new file mode 100644
index 000000000..06594d685
--- /dev/null
+++ b/stratosphere/ncm/source/lr_ilocationresolver.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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 tid) {
+ return redirector->FindRedirection(out, tid);
+ }
+
+ 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_tids, size_t num_tids) {
+ this->program_redirector.ClearRedirections(excluding_tids, num_tids);
+ this->debug_program_redirector.ClearRedirections(excluding_tids, num_tids);
+ this->app_control_redirector.ClearRedirections(excluding_tids, num_tids);
+ this->html_docs_redirector.ClearRedirections(excluding_tids, num_tids);
+ this->legal_info_redirector.ClearRedirections(excluding_tids, num_tids);
+ }
+ public:
+ virtual Result ResolveProgramPath(sf::Out out, ncm::ProgramId tid);
+ virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId tid);
+ virtual Result ResolveApplicationControlPath(sf::Out out, ncm::ProgramId tid);
+ virtual Result ResolveApplicationHtmlDocumentPath(sf::Out out, ncm::ProgramId tid);
+ virtual Result ResolveDataPath(sf::Out out, ncm::ProgramId tid);
+ virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId tid);
+ virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid);
+ virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ virtual Result ResolveApplicationLegalInformationPath(sf::Out out, ncm::ProgramId tid);
+ virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId tid);
+ virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ virtual Result Refresh();
+ virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId tid);
+ virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ virtual Result ClearApplicationRedirectionDeprecated();
+ virtual Result ClearApplicationRedirection(const sf::InArray &excluding_tids);
+ virtual Result EraseProgramRedirection(ncm::ProgramId tid);
+ virtual Result EraseApplicationControlRedirection(ncm::ProgramId tid);
+ virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId tid);
+ virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId tid);
+ virtual Result ResolveProgramPathForDebug(sf::Out out, ncm::ProgramId tid);
+ virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId tid);
+ virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId tid);
+ virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId tid);
+ 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),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_manager_service.cpp b/stratosphere/ncm/source/lr_manager_service.cpp
new file mode 100644
index 000000000..47b3d50a3
--- /dev/null
+++ b/stratosphere/ncm/source/lr_manager_service.cpp
@@ -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 .
+ */
+#include
+
+#include "impl/lr_manager.hpp"
+#include "lr_manager_service.hpp"
+
+namespace ams::lr {
+
+ Result LocationResolverManagerService::OpenLocationResolver(sf::Out> out, ncm::StorageId storage_id) {
+ return impl::OpenLocationResolver(out, storage_id);
+ }
+
+ Result LocationResolverManagerService::OpenRegisteredLocationResolver(sf::Out> out) {
+ return impl::OpenRegisteredLocationResolver(out);
+ }
+
+ Result LocationResolverManagerService::RefreshLocationResolver(ncm::StorageId storage_id) {
+ return impl::RefreshLocationResolver(storage_id);
+ }
+
+ Result LocationResolverManagerService::OpenAddOnContentLocationResolver(sf::Out> out) {
+ return impl::OpenAddOnContentLocationResolver(out);
+ }
+
+}
diff --git a/stratosphere/ncm/source/lr_manager_service.hpp b/stratosphere/ncm/source/lr_manager_service.hpp
new file mode 100644
index 000000000..bdab705af
--- /dev/null
+++ b/stratosphere/ncm/source/lr_manager_service.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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> out, ncm::StorageId storage_id);
+ virtual Result OpenRegisteredLocationResolver(sf::Out> out);
+ virtual Result RefreshLocationResolver(ncm::StorageId storage_id);
+ virtual Result OpenAddOnContentLocationResolver(sf::Out> 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),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_redirectonlylocationresolver.cpp b/stratosphere/ncm/source/lr_redirectonlylocationresolver.cpp
new file mode 100644
index 000000000..d2a6e0b07
--- /dev/null
+++ b/stratosphere/ncm/source/lr_redirectonlylocationresolver.cpp
@@ -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 .
+ */
+
+#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 out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->program_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ return ResultProgramNotFound();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectProgramPath(const Path &path, ncm::ProgramId tid) {
+ this->program_redirector.SetRedirection(tid, path);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveApplicationControlPath(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->app_control_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ return ResultControlNotFound();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveApplicationHtmlDocumentPath(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->html_docs_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ return ResultHtmlDocumentNotFound();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveDataPath(sf::Out out, ncm::ProgramId tid) {
+ return ResultDataNotFound();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->app_control_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationControlPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->app_control_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->html_docs_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->html_docs_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveApplicationLegalInformationPath(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->legal_info_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ return ResultLegalInformationNotFound();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->legal_info_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->legal_info_redirector.SetRedirection(tid, owner_tid, 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 tid) {
+ this->program_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->program_redirector.SetRedirection(tid, owner_tid, 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 &excluding_tids) {
+ this->ClearRedirections(excluding_tids.GetPointer(), excluding_tids.GetSize());
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseProgramRedirection(ncm::ProgramId tid) {
+ this->program_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseApplicationControlRedirection(ncm::ProgramId tid) {
+ this->app_control_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseApplicationHtmlDocumentRedirection(ncm::ProgramId tid) {
+ this->html_docs_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseApplicationLegalInformationRedirection(ncm::ProgramId tid) {
+ this->legal_info_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveProgramPathForDebug(sf::Out out, ncm::ProgramId tid) {
+ if (this->GetRedirectedPath(out.GetPointer(), &this->debug_program_redirector, tid)) {
+ return ResultSuccess();
+ }
+
+ R_TRY_CATCH(this->ResolveProgramPath(out.GetPointer(), tid)) {
+ R_CATCH(ResultProgramNotFound) {
+ return ResultDebugProgramNotFound();
+ }
+ } R_END_TRY_CATCH;
+
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectProgramPathForDebug(const Path &path, ncm::ProgramId tid) {
+ this->debug_program_redirector.SetRedirection(tid, path);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->debug_program_redirector.SetRedirection(tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->debug_program_redirector.SetRedirection(tid, owner_tid, path, impl::RedirectionFlags_Application);
+ return ResultSuccess();
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::ProgramId tid) {
+ this->debug_program_redirector.EraseRedirection(tid);
+ return ResultSuccess();
+ }
+
+}
diff --git a/stratosphere/ncm/source/lr_redirectonlylocationresolver.hpp b/stratosphere/ncm/source/lr_redirectonlylocationresolver.hpp
new file mode 100644
index 000000000..19e9ee0b9
--- /dev/null
+++ b/stratosphere/ncm/source/lr_redirectonlylocationresolver.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#include "lr_contentlocationresolver.hpp"
+
+namespace ams::lr {
+
+ class RedirectOnlyLocationResolverInterface : public ILocationResolver {
+ public:
+ ~RedirectOnlyLocationResolverInterface();
+ public:
+ virtual Result ResolveProgramPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result RedirectProgramPath(const Path &path, ncm::ProgramId tid) override;
+ virtual Result ResolveApplicationControlPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result ResolveApplicationHtmlDocumentPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result ResolveDataPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationControlPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationControlPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result ResolveApplicationLegalInformationPath(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationLegalInformationPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationLegalInformationPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result Refresh() override;
+ virtual Result RedirectApplicationProgramPathDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result ClearApplicationRedirectionDeprecated() override;
+ virtual Result ClearApplicationRedirection(const sf::InArray &excluding_tids) override;
+ virtual Result EraseProgramRedirection(ncm::ProgramId tid) override;
+ virtual Result EraseApplicationControlRedirection(ncm::ProgramId tid) override;
+ virtual Result EraseApplicationHtmlDocumentRedirection(ncm::ProgramId tid) override;
+ virtual Result EraseApplicationLegalInformationRedirection(ncm::ProgramId tid) override;
+ virtual Result ResolveProgramPathForDebug(sf::Out out, ncm::ProgramId tid) override;
+ virtual Result RedirectProgramPathForDebug(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebugDeprecated(const Path &path, ncm::ProgramId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebug(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) override;
+ virtual Result EraseProgramRedirectionForDebug(ncm::ProgramId tid) 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),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_registeredlocationresolver.cpp b/stratosphere/ncm/source/lr_registeredlocationresolver.cpp
new file mode 100644
index 000000000..d192d3b77
--- /dev/null
+++ b/stratosphere/ncm/source/lr_registeredlocationresolver.cpp
@@ -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 .
+ */
+
+#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* locations, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ if (!locations->Register(tid, path, owner_tid)) {
+ locations->Clear();
+ locations->Register(tid, path, owner_tid);
+ }
+ }
+
+ bool RegisteredLocationResolverInterface::ResolvePath(Path* out, impl::LocationRedirector* redirector, impl::RegisteredLocations* locations, ncm::ProgramId tid) {
+ if (!redirector->FindRedirection(out, tid)) {
+ if (!locations->Find(out, tid)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ Result RegisteredLocationResolverInterface::RefreshImpl(const ncm::ProgramId* excluding_tids, size_t num_tids) {
+ if (hos::GetVersion() < hos::Version_900) {
+ this->ClearRedirections();
+ return ResultSuccess();
+ }
+
+ if (num_tids == 0) {
+ this->ClearRedirections();
+ } else {
+ this->registered_program_locations.ClearExcluding(excluding_tids, num_tids);
+ this->registered_html_docs_locations.ClearExcluding(excluding_tids, num_tids);
+ }
+
+ this->program_redirector.ClearRedirections(excluding_tids, num_tids);
+ this->html_docs_redirector.ClearRedirections(excluding_tids, num_tids);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::ResolveProgramPath(sf::Out out, ncm::ProgramId tid) {
+ if (!this->ResolvePath(out.GetPointer(), &this->program_redirector, &this->registered_program_locations, tid)) {
+ return ResultProgramNotFound();
+ }
+
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->RegisterPath(path, &this->registered_program_locations, tid, ncm::ProgramId::Invalid);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->RegisterPath(path, &this->registered_program_locations, tid, owner_tid);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::UnregisterProgramPath(ncm::ProgramId tid) {
+ this->registered_program_locations.Unregister(tid);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->program_redirector.SetRedirection(tid, path);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->program_redirector.SetRedirection(tid, owner_tid, path);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::ResolveHtmlDocumentPath(sf::Out out, ncm::ProgramId tid) {
+ if (!this->ResolvePath(out.GetPointer(), &this->html_docs_redirector, &this->registered_html_docs_locations, tid)) {
+ return ResultHtmlDocumentNotFound();
+ }
+
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->RegisterPath(path, &this->registered_html_docs_locations, tid, ncm::ProgramId::Invalid);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->RegisterPath(path, &this->registered_html_docs_locations, tid, owner_tid);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::UnregisterHtmlDocumentPath(ncm::ProgramId tid) {
+ this->registered_html_docs_locations.Unregister(tid);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid) {
+ this->html_docs_redirector.SetRedirection(tid, path);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid) {
+ this->html_docs_redirector.SetRedirection(tid, owner_tid, path);
+ return ResultSuccess();
+ }
+
+ Result RegisteredLocationResolverInterface::Refresh() {
+ return this->RefreshImpl(nullptr, 0);
+ }
+
+ Result RegisteredLocationResolverInterface::RefreshExcluding(const sf::InArray &tids) {
+ return this->RefreshImpl(tids.GetPointer(), tids.GetSize());
+ }
+
+}
diff --git a/stratosphere/ncm/source/lr_registeredlocationresolver.hpp b/stratosphere/ncm/source/lr_registeredlocationresolver.hpp
new file mode 100644
index 000000000..0cdb9cb95
--- /dev/null
+++ b/stratosphere/ncm/source/lr_registeredlocationresolver.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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 registered_program_locations;
+ impl::LocationRedirector html_docs_redirector;
+ impl::RegisteredLocations registered_html_docs_locations;
+ private:
+ void ClearRedirections(u32 flags = impl::RedirectionFlags_None);
+ void RegisterPath(const Path& path, impl::RegisteredLocations* locations, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ bool ResolvePath(Path* out, impl::LocationRedirector* redirector, impl::RegisteredLocations* locations, ncm::ProgramId tid);
+ Result RefreshImpl(const ncm::ProgramId* excluding_tids, size_t num_tids);
+ 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 out, ncm::ProgramId tid);
+ Result RegisterProgramPathDeprecated(const Path &path, ncm::ProgramId tid);
+ Result RegisterProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ Result UnregisterProgramPath(ncm::ProgramId tid);
+ Result RedirectProgramPathDeprecated(const Path &path, ncm::ProgramId tid);
+ Result RedirectProgramPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ Result ResolveHtmlDocumentPath(sf::Out out, ncm::ProgramId tid);
+ Result RegisterHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid);
+ Result RegisterHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ Result UnregisterHtmlDocumentPath(ncm::ProgramId tid);
+ Result RedirectHtmlDocumentPathDeprecated(const Path &path, ncm::ProgramId tid);
+ Result RedirectHtmlDocumentPath(const Path &path, ncm::ProgramId tid, ncm::ProgramId owner_tid);
+ Result Refresh();
+ Result RefreshExcluding(const sf::InArray &tids);
+ 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),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/ncm_content_manager_service.cpp b/stratosphere/ncm/source/ncm_content_manager_service.cpp
new file mode 100644
index 000000000..0355c0356
--- /dev/null
+++ b/stratosphere/ncm/source/ncm_content_manager_service.cpp
@@ -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 .
+ */
+
+#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> out, StorageId storage_id) {
+ std::shared_ptr content_storage;
+ R_TRY(impl::OpenContentStorage(&content_storage, storage_id));
+ out.SetValue(std::move(content_storage));
+ return ResultSuccess();
+ }
+
+ Result ContentManagerService::OpenContentMetaDatabase(sf::Out> out, StorageId storage_id) {
+ std::shared_ptr 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();
+ }
+
+}
diff --git a/stratosphere/ncm/source/ncm_content_manager_service.hpp b/stratosphere/ncm/source/ncm_content_manager_service.hpp
new file mode 100644
index 000000000..81e414fb0
--- /dev/null
+++ b/stratosphere/ncm/source/ncm_content_manager_service.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#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> out, StorageId storage_id);
+ virtual Result OpenContentMetaDatabase(sf::Out> 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),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/ncm_contentmetadatabase.cpp b/stratosphere/ncm/source/ncm_contentmetadatabase.cpp
new file mode 100644
index 000000000..e462cb482
--- /dev/null
+++ b/stratosphere/ncm/source/ncm_contentmetadatabase.cpp
@@ -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 .
+ */
+
+#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(value);
+ }
+
+ template
+ inline const ExtendedHeaderType* GetValueExtendedHeader(const void* value) {
+ return reinterpret_cast(reinterpret_cast(value) + sizeof(ContentMetaHeader));
+ }
+
+ inline const ContentInfo* GetValueContentInfos(const void* value) {
+ return reinterpret_cast(reinterpret_cast(value) + sizeof(ContentMetaHeader) + GetValueHeader(value)->extended_header_size);
+ }
+
+ inline const ContentMetaInfo* GetValueContentMetaInfos(const void* value) {
+ auto header = GetValueHeader(value);
+ return reinterpret_cast(reinterpret_cast(GetValueContentInfos(value)) + sizeof(ContentInfo) * header->content_count);
+ }
+
+ Result GetContentMetaSize(size_t *out, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore *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 *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 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 tid) {
+ R_TRY(this->EnsureEnabled());
+
+ ContentMetaKey key = {0};
+ key.id = tid;
+
+ 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 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 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 out_count, const sf::OutArray &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 out_entries_total, sf::Out out_entries_written, const sf::OutArray & out_info, ContentMetaType type, ProgramId application_title_id, ProgramId title_id_min, ProgramId title_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(type) == 0 || key.type == type) && (title_id_min <= key.id && key.id <= title_id_max) && (key.install_type == ContentInstallType::Unknown || key.install_type == install_type))) {
+ continue;
+ }
+
+ if (static_cast(application_title_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_tid = key.id;
+
+ switch (key.type) {
+ case ContentMetaType::Application:
+ break;
+ default:
+ /* The first u64 of all non-application extended headers is the application title id. */
+ entry_application_tid = *GetValueExtendedHeader(value);
+ }
+
+ /* Application tid doesn't match filter, skip this entry. */
+ if (entry_application_tid != application_title_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 out_key, ProgramId title_id) {
+ R_TRY(this->EnsureEnabled());
+ ContentMetaKey key;
+ R_TRY(this->GetLatestContentMetaKeyImpl(&key, title_id));
+ out_key.SetValue(key);
+ return ResultSuccess();
+ }
+
+ Result ContentMetaDatabaseInterface::ListApplication(sf::Out out_entries_total, sf::Out out_entries_written, const sf::OutArray &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(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_tid = key.id;
+
+ switch (key.type) {
+ case ContentMetaType::Application:
+ break;
+ default:
+ /* The first u64 of all non-application extended headers is the application title id. */
+ application_tid = *GetValueExtendedHeader(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_title_id = application_tid;
+ 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 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 out, const sf::InArray & 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 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 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(value);
+ out_version.SetValue(ext_header->required_system_version);
+ return ResultSuccess();
+ }
+
+ Result ContentMetaDatabaseInterface::GetPatchId(sf::Out 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(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 & out_orphaned, const sf::InArray &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 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 out_entries_written, const sf::OutArray &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 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 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(value);
+ out_version.SetValue(ext_header->required_application_version);
+ return ResultSuccess();
+ }
+
+ if (key.type != ContentMetaType::AddOnContent) {
+ return ResultInvalidContentMetaKey();
+ }
+ const auto ext_header = GetValueExtendedHeader(value);
+ out_version.SetValue(ext_header->required_application_version);
+ return ResultSuccess();
+ }
+
+ Result ContentMetaDatabaseInterface::GetContentIdByTypeAndIdOffset(sf::Out 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 title_id) {
+ ContentMetaKey key;
+
+ R_TRY(this->GetLatestContentMetaKey(&key, title_id));
+ R_TRY(this->GetContentIdByType(out_content_id, key, ContentType::Program));
+ return ResultSuccess();
+ }
+
+ Result ContentMetaDatabaseInterface::GetLatestData(ContentId* out_content_id, ProgramId title_id) {
+ ContentMetaKey key;
+
+ R_TRY(this->GetLatestContentMetaKey(&key, title_id));
+ R_TRY(this->GetContentIdByType(out_content_id, key, ContentType::Data));
+ return ResultSuccess();
+ }
+
+ Result OnMemoryContentMetaDatabaseInterface::GetLatestContentMetaKey(sf::Out out_key, ProgramId title_id) {
+ R_TRY(this->EnsureEnabled());
+
+ const ContentMetaKey key = ContentMetaKey::Make(title_id, 0, ContentMetaType::Unknown);
+
+ std::optional found_key;
+ for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) {
+ if (entry->GetKey().id != title_id) {
+ break;
+ }
+
+ found_key = entry->GetKey();
+ }
+
+ if (!found_key) {
+ return ResultContentMetaNotFound();
+ }
+
+ *out_key = *found_key;
+ return ResultSuccess();
+ }
+
+ Result OnMemoryContentMetaDatabaseInterface::LookupOrphanContent(const sf::OutArray & out_orphaned, const sf::InArray &content_ids) {
+ return ResultInvalidContentMetaDatabase();
+ }
+
+ Result OnMemoryContentMetaDatabaseInterface::Commit() {
+ R_TRY(this->EnsureEnabled());
+ return ResultSuccess();
+ }
+
+}
diff --git a/stratosphere/ncm/source/ncm_contentmetadatabase.hpp b/stratosphere/ncm/source/ncm_contentmetadatabase.hpp
new file mode 100644
index 000000000..87b7b26cc
--- /dev/null
+++ b/stratosphere/ncm/source/ncm_contentmetadatabase.hpp
@@ -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 .
+ */
+
+#pragma once
+#include
+#include
+
+#include "ncm_icontentmetadatabase.hpp"
+
+namespace ams::ncm {
+
+ class ContentMetaDatabaseInterface : public IContentMetaDatabase {
+ public:
+ ContentMetaDatabaseInterface(ams::kvdb::MemoryKeyValueStore* kvs, const char* mount_name) : IContentMetaDatabase(kvs, mount_name) {
+ }
+ ContentMetaDatabaseInterface(ams::kvdb::MemoryKeyValueStore* kvs) : IContentMetaDatabase(kvs) {
+ }
+ private:
+ Result GetContentIdByTypeImpl(ContentId* out, const ContentMetaKey& key, ContentType type, std::optional id_offset);
+ Result GetLatestContentMetaKeyImpl(ContentMetaKey* out_key, ProgramId tid);
+ public:
+ virtual Result Set(ContentMetaKey key, sf::InBuffer value) override;
+ virtual Result Get(sf::Out out_size, ContentMetaKey key, sf::OutBuffer out_value) override;
+ virtual Result Remove(ContentMetaKey key) override;
+ virtual Result GetContentIdByType(sf::Out out_content_id, ContentMetaKey key, ContentType type) override;
+ virtual Result ListContentInfo(sf::Out out_entries_written, const sf::OutArray &out_info, ContentMetaKey key, u32 start_index) override;
+ virtual Result List(sf::Out out_entries_total, sf::Out out_entries_written, const sf::OutArray &out_info, ContentMetaType type, ProgramId application_title_id, ProgramId title_id_min, ProgramId title_id_max, ContentInstallType install_type) override;
+ virtual Result GetLatestContentMetaKey(sf::Out out_key, ProgramId tid) override;
+ virtual Result ListApplication(sf::Out out_entries_total, sf::Out out_entries_written, const sf::OutArray &out_keys, ContentMetaType type) override;
+ virtual Result Has(sf::Out out, ContentMetaKey key) override;
+ virtual Result HasAll(sf::Out out, const sf::InArray &keys) override;
+ virtual Result GetSize(sf::Out out_size, ContentMetaKey key) override;
+ virtual Result GetRequiredSystemVersion(sf::Out out_version, ContentMetaKey key) override;
+ virtual Result GetPatchId(sf::Out out_patch_id, ContentMetaKey key) override;
+ virtual Result DisableForcibly() override;
+ virtual Result LookupOrphanContent(const sf::OutArray &out_orphaned, const sf::InArray &content_ids) override;
+ virtual Result Commit() override;
+ virtual Result HasContent(sf::Out out, ContentMetaKey key, ContentId content_id) override;
+ virtual Result ListContentMetaInfo(sf::Out out_entries_written, const sf::OutArray &out_meta_info, ContentMetaKey key, u32 start_index) override;
+ virtual Result GetAttributes(sf::Out out_attributes, ContentMetaKey key) override;
+ virtual Result GetRequiredApplicationVersion(sf::Out