From fa3ef61ac938ac43c7223d688ad6365727f57883 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Sat, 22 Sep 2018 02:13:06 +0200 Subject: [PATCH] Added loader and added the ldr_cfg service --- .gitignore | 3 + loader/Makefile | 153 ++ loader/loader.json | 67 + loader/source/ini.c | 269 ++++ loader/source/ini.h | 130 ++ loader/source/ldr_cfg_service.cpp | 53 + loader/source/ldr_cfg_service.hpp | 25 + loader/source/ldr_content_management.cpp | 318 ++++ loader/source/ldr_content_management.hpp | 42 + loader/source/ldr_debug_monitor.cpp | 63 + loader/source/ldr_debug_monitor.hpp | 46 + loader/source/ldr_hid.cpp | 35 + loader/source/ldr_hid.hpp | 23 + loader/source/ldr_launch_queue.cpp | 99 ++ loader/source/ldr_launch_queue.hpp | 42 + loader/source/ldr_main.cpp | 131 ++ loader/source/ldr_map.cpp | 218 +++ loader/source/ldr_map.hpp | 172 ++ loader/source/ldr_npdm.cpp | 492 ++++++ loader/source/ldr_npdm.hpp | 112 ++ loader/source/ldr_nro.cpp | 134 ++ loader/source/ldr_nro.hpp | 72 + loader/source/ldr_nso.cpp | 356 +++++ loader/source/ldr_nso.hpp | 114 ++ loader/source/ldr_patcher.cpp | 187 +++ loader/source/ldr_patcher.hpp | 26 + loader/source/ldr_process_creation.cpp | 231 +++ loader/source/ldr_process_creation.hpp | 40 + loader/source/ldr_process_manager.cpp | 193 +++ loader/source/ldr_process_manager.hpp | 67 + loader/source/ldr_random.cpp | 65 + loader/source/ldr_random.hpp | 25 + loader/source/ldr_registration.cpp | 339 ++++ loader/source/ldr_registration.hpp | 99 ++ loader/source/ldr_ro_service.cpp | 187 +++ loader/source/ldr_ro_service.hpp | 59 + loader/source/ldr_shell.cpp | 48 + loader/source/ldr_shell.hpp | 42 + loader/source/lz4.c | 1857 ++++++++++++++++++++++ loader/source/lz4.h | 569 +++++++ loader/source/sha256.c | 113 ++ loader/source/sha256.h | 36 + loader/source/sha256_armv8.s | 163 ++ 43 files changed, 7515 insertions(+) create mode 100644 loader/Makefile create mode 100644 loader/loader.json create mode 100644 loader/source/ini.c create mode 100644 loader/source/ini.h create mode 100644 loader/source/ldr_cfg_service.cpp create mode 100644 loader/source/ldr_cfg_service.hpp create mode 100644 loader/source/ldr_content_management.cpp create mode 100644 loader/source/ldr_content_management.hpp create mode 100644 loader/source/ldr_debug_monitor.cpp create mode 100644 loader/source/ldr_debug_monitor.hpp create mode 100644 loader/source/ldr_hid.cpp create mode 100644 loader/source/ldr_hid.hpp create mode 100644 loader/source/ldr_launch_queue.cpp create mode 100644 loader/source/ldr_launch_queue.hpp create mode 100644 loader/source/ldr_main.cpp create mode 100644 loader/source/ldr_map.cpp create mode 100644 loader/source/ldr_map.hpp create mode 100644 loader/source/ldr_npdm.cpp create mode 100644 loader/source/ldr_npdm.hpp create mode 100644 loader/source/ldr_nro.cpp create mode 100644 loader/source/ldr_nro.hpp create mode 100644 loader/source/ldr_nso.cpp create mode 100644 loader/source/ldr_nso.hpp create mode 100644 loader/source/ldr_patcher.cpp create mode 100644 loader/source/ldr_patcher.hpp create mode 100644 loader/source/ldr_process_creation.cpp create mode 100644 loader/source/ldr_process_creation.hpp create mode 100644 loader/source/ldr_process_manager.cpp create mode 100644 loader/source/ldr_process_manager.hpp create mode 100644 loader/source/ldr_random.cpp create mode 100644 loader/source/ldr_random.hpp create mode 100644 loader/source/ldr_registration.cpp create mode 100644 loader/source/ldr_registration.hpp create mode 100644 loader/source/ldr_ro_service.cpp create mode 100644 loader/source/ldr_ro_service.hpp create mode 100644 loader/source/ldr_shell.cpp create mode 100644 loader/source/ldr_shell.hpp create mode 100644 loader/source/lz4.c create mode 100644 loader/source/lz4.h create mode 100644 loader/source/sha256.c create mode 100644 loader/source/sha256.h create mode 100644 loader/source/sha256_armv8.s diff --git a/.gitignore b/.gitignore index 5ea49f8..13b15e6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ build/ out/ .build-tools.cson + +*.kip +*.elf \ No newline at end of file diff --git a/loader/Makefile b/loader/Makefile new file mode 100644 index 0000000..41a6cf7 --- /dev/null +++ b/loader/Makefile @@ -0,0 +1,153 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# 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 +DATA := data +INCLUDES := include +EXEFS_SRC := exefs_src + +DEFINES := -DDISABLE_IPC + +#--------------------------------------------------------------------------------- +# 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 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lstratosphere -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/../libstratosphere + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).kip $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).kip + +$(OUTPUT).kip : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/loader/loader.json b/loader/loader.json new file mode 100644 index 0000000..27544a5 --- /dev/null +++ b/loader/loader.json @@ -0,0 +1,67 @@ +{ + "name" : "Loader", + "title_id" : "0x0100000000000001", + "main_thread_stack_size" : "0x4000", + "main_thread_priority" : 49, + "default_cpu_id" : 3, + "process_category" : 1, + "kernel_capabilities" : { + "handle_table_size" : 128, + "syscalls" : { + "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", + "svcSetProcessMemoryPermission" : "0x73", + "svcMapProcessMemory" : "0x74", + "svcUnmapProcessMemory" : "0x75", + "svcMapProcessCodeMemory" : "0x77", + "svcUnmapProcessCodeMemory" : "0x78", + "svcCreateProcess" : "0x79" + } + } +} \ No newline at end of file diff --git a/loader/source/ini.c b/loader/source/ini.c new file mode 100644 index 0000000..63626c7 --- /dev/null +++ b/loader/source/ini.c @@ -0,0 +1,269 @@ +/* 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 +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* 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); +} diff --git a/loader/source/ini.h b/loader/source/ini.h new file mode 100644 index 0000000..f45ba40 --- /dev/null +++ b/loader/source/ini.h @@ -0,0 +1,130 @@ +/* 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 + +/* 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__ */ diff --git a/loader/source/ldr_cfg_service.cpp b/loader/source/ldr_cfg_service.cpp new file mode 100644 index 0000000..2e21d2f --- /dev/null +++ b/loader/source/ldr_cfg_service.cpp @@ -0,0 +1,53 @@ +#include +#include +#include "ldr_cfg_service.hpp" + +enum CfgServiceCmd { + Cfg_Cmd_Get_KeyCombo = 0, + Cfg_Cmd_Set_KeyCombo = 1, + Cfg_Cmd_Get_Override = 2, + Cfg_Cmd_Set_Override = 3, +}; + +Result LoaderConfigService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) { + Result rc = 0xF601; + + switch ((CfgServiceCmd)cmd_id) { + case Cfg_Cmd_Get_KeyCombo: + rc = WrapIpcCommandImpl<&LoaderConfigService::getCurrOverrideKeyCombo>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Cfg_Cmd_Set_KeyCombo: + rc = WrapIpcCommandImpl<&LoaderConfigService::setCurrOverrideKeyCombo>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Cfg_Cmd_Get_Override: + rc = WrapIpcCommandImpl<&LoaderConfigService::getOverrideByDefault>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Cfg_Cmd_Set_Override: + rc = WrapIpcCommandImpl<&LoaderConfigService::setOverrideByDefault>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + default: + break; + } + return rc; +} + +std::tuple LoaderConfigService::getCurrOverrideKeyCombo() { + if (g_override_key_combination == 0x00) return { 0x01, 0x00 }; + else return { 0x00, g_override_key_combination }; +} + +std::tuple LoaderConfigService::setCurrOverrideKeyCombo(u64 combo) { + g_override_key_combination = combo; + + return { 0x00, g_override_key_combination }; +} + +std::tuple LoaderConfigService::getOverrideByDefault() { + return { 0x00, g_override_by_default }; +} + +std::tuple LoaderConfigService::setOverrideByDefault(bool enabled) { + g_override_by_default = enabled; + + return { 0x00, g_override_by_default }; +} diff --git a/loader/source/ldr_cfg_service.hpp b/loader/source/ldr_cfg_service.hpp new file mode 100644 index 0000000..ab1aee6 --- /dev/null +++ b/loader/source/ldr_cfg_service.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +extern u64 g_override_key_combination; +extern bool g_override_by_default; + +class LoaderConfigService final : public IServiceObject { + public: + Result dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) override; + Result handle_deferred() override { + /* This service will never defer. */ + return 0; + } + + LoaderConfigService *clone() override { + return new LoaderConfigService(); + } + + private: + std::tuple getCurrOverrideKeyCombo(); + std::tuple setCurrOverrideKeyCombo(u64 combo); + std::tuple getOverrideByDefault(); + std::tuple setOverrideByDefault(bool enabled); +}; diff --git a/loader/source/ldr_content_management.cpp b/loader/source/ldr_content_management.cpp new file mode 100644 index 0000000..874729b --- /dev/null +++ b/loader/source/ldr_content_management.cpp @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include +#include + +#include "ldr_registration.hpp" +#include "ldr_content_management.hpp" +#include "ldr_hid.hpp" + + +#include "ini.h" + +static FsFileSystem g_CodeFileSystem = {0}; +static FsFileSystem g_HblFileSystem = {0}; + +static std::vector g_created_titles; +static bool g_has_initialized_fs_dev = false; + +/* Default to Key R, hold disables override, HBL at atmosphere/hbl.nsp. */ +static bool g_mounted_hbl_nsp = false; +static char g_hbl_sd_path[FS_MAX_PATH+1] = "@Sdcard:/atmosphere/hbl.nsp\x00"; +u64 g_override_key_combination; +bool g_override_by_default; +static u64 g_override_hbl_tid = 0x010000000000100D; + +Result ContentManagement::MountCode(u64 tid, FsStorageId sid) { + char path[FS_MAX_PATH] = {0}; + Result rc; + + /* We defer SD card mounting, so if relevant ensure it is mounted. */ + if (!g_has_initialized_fs_dev) { + TryMountSdCard(); + } + + if (ShouldOverrideContents() && R_SUCCEEDED(MountCodeNspOnSd(tid))) { + return 0x0; + } + + if (R_FAILED(rc = ResolveContentPath(path, tid, sid))) { + return rc; + } + + /* Fix up path. */ + for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) { + if (path[i] == '\\') { + path[i] = '/'; + } + } + + /* Always re-initialize fsp-ldr, in case it's closed */ + if (R_FAILED(rc = fsldrInitialize())) { + return rc; + } + + if (R_FAILED(rc = fsldrOpenCodeFileSystem(tid, path, &g_CodeFileSystem))) { + fsldrExit(); + return rc; + } + + fsdevMountDevice("code", g_CodeFileSystem); + TryMountHblNspOnSd(); + + fsldrExit(); + return rc; +} + +Result ContentManagement::UnmountCode() { + if (g_mounted_hbl_nsp) { + fsdevUnmountDevice("hbl"); + g_mounted_hbl_nsp = false; + } + fsdevUnmountDevice("code"); + return 0; +} + + +void ContentManagement::TryMountHblNspOnSd() { + char path[FS_MAX_PATH+1] = {0}; + strncpy(path, g_hbl_sd_path, FS_MAX_PATH); + for (unsigned int i = 0; i < FS_MAX_PATH && path[i] != '\x00'; i++) { + if (path[i] == '\\') { + path[i] = '/'; + } + } + if (g_has_initialized_fs_dev && !g_mounted_hbl_nsp && R_SUCCEEDED(fsOpenFileSystemWithId(&g_HblFileSystem, 0, FsFileSystemType_ApplicationPackage, path))) { + fsdevMountDevice("hbl", g_HblFileSystem); + g_mounted_hbl_nsp = true; + } +} + +Result ContentManagement::MountCodeNspOnSd(u64 tid) { + char path[FS_MAX_PATH+1] = {0}; + snprintf(path, FS_MAX_PATH, "@Sdcard:/atmosphere/titles/%016lx/exefs.nsp", tid); + Result rc = fsOpenFileSystemWithId(&g_CodeFileSystem, 0, FsFileSystemType_ApplicationPackage, path); + + if (R_SUCCEEDED(rc)) { + fsdevMountDevice("code", g_CodeFileSystem); + TryMountHblNspOnSd(); + } + + return rc; +} + +Result ContentManagement::MountCodeForTidSid(Registration::TidSid *tid_sid) { + return MountCode(tid_sid->title_id, tid_sid->storage_id); +} + +Result ContentManagement::ResolveContentPath(char *out_path, u64 tid, FsStorageId sid) { + Result rc; + LrRegisteredLocationResolver reg; + LrLocationResolver lr; + char path[FS_MAX_PATH] = {0}; + + /* Try to get the path from the registered resolver. */ + if (R_FAILED(rc = lrOpenRegisteredLocationResolver(®))) { + return rc; + } + + if (R_SUCCEEDED(rc = lrRegLrResolveProgramPath(®, tid, path))) { + strncpy(out_path, path, FS_MAX_PATH); + } else if (rc != 0x408) { + return rc; + } + + serviceClose(®.s); + if (R_SUCCEEDED(rc)) { + return rc; + } + + /* If getting the path from the registered resolver fails, fall back to the normal resolver. */ + if (R_FAILED(rc = lrOpenLocationResolver(sid, &lr))) { + return rc; + } + + if (R_SUCCEEDED(rc = lrLrResolveProgramPath(&lr, tid, path))) { + strncpy(out_path, path, FS_MAX_PATH); + } + + serviceClose(&lr.s); + + return rc; +} + +Result ContentManagement::ResolveContentPathForTidSid(char *out_path, Registration::TidSid *tid_sid) { + return ResolveContentPath(out_path, tid_sid->title_id, tid_sid->storage_id); +} + +Result ContentManagement::RedirectContentPath(const char *path, u64 tid, FsStorageId sid) { + Result rc; + LrLocationResolver lr; + + if (R_FAILED(rc = lrOpenLocationResolver(sid, &lr))) { + return rc; + } + + rc = lrLrRedirectProgramPath(&lr, tid, path); + + serviceClose(&lr.s); + + return rc; +} + +Result ContentManagement::RedirectContentPathForTidSid(const char *path, Registration::TidSid *tid_sid) { + return RedirectContentPath(path, tid_sid->title_id, tid_sid->storage_id); +} + +bool ContentManagement::HasCreatedTitle(u64 tid) { + return std::find(g_created_titles.begin(), g_created_titles.end(), tid) != g_created_titles.end(); +} + +void ContentManagement::SetCreatedTitle(u64 tid) { + if (!HasCreatedTitle(tid)) { + g_created_titles.push_back(tid); + } +} + +static int LoaderIniHandler(void *user, const char *section, const char *name, const char *value) { + /* Taken and modified, with love, from Rajkosto's implementation. */ + if (strcasecmp(section, "config") == 0) { + if (strcasecmp(name, "hbl_tid") == 0) { + u64 override_tid = strtoul(value, NULL, 16); + if (override_tid != 0) { + g_override_hbl_tid = override_tid; + } + } else if (strcasecmp(name, "hbl_path") == 0) { + while (*value == '/' || *value == '\\') { + value++; + } + snprintf(g_hbl_sd_path, FS_MAX_PATH, "@Sdcard:/%s", value); + g_hbl_sd_path[FS_MAX_PATH] = 0; + } else if (strcasecmp(name, "override_key") == 0) { + if (value[0] == '!') { + g_override_by_default = true; + value++; + } else { + g_override_by_default = false; + } + + if (strcasecmp(value, "A") == 0) { + g_override_key_combination = KEY_A; + } else if (strcasecmp(value, "B") == 0) { + g_override_key_combination = KEY_B; + } else if (strcasecmp(value, "X") == 0) { + g_override_key_combination = KEY_X; + } else if (strcasecmp(value, "Y") == 0) { + g_override_key_combination = KEY_Y; + } else if (strcasecmp(value, "LS") == 0) { + g_override_key_combination = KEY_LSTICK; + } else if (strcasecmp(value, "RS") == 0) { + g_override_key_combination = KEY_RSTICK; + } else if (strcasecmp(value, "L") == 0) { + g_override_key_combination = KEY_L; + } else if (strcasecmp(value, "R") == 0) { + g_override_key_combination = KEY_R; + } else if (strcasecmp(value, "ZL") == 0) { + g_override_key_combination = KEY_ZL; + } else if (strcasecmp(value, "ZR") == 0) { + g_override_key_combination = KEY_ZR; + } else if (strcasecmp(value, "PLUS") == 0) { + g_override_key_combination = KEY_PLUS; + } else if (strcasecmp(value, "MINUS") == 0) { + g_override_key_combination = KEY_MINUS; + } else if (strcasecmp(value, "DLEFT") == 0) { + g_override_key_combination = KEY_DLEFT; + } else if (strcasecmp(value, "DUP") == 0) { + g_override_key_combination = KEY_DUP; + } else if (strcasecmp(value, "DRIGHT") == 0) { + g_override_key_combination = KEY_DRIGHT; + } else if (strcasecmp(value, "DDOWN") == 0) { + g_override_key_combination = KEY_DDOWN; + } else if (strcasecmp(value, "SL") == 0) { + g_override_key_combination = KEY_SL; + } else if (strcasecmp(value, "SR") == 0) { + g_override_key_combination = KEY_SR; + } else { + g_override_key_combination = 0; + } + } + } else { + return 0; + } + return 1; +} + +void ContentManagement::LoadConfiguration(FILE *config) { + g_override_key_combination = KEY_R; + g_override_by_default = true; + + if (config == NULL) { + return; + } + + char *config_str = new char[0x800]; + if (config_str != NULL) { + /* Read in string. */ + std::fill(config_str, config_str + FS_MAX_PATH, 0); + fread(config_str, 1, 0x7FF, config); + config_str[strlen(config_str)] = 0x0; + + ini_parse_string(config_str, LoaderIniHandler, NULL); + + delete[] config_str; + } + + fclose(config); +} + +void ContentManagement::TryMountSdCard() { + /* Mount SD card, if psc, bus, and pcv have been created. */ + if (!g_has_initialized_fs_dev && HasCreatedTitle(0x0100000000000021) && HasCreatedTitle(0x010000000000000A) && HasCreatedTitle(0x010000000000001A)) { + Handle tmp_hnd = 0; + static const char * const required_active_services[] = {"pcv", "gpio", "pinmux", "psc:c"}; + for (unsigned int i = 0; i < sizeof(required_active_services) / sizeof(required_active_services[0]); i++) { + if (R_FAILED(smGetServiceOriginal(&tmp_hnd, smEncodeName(required_active_services[i])))) { + return; + } else { + svcCloseHandle(tmp_hnd); + } + } + + if (R_SUCCEEDED(fsdevMountSdmc())) { + ContentManagement::LoadConfiguration(fopen("sdmc:/atmosphere/loader.ini", "r")); + g_has_initialized_fs_dev = true; + } + } +} + +bool ContentManagement::ShouldReplaceWithHBL(u64 tid) { + return g_mounted_hbl_nsp && tid == g_override_hbl_tid; +} + +bool ContentManagement::ShouldOverrideContents() { + if (HasCreatedTitle(0x0100000000001000)) { + u64 kDown = 0; + bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & g_override_key_combination) != 0)); + return g_has_initialized_fs_dev && (g_override_by_default ^ keys_triggered); + } else { + /* Always redirect before qlaunch. */ + return g_has_initialized_fs_dev; + } +} diff --git a/loader/source/ldr_content_management.hpp b/loader/source/ldr_content_management.hpp new file mode 100644 index 0000000..c241092 --- /dev/null +++ b/loader/source/ldr_content_management.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#include "ldr_registration.hpp" + +class ContentManagement { + public: + static Result MountCode(u64 tid, FsStorageId sid); + static Result MountCodeNspOnSd(u64 tid); + static void TryMountHblNspOnSd(); + static Result UnmountCode(); + static Result MountCodeForTidSid(Registration::TidSid *tid_sid); + + static Result ResolveContentPath(char *out_path, u64 tid, FsStorageId sid); + static Result RedirectContentPath(const char *path, u64 tid, FsStorageId sid); + static Result ResolveContentPathForTidSid(char *out_path, Registration::TidSid *tid_sid); + static Result RedirectContentPathForTidSid(const char *path, Registration::TidSid *tid_sid); + + static bool HasCreatedTitle(u64 tid); + static void SetCreatedTitle(u64 tid); + static void LoadConfiguration(FILE *config); + static void TryMountSdCard(); + + static bool ShouldReplaceWithHBL(u64 tid); + static bool ShouldOverrideContents(); +}; diff --git a/loader/source/ldr_debug_monitor.cpp b/loader/source/ldr_debug_monitor.cpp new file mode 100644 index 0000000..a2e12e3 --- /dev/null +++ b/loader/source/ldr_debug_monitor.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include +#include "ldr_debug_monitor.hpp" +#include "ldr_launch_queue.hpp" +#include "ldr_registration.hpp" + +Result DebugMonitorService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) { + Result rc = 0xF601; + + switch ((DebugMonitorServiceCmd)cmd_id) { + case Dmnt_Cmd_AddTitleToLaunchQueue: + rc = WrapIpcCommandImpl<&DebugMonitorService::add_title_to_launch_queue>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Dmnt_Cmd_ClearLaunchQueue: + rc = WrapIpcCommandImpl<&DebugMonitorService::clear_launch_queue>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Dmnt_Cmd_GetNsoInfo: + rc = WrapIpcCommandImpl<&DebugMonitorService::get_nso_info>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + default: + break; + } + return rc; +} + +std::tuple DebugMonitorService::add_title_to_launch_queue(u64 args_size, u64 tid, InPointer args) { + fprintf(stderr, "Add to launch queue: %p, %zX\n", args.pointer, std::min(args_size, args.num_elements)); + return {LaunchQueue::add(tid, args.pointer, std::min(args_size, args.num_elements))}; +} + +std::tuple DebugMonitorService::clear_launch_queue(u64 dat) { + fprintf(stderr, "Clear launch queue: %lx\n", dat); + LaunchQueue::clear(); + return {0}; +} + +std::tuple DebugMonitorService::get_nso_info(u64 pid, OutPointerWithClientSize out) { + u32 out_num_nsos = 0; + + std::fill(out.pointer, out.pointer + out.num_elements, (const Registration::NsoInfo){0}); + + Result rc = Registration::GetNsoInfosForProcessId(out.pointer, out.num_elements, pid, &out_num_nsos); + + return {rc, out_num_nsos}; +} diff --git a/loader/source/ldr_debug_monitor.hpp b/loader/source/ldr_debug_monitor.hpp new file mode 100644 index 0000000..d1b1c4e --- /dev/null +++ b/loader/source/ldr_debug_monitor.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#include +#include "ldr_registration.hpp" + +enum DebugMonitorServiceCmd { + Dmnt_Cmd_AddTitleToLaunchQueue = 0, + Dmnt_Cmd_ClearLaunchQueue = 1, + Dmnt_Cmd_GetNsoInfo = 2 +}; + +class DebugMonitorService final : public IServiceObject { + public: + Result dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) override; + Result handle_deferred() override { + /* This service will never defer. */ + return 0; + } + + DebugMonitorService *clone() override { + return new DebugMonitorService(); + } + + private: + /* Actual commands. */ + std::tuple add_title_to_launch_queue(u64 args_size, u64 tid, InPointer args); + std::tuple clear_launch_queue(u64 dat); + std::tuple get_nso_info(u64 pid, OutPointerWithClientSize out); +}; diff --git a/loader/source/ldr_hid.cpp b/loader/source/ldr_hid.cpp new file mode 100644 index 0000000..8bad937 --- /dev/null +++ b/loader/source/ldr_hid.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include + +#include "ldr_content_management.hpp" +#include "ldr_hid.hpp" + +static bool g_has_opened_hid_session = false; + +Result HidManagement::GetKeysDown(u64 *keys) { + if (!ContentManagement::HasCreatedTitle(0x0100000000000013) || R_FAILED(hidInitialize())) { + return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID); + } + + hidScanInput(); + *keys = hidKeysDown(CONTROLLER_P1_AUTO); + + hidExit(); + return 0x0; +} \ No newline at end of file diff --git a/loader/source/ldr_hid.hpp b/loader/source/ldr_hid.hpp new file mode 100644 index 0000000..b5529fd --- /dev/null +++ b/loader/source/ldr_hid.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +class HidManagement { + public: + static Result GetKeysDown(u64 *keys); +}; diff --git a/loader/source/ldr_launch_queue.cpp b/loader/source/ldr_launch_queue.cpp new file mode 100644 index 0000000..8fda858 --- /dev/null +++ b/loader/source/ldr_launch_queue.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include +#include "ldr_launch_queue.hpp" +#include "meta_tools.hpp" + +static std::array g_launch_queue = {0}; + +Result LaunchQueue::add(u64 tid, const char *args, u64 arg_size) { + if(arg_size > LAUNCH_QUEUE_ARG_SIZE_MAX) { + return 0x209; + } + + int idx = get_free_index(tid); + if(idx == LAUNCH_QUEUE_FULL) + return 0x409; + + g_launch_queue[idx].tid = tid; + g_launch_queue[idx].arg_size = arg_size; + + std::copy(args, args + arg_size, g_launch_queue[idx].args); + return 0x0; +} + +Result LaunchQueue::add_copy(u64 tid_base, u64 tid) { + int idx = get_index(tid_base); + if (idx == LAUNCH_QUEUE_FULL) { + return 0x0; + } + + return add(tid, g_launch_queue[idx].args, g_launch_queue[idx].arg_size); +} + + +Result LaunchQueue::add_item(const LaunchItem *item) { + if(item->arg_size > LAUNCH_QUEUE_ARG_SIZE_MAX) { + return 0x209; + } + + int idx = get_free_index(item->tid); + if(idx == LAUNCH_QUEUE_FULL) + return 0x409; + + g_launch_queue[idx] = *item; + return 0x0; +} + +int LaunchQueue::get_index(u64 tid) { + auto it = std::find_if(g_launch_queue.begin(), g_launch_queue.end(), member_equals_fn(&LaunchQueue::LaunchItem::tid, tid)); + if (it == g_launch_queue.end()) { + return LAUNCH_QUEUE_FULL; + } + return std::distance(g_launch_queue.begin(), it); +} + +int LaunchQueue::get_free_index(u64 tid) { + for(unsigned int i = 0; i < LAUNCH_QUEUE_SIZE; i++) { + if(g_launch_queue[i].tid == tid || g_launch_queue[i].tid == 0x0) { + return i; + } + } + return LAUNCH_QUEUE_FULL; +} + +bool LaunchQueue::contains(u64 tid) { + return get_index(tid) != LAUNCH_QUEUE_FULL; +} + +void LaunchQueue::clear() { + for (unsigned int i = 0; i < LAUNCH_QUEUE_SIZE; i++) { + g_launch_queue[i].tid = 0; + } +} + + +LaunchQueue::LaunchItem *LaunchQueue::get_item(u64 tid) { + int idx = get_index(tid); + if (idx == LAUNCH_QUEUE_FULL) { + return NULL; + } + return &g_launch_queue[idx]; +} diff --git a/loader/source/ldr_launch_queue.hpp b/loader/source/ldr_launch_queue.hpp new file mode 100644 index 0000000..74a2a03 --- /dev/null +++ b/loader/source/ldr_launch_queue.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#define LAUNCH_QUEUE_SIZE (10) +#define LAUNCH_QUEUE_FULL (-1) + +#define LAUNCH_QUEUE_ARG_SIZE_MAX (0x8000) + +class LaunchQueue { + public: + struct LaunchItem { + u64 tid; + u64 arg_size; + char args[LAUNCH_QUEUE_ARG_SIZE_MAX]; + }; + + static LaunchQueue::LaunchItem *get_item(u64 tid); + + static Result add(u64 tid, const char *args, u64 arg_size); + static Result add_item(const LaunchItem *item); + static Result add_copy(u64 tid_base, u64 new_tid); + static int get_index(u64 tid); + static int get_free_index(u64 tid); + static bool contains(u64 tid); + static void clear(); +}; \ No newline at end of file diff --git a/loader/source/ldr_main.cpp b/loader/source/ldr_main.cpp new file mode 100644 index 0000000..8905775 --- /dev/null +++ b/loader/source/ldr_main.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include + +#include +#include + +#include "ldr_process_manager.hpp" +#include "ldr_debug_monitor.hpp" +#include "ldr_shell.hpp" +#include "ldr_ro_service.hpp" +#include "ldr_cfg_service.hpp" + +extern "C" { + extern u32 __start__; + + u32 __nx_applet_type = AppletType_None; + + #define INNER_HEAP_SIZE 0x20000 + size_t nx_inner_heap_size = INNER_HEAP_SIZE; + char nx_inner_heap[INNER_HEAP_SIZE]; + + void __libnx_initheap(void); + void __appInit(void); + void __appExit(void); + +} + + +void __libnx_initheap(void) { + void* addr = nx_inner_heap; + size_t size = nx_inner_heap_size; + + /* Newlib */ + extern char* fake_heap_start; + extern char* fake_heap_end; + + fake_heap_start = (char*)addr; + fake_heap_end = (char*)addr + size; +} + +void __appInit(void) { + Result rc; + + /* Initialize services we need (TODO: SPL) */ + rc = smInitialize(); + if (R_FAILED(rc)) { + fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_SM)); + } + + rc = fsInitialize(); + if (R_FAILED(rc)) { + fatalSimple(MAKERESULT(Module_Libnx, LibnxError_InitFail_FS)); + } + + + rc = lrInitialize(); + if (R_FAILED(rc)) { + fatalSimple(0xCAFE << 4 | 1); + } + + rc = fsldrInitialize(); + if (R_FAILED(rc)) { + fatalSimple(0xCAFE << 4 | 2); + } + + rc = splInitialize(); + if (R_FAILED(rc)) { + fatalSimple(0xCAFE << 4 | 3); + } + + /* Check for exosphere API compatibility. */ + u64 exosphere_cfg; + if (R_FAILED(splGetConfig((SplConfigItem)65000, &exosphere_cfg))) { + //fatalSimple(0xCAFE << 4 | 0xFF); + /* TODO: Does Loader need to know about target firmware/master key revision? If so, extract from exosphere_cfg. */ + } + + //splExit(); +} + +void __appExit(void) { + /* Cleanup services. */ + fsdevUnmountAll(); + fsldrExit(); + lrExit(); + fsExit(); + smExit(); +} + +int main(int argc, char **argv) { + g_override_key_combination = KEY_R; + g_override_by_default = true; + + consoleDebugInit(debugDevice_SVC); + + /* TODO: What's a good timeout value to use here? */ + auto server_manager = std::make_unique(U64_MAX); + + /* Add services to manager. */ + server_manager->add_waitable(new ServiceServer("ldr:pm", 1)); + server_manager->add_waitable(new ServiceServer("ldr:shel", 3)); + server_manager->add_waitable(new ServiceServer("ldr:dmnt", 2)); + server_manager->add_waitable(new ServiceServer("ldr:cfg", 4)); + if (!kernelAbove300()) { + /* On 1.0.0-2.3.0, Loader services ldr:ro instead of ro. */ + server_manager->add_waitable(new ServiceServer("ldr:ro", 0x20)); + } + + /* Loop forever, servicing our services. */ + server_manager->process(); + + return 0; +} diff --git a/loader/source/ldr_map.cpp b/loader/source/ldr_map.cpp new file mode 100644 index 0000000..dc9393f --- /dev/null +++ b/loader/source/ldr_map.cpp @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include + +#include "ldr_map.hpp" +#include "ldr_random.hpp" + +Result MapUtils::LocateSpaceForMap(u64 *out, u64 out_size) { + if (kernelAbove200()) { + return LocateSpaceForMapModern(out, out_size); + } else { + return LocateSpaceForMapDeprecated(out, out_size); + } +} + + +Result MapUtils::MapCodeMemoryForProcess(Handle process_h, bool is_64_bit_address_space, u64 base_address, u64 size, u64 *out_code_memory_address) { + if (kernelAbove200()) { + return MapCodeMemoryForProcessModern(process_h, base_address, size, out_code_memory_address); + } else { + return MapCodeMemoryForProcessDeprecated(process_h, is_64_bit_address_space, base_address, size, out_code_memory_address); + } +} + +Result MapUtils::LocateSpaceForMapModern(u64 *out, u64 out_size) { + MemoryInfo mem_info = {0}; + AddressSpaceInfo address_space = {0}; + u32 page_info = 0; + u64 cur_base = 0, cur_end = 0; + Result rc; + + if (R_FAILED((rc = GetAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE)))) { + return rc; + } + + cur_base = address_space.addspace_base; + + rc = 0xD001; + cur_end = cur_base + out_size; + if (cur_end <= cur_base) { + return rc; + } + + while (true) { + if (address_space.heap_size && (address_space.heap_base <= cur_end - 1 && cur_base <= address_space.heap_end - 1)) { + /* If we overlap the heap region, go to the end of the heap region. */ + if (cur_base == address_space.heap_end) { + return rc; + } + cur_base = address_space.heap_end; + } else if (address_space.map_size && (address_space.map_base <= cur_end - 1 && cur_base <= address_space.map_end - 1)) { + /* If we overlap the map region, go to the end of the map region. */ + if (cur_base == address_space.map_end) { + return rc; + } + cur_base = address_space.map_end; + } else { + if (R_FAILED(svcQueryMemory(&mem_info, &page_info, cur_base))) { + /* TODO: panic. */ + } + if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { + *out = cur_base; + return 0x0; + } + if (mem_info.addr + mem_info.size <= cur_base) { + return rc; + } + cur_base = mem_info.addr + mem_info.size; + if (cur_base >= address_space.addspace_end) { + return rc; + } + } + cur_end = cur_base + out_size; + if (cur_base + out_size <= cur_base) { + return rc; + } + } +} + + +Result MapUtils::LocateSpaceForMapDeprecated(u64 *out, u64 out_size) { + MemoryInfo mem_info = {0}; + u32 page_info = 0; + Result rc; + + u64 cur_base = 0x8000000ULL; + if (R_FAILED((rc = svcQueryMemory(&mem_info, &page_info, cur_base)))) { + return rc; + } + + rc = 0xD001; + while (true) { + if (mem_info.type == 0x10) { + return rc; + } + if (mem_info.type == 0 && mem_info.addr - cur_base + mem_info.size >= out_size) { + *out = cur_base; + return 0x0; + } + u64 mem_end = mem_info.addr + mem_info.size; + if (mem_end < cur_base) { + return rc; + } + if (mem_end >> 31) { + break; + } + cur_base = mem_end; + if (R_FAILED((rc = svcQueryMemory(&mem_info, &page_info, cur_base)))) { + return rc; + } + } + return rc; +} + +Result MapUtils::MapCodeMemoryForProcessModern(Handle process_h, u64 base_address, u64 size, u64 *out_code_memory_address) { + AddressSpaceInfo address_space = {0}; + Result rc; + + if (R_FAILED((rc = GetAddressSpaceInfo(&address_space, CUR_PROCESS_HANDLE)))) { + return rc; + } + + if (size > address_space.addspace_size) { + return 0x6609; + } + + u64 try_address; + for (unsigned int i = 0; i < 0x200; i++) { + while (true) { + try_address = address_space.addspace_base + (RandomUtils::GetRandomU64((u64)(address_space.addspace_size - size) >> 12) << 12); + if (address_space.heap_size && (address_space.heap_base <= try_address + size - 1 && try_address <= address_space.heap_end - 1)) { + continue; + } + if (address_space.map_size && (address_space.map_base <= try_address + size - 1 && try_address <= address_space.map_end - 1)) { + continue; + } + break; + } + rc = svcMapProcessCodeMemory(process_h, try_address, base_address, size); + if (rc != 0xD401) { + break; + } + } + if (R_SUCCEEDED(rc)) { + *out_code_memory_address = try_address; + } + return rc; +} + +Result MapUtils::MapCodeMemoryForProcessDeprecated(Handle process_h, bool is_64_bit_address_space, u64 base_address, u64 size, u64 *out_code_memory_address) { + Result rc; + u64 addspace_base, addspace_size; + if (is_64_bit_address_space) { + addspace_base = 0x8000000ULL; + addspace_size = 0x78000000ULL; + } else { + addspace_base = 0x200000ULL; + addspace_size = 0x3FE0000ULL; + } + + if (size > addspace_size) { + return 0x6609; + } + + u64 try_address; + for (unsigned int i = 0; i < 0x200; i++) { + try_address = addspace_base + (RandomUtils::GetRandomU64((u64)(addspace_size - size) >> 12) << 12); + rc = svcMapProcessCodeMemory(process_h, try_address, base_address, size); + if (rc != 0xD401) { + break; + } + } + if (R_SUCCEEDED(rc)) { + *out_code_memory_address = try_address; + } + return rc; +} + +Result MapUtils::GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h) { + Result rc; + if (R_FAILED((rc = svcGetInfo(&out->heap_base, 4, CUR_PROCESS_HANDLE, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->heap_size, 5, CUR_PROCESS_HANDLE, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->map_base, 2, CUR_PROCESS_HANDLE, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->map_size, 3, CUR_PROCESS_HANDLE, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->addspace_base, 12, CUR_PROCESS_HANDLE, 0)))) { + return rc; + } + if (R_FAILED((rc = svcGetInfo(&out->addspace_size, 13, CUR_PROCESS_HANDLE, 0)))) { + return rc; + } + out->heap_end = out->heap_base + out->heap_size; + out->map_end = out->map_base + out->map_size; + out->addspace_end = out->addspace_base + out->addspace_size; + return 0; +} \ No newline at end of file diff --git a/loader/source/ldr_map.hpp b/loader/source/ldr_map.hpp new file mode 100644 index 0000000..7a623b5 --- /dev/null +++ b/loader/source/ldr_map.hpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +class MapUtils { + public: + struct AddressSpaceInfo { + u64 heap_base; + u64 heap_size; + u64 heap_end; + u64 map_base; + u64 map_size; + u64 map_end; + u64 addspace_base; + u64 addspace_size; + u64 addspace_end; + }; + static Result GetAddressSpaceInfo(AddressSpaceInfo *out, Handle process_h); + static Result LocateSpaceForMapDeprecated(u64 *out, u64 out_size); + static Result LocateSpaceForMapModern(u64 *out, u64 out_size); + static Result LocateSpaceForMap(u64 *out, u64 out_size); + + + static Result MapCodeMemoryForProcessDeprecated(Handle process_h, bool is_64_bit_address_space, u64 base_address, u64 size, u64 *out_code_memory_address); + static Result MapCodeMemoryForProcessModern(Handle process_h, u64 base_address, u64 size, u64 *out_code_memory_address); + static Result MapCodeMemoryForProcess(Handle process_h, bool is_64_bit_address_space, u64 base_address, u64 size, u64 *out_code_memory_address); +}; + +class AutoCloseMap { + private: + void *mapped_address = nullptr; + u64 base_address = 0; + u64 size = 0; + Handle process_handle = 0; + public: + ~AutoCloseMap() { + Close(); + } + + void *GetMappedAddress() { + return this->mapped_address; + } + + Result Open(Handle process_h, u64 address, u64 size) { + Result rc; + u64 try_address; + if (R_FAILED(rc = MapUtils::LocateSpaceForMap(&try_address, size))) { + return rc; + } + + if (R_FAILED((rc = svcMapProcessMemory((void *)try_address, process_h, address, size)))) { + return rc; + } + + this->mapped_address = (void *)try_address; + this->process_handle = process_h; + this->base_address = address; + this->size = size; + return 0; + } + + void Close() { + if (this->mapped_address) { + if (R_FAILED(svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->base_address, this->size))) { + /* TODO: panic(). */ + } + this->mapped_address = NULL; + } + } +}; + +struct MappedCodeMemory { + Handle process_handle; + u64 base_address; + u64 size; + u64 code_memory_address; + void *mapped_address; + + bool IsActive() { + return this->code_memory_address != 0; + } + + bool IsMapped() { + return this->mapped_address != NULL; + } + + /* Utility functions. */ + Result Open(Handle process_h, bool is_64_bit_address_space, u64 address, u64 size) { + Result rc; + if (this->IsActive()) { + return 0x19009; + } + + this->process_handle = process_h; + this->base_address = address; + this->size = size; + + if (R_FAILED((rc = MapUtils::MapCodeMemoryForProcess(this->process_handle, is_64_bit_address_space, this->base_address, this->size, &this->code_memory_address)))) { + Close(); + } + return rc; + } + + Result OpenAtAddress(Handle process_h, u64 address, u64 size, u64 target_code_memory_address) { + Result rc; + if (this->IsActive()) { + return 0x19009; + } + this->process_handle = process_h; + this->base_address = address; + this->size = size; + + if (R_SUCCEEDED((rc = svcMapProcessCodeMemory(this->process_handle, target_code_memory_address, this->base_address, this->size)))) { + this->code_memory_address = target_code_memory_address; + } else { + Close(); + } + return rc; + } + + Result Map() { + Result rc; + u64 try_address; + if (this->IsMapped()) { + return 0x19009; + } + if (R_FAILED(rc = MapUtils::LocateSpaceForMap(&try_address, size))) { + return rc; + } + + if (R_FAILED((rc = svcMapProcessMemory((void *)try_address, this->process_handle, this->code_memory_address, size)))) { + return rc; + } + + this->mapped_address = (void *)try_address; + return rc; + } + + void Unmap() { + if (this->IsMapped()) { + if (R_FAILED(svcUnmapProcessMemory(this->mapped_address, this->process_handle, this->code_memory_address, this->size))) { + /* TODO: panic(). */ + } + } + this->mapped_address = NULL; + } + + void Close() { + Unmap(); + if (this->IsActive()) { + if (R_FAILED(svcUnmapProcessCodeMemory(this->process_handle, this->code_memory_address, this->base_address, this->size))) { + /* TODO: panic(). */ + } + } + *this = (const MappedCodeMemory){0}; + } +}; diff --git a/loader/source/ldr_npdm.cpp b/loader/source/ldr_npdm.cpp new file mode 100644 index 0000000..2bb9eb6 --- /dev/null +++ b/loader/source/ldr_npdm.cpp @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include "ldr_npdm.hpp" +#include "ldr_registration.hpp" +#include "ldr_content_management.hpp" + +static NpdmUtils::NpdmCache g_npdm_cache = {0}; +static NpdmUtils::NpdmCache g_original_npdm_cache = {0}; +static char g_npdm_path[FS_MAX_PATH] = {0}; + +Result NpdmUtils::LoadNpdmFromCache(u64 tid, NpdmInfo *out) { + if (g_npdm_cache.info.title_id != tid) { + return LoadNpdm(tid, out); + } + *out = g_npdm_cache.info; + return 0; +} + +FILE *NpdmUtils::OpenNpdmFromHBL() { + std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0); + snprintf(g_npdm_path, FS_MAX_PATH, "hbl:/main.npdm"); + return fopen(g_npdm_path, "rb"); +} + +FILE *NpdmUtils::OpenNpdmFromExeFS() { + std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0); + snprintf(g_npdm_path, FS_MAX_PATH, "code:/main.npdm"); + return fopen(g_npdm_path, "rb"); +} + +FILE *NpdmUtils::OpenNpdmFromSdCard(u64 title_id) { + std::fill(g_npdm_path, g_npdm_path + FS_MAX_PATH, 0); + snprintf(g_npdm_path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/exefs/main.npdm", title_id); + return fopen(g_npdm_path, "rb"); +} + + +FILE *NpdmUtils::OpenNpdm(u64 title_id) { + if (ContentManagement::ShouldOverrideContents()) { + if (ContentManagement::ShouldReplaceWithHBL(title_id)) { + return OpenNpdmFromHBL(); + } + FILE *f_out = OpenNpdmFromSdCard(title_id); + if (f_out != NULL) { + return f_out; + } + } + return OpenNpdmFromExeFS(); +} + +Result NpdmUtils::LoadNpdmInternal(FILE *f_npdm, NpdmUtils::NpdmCache *cache) { + Result rc; + + cache->info = (const NpdmUtils::NpdmInfo){0}; + + rc = 0x202; + if (f_npdm == NULL) { + /* For generic "Couldn't open the file" error, just say the file doesn't exist. */ + return rc; + } + + fseek(f_npdm, 0, SEEK_END); + size_t npdm_size = ftell(f_npdm); + fseek(f_npdm, 0, SEEK_SET); + + rc = 0x609; + if ((npdm_size > sizeof(cache->buffer)) || (fread(cache->buffer, 1, npdm_size, f_npdm) != npdm_size)) { + fclose(f_npdm); + return rc; + } + + fclose(f_npdm); + + rc = 0x809; + if (npdm_size < sizeof(NpdmUtils::NpdmHeader)) { + return rc; + } + + /* For ease of access... */ + cache->info.header = (NpdmUtils::NpdmHeader *)(cache->buffer); + NpdmInfo *info = &cache->info; + + if (info->header->magic != MAGIC_META) { + return rc; + } + + if (info->header->mmu_flags > 0xF) { + return rc; + } + + if (info->header->aci0_offset < sizeof(NpdmUtils::NpdmHeader) || info->header->aci0_size < sizeof(NpdmUtils::NpdmAci0) || info->header->aci0_offset + info->header->aci0_size > npdm_size) { + return rc; + } + + info->aci0 = (NpdmAci0 *)(cache->buffer + info->header->aci0_offset); + + if (info->aci0->magic != MAGIC_ACI0) { + return rc; + } + + if (info->aci0->fah_size > info->header->aci0_size || info->aci0->fah_offset < sizeof(NpdmUtils::NpdmAci0) || info->aci0->fah_offset + info->aci0->fah_size > info->header->aci0_size) { + return rc; + } + + info->aci0_fah = (void *)((uintptr_t)info->aci0 + info->aci0->fah_offset); + + if (info->aci0->sac_size > info->header->aci0_size || info->aci0->sac_offset < sizeof(NpdmUtils::NpdmAci0) || info->aci0->sac_offset + info->aci0->sac_size > info->header->aci0_size) { + return rc; + } + + info->aci0_sac = (void *)((uintptr_t)info->aci0 + info->aci0->sac_offset); + + if (info->aci0->kac_size > info->header->aci0_size || info->aci0->kac_offset < sizeof(NpdmUtils::NpdmAci0) || info->aci0->kac_offset + info->aci0->kac_size > info->header->aci0_size) { + return rc; + } + + info->aci0_kac = (void *)((uintptr_t)info->aci0 + info->aci0->kac_offset); + + if (info->header->acid_offset < sizeof(NpdmUtils::NpdmHeader) || info->header->acid_size < sizeof(NpdmUtils::NpdmAcid) || info->header->acid_offset + info->header->acid_size > npdm_size) { + return rc; + } + + info->acid = (NpdmAcid *)(cache->buffer + info->header->acid_offset); + + if (info->acid->magic != MAGIC_ACID) { + return rc; + } + + /* TODO: Check if retail flag is set if not development hardware. */ + + if (info->acid->fac_size > info->header->acid_size || info->acid->fac_offset < sizeof(NpdmUtils::NpdmAcid) || info->acid->fac_offset + info->acid->fac_size > info->header->acid_size) { + return rc; + } + + info->acid_fac = (void *)((uintptr_t)info->acid + info->acid->fac_offset); + + if (info->acid->sac_size > info->header->acid_size || info->acid->sac_offset < sizeof(NpdmUtils::NpdmAcid) || info->acid->sac_offset + info->acid->sac_size > info->header->acid_size) { + return rc; + } + + info->acid_sac = (void *)((uintptr_t)info->acid + info->acid->sac_offset); + + if (info->acid->kac_size > info->header->acid_size || info->acid->kac_offset < sizeof(NpdmUtils::NpdmAcid) || info->acid->kac_offset + info->acid->kac_size > info->header->acid_size) { + return rc; + } + + info->acid_kac = (void *)((uintptr_t)info->acid + info->acid->kac_offset); + + rc = 0; + return rc; +} + +Result NpdmUtils::LoadNpdm(u64 tid, NpdmInfo *out) { + Result rc; + + rc = LoadNpdmInternal(OpenNpdm(tid), &g_npdm_cache); + + if (R_FAILED(rc)) { + return rc; + } + + NpdmInfo *info = &g_npdm_cache.info; + /* Override the ACID/ACI0 title ID, in order to facilitate HBL takeover of any title. */ + info->acid->title_id_range_min = tid; + info->acid->title_id_range_max = tid; + info->aci0->title_id = tid; + + if (ContentManagement::ShouldOverrideContents() && ContentManagement::ShouldReplaceWithHBL(tid) + && R_SUCCEEDED(LoadNpdmInternal(OpenNpdmFromExeFS(), &g_original_npdm_cache))) { + NpdmInfo *original_info = &g_original_npdm_cache.info; + /* Fix pool partition. */ + if (kernelAbove500()) { + info->acid->flags = (info->acid->flags & 0xFFFFFFC3) | (original_info->acid->flags & 0x0000003C); + } + /* Fix application type. */ + const u32 original_application_type = GetApplicationType((u32 *)original_info->aci0_kac, original_info->aci0->kac_size/sizeof(u32)) & 7; + u32 *caps = (u32 *)info->aci0_kac; + for (unsigned int i = 0; i < info->aci0->kac_size/sizeof(u32); i++) { + if ((caps[i] & 0x3FFF) == 0x1FFF) { + caps[i] = (caps[i] & 0xFFFE3FFF) | (original_application_type << 14); + } + } + caps = (u32 *)info->acid_kac; + for (unsigned int i = 0; i < info->acid->kac_size/sizeof(u32); i++) { + if ((caps[i] & 0x3FFF) == 0x1FFF) { + caps[i] = (caps[i] & 0xFFFE3FFF) | (original_application_type << 14); + } + } + } + + /* We validated! */ + info->title_id = tid; + *out = *info; + rc = 0; + + return rc; +} + +Result NpdmUtils::ValidateCapabilityAgainstRestrictions(u32 *restrict_caps, size_t num_restrict_caps, u32 *&cur_cap, size_t &caps_remaining) { + Result rc = 0; + u32 desc = *cur_cap++; + caps_remaining--; + unsigned int low_bits = 0; + while (desc & 1) { + desc >>= 1; + low_bits++; + } + desc >>= 1; + u32 r_desc = 0; + switch (low_bits) { + case 3: /* Kernel flags. */ + rc = 0xCE09; + for (size_t i = 0; i < num_restrict_caps; i++) { + if ((restrict_caps[i] & 0xF) == 0x7) { + r_desc = restrict_caps[i] >> 4; + u32 highest_thread_prio = desc & 0x3F; + u32 r_highest_thread_prio = r_desc & 0x3F; + desc >>= 6; + r_desc >>= 6; + u32 lowest_thread_prio = desc & 0x3F; + u32 r_lowest_thread_prio = r_desc & 0x3F; + desc >>= 6; + r_desc >>= 6; + u32 lowest_cpu_id = desc & 0xFF; + u32 r_lowest_cpu_id = r_desc & 0xFF; + desc >>= 8; + r_desc >>= 8; + u32 highest_cpu_id = desc & 0xFF; + u32 r_highest_cpu_id = r_desc & 0xFF; + if (highest_thread_prio > r_highest_thread_prio) { + break; + } + if (lowest_thread_prio > highest_thread_prio) { + break; + } + if (lowest_thread_prio < r_lowest_thread_prio) { + break; + } + if (lowest_cpu_id < r_lowest_cpu_id) { + break; + } + if (lowest_cpu_id > r_highest_cpu_id) { + break; + } + if (highest_cpu_id > r_highest_cpu_id) { + break; + } + /* Valid! */ + rc = 0; + break; + } + } + break; + case 4: /* Syscall mask. */ + rc = 0xD009; + for (size_t i = 0; i < num_restrict_caps; i++) { + if ((restrict_caps[i] & 0x1F) == 0xF) { + r_desc = restrict_caps[i] >> 5; + u32 syscall_base = (desc >> 24); + u32 r_syscall_base = (r_desc >> 24); + if (syscall_base != r_syscall_base) { + continue; + } + u32 syscall_mask = desc & 0xFFFFFF; + u32 r_syscall_mask = r_desc & 0xFFFFFF; + if ((r_syscall_mask & syscall_mask) != syscall_mask) { + break; + } + /* Valid! */ + rc = 0; + break; + } + } + break; + case 6: /* Map IO/Normal. */ { + rc = 0xD409; + if (caps_remaining == 0) { + break; + } + u32 next_cap = *cur_cap++; + caps_remaining--; + if ((next_cap & 0x7F) != 0x3F) { + break; + } + u32 next_desc = next_cap >> 7; + u32 base_addr = desc & 0xFFFFFF; + u32 base_size = next_desc & 0xFFFFFF; + /* Size check the mapping. */ + if (base_size >> 20) { + break; + } + u32 base_end = base_addr + base_size; + /* Validate it's possible to validate this mapping. */ + if (num_restrict_caps < 2) { + break; + } + for (size_t i = 0; i < num_restrict_caps - 1; i++) { + if ((restrict_caps[i] & 0x7F) == 0x3F) { + r_desc = restrict_caps[i] >> 7; + if ((restrict_caps[i+1] & 0x7F) != 0x3F) { + break; + } + u32 r_next_desc = restrict_caps[++i] >> 7; + u32 r_base_addr = r_desc & 0xFFFFFF; + u32 r_base_size = r_next_desc & 0xFFFFFF; + /* Size check the mapping. */ + if (r_base_size >> 20) { + break; + } + u32 r_base_end = r_base_addr + r_base_size; + /* Validate is_io matches. */ + if (((r_desc >> 24) & 1) ^ ((desc >> 24) & 1)) { + continue; + } + /* Validate is_ro matches. */ + if (((r_next_desc >> 24) & 1) ^ ((next_desc >> 24) & 1)) { + continue; + } + /* Validate bounds. */ + if (base_addr < r_base_addr || base_end > r_base_end) { + continue; + } + /* Valid! */ + rc = 0; + break; + } + } + } + break; + case 7: /* Map Normal Page. */ + rc = 0xD609; + for (size_t i = 0; i < num_restrict_caps; i++) { + if ((restrict_caps[i] & 0xFF) == 0x7F) { + r_desc = restrict_caps[i] >> 8; + if (r_desc != desc) { + continue; + } + /* Valid! */ + rc = 0; + break; + } + } + break; + case 11: /* IRQ Pair. */ + rc = 0x0; + for (unsigned int irq_i = 0; irq_i < 2; irq_i++) { + u32 irq = desc & 0x3FF; + desc >>= 10; + if (irq != 0x3FF) { + bool found = false; + for (size_t i = 0; i < num_restrict_caps && !found; i++) { + if ((restrict_caps[i] & 0xFFF) == 0x7FF) { + r_desc = restrict_caps[i] >> 12; + u32 r_irq_0 = r_desc & 0x3FF; + r_desc >>= 10; + u32 r_irq_1 = r_desc & 0x3FF; + found |= irq == r_irq_0 || irq == r_irq_1; + found |= r_irq_0 == 0x3FF && r_irq_1 == 0x3FF; + } + } + if (!found) { + rc = 0xDE09; + break; + } + } + } + break; + case 13: /* App Type. */ + rc = 0xE209; + if (num_restrict_caps) { + for (size_t i = 0; i < num_restrict_caps; i++) { + if ((restrict_caps[i] & 0x3FFF) == 0x1FFF) { + r_desc = restrict_caps[i] >> 14; + break; + } + } + } else { + r_desc = 0; + } + if (desc == r_desc) { + /* Valid! */ + rc = 0; + } + break; + case 14: /* Kernel Release Version. */ + rc = 0xE409; + if (num_restrict_caps) { + for (size_t i = 0; i < num_restrict_caps; i++) { + if ((restrict_caps[i] & 0x7FFF) == 0x3FFF) { + r_desc = restrict_caps[i] >> 15; + break; + } + } + } else { + r_desc = 0; + } + if (desc == r_desc) { + /* Valid! */ + rc = 0; + } + break; + case 15: /* Handle Table Size. */ + rc = 0xE609; + for (size_t i = 0; i < num_restrict_caps; i++) { + if ((restrict_caps[i] & 0xFFFF) == 0x7FFF) { + r_desc = restrict_caps[i] >> 16; + desc &= 0x3FF; + r_desc &= 0x3FF; + if (desc > r_desc) { + break; + } + /* Valid! */ + rc = 0; + break; + } + } + break; + case 16: /* Debug Flags. */ + rc = 0xE809; + if (num_restrict_caps) { + for (size_t i = 0; i < num_restrict_caps; i++) { + if ((restrict_caps[i] & 0x1FFFF) == 0xFFFF) { + r_desc = restrict_caps[i] >> 17; + break; + } + } + } else { + r_desc = 0; + } + if ((desc & ~r_desc) == 0) { + /* Valid! */ + rc = 0; + } + break; + case 32: /* Empty Descriptor. */ + rc = 0; + break; + default: /* Unrecognized Descriptor. */ + rc = 0xC809; + break; + } + return rc; +} + +Result NpdmUtils::ValidateCapabilities(u32 *acid_caps, size_t num_acid_caps, u32 *aci0_caps, size_t num_aci0_caps) { + Result rc = 0; + size_t remaining = num_aci0_caps; + u32 *cur_cap = aci0_caps; + while (remaining) { + if (R_FAILED((rc = ValidateCapabilityAgainstRestrictions(acid_caps, num_acid_caps, cur_cap, remaining)))) { + break; + } + } + + return rc; +} + +u32 NpdmUtils::GetApplicationType(u32 *caps, size_t num_caps) { + u32 application_type = 0; + for (unsigned int i = 0; i < num_caps; i++) { + if ((caps[i] & 0x3FFF) == 0x1FFF) { + u16 app_type = (caps[i] >> 14) & 7; + if (app_type == 1) { + application_type |= 1; + } else if (app_type == 2) { + application_type |= 2; + } + } + /* After 1.0.0, allow_debug is used as bit 4. */ + if (kernelAbove200() && (caps[i] & 0x1FFFF) == 0xFFFF) { + application_type |= (caps[i] >> 15) & 4; + } + } + return application_type; +} \ No newline at end of file diff --git a/loader/source/ldr_npdm.hpp b/loader/source/ldr_npdm.hpp new file mode 100644 index 0000000..3a7e721 --- /dev/null +++ b/loader/source/ldr_npdm.hpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "ldr_registration.hpp" + +#define MAGIC_META 0x4154454D +#define MAGIC_ACI0 0x30494341 +#define MAGIC_ACID 0x44494341 + +class NpdmUtils { + public: + struct NpdmHeader { + u32 magic; + u32 _0x4; + u32 _0x8; + u8 mmu_flags; + u8 _0xD; + u8 main_thread_prio; + u8 default_cpuid; + u32 _0x10; + u32 system_resource_size; + u32 process_category; + u32 main_stack_size; + char title_name[0x50]; + u32 aci0_offset; + u32 aci0_size; + u32 acid_offset; + u32 acid_size; + }; + struct NpdmAcid { + u8 signature[0x100]; + u8 modulus[0x100]; + u32 magic; + u32 size; + u32 _0x208; + u32 flags; + u64 title_id_range_min; + u64 title_id_range_max; + u32 fac_offset; + u32 fac_size; + u32 sac_offset; + u32 sac_size; + u32 kac_offset; + u32 kac_size; + u64 padding; + }; + struct NpdmAci0 { + u32 magic; + u8 _0x4[0xC]; + u64 title_id; + u64 _0x18; + u32 fah_offset; + u32 fah_size; + u32 sac_offset; + u32 sac_size; + u32 kac_offset; + u32 kac_size; + u64 padding; + }; + struct NpdmInfo { + NpdmHeader *header; + NpdmAcid *acid; + NpdmAci0 *aci0; + void *acid_fac; + void *acid_sac; + void *acid_kac; + void *aci0_fah; + void *aci0_sac; + void *aci0_kac; + u64 title_id; + }; + struct NpdmCache { + NpdmInfo info; + u8 buffer[0x8000]; + }; + + static_assert(sizeof(NpdmHeader) == 0x80, "Incorrectly defined NpdmHeader!"); + static_assert(sizeof(NpdmAcid) == 0x240, "Incorrectly defined NpdmAcid!"); + static_assert(sizeof(NpdmAci0) == 0x40, "Incorrectly defined NpdmAci0!"); + + static u32 GetApplicationType(u32 *caps, size_t num_caps); + + static Result ValidateCapabilityAgainstRestrictions(u32 *restrict_caps, size_t num_restrict_caps, u32 *&cur_cap, size_t &caps_remaining); + static Result ValidateCapabilities(u32 *acid_caps, size_t num_acid_caps, u32 *aci0_caps, size_t num_aci0_caps); + + + static FILE *OpenNpdmFromHBL(); + static FILE *OpenNpdmFromExeFS(); + static FILE *OpenNpdmFromSdCard(u64 tid); + static FILE *OpenNpdm(u64 tid); + static Result LoadNpdm(u64 tid, NpdmInfo *out); + static Result LoadNpdmFromCache(u64 tid, NpdmInfo *out); + private: + static Result LoadNpdmInternal(FILE *f_npdm, NpdmCache *cache); +}; \ No newline at end of file diff --git a/loader/source/ldr_nro.cpp b/loader/source/ldr_nro.cpp new file mode 100644 index 0000000..06d93a4 --- /dev/null +++ b/loader/source/ldr_nro.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include +#include +#include "sha256.h" +#include "ldr_nro.hpp" +#include "ldr_registration.hpp" +#include "ldr_map.hpp" +#include "ldr_random.hpp" + +Result NroUtils::ValidateNrrHeader(NrrHeader *header, u64 size, u64 title_id_min) { + if (header->magic != MAGIC_NRR0) { + return 0x6A09; + } + if (header->nrr_size != size) { + return 0xA409; + } + + /* TODO: Check NRR signature. */ + if (false) { + return 0x6C09; + } + + if (header->title_id_min != title_id_min) { + return 0x6A09; + } + + return 0x0; +} + +Result NroUtils::LoadNro(Registration::Process *target_proc, Handle process_h, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size, u64 *out_address) { + NroHeader *nro; + MappedCodeMemory mcm_nro = {0}; + MappedCodeMemory mcm_bss = {0}; + unsigned int i; + Result rc; + u8 nro_hash[0x20]; + struct sha256_state sha_ctx; + /* Ensure there is an available NRO slot. */ + if (std::all_of(target_proc->nro_infos.begin(), target_proc->nro_infos.end(), std::mem_fn(&Registration::NroInfo::in_use))) { + return 0x6E09; + } + for (i = 0; i < 0x200; i++) { + if (R_SUCCEEDED(mcm_nro.Open(process_h, target_proc->is_64_bit_addspace, nro_heap_address, nro_heap_size))) { + if (R_SUCCEEDED(mcm_bss.OpenAtAddress(process_h, bss_heap_address, bss_heap_size, mcm_nro.code_memory_address + nro_heap_size))) { + break; + } else { + mcm_nro.Close(); + } + } + } + if (i >= 0x200) { + return 0x6609; + } + if (R_FAILED((rc = mcm_nro.Map()))) { + goto LOAD_NRO_END; + } + + nro = (NroHeader *)mcm_nro.mapped_address; + if (nro->magic != MAGIC_NRO0) { + rc = 0x6809; + goto LOAD_NRO_END; + } + if (nro->nro_size != nro_heap_size || nro->bss_size != bss_heap_size) { + rc = 0x6809; + goto LOAD_NRO_END; + } + if ((nro->text_size & 0xFFF) || (nro->ro_size & 0xFFF) || (nro->rw_size & 0xFFF) || (nro->bss_size & 0xFFF)) { + rc = 0x6809; + goto LOAD_NRO_END; + } + if (nro->text_offset != 0 || nro->text_offset + nro->text_size != nro->ro_offset || nro->ro_offset + nro->ro_size != nro->rw_offset || nro->rw_offset + nro->rw_size != nro->nro_size) { + rc = 0x6809; + goto LOAD_NRO_END; + } + + + sha256_init(&sha_ctx); + sha256_update(&sha_ctx, (u8 *)nro, nro->nro_size); + sha256_finalize(&sha_ctx); + sha256_finish(&sha_ctx, nro_hash); + + if (!Registration::IsNroHashPresent(target_proc->index, nro_hash)) { + rc = 0x6C09; + goto LOAD_NRO_END; + } + + if (Registration::IsNroAlreadyLoaded(target_proc->index, nro_hash)) { + rc = 0x7209; + goto LOAD_NRO_END; + } + + if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address, nro->text_size, 5)))) { + goto LOAD_NRO_END; + } + + if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address + nro->ro_offset, nro->ro_size, 1)))) { + goto LOAD_NRO_END; + } + + if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, mcm_nro.code_memory_address + nro->rw_offset, nro->rw_size + nro->bss_size, 3)))) { + goto LOAD_NRO_END; + } + + Registration::AddNroToProcess(target_proc->index, &mcm_nro, &mcm_bss, nro->text_size, nro->ro_size, nro->rw_size, nro->build_id); + *out_address = mcm_nro.code_memory_address; + mcm_nro.Unmap(); + mcm_bss.Unmap(); + rc = 0x0; + +LOAD_NRO_END: + if (R_FAILED(rc)) { + mcm_nro.Close(); + mcm_bss.Close(); + } + return 0x0; +} diff --git a/loader/source/ldr_nro.hpp b/loader/source/ldr_nro.hpp new file mode 100644 index 0000000..1d0cdf5 --- /dev/null +++ b/loader/source/ldr_nro.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "ldr_registration.hpp" +#define MAGIC_NRO0 0x304F524E +#define MAGIC_NRR0 0x3052524E + +class NroUtils { + public: + struct NrrHeader { + u32 magic; + u32 _0x4; + u32 _0x8; + u32 _0xC; + u64 title_id_mask; + u64 title_id_pattern; + u64 _0x20; + u64 _0x28; + u8 modulus[0x100]; + u8 fixed_key_signature[0x100]; + u8 nrr_signature[0x100]; + u64 title_id_min; + u32 nrr_size; + u32 _0x33C; + u32 hash_offset; + u32 num_hashes; + u64 _0x348; + }; + + struct NroHeader { + u32 entrypoint_insn; + u32 mod_offset; + u64 padding; + u32 magic; + u32 _0x14; + u32 nro_size; + u32 _0x1C; + u32 text_offset; + u32 text_size; + u32 ro_offset; + u32 ro_size; + u32 rw_offset; + u32 rw_size; + u32 bss_size; + u32 _0x3C; + unsigned char build_id[0x20]; + u8 _0x60[0x20]; + }; + + + static_assert(sizeof(NrrHeader) == 0x350, "Incorrectly defined NrrHeader!"); + static_assert(sizeof(NroHeader) == 0x80, "Incorrectly defined NroHeader!"); + static Result ValidateNrrHeader(NrrHeader *header, u64 size, u64 title_id_min); + static Result LoadNro(Registration::Process *target_proc, Handle process_h, u64 nro_heap_address, u64 nro_heap_size, u64 bss_heap_address, u64 bss_heap_size, u64 *out_address); +}; \ No newline at end of file diff --git a/loader/source/ldr_nso.cpp b/loader/source/ldr_nso.cpp new file mode 100644 index 0000000..451093e --- /dev/null +++ b/loader/source/ldr_nso.cpp @@ -0,0 +1,356 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include +#include "sha256.h" +#include "lz4.h" +#include "ldr_nso.hpp" +#include "ldr_map.hpp" +#include "ldr_random.hpp" +#include "ldr_patcher.hpp" +#include "ldr_content_management.hpp" + +static NsoUtils::NsoHeader g_nso_headers[NSO_NUM_MAX] = {0}; +static bool g_nso_present[NSO_NUM_MAX] = {0}; + +static char g_nso_path[FS_MAX_PATH] = {0}; + +FILE *NsoUtils::OpenNsoFromHBL(unsigned int index) { + std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0); + snprintf(g_nso_path, FS_MAX_PATH, "hbl:/%s", NsoUtils::GetNsoFileName(index)); + return fopen(g_nso_path, "rb"); +} + +FILE *NsoUtils::OpenNsoFromExeFS(unsigned int index) { + std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0); + snprintf(g_nso_path, FS_MAX_PATH, "code:/%s", NsoUtils::GetNsoFileName(index)); + return fopen(g_nso_path, "rb"); +} + +FILE *NsoUtils::OpenNsoFromSdCard(unsigned int index, u64 title_id) { + std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0); + snprintf(g_nso_path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/exefs/%s", title_id, NsoUtils::GetNsoFileName(index)); + return fopen(g_nso_path, "rb"); +} + +bool NsoUtils::CheckNsoStubbed(unsigned int index, u64 title_id) { + std::fill(g_nso_path, g_nso_path + FS_MAX_PATH, 0); + snprintf(g_nso_path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/exefs/%s.stub", title_id, NsoUtils::GetNsoFileName(index)); + FILE *f = fopen(g_nso_path, "rb"); + bool ret = (f != NULL); + if (ret) { + fclose(f); + } + return ret; +} + +FILE *NsoUtils::OpenNso(unsigned int index, u64 title_id) { + if (ContentManagement::ShouldOverrideContents()) { + if (ContentManagement::ShouldReplaceWithHBL(title_id)) { + return OpenNsoFromHBL(index); + } + FILE *f_out = OpenNsoFromSdCard(index, title_id); + if (f_out != NULL) { + return f_out; + } else if (CheckNsoStubbed(index, title_id)) { + return NULL; + } + } + return OpenNsoFromExeFS(index); +} + +bool NsoUtils::IsNsoPresent(unsigned int index) { + return g_nso_present[index]; +} + + +unsigned char *NsoUtils::GetNsoBuildId(unsigned int index) { + if (g_nso_present[index]) { + return g_nso_headers[index].build_id; + } + return NULL; +} + +Result NsoUtils::LoadNsoHeaders(u64 title_id) { + FILE *f_nso; + + /* Zero out the cache. */ + std::fill(g_nso_present, g_nso_present + NSO_NUM_MAX, false); + std::fill(g_nso_headers, g_nso_headers + NSO_NUM_MAX, (const NsoUtils::NsoHeader &){0}); + + for (unsigned int i = 0; i < NSO_NUM_MAX; i++) { + f_nso = OpenNso(i, title_id); + if (f_nso != NULL) { + if (fread(&g_nso_headers[i], 1, sizeof(NsoUtils::NsoHeader), f_nso) != sizeof(NsoUtils::NsoHeader)) { + return 0xA09; + } + g_nso_present[i] = true; + fclose(f_nso); + f_nso = NULL; + continue; + } + if (1 < i && i < 12) { + /* If we failed to open a subsdk, there are no more subsdks. */ + i = 11; + } + } + + return 0x0; +} + +Result NsoUtils::ValidateNsoLoadSet() { + /* We *must* have a "main" NSO. */ + if (!g_nso_present[1]) { + return 0xA09; + } + + /* Behavior switches depending on whether we have an rtld. */ + if (g_nso_present[0]) { + /* If we have an rtld, dst offset for .text must be 0 for all other NSOs. */ + for (unsigned int i = 0; i < NSO_NUM_MAX; i++) { + if (g_nso_present[i] && g_nso_headers[i].segments[0].dst_offset != 0) { + return 0xA09; + } + } + } else { + /* If we don't have an rtld, we must ONLY have a main. */ + for (unsigned int i = 2; i < NSO_NUM_MAX; i++) { + if (g_nso_present[i]) { + return 0xA09; + } + } + /* That main's .text must be at dst_offset 0. */ + if (g_nso_headers[1].segments[0].dst_offset != 0) { + return 0xA09; + } + } + + return 0x0; +} + + +Result NsoUtils::CalculateNsoLoadExtents(u32 addspace_type, u32 args_size, NsoLoadExtents *extents) { + *extents = (const NsoUtils::NsoLoadExtents){0}; + /* Calculate base offsets. */ + for (unsigned int i = 0; i < NSO_NUM_MAX; i++) { + if (g_nso_present[i]) { + extents->nso_addresses[i] = extents->total_size; + u32 text_end = g_nso_headers[i].segments[0].dst_offset + g_nso_headers[i].segments[0].decomp_size; + u32 ro_end = g_nso_headers[i].segments[1].dst_offset + g_nso_headers[i].segments[1].decomp_size; + u32 rw_end = g_nso_headers[i].segments[2].dst_offset + g_nso_headers[i].segments[2].decomp_size + g_nso_headers[i].segments[2].align_or_total_size; + extents->nso_sizes[i] = text_end; + if (extents->nso_sizes[i] < ro_end) { + extents->nso_sizes[i] = ro_end; + } + if (extents->nso_sizes[i] < rw_end) { + extents->nso_sizes[i] = rw_end; + } + extents->nso_sizes[i] += 0xFFF; + extents->nso_sizes[i] &= ~0xFFFULL; + extents->total_size += extents->nso_sizes[i]; + if (args_size && !extents->args_size) { + extents->args_address = extents->total_size; + /* What the fuck? Where does 0x9007 come from? */ + extents->args_size = (2 * args_size + 0x9007); + extents->args_size &= ~0xFFFULL; + extents->total_size += extents->args_size; + } + } + } + + /* Calculate ASLR extents for address space type. */ + u64 addspace_start, addspace_size; + if (kernelAbove200()) { + switch (addspace_type & 0xE) { + case 0: + case 4: + addspace_start = 0x200000ULL; + addspace_size = 0x3FE00000ULL; + break; + case 2: + addspace_start = 0x8000000ULL; + addspace_size = 0x78000000ULL; + break; + case 6: + addspace_start = 0x8000000ULL; + addspace_size = 0x7FF8000000ULL; + break; + default: + /* TODO: Panic. */ + return 0xD001; + } + } else { + if (addspace_type & 2) { + addspace_start = 0x8000000ULL; + addspace_size = 0x78000000ULL; + } else { + addspace_start = 0x200000ULL; + addspace_size = 0x3FE00000ULL; + } + } + if (extents->total_size > addspace_size) { + return 0xD001; + } + + u64 aslr_slide = 0; + if (addspace_type & 0x20) { + aslr_slide = RandomUtils::GetRandomU64((addspace_size - extents->total_size) >> 21) << 21; + } + + extents->base_address = addspace_start + aslr_slide; + for (unsigned int i = 0; i < NSO_NUM_MAX; i++) { + if (g_nso_present[i]) { + extents->nso_addresses[i] += extents->base_address; + } + } + if (extents->args_address) { + extents->args_address += extents->base_address; + } + + return 0x0; +} + + + +Result NsoUtils::LoadNsoSegment(u64 title_id, unsigned int index, unsigned int segment, FILE *f_nso, u8 *map_base, u8 *map_end) { + bool is_compressed = ((g_nso_headers[index].flags >> segment) & 1) != 0; + bool check_hash = ((g_nso_headers[index].flags >> (segment + 3)) & 1) != 0; + size_t out_size = g_nso_headers[index].segments[segment].decomp_size; + size_t size = is_compressed ? g_nso_headers[index].compressed_sizes[segment] : out_size; + if (size > out_size) { + return 0xA09; + } + if ((u32)(size | out_size) >> 31) { + return 0xA09; + } + + u8 *dst_addr = map_base + g_nso_headers[index].segments[segment].dst_offset; + u8 *load_addr = is_compressed ? map_end - size : dst_addr; + + + fseek(f_nso, g_nso_headers[index].segments[segment].file_offset, SEEK_SET); + if (fread(load_addr, 1, size, f_nso) != size) { + return 0xA09; + } + + + if (is_compressed) { + if (LZ4_decompress_safe((char *)load_addr, (char *)dst_addr, size, out_size) != (int)out_size) { + return 0xA09; + } + } + + + if (check_hash) { + u8 hash[0x20] = {0}; + struct sha256_state sha_ctx; + sha256_init(&sha_ctx); + sha256_update(&sha_ctx, dst_addr, out_size); + sha256_finalize(&sha_ctx); + sha256_finish(&sha_ctx, hash); + + if (std::memcmp(g_nso_headers[index].section_hashes[segment], hash, sizeof(hash))) { + return 0xA09; + } + } + + return 0x0; +} + +Result NsoUtils::LoadNsosIntoProcessMemory(Handle process_h, u64 title_id, NsoLoadExtents *extents, u8 *args, u32 args_size) { + Result rc = 0xA09; + for (unsigned int i = 0; i < NSO_NUM_MAX; i++) { + if (g_nso_present[i]) { + AutoCloseMap nso_map; + if (R_FAILED((rc = nso_map.Open(process_h, extents->nso_addresses[i], extents->nso_sizes[i])))) { + return rc; + } + + u8 *map_base = (u8 *)nso_map.GetMappedAddress(); + + FILE *f_nso = OpenNso(i, title_id); + if (f_nso == NULL) { + /* Is there a better error to return here? */ + return 0xA09; + } + for (unsigned int seg = 0; seg < 3; seg++) { + if (R_FAILED((rc = LoadNsoSegment(title_id, i, seg, f_nso, map_base, map_base + extents->nso_sizes[i])))) { + fclose(f_nso); + return rc; + } + } + + fclose(f_nso); + f_nso = NULL; + /* Zero out memory before .text. */ + u64 text_base = 0, text_start = g_nso_headers[i].segments[0].dst_offset; + std::fill(map_base + text_base, map_base + text_start, 0); + /* Zero out memory before .rodata. */ + u64 ro_base = text_start + g_nso_headers[i].segments[0].decomp_size, ro_start = g_nso_headers[i].segments[1].dst_offset; + std::fill(map_base + ro_base, map_base + ro_start, 0); + /* Zero out memory before .rwdata. */ + u64 rw_base = ro_start + g_nso_headers[i].segments[1].decomp_size, rw_start = g_nso_headers[i].segments[2].dst_offset; + std::fill(map_base + rw_base, map_base + rw_start, 0); + /* Zero out .bss. */ + u64 bss_base = rw_start + g_nso_headers[i].segments[2].decomp_size, bss_size = g_nso_headers[i].segments[2].align_or_total_size; + std::fill(map_base + bss_base, map_base + bss_base + bss_size, 0); + + /* Apply patches to loaded module. */ + PatchUtils::ApplyPatches(&g_nso_headers[i], map_base, bss_base); + + nso_map.Close(); + + for (unsigned int seg = 0; seg < 3; seg++) { + u64 size = g_nso_headers[i].segments[seg].decomp_size; + if (seg == 2) { + size += g_nso_headers[i].segments[2].align_or_total_size; + } + size += 0xFFF; + size &= ~0xFFFULL; + const static unsigned int segment_perms[3] = {5, 1, 3}; + if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, extents->nso_addresses[i] + g_nso_headers[i].segments[seg].dst_offset, size, segment_perms[seg])))) { + return rc; + } + } + } + } + + /* Map in arguments. */ + if (args != NULL && args_size) { + AutoCloseMap args_map; + if (R_FAILED((rc = args_map.Open(process_h, extents->args_address, extents->args_size)))) { + return rc; + } + + NsoArgument *arg_map_base = (NsoArgument *)args_map.GetMappedAddress(); + + arg_map_base->allocated_space = extents->args_size; + arg_map_base->args_size = args_size; + std::fill(arg_map_base->_0x8, arg_map_base->_0x8 + sizeof(arg_map_base->_0x8), 0); + std::copy(args, args + args_size, arg_map_base->arguments); + + args_map.Close(); + + if (R_FAILED((rc = svcSetProcessMemoryPermission(process_h, extents->args_address, extents->args_size, 3)))) { + return rc; + } + } + + return rc; +} diff --git a/loader/source/ldr_nso.hpp b/loader/source/ldr_nso.hpp new file mode 100644 index 0000000..3649433 --- /dev/null +++ b/loader/source/ldr_nso.hpp @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#define MAGIC_NSO0 0x304F534E +#define NSO_NUM_MAX 13 + +class NsoUtils { + public: + struct NsoSegment { + u32 file_offset; + u32 dst_offset; + u32 decomp_size; + u32 align_or_total_size; + }; + + struct NsoHeader { + u32 magic; + u32 _0x4; + u32 _0x8; + u32 flags; + NsoSegment segments[3]; + u8 build_id[0x20]; + u32 compressed_sizes[3]; + u8 _0x6C[0x24]; + u64 dynstr_extents; + u64 dynsym_extents; + u8 section_hashes[3][0x20]; + }; + + struct NsoLoadExtents { + u64 base_address; + u64 total_size; + u64 args_address; + u64 args_size; + u64 nso_addresses[NSO_NUM_MAX]; + u64 nso_sizes[NSO_NUM_MAX]; + }; + + struct NsoArgument { + u32 allocated_space; + u32 args_size; + u8 _0x8[0x18]; + u8 arguments[]; + }; + + + static_assert(sizeof(NsoHeader) == 0x100, "Incorrectly defined NsoHeader!"); + + static const char *GetNsoFileName(unsigned int index) { + switch (index) { + case 0: + return "rtld"; + case 1: + return "main"; + case 2: + return "subsdk0"; + case 3: + return "subsdk1"; + case 4: + return "subsdk2"; + case 5: + return "subsdk3"; + case 6: + return "subsdk4"; + case 7: + return "subsdk5"; + case 8: + return "subsdk6"; + case 9: + return "subsdk7"; + case 10: + return "subsdk8"; + case 11: + return "subsdk9"; + case 12: + return "sdk"; + default: + /* TODO: Panic. */ + return "?"; + } + } + + static FILE *OpenNsoFromHBL(unsigned int index); + static FILE *OpenNsoFromExeFS(unsigned int index); + static FILE *OpenNsoFromSdCard(unsigned int index, u64 title_id); + static bool CheckNsoStubbed(unsigned int index, u64 title_id); + static FILE *OpenNso(unsigned int index, u64 title_id); + + static bool IsNsoPresent(unsigned int index); + static unsigned char *GetNsoBuildId(unsigned int index); + static Result LoadNsoHeaders(u64 title_id); + static Result ValidateNsoLoadSet(); + static Result CalculateNsoLoadExtents(u32 addspace_type, u32 args_size, NsoLoadExtents *extents); + + static Result LoadNsoSegment(u64 title_id, unsigned int index, unsigned int segment, FILE *f_nso, u8 *map_base, u8 *map_end); + static Result LoadNsosIntoProcessMemory(Handle process_h, u64 title_id, NsoLoadExtents *extents, u8 *args, u32 args_size); +}; \ No newline at end of file diff --git a/loader/source/ldr_patcher.cpp b/loader/source/ldr_patcher.cpp new file mode 100644 index 0000000..5fcbd5d --- /dev/null +++ b/loader/source/ldr_patcher.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include "ldr_patcher.hpp" + +/* IPS Patching adapted from Luma3DS (https://github.com/AuroraWright/Luma3DS/blob/master/sysmodules/loader/source/patcher.c) */ + +#define IPS_MAGIC "PATCH" +#define IPS_TAIL "EOF" + +#define IPS32_MAGIC "IPS32" +#define IPS32_TAIL "EEOF" + +static inline u8 HexNybbleToU8(const char nybble) { + if ('0' <= nybble && nybble <= '9') { + return nybble - '0'; + } else if ('a' <= nybble && nybble <= 'f') { + return nybble - 'a' + 0xa; + } else { + return nybble - 'A' + 0xA; + } +} + +static bool MatchesBuildId(const char *name, size_t name_len, const u8 *build_id) { + /* Validate name is hex build id. */ + for (unsigned int i = 0; i < name_len - 4; i++) { + if (isxdigit(name[i]) == 0) { + return false; + } + } + + /* Read build id from name. */ + u8 build_id_from_name[0x20] = {0}; + for (unsigned int name_ofs = 0, id_ofs = 0; name_ofs < name_len - 4; id_ofs++) { + build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]) << 4; + build_id_from_name[id_ofs] |= HexNybbleToU8(name[name_ofs++]); + } + + return memcmp(build_id, build_id_from_name, sizeof(build_id_from_name)) == 0; +} + +static void ApplyIpsPatch(u8 *mapped_nso, size_t mapped_size, bool is_ips32, FILE *f_ips) { + u8 buffer[4]; + while (fread(buffer, is_ips32 ? 4 : 3, 1, f_ips) == 1) { + if (is_ips32 && memcmp(buffer, IPS32_TAIL, 4) == 0) { + break; + } else if (!is_ips32 && memcmp(buffer, IPS_TAIL, 3) == 0) { + break; + } + + /* Offset of patch. */ + u32 patch_offset; + if (is_ips32) { + patch_offset = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]; + } else { + patch_offset = (buffer[0] << 16) | (buffer[1] << 8) | (buffer[2]); + } + + /* Size of patch. */ + if (fread(buffer, 2, 1, f_ips) != 1) { + break; + } + u32 patch_size = (buffer[0] << 8) | (buffer[1]); + + /* Check for RLE encoding. */ + if (patch_size == 0) { + /* Size of RLE. */ + if (fread(buffer, 2, 1, f_ips) != 1) { + break; + } + + u32 rle_size = (buffer[0] << 8) | (buffer[1]); + + /* Value for RLE. */ + if (fread(buffer, 1, 1, f_ips) != 1) { + break; + } + + if (patch_offset < sizeof(NsoUtils::NsoHeader)) { + if (patch_offset + rle_size > sizeof(NsoUtils::NsoHeader)) { + u32 diff = sizeof(NsoUtils::NsoHeader) - patch_offset; + patch_offset += diff; + rle_size -= diff; + goto IPS_RLE_PATCH_OFFSET_WITHIN_BOUNDS; + } + } else { + IPS_RLE_PATCH_OFFSET_WITHIN_BOUNDS: + patch_offset -= sizeof(NsoUtils::NsoHeader); + if (patch_offset + rle_size > mapped_size) { + rle_size = mapped_size - patch_offset; + } + memset(mapped_nso + patch_offset, buffer[0], rle_size); + } + } else { + if (patch_offset < sizeof(NsoUtils::NsoHeader)) { + if (patch_offset + patch_size > sizeof(NsoUtils::NsoHeader)) { + u32 diff = sizeof(NsoUtils::NsoHeader) - patch_offset; + patch_offset += diff; + patch_size -= diff; + fseek(f_ips, diff, SEEK_CUR); + goto IPS_DATA_PATCH_OFFSET_WITHIN_BOUNDS; + } else { + fseek(f_ips, patch_size, SEEK_CUR); + } + } else { + IPS_DATA_PATCH_OFFSET_WITHIN_BOUNDS: + patch_offset -= sizeof(NsoUtils::NsoHeader); + u32 read_size = patch_size; + if (patch_offset + read_size > mapped_size) { + read_size = mapped_size - patch_offset; + } + if (fread(mapped_nso + patch_offset, read_size, 1, f_ips) != 1) { + break; + } + if (patch_size > read_size) { + fseek(f_ips, patch_size - read_size, SEEK_CUR); + } + } + } + } +} + +void PatchUtils::ApplyPatches(const NsoUtils::NsoHeader *header, u8 *mapped_nso, size_t mapped_size) { + /* Inspect all patches from /atmosphere/exefs_patches/<*>/<*>.ips */ + char path[FS_MAX_PATH+1] = {0}; + snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/exefs_patches"); + DIR *patches_dir = opendir(path); + struct dirent *pdir_ent; + if (patches_dir != NULL) { + /* Iterate over the patches directory to find patch subdirectories. */ + while ((pdir_ent = readdir(patches_dir)) != NULL) { + if (strcmp(pdir_ent->d_name, ".") == 0 || strcmp(pdir_ent->d_name, "..") == 0) { + continue; + } + snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/exefs_patches/%s", pdir_ent->d_name); + DIR *patch_dir = opendir(path); + struct dirent *ent; + if (patch_dir != NULL) { + /* Iterate over the patch subdirectory to find .ips patches. */ + while ((ent = readdir(patch_dir)) != NULL) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { + continue; + } + size_t name_len = strlen(ent->d_name); + if ((4 < name_len && name_len <= 0x44) && ((name_len & 1) == 0) && strcmp(ent->d_name + name_len - 4, ".ips") == 0 && MatchesBuildId(ent->d_name, name_len, header->build_id)) { + snprintf(path, sizeof(path) - 1, "sdmc:/atmosphere/exefs_patches/%s/%s", pdir_ent->d_name, ent->d_name); + FILE *f_ips = fopen(path, "rb"); + if (f_ips != NULL) { + u8 header[5]; + if (fread(header, 5, 1, f_ips) == 1) { + if (memcmp(header, IPS_MAGIC, 5) == 0) { + ApplyIpsPatch(mapped_nso, mapped_size, false, f_ips); + } else if (memcmp(header, IPS32_MAGIC, 5) == 0) { + ApplyIpsPatch(mapped_nso, mapped_size, true, f_ips); + } + } + fclose(f_ips); + } + } + } + closedir(patch_dir); + } + } + closedir(patches_dir); + } +} \ No newline at end of file diff --git a/loader/source/ldr_patcher.hpp b/loader/source/ldr_patcher.hpp new file mode 100644 index 0000000..993ba27 --- /dev/null +++ b/loader/source/ldr_patcher.hpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "ldr_nso.hpp" + +class PatchUtils { + public: + static void ApplyPatches(const NsoUtils::NsoHeader *header, u8 *mapped_nso, size_t size); +}; \ No newline at end of file diff --git a/loader/source/ldr_process_creation.cpp b/loader/source/ldr_process_creation.cpp new file mode 100644 index 0000000..40d9d71 --- /dev/null +++ b/loader/source/ldr_process_creation.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include + +#include "ldr_process_creation.hpp" +#include "ldr_registration.hpp" +#include "ldr_launch_queue.hpp" +#include "ldr_content_management.hpp" +#include "ldr_npdm.hpp" +#include "ldr_nso.hpp" + +Result ProcessCreation::InitializeProcessInfo(NpdmUtils::NpdmInfo *npdm, Handle reslimit_h, u64 arg_flags, ProcessInfo *out_proc_info) { + /* Initialize a ProcessInfo using an npdm. */ + *out_proc_info = (const ProcessCreation::ProcessInfo){0}; + + /* Copy all but last char of name, insert NULL terminator. */ + std::copy(npdm->header->title_name, npdm->header->title_name + sizeof(out_proc_info->name) - 1, out_proc_info->name); + out_proc_info->name[sizeof(out_proc_info->name) - 1] = 0; + + /* Set title id. */ + out_proc_info->title_id = npdm->aci0->title_id; + + /* Set process category. */ + out_proc_info->process_category = npdm->header->process_category; + + /* Copy reslimit handle raw. */ + out_proc_info->reslimit_h = reslimit_h; + + /* Set IsAddressSpace64Bit, AddressSpaceType. */ + if (npdm->header->mmu_flags & 8) { + /* Invalid Address Space Type. */ + return 0x809; + } + out_proc_info->process_flags = (npdm->header->mmu_flags & 0xF); + /* Set Bit 4 (?) and EnableAslr based on argument flags. */ + out_proc_info->process_flags |= ((arg_flags & 3) << 4) ^ 0x20; + /* Set UseSystemMemBlocks if application type is 1. */ + u32 application_type = NpdmUtils::GetApplicationType((u32 *)npdm->aci0_kac, npdm->aci0->kac_size / sizeof(u32)); + if ((application_type & 3) == 1) { + out_proc_info->process_flags |= 0x40; + } + + /* 3.0.0+ System Resource Size. */ + if (kernelAbove300()) { + if (npdm->header->system_resource_size & 0x1FFFFF) { + return 0xA409; + } + if (npdm->header->system_resource_size) { + if ((out_proc_info->process_flags & 6) == 0) { + return 0x809; + } + if (!(((application_type & 3) == 1) || (kernelAbove600() && (application_type & 3) == 2))) { + return 0x809; + } + if (npdm->header->system_resource_size > 0x1FE00000) { + return 0x809; + } + } + out_proc_info->system_resource_num_pages = npdm->header->system_resource_size >> 12; + } else { + out_proc_info->system_resource_num_pages = 0; + } + + /* 5.0.0+ Pool Partition. */ + if (kernelAbove500()) { + u32 pool_partition_id = (npdm->acid->flags >> 2) & 0xF; + switch (pool_partition_id) { + case 0: /* Application. */ + if ((application_type & 3) == 2) { + out_proc_info->process_flags |= 0x80; + } + break; + case 1: /* Applet. */ + out_proc_info->process_flags |= 0x80; + break; + case 2: /* Sysmodule. */ + out_proc_info->process_flags |= 0x100; + break; + case 3: /* nvservices. */ + out_proc_info->process_flags |= 0x180; + break; + default: + return 0x809; + } + } + + return 0x0; +} + +Result ProcessCreation::CreateProcess(Handle *out_process_h, u64 index, char *nca_path, LaunchQueue::LaunchItem *launch_item, u64 arg_flags, Handle reslimit_h) { + NpdmUtils::NpdmInfo npdm_info = {0}; + ProcessInfo process_info = {0}; + NsoUtils::NsoLoadExtents nso_extents = {0}; + Registration::Process *target_process; + Handle process_h = 0; + u64 process_id = 0; + bool mounted_code = false; + Result rc; + + /* Get the process from the registration queue. */ + target_process = Registration::GetProcess(index); + if (target_process == NULL) { + return 0x1009; + } + + /* Mount the title's exefs. */ + if (target_process->tid_sid.storage_id != FsStorageId_None) { + rc = ContentManagement::MountCodeForTidSid(&target_process->tid_sid); + if (R_FAILED(rc)) { + return rc; + } + mounted_code = true; + } else { + if (R_SUCCEEDED(ContentManagement::MountCodeNspOnSd(target_process->tid_sid.title_id))) { + mounted_code = true; + } + } + + /* Load the process's NPDM. */ + rc = NpdmUtils::LoadNpdmFromCache(target_process->tid_sid.title_id, &npdm_info); + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + /* Validate the title we're loading is what we expect. */ + if (npdm_info.aci0->title_id < npdm_info.acid->title_id_range_min || npdm_info.aci0->title_id > npdm_info.acid->title_id_range_max) { + rc = 0x1209; + goto CREATE_PROCESS_END; + } + + /* Validate that the ACI0 Kernel Capabilities are valid and restricted by the ACID Kernel Capabilities. */ + rc = NpdmUtils::ValidateCapabilities((u32 *)npdm_info.acid_kac, npdm_info.acid->kac_size/sizeof(u32), (u32 *)npdm_info.aci0_kac, npdm_info.aci0->kac_size/sizeof(u32)); + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + /* Read in all NSO headers, see what NSOs are present. */ + rc = NsoUtils::LoadNsoHeaders(npdm_info.aci0->title_id); + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + /* Validate that the set of NSOs to be loaded is correct. */ + rc = NsoUtils::ValidateNsoLoadSet(); + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + /* Initialize the ProcessInfo. */ + rc = ProcessCreation::InitializeProcessInfo(&npdm_info, reslimit_h, arg_flags, &process_info); + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + /* Figure out where NSOs will be mapped, and how much space they (and arguments) will take up. */ + rc = NsoUtils::CalculateNsoLoadExtents(process_info.process_flags, launch_item != NULL ? launch_item->arg_size : 0, &nso_extents); + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + /* Set Address Space information in ProcessInfo. */ + process_info.code_addr = nso_extents.base_address; + process_info.code_num_pages = nso_extents.total_size + 0xFFF; + process_info.code_num_pages >>= 12; + + /* Call svcCreateProcess(). */ + rc = svcCreateProcess(&process_h, &process_info, (u32 *)npdm_info.aci0_kac, npdm_info.aci0->kac_size/sizeof(u32)); + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + + /* Load all NSOs into Process memory, and set permissions accordingly. */ + if (launch_item != NULL) { + rc = NsoUtils::LoadNsosIntoProcessMemory(process_h, npdm_info.aci0->title_id, &nso_extents, (u8 *)launch_item->args, launch_item->arg_size); + } else { + rc = NsoUtils::LoadNsosIntoProcessMemory(process_h, npdm_info.aci0->title_id, &nso_extents, NULL, 0); + } + if (R_FAILED(rc)) { + goto CREATE_PROCESS_END; + } + + /* Update the list of registered processes with the new process. */ + svcGetProcessId(&process_id, process_h); + bool is_64_bit_addspace; + if (kernelAbove200()) { + is_64_bit_addspace = (((npdm_info.header->mmu_flags >> 1) & 5) | 2) == 3; + } else { + is_64_bit_addspace = (npdm_info.header->mmu_flags & 0xE) == 0x2; + } + Registration::SetProcessIdTidAndIs64BitAddressSpace(index, process_id, npdm_info.aci0->title_id, is_64_bit_addspace); + for (unsigned int i = 0; i < NSO_NUM_MAX; i++) { + if (NsoUtils::IsNsoPresent(i)) { + Registration::AddNsoInfo(index, nso_extents.nso_addresses[i], nso_extents.nso_sizes[i], NsoUtils::GetNsoBuildId(i)); + } + } + + /* Send the pid/tid pair to anyone interested in man-in-the-middle-attacking it. */ + Registration::AssociatePidTidForMitM(index); + + rc = 0; +CREATE_PROCESS_END: + if (mounted_code) { + if (R_SUCCEEDED(rc) && target_process->tid_sid.storage_id != FsStorageId_None) { + rc = ContentManagement::UnmountCode(); + } else { + ContentManagement::UnmountCode(); + } + } + if (R_SUCCEEDED(rc)) { + *out_process_h = process_h; + } else { + svcCloseHandle(process_h); + } + return rc; +} diff --git a/loader/source/ldr_process_creation.hpp b/loader/source/ldr_process_creation.hpp new file mode 100644 index 0000000..40a8367 --- /dev/null +++ b/loader/source/ldr_process_creation.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#include "ldr_registration.hpp" +#include "ldr_launch_queue.hpp" +#include "ldr_npdm.hpp" + +/* Utilities for Process Creation, for Loader. */ + +class ProcessCreation { + public: + struct ProcessInfo { + u8 name[12]; + u32 process_category; + u64 title_id; + u64 code_addr; + u32 code_num_pages; + u32 process_flags; + Handle reslimit_h; + u32 system_resource_num_pages; + }; + static Result InitializeProcessInfo(NpdmUtils::NpdmInfo *npdm, Handle reslimit_h, u64 arg_flags, ProcessInfo *out_proc_info); + static Result CreateProcess(Handle *out_process_h, u64 index, char *nca_path, LaunchQueue::LaunchItem *launch_item, u64 arg_flags, Handle reslimit_h); +}; \ No newline at end of file diff --git a/loader/source/ldr_process_manager.cpp b/loader/source/ldr_process_manager.cpp new file mode 100644 index 0000000..a1be874 --- /dev/null +++ b/loader/source/ldr_process_manager.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include "ldr_process_manager.hpp" +#include "ldr_registration.hpp" +#include "ldr_launch_queue.hpp" +#include "ldr_content_management.hpp" +#include "ldr_npdm.hpp" + +Result ProcessManagerService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) { + Result rc = 0xF601; + + switch ((ProcessManagerServiceCmd)cmd_id) { + case Pm_Cmd_CreateProcess: + rc = WrapIpcCommandImpl<&ProcessManagerService::create_process>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Pm_Cmd_GetProgramInfo: + rc = WrapIpcCommandImpl<&ProcessManagerService::get_program_info>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Pm_Cmd_RegisterTitle: + rc = WrapIpcCommandImpl<&ProcessManagerService::register_title>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Pm_Cmd_UnregisterTitle: + rc = WrapIpcCommandImpl<&ProcessManagerService::unregister_title>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + default: + break; + } + return rc; +} + +std::tuple ProcessManagerService::create_process(u64 flags, u64 index, CopiedHandle reslimit_h) { + Result rc; + Registration::TidSid tid_sid; + LaunchQueue::LaunchItem *launch_item; + char nca_path[FS_MAX_PATH] = {0}; + Handle process_h = 0; + + fprintf(stderr, "CreateProcess(%016lx, %016lx, %08x);\n", flags, index, reslimit_h.handle); + + rc = Registration::GetRegisteredTidSid(index, &tid_sid); + if (R_FAILED(rc)) { + return {rc, MovedHandle{process_h}}; + } + + if (tid_sid.storage_id != FsStorageId_None) { + rc = ContentManagement::ResolveContentPathForTidSid(nca_path, &tid_sid); + if (R_FAILED(rc)) { + return {rc, MovedHandle{process_h}}; + } + } + + + launch_item = LaunchQueue::get_item(tid_sid.title_id); + + rc = ProcessCreation::CreateProcess(&process_h, index, nca_path, launch_item, flags, reslimit_h.handle); + + if (R_SUCCEEDED(rc)) { + ContentManagement::SetCreatedTitle(tid_sid.title_id); + } + + return {rc, MovedHandle{process_h}}; +} + +std::tuple ProcessManagerService::get_program_info(Registration::TidSid tid_sid, OutPointerWithServerSize out_program_info) { + Result rc; + char nca_path[FS_MAX_PATH] = {0}; + /* Zero output. */ + std::fill(out_program_info.pointer, out_program_info.pointer + out_program_info.num_elements, (const ProcessManagerService::ProgramInfo){0}); + + rc = populate_program_info_buffer(out_program_info.pointer, &tid_sid); + + if (R_FAILED(rc)) { + return {rc}; + } + + if (tid_sid.storage_id != FsStorageId_None && tid_sid.title_id != out_program_info.pointer->title_id) { + rc = ContentManagement::ResolveContentPathForTidSid(nca_path, &tid_sid); + if (R_FAILED(rc)) { + return {rc}; + } + + rc = ContentManagement::RedirectContentPath(nca_path, out_program_info.pointer->title_id, tid_sid.storage_id); + if (R_FAILED(rc)) { + return {rc}; + } + + rc = LaunchQueue::add_copy(tid_sid.title_id, out_program_info.pointer->title_id); + } + + return {rc}; +} + +std::tuple ProcessManagerService::register_title(Registration::TidSid tid_sid) { + u64 out_index = 0; + if (Registration::RegisterTidSid(&tid_sid, &out_index)) { + return {0, out_index}; + } else { + return {0xE09, out_index}; + } +} + +std::tuple ProcessManagerService::unregister_title(u64 index) { + if (Registration::UnregisterIndex(index)) { + return {0}; + } else { + return {0x1009}; + } +} + + +Result ProcessManagerService::populate_program_info_buffer(ProcessManagerService::ProgramInfo *out, Registration::TidSid *tid_sid) { + NpdmUtils::NpdmInfo info; + Result rc; + bool mounted_code = false; + + if (tid_sid->storage_id != FsStorageId_None) { + rc = ContentManagement::MountCodeForTidSid(tid_sid); + if (R_FAILED(rc)) { + return rc; + } + mounted_code = true; + } else if (R_SUCCEEDED(ContentManagement::MountCodeNspOnSd(tid_sid->title_id))) { + mounted_code = true; + } + + rc = NpdmUtils::LoadNpdm(tid_sid->title_id, &info); + + if (mounted_code) { + ContentManagement::UnmountCode(); + } + + if (R_FAILED(rc)) { + return rc; + } + + + out->main_thread_priority = info.header->main_thread_prio; + out->default_cpu_id = info.header->default_cpuid; + out->main_thread_stack_size = info.header->main_stack_size; + out->title_id = info.aci0->title_id; + + out->acid_fac_size = info.acid->fac_size; + out->aci0_sac_size = info.aci0->sac_size; + out->aci0_fah_size = info.aci0->fah_size; + + size_t offset = 0; + rc = 0x19009; + if (offset + info.acid->sac_size < sizeof(out->ac_buffer)) { + out->acid_sac_size = info.acid->sac_size; + std::memcpy(out->ac_buffer + offset, info.acid_sac, out->acid_sac_size); + offset += out->acid_sac_size; + if (offset + info.aci0->sac_size < sizeof(out->ac_buffer)) { + out->aci0_sac_size = info.aci0->sac_size; + std::memcpy(out->ac_buffer + offset, info.aci0_sac, out->aci0_sac_size); + offset += out->aci0_sac_size; + if (offset + info.acid->fac_size < sizeof(out->ac_buffer)) { + out->acid_fac_size = info.acid->fac_size; + std::memcpy(out->ac_buffer + offset, info.acid_fac, out->acid_fac_size); + offset += out->acid_fac_size; + if (offset + info.aci0->fah_size < sizeof(out->ac_buffer)) { + out->aci0_fah_size = info.aci0->fah_size; + std::memcpy(out->ac_buffer + offset, info.aci0_fah, out->aci0_fah_size); + offset += out->aci0_fah_size; + rc = 0; + } + } + } + } + + /* Parse application type. */ + if (R_SUCCEEDED(rc)) { + out->application_type = NpdmUtils::GetApplicationType((u32 *)info.acid_kac, info.acid->kac_size / sizeof(u32)); + } + + + return rc; +} diff --git a/loader/source/ldr_process_manager.hpp b/loader/source/ldr_process_manager.hpp new file mode 100644 index 0000000..c177a03 --- /dev/null +++ b/loader/source/ldr_process_manager.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "ldr_registration.hpp" +#include "ldr_process_creation.hpp" + +enum ProcessManagerServiceCmd { + Pm_Cmd_CreateProcess = 0, + Pm_Cmd_GetProgramInfo = 1, + Pm_Cmd_RegisterTitle = 2, + Pm_Cmd_UnregisterTitle = 3 +}; + +class ProcessManagerService final : public IServiceObject { + struct ProgramInfo { + u8 main_thread_priority; + u8 default_cpu_id; + u16 application_type; + u32 main_thread_stack_size; + u64 title_id; + u32 acid_sac_size; + u32 aci0_sac_size; + u32 acid_fac_size; + u32 aci0_fah_size; + u8 ac_buffer[0x3E0]; + }; + + static_assert(sizeof(ProcessManagerService::ProgramInfo) == 0x400, "Incorrect ProgramInfo definition."); + + public: + Result dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) override; + Result handle_deferred() override { + /* This service will never defer. */ + return 0; + } + + ProcessManagerService *clone() override { + return new ProcessManagerService(); + } + + private: + /* Actual commands. */ + std::tuple create_process(u64 flags, u64 index, CopiedHandle reslimit_h); + std::tuple get_program_info(Registration::TidSid tid_sid, OutPointerWithServerSize out_program_info); + std::tuple register_title(Registration::TidSid tid_sid); + std::tuple unregister_title(u64 index); + + /* Utilities */ + Result populate_program_info_buffer(ProcessManagerService::ProgramInfo *out, Registration::TidSid *tid_sid); +}; diff --git a/loader/source/ldr_random.cpp b/loader/source/ldr_random.cpp new file mode 100644 index 0000000..b18cb32 --- /dev/null +++ b/loader/source/ldr_random.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include + +#include "ldr_random.hpp" + +/* Official HOS uses TinyMT. This is high effort. Let's just use XorShift. */ +/* https://en.wikipedia.org/wiki/Xorshift */ + +static u32 g_random_state[4] = {0}; +static bool g_has_initialized = false; + +static void EnsureRandomState() { + if (g_has_initialized) { + return; + } + + /* Retrieve process entropy with svcGetInfo. */ + u64 val = 0; + for (unsigned int i = 0; i < 4; i++) { + if (R_FAILED(svcGetInfo(&val, 0xB, 0, i))) { + /* TODO: Panic? */ + } + g_random_state[i] = val & 0xFFFFFFFF; + } + + g_has_initialized = true; +} + +u32 RandomUtils::GetNext() { + EnsureRandomState(); + u32 s, t = g_random_state[3]; + t ^= t << 11; + t ^= t >> 8; + g_random_state[3] = g_random_state[2]; g_random_state[2] = g_random_state[1]; g_random_state[1] = (s = g_random_state[0]); + t ^= s; + t ^= s >> 19; + g_random_state[0] = t; + return t; +} + +/* These are slightly biased, but I think that's totally okay. */ +u32 RandomUtils::GetRandomU32(u32 max) { + return GetNext() % max; +} + +u64 RandomUtils::GetRandomU64(u64 max) { + u64 val = GetNext(); + val |= ((u64)GetNext()) << 32; + return val % max; +} diff --git a/loader/source/ldr_random.hpp b/loader/source/ldr_random.hpp new file mode 100644 index 0000000..d5848d7 --- /dev/null +++ b/loader/source/ldr_random.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +class RandomUtils { + public: + static u32 GetNext(); + static u32 GetRandomU32(u32 max); + static u64 GetRandomU64(u64 max); +}; \ No newline at end of file diff --git a/loader/source/ldr_registration.cpp b/loader/source/ldr_registration.cpp new file mode 100644 index 0000000..217cdfc --- /dev/null +++ b/loader/source/ldr_registration.cpp @@ -0,0 +1,339 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include +#include +#include "ldr_registration.hpp" +#include "ldr_nro.hpp" + +static Registration::List g_registration_list = {0}; +static u64 g_num_registered = 1; + +Registration::Process *Registration::GetFreeProcess() { + auto process_it = std::find_if_not(g_registration_list.processes.begin(), g_registration_list.processes.end(), std::mem_fn(&Registration::Process::in_use)); + if (process_it == g_registration_list.processes.end()) { + return nullptr; + } + return &*process_it; +} + +Registration::Process *Registration::GetProcess(u64 index) { + for (unsigned int i = 0; i < REGISTRATION_LIST_MAX; i++) { + if (g_registration_list.processes[i].in_use && g_registration_list.processes[i].index == index) { + return &g_registration_list.processes[i]; + } + } + return NULL; +} + +Registration::Process *Registration::GetProcessByProcessId(u64 pid) { + for (unsigned int i = 0; i < REGISTRATION_LIST_MAX; i++) { + if (g_registration_list.processes[i].in_use && g_registration_list.processes[i].process_id == pid) { + return &g_registration_list.processes[i]; + } + } + return NULL; +} + +Registration::Process *Registration::GetProcessByRoService(void *service) { + for (unsigned int i = 0; i < REGISTRATION_LIST_MAX; i++) { + if (g_registration_list.processes[i].in_use && g_registration_list.processes[i].owner_ro_service == service) { + return &g_registration_list.processes[i]; + } + } + return NULL; +} + +bool Registration::RegisterTidSid(const TidSid *tid_sid, u64 *out_index) { + Registration::Process *free_process = GetFreeProcess(); + if (free_process == NULL) { + return false; + } + + /* Reset the process. */ + *free_process = {0}; + free_process->tid_sid = *tid_sid; + free_process->in_use = true; + free_process->index = g_num_registered++; + *out_index = free_process->index; + return true; +} + +bool Registration::UnregisterIndex(u64 index) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + return false; + } + + /* Reset the process. */ + *target_process = {0}; + return true; +} + + +Result Registration::GetRegisteredTidSid(u64 index, Registration::TidSid *out) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + return 0x1009; + } + + *out = target_process->tid_sid; + + return 0; +} + +void Registration::SetProcessIdTidAndIs64BitAddressSpace(u64 index, u64 process_id, u64 tid, bool is_64_bit_addspace) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + return; + } + + target_process->process_id = process_id; + target_process->title_id = tid; + target_process->is_64_bit_addspace = is_64_bit_addspace; +} + +void Registration::AddNsoInfo(u64 index, u64 base_address, u64 size, const unsigned char *build_id) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + return; + } + + auto nso_info_it = std::find_if_not(target_process->nso_infos.begin(), target_process->nso_infos.end(), std::mem_fn(&Registration::NsoInfoHolder::in_use)); + if (nso_info_it != target_process->nso_infos.end()) { + nso_info_it->info.base_address = base_address; + nso_info_it->info.size = size; + std::copy(build_id, build_id + sizeof(nso_info_it->info.build_id), nso_info_it->info.build_id); + nso_info_it->in_use = true; + } +} + +void Registration::CloseRoService(void *service, Handle process_h) { + Registration::Process *target_process = GetProcessByRoService(service); + if (target_process == NULL) { + return; + } + for (unsigned int i = 0; i < NRR_INFO_MAX; i++) { + if (target_process->nrr_infos[i].IsActive() && target_process->nrr_infos[i].process_handle == process_h) { + target_process->nrr_infos[i].Close(); + } + } + target_process->owner_ro_service = NULL; +} + +Result Registration::AddNrrInfo(u64 index, MappedCodeMemory *nrr_info) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + /* TODO: panic() */ + return 0x7009; + } + + auto nrr_info_it = std::find_if_not(target_process->nrr_infos.begin(), target_process->nrr_infos.end(), std::mem_fn(&MappedCodeMemory::IsActive)); + if (nrr_info_it == target_process->nrr_infos.end()) { + return 0x7009; + } + *nrr_info_it = *nrr_info; + return 0; +} + +Result Registration::RemoveNrrInfo(u64 index, u64 base_address) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + /* Despite the fact that this should really be a panic condition, Nintendo returns 0x1009 in this case. */ + return 0x1009; + } + + for (unsigned int i = 0; i < NRR_INFO_MAX; i++) { + if (target_process->nrr_infos[i].IsActive() && target_process->nrr_infos[i].base_address == base_address) { + target_process->nrr_infos[i].Close(); + return 0; + } + } + return 0xAA09; +} + + +bool Registration::IsNroHashPresent(u64 index, u8 *nro_hash) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + /* TODO: panic */ + return false; + } + + for (unsigned int i = 0; i < NRR_INFO_MAX; i++) { + if (target_process->nrr_infos[i].IsActive()) { + NroUtils::NrrHeader *nrr = (NroUtils::NrrHeader *)target_process->nrr_infos[i].mapped_address; + /* Binary search. */ + int low = 0, high = (int)(nrr->num_hashes - 1); + while (low <= high) { + int mid = (low + high) / 2; + u8 *hash_in_nrr = (u8 *)nrr + nrr->hash_offset + 0x20 * mid; + int ret = std::memcmp(hash_in_nrr, nro_hash, 0x20); + if (ret == 0) { + return true; + } else if (ret > 0) { + high = mid - 1; + } else { + low = mid + 1; + } + } + } + } + return false; +} + +bool Registration::IsNroAlreadyLoaded(u64 index, u8 *build_id) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + /* TODO: panic */ + return true; + } + + for (unsigned int i = 0; i < NRO_INFO_MAX; i++) { + if (target_process->nro_infos[i].in_use && std::equal(build_id, build_id + 0x20, target_process->nro_infos[i].build_id)) { + return true; + } + } + return false; +} + +void Registration::AddNroToProcess(u64 index, MappedCodeMemory *nro, MappedCodeMemory *bss, u32 text_size, u32 ro_size, u32 rw_size, u8 *build_id) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + /* TODO: panic */ + return; + } + + auto nro_info_it = std::find_if_not(target_process->nro_infos.begin(), target_process->nro_infos.end(), std::mem_fn(&Registration::NroInfo::in_use)); + if (nro_info_it != target_process->nro_infos.end()) { + nro_info_it->base_address = nro->code_memory_address; + nro_info_it->nro_heap_address = nro->base_address; + nro_info_it->nro_heap_size = nro->size; + nro_info_it->bss_heap_address = bss->base_address; + nro_info_it->bss_heap_size = bss->size; + nro_info_it->text_size = text_size; + nro_info_it->ro_size = ro_size; + nro_info_it->rw_size = rw_size; + std::copy(build_id, build_id + sizeof(nro_info_it->build_id), nro_info_it->build_id); + nro_info_it->in_use = true; + } +} + +Result Registration::RemoveNroInfo(u64 index, Handle process_h, u64 nro_heap_address) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + return 0xA809; + } + + for (unsigned int i = 0; i < NRO_INFO_MAX; i++) { + if (target_process->nro_infos[i].in_use && target_process->nro_infos[i].nro_heap_address == nro_heap_address) { + NroInfo *info = &target_process->nro_infos[i]; + Result rc = svcUnmapProcessCodeMemory(process_h, info->base_address + info->text_size + info->ro_size + info->rw_size, info->bss_heap_address, info->bss_heap_size); + if (R_SUCCEEDED(rc)) { + rc = svcUnmapProcessCodeMemory(process_h, info->base_address + info->text_size + info->ro_size, nro_heap_address + info->text_size + info->ro_size, info->rw_size); + if (R_SUCCEEDED(rc)) { + rc = svcUnmapProcessCodeMemory(process_h, info->base_address, nro_heap_address, info->text_size + info->ro_size); + } + } + target_process->nro_infos[i] = (const NroInfo ){0}; + return rc; + } + } + return 0xA809; +} + +Result Registration::GetNsoInfosForProcessId(Registration::NsoInfo *out, u32 max_out, u64 process_id, u32 *num_written) { + Registration::Process *target_process = GetProcessByProcessId(process_id); + if (target_process == NULL) { + return 0x1009; + } + u32 cur = 0; + + for (unsigned int i = 0; i < NSO_INFO_MAX && cur < max_out; i++) { + if (target_process->nso_infos[i].in_use) { + out[cur++] = target_process->nso_infos[i].info; + } + } + + *num_written = cur; + + return 0; +} + +void Registration::AssociatePidTidForMitM(u64 index) { + Registration::Process *target_process = GetProcess(index); + if (target_process == NULL) { + return; + } + + Handle sm_hnd; + Result rc = svcConnectToNamedPort(&sm_hnd, "sm:"); + if (R_SUCCEEDED(rc)) { + /* Initialize. */ + { + IpcCommand c; + ipcInitialize(&c); + ipcSendPid(&c); + + struct { + u64 magic; + u64 cmd_id; + u64 zero; + u64 reserved[2]; + } *raw = (decltype(raw))ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 0; + raw->zero = 0; + + rc = ipcDispatch(sm_hnd); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + ipcParse(&r); + + struct { + u64 magic; + u64 result; + } *resp = (decltype(resp))r.Raw; + + rc = resp->result; + } + } + /* Associate. */ + if (R_SUCCEEDED(rc)) { + IpcCommand c; + ipcInitialize(&c); + struct { + u64 magic; + u64 cmd_id; + u64 process_id; + u64 title_id; + } *raw = (decltype(raw))ipcPrepareHeader(&c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 65002; + raw->process_id = target_process->process_id; + raw->title_id = target_process->tid_sid.title_id; + + ipcDispatch(sm_hnd); + } + svcCloseHandle(sm_hnd); + } +} \ No newline at end of file diff --git a/loader/source/ldr_registration.hpp b/loader/source/ldr_registration.hpp new file mode 100644 index 0000000..c953ab7 --- /dev/null +++ b/loader/source/ldr_registration.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "ldr_map.hpp" + +#define REGISTRATION_LIST_MAX (0x40) + +#define NSO_INFO_MAX (0x20) +#define NRR_INFO_MAX (0x40) +#define NRO_INFO_MAX (0x40) + +class Registration { + public: + struct NsoInfo { + u64 base_address; + u64 size; + unsigned char build_id[0x20]; + }; + + struct NsoInfoHolder { + bool in_use; + NsoInfo info; + }; + + struct NroInfo { + bool in_use; + u64 base_address; + u64 total_mapped_size; + u64 nro_heap_address; + u64 nro_heap_size; + u64 bss_heap_address; + u64 bss_heap_size; + u64 text_size; + u64 ro_size; + u64 rw_size; + unsigned char build_id[0x20]; + }; + + struct TidSid { + u64 title_id; + FsStorageId storage_id; + }; + + struct Process { + bool in_use; + bool is_64_bit_addspace; + u64 index; + u64 process_id; + u64 title_id; + Registration::TidSid tid_sid; + std::array nso_infos; + std::array nro_infos; + std::array nrr_infos; + void *owner_ro_service; + }; + + struct List { + std::array processes; + u64 num_processes; + }; + + static Registration::Process *GetFreeProcess(); + static Registration::Process *GetProcess(u64 index); + static Registration::Process *GetProcessByProcessId(u64 pid); + static Registration::Process *GetProcessByRoService(void *service); + static Result GetRegisteredTidSid(u64 index, Registration::TidSid *out); + static bool RegisterTidSid(const TidSid *tid_sid, u64 *out_index); + static bool UnregisterIndex(u64 index); + static void SetProcessIdTidAndIs64BitAddressSpace(u64 index, u64 process_id, u64 tid, bool is_64_bit_addspace); + static void AddNsoInfo(u64 index, u64 base_address, u64 size, const unsigned char *build_id); + static void CloseRoService(void *service, Handle process_h); + static Result AddNrrInfo(u64 index, MappedCodeMemory *nrr_info); + static Result RemoveNrrInfo(u64 index, u64 base_address); + static bool IsNroHashPresent(u64 index, u8 *nro_hash); + static bool IsNroAlreadyLoaded(u64 index, u8 *build_id); + static void AddNroToProcess(u64 index, MappedCodeMemory *nro, MappedCodeMemory *bss, u32 text_size, u32 ro_size, u32 rw_size, u8 *build_id); + static Result RemoveNroInfo(u64 index, Handle process_h, u64 base_address); + static Result GetNsoInfosForProcessId(NsoInfo *out, u32 max_out, u64 process_id, u32 *num_written); + + /* Atmosphere MitM Extension. */ + static void AssociatePidTidForMitM(u64 index); +}; diff --git a/loader/source/ldr_ro_service.cpp b/loader/source/ldr_ro_service.cpp new file mode 100644 index 0000000..4529710 --- /dev/null +++ b/loader/source/ldr_ro_service.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include +#include + +#include "ldr_ro_service.hpp" +#include "ldr_registration.hpp" +#include "ldr_map.hpp" +#include "ldr_nro.hpp" + +Result RelocatableObjectsService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) { + Result rc = 0xF601; + + switch ((RoServiceCmd)cmd_id) { + case Ro_Cmd_LoadNro: + rc = WrapIpcCommandImpl<&RelocatableObjectsService::load_nro>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Ro_Cmd_UnloadNro: + rc = WrapIpcCommandImpl<&RelocatableObjectsService::unload_nro>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Ro_Cmd_LoadNrr: + rc = WrapIpcCommandImpl<&RelocatableObjectsService::load_nrr>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Ro_Cmd_UnloadNrr: + rc = WrapIpcCommandImpl<&RelocatableObjectsService::unload_nrr>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Ro_Cmd_Initialize: + rc = WrapIpcCommandImpl<&RelocatableObjectsService::initialize>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + default: + break; + } + + return rc; +} + + +std::tuple RelocatableObjectsService::load_nro(PidDescriptor pid_desc, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size) { + Result rc; + u64 out_address = 0; + Registration::Process *target_proc = NULL; + if (!this->has_initialized || this->process_id != pid_desc.pid) { + rc = 0xAE09; + goto LOAD_NRO_END; + } + if (nro_address & 0xFFF) { + rc = 0xA209; + goto LOAD_NRO_END; + } + if (nro_address + nro_size <= nro_address || !nro_size || (nro_size & 0xFFF)) { + rc = 0xA409; + goto LOAD_NRO_END; + } + if (bss_size && bss_address + bss_size <= bss_address) { + rc = 0xA409; + goto LOAD_NRO_END; + } + /* Ensure no overflow for combined sizes. */ + if (U64_MAX - nro_size < bss_size) { + rc = 0xA409; + goto LOAD_NRO_END; + } + target_proc = Registration::GetProcessByProcessId(pid_desc.pid); + if (target_proc == NULL || (target_proc->owner_ro_service != NULL && (RelocatableObjectsService *)(target_proc->owner_ro_service) != this)) { + rc = 0xAC09; + goto LOAD_NRO_END; + } + target_proc->owner_ro_service = this; + + rc = NroUtils::LoadNro(target_proc, this->process_handle, nro_address, nro_size, bss_address, bss_size, &out_address); +LOAD_NRO_END: + return {rc, out_address}; +} + +std::tuple RelocatableObjectsService::unload_nro(PidDescriptor pid_desc, u64 nro_address) { + Registration::Process *target_proc = NULL; + if (!this->has_initialized || this->process_id != pid_desc.pid) { + return 0xAE09; + } + if (nro_address & 0xFFF) { + return 0xA209; + } + + target_proc = Registration::GetProcessByProcessId(pid_desc.pid); + if (target_proc == NULL || (target_proc->owner_ro_service != NULL && (RelocatableObjectsService *)(target_proc->owner_ro_service) != this)) { + return 0xAC09; + } + target_proc->owner_ro_service = this; + + return Registration::RemoveNroInfo(target_proc->index, this->process_handle, nro_address); +} + +std::tuple RelocatableObjectsService::load_nrr(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size) { + Result rc; + Registration::Process *target_proc = NULL; + MappedCodeMemory nrr_info = {0}; + + if (!this->has_initialized || this->process_id != pid_desc.pid) { + rc = 0xAE09; + goto LOAD_NRR_END; + } + if (nrr_address & 0xFFF) { + rc = 0xA209; + goto LOAD_NRR_END; + } + if (nrr_address + nrr_size <= nrr_address || !nrr_size || (nrr_size & 0xFFF)) { + rc = 0xA409; + goto LOAD_NRR_END; + } + + target_proc = Registration::GetProcessByProcessId(pid_desc.pid); + if (target_proc == NULL || (target_proc->owner_ro_service != NULL && (RelocatableObjectsService *)(target_proc->owner_ro_service) != this)) { + rc = 0xAC09; + goto LOAD_NRR_END; + } + target_proc->owner_ro_service = this; + + if (R_FAILED((rc = nrr_info.Open(this->process_handle, target_proc->is_64_bit_addspace, nrr_address, nrr_size)))) { + goto LOAD_NRR_END; + } + + if (R_FAILED((rc = nrr_info.Map()))) { + goto LOAD_NRR_END; + } + + rc = NroUtils::ValidateNrrHeader((NroUtils::NrrHeader *)nrr_info.mapped_address, nrr_size, target_proc->title_id); + if (R_SUCCEEDED(rc)) { + Registration::AddNrrInfo(target_proc->index, &nrr_info); + } + +LOAD_NRR_END: + if (R_FAILED(rc)) { + if (nrr_info.IsActive()) { + nrr_info.Close(); + } + } + return {rc}; +} + +std::tuple RelocatableObjectsService::unload_nrr(PidDescriptor pid_desc, u64 nrr_address) { + Registration::Process *target_proc = NULL; + if (!this->has_initialized || this->process_id != pid_desc.pid) { + return 0xAE09; + } + if (nrr_address & 0xFFF) { + return 0xA209; + } + + target_proc = Registration::GetProcessByProcessId(pid_desc.pid); + if (target_proc == NULL || (target_proc->owner_ro_service != NULL && (RelocatableObjectsService *)(target_proc->owner_ro_service) != this)) { + return 0xAC09; + } + target_proc->owner_ro_service = this; + + return Registration::RemoveNrrInfo(target_proc->index, nrr_address); +} + +std::tuple RelocatableObjectsService::initialize(PidDescriptor pid_desc, CopiedHandle process_h) { + u64 handle_pid; + Result rc = 0xAE09; + if (R_SUCCEEDED(svcGetProcessId(&handle_pid, process_h.handle)) && handle_pid == pid_desc.pid) { + if (this->has_initialized) { + svcCloseHandle(this->process_handle); + } + this->process_handle = process_h.handle; + this->process_id = handle_pid; + this->has_initialized = true; + rc = 0; + } + return {rc}; +} diff --git a/loader/source/ldr_ro_service.hpp b/loader/source/ldr_ro_service.hpp new file mode 100644 index 0000000..3e62258 --- /dev/null +++ b/loader/source/ldr_ro_service.hpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +#include +#include "ldr_registration.hpp" + +enum RoServiceCmd { + Ro_Cmd_LoadNro = 0, + Ro_Cmd_UnloadNro = 1, + Ro_Cmd_LoadNrr = 2, + Ro_Cmd_UnloadNrr = 3, + Ro_Cmd_Initialize = 4, +}; + +class RelocatableObjectsService final : public IServiceObject { + Handle process_handle = 0; + u64 process_id = U64_MAX; + bool has_initialized = false; + public: + ~RelocatableObjectsService() { + Registration::CloseRoService(this, this->process_handle); + if (this->has_initialized) { + svcCloseHandle(this->process_handle); + } + } + Result dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) override; + Result handle_deferred() override { + /* This service will never defer. */ + return 0; + } + + RelocatableObjectsService *clone() override { + return new RelocatableObjectsService(*this); + } + + private: + /* Actual commands. */ + std::tuple load_nro(PidDescriptor pid_desc, u64 nro_address, u64 nro_size, u64 bss_address, u64 bss_size); + std::tuple unload_nro(PidDescriptor pid_desc, u64 nro_address); + std::tuple load_nrr(PidDescriptor pid_desc, u64 nrr_address, u64 nrr_size); + std::tuple unload_nrr(PidDescriptor pid_desc, u64 nrr_address); + std::tuple initialize(PidDescriptor pid_desc, CopiedHandle process_h); +}; diff --git a/loader/source/ldr_shell.cpp b/loader/source/ldr_shell.cpp new file mode 100644 index 0000000..664388d --- /dev/null +++ b/loader/source/ldr_shell.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018 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 . + */ + +#include +#include +#include "ldr_shell.hpp" +#include "ldr_launch_queue.hpp" + +Result ShellService::dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) { + + Result rc = 0xF601; + + switch ((ShellServiceCmd)cmd_id) { + case Shell_Cmd_AddTitleToLaunchQueue: + rc = WrapIpcCommandImpl<&ShellService::add_title_to_launch_queue>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + case Shell_Cmd_ClearLaunchQueue: + rc = WrapIpcCommandImpl<&ShellService::clear_launch_queue>(this, r, out_c, pointer_buffer, pointer_buffer_size); + break; + default: + break; + } + return rc; +} + +std::tuple ShellService::add_title_to_launch_queue(u64 args_size, u64 tid, InPointer args) { + fprintf(stderr, "Add to launch queue: %p, %zX\n", args.pointer, std::min(args_size, args.num_elements)); + return {LaunchQueue::add(tid, args.pointer, std::min(args_size, args.num_elements))}; +} + +std::tuple ShellService::clear_launch_queue(u64 dat) { + fprintf(stderr, "Clear launch queue: %lx\n", dat); + LaunchQueue::clear(); + return {0}; +} diff --git a/loader/source/ldr_shell.hpp b/loader/source/ldr_shell.hpp new file mode 100644 index 0000000..7cc95f8 --- /dev/null +++ b/loader/source/ldr_shell.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2018 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +enum ShellServiceCmd { + Shell_Cmd_AddTitleToLaunchQueue = 0, + Shell_Cmd_ClearLaunchQueue = 1 +}; + +class ShellService final : public IServiceObject { + public: + Result dispatch(IpcParsedCommand &r, IpcCommand &out_c, u64 cmd_id, u8 *pointer_buffer, size_t pointer_buffer_size) override; + Result handle_deferred() override { + /* This service will never defer. */ + return 0; + } + + ShellService *clone() override { + return new ShellService(); + } + + private: + /* Actual commands. */ + std::tuple add_title_to_launch_queue(u64 args_size, u64 tid, InPointer args); + std::tuple clear_launch_queue(u64 dat); +}; diff --git a/loader/source/lz4.c b/loader/source/lz4.c new file mode 100644 index 0000000..33aa5c7 --- /dev/null +++ b/loader/source/lz4.c @@ -0,0 +1,1857 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-2017, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4_HEAPMODE +# define LZ4_HEAPMODE 0 +#endif + +/* + * ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define ACCELERATION_DEFAULT 1 + + +/*-************************************ +* CPU Feature Detection +**************************************/ +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which assembly generation depends on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ +# if defined(__GNUC__) && ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for Windows CE does not support Hardware bit count */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + +/*-************************************ +* Dependency +**************************************/ +#define LZ4_STATIC_LINKING_ONLY +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#ifdef _MSC_VER /* Visual Studio */ +# include +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 4293) /* disable: C4293: too large shift (32-bits) */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2_GCC_PPC64LE and LZ4_FORCE_O2_INLINE_GCC_PPC64LE + * Gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) +# define LZ4_FORCE_O2_GCC_PPC64LE __attribute__((optimize("O2"))) +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE __attribute__((optimize("O2"))) LZ4_FORCE_INLINE +#else +# define LZ4_FORCE_O2_GCC_PPC64LE +# define LZ4_FORCE_O2_INLINE_GCC_PPC64LE static +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#define likely(expr) expect((expr) != 0, 1) +#define unlikely(expr) expect((expr) != 0, 0) + + +/*-************************************ +* Memory routines +**************************************/ +#include /* malloc, calloc, free */ +#define ALLOC(s) malloc(s) +#define ALLOC_AND_ZERO(s) calloc(1,s) +#define FREEMEM free +#include /* memset, memcpy */ +#define MEM_INIT memset + + +/*-************************************ +* Basic Types +**************************************/ +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +/*-************************************ +* Reading and writing into memory +**************************************/ +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access through memcpy() */ + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_O2_INLINE_GCC_PPC64LE +void LZ4_wildCopy(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { memcpy(d,s,8); d+=8; s+=8; } while (d=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use only *after* variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include +static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + if (LZ4_isLittleEndian()) { + if (sizeof(val)==8) { +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64( &r, (U64)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctzll((U64)val) >> 3); +# else + static const int DeBruijnBytePos[64] = { 0, 0, 0, 0, 0, 1, 1, 2, + 0, 3, 1, 3, 1, 4, 2, 7, + 0, 2, 3, 6, 1, 5, 3, 5, + 1, 3, 4, 4, 2, 5, 6, 7, + 7, 0, 1, 2, 3, 3, 4, 6, + 2, 6, 5, 5, 3, 4, 5, 6, + 7, 1, 2, 4, 6, 4, 4, 5, + 7, 2, 6, 5, 7, 6, 7, 7 }; + return DeBruijnBytePos[((U64)((val & -(long long)val) * 0x0218A392CDABBD3FULL)) >> 58]; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward( &r, (U32)val ); + return (int)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_ctz((U32)val) >> 3); +# else + static const int DeBruijnBytePos[32] = { 0, 0, 3, 0, 3, 1, 3, 0, + 3, 2, 2, 1, 3, 2, 0, 1, + 3, 3, 1, 2, 2, 2, 2, 0, + 3, 1, 2, 0, 1, 0, 1, 1 }; + return DeBruijnBytePos[((U32)((val & -(S32)val) * 0x077CB531U)) >> 27]; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { /* 64-bits */ +# if defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse64( &r, val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clzll((U64)val) >> 3); +# else + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanReverse( &r, (unsigned long)val ); + return (unsigned)(r>>3); +# elif (defined(__clang__) || (defined(__GNUC__) && (__GNUC__>=3))) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (__builtin_clz((U32)val) >> 3); +# else + unsigned r; + if (!(val>>16)) { r=2; val>>=8; } else { r=0; val>>=24; } + r += (!val); + return r; +# endif + } + } +} + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { notLimited = 0, limitedOutput = 1 } limitedOutput_directive; +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Like usingExtDict, but everything concerning the preceding + * content is in a separate context, pointed to by + * ctx->dictCtx. ctx->dictionary, ctx->dictSize, and table + * entries in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + +typedef enum { endOnOutputSize = 0, endOnInputSize = 1 } endCondition_directive; +typedef enum { full = 0, partial = 1 } earlyEnd_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState() { return LZ4_STREAMSIZE; } + + +/*-****************************** +* Compression functions +********************************/ +static U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +static U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + static const U64 prime5bytes = 889523592379ULL; + static const U64 prime8bytes = 11400714785074694791ULL; + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + else + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +static void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +static void LZ4_putPositionOnHash(const BYTE* p, U32 h, void* tableBase, tableType_t const tableType, const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +static U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; assert(h < (1U << (LZ4_MEMORY_USAGE-2))); return hashTable[h]; } + if (tableType == byU16) { const U16* const hashTable = (const U16*) tableBase; assert(h < (1U << (LZ4_MEMORY_USAGE-1))); return hashTable[h]; } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* LZ4_getPosition(const BYTE* p, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void LZ4_prepareTable( + LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if (cctx->tableType != clearedTable) { + if (cctx->tableType != tableType + || (tableType == byU16 && cctx->currentOffset + inputSize >= 0xFFFFU) + || (tableType == byU32 && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > MAX_DISTANCE back, is faster + * than compressing without a gap. However, compressing with + * currentOffset == 0 is faster still, so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + inlined, to ensure branches are decided at compilation time */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + const int maxOutputSize, + const limitedOutput_directive outputLimited, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const U32 acceleration) +{ + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary + dictSize; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = dictDirective == usingDictCtx ? + dictionary + dictSize - dictCtx->currentOffset : /* is it possible that dictCtx->currentOffset != dictCtx->dictSize ? Yes if the dictionary context is not reset */ + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, tableType=%u", inputSize, tableType); + /* Init conditions */ + if ((U32)inputSize > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported inputSize, too large (or negative) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = tableType; + + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+MAX_DISTANCE < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) continue; /* match outside of valid area */ + if ((tableType != byU16) && (current - matchIndex > MAX_DISTANCE)) continue; /* too far - note: works even if matchIndex overflows */ + if (tableType == byU16) assert((current - matchIndex) <= MAX_DISTANCE); /* too_far presumed impossible with byU16 */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputLimited) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit))) + return 0; + if (litLength >= RUN_MASK) { + int len = (int)litLength-RUN_MASK; + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= MAX_DISTANCE && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= MAX_DISTANCE); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } + + /* Encode MatchLength */ + { unsigned matchCode; + + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += MINMATCH + matchCode; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += MINMATCH + matchCode; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ( outputLimited && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode>>8) > olimit)) ) + return 0; + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+MAX_DISTANCE >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && ((tableType==byU16) ? 1 : (current - matchIndex <= MAX_DISTANCE)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t const lastRun = (size_t)(iend - anchor); + if ( (outputLimited) && /* Check output buffer overflow */ + ((op - (BYTE*)dest) + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > (U32)maxOutputSize) ) + return 0; + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun<internal_donotuse; + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + LZ4_resetStream((LZ4_stream_t*)state); + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > MAX_DISTANCE)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) {; + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > MAX_DISTANCE)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > MAX_DISTANCE)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > MAX_DISTANCE)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (LZ4_HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + + +int LZ4_compress_default(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_fast(source, dest, inputSize, maxOutputSize, 1); +} + + +/* hidden debug function */ +/* strangely enough, gcc generates faster code when this function is uncommented, even if unused */ +int LZ4_compress_fast_force(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t ctx; + LZ4_resetStream(&ctx); + + if (inputSize < LZ4_64Klimit) + return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + else + return LZ4_compress_generic(&ctx.internal_donotuse, source, dest, inputSize, maxOutputSize, limitedOutput, sizeof(void*)==8 ? byU32 : byPtr, noDict, noDictIssue, acceleration); +} + + +/*-****************************** +* *_destSize() variant +********************************/ + +static int LZ4_compress_destSize_generic( + LZ4_stream_t_internal* const ctx, + const char* const src, + char* const dst, + int* const srcSizePtr, + const int targetDstSize, + const tableType_t tableType) +{ + const BYTE* ip = (const BYTE*) src; + const BYTE* base = (const BYTE*) src; + const BYTE* lowLimit = (const BYTE*) src; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = iend - LASTLITERALS; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + targetDstSize; + BYTE* const oMaxLit = op + targetDstSize - 2 /* offset */ - 8 /* because 8+MINMATCH==MFLIMIT */ - 1 /* token */; + BYTE* const oMaxMatch = op + targetDstSize - (LASTLITERALS + 1 /* token */); + BYTE* const oMaxSeq = oMaxLit - 1 /* token */; + + U32 forwardH; + + + /* Init conditions */ + if (targetDstSize < 1) return 0; /* Impossible to store anything */ + if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size, too large (or negative) */ + if ((tableType == byU16) && (*srcSizePtr>=LZ4_64Klimit)) return 0; /* Size too large (not within 64K limit) */ + if (*srcSizePtrhashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + + /* Find a match */ + { const BYTE* forwardIp = ip; + unsigned step = 1; + unsigned searchMatchNb = 1 << LZ4_skipTrigger; + + do { + U32 h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimit)) goto _last_literals; + + match = LZ4_getPositionOnHash(h, ctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, ctx->hashTable, tableType, base); + + } while ( ((tableType==byU16) ? 0 : (match + MAX_DISTANCE < ip)) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + } + + /* Catch up */ + while ((ip>anchor) && (match > lowLimit) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literal length */ + { unsigned litLength = (unsigned)(ip - anchor); + token = op++; + if (op + ((litLength+240)/255) + litLength > oMaxLit) { + /* Not enough space for a last match */ + op--; + goto _last_literals; + } + if (litLength>=RUN_MASK) { + unsigned len = litLength - RUN_MASK; + *token=(RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< oMaxMatch) { + /* Match description too long : reduce it */ + matchLength = (15-1) + (oMaxMatch-op) * 255; + } + ip += MINMATCH + matchLength; + + if (matchLength>=ML_MASK) { + *token += ML_MASK; + matchLength -= ML_MASK; + while (matchLength >= 255) { matchLength-=255; *op++ = 255; } + *op++ = (BYTE)matchLength; + } + else *token += (BYTE)(matchLength); + } + + anchor = ip; + + /* Test end of block */ + if (ip > mflimit) break; + if (op > oMaxSeq) break; + + /* Fill table */ + LZ4_putPosition(ip-2, ctx->hashTable, tableType, base); + + /* Test next position */ + match = LZ4_getPosition(ip, ctx->hashTable, tableType, base); + LZ4_putPosition(ip, ctx->hashTable, tableType, base); + if ( (match+MAX_DISTANCE>=ip) + && (LZ4_read32(match)==LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); + if (op + 1 /* token */ + ((lastRunSize+240)/255) /* litLength */ + lastRunSize /* literals */ > oend) { + /* adapt lastRunSize to fill 'dst' */ + lastRunSize = (oend-op) - 1; + lastRunSize -= (lastRunSize+240)/255; + } + ip = anchor + lastRunSize; + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize<= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, byU16); + } else { + tableType_t const tableType = ((sizeof(void*)==4) && ((uptrval)src > MAX_DISTANCE)) ? byPtr : byU32; + return LZ4_compress_destSize_generic(&state->internal_donotuse, src, dst, srcSizePtr, targetDstSize, tableType); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; +#else + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + +#if (LZ4_HEAPMODE) + FREEMEM(ctx); +#endif + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(LZ4_STREAMSIZE >= sizeof(LZ4_stream_t_internal)); /* A compilation error here means LZ4_STREAMSIZE is not large enough */ + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_resetStream(lz4s); + return lz4s; +} + +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + LZ4_prepareTable(dict, 0, tableType); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - 64 KB - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->currentOffset += 64 KB; + dict->tableType = tableType; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t *working_stream, const LZ4_stream_t *dictionary_stream) { + if (dictionary_stream != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (working_stream->internal_donotuse.currentOffset == 0) { + working_stream->internal_donotuse.currentOffset = 64 KB; + } + working_stream->internal_donotuse.dictCtx = &(dictionary_stream->internal_donotuse); + } else { + working_stream->internal_donotuse.dictCtx = NULL; + } +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + if (LZ4_dict->currentOffset + nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* streamPtr = &LZ4_stream->internal_donotuse; + const BYTE* const dictEnd = streamPtr->dictionary + streamPtr->dictSize; + + if (streamPtr->initCheck) return 0; /* Uninitialized structure detected */ + LZ4_renormDictT(streamPtr, inputSize); /* avoid index overflow */ + if (acceleration < 1) acceleration = ACCELERATION_DEFAULT; + + /* Check overlapping input/dictionary space */ + { const BYTE* sourceEnd = (const BYTE*) source + inputSize; + if ((sourceEnd > streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == (const BYTE*)source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + memcpy(streamPtr, streamPtr->dictCtx, sizeof(LZ4_stream_t)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : you don't need to call LZ4_loadDict() afterwards, + * dictionary is immediately usable, you can therefore call LZ4_compress_fast_continue(). + * Return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + + if ((U32)dictSize > 64 KB) dictSize = 64 KB; /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) dictSize = dict->dictSize; + + memmove(safeBuffer, previousDictEnd - dictSize, dictSize); + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-***************************** +* Decompression functions +*******************************/ +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_O2_GCC_PPC64LE +LZ4_FORCE_INLINE int LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + int endOnInput, /* endOnOutputSize, endOnInputSize */ + int partialDecoding, /* full, partial */ + int targetOutputSize, /* only used if partialDecoding==partial */ + int dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + BYTE* oexit = op + targetOutputSize; + + const BYTE* const dictEnd = (const BYTE*)dictStart + dictSize; + const unsigned inc32table[8] = {0, 1, 2, 1, 0, 4, 4, 4}; + const int dec64table[8] = {0, 0, 0, -1, -4, 1, 2, 3}; + + const int safeDecode = (endOnInput==endOnInputSize); + const int checkOffset = ((safeDecode) && (dictSize < (int)(64 KB))); + + + /* Special cases */ + if ((partialDecoding) && (oexit > oend-MFLIMIT)) oexit = oend-MFLIMIT; /* targetOutputSize too high => just decode everything */ + if ((endOnInput) && (unlikely(outputSize==0))) return ((srcSize==1) && (*ip==0)) ? 0 : -1; /* Empty output buffer */ + if ((!endOnInput) && (unlikely(outputSize==0))) return (*ip==0?1:-1); + + /* Main Loop : decode sequences */ + while (1) { + size_t length; + const BYTE* match; + size_t offset; + + unsigned const token = *ip++; + + /* shortcut for common case : + * in most circumstances, we expect to decode small matches (<= 18 bytes) separated by few literals (<= 14 bytes). + * this shortcut was tested on x86 and x64, where it improves decoding speed. + * it has not yet been benchmarked on ARM, Power, mips, etc. */ + if (((ip + 14 /*maxLL*/ + 2 /*offset*/ <= iend) + & (op + 14 /*maxLL*/ + 18 /*maxML*/ <= oend)) + & ((token < (15<> ML_BITS; + size_t const off = LZ4_readLE16(ip+ll); + const BYTE* const matchPtr = op + ll - off; /* pointer underflow risk ? */ + if ((off >= 8) /* do not deal with overlapping matches */ & (matchPtr >= lowPrefix)) { + size_t const ml = (token & ML_MASK) + MINMATCH; + memcpy(op, ip, 16); op += ll; ip += ll + 2 /*offset*/; + memcpy(op + 0, matchPtr + 0, 8); + memcpy(op + 8, matchPtr + 8, 8); + memcpy(op +16, matchPtr +16, 2); + op += ml; + continue; + } + } + + /* decode literal length */ + if ((length=(token>>ML_BITS)) == RUN_MASK) { + unsigned s; + do { + s = *ip++; + length += s; + } while ( likely(endOnInput ? ip(partialDecoding?oexit:oend-MFLIMIT)) || (ip+length>iend-(2+1+LASTLITERALS))) ) + || ((!endOnInput) && (cpy>oend-WILDCOPYLENGTH)) ) + { + if (partialDecoding) { + if (cpy > oend) goto _output_error; /* Error : write attempt beyond end of output buffer */ + if ((endOnInput) && (ip+length > iend)) goto _output_error; /* Error : read attempt beyond end of input buffer */ + } else { + if ((!endOnInput) && (cpy != oend)) goto _output_error; /* Error : block decoding must stop exactly there */ + if ((endOnInput) && ((ip+length != iend) || (cpy > oend))) goto _output_error; /* Error : input must be consumed */ + } + memcpy(op, ip, length); + ip += length; + op += length; + break; /* Necessarily EOF, due to parsing restrictions */ + } + LZ4_wildCopy(op, ip, cpy); + ip += length; op = cpy; + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + LZ4_write32(op, (U32)offset); /* costs ~1%; silence an msan warning when offset==0 */ + + /* get matchlength */ + length = token & ML_MASK; + if (length == ML_MASK) { + unsigned s; + do { + s = *ip++; + if ((endOnInput) && (ip > iend-LASTLITERALS)) goto _output_error; + length += s; + } while (s==255); + if ((safeDecode) && unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + + /* check external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + if (unlikely(op+length > oend-LASTLITERALS)) goto _output_error; /* doesn't respect parsing restriction */ + + if (length <= (size_t)(lowPrefix-match)) { + /* match can be copied as a single segment from external dictionary */ + memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match encompass external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix-match); + size_t const restSize = length - copySize; + memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op-lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + if (unlikely(offset<8)) { + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { memcpy(op, match, 8); match+=8; } + op += 8; + + if (unlikely(cpy>oend-12)) { + BYTE* const oCopyLimit = oend-(WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) goto _output_error; /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op16) LZ4_wildCopy(op+8, match+8, cpy); + } + op = cpy; /* correction */ + } + + /* end of decoding */ + if (endOnInput) + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + else + return (int) (((const char*)ip)-src); /* Nb of input bytes read */ + + /* Overflow error detected */ +_output_error: + return (int) (-(((const char*)ip)-src))-1; +} + + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, full, 0, noDict, (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_partial(const char* source, char* dest, int compressedSize, int targetOutputSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, endOnInputSize, partial, targetOutputSize, noDict, (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)(dest - 64 KB), NULL, 64 KB); +} + + +/*===== streaming decompression functions =====*/ + +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_streamDecode_t* lz4s = (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); + return lz4s; +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} + +/*! + * LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * Return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t) dictSize; + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixEnd == (BYTE*)dest) { + result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, full, 0, + usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += result; + lz4sd->prefixEnd += result; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + endOnInputSize, full, 0, + usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixEnd == (BYTE*)dest) { + result = LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, full, 0, + usingExtDict, lz4sd->prefixEnd - lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += originalSize; + lz4sd->prefixEnd += originalSize; + } else { + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_generic(source, dest, 0, originalSize, + endOnOutputSize, full, 0, + usingExtDict, (BYTE*)dest, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +LZ4_FORCE_O2_GCC_PPC64LE +LZ4_FORCE_INLINE int LZ4_decompress_usingDict_generic(const char* source, char* dest, int compressedSize, int maxOutputSize, int safe, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest, NULL, 0); + if (dictStart+dictSize == dest) { + if (dictSize >= (int)(64 KB - 1)) + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, withPrefix64k, (BYTE*)dest-64 KB, NULL, 0); + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, noDict, (BYTE*)dest-dictSize, NULL, 0); + } + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, safe, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + return LZ4_decompress_usingDict_generic(source, dest, compressedSize, maxOutputSize, 1, dictStart, dictSize); +} + +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + return LZ4_decompress_usingDict_generic(source, dest, 0, originalSize, 0, dictStart, dictSize); +} + +/* debug function */ +LZ4_FORCE_O2_GCC_PPC64LE +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, usingExtDict, (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) { return LZ4_compress_default(source, dest, inputSize, maxOutputSize); } +int LZ4_compress(const char* source, char* dest, int inputSize) { return LZ4_compress_default(source, dest, inputSize, LZ4_compressBound(inputSize)); } +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); } +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) { return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); } +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int maxDstSize) { return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, maxDstSize, 1); } +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) { return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); } + +/* +These function names are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) { return LZ4_decompress_fast(source, dest, outputSize); } +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) { return LZ4_decompress_safe(source, dest, isize, maxOutputSize); } + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState() { return LZ4_STREAMSIZE; } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +/* Obsolete streaming decompression functions */ + +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, endOnInputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); +} + +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_generic(source, dest, 0, originalSize, endOnOutputSize, full, 0, withPrefix64k, (BYTE*)dest - 64 KB, NULL, 64 KB); +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/loader/source/lz4.h b/loader/source/lz4.h new file mode 100644 index 0000000..0dfa19e --- /dev/null +++ b/loader/source/lz4.h @@ -0,0 +1,569 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2017, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed at 400 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h provides block compression functions. It gives full buffer control to user. + Decompressing an lz4-compressed block also requires metadata (such as compressed size). + Each application is free to encode such metadata in whichever way it wants. + + An additional format, called LZ4 frame specification (doc/lz4_Frame_format.md), + take care of encoding standard metadata alongside LZ4-compressed blocks. + If your application requires interoperability, it's recommended to use it. + A library is provided to take care of it, see lz4frame.h. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 8 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 2 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; unseful to check dll version */ + + +/*-************************************ +* Tuning parameter +**************************************/ +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; etc.) + * Increasing memory usage improves compression ratio + * Reduced memory usage may improve speed, thanks to cache effect + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE 14 +#endif + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + Compresses 'srcSize' bytes from buffer 'src' + into already allocated 'dst' buffer of size 'dstCapacity'. + Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + It also runs faster, so it's a recommended setting. + If the function cannot compress 'src' into a more limited 'dst' budget, + compression stops *immediately*, and the function result is zero. + Note : as a consequence, 'dst' content is not valid. + Note 2 : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + dstCapacity : size of buffer 'dst' (which must be already allocated) + return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + or 0 if compression fails */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + compressedSize : is the exact complete size of the compressed block. + dstCapacity : is the size of destination buffer, which must be already allocated. + return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + If destination buffer is not large enough, decoding will stop and output an error code (negative value). + If the source stream is detected malformed, the function will stop decoding and return a negative result. + This function is protected against malicious data packets. +*/ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! +LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! +LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by ACCELERATION_DEFAULT (currently == 1, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! +LZ4_compress_fast_extState() : + Same compression function, just using an externally allocated memory space to store compression state. + Use LZ4_sizeofState() to know how much memory must be allocated, + and allocate it on 8-bytes boundaries (using malloc() typically). + Then, provide it as 'void* state' to compression function. +*/ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! +LZ4_compress_destSize() : + Reverse the logic : compresses as much data as possible from 'src' buffer + into already allocated buffer 'dst' of size 'targetDestSize'. + This function either compresses the entire 'src' content into 'dst' if it's large enough, + or fill 'dst' buffer completely with as much data as possible from 'src'. + *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + New value is necessarily <= old value. + return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + or 0 if compression fails +*/ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! +LZ4_decompress_fast() : **unsafe!** +This function is a bit faster than LZ4_decompress_safe(), +but doesn't provide any security guarantee. + originalSize : is the uncompressed size to regenerate + Destination buffer must be already allocated, and its size must be >= 'originalSize' bytes. + return : number of bytes read from source buffer (== compressed size). + If the source stream is detected malformed, the function stops decoding and return a negative result. + note : This function respects memory boundaries for *properly formed* compressed data. + However, it does not provide any protection against malicious input. + It also doesn't know 'src' size, and implies it's >= compressed size. + Use this function in trusted environment **only**. +*/ +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); + +/*! +LZ4_decompress_safe_partial() : + This function decompress a compressed block of size 'srcSize' at position 'src' + into destination buffer 'dst' of size 'dstCapacity'. + The function will decompress a minimum of 'targetOutputSize' bytes, and stop after that. + However, it's not accurate, and may write more than 'targetOutputSize' (but always <= dstCapacity). + @return : the number of bytes decoded in the destination buffer (necessarily <= dstCapacity) + Note : this number can also be < targetOutputSize, if compressed block contains less data. + Therefore, always control how many bytes were decoded. + If source stream is detected malformed, function returns a negative result. + This function is protected against malicious data packets. +*/ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/*! LZ4_createStream() and LZ4_freeStream() : + * LZ4_createStream() will allocate and initialize an `LZ4_stream_t` structure. + * LZ4_freeStream() releases its memory. + */ +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure can be allocated once and re-used multiple times. + * Use this function to start compressing a new stream. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to load a static dictionary into LZ4_stream_t. + * Any previous data will be forgotten, only 'dictionary' will remain in memory. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * Important : The previous 64KB of compressed data is assumed to remain present and unmodified in memory! + * + * Special 1 : When input is a double-buffer, they can have any size, including < 64 KB. + * Make sure that buffers are separated by at least one byte. + * This way, each block only depends on previous block. + * Special 2 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * After an error, the stream status is invalid, it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* incomplete type (defined later) */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking structure. + * A tracking structure can be re-used multiple times sequentially. */ +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t structure can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionnally be set. Use NULL or size 0 for a reset order. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accept one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded all the data must be present. + * + * Special : if application sets a ring buffer for decompression, it must respect one of the following conditions : + * - Exactly same size as encoding buffer, with same update rule (block boundaries at same positions) + * In which case, the decoding & encoding ring buffer can have any size, including very small ones ( < 64 KB). + * - Larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * maxBlockSize is implementation dependent. It's the maximum size of any single block. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * - _At least_ 64 KB + 8 bytes + maxBlockSize. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including larger than decoding buffer. + * Whenever these conditions are not possible, save the last 64KB of decoded data into a safe buffer, + * and indicate where it is saved using LZ4_setStreamDecode() before decompressing next block. +*/ +LZ4LIB_API int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int srcSize, int dstCapacity); +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + */ +LZ4LIB_API int LZ4_decompress_safe_usingDict (const char* src, char* dst, int srcSize, int dstCapcity, const char* dictStart, int dictSize); +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + + +/*^********************************************** + * !!!!!! STATIC LINKING ONLY !!!!!! + ***********************************************/ + +/*-************************************ + * Unstable declarations + ************************************** + * Declarations in this section should be considered unstable. + * Use at your own peril, etc., etc. + * They may be removed in the future. + * Their signatures may change. + **************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +/*! LZ4_resetStream_fast() : + * When an LZ4_stream_t is known to be in a internally coherent state, + * it can often be prepared for a new compression with almost no work, only + * sometimes falling back to the full, expensive reset that is always required + * when the stream is in an indeterminate state (i.e., the reset performed by + * LZ4_resetStream()). + * + * LZ4_streams are guaranteed to be in a valid state when: + * - returned from LZ4_createStream() + * - reset by LZ4_resetStream() + * - memset(stream, 0, sizeof(LZ4_stream_t)) + * - the stream was in a valid state and was reset by LZ4_resetStream_fast() + * - the stream was in a valid state and was then used in any compression call + * that returned success + * - the stream was in an indeterminate state and was used in a compression + * call that fully reset the state (LZ4_compress_fast_extState()) and that + * returned success + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly + * initialized"). From a high level, the difference is that this function + * initializes the provided state with a call to LZ4_resetStream_fast() while + * LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows for the efficient use of a + * static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionary stream pointer may be NULL, in which + * case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_API void LZ4_attach_dictionary(LZ4_stream_t *working_stream, const LZ4_stream_t *dictionary_stream); + +#endif + +/*-************************************ + * Private definitions + ************************************** + * Do not use these definitions. + * They are exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Using these definitions will expose code to API and/or ABI break in future versions of the library. + **************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +#include + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + uint32_t hashTable[LZ4_HASH_SIZE_U32]; + uint32_t currentOffset; + uint16_t initCheck; + uint16_t tableType; + const uint8_t* dictionary; + const LZ4_stream_t_internal* dictCtx; + uint32_t dictSize; +}; + +typedef struct { + const uint8_t* externalDict; + size_t extDictSize; + const uint8_t* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#else + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + unsigned int hashTable[LZ4_HASH_SIZE_U32]; + unsigned int currentOffset; + unsigned short initCheck; + unsigned short tableType; + const unsigned char* dictionary; + const LZ4_stream_t_internal* dictCtx; + unsigned int dictSize; +}; + +typedef struct { + const unsigned char* externalDict; + size_t extDictSize; + const unsigned char* prefixEnd; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#endif + +/*! + * LZ4_stream_t : + * information structure to track an LZ4 stream. + * init this structure before first use. + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * it may change in a future version ! + */ +#define LZ4_STREAMSIZE_U64 ((1 << (LZ4_MEMORY_USAGE-3)) + 4) +#define LZ4_STREAMSIZE (LZ4_STREAMSIZE_U64 * sizeof(unsigned long long)) +union LZ4_stream_u { + unsigned long long table[LZ4_STREAMSIZE_U64]; + LZ4_stream_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_stream_t */ + + +/*! + * LZ4_streamDecode_t : + * information structure to track an LZ4 stream during decompression. + * init this structure using LZ4_setStreamDecode (or memset()) before first use + * note : only use in association with static linking ! + * this definition is not API/ABI safe, + * and may change in a future version ! + */ +#define LZ4_STREAMDECODESIZE_U64 4 +#define LZ4_STREAMDECODESIZE (LZ4_STREAMDECODESIZE_U64 * sizeof(unsigned long long)) +union LZ4_streamDecode_u { + unsigned long long table[LZ4_STREAMDECODESIZE_U64]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + Should deprecation warnings be a problem, + it is generally possible to disable them, + typically with -Wno-deprecated-declarations for gcc + or _CRT_SECURE_NO_WARNINGS in Visual. + Otherwise, it's also possible to define LZ4_DISABLE_DEPRECATE_WARNINGS */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif (LZ4_GCC_VERSION >= 405) || defined(__clang__) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif (LZ4_GCC_VERSION >= 301) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# else +# pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler") +# define LZ4_DEPRECATED(message) +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/* Obsolete compression functions */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* source, char* dest, int sourceSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* source, char* dest, int sourceSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/* Obsolete decompression functions */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions; degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/* Obsolete streaming decoding functions */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +#endif /* LZ4_H_2983827168210 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/loader/source/sha256.c b/loader/source/sha256.c new file mode 100644 index 0000000..02d40a7 --- /dev/null +++ b/loader/source/sha256.c @@ -0,0 +1,113 @@ +/* Based on linux source code */ +/* + * sha256_base.h - core logic for SHA-256 implementations + * + * Copyright (C) 2015 Linaro Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "sha256.h" + +#define unlikely(x) __builtin_expect(!!(x), 0) + +void sha256_block_data_order (uint32_t *ctx, const void *in, size_t num); + +int sha256_init(struct sha256_state *sctx) +{ + sctx->state[0] = SHA256_H0; + sctx->state[1] = SHA256_H1; + sctx->state[2] = SHA256_H2; + sctx->state[3] = SHA256_H3; + sctx->state[4] = SHA256_H4; + sctx->state[5] = SHA256_H5; + sctx->state[6] = SHA256_H6; + sctx->state[7] = SHA256_H7; + sctx->count = 0; + + return 0; +} + +int sha256_update(struct sha256_state *sctx, + const void *data, + size_t len) +{ + const u8 *data8 = (const u8 *)data; + unsigned int len32 = (unsigned int)len; + unsigned int partial = sctx->count % SHA256_BLOCK_SIZE; + + sctx->count += len32; + + if (unlikely((partial + len32) >= SHA256_BLOCK_SIZE)) { + int blocks; + + if (partial) { + int p = SHA256_BLOCK_SIZE - partial; + + memcpy(sctx->buf + partial, data8, p); + data8 += p; + len32 -= p; + + sha256_block_data_order(sctx->state, sctx->buf, 1); + } + + blocks = len32 / SHA256_BLOCK_SIZE; + len32 %= SHA256_BLOCK_SIZE; + + if (blocks) { + sha256_block_data_order(sctx->state, data8, blocks); + data8 += blocks * SHA256_BLOCK_SIZE; + } + partial = 0; + } + if (len32) + memcpy(sctx->buf + partial, data8, len32); + + return 0; +} + +int sha256_finalize(struct sha256_state *sctx) +{ + const int bit_offset = SHA256_BLOCK_SIZE - sizeof(u64); + u64 *bits = (u64 *)(sctx->buf + bit_offset); + unsigned int partial = sctx->count % SHA256_BLOCK_SIZE; + + sctx->buf[partial++] = 0x80; + if (partial > bit_offset) { + memset(sctx->buf + partial, 0x0, SHA256_BLOCK_SIZE - partial); + partial = 0; + + sha256_block_data_order(sctx->state, sctx->buf, 1); + } + + memset(sctx->buf + partial, 0x0, bit_offset - partial); + *bits = __builtin_bswap64(sctx->count << 3); + sha256_block_data_order(sctx->state, sctx->buf, 1); + + return 0; +} + +int sha256_finish(struct sha256_state *sctx, void *out) +{ + unsigned int digest_size = 32; + u32 *digest = (u32 *)out; + int i; + + // Switch: misalignment shouldn't be a problem here... + for (i = 0; digest_size > 0; i++, digest_size -= sizeof(u32)) + *digest++ = __builtin_bswap32(sctx->state[i]); + + *sctx = (struct sha256_state){}; + return 0; +} + +#ifdef __cplusplus +} +#endif diff --git a/loader/source/sha256.h b/loader/source/sha256.h new file mode 100644 index 0000000..cfb09f8 --- /dev/null +++ b/loader/source/sha256.h @@ -0,0 +1,36 @@ +#pragma once + +/* Based on linux source code */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define SHA256_DIGEST_SIZE 32 +#define SHA256_BLOCK_SIZE 64 + +#define SHA256_H0 0x6a09e667UL +#define SHA256_H1 0xbb67ae85UL +#define SHA256_H2 0x3c6ef372UL +#define SHA256_H3 0xa54ff53aUL +#define SHA256_H4 0x510e527fUL +#define SHA256_H5 0x9b05688cUL +#define SHA256_H6 0x1f83d9abUL +#define SHA256_H7 0x5be0cd19UL + +struct sha256_state { + u32 state[SHA256_DIGEST_SIZE / 4]; + u64 count; + u8 buf[SHA256_BLOCK_SIZE]; +}; + +int sha256_init(struct sha256_state *sctx); +int sha256_update(struct sha256_state *sctx, const void *data, size_t len); +int sha256_finalize(struct sha256_state *sctx); +int sha256_finish(struct sha256_state *sctx, void *out); + +#ifdef __cplusplus +} +#endif diff --git a/loader/source/sha256_armv8.s b/loader/source/sha256_armv8.s new file mode 100644 index 0000000..0420d38 --- /dev/null +++ b/loader/source/sha256_armv8.s @@ -0,0 +1,163 @@ +.section .text.sha256_armv8, "ax", %progbits +.align 5 +.arch armv8-a+crypto + +# SHA256 assembly implementation for ARMv8 AArch64 (based on linux source code) + +.global sha256_block_data_order +.type sha256_block_data_order,%function +sha256_block_data_order: + +.Lsha256prolog: + + stp x29, x30, [sp,#-64]! + mov x29, sp + adr x3, .LKConstant256 + str q8, [sp, #16] + ld1 {v16.4s-v19.4s}, [x3], #64 + ld1 {v0.4s}, [x0], #16 + ld1 {v20.4s-v23.4s}, [x3], #64 + add x2, x1, x2, lsl #6 + ld1 {v1.4s}, [x0] + ld1 {v24.4s-v27.4s}, [x3], #64 + sub x0, x0, #16 + str q9, [sp, #32] + str q10, [sp, #48] + ld1 {v28.4s-v31.4s}, [x3], #64 + +.Lsha256loop: + + ld1 {v5.16b-v8.16b}, [x1], #64 + mov v2.16b, v0.16b + mov v3.16b, v1.16b + + rev32 v5.16b, v5.16b + rev32 v6.16b, v6.16b + add v9.4s, v5.4s, v16.4s + rev32 v7.16b, v7.16b + add v10.4s, v6.4s, v17.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + sha256su0 v5.4s, v6.4s + rev32 v8.16b, v8.16b + add v9.4s, v7.4s, v18.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + sha256su0 v6.4s, v7.4s + sha256su1 v5.4s, v7.4s, v8.4s + add v10.4s, v8.4s, v19.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + sha256su0 v7.4s, v8.4s + sha256su1 v6.4s, v8.4s, v5.4s + add v9.4s, v5.4s, v20.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + sha256su0 v8.4s, v5.4s + sha256su1 v7.4s, v5.4s, v6.4s + add v10.4s, v6.4s, v21.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + sha256su0 v5.4s, v6.4s + sha256su1 v8.4s, v6.4s, v7.4s + add v9.4s, v7.4s, v22.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + sha256su0 v6.4s, v7.4s + sha256su1 v5.4s, v7.4s, v8.4s + add v10.4s, v8.4s, v23.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + sha256su0 v7.4s, v8.4s + sha256su1 v6.4s, v8.4s, v5.4s + add v9.4s, v5.4s, v24.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + sha256su0 v8.4s, v5.4s + sha256su1 v7.4s, v5.4s, v6.4s + add v10.4s, v6.4s, v25.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + sha256su0 v5.4s, v6.4s + sha256su1 v8.4s, v6.4s, v7.4s + add v9.4s, v7.4s, v26.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + sha256su0 v6.4s, v7.4s + sha256su1 v5.4s, v7.4s, v8.4s + add v10.4s, v8.4s, v27.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + sha256su0 v7.4s, v8.4s + sha256su1 v6.4s, v8.4s, v5.4s + add v9.4s, v5.4s, v28.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + sha256su0 v8.4s, v5.4s + sha256su1 v7.4s, v5.4s, v6.4s + add v10.4s, v6.4s, v29.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + sha256su1 v8.4s, v6.4s, v7.4s + add v9.4s, v7.4s, v30.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + add v10.4s, v8.4s, v31.4s + mov v4.16b, v2.16b + sha256h q2, q3, v9.4s + sha256h2 q3, q4, v9.4s + mov v4.16b, v2.16b + sha256h q2, q3, v10.4s + sha256h2 q3, q4, v10.4s + cmp x1, x2 + add v1.4s, v1.4s, v3.4s + add v0.4s, v0.4s, v2.4s + b.ne .Lsha256loop + +.Lsha256epilog: + + st1 {v0.4s,v1.4s}, [x0] + ldr q10, [sp, #48] + ldr q9, [sp, #32] + ldr q8, [sp, #16] + ldr x29, [sp], #64 + ret + +.align 5 +.LKConstant256: +.word 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5 +.word 0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5 +.word 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3 +.word 0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174 +.word 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc +.word 0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da +.word 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7 +.word 0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967 +.word 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13 +.word 0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85 +.word 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3 +.word 0xd192e819,0xd6990624,0xf40e3585,0x106aa070 +.word 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5 +.word 0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3 +.word 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208 +.word 0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 + +.size sha256_block_data_order,.-sha256_block_data_order +.align 2 + + +