diff --git a/stratosphere/Makefile b/stratosphere/Makefile
index 098c001a3..c1830a977 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 := libstratosphere $(MODULES)
diff --git a/stratosphere/libstratosphere/include/atmosphere/results/lr_results.hpp b/stratosphere/libstratosphere/include/atmosphere/results/lr_results.hpp
index c1983ee07..38afec514 100644
--- a/stratosphere/libstratosphere/include/atmosphere/results/lr_results.hpp
+++ b/stratosphere/libstratosphere/include/atmosphere/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/stratosphere/libstratosphere/include/atmosphere/results/ncm_results.hpp b/stratosphere/libstratosphere/include/atmosphere/results/ncm_results.hpp
index cdba12c7c..2216a9a3a 100644
--- a/stratosphere/libstratosphere/include/atmosphere/results/ncm_results.hpp
+++ b/stratosphere/libstratosphere/include/atmosphere/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/stratosphere/libstratosphere/include/stratosphere.hpp b/stratosphere/libstratosphere/include/stratosphere.hpp
index a432ace74..e4149af6d 100644
--- a/stratosphere/libstratosphere/include/stratosphere.hpp
+++ b/stratosphere/libstratosphere/include/stratosphere.hpp
@@ -37,7 +37,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/stratosphere/libstratosphere/include/stratosphere/lr.hpp b/stratosphere/libstratosphere/include/stratosphere/lr.hpp
new file mode 100644
index 000000000..51ea02934
--- /dev/null
+++ b/stratosphere/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/stratosphere/libstratosphere/include/stratosphere/lr/lr_types.hpp b/stratosphere/libstratosphere/include/stratosphere/lr/lr_types.hpp
new file mode 100644
index 000000000..7ef1a9ed6
--- /dev/null
+++ b/stratosphere/libstratosphere/include/stratosphere/lr/lr_types.hpp
@@ -0,0 +1,49 @@
+/*
+ * 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 sts::lr {
+
+ constexpr size_t MaxPathLen = 0x300;
+
+ struct Path {
+ char path[MaxPathLen];
+
+ Path() {
+ path[0] = '\0';
+ }
+
+ Path(const char* path) {
+ strncpy(this->path, path, MaxPathLen-1);
+ this->EnsureNullTerminated();
+ }
+
+ Path& operator=(const Path& other) {
+ /* N appears to always memcpy paths, so we will too. */
+ std::memcpy(this->path, other.path, MaxPathLen);
+ this->EnsureNullTerminated();
+ return *this;
+ }
+
+ void EnsureNullTerminated() {
+ path[MaxPathLen-1] = '\0';
+ }
+ };
+
+}
diff --git a/stratosphere/libstratosphere/include/stratosphere/ncm/ncm_types.hpp b/stratosphere/libstratosphere/include/stratosphere/ncm/ncm_types.hpp
index b21adb947..768ac9b95 100644
--- a/stratosphere/libstratosphere/include/stratosphere/ncm/ncm_types.hpp
+++ b/stratosphere/libstratosphere/include/stratosphere/ncm/ncm_types.hpp
@@ -19,6 +19,106 @@
namespace ams::ncm {
+ using Path = lr::Path;
+
+ 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 +544,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 {
+ TitleId 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(TitleId title_id, u32 version, ContentMetaType type) {
+ return { .id = title_id, .version = version, .type = type };
+ }
+
+ static constexpr ContentMetaKey Make(TitleId 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;
+ TitleId application_title_id;
+ };
+
+ static_assert(sizeof(ApplicationContentMetaKey) == 0x18, "ApplicationContentMetaKey definition!");
+
}
diff --git a/stratosphere/libstratosphere/include/stratosphere/util/util_uuid.hpp b/stratosphere/libstratosphere/include/stratosphere/util/util_uuid.hpp
new file mode 100644
index 000000000..70487e4d7
--- /dev/null
+++ b/stratosphere/libstratosphere/include/stratosphere/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 sts::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/ncm/Makefile b/stratosphere/ncm/Makefile
new file mode 100644
index 000000000..9db18530e
--- /dev/null
+++ b/stratosphere/ncm/Makefile
@@ -0,0 +1,160 @@
+#---------------------------------------------------------------------------------
+.SUFFIXES:
+#---------------------------------------------------------------------------------
+
+ifeq ($(strip $(DEVKITPRO)),)
+$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro")
+endif
+
+TOPDIR ?= $(CURDIR)
+include $(DEVKITPRO)/libnx/switch_rules
+
+AMSBRANCH := $(shell git symbolic-ref --short HEAD)
+AMSREV := $(AMSBRANCH)-$(shell git rev-parse --short HEAD)
+
+ifneq (, $(strip $(shell git status --porcelain 2>/dev/null)))
+ AMSREV := $(AMSREV)-dirty
+endif
+
+#---------------------------------------------------------------------------------
+# TARGET is the name of the output
+# BUILD is the directory where object files & intermediate files will be placed
+# SOURCES is a list of directories containing source code
+# DATA is a list of directories containing data files
+# INCLUDES is a list of directories containing header files
+# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
+#---------------------------------------------------------------------------------
+TARGET := $(notdir $(CURDIR))
+BUILD := build
+SOURCES := source source/impl source/boot2
+DATA := data
+INCLUDES := include ../../common/include
+EXEFS_SRC := exefs_src
+
+DEFINES := -DRESULT_ABORT_ON_ASSERT -DATMOSPHERE_GIT_BRANCH=\"$(AMSBRANCH)\" -DATMOSPHERE_GIT_REV=\"$(AMSREV)\"
+
+#---------------------------------------------------------------------------------
+# options for code generation
+#---------------------------------------------------------------------------------
+ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
+
+CFLAGS := -g -Wall -O2 -ffunction-sections \
+ $(ARCH) $(DEFINES)
+
+CFLAGS += $(INCLUDE) -D__SWITCH__ -DSM_ENABLE_SMHAX
+
+CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
+
+ASFLAGS := -g $(ARCH)
+LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
+
+LIBS := -lstratosphere -lnx
+
+#---------------------------------------------------------------------------------
+# list of directories containing libraries, this must be the top level containing
+# include and lib
+#---------------------------------------------------------------------------------
+LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere
+
+
+#---------------------------------------------------------------------------------
+# 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),$(notdir $(wildcard $(dir)/*.c)))
+CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
+SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.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..da5ab775d
--- /dev/null
+++ b/stratosphere/ncm/source/impl/lr_manager.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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 sts::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;
+ HosMutex g_mutex;
+
+ }
+
+ Result OpenLocationResolver(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);
+ }
+
+ out.SetValue(*resolver);
+ return ResultSuccess;
+ }
+
+ Result OpenRegisteredLocationResolver(Out> out) {
+ std::scoped_lock lk(g_mutex);
+
+ if (!g_registered_location_resolver) {
+ g_registered_location_resolver = std::make_shared();
+ }
+
+ out.SetValue(g_registered_location_resolver);
+ 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 ResultLrUnknownStorageId;
+ }
+
+ if (storage_id != ncm::StorageId::Host) {
+ (*resolver)->Refresh();
+ }
+
+ return ResultSuccess;
+ }
+
+ Result OpenAddOnContentLocationResolver(Out> out) {
+ std::scoped_lock lk(g_mutex);
+
+ if (!g_add_on_content_location_resolver) {
+ g_add_on_content_location_resolver = std::make_shared();
+ }
+
+ out.SetValue(g_add_on_content_location_resolver);
+ 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..865bb8189
--- /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 sts::lr::impl {
+
+ /* Location Resolver API. */
+ Result OpenLocationResolver(Out> out, ncm::StorageId storage_id);
+ Result OpenRegisteredLocationResolver(Out> out);
+ Result RefreshLocationResolver(ncm::StorageId storage_id);
+ Result OpenAddOnContentLocationResolver(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..2913eb26d
--- /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 sts::lr::impl {
+
+ class LocationRedirection : public util::IntrusiveListBaseNode {
+ NON_COPYABLE(LocationRedirection);
+ NON_MOVEABLE(LocationRedirection);
+ private:
+ ncm::TitleId title_id;
+ ncm::TitleId owner_tid;
+ Path path;
+ u32 flags;
+ public:
+ LocationRedirection(ncm::TitleId title_id, ncm::TitleId owner_tid, const Path& path, u32 flags) :
+ title_id(title_id), owner_tid(owner_tid), path(path), flags(flags) { /* ... */ }
+
+ ncm::TitleId GetTitleId() const {
+ return this->title_id;
+ }
+
+ ncm::TitleId 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::TitleId 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::TitleId title_id, const Path &path, u32 flags) {
+ this->SetRedirection(title_id, path, flags);
+ }
+
+ void LocationRedirector::SetRedirection(ncm::TitleId title_id, ncm::TitleId 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::TitleId title_id, u32 flags) {
+ for (auto &redirection : this->redirection_list) {
+ if (redirection.GetTitleId() == title_id) {
+ redirection.SetFlags(flags);
+ break;
+ }
+ }
+ }
+
+ void LocationRedirector::EraseRedirection(ncm::TitleId 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::TitleId* 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::TitleId 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..b1c26f7f9
--- /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 sts::lr::impl {
+
+ enum RedirectionFlags {
+ RedirectionFlags_None = (0 << 0),
+ RedirectionFlags_Application = (1 << 0),
+ };
+
+ class LocationRedirection;
+
+ class LocationRedirector {
+ NON_COPYABLE(LocationRedirector);
+ NON_MOVEABLE(LocationRedirector);
+ private:
+ sts::util::IntrusiveListBaseTraits::ListType redirection_list;
+ public:
+ LocationRedirector() { /* ... */ }
+
+ bool FindRedirection(Path *out, ncm::TitleId title_id);
+ void SetRedirection(ncm::TitleId title_id, const Path &path, u32 flags = RedirectionFlags_None);
+ void SetRedirection(ncm::TitleId title_id, ncm::TitleId owner_tid, const Path &path, u32 flags = RedirectionFlags_None);
+ void SetRedirectionFlags(ncm::TitleId title_id, u32 flags);
+ void EraseRedirection(ncm::TitleId title_id);
+ void ClearRedirections(u32 flags = RedirectionFlags_None);
+ void ClearRedirections(const ncm::TitleId* 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..4edd9db9b
--- /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 sts::lr::impl {
+
+ template
+ class RegisteredData {
+ NON_COPYABLE(RegisteredData);
+ NON_MOVEABLE(RegisteredData);
+ private:
+ struct Entry {
+ Value value;
+ ncm::TitleId 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::TitleId 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::TitleId 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::TitleId* 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::TitleId 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..e2b5d6d6a
--- /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 sts::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..27b5e0b7a
--- /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 sts::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(FS_CONTENTSTORAGEID_NandSystem), 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;
+
+ HosMutex 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::NandSystem, FS_CONTENTSTORAGEID_NandSystem);
+
+ if (R_FAILED(VerifyContentStorage(StorageId::NandSystem))) {
+ R_TRY(CreateContentStorage(StorageId::NandSystem));
+ }
+
+ R_TRY(ActivateContentStorage(StorageId::NandSystem));
+
+ /* 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_SurviveFactoryReset| FsSaveDataFlags_SurviveFactoryResetForRefurbishment;
+ nand_system_save_meta.space_id = FsSaveDataSpaceId_NandSystem;
+
+ 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::NandSystem, nand_system_save_meta, 0x800));
+
+ if (R_FAILED(VerifyContentMetaDatabase(StorageId::NandSystem))) {
+ R_TRY(CreateContentMetaDatabase(StorageId::NandSystem));
+
+ /* TODO: N supports a number of unused modes here, we don't bother implementing them currently. */
+ }
+
+ u32 current_flags = 0;
+ if (GetRuntimeFirmwareVersion() >= FirmwareVersion_200 && R_SUCCEEDED(fs::GetSaveDataFlags(¤t_flags, 0x8000000000000120)) && current_flags != (FsSaveDataFlags_SurviveFactoryReset | FsSaveDataFlags_SurviveFactoryResetForRefurbishment)) {
+ fs::SetSaveDataFlags(0x8000000000000120, FsSaveDataSpaceId_NandSystem, FsSaveDataFlags_SurviveFactoryReset | FsSaveDataFlags_SurviveFactoryResetForRefurbishment);
+ }
+
+ R_TRY(ActivateContentMetaDatabase(StorageId::NandSystem));
+
+ /* 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::NandUser, FS_CONTENTSTORAGEID_NandUser);
+
+ /* 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_NandSystem;
+
+ 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::NandUser, 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, FS_CONTENTSTORAGEID_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_SdCard;
+
+ 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, FS_CONTENTSTORAGEID_NandSystem);
+
+ /* 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 ResultNcmUnknownStorage;
+ }
+
+ ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ 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 ResultNcmUnknownStorage;
+ }
+
+ ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ 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 ResultNcmUnknownStorage;
+ }
+
+ ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ auto content_storage = entry->content_storage;
+
+ if (!content_storage) {
+ /* 1.0.0 activates content storages as soon as they are opened. */
+ if (GetRuntimeFirmwareVersion() == FirmwareVersion_100) {
+ R_TRY(ActivateContentStorage(storage_id));
+ content_storage = entry->content_storage;
+ } else {
+ switch (storage_id) {
+ case StorageId::GameCard:
+ return ResultNcmGameCardContentStorageNotActive;
+
+ case StorageId::NandSystem:
+ return ResultNcmNandSystemContentStorageNotActive;
+
+ case StorageId::NandUser:
+ return ResultNcmNandUserContentStorageNotActive;
+
+ case StorageId::SdCard:
+ return ResultNcmSdCardContentStorageNotActive;
+
+ default:
+ return ResultNcmUnknownContentStorageNotActive;
+ }
+ }
+ }
+
+ *out = std::move(content_storage);
+ return ResultSuccess;
+ }
+
+ Result CloseContentStorageForcibly(StorageId storage_id) {
+ std::scoped_lock lk(g_mutex);
+
+ if (storage_id == StorageId::None) {
+ return ResultNcmUnknownStorage;
+ }
+
+ ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ 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 ResultNcmUnknownStorage;
+ }
+
+ ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ /* 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, FsGameCardPartiton_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::NandSystem:
+ 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 ResultNcmUnknownStorage;
+ }
+
+ ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ /* 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 ResultNcmUnknownStorage;
+ }
+
+ ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ /* 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(ResultFsTargetNotFound) {
+ 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 ResultNcmUnknownStorage;
+ }
+
+ ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ 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 ResultNcmInvalidContentMetaDatabase;
+ }
+
+ 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 ResultNcmUnknownStorage;
+ }
+
+ ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ 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 (GetRuntimeFirmwareVersion() == FirmwareVersion_100) {
+ R_TRY(ActivateContentMetaDatabase(storage_id));
+ content_meta_db = entry->content_meta_database;
+ } else {
+ switch (storage_id) {
+ case StorageId::GameCard:
+ return ResultNcmGameCardContentMetaDatabaseNotActive;
+
+ case StorageId::NandSystem:
+ return ResultNcmNandSystemContentMetaDatabaseNotActive;
+
+ case StorageId::NandUser:
+ return ResultNcmNandUserContentMetaDatabaseNotActive;
+
+ case StorageId::SdCard:
+ return ResultNcmSdCardContentMetaDatabaseNotActive;
+
+ default:
+ return ResultNcmUnknownContentMetaDatabaseNotActive;
+ }
+ }
+ }
+
+ *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 ResultNcmUnknownStorage;
+ }
+
+ ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ 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 ResultNcmUnknownStorage;
+ }
+
+ ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
+
+ if (!entry) {
+ return ResultNcmUnknownStorage;
+ }
+
+ 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 ResultNcmUnknownStorage;
+ }
+
+ 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 ResultNcmUnknownStorage;
+ }
+
+ 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..3fdaf9d99
--- /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 sts::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..cde68810c
--- /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 sts::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, FS_OPEN_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, FS_CREATE_BIG_FILE)) {
+ R_CATCH(ResultFsPathAlreadyExists) {
+ return ResultNcmPlaceHolderAlreadyExists;
+ }
+ } 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(ResultFsPathNotFound) {
+ return ResultNcmPlaceHolderNotFound;
+ }
+ } 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(ResultFsPathNotFound) {
+ return ResultNcmPlaceHolderNotFound;
+ }
+ } 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(ResultFsPathNotFound) {
+ return ResultNcmPlaceHolderNotFound;
+ }
+ } 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..07baefa7e
--- /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 sts::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;
+ HosMutex 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..1e17437e4
--- /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 sts::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;
+ HosMutex 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..6747ab79c
--- /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 sts::lr {
+
+ Result AddOnContentLocationResolverInterface::ResolveAddOnContentPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ ncm::StorageId storage_id = ncm::StorageId::None;
+
+ if (!this->registered_storages.Find(&storage_id, tid)) {
+ return ResultLrAddOnContentNotFound;
+ }
+
+ 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.pointer, data_content_id));
+
+ return ResultSuccess;
+ }
+
+ Result AddOnContentLocationResolverInterface::RegisterAddOnContentStorageDeprecated(ncm::StorageId storage_id, ncm::TitleId tid) {
+ if (!this->registered_storages.Register(tid, storage_id, ncm::TitleId::Invalid)) {
+ return ResultLrTooManyRegisteredPaths;
+ }
+
+ return ResultSuccess;
+ }
+
+ Result AddOnContentLocationResolverInterface::RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::TitleId tid, ncm::TitleId application_tid) {
+ if (!this->registered_storages.Register(tid, storage_id, application_tid)) {
+ return ResultLrTooManyRegisteredPaths;
+ }
+
+ return ResultSuccess;
+ }
+
+ Result AddOnContentLocationResolverInterface::UnregisterAllAddOnContentPath() {
+ this->registered_storages.Clear();
+ return ResultSuccess;
+ }
+
+ Result AddOnContentLocationResolverInterface::RefreshApplicationAddOnContent(InBuffer tids) {
+ if (tids.num_elements == 0) {
+ this->registered_storages.Clear();
+ return ResultSuccess;
+ }
+
+ this->registered_storages.ClearExcluding(tids.buffer, tids.num_elements);
+ return ResultSuccess;
+ }
+
+ Result AddOnContentLocationResolverInterface::UnregisterApplicationAddOnContent(ncm::TitleId 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..882f7edbc
--- /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 sts::lr {
+
+ class AddOnContentLocationResolverInterface : public 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(GetRuntimeFirmwareVersion() < FirmwareVersion_900 ? 0x800 : 0x2) { /* ... */ }
+
+ virtual Result ResolveAddOnContentPath(OutPointerWithServerSize out, ncm::TitleId tid);
+ virtual Result RegisterAddOnContentStorageDeprecated(ncm::StorageId storage_id, ncm::TitleId tid);
+ virtual Result RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::TitleId tid, ncm::TitleId application_tid);
+ virtual Result UnregisterAllAddOnContentPath();
+ virtual Result RefreshApplicationAddOnContent(InBuffer tids);
+ virtual Result UnregisterApplicationAddOnContent(ncm::TitleId tid);
+ public:
+ DEFINE_SERVICE_DISPATCH_TABLE {
+ MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, ResolveAddOnContentPath, FirmwareVersion_200),
+ MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, RegisterAddOnContentStorageDeprecated, FirmwareVersion_200, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, RegisterAddOnContentStorage, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, UnregisterAllAddOnContentPath, FirmwareVersion_200),
+ MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, RefreshApplicationAddOnContent, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, UnregisterApplicationAddOnContent, FirmwareVersion_900),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_contentlocationresolver.cpp b/stratosphere/ncm/source/lr_contentlocationresolver.cpp
new file mode 100644
index 000000000..2b68d9cb5
--- /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 sts::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(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &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(ResultNcmContentMetaNotFound) {
+ return ResultLrProgramNotFound;
+ }
+ } R_END_TRY_CATCH;
+
+ this->GetContentStoragePath(out.pointer, program_content_id);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectProgramPath(InPointer path, ncm::TitleId tid) {
+ this->program_redirector.SetRedirection(tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::ResolveApplicationControlPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->app_control_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ return ResultLrControlNotFound;
+ }
+
+ Result ContentLocationResolverInterface::ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->html_docs_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ return ResultLrHtmlDocumentNotFound;
+ }
+
+ Result ContentLocationResolverInterface::ResolveDataPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ ncm::ContentId data_content_id;
+
+ R_TRY(this->content_meta_database->GetLatestData(&data_content_id, tid));
+ this->GetContentStoragePath(out.pointer, data_content_id);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationControlPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->app_control_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationControlPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->app_control_redirector.SetRedirection(tid, owner_tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->html_docs_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->html_docs_redirector.SetRedirection(tid, owner_tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::ResolveApplicationLegalInformationPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->legal_info_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ return ResultLrLegalInformationNotFound;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationLegalInformationPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->legal_info_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationLegalInformationPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->legal_info_redirector.SetRedirection(tid, owner_tid, *path.pointer, 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(InPointer path, ncm::TitleId tid) {
+ this->program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->program_redirector.SetRedirection(tid, owner_tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::ClearApplicationRedirectionDeprecated() {
+ this->ClearRedirections(impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::ClearApplicationRedirection(InBuffer excluding_tids) {
+ this->ClearRedirections(excluding_tids.buffer, excluding_tids.num_elements);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::EraseProgramRedirection(ncm::TitleId tid) {
+ this->program_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::EraseApplicationControlRedirection(ncm::TitleId tid) {
+ this->app_control_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::EraseApplicationHtmlDocumentRedirection(ncm::TitleId tid) {
+ this->html_docs_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::EraseApplicationLegalInformationRedirection(ncm::TitleId tid) {
+ this->legal_info_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::ResolveProgramPathForDebug(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->debug_program_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ R_TRY_CATCH(this->ResolveProgramPath(out.pointer, tid)) {
+ R_CATCH(ResultLrProgramNotFound) {
+ return ResultLrDebugProgramNotFound;
+ }
+ } R_END_TRY_CATCH;
+
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectProgramPathForDebug(InPointer path, ncm::TitleId tid) {
+ this->debug_program_redirector.SetRedirection(tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationProgramPathForDebugDeprecated(InPointer path, ncm::TitleId tid) {
+ this->debug_program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::RedirectApplicationProgramPathForDebug(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->debug_program_redirector.SetRedirection(tid, owner_tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result ContentLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::TitleId 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..30cb3239a
--- /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 sts::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(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectProgramPath(InPointer path, ncm::TitleId tid) override;
+ virtual Result ResolveApplicationControlPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result ResolveDataPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationControlPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationControlPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result ResolveApplicationLegalInformationPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationLegalInformationPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationLegalInformationPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result Refresh() override;
+ virtual Result RedirectApplicationProgramPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result ClearApplicationRedirectionDeprecated() override;
+ virtual Result ClearApplicationRedirection(InBuffer excluding_tids) override;
+ virtual Result EraseProgramRedirection(ncm::TitleId tid) override;
+ virtual Result EraseApplicationControlRedirection(ncm::TitleId tid) override;
+ virtual Result EraseApplicationHtmlDocumentRedirection(ncm::TitleId tid) override;
+ virtual Result EraseApplicationLegalInformationRedirection(ncm::TitleId tid) override;
+ virtual Result ResolveProgramPathForDebug(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectProgramPathForDebug(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebugDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebug(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result EraseProgramRedirectionForDebug(ncm::TitleId 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, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationControlPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationHtmlDocumentPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationHtmlDocumentPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveApplicationLegalInformationPath),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationLegalInformationPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationLegalInformationPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, Refresh),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPathDeprecated, FirmwareVersion_500, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ClearApplicationRedirectionDeprecated, FirmwareVersion_500, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ClearApplicationRedirection, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseProgramRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseApplicationControlRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseApplicationHtmlDocumentRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseApplicationLegalInformationRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveProgramPathForDebug, FirmwareVersion_700),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectProgramPathForDebug, FirmwareVersion_700),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPathForDebugDeprecated, FirmwareVersion_700, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPathForDebug, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseProgramRedirectionForDebug, FirmwareVersion_700),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_ilocationresolver.cpp b/stratosphere/ncm/source/lr_ilocationresolver.cpp
new file mode 100644
index 000000000..d10df8da2
--- /dev/null
+++ b/stratosphere/ncm/source/lr_ilocationresolver.cpp
@@ -0,0 +1,125 @@
+/*
+ * 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_ilocationresolver.hpp"
+
+namespace sts::lr {
+
+ Result ILocationResolver::ResolveProgramPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectProgramPath(InPointer path, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::ResolveApplicationControlPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::ResolveDataPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationControlPathDeprecated(InPointer path, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationControlPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::ResolveApplicationLegalInformationPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationLegalInformationPathDeprecated(InPointer path, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationLegalInformationPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::Refresh() {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationProgramPathDeprecated(InPointer path, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::ClearApplicationRedirectionDeprecated() {
+ std::abort();
+ }
+
+ Result ILocationResolver::ClearApplicationRedirection(InBuffer excluding_tids) {
+ std::abort();
+ }
+
+ Result ILocationResolver::EraseProgramRedirection(ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::EraseApplicationControlRedirection(ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::EraseApplicationHtmlDocumentRedirection(ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::EraseApplicationLegalInformationRedirection(ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::ResolveProgramPathForDebug(OutPointerWithServerSize out, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectProgramPathForDebug(InPointer path, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationProgramPathForDebugDeprecated(InPointer path, ncm::TitleId tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::RedirectApplicationProgramPathForDebug(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ std::abort();
+ }
+
+ Result ILocationResolver::EraseProgramRedirectionForDebug(ncm::TitleId tid) {
+ std::abort();
+ }
+
+}
diff --git a/stratosphere/ncm/source/lr_ilocationresolver.hpp b/stratosphere/ncm/source/lr_ilocationresolver.hpp
new file mode 100644
index 000000000..bf01d76fc
--- /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 sts::lr {
+
+ class ILocationResolver : public 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::TitleId 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::TitleId* 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(OutPointerWithServerSize out, ncm::TitleId tid);
+ virtual Result RedirectProgramPath(InPointer path, ncm::TitleId tid);
+ virtual Result ResolveApplicationControlPath(OutPointerWithServerSize out, ncm::TitleId tid);
+ virtual Result ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid);
+ virtual Result ResolveDataPath(OutPointerWithServerSize out, ncm::TitleId tid);
+ virtual Result RedirectApplicationControlPathDeprecated(InPointer path, ncm::TitleId tid);
+ virtual Result RedirectApplicationControlPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid);
+ virtual Result RedirectApplicationHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid);
+ virtual Result RedirectApplicationHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid);
+ virtual Result ResolveApplicationLegalInformationPath(OutPointerWithServerSize out, ncm::TitleId tid);
+ virtual Result RedirectApplicationLegalInformationPathDeprecated(InPointer path, ncm::TitleId tid);
+ virtual Result RedirectApplicationLegalInformationPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid);
+ virtual Result Refresh();
+ virtual Result RedirectApplicationProgramPathDeprecated(InPointer path, ncm::TitleId tid);
+ virtual Result RedirectApplicationProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid);
+ virtual Result ClearApplicationRedirectionDeprecated();
+ virtual Result ClearApplicationRedirection(InBuffer excluding_tids);
+ virtual Result EraseProgramRedirection(ncm::TitleId tid);
+ virtual Result EraseApplicationControlRedirection(ncm::TitleId tid);
+ virtual Result EraseApplicationHtmlDocumentRedirection(ncm::TitleId tid);
+ virtual Result EraseApplicationLegalInformationRedirection(ncm::TitleId tid);
+ virtual Result ResolveProgramPathForDebug(OutPointerWithServerSize out, ncm::TitleId tid);
+ virtual Result RedirectProgramPathForDebug(InPointer path, ncm::TitleId tid);
+ virtual Result RedirectApplicationProgramPathForDebugDeprecated(InPointer path, ncm::TitleId tid);
+ virtual Result RedirectApplicationProgramPathForDebug(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid);
+ virtual Result EraseProgramRedirectionForDebug(ncm::TitleId tid);
+ public:
+ DEFINE_SERVICE_DISPATCH_TABLE {
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ResolveProgramPath),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectProgramPath),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ResolveApplicationControlPath),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ResolveApplicationHtmlDocumentPath),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ResolveDataPath),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationControlPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationControlPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationHtmlDocumentPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationHtmlDocumentPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ResolveApplicationLegalInformationPath),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationLegalInformationPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationLegalInformationPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, Refresh),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationProgramPathDeprecated, FirmwareVersion_500, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationProgramPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ClearApplicationRedirectionDeprecated, FirmwareVersion_500, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ClearApplicationRedirection, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, EraseProgramRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, EraseApplicationControlRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, EraseApplicationHtmlDocumentRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, EraseApplicationLegalInformationRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, ResolveProgramPathForDebug, FirmwareVersion_700),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectProgramPathForDebug, FirmwareVersion_700),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationProgramPathForDebugDeprecated, FirmwareVersion_700, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, RedirectApplicationProgramPathForDebug, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(ILocationResolver, EraseProgramRedirectionForDebug, FirmwareVersion_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..01468eb1c
--- /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 sts::lr {
+
+ Result LocationResolverManagerService::OpenLocationResolver(Out> out, ncm::StorageId storage_id) {
+ return impl::OpenLocationResolver(out, storage_id);
+ }
+
+ Result LocationResolverManagerService::OpenRegisteredLocationResolver(Out> out) {
+ return impl::OpenRegisteredLocationResolver(out);
+ }
+
+ Result LocationResolverManagerService::RefreshLocationResolver(ncm::StorageId storage_id) {
+ return impl::RefreshLocationResolver(storage_id);
+ }
+
+ Result LocationResolverManagerService::OpenAddOnContentLocationResolver(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..81b887cfc
--- /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 sts::lr {
+
+ class LocationResolverManagerService final : public IServiceObject {
+ protected:
+ enum class CommandId {
+ OpenLocationResolver = 0,
+ OpenRegisteredLocationResolver = 1,
+ RefreshLocationResolver = 2,
+ OpenAddOnContentLocationResolver = 3,
+ };
+ public:
+ /* Actual commands. */
+ virtual Result OpenLocationResolver(Out> out, ncm::StorageId storage_id);
+ virtual Result OpenRegisteredLocationResolver(Out> out);
+ virtual Result RefreshLocationResolver(ncm::StorageId storage_id);
+ virtual Result OpenAddOnContentLocationResolver(Out> out);
+ public:
+ DEFINE_SERVICE_DISPATCH_TABLE {
+ MAKE_SERVICE_COMMAND_META(LocationResolverManagerService, OpenLocationResolver),
+ MAKE_SERVICE_COMMAND_META(LocationResolverManagerService, OpenRegisteredLocationResolver),
+ MAKE_SERVICE_COMMAND_META(LocationResolverManagerService, RefreshLocationResolver),
+ MAKE_SERVICE_COMMAND_META(LocationResolverManagerService, OpenAddOnContentLocationResolver, FirmwareVersion_200),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_redirectonlylocationresolver.cpp b/stratosphere/ncm/source/lr_redirectonlylocationresolver.cpp
new file mode 100644
index 000000000..d47c4af7c
--- /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 sts::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(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->program_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ return ResultLrProgramNotFound;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectProgramPath(InPointer path, ncm::TitleId tid) {
+ this->program_redirector.SetRedirection(tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveApplicationControlPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->app_control_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ return ResultLrControlNotFound;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->html_docs_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ return ResultLrHtmlDocumentNotFound;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveDataPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ return ResultLrDataNotFound;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationControlPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->app_control_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationControlPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->app_control_redirector.SetRedirection(tid, owner_tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->html_docs_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->html_docs_redirector.SetRedirection(tid, owner_tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveApplicationLegalInformationPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->legal_info_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ return ResultLrLegalInformationNotFound;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationLegalInformationPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->legal_info_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationLegalInformationPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->legal_info_redirector.SetRedirection(tid, owner_tid, *path.pointer, 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(InPointer path, ncm::TitleId tid) {
+ this->program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->program_redirector.SetRedirection(tid, owner_tid, *path.pointer, 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(InBuffer excluding_tids) {
+ this->ClearRedirections(excluding_tids.buffer, excluding_tids.num_elements);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseProgramRedirection(ncm::TitleId tid) {
+ this->program_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseApplicationControlRedirection(ncm::TitleId tid) {
+ this->app_control_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseApplicationHtmlDocumentRedirection(ncm::TitleId tid) {
+ this->html_docs_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseApplicationLegalInformationRedirection(ncm::TitleId tid) {
+ this->legal_info_redirector.EraseRedirection(tid);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::ResolveProgramPathForDebug(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (this->GetRedirectedPath(out.pointer, &this->debug_program_redirector, tid)) {
+ return ResultSuccess;
+ }
+
+ R_TRY_CATCH(this->ResolveProgramPath(out.pointer, tid)) {
+ R_CATCH(ResultLrProgramNotFound) {
+ return ResultLrDebugProgramNotFound;
+ }
+ } R_END_TRY_CATCH;
+
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectProgramPathForDebug(InPointer path, ncm::TitleId tid) {
+ this->debug_program_redirector.SetRedirection(tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathForDebugDeprecated(InPointer path, ncm::TitleId tid) {
+ this->debug_program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathForDebug(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->debug_program_redirector.SetRedirection(tid, owner_tid, *path.pointer, impl::RedirectionFlags_Application);
+ return ResultSuccess;
+ }
+
+ Result RedirectOnlyLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::TitleId 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..53881fded
--- /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 sts::lr {
+
+ class RedirectOnlyLocationResolverInterface : public ILocationResolver {
+ public:
+ ~RedirectOnlyLocationResolverInterface();
+ public:
+ virtual Result ResolveProgramPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectProgramPath(InPointer path, ncm::TitleId tid) override;
+ virtual Result ResolveApplicationControlPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result ResolveDataPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationControlPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationControlPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result ResolveApplicationLegalInformationPath(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationLegalInformationPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationLegalInformationPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result Refresh() override;
+ virtual Result RedirectApplicationProgramPathDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result ClearApplicationRedirectionDeprecated() override;
+ virtual Result ClearApplicationRedirection(InBuffer excluding_tids) override;
+ virtual Result EraseProgramRedirection(ncm::TitleId tid) override;
+ virtual Result EraseApplicationControlRedirection(ncm::TitleId tid) override;
+ virtual Result EraseApplicationHtmlDocumentRedirection(ncm::TitleId tid) override;
+ virtual Result EraseApplicationLegalInformationRedirection(ncm::TitleId tid) override;
+ virtual Result ResolveProgramPathForDebug(OutPointerWithServerSize out, ncm::TitleId tid) override;
+ virtual Result RedirectProgramPathForDebug(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebugDeprecated(InPointer path, ncm::TitleId tid) override;
+ virtual Result RedirectApplicationProgramPathForDebug(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) override;
+ virtual Result EraseProgramRedirectionForDebug(ncm::TitleId tid) override;
+ public:
+ DEFINE_SERVICE_DISPATCH_TABLE {
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ResolveProgramPath),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectProgramPath),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ResolveApplicationControlPath),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ResolveApplicationHtmlDocumentPath),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ResolveDataPath),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationControlPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationControlPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationHtmlDocumentPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationHtmlDocumentPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ResolveApplicationLegalInformationPath),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationLegalInformationPathDeprecated, FirmwareVersion_100, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationLegalInformationPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, Refresh),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationProgramPathDeprecated, FirmwareVersion_500, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationProgramPath, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ClearApplicationRedirectionDeprecated, FirmwareVersion_500, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ClearApplicationRedirection, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, EraseProgramRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, EraseApplicationControlRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, EraseApplicationHtmlDocumentRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, EraseApplicationLegalInformationRedirection, FirmwareVersion_500),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ResolveProgramPathForDebug, FirmwareVersion_700),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectProgramPathForDebug, FirmwareVersion_700),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationProgramPathForDebugDeprecated, FirmwareVersion_700, FirmwareVersion_810),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationProgramPathForDebug, FirmwareVersion_900),
+ MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, EraseProgramRedirectionForDebug, FirmwareVersion_700),
+ };
+ };
+
+}
diff --git a/stratosphere/ncm/source/lr_registeredlocationresolver.cpp b/stratosphere/ncm/source/lr_registeredlocationresolver.cpp
new file mode 100644
index 000000000..08380f040
--- /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 sts::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::TitleId tid, ncm::TitleId 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::TitleId tid) {
+ if (!redirector->FindRedirection(out, tid)) {
+ if (!locations->Find(out, tid)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ Result RegisteredLocationResolverInterface::RefreshImpl(const ncm::TitleId* excluding_tids, size_t num_tids) {
+ if (GetRuntimeFirmwareVersion() < FirmwareVersion_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(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (!this->ResolvePath(out.pointer, &this->program_redirector, &this->registered_program_locations, tid)) {
+ return ResultLrProgramNotFound;
+ }
+
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterProgramPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->RegisterPath(*path.pointer, &this->registered_program_locations, tid, ncm::TitleId::Invalid);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->RegisterPath(*path.pointer, &this->registered_program_locations, tid, owner_tid);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::UnregisterProgramPath(ncm::TitleId tid) {
+ this->registered_program_locations.Unregister(tid);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectProgramPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->program_redirector.SetRedirection(tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->program_redirector.SetRedirection(tid, owner_tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::ResolveHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid) {
+ if (!this->ResolvePath(out.pointer, &this->html_docs_redirector, &this->registered_html_docs_locations, tid)) {
+ return ResultLrHtmlDocumentNotFound;
+ }
+
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->RegisterPath(*path.pointer, &this->registered_html_docs_locations, tid, ncm::TitleId::Invalid);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RegisterHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->RegisterPath(*path.pointer, &this->registered_html_docs_locations, tid, owner_tid);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::UnregisterHtmlDocumentPath(ncm::TitleId tid) {
+ this->registered_html_docs_locations.Unregister(tid);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid) {
+ this->html_docs_redirector.SetRedirection(tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::RedirectHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid) {
+ this->html_docs_redirector.SetRedirection(tid, owner_tid, *path.pointer);
+ return ResultSuccess;
+ }
+
+ Result RegisteredLocationResolverInterface::Refresh() {
+ return this->RefreshImpl(nullptr, 0);
+ }
+
+ Result RegisteredLocationResolverInterface::RefreshExcluding(InBuffer tids) {
+ return this->RefreshImpl(tids.buffer, tids.num_elements);
+ }
+
+}
diff --git a/stratosphere/ncm/source/lr_registeredlocationresolver.hpp b/stratosphere/ncm/source/lr_registeredlocationresolver.hpp
new file mode 100644
index 000000000..0e4310de6
--- /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 sts::lr {
+
+ class RegisteredLocationResolverInterface final : public 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::TitleId tid, ncm::TitleId owner_tid);
+ bool ResolvePath(Path* out, impl::LocationRedirector* redirector, impl::RegisteredLocations* locations, ncm::TitleId tid);
+ Result RefreshImpl(const ncm::TitleId* excluding_tids, size_t num_tids);
+ public:
+ RegisteredLocationResolverInterface() : registered_program_locations(GetRuntimeFirmwareVersion() < FirmwareVersion_900 ? 0x10 : MaxRegisteredLocations), registered_html_docs_locations(GetRuntimeFirmwareVersion() < FirmwareVersion_900 ? 0x10 : MaxRegisteredLocations) { /* ... */ }
+ ~RegisteredLocationResolverInterface();
+
+ Result ResolveProgramPath(OutPointerWithServerSize out, ncm::TitleId tid);
+ Result RegisterProgramPathDeprecated(InPointer path, ncm::TitleId tid);
+ Result RegisterProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid);
+ Result UnregisterProgramPath(ncm::TitleId tid);
+ Result RedirectProgramPathDeprecated(InPointer