mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-23 04:41:12 +00:00
Implement NCM
This commit is contained in:
parent
72dd25a99e
commit
bb26633cce
45 changed files with 6236 additions and 1 deletions
|
@ -1,4 +1,4 @@
|
|||
MODULES := loader pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt
|
||||
MODULES := loader pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt ncm
|
||||
|
||||
SUBFOLDERS := libstratosphere $(MODULES)
|
||||
|
||||
|
|
160
stratosphere/ncm/Makefile
Normal file
160
stratosphere/ncm/Makefile
Normal file
|
@ -0,0 +1,160 @@
|
|||
#---------------------------------------------------------------------------------
|
||||
.SUFFIXES:
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
ifeq ($(strip $(DEVKITPRO)),)
|
||||
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/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
|
||||
#---------------------------------------------------------------------------------------
|
66
stratosphere/ncm/ncm.json
Normal file
66
stratosphere/ncm/ncm.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"name": "NCM",
|
||||
"title_id": "0x0100000000000002",
|
||||
"main_thread_stack_size": "0x20000",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
90
stratosphere/ncm/source/impl/lr_manager.cpp
Normal file
90
stratosphere/ncm/source/impl/lr_manager.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "../lr_contentlocationresolver.hpp"
|
||||
#include "../lr_redirectonlylocationresolver.hpp"
|
||||
#include "lr_manager.hpp"
|
||||
|
||||
namespace sts::lr::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
BoundedMap<ncm::StorageId, std::shared_ptr<ILocationResolver>, 5> g_location_resolvers;
|
||||
std::shared_ptr<RegisteredLocationResolverInterface> g_registered_location_resolver = nullptr;
|
||||
std::shared_ptr<AddOnContentLocationResolverInterface> g_add_on_content_location_resolver = nullptr;
|
||||
HosMutex g_mutex;
|
||||
|
||||
}
|
||||
|
||||
Result OpenLocationResolver(Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id) {
|
||||
std::scoped_lock lk(g_mutex);
|
||||
auto resolver = g_location_resolvers.Find(storage_id);
|
||||
|
||||
if (!resolver) {
|
||||
if (storage_id == ncm::StorageId::Host) {
|
||||
g_location_resolvers[storage_id] = std::make_shared<RedirectOnlyLocationResolverInterface>();
|
||||
} else {
|
||||
auto content_resolver = std::make_shared<ContentLocationResolverInterface>(storage_id);
|
||||
R_TRY(content_resolver->Refresh());
|
||||
g_location_resolvers[storage_id] = std::move(content_resolver);
|
||||
}
|
||||
}
|
||||
|
||||
/* Make a copy of the resolver for output. */
|
||||
auto tmp_resolver = g_location_resolvers[storage_id];
|
||||
out.SetValue(std::move(tmp_resolver));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpenRegisteredLocationResolver(Out<std::shared_ptr<RegisteredLocationResolverInterface>> out) {
|
||||
std::scoped_lock lk(g_mutex);
|
||||
|
||||
if (!g_registered_location_resolver) {
|
||||
g_registered_location_resolver = std::make_shared<RegisteredLocationResolverInterface>();
|
||||
}
|
||||
|
||||
/* Make a copy of the resolver for output. */
|
||||
auto tmp_resolver = g_registered_location_resolver;
|
||||
out.SetValue(std::move(tmp_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;
|
||||
}
|
||||
|
||||
(*resolver)->Refresh();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpenAddOnContentLocationResolver(Out<std::shared_ptr<AddOnContentLocationResolverInterface>> out) {
|
||||
std::scoped_lock lk(g_mutex);
|
||||
|
||||
if (!g_add_on_content_location_resolver) {
|
||||
g_add_on_content_location_resolver = std::make_shared<AddOnContentLocationResolverInterface>();
|
||||
}
|
||||
|
||||
/* Make a copy of the resolver for output. */
|
||||
auto tmp_resolver = g_add_on_content_location_resolver;
|
||||
out.SetValue(std::move(tmp_resolver));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
33
stratosphere/ncm/source/impl/lr_manager.hpp
Normal file
33
stratosphere/ncm/source/impl/lr_manager.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../lr_addoncontentlocationresolver.hpp"
|
||||
#include "../lr_ilocationresolver.hpp"
|
||||
#include "../lr_registeredlocationresolver.hpp"
|
||||
|
||||
namespace sts::lr::impl {
|
||||
|
||||
/* Location Resolver API. */
|
||||
Result OpenLocationResolver(Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id);
|
||||
Result OpenRegisteredLocationResolver(Out<std::shared_ptr<RegisteredLocationResolverInterface>> out);
|
||||
Result RefreshLocationResolver(ncm::StorageId storage_id);
|
||||
Result OpenAddOnContentLocationResolver(Out<std::shared_ptr<AddOnContentLocationResolverInterface>> out);
|
||||
|
||||
}
|
130
stratosphere/ncm/source/impl/lr_redirection.cpp
Normal file
130
stratosphere/ncm/source/impl/lr_redirection.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lr_redirection.hpp"
|
||||
|
||||
namespace sts::lr::impl {
|
||||
|
||||
bool LocationRedirector::FindRedirection(Path *out, ncm::TitleId title_id) {
|
||||
if (this->redirection_list.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end(); it++) {
|
||||
if (it->title_id == title_id) {
|
||||
*out = it->path;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocationRedirector::SetRedirection(ncm::TitleId title_id, const Path &path, u32 flags) {
|
||||
this->EraseRedirection(title_id);
|
||||
auto redirection = new LocationRedirection(title_id, path, flags);
|
||||
this->redirection_list.push_back(*redirection);
|
||||
}
|
||||
|
||||
void LocationRedirector::SetRedirectionFlags(ncm::TitleId title_id, u32 flags) {
|
||||
if (!this->redirection_list.empty()) {
|
||||
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end(); it++) {
|
||||
if (it->title_id == title_id) {
|
||||
it->flags = flags;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocationRedirector::EraseRedirection(ncm::TitleId title_id) {
|
||||
if (!this->redirection_list.empty()) {
|
||||
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end(); it++) {
|
||||
if (it->title_id == title_id) {
|
||||
auto old = it;
|
||||
this->redirection_list.erase(old);
|
||||
delete &(*old);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocationRedirector::ClearRedirections(u32 flags) {
|
||||
for (auto it = this->redirection_list.begin(); it != this->redirection_list.end();) {
|
||||
if ((it->flags & flags) == flags) {
|
||||
auto old = it;
|
||||
it = this->redirection_list.erase(it);
|
||||
delete &(*old);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RegisteredLocationRedirector::FindRedirection(Path *out, ncm::TitleId title_id) {
|
||||
auto redirection = this->redirections.Find(title_id);
|
||||
|
||||
if (redirection) {
|
||||
*out = *redirection;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RegisteredLocationRedirector::SetRedirection(ncm::TitleId title_id, const Path& path) {
|
||||
if (this->redirections.IsFull()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->redirections[title_id] = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegisteredLocationRedirector::EraseRedirection(ncm::TitleId title_id) {
|
||||
this->redirections.Remove(title_id);
|
||||
}
|
||||
|
||||
void RegisteredLocationRedirector::ClearRedirections() {
|
||||
this->redirections.RemoveAll();
|
||||
}
|
||||
|
||||
bool AddOnContentRedirector::FindRedirection(ncm::StorageId *out, ncm::TitleId title_id) {
|
||||
auto redirection = this->redirections.Find(title_id);
|
||||
|
||||
if (redirection) {
|
||||
*out = *redirection;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Result AddOnContentRedirector::SetRedirection(ncm::TitleId title_id, ncm::StorageId storage_id) {
|
||||
if (this->redirections.IsFull()) {
|
||||
return ResultLrTooManyRegisteredPaths;
|
||||
}
|
||||
|
||||
this->redirections[title_id] = storage_id;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void AddOnContentRedirector::ClearRedirections() {
|
||||
this->redirections.RemoveAll();
|
||||
}
|
||||
|
||||
}
|
78
stratosphere/ncm/source/impl/lr_redirection.hpp
Normal file
78
stratosphere/ncm/source/impl/lr_redirection.hpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../lr_types.hpp"
|
||||
|
||||
namespace sts::lr::impl {
|
||||
|
||||
enum RedirectionFlags {
|
||||
RedirectionFlags_None = (0 << 0),
|
||||
RedirectionFlags_Application = (1 << 0),
|
||||
};
|
||||
|
||||
class LocationRedirection : public util::IntrusiveListBaseNode<LocationRedirection> {
|
||||
NON_COPYABLE(LocationRedirection);
|
||||
NON_MOVEABLE(LocationRedirection);
|
||||
|
||||
public:
|
||||
ncm::TitleId title_id;
|
||||
Path path;
|
||||
u32 flags;
|
||||
|
||||
LocationRedirection(ncm::TitleId title_id, const Path& path, u32 flags) :
|
||||
title_id(title_id), path(path), flags(flags) {
|
||||
}
|
||||
};
|
||||
|
||||
class LocationRedirector {
|
||||
NON_COPYABLE(LocationRedirector);
|
||||
NON_MOVEABLE(LocationRedirector);
|
||||
private:
|
||||
sts::util::IntrusiveListBaseTraits<LocationRedirection>::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 SetRedirectionFlags(ncm::TitleId title_id, u32 flags);
|
||||
void EraseRedirection(ncm::TitleId title_id);
|
||||
void ClearRedirections(u32 flags = RedirectionFlags_None);
|
||||
};
|
||||
|
||||
class RegisteredLocationRedirector {
|
||||
private:
|
||||
BoundedMap<ncm::TitleId, Path, 16> redirections;
|
||||
public:
|
||||
bool FindRedirection(Path *out, ncm::TitleId title_id);
|
||||
bool SetRedirection(ncm::TitleId title_id, const Path& path);
|
||||
void EraseRedirection(ncm::TitleId title_id);
|
||||
void ClearRedirections();
|
||||
};
|
||||
|
||||
class AddOnContentRedirector {
|
||||
private:
|
||||
BoundedMap<ncm::TitleId, ncm::StorageId, 128> redirections;
|
||||
public:
|
||||
bool FindRedirection(ncm::StorageId *out, ncm::TitleId title_id);
|
||||
Result SetRedirection(ncm::TitleId title_id, ncm::StorageId storage_id);
|
||||
void ClearRedirections();
|
||||
};
|
||||
|
||||
}
|
695
stratosphere/ncm/source/impl/ncm_content_manager.cpp
Normal file
695
stratosphere/ncm/source/impl/ncm_content_manager.cpp
Normal file
|
@ -0,0 +1,695 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/kvdb/kvdb_memory_key_value_store.hpp>
|
||||
#include <optional>
|
||||
|
||||
#include "../ncm_contentmetadatabase.hpp"
|
||||
#include "../ncm_contentstorage.hpp"
|
||||
#include "../ncm_fs.hpp"
|
||||
#include "../ncm_make_path.hpp"
|
||||
#include "../ncm_readonlycontentstorage.hpp"
|
||||
#include "ncm_content_manager.hpp"
|
||||
|
||||
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<IContentStorage> 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 = 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;
|
||||
} PACKED;
|
||||
|
||||
static_assert(sizeof(SaveDataMeta) == 0x20, "SaveDataMeta definition!");
|
||||
|
||||
struct ContentMetaDBEntry {
|
||||
NON_COPYABLE(ContentMetaDBEntry);
|
||||
NON_MOVEABLE(ContentMetaDBEntry);
|
||||
|
||||
char mount_point[16];
|
||||
char meta_path[128];
|
||||
StorageId storage_id;
|
||||
SaveDataMeta save_meta;
|
||||
std::shared_ptr<IContentMetaDatabase> content_meta_database;
|
||||
std::optional<kvdb::MemoryKeyValueStore<ContentMetaKey>> kvs;
|
||||
u32 max_content_metas;
|
||||
|
||||
inline ContentMetaDBEntry() : storage_id(StorageId::None), save_meta({0}),
|
||||
content_meta_database(nullptr), kvs(std::nullopt), max_content_metas(0) {
|
||||
mount_point[0] = '\0';
|
||||
meta_path[0] = '\0';
|
||||
}
|
||||
|
||||
Result Initialize(StorageId storage_id, SaveDataMeta& save_meta, size_t max_content_metas) {
|
||||
this->storage_id = storage_id;
|
||||
this->max_content_metas = max_content_metas;
|
||||
this->save_meta = save_meta;
|
||||
this->content_meta_database = nullptr;
|
||||
this->kvs.reset();
|
||||
MountName mount_name = 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;
|
||||
|
||||
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<HosMutex> 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(GetSaveDataFlags(¤t_flags, 0x8000000000000120)) && current_flags != (FsSaveDataFlags_SurviveFactoryReset | FsSaveDataFlags_SurviveFactoryResetForRefurbishment)) {
|
||||
SetSaveDataFlags(0x8000000000000120, FsSaveDataSpaceId_NandSystem, FsSaveDataFlags_SurviveFactoryReset | FsSaveDataFlags_SurviveFactoryResetForRefurbishment);
|
||||
}
|
||||
|
||||
R_TRY(ActivateContentMetaDatabase(StorageId::NandSystem));
|
||||
|
||||
/* Now for NandUser's content storage entry. */
|
||||
cur_meta_index = g_num_content_meta_entries;
|
||||
g_num_content_meta_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<HosMutex> 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<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
|
||||
|
||||
if (!entry) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
R_TRY(MountContentStorage(entry->mount_point, entry->content_storage_id));
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
Unmount(entry->mount_point);
|
||||
};
|
||||
|
||||
R_TRY(EnsureDirectoryRecursively(entry->root_path));
|
||||
R_TRY(EnsureContentAndPlaceHolderRoot(entry->root_path));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result VerifyContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
|
||||
|
||||
if (!entry) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
MountName mount_name = CreateUniqueMountName();
|
||||
char mount_root[128] = {0};
|
||||
strcpy(mount_root, mount_name.name);
|
||||
strcat(mount_root, strchr(entry->root_path, ':'));
|
||||
R_TRY(MountContentStorage(mount_name.name, entry->content_storage_id));
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
Unmount(mount_name.name);
|
||||
};
|
||||
|
||||
R_TRY(CheckContentStorageDirectoriesExist(mount_root));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpenContentStorage(std::shared_ptr<IContentStorage>* out, StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
ContentStorageEntry* entry = FindContentStorageEntry(storage_id);
|
||||
|
||||
if (!entry) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
auto content_storage = entry->content_storage;
|
||||
|
||||
if (!content_storage) {
|
||||
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<HosMutex> 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();
|
||||
Unmount(entry->mount_point);
|
||||
entry->content_storage = nullptr;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ActivateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(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(GetGameCardHandle(&gc_hnd));
|
||||
R_TRY(MountGameCardPartition(entry->mount_point, gc_hnd, FsGameCardPartiton_Secure));
|
||||
auto mount_guard = SCOPE_GUARD { Unmount(entry->mount_point); };
|
||||
auto content_storage = std::make_shared<ReadOnlyContentStorageInterface>();
|
||||
|
||||
R_TRY(content_storage->Initialize(entry->root_path, path::MakeContentPathFlat));
|
||||
entry->content_storage = std::move(content_storage);
|
||||
mount_guard.Cancel();
|
||||
} else {
|
||||
R_TRY(MountContentStorage(entry->mount_point, entry->content_storage_id));
|
||||
auto mount_guard = SCOPE_GUARD { Unmount(entry->mount_point); };
|
||||
MakeContentPathFunc content_path_func = nullptr;
|
||||
MakePlaceHolderPathFunc placeholder_path_func = nullptr;
|
||||
bool delay_flush = false;
|
||||
auto content_storage = std::make_shared<ContentStorageInterface>();
|
||||
|
||||
switch (storage_id) {
|
||||
case StorageId::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));
|
||||
entry->content_storage = std::move(content_storage);
|
||||
mount_guard.Cancel();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result InactivateContentStorage(StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(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;
|
||||
Unmount(entry->mount_point);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CreateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || storage_id == StorageId::GameCard || static_cast<u8>(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(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(MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id));
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
Unmount(entry->mount_point);
|
||||
};
|
||||
|
||||
R_TRY(EnsureDirectoryRecursively(entry->mount_point));
|
||||
R_TRY(fsdevCommitDevice(entry->mount_point));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result VerifyContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::GameCard) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(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(MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id));
|
||||
mounted_save_data = true;
|
||||
}
|
||||
|
||||
bool has_meta_path = false;
|
||||
R_TRY(HasDirectory(&has_meta_path, entry->meta_path));
|
||||
if (!has_meta_path) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
if (mounted_save_data) {
|
||||
Unmount(entry->mount_point);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpenContentMetaDatabase(std::shared_ptr<IContentMetaDatabase>* out, StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(storage_id) == 6) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
|
||||
|
||||
if (!entry) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
std::shared_ptr<IContentMetaDatabase> content_meta_db = entry->content_meta_database;
|
||||
|
||||
if (!content_meta_db) {
|
||||
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<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
ContentMetaDBEntry* entry = FindContentMetaDBEntry(storage_id);
|
||||
|
||||
if (!entry) {
|
||||
return ResultNcmUnknownStorage;
|
||||
}
|
||||
|
||||
std::shared_ptr<IContentMetaDatabase> content_meta_db = entry->content_meta_database;
|
||||
|
||||
if (!content_meta_db) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
/* N doesn't bother checking the result of this */
|
||||
content_meta_db->DisableForcibly();
|
||||
|
||||
if (storage_id != StorageId::GameCard) {
|
||||
Unmount(entry->mount_point);
|
||||
}
|
||||
|
||||
entry->content_meta_database = nullptr;
|
||||
entry->kvs.reset();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CleanupContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(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<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(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(MountSystemSaveData(entry->mount_point, entry->save_meta.space_id, entry->save_meta.id));
|
||||
auto mount_guard = SCOPE_GUARD { Unmount(entry->mount_point); };
|
||||
R_TRY(entry->kvs->Initialize(entry->meta_path, entry->max_content_metas));
|
||||
R_TRY(entry->kvs->Load());
|
||||
|
||||
auto content_meta_database = std::make_shared<ContentMetaDatabaseInterface>(&*entry->kvs, entry->mount_point);
|
||||
entry->content_meta_database = std::move(content_meta_database);
|
||||
mount_guard.Cancel();
|
||||
} else {
|
||||
R_TRY(entry->kvs->Initialize(entry->max_content_metas));
|
||||
R_TRY(entry->kvs->Load());
|
||||
auto content_meta_database = std::make_shared<OnMemoryContentMetaDatabaseInterface>(&*entry->kvs);
|
||||
entry->content_meta_database = std::move(content_meta_database);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result InactivateContentMetaDatabase(StorageId storage_id) {
|
||||
std::scoped_lock<HosMutex> lk(g_mutex);
|
||||
|
||||
if (storage_id == StorageId::None || static_cast<u8>(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) {
|
||||
Unmount(entry->mount_point);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
47
stratosphere/ncm/source/impl/ncm_content_manager.hpp
Normal file
47
stratosphere/ncm/source/impl/ncm_content_manager.hpp
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#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<IContentStorage>* out, StorageId storage_id);
|
||||
Result CloseContentStorageForcibly(StorageId storage_id);
|
||||
Result ActivateContentStorage(StorageId storage_id);
|
||||
Result InactivateContentStorage(StorageId storage_id);
|
||||
|
||||
/* Content Meta Database Management. */
|
||||
Result CreateContentMetaDatabase(StorageId storage_id);
|
||||
Result VerifyContentMetaDatabase(StorageId storage_id);
|
||||
Result OpenContentMetaDatabase(std::shared_ptr<IContentMetaDatabase>* out, StorageId storage_id);
|
||||
Result CloseContentMetaDatabaseForcibly(StorageId storage_id);
|
||||
Result CleanupContentMetaDatabase(StorageId storage_id);
|
||||
Result ActivateContentMetaDatabase(StorageId storage_id);
|
||||
Result InactivateContentMetaDatabase(StorageId storage_id);
|
||||
|
||||
}
|
233
stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp
Normal file
233
stratosphere/ncm/source/impl/ncm_placeholder_accessor.cpp
Normal file
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ncm_placeholder_accessor.hpp"
|
||||
#include "../ncm_fs.hpp"
|
||||
#include "../ncm_utils.hpp"
|
||||
#include "../ncm_make_path.hpp"
|
||||
#include "../ncm_path_utils.hpp"
|
||||
|
||||
namespace sts::ncm::impl {
|
||||
|
||||
unsigned int PlaceHolderAccessor::GetDirectoryDepth() {
|
||||
if (this->make_placeholder_path_func == static_cast<MakePlaceHolderPathFunc>(path::MakePlaceHolderPathFlat)) {
|
||||
return 1;
|
||||
} else if (this->make_placeholder_path_func == static_cast<MakePlaceHolderPathFunc>(path::MakePlaceHolderPathHashByteLayered)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::abort();
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::GetPlaceHolderPathUncached(char* placeholder_path_out, PlaceHolderId placeholder_id) {
|
||||
std::scoped_lock<HosMutex> lock(this->cache_mutex);
|
||||
|
||||
if (placeholder_id != InvalidUuid) {
|
||||
CacheEntry* found_cache = NULL;
|
||||
|
||||
for (size_t i = 0; i < PlaceHolderAccessor::MaxCaches; i++) {
|
||||
CacheEntry* cache = &this->caches[i];
|
||||
|
||||
if (placeholder_id == cache->id) {
|
||||
found_cache = cache;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_cache) {
|
||||
/* Flush and close */
|
||||
fsync(fileno(found_cache->handle));
|
||||
fclose(found_cache->handle);
|
||||
std::fill(found_cache->id.uuid, found_cache->id.uuid + sizeof(PlaceHolderId), 0);
|
||||
}
|
||||
}
|
||||
|
||||
this->GetPlaceHolderPath(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->GetPlaceHolderPathUncached(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->GetPlaceHolderPathUncached(placeholder_path, placeholder_id);
|
||||
|
||||
R_TRY_CATCH(fsdevDeleteDirectoryRecursively(placeholder_path)) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultNcmPlaceHolderNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
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->GetPlaceHolderPath(placeholder_path, placeholder_id);
|
||||
errno = 0;
|
||||
*out_handle = fopen(placeholder_path, "w+b");
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result PlaceHolderAccessor::SetSize(PlaceHolderId placeholder_id, size_t size) {
|
||||
char placeholder_path[FS_MAX_PATH] = {0};
|
||||
errno = 0;
|
||||
this->GetPlaceHolderPath(placeholder_path, placeholder_id);
|
||||
truncate(placeholder_path, size);
|
||||
|
||||
if (errno != 0) {
|
||||
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<HosMutex> lock(this->cache_mutex);
|
||||
|
||||
if (placeholder_id == InvalidUuid) {
|
||||
*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 = InvalidUuid;
|
||||
f = cache_entry->handle;
|
||||
}
|
||||
|
||||
this->FlushCache(f, placeholder_id);
|
||||
|
||||
errno = 0;
|
||||
fseek(f, 0L, SEEK_END);
|
||||
size_t size = ftell(f);
|
||||
fseek(f, 0L, SEEK_SET);
|
||||
|
||||
if (errno != 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->GetPlaceHolderPath(placeholder_path, placeholder_id);
|
||||
R_TRY(EnsureParentDirectoryRecursively(placeholder_path));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool PlaceHolderAccessor::LoadFromCache(FILE** out_handle, PlaceHolderId placeholder_id) {
|
||||
std::scoped_lock<HosMutex> lk(this->cache_mutex);
|
||||
CacheEntry *entry = this->FindInCache(placeholder_id);
|
||||
if (entry == nullptr) {
|
||||
return false;
|
||||
}
|
||||
entry->id = InvalidUuid;
|
||||
*out_handle = entry->handle;
|
||||
return true;
|
||||
}
|
||||
|
||||
PlaceHolderAccessor::CacheEntry *PlaceHolderAccessor::FindInCache(PlaceHolderId placeholder_id) {
|
||||
if (placeholder_id == InvalidUuid) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < MaxCaches; i++) {
|
||||
if (placeholder_id == this->caches[i].id) {
|
||||
return &this->caches[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::FlushCache(FILE* handle, PlaceHolderId placeholder_id) {
|
||||
std::scoped_lock<HosMutex> lk(this->cache_mutex);
|
||||
CacheEntry* cache = nullptr;
|
||||
|
||||
/* Find an empty cache */
|
||||
for (size_t i = 0; i < MaxCaches; i++) {
|
||||
if (placeholder_id != InvalidUuid) {
|
||||
cache = &this->caches[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* No empty caches found. Let's clear cache 0. */
|
||||
if (cache == nullptr) {
|
||||
cache = &this->caches[0];
|
||||
|
||||
/* Flush and close */
|
||||
fsync(fileno(cache->handle));
|
||||
fclose(cache->handle);
|
||||
cache->id = InvalidUuid;
|
||||
}
|
||||
|
||||
cache->id = placeholder_id;
|
||||
cache->handle = handle;
|
||||
cache->counter = this->cur_counter;
|
||||
this->cur_counter++;
|
||||
}
|
||||
|
||||
void PlaceHolderAccessor::ClearAllCaches() {
|
||||
for (size_t i = 0; i < MaxCaches; i++) {
|
||||
CacheEntry* cache = &this->caches[i];
|
||||
|
||||
if (cache->id != InvalidUuid) {
|
||||
fsync(fileno(cache->handle));
|
||||
fclose(cache->handle);
|
||||
cache->id = InvalidUuid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
76
stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp
Normal file
76
stratosphere/ncm/source/impl/ncm_placeholder_accessor.hpp
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../ncm_types.hpp"
|
||||
#include "../ncm_path_utils.hpp"
|
||||
|
||||
namespace sts::ncm::impl {
|
||||
|
||||
class PlaceHolderAccessor {
|
||||
public:
|
||||
class CacheEntry {
|
||||
public:
|
||||
PlaceHolderId id;
|
||||
FILE* handle;
|
||||
u64 counter;
|
||||
};
|
||||
|
||||
public:
|
||||
static constexpr size_t MaxCaches = 0x2;
|
||||
|
||||
CacheEntry caches[MaxCaches];
|
||||
char* root_path;
|
||||
u64 cur_counter;
|
||||
HosMutex cache_mutex;
|
||||
MakePlaceHolderPathFunc make_placeholder_path_func;
|
||||
bool delay_flush;
|
||||
|
||||
PlaceHolderAccessor() : cur_counter(0), delay_flush(false) {
|
||||
for (size_t i = 0; i < MaxCaches; i++) {
|
||||
caches[i].id = InvalidUuid;
|
||||
}
|
||||
}
|
||||
|
||||
inline void GetPlaceHolderRootPath(char* out_placeholder_root) {
|
||||
path::GetPlaceHolderRootPath(out_placeholder_root, this->root_path);
|
||||
}
|
||||
|
||||
inline void GetPlaceHolderPath(char* out_placeholder_path, PlaceHolderId placeholder_id) {
|
||||
char placeholder_root_path[FS_MAX_PATH] = {0};
|
||||
this->GetPlaceHolderRootPath(placeholder_root_path);
|
||||
this->make_placeholder_path_func(out_placeholder_path, placeholder_id, placeholder_root_path);
|
||||
}
|
||||
|
||||
unsigned int GetDirectoryDepth();
|
||||
void GetPlaceHolderPathUncached(char* out_placeholder_path, PlaceHolderId placeholder_id);
|
||||
Result Create(PlaceHolderId placeholder_id, size_t size);
|
||||
Result Delete(PlaceHolderId placeholder_id);
|
||||
Result Open(FILE** out_handle, PlaceHolderId placeholder_id);
|
||||
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);
|
||||
|
||||
CacheEntry *FindInCache(PlaceHolderId placeholder_id);
|
||||
bool LoadFromCache(FILE** out_handle, PlaceHolderId placeholder_id);
|
||||
void FlushCache(FILE* handle, PlaceHolderId placeholder_id);
|
||||
void ClearAllCaches();
|
||||
};
|
||||
|
||||
}
|
31
stratosphere/ncm/source/impl/ncm_rights_cache.cpp
Normal file
31
stratosphere/ncm/source/impl/ncm_rights_cache.cpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ncm_rights_cache.hpp"
|
||||
|
||||
namespace sts::ncm::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
RightsIdCache g_rights_id_cache;
|
||||
|
||||
}
|
||||
|
||||
RightsIdCache* GetRightsIdCache() {
|
||||
return &g_rights_id_cache;
|
||||
}
|
||||
|
||||
}
|
44
stratosphere/ncm/source/impl/ncm_rights_cache.hpp
Normal file
44
stratosphere/ncm/source/impl/ncm_rights_cache.hpp
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "../ncm_types.hpp"
|
||||
|
||||
namespace sts::ncm::impl {
|
||||
|
||||
class RightsIdCache {
|
||||
public:
|
||||
static constexpr size_t MaxEntries = 0x80;
|
||||
public:
|
||||
struct Entry {
|
||||
public:
|
||||
Uuid uuid;
|
||||
FsRightsId rights_id;
|
||||
u64 key_generation;
|
||||
u64 last_accessed = 1;
|
||||
};
|
||||
|
||||
Entry entries[MaxEntries];
|
||||
u64 counter = 2;
|
||||
HosMutex mutex;
|
||||
};
|
||||
|
||||
RightsIdCache* GetRightsIdCache();
|
||||
|
||||
}
|
57
stratosphere/ncm/source/lr_addoncontentlocationresolver.cpp
Normal file
57
stratosphere/ncm/source/lr_addoncontentlocationresolver.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "impl/ncm_content_manager.hpp"
|
||||
#include "lr_addoncontentlocationresolver.hpp"
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
AddOnContentLocationResolverInterface::AddOnContentLocationResolverInterface() {
|
||||
this->redirector.ClearRedirections();
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverInterface::ResolveAddOnContentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
ncm::StorageId storage_id = ncm::StorageId::None;
|
||||
|
||||
if (!this->redirector.FindRedirection(&storage_id, tid)) {
|
||||
return ResultLrAddOnContentNotFound;
|
||||
}
|
||||
|
||||
std::shared_ptr<ncm::IContentMetaDatabase> content_meta_database;
|
||||
std::shared_ptr<ncm::IContentStorage> content_storage;
|
||||
R_TRY(ncm::impl::OpenContentMetaDatabase(&content_meta_database, storage_id));
|
||||
R_TRY(ncm::impl::OpenContentStorage(&content_storage, storage_id));
|
||||
|
||||
ncm::ContentId data_content_id;
|
||||
R_TRY(content_meta_database->GetLatestData(&data_content_id, tid));
|
||||
R_ASSERT(content_storage->GetPath(&path, data_content_id));
|
||||
*out.pointer = path;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverInterface::RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::TitleId tid) {
|
||||
R_TRY(this->redirector.SetRedirection(tid, storage_id));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result AddOnContentLocationResolverInterface::UnregisterAllAddOnContentPath() {
|
||||
this->redirector.ClearRedirections();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
49
stratosphere/ncm/source/lr_addoncontentlocationresolver.hpp
Normal file
49
stratosphere/ncm/source/lr_addoncontentlocationresolver.hpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "impl/lr_redirection.hpp"
|
||||
#include "lr_types.hpp"
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
class AddOnContentLocationResolverInterface : public IServiceObject {
|
||||
protected:
|
||||
enum class CommandId {
|
||||
ResolveAddOnContentPath = 0,
|
||||
RegisterAddOnContentStorage = 1,
|
||||
UnregisterAllAddOnContentPath = 2,
|
||||
};
|
||||
private:
|
||||
impl::AddOnContentRedirector redirector;
|
||||
public:
|
||||
AddOnContentLocationResolverInterface();
|
||||
public:
|
||||
virtual Result ResolveAddOnContentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid);
|
||||
virtual Result RegisterAddOnContentStorage(ncm::StorageId storage_id, ncm::TitleId tid);
|
||||
virtual Result UnregisterAllAddOnContentPath();
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, ResolveAddOnContentPath),
|
||||
MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, RegisterAddOnContentStorage),
|
||||
MAKE_SERVICE_COMMAND_META(AddOnContentLocationResolverInterface, UnregisterAllAddOnContentPath),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
198
stratosphere/ncm/source/lr_contentlocationresolver.cpp
Normal file
198
stratosphere/ncm/source/lr_contentlocationresolver.cpp
Normal file
|
@ -0,0 +1,198 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "impl/ncm_content_manager.hpp"
|
||||
#include "lr_contentlocationresolver.hpp"
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
ContentLocationResolverInterface::~ContentLocationResolverInterface() {
|
||||
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 ContentLocationResolverInterface::ResolveProgramPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->program_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
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;
|
||||
|
||||
R_ASSERT(this->content_storage->GetPath(&path, program_content_id));
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::RedirectProgramPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->program_redirector.SetRedirection(tid, *path.pointer);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::ResolveApplicationControlPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->app_control_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultLrControlNotFound;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->html_docs_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultLrHtmlDocumentNotFound;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::ResolveDataPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
ncm::ContentId data_content_id;
|
||||
|
||||
R_TRY(this->content_meta_database->GetLatestData(&data_content_id, tid));
|
||||
R_ASSERT(this->content_storage->GetPath(&path, data_content_id));
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::RedirectApplicationControlPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->app_control_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::RedirectApplicationHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->html_docs_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::ResolveApplicationLegalInformationPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->legal_info_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultLrLegalInformationNotFound;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::RedirectApplicationLegalInformationPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->legal_info_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::Refresh() {
|
||||
std::shared_ptr<ncm::IContentMetaDatabase> content_meta_database;
|
||||
std::shared_ptr<ncm::IContentStorage> content_storage;
|
||||
R_TRY(ncm::impl::OpenContentMetaDatabase(&content_meta_database, this->storage_id));
|
||||
R_TRY(ncm::impl::OpenContentStorage(&content_storage, this->storage_id));
|
||||
this->content_meta_database = std::move(content_meta_database);
|
||||
this->content_storage = std::move(content_storage);
|
||||
|
||||
this->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 ContentLocationResolverInterface::RedirectApplicationProgramPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::ClearApplicationRedirection() {
|
||||
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 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<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->debug_program_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
R_TRY_CATCH(this->ResolveProgramPath(&path, tid)) {
|
||||
R_CATCH(ResultLrProgramNotFound) {
|
||||
return ResultLrDebugProgramNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::RedirectProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->debug_program_redirector.SetRedirection(tid, *path.pointer);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::RedirectApplicationProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->debug_program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::TitleId tid) {
|
||||
this->debug_program_redirector.EraseRedirection(tid);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
84
stratosphere/ncm/source/lr_contentlocationresolver.hpp
Normal file
84
stratosphere/ncm/source/lr_contentlocationresolver.hpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#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<ncm::IContentMetaDatabase> content_meta_database;
|
||||
std::shared_ptr<ncm::IContentStorage> content_storage;
|
||||
public:
|
||||
ContentLocationResolverInterface(ncm::StorageId storage_id) : storage_id(storage_id) {
|
||||
}
|
||||
|
||||
~ContentLocationResolverInterface();
|
||||
public:
|
||||
virtual Result ResolveProgramPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectProgramPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result ResolveApplicationControlPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result ResolveDataPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectApplicationControlPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result RedirectApplicationHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result ResolveApplicationLegalInformationPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectApplicationLegalInformationPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result Refresh() override;
|
||||
virtual Result RedirectApplicationProgramPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result ClearApplicationRedirection() 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<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result RedirectApplicationProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) 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, RedirectApplicationControlPath),
|
||||
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationHtmlDocumentPath),
|
||||
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ResolveApplicationLegalInformationPath),
|
||||
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationLegalInformationPath),
|
||||
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, Refresh),
|
||||
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, RedirectApplicationProgramPath, FirmwareVersion_500),
|
||||
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, ClearApplicationRedirection, FirmwareVersion_500),
|
||||
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, RedirectApplicationProgramPathForDebug, FirmwareVersion_700),
|
||||
MAKE_SERVICE_COMMAND_META(ContentLocationResolverInterface, EraseProgramRedirectionForDebug, FirmwareVersion_700),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
81
stratosphere/ncm/source/lr_ilocationresolver.hpp
Normal file
81
stratosphere/ncm/source/lr_ilocationresolver.hpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#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,
|
||||
RedirectApplicationControlPath = 5,
|
||||
RedirectApplicationHtmlDocumentPath = 6,
|
||||
ResolveApplicationLegalInformationPath = 7,
|
||||
RedirectApplicationLegalInformationPath = 8,
|
||||
Refresh = 9,
|
||||
RedirectApplicationProgramPath = 10,
|
||||
ClearApplicationRedirection = 11,
|
||||
EraseProgramRedirection = 12,
|
||||
EraseApplicationControlRedirection = 13,
|
||||
EraseApplicationHtmlDocumentRedirection = 14,
|
||||
EraseApplicationLegalInformationRedirection = 15,
|
||||
ResolveProgramPathForDebug = 16,
|
||||
RedirectProgramPathForDebug = 17,
|
||||
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;
|
||||
public:
|
||||
virtual Result ResolveProgramPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) = 0;
|
||||
virtual Result RedirectProgramPath(ncm::TitleId tid, InPointer<const Path> path) = 0;
|
||||
virtual Result ResolveApplicationControlPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) = 0;
|
||||
virtual Result ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) = 0;
|
||||
virtual Result ResolveDataPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) = 0;
|
||||
virtual Result RedirectApplicationControlPath(ncm::TitleId tid, InPointer<const Path> path) = 0;
|
||||
virtual Result RedirectApplicationHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path) = 0;
|
||||
virtual Result ResolveApplicationLegalInformationPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) = 0;
|
||||
virtual Result RedirectApplicationLegalInformationPath(ncm::TitleId tid, InPointer<const Path> path) = 0;
|
||||
virtual Result Refresh() = 0;
|
||||
virtual Result RedirectApplicationProgramPath(ncm::TitleId tid, InPointer<const Path> path) = 0;
|
||||
virtual Result ClearApplicationRedirection() = 0;
|
||||
virtual Result EraseProgramRedirection(ncm::TitleId tid) = 0;
|
||||
virtual Result EraseApplicationControlRedirection(ncm::TitleId tid) = 0;
|
||||
virtual Result EraseApplicationHtmlDocumentRedirection(ncm::TitleId tid) = 0;
|
||||
virtual Result EraseApplicationLegalInformationRedirection(ncm::TitleId tid) = 0;
|
||||
virtual Result ResolveProgramPathForDebug(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) = 0;
|
||||
virtual Result RedirectProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) = 0;
|
||||
virtual Result RedirectApplicationProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) = 0;
|
||||
virtual Result EraseProgramRedirectionForDebug(ncm::TitleId tid) = 0;
|
||||
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {};
|
||||
};
|
||||
|
||||
}
|
38
stratosphere/ncm/source/lr_manager_service.cpp
Normal file
38
stratosphere/ncm/source/lr_manager_service.cpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "impl/lr_manager.hpp"
|
||||
#include "lr_manager_service.hpp"
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
Result LocationResolverManagerService::OpenLocationResolver(Out<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id) {
|
||||
return impl::OpenLocationResolver(out, storage_id);
|
||||
}
|
||||
|
||||
Result LocationResolverManagerService::OpenRegisteredLocationResolver(Out<std::shared_ptr<RegisteredLocationResolverInterface>> out) {
|
||||
return impl::OpenRegisteredLocationResolver(out);
|
||||
}
|
||||
|
||||
Result LocationResolverManagerService::RefreshLocationResolver(ncm::StorageId storage_id) {
|
||||
return impl::RefreshLocationResolver(storage_id);
|
||||
}
|
||||
|
||||
Result LocationResolverManagerService::OpenAddOnContentLocationResolver(Out<std::shared_ptr<AddOnContentLocationResolverInterface>> out) {
|
||||
return impl::OpenAddOnContentLocationResolver(out);
|
||||
}
|
||||
|
||||
}
|
50
stratosphere/ncm/source/lr_manager_service.hpp
Normal file
50
stratosphere/ncm/source/lr_manager_service.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "lr_addoncontentlocationresolver.hpp"
|
||||
#include "lr_ilocationresolver.hpp"
|
||||
#include "lr_registeredlocationresolver.hpp"
|
||||
|
||||
namespace 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<std::shared_ptr<ILocationResolver>> out, ncm::StorageId storage_id);
|
||||
virtual Result OpenRegisteredLocationResolver(Out<std::shared_ptr<RegisteredLocationResolverInterface>> out);
|
||||
virtual Result RefreshLocationResolver(ncm::StorageId storage_id);
|
||||
virtual Result OpenAddOnContentLocationResolver(Out<std::shared_ptr<AddOnContentLocationResolverInterface>> 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),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
174
stratosphere/ncm/source/lr_redirectonlylocationresolver.cpp
Normal file
174
stratosphere/ncm/source/lr_redirectonlylocationresolver.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "impl/ncm_content_manager.hpp"
|
||||
#include "lr_redirectonlylocationresolver.hpp"
|
||||
|
||||
namespace 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<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->program_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultLrProgramNotFound;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::RedirectProgramPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->program_redirector.SetRedirection(tid, *path.pointer);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::ResolveApplicationControlPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->app_control_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultLrControlNotFound;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->html_docs_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultLrHtmlDocumentNotFound;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::ResolveDataPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
return ResultLrDataNotFound;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::RedirectApplicationControlPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->app_control_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::RedirectApplicationHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->html_docs_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::ResolveApplicationLegalInformationPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->legal_info_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
return ResultLrLegalInformationNotFound;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::RedirectApplicationLegalInformationPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->legal_info_redirector.SetRedirection(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::RedirectApplicationProgramPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::ClearApplicationRedirection() {
|
||||
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::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<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (this->debug_program_redirector.FindRedirection(&path, tid)) {
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
R_TRY_CATCH(this->ResolveProgramPath(&path, tid)) {
|
||||
R_CATCH(ResultLrProgramNotFound) {
|
||||
return ResultLrDebugProgramNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::RedirectProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->debug_program_redirector.SetRedirection(tid, *path.pointer);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::RedirectApplicationProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
this->debug_program_redirector.SetRedirection(tid, *path.pointer, impl::RedirectionFlags_Application);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RedirectOnlyLocationResolverInterface::EraseProgramRedirectionForDebug(ncm::TitleId tid) {
|
||||
this->debug_program_redirector.EraseRedirection(tid);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
74
stratosphere/ncm/source/lr_redirectonlylocationresolver.hpp
Normal file
74
stratosphere/ncm/source/lr_redirectonlylocationresolver.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "lr_contentlocationresolver.hpp"
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
class RedirectOnlyLocationResolverInterface : public ILocationResolver {
|
||||
public:
|
||||
~RedirectOnlyLocationResolverInterface();
|
||||
public:
|
||||
virtual Result ResolveProgramPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectProgramPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result ResolveApplicationControlPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result ResolveApplicationHtmlDocumentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result ResolveDataPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectApplicationControlPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result RedirectApplicationHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result ResolveApplicationLegalInformationPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectApplicationLegalInformationPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result Refresh() override;
|
||||
virtual Result RedirectApplicationProgramPath(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result ClearApplicationRedirection() 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<Path, 0x1> out, ncm::TitleId tid) override;
|
||||
virtual Result RedirectProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) override;
|
||||
virtual Result RedirectApplicationProgramPathForDebug(ncm::TitleId tid, InPointer<const Path> path) 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, RedirectApplicationControlPath),
|
||||
MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationHtmlDocumentPath),
|
||||
MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ResolveApplicationLegalInformationPath),
|
||||
MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationLegalInformationPath),
|
||||
MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, Refresh),
|
||||
MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, RedirectApplicationProgramPath, FirmwareVersion_500),
|
||||
MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, ClearApplicationRedirection, FirmwareVersion_500),
|
||||
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, RedirectApplicationProgramPathForDebug, FirmwareVersion_700),
|
||||
MAKE_SERVICE_COMMAND_META(RedirectOnlyLocationResolverInterface, EraseProgramRedirectionForDebug, FirmwareVersion_700),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
108
stratosphere/ncm/source/lr_registeredlocationresolver.cpp
Normal file
108
stratosphere/ncm/source/lr_registeredlocationresolver.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "lr_registeredlocationresolver.hpp"
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
RegisteredLocationResolverInterface::RegisteredLocationResolverInterface() {
|
||||
this->registered_program_redirector.ClearRedirections();
|
||||
this->registered_html_docs_redirector.ClearRedirections();
|
||||
}
|
||||
|
||||
RegisteredLocationResolverInterface::~RegisteredLocationResolverInterface() {
|
||||
/* Ensure entries are deallocated */
|
||||
this->html_docs_redirector.ClearRedirections();
|
||||
this->program_redirector.ClearRedirections();
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::ResolveProgramPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (!this->program_redirector.FindRedirection(&path, tid)) {
|
||||
if (!this->registered_program_redirector.FindRedirection(&path, tid)) {
|
||||
return ResultLrProgramNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
*out.pointer = path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::RegisterProgramPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
Path tmp_path = *path.pointer;
|
||||
|
||||
if (!this->registered_program_redirector.SetRedirection(tid, tmp_path)) {
|
||||
this->registered_program_redirector.ClearRedirections();
|
||||
this->registered_program_redirector.SetRedirection(tid, tmp_path);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::UnregisterProgramPath(ncm::TitleId tid) {
|
||||
this->registered_program_redirector.EraseRedirection(tid);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::RedirectProgramPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
Path tmp_path = *path.pointer;
|
||||
this->program_redirector.SetRedirection(tid, tmp_path);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::ResolveHtmlDocumentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid) {
|
||||
Path path;
|
||||
|
||||
if (!this->html_docs_redirector.FindRedirection(&path, tid)) {
|
||||
if (!this->registered_html_docs_redirector.FindRedirection(&path, tid)) {
|
||||
return ResultLrProgramNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
*out.pointer = path;
|
||||
return ResultLrHtmlDocumentNotFound;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::RegisterHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
Path tmp_path = *path.pointer;
|
||||
|
||||
if (!this->registered_html_docs_redirector.SetRedirection(tid, tmp_path)) {
|
||||
this->registered_html_docs_redirector.ClearRedirections();
|
||||
this->registered_html_docs_redirector.SetRedirection(tid, tmp_path);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::UnregisterHtmlDocumentPath(ncm::TitleId tid) {
|
||||
this->registered_html_docs_redirector.EraseRedirection(tid);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::RedirectHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path) {
|
||||
Path tmp_path = *path.pointer;
|
||||
this->html_docs_redirector.SetRedirection(tid, tmp_path);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result RegisteredLocationResolverInterface::Refresh() {
|
||||
this->registered_program_redirector.ClearRedirections();
|
||||
this->registered_html_docs_redirector.ClearRedirections();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
71
stratosphere/ncm/source/lr_registeredlocationresolver.hpp
Normal file
71
stratosphere/ncm/source/lr_registeredlocationresolver.hpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "impl/lr_redirection.hpp"
|
||||
#include "lr_types.hpp"
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
class RegisteredLocationResolverInterface final : public IServiceObject {
|
||||
protected:
|
||||
enum class CommandId {
|
||||
ResolveProgramPath = 0,
|
||||
RegisterProgramPath = 1,
|
||||
UnregisterProgramPath = 2,
|
||||
RedirectProgramPath = 3,
|
||||
ResolveHtmlDocumentPath = 4,
|
||||
RegisterHtmlDocumentPath = 5,
|
||||
UnregisterHtmlDocumentPath = 6,
|
||||
RedirectHtmlDocumentPath = 7,
|
||||
Refresh = 8,
|
||||
};
|
||||
private:
|
||||
impl::LocationRedirector program_redirector;
|
||||
impl::RegisteredLocationRedirector registered_program_redirector;
|
||||
impl::LocationRedirector html_docs_redirector;
|
||||
impl::RegisteredLocationRedirector registered_html_docs_redirector;
|
||||
public:
|
||||
RegisteredLocationResolverInterface();
|
||||
~RegisteredLocationResolverInterface();
|
||||
|
||||
Result ResolveProgramPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid);
|
||||
Result RegisterProgramPath(ncm::TitleId tid, InPointer<const Path> path);
|
||||
Result UnregisterProgramPath(ncm::TitleId tid);
|
||||
Result RedirectProgramPath(ncm::TitleId tid, InPointer<const Path> path);
|
||||
Result ResolveHtmlDocumentPath(OutPointerWithServerSize<Path, 0x1> out, ncm::TitleId tid);
|
||||
Result RegisterHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path);
|
||||
Result UnregisterHtmlDocumentPath(ncm::TitleId tid);
|
||||
Result RedirectHtmlDocumentPath(ncm::TitleId tid, InPointer<const Path> path);
|
||||
Result Refresh();
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, ResolveProgramPath),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RegisterProgramPath),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, UnregisterProgramPath),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RedirectProgramPath),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, ResolveHtmlDocumentPath, FirmwareVersion_200),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RegisterHtmlDocumentPath, FirmwareVersion_200),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, UnregisterHtmlDocumentPath, FirmwareVersion_200),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, RedirectHtmlDocumentPath, FirmwareVersion_200),
|
||||
MAKE_SERVICE_COMMAND_META(RegisteredLocationResolverInterface, Refresh, FirmwareVersion_700),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
115
stratosphere/ncm/source/lr_types.hpp
Normal file
115
stratosphere/ncm/source/lr_types.hpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::lr {
|
||||
|
||||
constexpr size_t MaxPathLen = 0x300;
|
||||
|
||||
struct Path {
|
||||
char path[MaxPathLen];
|
||||
|
||||
Path() {
|
||||
path[0] = '\0';
|
||||
}
|
||||
|
||||
Path(const char* path) {
|
||||
strlcpy(this->path, path, MaxPathLen);
|
||||
}
|
||||
|
||||
Path& operator=(const Path& other) {
|
||||
/* N appears to always memcpy paths, so we will too. */
|
||||
memcpy(this->path, other.path, MaxPathLen);
|
||||
this->EnsureNullTerminated();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void EnsureNullTerminated() {
|
||||
const size_t len = strnlen(this->path, MaxPathLen);
|
||||
|
||||
if (len == MaxPathLen) {
|
||||
path[MaxPathLen-1] = '\0';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<class Key, class Value, size_t N>
|
||||
class BoundedMap {
|
||||
private:
|
||||
std::array<std::optional<Key>, N> keys;
|
||||
std::array<Value, N> values;
|
||||
public:
|
||||
Value *Find(const Key &key) {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
if (this->keys[i] && this->keys[i].value() == key) {
|
||||
return &this->values[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Remove(const Key &key) {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
if (this->keys[i] && this->keys[i].value() == key) {
|
||||
this->keys[i].reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveAll() {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
this->keys[i].reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool IsFull() {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
if (!this->keys[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Value &operator[](const Key &key) {
|
||||
/* Try to find an existing value. */
|
||||
{
|
||||
Value *value = this->Find(key);
|
||||
if (value) {
|
||||
return *value;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reference a new value. */
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
if (!this->keys[i]) {
|
||||
this->keys[i] = key;
|
||||
return this->values[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* We ran out of space in the map. */
|
||||
std::abort();
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr Result ResultLrDebugProgramNotFound = MAKERESULT(Module_Lr, 10);
|
||||
|
||||
}
|
84
stratosphere/ncm/source/ncm_content_manager_service.cpp
Normal file
84
stratosphere/ncm/source/ncm_content_manager_service.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "impl/ncm_content_manager.hpp"
|
||||
#include "ncm_content_manager_service.hpp"
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
ContentManagerService::~ContentManagerService() {
|
||||
impl::FinalizeContentManager();
|
||||
}
|
||||
|
||||
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<std::shared_ptr<IContentStorage>> out, StorageId storage_id) {
|
||||
std::shared_ptr<IContentStorage> content_storage;
|
||||
R_TRY(impl::OpenContentStorage(&content_storage, storage_id));
|
||||
out.SetValue(std::move(content_storage));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentManagerService::OpenContentMetaDatabase(Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id) {
|
||||
std::shared_ptr<IContentMetaDatabase> content_meta_database;
|
||||
R_TRY(impl::OpenContentMetaDatabase(&content_meta_database, storage_id));
|
||||
out.SetValue(std::move(content_meta_database));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentManagerService::CloseContentStorageForcibly(StorageId storage_id) {
|
||||
return impl::CloseContentStorageForcibly(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerService::CloseContentMetaDatabaseForcibly(StorageId storage_id) {
|
||||
return impl::CloseContentMetaDatabaseForcibly(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerService::CleanupContentMetaDatabase(StorageId storage_id) {
|
||||
return impl::CleanupContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerService::ActivateContentStorage(StorageId storage_id) {
|
||||
return impl::ActivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerService::InactivateContentStorage(StorageId storage_id) {
|
||||
return impl::InactivateContentStorage(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerService::ActivateContentMetaDatabase(StorageId storage_id) {
|
||||
return impl::ActivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
Result ContentManagerService::InactivateContentMetaDatabase(StorageId storage_id) {
|
||||
return impl::InactivateContentMetaDatabase(storage_id);
|
||||
}
|
||||
|
||||
}
|
77
stratosphere/ncm/source/ncm_content_manager_service.hpp
Normal file
77
stratosphere/ncm/source/ncm_content_manager_service.hpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "ncm_icontentmetadatabase.hpp"
|
||||
#include "ncm_icontentstorage.hpp"
|
||||
|
||||
namespace 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,
|
||||
};
|
||||
public:
|
||||
~ContentManagerService();
|
||||
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<std::shared_ptr<IContentStorage>> out, StorageId storage_id);
|
||||
virtual Result OpenContentMetaDatabase(Out<std::shared_ptr<IContentMetaDatabase>> out, StorageId storage_id);
|
||||
virtual Result CloseContentStorageForcibly(StorageId storage_id);
|
||||
virtual Result CloseContentMetaDatabaseForcibly(StorageId storage_id);
|
||||
virtual Result CleanupContentMetaDatabase(StorageId storage_id);
|
||||
virtual Result ActivateContentStorage(StorageId storage_id);
|
||||
virtual Result InactivateContentStorage(StorageId storage_id);
|
||||
virtual Result ActivateContentMetaDatabase(StorageId storage_id);
|
||||
virtual Result InactivateContentMetaDatabase(StorageId storage_id);
|
||||
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),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
697
stratosphere/ncm/source/ncm_contentmetadatabase.cpp
Normal file
697
stratosphere/ncm/source/ncm_contentmetadatabase.cpp
Normal file
|
@ -0,0 +1,697 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ncm_contentmetadatabase.hpp"
|
||||
#include "ncm_utils.hpp"
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
namespace {
|
||||
|
||||
struct InstallContentMetaHeader {
|
||||
u16 extended_header_size;
|
||||
u16 content_count;
|
||||
u16 content_meta_count;
|
||||
ContentMetaAttribute attributes;
|
||||
u8 padding;
|
||||
};
|
||||
|
||||
static_assert(sizeof(InstallContentMetaHeader) == 0x8, "InstallContentMetaHeader definition!");
|
||||
|
||||
struct ApplicationMetaExtendedHeader {
|
||||
TitleId patch_id;
|
||||
u32 required_system_version;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
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 InstallContentMetaHeader* GetValueHeader(const void* value) {
|
||||
return reinterpret_cast<const InstallContentMetaHeader*>(value);
|
||||
}
|
||||
|
||||
template<typename ExtendedHeaderType>
|
||||
inline const ExtendedHeaderType* GetValueExtendedHeader(const void* value) {
|
||||
return reinterpret_cast<const ExtendedHeaderType*>(reinterpret_cast<const u8*>(value) + sizeof(InstallContentMetaHeader));
|
||||
}
|
||||
|
||||
inline const ContentInfo* GetValueContentInfos(const void* value) {
|
||||
return reinterpret_cast<const ContentInfo*>(reinterpret_cast<const u8*>(value) + sizeof(InstallContentMetaHeader) + GetValueHeader(value)->extended_header_size);
|
||||
}
|
||||
|
||||
inline const ContentMetaInfo* GetValueContentMetaInfos(const void* value) {
|
||||
auto header = GetValueHeader(value);
|
||||
return reinterpret_cast<const ContentMetaInfo*>(reinterpret_cast<const u8*>(GetValueContentInfos(value)) + sizeof(ContentInfo) * header->content_count);
|
||||
}
|
||||
|
||||
Result GetContentMetaSize(size_t *out, const ContentMetaKey &key, const kvdb::MemoryKeyValueStore<ContentMetaKey> *kvs) {
|
||||
R_TRY_CATCH(kvs->GetValueSize(out, key)) {
|
||||
R_CATCH(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<ContentMetaKey> *kvs) {
|
||||
R_TRY(GetContentMetaSize(out_size, key, kvs));
|
||||
R_TRY(kvs->GetValuePointer(out_value_ptr, key));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::GetContentIdByTypeImpl(ContentId* out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
ContentMetaKey key = {0};
|
||||
key.id = tid;
|
||||
|
||||
if (this->kvs->GetCount() == 0) {
|
||||
return ResultNcmContentMetaNotFound;
|
||||
}
|
||||
|
||||
auto entry = this->kvs->lower_bound(key);
|
||||
if (entry == this->kvs->end()) {
|
||||
return ResultNcmContentMetaNotFound;
|
||||
}
|
||||
|
||||
bool found_key = false;
|
||||
|
||||
for (; 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<u8> value) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
R_TRY(this->kvs->Set(key, value.buffer, value.num_elements));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::Get(Out<u64> out_size, ContentMetaKey key, OutBuffer<u8> out_value) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
R_TRY(this->kvs->Get(out_size.GetPointer(), out_value.buffer, out_value.num_elements, key));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::Remove(ContentMetaKey key) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
R_TRY(this->kvs->Remove(key));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::GetContentIdByType(Out<ContentId> out_content_id, ContentMetaKey key, ContentType type) {
|
||||
ContentId content_id;
|
||||
R_TRY(this->GetContentIdByTypeImpl(&content_id, key, type, std::nullopt));
|
||||
out_content_id.SetValue(content_id);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::ListContentInfo(Out<u32> out_entries_written, OutBuffer<ContentInfo> out_info, ContentMetaKey key, u32 start_index) {
|
||||
if (start_index >> 0x1f != 0) {
|
||||
return ResultNcmInvalidOffset;
|
||||
}
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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 entries_written = 0;
|
||||
|
||||
if (out_info.num_elements == 0) {
|
||||
out_entries_written.SetValue(0);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
for (size_t i = start_index; i < out_info.num_elements; i++) {
|
||||
/* We have no more entries we can read out. */
|
||||
if (header->content_count <= start_index + i) {
|
||||
break;
|
||||
}
|
||||
|
||||
out_info[i] = content_infos[i];
|
||||
entries_written = i + 1;
|
||||
}
|
||||
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::List(Out<u32> out_entries_total, Out<u32> out_entries_written, OutBuffer<ContentMetaKey> out_info, ContentMetaType type, TitleId application_title_id, TitleId title_id_min, TitleId title_id_max, ContentInstallType install_type) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* If there are no entries then we've already successfully listed them all. */
|
||||
if (this->kvs->GetCount() == 0) {
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
|
||||
ContentMetaKey key = entry->GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!((static_cast<u8>(type) == 0 || key.type == type) && (title_id_min <= key.id && key.id <= title_id_max) && (key.install_type == ContentInstallType::Full || key.install_type == install_type))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (static_cast<u64>(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<TitleId>(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<ContentMetaKey> out_key, TitleId title_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
ContentMetaKey key;
|
||||
R_TRY(this->GetLatestContentMetaKeyImpl(&key, title_id));
|
||||
out_key.SetValue(key);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::ListApplication(Out<u32> out_entries_total, Out<u32> out_entries_written, OutBuffer<ApplicationContentMetaKey> out_keys, ContentMetaType type) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
size_t entries_total = 0;
|
||||
size_t entries_written = 0;
|
||||
|
||||
/* If there are no entries then we've already successfully listed them all. */
|
||||
if (this->kvs->GetCount() == 0) {
|
||||
out_entries_total.SetValue(entries_total);
|
||||
out_entries_written.SetValue(entries_written);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
for (auto entry = this->kvs->begin(); entry != this->kvs->end(); entry++) {
|
||||
ContentMetaKey key = entry->GetKey();
|
||||
|
||||
/* Check if this entry matches the given filters. */
|
||||
if (!((static_cast<u8>(type) == 0 || key.type == type))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const void* value = nullptr;
|
||||
size_t value_size = 0;
|
||||
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
|
||||
|
||||
if (key.type == ContentMetaType::Application || key.type == ContentMetaType::Patch || key.type == ContentMetaType::AddOnContent || key.type == ContentMetaType::Delta) {
|
||||
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<TitleId>(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<bool> out, ContentMetaKey key) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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<bool> out, InBuffer<ContentMetaKey> keys) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
if (keys.num_elements == 0) {
|
||||
out.SetValue(true);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < keys.num_elements; i++) {
|
||||
bool has = false;
|
||||
R_TRY(this->Has(&has, keys[i]));
|
||||
|
||||
if (!has) {
|
||||
out.SetValue(false);
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
|
||||
out.SetValue(true);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::GetSize(Out<u64> out_size, ContentMetaKey key) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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<u32> out_version, ContentMetaKey key) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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<ApplicationMetaExtendedHeader>(value);
|
||||
out_version.SetValue(ext_header->required_system_version);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::GetPatchId(Out<TitleId> out_patch_id, ContentMetaKey key) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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<ApplicationMetaExtendedHeader>(value);
|
||||
out_patch_id.SetValue(ext_header->patch_id);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::LookupOrphanContent(OutBuffer<bool> out_orphaned, InBuffer<ContentId> content_ids) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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() {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
R_TRY(this->kvs->Save());
|
||||
R_TRY(fsdevCommitDevice(this->mount_name));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::HasContent(Out<bool> out, ContentMetaKey key, ContentId content_id) {
|
||||
const void* value = nullptr;
|
||||
size_t value_size = 0;
|
||||
R_TRY(GetContentMetaValuePointer(&value, &value_size, key, this->kvs));
|
||||
const auto header = GetValueHeader(value);
|
||||
const ContentInfo* content_infos = GetValueContentInfos(value);
|
||||
|
||||
if (header->content_count > 0) {
|
||||
for (size_t i = 0; i < header->content_count; i++) {
|
||||
const ContentInfo* content_info = &content_infos[i];
|
||||
|
||||
if (content_id == content_info->content_id) {
|
||||
out.SetValue(false);
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.SetValue(false);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::ListContentMetaInfo(Out<u32> out_entries_written, OutBuffer<ContentMetaInfo> out_meta_info, ContentMetaKey key, u32 start_index) {
|
||||
if (start_index >> 0x1f != 0) {
|
||||
return ResultNcmInvalidOffset;
|
||||
}
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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<ContentMetaAttribute> out_attributes, ContentMetaKey key) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
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<u32> out_version, ContentMetaKey key) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
if (key.type != ContentMetaType::AddOnContent) {
|
||||
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<AddOnContentMetaExtendedHeader>(value);
|
||||
out_version.SetValue(ext_header->required_application_version);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::GetContentIdByTypeAndIdOffset(Out<ContentId> out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) {
|
||||
ContentId content_id;
|
||||
R_TRY(this->GetContentIdByTypeImpl(&content_id, key, type, std::optional(id_offset)));
|
||||
out_content_id.SetValue(content_id);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentMetaDatabaseInterface::GetLatestProgram(ContentId* out_content_id, 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<ContentMetaKey> out_key, TitleId tid) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
ContentMetaKey key = {0};
|
||||
key.id = tid;
|
||||
|
||||
if (this->kvs->GetCount() == 0) {
|
||||
return ResultNcmContentMetaNotFound;
|
||||
}
|
||||
|
||||
auto entry = this->kvs->lower_bound(key);
|
||||
if (entry == this->kvs->end() || entry->GetKey().id != key.id) {
|
||||
return ResultNcmContentMetaNotFound;
|
||||
}
|
||||
|
||||
for (; entry != this->kvs->end(); entry++) {
|
||||
if (entry->GetKey().id != key.id) {
|
||||
break;
|
||||
}
|
||||
|
||||
key = entry->GetKey();
|
||||
}
|
||||
|
||||
*out_key = key;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseInterface::LookupOrphanContent(OutBuffer<bool> out_orphaned, InBuffer<ContentId> content_ids) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
Result OnMemoryContentMetaDatabaseInterface::Commit() {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentMetaDatabase;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
120
stratosphere/ncm/source/ncm_contentmetadatabase.hpp
Normal file
120
stratosphere/ncm/source/ncm_contentmetadatabase.hpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "ncm_icontentmetadatabase.hpp"
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
class ContentMetaDatabaseInterface : public IContentMetaDatabase {
|
||||
public:
|
||||
ContentMetaDatabaseInterface(sts::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs, const char* mount_name) : IContentMetaDatabase(kvs, mount_name) {
|
||||
}
|
||||
ContentMetaDatabaseInterface(sts::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs) : IContentMetaDatabase(kvs) {
|
||||
}
|
||||
private:
|
||||
Result GetContentIdByTypeImpl(ContentId* out, const ContentMetaKey& key, ContentType type, std::optional<u8> id_offset);
|
||||
Result GetLatestContentMetaKeyImpl(ContentMetaKey* out_key, TitleId tid);
|
||||
public:
|
||||
virtual Result Set(ContentMetaKey key, InBuffer<u8> value) override;
|
||||
virtual Result Get(Out<u64> out_size, ContentMetaKey key, OutBuffer<u8> out_value) override;
|
||||
virtual Result Remove(ContentMetaKey key) override;
|
||||
virtual Result GetContentIdByType(Out<ContentId> out_content_id, ContentMetaKey key, ContentType type) override;
|
||||
virtual Result ListContentInfo(Out<u32> out_entries_written, OutBuffer<ContentInfo> out_info, ContentMetaKey key, u32 start_index) override;
|
||||
virtual Result List(Out<u32> out_entries_total, Out<u32> out_entries_written, OutBuffer<ContentMetaKey> out_info, ContentMetaType type, TitleId application_title_id, TitleId title_id_min, TitleId title_id_max, ContentInstallType install_type) override;
|
||||
virtual Result GetLatestContentMetaKey(Out<ContentMetaKey> out_key, TitleId tid) override;
|
||||
virtual Result ListApplication(Out<u32> out_entries_total, Out<u32> out_entries_written, OutBuffer<ApplicationContentMetaKey> out_keys, ContentMetaType type) override;
|
||||
virtual Result Has(Out<bool> out, ContentMetaKey key) override;
|
||||
virtual Result HasAll(Out<bool> out, InBuffer<ContentMetaKey> keys) override;
|
||||
virtual Result GetSize(Out<u64> out_size, ContentMetaKey key) override;
|
||||
virtual Result GetRequiredSystemVersion(Out<u32> out_version, ContentMetaKey key) override;
|
||||
virtual Result GetPatchId(Out<TitleId> out_patch_id, ContentMetaKey key) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result LookupOrphanContent(OutBuffer<bool> out_orphaned, InBuffer<ContentId> content_ids) override;
|
||||
virtual Result Commit() override;
|
||||
virtual Result HasContent(Out<bool> out, ContentMetaKey key, ContentId content_id) override;
|
||||
virtual Result ListContentMetaInfo(Out<u32> out_entries_written, OutBuffer<ContentMetaInfo> out_meta_info, ContentMetaKey key, u32 start_index) override;
|
||||
virtual Result GetAttributes(Out<ContentMetaAttribute> out_attributes, ContentMetaKey key) override;
|
||||
virtual Result GetRequiredApplicationVersion(Out<u32> out_version, ContentMetaKey key) override;
|
||||
virtual Result GetContentIdByTypeAndIdOffset(Out<ContentId> 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 {
|
||||
public:
|
||||
OnMemoryContentMetaDatabaseInterface(sts::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs) : ContentMetaDatabaseInterface(kvs) {
|
||||
}
|
||||
public:
|
||||
virtual Result GetLatestContentMetaKey(Out<ContentMetaKey> out_key, TitleId tid) override;
|
||||
virtual Result LookupOrphanContent(OutBuffer<bool> out_orphaned, InBuffer<ContentId> 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),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
762
stratosphere/ncm/source/ncm_contentstorage.cpp
Normal file
762
stratosphere/ncm/source/ncm_contentstorage.cpp
Normal file
|
@ -0,0 +1,762 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "impl/ncm_rights_cache.hpp"
|
||||
#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) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
R_TRY(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.root_path = this->root_path;
|
||||
this->placeholder_accessor.make_placeholder_path_func = *placeholder_path_func;
|
||||
this->placeholder_accessor.delay_flush = delay_flush;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void ContentStorageInterface::Finalize() {
|
||||
this->ClearContentCache();
|
||||
this->placeholder_accessor.ClearAllCaches();
|
||||
}
|
||||
|
||||
void ContentStorageInterface::ClearContentCache() {
|
||||
if (this->cached_content_id != InvalidUuid) {
|
||||
fclose(this->content_cache_file_handle);
|
||||
this->cached_content_id = InvalidUuid;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int ContentStorageInterface::GetContentDirectoryDepth() {
|
||||
if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPathFlat)) {
|
||||
return 1;
|
||||
} else if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPathHashByteLayered)) {
|
||||
return 2;
|
||||
} else if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPath10BitLayered)) {
|
||||
return 2;
|
||||
} else if (this->make_content_path_func == static_cast<MakeContentPathFunc>(path::MakeContentPathDualLayered)) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::OpenCachedContentFile(ContentId content_id) {
|
||||
if (this->cached_content_id == content_id) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
this->ClearContentCache();
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
this->GetContentPath(content_path, content_id);
|
||||
errno = 0;
|
||||
this->content_cache_file_handle = fopen(content_path, "rb");
|
||||
|
||||
if (this->content_cache_file_handle == NULL || errno != 0) {
|
||||
R_TRY_CATCH(fsdevGetLastResult()) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultNcmContentNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
}
|
||||
|
||||
this->cached_content_id = content_id;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GeneratePlaceHolderId(Out<PlaceHolderId> out) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
sts::rnd::GenerateRandomBytes(out.GetPointer(), sizeof(NcmNcaId));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
this->GetContentPath(content_path, content_id);
|
||||
|
||||
R_TRY(EnsureParentDirectoryRecursively(content_path));
|
||||
R_TRY(this->placeholder_accessor.Create(placeholder_id, size));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::DeletePlaceHolder(PlaceHolderId placeholder_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
return this->placeholder_accessor.Delete(placeholder_id);
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::HasPlaceHolder(Out<bool> out, PlaceHolderId placeholder_id) {
|
||||
if (this->disabled)
|
||||
return ResultNcmInvalidContentStorage;
|
||||
|
||||
char placeholder_path[FS_MAX_PATH] = {0};
|
||||
this->placeholder_accessor.GetPlaceHolderPath(placeholder_path, placeholder_id);
|
||||
|
||||
bool has = false;
|
||||
R_TRY(HasFile(&has, placeholder_path));
|
||||
out.SetValue(has);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer<u8> data) {
|
||||
/* Offset is too large */
|
||||
if (offset >> 0x3f != 0) {
|
||||
return ResultNcmInvalidOffset;
|
||||
}
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
FILE* f = nullptr;
|
||||
|
||||
R_TRY_CATCH(this->placeholder_accessor.Open(&f, placeholder_id)) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultNcmPlaceHolderNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
errno = 0;
|
||||
fseek(f, offset, SEEK_SET);
|
||||
fwrite(data.buffer, sizeof(u8), data.num_elements, f);
|
||||
|
||||
if (this->placeholder_accessor.delay_flush ^ 1) {
|
||||
fsync(fileno(f));
|
||||
}
|
||||
|
||||
this->placeholder_accessor.FlushCache(f, placeholder_id);
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::Register(PlaceHolderId placeholder_id, ContentId content_id) {
|
||||
this->ClearContentCache();
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char placeholder_path[FS_MAX_PATH] = {0};
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
|
||||
this->placeholder_accessor.GetPlaceHolderPathUncached(placeholder_path, placeholder_id);
|
||||
this->GetContentPath(content_path, content_id);
|
||||
|
||||
errno = 0;
|
||||
rename(placeholder_path, content_path);
|
||||
|
||||
if (errno != 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) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
this->ClearContentCache();
|
||||
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
|
||||
this->GetContentPath(content_path, content_id);
|
||||
|
||||
R_TRY_CATCH(fsdevDeleteDirectoryRecursively(content_path)) {
|
||||
R_CATCH(ResultFsPathNotFound) {
|
||||
return ResultNcmContentNotFound;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::Has(Out<bool> out, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
this->GetContentPath(content_path, content_id);
|
||||
|
||||
bool has = false;
|
||||
R_TRY(HasFile(&has, content_path));
|
||||
out.SetValue(has);
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetPath(OutPointerWithServerSize<lr::Path, 0x1> out, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
char common_path[FS_MAX_PATH] = {0};
|
||||
this->GetContentPath(content_path, content_id);
|
||||
R_TRY(ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
|
||||
*out.pointer = common_path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetPlaceHolderPath(OutPointerWithServerSize<lr::Path, 0x1> out, PlaceHolderId placeholder_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char placeholder_path[FS_MAX_PATH] = {0};
|
||||
char common_path[FS_MAX_PATH] = {0};
|
||||
this->placeholder_accessor.GetPlaceHolderPathUncached(placeholder_path, placeholder_id);
|
||||
R_TRY(ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path));
|
||||
*out.pointer = common_path;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::CleanupAllPlaceHolder() {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char placeholder_root_path[FS_MAX_PATH] = {0};
|
||||
this->placeholder_accessor.ClearAllCaches();
|
||||
this->placeholder_accessor.GetPlaceHolderRootPath(placeholder_root_path);
|
||||
|
||||
R_TRY(fsdevDeleteDirectoryRecursively(placeholder_root_path));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::ListPlaceHolder(Out<u32> out_count, OutBuffer<PlaceHolderId> out_buf) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char placeholder_root_path[FS_MAX_PATH] = {0};
|
||||
this->placeholder_accessor.GetPlaceHolderRootPath(placeholder_root_path);
|
||||
const unsigned int dir_depth = this->placeholder_accessor.GetDirectoryDepth();
|
||||
size_t entry_count = 0;
|
||||
|
||||
R_TRY(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<u32>(entry_count));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetContentCount(Out<u32> out_count) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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(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<u32> out_count, OutBuffer<ContentId> out_buf, u32 start_offset) {
|
||||
if (start_offset >> 0x1f != 0) {
|
||||
return ResultNcmInvalidOffset;
|
||||
}
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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(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<ContentId> content_id = GetContentIdFromString(dir_entry->d_name, name_len);
|
||||
|
||||
/* Skip to the next entry if the id was invalid. */
|
||||
if (!content_id) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
out_buf[entry_count++] = *content_id;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}));
|
||||
|
||||
out_count.SetValue(static_cast<u32>(entry_count));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetSizeFromContentId(Out<u64> out_size, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
this->GetContentPath(content_path, content_id);
|
||||
struct stat st;
|
||||
|
||||
errno = 0;
|
||||
stat(content_path, &st);
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
out_size.SetValue(st.st_size);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::DisableForcibly() {
|
||||
this->disabled = true;
|
||||
this->ClearContentCache();
|
||||
this->placeholder_accessor.ClearAllCaches();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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(EnsureParentDirectoryRecursively(new_content_path));
|
||||
|
||||
R_TRY(this->placeholder_accessor.EnsureRecursively(placeholder_id));
|
||||
this->placeholder_accessor.GetPlaceHolderPathUncached(placeholder_path, placeholder_id);
|
||||
errno = 0;
|
||||
rename(old_content_path, placeholder_path);
|
||||
|
||||
if (errno != 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) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
R_TRY(this->placeholder_accessor.SetSize(placeholder_id, size));
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::ReadContentIdFile(OutBuffer<u8> buf, ContentId content_id, u64 offset) {
|
||||
/* Offset is too large */
|
||||
if (offset >> 0x3f != 0) {
|
||||
return ResultNcmInvalidOffset;
|
||||
}
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
this->GetContentPath(content_path, content_id);
|
||||
R_TRY(this->OpenCachedContentFile(content_id));
|
||||
|
||||
errno = 0;
|
||||
fseek(this->content_cache_file_handle, offset, SEEK_SET);
|
||||
fread(buf.buffer, buf.num_elements, 1, this->content_cache_file_handle);
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetRightsIdFromPlaceHolderId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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.GetPlaceHolderPathUncached(placeholder_path, placeholder_id);
|
||||
R_TRY(ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path));
|
||||
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
|
||||
|
||||
out_rights_id.SetValue(rights_id);
|
||||
out_key_generation.SetValue(static_cast<u64>(key_generation));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetRightsIdFromContentId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
impl::RightsIdCache* rights_id_cache = impl::GetRightsIdCache();
|
||||
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(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 = &rights_id_cache->entries[i];
|
||||
|
||||
if (entry->last_accessed != 1 && content_id == entry->uuid) {
|
||||
entry->last_accessed = rights_id_cache->counter;
|
||||
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(ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
|
||||
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
|
||||
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(rights_id_cache->mutex);
|
||||
impl::RightsIdCache::Entry* eviction_candidate = &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 = &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;
|
||||
eviction_candidate->rights_id = rights_id;
|
||||
eviction_candidate->key_generation = key_generation;
|
||||
eviction_candidate->last_accessed = rights_id_cache->counter;
|
||||
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<u8> data) {
|
||||
FILE* f = nullptr;
|
||||
|
||||
/* Offset is too large */
|
||||
if (offset >> 0x3f != 0) {
|
||||
return ResultNcmInvalidOffset;
|
||||
}
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
errno = 0;
|
||||
f = fopen(content_path, "w+b");
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
fclose(f);
|
||||
};
|
||||
|
||||
if (f == NULL || errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
fseek(f, offset, SEEK_SET);
|
||||
fwrite(data.buffer, sizeof(u8), data.num_elements, f);
|
||||
fsync(fileno(f));
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetFreeSpaceSize(Out<u64> out_size) {
|
||||
struct statvfs st = {0};
|
||||
errno = 0;
|
||||
statvfs(this->root_path, &st);
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
out_size.SetValue(st.f_bfree);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetTotalSpaceSize(Out<u64> out_size) {
|
||||
struct statvfs st = {0};
|
||||
errno = 0;
|
||||
statvfs(this->root_path, &st);
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
out_size.SetValue(st.f_blocks);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::FlushPlaceHolder() {
|
||||
this->placeholder_accessor.ClearAllCaches();
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetSizeFromPlaceHolderId(Out<u64> out_size, PlaceHolderId placeholder_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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.GetPlaceHolderPathUncached(placeholder_path, placeholder_id);
|
||||
errno = 0;
|
||||
stat(placeholder_path, &st);
|
||||
|
||||
if (errno != 0) {
|
||||
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(TraverseDirectory(content_root_path, dir_depth, fix_file_attributes));
|
||||
|
||||
char placeholder_root_path[FS_MAX_PATH] = {0};
|
||||
this->placeholder_accessor.ClearAllCaches();
|
||||
this->placeholder_accessor.GetPlaceHolderRootPath(placeholder_root_path);
|
||||
dir_depth = this->placeholder_accessor.GetDirectoryDepth();
|
||||
|
||||
R_TRY(TraverseDirectory(placeholder_root_path, dir_depth, fix_file_attributes));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ContentStorageInterface::GetRightsIdFromPlaceHolderIdWithCache(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
impl::RightsIdCache* rights_id_cache = impl::GetRightsIdCache();
|
||||
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(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 = &rights_id_cache->entries[i];
|
||||
|
||||
if (entry->last_accessed != 1 && cache_content_id == entry->uuid) {
|
||||
entry->last_accessed = rights_id_cache->counter;
|
||||
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.GetPlaceHolderPathUncached(placeholder_path, placeholder_id);
|
||||
R_TRY(ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, placeholder_path));
|
||||
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
|
||||
|
||||
{
|
||||
std::scoped_lock<HosMutex> lk(rights_id_cache->mutex);
|
||||
impl::RightsIdCache::Entry* eviction_candidate = &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 = &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;
|
||||
eviction_candidate->rights_id = rights_id;
|
||||
eviction_candidate->key_generation = key_generation;
|
||||
eviction_candidate->last_accessed = rights_id_cache->counter;
|
||||
rights_id_cache->counter++;
|
||||
|
||||
/* Set output. */
|
||||
out_rights_id.SetValue(rights_id);
|
||||
out_key_generation.SetValue(key_generation);
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
}
|
115
stratosphere/ncm/source/ncm_contentstorage.hpp
Normal file
115
stratosphere/ncm/source/ncm_contentstorage.hpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "impl/ncm_placeholder_accessor.hpp"
|
||||
#include "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;
|
||||
public:
|
||||
~ContentStorageInterface();
|
||||
|
||||
Result Initialize(const char* root_path, MakeContentPathFunc content_path_func, MakePlaceHolderPathFunc placeholder_path_func, bool delay_flush);
|
||||
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<PlaceHolderId> out) override;
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
virtual Result HasPlaceHolder(Out<bool> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer<u8> data) override;
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
|
||||
virtual Result Delete(ContentId content_id) override;
|
||||
virtual Result Has(Out<bool> out, ContentId content_id) override;
|
||||
virtual Result GetPath(OutPointerWithServerSize<lr::Path, 0x1> out, ContentId content_id) override;
|
||||
virtual Result GetPlaceHolderPath(OutPointerWithServerSize<lr::Path, 0x1> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result CleanupAllPlaceHolder() override;
|
||||
virtual Result ListPlaceHolder(Out<u32> out_count, OutBuffer<PlaceHolderId> out_buf) override;
|
||||
virtual Result GetContentCount(Out<u32> out_count) override;
|
||||
virtual Result ListContentId(Out<u32> out_count, OutBuffer<ContentId> out_buf, u32 start_offset) override;
|
||||
virtual Result GetSizeFromContentId(Out<u64> out_size, ContentId content_id) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) override;
|
||||
virtual Result ReadContentIdFile(OutBuffer<u8> buf, ContentId content_id, u64 offset) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromContentId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, ContentId content_id) override;
|
||||
virtual Result WriteContentForDebug(ContentId content_id, u64 offset, InBuffer<u8> data) override;
|
||||
virtual Result GetFreeSpaceSize(Out<u64> out_size) override;
|
||||
virtual Result GetTotalSpaceSize(Out<u64> out_size) override;
|
||||
virtual Result FlushPlaceHolder() override;
|
||||
virtual Result GetSizeFromPlaceHolderId(Out<u64> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result RepairInvalidFileAttribute() override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(Out<FsRightsId> out_rights_id, Out<u64> 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),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
302
stratosphere/ncm/source/ncm_fs.cpp
Normal file
302
stratosphere/ncm/source/ncm_fs.cpp
Normal file
|
@ -0,0 +1,302 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "ncm_fs.hpp"
|
||||
#include "ncm_path_utils.hpp"
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
Result HasFile(bool* out, const char* path) {
|
||||
errno = 0;
|
||||
struct stat st;
|
||||
|
||||
*out = false;
|
||||
if (stat(path, &st) == 0 && S_ISREG(st.st_mode)) {
|
||||
*out = true;
|
||||
}
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result HasDirectory(bool* out, const char* path) {
|
||||
errno = 0;
|
||||
struct stat st;
|
||||
|
||||
*out = false;
|
||||
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
*out = true;
|
||||
}
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result CheckContentStorageDirectoriesExist(const char* root_path) {
|
||||
char content_root[FS_MAX_PATH] = {0};
|
||||
char placeholder_root[FS_MAX_PATH] = {0};
|
||||
|
||||
errno = 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) {
|
||||
return EnsureRecursively(dir_path, true);
|
||||
}
|
||||
|
||||
Result EnsureRecursively(const char* path, bool is_dir) {
|
||||
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] != ':') {
|
||||
/* Wipe the errno to prevent cross-contamination */
|
||||
errno = 0;
|
||||
/* Temporarily make the path terminate before the '/' */
|
||||
working_path_buf[i + 1] = 0;
|
||||
mkdir(working_path_buf + 1, S_IRWXU);
|
||||
|
||||
if (errno != 0) {
|
||||
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, false);
|
||||
}
|
||||
|
||||
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<HosMutex> 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 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;
|
||||
}
|
||||
|
||||
std::map<std::string, FsContentStorageId> 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();
|
||||
}
|
||||
|
||||
g_mount_content_storage[mount_point] = id;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result MountGameCardPartition(const char* mount_point, const FsGameCardHandle handle, FsGameCardPartiton partition) {
|
||||
FsFileSystem fs;
|
||||
R_TRY(fsOpenGameCardFileSystem(&fs, &handle, partition));
|
||||
|
||||
if (fsdevMountDevice(mount_point, fs) == -1) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
constexpr const char* SystemContentMountName = "@SystemContent";
|
||||
constexpr const char* UserContentMountName = "@UserContent";
|
||||
constexpr const char* SdCardContentMountName = "@SdCardContent";
|
||||
|
||||
Result ConvertToFsCommonPath(char* out_common_path, size_t out_len, const char* path) {
|
||||
if (!out_common_path || !path) {
|
||||
return ResultFsNullptrArgument;
|
||||
}
|
||||
|
||||
MountName mount_name = {0};
|
||||
const char* unqual_path = strchr(path, ':');
|
||||
|
||||
/* We should be given a qualified path. */
|
||||
if (!unqual_path || unqual_path > path + 0xf) {
|
||||
return ResultFsUnqualifiedPath;
|
||||
}
|
||||
|
||||
strncpy(mount_name.name, path, unqual_path - path);
|
||||
|
||||
if (!fsdevGetDeviceFileSystem(mount_name.name) || g_mount_content_storage.find(mount_name.name) == g_mount_content_storage.end()) {
|
||||
return ResultFsMountNameNotFound;
|
||||
}
|
||||
|
||||
FsContentStorageId content_storage_id = g_mount_content_storage[mount_name.name];
|
||||
char translated_path[FS_MAX_PATH] = {0};
|
||||
const char* common_mount_name;
|
||||
|
||||
switch (content_storage_id) {
|
||||
case FS_CONTENTSTORAGEID_NandSystem:
|
||||
common_mount_name = SystemContentMountName;
|
||||
break;
|
||||
|
||||
case FS_CONTENTSTORAGEID_NandUser:
|
||||
common_mount_name = UserContentMountName;
|
||||
break;
|
||||
|
||||
case FS_CONTENTSTORAGEID_SdCard:
|
||||
common_mount_name = SdCardContentMountName;
|
||||
break;
|
||||
|
||||
default:
|
||||
std::abort();
|
||||
};
|
||||
|
||||
if (fsdevTranslatePath(path, NULL, translated_path) == -1) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
snprintf(out_common_path, out_len, "%s:/%s", common_mount_name, 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;
|
||||
}
|
||||
|
||||
}
|
110
stratosphere/ncm/source/ncm_fs.hpp
Normal file
110
stratosphere/ncm/source/ncm_fs.hpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <sys/dirent.h>
|
||||
|
||||
#include "ncm_types.hpp"
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
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, bool is_dir);
|
||||
/* Create all parent directories for a file path */
|
||||
Result EnsureParentDirectoryRecursively(const char* path);
|
||||
|
||||
Result GetGameCardHandle(FsGameCardHandle* out_handle);
|
||||
|
||||
MountName CreateUniqueMountName();
|
||||
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<typename F>
|
||||
Result TraverseDirectory(const char* root_path, int max_level, F f) {
|
||||
bool should_continue = false;
|
||||
return TraverseDirectory(&should_continue, root_path, max_level, f);
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
Result TraverseDirectory(bool* out_should_continue, const char* root_path, int max_level, F f) {
|
||||
DIR *dir;
|
||||
struct dirent* dir_entry = nullptr;
|
||||
if (max_level < 1) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
bool retry_dir_read = true;
|
||||
while (retry_dir_read) {
|
||||
retry_dir_read = false;
|
||||
|
||||
if ((dir = opendir(root_path)) == nullptr) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
ON_SCOPE_EXIT { closedir(dir); };
|
||||
|
||||
while ((dir_entry = readdir(dir)) != nullptr) {
|
||||
if (strcmp(dir_entry->d_name, ".") == 0 || strcmp(dir_entry->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char current_path[FS_MAX_PATH];
|
||||
if (snprintf(current_path, FS_MAX_PATH-1, "%s/%s", root_path, dir_entry->d_name) < 0) {
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool should_continue = true;
|
||||
bool should_retry_dir_read = false;
|
||||
R_TRY(f(&should_continue, &should_retry_dir_read, current_path, dir_entry));
|
||||
|
||||
/* If the provided function wishes to terminate immediately, we should respect it. */
|
||||
if (!should_continue) {
|
||||
*out_should_continue = false;
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (should_retry_dir_read) {
|
||||
retry_dir_read = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (dir_entry->d_type == DT_DIR) {
|
||||
R_TRY(TraverseDirectory(&should_continue, current_path, max_level-1, f));
|
||||
|
||||
if (!should_continue) {
|
||||
*out_should_continue = false;
|
||||
return ResultSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
};
|
||||
|
||||
}
|
97
stratosphere/ncm/source/ncm_icontentmetadatabase.hpp
Normal file
97
stratosphere/ncm/source/ncm_icontentmetadatabase.hpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <stratosphere/kvdb/kvdb_memory_key_value_store.hpp>
|
||||
|
||||
#include "ncm_types.hpp"
|
||||
|
||||
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<ContentMetaKey>* kvs;
|
||||
char mount_name[16];
|
||||
bool disabled;
|
||||
public:
|
||||
IContentMetaDatabase(sts::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs) :
|
||||
kvs(kvs), disabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
IContentMetaDatabase(sts::kvdb::MemoryKeyValueStore<ContentMetaKey>* kvs, const char* mount_name) :
|
||||
IContentMetaDatabase(kvs)
|
||||
{
|
||||
strcpy(this->mount_name, mount_name);
|
||||
}
|
||||
public:
|
||||
/* Actual commands. */
|
||||
virtual Result Set(ContentMetaKey key, InBuffer<u8> value) = 0;
|
||||
virtual Result Get(Out<u64> out_size, ContentMetaKey key, OutBuffer<u8> out_value) = 0;
|
||||
virtual Result Remove(ContentMetaKey key) = 0;
|
||||
virtual Result GetContentIdByType(Out<ContentId> out_content_id, ContentMetaKey key, ContentType type) = 0;
|
||||
virtual Result ListContentInfo(Out<u32> out_entries_written, OutBuffer<ContentInfo> out_info, ContentMetaKey key, u32 start_index) = 0;
|
||||
virtual Result List(Out<u32> out_entries_total, Out<u32> out_entries_written, OutBuffer<ContentMetaKey> out_info, ContentMetaType meta_type, TitleId application_title_id, TitleId title_id_min, TitleId title_id_max, ContentInstallType install_type) = 0;
|
||||
virtual Result GetLatestContentMetaKey(Out<ContentMetaKey> out_key, TitleId tid) = 0;
|
||||
virtual Result ListApplication(Out<u32> out_entries_total, Out<u32> out_entries_written, OutBuffer<ApplicationContentMetaKey> out_keys, ContentMetaType meta_type) = 0;
|
||||
virtual Result Has(Out<bool> out, ContentMetaKey key) = 0;
|
||||
virtual Result HasAll(Out<bool> out, InBuffer<ContentMetaKey> keys) = 0;
|
||||
virtual Result GetSize(Out<u64> out_size, ContentMetaKey key) = 0;
|
||||
virtual Result GetRequiredSystemVersion(Out<u32> out_version, ContentMetaKey key) = 0;
|
||||
virtual Result GetPatchId(Out<TitleId> out_patch_id, ContentMetaKey key) = 0;
|
||||
virtual Result DisableForcibly() = 0;
|
||||
virtual Result LookupOrphanContent(OutBuffer<bool> out_orphaned, InBuffer<ContentId> content_ids) = 0;
|
||||
virtual Result Commit() = 0;
|
||||
virtual Result HasContent(Out<bool> out, ContentMetaKey key, ContentId content_id) = 0;
|
||||
virtual Result ListContentMetaInfo(Out<u32> out_entries_written, OutBuffer<ContentMetaInfo> out_meta_info, ContentMetaKey key, u32 start_index) = 0;
|
||||
virtual Result GetAttributes(Out<ContentMetaAttribute> out_attributes, ContentMetaKey key) = 0;
|
||||
virtual Result GetRequiredApplicationVersion(Out<u32> out_version, ContentMetaKey key) = 0;
|
||||
virtual Result GetContentIdByTypeAndIdOffset(Out<ContentId> out_content_id, ContentMetaKey key, ContentType type, u8 id_offset) = 0;
|
||||
|
||||
/* APIs. */
|
||||
virtual Result GetLatestProgram(ContentId* out_content_id, TitleId title_id) = 0;
|
||||
virtual Result GetLatestData(ContentId* out_content_id, TitleId title_id) = 0;
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {};
|
||||
};
|
||||
|
||||
}
|
95
stratosphere/ncm/source/ncm_icontentstorage.hpp
Normal file
95
stratosphere/ncm/source/ncm_icontentstorage.hpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "lr_types.hpp"
|
||||
#include "ncm_types.hpp"
|
||||
|
||||
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;
|
||||
public:
|
||||
virtual Result GeneratePlaceHolderId(Out<PlaceHolderId> out) = 0;
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) = 0;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) = 0;
|
||||
virtual Result HasPlaceHolder(Out<bool> out, PlaceHolderId placeholder_id) = 0;
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer<u8> data) = 0;
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) = 0;
|
||||
virtual Result Delete(ContentId content_id) = 0;
|
||||
virtual Result Has(Out<bool> out, ContentId content_id) = 0;
|
||||
virtual Result GetPath(OutPointerWithServerSize<lr::Path, 0x1> out, ContentId content_id) = 0;
|
||||
virtual Result GetPlaceHolderPath(OutPointerWithServerSize<lr::Path, 0x1> out, PlaceHolderId placeholder_id) = 0;
|
||||
virtual Result CleanupAllPlaceHolder() = 0;
|
||||
virtual Result ListPlaceHolder(Out<u32> out_count, OutBuffer<PlaceHolderId> out_buf) = 0;
|
||||
virtual Result GetContentCount(Out<u32> out_count) = 0;
|
||||
virtual Result ListContentId(Out<u32> out_count, OutBuffer<ContentId> out_buf, u32 start_offset) = 0;
|
||||
virtual Result GetSizeFromContentId(Out<u64> out_size, ContentId content_id) = 0;
|
||||
virtual Result DisableForcibly() = 0;
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) = 0;
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) = 0;
|
||||
virtual Result ReadContentIdFile(OutBuffer<u8> buf, ContentId content_id, u64 offset) = 0;
|
||||
virtual Result GetRightsIdFromPlaceHolderId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id) = 0;
|
||||
virtual Result GetRightsIdFromContentId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, ContentId content_id) = 0;
|
||||
virtual Result WriteContentForDebug(ContentId content_id, u64 offset, InBuffer<u8> data) = 0;
|
||||
virtual Result GetFreeSpaceSize(Out<u64> out_size) = 0;
|
||||
virtual Result GetTotalSpaceSize(Out<u64> out_size) = 0;
|
||||
virtual Result FlushPlaceHolder() = 0;
|
||||
virtual Result GetSizeFromPlaceHolderId(Out<u64> out, PlaceHolderId placeholder_id) = 0;
|
||||
virtual Result RepairInvalidFileAttribute() = 0;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) = 0;
|
||||
public:
|
||||
DEFINE_SERVICE_DISPATCH_TABLE {};
|
||||
};
|
||||
|
||||
}
|
83
stratosphere/ncm/source/ncm_main.cpp
Normal file
83
stratosphere/ncm/source/ncm_main.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "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 0x20000
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
R_ASSERT(smInitialize());
|
||||
R_ASSERT(fsInitialize());
|
||||
}
|
||||
|
||||
void __appExit(void) {
|
||||
/* Cleanup services. */
|
||||
fsdevUnmountAll();
|
||||
fsExit();
|
||||
smExit();
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
consoleDebugInit(debugDevice_SVC);
|
||||
|
||||
auto server_manager = new WaitableManager(2);
|
||||
|
||||
/* Initialize content manager implementation. */
|
||||
R_ASSERT(sts::ncm::impl::InitializeContentManager());
|
||||
|
||||
server_manager->AddWaitable(new ServiceServer<sts::ncm::ContentManagerService>("ncm", 0x10));
|
||||
server_manager->AddWaitable(new ServiceServer<sts::lr::LocationResolverManagerService>("lr", 0x10));
|
||||
|
||||
/* Loop forever, servicing our services. */
|
||||
server_manager->Process();
|
||||
|
||||
delete server_manager;
|
||||
|
||||
return 0;
|
||||
}
|
93
stratosphere/ncm/source/ncm_make_path.cpp
Normal file
93
stratosphere/ncm/source/ncm_make_path.cpp
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ncm_make_path.hpp"
|
||||
#include "ncm_path_utils.hpp"
|
||||
|
||||
namespace sts::ncm::path {
|
||||
|
||||
namespace {
|
||||
|
||||
u16 Get16BitSha256HashPrefix(Uuid uuid) {
|
||||
u8 hash[SHA256_HASH_SIZE];
|
||||
sha256CalculateHash(hash, uuid.uuid, sizeof(Uuid));
|
||||
return static_cast<u16>(hash[0]) | (static_cast<u16>(hash[1]) << 8);
|
||||
}
|
||||
|
||||
u8 Get8BitSha256HashPrefix(Uuid uuid) {
|
||||
u8 hash[SHA256_HASH_SIZE];
|
||||
sha256CalculateHash(hash, uuid.uuid, sizeof(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);
|
||||
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) >> 6) & 0x3FF;
|
||||
GetContentFileName(content_name, content_id);
|
||||
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash, content_name) < 0) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void MakeContentPathHashByteLayered(char* path_out, ContentId content_id, const char* root) {
|
||||
char content_name[FS_MAX_PATH] = {0};
|
||||
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(content_id));
|
||||
GetContentFileName(content_name, content_id);
|
||||
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash_byte, content_name) < 0) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void MakePlaceHolderPathFlat(char* path_out, PlaceHolderId placeholder_id, const char* root) {
|
||||
char placeholder_name[FS_MAX_PATH] = {0};
|
||||
GetPlaceHolderFileName(placeholder_name, placeholder_id);
|
||||
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%s", root, placeholder_name) < 0) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void MakePlaceHolderPathHashByteLayered(char* path_out, PlaceHolderId placeholder_id, const char* root) {
|
||||
char placeholder_name[FS_MAX_PATH] = {0};
|
||||
const u32 hash_byte = static_cast<u32>(Get8BitSha256HashPrefix(placeholder_id));
|
||||
GetPlaceHolderFileName(placeholder_name, placeholder_id);
|
||||
if (snprintf(path_out, FS_MAX_PATH-1, "%s/%08X/%s", root, hash_byte, placeholder_name) < 0) {
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
stratosphere/ncm/source/ncm_make_path.hpp
Normal file
33
stratosphere/ncm/source/ncm_make_path.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "ncm_types.hpp"
|
||||
|
||||
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);
|
||||
|
||||
}
|
90
stratosphere/ncm/source/ncm_path_utils.cpp
Normal file
90
stratosphere/ncm/source/ncm_path_utils.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ncm_path_utils.hpp"
|
||||
#include "ncm_utils.hpp"
|
||||
|
||||
namespace 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(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);
|
||||
}
|
||||
|
||||
}
|
54
stratosphere/ncm/source/ncm_path_utils.hpp
Normal file
54
stratosphere/ncm/source/ncm_path_utils.hpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "ncm_types.hpp"
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
273
stratosphere/ncm/source/ncm_readonlycontentstorage.cpp
Normal file
273
stratosphere/ncm/source/ncm_readonlycontentstorage.cpp
Normal file
|
@ -0,0 +1,273 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ncm_fs.hpp"
|
||||
#include "ncm_path_utils.hpp"
|
||||
#include "ncm_readonlycontentstorage.hpp"
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
Result ReadOnlyContentStorageInterface::Initialize(const char* root_path, MakeContentPathFunc content_path_func) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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<PlaceHolderId> 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<bool> out, PlaceHolderId placeholder_id) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer<u8> 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<bool> out, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
char content_path[FS_MAX_PATH] = {0};
|
||||
this->make_content_path_func(content_path, content_id, this->root_path);
|
||||
|
||||
bool has = false;
|
||||
R_TRY(HasFile(&has, content_path));
|
||||
|
||||
if (!has) {
|
||||
path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path);
|
||||
R_TRY(HasFile(&has, content_path));
|
||||
}
|
||||
|
||||
out.SetValue(has);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetPath(OutPointerWithServerSize<lr::Path, 0x1> out, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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(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(ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
|
||||
*out.pointer = common_path;
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetPlaceHolderPath(OutPointerWithServerSize<lr::Path, 0x1> out, PlaceHolderId placeholder_id) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::CleanupAllPlaceHolder() {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::ListPlaceHolder(Out<u32> out_count, OutBuffer<PlaceHolderId> out_buf) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetContentCount(Out<u32> out_count) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::ListContentId(Out<u32> out_count, OutBuffer<ContentId> out_buf, u32 start_offset) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetSizeFromContentId(Out<u64> out_size, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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(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;
|
||||
errno = 0;
|
||||
stat(content_path, &st);
|
||||
|
||||
if (errno != 0) {
|
||||
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<u8> buf, ContentId content_id, u64 offset) {
|
||||
/* Offset is too large */
|
||||
if (offset >> 0x3f != 0) {
|
||||
return ResultNcmInvalidOffset;
|
||||
}
|
||||
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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(HasFile(&is_content_file, content_path));
|
||||
|
||||
if (!is_content_file) {
|
||||
path::GetContentMetaPath(content_path, content_id, this->make_content_path_func, this->root_path);
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
FILE* f = fopen(content_path, "rb");
|
||||
|
||||
ON_SCOPE_EXIT {
|
||||
fclose(f);
|
||||
};
|
||||
|
||||
if (f == NULL || errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
fseek(f, offset, SEEK_SET);
|
||||
fread(buf.buffer, buf.num_elements, 1, f);
|
||||
|
||||
if (errno != 0) {
|
||||
return fsdevGetLastResult();
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetRightsIdFromPlaceHolderId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetRightsIdFromContentId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, ContentId content_id) {
|
||||
if (this->disabled) {
|
||||
return ResultNcmInvalidContentStorage;
|
||||
}
|
||||
|
||||
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(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(ConvertToFsCommonPath(common_path, FS_MAX_PATH-1, content_path));
|
||||
R_TRY(fsGetRightsIdAndKeyGenerationByPath(common_path, &key_generation, &rights_id));
|
||||
|
||||
out_rights_id.SetValue(rights_id);
|
||||
out_key_generation.SetValue(static_cast<u64>(key_generation));
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::WriteContentForDebug(ContentId content_id, u64 offset, InBuffer<u8> data) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetFreeSpaceSize(Out<u64> out_size) {
|
||||
out_size.SetValue(0);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetTotalSpaceSize(Out<u64> out_size) {
|
||||
out_size.SetValue(0);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::FlushPlaceHolder() {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetSizeFromPlaceHolderId(Out<u64> out, PlaceHolderId placeholder_id) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::RepairInvalidFileAttribute() {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
Result ReadOnlyContentStorageInterface::GetRightsIdFromPlaceHolderIdWithCache(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id, ContentId cache_content_id) {
|
||||
return ResultNcmInvalidContentStorageOperation;
|
||||
}
|
||||
|
||||
}
|
91
stratosphere/ncm/source/ncm_readonlycontentstorage.hpp
Normal file
91
stratosphere/ncm/source/ncm_readonlycontentstorage.hpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#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<PlaceHolderId> out) override;
|
||||
virtual Result CreatePlaceHolder(PlaceHolderId placeholder_id, ContentId content_id, u64 size) override;
|
||||
virtual Result DeletePlaceHolder(PlaceHolderId placeholder_id) override;
|
||||
virtual Result HasPlaceHolder(Out<bool> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result WritePlaceHolder(PlaceHolderId placeholder_id, u64 offset, InBuffer<u8> data) override;
|
||||
virtual Result Register(PlaceHolderId placeholder_id, ContentId content_id) override;
|
||||
virtual Result Delete(ContentId content_id) override;
|
||||
virtual Result Has(Out<bool> out, ContentId content_id) override;
|
||||
virtual Result GetPath(OutPointerWithServerSize<lr::Path, 0x1> out, ContentId content_id) override;
|
||||
virtual Result GetPlaceHolderPath(OutPointerWithServerSize<lr::Path, 0x1> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result CleanupAllPlaceHolder() override;
|
||||
virtual Result ListPlaceHolder(Out<u32> out_count, OutBuffer<PlaceHolderId> out_buf) override;
|
||||
virtual Result GetContentCount(Out<u32> out_count) override;
|
||||
virtual Result ListContentId(Out<u32> out_count, OutBuffer<ContentId> out_buf, u32 start_offset) override;
|
||||
virtual Result GetSizeFromContentId(Out<u64> out_size, ContentId content_id) override;
|
||||
virtual Result DisableForcibly() override;
|
||||
virtual Result RevertToPlaceHolder(PlaceHolderId placeholder_id, ContentId old_content_id, ContentId new_content_id) override;
|
||||
virtual Result SetPlaceHolderSize(PlaceHolderId placeholder_id, u64 size) override;
|
||||
virtual Result ReadContentIdFile(OutBuffer<u8> buf, ContentId content_id, u64 offset) override;
|
||||
virtual Result GetRightsIdFromPlaceHolderId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, PlaceHolderId placeholder_id) override;
|
||||
virtual Result GetRightsIdFromContentId(Out<FsRightsId> out_rights_id, Out<u64> out_key_generation, ContentId content_id) override;
|
||||
virtual Result WriteContentForDebug(ContentId content_id, u64 offset, InBuffer<u8> data) override;
|
||||
virtual Result GetFreeSpaceSize(Out<u64> out_size) override;
|
||||
virtual Result GetTotalSpaceSize(Out<u64> out_size) override;
|
||||
virtual Result FlushPlaceHolder() override;
|
||||
virtual Result GetSizeFromPlaceHolderId(Out<u64> out, PlaceHolderId placeholder_id) override;
|
||||
virtual Result RepairInvalidFileAttribute() override;
|
||||
virtual Result GetRightsIdFromPlaceHolderIdWithCache(Out<FsRightsId> out_rights_id, Out<u64> 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),
|
||||
};
|
||||
};
|
||||
|
||||
}
|
164
stratosphere/ncm/source/ncm_types.hpp
Normal file
164
stratosphere/ncm/source/ncm_types.hpp
Normal file
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
struct MountName {
|
||||
char name[0x10];
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
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 } };
|
||||
|
||||
typedef Uuid ContentId;
|
||||
typedef Uuid PlaceHolderId;
|
||||
|
||||
enum class ContentMetaType : u8 {
|
||||
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 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 {
|
||||
if (this->id != other.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->version != other.version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->type != other.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->install_type != other.install_type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator!=(const ContentMetaKey& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
} PACKED;
|
||||
|
||||
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;
|
||||
} PACKED;
|
||||
|
||||
struct ContentInfo {
|
||||
ContentId content_id;
|
||||
u8 size[6];
|
||||
ContentType content_type;
|
||||
u8 id_offset;
|
||||
} PACKED;
|
||||
|
||||
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);
|
||||
|
||||
// TODO: Move to libstrat
|
||||
static constexpr Result ResultNcmStoragePathNotFound = MAKERESULT(Module_Ncm, 1);
|
||||
static constexpr Result ResultNcmInvalidPlaceHolderDirectoryEntry = MAKERESULT(Module_Ncm, 170);
|
||||
static constexpr Result ResultNcmInvalidContentStorageOperation = MAKERESULT(Module_Ncm, 190);
|
||||
static constexpr Result ResultNcmStorageRootNotFound = MAKERESULT(Module_Ncm, 310);
|
||||
static constexpr Result ResultFsUnqualifiedPath = MAKERESULT(Module_Fs, 6065);
|
||||
static constexpr Result ResultFsMountNameNotFound = MAKERESULT(Module_Fs, 6905);
|
||||
|
||||
}
|
81
stratosphere/ncm/source/ncm_utils.cpp
Normal file
81
stratosphere/ncm/source/ncm_utils.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ncm_utils.hpp"
|
||||
|
||||
namespace sts::ncm {
|
||||
|
||||
void GetStringFromContentId(char* out, ContentId content_id) {
|
||||
for (size_t i = 0; i < sizeof(ContentId); i++) {
|
||||
snprintf(out+i, 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, 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<ContentId> GetContentIdFromString(const char* str, size_t len) {
|
||||
ContentId content_id = {0};
|
||||
|
||||
if (len < 0x20) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
char byte_string[2];
|
||||
char* end_ptr;
|
||||
u64 converted_val;
|
||||
|
||||
for (size_t i = 0; i < sizeof(ContentId); i++) {
|
||||
const char* char_par = str + i * 2;
|
||||
|
||||
byte_string[0] = char_par[0];
|
||||
byte_string[1] = char_par[1];
|
||||
|
||||
converted_val = strtoull(byte_string, &end_ptr, 0x10);
|
||||
content_id.uuid[i] = (u8)converted_val;
|
||||
}
|
||||
|
||||
return std::optional<ContentId>(content_id);
|
||||
}
|
||||
|
||||
}
|
32
stratosphere/ncm/source/ncm_utils.hpp
Normal file
32
stratosphere/ncm/source/ncm_utils.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright (c) 2019 Adubbz
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <switch.h>
|
||||
#include <stratosphere.hpp>
|
||||
#include <sys/dirent.h>
|
||||
|
||||
#include "ncm_types.hpp"
|
||||
|
||||
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<ContentId> GetContentIdFromString(const char* str, size_t len);
|
||||
|
||||
};
|
Loading…
Reference in a new issue