diff --git a/.gitmodules b/.gitmodules index 1afbb8c..ceda820 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/libtesla"] path = libs/libtesla url = https://github.com/WerWolv/libtesla +[submodule "libs/inih"] + path = libs/inih + url = https://github.com/benhoyt/inih.git diff --git a/Makefile b/Makefile index 4e70d2a..78f11a2 100644 --- a/Makefile +++ b/Makefile @@ -37,14 +37,14 @@ include $(DEVKITPRO)/libnx/switch_rules # of a homebrew executable (.nro). This is intended to be used for sysmodules. # NACP building is skipped as well. #--------------------------------------------------------------------------------- -APP_TITLE := Tesla Overlay Template +APP_TITLE := fastCFWswitch APP_VERSION := 1.0.0 TARGET := $(notdir $(CURDIR)) BUILD := build -SOURCES := source +SOURCES := source libs/inih DATA := data -INCLUDES := include libs/libtesla/include +INCLUDES := include libs/libtesla/include libs/inih NO_ICON := 1 @@ -56,7 +56,7 @@ ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE CFLAGS := -g -Wall -O2 -ffunction-sections \ $(ARCH) $(DEFINES) -CFLAGS += $(INCLUDE) -D__SWITCH__ +CFLAGS += $(INCLUDE) -I -D__SWITCH__ CXXFLAGS := $(CFLAGS) -fno-exceptions -std=c++17 diff --git a/README.md b/README.md index f57f076..2ede045 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# Tesla-Template -A template repository for Tesla Overlay Homebrews +# fastCFWswitch +A tesla based overlay to boot into different payloads on the Nintendo Switch. \ No newline at end of file diff --git a/libs/inih b/libs/inih new file mode 160000 index 0000000..3512171 --- /dev/null +++ b/libs/inih @@ -0,0 +1 @@ +Subproject commit 351217124ddb3e3fe2b982248a04c672350bb0af diff --git a/libs/libtesla b/libs/libtesla index 2f59ad6..f0e87f1 160000 --- a/libs/libtesla +++ b/libs/libtesla @@ -1 +1 @@ -Subproject commit 2f59ad660890abc9c17bdbc63c7646a3a11fb2ef +Subproject commit f0e87f12582ac3eeb2c3d3f98b1b054e45479095 diff --git a/out/config/fastCFWSwitch/config.ini b/out/config/fastCFWSwitch/config.ini new file mode 100644 index 0000000..2476a88 --- /dev/null +++ b/out/config/fastCFWSwitch/config.ini @@ -0,0 +1,18 @@ +[CFWS] +type=section +name=cfws + +[ATMOSPHERE] +name=atmosphere +path=/atmosphere/reboot_payload.bin + +[SXOS] +name=SxOS +path=/sxos/reboot_payload.bin + +[TOOLS] +name=Tools + +[HEKATE] +name=Hekate +path=/hekate_ctcaer_4.9.1.bin \ No newline at end of file diff --git a/source/configParser.cpp b/source/configParser.cpp new file mode 100644 index 0000000..53194ce --- /dev/null +++ b/source/configParser.cpp @@ -0,0 +1,161 @@ +#include "configParser.h" +#include "ini.h" +#include +#include +#include + +#include "section.h" +#include "payload.h" + +using namespace fastCFWSwitcher; + +namespace fastCFWSwitcher { + class ConfigEntry{ + public: + ConfigEntry(std::string section, int pos) : section(section), pos(pos){ + this->type.assign("payload"); + } + void setType(const char * type){ + this->type.assign(type); + } + void setName(const char * name){ + this->name.assign(name); + } + void setPath(const char * path){ + this->path.assign(path); + } + + Element* toElement(){ + if(type == "section" || path.empty()){ + return (Element*) new Section(name); + } else if(type == "payload"){ + return (Element*) new Payload(name, path); + } + return nullptr; + } + + static bool comparePos(const ConfigEntry* first, const ConfigEntry* second) + { + return (first->pos < second->pos); + } + + std::string section; + int pos; + std::string type; + std::string name; + std::string path; + private: + }; +} + +int handler(void *user, const char *section, const char *name, const char *value) + +{ + std::map* elementsMap = (std::map*) user; + + std::string sectionString(section); + fastCFWSwitcher::ConfigEntry* configEntry ; + + std::map::iterator it = elementsMap->find(sectionString); + if( it != elementsMap->end() ){ + configEntry = it->second; + } else { + configEntry = new fastCFWSwitcher::ConfigEntry(sectionString, elementsMap->size()); + elementsMap->insert(std::pair(sectionString, configEntry)); + } + + if (!strcmp("type", name) ) { + configEntry->setType(value); + } else if (!strcmp("name", name) ) { + configEntry->setName(value); + } else if (!strcmp("path", name)) { + configEntry->setPath(value); + } + return 1; +} + +void ConfigParser::setError(std::string errorString){ + this->list->addItem(new tsl::elm::CategoryHeader(errorString)); +} + +std::list* ConfigParser::getElements(){ + + // Read settings file + + FsFileSystem fsSdmc; + + if(R_FAILED(fsOpenSdCardFileSystem(&fsSdmc))){ + setError("open sd failed\n"); + return nullptr; + } + + /* Open config file. */ + FsFile fileConfig; + if (R_FAILED(fsFsOpenFile(&fsSdmc, this->filePath, FsOpenMode_Read, &fileConfig))){ + setError("open config file failed "); + fsFsClose(&fsSdmc); + return nullptr; + } + + + /* Get config file size. */ + s64 configFileSize; + if (R_FAILED(fsFileGetSize(&fileConfig, &configFileSize))){ + setError("get file size failed\n"); + fsFileClose(&fileConfig); + fsFsClose(&fsSdmc); + return nullptr; + } + + std::vector fileString(configFileSize+1); + fileString[configFileSize] = 0; + + u64 readSize; + Result rc = fsFileRead(&fileConfig, 0, &fileString[0], configFileSize, FsReadOption_None, &readSize); + if (R_FAILED(rc) || readSize != static_cast(configFileSize)){ + setError("read file failed\n"); + fsFileClose(&fileConfig); + fsFsClose(&fsSdmc); + return nullptr; + } + + fsFileClose(&fileConfig); + fsFsClose(&fsSdmc); + + + //parse ini file + std::map elementsMap; + + int config_err = ini_parse_string(&fileString[0], handler, &elementsMap); + if(config_err<0){ + setError("An error occured."); + return nullptr; + } else if(config_err>0){ + std::stringstream ss; + ss<< std::hex << config_err; // int decimal_value + std::string res ( ss.str() ); + setError("Bad config file, first error on line "+res); + return nullptr; + } + std::list* elementsList = new std::list(); + for (auto const& x : elementsMap) + { + elementsList->push_back(x.second); + } + + elementsList->sort(fastCFWSwitcher::ConfigEntry::comparePos); + + std::list* payloadList = new std::list(); + + for (fastCFWSwitcher::ConfigEntry* entry : *elementsList) + { + fastCFWSwitcher::Element* element = entry->toElement(); + if(element!=nullptr){ + payloadList->push_back(element); + //setError(x.second->path); + } else { + setError("error "+entry->section); + } + } + return payloadList; +} \ No newline at end of file diff --git a/source/configParser.h b/source/configParser.h new file mode 100644 index 0000000..4ed6443 --- /dev/null +++ b/source/configParser.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include "element.h" +#include + +#define CONFIG_FILE_PATH "/config/fastCFWSwitch/config.ini" + +namespace fastCFWSwitcher{ + class ConfigParser{ + public: + ConfigParser(const char* filePath, tsl::elm::List* list) : filePath(filePath), list(list){ + }; + ~ConfigParser(){ + } + std::list* getElements(); + void setError(std::string errorString); + + private: + const char* filePath; + tsl::elm::List* list; + }; +} diff --git a/source/element.h b/source/element.h new file mode 100644 index 0000000..b7188f8 --- /dev/null +++ b/source/element.h @@ -0,0 +1,10 @@ +#pragma once +#include +namespace fastCFWSwitcher{ + class PayloadHandler; + + class Element{ + public: + virtual tsl::elm::ListItem* toListItem(fastCFWSwitcher::PayloadHandler* payloadHandler) = 0; + }; +} diff --git a/source/main.cpp b/source/main.cpp index fad560a..faf0f11 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,5 +1,9 @@ #define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one #include // The Tesla Header +#include "payloadHandler.h" +#include "payload.h" +#include "section.h" +#include "configParser.h" class GuiTest : public tsl::Gui { @@ -9,15 +13,35 @@ class GuiTest : public tsl::Gui { // Called when this Gui gets loaded to create the UI // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore virtual tsl::elm::Element* createUI() override { - // A OverlayFrame is the base element every overlay consists of. This will draw the default Title and Subtitle. - // If you need more information in the header or want to change it's look, use a HeaderOverlayFrame. - auto frame = new tsl::elm::OverlayFrame("Tesla Example", "v1.3.1"); - - // A list that can contain sub elements and handles scrolling + auto frame = new tsl::elm::OverlayFrame("Switch CFW", "v1.0.0"); auto list = new tsl::elm::List(); - // Create and add a new list item to the list - list->addItem(new tsl::elm::ListItem("Default List Item")); + fastCFWSwitcher::ConfigParser* configParser = new fastCFWSwitcher::ConfigParser(CONFIG_FILE_PATH, list); + fastCFWSwitcher::PayloadHandler* payloadHandler = new fastCFWSwitcher::PayloadHandler(frame); + + + std::list* payloadList = configParser->getElements(); + + /*fastCFWSwitcher::Element* payload = (fastCFWSwitcher::Element*) new fastCFWSwitcher::Payload("atmosphere", "/atmosphere/reboot_payload.bin"); + payloadList.push_back(payload); + + payload = (fastCFWSwitcher::Element*) new fastCFWSwitcher::Payload("SXOS", "/sxos/reboot_payload.bin"); + payloadList.push_back(payload); + + payload = (fastCFWSwitcher::Element*) new fastCFWSwitcher::Section("Tools"); + payloadList.push_back(payload); + payload = (fastCFWSwitcher::Element*) new fastCFWSwitcher::Payload("Hekate", "/bootloader/reboot_payload.bin"); + payloadList.push_back(payload); +*/ + + if(payloadList!=nullptr){ + for(fastCFWSwitcher::Element* curPayload : *payloadList){ + auto item = curPayload->toListItem(payloadHandler); + list->addItem(item); + } + } else { + list->addItem(new tsl::elm::CategoryHeader("list is null")); + } // Add the list to the frame for it to be drawn frame->setContent(list); @@ -35,9 +59,10 @@ class GuiTest : public tsl::Gui { virtual bool handleInput(u64 keysDown, u64 keysHeld, touchPosition touchInput, JoystickPosition leftJoyStick, JoystickPosition rightJoyStick) override { return false; // Return true here to singal the inputs have been consumed } + }; -class OverlayTest : public tsl::Overlay { +class FastCFWSwitchOverlay : public tsl::Overlay { public: // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys virtual void initServices() override {} // Called at the start to initialize all services necessary for this Overlay @@ -52,5 +77,5 @@ class OverlayTest : public tsl::Overlay { }; int main(int argc, char **argv) { - return tsl::loop(argc, argv); -} \ No newline at end of file + return tsl::loop(argc, argv); +} diff --git a/source/payload.cpp b/source/payload.cpp new file mode 100644 index 0000000..5b45ae2 --- /dev/null +++ b/source/payload.cpp @@ -0,0 +1,23 @@ +#include "payload.h" +#include "payloadHandler.h" +#include "element.h" + +using namespace fastCFWSwitcher; +using namespace tsl::elm; + + + + +ListItem* Payload::toListItem(PayloadHandler* payloadHandler){ + auto item = new ListItem(this->name); + std::function onClick = [item, payloadHandler, this](u64 keys) { + if(keys & KEY_A){ + item->setText("rebooting"); + payloadHandler->rebootToPayload(this); + return true; + } + return false; + }; + item->setClickListener(onClick); + return item; +} \ No newline at end of file diff --git a/source/payload.h b/source/payload.h new file mode 100644 index 0000000..18a92b9 --- /dev/null +++ b/source/payload.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include "element.h" + +namespace fastCFWSwitcher { + class Payload : Element { + + public: + Payload(std::string name, std::string path) : name(name), path(path){ + } + ~Payload(){}; + tsl::elm::ListItem* toListItem(fastCFWSwitcher::PayloadHandler* payloadHandler); + std::string getPath(){ + return path; + } + + private: + std::string name; + std::string path; + }; +} diff --git a/source/payloadHandler.cpp b/source/payloadHandler.cpp new file mode 100644 index 0000000..77df65b --- /dev/null +++ b/source/payloadHandler.cpp @@ -0,0 +1,140 @@ + + +#include + +#include "payload.h" +#include "payloadHandler.h" + +using namespace fastCFWSwitcher; + +#define IRAM_PAYLOAD_MAX_SIZE 0x2F000 +#define IRAM_PAYLOAD_BASE 0x40010000 + +alignas(0x1000) static u8 g_ff_page[0x1000]; +alignas(0x1000) static u8 g_work_page[0x1000]; +alignas(0x1000) static u8 g_reboot_payload[IRAM_PAYLOAD_MAX_SIZE]; + + +void PayloadHandler::rebootToPayload(fastCFWSwitcher::Payload* payload){ + reboot_to_payload(payload->getPath().c_str()); +} + +void PayloadHandler::do_iram_dram_copy(void *buf, uintptr_t iram_addr, size_t size, int option) { + memcpy(g_work_page, buf, size); + + SecmonArgs args = {0}; + args.X[0] = 0xF0000201; /* smcAmsIramCopy */ + args.X[1] = (uintptr_t)g_work_page; /* DRAM Address */ + args.X[2] = iram_addr; /* IRAM Address */ + args.X[3] = size; /* Copy size */ + args.X[4] = option; /* 0 = Read, 1 = Write */ + svcCallSecureMonitor(&args); + + memcpy(buf, g_work_page, size); +} + +void PayloadHandler::copy_to_iram(uintptr_t iram_addr, void *buf, size_t size) { + do_iram_dram_copy(buf, iram_addr, size, 1); +} +void PayloadHandler::copy_from_iram(void *buf, uintptr_t iram_addr, size_t size) { + do_iram_dram_copy(buf, iram_addr, size, 0); +} + + +void PayloadHandler::clear_iram(void) { + memset(g_ff_page, 0xFF, sizeof(g_ff_page)); + for (size_t i = 0; i < IRAM_PAYLOAD_MAX_SIZE; i += sizeof(g_ff_page)) { + copy_to_iram(IRAM_PAYLOAD_BASE + i, g_ff_page, sizeof(g_ff_page)); + } +} + +void PayloadHandler::setError(std::string errorString){ + this->frame->setSubtitle(errorString); +} + +//todo better error handling +void PayloadHandler::reboot_to_payload(const char* payloadPath) { + smInitialize(); + Result splResult = splInitialize(); + smExit(); + if (R_FAILED(splResult)){ + std::stringstream ss; + ss<< std::hex << splResult; // int decimal_value + std::string res ( ss.str() ); + setError("splInitFailed: "+res); + return ; + } + + FsFileSystem fsSdmc; + FsFile fileConfig; + + if(R_FAILED(fsOpenSdCardFileSystem(&fsSdmc))){ + setError("open sd failed\n"); + splExit(); + return ; + } + + /* Open config file. */ + if (R_FAILED(fsFsOpenFile(&fsSdmc, payloadPath, FsOpenMode_Read, &fileConfig))){ + setError("open file failed\n"); + fsFsClose(&fsSdmc); + splExit(); + return ; + } + + + /* Get config file size. */ + s64 configFileSize; + if (R_FAILED(fsFileGetSize(&fileConfig, &configFileSize))){ + setError("get file size failed\n"); + fsFileClose(&fileConfig); + fsFsClose(&fsSdmc); + splExit(); + return ; + } + + if(configFileSize>sizeof(g_reboot_payload)){ + setError("to big\n"); + fsFileClose(&fileConfig); + fsFsClose(&fsSdmc); + splExit(); + return; + } + + memset(g_reboot_payload, 0xFF, sizeof(g_reboot_payload)); + u64 readSize; + Result rc = fsFileRead(&fileConfig, 0, g_reboot_payload, configFileSize, FsReadOption_None, &readSize); + if (R_FAILED(rc) || readSize != static_cast(configFileSize)){ + setError("read file failed\n"); + fsFileClose(&fileConfig); + fsFsClose(&fsSdmc); + splExit(); + return ; + } + + fsFileClose(&fileConfig); + fsFsClose(&fsSdmc); + clear_iram(); + + for (size_t i = 0; i < IRAM_PAYLOAD_MAX_SIZE; i += 0x1000) { + copy_to_iram(IRAM_PAYLOAD_BASE + i, &g_reboot_payload[i], 0x1000); + } + + //check for corectness + int result=1; + for (size_t i = 0; i < IRAM_PAYLOAD_MAX_SIZE; i += 0x1000) { + copy_from_iram(&g_ff_page, IRAM_PAYLOAD_BASE + i, 0x1000); + result = memcmp(&g_ff_page,&g_reboot_payload[i], 0x1000); + if(result){ + result=i; + break; + } + } + if(result){ + setError("cmp failed"+std::to_string(result)); + } else{ + splSetConfig((SplConfigItem)65001, 2); + } + + splExit(); +} diff --git a/source/payloadHandler.h b/source/payloadHandler.h new file mode 100644 index 0000000..a90d193 --- /dev/null +++ b/source/payloadHandler.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include "payload.h" + +namespace fastCFWSwitcher { + + class PayloadHandler{ + + public: + PayloadHandler(tsl::elm::OverlayFrame* frame) : frame(frame){ + } + ~PayloadHandler(){} + void rebootToPayload(fastCFWSwitcher::Payload* payload); + + private: + void setError(std::string errorString); + void do_iram_dram_copy(void *buf, uintptr_t iram_addr, size_t size, int option); + void copy_to_iram(uintptr_t iram_addr, void *buf, size_t size); + void copy_from_iram(void *buf, uintptr_t iram_addr, size_t size) ; + void clear_iram(void); + void reboot_to_payload(const char* payloadPath); + tsl::elm::OverlayFrame* frame; + }; +} \ No newline at end of file diff --git a/source/section.cpp b/source/section.cpp new file mode 100644 index 0000000..0009b9c --- /dev/null +++ b/source/section.cpp @@ -0,0 +1,9 @@ +#include "section.h" +#include "element.h" + +using namespace fastCFWSwitcher; +using namespace tsl::elm; + +ListItem* Section::toListItem(fastCFWSwitcher::PayloadHandler* PayloadHandler){ + return new CategoryHeader(this->name); +} \ No newline at end of file diff --git a/source/section.h b/source/section.h new file mode 100644 index 0000000..33cfb3e --- /dev/null +++ b/source/section.h @@ -0,0 +1,17 @@ +#pragma once +#include +#include "element.h" + +namespace fastCFWSwitcher { + class Section : Element { + + public: + Section(std::string name) : name(name){ + } + ~Section(){} + tsl::elm::ListItem* toListItem(fastCFWSwitcher::PayloadHandler* PayloadHandler); + + private: + std::string name; + }; +}