mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-24 13:31:12 +00:00
319 lines
12 KiB
C
319 lines
12 KiB
C
/*
|
|
* Copyright (c) 2018-2019 Atmosphère-NX
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include "utils.h"
|
|
#include "loader.h"
|
|
#include "fs_utils.h"
|
|
#include "stage2.h"
|
|
#include "lib/ini.h"
|
|
#include "lib/log.h"
|
|
|
|
static loader_ctx_t g_loader_ctx = {0};
|
|
|
|
loader_ctx_t *get_loader_ctx(void) {
|
|
return &g_loader_ctx;
|
|
}
|
|
|
|
static int loadlist_entry_ini_handler(void *user, const char *section, const char *name, const char *value) {
|
|
load_file_t *load_file_ctx = (load_file_t *)user;
|
|
uintptr_t x = 0;
|
|
const char *ext = NULL;
|
|
|
|
if (strcmp(section, "stage2") == 0) {
|
|
if (strstr(name, load_file_ctx->key) == name) {
|
|
ext = name + strlen(load_file_ctx->key);
|
|
if (strcmp(ext, "_path") == 0) {
|
|
/* Copy in the path. */
|
|
strncpy(load_file_ctx->path, value, sizeof(load_file_ctx->path) - 1);
|
|
load_file_ctx->path[sizeof(load_file_ctx->path) - 1] = '\0';
|
|
} else if (strcmp(ext, "_addr") == 0) {
|
|
/* Read in load address as a hex string. */
|
|
sscanf(value, "%x", &x);
|
|
load_file_ctx->load_address = x;
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void load_list_entry(const char *key) {
|
|
load_file_t load_file_ctx = {0};
|
|
struct stat st;
|
|
size_t size;
|
|
size_t entry_num;
|
|
chainloader_entry_t *entry;
|
|
load_file_ctx.key = key;
|
|
|
|
if (ini_parse_string(get_loader_ctx()->bct0, loadlist_entry_ini_handler, &load_file_ctx) < 0) {
|
|
fatal_error("Failed to parse BCT.ini!\n");
|
|
}
|
|
|
|
if (load_file_ctx.load_address == 0 || load_file_ctx.path[0] == '\x00') {
|
|
fatal_error("Failed to determine where to load %s from!\n", key);
|
|
}
|
|
|
|
if (strlen(load_file_ctx.path) > LOADER_MAX_PATH_SIZE) {
|
|
fatal_error("The filename for %s is too long!\n", key);
|
|
}
|
|
|
|
if (!check_32bit_address_loadable(load_file_ctx.load_address)) {
|
|
fatal_error("Load address 0x%08x is invalid (%s)!\n", load_file_ctx.load_address, key);
|
|
}
|
|
|
|
if (stat(load_file_ctx.path, &st) == -1) {
|
|
fatal_error("Failed to stat file %s: %s!\n", load_file_ctx.path, strerror(errno));
|
|
}
|
|
|
|
size = (size_t)st.st_size;
|
|
if (!check_32bit_address_range_loadable(load_file_ctx.load_address, size)) {
|
|
fatal_error("%s has an invalid load address & size combination!\n", key);
|
|
}
|
|
|
|
entry_num = g_chainloader_num_entries++;
|
|
entry = &g_chainloader_entries[entry_num];
|
|
entry->load_address = load_file_ctx.load_address;
|
|
entry->size = size;
|
|
entry->num = entry_num;
|
|
get_loader_ctx()->nb_files_to_load++;
|
|
|
|
strcpy(get_loader_ctx()->file_paths_to_load[entry_num], load_file_ctx.path);
|
|
}
|
|
|
|
static void parse_loadlist(const char *ll) {
|
|
print(SCREEN_LOG_LEVEL_DEBUG, "Parsing load list: %s\n", ll);
|
|
|
|
char load_list[0x200] = {0};
|
|
strncpy(load_list, ll, 0x200 - 1);
|
|
|
|
char *entry, *p;
|
|
|
|
/* entry will point to the start of the current entry. p will point to the current location in the list. */
|
|
entry = load_list;
|
|
p = load_list;
|
|
|
|
while (*p) {
|
|
if (*p == ' ' || *p == '\t') {
|
|
/* We're at the end of an entry. */
|
|
*p = '\x00';
|
|
|
|
/* Load the entry. */
|
|
load_list_entry(entry);
|
|
/* Skip to the next delimiter. */
|
|
for (; *p == ' ' || *p == '\t' || *p == '\x00'; p++) { }
|
|
if (*p != '|') {
|
|
fatal_error("Load list is malformed!\n");
|
|
} else {
|
|
/* Skip to the next entry. */
|
|
for (; *p == ' ' || *p == '\t' || *p == '|'; p++) { }
|
|
entry = p;
|
|
}
|
|
}
|
|
|
|
p++;
|
|
if (*p == '\x00') {
|
|
/* We're at the end of the line, load the last entry */
|
|
load_list_entry(entry);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int loadlist_ini_handler(void *user, const char *section, const char *name, const char *value) {
|
|
loader_ctx_t *loader_ctx = (loader_ctx_t *)user;
|
|
uintptr_t x = 0;
|
|
|
|
if (strcmp(section, "stage2") == 0) {
|
|
if (strcmp(name, LOADER_LOADLIST_KEY) == 0) {
|
|
parse_loadlist(value);
|
|
} else if (strcmp(name, LOADER_ENTRYPOINT_KEY) == 0) {
|
|
/* Read in entrypoint as a hex string. */
|
|
sscanf(value, "%x", &x);
|
|
loader_ctx->chainload_entrypoint = x;
|
|
} else if (strcmp(name, LOADER_CUSTOMSPLASH_KEY) == 0) {
|
|
strncpy(loader_ctx->custom_splash_path, value, LOADER_MAX_PATH_SIZE);
|
|
loader_ctx->custom_splash_path[LOADER_MAX_PATH_SIZE] = '\0';
|
|
} else if (strcmp(name, LOADER_PACKAGE2_KEY) == 0) {
|
|
strncpy(loader_ctx->package2_path, value, LOADER_MAX_PATH_SIZE);
|
|
loader_ctx->package2_path[LOADER_MAX_PATH_SIZE] = '\0';
|
|
} else if (strcmp(name, LOADER_EXOSPHERE_KEY) == 0) {
|
|
strncpy(loader_ctx->exosphere_path, value, LOADER_MAX_PATH_SIZE);
|
|
loader_ctx->exosphere_path[LOADER_MAX_PATH_SIZE] = '\0';
|
|
} else if (strcmp(name, LOADER_TSECFW_KEY) == 0) {
|
|
strncpy(loader_ctx->tsecfw_path, value, LOADER_MAX_PATH_SIZE);
|
|
loader_ctx->tsecfw_path[LOADER_MAX_PATH_SIZE] = '\0';
|
|
} else if (strcmp(name, LOADER_WARMBOOT_KEY) == 0) {
|
|
strncpy(loader_ctx->warmboot_path, value, LOADER_MAX_PATH_SIZE);
|
|
loader_ctx->warmboot_path[LOADER_MAX_PATH_SIZE] = '\0';
|
|
} else {
|
|
return 0;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int chainloader_entry_comparator(const void *a, const void *b) {
|
|
const chainloader_entry_t *e1 = (const chainloader_entry_t *)a;
|
|
const chainloader_entry_t *e2 = (const chainloader_entry_t *)b;
|
|
|
|
return (int)(e1->load_address - e2->load_address);
|
|
}
|
|
|
|
static uintptr_t find_carveout(chainloader_entry_t *entries, size_t num_entries, size_t requested) {
|
|
for(size_t i = 0; i < num_entries; i++) {
|
|
uintptr_t lbound = entries[i].load_address + entries[i].size;
|
|
if (check_32bit_address_range_loadable(lbound, requested)) {
|
|
if (i == num_entries - 1 || lbound + requested <= entries[i + 1].load_address)
|
|
return lbound;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void load_payload(const char *bct0) {
|
|
loader_ctx_t *ctx = get_loader_ctx();
|
|
bool can_load_in_place = true;
|
|
|
|
extern uint8_t __chainloader_start__[], __chainloader_end__[];
|
|
extern uint8_t __stack_bottom__[], __stack_top__[];
|
|
extern uint8_t __heap_start__[], __heap_end__[];
|
|
extern uint8_t __start__[], __end__[];
|
|
|
|
static chainloader_entry_t nonallocatable_entries[] = {
|
|
{.load_address = 0x00000000, .size = 0x40010000}, /* Unknown/Invalid + Low IRAM */
|
|
{.load_address = 0x40040000, .size = 0x80000000 - 0x40040000}, /* Invalid/MMIO */
|
|
{.load_address = (uintptr_t)__chainloader_start__, .size = 0},
|
|
{.load_address = (uintptr_t)__stack_bottom__, .size = 0},
|
|
{.load_address = (uintptr_t)__heap_start__, .size = 0},
|
|
{.load_address = (uintptr_t)__start__, .size = 0},
|
|
{.load_address = 0, .size = 0}, /* Min to max loaded address */
|
|
};
|
|
static const size_t num_nonallocatable_entries = sizeof(nonallocatable_entries) / sizeof(nonallocatable_entries[0]);
|
|
if (nonallocatable_entries[2].size == 0) {
|
|
/* Initializers aren't computable at load time */
|
|
nonallocatable_entries[2].size = __chainloader_end__ - __chainloader_start__;
|
|
nonallocatable_entries[3].size = __stack_top__ - __stack_bottom__;
|
|
nonallocatable_entries[4].size = __heap_end__ - __heap_start__;
|
|
nonallocatable_entries[5].size = __end__ - __start__;
|
|
}
|
|
|
|
/* Set BCT0 global. */
|
|
ctx->bct0 = bct0;
|
|
|
|
if (ini_parse_string(ctx->bct0, loadlist_ini_handler, ctx) < 0) {
|
|
fatal_error("Failed to parse BCT.ini!\n");
|
|
}
|
|
|
|
if (ctx->chainload_entrypoint != 0 || ctx->nb_files_to_load > 0) {
|
|
fatal_error("loadlist must be empty when booting Horizon!\n");
|
|
}
|
|
|
|
/* Sort the entries by ascending load addresses */
|
|
qsort(g_chainloader_entries, g_chainloader_num_entries, sizeof(chainloader_entry_t), chainloader_entry_comparator);
|
|
|
|
/* Check for possible overlap and find the entrypoint, also determine whether we can load in-place */
|
|
ctx->file_id_of_entrypoint = ctx->nb_files_to_load;
|
|
for (size_t i = 0; i < ctx->nb_files_to_load; i++) {
|
|
if (i != ctx->nb_files_to_load - 1 &&
|
|
overlaps(
|
|
g_chainloader_entries[i].load_address,
|
|
g_chainloader_entries[i].load_address + g_chainloader_entries[i].size,
|
|
g_chainloader_entries[i + 1].load_address,
|
|
g_chainloader_entries[i + 1].load_address + g_chainloader_entries[i + 1].size
|
|
)
|
|
) {
|
|
fatal_error(
|
|
"Loading ranges for files %s and %s overlap!\n",
|
|
ctx->file_paths_to_load[g_chainloader_entries[i].num],
|
|
ctx->file_paths_to_load[g_chainloader_entries[i + 1].num]
|
|
);
|
|
generic_panic();
|
|
}
|
|
|
|
if(ctx->chainload_entrypoint >= g_chainloader_entries[i].load_address &&
|
|
ctx->chainload_entrypoint < g_chainloader_entries[i].load_address + g_chainloader_entries[i].size) {
|
|
ctx->file_id_of_entrypoint = g_chainloader_entries[i].num;
|
|
}
|
|
|
|
can_load_in_place = can_load_in_place &&
|
|
check_32bit_address_range_in_program(
|
|
g_chainloader_entries[i].load_address,
|
|
g_chainloader_entries[i].load_address + g_chainloader_entries[i].size
|
|
);
|
|
}
|
|
|
|
if (ctx->chainload_entrypoint != 0 && ctx->file_id_of_entrypoint >= ctx->nb_files_to_load) {
|
|
fatal_error("Entrypoint not in range of any of the files!\n");
|
|
}
|
|
|
|
if (can_load_in_place) {
|
|
for (size_t i = 0; i < ctx->nb_files_to_load; i++) {
|
|
chainloader_entry_t *entry = &g_chainloader_entries[i];
|
|
entry->src_address = entry->load_address;
|
|
if (read_from_file((void *)entry->src_address, entry->size, ctx->file_paths_to_load[entry->num]) != entry->size) {
|
|
fatal_error("Failed to read file %s: %s!\n", ctx->file_paths_to_load[entry->num], strerror(errno));
|
|
}
|
|
}
|
|
} else {
|
|
size_t total_size = 0;
|
|
uintptr_t carveout, min_addr, max_addr, pos;
|
|
|
|
for (size_t i = 0; i < ctx->nb_files_to_load; i++) {
|
|
total_size += g_chainloader_entries[i].size;
|
|
}
|
|
|
|
min_addr = g_chainloader_entries[g_chainloader_num_entries - 1].load_address;
|
|
max_addr = g_chainloader_entries[g_chainloader_num_entries - 1].load_address +
|
|
g_chainloader_entries[g_chainloader_num_entries - 1].size;
|
|
|
|
nonallocatable_entries[num_nonallocatable_entries - 1].load_address = min_addr;
|
|
nonallocatable_entries[num_nonallocatable_entries - 1].size = max_addr - min_addr;
|
|
|
|
/* We need to find a carveout, first outside the loaded range, then inside */
|
|
qsort(nonallocatable_entries, num_nonallocatable_entries, sizeof(chainloader_entry_t), chainloader_entry_comparator);
|
|
|
|
carveout = find_carveout(nonallocatable_entries, num_nonallocatable_entries, total_size);
|
|
if (carveout == 0) {
|
|
carveout = find_carveout(g_chainloader_entries, g_chainloader_num_entries, total_size);
|
|
}
|
|
if (carveout == 0) {
|
|
fatal_error("Failed to find a carveout!\n");
|
|
}
|
|
|
|
/* Finally, read the files into the carveout */
|
|
pos = carveout;
|
|
for (size_t i = 0; i < ctx->nb_files_to_load; i++) {
|
|
chainloader_entry_t *entry = &g_chainloader_entries[i];
|
|
entry->src_address = pos;
|
|
if (read_from_file((void *)entry->src_address, entry->size, ctx->file_paths_to_load[entry->num]) != entry->size) {
|
|
fatal_error("Failed to read file %s: %s!\n", ctx->file_paths_to_load[entry->num], strerror(errno));
|
|
}
|
|
pos += entry->size;
|
|
}
|
|
}
|
|
}
|