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 path, ncm::TitleId tid); + Result RedirectProgramPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid); + Result ResolveHtmlDocumentPath(OutPointerWithServerSize out, ncm::TitleId tid); + Result RegisterHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid); + Result RegisterHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid); + Result UnregisterHtmlDocumentPath(ncm::TitleId tid); + Result RedirectHtmlDocumentPathDeprecated(InPointer path, ncm::TitleId tid); + Result RedirectHtmlDocumentPath(InPointer path, ncm::TitleId tid, ncm::TitleId owner_tid); + Result Refresh(); + Result RefreshExcluding(InBuffer tids); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, ResolveProgramPath), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RegisterProgramPathDeprecated, FirmwareVersion_100, FirmwareVersion_810), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RegisterProgramPath, FirmwareVersion_900), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, UnregisterProgramPath), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RedirectProgramPathDeprecated, FirmwareVersion_100, FirmwareVersion_810), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RedirectProgramPath, FirmwareVersion_900), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, ResolveHtmlDocumentPath, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RegisterHtmlDocumentPathDeprecated, FirmwareVersion_200, FirmwareVersion_810), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RegisterHtmlDocumentPath, FirmwareVersion_900), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, UnregisterHtmlDocumentPath, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RedirectHtmlDocumentPathDeprecated, FirmwareVersion_200, FirmwareVersion_810), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RedirectHtmlDocumentPathDeprecated, FirmwareVersion_900), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, Refresh, FirmwareVersion_700), + MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RefreshExcluding, FirmwareVersion_900), + }; + }; + +} diff --git a/stratosphere/ncm/source/ncm_content_manager_service.cpp b/stratosphere/ncm/source/ncm_content_manager_service.cpp new file mode 100644 index 000000000..d0bcdffa6 --- /dev/null +++ b/stratosphere/ncm/source/ncm_content_manager_service.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "impl/ncm_content_manager.hpp" +#include "ncm_content_manager_service.hpp" + +namespace sts::ncm { + + Result ContentManagerService::CreateContentStorage(StorageId storage_id) { + return impl::CreateContentStorage(storage_id); + } + + Result ContentManagerService::CreateContentMetaDatabase(StorageId storage_id) { + return impl::CreateContentMetaDatabase(storage_id); + } + + Result ContentManagerService::VerifyContentStorage(StorageId storage_id) { + return impl::VerifyContentStorage(storage_id); + } + + Result ContentManagerService::VerifyContentMetaDatabase(StorageId storage_id) { + return impl::VerifyContentMetaDatabase(storage_id); + } + + Result ContentManagerService::OpenContentStorage(Out> out, StorageId storage_id) { + std::shared_ptr content_storage; + R_TRY(impl::OpenContentStorage(&content_storage, storage_id)); + out.SetValue(std::move(content_storage)); + return ResultSuccess; + } + + Result ContentManagerService::OpenContentMetaDatabase(Out> out, StorageId storage_id) { + std::shared_ptr content_meta_database; + R_TRY(impl::OpenContentMetaDatabase(&content_meta_database, storage_id)); + out.SetValue(std::move(content_meta_database)); + return ResultSuccess; + } + + Result ContentManagerService::CloseContentStorageForcibly(StorageId storage_id) { + return impl::CloseContentStorageForcibly(storage_id); + } + + Result ContentManagerService::CloseContentMetaDatabaseForcibly(StorageId storage_id) { + return impl::CloseContentMetaDatabaseForcibly(storage_id); + } + + Result ContentManagerService::CleanupContentMetaDatabase(StorageId storage_id) { + return impl::CleanupContentMetaDatabase(storage_id); + } + + Result ContentManagerService::ActivateContentStorage(StorageId storage_id) { + return impl::ActivateContentStorage(storage_id); + } + + Result ContentManagerService::InactivateContentStorage(StorageId storage_id) { + return impl::InactivateContentStorage(storage_id); + } + + Result ContentManagerService::ActivateContentMetaDatabase(StorageId storage_id) { + return impl::ActivateContentMetaDatabase(storage_id); + } + + Result ContentManagerService::InactivateContentMetaDatabase(StorageId storage_id) { + return impl::InactivateContentMetaDatabase(storage_id); + } + + Result ContentManagerService::InvalidateRightsIdCache() { + return impl::InvalidateRightsIdCache(); + } + +} diff --git a/stratosphere/ncm/source/ncm_content_manager_service.hpp b/stratosphere/ncm/source/ncm_content_manager_service.hpp new file mode 100644 index 000000000..48ddd1c65 --- /dev/null +++ b/stratosphere/ncm/source/ncm_content_manager_service.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "ncm_icontentmetadatabase.hpp" +#include "ncm_icontentstorage.hpp" + +namespace sts::ncm { + + class ContentManagerService final : public IServiceObject { + protected: + enum class CommandId { + CreateContentStorage = 0, + CreateContentMetaDatabase = 1, + VerifyContentStorage = 2, + VerifyContentMetaDatabase = 3, + OpenContentStorage = 4, + OpenContentMetaDatabase = 5, + CloseContentStorageForcibly = 6, + CloseContentMetaDatabaseForcibly = 7, + CleanupContentMetaDatabase = 8, + ActivateContentStorage = 9, + InactivateContentStorage = 10, + ActivateContentMetaDatabase = 11, + InactivateContentMetaDatabase = 12, + InvalidateRightsIdCache = 13, + }; + public: + virtual Result CreateContentStorage(StorageId storage_id); + virtual Result CreateContentMetaDatabase(StorageId storage_id); + virtual Result VerifyContentStorage(StorageId storage_id); + virtual Result VerifyContentMetaDatabase(StorageId storage_id); + virtual Result OpenContentStorage(Out> out, StorageId storage_id); + virtual Result OpenContentMetaDatabase(Out> out, StorageId storage_id); + virtual Result CloseContentStorageForcibly(StorageId storage_id); + virtual Result CloseContentMetaDatabaseForcibly(StorageId storage_id); + virtual Result CleanupContentMetaDatabase(StorageId storage_id); + virtual Result ActivateContentStorage(StorageId storage_id); + virtual Result InactivateContentStorage(StorageId storage_id); + virtual Result ActivateContentMetaDatabase(StorageId storage_id); + virtual Result InactivateContentMetaDatabase(StorageId storage_id); + virtual Result InvalidateRightsIdCache(); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(ContentManagerService, CreateContentStorage), + MAKE_SERVICE_COMMAND_META(ContentManagerService, CreateContentMetaDatabase), + MAKE_SERVICE_COMMAND_META(ContentManagerService, VerifyContentStorage), + MAKE_SERVICE_COMMAND_META(ContentManagerService, VerifyContentMetaDatabase), + MAKE_SERVICE_COMMAND_META(ContentManagerService, OpenContentStorage), + MAKE_SERVICE_COMMAND_META(ContentManagerService, OpenContentMetaDatabase), + MAKE_SERVICE_COMMAND_META(ContentManagerService, CloseContentStorageForcibly, FirmwareVersion_100, FirmwareVersion_100), + MAKE_SERVICE_COMMAND_META(ContentManagerService, CloseContentMetaDatabaseForcibly, FirmwareVersion_100, FirmwareVersion_100), + MAKE_SERVICE_COMMAND_META(ContentManagerService, CleanupContentMetaDatabase), + MAKE_SERVICE_COMMAND_META(ContentManagerService, ActivateContentStorage, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentManagerService, InactivateContentStorage, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentManagerService, ActivateContentMetaDatabase, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentManagerService, InactivateContentMetaDatabase, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentManagerService, InvalidateRightsIdCache, FirmwareVersion_900), + }; + }; + +} diff --git a/stratosphere/ncm/source/ncm_contentmetadatabase.cpp b/stratosphere/ncm/source/ncm_contentmetadatabase.cpp new file mode 100644 index 000000000..beccb2cc1 --- /dev/null +++ b/stratosphere/ncm/source/ncm_contentmetadatabase.cpp @@ -0,0 +1,620 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ncm_contentmetadatabase.hpp" +#include "ncm_utils.hpp" + +namespace sts::ncm { + + namespace { + + struct ContentMetaHeader { + u16 extended_header_size; + u16 content_count; + u16 content_meta_count; + ContentMetaAttribute attributes; + StorageId storage_id; + }; + + static_assert(sizeof(ContentMetaHeader) == 0x8, "ContentMetaHeader definition!"); + + struct ApplicationMetaExtendedHeader { + TitleId patch_id; + u32 required_system_version; + u32 required_application_version; + }; + + struct PatchMetaExtendedHeader { + TitleId application_id; + u32 required_system_version; + u32 extended_data_size; + u8 reserved[0x8]; + }; + + struct AddOnContentMetaExtendedHeader { + TitleId application_id; + u32 required_application_version; + u32 padding; + }; + + struct SystemUpdateMetaExtendedHeader { + u32 extended_data_size; + }; + + inline const ContentMetaHeader* GetValueHeader(const void* value) { + return reinterpret_cast(value); + } + + template + inline const ExtendedHeaderType* GetValueExtendedHeader(const void* value) { + return reinterpret_cast(reinterpret_cast(value) + sizeof(ContentMetaHeader)); + } + + inline const ContentInfo* GetValueContentInfos(const void* value) { + return reinterpret_cast(reinterpret_cast(value) + sizeof(ContentMetaHeader) + GetValueHeader(value)->extended_header_size); + } + + inline const ContentMetaInfo* GetValueContentMetaInfos(const void* value) { + auto header = GetValueHeader(value); + return reinterpret_cast(reinterpret_cast(GetValueContentInfos(value)) + sizeof(ContentInfo) * header->content_count); + } + + Result GetContentMetaSize(size_t *out, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore *kvs) { + R_TRY_CATCH(kvs->GetValueSize(out, key)) { + R_CATCH(ResultKvdbKeyNotFound) { + return ResultNcmContentMetaNotFound; + } + } R_END_TRY_CATCH; + + return ResultSuccess; + } + + Result GetContentMetaValuePointer(const void **out_value_ptr, size_t *out_size, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore *kvs) { + R_TRY(GetContentMetaSize(out_size, key, kvs)); + R_TRY(kvs->GetValuePointer(out_value_ptr, key)); + return ResultSuccess; + } + + } + + Result ContentMetaDatabaseInterface::GetContentIdByTypeImpl(ContentId* out, const ContentMetaKey& key, ContentType type, std::optional id_offset) { + R_TRY(this->EnsureEnabled()); + + const auto it = this->kvs->lower_bound(key); + if (it == this->kvs->end() || it->GetKey().id != key.id) { + return ResultNcmContentMetaNotFound; + } + + const auto stored_key = it->GetKey(); + const void* value = nullptr; + size_t value_size = 0; + + R_TRY(GetContentMetaValuePointer(&value, &value_size, stored_key, this->kvs)); + const auto header = GetValueHeader(value); + + if (header->content_count == 0) { + return ResultNcmContentNotFound; + } + + const ContentInfo* content_infos = GetValueContentInfos(value); + const ContentInfo* found_content_info = nullptr; + + if (id_offset) { + for (size_t i = 0; i < header->content_count; i++) { + const ContentInfo* content_info = &content_infos[i]; + + if (content_info->content_type == type && content_info->id_offset == *id_offset) { + found_content_info = content_info; + break; + } + } + } else { + const ContentInfo* lowest_id_offset_info = nullptr; + + for (size_t i = 0; i < header->content_count; i++) { + const ContentInfo* content_info = &content_infos[i]; + + if (content_info->content_type == type && (!lowest_id_offset_info || lowest_id_offset_info->id_offset > content_info->id_offset)) { + lowest_id_offset_info = content_info; + } + } + + found_content_info = lowest_id_offset_info; + } + + if (!found_content_info) { + return ResultNcmContentNotFound; + } + + *out = found_content_info->content_id; + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetLatestContentMetaKeyImpl(ContentMetaKey* out_key, TitleId tid) { + R_TRY(this->EnsureEnabled()); + + ContentMetaKey key = {0}; + key.id = tid; + + bool found_key = false; + for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) { + if (entry->GetKey().id != key.id) { + break; + } + + if (entry->GetKey().install_type == ContentInstallType::Full) { + key = entry->GetKey(); + found_key = true; + } + } + + if (!found_key) { + return ResultNcmContentMetaNotFound; + } + + *out_key = key; + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::Set(ContentMetaKey key, InBuffer value) { + R_TRY(this->EnsureEnabled()); + R_TRY(this->kvs->Set(key, value.buffer, value.num_elements)); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::Get(Out out_size, ContentMetaKey key, OutBuffer out_value) { + R_TRY(this->EnsureEnabled()); + R_TRY(this->kvs->Get(out_size.GetPointer(), out_value.buffer, out_value.num_elements, key)); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::Remove(ContentMetaKey key) { + R_TRY(this->EnsureEnabled()); + R_TRY(this->kvs->Remove(key)); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetContentIdByType(Out out_content_id, ContentMetaKey key, ContentType type) { + ContentId content_id; + R_TRY(this->GetContentIdByTypeImpl(&content_id, key, type, std::nullopt)); + out_content_id.SetValue(content_id); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::ListContentInfo(Out out_count, OutBuffer out_info, ContentMetaKey key, u32 offset) { + if (offset >> 0x1f != 0) { + return ResultNcmInvalidOffset; + } + + R_TRY(this->EnsureEnabled()); + const void *value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + const auto header = GetValueHeader(value); + const auto content_infos = GetValueContentInfos(value); + + size_t count; + for (count = 0; offset + count < header->content_count && count < out_info.num_elements; count++) { + out_info[count] = content_infos[offset + count]; + } + + out_count.SetValue(count); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::List(Out out_entries_total, Out out_entries_written, OutBuffer out_info, ContentMetaType type, TitleId application_title_id, TitleId title_id_min, TitleId title_id_max, ContentInstallType install_type) { + R_TRY(this->EnsureEnabled()); + + size_t entries_total = 0; + size_t entries_written = 0; + + /* If there are no entries then we've already successfully listed them all. */ + if (this->kvs->GetCount() == 0) { + out_entries_total.SetValue(entries_total); + out_entries_written.SetValue(entries_written); + return ResultSuccess; + } + + for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) { + ContentMetaKey key = entry->GetKey(); + + /* Check if this entry matches the given filters. */ + if (!((static_cast(type) == 0 || key.type == type) && (title_id_min <= key.id && key.id <= title_id_max) && (key.install_type == ContentInstallType::Unknown || key.install_type == install_type))) { + continue; + } + + if (static_cast(application_title_id) != 0) { + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + + /* Each of these types are owned by an application. We need to check if their owner application matches the filter. */ + if (key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent || key.type == ContentMetaType::Delta) { + TitleId entry_application_tid = key.id; + + switch (key.type) { + case ContentMetaType::Application: + break; + default: + /* The first u64 of all non-application extended headers is the application title id. */ + entry_application_tid = *GetValueExtendedHeader(value); + } + + /* Application tid doesn't match filter, skip this entry. */ + if (entry_application_tid != application_title_id) { + continue; + } + } + } + + /* Write the entry to the output buffer. */ + if (entries_written < out_info.num_elements) { + out_info[entries_written] = key; + entries_written++; + } + + entries_total++; + } + + out_entries_total.SetValue(entries_total); + out_entries_written.SetValue(entries_written); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetLatestContentMetaKey(Out out_key, TitleId title_id) { + R_TRY(this->EnsureEnabled()); + ContentMetaKey key; + R_TRY(this->GetLatestContentMetaKeyImpl(&key, title_id)); + out_key.SetValue(key); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::ListApplication(Out out_entries_total, Out out_entries_written, OutBuffer out_keys, ContentMetaType type) { + R_TRY(this->EnsureEnabled()); + + size_t entries_total = 0; + size_t entries_written = 0; + + /* If there are no entries then we've already successfully listed them all. */ + if (this->kvs->GetCount() == 0) { + out_entries_total.SetValue(entries_total); + out_entries_written.SetValue(entries_written); + return ResultSuccess; + } + + for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) { + ContentMetaKey key = entry->GetKey(); + + /* Check if this entry matches the given filters. */ + if (!((static_cast(type) == 0 || key.type == type))) { + continue; + } + + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + + if (key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent || key.type == ContentMetaType::Delta) { + TitleId application_tid = key.id; + + switch (key.type) { + case ContentMetaType::Application: + break; + default: + /* The first u64 of all non-application extended headers is the application title id. */ + application_tid = *GetValueExtendedHeader(value); + } + + /* Write the entry to the output buffer. */ + if (entries_written < out_keys.num_elements) { + ApplicationContentMetaKey* out_app_key = &out_keys[entries_written]; + out_app_key->application_title_id = application_tid; + out_app_key->key = key; + entries_written++; + } + + entries_total++; + } + } + + out_entries_total.SetValue(entries_total); + out_entries_written.SetValue(entries_written); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::Has(Out out, ContentMetaKey key) { + R_TRY(this->EnsureEnabled()); + + if (this->kvs->GetCount() == 0) { + out.SetValue(false); + return ResultSuccess; + } + + bool has = false; + const auto it = this->kvs->lower_bound(key); + if (it != this->kvs->end()) { + has = it->GetKey() == key; + } + + out.SetValue(has); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::HasAll(Out out, InBuffer keys) { + R_TRY(this->EnsureEnabled()); + + bool has = true; + for (size_t i = 0; i < keys.num_elements && has; i++) { + R_TRY(this->Has(&has, keys[i])); + } + + out.SetValue(has); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetSize(Out out_size, ContentMetaKey key) { + R_TRY(this->EnsureEnabled()); + + if (this->kvs->GetCount() == 0) { + return ResultNcmContentMetaNotFound; + } + + const auto it = this->kvs->lower_bound(key); + if (it == this->kvs->end() || it->GetKey() != key) { + return ResultNcmContentMetaNotFound; + } + + out_size.SetValue(it->GetValueSize()); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetRequiredSystemVersion(Out out_version, ContentMetaKey key) { + R_TRY(this->EnsureEnabled()); + + if (key.type != ContentMetaType::Application && key.type != ContentMetaType::Patch) { + return ResultNcmInvalidContentMetaKey; + } + + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + + /* Required system version is at the same offset for the patch and application extended header. + We use the application header for convenience. */ + const auto ext_header = GetValueExtendedHeader(value); + out_version.SetValue(ext_header->required_system_version); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetPatchId(Out out_patch_id, ContentMetaKey key) { + R_TRY(this->EnsureEnabled()); + + if (key.type != ContentMetaType::Application) { + return ResultNcmInvalidContentMetaKey; + } + + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + const auto ext_header = GetValueExtendedHeader(value); + + out_patch_id.SetValue(ext_header->patch_id); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::DisableForcibly() { + this->disabled = true; + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::LookupOrphanContent(OutBuffer out_orphaned, InBuffer content_ids) { + R_TRY(this->EnsureEnabled()); + + if (out_orphaned.num_elements < content_ids.num_elements) { + return ResultNcmBufferInsufficient; + } + + /* Default to orphaned for all content ids. */ + if (out_orphaned.num_elements > 0) { + std::fill_n(out_orphaned.buffer, out_orphaned.num_elements, true); + } + + if (this->kvs->GetCount() == 0) { + return ResultSuccess; + } + + for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) { + const auto value = entry->GetValuePointer(); + const auto header = GetValueHeader(value); + + if (header->content_count == 0) { + continue; + } + + if (content_ids.num_elements == 0) { + continue; + } + + const ContentInfo* content_infos = GetValueContentInfos(value); + for (size_t i = 0; i < header->content_count; i++) { + const ContentInfo* content_info = &content_infos[i]; + + /* Check if any of this entry's content infos matches one of the provided content ids. + If they do, then the content id isn't orphaned. */ + for (size_t j = 0; j < content_ids.num_elements; j++) { + const ContentId content_id = content_ids[j]; + + if (content_id == content_info->content_id) { + out_orphaned[j] = false; + break; + } + } + } + } + + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::Commit() { + R_TRY(this->EnsureEnabled()); + R_TRY(this->kvs->Save()); + R_TRY(fsdevCommitDevice(this->mount_name)); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::HasContent(Out out, ContentMetaKey key, ContentId content_id) { + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + const auto header = GetValueHeader(value); + const ContentInfo* content_infos = GetValueContentInfos(value); + + if (header->content_count > 0) { + for (size_t i = 0; i < header->content_count; i++) { + const ContentInfo* content_info = &content_infos[i]; + + if (content_id == content_info->content_id) { + out.SetValue(false); + return ResultSuccess; + } + } + } + + out.SetValue(false); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::ListContentMetaInfo(Out out_entries_written, OutBuffer out_meta_info, ContentMetaKey key, u32 start_index) { + if (start_index >> 0x1f != 0) { + return ResultNcmInvalidOffset; + } + + R_TRY(this->EnsureEnabled()); + + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + const auto header = GetValueHeader(value); + const auto content_meta_infos = GetValueContentMetaInfos(value); + size_t entries_written = 0; + + if (out_meta_info.num_elements == 0) { + out_entries_written.SetValue(0); + return ResultSuccess; + } + + for (size_t i = start_index; i < out_meta_info.num_elements; i++) { + /* We have no more entries we can read out. */ + if (header->content_meta_count <= start_index + i) { + break; + } + + out_meta_info[i] = content_meta_infos[i]; + entries_written = i + 1; + } + + out_entries_written.SetValue(entries_written); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetAttributes(Out out_attributes, ContentMetaKey key) { + R_TRY(this->EnsureEnabled()); + + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + const auto header = GetValueHeader(value); + out_attributes.SetValue(header->attributes); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetRequiredApplicationVersion(Out out_version, ContentMetaKey key) { + R_TRY(this->EnsureEnabled()); + + const void* value = nullptr; + size_t value_size = 0; + R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs)); + + /* As of 9.0.0, applications can be dependent on a specific base application version. */ + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_900 && key.type == ContentMetaType::Application) { + const auto ext_header = GetValueExtendedHeader(value); + out_version.SetValue(ext_header->required_application_version); + return ResultSuccess; + } + + if (key.type != ContentMetaType::AddOnContent) { + return ResultNcmInvalidContentMetaKey; + } + const auto ext_header = GetValueExtendedHeader(value); + out_version.SetValue(ext_header->required_application_version); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetContentIdByTypeAndIdOffset(Out out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) { + ContentId content_id; + R_TRY(this->GetContentIdByTypeImpl(&content_id, key, type, std::optional(id_offset))); + out_content_id.SetValue(content_id); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetLatestProgram(ContentId* out_content_id, TitleId title_id) { + ContentMetaKey key; + + R_TRY(this->GetLatestContentMetaKey(&key, title_id)); + R_TRY(this->GetContentIdByType(out_content_id, key, ContentType::Program)); + return ResultSuccess; + } + + Result ContentMetaDatabaseInterface::GetLatestData(ContentId* out_content_id, TitleId title_id) { + ContentMetaKey key; + + R_TRY(this->GetLatestContentMetaKey(&key, title_id)); + R_TRY(this->GetContentIdByType(out_content_id, key, ContentType::Data)); + return ResultSuccess; + } + + Result OnMemoryContentMetaDatabaseInterface::GetLatestContentMetaKey(Out out_key, TitleId title_id) { + R_TRY(this->EnsureEnabled()); + + const ContentMetaKey key = ContentMetaKey::Make(title_id, 0, ContentMetaType::Unknown); + + std::optional found_key; + for (auto entry = this->kvs->lower_bound(key); entry != this->kvs->end(); entry++) { + if (entry->GetKey().id != title_id) { + break; + } + + found_key = entry->GetKey(); + } + + if (!found_key) { + return ResultNcmContentMetaNotFound; + } + + *out_key = *found_key; + return ResultSuccess; + } + + Result OnMemoryContentMetaDatabaseInterface::LookupOrphanContent(OutBuffer out_orphaned, InBuffer content_ids) { + return ResultNcmInvalidContentMetaDatabase; + } + + Result OnMemoryContentMetaDatabaseInterface::Commit() { + R_TRY(this->EnsureEnabled()); + return ResultSuccess; + } + +} diff --git a/stratosphere/ncm/source/ncm_contentmetadatabase.hpp b/stratosphere/ncm/source/ncm_contentmetadatabase.hpp new file mode 100644 index 000000000..1ec7ef2b8 --- /dev/null +++ b/stratosphere/ncm/source/ncm_contentmetadatabase.hpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "ncm_icontentmetadatabase.hpp" + +namespace sts::ncm { + + class ContentMetaDatabaseInterface : public IContentMetaDatabase { + public: + ContentMetaDatabaseInterface(sts::kvdb::MemoryKeyValueStore* kvs, const char* mount_name) : IContentMetaDatabase(kvs, mount_name) { + } + ContentMetaDatabaseInterface(sts::kvdb::MemoryKeyValueStore* kvs) : IContentMetaDatabase(kvs) { + } + private: + Result GetContentIdByTypeImpl(ContentId* out, const ContentMetaKey& key, ContentType type, std::optional id_offset); + Result GetLatestContentMetaKeyImpl(ContentMetaKey* out_key, TitleId tid); + public: + virtual Result Set(ContentMetaKey key, InBuffer value) override; + virtual Result Get(Out out_size, ContentMetaKey key, OutBuffer out_value) override; + virtual Result Remove(ContentMetaKey key) override; + virtual Result GetContentIdByType(Out out_content_id, ContentMetaKey key, ContentType type) override; + virtual Result ListContentInfo(Out out_entries_written, OutBuffer out_info, ContentMetaKey key, u32 start_index) override; + virtual Result List(Out out_entries_total, Out out_entries_written, OutBuffer out_info, ContentMetaType type, TitleId application_title_id, TitleId title_id_min, TitleId title_id_max, ContentInstallType install_type) override; + virtual Result GetLatestContentMetaKey(Out out_key, TitleId tid) override; + virtual Result ListApplication(Out out_entries_total, Out out_entries_written, OutBuffer out_keys, ContentMetaType type) override; + virtual Result Has(Out out, ContentMetaKey key) override; + virtual Result HasAll(Out out, InBuffer keys) override; + virtual Result GetSize(Out out_size, ContentMetaKey key) override; + virtual Result GetRequiredSystemVersion(Out out_version, ContentMetaKey key) override; + virtual Result GetPatchId(Out out_patch_id, ContentMetaKey key) override; + virtual Result DisableForcibly() override; + virtual Result LookupOrphanContent(OutBuffer out_orphaned, InBuffer content_ids) override; + virtual Result Commit() override; + virtual Result HasContent(Out out, ContentMetaKey key, ContentId content_id) override; + virtual Result ListContentMetaInfo(Out out_entries_written, OutBuffer out_meta_info, ContentMetaKey key, u32 start_index) override; + virtual Result GetAttributes(Out out_attributes, ContentMetaKey key) override; + virtual Result GetRequiredApplicationVersion(Out out_version, ContentMetaKey key) override; + virtual Result GetContentIdByTypeAndIdOffset(Out out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) override; + + /* APIs. */ + virtual Result GetLatestProgram(ContentId* out_content_id, TitleId title_id) override; + virtual Result GetLatestData(ContentId* out_content_id, TitleId title_id) override; + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, Set), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, Get), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, Remove), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetContentIdByType), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, ListContentInfo), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, List), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetLatestContentMetaKey), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, ListApplication), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, Has), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, HasAll), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetSize), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetRequiredSystemVersion), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetPatchId), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, DisableForcibly), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, LookupOrphanContent), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, Commit), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, HasContent), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, ListContentMetaInfo), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetAttributes), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetRequiredApplicationVersion, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentMetaDatabaseInterface, GetContentIdByTypeAndIdOffset, FirmwareVersion_500), + }; + }; + + class OnMemoryContentMetaDatabaseInterface : public ContentMetaDatabaseInterface { + private: + enum class CommandId { + Set = 0, + Get = 1, + Remove = 2, + GetContentIdByType = 3, + ListContentInfo = 4, + List = 5, + GetLatestContentMetaKey = 6, + ListApplication = 7, + Has = 8, + HasAll = 9, + GetSize = 10, + GetRequiredSystemVersion = 11, + GetPatchId = 12, + DisableForcibly = 13, + LookupOrphanContent = 14, + Commit = 15, + HasContent = 16, + ListContentMetaInfo = 17, + GetAttributes = 18, + GetRequiredApplicationVersion = 19, + GetContentIdByTypeAndIdOffset = 20, + }; + public: + OnMemoryContentMetaDatabaseInterface(sts::kvdb::MemoryKeyValueStore* kvs) : ContentMetaDatabaseInterface(kvs) { + } + public: + virtual Result GetLatestContentMetaKey(Out out_key, TitleId tid) override; + virtual Result LookupOrphanContent(OutBuffer out_orphaned, InBuffer content_ids) override; + virtual Result Commit() override; + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, Set), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, Get), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, Remove), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetContentIdByType), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, ListContentInfo), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, List), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetLatestContentMetaKey), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, ListApplication), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, Has), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, HasAll), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetSize), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetRequiredSystemVersion), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetPatchId), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, DisableForcibly), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, LookupOrphanContent), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, Commit), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, HasContent), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, ListContentMetaInfo), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetAttributes), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetRequiredApplicationVersion, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(OnMemoryContentMetaDatabaseInterface, GetContentIdByTypeAndIdOffset, FirmwareVersion_500), + }; + }; + +} diff --git a/stratosphere/ncm/source/ncm_contentstorage.cpp b/stratosphere/ncm/source/ncm_contentstorage.cpp new file mode 100644 index 000000000..75e44ed4d --- /dev/null +++ b/stratosphere/ncm/source/ncm_contentstorage.cpp @@ -0,0 +1,654 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ncm_contentstorage.hpp" +#include "ncm_fs.hpp" +#include "ncm_make_path.hpp" +#include "ncm_utils.hpp" + +namespace sts::ncm { + + ContentStorageInterface::~ContentStorageInterface() { + this->Finalize(); + } + + Result ContentStorageInterface::Initialize(const char* root_path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush, impl::RightsIdCache* rights_id_cache) { + R_TRY(this->EnsureEnabled()); + R_TRY(fs::CheckContentStorageDirectoriesExist(root_path)); + const size_t root_path_len = strnlen(root_path, FS_MAX_PATH-1); + + if (root_path_len >= FS_MAX_PATH-1) { + std::abort(); + } + + strncpy(this->root_path, root_path, FS_MAX_PATH-2); + this->make_content_path_func = *content_path_func; + this->placeholder_accessor.Initialize(this->root_path, *placeholder_path_func, delay_flush); + this->rights_id_cache = rights_id_cache; + return ResultSuccess; + } + + void ContentStorageInterface::Finalize() { + this->ClearContentCache(); + this->placeholder_accessor.InvalidateAll(); + } + + void ContentStorageInterface::ClearContentCache() { + if (this->cached_content_id != InvalidContentId) { + fclose(this->content_cache_file_handle); + this->cached_content_id = InvalidContentId; + } + } + + unsigned int ContentStorageInterface::GetContentDirectoryDepth() { + if (this->make_content_path_func == static_cast(path::MakeContentPathFlat)) { + return 1; + } else if (this->make_content_path_func == static_cast(path::MakeContentPathHashByteLayered)) { + return 2; + } else if (this->make_content_path_func == static_cast(path::MakeContentPath10BitLayered)) { + return 2; + } else if (this->make_content_path_func == static_cast(path::MakeContentPathDualLayered)) { + return 3; + } + + std::abort(); + } + + Result ContentStorageInterface::OpenCachedContentFile(ContentId content_id) { + if (this->cached_content_id == content_id) { + return ResultSuccess; + } + + this->ClearContentCache(); + char content_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + + R_TRY_CATCH(fs::OpenFile(&this->content_cache_file_handle, content_path, FS_OPEN_READ)) { + R_CATCH(ResultFsPathNotFound) { + return ResultNcmContentNotFound; + } + } R_END_TRY_CATCH; + + this->cached_content_id = content_id; + return ResultSuccess; + } + + Result ContentStorageInterface::GeneratePlaceHolderId(Out out) { + R_TRY(this->EnsureEnabled()); + + sts::rnd::GenerateRandomBytes(out.GetPointer(), sizeof(NcmNcaId)); + char placeholder_str[FS_MAX_PATH] = {0}; + GetStringFromPlaceHolderId(placeholder_str, *out.GetPointer()); + return ResultSuccess; + } + + Result ContentStorageInterface::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) { + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + + R_TRY(fs::EnsureParentDirectoryRecursively(content_path)); + R_TRY(this->placeholder_accessor.Create(placeholder_id, size)); + + return ResultSuccess; + } + + Result ContentStorageInterface::DeletePlaceHolder(PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + return this->placeholder_accessor.Delete(placeholder_id); + } + + Result ContentStorageInterface::HasPlaceHolder(Out out, PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + + char placeholder_path[FS_MAX_PATH] = {0}; + this->placeholder_accessor.MakePath(placeholder_path, placeholder_id); + + bool has = false; + R_TRY(fs::HasFile(&has, placeholder_path)); + out.SetValue(has); + + return ResultSuccess; + } + + Result ContentStorageInterface::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer data) { + /* Offset is too large */ + if (offset >> 0x3f != 0) { + return ResultNcmInvalidOffset; + } + + R_TRY(this->EnsureEnabled()); + R_TRY(this->placeholder_accessor.Write(placeholder_id, offset, data.buffer, data.num_elements)); + return ResultSuccess; + } + + Result ContentStorageInterface::Register(PlaceHolderId placeholder_id, ContentId content_id) { + this->ClearContentCache(); + R_TRY(this->EnsureEnabled()); + + char placeholder_path[FS_MAX_PATH] = {0}; + char content_path[FS_MAX_PATH] = {0}; + + this->placeholder_accessor.GetPath(placeholder_path, placeholder_id); + this->GetContentPath(content_path, content_id); + + if (rename(placeholder_path, content_path) != 0) { + R_TRY_CATCH(fsdevGetLastResult()) { + R_CATCH(ResultFsPathNotFound) { + return ResultNcmPlaceHolderNotFound; + } + R_CATCH(ResultFsPathAlreadyExists) { + return ResultNcmContentAlreadyExists; + } + } R_END_TRY_CATCH; + } + + return ResultSuccess; + } + + Result ContentStorageInterface::Delete(ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + this->ClearContentCache(); + char content_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + + if (std::remove(content_path) != 0) { + R_TRY_CATCH(fsdevGetLastResult()) { + R_CATCH(ResultFsPathNotFound) { + return ResultNcmContentNotFound; + } + } R_END_TRY_CATCH; + } + + return ResultSuccess; + } + + Result ContentStorageInterface::Has(Out out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + + bool has = false; + R_TRY(fs::HasFile(&has, content_path)); + out.SetValue(has); + + return ResultSuccess; + } + + Result ContentStorageInterface::GetPath(OutPointerWithServerSize out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + char common_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path)); + *out.pointer = common_path; + return ResultSuccess; + } + + Result ContentStorageInterface::GetPlaceHolderPath(OutPointerWithServerSize out, PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + + char placeholder_path[FS_MAX_PATH] = {0}; + char common_path[FS_MAX_PATH] = {0}; + this->placeholder_accessor.GetPath(placeholder_path, placeholder_id); + R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path)); + *out.pointer = common_path; + return ResultSuccess; + } + + Result ContentStorageInterface::CleanupAllPlaceHolder() { + R_TRY(this->EnsureEnabled()); + + char placeholder_root_path[FS_MAX_PATH] = {0}; + this->placeholder_accessor.InvalidateAll(); + this->placeholder_accessor.MakeRootPath(placeholder_root_path); + + /* Nintendo uses CleanDirectoryRecursively which is 3.0.0+. + We'll just delete the directory and recreate it to support all firmwares. */ + R_TRY(fsdevDeleteDirectoryRecursively(placeholder_root_path)); + + if (mkdir(placeholder_root_path, S_IRWXU) == -1) { + return fsdevGetLastResult(); + } + + return ResultSuccess; + } + + Result ContentStorageInterface::ListPlaceHolder(Out out_count, OutBuffer out_buf) { + R_TRY(this->EnsureEnabled()); + + char placeholder_root_path[FS_MAX_PATH] = {0}; + this->placeholder_accessor.MakeRootPath(placeholder_root_path); + const unsigned int dir_depth = this->placeholder_accessor.GetDirectoryDepth(); + size_t entry_count = 0; + + R_TRY(fs::TraverseDirectory(placeholder_root_path, dir_depth, [&](bool* should_continue, bool* should_retry_dir_read, const char* current_path, struct dirent* dir_entry) { + *should_continue = true; + *should_retry_dir_read = false; + + if (dir_entry->d_type == DT_REG) { + if (entry_count > out_buf.num_elements) { + return ResultNcmBufferInsufficient; + } + + PlaceHolderId cur_entry_placeholder_id = {0}; + R_TRY(GetPlaceHolderIdFromDirEntry(&cur_entry_placeholder_id, dir_entry)); + out_buf[entry_count++] = cur_entry_placeholder_id; + } + + return ResultSuccess; + })); + + out_count.SetValue(static_cast(entry_count)); + return ResultSuccess; + } + + Result ContentStorageInterface::GetContentCount(Out out_count) { + R_TRY(this->EnsureEnabled()); + + char content_root_path[FS_MAX_PATH] = {0}; + this->GetContentRootPath(content_root_path); + const unsigned int dir_depth = this->GetContentDirectoryDepth(); + u32 content_count = 0; + + R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, [&](bool* should_continue, bool* should_retry_dir_read, const char* current_path, struct dirent* dir_entry) { + *should_continue = true; + *should_retry_dir_read = false; + + if (dir_entry->d_type == DT_REG) { + content_count++; + } + + return ResultSuccess; + })); + + out_count.SetValue(content_count); + return ResultSuccess; + } + + Result ContentStorageInterface::ListContentId(Out out_count, OutBuffer out_buf, u32 start_offset) { + if (start_offset >> 0x1f != 0) { + return ResultNcmInvalidOffset; + } + + R_TRY(this->EnsureEnabled()); + + char content_root_path[FS_MAX_PATH] = {0}; + this->GetContentRootPath(content_root_path); + const unsigned int dir_depth = this->GetContentDirectoryDepth(); + size_t entry_count = 0; + + R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, [&](bool* should_continue, bool* should_retry_dir_read, const char* current_path, struct dirent* dir_entry) { + *should_retry_dir_read = false; + *should_continue = true; + + if (dir_entry->d_type == DT_REG) { + /* Skip entries until we reach the start offset. */ + if (start_offset > 0) { + start_offset--; + return ResultSuccess; + } + + /* We don't necessarily expect to be able to completely fill the output buffer. */ + if (entry_count > out_buf.num_elements) { + *should_continue = false; + return ResultSuccess; + } + + size_t name_len = strlen(dir_entry->d_name); + std::optional content_id = GetContentIdFromString(dir_entry->d_name, name_len); + + /* Skip to the next entry if the id was invalid. */ + if (!content_id) { + return ResultSuccess; + } + + out_buf[entry_count++] = *content_id; + } + + return ResultSuccess; + })); + + for (size_t i = 0; i < entry_count; i++) { + char content_name[sizeof(ContentId)*2+1] = {0}; + GetStringFromContentId(content_name, out_buf[i]); + } + + out_count.SetValue(static_cast(entry_count)); + return ResultSuccess; + } + + Result ContentStorageInterface::GetSizeFromContentId(Out out_size, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + struct stat st; + + if (stat(content_path, &st) == -1) { + return fsdevGetLastResult(); + } + + out_size.SetValue(st.st_size); + return ResultSuccess; + } + + Result ContentStorageInterface::DisableForcibly() { + this->disabled = true; + this->ClearContentCache(); + this->placeholder_accessor.InvalidateAll(); + return ResultSuccess; + } + + Result ContentStorageInterface::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + R_TRY(this->EnsureEnabled()); + + char old_content_path[FS_MAX_PATH] = {0}; + char new_content_path[FS_MAX_PATH] = {0}; + char placeholder_path[FS_MAX_PATH] = {0}; + + this->ClearContentCache(); + + /* Ensure the new content path is ready. */ + this->GetContentPath(new_content_path, new_content_id); + R_TRY(fs::EnsureParentDirectoryRecursively(new_content_path)); + + R_TRY(this->placeholder_accessor.EnsureRecursively(placeholder_id)); + this->placeholder_accessor.GetPath(placeholder_path, placeholder_id); + if (rename(old_content_path, placeholder_path) != 0) { + R_TRY_CATCH(fsdevGetLastResult()) { + R_CATCH(ResultFsPathNotFound) { + return ResultNcmPlaceHolderNotFound; + } + R_CATCH(ResultFsPathAlreadyExists) { + return ResultNcmPlaceHolderAlreadyExists; + } + } R_END_TRY_CATCH; + } + + return ResultSuccess; + } + + Result ContentStorageInterface::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) { + R_TRY(this->EnsureEnabled()); + R_TRY(this->placeholder_accessor.SetSize(placeholder_id, size)); + return ResultSuccess; + } + + Result ContentStorageInterface::ReadContentIdFile(OutBuffer buf, ContentId content_id, u64 offset) { + /* Offset is too large */ + if (offset >> 0x3f != 0) { + return ResultNcmInvalidOffset; + } + + R_TRY(this->EnsureEnabled()); + char content_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + R_TRY(this->OpenCachedContentFile(content_id)); + R_TRY(fs::ReadFile(this->content_cache_file_handle, offset, buf.buffer, buf.num_elements)); + + return ResultSuccess; + } + + Result ContentStorageInterface::GetRightsIdFromPlaceHolderId(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + + FsRightsId rights_id = {0}; + u8 key_generation = 0; + + char placeholder_path[FS_MAX_PATH] = {0}; + char common_path[FS_MAX_PATH] = {0}; + this->placeholder_accessor.GetPath(placeholder_path, placeholder_id); + R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path)); + R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id)); + + out_rights_id.SetValue(rights_id); + out_key_generation.SetValue(static_cast(key_generation)); + + return ResultSuccess; + } + + Result ContentStorageInterface::GetRightsIdFromContentId(Out out_rights_id, Out out_key_generation, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + { + std::scoped_lock lk(this->rights_id_cache->mutex); + + /* Attempt to locate the content id in the cache. */ + for (size_t i = 0; i < impl::RightsIdCache::MaxEntries; i++) { + impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i]; + + if (entry->last_accessed != 1 && content_id == entry->uuid) { + entry->last_accessed = this->rights_id_cache->counter; + this->rights_id_cache->counter++; + out_rights_id.SetValue(entry->rights_id); + out_key_generation.SetValue(entry->key_generation); + return ResultSuccess; + } + } + } + + FsRightsId rights_id = {0}; + u8 key_generation = 0; + char content_path[FS_MAX_PATH] = {0}; + char common_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path)); + R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id)); + + { + std::scoped_lock lk(this->rights_id_cache->mutex); + impl::RightsIdCache::Entry* eviction_candidate = &this->rights_id_cache->entries[0]; + + /* Find a suitable existing entry to store our new one at. */ + for (size_t i = 1; i < impl::RightsIdCache::MaxEntries; i++) { + impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i]; + + /* Change eviction candidates if the uuid already matches ours, or if the uuid doesn't already match and the last_accessed count is lower */ + if (content_id == entry->uuid || (content_id != eviction_candidate->uuid && entry->last_accessed < eviction_candidate->last_accessed)) { + eviction_candidate = entry; + } + } + + /* Update the cache. */ + eviction_candidate->uuid = content_id.uuid; + eviction_candidate->rights_id = rights_id; + eviction_candidate->key_generation = key_generation; + eviction_candidate->last_accessed = this->rights_id_cache->counter; + this->rights_id_cache->counter++; + + /* Set output. */ + out_rights_id.SetValue(rights_id); + out_key_generation.SetValue(key_generation); + } + + return ResultSuccess; + } + + Result ContentStorageInterface::WriteContentForDebug(ContentId content_id, u64 offset, InBuffer data) { + /* Offset is too large */ + if (offset >> 0x3f != 0) { + return ResultNcmInvalidOffset; + } + + R_TRY(this->EnsureEnabled()); + + bool is_development = false; + + if (R_FAILED(splIsDevelopment(&is_development)) || !is_development) { + std::abort(); + } + + this->ClearContentCache(); + + char content_path[FS_MAX_PATH] = {0}; + this->GetContentPath(content_path, content_id); + + FILE* f = nullptr; + R_TRY(fs::OpenFile(&f, content_path, FS_OPEN_WRITE)); + + ON_SCOPE_EXIT { + fclose(f); + }; + + R_TRY(fs::WriteFile(f, offset, data.buffer, data.num_elements, FS_WRITEOPTION_FLUSH)); + + return ResultSuccess; + } + + Result ContentStorageInterface::GetFreeSpaceSize(Out out_size) { + struct statvfs st = {0}; + if (statvfs(this->root_path, &st) == -1) { + return fsdevGetLastResult(); + } + + out_size.SetValue(st.f_bfree); + return ResultSuccess; + } + + Result ContentStorageInterface::GetTotalSpaceSize(Out out_size) { + struct statvfs st = {0}; + if (statvfs(this->root_path, &st) == -1) { + return fsdevGetLastResult(); + } + + out_size.SetValue(st.f_blocks); + return ResultSuccess; + } + + Result ContentStorageInterface::FlushPlaceHolder() { + this->placeholder_accessor.InvalidateAll(); + return ResultSuccess; + } + + Result ContentStorageInterface::GetSizeFromPlaceHolderId(Out out_size, PlaceHolderId placeholder_id) { + R_TRY(this->EnsureEnabled()); + + bool found_in_cache = false; + size_t size = 0; + + R_TRY(this->placeholder_accessor.GetSize(&found_in_cache, &size, placeholder_id)); + + if (found_in_cache) { + out_size.SetValue(size); + return ResultSuccess; + } + + char placeholder_path[FS_MAX_PATH] = {0}; + struct stat st; + + this->placeholder_accessor.GetPath(placeholder_path, placeholder_id); + if (stat(placeholder_path, &st) == -1) { + return fsdevGetLastResult(); + } + + out_size.SetValue(st.st_size); + return ResultSuccess; + } + + Result ContentStorageInterface::RepairInvalidFileAttribute() { + char content_root_path[FS_MAX_PATH] = {0}; + this->GetContentRootPath(content_root_path); + unsigned int dir_depth = this->GetContentDirectoryDepth(); + auto fix_file_attributes = [&](bool* should_continue, bool* should_retry_dir_read, const char* current_path, struct dirent* dir_entry) { + *should_retry_dir_read = false; + *should_continue = true; + + if (dir_entry->d_type == DT_DIR) { + if (path::IsNcaPath(current_path)) { + if (R_SUCCEEDED(fsdevSetArchiveBit(current_path))) { + *should_retry_dir_read = true; + } + } + } + + return ResultSuccess; + }; + + R_TRY(fs::TraverseDirectory(content_root_path, dir_depth, fix_file_attributes)); + + char placeholder_root_path[FS_MAX_PATH] = {0}; + this->placeholder_accessor.InvalidateAll(); + this->placeholder_accessor.MakeRootPath(placeholder_root_path); + dir_depth = this->placeholder_accessor.GetDirectoryDepth(); + + R_TRY(fs::TraverseDirectory(placeholder_root_path, dir_depth, fix_file_attributes)); + + return ResultSuccess; + } + + Result ContentStorageInterface::GetRightsIdFromPlaceHolderIdWithCache(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) { + R_TRY(this->EnsureEnabled()); + + { + std::scoped_lock lk(this->rights_id_cache->mutex); + + /* Attempt to locate the content id in the cache. */ + for (size_t i = 0; i < impl::RightsIdCache::MaxEntries; i++) { + impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i]; + + if (entry->last_accessed != 1 && cache_content_id == entry->uuid) { + entry->last_accessed = this->rights_id_cache->counter; + this->rights_id_cache->counter++; + out_rights_id.SetValue(entry->rights_id); + out_key_generation.SetValue(entry->key_generation); + return ResultSuccess; + } + } + } + + FsRightsId rights_id = {0}; + u8 key_generation = 0; + char placeholder_path[FS_MAX_PATH] = {0}; + char common_path[FS_MAX_PATH] = {0}; + this->placeholder_accessor.GetPath(placeholder_path, placeholder_id); + R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path)); + R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id)); + + { + std::scoped_lock lk(this->rights_id_cache->mutex); + impl::RightsIdCache::Entry* eviction_candidate = &this->rights_id_cache->entries[0]; + + /* Find a suitable existing entry to store our new one at. */ + for (size_t i = 1; i < impl::RightsIdCache::MaxEntries; i++) { + impl::RightsIdCache::Entry* entry = &this->rights_id_cache->entries[i]; + + /* Change eviction candidates if the uuid already matches ours, or if the uuid doesn't already match and the last_accessed count is lower */ + if (cache_content_id == entry->uuid || (cache_content_id != eviction_candidate->uuid && entry->last_accessed < eviction_candidate->last_accessed)) { + eviction_candidate = entry; + } + } + + /* Update the cache. */ + eviction_candidate->uuid = cache_content_id.uuid; + eviction_candidate->rights_id = rights_id; + eviction_candidate->key_generation = key_generation; + eviction_candidate->last_accessed = this->rights_id_cache->counter; + this->rights_id_cache->counter++; + + /* Set output. */ + out_rights_id.SetValue(rights_id); + out_key_generation.SetValue(key_generation); + } + + return ResultSuccess; + } + +} diff --git a/stratosphere/ncm/source/ncm_contentstorage.hpp b/stratosphere/ncm/source/ncm_contentstorage.hpp new file mode 100644 index 000000000..bd0939187 --- /dev/null +++ b/stratosphere/ncm/source/ncm_contentstorage.hpp @@ -0,0 +1,117 @@ +/* + * 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/ncm_placeholder_accessor.hpp" +#include "impl/ncm_rights_cache.hpp" +#include "ncm_icontentstorage.hpp" +#include "ncm_path_utils.hpp" + +namespace sts::ncm { + + class ContentStorageInterface : public IContentStorage { + protected: + impl::PlaceHolderAccessor placeholder_accessor; + ContentId cached_content_id; + FILE* content_cache_file_handle; + impl::RightsIdCache* rights_id_cache; + public: + ~ContentStorageInterface(); + + Result Initialize(const char* root_path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush, impl::RightsIdCache* rights_id_cache); + void Finalize(); + private: + void ClearContentCache(); + unsigned int GetContentDirectoryDepth(); + Result OpenCachedContentFile(ContentId content_id); + + inline void GetContentRootPath(char* out_content_root) { + path::GetContentRootPath(out_content_root, this->root_path); + } + + inline void GetContentPath(char* out_content_path, ContentId content_id) { + char content_root_path[FS_MAX_PATH] = {0}; + + this->GetContentRootPath(content_root_path); + this->make_content_path_func(out_content_path, content_id, content_root_path); + } + public: + virtual Result GeneratePlaceHolderId(Out out) override; + virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override; + virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override; + virtual Result HasPlaceHolder(Out out, PlaceHolderId placeholder_id) override; + virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer data) override; + virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override; + virtual Result Delete(ContentId content_id) override; + virtual Result Has(Out out, ContentId content_id) override; + virtual Result GetPath(OutPointerWithServerSize out, ContentId content_id) override; + virtual Result GetPlaceHolderPath(OutPointerWithServerSize out, PlaceHolderId placeholder_id) override; + virtual Result CleanupAllPlaceHolder() override; + virtual Result ListPlaceHolder(Out out_count, OutBuffer out_buf) override; + virtual Result GetContentCount(Out out_count) override; + virtual Result ListContentId(Out out_count, OutBuffer out_buf, u32 start_offset) override; + virtual Result GetSizeFromContentId(Out out_size, ContentId content_id) override; + virtual Result DisableForcibly() override; + virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override; + virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) override; + virtual Result ReadContentIdFile(OutBuffer buf, ContentId content_id, u64 offset) override; + virtual Result GetRightsIdFromPlaceHolderId(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id) override; + virtual Result GetRightsIdFromContentId(Out out_rights_id, Out out_key_generation, ContentId content_id) override; + virtual Result WriteContentForDebug(ContentId content_id, u64 offset, InBuffer data) override; + virtual Result GetFreeSpaceSize(Out out_size) override; + virtual Result GetTotalSpaceSize(Out out_size) override; + virtual Result FlushPlaceHolder() override; + virtual Result GetSizeFromPlaceHolderId(Out out, PlaceHolderId placeholder_id) override; + virtual Result RepairInvalidFileAttribute() override; + virtual Result GetRightsIdFromPlaceHolderIdWithCache(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) override; + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GeneratePlaceHolderId), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, CreatePlaceHolder), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, DeletePlaceHolder), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, HasPlaceHolder), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, WritePlaceHolder), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, Register), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, Delete), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, Has), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetPath), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetPlaceHolderPath), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, CleanupAllPlaceHolder), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, ListPlaceHolder), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GeneratePlaceHolderId), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetContentCount), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, ListContentId), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetSizeFromContentId), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, DisableForcibly), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, RevertToPlaceHolder, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, SetPlaceHolderSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, ReadContentIdFile, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetRightsIdFromPlaceHolderId, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetRightsIdFromContentId, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, WriteContentForDebug, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetFreeSpaceSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetTotalSpaceSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, FlushPlaceHolder, FirmwareVersion_300), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetSizeFromPlaceHolderId, FirmwareVersion_400), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, RepairInvalidFileAttribute, FirmwareVersion_400), + MAKE_SERVICE_COMMAND_META(ContentStorageInterface, GetRightsIdFromPlaceHolderIdWithCache, FirmwareVersion_800), + }; + }; + +} diff --git a/stratosphere/ncm/source/ncm_fs.cpp b/stratosphere/ncm/source/ncm_fs.cpp new file mode 100644 index 000000000..abecb6647 --- /dev/null +++ b/stratosphere/ncm/source/ncm_fs.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "ncm_fs.hpp" +#include "ncm_path_utils.hpp" + +namespace sts::ncm::fs { + + Result OpenFile(FILE** out, const char* path, u32 mode) { + bool has = false; + + /* Manually check if the file already exists, so it doesn't get created automatically. */ + R_TRY(HasFile(&has, path)); + if (!has) { + return ResultFsPathNotFound; + } + + const char* fopen_mode = ""; + + if (mode & FS_OPEN_WRITE) { + fopen_mode = "r+b"; + } else if (mode & FS_OPEN_READ) { + fopen_mode = "rb"; + } + FILE* f = fopen(path, fopen_mode); + + if (f == nullptr) { + return fsdevGetLastResult(); + } + + *out = f; + return ResultSuccess; + } + + Result WriteFile(FILE* f, size_t offset, const void* buffer, size_t size, u32 option) { + if (fseek(f, 0, SEEK_END) != 0) { + return fsdevGetLastResult(); + } + size_t existing_size = ftell(f); + + if (offset + size > existing_size) { + return ResultFsFileExtensionWithoutOpenModeAllowAppend; + } + + if (fseek(f, offset, SEEK_SET) != 0) { + return fsdevGetLastResult(); + } + + if (fwrite(buffer, 1, size, f) != size) { + return fsdevGetLastResult(); + } + + if (option & FS_WRITEOPTION_FLUSH) { + fflush(f); + } + + return ResultSuccess; + } + + Result ReadFile(FILE* f, size_t offset, void* buffer, size_t size) { + if (fseek(f, offset, SEEK_SET) != 0) { + return fsdevGetLastResult(); + } + + if (fread(buffer, 1, size, f) != size && ferror(f)) { + return fsdevGetLastResult(); + } + + return ResultSuccess; + } + + Result HasFile(bool* out, const char* path) { + struct stat st; + + if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) { + *out = true; + } else { + R_TRY_CATCH(fsdevGetLastResult()) { + R_CATCH(ResultFsPathNotFound) { + *out = false; + } + } R_END_TRY_CATCH; + } + + return ResultSuccess; + } + + Result HasDirectory(bool* out, const char* path) { + struct stat st; + + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + *out = true; + } else { + R_TRY_CATCH(fsdevGetLastResult()) { + R_CATCH(ResultFsPathNotFound) { + *out = false; + } + } R_END_TRY_CATCH; + } + + return ResultSuccess; + } + + Result CheckContentStorageDirectoriesExist(const char* root_path) { + char content_root[FS_MAX_PATH] = {0}; + char placeholder_root[FS_MAX_PATH] = {0}; + + bool has_root = false; + R_TRY(HasDirectory(&has_root, root_path)); + if (!has_root) { + return ResultNcmStorageRootNotFound; + } + + path::GetContentRootPath(content_root, root_path); + + bool has_content_root = false; + R_TRY(HasDirectory(&has_content_root, content_root)); + if (!has_content_root) { + return ResultNcmStoragePathNotFound; + } + + path::GetPlaceHolderRootPath(placeholder_root, root_path); + + bool has_placeholder_root = false; + R_TRY(HasDirectory(&has_placeholder_root, placeholder_root)); + if (!has_placeholder_root) { + return ResultNcmStoragePathNotFound; + } + + return ResultSuccess; + } + + Result EnsureContentAndPlaceHolderRoot(const char* root_path) { + char content_root[FS_MAX_PATH] = {0}; + char placeholder_root[FS_MAX_PATH] = {0}; + + path::GetContentRootPath(content_root, root_path); + R_TRY(EnsureDirectoryRecursively(content_root)); + path::GetPlaceHolderRootPath(placeholder_root, root_path); + R_TRY(EnsureDirectoryRecursively(placeholder_root)); + + return ResultSuccess; + } + + Result EnsureDirectoryRecursively(const char* dir_path) { + R_TRY(EnsureRecursively(dir_path)); + if (mkdir(dir_path, S_IRWXU) == -1) { + R_TRY_CATCH(fsdevGetLastResult()) { + R_CATCH(ResultFsPathAlreadyExists) { + /* If the path already exists, that's okay. Anything else is an error. */ + } + } R_END_TRY_CATCH; + } + return ResultSuccess; + } + + Result EnsureRecursively(const char* path) { + if (!path) { + return ResultFsNullptrArgument; + } + + size_t path_len = strlen(path); + char working_path_buf[FS_MAX_PATH] = {0}; + + if (path_len + 1 < FS_MAX_PATH) { + strncpy(working_path_buf + 1, path, FS_MAX_PATH-1); + + if (path_len != 0) { + for (size_t i = 0; i < path_len; i++) { + if (i != 0 && working_path_buf[i + 1] == '/' && working_path_buf[i] != ':') { + /* Temporarily make the path terminate before the '/' */ + working_path_buf[i + 1] = 0; + if (mkdir(working_path_buf + 1, S_IRWXU) == -1) { + R_TRY_CATCH(fsdevGetLastResult()) { + R_CATCH(ResultFsPathAlreadyExists) { + /* If the path already exists, that's okay. Anything else is an error. */ + } + } R_END_TRY_CATCH; + } + + /* Restore the path to its former state */ + working_path_buf[i + 1] = '/'; + } + } + } + } else { + return ResultNcmAllocationFailed; + } + + return ResultSuccess; + } + + Result EnsureParentDirectoryRecursively(const char* path) { + return EnsureRecursively(path); + } + + Result GetGameCardHandle(FsGameCardHandle* out_handle) { + FsDeviceOperator devop; + R_TRY(fsOpenDeviceOperator(&devop)); + + /* Ensure we close even on early return. */ + ON_SCOPE_EXIT { fsDeviceOperatorClose(&devop); }; + + R_TRY(fsDeviceOperatorGetGameCardHandle(&devop, out_handle)); + return ResultSuccess; + } + + static u32 g_mount_index = 0; + static HosMutex g_mount_index_lock; + + MountName CreateUniqueMountName() { + std::scoped_lock lk(g_mount_index_lock); + MountName mount_name; + g_mount_index++; + snprintf(mount_name.name, sizeof(MountName), "@ncm%08x", g_mount_index); + return mount_name; + } + + Result GetMountNameFromPath(MountName* mount_name, const char* path) { + const char* unqual_path = strchr(path, ':'); + + /* We should be given a qualified path. */ + if (!unqual_path || unqual_path > path + 0xf) { + return ResultFsInvalidMountName; + } + + strncpy(mount_name->name, path, unqual_path - path); + return ResultSuccess; + } + + Result MountSystemSaveData(const char* mount_point, FsSaveDataSpaceId space_id, u64 save_id) { + if (!mount_point) { + return ResultFsNullptrArgument; + } + + FsSave save = { + .saveID = save_id, + .saveDataType = FsSaveDataType_SystemSaveData, + }; + + FsFileSystem fs; + R_TRY(fsMountSystemSaveData(&fs, space_id, &save)); + + if (fsdevMountDevice(mount_point, fs) == -1) { + std::abort(); + } + + return ResultSuccess; + } + + constexpr const char* SystemContentMountName = "@SystemContent"; + constexpr const char* UserContentMountName = "@UserContent"; + constexpr const char* SdCardContentMountName = "@SdCardContent"; + constexpr const char* GameCardMountNameBase = "@Gc"; + + constexpr const char* GameCardPartitionLetters[3] = { "U", "N", "S" }; + + /* Maps mount names to their common mount names. */ + std::map g_mount_content_storage; + + Result MountContentStorage(const char* mount_point, FsContentStorageId id) { + if (!mount_point) { + return ResultFsNullptrArgument; + } + + FsFileSystem fs; + R_TRY(fsOpenContentStorageFileSystem(&fs, id)); + + if (fsdevMountDevice(mount_point, fs) == -1) { + std::abort(); + } + + switch (id) { + case FS_CONTENTSTORAGEID_NandSystem: + g_mount_content_storage[mount_point] = SystemContentMountName; + break; + + case FS_CONTENTSTORAGEID_NandUser: + g_mount_content_storage[mount_point] = UserContentMountName; + break; + + case FS_CONTENTSTORAGEID_SdCard: + g_mount_content_storage[mount_point] = SdCardContentMountName; + break; + + default: + std::abort(); + }; + return ResultSuccess; + } + + Result MountGameCardPartition(const char* mount_point, const FsGameCardHandle handle, FsGameCardPartiton partition) { + if (partition > 2) { + std::abort(); + } + + FsFileSystem fs; + R_TRY(fsOpenGameCardFileSystem(&fs, &handle, partition)); + + if (fsdevMountDevice(mount_point, fs) == -1) { + std::abort(); + } + + MountName mount = {0}; + snprintf(mount.name, sizeof(MountName), "%s%s%08x", GameCardMountNameBase, GameCardPartitionLetters[partition], handle.value); + g_mount_content_storage[mount_point] = mount.name; + return ResultSuccess; + } + + Result Unmount(const char* mount_point) { + if (!mount_point) { + return ResultFsNullptrArgument; + } + + /* Erase any content storage mappings which may potentially exist. */ + g_mount_content_storage.erase(mount_point); + + if (fsdevUnmountDevice(mount_point) == -1) { + std::abort(); + } + + return ResultSuccess; + } + + Result ConvertToFsCommonPath(char* out_common_path, size_t out_len, const char* path) { + if (!out_common_path || !path) { + return ResultFsNullptrArgument; + } + + MountName mount_name = {0}; + R_TRY(GetMountNameFromPath(&mount_name, path)); + + if (!fsdevGetDeviceFileSystem(mount_name.name) || g_mount_content_storage.find(mount_name.name) == g_mount_content_storage.end()) { + return ResultFsMountNameNotFound; + } + + char translated_path[FS_MAX_PATH] = {0}; + std::string common_mount_name = g_mount_content_storage[mount_name.name]; + + if (fsdevTranslatePath(path, NULL, translated_path) == -1) { + std::abort(); + } + + snprintf(out_common_path, out_len, "%s:%s", common_mount_name.c_str(), translated_path); + return ResultSuccess; + } + + Result GetSaveDataFlags(u32* out_flags, u64 save_id) { + FsSaveDataExtraData extra_data; + + R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id)); + *out_flags = extra_data.flags; + return ResultSuccess; + } + + Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags) { + FsSaveDataExtraData extra_data; + + R_TRY(fsReadSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), save_id)); + extra_data.flags = flags; + R_TRY(fsWriteSaveDataFileSystemExtraData(&extra_data, sizeof(FsSaveDataExtraData), space_id, save_id)); + return ResultSuccess; + } + +} diff --git a/stratosphere/ncm/source/ncm_fs.hpp b/stratosphere/ncm/source/ncm_fs.hpp new file mode 100644 index 000000000..9ff613c9c --- /dev/null +++ b/stratosphere/ncm/source/ncm_fs.hpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include + +namespace sts::ncm::fs { + + Result OpenFile(FILE** out, const char* path, u32 mode); + Result WriteFile(FILE* f, size_t offset, const void* buffer, size_t size, u32 option); + Result ReadFile(FILE* f, size_t offset, void* buffer, size_t size); + + Result HasFile(bool* out, const char* path); + Result HasDirectory(bool* out, const char* path); + + Result CheckContentStorageDirectoriesExist(const char* root_path); + Result EnsureContentAndPlaceHolderRoot(const char* root_path); + + Result EnsureDirectoryRecursively(const char* dir_path); + Result EnsureRecursively(const char* path); + /* Create all parent directories for a file path */ + Result EnsureParentDirectoryRecursively(const char* path); + + Result GetGameCardHandle(FsGameCardHandle* out_handle); + + MountName CreateUniqueMountName(); + Result GetMountNameFromPath(MountName* mount_name, const char* path); + + Result MountSystemSaveData(const char* mount_point, FsSaveDataSpaceId space_id, u64 save_id); + Result MountContentStorage(const char* mount_point, FsContentStorageId id); + Result MountGameCardPartition(const char* mount_point, const FsGameCardHandle handle, FsGameCardPartiton partition); + Result Unmount(const char* mount_point); + Result ConvertToFsCommonPath(char* out_common_path, size_t len, const char* path); + + Result GetSaveDataFlags(u32* out_flags, u64 save_id); + Result SetSaveDataFlags(u64 save_id, FsSaveDataSpaceId space_id, u32 flags); + + template + Result TraverseDirectory(bool* out_should_continue, const char* root_path, int max_level, F f) { + DIR *dir; + struct dirent* dir_entry = nullptr; + if (max_level < 1) { + return ResultSuccess; + } + + bool retry_dir_read = true; + while (retry_dir_read) { + retry_dir_read = false; + + if ((dir = opendir(root_path)) == nullptr) { + return fsdevGetLastResult(); + } + ON_SCOPE_EXIT { closedir(dir); }; + + while ((dir_entry = readdir(dir)) != nullptr) { + if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) { + continue; + } + + char current_path[FS_MAX_PATH]; + if (snprintf(current_path, FS_MAX_PATH-1, "%s/%s", root_path, dir_entry->d_name) < 0) { + std::abort(); + } + + bool should_continue = true; + bool should_retry_dir_read = false; + R_TRY(f(&should_continue, &should_retry_dir_read, current_path, dir_entry)); + + /* If the provided function wishes to terminate immediately, we should respect it. */ + if (!should_continue) { + *out_should_continue = false; + return ResultSuccess; + } + if (should_retry_dir_read) { + retry_dir_read = true; + break; + } + + if (dir_entry->d_type == DT_DIR) { + R_TRY(TraverseDirectory(&should_continue, current_path, max_level-1, f)); + + if (!should_continue) { + *out_should_continue = false; + return ResultSuccess; + } + } + } + } + + return ResultSuccess; + }; + + template + Result TraverseDirectory(const char* root_path, int max_level, F f) { + bool should_continue = false; + return TraverseDirectory(&should_continue, root_path, max_level, f); + } + +} diff --git a/stratosphere/ncm/source/ncm_icontentmetadatabase.cpp b/stratosphere/ncm/source/ncm_icontentmetadatabase.cpp new file mode 100644 index 000000000..2abb63ede --- /dev/null +++ b/stratosphere/ncm/source/ncm_icontentmetadatabase.cpp @@ -0,0 +1,121 @@ +/* + * 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_icontentmetadatabase.hpp" + +namespace sts::ncm { + + Result IContentMetaDatabase::EnsureEnabled() { + if (this->disabled) { + return ResultNcmInvalidContentMetaDatabase; + } + + return ResultSuccess; + } + + Result IContentMetaDatabase::Set(ContentMetaKey key, InBuffer value) { + std::abort(); + } + + Result IContentMetaDatabase::Get(Out out_size, ContentMetaKey key, OutBuffer out_value) { + std::abort(); + } + + Result IContentMetaDatabase::Remove(ContentMetaKey key) { + std::abort(); + } + + Result IContentMetaDatabase::GetContentIdByType(Out out_content_id, ContentMetaKey key, ContentType type) { + std::abort(); + } + + Result IContentMetaDatabase::ListContentInfo(Out out_entries_written, OutBuffer out_info, ContentMetaKey key, u32 start_index) { + std::abort(); + } + + Result IContentMetaDatabase::List(Out out_entries_total, Out out_entries_written, OutBuffer out_info, ContentMetaType type, TitleId application_title_id, TitleId title_id_min, TitleId title_id_max, ContentInstallType install_type) { + std::abort(); + } + + Result IContentMetaDatabase::GetLatestContentMetaKey(Out out_key, TitleId title_id) { + std::abort(); + } + + Result IContentMetaDatabase::ListApplication(Out out_entries_total, Out out_entries_written, OutBuffer out_keys, ContentMetaType type) { + std::abort(); + } + + Result IContentMetaDatabase::Has(Out out, ContentMetaKey key) { + std::abort(); + } + + Result IContentMetaDatabase::HasAll(Out out, InBuffer keys) { + std::abort(); + } + + Result IContentMetaDatabase::GetSize(Out out_size, ContentMetaKey key) { + std::abort(); + } + + Result IContentMetaDatabase::GetRequiredSystemVersion(Out out_version, ContentMetaKey key) { + std::abort(); + } + + Result IContentMetaDatabase::GetPatchId(Out out_patch_id, ContentMetaKey key) { + std::abort(); + } + + Result IContentMetaDatabase::DisableForcibly() { + std::abort(); + } + + Result IContentMetaDatabase::LookupOrphanContent(OutBuffer out_orphaned, InBuffer content_ids) { + std::abort(); + } + + Result IContentMetaDatabase::Commit() { + std::abort(); + } + + Result IContentMetaDatabase::HasContent(Out out, ContentMetaKey key, ContentId content_id) { + std::abort(); + } + + Result IContentMetaDatabase::ListContentMetaInfo(Out out_entries_written, OutBuffer out_meta_info, ContentMetaKey key, u32 start_index) { + std::abort(); + } + + Result IContentMetaDatabase::GetAttributes(Out out_attributes, ContentMetaKey key) { + std::abort(); + } + + Result IContentMetaDatabase::GetRequiredApplicationVersion(Out out_version, ContentMetaKey key) { + std::abort(); + } + + Result IContentMetaDatabase::GetContentIdByTypeAndIdOffset(Out out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) { + std::abort(); + } + + Result IContentMetaDatabase::GetLatestProgram(ContentId* out_content_id, TitleId title_id) { + std::abort(); + } + + Result IContentMetaDatabase::GetLatestData(ContentId* out_content_id, TitleId title_id) { + std::abort(); + } + +} diff --git a/stratosphere/ncm/source/ncm_icontentmetadatabase.hpp b/stratosphere/ncm/source/ncm_icontentmetadatabase.hpp new file mode 100644 index 000000000..194b4e541 --- /dev/null +++ b/stratosphere/ncm/source/ncm_icontentmetadatabase.hpp @@ -0,0 +1,119 @@ +/* + * 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 + +namespace sts::ncm { + + class IContentMetaDatabase : public IServiceObject { + protected: + enum class CommandId { + Set = 0, + Get = 1, + Remove = 2, + GetContentIdByType = 3, + ListContentInfo = 4, + List = 5, + GetLatestContentMetaKey = 6, + ListApplication = 7, + Has = 8, + HasAll = 9, + GetSize = 10, + GetRequiredSystemVersion = 11, + GetPatchId = 12, + DisableForcibly = 13, + LookupOrphanContent = 14, + Commit = 15, + HasContent = 16, + ListContentMetaInfo = 17, + GetAttributes = 18, + GetRequiredApplicationVersion = 19, + GetContentIdByTypeAndIdOffset = 20, + }; + protected: + sts::kvdb::MemoryKeyValueStore* kvs; + char mount_name[16]; + bool disabled; + protected: + Result EnsureEnabled(); + public: + IContentMetaDatabase(sts::kvdb::MemoryKeyValueStore* kvs) : + kvs(kvs), disabled(false) + { + } + + IContentMetaDatabase(sts::kvdb::MemoryKeyValueStore* kvs, const char* mount_name) : + IContentMetaDatabase(kvs) + { + strcpy(this->mount_name, mount_name); + } + public: + /* Actual commands. */ + virtual Result Set(ContentMetaKey key, InBuffer value); + virtual Result Get(Out out_size, ContentMetaKey key, OutBuffer out_value); + virtual Result Remove(ContentMetaKey key); + virtual Result GetContentIdByType(Out out_content_id, ContentMetaKey key, ContentType type); + virtual Result ListContentInfo(Out out_entries_written, OutBuffer out_info, ContentMetaKey key, u32 start_index); + virtual Result List(Out out_entries_total, Out out_entries_written, OutBuffer out_info, ContentMetaType meta_type, TitleId application_title_id, TitleId title_id_min, TitleId title_id_max, ContentInstallType install_type); + virtual Result GetLatestContentMetaKey(Out out_key, TitleId tid); + virtual Result ListApplication(Out out_entries_total, Out out_entries_written, OutBuffer out_keys, ContentMetaType meta_type); + virtual Result Has(Out out, ContentMetaKey key); + virtual Result HasAll(Out out, InBuffer keys); + virtual Result GetSize(Out out_size, ContentMetaKey key); + virtual Result GetRequiredSystemVersion(Out out_version, ContentMetaKey key); + virtual Result GetPatchId(Out out_patch_id, ContentMetaKey key); + virtual Result DisableForcibly(); + virtual Result LookupOrphanContent(OutBuffer out_orphaned, InBuffer content_ids); + virtual Result Commit(); + virtual Result HasContent(Out out, ContentMetaKey key, ContentId content_id); + virtual Result ListContentMetaInfo(Out out_entries_written, OutBuffer out_meta_info, ContentMetaKey key, u32 start_index); + virtual Result GetAttributes(Out out_attributes, ContentMetaKey key); + virtual Result GetRequiredApplicationVersion(Out out_version, ContentMetaKey key); + virtual Result GetContentIdByTypeAndIdOffset(Out out_content_id, ContentMetaKey key, ContentType type, u8 id_offset); + + /* APIs. */ + virtual Result GetLatestProgram(ContentId* out_content_id, TitleId title_id); + virtual Result GetLatestData(ContentId* out_content_id, TitleId title_id); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, Set), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, Get), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, Remove), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetContentIdByType), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, ListContentInfo), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, List), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetLatestContentMetaKey), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, ListApplication), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, Has), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, HasAll), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetSize), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetRequiredSystemVersion), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetPatchId), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, DisableForcibly), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, LookupOrphanContent), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, Commit), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, HasContent), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, ListContentMetaInfo), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetAttributes), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetRequiredApplicationVersion, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentMetaDatabase, GetContentIdByTypeAndIdOffset, FirmwareVersion_500), + }; + }; + +} diff --git a/stratosphere/ncm/source/ncm_icontentstorage.cpp b/stratosphere/ncm/source/ncm_icontentstorage.cpp new file mode 100644 index 000000000..7ac23e210 --- /dev/null +++ b/stratosphere/ncm/source/ncm_icontentstorage.cpp @@ -0,0 +1,141 @@ +/* + * 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_icontentstorage.hpp" + +namespace sts::ncm { + + Result IContentStorage::EnsureEnabled() { + if (this->disabled) { + return ResultNcmInvalidContentStorage; + } + + return ResultSuccess; + } + + Result IContentStorage::GeneratePlaceHolderId(Out out) { + std::abort(); + } + + Result IContentStorage::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) { + std::abort(); + } + + Result IContentStorage::DeletePlaceHolder(PlaceHolderId placeholder_id) { + std::abort(); + } + + Result IContentStorage::HasPlaceHolder(Out out, PlaceHolderId placeholder_id) { + std::abort(); + } + + Result IContentStorage::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer data) { + std::abort(); + } + + Result IContentStorage::Register(PlaceHolderId placeholder_id, ContentId content_id) { + std::abort(); + } + + Result IContentStorage::Delete(ContentId content_id) { + std::abort(); + } + + Result IContentStorage::Has(Out out, ContentId content_id) { + std::abort(); + } + + Result IContentStorage::GetPath(OutPointerWithServerSize out, ContentId content_id) { + std::abort(); + } + + Result IContentStorage::GetPlaceHolderPath(OutPointerWithServerSize out, PlaceHolderId placeholder_id) { + std::abort(); + } + + Result IContentStorage::CleanupAllPlaceHolder() { + std::abort(); + } + + Result IContentStorage::ListPlaceHolder(Out out_count, OutBuffer out_buf) { + std::abort(); + } + + Result IContentStorage::GetContentCount(Out out_count) { + std::abort(); + } + + Result IContentStorage::ListContentId(Out out_count, OutBuffer out_buf, u32 start_offset) { + std::abort(); + } + + Result IContentStorage::GetSizeFromContentId(Out out_size, ContentId content_id) { + std::abort(); + } + + Result IContentStorage::DisableForcibly() { + std::abort(); + } + + Result IContentStorage::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + std::abort(); + } + + Result IContentStorage::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) { + std::abort(); + } + + Result IContentStorage::ReadContentIdFile(OutBuffer buf, ContentId content_id, u64 offset) { + std::abort(); + } + + Result IContentStorage::GetRightsIdFromPlaceHolderId(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id) { + std::abort(); + } + + Result IContentStorage::GetRightsIdFromContentId(Out out_rights_id, Out out_key_generation, ContentId content_id) { + std::abort(); + } + + Result IContentStorage::WriteContentForDebug(ContentId content_id, u64 offset, InBuffer data) { + std::abort(); + } + + Result IContentStorage::GetFreeSpaceSize(Out out_size) { + std::abort(); + } + + Result IContentStorage::GetTotalSpaceSize(Out out_size) { + std::abort(); + } + + Result IContentStorage::FlushPlaceHolder() { + std::abort(); + } + + Result IContentStorage::GetSizeFromPlaceHolderId(Out out_size, PlaceHolderId placeholder_id) { + std::abort(); + } + + Result IContentStorage::RepairInvalidFileAttribute() { + std::abort(); + } + + Result IContentStorage::GetRightsIdFromPlaceHolderIdWithCache(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) { + std::abort(); + } + +} diff --git a/stratosphere/ncm/source/ncm_icontentstorage.hpp b/stratosphere/ncm/source/ncm_icontentstorage.hpp new file mode 100644 index 000000000..01300b583 --- /dev/null +++ b/stratosphere/ncm/source/ncm_icontentstorage.hpp @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace sts::ncm { + + class IContentStorage : public IServiceObject { + protected: + enum class CommandId { + GeneratePlaceHolderId = 0, + CreatePlaceHolder = 1, + DeletePlaceHolder = 2, + HasPlaceHolder = 3, + WritePlaceHolder = 4, + Register = 5, + Delete = 6, + Has = 7, + GetPath = 8, + GetPlaceHolderPath = 9, + CleanupAllPlaceHolder = 10, + ListPlaceHolder = 11, + GetContentCount = 12, + ListContentId = 13, + GetSizeFromContentId = 14, + DisableForcibly = 15, + RevertToPlaceHolder = 16, + SetPlaceHolderSize = 17, + ReadContentIdFile = 18, + GetRightsIdFromPlaceHolderId = 19, + GetRightsIdFromContentId = 20, + WriteContentForDebug = 21, + GetFreeSpaceSize = 22, + GetTotalSpaceSize = 23, + FlushPlaceHolder = 24, + GetSizeFromPlaceHolderId = 25, + RepairInvalidFileAttribute = 26, + GetRightsIdFromPlaceHolderIdWithCache = 27, + }; + protected: + char root_path[FS_MAX_PATH-1]; + MakeContentPathFunc make_content_path_func; + bool disabled; + protected: + Result EnsureEnabled(); + public: + virtual Result GeneratePlaceHolderId(Out out); + virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size); + virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id); + virtual Result HasPlaceHolder(Out out, PlaceHolderId placeholder_id); + virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer data); + virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id); + virtual Result Delete(ContentId content_id); + virtual Result Has(Out out, ContentId content_id); + virtual Result GetPath(OutPointerWithServerSize out, ContentId content_id); + virtual Result GetPlaceHolderPath(OutPointerWithServerSize out, PlaceHolderId placeholder_id); + virtual Result CleanupAllPlaceHolder(); + virtual Result ListPlaceHolder(Out out_count, OutBuffer out_buf); + virtual Result GetContentCount(Out out_count); + virtual Result ListContentId(Out out_count, OutBuffer out_buf, u32 start_offset); + virtual Result GetSizeFromContentId(Out out_size, ContentId content_id); + virtual Result DisableForcibly(); + virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id); + virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size); + virtual Result ReadContentIdFile(OutBuffer buf, ContentId content_id, u64 offset); + virtual Result GetRightsIdFromPlaceHolderId(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id); + virtual Result GetRightsIdFromContentId(Out out_rights_id, Out out_key_generation, ContentId content_id); + virtual Result WriteContentForDebug(ContentId content_id, u64 offset, InBuffer data); + virtual Result GetFreeSpaceSize(Out out_size); + virtual Result GetTotalSpaceSize(Out out_size); + virtual Result FlushPlaceHolder(); + virtual Result GetSizeFromPlaceHolderId(Out out, PlaceHolderId placeholder_id); + virtual Result RepairInvalidFileAttribute(); + virtual Result GetRightsIdFromPlaceHolderIdWithCache(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(IContentStorage, GeneratePlaceHolderId), + MAKE_SERVICE_COMMAND_META(IContentStorage, CreatePlaceHolder), + MAKE_SERVICE_COMMAND_META(IContentStorage, DeletePlaceHolder), + MAKE_SERVICE_COMMAND_META(IContentStorage, HasPlaceHolder), + MAKE_SERVICE_COMMAND_META(IContentStorage, WritePlaceHolder), + MAKE_SERVICE_COMMAND_META(IContentStorage, Register), + MAKE_SERVICE_COMMAND_META(IContentStorage, Delete), + MAKE_SERVICE_COMMAND_META(IContentStorage, Has), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetPath), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetPlaceHolderPath), + MAKE_SERVICE_COMMAND_META(IContentStorage, CleanupAllPlaceHolder), + MAKE_SERVICE_COMMAND_META(IContentStorage, ListPlaceHolder), + MAKE_SERVICE_COMMAND_META(IContentStorage, GeneratePlaceHolderId), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetContentCount), + MAKE_SERVICE_COMMAND_META(IContentStorage, ListContentId), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetSizeFromContentId), + MAKE_SERVICE_COMMAND_META(IContentStorage, DisableForcibly), + MAKE_SERVICE_COMMAND_META(IContentStorage, RevertToPlaceHolder, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, SetPlaceHolderSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, ReadContentIdFile, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetRightsIdFromPlaceHolderId, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetRightsIdFromContentId, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, WriteContentForDebug, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetFreeSpaceSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetTotalSpaceSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(IContentStorage, FlushPlaceHolder, FirmwareVersion_300), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetSizeFromPlaceHolderId, FirmwareVersion_400), + MAKE_SERVICE_COMMAND_META(IContentStorage, RepairInvalidFileAttribute, FirmwareVersion_400), + MAKE_SERVICE_COMMAND_META(IContentStorage, GetRightsIdFromPlaceHolderIdWithCache, FirmwareVersion_800), + }; + }; + +} diff --git a/stratosphere/ncm/source/ncm_main.cpp b/stratosphere/ncm/source/ncm_main.cpp new file mode 100644 index 000000000..a2fda6ee8 --- /dev/null +++ b/stratosphere/ncm/source/ncm_main.cpp @@ -0,0 +1,167 @@ +/* + * 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 "impl/ncm_content_manager.hpp" +#include "lr_manager_service.hpp" +#include "ncm_content_manager_service.hpp" + +extern "C" { + extern u32 __start__; + + u32 __nx_applet_type = AppletType_None; + + #define INNER_HEAP_SIZE 0x100000 + size_t nx_inner_heap_size = INNER_HEAP_SIZE; + char nx_inner_heap[INNER_HEAP_SIZE]; + + void __libnx_initheap(void); + void __appInit(void); + void __appExit(void); + + /* Exception handling. */ + alignas(16) u8 __nx_exception_stack[0x1000]; + u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); + void __libnx_exception_handler(ThreadExceptionDump *ctx); + void __libstratosphere_exception_handler(AtmosphereFatalErrorContext *ctx); +} + +sts::ncm::TitleId __stratosphere_title_id = sts::ncm::TitleId::Ncm; + +namespace { + + /* Convenience definitions. */ + constexpr uintptr_t IramBase = 0x40000000ull; + constexpr uintptr_t IramPayloadBase = 0x40010000ull; + constexpr size_t IramSize = 0x40000; + constexpr size_t IramPayloadMaxSize = 0x2E000; + + /* Globals. */ + u8 __attribute__ ((aligned (0x1000))) g_work_page[0x1000]; + + /* Helpers. */ + void ClearIram() { + /* Make page FFs. */ + memset(g_work_page, 0xFF, sizeof(g_work_page)); + + /* Overwrite all of IRAM with FFs. */ + for (size_t ofs = 0; ofs < IramSize; ofs += sizeof(g_work_page)) { + CopyToIram(IramBase + ofs, g_work_page, sizeof(g_work_page)); + } + } + + void DoReboot(AtmosphereFatalErrorContext *ctx) { + /* Ensure clean IRAM state. */ + ClearIram(); + + /* Copy in fatal error context, if relevant. */ + if (ctx != nullptr) { + std::memset(g_work_page, 0xCC, sizeof(g_work_page)); + std::memcpy(g_work_page, ctx, sizeof(*ctx)); + CopyToIram(IramPayloadBase + IramPayloadMaxSize, g_work_page, sizeof(g_work_page)); + } + + RebootToRcm(); + } + +} + +void __libnx_exception_handler(ThreadExceptionDump *ctx) { + StratosphereCrashHandler(ctx); +} + +void __libstratosphere_exception_handler(AtmosphereFatalErrorContext *ctx) { + DoReboot(ctx); +} + +void __libnx_initheap(void) { + void* addr = nx_inner_heap; + size_t size = nx_inner_heap_size; + + /* Newlib */ + extern char* fake_heap_start; + extern char* fake_heap_end; + + fake_heap_start = (char*)addr; + fake_heap_end = (char*)addr + size; +} + +void __appInit(void) { + SetFirmwareVersionForLibnx(); + + DoWithSmSession([&]() { + R_ASSERT(fsInitialize()); + }); + + CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION); +} + +void __appExit(void) { + /* Cleanup services. */ + fsdevUnmountAll(); + fsExit(); +} + +struct ServerOptions { + static constexpr size_t PointerBufferSize = 0x400; + static constexpr size_t MaxDomains = 0; + static constexpr size_t MaxDomainObjects = 0; +}; + +void ContentManagerServerMain(void* arg) { + static auto s_server_manager = WaitableManager(1); + + /* Create services. */ + s_server_manager.AddWaitable(new ServiceServer("ncm", 0x10)); + + /* Loop forever, servicing our services. */ + s_server_manager.Process(); +} + +void LocationResolverServerMain(void* arg) { + static auto s_server_manager = WaitableManager(1); + + /* Create services. */ + s_server_manager.AddWaitable(new ServiceServer("lr", 0x10)); + + /* Loop forever, servicing our services. */ + s_server_manager.Process(); +} + +int main(int argc, char **argv) +{ + /* Initialize content manager implementation. */ + R_ASSERT(sts::ncm::impl::InitializeContentManager()); + + static HosThread s_content_manager_thread; + static HosThread s_location_resolver_thread; + + R_ASSERT(s_content_manager_thread.Initialize(&ContentManagerServerMain, nullptr, 0x4000, 0x15)); + R_ASSERT(s_content_manager_thread.Start()); + + R_ASSERT(s_location_resolver_thread.Initialize(&LocationResolverServerMain, nullptr, 0x4000, 0x15)); + R_ASSERT(s_location_resolver_thread.Start()); + + s_content_manager_thread.Join(); + s_location_resolver_thread.Join(); + + sts::ncm::impl::FinalizeContentManager(); + + return 0; +} diff --git a/stratosphere/ncm/source/ncm_make_path.cpp b/stratosphere/ncm/source/ncm_make_path.cpp new file mode 100644 index 000000000..7ca9a7047 --- /dev/null +++ b/stratosphere/ncm/source/ncm_make_path.cpp @@ -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 . + */ + +#include "ncm_make_path.hpp" +#include "ncm_path_utils.hpp" + +namespace sts::ncm::path { + + namespace { + + u16 Get16BitSha256HashPrefix(util::Uuid uuid) { + u8 hash[SHA256_HASH_SIZE]; + sha256CalculateHash(hash, uuid.uuid, sizeof(util::Uuid)); + return static_cast(hash[0]) | (static_cast(hash[1]) << 8); + } + + u8 Get8BitSha256HashPrefix(util::Uuid uuid) { + u8 hash[SHA256_HASH_SIZE]; + sha256CalculateHash(hash, uuid.uuid, sizeof(util::Uuid)); + return hash[0]; + } + + } + + void MakeContentPathFlat(char* path_out, ContentId content_id, const char* root) { + char content_name[FS_MAX_PATH] = {0}; + GetContentFileName(content_name, content_id); + if (snprintf(path_out, FS_MAX_PATH-1, "%s/%s", root, content_name) < 0) { + std::abort(); + } + } + + void MakeContentPathDualLayered(char* path_out, ContentId content_id, const char* root) { + char content_name[FS_MAX_PATH] = {0}; + const u16 hash = Get16BitSha256HashPrefix(content_id.uuid); + const u32 hash_lower = (hash >> 4) & 0x3f; + const u32 hash_upper = (hash >> 10) & 0x3f; + + GetContentFileName(content_name, content_id); + if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%08X/%s", root, hash_upper, hash_lower, content_name) < 0) { + std::abort(); + } + } + + void MakeContentPath10BitLayered(char* path_out, ContentId content_id, const char* root) { + char content_name[FS_MAX_PATH] = {0}; + const u32 hash = (Get16BitSha256HashPrefix(content_id.uuid) >> 6) & 0x3FF; + GetContentFileName(content_name, content_id); + if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash, content_name) < 0) { + std::abort(); + } + } + + void MakeContentPathHashByteLayered(char* path_out, ContentId content_id, const char* root) { + char content_name[FS_MAX_PATH] = {0}; + const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(content_id.uuid)); + GetContentFileName(content_name, content_id); + if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash_byte, content_name) < 0) { + std::abort(); + } + } + + void MakePlaceHolderPathFlat(char* path_out, PlaceHolderId placeholder_id, const char* root) { + char placeholder_name[FS_MAX_PATH] = {0}; + GetPlaceHolderFileName(placeholder_name, placeholder_id); + if (snprintf(path_out, FS_MAX_PATH-1, "%s/%s", root, placeholder_name) < 0) { + std::abort(); + } + } + + void MakePlaceHolderPathHashByteLayered(char* path_out, PlaceHolderId placeholder_id, const char* root) { + char placeholder_name[FS_MAX_PATH] = {0}; + const u32 hash_byte = static_cast(Get8BitSha256HashPrefix(placeholder_id.uuid)); + GetPlaceHolderFileName(placeholder_name, placeholder_id); + if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash_byte, placeholder_name) < 0) { + std::abort(); + } + } + +} diff --git a/stratosphere/ncm/source/ncm_make_path.hpp b/stratosphere/ncm/source/ncm_make_path.hpp new file mode 100644 index 000000000..f71458815 --- /dev/null +++ b/stratosphere/ncm/source/ncm_make_path.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace sts::ncm::path { + + void MakeContentPathFlat(char* out_path, ContentId content_id, const char* root); + void MakeContentPathHashByteLayered(char* out_path, ContentId content_id, const char* root); + void MakeContentPath10BitLayered(char* out_path, ContentId content_id, const char* root); + void MakeContentPathDualLayered(char* out_path, ContentId content_id, const char* root); + + void MakePlaceHolderPathFlat(char* out_path, PlaceHolderId placeholder_id, const char* root); + void MakePlaceHolderPathHashByteLayered(char* out_path, PlaceHolderId placeholder_id, const char* root); + +} diff --git a/stratosphere/ncm/source/ncm_path_utils.cpp b/stratosphere/ncm/source/ncm_path_utils.cpp new file mode 100644 index 000000000..c0bb22481 --- /dev/null +++ b/stratosphere/ncm/source/ncm_path_utils.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ncm_path_utils.hpp" +#include "ncm_utils.hpp" + +namespace sts::ncm::path { + + void GetContentMetaPath(char* out, ContentId content_id, MakeContentPathFunc path_func, const char* root_path) { + char tmp_path[FS_MAX_PATH-1] = {0}; + char content_path[FS_MAX_PATH-1] = {0}; + path_func(content_path, content_id, root_path); + const size_t len = strnlen(content_path, FS_MAX_PATH-1); + const size_t len_no_extension = len - 4; + + if (len_no_extension > len || len_no_extension >= FS_MAX_PATH-1) { + std::abort(); + } + + strncpy(tmp_path, content_path, len_no_extension); + memcpy(out, tmp_path, FS_MAX_PATH-1); + const size_t out_len = strnlen(out, FS_MAX_PATH-1); + + if (out_len + 9 >= FS_MAX_PATH-1) { + std::abort(); + } + + strncat(out, ".cnmt.nca", 0x2ff - out_len); + } + + void GetContentFileName(char* out, ContentId content_id) { + char content_name[sizeof(ContentId)*2+1] = {0}; + GetStringFromContentId(content_name, content_id); + snprintf(out, FS_MAX_PATH-1, "%s%s", content_name, ".nca"); + } + + void GetPlaceHolderFileName(char* out, PlaceHolderId placeholder_id) { + char placeholder_name[sizeof(PlaceHolderId)*2+1] = {0}; + GetStringFromPlaceHolderId(placeholder_name, placeholder_id); + snprintf(out, FS_MAX_PATH-1, "%s%s", placeholder_name, ".nca"); + } + + bool IsNcaPath(const char* path) { + PathView path_view(path); + + if (!path_view.HasSuffix(".nca")) { + return false; + } + + std::string_view file_name = path_view.GetFileName(); + + if (file_name.length() != 0x24) { + return false; + } + + for (size_t i = 0; i < sizeof(util::Uuid)*2; i++) { + if (!std::isxdigit(file_name.at(i))) { + return false; + } + } + + return true; + } + + bool PathView::HasPrefix(std::string_view prefix) const { + return this->path.compare(0, prefix.length(), prefix) == 0; + } + + bool PathView::HasSuffix(std::string_view suffix) const { + return this->path.compare(this->path.length() - suffix.length(), suffix.length(), suffix) == 0; + } + + std::string_view PathView::GetFileName() const { + return this->path.substr(this->path.find_last_of("/") + 1); + } + +} diff --git a/stratosphere/ncm/source/ncm_path_utils.hpp b/stratosphere/ncm/source/ncm_path_utils.hpp new file mode 100644 index 000000000..cf13e2fae --- /dev/null +++ b/stratosphere/ncm/source/ncm_path_utils.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace sts::ncm::path { + + inline void GetContentRootPath(char* out_content_root, const char* root_path) { + /* TODO: Replace with BoundedString? */ + if (snprintf(out_content_root, FS_MAX_PATH-1, "%s%s", root_path, "/registered") < 0) { + std::abort(); + } + } + + inline void GetPlaceHolderRootPath(char* out_placeholder_root, const char* root_path) { + /* TODO: Replace with BoundedString? */ + if (snprintf(out_placeholder_root, FS_MAX_PATH, "%s%s", root_path, "/placehld") < 0) { + std::abort(); + } + } + + void GetContentMetaPath(char* out, ContentId content_id, MakeContentPathFunc path_func, const char* root_path); + void GetContentFileName(char* out, ContentId content_id); + void GetPlaceHolderFileName(char* out, PlaceHolderId placeholder_id); + bool IsNcaPath(const char* path); + + class PathView { + private: + std::string_view path; /* Nintendo uses nn::util::string_view here. */ + public: + PathView(std::string_view p) : path(p) { /* ...*/ } + bool HasPrefix(std::string_view prefix) const; + bool HasSuffix(std::string_view suffix) const; + std::string_view GetFileName() const; + }; + +} diff --git a/stratosphere/ncm/source/ncm_readonlycontentstorage.cpp b/stratosphere/ncm/source/ncm_readonlycontentstorage.cpp new file mode 100644 index 000000000..204a68f9e --- /dev/null +++ b/stratosphere/ncm/source/ncm_readonlycontentstorage.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ncm_fs.hpp" +#include "ncm_path_utils.hpp" +#include "ncm_readonlycontentstorage.hpp" + +namespace sts::ncm { + + Result ReadOnlyContentStorageInterface::Initialize(const char* root_path, MakeContentPathFunc content_path_func) { + R_TRY(this->EnsureEnabled()); + + const size_t root_path_len = strnlen(root_path, FS_MAX_PATH-1); + + if (root_path_len >= FS_MAX_PATH-1) { + std::abort(); + } + + strncpy(this->root_path, root_path, FS_MAX_PATH-2); + this->make_content_path_func = *content_path_func; + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::GeneratePlaceHolderId(Out out) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::DeletePlaceHolder(PlaceHolderId placeholder_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::HasPlaceHolder(Out out, PlaceHolderId placeholder_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer data) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::Register(PlaceHolderId placeholder_id, ContentId content_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::Delete(ContentId content_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::Has(Out out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + this->make_content_path_func(content_path, content_id, this->root_path); + + bool has = false; + R_TRY(fs::HasFile(&has, content_path)); + + if (!has) { + path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path); + R_TRY(fs::HasFile(&has, content_path)); + } + + out.SetValue(has); + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::GetPath(OutPointerWithServerSize out, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + char common_path[FS_MAX_PATH] = {0}; + bool is_content_meta_file = false; + + path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path); + R_TRY(fs::HasFile(&is_content_meta_file, content_path)); + + if (!is_content_meta_file) { + this->make_content_path_func(content_path, content_id, this->root_path); + } + + R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path)); + *out.pointer = common_path; + + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::GetPlaceHolderPath(OutPointerWithServerSize out, PlaceHolderId placeholder_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::CleanupAllPlaceHolder() { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::ListPlaceHolder(Out out_count, OutBuffer out_buf) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::GetContentCount(Out out_count) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::ListContentId(Out out_count, OutBuffer out_buf, u32 start_offset) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::GetSizeFromContentId(Out out_size, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + bool is_content_file = false; + + this->make_content_path_func(content_path, content_id, this->root_path); + R_TRY(fs::HasFile(&is_content_file, content_path)); + + if (!is_content_file) { + path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path); + } + + struct stat st; + if (stat(content_path, &st) == -1) { + return fsdevGetLastResult(); + } + + out_size.SetValue(st.st_size); + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::DisableForcibly() { + this->disabled = true; + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::ReadContentIdFile(OutBuffer buf, ContentId content_id, u64 offset) { + /* Offset is too large */ + if (offset >> 0x3f != 0) { + return ResultNcmInvalidOffset; + } + + R_TRY(this->EnsureEnabled()); + + char content_path[FS_MAX_PATH] = {0}; + bool is_content_file = false; + + this->make_content_path_func(content_path, content_id, this->root_path); + R_TRY(fs::HasFile(&is_content_file, content_path)); + + if (!is_content_file) { + path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path); + } + + FILE* f = nullptr; + R_TRY(fs::OpenFile(&f, content_path, FS_OPEN_READ)); + + ON_SCOPE_EXIT { + fclose(f); + }; + + R_TRY(fs::ReadFile(f, offset, buf.buffer, buf.num_elements)); + + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::GetRightsIdFromPlaceHolderId(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::GetRightsIdFromContentId(Out out_rights_id, Out out_key_generation, ContentId content_id) { + R_TRY(this->EnsureEnabled()); + + FsRightsId rights_id = {0}; + u8 key_generation = 0; + + char content_path[FS_MAX_PATH] = {0}; + char common_path[FS_MAX_PATH] = {0}; + bool is_content_meta_file = false; + + path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path); + R_TRY(fs::HasFile(&is_content_meta_file, content_path)); + + if (!is_content_meta_file) { + this->make_content_path_func(content_path, content_id, this->root_path); + } + + R_TRY(fs::ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path)); + R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id)); + + out_rights_id.SetValue(rights_id); + out_key_generation.SetValue(static_cast(key_generation)); + + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::WriteContentForDebug(ContentId content_id, u64 offset, InBuffer data) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::GetFreeSpaceSize(Out out_size) { + out_size.SetValue(0); + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::GetTotalSpaceSize(Out out_size) { + out_size.SetValue(0); + return ResultSuccess; + } + + Result ReadOnlyContentStorageInterface::FlushPlaceHolder() { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::GetSizeFromPlaceHolderId(Out out, PlaceHolderId placeholder_id) { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::RepairInvalidFileAttribute() { + return ResultNcmInvalidContentStorageOperation; + } + + Result ReadOnlyContentStorageInterface::GetRightsIdFromPlaceHolderIdWithCache(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) { + return ResultNcmInvalidContentStorageOperation; + } + +} diff --git a/stratosphere/ncm/source/ncm_readonlycontentstorage.hpp b/stratosphere/ncm/source/ncm_readonlycontentstorage.hpp new file mode 100644 index 000000000..156eab7fb --- /dev/null +++ b/stratosphere/ncm/source/ncm_readonlycontentstorage.hpp @@ -0,0 +1,91 @@ +/* + * 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_icontentstorage.hpp" + +namespace sts::ncm { + + class ReadOnlyContentStorageInterface : public IContentStorage { + public: + Result Initialize(const char* root_path, MakeContentPathFunc content_path_func); + public: + virtual Result GeneratePlaceHolderId(Out out) override; + virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override; + virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override; + virtual Result HasPlaceHolder(Out out, PlaceHolderId placeholder_id) override; + virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer data) override; + virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override; + virtual Result Delete(ContentId content_id) override; + virtual Result Has(Out out, ContentId content_id) override; + virtual Result GetPath(OutPointerWithServerSize out, ContentId content_id) override; + virtual Result GetPlaceHolderPath(OutPointerWithServerSize out, PlaceHolderId placeholder_id) override; + virtual Result CleanupAllPlaceHolder() override; + virtual Result ListPlaceHolder(Out out_count, OutBuffer out_buf) override; + virtual Result GetContentCount(Out out_count) override; + virtual Result ListContentId(Out out_count, OutBuffer out_buf, u32 start_offset) override; + virtual Result GetSizeFromContentId(Out out_size, ContentId content_id) override; + virtual Result DisableForcibly() override; + virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override; + virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) override; + virtual Result ReadContentIdFile(OutBuffer buf, ContentId content_id, u64 offset) override; + virtual Result GetRightsIdFromPlaceHolderId(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id) override; + virtual Result GetRightsIdFromContentId(Out out_rights_id, Out out_key_generation, ContentId content_id) override; + virtual Result WriteContentForDebug(ContentId content_id, u64 offset, InBuffer data) override; + virtual Result GetFreeSpaceSize(Out out_size) override; + virtual Result GetTotalSpaceSize(Out out_size) override; + virtual Result FlushPlaceHolder() override; + virtual Result GetSizeFromPlaceHolderId(Out out, PlaceHolderId placeholder_id) override; + virtual Result RepairInvalidFileAttribute() override; + virtual Result GetRightsIdFromPlaceHolderIdWithCache(Out out_rights_id, Out out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) override; + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GeneratePlaceHolderId), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, CreatePlaceHolder), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, DeletePlaceHolder), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, HasPlaceHolder), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, WritePlaceHolder), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, Register), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, Delete), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, Has), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetPath), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetPlaceHolderPath), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, CleanupAllPlaceHolder), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, ListPlaceHolder), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GeneratePlaceHolderId), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetContentCount), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, ListContentId), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetSizeFromContentId), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, DisableForcibly), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, RevertToPlaceHolder, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, SetPlaceHolderSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, ReadContentIdFile, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetRightsIdFromPlaceHolderId, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetRightsIdFromContentId, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, WriteContentForDebug, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetFreeSpaceSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetTotalSpaceSize, FirmwareVersion_200), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, FlushPlaceHolder, FirmwareVersion_300), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetSizeFromPlaceHolderId, FirmwareVersion_400), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, RepairInvalidFileAttribute, FirmwareVersion_400), + MAKE_SERVICE_COMMAND_META(ReadOnlyContentStorageInterface, GetRightsIdFromPlaceHolderIdWithCache, FirmwareVersion_800), + }; + }; + +} diff --git a/stratosphere/ncm/source/ncm_utils.cpp b/stratosphere/ncm/source/ncm_utils.cpp new file mode 100644 index 000000000..05f55edaf --- /dev/null +++ b/stratosphere/ncm/source/ncm_utils.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ncm_utils.hpp" + +namespace sts::ncm { + + void GetStringFromContentId(char* out, ContentId content_id) { + for (size_t i = 0; i < sizeof(ContentId); i++) { + snprintf(out+i*2, 3, "%02x", content_id.uuid[i]); + } + } + + void GetStringFromPlaceHolderId(char* out, PlaceHolderId placeholder_id) { + for (size_t i = 0; i < sizeof(PlaceHolderId); i++) { + snprintf(out+i*2, 3, "%02x", placeholder_id.uuid[i]); + } + } + + Result GetPlaceHolderIdFromDirEntry(PlaceHolderId* out, struct dirent* dir_entry) { + if (strnlen(dir_entry->d_name, 0x30) != 0x24 || strncmp(dir_entry->d_name + 0x20, ".nca", 4) != 0) { + return ResultNcmInvalidPlaceHolderDirectoryEntry; + } + + PlaceHolderId placeholder_id = {0}; + char byte_string[2]; + char* end_ptr; + u64 converted_val; + + for (size_t i = 0; i < sizeof(PlaceHolderId); i++) { + char* name_char_pair = dir_entry->d_name + i * 2; + + byte_string[0] = name_char_pair[0]; + byte_string[1] = name_char_pair[1]; + + converted_val = strtoull(byte_string, &end_ptr, 0x10); + placeholder_id.uuid[i] = (u8)converted_val; + } + + *out = placeholder_id; + return ResultSuccess; + } + + std::optional GetContentIdFromString(const char* str, size_t len) { + ContentId content_id = {0}; + + if (len < 0x20) { + return std::nullopt; + } + + char byte_string[2]; + char* end_ptr; + u64 converted_val; + + for (size_t i = 0; i < sizeof(ContentId); i++) { + const char* char_par = str + i * 2; + + byte_string[0] = char_par[0]; + byte_string[1] = char_par[1]; + + converted_val = strtoull(byte_string, &end_ptr, 0x10); + content_id.uuid[i] = (u8)converted_val; + } + + return std::optional(content_id); + } + +} diff --git a/stratosphere/ncm/source/ncm_utils.hpp b/stratosphere/ncm/source/ncm_utils.hpp new file mode 100644 index 000000000..920357a8b --- /dev/null +++ b/stratosphere/ncm/source/ncm_utils.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Adubbz + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include + +namespace sts::ncm { + + void GetStringFromContentId(char* out, ContentId content_id); + void GetStringFromPlaceHolderId(char* out, PlaceHolderId placeholder_id); + + Result GetPlaceHolderIdFromDirEntry(PlaceHolderId* out, struct dirent* dir_entry); + std::optional GetContentIdFromString(const char* str, size_t len); + +};