diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 0836926..64b5e54 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -14,7 +14,8 @@ "defines": [ "SWITCH", "__SWITCH__", - "__aarch64__" + "__aarch64__", + "VERSION=\"\"" ], "compilerPath": "C:/devkitPro/devkitA64/bin/aarch64-none-elf-g++", "cStandard": "c11", @@ -36,7 +37,8 @@ "defines": [ "SWITCH", "__SWITCH__", - "__aarch64__" + "__aarch64__", + "VERSION=\"\"" ], "compilerPath": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-g++", "cStandard": "c11", diff --git a/Makefile b/Makefile index 2740105..a9192d0 100644 --- a/Makefile +++ b/Makefile @@ -38,13 +38,18 @@ include $(DEVKITPRO)/libnx/switch_rules # NACP building is skipped as well. #--------------------------------------------------------------------------------- APP_TITLE := Sysmodules +APP_VERSION := 1.2.0 -TARGET := $(notdir $(CURDIR)) +TARGET := ovlSysmodules BUILD := build SOURCES := source DATA := data INCLUDES := include libs/libtesla/include +ifeq ($(RELEASE),) + APP_VERSION := $(APP_VERSION)-$(shell git describe --dirty --always) +endif + NO_ICON := 1 #--------------------------------------------------------------------------------- @@ -55,7 +60,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) -D__SWITCH__ -DVERSION=\"v$(APP_VERSION)\" CXXFLAGS := $(CFLAGS) -fno-exceptions -std=c++17 diff --git a/include/dir_iterator.hpp b/include/dir_iterator.hpp new file mode 100644 index 0000000..8ee185d --- /dev/null +++ b/include/dir_iterator.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +class FsDirIterator { + private: + FsDir m_dir; + FsDirectoryEntry entry; + s64 count; + + public: + FsDirIterator() = default; + FsDirIterator(FsDir dir); + + ~FsDirIterator() = default; + + const FsDirectoryEntry &operator*() const; + const FsDirectoryEntry *operator->() const; + FsDirIterator &operator++(); + + bool operator!=(const FsDirIterator &rhs); +}; + +inline FsDirIterator begin(FsDirIterator iter) noexcept { return iter; } + +inline FsDirIterator end(FsDirIterator) noexcept { return FsDirIterator(); } diff --git a/include/gui_main.hpp b/include/gui_main.hpp new file mode 100644 index 0000000..74bae13 --- /dev/null +++ b/include/gui_main.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +struct SystemModule { + tsl::elm::ListItem *listItem; + u64 programId; + bool needReboot; +}; + +class GuiMain : public tsl::Gui { + private: + FsFileSystem m_fs; + std::list m_sysmoduleListItems; + bool m_scanned; + + public: + GuiMain(); + ~GuiMain(); + + virtual tsl::elm::Element *createUI(); + virtual void update() override; + + private: + void updateStatus(const SystemModule &module); + bool hasFlag(const SystemModule &module); + bool isRunning(const SystemModule &module); +}; \ No newline at end of file diff --git a/libs/libtesla b/libs/libtesla index 1dcfe94..1ba52ab 160000 --- a/libs/libtesla +++ b/libs/libtesla @@ -1 +1 @@ -Subproject commit 1dcfe94ca51f1d0e637812a5cd6f72b6e1804966 +Subproject commit 1ba52ab5d8601d5b3b17c2915bc07a307958d96c diff --git a/source/dir_iterator.cpp b/source/dir_iterator.cpp new file mode 100644 index 0000000..3432d77 --- /dev/null +++ b/source/dir_iterator.cpp @@ -0,0 +1,24 @@ +#include "dir_iterator.hpp" + +FsDirIterator::FsDirIterator(FsDir dir) : m_dir(dir) { + if (R_FAILED(fsDirRead(&this->m_dir, &this->count, 1, &this->entry))) + this->count = 0; +} + +const FsDirectoryEntry &FsDirIterator::operator*() const { + return this->entry; +} + +const FsDirectoryEntry *FsDirIterator::operator->() const { + return &**this; +} + +FsDirIterator &FsDirIterator::operator++() { + if (R_FAILED(fsDirRead(&this->m_dir, &this->count, 1, &this->entry))) + this->count = 0; + return *this; +} + +bool FsDirIterator::operator!=(const FsDirIterator &__rhs) { + return this->count > 0; +} diff --git a/source/gui_main.cpp b/source/gui_main.cpp new file mode 100644 index 0000000..b00b328 --- /dev/null +++ b/source/gui_main.cpp @@ -0,0 +1,188 @@ +#include "gui_main.hpp" + +#include "dir_iterator.hpp" + +#include +using json = nlohmann::json; + +constexpr const char *const amsContentsPath = "/atmosphere/contents"; +constexpr const char *const boot2FlagFormat = "/atmosphere/contents/%016lX/flags/boot2.flag"; + +static char pathBuffer[FS_MAX_PATH]; + +constexpr const char *const descriptions[2][2] = { + [0] = { + [0] = "Off | \uE098", + [1] = "Off | \uE0F4", + }, + [1] = { + [0] = "On | \uE098", + [1] = "On | \uE0F4", + }, +}; + +GuiMain::GuiMain() { + Result rc = fsOpenSdCardFileSystem(&this->m_fs); + if (R_FAILED(rc)) + return; + + FsDir contentDir; + std::strcpy(pathBuffer, amsContentsPath); + rc = fsFsOpenDirectory(&this->m_fs, pathBuffer, FsDirOpenMode_ReadDirs, &contentDir); + if (R_FAILED(rc)) + return; + tsl::hlp::ScopeGuard dirGuard([&] { fsDirClose(&contentDir); }); + + /* Iterate over contents folder. */ + for (const auto &entry : FsDirIterator(contentDir)) { + FsFile toolboxFile; + std::snprintf(pathBuffer, FS_MAX_PATH, "/atmosphere/contents/%s/toolbox.json", entry.name); + rc = fsFsOpenFile(&this->m_fs, pathBuffer, FsOpenMode_Read, &toolboxFile); + if (R_FAILED(rc)) + continue; + tsl::hlp::ScopeGuard fileGuard([&] { fsFileClose(&toolboxFile); }); + + /* Get toolbox file size. */ + s64 size; + rc = fsFileGetSize(&toolboxFile, &size); + if (R_FAILED(rc)) + continue; + + /* Read toolbox file. */ + std::string toolBoxData(size, '\0'); + u64 bytesRead; + rc = fsFileRead(&toolboxFile, 0, toolBoxData.data(), size, FsReadOption_None, &bytesRead); + if (R_FAILED(rc)) + continue; + + /* Parse toolbox file data. */ + json toolboxFileContent = json::parse(toolBoxData); + + const std::string &sysmoduleProgramIdString = toolboxFileContent["tid"]; + u64 sysmoduleProgramId = std::strtoul(sysmoduleProgramIdString.c_str(), nullptr, 16); + + /* Let's not allow Tesla to be killed with this. */ + if (sysmoduleProgramId == 0x010000000007E51AULL) + continue; + + SystemModule module = { + .listItem = new tsl::elm::ListItem(toolboxFileContent["name"]), + .programId = sysmoduleProgramId, + .needReboot = toolboxFileContent["requires_reboot"], + }; + + module.listItem->setClickListener([this, module](u64 click) -> bool { + if (click & KEY_A && !module.needReboot) { + if (this->isRunning(module)) { + /* Kill process. */ + pmshellTerminateProgram(module.programId); + } else { + /* Start process. */ + const NcmProgramLocation programLocation{ + .program_id = module.programId, + .storageID = NcmStorageId_None, + }; + u64 pid = 0; + pmshellLaunchProgram(0, &programLocation, &pid); + } + return true; + } + + if (click & KEY_Y) { + std::snprintf(pathBuffer, FS_MAX_PATH, boot2FlagFormat, module.programId); + if (this->hasFlag(module)) { + /* Remove boot2 flag file. */ + fsFsDeleteFile(&this->m_fs, pathBuffer); + } else { + /* Create boot2 flag file. */ + fsFsCreateFile(&this->m_fs, pathBuffer, 0, FsCreateOption(0)); + } + return true; + } + + return false; + }); + this->m_sysmoduleListItems.push_back(std::move(module)); + } + this->m_scanned = true; +} + +GuiMain::~GuiMain() { + fsFsClose(&this->m_fs); +} + +tsl::elm::Element *GuiMain::createUI() { + tsl::elm::OverlayFrame *rootFrame = new tsl::elm::OverlayFrame("Sysmodules", VERSION); + + if (this->m_sysmoduleListItems.size() == 0) { + const char *description = this->m_scanned ? "No sysmodules found!" : "Scan failed!"; + + auto *warning = new tsl::elm::CustomDrawer([description](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { + renderer->drawString("\uE150", false, 180, 250, 90, renderer->a(0xFFFF)); + renderer->drawString(description, false, 110, 340, 25, renderer->a(0xFFFF)); + }); + + rootFrame->setContent(warning); + } else { + tsl::elm::List *sysmoduleList = new tsl::elm::List(); + sysmoduleList->addItem(new tsl::elm::CategoryHeader("Dynamic | \uE0E0 Toggle | \uE0E3 Toggle auto start", true)); + sysmoduleList->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { + renderer->drawString("\uE016 These sysmodules can be toggled at any time.", false, x + 5, y + 20, 15, renderer->a(tsl::style::color::ColorDescription)); + }), 30); + for (const auto &module : this->m_sysmoduleListItems) { + if (!module.needReboot) + sysmoduleList->addItem(module.listItem); + } + + sysmoduleList->addItem(new tsl::elm::CategoryHeader("Static | \uE0E3 Toggle auto start", true)); + sysmoduleList->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { + renderer->drawString("\uE016 These sysmodules need a reboot to work.", false, x + 5, y + 20, 15, renderer->a(tsl::style::color::ColorDescription)); + }), 30); + for (const auto &module : this->m_sysmoduleListItems) { + if (module.needReboot) + sysmoduleList->addItem(module.listItem); + } + rootFrame->setContent(sysmoduleList); + } + + return rootFrame; +} + +void GuiMain::update() { + static u32 counter = 0; + + if (counter++ % 20 != 0) + return; + + for (const auto &module : this->m_sysmoduleListItems) { + this->updateStatus(module); + } +} + +void GuiMain::updateStatus(const SystemModule &module) { + bool running = this->isRunning(module); + bool hasFlag = this->hasFlag(module); + + const char *desc = descriptions[running][hasFlag]; + module.listItem->setValue(desc); +} + +bool GuiMain::hasFlag(const SystemModule &module) { + FsFile flagFile; + std::snprintf(pathBuffer, FS_MAX_PATH, boot2FlagFormat, module.programId); + Result rc = fsFsOpenFile(&this->m_fs, pathBuffer, FsOpenMode_Read, &flagFile); + if (R_SUCCEEDED(rc)) { + fsFileClose(&flagFile); + return true; + } else { + return false; + } +} + +bool GuiMain::isRunning(const SystemModule &module) { + u64 pid = 0; + if (R_FAILED(pmdmntGetProcessId(&pid, module.programId))) + return false; + + return pid > 0; +} diff --git a/source/main.cpp b/source/main.cpp index 0544f60..b88dcb2 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,120 +1,12 @@ #define TESLA_INIT_IMPL -#include - -#include -#include -#include -#include - -using json = nlohmann::json; - - -bool isProgramRunning(u64 programId) { - u64 pid = 0; - if (R_FAILED(pmdmntGetProcessId(&pid, programId))) - return false; - - return pid > 0; -} - -class GuiMain : public tsl::Gui { -public: - GuiMain() { - - } - - ~GuiMain() { } - - virtual tsl::elm::Element* createUI() { - tsl::elm::OverlayFrame *rootFrame = new tsl::elm::OverlayFrame("Sysmodules", "v1.1.0"); - tsl::elm::List *sysmoduleList = new tsl::elm::List(); - - for (auto contentsFolder : std::filesystem::directory_iterator("sdmc:/atmosphere/contents")) { - auto toolboxFile = contentsFolder.path() / "toolbox.json"; - - if (std::filesystem::exists(toolboxFile)) { - json toolboxFileContent; - std::ifstream(toolboxFile) >> toolboxFileContent; - - std::string sysmoduleName = toolboxFileContent["name"]; - std::string sysmoduleProgramIdString = toolboxFileContent["tid"]; - u64 sysmoduleProgramId = strtoul(sysmoduleProgramIdString.c_str(), nullptr, 16); - bool sysmoduleRequiresReboot = toolboxFileContent["requires_reboot"]; - - if (sysmoduleRequiresReboot) - continue; - - // Let's not allow Tesla to be killed with this - if (sysmoduleProgramId == 0x010000000007E51AULL) - continue; - - if (contentsFolder.path().string().find(sysmoduleProgramIdString) != std::string::npos) { - - auto listEntry = new tsl::elm::ToggleListItem(sysmoduleName, isProgramRunning(sysmoduleProgramId)); - listEntry->setStateChangedListener([listEntry, sysmoduleProgramId](bool state) -> bool { - if (!isProgramRunning(sysmoduleProgramId)) { - const NcmProgramLocation programLocation { - .program_id = sysmoduleProgramId, - .storageID = NcmStorageId_None, - }; - u64 pid = 0; - - if (R_FAILED(pmshellLaunchProgram(0, &programLocation, &pid))) - listEntry->setState(false); - } else { - if (R_FAILED(pmshellTerminateProgram(sysmoduleProgramId))) - listEntry->setState(true); - } - - return true; - }); - - this->m_sysmoduleListItems.insert({ sysmoduleProgramId, listEntry }); - sysmoduleList->addItem(listEntry); - } - } - } - - - if (this->m_sysmoduleListItems.size() == 0) { - auto warning = new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, u16 x, u16 y, u16 w, u16 h){ - renderer->drawString("\uE150", false, 180, 250, 90, renderer->a(0xFFFF)); - renderer->drawString("No sysmodules found!", false, 110, 340, 25, renderer->a(0xFFFF)); - }); - - delete sysmoduleList; - - rootFrame->setContent(warning); - } else { - rootFrame->setContent(sysmoduleList); - } - - - return rootFrame; - } - - virtual void update() { - static u32 counter = 0; - - if (counter++ % 20 != 0) return; - - for (const auto &[programId, listItem] : this->m_sysmoduleListItems) { - listItem->setState(isProgramRunning(programId)); - } - } - -private: - std::map m_sysmoduleListItems; - -}; - +#include "gui_main.hpp" class OverlaySysmodules : public tsl::Overlay { -public: - OverlaySysmodules() { } - ~OverlaySysmodules() { } - - void initServices() override { + public: + OverlaySysmodules() {} + ~OverlaySysmodules() {} + + void initServices() override { pmshellInitialize(); } @@ -123,12 +15,10 @@ class OverlaySysmodules : public tsl::Overlay { } std::unique_ptr loadInitialGui() override { - return initially(); + return std::make_unique(); } - }; - int main(int argc, char **argv) { return tsl::loop(argc, argv); } \ No newline at end of file