mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-11-10 07:06:34 +00:00
Delete pre-rewrite ams_mitm code
This commit is contained in:
parent
bbdc643b6d
commit
2bae1ad116
77 changed files with 0 additions and 8873 deletions
|
@ -1,170 +0,0 @@
|
||||||
#---------------------------------------------------------------------------------
|
|
||||||
.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/fs_mitm source/set_mitm source/bpc_mitm source/ns_mitm source/hid_mitm
|
|
||||||
DATA := data
|
|
||||||
INCLUDES := include ../../common/include
|
|
||||||
EXEFS_SRC := exefs_src
|
|
||||||
|
|
||||||
DEFINES := -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__
|
|
||||||
|
|
||||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17
|
|
||||||
|
|
||||||
CXXWRAPS := -Wl,--wrap,__cxa_pure_virtual \
|
|
||||||
-Wl,--wrap,__cxa_throw \
|
|
||||||
-Wl,--wrap,__cxa_rethrow \
|
|
||||||
-Wl,--wrap,__cxa_allocate_exception \
|
|
||||||
-Wl,--wrap,__cxa_begin_catch \
|
|
||||||
-Wl,--wrap,__cxa_end_catch \
|
|
||||||
-Wl,--wrap,__cxa_call_unexpected \
|
|
||||||
-Wl,--wrap,__cxa_call_terminate \
|
|
||||||
-Wl,--wrap,__gxx_personality_v0
|
|
||||||
|
|
||||||
ASFLAGS := -g $(ARCH)
|
|
||||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) $(CXXWRAPS) -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
|
|
||||||
#---------------------------------------------------------------------------------------
|
|
|
@ -1,79 +0,0 @@
|
||||||
{
|
|
||||||
"name": "ams.mitm",
|
|
||||||
"title_id": "0x010041544D530000",
|
|
||||||
"main_thread_stack_size": "0x20000",
|
|
||||||
"main_thread_priority": 43,
|
|
||||||
"default_cpu_id": 3,
|
|
||||||
"process_category": 1,
|
|
||||||
"kernel_capabilities": [
|
|
||||||
{
|
|
||||||
"type": "handle_table_size",
|
|
||||||
"value": 512
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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",
|
|
||||||
"svcCreateEvent": "0x45",
|
|
||||||
"svcCreateInterruptEvent": "0x53",
|
|
||||||
"svcReadWriteRegister": "0x4E",
|
|
||||||
"svcQueryIoMapping": "0x55",
|
|
||||||
"svcCreateDeviceAddressSpace": "0x56",
|
|
||||||
"svcAttachDeviceAddressSpace": "0x57",
|
|
||||||
"svcDetachDeviceAddressSpace": "0x58",
|
|
||||||
"svcMapDeviceAddressSpaceAligned": "0x5a",
|
|
||||||
"svcUnmapDeviceAddressSpace": "0x5c",
|
|
||||||
"svcGetSystemInfo": "0x6f",
|
|
||||||
"svcManageNamedPort": "0x71",
|
|
||||||
"svcCallSecureMonitor": "0x7F"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "amsmitm_modules.hpp"
|
|
||||||
#include "utils.hpp"
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
extern u32 __start__;
|
|
||||||
|
|
||||||
u32 __nx_applet_type = AppletType_None;
|
|
||||||
|
|
||||||
#define INNER_HEAP_SIZE 0x1000000
|
|
||||||
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
|
|
||||||
char nx_inner_heap[INNER_HEAP_SIZE];
|
|
||||||
|
|
||||||
void __libnx_initheap(void);
|
|
||||||
void __appInit(void);
|
|
||||||
void __appExit(void);
|
|
||||||
|
|
||||||
/* Exception handling. */
|
|
||||||
alignas(16) u8 __nx_exception_stack[0x1000];
|
|
||||||
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx);
|
|
||||||
void __libstratosphere_exception_handler(AtmosphereFatalErrorContext *ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
ams::ncm::ProgramId __stratosphere_program_id = ams::ncm::ProgramId::AtmosphereMitm;
|
|
||||||
|
|
||||||
void __libnx_exception_handler(ThreadExceptionDump *ctx) {
|
|
||||||
StratosphereCrashHandler(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void __libstratosphere_exception_handler(AtmosphereFatalErrorContext *ctx) {
|
|
||||||
/* We're bpc-mitm (or ams_mitm, anyway), so manually reboot to fatal error. */
|
|
||||||
Utils::RebootToFatalError(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void __libnx_initheap(void) {
|
|
||||||
void* addr = nx_inner_heap;
|
|
||||||
size_t size = nx_inner_heap_size;
|
|
||||||
|
|
||||||
/* Newlib */
|
|
||||||
extern char* fake_heap_start;
|
|
||||||
extern char* fake_heap_end;
|
|
||||||
|
|
||||||
fake_heap_start = (char*)addr;
|
|
||||||
fake_heap_end = (char*)addr + size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void __appInit(void) {
|
|
||||||
SetFirmwareVersionForLibnx();
|
|
||||||
|
|
||||||
DoWithSmSession([&]() {
|
|
||||||
R_ASSERT(fsInitialize());
|
|
||||||
R_ASSERT(pmdmntInitialize());
|
|
||||||
R_ASSERT(pminfoInitialize());
|
|
||||||
R_ASSERT(splFsInitialize());
|
|
||||||
});
|
|
||||||
|
|
||||||
CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
|
|
||||||
}
|
|
||||||
|
|
||||||
void __appExit(void) {
|
|
||||||
/* Cleanup services. */
|
|
||||||
splFsExit();
|
|
||||||
pminfoExit();
|
|
||||||
pmdmntExit();
|
|
||||||
fsExit();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
|
||||||
consoleDebugInit(debugDevice_SVC);
|
|
||||||
ams::os::Thread initializer_thread;
|
|
||||||
|
|
||||||
LaunchAllMitmModules();
|
|
||||||
|
|
||||||
R_ASSERT(initializer_thread.Initialize(&Utils::InitializeThreadFunc, NULL, 0x4000, 0x15));
|
|
||||||
R_ASSERT(initializer_thread.Start());
|
|
||||||
|
|
||||||
/* Wait for all mitm modules to end. */
|
|
||||||
WaitAllMitmModules();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include <cstring>
|
|
||||||
#include "debug.hpp"
|
|
||||||
|
|
||||||
#include "amsmitm_modules.hpp"
|
|
||||||
|
|
||||||
#include "fs_mitm/fsmitm_main.hpp"
|
|
||||||
#include "set_mitm/setmitm_main.hpp"
|
|
||||||
#include "bpc_mitm/bpcmitm_main.hpp"
|
|
||||||
#include "ns_mitm/nsmitm_main.hpp"
|
|
||||||
#include "hid_mitm/hidmitm_main.hpp"
|
|
||||||
|
|
||||||
static ams::os::Thread g_module_threads[MitmModuleId_Count];
|
|
||||||
|
|
||||||
static const struct {
|
|
||||||
ThreadFunc main;
|
|
||||||
u32 priority;
|
|
||||||
u32 stack_size;
|
|
||||||
} g_module_definitions[MitmModuleId_Count] = {
|
|
||||||
{ &FsMitmMain, FsMitmPriority, FsMitmStackSize }, /* FsMitm */
|
|
||||||
{ &SetMitmMain, SetMitmPriority, SetMitmStackSize }, /* SetMitm */
|
|
||||||
{ &BpcMitmMain, BpcMitmPriority, BpcMitmStackSize }, /* BpcMitm */
|
|
||||||
{ &NsMitmMain, NsMitmPriority, NsMitmStackSize }, /* NsMitm */
|
|
||||||
{ &HidMitmMain, HidMitmPriority, HidMitmStackSize }, /* HidMitm */
|
|
||||||
};
|
|
||||||
|
|
||||||
void LaunchAllMitmModules() {
|
|
||||||
/* Create thread for each module. */
|
|
||||||
for (u32 i = 0; i < static_cast<u32>(MitmModuleId_Count); i++) {
|
|
||||||
const auto cur_module = &g_module_definitions[i];
|
|
||||||
R_ASSERT(g_module_threads[i].Initialize(cur_module->main, nullptr, cur_module->stack_size, cur_module->priority));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start thread for each module. */
|
|
||||||
for (u32 i = 0; i < static_cast<u32>(MitmModuleId_Count); i++) {
|
|
||||||
R_ASSERT(g_module_threads[i].Start());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WaitAllMitmModules() {
|
|
||||||
/* Wait on thread for each module. */
|
|
||||||
for (u32 i = 0; i < static_cast<u32>(MitmModuleId_Count); i++) {
|
|
||||||
g_module_threads[i].Join();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
enum MitmModuleId : u32 {
|
|
||||||
MitmModuleId_FsMitm = 0,
|
|
||||||
MitmModuleId_SetMitm = 1,
|
|
||||||
MitmModuleId_BpcMitm = 2,
|
|
||||||
MitmModuleId_NsMitm = 3,
|
|
||||||
MitmModuleId_HidMitm = 4,
|
|
||||||
|
|
||||||
/* Always keep this at the end. */
|
|
||||||
MitmModuleId_Count,
|
|
||||||
};
|
|
||||||
|
|
||||||
void LaunchAllMitmModules();
|
|
||||||
|
|
||||||
void WaitAllMitmModules();
|
|
|
@ -1,32 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "bpc_ams_service.hpp"
|
|
||||||
#include "bpcmitm_reboot_manager.hpp"
|
|
||||||
|
|
||||||
Result BpcAtmosphereService::RebootToFatalError(InBuffer<AtmosphereFatalErrorContext> ctx) {
|
|
||||||
if (ctx.buffer == nullptr || ctx.num_elements != 1) {
|
|
||||||
return ResultKernelConnectionClosed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reboot to fusee with the input context. */
|
|
||||||
BpcRebootManager::RebootForFatalError(ctx.buffer);
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
class BpcAtmosphereService : public IServiceObject {
|
|
||||||
enum class CommandId {
|
|
||||||
RebootToFatalError = 65000,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
Result RebootToFatalError(InBuffer<AtmosphereFatalErrorContext> ctx);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(BpcAtmosphereService, RebootToFatalError),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "bpc_mitm_service.hpp"
|
|
||||||
#include "bpcmitm_reboot_manager.hpp"
|
|
||||||
|
|
||||||
void BpcMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
|
||||||
/* Nothing to do here */
|
|
||||||
}
|
|
||||||
|
|
||||||
Result BpcMitmService::ShutdownSystem() {
|
|
||||||
/* Use exosphere + reboot to perform real shutdown, instead of fake shutdown. */
|
|
||||||
PerformShutdownSmc();
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result BpcMitmService::RebootSystem() {
|
|
||||||
return BpcRebootManager::PerformReboot();
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
class BpcMitmService : public IMitmServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
ShutdownSystem = 0,
|
|
||||||
RebootSystem = 1,
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
BpcMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::ProgramId tid) : IMitmServiceObject(s, pid, tid) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ShouldMitm(u64 pid, ams::ncm::ProgramId tid) {
|
|
||||||
/* We will mitm:
|
|
||||||
* - am, to intercept the Reboot/Power buttons in the overlay menu.
|
|
||||||
* - fatal, to simplify payload reboot logic significantly
|
|
||||||
* - applications, to allow homebrew to take advantage of the feature.
|
|
||||||
*/
|
|
||||||
return tid == ams::ncm::ProgramId::Am || tid == ams::ncm::ProgramId::Fatal || ams::ncm::IsApplicationProgramId(tid) || Utils::IsHblTid(static_cast<u64>(tid));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* Overridden commands. */
|
|
||||||
Result ShutdownSystem();
|
|
||||||
Result RebootSystem();
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(BpcMitmService, ShutdownSystem),
|
|
||||||
MAKE_SERVICE_COMMAND_META(BpcMitmService, RebootSystem),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "bpcmitm_main.hpp"
|
|
||||||
#include "bpc_mitm_service.hpp"
|
|
||||||
#include "bpc_ams_service.hpp"
|
|
||||||
#include "bpcmitm_reboot_manager.hpp"
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
void BpcMitmMain(void *arg) {
|
|
||||||
/* Wait for initialization to occur */
|
|
||||||
Utils::WaitSdInitialized();
|
|
||||||
|
|
||||||
/* Load a payload off of the SD */
|
|
||||||
BpcRebootManager::Initialize();
|
|
||||||
|
|
||||||
/* Create server manager */
|
|
||||||
static auto s_server_manager = WaitableManager(2);
|
|
||||||
|
|
||||||
/* Create bpc mitm. */
|
|
||||||
const char *service_name = "bpc";
|
|
||||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_200) {
|
|
||||||
service_name = "bpc:c";
|
|
||||||
}
|
|
||||||
AddMitmServerToManager<BpcMitmService>(&s_server_manager, service_name, 13);
|
|
||||||
|
|
||||||
/* Extension: Allow for reboot-to-error. */
|
|
||||||
/* Must be managed port in order for sm to be able to access. */
|
|
||||||
s_server_manager.AddWaitable(new ManagedPortServer<BpcAtmosphereService>("bpc:ams", 1));
|
|
||||||
|
|
||||||
/* Loop forever, servicing our services. */
|
|
||||||
s_server_manager.Process();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
constexpr u32 BpcMitmPriority = 32;
|
|
||||||
constexpr u32 BpcMitmStackSize = 0x8000;
|
|
||||||
|
|
||||||
void BpcMitmMain(void *arg);
|
|
|
@ -1,122 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include <strings.h>
|
|
||||||
#include "bpcmitm_reboot_manager.hpp"
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
/* TODO: Find a way to pre-populate this with the contents of fusee-primary. */
|
|
||||||
static u8 g_reboot_payload[IRAM_PAYLOAD_MAX_SIZE] __attribute__ ((aligned (0x1000)));
|
|
||||||
static u8 g_work_page[0x1000] __attribute__ ((aligned (0x1000)));
|
|
||||||
static bool g_payload_loaded = false;
|
|
||||||
static BpcRebootType g_reboot_type = BpcRebootType::ToPayload;
|
|
||||||
|
|
||||||
void BpcRebootManager::Initialize() {
|
|
||||||
/* Open payload file. */
|
|
||||||
FsFile payload_file;
|
|
||||||
if (R_FAILED(Utils::OpenSdFile("/atmosphere/reboot_payload.bin", FS_OPEN_READ, &payload_file))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ON_SCOPE_EXIT { fsFileClose(&payload_file); };
|
|
||||||
|
|
||||||
/* Clear payload buffer */
|
|
||||||
std::memset(g_reboot_payload, 0xFF, sizeof(g_reboot_payload));
|
|
||||||
|
|
||||||
/* Read payload file. */
|
|
||||||
size_t actual_size;
|
|
||||||
fsFileRead(&payload_file, 0, g_reboot_payload, IRAM_PAYLOAD_MAX_SIZE, FS_READOPTION_NONE, &actual_size);
|
|
||||||
|
|
||||||
g_payload_loaded = true;
|
|
||||||
|
|
||||||
/* Figure out what kind of reboot we're gonna be doing. */
|
|
||||||
{
|
|
||||||
char reboot_type[0x40] = {0};
|
|
||||||
u64 reboot_type_size = 0;
|
|
||||||
if (R_SUCCEEDED(Utils::GetSettingsItemValue("atmosphere", "power_menu_reboot_function", reboot_type, sizeof(reboot_type)-1, &reboot_type_size))) {
|
|
||||||
if (strcasecmp(reboot_type, "stock") == 0 || strcasecmp(reboot_type, "normal") == 0 || strcasecmp(reboot_type, "standard") == 0) {
|
|
||||||
g_reboot_type = BpcRebootType::Standard;
|
|
||||||
} else if (strcasecmp(reboot_type, "rcm") == 0) {
|
|
||||||
g_reboot_type = BpcRebootType::ToRcm;
|
|
||||||
} else if (strcasecmp(reboot_type, "payload") == 0) {
|
|
||||||
g_reboot_type = BpcRebootType::ToPayload;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ClearIram() {
|
|
||||||
/* Make page FFs. */
|
|
||||||
memset(g_work_page, 0xFF, sizeof(g_work_page));
|
|
||||||
|
|
||||||
/* Overwrite all of IRAM with FFs. */
|
|
||||||
for (size_t ofs = 0; ofs < IRAM_SIZE; ofs += sizeof(g_work_page)) {
|
|
||||||
CopyToIram(IRAM_BASE + ofs, g_work_page, sizeof(g_work_page));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void DoRebootToPayload() {
|
|
||||||
/* If we don't actually have a payload loaded, just go to RCM. */
|
|
||||||
if (!g_payload_loaded) {
|
|
||||||
RebootToRcm();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure clean IRAM state. */
|
|
||||||
ClearIram();
|
|
||||||
|
|
||||||
/* Copy in payload. */
|
|
||||||
for (size_t ofs = 0; ofs < sizeof(g_reboot_payload); ofs += 0x1000) {
|
|
||||||
CopyToIram(IRAM_PAYLOAD_BASE + ofs, &g_reboot_payload[ofs], 0x1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
RebootToIramPayload();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result BpcRebootManager::PerformReboot() {
|
|
||||||
switch (g_reboot_type) {
|
|
||||||
case BpcRebootType::Standard:
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
case BpcRebootType::ToRcm:
|
|
||||||
RebootToRcm();
|
|
||||||
return ResultSuccess();
|
|
||||||
case BpcRebootType::ToPayload:
|
|
||||||
default:
|
|
||||||
DoRebootToPayload();
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void BpcRebootManager::RebootForFatalError(AtmosphereFatalErrorContext *ctx) {
|
|
||||||
/* Ensure clean IRAM state. */
|
|
||||||
ClearIram();
|
|
||||||
|
|
||||||
|
|
||||||
/* Copy in payload. */
|
|
||||||
for (size_t ofs = 0; ofs < sizeof(g_reboot_payload); ofs += 0x1000) {
|
|
||||||
CopyToIram(IRAM_PAYLOAD_BASE + ofs, &g_reboot_payload[ofs], 0x1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(g_work_page, ctx, sizeof(*ctx));
|
|
||||||
CopyToIram(IRAM_PAYLOAD_BASE + IRAM_PAYLOAD_MAX_SIZE, g_work_page, sizeof(g_work_page));
|
|
||||||
|
|
||||||
/* If we don't actually have a payload loaded, just go to RCM. */
|
|
||||||
if (!g_payload_loaded) {
|
|
||||||
RebootToRcm();
|
|
||||||
}
|
|
||||||
|
|
||||||
RebootToIramPayload();
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#define IRAM_BASE 0x40000000ull
|
|
||||||
#define IRAM_SIZE 0x40000
|
|
||||||
#define IRAM_PAYLOAD_MAX_SIZE 0x2E000
|
|
||||||
#define IRAM_PAYLOAD_BASE 0x40010000ull
|
|
||||||
|
|
||||||
enum class BpcRebootType : u32 {
|
|
||||||
Standard,
|
|
||||||
ToRcm,
|
|
||||||
ToPayload,
|
|
||||||
};
|
|
||||||
|
|
||||||
class BpcRebootManager {
|
|
||||||
public:
|
|
||||||
static void Initialize();
|
|
||||||
static Result PerformReboot();
|
|
||||||
static void RebootForFatalError(AtmosphereFatalErrorContext *ctx);
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include <cstring>
|
|
||||||
#include "debug.hpp"
|
|
||||||
|
|
||||||
void Reboot() {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
void Log(const void *data, int size) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
void Reboot();
|
|
||||||
void Log(const void *data, int size);
|
|
|
@ -1,117 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include "fs_dir_utils.hpp"
|
|
||||||
#include "fs_ifile.hpp"
|
|
||||||
|
|
||||||
Result FsDirUtils::CopyFile(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_parent_path, const FsPath &src_path, const FsDirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size) {
|
|
||||||
std::unique_ptr<IFile> src_file;
|
|
||||||
std::unique_ptr<IFile> dst_file;
|
|
||||||
const u64 file_size = dir_ent->fileSize;
|
|
||||||
|
|
||||||
/* Open source file for reading. */
|
|
||||||
R_TRY(src_fs->OpenFile(src_file, src_path, OpenMode_Read));
|
|
||||||
|
|
||||||
/* Create and open destination file. */
|
|
||||||
{
|
|
||||||
FsPath dst_path;
|
|
||||||
/* TODO: Error code? N aborts here. */
|
|
||||||
AMS_ASSERT(static_cast<size_t>(snprintf(dst_path.str, sizeof(dst_path.str), "%s%s", dst_parent_path.str, dir_ent->name)) < sizeof(dst_path));
|
|
||||||
|
|
||||||
R_TRY(dst_fs->CreateFile(dst_path, file_size));
|
|
||||||
R_TRY(dst_fs->OpenFile(dst_file, dst_path, OpenMode_Write));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read/Write work_buf_size chunks. */
|
|
||||||
u64 offset = 0;
|
|
||||||
while (offset < file_size) {
|
|
||||||
u64 read_size;
|
|
||||||
R_TRY(src_file->Read(&read_size, offset, work_buf, work_buf_size));
|
|
||||||
R_TRY(dst_file->Write(offset, work_buf, read_size));
|
|
||||||
|
|
||||||
offset += read_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsDirUtils::CopyDirectoryRecursively(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_path, const FsPath &src_path, void *work_buf, size_t work_buf_size) {
|
|
||||||
FsPath work_path = dst_path;
|
|
||||||
|
|
||||||
return IterateDirectoryRecursively(src_fs, src_path,
|
|
||||||
[&](const FsPath &path, const FsDirectoryEntry *dir_ent) -> Result { /* On Enter Directory */
|
|
||||||
/* Update path, create new dir. */
|
|
||||||
strncat(work_path.str, dir_ent->name, sizeof(work_path) - strnlen(work_path.str, sizeof(work_path) - 1) - 1);
|
|
||||||
strncat(work_path.str, "/", sizeof(work_path) - strnlen(work_path.str, sizeof(work_path) - 1) - 1);
|
|
||||||
return dst_fs->CreateDirectory(work_path);
|
|
||||||
},
|
|
||||||
[&](const FsPath &path, const FsDirectoryEntry *dir_ent) -> Result { /* On Exit Directory */
|
|
||||||
/* Check we have a parent directory. */
|
|
||||||
const size_t work_path_len = strnlen(work_path.str, sizeof(work_path));
|
|
||||||
if (work_path_len < 2) {
|
|
||||||
return ResultFsInvalidPathFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find previous separator, add NULL terminator */
|
|
||||||
char *p = &work_path.str[work_path_len - 2];
|
|
||||||
while (*p != '/' && p > work_path.str) {
|
|
||||||
p--;
|
|
||||||
}
|
|
||||||
p[1] = 0;
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
},
|
|
||||||
[&](const FsPath &path, const FsDirectoryEntry *dir_ent) -> Result { /* On File */
|
|
||||||
/* Just copy the file to the new fs. */
|
|
||||||
return CopyFile(dst_fs, src_fs, work_path, path, dir_ent, work_buf, work_buf_size);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsDirUtils::EnsureDirectoryExists(IFileSystem *fs, const FsPath &path) {
|
|
||||||
FsPath normal_path;
|
|
||||||
size_t normal_path_len;
|
|
||||||
|
|
||||||
/* Normalize the path. */
|
|
||||||
R_TRY(FsPathUtils::Normalize(normal_path.str, sizeof(normal_path.str) - 1, path.str, &normal_path_len));
|
|
||||||
|
|
||||||
/* Repeatedly call CreateDirectory on each directory leading to the target. */
|
|
||||||
for (size_t i = 1; i < normal_path_len; i++) {
|
|
||||||
/* If we detect a separator, we're done. */
|
|
||||||
if (normal_path.str[i] == '/') {
|
|
||||||
normal_path.str[i] = 0;
|
|
||||||
{
|
|
||||||
R_TRY_CATCH(fs->CreateDirectory(normal_path)) {
|
|
||||||
R_CATCH(ResultFsPathAlreadyExists) {
|
|
||||||
/* If path already exists, there's no problem. */
|
|
||||||
}
|
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
}
|
|
||||||
normal_path.str[i] = '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Call CreateDirectory on the final path. */
|
|
||||||
R_TRY_CATCH(fs->CreateDirectory(normal_path)) {
|
|
||||||
R_CATCH(ResultFsPathAlreadyExists) {
|
|
||||||
/* If path already exists, there's no problem. */
|
|
||||||
}
|
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
#include "fs_ifilesystem.hpp"
|
|
||||||
|
|
||||||
class FsDirUtils {
|
|
||||||
private:
|
|
||||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
|
||||||
static Result IterateDirectoryRecursivelyInternal(IFileSystem *fs, FsPath &work_path, FsDirectoryEntry *ent_buf, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
|
||||||
std::unique_ptr<IDirectory> dir;
|
|
||||||
|
|
||||||
/* Open the directory. */
|
|
||||||
R_TRY(fs->OpenDirectory(dir, work_path, DirectoryOpenMode_All));
|
|
||||||
|
|
||||||
const size_t parent_len = strnlen(work_path.str, sizeof(work_path.str) - 1);
|
|
||||||
|
|
||||||
/* Read and handle entries. */
|
|
||||||
while (true) {
|
|
||||||
/* Read a single entry. */
|
|
||||||
u64 read_count;
|
|
||||||
R_TRY(dir->Read(&read_count, ent_buf, 1));
|
|
||||||
|
|
||||||
/* If we're out of entries, we're done. */
|
|
||||||
if (read_count == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t child_name_len = strnlen(ent_buf->name, sizeof(ent_buf->name) - 1);
|
|
||||||
const bool is_dir = ent_buf->type == ENTRYTYPE_DIR;
|
|
||||||
const size_t child_path_len = parent_len + child_name_len + (is_dir ? 1 : 0);
|
|
||||||
|
|
||||||
/* Validate child path size. */
|
|
||||||
if (child_path_len >= sizeof(work_path.str)) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
strncat(work_path.str, ent_buf->name, sizeof(work_path.str) - 1 - parent_len);
|
|
||||||
if (is_dir) {
|
|
||||||
/* Enter directory. */
|
|
||||||
R_TRY(on_enter_dir(work_path, ent_buf));
|
|
||||||
|
|
||||||
/* Append separator, recurse. */
|
|
||||||
strncat(work_path.str, "/", sizeof(work_path.str) - 1 - parent_len - child_name_len);
|
|
||||||
R_TRY(IterateDirectoryRecursivelyInternal(fs, work_path, ent_buf, on_enter_dir, on_exit_dir, on_file));
|
|
||||||
|
|
||||||
/* Exit directory. */
|
|
||||||
R_TRY(on_exit_dir(work_path, ent_buf));
|
|
||||||
} else {
|
|
||||||
/* Call file handler. */
|
|
||||||
R_TRY(on_file(work_path, ent_buf));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Restore parent path. */
|
|
||||||
work_path.str[parent_len] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
|
||||||
static Result IterateDirectoryRecursively(IFileSystem *fs, const FsPath &root_path, FsPath &work_path, FsDirectoryEntry *ent_buf, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
|
||||||
/* Ensure valid root path. */
|
|
||||||
size_t root_path_len = strnlen(root_path.str, sizeof(root_path.str));
|
|
||||||
if (root_path_len > FS_MAX_PATH - 1 || ((root_path_len == FS_MAX_PATH - 1) && root_path.str[root_path_len-1] != '/')) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy path, ensure terminating separator. */
|
|
||||||
memcpy(work_path.str, root_path.str, root_path_len);
|
|
||||||
if (work_path.str[root_path_len-1] != '/') {
|
|
||||||
root_path_len++;
|
|
||||||
work_path.str[root_path_len-1] = '/';
|
|
||||||
}
|
|
||||||
work_path.str[root_path_len] = 0;
|
|
||||||
|
|
||||||
/* Actually iterate. */
|
|
||||||
return IterateDirectoryRecursivelyInternal(fs, work_path, ent_buf, on_enter_dir, on_exit_dir, on_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helper for not specifying work path/entry buffer. */
|
|
||||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
|
||||||
static Result IterateDirectoryRecursively(IFileSystem *fs, const FsPath &root_path, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
|
||||||
FsDirectoryEntry dir_ent = {0};
|
|
||||||
FsPath work_path = {0};
|
|
||||||
return IterateDirectoryRecursively(fs, root_path, work_path, &dir_ent, on_enter_dir, on_exit_dir, on_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helper for iterating over the filesystem root. */
|
|
||||||
template<typename OnEnterDir, typename OnExitDir, typename OnFile>
|
|
||||||
static Result IterateDirectoryRecursively(IFileSystem *fs, OnEnterDir on_enter_dir, OnExitDir on_exit_dir, OnFile on_file) {
|
|
||||||
return IterateDirectoryRecursively(fs, FsPathUtils::RootPath, on_enter_dir, on_exit_dir, on_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy API. */
|
|
||||||
static Result CopyFile(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_parent_path, const FsPath &src_path, const FsDirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size);
|
|
||||||
static Result CopyFile(IFileSystem *fs, const FsPath &dst_parent_path, const FsPath &src_path, const FsDirectoryEntry *dir_ent, void *work_buf, size_t work_buf_size) {
|
|
||||||
return CopyFile(fs, fs, dst_parent_path, src_path, dir_ent, work_buf, work_buf_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result CopyDirectoryRecursively(IFileSystem *dst_fs, IFileSystem *src_fs, const FsPath &dst_path, const FsPath &src_path, void *work_buf, size_t work_buf_size);
|
|
||||||
static Result CopyDirectoryRecursively(IFileSystem *fs, const FsPath &dst_path, const FsPath &src_path, void *work_buf, size_t work_buf_size) {
|
|
||||||
return CopyDirectoryRecursively(fs, fs, dst_path, src_path, work_buf, work_buf_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure directory existence. */
|
|
||||||
static Result EnsureDirectoryExists(IFileSystem *fs, const FsPath &path);
|
|
||||||
|
|
||||||
/* Other Utility. */
|
|
||||||
template<typename F>
|
|
||||||
static Result RetryUntilTargetNotLocked(F f) {
|
|
||||||
const size_t MaxRetries = 10;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < MaxRetries; i++) {
|
|
||||||
R_TRY_CATCH(f()) {
|
|
||||||
R_CATCH(ResultFsTargetLocked) {
|
|
||||||
/* If target is locked, wait 100ms and try again. */
|
|
||||||
svcSleepThread(100'000'000ul);
|
|
||||||
}
|
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,182 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "fs_directory_redirection_filesystem.hpp"
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
|
|
||||||
static char *GetNormalizedDirectory(const char *dir_prefix) {
|
|
||||||
/* Normalize the path. */
|
|
||||||
char normal_path[FS_MAX_PATH + 1];
|
|
||||||
size_t normal_path_len;
|
|
||||||
R_ASSERT(FsPathUtils::Normalize(normal_path, sizeof(normal_path), dir_prefix, &normal_path_len));
|
|
||||||
|
|
||||||
/* Ensure terminating '/' */
|
|
||||||
if (normal_path[normal_path_len-1] != '/') {
|
|
||||||
AMS_ASSERT(normal_path_len + 2 <= sizeof(normal_path));
|
|
||||||
strncat(normal_path, "/", 2);
|
|
||||||
normal_path[sizeof(normal_path)-1] = 0;
|
|
||||||
normal_path_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *output = static_cast<char *>(std::malloc(normal_path_len + 1));
|
|
||||||
AMS_ASSERT(output != nullptr);
|
|
||||||
|
|
||||||
std::strncpy(output, normal_path, normal_path_len + 1);
|
|
||||||
output[normal_path_len] = 0;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::Initialize(const char *before, const char *after) {
|
|
||||||
if (strnlen(before, FS_MAX_PATH) >= FS_MAX_PATH || strnlen(after, FS_MAX_PATH) >= FS_MAX_PATH) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->before_dir = GetNormalizedDirectory(before);
|
|
||||||
this->after_dir = GetNormalizedDirectory(after);
|
|
||||||
this->before_dir_len = strlen(this->before_dir);
|
|
||||||
this->after_dir_len = strlen(this->after_dir);
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) {
|
|
||||||
FsPath tmp_rel_path;
|
|
||||||
R_TRY(FsPathUtils::Normalize(tmp_rel_path.str, sizeof(tmp_rel_path), relative_path, nullptr));
|
|
||||||
|
|
||||||
if (std::strncmp(tmp_rel_path.str, this->before_dir, this->before_dir_len) == 0) {
|
|
||||||
if (this->after_dir_len + strnlen(tmp_rel_path.str, FS_MAX_PATH) - this->before_dir_len > out_size) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy after path. */
|
|
||||||
std::strncpy(out, this->after_dir, out_size);
|
|
||||||
out[out_size - 1] = 0;
|
|
||||||
|
|
||||||
/* Normalize it. */
|
|
||||||
return FsPathUtils::Normalize(out + this->after_dir_len - 1, out_size - (this->after_dir_len - 1), relative_path + this->before_dir_len - 1, nullptr);
|
|
||||||
} else if (std::memcmp(tmp_rel_path.str, this->before_dir, this->before_dir_len - 1) == 0) {
|
|
||||||
/* Handling raw directory. */
|
|
||||||
if (this->after_dir_len + 1 > out_size) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
std::strncpy(out, this->after_dir, out_size);
|
|
||||||
out[out_size - 1] = 0;
|
|
||||||
return ResultSuccess();
|
|
||||||
} else {
|
|
||||||
return FsPathUtils::Normalize(out, out_size, relative_path, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->CreateFile(full_path, size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::DeleteFileImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->DeleteFile(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::CreateDirectoryImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->CreateDirectory(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::DeleteDirectoryImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->DeleteDirectory(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->DeleteDirectoryRecursively(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
FsPath full_old_path, full_new_path;
|
|
||||||
R_TRY(GetFullPath(full_old_path, old_path));
|
|
||||||
R_TRY(GetFullPath(full_new_path, new_path));
|
|
||||||
return this->base_fs->RenameFile(full_old_path, full_new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
FsPath full_old_path, full_new_path;
|
|
||||||
R_TRY(GetFullPath(full_old_path, old_path));
|
|
||||||
R_TRY(GetFullPath(full_new_path, new_path));
|
|
||||||
return this->base_fs->RenameDirectory(full_old_path, full_new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->GetEntryType(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->OpenFile(out_file, full_path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->OpenDirectory(out_dir, full_path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::CommitImpl() {
|
|
||||||
return this->base_fs->Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->GetFreeSpaceSize(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->GetTotalSpaceSize(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->CleanDirectoryRecursively(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->GetFileTimeStampRaw(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectoryRedirectionFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
return this->base_fs->QueryEntry(out, out_size, in, in_size, query, full_path);
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_ifilesystem.hpp"
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
|
|
||||||
class DirectoryRedirectionFileSystem : public IFileSystem {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<IFileSystem> base_fs;
|
|
||||||
char *before_dir = nullptr;
|
|
||||||
size_t before_dir_len = 0;
|
|
||||||
char *after_dir = nullptr;
|
|
||||||
size_t after_dir_len = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
DirectoryRedirectionFileSystem(IFileSystem *fs, const char *before, const char *after) : base_fs(fs) {
|
|
||||||
R_ASSERT(this->Initialize(before, after));
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectoryRedirectionFileSystem(std::shared_ptr<IFileSystem> fs, const char *before, const char *after) : base_fs(fs) {
|
|
||||||
R_ASSERT(this->Initialize(before, after));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
virtual ~DirectoryRedirectionFileSystem() {
|
|
||||||
if (this->before_dir != nullptr) {
|
|
||||||
free(this->before_dir);
|
|
||||||
}
|
|
||||||
if (this->after_dir != nullptr) {
|
|
||||||
free(this->after_dir);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Result Initialize(const char *before, const char *after);
|
|
||||||
protected:
|
|
||||||
Result GetFullPath(char *out, size_t out_size, const char *relative_path);
|
|
||||||
Result GetFullPath(FsPath &full_path, const FsPath &relative_path) {
|
|
||||||
return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) override;
|
|
||||||
virtual Result DeleteFileImpl(const FsPath &path) override;
|
|
||||||
virtual Result CreateDirectoryImpl(const FsPath &path) override;
|
|
||||||
virtual Result DeleteDirectoryImpl(const FsPath &path) override;
|
|
||||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) override;
|
|
||||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) override;
|
|
||||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) override;
|
|
||||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) override;
|
|
||||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) override;
|
|
||||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) override;
|
|
||||||
virtual Result CommitImpl() override;
|
|
||||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
|
||||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
|
||||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) override;
|
|
||||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) override;
|
|
||||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) override;
|
|
||||||
};
|
|
|
@ -1,335 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "fs_directory_savedata_filesystem.hpp"
|
|
||||||
#include "fs_dir_utils.hpp"
|
|
||||||
|
|
||||||
class DirectorySaveDataFile : public IFile {
|
|
||||||
private:
|
|
||||||
std::unique_ptr<IFile> base_file;
|
|
||||||
DirectorySaveDataFileSystem *parent_fs; /* TODO: shared_ptr + enabled_shared_from_this? */
|
|
||||||
int open_mode;
|
|
||||||
public:
|
|
||||||
DirectorySaveDataFile(std::unique_ptr<IFile> f, DirectorySaveDataFileSystem *p, int m) : base_file(std::move(f)), parent_fs(p), open_mode(m) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~DirectorySaveDataFile() {
|
|
||||||
/* Observe closing of writable file. */
|
|
||||||
if (this->open_mode & OpenMode_Write) {
|
|
||||||
this->parent_fs->OnWritableFileClose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) override {
|
|
||||||
return this->base_file->Read(out, offset, buffer, size);
|
|
||||||
}
|
|
||||||
virtual Result GetSizeImpl(u64 *out) override {
|
|
||||||
return this->base_file->GetSize(out);
|
|
||||||
}
|
|
||||||
virtual Result FlushImpl() override {
|
|
||||||
return this->base_file->Flush();
|
|
||||||
}
|
|
||||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, u32 option) override {
|
|
||||||
return this->base_file->Write(offset, buffer, size, option);
|
|
||||||
}
|
|
||||||
virtual Result SetSizeImpl(u64 size) override {
|
|
||||||
return this->base_file->SetSize(size);
|
|
||||||
}
|
|
||||||
virtual Result OperateRangeImpl(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
|
||||||
return this->base_file->OperateRange(operation_type, offset, size, out_range_info);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* ================================================================================================ */
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::Initialize() {
|
|
||||||
DirectoryEntryType ent_type;
|
|
||||||
|
|
||||||
/* Check that the working directory exists. */
|
|
||||||
R_TRY_CATCH(this->fs->GetEntryType(&ent_type, WorkingDirectoryPath)) {
|
|
||||||
/* If path isn't found, create working directory and committed directory. */
|
|
||||||
R_CATCH(ResultFsPathNotFound) {
|
|
||||||
R_TRY(this->fs->CreateDirectory(WorkingDirectoryPath));
|
|
||||||
R_TRY(this->fs->CreateDirectory(CommittedDirectoryPath));
|
|
||||||
}
|
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
/* Now check for the committed directory. */
|
|
||||||
R_TRY_CATCH(this->fs->GetEntryType(&ent_type, CommittedDirectoryPath)) {
|
|
||||||
/* Committed doesn't exist, so synchronize and rename. */
|
|
||||||
R_CATCH(ResultFsPathNotFound) {
|
|
||||||
R_TRY(this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath));
|
|
||||||
return this->fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath);
|
|
||||||
}
|
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
/* If committed exists, synchronize it to the working directory. */
|
|
||||||
return this->SynchronizeDirectory(WorkingDirectoryPath, CommittedDirectoryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::SynchronizeDirectory(const FsPath &dst_dir, const FsPath &src_dir) {
|
|
||||||
/* Delete destination dir and recreate it. */
|
|
||||||
R_TRY_CATCH(this->fs->DeleteDirectoryRecursively(dst_dir)) {
|
|
||||||
R_CATCH(ResultFsPathNotFound) {
|
|
||||||
/* Nintendo returns error unconditionally, but I think that's a bug in their code. */
|
|
||||||
}
|
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
R_TRY(this->fs->CreateDirectory(dst_dir));
|
|
||||||
|
|
||||||
/* Get a buffer to work with. */
|
|
||||||
void *work_buf = nullptr;
|
|
||||||
size_t work_buf_size = 0;
|
|
||||||
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBuffersize));
|
|
||||||
ON_SCOPE_EXIT { free(work_buf); };
|
|
||||||
|
|
||||||
return FsDirUtils::CopyDirectoryRecursively(this->fs, dst_dir, src_dir, work_buf, work_buf_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::AllocateWorkBuffer(void **out_buf, size_t *out_size, const size_t ideal_size) {
|
|
||||||
size_t try_size = ideal_size;
|
|
||||||
|
|
||||||
/* Repeatedly try to allocate until success. */
|
|
||||||
while (true) {
|
|
||||||
void *buf = malloc(try_size);
|
|
||||||
if (buf != nullptr) {
|
|
||||||
*out_buf = buf;
|
|
||||||
*out_size = try_size;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Divide size by two. */
|
|
||||||
try_size >>= 1;
|
|
||||||
AMS_ASSERT(try_size > 0x200);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Return a result here? Nintendo does not, but they have other allocation failed results. */
|
|
||||||
/* Consider returning ResultFsAllocationFailureInDirectorySaveDataFileSystem? */
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) {
|
|
||||||
/* Validate path. */
|
|
||||||
if (1 + strnlen(relative_path, FS_MAX_PATH) >= out_size) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
if (relative_path[0] != '/') {
|
|
||||||
return ResultFsInvalidPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy working directory path. */
|
|
||||||
std::strncpy(out, WorkingDirectoryPath.str, out_size);
|
|
||||||
out[out_size-1] = 0;
|
|
||||||
|
|
||||||
/* Normalize it. */
|
|
||||||
constexpr size_t working_len = WorkingDirectoryPathLen - 1;
|
|
||||||
return FsPathUtils::Normalize(out + working_len, out_size - working_len, relative_path, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DirectorySaveDataFileSystem::OnWritableFileClose() {
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
this->open_writable_files--;
|
|
||||||
|
|
||||||
/* TODO: Abort if < 0? N does not. */
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::CopySaveFromProxy() {
|
|
||||||
if (this->proxy_save_fs != nullptr) {
|
|
||||||
/* Get a buffer to work with. */
|
|
||||||
void *work_buf = nullptr;
|
|
||||||
size_t work_buf_size = 0;
|
|
||||||
R_TRY(this->AllocateWorkBuffer(&work_buf, &work_buf_size, IdealWorkBuffersize));
|
|
||||||
ON_SCOPE_EXIT { free(work_buf); };
|
|
||||||
|
|
||||||
R_TRY(FsDirUtils::CopyDirectoryRecursively(this, this->proxy_save_fs.get(), FsPathUtils::RootPath, FsPathUtils::RootPath, work_buf, work_buf_size));
|
|
||||||
return this->Commit();
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================================================================================================ */
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->CreateFile(full_path, size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::DeleteFileImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->DeleteFile(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::CreateDirectoryImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->CreateDirectory(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::DeleteDirectoryImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->DeleteDirectory(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->DeleteDirectoryRecursively(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
FsPath full_old_path, full_new_path;
|
|
||||||
R_TRY(GetFullPath(full_old_path, old_path));
|
|
||||||
R_TRY(GetFullPath(full_new_path, new_path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->RenameFile(full_old_path, full_new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
FsPath full_old_path, full_new_path;
|
|
||||||
R_TRY(GetFullPath(full_old_path, old_path));
|
|
||||||
R_TRY(GetFullPath(full_new_path, new_path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->RenameDirectory(full_old_path, full_new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->GetEntryType(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
|
|
||||||
{
|
|
||||||
/* Open the raw file. */
|
|
||||||
std::unique_ptr<IFile> file;
|
|
||||||
R_TRY(this->fs->OpenFile(file, full_path, mode));
|
|
||||||
|
|
||||||
/* Create DirectorySaveDataFile wrapper. */
|
|
||||||
out_file = std::make_unique<DirectorySaveDataFile>(std::move(file), this, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for allocation failure. */
|
|
||||||
if (out_file == nullptr) {
|
|
||||||
return ResultFsAllocationFailureInDirectorySaveDataFileSystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Increment open writable files, if needed. */
|
|
||||||
if (mode & OpenMode_Write) {
|
|
||||||
this->open_writable_files++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->OpenDirectory(out_dir, full_path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::CommitImpl() {
|
|
||||||
/* Here, Nintendo does the following (with retries): */
|
|
||||||
/* - Rename Committed -> Synchronizing. */
|
|
||||||
/* - Synchronize Working -> Synchronizing (deleting Synchronizing). */
|
|
||||||
/* - Rename Synchronizing -> Committed. */
|
|
||||||
/* I think this is not the optimal order to do things, as the previous committed directory */
|
|
||||||
/* will be deleted if there is an error during synchronization. */
|
|
||||||
/* Instead, we will synchronize first, then delete committed, then rename. */
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
|
|
||||||
/* Ensure we don't have any open writable files. */
|
|
||||||
if (this->open_writable_files != 0) {
|
|
||||||
return ResultFsPreconditionViolation;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto SynchronizeWorkingDir = [&]() { return this->SynchronizeDirectory(SynchronizingDirectoryPath, WorkingDirectoryPath); };
|
|
||||||
const auto DeleteCommittedDir = [&]() { return this->fs->DeleteDirectoryRecursively(CommittedDirectoryPath); };
|
|
||||||
const auto RenameSynchDir = [&]() { return this->fs->RenameDirectory(SynchronizingDirectoryPath, CommittedDirectoryPath); };
|
|
||||||
|
|
||||||
/* Synchronize working directory. */
|
|
||||||
R_TRY(FsDirUtils::RetryUntilTargetNotLocked(std::move(SynchronizeWorkingDir)));
|
|
||||||
|
|
||||||
/* Delete committed directory. */
|
|
||||||
R_TRY_CATCH(FsDirUtils::RetryUntilTargetNotLocked(std::move(DeleteCommittedDir))) {
|
|
||||||
R_CATCH(ResultFsPathNotFound) {
|
|
||||||
/* It is okay for us to not have a committed directory here. */
|
|
||||||
}
|
|
||||||
} R_END_TRY_CATCH;
|
|
||||||
|
|
||||||
/* Rename synchronizing directory to committed directory. */
|
|
||||||
R_TRY(FsDirUtils::RetryUntilTargetNotLocked(std::move(RenameSynchDir)));
|
|
||||||
|
|
||||||
/* TODO: Should I call this->fs->Commit()? Nintendo does not. */
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
return this->fs->CleanDirectoryRecursively(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
|
||||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DirectorySaveDataFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
|
||||||
/* TODO: How should this work? N returns ResultFsNotImplemented. */
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_ifilesystem.hpp"
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
|
|
||||||
class DirectorySaveDataFileSystem : public IFileSystem {
|
|
||||||
private:
|
|
||||||
static constexpr FsPath CommittedDirectoryPath = EncodeConstantFsPath("/0/");
|
|
||||||
static constexpr FsPath WorkingDirectoryPath = EncodeConstantFsPath("/1/");
|
|
||||||
static constexpr FsPath SynchronizingDirectoryPath = EncodeConstantFsPath("/_/");
|
|
||||||
|
|
||||||
static constexpr size_t CommittedDirectoryPathLen = GetConstantFsPathLen(CommittedDirectoryPath);
|
|
||||||
static constexpr size_t WorkingDirectoryPathLen = GetConstantFsPathLen(WorkingDirectoryPath);
|
|
||||||
static constexpr size_t SynchronizingDirectoryPathLen = GetConstantFsPathLen(SynchronizingDirectoryPath);
|
|
||||||
|
|
||||||
static constexpr size_t IdealWorkBuffersize = 0x100000; /* 1 MB */
|
|
||||||
private:
|
|
||||||
std::shared_ptr<IFileSystem> shared_fs;
|
|
||||||
std::unique_ptr<IFileSystem> unique_fs;
|
|
||||||
std::unique_ptr<IFileSystem> proxy_save_fs;
|
|
||||||
IFileSystem *fs;
|
|
||||||
ams::os::Mutex lock;
|
|
||||||
size_t open_writable_files = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
DirectorySaveDataFileSystem(IFileSystem *fs, std::unique_ptr<IFileSystem> pfs) : unique_fs(fs), proxy_save_fs(std::move(pfs)) {
|
|
||||||
this->fs = this->unique_fs.get();
|
|
||||||
R_ASSERT(this->Initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectorySaveDataFileSystem(std::unique_ptr<IFileSystem> fs, std::unique_ptr<IFileSystem> pfs) : unique_fs(std::move(fs)), proxy_save_fs(std::move(pfs)) {
|
|
||||||
this->fs = this->unique_fs.get();
|
|
||||||
R_ASSERT(this->Initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectorySaveDataFileSystem(std::shared_ptr<IFileSystem> fs, std::unique_ptr<IFileSystem> pfs) : shared_fs(fs), proxy_save_fs(std::move(pfs)) {
|
|
||||||
this->fs = this->shared_fs.get();
|
|
||||||
R_ASSERT(this->Initialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~DirectorySaveDataFileSystem() { }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Result Initialize();
|
|
||||||
protected:
|
|
||||||
Result SynchronizeDirectory(const FsPath &dst_dir, const FsPath &src_dir);
|
|
||||||
Result AllocateWorkBuffer(void **out_buf, size_t *out_size, const size_t ideal_size);
|
|
||||||
|
|
||||||
Result GetFullPath(char *out, size_t out_size, const char *relative_path);
|
|
||||||
Result GetFullPath(FsPath &full_path, const FsPath &relative_path) {
|
|
||||||
return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str);
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
void OnWritableFileClose();
|
|
||||||
Result CopySaveFromProxy();
|
|
||||||
public:
|
|
||||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) override;
|
|
||||||
virtual Result DeleteFileImpl(const FsPath &path) override;
|
|
||||||
virtual Result CreateDirectoryImpl(const FsPath &path) override;
|
|
||||||
virtual Result DeleteDirectoryImpl(const FsPath &path) override;
|
|
||||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) override;
|
|
||||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) override;
|
|
||||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) override;
|
|
||||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) override;
|
|
||||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) override;
|
|
||||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) override;
|
|
||||||
virtual Result CommitImpl() override;
|
|
||||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
|
||||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
|
||||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) override;
|
|
||||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) override;
|
|
||||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) override;
|
|
||||||
};
|
|
|
@ -1,103 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "fs_file_storage.hpp"
|
|
||||||
|
|
||||||
Result FileStorage::UpdateSize() {
|
|
||||||
if (this->size == InvalidSize) {
|
|
||||||
return this->file->GetSize(&this->size);
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FileStorage::Read(void *buffer, size_t size, u64 offset) {
|
|
||||||
u64 read_size;
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
if (buffer == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
R_TRY(this->UpdateSize());
|
|
||||||
if (!IStorage::IsRangeValid(offset, size, this->size)) {
|
|
||||||
return ResultFsOutOfRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Nintendo doesn't do check output read size, but we will for safety. */
|
|
||||||
R_TRY(this->file->Read(&read_size, offset, buffer, size));
|
|
||||||
if (read_size != size && read_size) {
|
|
||||||
return this->Read(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(buffer) + read_size), size - read_size, offset + read_size);
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FileStorage::Write(void *buffer, size_t size, u64 offset) {
|
|
||||||
if (size == 0) {
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
if (buffer == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
R_TRY(this->UpdateSize());
|
|
||||||
if (!IStorage::IsRangeValid(offset, size, this->size)) {
|
|
||||||
return ResultFsOutOfRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->file->Write(offset, buffer, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FileStorage::Flush() {
|
|
||||||
return this->file->Flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FileStorage::GetSize(u64 *out_size) {
|
|
||||||
R_TRY(this->UpdateSize());
|
|
||||||
*out_size = this->size;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FileStorage::SetSize(u64 size) {
|
|
||||||
this->size = InvalidSize;
|
|
||||||
return this->file->SetSize(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FileStorage::OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
|
||||||
switch (operation_type) {
|
|
||||||
case FsOperationId_InvalidateCache:
|
|
||||||
case FsOperationId_QueryRange:
|
|
||||||
if (size == 0) {
|
|
||||||
if (operation_type == FsOperationId_QueryRange) {
|
|
||||||
if (out_range_info == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
/* N checks for size == sizeof(*out_range_info) here, but that's because their wrapper api is bad. */
|
|
||||||
std::memset(out_range_info, 0, sizeof(*out_range_info));
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
R_TRY(this->UpdateSize());
|
|
||||||
/* N checks for positivity + signed overflow on offset/size here, but we're using unsigned types... */
|
|
||||||
return this->file->OperateRange(operation_type, offset, size, out_range_info);
|
|
||||||
default:
|
|
||||||
return ResultFsUnsupportedOperation;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fs_shim.h"
|
|
||||||
|
|
||||||
#include "../debug.hpp"
|
|
||||||
#include "fs_istorage.hpp"
|
|
||||||
#include "fs_ifile.hpp"
|
|
||||||
|
|
||||||
class FileStorage : public IStorage {
|
|
||||||
public:
|
|
||||||
static constexpr u64 InvalidSize = UINT64_MAX;
|
|
||||||
private:
|
|
||||||
std::shared_ptr<IFile> base_file;
|
|
||||||
IFile *file;
|
|
||||||
u64 size;
|
|
||||||
public:
|
|
||||||
FileStorage(IFile *f) : base_file(f) {
|
|
||||||
this->file = this->base_file.get();
|
|
||||||
this->size = InvalidSize;
|
|
||||||
};
|
|
||||||
FileStorage(std::shared_ptr<IFile> f) : base_file(f) {
|
|
||||||
this->file = this->base_file.get();
|
|
||||||
this->size = InvalidSize;
|
|
||||||
};
|
|
||||||
virtual ~FileStorage() {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
protected:
|
|
||||||
Result UpdateSize();
|
|
||||||
public:
|
|
||||||
virtual Result Read(void *buffer, size_t size, u64 offset) override;
|
|
||||||
virtual Result Write(void *buffer, size_t size, u64 offset) override;
|
|
||||||
virtual Result Flush() override;
|
|
||||||
virtual Result GetSize(u64 *out_size) override;
|
|
||||||
virtual Result SetSize(u64 size) override;
|
|
||||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override;
|
|
||||||
};
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
enum OpenMode {
|
|
||||||
OpenMode_Read = (1 << 0),
|
|
||||||
OpenMode_Write = (1 << 1),
|
|
||||||
OpenMode_Append = (1 << 2),
|
|
||||||
|
|
||||||
OpenMode_ReadWrite = OpenMode_Read | OpenMode_Write,
|
|
||||||
OpenMode_All = OpenMode_ReadWrite | OpenMode_Append,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DirectoryOpenMode {
|
|
||||||
DirectoryOpenMode_Directories = (1 << 0),
|
|
||||||
DirectoryOpenMode_Files = (1 << 1),
|
|
||||||
|
|
||||||
DirectoryOpenMode_All = (DirectoryOpenMode_Directories | DirectoryOpenMode_Files),
|
|
||||||
};
|
|
||||||
|
|
||||||
enum DirectoryEntryType {
|
|
||||||
DirectoryEntryType_Directory,
|
|
||||||
DirectoryEntryType_File,
|
|
||||||
};
|
|
|
@ -1,115 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
class IDirectory {
|
|
||||||
public:
|
|
||||||
virtual ~IDirectory() {}
|
|
||||||
|
|
||||||
Result Read(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) {
|
|
||||||
if (out_count == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
if (max_entries == 0) {
|
|
||||||
*out_count = 0;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
if (out_entries == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return ReadImpl(out_count, out_entries, max_entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetEntryCount(uint64_t *count) {
|
|
||||||
if (count == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return GetEntryCountImpl(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* ...? */
|
|
||||||
private:
|
|
||||||
virtual Result ReadImpl(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) = 0;
|
|
||||||
virtual Result GetEntryCountImpl(uint64_t *count) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IDirectoryInterface : public IServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
Read = 0,
|
|
||||||
GetEntryCount = 1,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
std::unique_ptr<IDirectory> base_dir;
|
|
||||||
public:
|
|
||||||
IDirectoryInterface(IDirectory *d) : base_dir(d) {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
IDirectoryInterface(std::unique_ptr<IDirectory> d) : base_dir(std::move(d)) {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
/* Actual command API. */
|
|
||||||
virtual Result Read(OutBuffer<FsDirectoryEntry> buffer, Out<u64> out_count) final {
|
|
||||||
return this->base_dir->Read(out_count.GetPointer(), buffer.buffer, buffer.num_elements);
|
|
||||||
};
|
|
||||||
virtual Result GetEntryCount(Out<u64> out_count) final {
|
|
||||||
return this->base_dir->GetEntryCount(out_count.GetPointer());
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
/* 1.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IDirectoryInterface, Read),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IDirectoryInterface, GetEntryCount),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ProxyDirectory : public IDirectory {
|
|
||||||
private:
|
|
||||||
std::unique_ptr<FsDir> base_dir;
|
|
||||||
public:
|
|
||||||
ProxyDirectory(FsDir *d) : base_dir(d) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
ProxyDirectory(std::unique_ptr<FsDir> d) : base_dir(std::move(d)) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
ProxyDirectory(FsDir d) {
|
|
||||||
this->base_dir = std::make_unique<FsDir>(d);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~ProxyDirectory() {
|
|
||||||
fsDirClose(this->base_dir.get());
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
virtual Result ReadImpl(uint64_t *out_count, FsDirectoryEntry *out_entries, uint64_t max_entries) {
|
|
||||||
size_t count;
|
|
||||||
R_TRY(fsDirRead(this->base_dir.get(), 0, &count, max_entries, out_entries));
|
|
||||||
|
|
||||||
*out_count = count;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
virtual Result GetEntryCountImpl(uint64_t *count) {
|
|
||||||
return fsDirGetEntryCount(this->base_dir.get(), count);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,195 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_shim.h"
|
|
||||||
|
|
||||||
class IFile {
|
|
||||||
public:
|
|
||||||
virtual ~IFile() {}
|
|
||||||
|
|
||||||
Result Read(uint64_t *out, uint64_t offset, void *buffer, uint64_t size, uint32_t flags) {
|
|
||||||
(void)(flags);
|
|
||||||
if (out == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
if (size == 0) {
|
|
||||||
*out = 0;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
if (buffer == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return ReadImpl(out, offset, buffer, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Read(uint64_t *out, uint64_t offset, void *buffer, uint64_t size) {
|
|
||||||
return Read(out, offset, buffer, size, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetSize(uint64_t *out) {
|
|
||||||
if (out == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return GetSizeImpl(out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Flush() {
|
|
||||||
return FlushImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(uint64_t offset, void *buffer, uint64_t size, uint32_t flags) {
|
|
||||||
if (size == 0) {
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
if (buffer == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return WriteImpl(offset, buffer, size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Write(uint64_t offset, void *buffer, uint64_t size, bool flush = false) {
|
|
||||||
if (size == 0) {
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
if (buffer == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return WriteImpl(offset, buffer, size, flush);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetSize(uint64_t size) {
|
|
||||||
return SetSizeImpl(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
|
||||||
if (operation_type == FsOperationId_QueryRange) {
|
|
||||||
return OperateRangeImpl(operation_type, offset, size, out_range_info);
|
|
||||||
}
|
|
||||||
return ResultFsUnsupportedOperation;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* ...? */
|
|
||||||
private:
|
|
||||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) = 0;
|
|
||||||
virtual Result GetSizeImpl(u64 *out) = 0;
|
|
||||||
virtual Result FlushImpl() = 0;
|
|
||||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, u32 option) = 0;
|
|
||||||
virtual Result SetSizeImpl(u64 size) = 0;
|
|
||||||
virtual Result OperateRangeImpl(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IFileInterface : public IServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
Read = 0,
|
|
||||||
Write = 1,
|
|
||||||
Flush = 2,
|
|
||||||
SetSize = 3,
|
|
||||||
GetSize = 4,
|
|
||||||
OperateRange = 5,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
std::unique_ptr<IFile> base_file;
|
|
||||||
public:
|
|
||||||
IFileInterface(IFile *f) : base_file(f) {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
IFileInterface(std::unique_ptr<IFile> f) : base_file(std::move(f)) {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
/* Actual command API. */
|
|
||||||
virtual Result Read(OutBuffer<u8, BufferType_Type1> buffer, Out<u64> out_read, u64 offset, u64 size, u32 read_flags) final {
|
|
||||||
return this->base_file->Read(out_read.GetPointer(), offset, buffer.buffer, std::min(buffer.num_elements, size), read_flags);
|
|
||||||
};
|
|
||||||
virtual Result Write(InBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size, u32 write_flags) final {
|
|
||||||
return this->base_file->Write(offset, buffer.buffer, std::min(buffer.num_elements, size), write_flags);
|
|
||||||
};
|
|
||||||
virtual Result Flush() final {
|
|
||||||
return this->base_file->Flush();
|
|
||||||
};
|
|
||||||
virtual Result SetSize(u64 size) final {
|
|
||||||
return this->base_file->SetSize(size);
|
|
||||||
};
|
|
||||||
virtual Result GetSize(Out<u64> size) final {
|
|
||||||
return this->base_file->GetSize(size.GetPointer());
|
|
||||||
};
|
|
||||||
virtual Result OperateRange(Out<FsRangeInfo> range_info, u32 operation_type, u64 offset, u64 size) final {
|
|
||||||
return this->base_file->OperateRange(static_cast<FsOperationId>(operation_type), offset, size, range_info.GetPointer());
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
/* 1.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileInterface, Read),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileInterface, Write),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileInterface, Flush),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileInterface, SetSize),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileInterface, GetSize),
|
|
||||||
|
|
||||||
/* 4.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileInterface, OperateRange, FirmwareVersion_400),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ProxyFile : public IFile {
|
|
||||||
private:
|
|
||||||
std::unique_ptr<FsFile> base_file;
|
|
||||||
public:
|
|
||||||
ProxyFile(FsFile *f) : base_file(f) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
ProxyFile(std::unique_ptr<FsFile> f) : base_file(std::move(f)) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
ProxyFile(FsFile f) {
|
|
||||||
this->base_file = std::make_unique<FsFile>(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~ProxyFile() {
|
|
||||||
fsFileClose(this->base_file.get());
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
virtual Result ReadImpl(u64 *out, u64 offset, void *buffer, u64 size) override {
|
|
||||||
size_t out_sz;
|
|
||||||
R_TRY(fsFileRead(this->base_file.get(), offset, buffer, size, FS_READOPTION_NONE, &out_sz));
|
|
||||||
|
|
||||||
*out = out_sz;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
virtual Result GetSizeImpl(u64 *out) override {
|
|
||||||
return fsFileGetSize(this->base_file.get(), out);
|
|
||||||
}
|
|
||||||
virtual Result FlushImpl() override {
|
|
||||||
return fsFileFlush(this->base_file.get());
|
|
||||||
}
|
|
||||||
virtual Result WriteImpl(u64 offset, void *buffer, u64 size, u32 option) override {
|
|
||||||
return fsFileWrite(this->base_file.get(), offset, buffer, size, option);
|
|
||||||
}
|
|
||||||
virtual Result SetSizeImpl(u64 size) override {
|
|
||||||
return fsFileSetSize(this->base_file.get(), size);
|
|
||||||
}
|
|
||||||
virtual Result OperateRangeImpl(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
|
||||||
return fsFileOperateRange(this->base_file.get(), operation_type, offset, size, out_range_info);
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,463 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
#include "fs_filesystem_types.hpp"
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
|
|
||||||
#include "fs_ifile.hpp"
|
|
||||||
#include "fs_idirectory.hpp"
|
|
||||||
|
|
||||||
class IFile;
|
|
||||||
class IDirectory;
|
|
||||||
|
|
||||||
class IFileSystem {
|
|
||||||
public:
|
|
||||||
virtual ~IFileSystem() {}
|
|
||||||
|
|
||||||
Result CreateFile(const FsPath &path, uint64_t size, int flags) {
|
|
||||||
return CreateFileImpl(path, size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result CreateFile(const FsPath &path, uint64_t size) {
|
|
||||||
return CreateFileImpl(path, size, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DeleteFile(const FsPath &path) {
|
|
||||||
return DeleteFileImpl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result CreateDirectory(const FsPath &path) {
|
|
||||||
return CreateDirectoryImpl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DeleteDirectory(const FsPath &path) {
|
|
||||||
return DeleteDirectoryImpl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result DeleteDirectoryRecursively(const FsPath &path) {
|
|
||||||
return DeleteDirectoryRecursivelyImpl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result RenameFile(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
return RenameFileImpl(old_path, new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result RenameDirectory(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
return RenameDirectoryImpl(old_path, new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetEntryType(DirectoryEntryType *out, const FsPath &path) {
|
|
||||||
if (out == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return GetEntryTypeImpl(out, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result OpenFile(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
|
||||||
if (!(mode & OpenMode_ReadWrite)) {
|
|
||||||
return ResultFsInvalidArgument;
|
|
||||||
}
|
|
||||||
if (mode & ~OpenMode_All) {
|
|
||||||
return ResultFsInvalidArgument;
|
|
||||||
}
|
|
||||||
return OpenFileImpl(out_file, path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result OpenDirectory(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
|
||||||
if (!(mode & DirectoryOpenMode_All)) {
|
|
||||||
return ResultFsInvalidArgument;
|
|
||||||
}
|
|
||||||
if (mode & ~DirectoryOpenMode_All) {
|
|
||||||
return ResultFsInvalidArgument;
|
|
||||||
}
|
|
||||||
return OpenDirectoryImpl(out_dir, path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Commit() {
|
|
||||||
return CommitImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetFreeSpaceSize(uint64_t *out, const FsPath &path) {
|
|
||||||
if (out == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return GetFreeSpaceSizeImpl(out, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetTotalSpaceSize(uint64_t *out, const FsPath &path) {
|
|
||||||
if (out == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return GetTotalSpaceSizeImpl(out, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result CleanDirectoryRecursively(const FsPath &path) {
|
|
||||||
return CleanDirectoryRecursivelyImpl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result GetFileTimeStampRaw(FsTimeStampRaw *out, const FsPath &path) {
|
|
||||||
if (out == nullptr) {
|
|
||||||
return ResultFsNullptrArgument;
|
|
||||||
}
|
|
||||||
return GetFileTimeStampRawImpl(out, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result QueryEntry(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
|
||||||
return QueryEntryImpl(out, out_size, in, in_size, query, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* ...? */
|
|
||||||
private:
|
|
||||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) = 0;
|
|
||||||
virtual Result DeleteFileImpl(const FsPath &path) = 0;
|
|
||||||
virtual Result CreateDirectoryImpl(const FsPath &path) = 0;
|
|
||||||
virtual Result DeleteDirectoryImpl(const FsPath &path) = 0;
|
|
||||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) = 0;
|
|
||||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) = 0;
|
|
||||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) = 0;
|
|
||||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) = 0;
|
|
||||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) = 0;
|
|
||||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) = 0;
|
|
||||||
virtual Result CommitImpl() = 0;
|
|
||||||
|
|
||||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
(void)(out);
|
|
||||||
(void)(path);
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
(void)(out);
|
|
||||||
(void)(path);
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) = 0;
|
|
||||||
|
|
||||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
|
||||||
(void)(out);
|
|
||||||
(void)(path);
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
|
||||||
(void)(out);
|
|
||||||
(void)(out_size);
|
|
||||||
(void)(in);
|
|
||||||
(void)(in_size);
|
|
||||||
(void)(query);
|
|
||||||
(void)(path);
|
|
||||||
return ResultFsNotImplemented;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class IFileSystemInterface : public IServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
/* 1.0.0+ */
|
|
||||||
CreateFile = 0,
|
|
||||||
DeleteFile = 1,
|
|
||||||
CreateDirectory = 2,
|
|
||||||
DeleteDirectory = 3,
|
|
||||||
DeleteDirectoryRecursively = 4,
|
|
||||||
RenameFile = 5,
|
|
||||||
RenameDirectory = 6,
|
|
||||||
GetEntryType = 7,
|
|
||||||
OpenFile = 8,
|
|
||||||
OpenDirectory = 9,
|
|
||||||
Commit = 10,
|
|
||||||
GetFreeSpaceSize = 11,
|
|
||||||
GetTotalSpaceSize = 12,
|
|
||||||
|
|
||||||
/* 3.0.0+ */
|
|
||||||
CleanDirectoryRecursively = 13,
|
|
||||||
GetFileTimeStampRaw = 14,
|
|
||||||
|
|
||||||
/* 4.0.0+ */
|
|
||||||
QueryEntry = 15,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
std::unique_ptr<IFileSystem> unique_fs;
|
|
||||||
std::shared_ptr<IFileSystem> shared_fs;
|
|
||||||
IFileSystem *base_fs;
|
|
||||||
public:
|
|
||||||
IFileSystemInterface(IFileSystem *fs) : unique_fs(fs) {
|
|
||||||
this->base_fs = this->unique_fs.get();
|
|
||||||
};
|
|
||||||
IFileSystemInterface(std::unique_ptr<IFileSystem> fs) : unique_fs(std::move(fs)) {
|
|
||||||
this->base_fs = this->unique_fs.get();
|
|
||||||
};
|
|
||||||
IFileSystemInterface(std::shared_ptr<IFileSystem> fs) : shared_fs(fs) {
|
|
||||||
this->base_fs = this->shared_fs.get();
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
/* Actual command API. */
|
|
||||||
virtual Result CreateFile(InPointer<char> in_path, uint64_t size, int flags) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->CreateFile(path, size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result DeleteFile(InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->DeleteFile(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result CreateDirectory(InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->CreateDirectory(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result DeleteDirectory(InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->DeleteDirectory(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result DeleteDirectoryRecursively(InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->DeleteDirectoryRecursively(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result RenameFile(InPointer<char> in_old_path, InPointer<char> in_new_path) final {
|
|
||||||
FsPath old_path;
|
|
||||||
FsPath new_path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&old_path, in_old_path.pointer));
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&new_path, in_new_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->RenameFile(old_path, new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result RenameDirectory(InPointer<char> in_old_path, InPointer<char> in_new_path) final {
|
|
||||||
FsPath old_path;
|
|
||||||
FsPath new_path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&old_path, in_old_path.pointer));
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&new_path, in_new_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->RenameDirectory(old_path, new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
virtual Result GetEntryType(Out<u32> out_type, InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
DirectoryEntryType type;
|
|
||||||
R_TRY(this->base_fs->GetEntryType(&type, path));
|
|
||||||
|
|
||||||
out_type.SetValue(type);
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result OpenFile(Out<std::shared_ptr<IFileInterface>> out_intf, InPointer<char> in_path, uint32_t mode) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
std::unique_ptr<IFile> out_file;
|
|
||||||
R_TRY(this->base_fs->OpenFile(out_file, path, static_cast<OpenMode>(mode)));
|
|
||||||
|
|
||||||
out_intf.SetValue(std::make_shared<IFileInterface>(std::move(out_file)));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result OpenDirectory(Out<std::shared_ptr<IDirectoryInterface>> out_intf, InPointer<char> in_path, uint32_t mode) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
std::unique_ptr<IDirectory> out_dir;
|
|
||||||
R_TRY(this->base_fs->OpenDirectory(out_dir, path, static_cast<DirectoryOpenMode>(mode)));
|
|
||||||
|
|
||||||
out_intf.SetValue(std::make_shared<IDirectoryInterface>(std::move(out_dir)));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result Commit() final {
|
|
||||||
return this->base_fs->Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetFreeSpaceSize(Out<uint64_t> out_size, InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->GetFreeSpaceSize(out_size.GetPointer(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetTotalSpaceSize(Out<uint64_t> out_size, InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->GetTotalSpaceSize(out_size.GetPointer(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result CleanDirectoryRecursively(InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->CleanDirectoryRecursively(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetFileTimeStampRaw(Out<FsTimeStampRaw> out_timestamp, InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->GetFileTimeStampRaw(out_timestamp.GetPointer(), path);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result QueryEntry(OutBuffer<char, BufferType_Type1> out_buffer, InBuffer<char, BufferType_Type1> in_buffer, int query, InPointer<char> in_path) final {
|
|
||||||
FsPath path;
|
|
||||||
R_TRY(FsPathUtils::ConvertPathForServiceObject(&path, in_path.pointer));
|
|
||||||
|
|
||||||
return this->base_fs->QueryEntry(out_buffer.buffer, out_buffer.num_elements, in_buffer.buffer, in_buffer.num_elements, query, path);
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
/* 1.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, CreateFile),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, DeleteFile),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, CreateDirectory),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, DeleteDirectory),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, DeleteDirectoryRecursively),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, RenameFile),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, RenameDirectory),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetEntryType),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, OpenFile),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, OpenDirectory),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, Commit),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetFreeSpaceSize),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetTotalSpaceSize),
|
|
||||||
|
|
||||||
/* 3.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, CleanDirectoryRecursively, FirmwareVersion_300),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, GetFileTimeStampRaw, FirmwareVersion_300),
|
|
||||||
|
|
||||||
/* 4.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IFileSystemInterface, QueryEntry, FirmwareVersion_400),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ProxyFileSystem : public IFileSystem {
|
|
||||||
private:
|
|
||||||
std::unique_ptr<FsFileSystem> base_fs;
|
|
||||||
public:
|
|
||||||
ProxyFileSystem(FsFileSystem *fs) : base_fs(fs) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
ProxyFileSystem(std::unique_ptr<FsFileSystem> fs) : base_fs(std::move(fs)) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
ProxyFileSystem(FsFileSystem fs) {
|
|
||||||
this->base_fs = std::make_unique<FsFileSystem>(fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~ProxyFileSystem() {
|
|
||||||
fsFsClose(this->base_fs.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
|
||||||
return fsFsCreateFile(this->base_fs.get(), path.str, size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result DeleteFileImpl(const FsPath &path) {
|
|
||||||
return fsFsDeleteFile(this->base_fs.get(), path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result CreateDirectoryImpl(const FsPath &path) {
|
|
||||||
return fsFsCreateDirectory(this->base_fs.get(), path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result DeleteDirectoryImpl(const FsPath &path) {
|
|
||||||
return fsFsDeleteDirectory(this->base_fs.get(), path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
return fsFsDeleteDirectoryRecursively(this->base_fs.get(), path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
return fsFsRenameFile(this->base_fs.get(), old_path.str, new_path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
return fsFsRenameDirectory(this->base_fs.get(), old_path.str, new_path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
|
||||||
FsEntryType type;
|
|
||||||
R_TRY(fsFsGetEntryType(this->base_fs.get(), path.str, &type));
|
|
||||||
|
|
||||||
*out = static_cast<DirectoryEntryType>(static_cast<u32>(type));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
|
||||||
FsFile f;
|
|
||||||
R_TRY(fsFsOpenFile(this->base_fs.get(), path.str, static_cast<int>(mode), &f));
|
|
||||||
|
|
||||||
out_file = std::make_unique<ProxyFile>(f);
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
|
||||||
FsDir d;
|
|
||||||
R_TRY(fsFsOpenDirectory(this->base_fs.get(), path.str, static_cast<int>(mode), &d));
|
|
||||||
|
|
||||||
out_dir = std::make_unique<ProxyDirectory>(d);
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result CommitImpl() {
|
|
||||||
return fsFsCommit(this->base_fs.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
return fsFsGetFreeSpace(this->base_fs.get(), path.str, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
return fsFsGetTotalSpace(this->base_fs.get(), path.str, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
return fsFsCleanDirectoryRecursively(this->base_fs.get(), path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
|
||||||
return fsFsGetFileTimeStampRaw(this->base_fs.get(), path.str, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
|
||||||
return fsFsQueryEntry(this->base_fs.get(), out, out_size, in, in_size, path.str,static_cast<FsFileSystemQueryType>(query));
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,175 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fs_shim.h"
|
|
||||||
|
|
||||||
#include "../debug.hpp"
|
|
||||||
|
|
||||||
class IStorage {
|
|
||||||
public:
|
|
||||||
virtual ~IStorage();
|
|
||||||
|
|
||||||
virtual Result Read(void *buffer, size_t size, u64 offset) = 0;
|
|
||||||
virtual Result Write(void *buffer, size_t size, u64 offset) = 0;
|
|
||||||
virtual Result Flush() = 0;
|
|
||||||
virtual Result SetSize(u64 size) = 0;
|
|
||||||
virtual Result GetSize(u64 *out_size) = 0;
|
|
||||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
|
||||||
|
|
||||||
static inline bool IsRangeValid(uint64_t offset, uint64_t size, uint64_t total_size) {
|
|
||||||
return size <= total_size && offset <= total_size - size;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class IStorageInterface : public IServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
Read = 0,
|
|
||||||
Write = 1,
|
|
||||||
Flush = 2,
|
|
||||||
SetSize = 3,
|
|
||||||
GetSize = 4,
|
|
||||||
OperateRange = 5,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
IStorage *base_storage;
|
|
||||||
public:
|
|
||||||
IStorageInterface(IStorage *s) : base_storage(s) {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
|
|
||||||
~IStorageInterface() {
|
|
||||||
delete base_storage;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
/* Actual command API. */
|
|
||||||
virtual Result Read(OutBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size) final {
|
|
||||||
return this->base_storage->Read(buffer.buffer, std::min(buffer.num_elements, size), offset);
|
|
||||||
};
|
|
||||||
virtual Result Write(InBuffer<u8, BufferType_Type1> buffer, u64 offset, u64 size) final {
|
|
||||||
return this->base_storage->Write(buffer.buffer, std::min(buffer.num_elements, size), offset);
|
|
||||||
};
|
|
||||||
virtual Result Flush() final {
|
|
||||||
return this->base_storage->Flush();
|
|
||||||
};
|
|
||||||
virtual Result SetSize(u64 size) final {
|
|
||||||
return this->base_storage->SetSize(size);
|
|
||||||
};
|
|
||||||
virtual Result GetSize(Out<u64> size) final {
|
|
||||||
return this->base_storage->GetSize(size.GetPointer());
|
|
||||||
};
|
|
||||||
virtual Result OperateRange(Out<FsRangeInfo> range_info, u32 operation_type, u64 offset, u64 size) final {
|
|
||||||
return this->base_storage->OperateRange(static_cast<FsOperationId>(operation_type), offset, size, range_info.GetPointer());
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
/* 1.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, Read),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, Write),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, Flush),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, SetSize),
|
|
||||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, GetSize),
|
|
||||||
|
|
||||||
/* 4.0.0- */
|
|
||||||
MAKE_SERVICE_COMMAND_META(IStorageInterface, OperateRange, FirmwareVersion_400),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class IROStorage : public IStorage {
|
|
||||||
public:
|
|
||||||
virtual Result Read(void *buffer, size_t size, u64 offset) = 0;
|
|
||||||
virtual Result Write(void *buffer, size_t size, u64 offset) final {
|
|
||||||
(void)(buffer);
|
|
||||||
(void)(offset);
|
|
||||||
(void)(size);
|
|
||||||
return ResultFsUnsupportedOperation;
|
|
||||||
};
|
|
||||||
virtual Result Flush() final {
|
|
||||||
return ResultSuccess();
|
|
||||||
};
|
|
||||||
virtual Result SetSize(u64 size) final {
|
|
||||||
(void)(size);
|
|
||||||
return ResultFsUnsupportedOperation;
|
|
||||||
};
|
|
||||||
virtual Result GetSize(u64 *out_size) = 0;
|
|
||||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class ProxyStorage : public IStorage {
|
|
||||||
private:
|
|
||||||
FsStorage *base_storage;
|
|
||||||
public:
|
|
||||||
ProxyStorage(FsStorage *s) : base_storage(s) {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
ProxyStorage(FsStorage s) {
|
|
||||||
this->base_storage = new FsStorage(s);
|
|
||||||
};
|
|
||||||
virtual ~ProxyStorage() {
|
|
||||||
fsStorageClose(base_storage);
|
|
||||||
delete base_storage;
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
virtual Result Read(void *buffer, size_t size, u64 offset) override {
|
|
||||||
return fsStorageRead(this->base_storage, offset, buffer, size);
|
|
||||||
};
|
|
||||||
virtual Result Write(void *buffer, size_t size, u64 offset) override {
|
|
||||||
return fsStorageWrite(this->base_storage, offset, buffer, size);
|
|
||||||
};
|
|
||||||
virtual Result Flush() override {
|
|
||||||
return fsStorageFlush(this->base_storage);
|
|
||||||
};
|
|
||||||
virtual Result GetSize(u64 *out_size) override {
|
|
||||||
return fsStorageGetSize(this->base_storage, out_size);
|
|
||||||
};
|
|
||||||
virtual Result SetSize(u64 size) override {
|
|
||||||
return fsStorageSetSize(this->base_storage, size);
|
|
||||||
};
|
|
||||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
|
||||||
return fsStorageOperateRange(this->base_storage, operation_type, offset, size, out_range_info);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class ReadOnlyStorageAdapter : public IROStorage {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<IStorage> base_storage;
|
|
||||||
IStorage *storage;
|
|
||||||
public:
|
|
||||||
ReadOnlyStorageAdapter(IStorage *s) : base_storage(s) {
|
|
||||||
this->storage = this->base_storage.get();
|
|
||||||
}
|
|
||||||
ReadOnlyStorageAdapter(std::shared_ptr<IStorage> s) : base_storage(s) {
|
|
||||||
this->storage = this->base_storage.get();
|
|
||||||
}
|
|
||||||
virtual ~ReadOnlyStorageAdapter() {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
virtual Result Read(void *buffer, size_t size, u64 offset) override {
|
|
||||||
return this->base_storage->Read(buffer, size, offset);
|
|
||||||
};
|
|
||||||
virtual Result GetSize(u64 *out_size) override {
|
|
||||||
return this->base_storage->GetSize(out_size);
|
|
||||||
};
|
|
||||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
|
||||||
return this->base_storage->OperateRange(operation_type, offset, size, out_range_info);
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,267 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
|
|
||||||
Result FsPathUtils::VerifyPath(const char *path, size_t max_path_len, size_t max_name_len) {
|
|
||||||
const char *cur = path;
|
|
||||||
size_t name_len = 0;
|
|
||||||
|
|
||||||
for (size_t path_len = 0; path_len <= max_path_len && name_len <= max_name_len; path_len++) {
|
|
||||||
const char c = *(cur++);
|
|
||||||
/* If terminated, we're done. */
|
|
||||||
if (c == 0) {
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Nintendo converts the path from utf-8 to utf-32, one character at a time. */
|
|
||||||
/* We should do this. */
|
|
||||||
|
|
||||||
/* Banned characters: :*?<>| */
|
|
||||||
if (c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|') {
|
|
||||||
return ResultFsInvalidCharacter;
|
|
||||||
}
|
|
||||||
|
|
||||||
name_len++;
|
|
||||||
/* Check for separator. */
|
|
||||||
if (c == '/' || c == '\\') {
|
|
||||||
name_len = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsPathUtils::ConvertPathForServiceObject(FsPath *out, const char *path) {
|
|
||||||
/* Check for nullptr. */
|
|
||||||
if (out == nullptr || path == nullptr) {
|
|
||||||
return ResultFsInvalidPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy string, NULL terminate. */
|
|
||||||
/* NOTE: Nintendo adds an extra char at 0x301 for NULL terminator */
|
|
||||||
/* But then forces 0x300 to NULL anyway... */
|
|
||||||
std::strncpy(out->str, path, sizeof(out->str) - 1);
|
|
||||||
out->str[sizeof(out->str)-1] = 0;
|
|
||||||
|
|
||||||
/* Replace any instances of \ with / */
|
|
||||||
for (size_t i = 0; i < sizeof(out->str); i++) {
|
|
||||||
if (out->str[i] == '\\') {
|
|
||||||
out->str[i] = '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Nintendo allows some liberties if the path is a windows path. Who knows why... */
|
|
||||||
const auto prefix_len = FsPathUtils::IsWindowsAbsolutePath(path) ? 2 : 0;
|
|
||||||
const size_t max_len = (FS_MAX_PATH-1) - prefix_len;
|
|
||||||
return FsPathUtils::VerifyPath(out->str + prefix_len, max_len, max_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsPathUtils::IsNormalized(bool *out, const char *path) {
|
|
||||||
/* Nintendo uses a state machine here. */
|
|
||||||
enum class PathState {
|
|
||||||
Start,
|
|
||||||
Normal,
|
|
||||||
FirstSeparator,
|
|
||||||
Separator,
|
|
||||||
CurrentDir,
|
|
||||||
ParentDir,
|
|
||||||
WindowsDriveLetter,
|
|
||||||
};
|
|
||||||
|
|
||||||
PathState state = PathState::Start;
|
|
||||||
|
|
||||||
for (const char *cur = path; *cur != 0; cur++) {
|
|
||||||
const char c = *cur;
|
|
||||||
switch (state) {
|
|
||||||
case PathState::Start:
|
|
||||||
if (IsWindowsDriveLetter(c)) {
|
|
||||||
state = PathState::WindowsDriveLetter;
|
|
||||||
} else if (c == '/') {
|
|
||||||
state = PathState::FirstSeparator;
|
|
||||||
} else {
|
|
||||||
return ResultFsInvalidPathFormat;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PathState::Normal:
|
|
||||||
if (c == '/') {
|
|
||||||
state = PathState::Separator;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PathState::FirstSeparator:
|
|
||||||
case PathState::Separator:
|
|
||||||
/* It is unclear why first separator and separator are separate states... */
|
|
||||||
if (c == '/') {
|
|
||||||
*out = false;
|
|
||||||
return ResultSuccess();
|
|
||||||
} else if (c == '.') {
|
|
||||||
state = PathState::CurrentDir;
|
|
||||||
} else {
|
|
||||||
state = PathState::Normal;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PathState::CurrentDir:
|
|
||||||
if (c == '/') {
|
|
||||||
*out = false;
|
|
||||||
return ResultSuccess();
|
|
||||||
} else if (c == '.') {
|
|
||||||
state = PathState::ParentDir;
|
|
||||||
} else {
|
|
||||||
state = PathState::Normal;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PathState::ParentDir:
|
|
||||||
if (c == '/') {
|
|
||||||
*out = false;
|
|
||||||
return ResultSuccess();
|
|
||||||
} else {
|
|
||||||
state = PathState::Normal;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PathState::WindowsDriveLetter:
|
|
||||||
if (c == ':') {
|
|
||||||
*out = true;
|
|
||||||
return ResultSuccess();
|
|
||||||
} else {
|
|
||||||
return ResultFsInvalidPathFormat;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case PathState::Start:
|
|
||||||
case PathState::WindowsDriveLetter:
|
|
||||||
return ResultFsInvalidPathFormat;
|
|
||||||
case PathState::FirstSeparator:
|
|
||||||
case PathState::Normal:
|
|
||||||
*out = true;
|
|
||||||
break;
|
|
||||||
case PathState::CurrentDir:
|
|
||||||
case PathState::ParentDir:
|
|
||||||
case PathState::Separator:
|
|
||||||
*out = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsPathUtils::Normalize(char *out, size_t max_out_size, const char *src, size_t *out_len) {
|
|
||||||
/* Paths must start with / */
|
|
||||||
if (src[0] != '/') {
|
|
||||||
return ResultFsInvalidPathFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool skip_next_sep = false;
|
|
||||||
size_t i = 0;
|
|
||||||
size_t len = 0;
|
|
||||||
|
|
||||||
while (src[i] != 0) {
|
|
||||||
if (src[i] == '/') {
|
|
||||||
/* Swallow separators. */
|
|
||||||
while (src[++i] == '/') { }
|
|
||||||
if (src[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Handle skip if needed */
|
|
||||||
if (!skip_next_sep) {
|
|
||||||
if (len + 1 == max_out_size) {
|
|
||||||
out[len] = 0;
|
|
||||||
if (out_len != nullptr) {
|
|
||||||
*out_len = len;
|
|
||||||
}
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
out[len++] = '/';
|
|
||||||
|
|
||||||
/* TODO: N has some weird windows support stuff here under a bool. */
|
|
||||||
/* Boolean is normally false though? */
|
|
||||||
}
|
|
||||||
skip_next_sep = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See length of current dir. */
|
|
||||||
size_t dir_len = 0;
|
|
||||||
while (src[i+dir_len] != '/' && src[i+dir_len] != 0) {
|
|
||||||
dir_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FsPathUtils::IsCurrentDirectory(&src[i])) {
|
|
||||||
skip_next_sep = true;
|
|
||||||
} else if (FsPathUtils::IsParentDirectory(&src[i])) {
|
|
||||||
if (len == 1) {
|
|
||||||
return ResultFsDirectoryUnobtainable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Walk up a directory. */
|
|
||||||
len -= 2;
|
|
||||||
while (out[len] != '/') {
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Copy, possibly truncating. */
|
|
||||||
if (len + dir_len + 1 <= max_out_size) {
|
|
||||||
for (size_t j = 0; j < dir_len; j++) {
|
|
||||||
out[len++] = src[i+j];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const size_t copy_len = max_out_size - 1 - len;
|
|
||||||
for (size_t j = 0; j < copy_len; j++) {
|
|
||||||
out[len++] = src[i+j];
|
|
||||||
}
|
|
||||||
out[len] = 0;
|
|
||||||
if (out_len != nullptr) {
|
|
||||||
*out_len = len;
|
|
||||||
}
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
i += dir_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skip_next_sep) {
|
|
||||||
len--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len == 0 && max_out_size) {
|
|
||||||
out[len++] = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max_out_size < len - 1) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NULL terminate. */
|
|
||||||
out[len] = 0;
|
|
||||||
if (out_len != nullptr) {
|
|
||||||
*out_len = len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Assert normalized. */
|
|
||||||
bool normalized = false;
|
|
||||||
R_ASSERT(FsPathUtils::IsNormalized(&normalized, out));
|
|
||||||
AMS_ASSERT(normalized);
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
struct FsPath {
|
|
||||||
char str[FS_MAX_PATH];
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr FsPath EncodeConstantFsPath(const char *p) {
|
|
||||||
FsPath path = {0};
|
|
||||||
for (size_t i = 0; i < sizeof(path) - 1; i++) {
|
|
||||||
path.str[i] = p[i];
|
|
||||||
if (p[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr size_t GetConstantFsPathLen(FsPath path) {
|
|
||||||
size_t len = 0;
|
|
||||||
for (size_t i = 0; i < sizeof(path) - 1; i++) {
|
|
||||||
if (path.str[i] == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
len++;
|
|
||||||
}
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
class FsPathUtils {
|
|
||||||
public:
|
|
||||||
static constexpr FsPath RootPath = EncodeConstantFsPath("/");
|
|
||||||
public:
|
|
||||||
static Result VerifyPath(const char *path, size_t max_path_len, size_t max_name_len);
|
|
||||||
static Result ConvertPathForServiceObject(FsPath *out, const char *path);
|
|
||||||
|
|
||||||
static Result IsNormalized(bool *out, const char *path);
|
|
||||||
static Result Normalize(char *out, size_t max_out_size, const char *src, size_t *out_size);
|
|
||||||
|
|
||||||
static bool IsWindowsDriveLetter(const char c) {
|
|
||||||
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z');
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsCurrentDirectory(const char *path) {
|
|
||||||
return path[0] == '.' && (path[1] == 0 || path[1] == '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsParentDirectory(const char *path) {
|
|
||||||
return path[0] == '.' && path[1] == '.' && (path[2] == 0 || path[2] == '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsWindowsAbsolutePath(const char *path) {
|
|
||||||
/* Nintendo uses this in path comparisons... */
|
|
||||||
return IsWindowsDriveLetter(path[0]) && path[1] == ':';
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,125 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
#include <cstring>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include "fs_save_utils.hpp"
|
|
||||||
|
|
||||||
Result FsSaveUtils::GetSaveDataSpaceIdString(const char **out_str, u8 space_id) {
|
|
||||||
const char *str = nullptr;
|
|
||||||
|
|
||||||
switch (space_id) {
|
|
||||||
case FsSaveDataSpaceId_NandSystem:
|
|
||||||
case 100: /* ProperSystem */
|
|
||||||
case 101: /* SafeMode */
|
|
||||||
str = "sys";
|
|
||||||
break;
|
|
||||||
case FsSaveDataSpaceId_NandUser:
|
|
||||||
str = "user";
|
|
||||||
break;
|
|
||||||
case FsSaveDataSpaceId_SdCard:
|
|
||||||
str = "sd_sys";
|
|
||||||
break;
|
|
||||||
case FsSaveDataSpaceId_TemporaryStorage:
|
|
||||||
str = "temp";
|
|
||||||
break;
|
|
||||||
case 4: /* SdUser */
|
|
||||||
str = "sd_user";
|
|
||||||
break;
|
|
||||||
default: /* Unexpected */
|
|
||||||
str = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str == nullptr) {
|
|
||||||
return ResultFsInvalidSaveDataSpaceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out_str) {
|
|
||||||
*out_str = str;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsSaveUtils::GetSaveDataTypeString(const char **out_str, u8 save_data_type) {
|
|
||||||
const char *str = nullptr;
|
|
||||||
|
|
||||||
switch (save_data_type) {
|
|
||||||
case FsSaveDataType_SystemSaveData:
|
|
||||||
str = "system";
|
|
||||||
break;
|
|
||||||
case FsSaveDataType_SaveData:
|
|
||||||
str = "account";
|
|
||||||
break;
|
|
||||||
case FsSaveDataType_BcatDeliveryCacheStorage:
|
|
||||||
str = "bcat";
|
|
||||||
break;
|
|
||||||
case FsSaveDataType_DeviceSaveData:
|
|
||||||
str = "device";
|
|
||||||
break;
|
|
||||||
case FsSaveDataType_TemporaryStorage:
|
|
||||||
str = "temp";
|
|
||||||
break;
|
|
||||||
case FsSaveDataType_CacheStorage:
|
|
||||||
str = "cache";
|
|
||||||
break;
|
|
||||||
case 6: /* System Bcat Save */
|
|
||||||
str = "bcat_sys";
|
|
||||||
break;
|
|
||||||
default: /* Unexpected */
|
|
||||||
str = nullptr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str == nullptr) {
|
|
||||||
/* TODO: Better result? */
|
|
||||||
return ResultFsInvalidArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out_str) {
|
|
||||||
*out_str = str;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsSaveUtils::GetSaveDataDirectoryPath(FsPath &out_path, u8 space_id, u8 save_data_type, u64 program_id, u128 user_id, u64 save_id) {
|
|
||||||
const char *space_id_str, *save_type_str;
|
|
||||||
|
|
||||||
/* Get space_id, save_data_type strings. */
|
|
||||||
R_TRY(GetSaveDataSpaceIdString(&space_id_str, space_id));
|
|
||||||
R_TRY(GetSaveDataTypeString(&save_type_str, save_data_type));
|
|
||||||
|
|
||||||
/* Clear and initialize the path. */
|
|
||||||
std::memset(&out_path, 0, sizeof(out_path));
|
|
||||||
const bool is_system = (save_id != 0 && user_id == 0);
|
|
||||||
size_t out_path_len;
|
|
||||||
if (is_system) {
|
|
||||||
out_path_len = static_cast<size_t>(snprintf(out_path.str, sizeof(out_path.str), "/atmosphere/saves/sysnand/%s/%s/%016lx",
|
|
||||||
space_id_str, save_type_str, save_id));
|
|
||||||
} else {
|
|
||||||
out_path_len = static_cast<size_t>(snprintf(out_path.str, sizeof(out_path.str), "/atmosphere/saves/sysnand/%s/%s/%016lx/%016lx%016lx",
|
|
||||||
space_id_str, save_type_str, program_id, static_cast<u64>(user_id >> 64ul), static_cast<u64>(user_id)));
|
|
||||||
}
|
|
||||||
if (out_path_len >= sizeof(out_path)) {
|
|
||||||
/* TODO: Should we abort here? */
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
#include "fs_ifilesystem.hpp"
|
|
||||||
|
|
||||||
class FsSaveUtils {
|
|
||||||
private:
|
|
||||||
static Result GetSaveDataSpaceIdString(const char **out_str, u8 space_id);
|
|
||||||
static Result GetSaveDataTypeString(const char **out_str, u8 save_data_type);
|
|
||||||
public:
|
|
||||||
static Result GetSaveDataDirectoryPath(FsPath &out_path, u8 space_id, u8 save_data_type, u64 program_id, u128 user_id, u64 save_id);
|
|
||||||
};
|
|
|
@ -1,266 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include "fs_shim.h"
|
|
||||||
|
|
||||||
/* Missing fsp-srv commands. */
|
|
||||||
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisStorageId PartitionId) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u32 PartitionId;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 12;
|
|
||||||
raw->PartitionId = PartitionId;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 200;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data_id, FsStorage* out) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
FsStorageId storage_id;
|
|
||||||
u64 data_id;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 202;
|
|
||||||
raw->storage_id = storage_id;
|
|
||||||
raw->data_id = data_id;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType) {
|
|
||||||
if (hosversionBefore(2, 0, 0)) {
|
|
||||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u32 fsType;
|
|
||||||
u64 titleId;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 7;
|
|
||||||
raw->fsType = fsType;
|
|
||||||
raw->titleId = titleId;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType, const char* contentPath) {
|
|
||||||
if (hosversionBefore(2, 0, 0)) {
|
|
||||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
char sendStr[FS_MAX_PATH] = {0};
|
|
||||||
strncpy(sendStr, contentPath, sizeof(sendStr)-1);
|
|
||||||
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
ipcAddSendStatic(&c, sendStr, sizeof(sendStr), 0);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u32 fsType;
|
|
||||||
u64 titleId;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 8;
|
|
||||||
raw->fsType = fsType;
|
|
||||||
raw->titleId = titleId;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result fsOpenSaveDataFileSystemFwd(Service *s, FsFileSystem* out, u8 inval, FsSave *save) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u64 inval;//Actually u8.
|
|
||||||
FsSave save;
|
|
||||||
} PACKED *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 51;
|
|
||||||
raw->inval = (u64)inval;
|
|
||||||
memcpy(&raw->save, save, sizeof(FsSave));
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
/**
|
|
||||||
* @file fs_shim.h
|
|
||||||
* @brief Filesystem Services (fs) IPC wrapper. To be merged into libnx, eventually.
|
|
||||||
* @author SciresM
|
|
||||||
* @copyright libnx Authors
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Missing fsp-srv commands. */
|
|
||||||
Result fsOpenBisStorageFwd(Service* s, FsStorage* out, FsBisStorageId PartitionId);
|
|
||||||
Result fsOpenDataStorageByCurrentProcessFwd(Service* s, FsStorage* out);
|
|
||||||
Result fsOpenDataStorageByDataIdFwd(Service* s, FsStorageId storage_id, u64 data_id, FsStorage* out);
|
|
||||||
Result fsOpenFileSystemWithPatchFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType);
|
|
||||||
Result fsOpenFileSystemWithIdFwd(Service* s, FsFileSystem* out, u64 titleId, FsFileSystemType fsType, const char* contentPath);
|
|
||||||
Result fsOpenSaveDataFileSystemFwd(Service* s, FsFileSystem* out, u8 inval, FsSave *save);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,177 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "fs_subdirectory_filesystem.hpp"
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::Initialize(const char *bp) {
|
|
||||||
if (strnlen(bp, FS_MAX_PATH) >= FS_MAX_PATH) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Normalize the path. */
|
|
||||||
char normal_path[FS_MAX_PATH + 1];
|
|
||||||
size_t normal_path_len;
|
|
||||||
R_ASSERT(FsPathUtils::Normalize(normal_path, sizeof(normal_path), bp, &normal_path_len));
|
|
||||||
|
|
||||||
/* Ensure terminating '/' */
|
|
||||||
if (normal_path[normal_path_len-1] != '/') {
|
|
||||||
AMS_ASSERT(normal_path_len + 2 <= sizeof(normal_path));
|
|
||||||
|
|
||||||
strncat(normal_path, "/", 2);
|
|
||||||
normal_path[sizeof(normal_path)-1] = 0;
|
|
||||||
normal_path_len++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->base_path_len = normal_path_len + 1;
|
|
||||||
this->base_path = reinterpret_cast<char *>(malloc(this->base_path_len));
|
|
||||||
if (this->base_path == nullptr) {
|
|
||||||
return ResultFsAllocationFailureInSubDirectoryFileSystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::strncpy(this->base_path, normal_path, this->base_path_len);
|
|
||||||
this->base_path[this->base_path_len-1] = 0;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::GetFullPath(char *out, size_t out_size, const char *relative_path) {
|
|
||||||
if (this->base_path_len + strnlen(relative_path, FS_MAX_PATH) > out_size) {
|
|
||||||
return ResultFsTooLongPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy base path. */
|
|
||||||
std::strncpy(out, this->base_path, out_size);
|
|
||||||
out[out_size-1] = 0;
|
|
||||||
|
|
||||||
/* Normalize it. */
|
|
||||||
return FsPathUtils::Normalize(out + this->base_path_len - 2, out_size - (this->base_path_len - 2), relative_path, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::CreateFileImpl(const FsPath &path, uint64_t size, int flags) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->CreateFile(full_path, size, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::DeleteFileImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->DeleteFile(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::CreateDirectoryImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->CreateDirectory(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::DeleteDirectoryImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->DeleteDirectory(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::DeleteDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->DeleteDirectoryRecursively(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::RenameFileImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
FsPath full_old_path, full_new_path;
|
|
||||||
R_TRY(GetFullPath(full_old_path, old_path));
|
|
||||||
R_TRY(GetFullPath(full_new_path, new_path));
|
|
||||||
|
|
||||||
return this->base_fs->RenameFile(full_old_path, full_new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) {
|
|
||||||
FsPath full_old_path, full_new_path;
|
|
||||||
R_TRY(GetFullPath(full_old_path, old_path));
|
|
||||||
R_TRY(GetFullPath(full_new_path, new_path));
|
|
||||||
|
|
||||||
return this->base_fs->RenameDirectory(full_old_path, full_new_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->GetEntryType(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->OpenFile(out_file, full_path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->OpenDirectory(out_dir, full_path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::CommitImpl() {
|
|
||||||
return this->base_fs->Commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->GetFreeSpaceSize(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->GetTotalSpaceSize(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::CleanDirectoryRecursivelyImpl(const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->CleanDirectoryRecursively(full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->GetFileTimeStampRaw(out, full_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SubDirectoryFileSystem::QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) {
|
|
||||||
FsPath full_path;
|
|
||||||
R_TRY(GetFullPath(full_path, path));
|
|
||||||
|
|
||||||
return this->base_fs->QueryEntry(out, out_size, in, in_size, query, full_path);
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_ifilesystem.hpp"
|
|
||||||
#include "fs_path_utils.hpp"
|
|
||||||
|
|
||||||
class SubDirectoryFileSystem : public IFileSystem {
|
|
||||||
private:
|
|
||||||
std::shared_ptr<IFileSystem> base_fs;
|
|
||||||
char *base_path = nullptr;
|
|
||||||
size_t base_path_len = 0;
|
|
||||||
|
|
||||||
public:
|
|
||||||
SubDirectoryFileSystem(IFileSystem *fs, const char *bp) : base_fs(fs) {
|
|
||||||
R_ASSERT(this->Initialize(bp));
|
|
||||||
}
|
|
||||||
|
|
||||||
SubDirectoryFileSystem(std::shared_ptr<IFileSystem> fs, const char *bp) : base_fs(fs) {
|
|
||||||
R_ASSERT(this->Initialize(bp));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
virtual ~SubDirectoryFileSystem() {
|
|
||||||
if (this->base_path != nullptr) {
|
|
||||||
free(this->base_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
Result Initialize(const char *bp);
|
|
||||||
protected:
|
|
||||||
Result GetFullPath(char *out, size_t out_size, const char *relative_path);
|
|
||||||
Result GetFullPath(FsPath &full_path, const FsPath &relative_path) {
|
|
||||||
return GetFullPath(full_path.str, sizeof(full_path.str), relative_path.str);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual Result CreateFileImpl(const FsPath &path, uint64_t size, int flags) override;
|
|
||||||
virtual Result DeleteFileImpl(const FsPath &path) override;
|
|
||||||
virtual Result CreateDirectoryImpl(const FsPath &path) override;
|
|
||||||
virtual Result DeleteDirectoryImpl(const FsPath &path) override;
|
|
||||||
virtual Result DeleteDirectoryRecursivelyImpl(const FsPath &path) override;
|
|
||||||
virtual Result RenameFileImpl(const FsPath &old_path, const FsPath &new_path) override;
|
|
||||||
virtual Result RenameDirectoryImpl(const FsPath &old_path, const FsPath &new_path) override;
|
|
||||||
virtual Result GetEntryTypeImpl(DirectoryEntryType *out, const FsPath &path) override;
|
|
||||||
virtual Result OpenFileImpl(std::unique_ptr<IFile> &out_file, const FsPath &path, OpenMode mode) override;
|
|
||||||
virtual Result OpenDirectoryImpl(std::unique_ptr<IDirectory> &out_dir, const FsPath &path, DirectoryOpenMode mode) override;
|
|
||||||
virtual Result CommitImpl() override;
|
|
||||||
virtual Result GetFreeSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
|
||||||
virtual Result GetTotalSpaceSizeImpl(uint64_t *out, const FsPath &path) override;
|
|
||||||
virtual Result CleanDirectoryRecursivelyImpl(const FsPath &path) override;
|
|
||||||
virtual Result GetFileTimeStampRawImpl(FsTimeStampRaw *out, const FsPath &path) override;
|
|
||||||
virtual Result QueryEntryImpl(char *out, uint64_t out_size, const char *in, uint64_t in_size, int query, const FsPath &path) override;
|
|
||||||
};
|
|
|
@ -1,110 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fsmitm_boot0storage.hpp"
|
|
||||||
|
|
||||||
static ams::os::Mutex g_boot0_mutex;
|
|
||||||
static u8 g_boot0_bct_buffer[Boot0Storage::BctEndOffset];
|
|
||||||
|
|
||||||
bool Boot0Storage::CanModifyBctPubks() {
|
|
||||||
if (IsRcmBugPatched()) {
|
|
||||||
/* RCM bug patched. */
|
|
||||||
/* Only allow NS to update the BCT pubks. */
|
|
||||||
/* AutoRCM on a patched unit will cause a brick, so homebrew should NOT be allowed to write. */
|
|
||||||
return this->program_id == ams::ncm::ProgramId::Ns;
|
|
||||||
} else {
|
|
||||||
/* RCM bug unpatched. */
|
|
||||||
/* Allow homebrew but not NS to update the BCT pubks. */
|
|
||||||
return this->program_id != ams::ncm::ProgramId::Ns;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Boot0Storage::Read(void *_buffer, size_t size, u64 offset) {
|
|
||||||
std::scoped_lock lk{g_boot0_mutex};
|
|
||||||
|
|
||||||
return Base::Read(_buffer, size, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Boot0Storage::Write(void *_buffer, size_t size, u64 offset) {
|
|
||||||
std::scoped_lock lk{g_boot0_mutex};
|
|
||||||
|
|
||||||
u8 *buffer = static_cast<u8 *>(_buffer);
|
|
||||||
|
|
||||||
/* Protect the keyblob region from writes. */
|
|
||||||
if (offset <= EksStart) {
|
|
||||||
if (offset + size < EksStart) {
|
|
||||||
/* Fall through, no need to do anything here. */
|
|
||||||
} else {
|
|
||||||
if (offset + size < EksEnd) {
|
|
||||||
/* Adjust size to avoid writing end of data. */
|
|
||||||
size = EksStart - offset;
|
|
||||||
} else {
|
|
||||||
/* Perform portion of write falling past end of keyblobs. */
|
|
||||||
const u64 diff = EksEnd - offset;
|
|
||||||
R_TRY(Base::Write(buffer + diff, size - diff, EksEnd));
|
|
||||||
/* Adjust size to avoid writing end of data. */
|
|
||||||
size = EksStart - offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (offset < EksEnd) {
|
|
||||||
if (offset + size < EksEnd) {
|
|
||||||
/* Ignore writes falling strictly within the region. */
|
|
||||||
return ResultSuccess();
|
|
||||||
} else {
|
|
||||||
/* Only write past the end of the keyblob region. */
|
|
||||||
buffer = buffer + (EksEnd - offset);
|
|
||||||
size -= (EksEnd - offset);
|
|
||||||
offset = EksEnd;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Fall through, no need to do anything here. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size == 0) {
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We care about protecting autorcm from NS. */
|
|
||||||
if (CanModifyBctPubks() || offset >= BctEndOffset || (offset + BctSize >= BctEndOffset && offset % BctSize >= BctPubkEnd)) {
|
|
||||||
return Base::Write(buffer, size, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* First, let's deal with the data past the end. */
|
|
||||||
if (offset + size >= BctEndOffset) {
|
|
||||||
const u64 diff = BctEndOffset - offset;
|
|
||||||
R_TRY(ProxyStorage::Write(buffer + diff, size - diff, BctEndOffset));
|
|
||||||
size = diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read in the current BCT region. */
|
|
||||||
R_TRY(ProxyStorage::Read(g_boot0_bct_buffer, BctEndOffset, 0));
|
|
||||||
|
|
||||||
/* Update the bct buffer. */
|
|
||||||
for (u64 cur_ofs = offset; cur_ofs < BctEndOffset && cur_ofs < offset + size; cur_ofs++) {
|
|
||||||
const u64 cur_bct_rel_ofs = cur_ofs % BctSize;
|
|
||||||
if (cur_bct_rel_ofs < BctPubkStart || BctPubkEnd <= cur_bct_rel_ofs) {
|
|
||||||
g_boot0_bct_buffer[cur_ofs] = buffer[cur_ofs - offset];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ProxyStorage::Write(g_boot0_bct_buffer, BctEndOffset, 0);
|
|
||||||
}
|
|
|
@ -1,143 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <cstring>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_istorage.hpp"
|
|
||||||
|
|
||||||
/* Represents a sectored storage. */
|
|
||||||
template<u64 SectorSize>
|
|
||||||
class SectoredProxyStorage : public ProxyStorage {
|
|
||||||
private:
|
|
||||||
u64 cur_seek = 0;
|
|
||||||
u64 cur_sector = 0;
|
|
||||||
u64 cur_sector_ofs = 0;
|
|
||||||
u8 sector_buf[SectorSize];
|
|
||||||
private:
|
|
||||||
void Seek(u64 offset) {
|
|
||||||
this->cur_sector_ofs = offset % SectorSize;
|
|
||||||
this->cur_seek = offset - this->cur_sector_ofs;
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
SectoredProxyStorage(FsStorage *s) : ProxyStorage(s) { }
|
|
||||||
SectoredProxyStorage(FsStorage s) : ProxyStorage(s) { }
|
|
||||||
public:
|
|
||||||
virtual Result Read(void *_buffer, size_t size, u64 offset) override {
|
|
||||||
u8 *buffer = static_cast<u8 *>(_buffer);
|
|
||||||
this->Seek(offset);
|
|
||||||
|
|
||||||
if (this->cur_sector_ofs == 0 && size % SectorSize == 0) {
|
|
||||||
/* Fast case. */
|
|
||||||
return ProxyStorage::Read(buffer, size, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, this->cur_seek));
|
|
||||||
|
|
||||||
if (size + this->cur_sector_ofs <= SectorSize) {
|
|
||||||
memcpy(buffer, sector_buf + this->cur_sector_ofs, size);
|
|
||||||
} else {
|
|
||||||
/* Leaving the sector... */
|
|
||||||
size_t ofs = SectorSize - this->cur_sector_ofs;
|
|
||||||
memcpy(buffer, sector_buf + this->cur_sector_ofs, ofs);
|
|
||||||
size -= ofs;
|
|
||||||
|
|
||||||
/* We're guaranteed alignment, here. */
|
|
||||||
const size_t aligned_remaining_size = size - (size % SectorSize);
|
|
||||||
if (aligned_remaining_size) {
|
|
||||||
R_TRY(ProxyStorage::Read(buffer + ofs, aligned_remaining_size, offset + ofs));
|
|
||||||
ofs += aligned_remaining_size;
|
|
||||||
size -= aligned_remaining_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Read any leftover data. */
|
|
||||||
if (size) {
|
|
||||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, offset + ofs));
|
|
||||||
memcpy(buffer + ofs, sector_buf, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result Write(void *_buffer, size_t size, u64 offset) override {
|
|
||||||
u8 *buffer = static_cast<u8 *>(_buffer);
|
|
||||||
this->Seek(offset);
|
|
||||||
|
|
||||||
if (this->cur_sector_ofs == 0 && size % SectorSize == 0) {
|
|
||||||
/* Fast case. */
|
|
||||||
return ProxyStorage::Write(buffer, size, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, this->cur_seek));
|
|
||||||
|
|
||||||
if (size + this->cur_sector_ofs <= SectorSize) {
|
|
||||||
memcpy(this->sector_buf + this->cur_sector_ofs, buffer, size);
|
|
||||||
R_TRY(ProxyStorage::Write(this->sector_buf, SectorSize, this->cur_seek));
|
|
||||||
} else {
|
|
||||||
/* Leaving the sector... */
|
|
||||||
size_t ofs = SectorSize - this->cur_sector_ofs;
|
|
||||||
memcpy(this->sector_buf + this->cur_sector_ofs, buffer, ofs);
|
|
||||||
R_TRY(ProxyStorage::Write(this->sector_buf, ofs, this->cur_seek));
|
|
||||||
size -= ofs;
|
|
||||||
|
|
||||||
/* We're guaranteed alignment, here. */
|
|
||||||
const size_t aligned_remaining_size = size - (size % SectorSize);
|
|
||||||
if (aligned_remaining_size) {
|
|
||||||
R_TRY(ProxyStorage::Write(buffer + ofs, aligned_remaining_size, offset + ofs));
|
|
||||||
ofs += aligned_remaining_size;
|
|
||||||
size -= aligned_remaining_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write any leftover data. */
|
|
||||||
if (size) {
|
|
||||||
R_TRY(ProxyStorage::Read(this->sector_buf, SectorSize, offset + ofs));
|
|
||||||
memcpy(this->sector_buf, buffer + ofs, size);
|
|
||||||
R_TRY(ProxyStorage::Write(this->sector_buf, SectorSize, this->cur_seek));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Represents an RCM-preserving BOOT0 partition. */
|
|
||||||
class Boot0Storage : public SectoredProxyStorage<0x200> {
|
|
||||||
using Base = SectoredProxyStorage<0x200>;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static constexpr u64 BctEndOffset = 0xFC000;
|
|
||||||
static constexpr u64 BctSize = 0x4000;
|
|
||||||
static constexpr u64 BctPubkStart = 0x210;
|
|
||||||
static constexpr u64 BctPubkSize = 0x100;
|
|
||||||
static constexpr u64 BctPubkEnd = BctPubkStart + BctPubkSize;
|
|
||||||
|
|
||||||
static constexpr u64 EksStart = 0x180000;
|
|
||||||
static constexpr u64 EksSize = 0x4000;
|
|
||||||
static constexpr u64 EksEnd = EksStart + EksSize;
|
|
||||||
private:
|
|
||||||
ams::ncm::ProgramId program_id;
|
|
||||||
private:
|
|
||||||
bool CanModifyBctPubks();
|
|
||||||
public:
|
|
||||||
Boot0Storage(FsStorage *s, ams::ncm::ProgramId t) : Base(s), program_id(t) { }
|
|
||||||
Boot0Storage(FsStorage s, ams::ncm::ProgramId t) : Base(s), program_id(t) { }
|
|
||||||
public:
|
|
||||||
virtual Result Read(void *_buffer, size_t size, u64 offset) override;
|
|
||||||
virtual Result Write(void *_buffer, size_t size, u64 offset) override;
|
|
||||||
};
|
|
|
@ -1,151 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fsmitm_layeredrom.hpp"
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "../debug.hpp"
|
|
||||||
|
|
||||||
IStorage::~IStorage() = default;
|
|
||||||
|
|
||||||
LayeredRomFS::LayeredRomFS(std::shared_ptr<IROStorage> s_r, std::shared_ptr<IROStorage> f_r, u64 tid) : storage_romfs(s_r), file_romfs(f_r), program_id(tid) {
|
|
||||||
/* Start building the new virtual romfs. */
|
|
||||||
RomFSBuildContext build_ctx(this->program_id);
|
|
||||||
this->p_source_infos = std::shared_ptr<std::vector<RomFSSourceInfo>>(new std::vector<RomFSSourceInfo>(), [](std::vector<RomFSSourceInfo> *to_delete) {
|
|
||||||
for (unsigned int i = 0; i < to_delete->size(); i++) {
|
|
||||||
(*to_delete)[i].Cleanup();
|
|
||||||
}
|
|
||||||
delete to_delete;
|
|
||||||
});
|
|
||||||
if (Utils::IsSdInitialized()) {
|
|
||||||
build_ctx.MergeSdFiles();
|
|
||||||
}
|
|
||||||
if (this->file_romfs) {
|
|
||||||
build_ctx.MergeRomStorage(this->file_romfs.get(), RomFSDataSource::FileRomFS);
|
|
||||||
}
|
|
||||||
if (this->storage_romfs) {
|
|
||||||
build_ctx.MergeRomStorage(this->storage_romfs.get(), RomFSDataSource::BaseRomFS);
|
|
||||||
}
|
|
||||||
build_ctx.Build(this->p_source_infos.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Result LayeredRomFS::Read(void *buffer, size_t size, u64 offset) {
|
|
||||||
/* Size zero reads should always succeed. */
|
|
||||||
if (size == 0) {
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Validate size. */
|
|
||||||
u64 virt_size = (*this->p_source_infos)[this->p_source_infos->size() - 1].virtual_offset + (*this->p_source_infos)[this->p_source_infos->size() - 1].size;
|
|
||||||
if (offset >= virt_size) {
|
|
||||||
return ResultFsInvalidOffset;
|
|
||||||
}
|
|
||||||
if (virt_size - offset < size) {
|
|
||||||
size = virt_size - offset;
|
|
||||||
}
|
|
||||||
/* Find first source info via binary search. */
|
|
||||||
u32 cur_source_ind = 0;
|
|
||||||
u32 low = 0, high = this->p_source_infos->size() - 1;
|
|
||||||
while (low <= high) {
|
|
||||||
u32 mid = (low + high) / 2;
|
|
||||||
if ((*this->p_source_infos)[mid].virtual_offset > offset) {
|
|
||||||
/* Too high. */
|
|
||||||
high = mid - 1;
|
|
||||||
} else {
|
|
||||||
/* sources[mid].virtual_offset <= offset, invariant */
|
|
||||||
if (mid == this->p_source_infos->size() - 1 || (*this->p_source_infos)[mid + 1].virtual_offset > offset) {
|
|
||||||
/* Success */
|
|
||||||
cur_source_ind = mid;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
low = mid + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t read_so_far = 0;
|
|
||||||
while (read_so_far < size) {
|
|
||||||
RomFSSourceInfo *cur_source = &((*this->p_source_infos)[cur_source_ind]);
|
|
||||||
if (cur_source->virtual_offset + cur_source->size > offset) {
|
|
||||||
u64 cur_read_size = size - read_so_far;
|
|
||||||
if (cur_read_size > cur_source->size - (offset - cur_source->virtual_offset)) {
|
|
||||||
cur_read_size = cur_source->size - (offset - cur_source->virtual_offset);
|
|
||||||
}
|
|
||||||
switch (cur_source->type) {
|
|
||||||
case RomFSDataSource::MetaData:
|
|
||||||
{
|
|
||||||
FsFile file;
|
|
||||||
R_ASSERT(Utils::OpenSdFileForAtmosphere(this->program_id, ROMFS_METADATA_FILE_PATH, FS_OPEN_READ, &file));
|
|
||||||
size_t out_read;
|
|
||||||
R_ASSERT(fsFileRead(&file, (offset - cur_source->virtual_offset), (void *)((uintptr_t)buffer + read_so_far), cur_read_size, FS_READOPTION_NONE, &out_read));
|
|
||||||
AMS_ASSERT(out_read == cur_read_size);
|
|
||||||
fsFileClose(&file);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::LooseFile:
|
|
||||||
{
|
|
||||||
FsFile file;
|
|
||||||
R_ASSERT(Utils::OpenRomFSSdFile(this->program_id, cur_source->loose_source_info.path, FS_OPEN_READ, &file));
|
|
||||||
size_t out_read;
|
|
||||||
R_ASSERT(fsFileRead(&file, (offset - cur_source->virtual_offset), (void *)((uintptr_t)buffer + read_so_far), cur_read_size, FS_READOPTION_NONE, &out_read));
|
|
||||||
AMS_ASSERT(out_read == cur_read_size);
|
|
||||||
fsFileClose(&file);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::Memory:
|
|
||||||
{
|
|
||||||
memcpy((void *)((uintptr_t)buffer + read_so_far), cur_source->memory_source_info.data + (offset - cur_source->virtual_offset), cur_read_size);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::BaseRomFS:
|
|
||||||
{
|
|
||||||
R_ASSERT(this->storage_romfs->Read((void *)((uintptr_t)buffer + read_so_far), cur_read_size, cur_source->base_source_info.offset + (offset - cur_source->virtual_offset)));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::FileRomFS:
|
|
||||||
{
|
|
||||||
R_ASSERT(this->file_romfs->Read((void *)((uintptr_t)buffer + read_so_far), cur_read_size, cur_source->base_source_info.offset + (offset - cur_source->virtual_offset)));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
|
||||||
}
|
|
||||||
read_so_far += cur_read_size;
|
|
||||||
offset += cur_read_size;
|
|
||||||
} else {
|
|
||||||
/* Handle padding explicitly. */
|
|
||||||
cur_source_ind++;
|
|
||||||
/* Zero out the padding we skip, here. */
|
|
||||||
memset((void *)((uintptr_t)buffer + read_so_far), 0, ((*this->p_source_infos)[cur_source_ind]).virtual_offset - offset);
|
|
||||||
read_so_far += ((*this->p_source_infos)[cur_source_ind]).virtual_offset - offset;
|
|
||||||
offset = ((*this->p_source_infos)[cur_source_ind]).virtual_offset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
Result LayeredRomFS::GetSize(u64 *out_size) {
|
|
||||||
*out_size = (*this->p_source_infos)[this->p_source_infos->size() - 1].virtual_offset + (*this->p_source_infos)[this->p_source_infos->size() - 1].size;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
Result LayeredRomFS::OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) {
|
|
||||||
/* TODO: How should I implement this for a virtual romfs? */
|
|
||||||
if (operation_type == FsOperationId_QueryRange) {
|
|
||||||
*out_range_info = {0};
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_istorage.hpp"
|
|
||||||
#include "fsmitm_romfsbuild.hpp"
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
|
|
||||||
/* Represents a merged RomFS. */
|
|
||||||
class LayeredRomFS : public IROStorage {
|
|
||||||
private:
|
|
||||||
/* Data Sources. */
|
|
||||||
std::shared_ptr<IROStorage> storage_romfs;
|
|
||||||
std::shared_ptr<IROStorage> file_romfs;
|
|
||||||
/* Information about the merged RomFS. */
|
|
||||||
u64 program_id;
|
|
||||||
std::shared_ptr<std::vector<RomFSSourceInfo>> p_source_infos;
|
|
||||||
|
|
||||||
public:
|
|
||||||
LayeredRomFS(std::shared_ptr<IROStorage> s_r, std::shared_ptr<IROStorage> f_r, u64 tid);
|
|
||||||
virtual ~LayeredRomFS() = default;
|
|
||||||
|
|
||||||
virtual Result Read(void *buffer, size_t size, u64 offset) override;
|
|
||||||
virtual Result GetSize(u64 *out_size) override;
|
|
||||||
virtual Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override;
|
|
||||||
};
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fsmitm_main.hpp"
|
|
||||||
#include "fsmitm_service.hpp"
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
struct FsMitmManagerOptions {
|
|
||||||
static const size_t PointerBufferSize = 0x800;
|
|
||||||
static const size_t MaxDomains = 0x40;
|
|
||||||
static const size_t MaxDomainObjects = 0x4000;
|
|
||||||
};
|
|
||||||
using FsMitmManager = WaitableManager<FsMitmManagerOptions>;
|
|
||||||
|
|
||||||
void FsMitmMain(void *arg) {
|
|
||||||
/* Create server manager. */
|
|
||||||
static auto s_server_manager = FsMitmManager(5);
|
|
||||||
|
|
||||||
/* Create fsp-srv mitm. */
|
|
||||||
AddMitmServerToManager<FsMitmService>(&s_server_manager, "fsp-srv", 61);
|
|
||||||
|
|
||||||
/* Loop forever, servicing our services. */
|
|
||||||
s_server_manager.Process();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
constexpr u32 FsMitmPriority = 43;
|
|
||||||
constexpr u32 FsMitmStackSize = 0x8000;
|
|
||||||
|
|
||||||
void FsMitmMain(void *arg);
|
|
|
@ -1,408 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "fsmitm_romfsbuild.hpp"
|
|
||||||
|
|
||||||
#include "../debug.hpp"
|
|
||||||
|
|
||||||
void RomFSBuildContext::VisitDirectory(FsFileSystem *filesys, RomFSBuildDirectoryContext *parent) {
|
|
||||||
FsDir dir;
|
|
||||||
|
|
||||||
std::vector<RomFSBuildDirectoryContext *> child_dirs;
|
|
||||||
|
|
||||||
/* Open the current parent directory. */
|
|
||||||
R_ASSERT(Utils::OpenRomFSDir(filesys, this->program_id, parent->path, &dir));
|
|
||||||
{
|
|
||||||
ON_SCOPE_EXIT { fsDirClose(&dir); };
|
|
||||||
|
|
||||||
u64 read_entries;
|
|
||||||
while (true) {
|
|
||||||
R_ASSERT(fsDirRead(&dir, 0, &read_entries, 1, &this->dir_entry));
|
|
||||||
if (read_entries != 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
AMS_ASSERT(this->dir_entry.type == ENTRYTYPE_DIR || this->dir_entry.type == ENTRYTYPE_FILE);
|
|
||||||
if (this->dir_entry.type == ENTRYTYPE_DIR) {
|
|
||||||
RomFSBuildDirectoryContext *child = new RomFSBuildDirectoryContext({0});
|
|
||||||
/* Set child's path. */
|
|
||||||
child->cur_path_ofs = parent->path_len + 1;
|
|
||||||
child->path_len = child->cur_path_ofs + strlen(this->dir_entry.name);
|
|
||||||
child->path = new char[child->path_len + 1];
|
|
||||||
strcpy(child->path, parent->path);
|
|
||||||
if (child->path_len > FS_MAX_PATH - 1) {
|
|
||||||
fatalSimple(ResultFsTooLongPath);
|
|
||||||
}
|
|
||||||
strcat(child->path + parent->path_len, "/");
|
|
||||||
strcat(child->path + parent->path_len, this->dir_entry.name);
|
|
||||||
|
|
||||||
if (!this->AddDirectory(parent, child, NULL)) {
|
|
||||||
delete[] child->path;
|
|
||||||
delete child;
|
|
||||||
} else {
|
|
||||||
child_dirs.push_back(child);
|
|
||||||
}
|
|
||||||
} else /* if (this->dir_entry.type == ENTRYTYPE_FILE) */ {
|
|
||||||
RomFSBuildFileContext *child = new RomFSBuildFileContext({0});
|
|
||||||
/* Set child's path. */
|
|
||||||
child->cur_path_ofs = parent->path_len + 1;
|
|
||||||
child->path_len = child->cur_path_ofs + strlen(this->dir_entry.name);
|
|
||||||
child->path = new char[child->path_len + 1];
|
|
||||||
strcpy(child->path, parent->path);
|
|
||||||
if (child->path_len > FS_MAX_PATH - 1) {
|
|
||||||
fatalSimple(ResultFsTooLongPath);
|
|
||||||
}
|
|
||||||
strcat(child->path + parent->path_len, "/");
|
|
||||||
strcat(child->path + parent->path_len, this->dir_entry.name);
|
|
||||||
|
|
||||||
child->source = this->cur_source_type;
|
|
||||||
|
|
||||||
child->size = this->dir_entry.fileSize;
|
|
||||||
|
|
||||||
if (!this->AddFile(parent, child)) {
|
|
||||||
delete[] child->path;
|
|
||||||
delete child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &child : child_dirs) {
|
|
||||||
this->VisitDirectory(filesys, child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RomFSBuildContext::MergeSdFiles() {
|
|
||||||
FsFileSystem sd_filesystem;
|
|
||||||
FsDir dir;
|
|
||||||
if (!Utils::IsSdInitialized()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (R_FAILED((Utils::OpenSdDirForAtmosphere(this->program_id, "/romfs", &dir)))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fsDirClose(&dir);
|
|
||||||
if (R_FAILED(fsMountSdcard(&sd_filesystem))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this->cur_source_type = RomFSDataSource::LooseFile;
|
|
||||||
this->VisitDirectory(&sd_filesystem, this->root);
|
|
||||||
fsFsClose(&sd_filesystem);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RomFSBuildContext::VisitDirectory(RomFSBuildDirectoryContext *parent, u32 parent_offset, void *dir_table, size_t dir_table_size, void *file_table, size_t file_table_size) {
|
|
||||||
RomFSDirectoryEntry *parent_entry = romfs_get_direntry(dir_table, parent_offset);
|
|
||||||
if (parent_entry->file != ROMFS_ENTRY_EMPTY) {
|
|
||||||
RomFSFileEntry *cur_file = romfs_get_fentry(file_table, parent_entry->file);
|
|
||||||
while (cur_file != NULL) {
|
|
||||||
RomFSBuildFileContext *child = new RomFSBuildFileContext({0});
|
|
||||||
/* Set child's path. */
|
|
||||||
child->cur_path_ofs = parent->path_len + 1;
|
|
||||||
child->path_len = child->cur_path_ofs + cur_file->name_size;
|
|
||||||
child->path = new char[child->path_len + 1];
|
|
||||||
strcpy(child->path, parent->path);
|
|
||||||
if (child->path_len > FS_MAX_PATH - 1) {
|
|
||||||
fatalSimple(ResultFsTooLongPath);
|
|
||||||
}
|
|
||||||
strcat(child->path + parent->path_len, "/");
|
|
||||||
strncat(child->path + parent->path_len, cur_file->name, cur_file->name_size);
|
|
||||||
child->size = cur_file->size;
|
|
||||||
|
|
||||||
child->source = this->cur_source_type;
|
|
||||||
child->orig_offset = cur_file->offset;
|
|
||||||
if (!this->AddFile(parent, child)) {
|
|
||||||
delete[] child->path;
|
|
||||||
delete child;
|
|
||||||
}
|
|
||||||
if (cur_file->sibling == ROMFS_ENTRY_EMPTY) {
|
|
||||||
cur_file = NULL;
|
|
||||||
} else {
|
|
||||||
cur_file = romfs_get_fentry(file_table, cur_file->sibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (parent_entry->child != ROMFS_ENTRY_EMPTY) {
|
|
||||||
RomFSDirectoryEntry *cur_child = romfs_get_direntry(dir_table, parent_entry->child);
|
|
||||||
u32 cur_child_offset = parent_entry->child;
|
|
||||||
while (cur_child != NULL) {
|
|
||||||
RomFSBuildDirectoryContext *child = new RomFSBuildDirectoryContext({0});
|
|
||||||
/* Set child's path. */
|
|
||||||
child->cur_path_ofs = parent->path_len + 1;
|
|
||||||
child->path_len = child->cur_path_ofs + cur_child->name_size;
|
|
||||||
child->path = new char[child->path_len + 1];
|
|
||||||
strcpy(child->path, parent->path);
|
|
||||||
if (child->path_len > FS_MAX_PATH - 1) {
|
|
||||||
fatalSimple(ResultFsTooLongPath);
|
|
||||||
}
|
|
||||||
strcat(child->path + parent->path_len, "/");
|
|
||||||
strncat(child->path + parent->path_len, cur_child->name, cur_child->name_size);
|
|
||||||
|
|
||||||
RomFSBuildDirectoryContext *real = NULL;
|
|
||||||
if (!this->AddDirectory(parent, child, &real)) {
|
|
||||||
delete[] child->path;
|
|
||||||
delete child;
|
|
||||||
}
|
|
||||||
if (real == NULL) {
|
|
||||||
/* TODO: Better error. */
|
|
||||||
fatalSimple(ResultKernelConnectionClosed);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->VisitDirectory(real, cur_child_offset, dir_table, dir_table_size, file_table, file_table_size);
|
|
||||||
|
|
||||||
if (cur_child->sibling == ROMFS_ENTRY_EMPTY) {
|
|
||||||
cur_child = NULL;
|
|
||||||
} else {
|
|
||||||
cur_child_offset = cur_child->sibling;
|
|
||||||
cur_child = romfs_get_direntry(dir_table, cur_child->sibling);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RomFSBuildContext::MergeRomStorage(IROStorage *storage, RomFSDataSource source) {
|
|
||||||
RomFSHeader header;
|
|
||||||
R_ASSERT(storage->Read(&header, sizeof(header), 0));
|
|
||||||
AMS_ASSERT(header.header_size == sizeof(header));
|
|
||||||
|
|
||||||
/* Read tables. */
|
|
||||||
auto dir_table = std::make_unique<u8[]>(header.dir_table_size);
|
|
||||||
auto file_table = std::make_unique<u8[]>(header.file_table_size);
|
|
||||||
R_ASSERT(storage->Read(dir_table.get(), header.dir_table_size, header.dir_table_ofs));
|
|
||||||
R_ASSERT(storage->Read(file_table.get(), header.file_table_size, header.file_table_ofs));
|
|
||||||
|
|
||||||
this->cur_source_type = source;
|
|
||||||
this->VisitDirectory(this->root, 0x0, dir_table.get(), (size_t)header.dir_table_size, file_table.get(), (size_t)header.file_table_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RomFSBuildContext::AddDirectory(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildDirectoryContext *dir_ctx, RomFSBuildDirectoryContext **out_dir_ctx) {
|
|
||||||
/* Check whether it's already in the known directories. */
|
|
||||||
auto existing = this->directories.find(dir_ctx->path);
|
|
||||||
if (existing != this->directories.end()) {
|
|
||||||
if (out_dir_ctx) {
|
|
||||||
*out_dir_ctx = existing->second;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add a new directory. */
|
|
||||||
this->num_dirs++;
|
|
||||||
this->dir_table_size += sizeof(RomFSDirectoryEntry) + ((dir_ctx->path_len - dir_ctx->cur_path_ofs + 3) & ~3);
|
|
||||||
dir_ctx->parent = parent_dir_ctx;
|
|
||||||
this->directories.insert({dir_ctx->path, dir_ctx});
|
|
||||||
|
|
||||||
if (out_dir_ctx) {
|
|
||||||
*out_dir_ctx = dir_ctx;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RomFSBuildContext::AddFile(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildFileContext *file_ctx) {
|
|
||||||
/* Check whether it's already in the known files. */
|
|
||||||
auto existing = this->files.find(file_ctx->path);
|
|
||||||
if (existing != this->files.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add a new file. */
|
|
||||||
this->num_files++;
|
|
||||||
this->file_table_size += sizeof(RomFSFileEntry) + ((file_ctx->path_len - file_ctx->cur_path_ofs + 3) & ~3);
|
|
||||||
file_ctx->parent = parent_dir_ctx;
|
|
||||||
this->files.insert({file_ctx->path, file_ctx});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RomFSBuildContext::Build(std::vector<RomFSSourceInfo> *out_infos) {
|
|
||||||
RomFSBuildFileContext *cur_file;
|
|
||||||
RomFSBuildDirectoryContext *cur_dir;
|
|
||||||
u32 entry_offset;
|
|
||||||
|
|
||||||
u32 dir_hash_table_entry_count = romfs_get_hash_table_count(this->num_dirs);
|
|
||||||
u32 file_hash_table_entry_count = romfs_get_hash_table_count(this->num_files);
|
|
||||||
this->dir_hash_table_size = 4 * dir_hash_table_entry_count;
|
|
||||||
this->file_hash_table_size = 4 * file_hash_table_entry_count;
|
|
||||||
|
|
||||||
/* Assign metadata pointers */
|
|
||||||
auto *header = reinterpret_cast<RomFSHeader*>(std::malloc(sizeof(RomFSHeader)));
|
|
||||||
*header = {};
|
|
||||||
const size_t metadata_size = this->dir_hash_table_size + this->dir_table_size + this->file_hash_table_size + this->file_table_size;
|
|
||||||
u8 *metadata = reinterpret_cast<u8*>(std::malloc(metadata_size));
|
|
||||||
u32 *dir_hash_table = (u32 *)((uintptr_t)metadata);
|
|
||||||
RomFSDirectoryEntry *dir_table = (RomFSDirectoryEntry *)((uintptr_t)dir_hash_table + this->dir_hash_table_size);
|
|
||||||
u32 *file_hash_table = (u32 *)((uintptr_t)dir_table + this->dir_table_size);
|
|
||||||
RomFSFileEntry *file_table = (RomFSFileEntry *)((uintptr_t)file_hash_table + this->file_hash_table_size);
|
|
||||||
|
|
||||||
/* Clear out hash tables. */
|
|
||||||
for (u32 i = 0; i < dir_hash_table_entry_count; i++) {
|
|
||||||
dir_hash_table[i] = ROMFS_ENTRY_EMPTY;
|
|
||||||
}
|
|
||||||
for (u32 i = 0; i < file_hash_table_entry_count; i++) {
|
|
||||||
file_hash_table[i] = ROMFS_ENTRY_EMPTY;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_infos->clear();
|
|
||||||
out_infos->emplace_back(0, sizeof(*header), header, RomFSDataSource::Memory);
|
|
||||||
|
|
||||||
/* Determine file offsets. */
|
|
||||||
entry_offset = 0;
|
|
||||||
RomFSBuildFileContext *prev_file = NULL;
|
|
||||||
for (const auto &it : this->files) {
|
|
||||||
cur_file = it.second;
|
|
||||||
this->file_partition_size = (this->file_partition_size + 0xFULL) & ~0xFULL;
|
|
||||||
/* Check for extra padding in the original romfs source and preserve it, to help ourselves later. */
|
|
||||||
if (prev_file && prev_file->source == cur_file->source && (prev_file->source == RomFSDataSource::BaseRomFS || prev_file->source == RomFSDataSource::FileRomFS)) {
|
|
||||||
u64 expected = (this->file_partition_size - prev_file->offset + prev_file->orig_offset);
|
|
||||||
if (expected != cur_file->orig_offset) {
|
|
||||||
if (expected > cur_file->orig_offset) {
|
|
||||||
/* This case should NEVER happen. */
|
|
||||||
/* TODO: Better error. */
|
|
||||||
fatalSimple(ResultKernelConnectionClosed);
|
|
||||||
}
|
|
||||||
this->file_partition_size += cur_file->orig_offset - expected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cur_file->offset = this->file_partition_size;
|
|
||||||
this->file_partition_size += cur_file->size;
|
|
||||||
cur_file->entry_offset = entry_offset;
|
|
||||||
entry_offset += sizeof(RomFSFileEntry) + ((cur_file->path_len - cur_file->cur_path_ofs + 3) & ~3);
|
|
||||||
prev_file = cur_file;
|
|
||||||
}
|
|
||||||
/* Assign deferred parent/sibling ownership. */
|
|
||||||
for (auto it = this->files.rbegin(); it != this->files.rend(); it++) {
|
|
||||||
cur_file = it->second;
|
|
||||||
cur_file->sibling = cur_file->parent->file;
|
|
||||||
cur_file->parent->file = cur_file;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Determine directory offsets. */
|
|
||||||
entry_offset = 0;
|
|
||||||
for (const auto &it : this->directories) {
|
|
||||||
cur_dir = it.second;
|
|
||||||
cur_dir->entry_offset = entry_offset;
|
|
||||||
entry_offset += sizeof(RomFSDirectoryEntry) + ((cur_dir->path_len - cur_dir->cur_path_ofs + 3) & ~3);
|
|
||||||
}
|
|
||||||
/* Assign deferred parent/sibling ownership. */
|
|
||||||
for (auto it = this->directories.rbegin(); it->second != this->root; it++) {
|
|
||||||
cur_dir = it->second;
|
|
||||||
cur_dir->sibling = cur_dir->parent->child;
|
|
||||||
cur_dir->parent->child = cur_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Populate file tables. */
|
|
||||||
for (const auto &it : this->files) {
|
|
||||||
cur_file = it.second;
|
|
||||||
RomFSFileEntry *cur_entry = romfs_get_fentry(file_table, cur_file->entry_offset);
|
|
||||||
|
|
||||||
cur_entry->parent = cur_file->parent->entry_offset;
|
|
||||||
cur_entry->sibling = (cur_file->sibling == NULL) ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
|
|
||||||
cur_entry->offset = cur_file->offset;
|
|
||||||
cur_entry->size = cur_file->size;
|
|
||||||
|
|
||||||
u32 name_size = cur_file->path_len - cur_file->cur_path_ofs;
|
|
||||||
u32 hash = romfs_calc_path_hash(cur_file->parent->entry_offset, (unsigned char *)cur_file->path + cur_file->cur_path_ofs, 0, name_size);
|
|
||||||
cur_entry->hash = file_hash_table[hash % file_hash_table_entry_count];
|
|
||||||
file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
|
|
||||||
|
|
||||||
cur_entry->name_size = name_size;
|
|
||||||
memset(cur_entry->name, 0, (cur_entry->name_size + 3) & ~3);
|
|
||||||
memcpy(cur_entry->name, cur_file->path + cur_file->cur_path_ofs, name_size);
|
|
||||||
|
|
||||||
switch (cur_file->source) {
|
|
||||||
case RomFSDataSource::BaseRomFS:
|
|
||||||
case RomFSDataSource::FileRomFS:
|
|
||||||
/* Try to compact, if possible. */
|
|
||||||
if (out_infos->back().type == cur_file->source) {
|
|
||||||
out_infos->back().size = cur_file->offset + ROMFS_FILEPARTITION_OFS + cur_file->size - out_infos->back().virtual_offset;
|
|
||||||
} else {
|
|
||||||
out_infos->emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->size, cur_file->orig_offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::LooseFile:
|
|
||||||
{
|
|
||||||
char *path = new char[cur_file->path_len + 1];
|
|
||||||
strcpy(path, cur_file->path);
|
|
||||||
out_infos->emplace_back(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->size, path, cur_file->source);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/* TODO: Better error. */
|
|
||||||
fatalSimple(ResultKernelConnectionClosed);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Populate dir tables. */
|
|
||||||
for (const auto &it : this->directories) {
|
|
||||||
cur_dir = it.second;
|
|
||||||
RomFSDirectoryEntry *cur_entry = romfs_get_direntry(dir_table, cur_dir->entry_offset);
|
|
||||||
cur_entry->parent = cur_dir == this->root ? 0 : cur_dir->parent->entry_offset;
|
|
||||||
cur_entry->sibling = (cur_dir->sibling == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
|
|
||||||
cur_entry->child = (cur_dir->child == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
|
|
||||||
cur_entry->file = (cur_dir->file == NULL) ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
|
|
||||||
|
|
||||||
u32 name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
|
|
||||||
u32 hash = romfs_calc_path_hash(cur_dir == this->root ? 0 : cur_dir->parent->entry_offset, (unsigned char *)cur_dir->path + cur_dir->cur_path_ofs, 0, name_size);
|
|
||||||
cur_entry->hash = dir_hash_table[hash % dir_hash_table_entry_count];
|
|
||||||
dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
|
|
||||||
|
|
||||||
cur_entry->name_size = name_size;
|
|
||||||
memset(cur_entry->name, 0, (cur_entry->name_size + 3) & ~3);
|
|
||||||
memcpy(cur_entry->name, cur_dir->path + cur_dir->cur_path_ofs, name_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delete directories. */
|
|
||||||
for (const auto &it : this->directories) {
|
|
||||||
cur_dir = it.second;
|
|
||||||
delete[] cur_dir->path;
|
|
||||||
delete cur_dir;
|
|
||||||
}
|
|
||||||
this->root = NULL;
|
|
||||||
this->directories.clear();
|
|
||||||
|
|
||||||
/* Delete files. */
|
|
||||||
for (const auto &it : this->files) {
|
|
||||||
cur_file = it.second;
|
|
||||||
delete[] cur_file->path;
|
|
||||||
delete cur_file;
|
|
||||||
}
|
|
||||||
this->files.clear();
|
|
||||||
|
|
||||||
/* Set header fields. */
|
|
||||||
header->header_size = sizeof(*header);
|
|
||||||
header->file_hash_table_size = this->file_hash_table_size;
|
|
||||||
header->file_table_size = this->file_table_size;
|
|
||||||
header->dir_hash_table_size = this->dir_hash_table_size;
|
|
||||||
header->dir_table_size = this->dir_table_size;
|
|
||||||
header->file_partition_ofs = ROMFS_FILEPARTITION_OFS;
|
|
||||||
header->dir_hash_table_ofs = (header->file_partition_ofs + this->file_partition_size + 3ULL) & ~3ULL;
|
|
||||||
header->dir_table_ofs = header->dir_hash_table_ofs + header->dir_hash_table_size;
|
|
||||||
header->file_hash_table_ofs = header->dir_table_ofs + header->dir_table_size;
|
|
||||||
header->file_table_ofs = header->file_hash_table_ofs + header->file_hash_table_size;
|
|
||||||
|
|
||||||
/* Try to save metadata to the SD card, to save on memory space. */
|
|
||||||
if (R_SUCCEEDED(Utils::SaveSdFileForAtmosphere(this->program_id, ROMFS_METADATA_FILE_PATH, metadata, metadata_size))) {
|
|
||||||
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, RomFSDataSource::MetaData);
|
|
||||||
std::free(metadata);
|
|
||||||
} else {
|
|
||||||
out_infos->emplace_back(header->dir_hash_table_ofs, metadata_size, metadata, RomFSDataSource::Memory);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include "../debug.hpp"
|
|
||||||
#include "fs_istorage.hpp"
|
|
||||||
|
|
||||||
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF
|
|
||||||
#define ROMFS_FILEPARTITION_OFS 0x200
|
|
||||||
|
|
||||||
#define ROMFS_METADATA_FILE_PATH "romfs_metadata.bin"
|
|
||||||
|
|
||||||
/* Types for RomFS Meta construction. */
|
|
||||||
enum class RomFSDataSource {
|
|
||||||
BaseRomFS,
|
|
||||||
FileRomFS,
|
|
||||||
LooseFile,
|
|
||||||
MetaData,
|
|
||||||
Memory,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSBaseSourceInfo {
|
|
||||||
u64 offset;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSFileSourceInfo {
|
|
||||||
u64 offset;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSLooseSourceInfo {
|
|
||||||
const char *path;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSMemorySourceInfo {
|
|
||||||
const u8 *data;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSMetaDataSourceInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSSourceInfo {
|
|
||||||
u64 virtual_offset;
|
|
||||||
u64 size;
|
|
||||||
union {
|
|
||||||
RomFSBaseSourceInfo base_source_info;
|
|
||||||
RomFSFileSourceInfo file_source_info;
|
|
||||||
RomFSLooseSourceInfo loose_source_info;
|
|
||||||
RomFSMemorySourceInfo memory_source_info;
|
|
||||||
RomFSMetaDataSourceInfo metadata_source_info;
|
|
||||||
};
|
|
||||||
RomFSDataSource type;
|
|
||||||
|
|
||||||
RomFSSourceInfo(u64 v_o, u64 s, u64 offset, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
|
|
||||||
switch (this->type) {
|
|
||||||
case RomFSDataSource::BaseRomFS:
|
|
||||||
this->base_source_info.offset = offset;
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::FileRomFS:
|
|
||||||
this->file_source_info.offset = offset;
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::LooseFile:
|
|
||||||
case RomFSDataSource::MetaData:
|
|
||||||
case RomFSDataSource::Memory:
|
|
||||||
default:
|
|
||||||
/* TODO: Better error. */
|
|
||||||
fatalSimple(ResultKernelConnectionClosed);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RomFSSourceInfo(u64 v_o, u64 s, const void *arg, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
|
|
||||||
switch (this->type) {
|
|
||||||
case RomFSDataSource::LooseFile:
|
|
||||||
this->loose_source_info.path = (decltype(this->loose_source_info.path))arg;
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::Memory:
|
|
||||||
this->memory_source_info.data = (decltype(this->memory_source_info.data))arg;
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::MetaData:
|
|
||||||
case RomFSDataSource::BaseRomFS:
|
|
||||||
case RomFSDataSource::FileRomFS:
|
|
||||||
default:
|
|
||||||
/* TODO: Better error. */
|
|
||||||
fatalSimple(ResultKernelConnectionClosed);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RomFSSourceInfo(u64 v_o, u64 s, RomFSDataSource t) : virtual_offset(v_o), size(s), type(t) {
|
|
||||||
switch (this->type) {
|
|
||||||
case RomFSDataSource::MetaData:
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::LooseFile:
|
|
||||||
case RomFSDataSource::Memory:
|
|
||||||
case RomFSDataSource::BaseRomFS:
|
|
||||||
case RomFSDataSource::FileRomFS:
|
|
||||||
default:
|
|
||||||
/* TODO: Better error. */
|
|
||||||
fatalSimple(ResultKernelConnectionClosed);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Cleanup() {
|
|
||||||
switch (this->type) {
|
|
||||||
case RomFSDataSource::BaseRomFS:
|
|
||||||
case RomFSDataSource::FileRomFS:
|
|
||||||
case RomFSDataSource::MetaData:
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::LooseFile:
|
|
||||||
delete[] this->loose_source_info.path;
|
|
||||||
break;
|
|
||||||
case RomFSDataSource::Memory:
|
|
||||||
std::free((void*)this->memory_source_info.data);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
/* TODO: Better error. */
|
|
||||||
fatalSimple(ResultKernelConnectionClosed);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool Compare(RomFSSourceInfo *a, RomFSSourceInfo *b) {
|
|
||||||
return (a->virtual_offset < b->virtual_offset);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Types for building a RomFS. */
|
|
||||||
struct RomFSHeader {
|
|
||||||
u64 header_size;
|
|
||||||
u64 dir_hash_table_ofs;
|
|
||||||
u64 dir_hash_table_size;
|
|
||||||
u64 dir_table_ofs;
|
|
||||||
u64 dir_table_size;
|
|
||||||
u64 file_hash_table_ofs;
|
|
||||||
u64 file_hash_table_size;
|
|
||||||
u64 file_table_ofs;
|
|
||||||
u64 file_table_size;
|
|
||||||
u64 file_partition_ofs;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(RomFSHeader) == 0x50, "Incorrect RomFS Header definition!");
|
|
||||||
|
|
||||||
struct RomFSDirectoryEntry {
|
|
||||||
u32 parent;
|
|
||||||
u32 sibling;
|
|
||||||
u32 child;
|
|
||||||
u32 file;
|
|
||||||
u32 hash;
|
|
||||||
u32 name_size;
|
|
||||||
char name[];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "Incorrect RomFSDirectoryEntry definition!");
|
|
||||||
|
|
||||||
struct RomFSFileEntry {
|
|
||||||
u32 parent;
|
|
||||||
u32 sibling;
|
|
||||||
u64 offset;
|
|
||||||
u64 size;
|
|
||||||
u32 hash;
|
|
||||||
u32 name_size;
|
|
||||||
char name[];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(RomFSFileEntry) == 0x20, "Incorrect RomFSFileEntry definition!");
|
|
||||||
|
|
||||||
struct RomFSBuildFileContext;
|
|
||||||
|
|
||||||
/* Used as comparator for std::map<char *, RomFSBuild*Context> */
|
|
||||||
struct build_ctx_cmp {
|
|
||||||
bool operator()(const char *a, const char *b) const {
|
|
||||||
return strcmp(a, b) < 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSBuildDirectoryContext {
|
|
||||||
char *path;
|
|
||||||
u32 cur_path_ofs;
|
|
||||||
u32 path_len;
|
|
||||||
u32 entry_offset = 0;
|
|
||||||
RomFSBuildDirectoryContext *parent = NULL;
|
|
||||||
RomFSBuildDirectoryContext *child = NULL;
|
|
||||||
RomFSBuildDirectoryContext *sibling = NULL;
|
|
||||||
RomFSBuildFileContext *file = NULL;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RomFSBuildFileContext {
|
|
||||||
char *path;
|
|
||||||
u32 cur_path_ofs;
|
|
||||||
u32 path_len;
|
|
||||||
u32 entry_offset = 0;
|
|
||||||
u64 offset = 0;
|
|
||||||
u64 size = 0;
|
|
||||||
RomFSBuildDirectoryContext *parent = NULL;
|
|
||||||
RomFSBuildFileContext *sibling = NULL;
|
|
||||||
RomFSDataSource source{0};
|
|
||||||
u64 orig_offset = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RomFSBuildContext {
|
|
||||||
private:
|
|
||||||
u64 program_id;
|
|
||||||
RomFSBuildDirectoryContext *root;
|
|
||||||
std::map<char *, RomFSBuildDirectoryContext *, build_ctx_cmp> directories;
|
|
||||||
std::map<char *, RomFSBuildFileContext *, build_ctx_cmp> files;
|
|
||||||
u64 num_dirs = 0;
|
|
||||||
u64 num_files = 0;
|
|
||||||
u64 dir_table_size = 0;
|
|
||||||
u64 file_table_size = 0;
|
|
||||||
u64 dir_hash_table_size = 0;
|
|
||||||
u64 file_hash_table_size = 0;
|
|
||||||
u64 file_partition_size = 0;
|
|
||||||
|
|
||||||
FsDirectoryEntry dir_entry;
|
|
||||||
RomFSDataSource cur_source_type;
|
|
||||||
|
|
||||||
void VisitDirectory(FsFileSystem *filesys, RomFSBuildDirectoryContext *parent);
|
|
||||||
void VisitDirectory(RomFSBuildDirectoryContext *parent, u32 parent_offset, void *dir_table, size_t dir_table_size, void *file_table, size_t file_table_size);
|
|
||||||
|
|
||||||
bool AddDirectory(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildDirectoryContext *dir_ctx, RomFSBuildDirectoryContext **out_dir_ctx);
|
|
||||||
bool AddFile(RomFSBuildDirectoryContext *parent_dir_ctx, RomFSBuildFileContext *file_ctx);
|
|
||||||
public:
|
|
||||||
RomFSBuildContext(u64 tid) : program_id(tid) {
|
|
||||||
this->root = new RomFSBuildDirectoryContext({0});
|
|
||||||
this->root->path = new char[1];
|
|
||||||
this->root->path[0] = '\x00';
|
|
||||||
this->directories.insert({this->root->path, this->root});
|
|
||||||
this->num_dirs = 1;
|
|
||||||
this->dir_table_size = 0x18;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MergeSdFiles();
|
|
||||||
void MergeRomStorage(IROStorage *storage, RomFSDataSource source);
|
|
||||||
|
|
||||||
/* This finalizes the context. */
|
|
||||||
void Build(std::vector<RomFSSourceInfo> *out_infos);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static inline RomFSDirectoryEntry *romfs_get_direntry(void *directories, uint32_t offset) {
|
|
||||||
return (RomFSDirectoryEntry *)((uintptr_t)directories + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline RomFSFileEntry *romfs_get_fentry(void *files, uint32_t offset) {
|
|
||||||
return (RomFSFileEntry *)((uintptr_t)files + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t romfs_calc_path_hash(uint32_t parent, const unsigned char *path, uint32_t start, size_t path_len) {
|
|
||||||
uint32_t hash = parent ^ 123456789;
|
|
||||||
for (uint32_t i = 0; i < path_len; i++) {
|
|
||||||
hash = (hash >> 5) | (hash << 27);
|
|
||||||
hash ^= path[start + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline uint32_t romfs_get_hash_table_count(uint32_t num_entries) {
|
|
||||||
if (num_entries < 3) {
|
|
||||||
return 3;
|
|
||||||
} else if (num_entries < 19) {
|
|
||||||
return num_entries | 1;
|
|
||||||
}
|
|
||||||
uint32_t count = num_entries;
|
|
||||||
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "fs_istorage.hpp"
|
|
||||||
|
|
||||||
/* Represents a RomFS stored in some file. */
|
|
||||||
class RomFileStorage : public IROStorage {
|
|
||||||
private:
|
|
||||||
FsFile *base_file;
|
|
||||||
public:
|
|
||||||
RomFileStorage(FsFile *f) : base_file(f) {
|
|
||||||
/* ... */
|
|
||||||
};
|
|
||||||
RomFileStorage(FsFile f) {
|
|
||||||
this->base_file = new FsFile(f);
|
|
||||||
};
|
|
||||||
virtual ~RomFileStorage() {
|
|
||||||
fsFileClose(base_file);
|
|
||||||
delete base_file;
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
Result Read(void *buffer, size_t size, u64 offset) override {
|
|
||||||
size_t out_sz = 0;
|
|
||||||
R_TRY(fsFileRead(this->base_file, offset, buffer, size, FS_READOPTION_NONE, &out_sz));
|
|
||||||
if (out_sz != size && out_sz) {
|
|
||||||
R_TRY(this->Read((void *)((uintptr_t)buffer + out_sz), size - out_sz, offset + out_sz));
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
|
||||||
};
|
|
||||||
Result GetSize(u64 *out_size) override {
|
|
||||||
return fsFileGetSize(this->base_file, out_size);
|
|
||||||
};
|
|
||||||
Result OperateRange(FsOperationId operation_type, u64 offset, u64 size, FsRangeInfo *out_range_info) override {
|
|
||||||
/* TODO: Merge into libnx? */
|
|
||||||
return fsFileOperateRange(this->base_file, operation_type, offset, size, out_range_info);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Represents a RomFS accessed via some IStorage. */
|
|
||||||
using RomInterfaceStorage = ROProxyStorage;
|
|
|
@ -1,386 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fsmitm_service.hpp"
|
|
||||||
#include "fs_shim.h"
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "fsmitm_boot0storage.hpp"
|
|
||||||
#include "fsmitm_layeredrom.hpp"
|
|
||||||
|
|
||||||
#include "fs_dir_utils.hpp"
|
|
||||||
#include "fs_save_utils.hpp"
|
|
||||||
#include "fs_file_storage.hpp"
|
|
||||||
#include "fs_subdirectory_filesystem.hpp"
|
|
||||||
#include "fs_directory_redirection_filesystem.hpp"
|
|
||||||
#include "fs_directory_savedata_filesystem.hpp"
|
|
||||||
|
|
||||||
#include "../debug.hpp"
|
|
||||||
|
|
||||||
static ams::os::Mutex g_StorageCacheLock;
|
|
||||||
static std::unordered_map<u64, std::weak_ptr<IStorageInterface>> g_StorageCache;
|
|
||||||
|
|
||||||
static bool StorageCacheGetEntry(ams::ncm::ProgramId program_id, std::shared_ptr<IStorageInterface> *out) {
|
|
||||||
std::scoped_lock lock(g_StorageCacheLock);
|
|
||||||
if (g_StorageCache.find(static_cast<u64>(program_id)) == g_StorageCache.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto intf = g_StorageCache[static_cast<u64>(program_id)].lock();
|
|
||||||
if (intf != nullptr) {
|
|
||||||
*out = intf;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void StorageCacheSetEntry(ams::ncm::ProgramId program_id, std::shared_ptr<IStorageInterface> *ptr) {
|
|
||||||
std::scoped_lock lock(g_StorageCacheLock);
|
|
||||||
|
|
||||||
/* Ensure we always use the cached copy if present. */
|
|
||||||
if (g_StorageCache.find(static_cast<u64>(program_id)) != g_StorageCache.end()) {
|
|
||||||
auto intf = g_StorageCache[static_cast<u64>(program_id)].lock();
|
|
||||||
if (intf != nullptr) {
|
|
||||||
*ptr = intf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_StorageCache[static_cast<u64>(program_id)] = *ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FsMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
|
||||||
auto this_ptr = static_cast<FsMitmService *>(obj);
|
|
||||||
switch (static_cast<CommandId>(ctx->cmd_id)) {
|
|
||||||
case CommandId::SetCurrentProcess:
|
|
||||||
if (R_SUCCEEDED(ctx->rc)) {
|
|
||||||
this_ptr->has_initialized = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsMitmService::OpenHblWebContentFileSystem(Out<std::shared_ptr<IFileSystemInterface>> &out_fs) {
|
|
||||||
/* Mount the SD card using fs.mitm's session. */
|
|
||||||
FsFileSystem sd_fs;
|
|
||||||
R_TRY(fsMountSdcard(&sd_fs));
|
|
||||||
|
|
||||||
/* Set output filesystem. */
|
|
||||||
std::unique_ptr<IFileSystem> web_ifs = std::make_unique<SubDirectoryFileSystem>(std::make_shared<ProxyFileSystem>(sd_fs), AtmosphereHblWebContentDir);
|
|
||||||
out_fs.SetValue(std::make_shared<IFileSystemInterface>(std::move(web_ifs)));
|
|
||||||
if (out_fs.IsDomain()) {
|
|
||||||
out_fs.ChangeObjectId(sd_fs.s.object_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsMitmService::OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out_fs, u64 program_id, u32 filesystem_type) {
|
|
||||||
/* Check for eligibility. */
|
|
||||||
{
|
|
||||||
FsDir d;
|
|
||||||
if (!Utils::IsWebAppletTid(static_cast<u64>(this->program_id)) || filesystem_type != FsFileSystemType_ContentManual || !Utils::IsHblTid(program_id) ||
|
|
||||||
R_FAILED(Utils::OpenSdDir(AtmosphereHblWebContentDir, &d))) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
fsDirClose(&d);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If there's an existing filesystem, don't override. */
|
|
||||||
/* TODO: Multiplex, overriding existing content with HBL content. */
|
|
||||||
{
|
|
||||||
FsFileSystem fs;
|
|
||||||
if (R_SUCCEEDED(fsOpenFileSystemWithPatchFwd(this->forward_service.get(), &fs, program_id, static_cast<FsFileSystemType>(filesystem_type)))) {
|
|
||||||
fsFsClose(&fs);
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->OpenHblWebContentFileSystem(out_fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsMitmService::OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out_fs, InPointer<char> path, u64 program_id, u32 filesystem_type) {
|
|
||||||
/* Check for eligibility. */
|
|
||||||
{
|
|
||||||
FsDir d;
|
|
||||||
if (!Utils::IsWebAppletTid(static_cast<u64>(this->program_id)) || filesystem_type != FsFileSystemType_ContentManual || !Utils::IsHblTid(program_id) ||
|
|
||||||
R_FAILED(Utils::OpenSdDir(AtmosphereHblWebContentDir, &d))) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
fsDirClose(&d);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If there's an existing filesystem, don't override. */
|
|
||||||
/* TODO: Multiplex, overriding existing content with HBL content. */
|
|
||||||
{
|
|
||||||
FsFileSystem fs;
|
|
||||||
if (R_SUCCEEDED(fsOpenFileSystemWithIdFwd(this->forward_service.get(), &fs, program_id, static_cast<FsFileSystemType>(filesystem_type), path.pointer))) {
|
|
||||||
fsFsClose(&fs);
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this->OpenHblWebContentFileSystem(out_fs);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsMitmService::OpenSdCardFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out_fs) {
|
|
||||||
/* We only care about redirecting this for NS/Emummc. */
|
|
||||||
if (this->program_id != ams::ncm::ProgramId::Ns) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
if (!IsEmummc()) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mount the SD card. */
|
|
||||||
FsFileSystem sd_fs;
|
|
||||||
R_TRY(fsMountSdcard(&sd_fs));
|
|
||||||
|
|
||||||
/* Set output filesystem. */
|
|
||||||
std::unique_ptr<IFileSystem> redir_fs = std::make_unique<DirectoryRedirectionFileSystem>(new ProxyFileSystem(sd_fs), "/Nintendo", GetEmummcNintendoDirPath());
|
|
||||||
out_fs.SetValue(std::make_shared<IFileSystemInterface>(std::move(redir_fs)));
|
|
||||||
if (out_fs.IsDomain()) {
|
|
||||||
out_fs.ChangeObjectId(sd_fs.s.object_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result FsMitmService::OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out_fs, u8 space_id, FsSave save_struct) {
|
|
||||||
bool should_redirect_saves = false;
|
|
||||||
const bool has_redirect_save_flags = Utils::HasFlag(static_cast<u64>(this->program_id), "redirect_save");
|
|
||||||
if (R_FAILED(Utils::GetSettingsItemBooleanValue("atmosphere", "fsmitm_redirect_saves_to_sd", &should_redirect_saves))) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For now, until we're sure this is robust, only intercept normal savedata , check if flag exist*/
|
|
||||||
if (!has_redirect_save_flags || !should_redirect_saves || save_struct.saveDataType != FsSaveDataType_SaveData) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Verify we can open the save. */
|
|
||||||
FsFileSystem save_fs;
|
|
||||||
if (R_FAILED(fsOpenSaveDataFileSystemFwd(this->forward_service.get(), &save_fs, space_id, &save_struct))) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
std::unique_ptr<IFileSystem> save_ifs = std::make_unique<ProxyFileSystem>(save_fs);
|
|
||||||
|
|
||||||
{
|
|
||||||
/* Mount the SD card using fs.mitm's session. */
|
|
||||||
FsFileSystem sd_fs;
|
|
||||||
R_TRY(fsMountSdcard(&sd_fs));
|
|
||||||
std::shared_ptr<IFileSystem> sd_ifs = std::make_shared<ProxyFileSystem>(sd_fs);
|
|
||||||
|
|
||||||
|
|
||||||
/* Verify that we can open the save directory, and that it exists. */
|
|
||||||
const u64 target_tid = save_struct.titleID == 0 ? static_cast<u64>(this->program_id) : save_struct.titleID;
|
|
||||||
FsPath save_dir_path;
|
|
||||||
R_TRY(FsSaveUtils::GetSaveDataDirectoryPath(save_dir_path, space_id, save_struct.saveDataType, target_tid, save_struct.userID, save_struct.saveID));
|
|
||||||
|
|
||||||
/* Check if this is the first time we're making the save. */
|
|
||||||
bool is_new_save = false;
|
|
||||||
{
|
|
||||||
DirectoryEntryType ent;
|
|
||||||
if (sd_ifs->GetEntryType(&ent, save_dir_path) == ResultFsPathNotFound) {
|
|
||||||
is_new_save = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure the directory exists. */
|
|
||||||
R_TRY(FsDirUtils::EnsureDirectoryExists(sd_ifs.get(), save_dir_path));
|
|
||||||
|
|
||||||
std::shared_ptr<DirectorySaveDataFileSystem> dirsave_ifs = std::make_shared<DirectorySaveDataFileSystem>(new SubDirectoryFileSystem(sd_ifs, save_dir_path.str), std::move(save_ifs));
|
|
||||||
|
|
||||||
/* If it's the first time we're making the save, copy existing savedata over. */
|
|
||||||
if (is_new_save) {
|
|
||||||
/* TODO: Check error? */
|
|
||||||
dirsave_ifs->CopySaveFromProxy();
|
|
||||||
}
|
|
||||||
|
|
||||||
out_fs.SetValue(std::make_shared<IFileSystemInterface>(static_cast<std::shared_ptr<IFileSystem>>(dirsave_ifs)));
|
|
||||||
if (out_fs.IsDomain()) {
|
|
||||||
out_fs.ChangeObjectId(sd_fs.s.object_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Gate access to the BIS partitions. */
|
|
||||||
Result FsMitmService::OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out_storage, u32 _bis_partition_id) {
|
|
||||||
const FsBisStorageId bis_partition_id = static_cast<FsBisStorageId>(_bis_partition_id);
|
|
||||||
|
|
||||||
/* Try to open a storage for the partition. */
|
|
||||||
FsStorage bis_storage;
|
|
||||||
R_TRY(fsOpenBisStorageFwd(this->forward_service.get(), &bis_storage, bis_partition_id));
|
|
||||||
|
|
||||||
const bool is_sysmodule = ams::ncm::IsSystemProgramId(this->program_id);
|
|
||||||
const bool has_bis_write_flag = Utils::HasFlag(static_cast<u64>(this->program_id), "bis_write");
|
|
||||||
const bool has_cal0_read_flag = Utils::HasFlag(static_cast<u64>(this->program_id), "cal_read");
|
|
||||||
|
|
||||||
/* Set output storage. */
|
|
||||||
if (bis_partition_id == FsBisStorageId_Boot0) {
|
|
||||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new Boot0Storage(bis_storage, this->program_id)));
|
|
||||||
} else if (bis_partition_id == FsBisStorageId_CalibrationBinary) {
|
|
||||||
/* PRODINFO should *never* be writable. */
|
|
||||||
if (is_sysmodule || has_cal0_read_flag) {
|
|
||||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ReadOnlyStorageAdapter(new ProxyStorage(bis_storage))));
|
|
||||||
} else {
|
|
||||||
/* Do not allow non-sysmodules to read *or* write CAL0. */
|
|
||||||
fsStorageClose(&bis_storage);
|
|
||||||
return ResultFsPermissionDenied;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (is_sysmodule || has_bis_write_flag) {
|
|
||||||
/* Sysmodules should still be allowed to read and write. */
|
|
||||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ProxyStorage(bis_storage)));
|
|
||||||
} else if (Utils::IsHblTid(static_cast<u64>(this->program_id)) &&
|
|
||||||
((FsBisStorageId_BootConfigAndPackage2NormalMain <= bis_partition_id && bis_partition_id <= FsBisStorageId_BootConfigAndPackage2RepairSub) ||
|
|
||||||
bis_partition_id == FsBisStorageId_Boot1)) {
|
|
||||||
/* Allow HBL to write to boot1 (safe firm) + package2. */
|
|
||||||
/* This is needed to not break compatibility with ChoiDujourNX, which does not check for write access before beginning an update. */
|
|
||||||
/* TODO: get fixed so that this can be turned off without causing bricks :/ */
|
|
||||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ProxyStorage(bis_storage)));
|
|
||||||
} else {
|
|
||||||
/* Non-sysmodules should be allowed to read. */
|
|
||||||
out_storage.SetValue(std::make_shared<IStorageInterface>(new ReadOnlyStorageAdapter(new ProxyStorage(bis_storage))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy domain id. */
|
|
||||||
if (out_storage.IsDomain()) {
|
|
||||||
out_storage.ChangeObjectId(bis_storage.s.object_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add redirection for RomFS to the SD card. */
|
|
||||||
Result FsMitmService::OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out_storage) {
|
|
||||||
if (!this->should_override_contents) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we don't have anything to modify, there's no sense in maintaining a copy of the metadata tables. */
|
|
||||||
if (!Utils::HasSdRomfsContent(static_cast<u64>(this->program_id))) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to get from the cache. */
|
|
||||||
{
|
|
||||||
std::shared_ptr<IStorageInterface> cached_storage = nullptr;
|
|
||||||
bool has_cache = StorageCacheGetEntry(this->program_id, &cached_storage);
|
|
||||||
|
|
||||||
if (has_cache) {
|
|
||||||
if (out_storage.IsDomain()) {
|
|
||||||
/* TODO: Don't leak object id? */
|
|
||||||
FsStorage s = {0};
|
|
||||||
R_TRY(fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &s));
|
|
||||||
out_storage.ChangeObjectId(s.s.object_id);
|
|
||||||
}
|
|
||||||
out_storage.SetValue(std::move(cached_storage));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to open process romfs. */
|
|
||||||
FsStorage data_storage;
|
|
||||||
R_TRY(fsOpenDataStorageByCurrentProcessFwd(this->forward_service.get(), &data_storage));
|
|
||||||
|
|
||||||
/* Make new layered romfs, cacheing to storage. */
|
|
||||||
{
|
|
||||||
std::shared_ptr<IStorageInterface> storage_to_cache = nullptr;
|
|
||||||
/* TODO: Is there a sensible path that ends in ".romfs" we can use?" */
|
|
||||||
FsFile data_file;
|
|
||||||
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(static_cast<u64>(this->program_id), "romfs.bin", FS_OPEN_READ, &data_file))) {
|
|
||||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), std::make_shared<ReadOnlyStorageAdapter>(new FileStorage(new ProxyFile(data_file))), static_cast<u64>(this->program_id)));
|
|
||||||
} else {
|
|
||||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), nullptr, static_cast<u64>(this->program_id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageCacheSetEntry(this->program_id, &storage_to_cache);
|
|
||||||
|
|
||||||
out_storage.SetValue(std::move(storage_to_cache));
|
|
||||||
if (out_storage.IsDomain()) {
|
|
||||||
out_storage.ChangeObjectId(data_storage.s.object_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Add redirection for System Data Archives to the SD card. */
|
|
||||||
Result FsMitmService::OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out_storage, u64 data_id, u8 sid) {
|
|
||||||
if (!this->should_override_contents) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we don't have anything to modify, there's no sense in maintaining a copy of the metadata tables. */
|
|
||||||
if (!Utils::HasSdRomfsContent(data_id)) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
FsStorageId storage_id = static_cast<FsStorageId>(sid);
|
|
||||||
|
|
||||||
/* Try to get from the cache. */
|
|
||||||
{
|
|
||||||
std::shared_ptr<IStorageInterface> cached_storage = nullptr;
|
|
||||||
bool has_cache = StorageCacheGetEntry(ams::ncm::ProgramId{data_id}, &cached_storage);
|
|
||||||
|
|
||||||
if (has_cache) {
|
|
||||||
if (out_storage.IsDomain()) {
|
|
||||||
/* TODO: Don't leak object id? */
|
|
||||||
FsStorage s = {0};
|
|
||||||
R_TRY(fsOpenDataStorageByDataIdFwd(this->forward_service.get(), storage_id, data_id, &s));
|
|
||||||
out_storage.ChangeObjectId(s.s.object_id);
|
|
||||||
}
|
|
||||||
out_storage.SetValue(std::move(cached_storage));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to open data storage. */
|
|
||||||
FsStorage data_storage;
|
|
||||||
R_TRY(fsOpenDataStorageByDataIdFwd(this->forward_service.get(), storage_id, data_id, &data_storage));
|
|
||||||
|
|
||||||
/* Make new layered romfs, cacheing to storage. */
|
|
||||||
{
|
|
||||||
std::shared_ptr<IStorageInterface> storage_to_cache = nullptr;
|
|
||||||
/* TODO: Is there a sensible path that ends in ".romfs" we can use?" */
|
|
||||||
FsFile data_file;
|
|
||||||
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(data_id, "romfs.bin", FS_OPEN_READ, &data_file))) {
|
|
||||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), std::make_shared<ReadOnlyStorageAdapter>(new FileStorage(new ProxyFile(data_file))), data_id));
|
|
||||||
} else {
|
|
||||||
storage_to_cache = std::make_shared<IStorageInterface>(new LayeredRomFS(std::make_shared<ReadOnlyStorageAdapter>(new ProxyStorage(data_storage)), nullptr, data_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageCacheSetEntry(ams::ncm::ProgramId{data_id}, &storage_to_cache);
|
|
||||||
|
|
||||||
out_storage.SetValue(std::move(storage_to_cache));
|
|
||||||
if (out_storage.IsDomain()) {
|
|
||||||
out_storage.ChangeObjectId(data_storage.s.object_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "fs_istorage.hpp"
|
|
||||||
#include "fs_ifilesystem.hpp"
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
class FsMitmService : public IMitmServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
OpenFileSystemDeprecated = 0,
|
|
||||||
|
|
||||||
SetCurrentProcess = 1,
|
|
||||||
OpenFileSystemWithPatch = 7,
|
|
||||||
OpenFileSystemWithId = 8,
|
|
||||||
|
|
||||||
OpenSdCardFileSystem = 18,
|
|
||||||
|
|
||||||
OpenSaveDataFileSystem = 51,
|
|
||||||
|
|
||||||
OpenBisStorage = 12,
|
|
||||||
OpenDataStorageByCurrentProcess = 200,
|
|
||||||
OpenDataStorageByDataId = 202,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
static constexpr const char *AtmosphereHblWebContentDir = "/atmosphere/hbl_html";
|
|
||||||
private:
|
|
||||||
bool has_initialized = false;
|
|
||||||
bool should_override_contents;
|
|
||||||
public:
|
|
||||||
FsMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::ProgramId tid) : IMitmServiceObject(s, pid, tid) {
|
|
||||||
if (Utils::HasSdDisableMitMFlag(static_cast<u64>(this->program_id))) {
|
|
||||||
this->should_override_contents = false;
|
|
||||||
} else {
|
|
||||||
this->should_override_contents = (this->program_id >= ams::ncm::ProgramId::ApplicationStart || Utils::HasSdMitMFlag(static_cast<u64>(this->program_id))) && Utils::HasOverrideButton(static_cast<u64>(this->program_id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ShouldMitm(u64 pid, ams::ncm::ProgramId tid) {
|
|
||||||
/* Don't Mitm KIPs */
|
|
||||||
if (pid < 0x50) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::atomic_bool has_launched_qlaunch = false;
|
|
||||||
|
|
||||||
/* TODO: intercepting everything seems to cause issues with sleep mode, for some reason. */
|
|
||||||
/* Figure out why, and address it. */
|
|
||||||
if (tid == ams::ncm::ProgramId::AppletQlaunch || tid == ams::ncm::ProgramId::AppletMaintenanceMenu) {
|
|
||||||
has_launched_qlaunch = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return has_launched_qlaunch || tid == ams::ncm::ProgramId::Ns || tid >= ams::ncm::ProgramId::ApplicationStart || Utils::HasSdMitMFlag(static_cast<u64>(tid));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
|
||||||
private:
|
|
||||||
Result OpenHblWebContentFileSystem(Out<std::shared_ptr<IFileSystemInterface>> &out);
|
|
||||||
protected:
|
|
||||||
/* Overridden commands. */
|
|
||||||
Result OpenFileSystemWithPatch(Out<std::shared_ptr<IFileSystemInterface>> out, u64 program_id, u32 filesystem_type);
|
|
||||||
Result OpenFileSystemWithId(Out<std::shared_ptr<IFileSystemInterface>> out, InPointer<char> path, u64 program_id, u32 filesystem_type);
|
|
||||||
Result OpenSdCardFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out);
|
|
||||||
Result OpenSaveDataFileSystem(Out<std::shared_ptr<IFileSystemInterface>> out, u8 space_id, FsSave save_struct);
|
|
||||||
Result OpenBisStorage(Out<std::shared_ptr<IStorageInterface>> out, u32 bis_partition_id);
|
|
||||||
Result OpenDataStorageByCurrentProcess(Out<std::shared_ptr<IStorageInterface>> out);
|
|
||||||
Result OpenDataStorageByDataId(Out<std::shared_ptr<IStorageInterface>> out, u64 data_id, u8 storage_id);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
/* TODO MAKE_SERVICE_COMMAND_META(FsMitmService, OpenFileSystemDeprecated), */
|
|
||||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenFileSystemWithPatch, FirmwareVersion_200),
|
|
||||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenFileSystemWithId, FirmwareVersion_200),
|
|
||||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenSdCardFileSystem),
|
|
||||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenSaveDataFileSystem),
|
|
||||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenBisStorage),
|
|
||||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenDataStorageByCurrentProcess),
|
|
||||||
MAKE_SERVICE_COMMAND_META(FsMitmService, OpenDataStorageByDataId),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "hid_shim.h"
|
|
||||||
#include "hid_mitm_service.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
std::unordered_map<u64, u64> g_hbl_map;
|
|
||||||
|
|
||||||
bool ShouldSetSystemExt(u64 process_id) {
|
|
||||||
return g_hbl_map.find(process_id) != g_hbl_map.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddSession(u64 process_id) {
|
|
||||||
if (g_hbl_map.find(process_id) != g_hbl_map.end()) {
|
|
||||||
g_hbl_map[process_id]++;
|
|
||||||
} else {
|
|
||||||
g_hbl_map[process_id] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RemoveSession(u64 process_id) {
|
|
||||||
if ((--g_hbl_map[process_id]) == 0) {
|
|
||||||
g_hbl_map.erase(process_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
HidMitmService::~HidMitmService() {
|
|
||||||
if (this->should_set_system_ext) {
|
|
||||||
RemoveSession(this->process_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HidMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
|
||||||
/* Nothing to do here */
|
|
||||||
}
|
|
||||||
|
|
||||||
Result HidMitmService::SetSupportedNpadStyleSet(u64 applet_resource_user_id, u32 style_set, PidDescriptor pid_desc) {
|
|
||||||
if (!this->should_set_system_ext && (style_set & (TYPE_SYSTEM | TYPE_SYSTEM_EXT))) {
|
|
||||||
/* Guaranteed: hbmenu 3.1.1 will cause this to be true. */
|
|
||||||
/* This prevents setting this for non-HBL. */
|
|
||||||
this->should_set_system_ext = true;
|
|
||||||
AddSession(this->process_id);
|
|
||||||
}
|
|
||||||
if (ShouldSetSystemExt(this->process_id)) {
|
|
||||||
style_set |= TYPE_SYSTEM | TYPE_SYSTEM_EXT;
|
|
||||||
}
|
|
||||||
return hidSetSupportedNpadStyleSetFwd(this->forward_service.get(), this->process_id, applet_resource_user_id, static_cast<HidControllerType>(style_set));
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
class HidMitmService : public IMitmServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
SetSupportedNpadStyleSet = 100,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
bool should_set_system_ext = false;
|
|
||||||
public:
|
|
||||||
HidMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::ProgramId tid) : IMitmServiceObject(s, pid, tid) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
~HidMitmService();
|
|
||||||
|
|
||||||
static bool ShouldMitm(u64 pid, ams::ncm::ProgramId tid) {
|
|
||||||
/* TODO: Consider removing in Atmosphere 0.10.0/1.0.0. */
|
|
||||||
/* We will mitm:
|
|
||||||
* - hbl, to help homebrew not need to be recompiled.
|
|
||||||
*/
|
|
||||||
return Utils::IsHblTid(static_cast<u64>(tid));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* Overridden commands. */
|
|
||||||
Result SetSupportedNpadStyleSet(u64 applet_resource_user_id, u32 style_set, PidDescriptor pid_desc);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(HidMitmService, SetSupportedNpadStyleSet),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <switch.h>
|
|
||||||
#include "hid_shim.h"
|
|
||||||
|
|
||||||
/* Command forwarders. */
|
|
||||||
Result hidSetSupportedNpadStyleSetFwd(Service* s, u64 process_id, u64 aruid, HidControllerType type) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u32 type;
|
|
||||||
u64 AppletResourceUserId;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
ipcSendPid(&c);
|
|
||||||
|
|
||||||
raw = ipcPrepareHeader(&c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 100;
|
|
||||||
raw->type = type;
|
|
||||||
raw->AppletResourceUserId = aruid;
|
|
||||||
|
|
||||||
/* Override Process ID. */
|
|
||||||
{
|
|
||||||
u32 *cmd_buf = (u32 *)armGetTls();
|
|
||||||
cmd_buf[3] = (u32)(process_id >> 0);
|
|
||||||
cmd_buf[4] = (u32)((process_id >> 32) & 0xFFFF) | 0xFFFE0000ul;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
ipcParse(&r);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
/**
|
|
||||||
* @file hid_shim.h
|
|
||||||
* @brief Human Interface Devices Services (hid) IPC wrapper.
|
|
||||||
* @author SciresM
|
|
||||||
* @copyright libnx Authors
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Command forwarders. */
|
|
||||||
Result hidSetSupportedNpadStyleSetFwd(Service* s, u64 process_id, u64 aruid, HidControllerType type);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "hidmitm_main.hpp"
|
|
||||||
#include "hid_mitm_service.hpp"
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
struct HidMitmManagerOptions {
|
|
||||||
static const size_t PointerBufferSize = 0x200;
|
|
||||||
static const size_t MaxDomains = 0x0;
|
|
||||||
static const size_t MaxDomainObjects = 0x0;
|
|
||||||
};
|
|
||||||
using HidMitmManager = WaitableManager<HidMitmManagerOptions>;
|
|
||||||
|
|
||||||
void HidMitmMain(void *arg) {
|
|
||||||
/* This is only necessary on 9.x+ */
|
|
||||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_900) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure we can talk to HID. */
|
|
||||||
Utils::WaitHidAvailable();
|
|
||||||
|
|
||||||
/* Create server manager */
|
|
||||||
static auto s_server_manager = HidMitmManager(1);
|
|
||||||
|
|
||||||
/* Create hid mitm. */
|
|
||||||
/* Note: official HID passes 100 as sessions, despite this being > 0x40. */
|
|
||||||
AddMitmServerToManager<HidMitmService>(&s_server_manager, "hid", 100);
|
|
||||||
|
|
||||||
/* Loop forever, servicing our services. */
|
|
||||||
s_server_manager.Process();
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
constexpr u32 HidMitmPriority = 47;
|
|
||||||
constexpr u32 HidMitmStackSize = 0x8000;
|
|
||||||
|
|
||||||
void HidMitmMain(void *arg);
|
|
|
@ -1,269 +0,0 @@
|
||||||
/* inih -- simple .INI file parser
|
|
||||||
|
|
||||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
|
||||||
home page for more info:
|
|
||||||
|
|
||||||
https://github.com/benhoyt/inih
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
|
||||||
#define _CRT_SECURE_NO_WARNINGS
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "ini.h"
|
|
||||||
|
|
||||||
#if !INI_USE_STACK
|
|
||||||
#include <stdlib.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MAX_SECTION 72
|
|
||||||
#define MAX_NAME 72
|
|
||||||
|
|
||||||
/* Used by ini_parse_string() to keep track of string parsing state. */
|
|
||||||
typedef struct {
|
|
||||||
const char* ptr;
|
|
||||||
size_t num_left;
|
|
||||||
} ini_parse_string_ctx;
|
|
||||||
|
|
||||||
/* Strip whitespace chars off end of given string, in place. Return s. */
|
|
||||||
static char* rstrip(char* s)
|
|
||||||
{
|
|
||||||
char* p = s + strlen(s);
|
|
||||||
while (p > s && isspace((unsigned char)(*--p)))
|
|
||||||
*p = '\0';
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return pointer to first non-whitespace char in given string. */
|
|
||||||
static char* lskip(const char* s)
|
|
||||||
{
|
|
||||||
while (*s && isspace((unsigned char)(*s)))
|
|
||||||
s++;
|
|
||||||
return (char*)s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return pointer to first char (of chars) or inline comment in given string,
|
|
||||||
or pointer to null at end of string if neither found. Inline comment must
|
|
||||||
be prefixed by a whitespace character to register as a comment. */
|
|
||||||
static char* find_chars_or_comment(const char* s, const char* chars)
|
|
||||||
{
|
|
||||||
#if INI_ALLOW_INLINE_COMMENTS
|
|
||||||
int was_space = 0;
|
|
||||||
while (*s && (!chars || !strchr(chars, *s)) &&
|
|
||||||
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
|
||||||
was_space = isspace((unsigned char)(*s));
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
while (*s && (!chars || !strchr(chars, *s))) {
|
|
||||||
s++;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return (char*)s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
|
||||||
static char* strncpy0(char* dest, const char* src, size_t size)
|
|
||||||
{
|
|
||||||
strncpy(dest, src, size - 1);
|
|
||||||
dest[size - 1] = '\0';
|
|
||||||
return dest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See documentation in header file. */
|
|
||||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
|
||||||
void* user)
|
|
||||||
{
|
|
||||||
/* Uses a fair bit of stack (use heap instead if you need to) */
|
|
||||||
#if INI_USE_STACK
|
|
||||||
char line[INI_MAX_LINE];
|
|
||||||
int max_line = INI_MAX_LINE;
|
|
||||||
#else
|
|
||||||
char* line;
|
|
||||||
int max_line = INI_INITIAL_ALLOC;
|
|
||||||
#endif
|
|
||||||
#if INI_ALLOW_REALLOC
|
|
||||||
char* new_line;
|
|
||||||
int offset;
|
|
||||||
#endif
|
|
||||||
char section[MAX_SECTION] = "";
|
|
||||||
char prev_name[MAX_NAME] = "";
|
|
||||||
|
|
||||||
char* start;
|
|
||||||
char* end;
|
|
||||||
char* name;
|
|
||||||
char* value;
|
|
||||||
int lineno = 0;
|
|
||||||
int error = 0;
|
|
||||||
|
|
||||||
#if !INI_USE_STACK
|
|
||||||
line = (char*)malloc(INI_INITIAL_ALLOC);
|
|
||||||
if (!line) {
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if INI_HANDLER_LINENO
|
|
||||||
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
|
|
||||||
#else
|
|
||||||
#define HANDLER(u, s, n, v) handler(u, s, n, v)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Scan through stream line by line */
|
|
||||||
while (reader(line, max_line, stream) != NULL) {
|
|
||||||
#if INI_ALLOW_REALLOC
|
|
||||||
offset = strlen(line);
|
|
||||||
while (offset == max_line - 1 && line[offset - 1] != '\n') {
|
|
||||||
max_line *= 2;
|
|
||||||
if (max_line > INI_MAX_LINE)
|
|
||||||
max_line = INI_MAX_LINE;
|
|
||||||
new_line = realloc(line, max_line);
|
|
||||||
if (!new_line) {
|
|
||||||
free(line);
|
|
||||||
return -2;
|
|
||||||
}
|
|
||||||
line = new_line;
|
|
||||||
if (reader(line + offset, max_line - offset, stream) == NULL)
|
|
||||||
break;
|
|
||||||
if (max_line >= INI_MAX_LINE)
|
|
||||||
break;
|
|
||||||
offset += strlen(line + offset);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
lineno++;
|
|
||||||
|
|
||||||
start = line;
|
|
||||||
#if INI_ALLOW_BOM
|
|
||||||
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
|
||||||
(unsigned char)start[1] == 0xBB &&
|
|
||||||
(unsigned char)start[2] == 0xBF) {
|
|
||||||
start += 3;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
start = lskip(rstrip(start));
|
|
||||||
|
|
||||||
if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
|
|
||||||
/* Start-of-line comment */
|
|
||||||
}
|
|
||||||
#if INI_ALLOW_MULTILINE
|
|
||||||
else if (*prev_name && *start && start > line) {
|
|
||||||
/* Non-blank line with leading whitespace, treat as continuation
|
|
||||||
of previous name's value (as per Python configparser). */
|
|
||||||
if (!HANDLER(user, section, prev_name, start) && !error)
|
|
||||||
error = lineno;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
else if (*start == '[') {
|
|
||||||
/* A "[section]" line */
|
|
||||||
end = find_chars_or_comment(start + 1, "]");
|
|
||||||
if (*end == ']') {
|
|
||||||
*end = '\0';
|
|
||||||
strncpy0(section, start + 1, sizeof(section));
|
|
||||||
*prev_name = '\0';
|
|
||||||
}
|
|
||||||
else if (!error) {
|
|
||||||
/* No ']' found on section line */
|
|
||||||
error = lineno;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (*start) {
|
|
||||||
/* Not a comment, must be a name[=:]value pair */
|
|
||||||
end = find_chars_or_comment(start, "=:");
|
|
||||||
if (*end == '=' || *end == ':') {
|
|
||||||
*end = '\0';
|
|
||||||
name = rstrip(start);
|
|
||||||
value = end + 1;
|
|
||||||
#if INI_ALLOW_INLINE_COMMENTS
|
|
||||||
end = find_chars_or_comment(value, NULL);
|
|
||||||
if (*end)
|
|
||||||
*end = '\0';
|
|
||||||
#endif
|
|
||||||
value = lskip(value);
|
|
||||||
rstrip(value);
|
|
||||||
|
|
||||||
/* Valid name[=:]value pair found, call handler */
|
|
||||||
strncpy0(prev_name, name, sizeof(prev_name));
|
|
||||||
if (!HANDLER(user, section, name, value) && !error)
|
|
||||||
error = lineno;
|
|
||||||
}
|
|
||||||
else if (!error) {
|
|
||||||
/* No '=' or ':' found on name[=:]value line */
|
|
||||||
error = lineno;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if INI_STOP_ON_FIRST_ERROR
|
|
||||||
if (error)
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !INI_USE_STACK
|
|
||||||
free(line);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See documentation in header file. */
|
|
||||||
int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
|
||||||
{
|
|
||||||
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See documentation in header file. */
|
|
||||||
int ini_parse(const char* filename, ini_handler handler, void* user)
|
|
||||||
{
|
|
||||||
FILE* file;
|
|
||||||
int error;
|
|
||||||
|
|
||||||
file = fopen(filename, "r");
|
|
||||||
if (!file)
|
|
||||||
return -1;
|
|
||||||
error = ini_parse_file(file, handler, user);
|
|
||||||
fclose(file);
|
|
||||||
return error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* An ini_reader function to read the next line from a string buffer. This
|
|
||||||
is the fgets() equivalent used by ini_parse_string(). */
|
|
||||||
static char* ini_reader_string(char* str, int num, void* stream) {
|
|
||||||
ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
|
|
||||||
const char* ctx_ptr = ctx->ptr;
|
|
||||||
size_t ctx_num_left = ctx->num_left;
|
|
||||||
char* strp = str;
|
|
||||||
char c;
|
|
||||||
|
|
||||||
if (ctx_num_left == 0 || num < 2)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
while (num > 1 && ctx_num_left != 0) {
|
|
||||||
c = *ctx_ptr++;
|
|
||||||
ctx_num_left--;
|
|
||||||
*strp++ = c;
|
|
||||||
if (c == '\n')
|
|
||||||
break;
|
|
||||||
num--;
|
|
||||||
}
|
|
||||||
|
|
||||||
*strp = '\0';
|
|
||||||
ctx->ptr = ctx_ptr;
|
|
||||||
ctx->num_left = ctx_num_left;
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See documentation in header file. */
|
|
||||||
int ini_parse_string(const char* string, ini_handler handler, void* user) {
|
|
||||||
ini_parse_string_ctx ctx;
|
|
||||||
|
|
||||||
ctx.ptr = string;
|
|
||||||
ctx.num_left = strlen(string);
|
|
||||||
return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
|
|
||||||
user);
|
|
||||||
}
|
|
|
@ -1,130 +0,0 @@
|
||||||
/* inih -- simple .INI file parser
|
|
||||||
|
|
||||||
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
|
||||||
home page for more info:
|
|
||||||
|
|
||||||
https://github.com/benhoyt/inih
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __INI_H__
|
|
||||||
#define __INI_H__
|
|
||||||
|
|
||||||
/* Make this header file easier to include in C++ code */
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
/* Nonzero if ini_handler callback should accept lineno parameter. */
|
|
||||||
#ifndef INI_HANDLER_LINENO
|
|
||||||
#define INI_HANDLER_LINENO 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Typedef for prototype of handler function. */
|
|
||||||
#if INI_HANDLER_LINENO
|
|
||||||
typedef int (*ini_handler)(void* user, const char* section,
|
|
||||||
const char* name, const char* value,
|
|
||||||
int lineno);
|
|
||||||
#else
|
|
||||||
typedef int (*ini_handler)(void* user, const char* section,
|
|
||||||
const char* name, const char* value);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Typedef for prototype of fgets-style reader function. */
|
|
||||||
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
|
||||||
|
|
||||||
/* Parse given INI-style file. May have [section]s, name=value pairs
|
|
||||||
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
|
||||||
is "" if name=value pair parsed before any section heading. name:value
|
|
||||||
pairs are also supported as a concession to Python's configparser.
|
|
||||||
|
|
||||||
For each name=value pair parsed, call handler function with given user
|
|
||||||
pointer as well as section, name, and value (data only valid for duration
|
|
||||||
of handler call). Handler should return nonzero on success, zero on error.
|
|
||||||
|
|
||||||
Returns 0 on success, line number of first error on parse error (doesn't
|
|
||||||
stop on first error), -1 on file open error, or -2 on memory allocation
|
|
||||||
error (only when INI_USE_STACK is zero).
|
|
||||||
*/
|
|
||||||
int ini_parse(const char* filename, ini_handler handler, void* user);
|
|
||||||
|
|
||||||
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
|
||||||
close the file when it's finished -- the caller must do that. */
|
|
||||||
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
|
||||||
|
|
||||||
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
|
||||||
filename. Used for implementing custom or string-based I/O (see also
|
|
||||||
ini_parse_string). */
|
|
||||||
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
|
||||||
void* user);
|
|
||||||
|
|
||||||
/* Same as ini_parse(), but takes a zero-terminated string with the INI data
|
|
||||||
instead of a file. Useful for parsing INI data from a network socket or
|
|
||||||
already in memory. */
|
|
||||||
int ini_parse_string(const char* string, ini_handler handler, void* user);
|
|
||||||
|
|
||||||
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
|
||||||
configparser. If allowed, ini_parse() will call the handler with the same
|
|
||||||
name for each subsequent line parsed. */
|
|
||||||
#ifndef INI_ALLOW_MULTILINE
|
|
||||||
#define INI_ALLOW_MULTILINE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
|
||||||
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
|
||||||
#ifndef INI_ALLOW_BOM
|
|
||||||
#define INI_ALLOW_BOM 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Chars that begin a start-of-line comment. Per Python configparser, allow
|
|
||||||
both ; and # comments at the start of a line by default. */
|
|
||||||
#ifndef INI_START_COMMENT_PREFIXES
|
|
||||||
#define INI_START_COMMENT_PREFIXES ";#"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Nonzero to allow inline comments (with valid inline comment characters
|
|
||||||
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
|
||||||
Python 3.2+ configparser behaviour. */
|
|
||||||
#ifndef INI_ALLOW_INLINE_COMMENTS
|
|
||||||
#define INI_ALLOW_INLINE_COMMENTS 1
|
|
||||||
#endif
|
|
||||||
#ifndef INI_INLINE_COMMENT_PREFIXES
|
|
||||||
#define INI_INLINE_COMMENT_PREFIXES ";"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
|
|
||||||
#ifndef INI_USE_STACK
|
|
||||||
#define INI_USE_STACK 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Maximum line length for any line in INI file (stack or heap). Note that
|
|
||||||
this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
|
|
||||||
#ifndef INI_MAX_LINE
|
|
||||||
#define INI_MAX_LINE 200
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
|
|
||||||
fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
|
|
||||||
zero. */
|
|
||||||
#ifndef INI_ALLOW_REALLOC
|
|
||||||
#define INI_ALLOW_REALLOC 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
|
|
||||||
is zero. */
|
|
||||||
#ifndef INI_INITIAL_ALLOC
|
|
||||||
#define INI_INITIAL_ALLOC 200
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Stop parsing on first error (default is to keep parsing). */
|
|
||||||
#ifndef INI_STOP_ON_FIRST_ERROR
|
|
||||||
#define INI_STOP_ON_FIRST_ERROR 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* __INI_H__ */
|
|
|
@ -1,189 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <switch.h>
|
|
||||||
#include "ns_shim.h"
|
|
||||||
|
|
||||||
/* Command forwarders. */
|
|
||||||
Result nsGetDocumentInterfaceFwd(Service* s, NsDocumentInterface* out) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 7999;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
serviceCreateSubservice(&out->s, s, &r, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result nsamGetApplicationContentPathFwd(Service* s, void* out, size_t out_size, u64 app_id, FsStorageId storage_id) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
ipcAddRecvBuffer(&c, out, out_size, 0);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u8 storage_id;
|
|
||||||
u64 app_id;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 21;
|
|
||||||
raw->storage_id = storage_id;
|
|
||||||
raw->app_id = app_id;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result nsamResolveApplicationContentPathFwd(Service* s, u64 title_id, FsStorageId storage_id) {
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u8 storage_id;
|
|
||||||
u64 title_id;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 23;
|
|
||||||
raw->storage_id = storage_id;
|
|
||||||
raw->title_id = title_id;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result nsamGetRunningApplicationProgramIdFwd(Service* s, u64* out_tid, u64 app_id) {
|
|
||||||
if (hosversionBefore(6, 0, 0)) {
|
|
||||||
return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
u64 app_id;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 92;
|
|
||||||
raw->app_id = app_id;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
u64 title_id;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
if (out_tid) {
|
|
||||||
*out_tid = resp->title_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Web forwarders */
|
|
||||||
Result nswebGetApplicationContentPath(NsDocumentInterface* doc, void* out, size_t out_size, u64 app_id, FsStorageId storage_id) {
|
|
||||||
return nsamGetApplicationContentPathFwd(&doc->s, out, out_size, app_id, storage_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result nswebResolveApplicationContentPath(NsDocumentInterface* doc, u64 title_id, FsStorageId storage_id) {
|
|
||||||
return nsamResolveApplicationContentPathFwd(&doc->s, title_id, storage_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result nswebGetRunningApplicationProgramId(NsDocumentInterface* doc, u64* out_tid, u64 app_id) {
|
|
||||||
return nsamGetRunningApplicationProgramIdFwd(&doc->s, out_tid, app_id);
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
/**
|
|
||||||
* @file ns_shim.h
|
|
||||||
* @brief Nintendo Shell Services (ns) IPC wrapper.
|
|
||||||
* @author SciresM
|
|
||||||
* @copyright libnx Authors
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
Service s;
|
|
||||||
} NsDocumentInterface;
|
|
||||||
|
|
||||||
/* Command forwarders. */
|
|
||||||
Result nsGetDocumentInterfaceFwd(Service* s, NsDocumentInterface* out);
|
|
||||||
|
|
||||||
Result nsamGetApplicationContentPathFwd(Service* s, void* out, size_t out_size, u64 app_id, FsStorageId storage_id);
|
|
||||||
Result nsamResolveApplicationContentPathFwd(Service* s, u64 title_id, FsStorageId storage_id);
|
|
||||||
Result nsamGetRunningApplicationProgramIdFwd(Service* s, u64* out_tid, u64 app_id);
|
|
||||||
|
|
||||||
Result nswebGetApplicationContentPath(NsDocumentInterface* doc, void* out, size_t out_size, u64 app_id, FsStorageId storage_id);
|
|
||||||
Result nswebResolveApplicationContentPath(NsDocumentInterface* doc, u64 title_id, FsStorageId storage_id);
|
|
||||||
Result nswebGetRunningApplicationProgramId(NsDocumentInterface* doc, u64* out_tid, u64 app_id);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "nsmitm_am_service.hpp"
|
|
||||||
#include "ns_shim.h"
|
|
||||||
|
|
||||||
void NsAmMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
|
||||||
/* Nothing to do here */
|
|
||||||
}
|
|
||||||
|
|
||||||
Result NsAmMitmService::GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type) {
|
|
||||||
return nsamGetApplicationContentPathFwd(this->forward_service.get(), out_path.buffer, out_path.num_elements, app_id, static_cast<FsStorageId>(storage_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
Result NsAmMitmService::ResolveApplicationContentPath(u64 program_id, u8 storage_type) {
|
|
||||||
/* Always succeed for web applet asking about HBL. */
|
|
||||||
if (Utils::IsWebAppletTid(static_cast<u64>(this->program_id)) && Utils::IsHblTid(program_id)) {
|
|
||||||
nsamResolveApplicationContentPathFwd(this->forward_service.get(), program_id, static_cast<FsStorageId>(storage_type));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nsamResolveApplicationContentPathFwd(this->forward_service.get(), program_id, static_cast<FsStorageId>(storage_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
Result NsAmMitmService::GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id) {
|
|
||||||
return nsamGetRunningApplicationProgramIdFwd(this->forward_service.get(), out_tid.GetPointer(), app_id);
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
class NsAmMitmService : public IMitmServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
GetApplicationContentPath = 21,
|
|
||||||
ResolveApplicationContentPath = 23,
|
|
||||||
GetRunningApplicationProgramId = 92,
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
NsAmMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::ProgramId tid) : IMitmServiceObject(s, pid, tid) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ShouldMitm(u64 pid, ams::ncm::ProgramId tid) {
|
|
||||||
/* We will mitm:
|
|
||||||
* - web applets, to facilitate hbl web browser launching.
|
|
||||||
*/
|
|
||||||
return Utils::IsWebAppletTid(static_cast<u64>(tid));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* Overridden commands. */
|
|
||||||
Result GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type);
|
|
||||||
Result ResolveApplicationContentPath(u64 program_id, u8 storage_type);
|
|
||||||
Result GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(NsAmMitmService, GetApplicationContentPath),
|
|
||||||
MAKE_SERVICE_COMMAND_META(NsAmMitmService, ResolveApplicationContentPath),
|
|
||||||
MAKE_SERVICE_COMMAND_META(NsAmMitmService, GetRunningApplicationProgramId, FirmwareVersion_600),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
#include "nsmitm_main.hpp"
|
|
||||||
|
|
||||||
#include "nsmitm_am_service.hpp"
|
|
||||||
#include "nsmitm_web_service.hpp"
|
|
||||||
|
|
||||||
void NsMitmMain(void *arg) {
|
|
||||||
/* Wait for initialization to occur */
|
|
||||||
Utils::WaitSdInitialized();
|
|
||||||
|
|
||||||
/* Ensure we can talk to NS. */
|
|
||||||
DoWithSmSession([&]() {
|
|
||||||
R_ASSERT(nsInitialize());
|
|
||||||
});
|
|
||||||
nsExit();
|
|
||||||
|
|
||||||
/* Create server manager */
|
|
||||||
static auto s_server_manager = WaitableManager(1);
|
|
||||||
|
|
||||||
/* Create ns mitm. */
|
|
||||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_300) {
|
|
||||||
AddMitmServerToManager<NsAmMitmService>(&s_server_manager, "ns:am", 5);
|
|
||||||
} else {
|
|
||||||
AddMitmServerToManager<NsWebMitmService>(&s_server_manager, "ns:web", 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loop forever, servicing our services. */
|
|
||||||
s_server_manager.Process();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
constexpr u32 NsMitmPriority = 48;
|
|
||||||
constexpr u32 NsMitmStackSize = 0x4000;
|
|
||||||
|
|
||||||
void NsMitmMain(void *arg);
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include "nsmitm_web_service.hpp"
|
|
||||||
|
|
||||||
void NsWebMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
|
||||||
/* Nothing to do here */
|
|
||||||
}
|
|
||||||
|
|
||||||
Result NsWebMitmService::GetDocumentInterface(Out<std::shared_ptr<NsDocumentService>> out_intf) {
|
|
||||||
/* Open a document interface. */
|
|
||||||
NsDocumentInterface doc;
|
|
||||||
R_TRY(nsGetDocumentInterfaceFwd(this->forward_service.get(), &doc));
|
|
||||||
|
|
||||||
/* Set output interface. */
|
|
||||||
out_intf.SetValue(std::move(std::make_shared<NsDocumentService>(static_cast<u64>(this->program_id), doc)));
|
|
||||||
if (out_intf.IsDomain()) {
|
|
||||||
out_intf.ChangeObjectId(doc.s.object_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result NsDocumentService::GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type) {
|
|
||||||
return nswebGetApplicationContentPath(this->srv.get(), out_path.buffer, out_path.num_elements, app_id, static_cast<FsStorageId>(storage_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
Result NsDocumentService::ResolveApplicationContentPath(u64 program_id, u8 storage_type) {
|
|
||||||
/* Always succeed for web applet asking about HBL. */
|
|
||||||
if (Utils::IsWebAppletTid(static_cast<u64>(this->program_id)) && Utils::IsHblTid(program_id)) {
|
|
||||||
nswebResolveApplicationContentPath(this->srv.get(), program_id, static_cast<FsStorageId>(storage_type));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nswebResolveApplicationContentPath(this->srv.get(), program_id, static_cast<FsStorageId>(storage_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
Result NsDocumentService::GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id) {
|
|
||||||
return nswebGetRunningApplicationProgramId(this->srv.get(), out_tid.GetPointer(), app_id);
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
#include "ns_shim.h"
|
|
||||||
|
|
||||||
class NsDocumentService : public IServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
GetApplicationContentPath = 21,
|
|
||||||
ResolveApplicationContentPath = 23,
|
|
||||||
GetRunningApplicationProgramId = 92,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
u64 program_id;
|
|
||||||
std::unique_ptr<NsDocumentInterface> srv;
|
|
||||||
public:
|
|
||||||
NsDocumentService(u64 t, NsDocumentInterface *s) : program_id(t), srv(s) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
NsDocumentService(u64 t, std::unique_ptr<NsDocumentInterface> s) : program_id(t), srv(std::move(s)) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
NsDocumentService(u64 t, NsDocumentInterface s) : program_id(t) {
|
|
||||||
srv = std::make_unique<NsDocumentInterface>(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~NsDocumentService() {
|
|
||||||
serviceClose(&srv->s);
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
/* Actual command API. */
|
|
||||||
Result GetApplicationContentPath(OutBuffer<u8> out_path, u64 app_id, u8 storage_type);
|
|
||||||
Result ResolveApplicationContentPath(u64 program_id, u8 storage_type);
|
|
||||||
Result GetRunningApplicationProgramId(Out<u64> out_tid, u64 app_id);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(NsDocumentService, GetApplicationContentPath),
|
|
||||||
MAKE_SERVICE_COMMAND_META(NsDocumentService, ResolveApplicationContentPath),
|
|
||||||
MAKE_SERVICE_COMMAND_META(NsDocumentService, GetRunningApplicationProgramId, FirmwareVersion_600),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
class NsWebMitmService : public IMitmServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
GetDocumentInterface = 7999,
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
NsWebMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::ProgramId tid) : IMitmServiceObject(s, pid, tid) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ShouldMitm(u64 pid, ams::ncm::ProgramId tid) {
|
|
||||||
/* We will mitm:
|
|
||||||
* - web applets, to facilitate hbl web browser launching.
|
|
||||||
*/
|
|
||||||
return Utils::IsWebAppletTid(static_cast<u64>(tid));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* Overridden commands. */
|
|
||||||
Result GetDocumentInterface(Out<std::shared_ptr<NsDocumentService>> out_intf);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(NsWebMitmService, GetDocumentInterface, FirmwareVersion_300),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <switch.h>
|
|
||||||
#include "set_mitm_service.hpp"
|
|
||||||
|
|
||||||
void SetMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
|
||||||
/* No commands need postprocessing. */
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SetMitmService::IsValidLanguageCode(u64 lang_code) {
|
|
||||||
static constexpr u64 s_valid_language_codes[] = {
|
|
||||||
LanguageCode_Japanese,
|
|
||||||
LanguageCode_AmericanEnglish,
|
|
||||||
LanguageCode_French,
|
|
||||||
LanguageCode_German,
|
|
||||||
LanguageCode_Italian,
|
|
||||||
LanguageCode_Spanish,
|
|
||||||
LanguageCode_Chinese,
|
|
||||||
LanguageCode_Korean,
|
|
||||||
LanguageCode_Dutch,
|
|
||||||
LanguageCode_Portuguese,
|
|
||||||
LanguageCode_Russian,
|
|
||||||
LanguageCode_Taiwanese,
|
|
||||||
LanguageCode_BritishEnglish,
|
|
||||||
LanguageCode_CanadianFrench,
|
|
||||||
LanguageCode_LatinAmericanSpanish,
|
|
||||||
LanguageCode_SimplifiedChinese,
|
|
||||||
LanguageCode_TraditionalChinese,
|
|
||||||
};
|
|
||||||
size_t num_language_codes = ams::util::size(s_valid_language_codes);
|
|
||||||
if (GetRuntimeFirmwareVersion() < FirmwareVersion_400) {
|
|
||||||
/* 4.0.0 added simplified and traditional chinese. */
|
|
||||||
num_language_codes -= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < num_language_codes; i++) {
|
|
||||||
if (lang_code == s_valid_language_codes[i]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SetMitmService::IsValidRegionCode(u32 region_code) {
|
|
||||||
return region_code < RegionCode_Max;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetMitmService::EnsureLocale() {
|
|
||||||
std::scoped_lock lk(this->lock);
|
|
||||||
|
|
||||||
if (!this->got_locale) {
|
|
||||||
std::memset(&this->locale, 0xCC, sizeof(this->locale));
|
|
||||||
if (this->program_id == ams::ncm::ProgramId::Ns) {
|
|
||||||
u64 app_pid = 0;
|
|
||||||
u64 app_tid = 0;
|
|
||||||
R_TRY(pmdmntGetApplicationPid(&app_pid));
|
|
||||||
R_TRY(pminfoGetProgramId(&app_tid, app_pid));
|
|
||||||
this->locale = Utils::GetTitleOverrideLocale(app_tid);
|
|
||||||
} else {
|
|
||||||
this->locale = Utils::GetTitleOverrideLocale(static_cast<u64>(this->program_id));
|
|
||||||
this->got_locale = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetMitmService::GetLanguageCode(Out<u64> out_lang_code) {
|
|
||||||
this->EnsureLocale();
|
|
||||||
|
|
||||||
if (!IsValidLanguageCode(this->locale.language_code)) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_lang_code.SetValue(this->locale.language_code);
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetMitmService::GetRegionCode(Out<u32> out_region_code) {
|
|
||||||
this->EnsureLocale();
|
|
||||||
|
|
||||||
if (!IsValidRegionCode(this->locale.region_code)) {
|
|
||||||
return ResultAtmosphereMitmShouldForwardToSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_region_code.SetValue(this->locale.region_code);
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
class SetMitmService : public IMitmServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
GetLanguageCode = 0,
|
|
||||||
GetRegionCode = 4,
|
|
||||||
};
|
|
||||||
private:
|
|
||||||
ams::os::Mutex lock;
|
|
||||||
OverrideLocale locale;
|
|
||||||
bool got_locale;
|
|
||||||
public:
|
|
||||||
SetMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::ProgramId tid) : IMitmServiceObject(s, pid, tid) {
|
|
||||||
this->got_locale = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ShouldMitm(u64 pid, ams::ncm::ProgramId tid) {
|
|
||||||
/* Mitm all applications. */
|
|
||||||
return tid == ams::ncm::ProgramId::Ns || ams::ncm::IsApplicationProgramId(tid);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
static bool IsValidLanguageCode(u64 lang_code);
|
|
||||||
static bool IsValidRegionCode(u32 region_code);
|
|
||||||
|
|
||||||
Result EnsureLocale();
|
|
||||||
protected:
|
|
||||||
/* Overridden commands. */
|
|
||||||
Result GetLanguageCode(Out<u64> out_lang_code);
|
|
||||||
Result GetRegionCode(Out<u32> out_region_code);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(SetMitmService, GetLanguageCode),
|
|
||||||
MAKE_SERVICE_COMMAND_META(SetMitmService, GetRegionCode),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "setmitm_main.hpp"
|
|
||||||
#include "setsys_mitm_service.hpp"
|
|
||||||
#include "setsys_settings_items.hpp"
|
|
||||||
#include "setsys_firmware_version.hpp"
|
|
||||||
|
|
||||||
#include "set_mitm_service.hpp"
|
|
||||||
|
|
||||||
#include "../utils.hpp"
|
|
||||||
|
|
||||||
struct SetSysManagerOptions {
|
|
||||||
static const size_t PointerBufferSize = 0x100;
|
|
||||||
static const size_t MaxDomains = 4;
|
|
||||||
static const size_t MaxDomainObjects = 0x100;
|
|
||||||
};
|
|
||||||
|
|
||||||
using SetMitmManager = WaitableManager<SetSysManagerOptions>;
|
|
||||||
|
|
||||||
void SetMitmMain(void *arg) {
|
|
||||||
/* Wait for SD to initialize. */
|
|
||||||
Utils::WaitSdInitialized();
|
|
||||||
|
|
||||||
/* Initialize version manager. */
|
|
||||||
VersionManager::Initialize();
|
|
||||||
|
|
||||||
/* Create server manager */
|
|
||||||
static auto s_server_manager = SetMitmManager(4);
|
|
||||||
|
|
||||||
/* Create set:sys mitm. */
|
|
||||||
AddMitmServerToManager<SetSysMitmService>(&s_server_manager, "set:sys", 60);
|
|
||||||
|
|
||||||
/* Create set mitm. */
|
|
||||||
AddMitmServerToManager<SetMitmService>(&s_server_manager, "set", 60);
|
|
||||||
|
|
||||||
/* Loop forever, servicing our services. */
|
|
||||||
s_server_manager.Process();
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <malloc.h>
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <atmosphere.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
constexpr u32 SetMitmPriority = 43;
|
|
||||||
constexpr u32 SetMitmStackSize = 0x8000;
|
|
||||||
|
|
||||||
void SetMitmMain(void *arg);
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#include "setsys_firmware_version.hpp"
|
|
||||||
|
|
||||||
static ams::os::Mutex g_version_mutex;
|
|
||||||
static bool g_got_version = false;
|
|
||||||
static SetSysFirmwareVersion g_ams_fw_version = {0};
|
|
||||||
static SetSysFirmwareVersion g_fw_version = {0};
|
|
||||||
|
|
||||||
void VersionManager::Initialize() {
|
|
||||||
std::scoped_lock lock(g_version_mutex);
|
|
||||||
|
|
||||||
if (g_got_version) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mount firmware version data archive. */
|
|
||||||
R_ASSERT(romfsMountFromDataArchive(static_cast<u64>(ams::ncm::ProgramId::ArchiveSystemVersion), FsStorageId_NandSystem, "sysver"));
|
|
||||||
{
|
|
||||||
ON_SCOPE_EXIT { romfsUnmount("sysver"); };
|
|
||||||
|
|
||||||
SetSysFirmwareVersion fw_ver;
|
|
||||||
|
|
||||||
/* Firmware version file must exist. */
|
|
||||||
FILE *f = fopen("sysver:/file", "rb");
|
|
||||||
AMS_ASSERT(f != NULL);
|
|
||||||
ON_SCOPE_EXIT { fclose(f); };
|
|
||||||
|
|
||||||
/* Must be possible to read firmware version from file. */
|
|
||||||
AMS_ASSERT(fread(&fw_ver, sizeof(fw_ver), 1, f) == 1);
|
|
||||||
|
|
||||||
g_fw_version = fw_ver;
|
|
||||||
g_ams_fw_version = fw_ver;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modify the output firmware version. */
|
|
||||||
{
|
|
||||||
u32 major, minor, micro;
|
|
||||||
GetAtmosphereApiVersion(&major, &minor, µ, nullptr, nullptr);
|
|
||||||
const char emummc_char = IsEmummc() ? 'E' : 'S';
|
|
||||||
{
|
|
||||||
char display_version[sizeof(g_ams_fw_version.display_version)] = {0};
|
|
||||||
std::snprintf(display_version, sizeof(display_version), "%s|AMS %u.%u.%u|%c", g_ams_fw_version.display_version, major, minor, micro, emummc_char);
|
|
||||||
std::memcpy(g_ams_fw_version.display_version, display_version, sizeof(g_ams_fw_version.display_version));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_got_version = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result VersionManager::GetFirmwareVersion(ams::ncm::ProgramId program_id, SetSysFirmwareVersion *out) {
|
|
||||||
VersionManager::Initialize();
|
|
||||||
|
|
||||||
/* Report atmosphere string to qlaunch, maintenance and nothing else. */
|
|
||||||
if (program_id == ams::ncm::ProgramId::AppletQlaunch || program_id == ams::ncm::ProgramId::AppletMaintenanceMenu) {
|
|
||||||
*out = g_ams_fw_version;
|
|
||||||
} else {
|
|
||||||
*out = g_fw_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
class VersionManager {
|
|
||||||
public:
|
|
||||||
static void Initialize();
|
|
||||||
static Result GetFirmwareVersion(ams::ncm::ProgramId program_id, SetSysFirmwareVersion *out);
|
|
||||||
};
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <switch.h>
|
|
||||||
#include "setsys_mitm_service.hpp"
|
|
||||||
#include "setsys_firmware_version.hpp"
|
|
||||||
#include "setsys_settings_items.hpp"
|
|
||||||
|
|
||||||
void SetSysMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
|
|
||||||
/* No commands need postprocessing. */
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetSysMitmService::GetFirmwareVersion(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out) {
|
|
||||||
/* Get firmware version from manager. */
|
|
||||||
R_TRY(VersionManager::GetFirmwareVersion(this->program_id, out.pointer));
|
|
||||||
|
|
||||||
/* GetFirmwareVersion sanitizes these fields. */
|
|
||||||
out.pointer->revision_major = 0;
|
|
||||||
out.pointer->revision_minor = 0;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetSysMitmService::GetFirmwareVersion2(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out) {
|
|
||||||
return VersionManager::GetFirmwareVersion(this->program_id, out.pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetSysMitmService::GetSettingsItemValueSize(Out<u64> out_size, InPointer<char> in_name, InPointer<char> in_key) {
|
|
||||||
char name[SET_MAX_NAME_SIZE] = {0};
|
|
||||||
char key[SET_MAX_NAME_SIZE] = {0};
|
|
||||||
|
|
||||||
/* Validate name and key. */
|
|
||||||
R_TRY(SettingsItemManager::ValidateName(in_name.pointer));
|
|
||||||
R_TRY(SettingsItemManager::ValidateKey(in_key.pointer));
|
|
||||||
|
|
||||||
if (in_name.num_elements < SET_MAX_NAME_SIZE) {
|
|
||||||
strncpy(name, in_name.pointer, in_name.num_elements);
|
|
||||||
} else {
|
|
||||||
strncpy(name, in_name.pointer, SET_MAX_NAME_SIZE-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_key.num_elements < SET_MAX_NAME_SIZE) {
|
|
||||||
strncpy(key, in_key.pointer, in_key.num_elements);
|
|
||||||
} else {
|
|
||||||
strncpy(key, in_key.pointer, SET_MAX_NAME_SIZE-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to get override setting, fall back to real setting. */
|
|
||||||
if (R_FAILED(SettingsItemManager::GetValueSize(name, key, out_size.GetPointer()))) {
|
|
||||||
R_TRY(setsysGetSettingsItemValueSize(name, key, out_size.GetPointer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SetSysMitmService::GetSettingsItemValue(Out<u64> out_size, OutBuffer<u8> out_value, InPointer<char> in_name, InPointer<char> in_key) {
|
|
||||||
char name[SET_MAX_NAME_SIZE] = {0};
|
|
||||||
char key[SET_MAX_NAME_SIZE] = {0};
|
|
||||||
|
|
||||||
/* Validate name and key. */
|
|
||||||
R_TRY(SettingsItemManager::ValidateName(in_name.pointer));
|
|
||||||
R_TRY(SettingsItemManager::ValidateKey(in_key.pointer));
|
|
||||||
|
|
||||||
if (out_value.buffer == nullptr) {
|
|
||||||
return ResultSettingsItemValueBufferNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_name.num_elements < SET_MAX_NAME_SIZE) {
|
|
||||||
strncpy(name, in_name.pointer, in_name.num_elements);
|
|
||||||
} else {
|
|
||||||
strncpy(name, in_name.pointer, SET_MAX_NAME_SIZE-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_key.num_elements < SET_MAX_NAME_SIZE) {
|
|
||||||
strncpy(key, in_key.pointer, in_key.num_elements);
|
|
||||||
} else {
|
|
||||||
strncpy(key, in_key.pointer, SET_MAX_NAME_SIZE-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Try to get override setting, fall back to real setting. */
|
|
||||||
if (R_FAILED(SettingsItemManager::GetValue(name, key, out_value.buffer, out_value.num_elements, out_size.GetPointer()))) {
|
|
||||||
R_TRY(setsysGetSettingsItemValueFwd(this->forward_service.get(), name, key, out_value.buffer, out_value.num_elements, out_size.GetPointer()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
#include "setsys_shim.h"
|
|
||||||
|
|
||||||
class SetSysMitmService : public IMitmServiceObject {
|
|
||||||
private:
|
|
||||||
enum class CommandId {
|
|
||||||
GetFirmwareVersion = 3,
|
|
||||||
GetFirmwareVersion2 = 4,
|
|
||||||
GetSettingsItemValueSize = 37,
|
|
||||||
GetSettingsItemValue = 38,
|
|
||||||
};
|
|
||||||
public:
|
|
||||||
SetSysMitmService(std::shared_ptr<Service> s, u64 pid, ams::ncm::ProgramId tid) : IMitmServiceObject(s, pid, tid) {
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool ShouldMitm(u64 pid, ams::ncm::ProgramId tid) {
|
|
||||||
/* Mitm everything. */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/* Overridden commands. */
|
|
||||||
Result GetFirmwareVersion(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out);
|
|
||||||
Result GetFirmwareVersion2(OutPointerWithServerSize<SetSysFirmwareVersion, 0x1> out);
|
|
||||||
Result GetSettingsItemValueSize(Out<u64> out_size, InPointer<char> name, InPointer<char> key);
|
|
||||||
Result GetSettingsItemValue(Out<u64> out_size, OutBuffer<u8> out_value, InPointer<char> name, InPointer<char> key);
|
|
||||||
public:
|
|
||||||
DEFINE_SERVICE_DISPATCH_TABLE {
|
|
||||||
MAKE_SERVICE_COMMAND_META(SetSysMitmService, GetFirmwareVersion),
|
|
||||||
MAKE_SERVICE_COMMAND_META(SetSysMitmService, GetFirmwareVersion2),
|
|
||||||
MAKE_SERVICE_COMMAND_META(SetSysMitmService, GetSettingsItemValueSize),
|
|
||||||
MAKE_SERVICE_COMMAND_META(SetSysMitmService, GetSettingsItemValue),
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,304 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <mutex>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <strings.h>
|
|
||||||
#include <ctype.h>
|
|
||||||
|
|
||||||
#include "setsys_settings_items.hpp"
|
|
||||||
#include "../utils.hpp"
|
|
||||||
#include "../ini.h"
|
|
||||||
|
|
||||||
struct SettingsItemValue {
|
|
||||||
size_t size;
|
|
||||||
u8 *data;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<std::string, SettingsItemValue> g_settings_items;
|
|
||||||
|
|
||||||
static bool g_threw_fatal = false;
|
|
||||||
static ams::os::Thread g_fatal_thread;
|
|
||||||
|
|
||||||
static void FatalThreadFunc(void *arg) {
|
|
||||||
svcSleepThread(5000000000ULL);
|
|
||||||
fatalSimple(static_cast<Result>(reinterpret_cast<uintptr_t>(arg)));
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsCorrectFormat(const char *str, size_t len) {
|
|
||||||
if (len > 0 && str[len - 1] == '.') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
|
||||||
const char c = *(str++);
|
|
||||||
|
|
||||||
if ('a' <= c && c <= 'z') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('0' <= c && c <= '9') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (c == '-' || c == '.' || c == '_') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SettingsItemManager::ValidateName(const char *name, size_t max_size) {
|
|
||||||
if (name == nullptr) {
|
|
||||||
return ResultSettingsItemNameNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t name_len = strnlen(name, std::min(max_size, MaxNameLength + 1));
|
|
||||||
if (name_len == 0) {
|
|
||||||
return ResultSettingsItemNameEmpty;
|
|
||||||
} else if (name_len > MaxNameLength) {
|
|
||||||
return ResultSettingsItemNameTooLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsCorrectFormat(name, name_len)) {
|
|
||||||
return ResultSettingsItemNameInvalidFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SettingsItemManager::ValidateName(const char *name) {
|
|
||||||
return ValidateName(name, MaxNameLength + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SettingsItemManager::ValidateKey(const char *key, size_t max_size) {
|
|
||||||
if (key == nullptr) {
|
|
||||||
return ResultSettingsItemKeyNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t key_len = strnlen(key, std::min(max_size, MaxKeyLength + 1));
|
|
||||||
if (key_len == 0) {
|
|
||||||
return ResultSettingsItemKeyEmpty;
|
|
||||||
} else if (key_len > MaxKeyLength) {
|
|
||||||
return ResultSettingsItemKeyTooLong;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsCorrectFormat(key, key_len)) {
|
|
||||||
return ResultSettingsItemKeyInvalidFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SettingsItemManager::ValidateKey(const char *key) {
|
|
||||||
return ValidateKey(key, MaxKeyLength + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsHexadecimal(const char *str) {
|
|
||||||
while (*str) {
|
|
||||||
if (isxdigit((unsigned char)*str)) {
|
|
||||||
str++;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char hextoi(char c) {
|
|
||||||
if ('a' <= c && c <= 'f') return c - 'a' + 0xA;
|
|
||||||
if ('A' <= c && c <= 'F') return c - 'A' + 0xA;
|
|
||||||
if ('0' <= c && c <= '9') return c - '0';
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result ParseValue(const char *name, const char *key, const char *val_tup) {
|
|
||||||
const char *delimiter = strchr(val_tup, '!');
|
|
||||||
const char *value_str = delimiter + 1;
|
|
||||||
const char *type = val_tup;
|
|
||||||
|
|
||||||
if (delimiter == NULL) {
|
|
||||||
return ResultSettingsItemValueInvalidFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (isspace((unsigned char)*type) && type != delimiter) {
|
|
||||||
type++;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t type_len = delimiter - type;
|
|
||||||
size_t value_len = strlen(value_str);
|
|
||||||
if (delimiter == NULL || value_len == 0 || type_len == 0) {
|
|
||||||
return ResultSettingsItemValueInvalidFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string kv = std::string(name).append("!").append(key);
|
|
||||||
SettingsItemValue value;
|
|
||||||
|
|
||||||
if (strncasecmp(type, "str", type_len) == 0 || strncasecmp(type, "string", type_len) == 0) {
|
|
||||||
/* String */
|
|
||||||
value.size = value_len + 1;
|
|
||||||
value.data = reinterpret_cast<u8 *>(strdup(value_str));
|
|
||||||
if (value.data == nullptr) {
|
|
||||||
return ResultSettingsItemValueAllocationFailed;
|
|
||||||
}
|
|
||||||
} else if (strncasecmp(type, "hex", type_len) == 0 || strncasecmp(type, "bytes", type_len) == 0) {
|
|
||||||
/* hex */
|
|
||||||
if (value_len % 2 || !IsHexadecimal(value_str)) {
|
|
||||||
return ResultSettingsItemValueInvalidFormat;
|
|
||||||
}
|
|
||||||
value.size = value_len / 2;
|
|
||||||
u8 *data = reinterpret_cast<u8 *>(malloc(value.size));
|
|
||||||
if (data == nullptr) {
|
|
||||||
return ResultSettingsItemValueAllocationFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(data, 0, value.size);
|
|
||||||
for (size_t i = 0; i < value_len; i++) {
|
|
||||||
data[i >> 1] |= hextoi(value_str[i]) << (4 * (i & 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
value.data = data;
|
|
||||||
} else if (strncasecmp(type, "u8", type_len) == 0) {
|
|
||||||
/* u8 */
|
|
||||||
value.size = sizeof(u8);
|
|
||||||
u8 *data = reinterpret_cast<u8 *>(malloc(value.size));
|
|
||||||
if (data == nullptr) {
|
|
||||||
return ResultSettingsItemValueAllocationFailed;
|
|
||||||
}
|
|
||||||
*data = (u8)(strtoul(value_str, nullptr, 0));
|
|
||||||
value.data = reinterpret_cast<u8 *>(data);
|
|
||||||
} else if (strncasecmp(type, "u16", type_len) == 0) {
|
|
||||||
/* u16 */
|
|
||||||
value.size = sizeof(u16);
|
|
||||||
u16 *data = reinterpret_cast<u16 *>(malloc(value.size));
|
|
||||||
if (data == nullptr) {
|
|
||||||
return ResultSettingsItemValueAllocationFailed;
|
|
||||||
}
|
|
||||||
*data = (u16)(strtoul(value_str, nullptr, 0));
|
|
||||||
value.data = reinterpret_cast<u8 *>(data);
|
|
||||||
} else if (strncasecmp(type, "u32", type_len) == 0) {
|
|
||||||
/* u32 */
|
|
||||||
value.size = sizeof(u32);
|
|
||||||
u32 *data = reinterpret_cast<u32 *>(malloc(value.size));
|
|
||||||
if (data == nullptr) {
|
|
||||||
return ResultSettingsItemValueAllocationFailed;
|
|
||||||
}
|
|
||||||
*data = (u32)(strtoul(value_str, nullptr, 0));
|
|
||||||
value.data = reinterpret_cast<u8 *>(data);
|
|
||||||
} else if (strncasecmp(type, "u64", type_len) == 0) {
|
|
||||||
/* u64 */
|
|
||||||
value.size = sizeof(u64);
|
|
||||||
u64 *data = reinterpret_cast<u64 *>(malloc(value.size));
|
|
||||||
if (data == nullptr) {
|
|
||||||
return ResultSettingsItemValueAllocationFailed;
|
|
||||||
}
|
|
||||||
*data = (u64)(strtoul(value_str, nullptr, 0));
|
|
||||||
value.data = reinterpret_cast<u8 *>(data);
|
|
||||||
} else {
|
|
||||||
return ResultSettingsItemValueInvalidFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_settings_items[kv] = value;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result ParseSettingsItemValue(const char *name, const char *key, const char *value) {
|
|
||||||
/* Validate name and key, then parse value. */
|
|
||||||
R_TRY(SettingsItemManager::ValidateName(name));
|
|
||||||
R_TRY(SettingsItemManager::ValidateKey(name));
|
|
||||||
R_TRY(ParseValue(name, key, value));
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
static int SettingsItemIniHandler(void *user, const char *name, const char *key, const char *value) {
|
|
||||||
Result *user_res = reinterpret_cast<Result *>(user);
|
|
||||||
|
|
||||||
/* Stop parsing after we fail to parse a value. */
|
|
||||||
if (R_FAILED(*user_res)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
*user_res = ParseSettingsItemValue(name, key, value);
|
|
||||||
return R_SUCCEEDED(*user_res) ? 1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Result LoadConfigurationImpl() {
|
|
||||||
/* Open file. */
|
|
||||||
FsFile config_file;
|
|
||||||
R_TRY(Utils::OpenSdFile("/atmosphere/system_settings.ini", FS_OPEN_READ, &config_file));
|
|
||||||
ON_SCOPE_EXIT {
|
|
||||||
fsFileClose(&config_file);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Allocate buffer. */
|
|
||||||
std::string config_buf(0xFFFF, '\0');
|
|
||||||
|
|
||||||
/* Read from file. */
|
|
||||||
size_t actual_size;
|
|
||||||
R_TRY(fsFileRead(&config_file, 0, config_buf.data(), config_buf.size(), FS_READOPTION_NONE, &actual_size));
|
|
||||||
|
|
||||||
/* Parse. */
|
|
||||||
Result parse_res = ResultSuccess;
|
|
||||||
ini_parse_string(config_buf.c_str(), SettingsItemIniHandler, &parse_res);
|
|
||||||
return parse_res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SettingsItemManager::LoadConfiguration() {
|
|
||||||
const Result load_res = LoadConfigurationImpl();
|
|
||||||
if (R_FAILED(load_res) && !g_threw_fatal) {
|
|
||||||
/* Report error if we encountered one. */
|
|
||||||
g_threw_fatal = true;
|
|
||||||
g_fatal_thread.Initialize(&FatalThreadFunc, reinterpret_cast<void *>(load_res), 0x1000, 49);
|
|
||||||
g_fatal_thread.Start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SettingsItemManager::GetValueSize(const char *name, const char *key, u64 *out_size) {
|
|
||||||
const std::string kv = std::string(name).append("!").append(key);
|
|
||||||
|
|
||||||
auto it = g_settings_items.find(kv);
|
|
||||||
if (it == g_settings_items.end()) {
|
|
||||||
return ResultSettingsItemNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
*out_size = it->second.size;
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result SettingsItemManager::GetValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size) {
|
|
||||||
const std::string kv = std::string(name).append("!").append(key);
|
|
||||||
|
|
||||||
auto it = g_settings_items.find(kv);
|
|
||||||
if (it == g_settings_items.end()) {
|
|
||||||
return ResultSettingsItemNotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t copy_size = it->second.size;
|
|
||||||
if (max_size < copy_size) {
|
|
||||||
copy_size = max_size;
|
|
||||||
}
|
|
||||||
*out_size = copy_size;
|
|
||||||
|
|
||||||
memcpy(out, it->second.data, copy_size);
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
class SettingsItemManager {
|
|
||||||
public:
|
|
||||||
static constexpr size_t MaxNameLength = 64;
|
|
||||||
static constexpr size_t MaxKeyLength = 64;
|
|
||||||
public:
|
|
||||||
static Result ValidateName(const char *name, size_t max_size);
|
|
||||||
static Result ValidateName(const char *name);
|
|
||||||
|
|
||||||
static Result ValidateKey(const char *key, size_t max_size);
|
|
||||||
static Result ValidateKey(const char *key);
|
|
||||||
|
|
||||||
static void LoadConfiguration();
|
|
||||||
static Result GetValueSize(const char *name, const char *key, u64 *out_size);
|
|
||||||
static Result GetValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size);
|
|
||||||
};
|
|
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <switch.h>
|
|
||||||
#include "setsys_shim.h"
|
|
||||||
|
|
||||||
/* Command forwarders. */
|
|
||||||
Result setsysGetSettingsItemValueFwd(Service *s, const char *name, const char *item_key, void *value_out, size_t value_out_size, u64 *size_out) {
|
|
||||||
char send_name[SET_MAX_NAME_SIZE];
|
|
||||||
char send_item_key[SET_MAX_NAME_SIZE];
|
|
||||||
|
|
||||||
memset(send_name, 0, SET_MAX_NAME_SIZE);
|
|
||||||
memset(send_item_key, 0, SET_MAX_NAME_SIZE);
|
|
||||||
strncpy(send_name, name, SET_MAX_NAME_SIZE-1);
|
|
||||||
strncpy(send_item_key, item_key, SET_MAX_NAME_SIZE-1);
|
|
||||||
|
|
||||||
IpcCommand c;
|
|
||||||
ipcInitialize(&c);
|
|
||||||
ipcAddSendStatic(&c, send_name, SET_MAX_NAME_SIZE, 0);
|
|
||||||
ipcAddSendStatic(&c, send_item_key, SET_MAX_NAME_SIZE, 0);
|
|
||||||
ipcAddRecvBuffer(&c, value_out, value_out_size, 0);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 cmd_id;
|
|
||||||
} *raw;
|
|
||||||
|
|
||||||
raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
|
|
||||||
|
|
||||||
raw->magic = SFCI_MAGIC;
|
|
||||||
raw->cmd_id = 38;
|
|
||||||
|
|
||||||
Result rc = serviceIpcDispatch(s);
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
IpcParsedCommand r;
|
|
||||||
struct {
|
|
||||||
u64 magic;
|
|
||||||
u64 result;
|
|
||||||
u64 size_out;
|
|
||||||
} *resp;
|
|
||||||
|
|
||||||
serviceIpcParse(s, &r, sizeof(*resp));
|
|
||||||
resp = r.Raw;
|
|
||||||
|
|
||||||
rc = resp->result;
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
|
||||||
*size_out = resp->size_out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
/**
|
|
||||||
* @file setsys_shim.h
|
|
||||||
* @brief System Settings Services (set:sys) IPC wrapper. To be merged into libnx, eventually.
|
|
||||||
* @author SciresM
|
|
||||||
* @copyright libnx Authors
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Command forwarders. */
|
|
||||||
Result setsysGetSettingsItemValueFwd(Service* s, const char *name, const char *item_key, void *value_out, size_t value_out_size, u64 *size_out);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1,801 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
#include <stratosphere/spl.hpp>
|
|
||||||
#include <atomic>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <strings.h>
|
|
||||||
|
|
||||||
#include "debug.hpp"
|
|
||||||
#include "utils.hpp"
|
|
||||||
#include "ini.h"
|
|
||||||
|
|
||||||
#include "set_mitm/setsys_settings_items.hpp"
|
|
||||||
#include "bpc_mitm/bpcmitm_reboot_manager.hpp"
|
|
||||||
|
|
||||||
static FsFileSystem g_sd_filesystem = {0};
|
|
||||||
|
|
||||||
/* Non-autoclear events for SD/HID init. */
|
|
||||||
static ams::os::Event g_sd_event(false), g_hid_event(false);
|
|
||||||
|
|
||||||
static std::vector<u64> g_mitm_flagged_tids;
|
|
||||||
static std::vector<u64> g_disable_mitm_flagged_tids;
|
|
||||||
static std::atomic_bool g_has_initialized = false;
|
|
||||||
static std::atomic_bool g_has_hid_session = false;
|
|
||||||
|
|
||||||
/* Content override support variables/types */
|
|
||||||
static OverrideKey g_default_override_key = {
|
|
||||||
.key_combination = KEY_L,
|
|
||||||
.override_by_default = true
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HblOverrideConfig {
|
|
||||||
OverrideKey override_key;
|
|
||||||
u64 program_id;
|
|
||||||
bool override_any_app;
|
|
||||||
};
|
|
||||||
|
|
||||||
static HblOverrideConfig g_hbl_override_config = {
|
|
||||||
.override_key = {
|
|
||||||
.key_combination = KEY_R,
|
|
||||||
.override_by_default = false
|
|
||||||
},
|
|
||||||
.program_id = static_cast<u64>(ams::ncm::ProgramId::AppletPhotoViewer),
|
|
||||||
.override_any_app = true
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Static buffer for loader.ini contents at runtime. */
|
|
||||||
static char g_config_ini_data[0x800];
|
|
||||||
|
|
||||||
/* Backup file for CAL0 partition. */
|
|
||||||
static constexpr size_t ProdinfoSize = 0x8000;
|
|
||||||
static FsFile g_cal0_file = {};
|
|
||||||
static FsFile g_bis_key_file = {};
|
|
||||||
static u8 g_cal0_storage_backup[ProdinfoSize];
|
|
||||||
static u8 g_cal0_backup[ProdinfoSize];
|
|
||||||
|
|
||||||
/* Emummc-related file. */
|
|
||||||
static FsFile g_emummc_file = {0};
|
|
||||||
|
|
||||||
static bool IsHexadecimal(const char *str) {
|
|
||||||
while (*str) {
|
|
||||||
if (isxdigit((unsigned char)*str)) {
|
|
||||||
str++;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Utils::InitializeThreadFunc(void *args) {
|
|
||||||
/* Get required services. */
|
|
||||||
DoWithSmSession([&]() {
|
|
||||||
Handle tmp_hnd = 0;
|
|
||||||
static const char * const required_active_services[] = {"pcv", "gpio", "pinmux", "psc:m"};
|
|
||||||
for (unsigned int i = 0; i < ams::util::size(required_active_services); i++) {
|
|
||||||
R_ASSERT(smGetServiceOriginal(&tmp_hnd, smEncodeName(required_active_services[i])));
|
|
||||||
svcCloseHandle(tmp_hnd);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Mount SD. */
|
|
||||||
while (R_FAILED(fsMountSdcard(&g_sd_filesystem))) {
|
|
||||||
svcSleepThread(1000000ULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Back up CAL0, if it's not backed up already. */
|
|
||||||
fsFsCreateDirectory(&g_sd_filesystem, "/atmosphere/automatic_backups");
|
|
||||||
{
|
|
||||||
FsStorage cal0_storage;
|
|
||||||
R_ASSERT(fsOpenBisStorage(&cal0_storage, FsBisStorageId_CalibrationBinary));
|
|
||||||
R_ASSERT(fsStorageRead(&cal0_storage, 0, g_cal0_storage_backup, ProdinfoSize));
|
|
||||||
fsStorageClose(&cal0_storage);
|
|
||||||
|
|
||||||
char serial_number[0x40] = {0};
|
|
||||||
memcpy(serial_number, g_cal0_storage_backup + 0x250, 0x18);
|
|
||||||
|
|
||||||
/* Automatically backup PRODINFO. */
|
|
||||||
{
|
|
||||||
char prodinfo_backup_path[FS_MAX_PATH] = {0};
|
|
||||||
if (strlen(serial_number) > 0) {
|
|
||||||
snprintf(prodinfo_backup_path, sizeof(prodinfo_backup_path) - 1, "/atmosphere/automatic_backups/%s_PRODINFO.bin", serial_number);
|
|
||||||
} else {
|
|
||||||
snprintf(prodinfo_backup_path, sizeof(prodinfo_backup_path) - 1, "/atmosphere/automatic_backups/PRODINFO.bin");
|
|
||||||
}
|
|
||||||
|
|
||||||
fsFsCreateFile(&g_sd_filesystem, prodinfo_backup_path, ProdinfoSize, 0);
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, prodinfo_backup_path, FS_OPEN_READ | FS_OPEN_WRITE, &g_cal0_file))) {
|
|
||||||
bool has_auto_backup = false;
|
|
||||||
size_t read = 0;
|
|
||||||
if (R_SUCCEEDED(fsFileRead(&g_cal0_file, 0, g_cal0_backup, sizeof(g_cal0_backup), FS_READOPTION_NONE, &read)) && read == sizeof(g_cal0_backup)) {
|
|
||||||
bool is_cal0_valid = true;
|
|
||||||
is_cal0_valid &= memcmp(g_cal0_backup, "CAL0", 4) == 0;
|
|
||||||
is_cal0_valid &= memcmp(g_cal0_backup + 0x250, serial_number, 0x18) == 0;
|
|
||||||
u32 cal0_size = ((u32 *)g_cal0_backup)[2];
|
|
||||||
is_cal0_valid &= cal0_size + 0x40 <= ProdinfoSize;
|
|
||||||
if (is_cal0_valid) {
|
|
||||||
u8 calc_hash[0x20];
|
|
||||||
sha256CalculateHash(calc_hash, g_cal0_backup + 0x40, cal0_size);
|
|
||||||
is_cal0_valid &= memcmp(calc_hash, g_cal0_backup + 0x20, sizeof(calc_hash)) == 0;
|
|
||||||
}
|
|
||||||
has_auto_backup = is_cal0_valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!has_auto_backup) {
|
|
||||||
fsFileSetSize(&g_cal0_file, ProdinfoSize);
|
|
||||||
fsFileWrite(&g_cal0_file, 0, g_cal0_storage_backup, ProdinfoSize, FS_WRITEOPTION_FLUSH);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* NOTE: g_cal0_file is intentionally not closed here. This prevents any other process from opening it. */
|
|
||||||
memset(g_cal0_storage_backup, 0, sizeof(g_cal0_storage_backup));
|
|
||||||
memset(g_cal0_backup, 0, sizeof(g_cal0_backup));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Automatically backup BIS keys. */
|
|
||||||
{
|
|
||||||
u64 key_generation = 0;
|
|
||||||
if (GetRuntimeFirmwareVersion() >= FirmwareVersion_500) {
|
|
||||||
R_ASSERT(splGetConfig(SplConfigItem_NewKeyGeneration, &key_generation));
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr u8 const BisKeySources[4][2][0x10] = {
|
|
||||||
{
|
|
||||||
{0xF8, 0x3F, 0x38, 0x6E, 0x2C, 0xD2, 0xCA, 0x32, 0xA8, 0x9A, 0xB9, 0xAA, 0x29, 0xBF, 0xC7, 0x48},
|
|
||||||
{0x7D, 0x92, 0xB0, 0x3A, 0xA8, 0xBF, 0xDE, 0xE1, 0xA7, 0x4C, 0x3B, 0x6E, 0x35, 0xCB, 0x71, 0x06}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{0x41, 0x00, 0x30, 0x49, 0xDD, 0xCC, 0xC0, 0x65, 0x64, 0x7A, 0x7E, 0xB4, 0x1E, 0xED, 0x9C, 0x5F},
|
|
||||||
{0x44, 0x42, 0x4E, 0xDA, 0xB4, 0x9D, 0xFC, 0xD9, 0x87, 0x77, 0x24, 0x9A, 0xDC, 0x9F, 0x7C, 0xA4}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{0x52, 0xC2, 0xE9, 0xEB, 0x09, 0xE3, 0xEE, 0x29, 0x32, 0xA1, 0x0C, 0x1F, 0xB6, 0xA0, 0x92, 0x6C},
|
|
||||||
{0x4D, 0x12, 0xE1, 0x4B, 0x2A, 0x47, 0x4C, 0x1C, 0x09, 0xCB, 0x03, 0x59, 0xF0, 0x15, 0xF4, 0xE4}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{0x52, 0xC2, 0xE9, 0xEB, 0x09, 0xE3, 0xEE, 0x29, 0x32, 0xA1, 0x0C, 0x1F, 0xB6, 0xA0, 0x92, 0x6C},
|
|
||||||
{0x4D, 0x12, 0xE1, 0x4B, 0x2A, 0x47, 0x4C, 0x1C, 0x09, 0xCB, 0x03, 0x59, 0xF0, 0x15, 0xF4, 0xE4}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
u8 bis_keys[4][2][0x10] = {};
|
|
||||||
|
|
||||||
/* TODO: Clean this up in ams_mitm refactor. */
|
|
||||||
for (size_t partition = 0; partition < 4; partition++) {
|
|
||||||
if (partition == 0) {
|
|
||||||
for (size_t i = 0; i < 2; i++) {
|
|
||||||
R_ASSERT(splFsGenerateSpecificAesKey(BisKeySources[partition][i], key_generation, i, bis_keys[partition][i]));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
static constexpr u8 const BisKekSource[0x10] = {
|
|
||||||
0x34, 0xC1, 0xA0, 0xC4, 0x82, 0x58, 0xF8, 0xB4, 0xFA, 0x9E, 0x5E, 0x6A, 0xDA, 0xFC, 0x7E, 0x4F,
|
|
||||||
};
|
|
||||||
|
|
||||||
const u32 option = (partition == 3 && ams::spl::IsRecoveryBoot()) ? 0x4 : 0x1;
|
|
||||||
|
|
||||||
u8 access_key[0x10];
|
|
||||||
R_ASSERT(splCryptoGenerateAesKek(BisKekSource, key_generation, option, access_key));
|
|
||||||
for (size_t i = 0; i < 2; i++) {
|
|
||||||
R_ASSERT(splCryptoGenerateAesKey(access_key, BisKeySources[partition][i], bis_keys[partition][i]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char bis_key_backup_path[FS_MAX_PATH] = {0};
|
|
||||||
if (strlen(serial_number) > 0) {
|
|
||||||
snprintf(bis_key_backup_path, sizeof(bis_key_backup_path) - 1, "/atmosphere/automatic_backups/%s_BISKEYS.bin", serial_number);
|
|
||||||
} else {
|
|
||||||
snprintf(bis_key_backup_path, sizeof(bis_key_backup_path) - 1, "/atmosphere/automatic_backups/BISKEYS.bin");
|
|
||||||
}
|
|
||||||
|
|
||||||
fsFsCreateFile(&g_sd_filesystem, bis_key_backup_path, sizeof(bis_keys), 0);
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, bis_key_backup_path, FS_OPEN_READ | FS_OPEN_WRITE, &g_bis_key_file))) {
|
|
||||||
fsFileSetSize(&g_bis_key_file, sizeof(bis_keys));
|
|
||||||
fsFileWrite(&g_bis_key_file, 0, bis_keys, sizeof(bis_keys), FS_WRITEOPTION_FLUSH);
|
|
||||||
/* NOTE: g_bis_key_file is intentionally not closed here. This prevents any other process from opening it. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for MitM flags. */
|
|
||||||
FsDir titles_dir;
|
|
||||||
if (R_SUCCEEDED(fsFsOpenDirectory(&g_sd_filesystem, "/atmosphere/titles", FS_DIROPEN_DIRECTORY, &titles_dir))) {
|
|
||||||
FsDirectoryEntry dir_entry;
|
|
||||||
FsFile f;
|
|
||||||
u64 read_entries;
|
|
||||||
while (R_SUCCEEDED((fsDirRead(&titles_dir, 0, &read_entries, 1, &dir_entry))) && read_entries == 1) {
|
|
||||||
if (strlen(dir_entry.name) == 0x10 && IsHexadecimal(dir_entry.name)) {
|
|
||||||
u64 program_id = strtoul(dir_entry.name, NULL, 16);
|
|
||||||
char title_path[FS_MAX_PATH] = {0};
|
|
||||||
strcpy(title_path, "/atmosphere/titles/");
|
|
||||||
strcat(title_path, dir_entry.name);
|
|
||||||
strcat(title_path, "/flags/fsmitm.flag");
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
|
|
||||||
g_mitm_flagged_tids.push_back(program_id);
|
|
||||||
fsFileClose(&f);
|
|
||||||
} else {
|
|
||||||
/* TODO: Deprecate. */
|
|
||||||
memset(title_path, 0, sizeof(title_path));
|
|
||||||
strcpy(title_path, "/atmosphere/titles/");
|
|
||||||
strcat(title_path, dir_entry.name);
|
|
||||||
strcat(title_path, "/fsmitm.flag");
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
|
|
||||||
g_mitm_flagged_tids.push_back(program_id);
|
|
||||||
fsFileClose(&f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(title_path, 0, sizeof(title_path));
|
|
||||||
strcpy(title_path, "/atmosphere/titles/");
|
|
||||||
strcat(title_path, dir_entry.name);
|
|
||||||
strcat(title_path, "/flags/fsmitm_disable.flag");
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
|
|
||||||
g_disable_mitm_flagged_tids.push_back(program_id);
|
|
||||||
fsFileClose(&f);
|
|
||||||
} else {
|
|
||||||
/* TODO: Deprecate. */
|
|
||||||
memset(title_path, 0, sizeof(title_path));
|
|
||||||
strcpy(title_path, "/atmosphere/titles/");
|
|
||||||
strcat(title_path, dir_entry.name);
|
|
||||||
strcat(title_path, "/fsmitm_disable.flag");
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, title_path, FS_OPEN_READ, &f))) {
|
|
||||||
g_disable_mitm_flagged_tids.push_back(program_id);
|
|
||||||
fsFileClose(&f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fsDirClose(&titles_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils::RefreshConfiguration();
|
|
||||||
|
|
||||||
/* If we're emummc, persist a write handle to prevent other processes from touching the image. */
|
|
||||||
if (IsEmummc()) {
|
|
||||||
const char *emummc_file_path = GetEmummcFilePath();
|
|
||||||
if (emummc_file_path != nullptr) {
|
|
||||||
char emummc_path[0x100] = {0};
|
|
||||||
std::strncpy(emummc_path, emummc_file_path, 0x80);
|
|
||||||
std::strcat(emummc_path, "/eMMC");
|
|
||||||
fsFsOpenFile(&g_sd_filesystem, emummc_file_path, FS_OPEN_READ | FS_OPEN_WRITE, &g_emummc_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Initialize set:sys. */
|
|
||||||
DoWithSmSession([&]() {
|
|
||||||
R_ASSERT(setsysInitialize());
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Signal SD is initialized. */
|
|
||||||
g_has_initialized = true;
|
|
||||||
|
|
||||||
/* Load custom settings configuration. */
|
|
||||||
SettingsItemManager::LoadConfiguration();
|
|
||||||
|
|
||||||
/* Signal to waiters that we are ready. */
|
|
||||||
g_sd_event.Signal();
|
|
||||||
|
|
||||||
/* Initialize HID. */
|
|
||||||
while (!g_has_hid_session) {
|
|
||||||
DoWithSmSession([&]() {
|
|
||||||
if (R_SUCCEEDED(hidInitialize())) {
|
|
||||||
g_has_hid_session = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!g_has_hid_session) {
|
|
||||||
svcSleepThread(1000000ULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Signal to waiters that we are ready. */
|
|
||||||
g_hid_event.Signal();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::IsSdInitialized() {
|
|
||||||
return g_has_initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Utils::WaitSdInitialized() {
|
|
||||||
g_sd_event.Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::IsHidAvailable() {
|
|
||||||
return g_has_hid_session;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Utils::WaitHidAvailable() {
|
|
||||||
g_hid_event.Wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::OpenSdFile(const char *fn, int flags, FsFile *out) {
|
|
||||||
if (!IsSdInitialized()) {
|
|
||||||
return ResultFsSdCardNotPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fsFsOpenFile(&g_sd_filesystem, fn, flags, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::OpenSdFileForAtmosphere(u64 program_id, const char *fn, int flags, FsFile *out) {
|
|
||||||
if (!IsSdInitialized()) {
|
|
||||||
return ResultFsSdCardNotPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
char path[FS_MAX_PATH];
|
|
||||||
if (*fn == '/') {
|
|
||||||
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx%s", program_id, fn);
|
|
||||||
} else {
|
|
||||||
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/%s", program_id, fn);
|
|
||||||
}
|
|
||||||
return fsFsOpenFile(&g_sd_filesystem, path, flags, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::OpenRomFSSdFile(u64 program_id, const char *fn, int flags, FsFile *out) {
|
|
||||||
if (!IsSdInitialized()) {
|
|
||||||
return ResultFsSdCardNotPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return OpenRomFSFile(&g_sd_filesystem, program_id, fn, flags, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::OpenSdDir(const char *path, FsDir *out) {
|
|
||||||
if (!IsSdInitialized()) {
|
|
||||||
return ResultFsSdCardNotPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fsFsOpenDirectory(&g_sd_filesystem, path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::OpenSdDirForAtmosphere(u64 program_id, const char *path, FsDir *out) {
|
|
||||||
if (!IsSdInitialized()) {
|
|
||||||
return ResultFsSdCardNotPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
char safe_path[FS_MAX_PATH];
|
|
||||||
if (*path == '/') {
|
|
||||||
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx%s", program_id, path);
|
|
||||||
} else {
|
|
||||||
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx/%s", program_id, path);
|
|
||||||
}
|
|
||||||
return fsFsOpenDirectory(&g_sd_filesystem, safe_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::OpenRomFSSdDir(u64 program_id, const char *path, FsDir *out) {
|
|
||||||
if (!IsSdInitialized()) {
|
|
||||||
return ResultFsSdCardNotPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return OpenRomFSDir(&g_sd_filesystem, program_id, path, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Result Utils::OpenRomFSFile(FsFileSystem *fs, u64 program_id, const char *fn, int flags, FsFile *out) {
|
|
||||||
char path[FS_MAX_PATH];
|
|
||||||
if (*fn == '/') {
|
|
||||||
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/romfs%s", program_id, fn);
|
|
||||||
} else {
|
|
||||||
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/romfs/%s", program_id, fn);
|
|
||||||
}
|
|
||||||
return fsFsOpenFile(fs, path, flags, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::OpenRomFSDir(FsFileSystem *fs, u64 program_id, const char *path, FsDir *out) {
|
|
||||||
char safe_path[FS_MAX_PATH];
|
|
||||||
if (*path == '/') {
|
|
||||||
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx/romfs%s", program_id, path);
|
|
||||||
} else {
|
|
||||||
snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx/romfs/%s", program_id, path);
|
|
||||||
}
|
|
||||||
return fsFsOpenDirectory(fs, safe_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::HasSdRomfsContent(u64 program_id) {
|
|
||||||
/* Check for romfs.bin. */
|
|
||||||
FsFile data_file;
|
|
||||||
if (R_SUCCEEDED(Utils::OpenSdFileForAtmosphere(program_id, "romfs.bin", FS_OPEN_READ, &data_file))) {
|
|
||||||
fsFileClose(&data_file);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for romfs folder with non-zero content. */
|
|
||||||
FsDir dir;
|
|
||||||
if (R_FAILED(Utils::OpenRomFSSdDir(program_id, "", &dir))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ON_SCOPE_EXIT {
|
|
||||||
fsDirClose(&dir);
|
|
||||||
};
|
|
||||||
|
|
||||||
FsDirectoryEntry dir_entry;
|
|
||||||
u64 read_entries;
|
|
||||||
return R_SUCCEEDED(fsDirRead(&dir, 0, &read_entries, 1, &dir_entry)) && read_entries == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::SaveSdFileForAtmosphere(u64 program_id, const char *fn, void *data, size_t size) {
|
|
||||||
if (!IsSdInitialized()) {
|
|
||||||
return ResultFsSdCardNotPresent;
|
|
||||||
}
|
|
||||||
|
|
||||||
char path[FS_MAX_PATH];
|
|
||||||
if (*fn == '/') {
|
|
||||||
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx%s", program_id, fn);
|
|
||||||
} else {
|
|
||||||
snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/%s", program_id, fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unconditionally create. */
|
|
||||||
FsFile f;
|
|
||||||
fsFsCreateFile(&g_sd_filesystem, path, size, 0);
|
|
||||||
|
|
||||||
/* Try to open. */
|
|
||||||
R_TRY(fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ | FS_OPEN_WRITE, &f));
|
|
||||||
ON_SCOPE_EXIT { fsFileClose(&f); };
|
|
||||||
|
|
||||||
/* Try to make it big enough. */
|
|
||||||
R_TRY(fsFileSetSize(&f, size));
|
|
||||||
|
|
||||||
/* Try to write the data. */
|
|
||||||
R_TRY(fsFileWrite(&f, 0, data, size, FS_WRITEOPTION_FLUSH));
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::IsHblTid(u64 _tid) {
|
|
||||||
const ams::ncm::ProgramId tid{_tid};
|
|
||||||
return (g_hbl_override_config.override_any_app && ams::ncm::IsApplicationProgramId(tid)) || (_tid == g_hbl_override_config.program_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::IsWebAppletTid(u64 _tid) {
|
|
||||||
const ams::ncm::ProgramId tid{_tid};
|
|
||||||
return tid == ams::ncm::ProgramId::AppletWeb || tid == ams::ncm::ProgramId::AppletOfflineWeb || tid == ams::ncm::ProgramId::AppletLoginShare || tid == ams::ncm::ProgramId::AppletWifiWebAuth;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::HasTitleFlag(u64 tid, const char *flag) {
|
|
||||||
if (IsSdInitialized()) {
|
|
||||||
FsFile f;
|
|
||||||
char flag_path[FS_MAX_PATH];
|
|
||||||
|
|
||||||
memset(flag_path, 0, sizeof(flag_path));
|
|
||||||
snprintf(flag_path, sizeof(flag_path) - 1, "flags/%s.flag", flag);
|
|
||||||
if (R_SUCCEEDED(OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f))) {
|
|
||||||
fsFileClose(&f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO: Deprecate. */
|
|
||||||
snprintf(flag_path, sizeof(flag_path) - 1, "%s.flag", flag);
|
|
||||||
if (R_SUCCEEDED(OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f))) {
|
|
||||||
fsFileClose(&f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::HasGlobalFlag(const char *flag) {
|
|
||||||
if (IsSdInitialized()) {
|
|
||||||
FsFile f;
|
|
||||||
char flag_path[FS_MAX_PATH] = {0};
|
|
||||||
snprintf(flag_path, sizeof(flag_path), "/atmosphere/flags/%s.flag", flag);
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, flag_path, FS_OPEN_READ, &f))) {
|
|
||||||
fsFileClose(&f);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::HasHblFlag(const char *flag) {
|
|
||||||
char hbl_flag[FS_MAX_PATH] = {0};
|
|
||||||
snprintf(hbl_flag, sizeof(hbl_flag), "hbl_%s", flag);
|
|
||||||
return HasGlobalFlag(hbl_flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::HasFlag(u64 tid, const char *flag) {
|
|
||||||
return HasTitleFlag(tid, flag) || (IsHblTid(tid) && HasHblFlag(flag));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::HasSdMitMFlag(u64 tid) {
|
|
||||||
if (IsHblTid(tid)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsSdInitialized()) {
|
|
||||||
return std::find(g_mitm_flagged_tids.begin(), g_mitm_flagged_tids.end(), tid) != g_mitm_flagged_tids.end();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Utils::HasSdDisableMitMFlag(u64 tid) {
|
|
||||||
if (IsSdInitialized()) {
|
|
||||||
return std::find(g_disable_mitm_flagged_tids.begin(), g_disable_mitm_flagged_tids.end(), tid) != g_disable_mitm_flagged_tids.end();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::GetKeysHeld(u64 *keys) {
|
|
||||||
if (!Utils::IsHidAvailable()) {
|
|
||||||
return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
|
|
||||||
}
|
|
||||||
|
|
||||||
hidScanInput();
|
|
||||||
*keys = 0;
|
|
||||||
|
|
||||||
for (int controller = 0; controller < 10; controller++) {
|
|
||||||
*keys |= hidKeysHeld((HidControllerID) controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool HasOverrideKey(OverrideKey *cfg) {
|
|
||||||
u64 kDown = 0;
|
|
||||||
bool keys_triggered = (R_SUCCEEDED(Utils::GetKeysHeld(&kDown)) && ((kDown & cfg->key_combination) != 0));
|
|
||||||
return Utils::IsSdInitialized() && (cfg->override_by_default ^ keys_triggered);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool Utils::HasOverrideButton(u64 tid) {
|
|
||||||
if ((!ams::ncm::IsApplicationProgramId(ams::ncm::ProgramId{tid})) || (!IsSdInitialized())) {
|
|
||||||
/* Disable button override disable for non-applications. */
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unconditionally refresh loader.ini contents. */
|
|
||||||
RefreshConfiguration();
|
|
||||||
|
|
||||||
if (IsHblTid(tid) && HasOverrideKey(&g_hbl_override_config.override_key)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
OverrideKey title_cfg = GetTitleOverrideKey(tid);
|
|
||||||
return HasOverrideKey(&title_cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
static OverrideKey ParseOverrideKey(const char *value) {
|
|
||||||
OverrideKey cfg;
|
|
||||||
|
|
||||||
/* Parse on by default. */
|
|
||||||
if (value[0] == '!') {
|
|
||||||
cfg.override_by_default = true;
|
|
||||||
value++;
|
|
||||||
} else {
|
|
||||||
cfg.override_by_default = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse key combination. */
|
|
||||||
if (strcasecmp(value, "A") == 0) {
|
|
||||||
cfg.key_combination = KEY_A;
|
|
||||||
} else if (strcasecmp(value, "B") == 0) {
|
|
||||||
cfg.key_combination = KEY_B;
|
|
||||||
} else if (strcasecmp(value, "X") == 0) {
|
|
||||||
cfg.key_combination = KEY_X;
|
|
||||||
} else if (strcasecmp(value, "Y") == 0) {
|
|
||||||
cfg.key_combination = KEY_Y;
|
|
||||||
} else if (strcasecmp(value, "LS") == 0) {
|
|
||||||
cfg.key_combination = KEY_LSTICK;
|
|
||||||
} else if (strcasecmp(value, "RS") == 0) {
|
|
||||||
cfg.key_combination = KEY_RSTICK;
|
|
||||||
} else if (strcasecmp(value, "L") == 0) {
|
|
||||||
cfg.key_combination = KEY_L;
|
|
||||||
} else if (strcasecmp(value, "R") == 0) {
|
|
||||||
cfg.key_combination = KEY_R;
|
|
||||||
} else if (strcasecmp(value, "ZL") == 0) {
|
|
||||||
cfg.key_combination = KEY_ZL;
|
|
||||||
} else if (strcasecmp(value, "ZR") == 0) {
|
|
||||||
cfg.key_combination = KEY_ZR;
|
|
||||||
} else if (strcasecmp(value, "PLUS") == 0) {
|
|
||||||
cfg.key_combination = KEY_PLUS;
|
|
||||||
} else if (strcasecmp(value, "MINUS") == 0) {
|
|
||||||
cfg.key_combination = KEY_MINUS;
|
|
||||||
} else if (strcasecmp(value, "DLEFT") == 0) {
|
|
||||||
cfg.key_combination = KEY_DLEFT;
|
|
||||||
} else if (strcasecmp(value, "DUP") == 0) {
|
|
||||||
cfg.key_combination = KEY_DUP;
|
|
||||||
} else if (strcasecmp(value, "DRIGHT") == 0) {
|
|
||||||
cfg.key_combination = KEY_DRIGHT;
|
|
||||||
} else if (strcasecmp(value, "DDOWN") == 0) {
|
|
||||||
cfg.key_combination = KEY_DDOWN;
|
|
||||||
} else if (strcasecmp(value, "SL") == 0) {
|
|
||||||
cfg.key_combination = KEY_SL;
|
|
||||||
} else if (strcasecmp(value, "SR") == 0) {
|
|
||||||
cfg.key_combination = KEY_SR;
|
|
||||||
} else {
|
|
||||||
cfg.key_combination = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int FsMitmIniHandler(void *user, const char *section, const char *name, const char *value) {
|
|
||||||
/* Taken and modified, with love, from Rajkosto's implementation. */
|
|
||||||
if (strcasecmp(section, "hbl_config") == 0) {
|
|
||||||
if (strcasecmp(name, "program_id") == 0) {
|
|
||||||
if (strcasecmp(value, "app") == 0) {
|
|
||||||
/* DEPRECATED */
|
|
||||||
g_hbl_override_config.override_any_app = true;
|
|
||||||
g_hbl_override_config.program_id = 0;
|
|
||||||
} else {
|
|
||||||
u64 override_tid = strtoul(value, NULL, 16);
|
|
||||||
if (override_tid != 0) {
|
|
||||||
g_hbl_override_config.program_id = override_tid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (strcasecmp(name, "override_key") == 0) {
|
|
||||||
g_hbl_override_config.override_key = ParseOverrideKey(value);
|
|
||||||
} else if (strcasecmp(name, "override_any_app") == 0) {
|
|
||||||
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
|
|
||||||
g_hbl_override_config.override_any_app = true;
|
|
||||||
} else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) {
|
|
||||||
g_hbl_override_config.override_any_app = false;
|
|
||||||
} else {
|
|
||||||
/* I guess we default to not changing the value? */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (strcasecmp(section, "default_config") == 0) {
|
|
||||||
if (strcasecmp(name, "override_key") == 0) {
|
|
||||||
g_default_override_key = ParseOverrideKey(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int FsMitmContentSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
|
|
||||||
/* We'll output an override key when relevant. */
|
|
||||||
OverrideKey *user_cfg = reinterpret_cast<OverrideKey *>(user);
|
|
||||||
|
|
||||||
if (strcasecmp(section, "override_config") == 0) {
|
|
||||||
if (strcasecmp(name, "override_key") == 0) {
|
|
||||||
*user_cfg = ParseOverrideKey(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
OverrideKey Utils::GetTitleOverrideKey(u64 tid) {
|
|
||||||
OverrideKey cfg = g_default_override_key;
|
|
||||||
char path[FS_MAX_PATH+1] = {0};
|
|
||||||
snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/config.ini", tid);
|
|
||||||
FsFile cfg_file;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ, &cfg_file))) {
|
|
||||||
ON_SCOPE_EXIT { fsFileClose(&cfg_file); };
|
|
||||||
|
|
||||||
size_t config_file_size = 0x20000;
|
|
||||||
fsFileGetSize(&cfg_file, &config_file_size);
|
|
||||||
|
|
||||||
char *config_buf = reinterpret_cast<char *>(calloc(1, config_file_size + 1));
|
|
||||||
if (config_buf != NULL) {
|
|
||||||
ON_SCOPE_EXIT { free(config_buf); };
|
|
||||||
|
|
||||||
/* Read title ini contents. */
|
|
||||||
fsFileRead(&cfg_file, 0, config_buf, config_file_size, FS_READOPTION_NONE, &config_file_size);
|
|
||||||
|
|
||||||
/* Parse title ini. */
|
|
||||||
ini_parse_string(config_buf, FsMitmContentSpecificIniHandler, &cfg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cfg;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int FsMitmContentSpecificLocaleIniHandler(void *user, const char *section, const char *name, const char *value) {
|
|
||||||
/* We'll output an override locale when relevant. */
|
|
||||||
OverrideLocale *user_locale = reinterpret_cast<OverrideLocale *>(user);
|
|
||||||
|
|
||||||
if (strcasecmp(section, "override_config") == 0) {
|
|
||||||
if (strcasecmp(name, "override_language") == 0) {
|
|
||||||
user_locale->language_code = EncodeLanguageCode(value);
|
|
||||||
} else if (strcasecmp(name, "override_region") == 0) {
|
|
||||||
if (strcasecmp(value, "jpn") == 0) {
|
|
||||||
user_locale->region_code = RegionCode_Japan;
|
|
||||||
} else if (strcasecmp(value, "usa") == 0) {
|
|
||||||
user_locale->region_code = RegionCode_America;
|
|
||||||
} else if (strcasecmp(value, "eur") == 0) {
|
|
||||||
user_locale->region_code = RegionCode_Europe;
|
|
||||||
} else if (strcasecmp(value, "aus") == 0) {
|
|
||||||
user_locale->region_code = RegionCode_Australia;
|
|
||||||
} else if (strcasecmp(value, "chn") == 0) {
|
|
||||||
user_locale->region_code = RegionCode_China;
|
|
||||||
} else if (strcasecmp(value, "kor") == 0) {
|
|
||||||
user_locale->region_code = RegionCode_Korea;
|
|
||||||
} else if (strcasecmp(value, "twn") == 0) {
|
|
||||||
user_locale->region_code = RegionCode_Taiwan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
OverrideLocale Utils::GetTitleOverrideLocale(u64 tid) {
|
|
||||||
OverrideLocale locale;
|
|
||||||
std::memset(&locale, 0xCC, sizeof(locale));
|
|
||||||
char path[FS_MAX_PATH+1] = {0};
|
|
||||||
snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/config.ini", tid);
|
|
||||||
FsFile cfg_file;
|
|
||||||
|
|
||||||
if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ, &cfg_file))) {
|
|
||||||
ON_SCOPE_EXIT { fsFileClose(&cfg_file); };
|
|
||||||
|
|
||||||
size_t config_file_size = 0x20000;
|
|
||||||
fsFileGetSize(&cfg_file, &config_file_size);
|
|
||||||
|
|
||||||
char *config_buf = reinterpret_cast<char *>(calloc(1, config_file_size + 1));
|
|
||||||
if (config_buf != NULL) {
|
|
||||||
ON_SCOPE_EXIT { free(config_buf); };
|
|
||||||
|
|
||||||
/* Read title ini contents. */
|
|
||||||
fsFileRead(&cfg_file, 0, config_buf, config_file_size, FS_READOPTION_NONE, &config_file_size);
|
|
||||||
|
|
||||||
/* Parse title ini. */
|
|
||||||
ini_parse_string(config_buf, FsMitmContentSpecificLocaleIniHandler, &locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Utils::RefreshConfiguration() {
|
|
||||||
FsFile config_file;
|
|
||||||
if (R_FAILED(fsFsOpenFile(&g_sd_filesystem, "/atmosphere/loader.ini", FS_OPEN_READ, &config_file))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 size;
|
|
||||||
if (R_FAILED(fsFileGetSize(&config_file, &size))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size = std::min(size, (decltype(size))0x7FF);
|
|
||||||
|
|
||||||
/* Read in string. */
|
|
||||||
std::fill(g_config_ini_data, g_config_ini_data + 0x800, 0);
|
|
||||||
size_t r_s;
|
|
||||||
fsFileRead(&config_file, 0, g_config_ini_data, size, FS_READOPTION_NONE, &r_s);
|
|
||||||
fsFileClose(&config_file);
|
|
||||||
|
|
||||||
ini_parse_string(g_config_ini_data, FsMitmIniHandler, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::GetSettingsItemValueSize(const char *name, const char *key, u64 *out_size) {
|
|
||||||
return SettingsItemManager::GetValueSize(name, key, out_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::GetSettingsItemValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size) {
|
|
||||||
return SettingsItemManager::GetValue(name, key, out, max_size, out_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result Utils::GetSettingsItemBooleanValue(const char *name, const char *key, bool *out) {
|
|
||||||
u8 val = 0;
|
|
||||||
u64 out_size;
|
|
||||||
R_TRY(Utils::GetSettingsItemValue(name, key, &val, sizeof(val), &out_size));
|
|
||||||
|
|
||||||
if (out) {
|
|
||||||
*out = val != 0;
|
|
||||||
}
|
|
||||||
return ResultSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Utils::RebootToFatalError(AtmosphereFatalErrorContext *ctx) {
|
|
||||||
BpcRebootManager::RebootForFatalError(ctx);
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
||||||
*
|
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms and conditions of the GNU General Public License,
|
|
||||||
* version 2, as published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
#include <switch.h>
|
|
||||||
#include <stratosphere.hpp>
|
|
||||||
|
|
||||||
struct OverrideKey {
|
|
||||||
u64 key_combination;
|
|
||||||
bool override_by_default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OverrideLocale {
|
|
||||||
u64 language_code;
|
|
||||||
u32 region_code;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum RegionCode : u32 {
|
|
||||||
RegionCode_Japan = 0,
|
|
||||||
RegionCode_America = 1,
|
|
||||||
RegionCode_Europe = 2,
|
|
||||||
RegionCode_Australia = 3,
|
|
||||||
RegionCode_China = 4,
|
|
||||||
RegionCode_Korea = 5,
|
|
||||||
RegionCode_Taiwan = 6,
|
|
||||||
|
|
||||||
RegionCode_Max,
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr inline u64 EncodeLanguageCode(const char *code) {
|
|
||||||
u64 lang_code = 0;
|
|
||||||
for (size_t i = 0; i < sizeof(lang_code); i++) {
|
|
||||||
if (code[i] == '\x00') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
lang_code |= static_cast<u64>(code[i]) << (8ul * i);
|
|
||||||
}
|
|
||||||
return lang_code;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum LanguageCode : u64 {
|
|
||||||
LanguageCode_Japanese = EncodeLanguageCode("ja"),
|
|
||||||
LanguageCode_AmericanEnglish = EncodeLanguageCode("en-US"),
|
|
||||||
LanguageCode_French = EncodeLanguageCode("fr"),
|
|
||||||
LanguageCode_German = EncodeLanguageCode("de"),
|
|
||||||
LanguageCode_Italian = EncodeLanguageCode("it"),
|
|
||||||
LanguageCode_Spanish = EncodeLanguageCode("es"),
|
|
||||||
LanguageCode_Chinese = EncodeLanguageCode("zh-CN"),
|
|
||||||
LanguageCode_Korean = EncodeLanguageCode("ko"),
|
|
||||||
LanguageCode_Dutch = EncodeLanguageCode("nl"),
|
|
||||||
LanguageCode_Portuguese = EncodeLanguageCode("pt"),
|
|
||||||
LanguageCode_Russian = EncodeLanguageCode("ru"),
|
|
||||||
LanguageCode_Taiwanese = EncodeLanguageCode("zh-TW"),
|
|
||||||
LanguageCode_BritishEnglish = EncodeLanguageCode("en-GB"),
|
|
||||||
LanguageCode_CanadianFrench = EncodeLanguageCode("fr-CA"),
|
|
||||||
LanguageCode_LatinAmericanSpanish = EncodeLanguageCode("es-419"),
|
|
||||||
/* 4.0.0+ */
|
|
||||||
LanguageCode_SimplifiedChinese = EncodeLanguageCode("zh-Hans"),
|
|
||||||
LanguageCode_TraditionalChinese = EncodeLanguageCode("zh-Hant"),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
class Utils {
|
|
||||||
public:
|
|
||||||
static bool IsSdInitialized();
|
|
||||||
static void WaitSdInitialized();
|
|
||||||
|
|
||||||
static Result OpenSdFile(const char *fn, int flags, FsFile *out);
|
|
||||||
static Result OpenSdFileForAtmosphere(u64 program_id, const char *fn, int flags, FsFile *out);
|
|
||||||
static Result OpenRomFSSdFile(u64 program_id, const char *fn, int flags, FsFile *out);
|
|
||||||
static Result OpenSdDir(const char *path, FsDir *out);
|
|
||||||
static Result OpenSdDirForAtmosphere(u64 program_id, const char *path, FsDir *out);
|
|
||||||
static Result OpenRomFSSdDir(u64 program_id, const char *path, FsDir *out);
|
|
||||||
|
|
||||||
static Result OpenRomFSFile(FsFileSystem *fs, u64 program_id, const char *fn, int flags, FsFile *out);
|
|
||||||
static Result OpenRomFSDir(FsFileSystem *fs, u64 program_id, const char *path, FsDir *out);
|
|
||||||
|
|
||||||
static Result SaveSdFileForAtmosphere(u64 program_id, const char *fn, void *data, size_t size);
|
|
||||||
|
|
||||||
static bool HasSdRomfsContent(u64 program_id);
|
|
||||||
|
|
||||||
/* Delayed Initialization + MitM detection. */
|
|
||||||
static void InitializeThreadFunc(void *args);
|
|
||||||
|
|
||||||
static bool IsHblTid(u64 tid);
|
|
||||||
static bool IsWebAppletTid(u64 tid);
|
|
||||||
|
|
||||||
static bool HasTitleFlag(u64 tid, const char *flag);
|
|
||||||
static bool HasHblFlag(const char *flag);
|
|
||||||
static bool HasGlobalFlag(const char *flag);
|
|
||||||
static bool HasFlag(u64 tid, const char *flag);
|
|
||||||
|
|
||||||
static bool HasSdMitMFlag(u64 tid);
|
|
||||||
static bool HasSdDisableMitMFlag(u64 tid);
|
|
||||||
|
|
||||||
|
|
||||||
static bool IsHidAvailable();
|
|
||||||
static void WaitHidAvailable();
|
|
||||||
static Result GetKeysHeld(u64 *keys);
|
|
||||||
|
|
||||||
static OverrideKey GetTitleOverrideKey(u64 tid);
|
|
||||||
static bool HasOverrideButton(u64 tid);
|
|
||||||
|
|
||||||
static OverrideLocale GetTitleOverrideLocale(u64 tid);
|
|
||||||
|
|
||||||
/* Settings! */
|
|
||||||
static Result GetSettingsItemValueSize(const char *name, const char *key, u64 *out_size);
|
|
||||||
static Result GetSettingsItemValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size);
|
|
||||||
|
|
||||||
static Result GetSettingsItemBooleanValue(const char *name, const char *key, bool *out);
|
|
||||||
|
|
||||||
/* Error occurred. */
|
|
||||||
static void RebootToFatalError(AtmosphereFatalErrorContext *ctx);
|
|
||||||
private:
|
|
||||||
static void RefreshConfiguration();
|
|
||||||
};
|
|
Loading…
Reference in a new issue