├── .github └── workflows │ └── docker-image.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── README.md ├── cmake ├── FindLIBNX.cmake ├── borealisLib.cmake ├── clang-dev-tools.cmake ├── devkita64-libnx.cmake ├── nx-utils.cmake ├── options.cmake └── utils.cmake ├── resources ├── icon_corner.psd └── icon_gui.psd ├── screenshots ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg └── overlay │ ├── 0.png │ ├── 1.png │ ├── 2.png │ └── 3.png ├── setup_env.sh ├── src ├── Applications │ ├── CMakeLists.txt │ ├── SimpleModManager │ │ ├── CMakeLists.txt │ │ ├── include │ │ │ └── SimpleModManager.h │ │ ├── resources │ │ │ ├── assets │ │ │ │ └── icon_gui.jpg │ │ │ └── romfs │ │ │ │ └── images │ │ │ │ ├── icon_corner.png │ │ │ │ ├── portrait.jpg │ │ │ │ └── unknown.png │ │ └── src │ │ │ └── SimpleModManager.cpp │ ├── SimpleModManagerConsole │ │ ├── CMakeLists.txt │ │ ├── assets │ │ │ ├── icon.jpg │ │ │ └── icon.png │ │ └── src │ │ │ └── SimpleModManagerConsole.cpp │ └── SimpleModManagerOverlay │ │ ├── CMakeLists.txt │ │ ├── assets │ │ ├── icon.jpg │ │ └── icon.png │ │ ├── include │ │ ├── ExampleGui.h │ │ ├── GameBrowserGui.h │ │ ├── OverlayGuiLoader.h │ │ └── implementation │ │ │ ├── GameBrowserGui.impl.h │ │ │ └── OverlayGui.impl.h │ │ └── src │ │ └── SimpleModManagerOverlay.cpp ├── CMakeLists.txt ├── ModManagerCore │ ├── CMakeLists.txt │ ├── include │ │ ├── ConfigHandler.h │ │ ├── ConsoleHandler.h │ │ ├── GameBrowser.h │ │ ├── ModManager.h │ │ ├── ModsPresetHandler.h │ │ ├── Selector.h │ │ └── Toolbox.h │ └── src │ │ ├── ConfigHandler.cpp │ │ ├── GameBrowser.cpp │ │ ├── ModManager.cpp │ │ ├── ModsPreseter.cpp │ │ ├── Selector.cpp │ │ └── Toolbox.cpp ├── ModManagerGui │ ├── CMakeLists.txt │ ├── CoreExtension │ │ ├── CMakeLists.txt │ │ ├── include │ │ │ └── GuiModManager.h │ │ └── src │ │ │ └── GuiModManager.cpp │ ├── FrameGameBrowser │ │ ├── CMakeLists.txt │ │ ├── include │ │ │ ├── FrameRoot.h │ │ │ ├── TabAbout.h │ │ │ ├── TabGames.h │ │ │ └── TabGeneralSettings.h │ │ └── src │ │ │ ├── FrameRoot.cpp │ │ │ ├── TabAbout.cpp │ │ │ ├── TabGames.cpp │ │ │ └── TabGeneralSettings.cpp │ └── FrameModBrowser │ │ ├── CMakeLists.txt │ │ ├── include │ │ ├── FrameModBrowser.h │ │ ├── TabModBrowser.h │ │ ├── TabModOptions.h │ │ ├── TabModPlugins.h │ │ ├── TabModPresets.h │ │ └── ThumbnailPresetEditor.h │ │ └── src │ │ ├── FrameModBrowser.cpp │ │ ├── TabModBrowser.cpp │ │ ├── TabModOptions.cpp │ │ ├── TabModPlugins.cpp │ │ ├── TabModPresets.cpp │ │ └── ThumbnailPresetEditor.cpp └── ModManagerOverlay │ ├── CMakeLists.txt │ ├── include │ ├── ChangeConfigPresetGui.h │ └── ModBrowserGui.h │ └── src │ ├── ChangeConfigPresetGui.cpp │ └── ModBrowserGui.cpp └── version_config.h.in /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Build the Docker image 18 | run: docker build . --file Dockerfile --tag my-image-name:$(date +%s) 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignored files 2 | external 3 | cmake-build-release 4 | build 5 | 6 | # Don't track IDE caches 7 | .vscode 8 | .idea 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/borealis"] 2 | path = submodules/borealis 3 | url = https://github.com/nadrino/borealis 4 | [submodule "submodules/libtesla"] 5 | path = submodules/libtesla 6 | url = https://github.com/nadrino/libtesla.git 7 | [submodule "submodules/cpp-generic-toolbox"] 8 | path = submodules/cpp-generic-toolbox 9 | url = https://github.com/nadrino/cpp-generic-toolbox.git 10 | [submodule "submodules/simple-cpp-logger"] 11 | path = submodules/simple-cpp-logger 12 | url = https://github.com/nadrino/simple-cpp-logger.git 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019 SwitchPy Team. All rights reserved. 2 | # Licensed under the MIT license. 3 | # Refer to the LICENSE file included. 4 | # 5 | # libnx CMake template for Nintendo Switch homebrew development. 6 | 7 | cmake_minimum_required(VERSION 3.10) 8 | 9 | project(SimpleModManager) 10 | 11 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 12 | include(options) 13 | include(utils) 14 | include(devkita64-libnx) 15 | 16 | # needed by tesla lib 17 | set( CMAKE_CXX_STANDARD 20 ) 18 | 19 | find_package(LIBNX REQUIRED) 20 | if (NOT LIBNX_FOUND) 21 | cmake_panic("Unable to detect libnx on this system.") 22 | endif () 23 | 24 | 25 | find_package(ZLIB REQUIRED) 26 | if (${ZLIB_FOUND}) 27 | message("ZLIB found : ${ZLIB_VERSION_STRING}") 28 | message("ZLIB_INCLUDE_DIRS = ${ZLIB_INCLUDE_DIRS}") 29 | message("ZLIB_LIBRARIES = ${ZLIB_LIBRARIES}") 30 | else() 31 | message(FATAL_ERROR "ZLIB has not been found.") 32 | endif () 33 | 34 | find_package(Freetype REQUIRED) 35 | if (${FREETYPE_FOUND}) 36 | message("Freetype found : ${FREETYPE_VERSION_STRING}") 37 | message("FREETYPE_INCLUDE_DIRS = ${FREETYPE_INCLUDE_DIRS}") 38 | message("FREETYPE_LIBRARIES = ${FREETYPE_LIBRARIES}") 39 | else() 40 | message(FATAL_ERROR "FREETYPE has not been found.") 41 | endif () 42 | 43 | 44 | find_package(BZip2 REQUIRED) 45 | if (${BZIP2_FOUND}) 46 | message("BZIP2 found : ${BZIP2_VERSION_STRING}") 47 | message("BZIP2_INCLUDE_DIRS = ${BZIP2_INCLUDE_DIRS}") 48 | message("BZIP2_LIBRARIES = ${BZIP2_LIBRARIES}") 49 | else() 50 | message(FATAL_ERROR "BZIP2 has not been found.") 51 | endif () 52 | 53 | #find_package(SDL2 REQUIRED) 54 | #message("SDL2_INCLUDE_DIRS = ${SDL2_INCLUDE_DIRS}") 55 | #message("SDL2_LIBRARIES = ${SDL2_LIBRARIES}") 56 | 57 | 58 | include_directories(${PROJECT_BINARY_DIR}) 59 | include_directories("${PORTLIBS}/include") 60 | include_directories("${LIBNX}/include") 61 | include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include") 62 | include_directories("${ZLIB_INCLUDE_DIRS}") 63 | include_directories("${FREETYPE_INCLUDE_DIRS}") 64 | #include_directories("${SDL2_INCLUDE_DIRS}") 65 | 66 | set(VERSION_MAJOR 2) 67 | set(VERSION_MINOR 1) 68 | set(VERSION_MICRO 4) 69 | set(VERSION_TAG "\"\"") 70 | set(APP_VERSION 71 | "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}") 72 | 73 | add_definitions( -DVERSION_APP=${APP_VERSION} ) 74 | add_definitions( -DVERSION_MAJOR_APP=${VERSION_MAJOR} ) 75 | add_definitions( -DVERSION_MINOR_APP=${VERSION_MINOR} ) 76 | add_definitions( -DVERSION_MICRO_APP=${VERSION_MICRO} ) 77 | 78 | if (NOT DEFINED CMAKE_BUILD_TYPE_INIT) 79 | set(CMAKE_BUILD_TYPE_INIT Release) 80 | endif () 81 | 82 | 83 | include(nx-utils) 84 | 85 | cmake_info("Building ${APP_TITLE} version ${APP_VERSION}.") 86 | 87 | # SimpleModManager Core 88 | set(SMM_CORE_DIR ${PROJECT_SOURCE_DIR}/core) 89 | set(SMM_CORE_SOURCE_DIR ${SMM_CORE_DIR}/source) 90 | set(SMM_CORE_INCLUDE_DIR ${SMM_CORE_DIR}/include) 91 | 92 | include_directories(${SMM_CORE_INCLUDE_DIR}) 93 | 94 | # Submodules 95 | set( SUBMODULES_DIR ${PROJECT_SOURCE_DIR}/submodules ) 96 | include_directories( ${SUBMODULES_DIR}/cpp-generic-toolbox/include ) 97 | 98 | include_directories( ${SUBMODULES_DIR}/simple-cpp-logger/include ) 99 | add_definitions( -D LOGGER_MAX_LOG_LEVEL_PRINTED=6 ) 100 | add_definitions( -D LOGGER_PREFIX_LEVEL=3 ) 101 | add_definitions( -D LOGGER_ENABLE_COLORS_ON_USER_HEADER=1 ) 102 | add_definitions( -D LOGGER_TIME_FORMAT="\\\"%d/%m/%Y %H:%M:%S"\\\" ) 103 | add_definitions( -D LOGGER_PREFIX_FORMAT="\\\"{TIME} {USER_HEADER} {FILELINE}"\\\" ) 104 | 105 | # Auto Generated 106 | configure_file( version_config.h.in ${CMAKE_BINARY_DIR}/generated/version_config.h ) 107 | include_directories( ${CMAKE_BINARY_DIR}/generated/ ) 108 | 109 | include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/borealisLib.cmake ) 110 | 111 | # This project 112 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/src ) 113 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM devkitpro/devkita64 as base 2 | 3 | ENV DEVKITPRO /opt/devkitpro 4 | ENV DEVKITA64 ${DEVKITPRO}/devkitA64 5 | ENV DEVKITARM ${DEVKITPRO}/devkitARM 6 | ENV DEVKITPPC ${DEVKITPRO}/devkitPPC 7 | ENV PORTLIBS_PREFIX ${DEVKITPRO}/portlibs/switch 8 | 9 | ENV PATH ${DEVKITPRO}/tools/bin:$PATH 10 | ENV PATH ${DEVKITA64}/bin/:$PATH 11 | 12 | ENV WORK_DIR /home/work 13 | RUN mkdir -p $WORK_DIR 14 | WORKDIR $WORK_DIR 15 | 16 | ENV REPO_DIR $WORK_DIR/repo 17 | ENV BUILD_DIR $WORK_DIR/build 18 | ENV INSTALL_DIR $WORK_DIR/install 19 | 20 | RUN mkdir -p $REPO_DIR 21 | RUN mkdir -p $BUILD_DIR 22 | RUN mkdir -p $INSTALL_DIR 23 | 24 | SHELL ["/bin/bash", "-c"] 25 | 26 | RUN mkdir -p $REPO_DIR/SimpleModManager 27 | RUN mkdir -p $BUILD_DIR/SimpleModManager 28 | COPY . $REPO_DIR/SimpleModManager 29 | 30 | RUN cd $REPO_DIR/SimpleModManager && \ 31 | git submodule update --init --recursive && \ 32 | cd $BUILD_DIR/SimpleModManager && \ 33 | # for some reason yaml-cpp in not found by cmake, so put the paths manually 34 | cmake \ 35 | -D CMAKE_INSTALL_PREFIX=$INSTALL_DIR \ 36 | -D CMAKE_TOOLCHAIN_FILE=$REPO_DIR/SimpleModManager/cmake/devkita64-libnx.cmake \ 37 | $REPO_DIR/SimpleModManager && \ 38 | make -j3 install 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/badge/License-GPLv3-blue.svg) [![GitHub version](https://badge.fury.io/gh/nadrino%2FSimpleModManager.svg)](https://github.com/nadrino/SimpleModManager/releases/) [![Github all releases](https://img.shields.io/github/downloads/nadrino/SimpleModManager/total.svg)](https://GitHub.com/nadrino/SimpleModManager/releases/) 2 | 3 | # SimpleModManager 4 | 5 | SimpleModManager is an homebrew app for the Nintendo Switch CFW : Atmosphere. 6 | It allows to manage your mods (via LayeredFS). 7 | 8 |

9 | 10 | ## Screenshots 11 | 12 | ![](./screenshots/1.jpg) 13 | 14 |
15 | Spoiler: More Screenshots 16 | 17 | ![](./screenshots/2.jpg) 18 | ![](./screenshots/3.jpg) 19 | ![](./screenshots/4.jpg) 20 | ![](./screenshots/5.jpg) 21 | ![](./screenshots/6.jpg) 22 | 23 |
24 | 25 | 26 | ## How to install (manually) 27 | - Download the latest version in the [release page](https://github.com/nadrino/SimpleModManager/releases) 28 | - Place the .nro file in the `/switch/` folder of your SDcard. 29 | - At the root of your SDcard, create a `/mods/` folder. 30 | - Tree structure : `/mods///` 31 | - For plugins: `/mods//.plugins/.smm` 32 | 33 | Example : `/mods/The Legend of Zelda - Breath of the Wild/First Person View/contents/01007EF00011E000/romfs/Actor/Pack/GameRomCamera.sbactorpack` 34 | 35 | 36 | ## Build From Source 37 | 38 | ### Prerequisites (macos) 39 | - Install XCode via the App Store 40 | - Launch : 41 | ```bash 42 | xcode-select --install 43 | ``` 44 | - Download DevKitPro : https://github.com/devkitPro/pacman/releases 45 | ```bash 46 | sudo installer -pkg /path/to/devkitpro-pacman-installer.pkg -target / 47 | ``` 48 | - Define environment (add the following lines to your bashrc) : 49 | ```bash 50 | function setup_devkitpro() 51 | { 52 | echo "Seting up DevKitPro..." >&2 53 | export DEVKITPRO=/opt/devkitpro 54 | export DEVKITA64=${DEVKITPRO}/devkitA64 55 | export DEVKITARM=${DEVKITPRO}/devkitARM 56 | export DEVKITPPC=${DEVKITPRO}/devkitPPC 57 | export PORTLIBS_PREFIX=${DEVKITPRO}/portlibs/switch 58 | 59 | export PATH=${DEVKITPRO}/tools/bin:$PATH 60 | export PATH=${DEVKITA64}/bin/:$PATH 61 | 62 | source $DEVKITPRO/switchvars.sh 63 | return; 64 | } 65 | export -f setup_devkitpro 66 | ``` 67 | - Source your bashrc and execute "setup_devkitpro" 68 | - Install packages (all are not needed, this is just a reminder for me!) 69 | ```bash 70 | sudo dkp-pacman -Sy \ 71 | switch-bulletphysics switch-bzip2 switch-curl\ 72 | switch-examples switch-ffmpeg switch-flac switch-freetype\ 73 | switch-giflib switch-glad switch-glfw switch-glm\ 74 | switch-jansson switch-libass switch-libconfig\ 75 | switch-libdrm_nouveau switch-libexpat switch-libfribidi\ 76 | switch-libgd switch-libjpeg-turbo switch-libjson-c\ 77 | switch-liblzma switch-liblzo2 switch-libmad switch-libmikmod\ 78 | switch-libmodplug switch-libogg switch-libopus\ 79 | switch-libpcre2 switch-libpng switch-libsamplerate\ 80 | switch-libsodium switch-libtheora switch-libtimidity\ 81 | switch-libvorbis switch-libvorbisidec switch-libvpx\ 82 | switch-libwebp switch-libxml2 switch-mbedtls switch-mesa\ 83 | switch-miniupnpc switch-mpg123 switch-ode switch-oniguruma\ 84 | switch-opusfile switch-pkg-config switch-sdl2 switch-sdl2_gfx\ 85 | switch-sdl2_image switch-sdl2_mixer switch-sdl2_net\ 86 | switch-sdl2_ttf switch-smpeg2 switch-zlib switch-zziplib\ 87 | devkitA64 devkitpro-keyring general-tools pkg-config\ 88 | libnx libfilesystem switch-tools devkitpro-pkgbuild-helpers\ 89 | -r /System/Volumes/Data 90 | sudo dkp-pacman -Suy -r /System/Volumes/Data 91 | ``` 92 | 93 | ### Compile 94 | ```bash 95 | git clone https://github.com/nadrino/SimpleModManager.git 96 | cd SimpleModManager 97 | mkdir build 98 | cd build 99 | cmake ../ -DCMAKE_TOOLCHAIN_FILE=../cmake/devkita64-libnx.cmake 100 | make 101 | ``` 102 | 103 | 104 | 105 | 106 | ## Plugins 107 | Plugins can be any hbmenu nro, but should be linked against [libsmm](https://github.com/withertech/libsmm) and have the 108 | ```c++ 109 | void smmInit(); 110 | ``` 111 | called in initialization and 112 | ```c++ 113 | void smmExit(); 114 | ``` 115 | called in deinitialization 116 | 117 | use 118 | ```c++ 119 | std::string smmModPathForCfwPath(std::string path); 120 | ``` 121 | to get the path to a file under `sdmc:/mods/...` from a path to a file under `sdmc:/atmosphere/...` 122 | 123 | and include 124 | ```c++ 125 | #import 126 | ``` 127 | and add 128 | ```makefile 129 | LIBS := -lsmm -lnx 130 | ``` 131 | to your makefile 132 | 133 | Example : 134 | 135 |
136 | main.cpp 137 | 138 | ```c++ 139 | 140 | // Include the most common headers from the C standard library 141 | #include 142 | #include 143 | #include 144 | 145 | // Include the main libnx system header, for Switch development 146 | #include 147 | 148 | #include 149 | 150 | // Main program entrypoint 151 | int main(int argc, char* argv[]) 152 | { 153 | // This example uses a text console, as a simple way to output text to the screen. 154 | // If you want to write a software-rendered graphics application, 155 | // take a look at the graphics/simplegfx example, which uses the libnx Framebuffer API instead. 156 | // If on the other hand you want to write an OpenGL based application, 157 | // take a look at the graphics/opengl set of examples, which uses EGL instead. 158 | consoleInit(NULL); 159 | smmInit(); 160 | // Configure our supported input layout: a single player with standard controller styles 161 | padConfigureInput(1, HidNpadStyleSet_NpadStandard); 162 | 163 | // Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller) 164 | PadState pad; 165 | padInitializeDefault(&pad); 166 | 167 | // Other initialization goes here. As a demonstration, we print hello world. 168 | printf(smmModPathForCfwPath("sdmc:/atmosphere/contents/01000A10041EA000/romfs/Skyrim.ini").c_str()); 169 | 170 | // Main loop 171 | while (appletMainLoop()) 172 | { 173 | // Scan the gamepad. This should be done once for each frame 174 | padUpdate(&pad); 175 | 176 | // padGetButtonsDown returns the set of buttons that have been 177 | // newly pressed in this frame compared to the previous one 178 | u64 kDown = padGetButtonsDown(&pad); 179 | 180 | if (kDown & HidNpadButton_Plus) 181 | break; // break in order to return to hbmenu 182 | 183 | // Your code goes here 184 | 185 | // Update the console, sending a new frame to the display 186 | consoleUpdate(NULL); 187 | } 188 | 189 | smmExit(); 190 | // Deinitialize and clean up resources used by the console (important!) 191 | consoleExit(NULL); 192 | return 0; 193 | } 194 | ``` 195 |
196 | 197 |
198 | makefile 199 | 200 | ```makefile 201 | #--------------------------------------------------------------------------------- 202 | .SUFFIXES: 203 | #--------------------------------------------------------------------------------- 204 | 205 | ifeq ($(strip $(DEVKITPRO)),) 206 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 207 | endif 208 | 209 | TOPDIR ?= $(CURDIR) 210 | include $(DEVKITPRO)/libnx/switch_rules 211 | 212 | #--------------------------------------------------------------------------------- 213 | # TARGET is the name of the output 214 | # BUILD is the directory where object files & intermediate files will be placed 215 | # SOURCES is a list of directories containing source code 216 | # DATA is a list of directories containing data files 217 | # INCLUDES is a list of directories containing header files 218 | # EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". 219 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 220 | # 221 | # NO_ICON: if set to anything, do not use icon. 222 | # NO_NACP: if set to anything, no .nacp file is generated. 223 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 224 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 225 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 226 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 227 | # ICON is the filename of the icon (.jpg), relative to the project folder. 228 | # If not set, it attempts to use one of the following (in this order): 229 | # - .jpg 230 | # - icon.jpg 231 | # - /default_icon.jpg 232 | #--------------------------------------------------------------------------------- 233 | TARGET := $(notdir $(CURDIR)) 234 | BUILD := build 235 | SOURCES := source 236 | DATA := data 237 | INCLUDES := include 238 | EXEFS_SRC := exefs_src 239 | #ROMFS := romfs 240 | 241 | #--------------------------------------------------------------------------------- 242 | # options for code generation 243 | #--------------------------------------------------------------------------------- 244 | ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE 245 | 246 | CFLAGS := -g -Wall -O2 -ffunction-sections \ 247 | $(ARCH) $(DEFINES) 248 | 249 | CFLAGS += $(INCLUDE) -D__SWITCH__ 250 | 251 | CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions 252 | 253 | ASFLAGS := -g $(ARCH) 254 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 255 | 256 | LIBS := -lsmm -lnx 257 | 258 | #--------------------------------------------------------------------------------- 259 | # list of directories containing libraries, this must be the top level containing 260 | # include and lib 261 | #--------------------------------------------------------------------------------- 262 | LIBDIRS := $(PORTLIBS) $(LIBNX) 263 | 264 | 265 | #--------------------------------------------------------------------------------- 266 | # no real need to edit anything past this point unless you need to add additional 267 | # rules for different file extensions 268 | #--------------------------------------------------------------------------------- 269 | ifneq ($(BUILD),$(notdir $(CURDIR))) 270 | #--------------------------------------------------------------------------------- 271 | 272 | export OUTPUT := $(CURDIR)/$(TARGET) 273 | export TOPDIR := $(CURDIR) 274 | 275 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 276 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 277 | 278 | export DEPSDIR := $(CURDIR)/$(BUILD) 279 | 280 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 281 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 282 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 283 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 284 | 285 | #--------------------------------------------------------------------------------- 286 | # use CXX for linking C++ projects, CC for standard C 287 | #--------------------------------------------------------------------------------- 288 | ifeq ($(strip $(CPPFILES)),) 289 | #--------------------------------------------------------------------------------- 290 | export LD := $(CC) 291 | #--------------------------------------------------------------------------------- 292 | else 293 | #--------------------------------------------------------------------------------- 294 | export LD := $(CXX) 295 | #--------------------------------------------------------------------------------- 296 | endif 297 | #--------------------------------------------------------------------------------- 298 | 299 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 300 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 301 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 302 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 303 | 304 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 305 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 306 | -I$(CURDIR)/$(BUILD) 307 | 308 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 309 | 310 | export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) 311 | 312 | ifeq ($(strip $(ICON)),) 313 | icons := $(wildcard *.jpg) 314 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 315 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 316 | else 317 | ifneq (,$(findstring icon.jpg,$(icons))) 318 | export APP_ICON := $(TOPDIR)/icon.jpg 319 | endif 320 | endif 321 | else 322 | export APP_ICON := $(TOPDIR)/$(ICON) 323 | endif 324 | 325 | ifeq ($(strip $(NO_ICON)),) 326 | export NROFLAGS += --icon=$(APP_ICON) 327 | endif 328 | 329 | ifeq ($(strip $(NO_NACP)),) 330 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 331 | endif 332 | 333 | ifneq ($(APP_TITLEID),) 334 | export NACPFLAGS += --titleid=$(APP_TITLEID) 335 | endif 336 | 337 | ifneq ($(ROMFS),) 338 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 339 | endif 340 | 341 | .PHONY: $(BUILD) clean all 342 | 343 | #--------------------------------------------------------------------------------- 344 | all: $(BUILD) 345 | 346 | $(BUILD): 347 | @[ -d $@ ] || mkdir -p $@ 348 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 349 | 350 | #--------------------------------------------------------------------------------- 351 | clean: 352 | @echo clean ... 353 | @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf 354 | 355 | 356 | #--------------------------------------------------------------------------------- 357 | else 358 | .PHONY: all 359 | 360 | DEPENDS := $(OFILES:.o=.d) 361 | 362 | #--------------------------------------------------------------------------------- 363 | # main targets 364 | #--------------------------------------------------------------------------------- 365 | all : $(OUTPUT).pfs0 $(OUTPUT).nro 366 | 367 | $(OUTPUT).pfs0 : $(OUTPUT).nso 368 | 369 | $(OUTPUT).nso : $(OUTPUT).elf 370 | 371 | ifeq ($(strip $(NO_NACP)),) 372 | $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp 373 | else 374 | $(OUTPUT).nro : $(OUTPUT).elf 375 | endif 376 | 377 | $(OUTPUT).elf : $(OFILES) 378 | 379 | $(OFILES_SRC) : $(HFILES_BIN) 380 | 381 | #--------------------------------------------------------------------------------- 382 | # you need a rule like this for each extension you use as binary data 383 | #--------------------------------------------------------------------------------- 384 | %.bin.o %_bin.h : %.bin 385 | #--------------------------------------------------------------------------------- 386 | @echo $(notdir $<) 387 | @$(bin2o) 388 | 389 | -include $(DEPENDS) 390 | 391 | #--------------------------------------------------------------------------------------- 392 | endif 393 | #--------------------------------------------------------------------------------------- 394 | ``` 395 |
396 | 397 | ## Prebuilt Binaries 398 | - To download please refer to this link : [Releases](https://github.com/nadrino/SimpleModManager/releases). 399 | - For libsmm there is a dkp-pacman package in the releases for [libsmm](https://github.com/withertech/libsmm/releases) 400 | 401 | 402 | 403 | -------------------------------------------------------------------------------- /cmake/FindLIBNX.cmake: -------------------------------------------------------------------------------- 1 | # Tries to find libnx 2 | # Once done, this will define: 3 | # > LIBNX_FOUND - The system has libnx 4 | # > LIBNX_INCLUDE_DIRS - The libnx include directories 5 | # > LIBNX_LIBRARIES - The libnx libraries required for using it 6 | # 7 | # It also adds an imported target named `switch::libnx`. 8 | 9 | if (NOT SWITCH) 10 | cmake_panic("This helper can only be used if you are using the Switch toolchain file.") 11 | endif () 12 | 13 | set(LIBNX_PATHS $ENV{LIBNX} libnx ${LIBNX} ${DEVKITPRO}/libnx) 14 | 15 | find_path(LIBNX_INCLUDE_DIR switch.h 16 | PATHS ${LIBNX_PATHS} 17 | PATH_SUFFIXES include) 18 | 19 | find_library(LIBNX_LIBRARY NAMES libnx.a 20 | PATHS ${LIBNX_PATHS} 21 | PATH_SUFFIXES lib) 22 | 23 | set(LIBNX_INCLUDE_DIRS ${LIBNX_INCLUDE_DIR}) 24 | set(LIBNX_LIBRARIES ${LIBNX_LIBRARY}) 25 | 26 | # Handle the QUIETLY and REQUIRED arguments and set LIBNX_FOUND to TRUE if all above variables are TRUE. 27 | include(FindPackageHandleStandardArgs) 28 | find_package_handle_standard_args(LIBNX DEFAULT_MSG 29 | LIBNX_INCLUDE_DIR LIBNX_LIBRARY) 30 | 31 | mark_as_advanced(LIBNX_INCLUDE_DIR LIBNX_LIBRARY) 32 | if (LIBNX_FOUND) 33 | set(LIBNX ${LIBNX_INCLUDE_DIR}/..) 34 | cmake_info("Setting LIBNX to ${LIBNX}") 35 | 36 | add_library(switch::libnx STATIC IMPORTED GLOBAL) 37 | set_target_properties(switch::libnx PROPERTIES 38 | IMPORTED_LOCATION ${LIBNX_LIBRARY} 39 | INTERFACE_INCLUDE_DIRECTORIES ${LIBNX_INCLUDE_DIR}) 40 | endif () 41 | -------------------------------------------------------------------------------- /cmake/borealisLib.cmake: -------------------------------------------------------------------------------- 1 | ########################### 2 | # Definition of Borealis lib build 3 | ########################### 4 | 5 | set( BOREALIS_DIR ${SUBMODULES_DIR}/borealis ) 6 | set(BOREALIS_INC_DIR ${BOREALIS_DIR}/library/include) 7 | 8 | cmake_info("BOREALIS_DIR is ${BOREALIS_DIR}") 9 | add_compile_definitions( BOREALIS_RESOURCES=\"romfs:/borealis/\" ) 10 | 11 | 12 | file( GLOB BOREALIS_SRC 13 | "${BOREALIS_DIR}/library/lib/*.cpp" 14 | "${BOREALIS_DIR}/library/lib/extern/*/*.c" 15 | "${BOREALIS_DIR}/library/lib/extern/*/*/*.c" 16 | "${BOREALIS_DIR}/library/lib/*.cpp" 17 | "${BOREALIS_DIR}/library/lib/*.c" 18 | "${BOREALIS_DIR}/library/lib/extern/glad/*.c" 19 | "${BOREALIS_DIR}/library/lib/extern/nanovg/*.c" 20 | "${BOREALIS_DIR}/library/lib/extern/libretro-common/compat/*.c" 21 | "${BOREALIS_DIR}/library/lib/extern/libretro-common/encodings/*.c" 22 | "${BOREALIS_DIR}/library/lib/extern/libretro-common/features/*.c" 23 | "${BOREALIS_DIR}/library/lib/extern/fmt/src/*.cc" 24 | ) 25 | 26 | add_library( Borealis STATIC ${BOREALIS_SRC} ) 27 | install( TARGETS Borealis DESTINATION lib ) 28 | 29 | target_include_directories( Borealis PUBLIC 30 | ${BOREALIS_DIR}/library/lib/extern/fmt/include 31 | ${BOREALIS_INC_DIR} 32 | ${BOREALIS_INC_DIR}/borealis 33 | ${BOREALIS_INC_DIR}/borealis/extern 34 | ${BOREALIS_INC_DIR}/borealis/extern/glad 35 | ${BOREALIS_INC_DIR}/borealis/extern/nanovg 36 | ${BOREALIS_INC_DIR}/borealis/extern/libretro-common 37 | ${PROJECT_SOURCE_DIR}/shortcuts 38 | ${PROJECT_SOURCE_DIR}/shortcuts/libretro-common 39 | # ${SUBMODULES_DIR}/json/include 40 | ) 41 | 42 | target_link_libraries( Borealis PUBLIC 43 | switch::libnx 44 | -L/opt/devkitpro/portlibs/switch/lib 45 | -L/opt/devkitpro/libnx/lib 46 | ${ZLIB_LIBRARIES} 47 | ${FREETYPE_LIBRARIES} 48 | -lglfw3 -lEGL -lglad -lglapi -ldrm_nouveau -lm -lnx 49 | ) 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /cmake/clang-dev-tools.cmake: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE 2 | ALL_CXX_SOURCE_FILES 3 | *.c *.cpp *.cc *.h *.hpp) 4 | 5 | find_program(CLANG_FORMAT "clang-format") 6 | if (CLANG_FORMAT) 7 | add_custom_target( 8 | clang_format 9 | COMMAND /usr/bin/clang-format 10 | -i 11 | -style=file 12 | ${ALL_CXX_SOURCE_FILES}) 13 | endif () 14 | 15 | find_program(CLANG_TIDY "clang-tidy") 16 | if (CLANG_TIDY) 17 | add_custom_target( 18 | clang-tidy 19 | COMMAND /usr/bin/clang-tidy 20 | ${ALL_CXX_SOURCE_FILES} 21 | -config='' 22 | -- 23 | -std=c++${CMAKE_CXX_STANDARD} 24 | ${INCLUDE_DIRECTORIES}) 25 | endif () 26 | -------------------------------------------------------------------------------- /cmake/devkita64-libnx.cmake: -------------------------------------------------------------------------------- 1 | if (NOT DEFINED ENV{DEVKITPRO}) 2 | cmake_panic("Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 3 | endif () 4 | 5 | set(CMAKE_SYSTEM_NAME Generic) 6 | set(CMAKE_SYSTEM_PROCESSOR aarch64) 7 | set(SWITCH TRUE) # To be used for multiplatform projects 8 | 9 | # devkitPro paths are broken on Windows. We need to use this macro to fix those. 10 | macro(msys_to_cmake_path msys_path resulting_path) 11 | if (WIN32) 12 | string(REGEX REPLACE "^/([a-zA-Z])/" "\\1:/" ${resulting_path} ${msys_path}) 13 | else () 14 | set(${resulting_path} ${msys_path}) 15 | endif () 16 | endmacro() 17 | 18 | msys_to_cmake_path($ENV{DEVKITPRO} DEVKITPRO) 19 | set(DEVKITA64 ${DEVKITPRO}/devkitA64) 20 | set(LIBNX ${DEVKITPRO}/libnx) 21 | set(PORTLIBS_PATH ${DEVKITPRO}/portlibs) 22 | set(PORTLIBS ${PORTLIBS_PATH}/switch) 23 | 24 | set(TOOLCHAIN_PREFIX ${DEVKITA64}/bin/aarch64-none-elf-) 25 | if (WIN32) 26 | set(TOOLCHAIN_SUFFIX ".exe") 27 | else () 28 | set(TOOLCHAIN_SUFFIX "") 29 | endif () 30 | 31 | set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc${TOOLCHAIN_SUFFIX}) 32 | set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g++${TOOLCHAIN_SUFFIX}) 33 | set(CMAKE_ASM_COMPILER ${TOOLCHAIN_PREFIX}as${TOOLCHAIN_SUFFIX}) 34 | 35 | set(PKG_CONFIG_EXECUTABLE ${TOOLCHAIN_PREFIX}pkg-config${TOOLCHAIN_SUFFIX}) 36 | set(CMAKE_AR ${TOOLCHAIN_PREFIX}gcc-ar${TOOLCHAIN_SUFFIX} CACHE STRING "") 37 | set(CMAKE_RANLIB ${TOOLCHAIN_PREFIX}gcc-ranlib${TOOLCHAIN_SUFFIX} CACHE STRING "") 38 | set(CMAKE_LD "/${TOOLCHAIN_PREFIX}ld${TOOLCHAIN_SUFFIX}" CACHE INTERNAL "") 39 | set(CMAKE_OBJCOPY "${TOOLCHAIN_PREFIX}objcopy${TOOLCHAIN_SUFFIX}" CACHE INTERNAL "") 40 | set(CMAKE_SIZE_UTIL "${TOOLCHAIN_PREFIX}size${TOOLCHAIN_SUFFIX}" CACHE INTERNAL "") 41 | 42 | set(WITH_PORTLIBS ON CACHE BOOL "use portlibs ?") 43 | if (WITH_PORTLIBS) 44 | set(CMAKE_FIND_ROOT_PATH ${DEVKITA64} ${DEVKITPRO} ${LIBNX} ${PORTLIBS}) 45 | else () 46 | set(CMAKE_FIND_ROOT_PATH ${DEVKITA64} ${DEVKITPRO} ${LIBNX}) 47 | endif () 48 | 49 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 50 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 51 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 52 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 53 | 54 | add_definitions(-D__SWITCH__) 55 | set(ARCH "-march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE") 56 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -MMD -MP -g -Wall -O2 -ffunction-sections ${ARCH}") 57 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -fexceptions -Os -fdata-sections -ffunction-sections") 58 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} -fno-exceptions") 59 | #set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions") 60 | set(CMAKE_EXE_LINKER_FLAGS_INIT "${ARCH} -ftls-model=local-exec -L${LIBNX}/lib -L${PORTLIBS}/lib -Wl,--gc-sections") 61 | set(CMAKE_MODULE_LINKER_FLAGS_INIT ${CMAKE_EXE_LINKER_FLAGS_INIT}) 62 | 63 | set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "Shared libs not available") 64 | set(CMAKE_INSTALL_PREFIX ${PORTLIBS}) 65 | set(CMAKE_PREFIX_PATH ${PORTLIBS}) 66 | -------------------------------------------------------------------------------- /cmake/nx-utils.cmake: -------------------------------------------------------------------------------- 1 | if (NOT SWITCH) 2 | cmake_panic("These utils can only be used if you are using the Switch toolchain file.") 3 | endif () 4 | 5 | ############# 6 | ## ELF2NRO ## 7 | ############# 8 | if (NOT ELF2NRO) 9 | find_program(ELF2NRO elf2nro ${DEVKITPRO}/tools/bin) 10 | if (ELF2NRO) 11 | cmake_info("elf2nro: ${ELF2NRO} - found") 12 | else () 13 | cmake_warning("elf2nro - not found") 14 | endif () 15 | endif () 16 | 17 | ############# 18 | ## ELF2KIP ## 19 | ############# 20 | if (NOT ELF2KIP) 21 | find_program(ELF2KIP elf2kip ${DEVKITPRO}/tools/bin) 22 | if (ELF2KIP) 23 | cmake_info("elf2kip: ${ELF2KIP} - found") 24 | else () 25 | cmake_warning("elf2kip - not found") 26 | endif () 27 | endif () 28 | 29 | ############# 30 | ## ELF2NSO ## 31 | ############# 32 | if (NOT ELF2NSO) 33 | find_program(ELF2NSO elf2nso ${DEVKITPRO}/tools/bin) 34 | if (ELF2NSO) 35 | cmake_info("elf2nso: ${ELF2NSO} - found") 36 | else () 37 | cmake_warning("elf2nso - not found") 38 | endif () 39 | endif () 40 | 41 | ############# 42 | ## BIN2S ## 43 | ############# 44 | if (NOT BIN2S) 45 | find_program(BIN2S bin2s ${DEVKITPRO}/tools/bin) 46 | if (BIN2S) 47 | cmake_info("bin2s: ${BIN2S} - found") 48 | else () 49 | cmake_warning("bin2s - not found") 50 | endif () 51 | endif () 52 | 53 | ############# 54 | ## RAW2C ## 55 | ############# 56 | if (NOT RAW2C) 57 | find_program(RAW2C raw2c ${DEVKITPRO}/tools/bin) 58 | if (RAW2C) 59 | cmake_info("raw2c: ${RAW2C} - found") 60 | else () 61 | cmake_warning("raw2c - not found") 62 | endif () 63 | endif () 64 | 65 | ################## 66 | ## BUILD_PFS0 ## 67 | ################## 68 | if (NOT BUILD_PFS0) 69 | find_program(BUILD_PFS0 build_pfs0 ${DEVKITPRO}/tools/bin) 70 | if (BUILD_PFS0) 71 | cmake_info("build_pfs0: ${BUILD_PFS0} - found") 72 | else () 73 | cmake_warning("build_pfs0 - not found") 74 | endif () 75 | endif () 76 | 77 | ################ 78 | ## NACPTOOL ## 79 | ################ 80 | if (NOT NACPTOOL) 81 | find_program(NACPTOOL nacptool ${DEVKITPRO}/tools/bin) 82 | if (NACPTOOL) 83 | cmake_info("nacptool: ${NACPTOOL} - found") 84 | else () 85 | cmake_warning("nacptool - not found") 86 | endif () 87 | endif () 88 | 89 | macro(acquire_homebrew_icon target) 90 | # This basically imitates the behavior of the Makefiles 91 | # from the switchbrew/switch-examples repository. 92 | message(${target}.jpg) 93 | if (EXISTS ${target}.jpg) 94 | set(APP_ICON ${target}.jpg) 95 | elseif (EXISTS ${PROJECT_SOURCE_DIR}/assets/icon.jpg) 96 | set(APP_ICON ${PROJECT_SOURCE_DIR}/assets/icon.jpg) 97 | elseif (LIBNX) 98 | set(APP_ICON ${LIBNX}/default_icon.jpg) 99 | else () 100 | cmake_panic("No icon found, please provide one!") 101 | endif () 102 | endmacro() 103 | 104 | function(add_nso_target target) 105 | add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target}.nso 106 | COMMAND ${ELF2NSO} ${CMAKE_CURRENT_BINARY_DIR}/${target}.elf ${CMAKE_CURRENT_BINARY_DIR}/${target}.nso 107 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} 108 | VERBATIM) 109 | 110 | if (CMAKE_RUNTIME_OUTPUT_DIRECTORY) 111 | add_custom_target(${target}.nso ALL SOURCES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.nso) 112 | else () 113 | add_custom_target(${target}.nso ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${target}.nso) 114 | endif () 115 | endfunction() 116 | 117 | function(add_nacp target) 118 | set(__NACP_COMMAND ${NACPTOOL} --create ${APP_TITLE} ${APP_AUTHOR} ${APP_VERSION} ${CMAKE_CURRENT_BINARY_DIR}/${target}) 119 | 120 | add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target} 121 | COMMAND ${__NACP_COMMAND} 122 | WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} 123 | VERBATIM) 124 | endfunction() 125 | 126 | function(add_nro_target target) 127 | 128 | if (NOT APP_ROMFS) 129 | set(__NRO_COMMAND ${ELF2NRO} 130 | $ 131 | ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro 132 | --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp 133 | --icon=${APP_ICON} 134 | ) 135 | else() 136 | message("Building with ROMFS: ${APP_ROMFS}") 137 | set(__NRO_COMMAND ${ELF2NRO} 138 | $ 139 | ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro 140 | --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp 141 | --romfsdir=${APP_ROMFS} 142 | --icon=${APP_ICON} 143 | ) 144 | endif() 145 | 146 | # set(__NRO_COMMAND 147 | # ${ELF2NRO} $ ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp --icon=${APP_ICON}) 148 | 149 | if (NOT ${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp) 150 | add_nacp(${target}.nacp) 151 | endif () 152 | 153 | add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro 154 | COMMAND ${__NRO_COMMAND} 155 | DEPENDS ${target}.elf ${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp 156 | VERBATIM) 157 | 158 | if (CMAKE_RUNTIME_OUTPUT_DIRECTORY) 159 | add_custom_target(${target}.nro ALL SOURCES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.nro) 160 | else () 161 | add_custom_target(${target}.nro ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${target}.nro) 162 | endif () 163 | endfunction() 164 | 165 | function(add_ovl_target target) 166 | 167 | if (NOT APP_ROMFS) 168 | set(__NRO_COMMAND ${ELF2NRO} 169 | $ 170 | ${CMAKE_CURRENT_BINARY_DIR}/${target}.ovl 171 | --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp 172 | --icon=${APP_ICON} 173 | ) 174 | else() 175 | set(__NRO_COMMAND ${ELF2NRO} 176 | $ 177 | ${CMAKE_CURRENT_BINARY_DIR}/${target}.ovl 178 | --nacp=${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp 179 | --romfs=${APP_ROMFS} 180 | --icon=${APP_ICON} 181 | ) 182 | endif() 183 | 184 | if (NOT ${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp) 185 | add_nacp(${target}.nacp) 186 | endif () 187 | 188 | add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${target}.ovl 189 | COMMAND ${__NRO_COMMAND} 190 | DEPENDS ${target}.elf ${CMAKE_CURRENT_BINARY_DIR}/${target}.nacp 191 | VERBATIM) 192 | 193 | if (CMAKE_RUNTIME_OUTPUT_DIRECTORY) 194 | add_custom_target(${target}.ovl ALL SOURCES ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}.ovl) 195 | else () 196 | add_custom_target(${target}.ovl ALL SOURCES ${CMAKE_CURRENT_BINARY_DIR}/${target}.ovl) 197 | endif () 198 | endfunction() 199 | 200 | function(build_switch_binaries target) 201 | get_filename_component(target_we ${target} NAME_WE) 202 | 203 | if (${ARGC} GREATER 1) 204 | set(APP_TITLE ${ARGV1}) 205 | else () 206 | if (NOT APP_TITLE) 207 | set(APP_TITLE ${target_we}) 208 | endif () 209 | endif () 210 | 211 | if (${ARGC} GREATER 2) 212 | set(APP_AUTHOR ${ARGV2}) 213 | else () 214 | if (NOT APP_AUTHOR) 215 | set(APP_AUTHOR "Unspecified Author") 216 | endif () 217 | endif () 218 | 219 | if (${ARGC} GREATER 3) 220 | set(APP_ICON ${ARGV3}) 221 | else () 222 | if (NOT APP_ICON) 223 | acquire_homebrew_icon(${target_we}) 224 | endif () 225 | endif () 226 | 227 | if (${ARGC} GREATER 4) 228 | set(APP_VERSION ${ARGV4}) 229 | else () 230 | if (NOT APP_VERSION) 231 | set(APP_VERSION "1.0.0") 232 | endif () 233 | endif () 234 | 235 | if (${ARGC} GREATER 5) 236 | set(APP_ROMFS ${ARGV5}) 237 | endif () 238 | 239 | # Build the binaries 240 | # add_nso_target(${target_we}) 241 | add_nro_target(${target_we}) 242 | endfunction() 243 | 244 | function(build_switch_ovl_binaries target) 245 | 246 | get_filename_component(target_we ${target} NAME_WE) 247 | 248 | if (${ARGC} GREATER 1) 249 | set(APP_TITLE ${ARGV1}) 250 | else () 251 | if (NOT APP_TITLE) 252 | set(APP_TITLE ${target_we}) 253 | endif () 254 | endif () 255 | 256 | if (${ARGC} GREATER 2) 257 | set(APP_AUTHOR ${ARGV2}) 258 | else () 259 | if (NOT APP_AUTHOR) 260 | set(APP_AUTHOR "Unspecified Author") 261 | endif () 262 | endif () 263 | 264 | if (${ARGC} GREATER 3) 265 | set(APP_ICON ${ARGV3}) 266 | else () 267 | if (NOT APP_ICON) 268 | acquire_homebrew_icon(${target_we}) 269 | endif () 270 | endif () 271 | 272 | if (${ARGC} GREATER 4) 273 | set(APP_VERSION ${ARGV4}) 274 | else () 275 | if (NOT APP_VERSION) 276 | set(APP_VERSION "1.0.0") 277 | endif () 278 | endif () 279 | 280 | # Build the binaries 281 | # add_nso_target(${target_we}) 282 | add_ovl_target(${target_we}) 283 | endfunction() 284 | -------------------------------------------------------------------------------- /cmake/options.cmake: -------------------------------------------------------------------------------- 1 | # If the verbose mode is activated, CMake will log more 2 | # information while building. This information may include 3 | # statistics, dependencies and versions. 4 | option(cmake_VERBOSE "Enable for verbose logging." ON) 5 | 6 | # Whether to set the language standard to C++ 17 or C++ 11. 7 | option(USE_CPP_17 "Enable this for C++17 language standard." ON) 8 | -------------------------------------------------------------------------------- /cmake/utils.cmake: -------------------------------------------------------------------------------- 1 | function(cmake_info message) 2 | if (cmake_VERBOSE) 3 | message("Build-Info: ${message}") 4 | endif () 5 | endfunction() 6 | 7 | function(cmake_warning message) 8 | if (cmake_VERBOSE) 9 | message(WARNING "${message}") 10 | endif () 11 | endfunction() 12 | 13 | function(cmake_panic message) 14 | message(FATAL_ERROR "${message}") 15 | endfunction() 16 | -------------------------------------------------------------------------------- /resources/icon_corner.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/resources/icon_corner.psd -------------------------------------------------------------------------------- /resources/icon_gui.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/resources/icon_gui.psd -------------------------------------------------------------------------------- /screenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/1.jpg -------------------------------------------------------------------------------- /screenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/2.jpg -------------------------------------------------------------------------------- /screenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/3.jpg -------------------------------------------------------------------------------- /screenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/4.jpg -------------------------------------------------------------------------------- /screenshots/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/5.jpg -------------------------------------------------------------------------------- /screenshots/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/6.jpg -------------------------------------------------------------------------------- /screenshots/overlay/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/overlay/0.png -------------------------------------------------------------------------------- /screenshots/overlay/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/overlay/1.png -------------------------------------------------------------------------------- /screenshots/overlay/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/overlay/2.png -------------------------------------------------------------------------------- /screenshots/overlay/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/screenshots/overlay/3.png -------------------------------------------------------------------------------- /setup_env.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | function setup_devkitpro() 4 | { 5 | echo "Seting up DevKitPro..." >&2 6 | export DEVKITPRO=/opt/devkitpro 7 | export DEVKITA64=${DEVKITPRO}/devkitA64 8 | export DEVKITARM=${DEVKITPRO}/devkitARM 9 | export DEVKITPPC=${DEVKITPRO}/devkitPPC 10 | export PORTLIBS_PREFIX=${DEVKITPRO}/portlibs/switch 11 | 12 | export PATH=${DEVKITPRO}/tools/bin:$PATH 13 | export PATH=${DEVKITA64}/bin/:$PATH 14 | 15 | return; 16 | }; export -f setup_devkitpro 17 | 18 | function send_file(){ 19 | 20 | HOST='192.168.1.75:5000' 21 | lftp -e "cd $2; put $1; bye" $HOST 22 | 23 | }; export -f send_file 24 | 25 | 26 | setup_devkitpro 27 | -------------------------------------------------------------------------------- /src/Applications/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Applications definition 3 | # 4 | 5 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/SimpleModManager ) 6 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/SimpleModManagerConsole ) 7 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/SimpleModManagerOverlay ) 8 | 9 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManager/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Building GUI app 2 | 3 | 4 | 5 | # Replace this with the name of your application 6 | set( GUI_NAME "SimpleModManager") 7 | set( GUI_APP "${GUI_NAME}") 8 | set( GUI_DIR ${PROJECT_SOURCE_DIR}/gui) 9 | 10 | # Meta information about the app 11 | set( GUI_TITLE ${GUI_NAME}) 12 | set( GUI_AUTHOR "Nadrino") 13 | set( GUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/resources/assets/icon_gui.jpg") 14 | set( GUI_ROMFS "${CMAKE_CURRENT_SOURCE_DIR}/resources/romfs") 15 | 16 | set( SRC_FILES src/SimpleModManager.cpp ) 17 | 18 | 19 | add_executable( ${GUI_APP}.elf ${SRC_FILES} ) 20 | target_include_directories( ${GUI_APP}.elf PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) 21 | 22 | target_link_libraries( 23 | ${GUI_APP}.elf PUBLIC 24 | CoreExtension 25 | FrameGameBrowser 26 | FrameModBrowser 27 | Borealis 28 | switch::libnx 29 | -L/opt/devkitpro/portlibs/switch/lib 30 | -L/opt/devkitpro/libnx/lib 31 | ${ZLIB_LIBRARIES} 32 | ${FREETYPE_LIBRARIES} 33 | -lglfw3 -lEGL -lglad -lglapi -ldrm_nouveau -lm -lnx 34 | ) 35 | 36 | set_target_properties(${GUI_APP}.elf PROPERTIES 37 | LINKER_LANGUAGE CXX # Replace this with C if you have C source files 38 | LINK_FLAGS "-specs=${LIBNX}/switch.specs -Wl,-no-as-needed -Wl,-Map,.map" 39 | ) 40 | 41 | 42 | set(CMAKE_BUILD_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${GUI_APP}.elf.dir) 43 | set(BUILD_ROMFS ${CMAKE_BUILD_DIRECTORY}/Resources) 44 | cmake_info("Resources will be gathered in: ${BUILD_ROMFS}") 45 | add_custom_command( 46 | TARGET ${GUI_APP}.elf 47 | PRE_BUILD 48 | COMMAND ${CMAKE_COMMAND} -E echo "Gathering resources..." 49 | COMMAND ${CMAKE_COMMAND} -E remove_directory ${BUILD_ROMFS} 50 | COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_ROMFS} 51 | COMMAND ${CMAKE_COMMAND} -E make_directory ${BUILD_ROMFS}/borealis 52 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${GUI_ROMFS}/. ${BUILD_ROMFS}/. 53 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${BOREALIS_DIR}/resources/. ${BUILD_ROMFS}/borealis/. 54 | COMMAND ${CMAKE_COMMAND} -E echo "Resources have been gathered." 55 | ) 56 | 57 | build_switch_binaries( 58 | ${GUI_APP}.elf 59 | ${GUI_TITLE} ${GUI_AUTHOR} ${GUI_ICON} ${APP_VERSION} ${BUILD_ROMFS} 60 | ) 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManager/include/SimpleModManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien Blanchet on 14/04/2023. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_SIMPLEMODMANAGER_H 6 | #define SIMPLEMODMANAGER_SIMPLEMODMANAGER_H 7 | 8 | void runGui(); 9 | 10 | 11 | #endif //SIMPLEMODMANAGER_SIMPLEMODMANAGER_H 12 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManager/resources/assets/icon_gui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManager/resources/assets/icon_gui.jpg -------------------------------------------------------------------------------- /src/Applications/SimpleModManager/resources/romfs/images/icon_corner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManager/resources/romfs/images/icon_corner.png -------------------------------------------------------------------------------- /src/Applications/SimpleModManager/resources/romfs/images/portrait.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManager/resources/romfs/images/portrait.jpg -------------------------------------------------------------------------------- /src/Applications/SimpleModManager/resources/romfs/images/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManager/resources/romfs/images/unknown.png -------------------------------------------------------------------------------- /src/Applications/SimpleModManager/src/SimpleModManager.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien Blanchet on 14/04/2023. 3 | // 4 | 5 | 6 | #include "SimpleModManager.h" 7 | 8 | #include 9 | #include "ConsoleHandler.h" 10 | 11 | #include "ConfigHandler.h" 12 | 13 | #include "Logger.h" 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include "iostream" 20 | 21 | #include "switch.h" 22 | 23 | 24 | LoggerInit([]{ 25 | Logger::setUserHeaderStr("[SimpleModManager.nro]"); 26 | }); 27 | 28 | 29 | int main(int argc, char* argv[]){ 30 | LogInfo << "SimpleModManager is starting..." << std::endl; 31 | 32 | // https://github.com/jbeder/yaml-cpp/wiki/Tutorial 33 | // YAML::Node config = YAML::LoadFile("config.yaml"); 34 | // if (config["lastLogin"]) { 35 | // std::cout << "Last logged in: " << config["lastLogin"].as() << "\n"; 36 | // } 37 | // const auto username = config["username"].as(); 38 | // const auto password = config["password"].as(); 39 | 40 | ConfigHandler c; 41 | if( c.getConfig().useGui ){ runGui(); } 42 | else{ 43 | consoleInit(nullptr); 44 | ConsoleHandler::run(); 45 | consoleExit(nullptr); 46 | } 47 | 48 | // Exit 49 | return EXIT_SUCCESS; 50 | } 51 | 52 | 53 | void runGui(){ 54 | LogInfo << "Starting GUI..." << std::endl; 55 | LogThrowIf(R_FAILED(nsInitialize()), "nsInitialize Failed"); 56 | 57 | brls::Logger::setLogLevel(brls::LogLevel::ERROR); 58 | 59 | brls::i18n::loadTranslations("en-US"); 60 | LogThrowIf(not brls::Application::init("SimpleModManager"), "Unable to init Borealis application"); 61 | 62 | LogInfo << "Creating root frame..." << std::endl; 63 | auto* mainFrame = new FrameRoot(); 64 | 65 | LogInfo << "Pushing to view" << std::endl; 66 | brls::Application::pushView( mainFrame ); 67 | mainFrame->registerAction( "", brls::Key::PLUS, []{return true;}, true ); 68 | mainFrame->updateActionHint( brls::Key::PLUS, "" ); // make the change visible 69 | 70 | while( brls::Application::mainLoop() ){ } 71 | 72 | nsExit(); 73 | } 74 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerConsole/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # CMakeLists for the Homebrew Application 3 | # 4 | 5 | # Replace this with the name of your application 6 | set(HOMEBREW_NAME "SimpleModManagerConsole") 7 | set(HOMEBREW_APP "${HOMEBREW_NAME}") 8 | 9 | # Meta information about the app 10 | set(HOMEBREW_TITLE ${HOMEBREW_NAME}) 11 | set(HOMEBREW_AUTHOR "Nadrino") 12 | set(HOMEBREW_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/icon.jpg") 13 | 14 | set( SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/SimpleModManagerConsole.cpp ) 15 | 16 | build_switch_binaries( ${HOMEBREW_APP}.elf 17 | ${HOMEBREW_TITLE} ${HOMEBREW_AUTHOR} ${HOMEBREW_ICON} ${APP_VERSION}) # need to be defined before add_executable... CMake works in myterious ways 18 | 19 | add_executable(${HOMEBREW_APP}.elf 20 | ${SRC_FILES} 21 | ) 22 | 23 | target_link_libraries( ${HOMEBREW_APP}.elf 24 | ModManagerCore 25 | switch::libnx 26 | -L/opt/devkitpro/portlibs/switch/lib 27 | ${ZLIB_LIBRARIES} 28 | # -lSDL2 -march=armv8-a -fPIE -L/opt/devkitpro/libnx/lib -lEGL -lglapi -ldrm_nouveau -lnx 29 | ) 30 | 31 | set_target_properties(${HOMEBREW_APP}.elf PROPERTIES 32 | LINKER_LANGUAGE CXX # Replace this with C if you have C source files 33 | LINK_FLAGS "-specs=${LIBNX}/switch.specs -Wl,-no-as-needed -Wl,-Map,.map" 34 | ) 35 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerConsole/assets/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManagerConsole/assets/icon.jpg -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerConsole/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManagerConsole/assets/icon.png -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerConsole/src/SimpleModManagerConsole.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include "ConsoleHandler.h" 4 | 5 | #include 6 | 7 | #include "chrono" 8 | #include "thread" 9 | 10 | 11 | // MAIN 12 | int main( int argc, char **argv ){ 13 | consoleInit(nullptr); 14 | ConsoleHandler::run(); 15 | consoleExit(nullptr); 16 | return EXIT_SUCCESS; 17 | } 18 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # CMakeLists for the Tesla Overlay 3 | # 4 | 5 | # Replace this with the name of your application 6 | set( OVL_NAME "SimpleModManagerOverlay" ) 7 | set( OVL_APP "${OVL_NAME}" ) 8 | 9 | # Meta information about the app 10 | set( OVL_TITLE "SimpleModManager" ) 11 | set( OVL_AUTHOR "Nadrino" ) 12 | set( OVL_ICON ${CMAKE_CURRENT_SOURCE_DIR}/assets/icon.jpg ) 13 | 14 | # sources 15 | set( SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/SimpleModManagerOverlay.cpp ) 16 | 17 | 18 | add_executable( ${OVL_APP}.elf ${SRC_FILES} ) 19 | target_include_directories( ${OVL_APP}.elf PUBLIC 20 | ${CMAKE_CURRENT_SOURCE_DIR}/include 21 | ${SUBMODULES_DIR}/libtesla/include 22 | ) 23 | 24 | target_link_libraries( ${OVL_APP}.elf 25 | # ModManagerOverlay 26 | ModManagerCore 27 | switch::libnx 28 | -L/opt/devkitpro/portlibs/switch/lib 29 | ${ZLIB_LIBRARIES} 30 | ) 31 | 32 | set_target_properties( ${OVL_APP}.elf PROPERTIES 33 | LINKER_LANGUAGE CXX # Replace this with C if you have C source files 34 | LINK_FLAGS "-specs=${LIBNX}/switch.specs -Wl,-no-as-needed -Wl,-Map,.map" 35 | ) 36 | 37 | build_switch_ovl_binaries( ${OVL_APP}.elf ${OVL_TITLE} ${OVL_AUTHOR} ${OVL_ICON} ${APP_VERSION} ) 38 | 39 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/assets/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManagerOverlay/assets/icon.jpg -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nadrino/SimpleModManager/09061cec78460bcc8cf0ff8d3c90dc2e065c494e/src/Applications/SimpleModManagerOverlay/assets/icon.png -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/include/ExampleGui.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien Blanchet on 18/04/2023. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_EXAMPLEGUI_H 6 | #define SIMPLEMODMANAGER_EXAMPLEGUI_H 7 | 8 | #include "tesla.hpp" 9 | 10 | 11 | class GuiSecondary : public tsl::Gui { 12 | public: 13 | GuiSecondary() {} 14 | 15 | virtual tsl::elm::Element* createUI() override { 16 | auto *rootFrame = new tsl::elm::OverlayFrame("Tesla Example", "v1.3.2 - Secondary Gui"); 17 | 18 | rootFrame->setContent(new tsl::elm::DebugRectangle(tsl::Color{ 0x8, 0x3, 0x8, 0xF })); 19 | 20 | return rootFrame; 21 | } 22 | }; 23 | 24 | class GuiTest : public tsl::Gui { 25 | public: 26 | GuiTest(u8 arg1, u8 arg2, bool arg3) { } 27 | 28 | // Called when this Gui gets loaded to create the UI 29 | // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore 30 | virtual tsl::elm::Element* createUI() override { 31 | // A OverlayFrame is the base element every overlay consists of. This will draw the default Title and Subtitle. 32 | // If you need more information in the header or want to change it's look, use a HeaderOverlayFrame. 33 | auto frame = new tsl::elm::OverlayFrame("Tesla Example", "v1.3.2"); 34 | 35 | // A list that can contain sub elements and handles scrolling 36 | auto list = new tsl::elm::List(); 37 | 38 | // List Items 39 | list->addItem(new tsl::elm::CategoryHeader("List items")); 40 | 41 | auto *clickableListItem = new tsl::elm::ListItem("Clickable List Item", "..."); 42 | clickableListItem->setClickListener([](u64 keys) { 43 | if (keys & HidNpadButton_A) { 44 | tsl::changeTo(); 45 | return true; 46 | } 47 | 48 | return false; 49 | }); 50 | 51 | list->addItem(clickableListItem); 52 | list->addItem(new tsl::elm::ListItem("Default List Item")); 53 | list->addItem(new tsl::elm::ListItem("Default List Item with an extra long name to trigger truncation and scrolling")); 54 | list->addItem(new tsl::elm::ToggleListItem("Toggle List Item", true)); 55 | 56 | // Custom Drawer, a element that gives direct access to the renderer 57 | list->addItem(new tsl::elm::CategoryHeader("Custom Drawer", true)); 58 | list->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { 59 | renderer->drawCircle(x + 40, y + 40, 20, true, renderer->a(0xF00F)); 60 | renderer->drawCircle(x + 50, y + 50, 20, true, renderer->a(0xF0F0)); 61 | renderer->drawRect(x + 130, y + 30, 60, 40, renderer->a(0xFF00)); 62 | renderer->drawString("Hello :)", false, x + 250, y + 70, 20, renderer->a(0xFF0F)); 63 | renderer->drawRect(x + 40, y + 90, 300, 10, renderer->a(0xF0FF)); 64 | }), 100); 65 | 66 | // Track bars 67 | list->addItem(new tsl::elm::CategoryHeader("Track bars")); 68 | list->addItem(new tsl::elm::TrackBar("\u2600")); 69 | list->addItem(new tsl::elm::StepTrackBar("\uE13C", 20)); 70 | list->addItem(new tsl::elm::NamedStepTrackBar("\uE132", { "Selection 1", "Selection 2", "Selection 3" })); 71 | 72 | // Add the list to the frame for it to be drawn 73 | frame->setContent(list); 74 | 75 | // Return the frame to have it become the top level element of this Gui 76 | return frame; 77 | } 78 | 79 | // Called once every frame to update values 80 | virtual void update() override { 81 | 82 | } 83 | 84 | // Called once every frame to handle inputs not handled by other UI elements 85 | virtual bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState joyStickPosLeft, HidAnalogStickState joyStickPosRight) override { 86 | return false; // Return true here to signal the inputs have been consumed 87 | } 88 | }; 89 | 90 | class OverlayTest : public tsl::Overlay { 91 | public: 92 | // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys 93 | virtual void initServices() override {} // Called at the start to initialize all services necessary for this Overlay 94 | virtual void exitServices() override {} // Called at the end to clean up all services previously initialized 95 | 96 | virtual void onShow() override {} // Called before overlay wants to change from invisible to visible state 97 | virtual void onHide() override {} // Called before overlay wants to change from visible to invisible state 98 | 99 | virtual std::unique_ptr loadInitialGui() override { 100 | return initially(1, 2, true); // Initial Gui to load. It's possible to pass arguments to it's constructor like this 101 | } 102 | }; 103 | 104 | #endif //SIMPLEMODMANAGER_EXAMPLEGUI_H 105 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/include/GameBrowserGui.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 06/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_GAMEBROWSERGUI_H 6 | #define SIMPLEMODMANAGER_GAMEBROWSERGUI_H 7 | 8 | #include "GameBrowser.h" 9 | #include "ConfigHandler.h" 10 | 11 | #include 12 | 13 | #include "memory" 14 | 15 | 16 | class GameBrowserGui : public tsl::Gui { 17 | 18 | public: 19 | GameBrowserGui() = default; 20 | 21 | // Called when this Gui gets loaded to create the UI 22 | // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore 23 | tsl::elm::Element* createUI() override; 24 | 25 | 26 | void update() override; 27 | 28 | // Called once every frame to handle inputs not handled by other UI elements 29 | bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) override; 30 | 31 | protected: 32 | void fillItemList(); 33 | 34 | private: 35 | // GameBrowser _gameBrowser_; 36 | // ConfigHandler c; 37 | // std::unique_ptr _gameBrowser_{nullptr}; 38 | // std::unique_ptr _h_{}; 39 | std::unique_ptr _c_{}; 40 | 41 | tsl::elm::OverlayFrame* _frame_{nullptr}; 42 | tsl::elm::List* _list_{nullptr}; 43 | 44 | }; 45 | 46 | #include "implementation/GameBrowserGui.impl.h" 47 | 48 | 49 | #endif //SIMPLEMODMANAGER_GAMEBROWSERGUI_H 50 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/include/OverlayGuiLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien Blanchet on 18/04/2023. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_OVERLAYGUILOADER_H 6 | #define SIMPLEMODMANAGER_OVERLAYGUILOADER_H 7 | 8 | #include "tesla.hpp" 9 | 10 | 11 | class OverlayGuiLoader : public tsl::Overlay { 12 | 13 | public: 14 | std::unique_ptr loadInitialGui() override; 15 | 16 | void initServices() override; 17 | void exitServices() override; 18 | 19 | void onShow() override; 20 | void onHide() override; 21 | 22 | }; 23 | 24 | #include "implementation/OverlayGui.impl.h" 25 | 26 | #endif //SIMPLEMODMANAGER_OVERLAYGUILOADER_H 27 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/include/implementation/GameBrowserGui.impl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 06/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_GAMEBROWSERGUI_IMPL_H 6 | #define SIMPLEMODMANAGER_GAMEBROWSERGUI_IMPL_H 7 | 8 | 9 | #include "GameBrowserGui.h" 10 | 11 | // 12 | //#include 13 | #include "ConfigHandler.h" 14 | //#include "GameBrowser.h" 15 | #include "Toolbox.h" 16 | 17 | 18 | tsl::elm::Element *GameBrowserGui::createUI() { 19 | // A OverlayFrame is the base element every overlay consists of. This will draw the default Title and Subtitle. 20 | // If you need more information in the header or want to change it's look, use a HeaderOverlayFrame. 21 | _frame_ = new tsl::elm::OverlayFrame("SimpleModManager", "DOESPATH v" + Toolbox::getAppVersion()); 22 | 23 | // _gameBrowser_ = std::make_unique(); 24 | // _c_ = std::make_unique(); 25 | 26 | GenericToolbox::isFile("/config/SimpleModManager/parameters.ini"); 27 | 28 | // A list that can contain sub elements and handles scrolling 29 | _list_ = new tsl::elm::List(); 30 | 31 | fillItemList(); 32 | 33 | // Return the frame to have it become the top level element of this Gui 34 | return _frame_; 35 | } 36 | 37 | void GameBrowserGui::update() { 38 | // Called once every frame to update values 39 | } 40 | bool GameBrowserGui::handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) { 41 | // Return true here to singal the inputs have been consumed 42 | return false; 43 | } 44 | 45 | 46 | void GameBrowserGui::fillItemList() { 47 | 48 | _list_->clear(); 49 | 50 | // List Items 51 | _list_->addItem(new tsl::elm::CategoryHeader("Folder : ")); 52 | 53 | 54 | 55 | auto *clickableListItem = new tsl::elm::ListItem("TEST"); 56 | clickableListItem->setClickListener([](u64 keys) { 57 | if (keys & HidNpadButton_A) { 58 | ConfigHolder g; 59 | return true; 60 | } 61 | return false; 62 | }); 63 | _list_->addItem(clickableListItem); 64 | 65 | 66 | // auto mod_folders_list = GlobalObjects::gGameBrowser.getSelector().getSelectionList(); 67 | // for (int i_folder = 0; i_folder < int(mod_folders_list.size()); i_folder++) { 68 | // 69 | // auto *clickableListItem = new tsl::elm::ListItem(mod_folders_list[i_folder]); 70 | // std::string selected_folder = mod_folders_list[i_folder]; 71 | // clickableListItem->setClickListener([selected_folder](u64 keys) { 72 | //// if (keys & HidNpadButton_A) { 73 | //// tsl::changeTo(selected_folder); 74 | //// return true; 75 | //// } 76 | // return false; 77 | // }); 78 | // _list_->addItem(clickableListItem); 79 | // 80 | // } 81 | // 82 | // 83 | // _list_->addItem(new tsl::elm::ToggleListItem("Toggle List Item", true)); 84 | // 85 | // // Custom Drawer, a element that gives direct access to the renderer 86 | // _list_->addItem(new tsl::elm::CategoryHeader("Custom Drawer", true)); 87 | // 88 | // _list_->addItem(new tsl::elm::CustomDrawer([](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { 89 | // renderer->drawCircle(x + 40, y + 40, 20, true, renderer->a(0xF00F)); 90 | // renderer->drawCircle(x + 50, y + 50, 20, true, renderer->a(0xF0F0)); 91 | // renderer->drawRect(x + 130, y + 30, 60, 40, renderer->a(0xFF00)); 92 | // renderer->drawString("Hello :)", false, x + 250, y + 70, 20, renderer->a(0xFF0F)); 93 | // renderer->drawRect(x + 40, y + 90, 300, 10, renderer->a(0xF0FF)); 94 | // }), 100); 95 | // 96 | // // Track bars 97 | // _list_->addItem(new tsl::elm::CategoryHeader("Track bars")); 98 | // _list_->addItem(new tsl::elm::TrackBar("\u2600")); 99 | // _list_->addItem(new tsl::elm::StepTrackBar("\uE13C", 20)); 100 | // _list_->addItem(new tsl::elm::NamedStepTrackBar("\uE132", { "Selection 1", "Selection 2", "Selection 3" })); 101 | 102 | // Add the list to the frame for it to be drawn 103 | _frame_->setContent(_list_); 104 | 105 | } 106 | 107 | #endif // SIMPLEMODMANAGER_GAMEBROWSERGUI_IMPL_H 108 | 109 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/include/implementation/OverlayGui.impl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien Blanchet on 18/04/2023. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_OVERLAYGUI_IMPL_H 6 | #define SIMPLEMODMANAGER_OVERLAYGUI_IMPL_H 7 | 8 | #include "OverlayGuiLoader.h" 9 | 10 | #include 11 | 12 | 13 | std::unique_ptr OverlayGuiLoader::loadInitialGui() { 14 | // Initial Gui to load. It's possible to pass arguments to its constructor like this 15 | return initially(); 16 | } 17 | 18 | void OverlayGuiLoader::initServices() { 19 | // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys 20 | tsl::hlp::ScopeGuard dirGuard( [&]{} ); 21 | } 22 | void OverlayGuiLoader::exitServices() { 23 | // Called at the end to clean up all services previously initialized 24 | } 25 | 26 | void OverlayGuiLoader::onShow(){ 27 | // Called before overlay wants to change from invisible to visible state 28 | } 29 | void OverlayGuiLoader::onHide(){ 30 | // Called before overlay wants to change from visible to invisible state 31 | } 32 | 33 | 34 | #endif //SIMPLEMODMANAGER_OVERLAYGUI_IMPL_H 35 | -------------------------------------------------------------------------------- /src/Applications/SimpleModManagerOverlay/src/SimpleModManagerOverlay.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien Blanchet on 18/04/2023. 3 | // 4 | 5 | // Needed before including tesla header in multiple files 6 | #define TESLA_INIT_IMPL 7 | 8 | #include "OverlayGuiLoader.h" 9 | //#include "ExampleGui.h" 10 | 11 | #include // The Tesla Header 12 | 13 | #include "switch.h" 14 | 15 | 16 | int main(int argc, char **argv) { 17 | return tsl::loop(argc, argv); 18 | } 19 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Applications & Libs 3 | # 4 | 5 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/Applications ) 6 | 7 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/ModManagerCore ) 8 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/ModManagerGui ) 9 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/ModManagerOverlay ) 10 | 11 | -------------------------------------------------------------------------------- /src/ModManagerCore/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Building ModManagerCore lib 3 | # 4 | 5 | 6 | set( SRC_FILES 7 | ${CMAKE_CURRENT_SOURCE_DIR}/src/GameBrowser.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ModManager.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ModsPreseter.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ConfigHandler.cpp 11 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Selector.cpp 12 | ${CMAKE_CURRENT_SOURCE_DIR}/src/Toolbox.cpp 13 | ) 14 | 15 | 16 | add_library( ModManagerCore STATIC ${SRC_FILES} ) 17 | install( TARGETS ModManagerCore DESTINATION lib ) 18 | 19 | target_include_directories( ModManagerCore PUBLIC 20 | ${CMAKE_CURRENT_SOURCE_DIR}/include 21 | ) 22 | 23 | target_link_libraries( ModManagerCore PUBLIC 24 | switch::libnx 25 | -L/opt/devkitpro/portlibs/switch/lib 26 | -L/opt/devkitpro/libnx/lib 27 | ${ZLIB_LIBRARIES} 28 | ${FREETYPE_LIBRARIES} 29 | # -lglfw3 -lEGL -lglad -lglapi -ldrm_nouveau -lm -lnx 30 | ) 31 | -------------------------------------------------------------------------------- /src/ModManagerCore/include/ConfigHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 16/10/2019. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_CONFIGHANDLER_H 6 | #define SIMPLEMODMANAGER_CONFIGHANDLER_H 7 | 8 | 9 | #include "GenericToolbox.String.h" 10 | #include "GenericToolbox.Macro.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | 19 | struct PresetConfig{ 20 | std::string name{}; 21 | std::string installBaseFolder{}; 22 | }; 23 | 24 | struct ConfigHolder{ 25 | 26 | #define ENUM_NAME SortGameList 27 | #define ENUM_FIELDS \ 28 | ENUM_FIELD( NbMods, 0 ) \ 29 | ENUM_FIELD( Alphabetical ) \ 30 | ENUM_FIELD( NoSort ) 31 | #include "GenericToolbox.MakeEnum.h" 32 | 33 | bool useGui{true}; 34 | SortGameList sortGameList{SortGameList::NbMods}; 35 | std::string baseFolder{"/mods"}; 36 | int selectedPresetIndex{0}; 37 | std::vector presetList{ 38 | {{"default"}, {"/atmosphere"}}, 39 | {{"reinx"}, {"/reinx"}}, 40 | {{"sxos"}, {"/sxos"}}, 41 | {{"root"}, {"/"}}, 42 | }; 43 | std::string lastSmmVersion{}; 44 | 45 | std::string configFilePath{"/config/SimpleModManager/parameters.ini"}; 46 | 47 | void setSelectedPresetIndex(int selectedPresetIndex_); 48 | void setSelectedPreset(const std::string& preset_); 49 | [[nodiscard]] std::string getCurrentPresetName() const; 50 | [[nodiscard]] const PresetConfig& getCurrentPreset() const { return presetList[selectedPresetIndex]; } 51 | 52 | [[nodiscard]] std::string getSummary() const { 53 | std::stringstream ss; 54 | ss << GET_VAR_NAME_VALUE(useGui) << std::endl; 55 | ss << GET_VAR_NAME_VALUE(sortGameList.toString()) << std::endl; 56 | ss << GET_VAR_NAME_VALUE(baseFolder) << std::endl; 57 | ss << GET_VAR_NAME_VALUE(selectedPresetIndex) << std::endl; 58 | ss << GET_VAR_NAME_VALUE(lastSmmVersion) << std::endl; 59 | ss << GET_VAR_NAME_VALUE(configFilePath) << std::endl; 60 | ss << GenericToolbox::toString(presetList, [](const PresetConfig& p){ return p.name + " -> " + p.installBaseFolder; }); 61 | return ss.str(); 62 | } 63 | }; 64 | 65 | class ConfigHandler { 66 | 67 | public: 68 | ConfigHandler(){ this->loadConfig(); } 69 | 70 | // getters 71 | [[nodiscard]] const ConfigHolder &getConfig() const{ return _config_; } 72 | ConfigHolder &getConfig(){ return _config_; } 73 | 74 | // io 75 | void loadConfig(const std::string& configFilePath_ = ""); 76 | void dumpConfigToFile() const; 77 | 78 | // change preset 79 | void setCurrentConfigPresetId(int selectedPresetId_); 80 | void selectPresetWithName(const std::string &presetName_); 81 | void selectNextPreset(); 82 | void selectPreviousPreset(); 83 | 84 | private: 85 | ConfigHolder _config_{}; 86 | 87 | }; 88 | 89 | 90 | #endif //SIMPLEMODMANAGER_CONFIGHANDLER_H 91 | -------------------------------------------------------------------------------- /src/ModManagerCore/include/ConsoleHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien Blanchet on 25/04/2023. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_CONSOLEHANDLER_H 6 | #define SIMPLEMODMANAGER_CONSOLEHANDLER_H 7 | 8 | #include "ConfigHandler.h" 9 | #include "Toolbox.h" 10 | #include "Selector.h" 11 | #include "GameBrowser.h" 12 | 13 | #include "GenericToolbox.Switch.h" 14 | 15 | #include "string" 16 | #include "iostream" 17 | #include "thread" 18 | #include "chrono" 19 | 20 | 21 | namespace ConsoleHandler{ 22 | 23 | struct FpsCap{ 24 | explicit FpsCap(double fps_) : createDate(std::chrono::system_clock::now()), fpsMax(1/fps_) {} 25 | ~FpsCap(){ 26 | auto timeDiff = std::chrono::duration_cast(std::chrono::system_clock::now() - createDate); 27 | std::this_thread::sleep_for(fpsMax - timeDiff); 28 | } 29 | 30 | std::chrono::system_clock::time_point createDate; 31 | std::chrono::duration fpsMax; 32 | }; 33 | 34 | void upgradeFrom150(){ 35 | std::string oldPath = GenericToolbox::getCurrentWorkingDirectory() + "/parameters.ini"; // before 1.5.0 36 | if(GenericToolbox::isFile(oldPath)){ 37 | ConfigHandler p; 38 | 39 | // get the new default path 40 | std::string newPath = p.getConfig().configFilePath; 41 | 42 | // load the old file 43 | p.loadConfig( oldPath ); 44 | 45 | // change its path 46 | p.getConfig().configFilePath = newPath; 47 | 48 | // write to the new path 49 | p.dumpConfigToFile(); 50 | 51 | // delete the old config file 52 | GenericToolbox::rm( oldPath ); 53 | 54 | GenericToolbox::Switch::Terminal::printLeft(""); 55 | GenericToolbox::Switch::Terminal::printLeft("Welcome in SimpleModManager v" + Toolbox::getAppVersion(), GenericToolbox::ColorCodes::greenBackground); 56 | GenericToolbox::Switch::Terminal::printLeft(""); 57 | GenericToolbox::Switch::Terminal::printLeft(""); 58 | GenericToolbox::Switch::Terminal::printLeft(""); 59 | GenericToolbox::Switch::Terminal::printLeft(""); 60 | GenericToolbox::Switch::Terminal::printLeft(" > Looks like you've been running on a version <= " + Toolbox::getAppVersion()); 61 | GenericToolbox::Switch::Terminal::printLeft(" > Now parameters.ini is read from : " + p.getConfig().configFilePath); 62 | GenericToolbox::Switch::Terminal::printLeft(" > The old file has been moved to this location."); 63 | GenericToolbox::Switch::Terminal::printLeft(""); 64 | GenericToolbox::Switch::Terminal::printLeft(""); 65 | 66 | Selector::askQuestion("Confirm by pressing A.", {"Ok"}); 67 | } 68 | } 69 | void run(){ 70 | // legacy 71 | upgradeFrom150(); 72 | 73 | // Configure our supported input layout: a single player with standard controller styles 74 | padConfigureInput(1, HidNpadStyleSet_NpadStandard); 75 | 76 | GameBrowser gameBrowser; 77 | gameBrowser.printTerminal(); 78 | 79 | // Initialize the default gamepad (which reads handheld mode inputs as well as the first connected controller) 80 | PadState pad; 81 | padInitializeAny( &pad ); 82 | 83 | // Main loop 84 | u64 kDown, kHeld; 85 | while( appletMainLoop() ) { 86 | FpsCap fpsGuard(30); 87 | 88 | //Scan all the inputs. This should be done once for each frame 89 | padUpdate( &pad ); 90 | 91 | //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame) 92 | kDown = padGetButtonsDown( &pad ); 93 | kHeld = padGetButtons( &pad ); 94 | 95 | if( kDown & HidNpadButton_B ){ 96 | // quit 97 | if( not gameBrowser.isGameSelected() ){ 98 | break; 99 | } 100 | } 101 | 102 | gameBrowser.scanInputs( kDown, kHeld ); 103 | 104 | if( kDown == 0 and kHeld == 0 ){ 105 | // don't reprint 106 | continue; 107 | } 108 | 109 | gameBrowser.printTerminal(); 110 | std::cout << std::flush; 111 | 112 | // std::this_thread::sleep_for(std::chrono::milliseconds(100)); 113 | } // while 114 | } 115 | 116 | } 117 | 118 | #endif //SIMPLEMODMANAGER_CONSOLEHANDLER_H 119 | -------------------------------------------------------------------------------- /src/ModManagerCore/include/GameBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 03/09/2019. 3 | // 4 | 5 | #ifndef SWITCHTEMPLATE_BROWSER_H 6 | #define SWITCHTEMPLATE_BROWSER_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | 18 | class GameBrowser{ 19 | 20 | public: 21 | GameBrowser(); 22 | 23 | void setIsGameSelected(bool isGameSelected); 24 | 25 | // getters 26 | bool isGameSelected() const; 27 | const ConfigHandler &getConfigHandler() const; 28 | const Selector &getSelector() const; 29 | Selector &getSelector(); 30 | ModManager &getModManager(); 31 | ModsPresetHandler &getModPresetHandler(); 32 | ConfigHandler &getConfigHandler(); 33 | 34 | // browse 35 | void selectGame(const std::string &gameName_); 36 | 37 | // IO 38 | void scanInputs(u64 kDown, u64 kHeld); 39 | void printTerminal(); 40 | void rebuildSelectorMenu(); 41 | 42 | // utils -> move to gui lib?? 43 | uint8_t* getFolderIcon(const std::string& gameFolder_); 44 | 45 | protected: 46 | void init(); 47 | 48 | private: 49 | bool _isGameSelected_{false}; 50 | 51 | Selector _selector_; 52 | ModManager _modManager_{this}; 53 | ConfigHandler _configHandler_; 54 | ModsPresetHandler _modPresetHandler_; 55 | 56 | }; 57 | 58 | #endif //SWITCHTEMPLATE_BROWSER_H 59 | -------------------------------------------------------------------------------- /src/ModManagerCore/include/ModManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 06/09/2019. 3 | // 4 | 5 | #ifndef MODAPPLIER_MOD_MANAGER_H 6 | #define MODAPPLIER_MOD_MANAGER_H 7 | 8 | #include 9 | #include "Selector.h" 10 | 11 | #include "GenericToolbox.Map.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | struct ApplyCache{ 19 | std::string statusStr{"UNCHECKED"}; 20 | double applyFraction{0}; 21 | }; 22 | 23 | struct ModEntry{ 24 | ModEntry() = default; 25 | explicit ModEntry(std::string modName_): modName(std::move(modName_)) {} 26 | 27 | std::string modName; 28 | std::map applyCache; 29 | 30 | [[nodiscard]] const ApplyCache* getCache(const std::string& preset_) const { 31 | if( not GenericToolbox::isIn(preset_, applyCache) ){ 32 | return nullptr; 33 | } 34 | return &applyCache.at(preset_); 35 | } 36 | [[nodiscard]] std::string getStatus(const std::string& preset_) const { 37 | auto* cache{this->getCache(preset_)}; 38 | if( cache == nullptr ){ return {}; } 39 | return cache->statusStr; 40 | } 41 | [[nodiscard]] double getStatusFraction(const std::string& preset_) const { 42 | auto* cache{this->getCache(preset_)}; 43 | if( cache == nullptr ){ return 0; } 44 | return cache->applyFraction; 45 | } 46 | }; 47 | 48 | ENUM_EXPANDER( 49 | ResultModAction, 0, 50 | Success, 51 | Fail, 52 | Abort 53 | ); 54 | 55 | 56 | class GameBrowser; 57 | 58 | class ModManager { 59 | 60 | public: 61 | explicit ModManager(GameBrowser* owner_); 62 | 63 | // setters 64 | void setAllowAbort(bool allowAbort); 65 | void setGameName(const std::string &gameName); 66 | void setGameFolderPath(const std::string &gameFolderPath_); 67 | void setIgnoredFileList(std::vector& ignoredFileList_); 68 | 69 | // getters 70 | [[nodiscard]] const std::string &getGameName() const; 71 | [[nodiscard]] const std::string &getGameFolderPath() const; 72 | const Selector &getSelector() const; 73 | [[nodiscard]] const std::vector & getIgnoredFileList() const; 74 | const std::vector &getModList() const; 75 | 76 | std::vector &getModList(); 77 | std::vector & getIgnoredFileList(); 78 | 79 | // shortcuts 80 | const ConfigHolder& getConfig() const; 81 | ConfigHolder& getConfig(); 82 | 83 | // selector related 84 | void updateModList(); 85 | void dumpModStatusCache(); 86 | void reloadModStatusCache(); 87 | void resetAllModsCacheAndFile(); 88 | 89 | // mod management 90 | void resetModCache(int modIndex_); 91 | void resetModCache(const std::string &modName_); 92 | 93 | ResultModAction updateModStatus(int modIndex_); 94 | ResultModAction updateModStatus(const std::string& modName_); 95 | ResultModAction updateAllModStatus(); 96 | 97 | ResultModAction applyMod(int modIndex_, bool overrideConflicts_ = false); 98 | ResultModAction applyMod(const std::string& modName_, bool overrideConflicts_ = false); 99 | ResultModAction applyModList(const std::vector &modNamesList_); 100 | 101 | void removeMod(int modIndex_); 102 | void removeMod(const std::string &modName_); 103 | 104 | 105 | 106 | // terminal 107 | void scanInputs(u64 kDown, u64 kHeld); 108 | void printTerminal(); 109 | void rebuildSelectorMenu(); 110 | void displayModFilesStatus(const std::string &modName_); 111 | 112 | // utils 113 | int getModIndex(const std::string& modName_); 114 | 115 | // preset 116 | void reloadCustomPreset(); 117 | void setCustomPreset(const std::string &presetName_); 118 | const PresetConfig& fetchCurrentPreset() const; 119 | 120 | const std::string &getCurrentPresetName() const; 121 | 122 | protected: 123 | void displayConflictsWithOtherMods(size_t modIndex_); 124 | 125 | private: 126 | GameBrowser* _owner_{nullptr}; 127 | 128 | bool _ignoreCacheFiles_{true}; 129 | bool _allowAbort_{true}; 130 | std::string _gameFolderPath_{}; 131 | std::string _gameName_{}; 132 | std::vector _ignoredFileList_{}; 133 | 134 | Selector _selector_; 135 | std::vector _modList_{}; 136 | 137 | std::string _currentPresetName_{}; 138 | }; 139 | 140 | 141 | #endif //MODAPPLIER_MOD_MANAGER_H 142 | -------------------------------------------------------------------------------- /src/ModManagerCore/include/ModsPresetHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 13/02/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_MODSPRESETHANDLER_H 6 | #define SIMPLEMODMANAGER_MODSPRESETHANDLER_H 7 | 8 | #include "Selector.h" 9 | 10 | #include 11 | #include 12 | #include "map" 13 | 14 | 15 | struct PresetData { 16 | std::string name{}; 17 | std::vector modList{}; 18 | }; 19 | 20 | class ModsPresetHandler { 21 | 22 | public: 23 | 24 | ModsPresetHandler() = default; 25 | 26 | void setModFolder(const std::string &gameFolder_); 27 | 28 | [[nodiscard]] const std::vector &getPresetList() const; 29 | std::vector &getPresetList(); 30 | 31 | Selector &getSelector(); 32 | 33 | void selectModPreset(); 34 | void createNewPreset(); 35 | void deleteSelectedPreset(); 36 | void editPreset( size_t entryIndex_ ); 37 | void deletePreset( size_t entryIndex ); 38 | void showConflictingFiles( size_t entryIndex_ ); 39 | 40 | void deletePreset( const std::string& presetName_ ); 41 | 42 | [[nodiscard]] std::vector generatePresetNameList() const; 43 | 44 | // non native getters 45 | std::string getSelectedModPresetName() const; 46 | [[nodiscard]] const std::vector& getSelectedPresetModList() const; 47 | 48 | void writeConfigFile(); 49 | void readConfigFile(); 50 | 51 | protected: 52 | void fillSelector(); 53 | 54 | private: 55 | std::string _gameFolder_{}; 56 | 57 | std::vector _presetList_{}; 58 | 59 | // std::vector _presets_list_; 60 | // std::map> _dataHandler_; 61 | 62 | Selector _selector_; 63 | 64 | }; 65 | 66 | 67 | 68 | 69 | #endif //SIMPLEMODMANAGER_MODSPRESETHANDLER_H 70 | -------------------------------------------------------------------------------- /src/ModManagerCore/include/Selector.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 12/09/2019. 3 | // 4 | 5 | #ifndef SMM_CORE_SELECTOR_H 6 | #define SMM_CORE_SELECTOR_H 7 | 8 | 9 | #include "GenericToolbox.Switch.h" 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include "map" 16 | #include "sstream" 17 | 18 | 19 | struct MenuLine{ 20 | std::stringstream leftPrint{}; 21 | std::stringstream rightPrint{}; 22 | 23 | template MenuLine &operator<<(const T &data){ 24 | leftPrint << data; 25 | return *this; 26 | } 27 | template MenuLine &operator>>(const T &data){ 28 | rightPrint << data; 29 | return *this; 30 | } 31 | void print() const{ 32 | if( not leftPrint.str().empty() and not rightPrint.str().empty() ){ 33 | GenericToolbox::Switch::Terminal::printLeftRight( leftPrint.str(), rightPrint.str() ); 34 | } 35 | else if( not leftPrint.str().empty() ){ 36 | GenericToolbox::Switch::Terminal::printLeft( leftPrint.str() ); 37 | } 38 | else if( not rightPrint.str().empty() ){ 39 | GenericToolbox::Switch::Terminal::printRight( rightPrint.str() ); 40 | } 41 | } 42 | bool empty() const{ 43 | return leftPrint.str().empty() and leftPrint.str().empty(); 44 | } 45 | }; 46 | 47 | struct MenuLineList{ 48 | std::vector lineList; 49 | 50 | template MenuLineList &operator<<(const T &data){ 51 | if( lineList.empty() ) lineList.emplace_back(); 52 | lineList.back().leftPrint << data; 53 | return *this; 54 | } 55 | template MenuLineList &operator>>(const T &data){ 56 | if( lineList.empty() ) lineList.emplace_back(); 57 | lineList.back().rightPrint << data; 58 | return *this; 59 | } 60 | MenuLineList &operator<<(std::ostream &(*f)(std::ostream &)){ 61 | // next line with std::endl 62 | lineList.emplace_back(); 63 | return *this; 64 | } 65 | 66 | void clear(){ lineList.clear(); } 67 | [[nodiscard]] bool empty() const { return lineList.empty(); } 68 | size_t size() const { 69 | if( this->empty() ) return 0; 70 | if( lineList.back().empty() ) return lineList.size() - 1; // last line won't be printed 71 | return lineList.size(); 72 | } 73 | }; 74 | 75 | struct SelectorEntry{ 76 | std::string title{}; 77 | std::string tag{}; 78 | std::vector description{}; 79 | 80 | [[nodiscard]] size_t getNbPrintLines() const { return 1 + description.size(); } 81 | }; 82 | 83 | 84 | class Selector { 85 | 86 | public: 87 | Selector() = default; 88 | explicit Selector(const std::vector& entryTitleList_){ this->setEntryList(entryTitleList_); } 89 | 90 | // non native setters 91 | void setEntryList(const std::vector& entryTitleList_); 92 | void setTag(size_t entryIndex_, const std::string &tag_); 93 | void setTagList(const std::vector& tagList_); 94 | void setDescriptionList(const std::vector> &descriptionList_); 95 | void clearTags(); 96 | void clearDescriptions(); 97 | 98 | // native getters 99 | size_t getCursorPosition() const; 100 | [[nodiscard]] const std::vector &getEntryList() const; 101 | MenuLineList &getHeader(); 102 | MenuLineList &getFooter(); 103 | std::vector &getEntryList(); 104 | 105 | // non native getters 106 | const SelectorEntry& getSelectedEntry() const; 107 | const std::string& getSelectedEntryTitle() const; 108 | size_t getNbMenuLines() const; 109 | size_t getCursorPage() const; 110 | size_t getNbPages() const; 111 | bool isSelectedEntry(const SelectorEntry& entry_) const; 112 | 113 | // io 114 | void printTerminal() const; 115 | void scanInputs(u64 kDown, u64 kHeld); 116 | void clearMenu(); 117 | 118 | // cursor moving 119 | void moveCursorPosition(long cursorPosition_); 120 | void jumpToPage(long pageIndex_); 121 | void selectNextEntry(); 122 | void selectPrevious(); 123 | void jumpToNextPage(); 124 | void jumpToPreviousPage(); 125 | 126 | void invalidatePageCache() const; 127 | void refillPageEntryCache() const; 128 | 129 | // printout 130 | static void printMenu(const MenuLineList& menu_); 131 | static std::string askQuestion( 132 | const std::string& question_, const std::vector& answers_, 133 | const std::vector>& descriptions_= {} 134 | ); 135 | 136 | private: 137 | // user parameters 138 | std::string _cursorMarker_{">"}; 139 | 140 | // selector data 141 | size_t _cursorPosition_{0}; 142 | MenuLineList _header_{}; 143 | MenuLineList _footer_{}; 144 | std::vector _entryList_{}; 145 | 146 | // caches 147 | mutable bool _isPageEntryCacheValid_{false}; 148 | mutable std::vector> _pageEntryCache_{}; // _entryPageMap_[iPage][iEntry] = entryIndex; 149 | u64 _previousKheld_{0}; 150 | u64 _holdingTiks_{0}; 151 | 152 | static const u64 holdTickThreashold{2}; 153 | static const u64 repeatTick{1}; 154 | 155 | // dummies 156 | static const SelectorEntry _dummyEntry_; 157 | 158 | }; 159 | 160 | 161 | #endif //SMM_CORE_SELECTOR_H 162 | -------------------------------------------------------------------------------- /src/ModManagerCore/include/Toolbox.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 04/09/2019. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_TOOLBOX_H 6 | #define SIMPLEMODMANAGER_TOOLBOX_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace Toolbox{ 16 | //! External function 17 | std::string getAppVersion(); 18 | } 19 | 20 | #endif //SIMPLEMODMANAGER_TOOLBOX_H 21 | -------------------------------------------------------------------------------- /src/ModManagerCore/src/ConfigHandler.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 16/10/2019. 3 | // 4 | 5 | #include "ConfigHandler.h" 6 | #include "Toolbox.h" 7 | 8 | #include "GenericToolbox.Switch.h" 9 | #include "GenericToolbox.Vector.h" 10 | 11 | #include 12 | 13 | // struct 14 | void ConfigHolder::setSelectedPresetIndex(int selectedPresetIndex_){ 15 | if( presetList.empty() ){ selectedPresetIndex = -1; return; } 16 | selectedPresetIndex_ %= int( presetList.size() ); 17 | if( selectedPresetIndex_ < 0 ) selectedPresetIndex_ += int( presetList.size() ); 18 | selectedPresetIndex = selectedPresetIndex_; 19 | } 20 | void ConfigHolder::setSelectedPreset(const std::string& preset_){ 21 | setSelectedPresetIndex( GenericToolbox::findElementIndex(preset_, presetList, [](const PresetConfig& entry_){ return entry_.name; }) ); 22 | } 23 | std::string ConfigHolder::getCurrentPresetName() const{ 24 | if( presetList.empty() or selectedPresetIndex >= int( presetList.size() ) ){ return {}; } 25 | return presetList[selectedPresetIndex].name; 26 | } 27 | 28 | // io 29 | void ConfigHandler::loadConfig(const std::string &configFilePath_) { 30 | ConfigHolder config{}; // loads defaults 31 | config.presetList.clear(); // reset the default presets 32 | config.configFilePath = configFilePath_; 33 | 34 | if( config.configFilePath.empty() ){ 35 | // default path or reload 36 | config.configFilePath = _config_.configFilePath; 37 | } 38 | 39 | std::string lastUsedPresetName{"default"}; 40 | 41 | if( not GenericToolbox::isFile(config.configFilePath) ){ 42 | // immediately dump the default config to the file 43 | this->dumpConfigToFile(); 44 | return; 45 | } 46 | 47 | // parse the config file 48 | auto configLines = GenericToolbox::dumpFileAsVectorString( config.configFilePath, true ); 49 | for( auto& line : configLines ){ 50 | 51 | // removing heading and trailing spaces 52 | GenericToolbox::trimInputString(line, " "); 53 | 54 | // check if it is a comment 55 | if( GenericToolbox::startsWith(line, "#") ) continue; 56 | 57 | // check if it is a valid piece of data 58 | auto elements = GenericToolbox::splitString(line, "="); 59 | if( elements.size() != 2 ) continue; 60 | 61 | // removing heading and trailing spaces 62 | GenericToolbox::trimInputString( elements[0], " " ); 63 | GenericToolbox::trimInputString( elements[1], " " ); 64 | 65 | if ( elements[0] == "use-gui" ){ 66 | config.useGui = GenericToolbox::toBool( elements[1] ); 67 | } 68 | else if( elements[0] == "sort-game-list-by" ){ 69 | config.sortGameList = ConfigHolder::SortGameList::toEnum( elements[1] ); 70 | } 71 | else if( elements[0] == "stored-mods-base-folder" ){ 72 | config.baseFolder = elements[1]; 73 | } 74 | else if( elements[0] == "last-preset-used" ){ 75 | lastUsedPresetName = elements[1]; 76 | } 77 | else if( elements[0] == "last-program-version" ){ 78 | config.lastSmmVersion = elements[1]; 79 | } 80 | else if( elements[0] == "preset" ){ 81 | config.presetList.emplace_back(); 82 | config.presetList.back().name = elements[1]; 83 | } 84 | else if( elements[0] == "install-mods-base-folder" ){ 85 | if( config.presetList.empty() ){ 86 | config.presetList.emplace_back(); 87 | config.presetList.back().name = "default"; 88 | } 89 | config.presetList.back().installBaseFolder = elements[1]; 90 | } 91 | 92 | } // lines 93 | 94 | // look for the selected preset index. If not found, will stay at 0 95 | this->selectPresetWithName( lastUsedPresetName ); 96 | 97 | // copy to the member 98 | _config_ = config; 99 | 100 | // rewrite for cleanup 101 | this->dumpConfigToFile(); 102 | } 103 | void ConfigHandler::dumpConfigToFile() const { 104 | 105 | GenericToolbox::mkdir( GenericToolbox::getFolderPath( _config_.configFilePath ) ); 106 | 107 | std::stringstream ssConfig; 108 | ssConfig << "# This is a config file" << std::endl; 109 | ssConfig << std::endl; 110 | ssConfig << "# folder where mods are stored" << std::endl; 111 | ssConfig << "stored-mods-base-folder = " << _config_.baseFolder << std::endl; 112 | ssConfig << "use-gui = " << _config_.useGui << std::endl; 113 | ssConfig << "sort-game-list-by = " << _config_.sortGameList.toString() << std::endl; 114 | ssConfig << "last-preset-used = " << _config_.getCurrentPresetName() << std::endl; 115 | ssConfig << std::endl; 116 | ssConfig << std::endl; 117 | for( auto &preset : _config_.presetList ){ 118 | ssConfig << "########################################" << std::endl; 119 | ssConfig << "# preset that can be changed in the app" << std::endl; 120 | ssConfig << "preset = " << preset.name << std::endl; 121 | ssConfig << std::endl; 122 | ssConfig << "# base folder where mods are installed" << std::endl; 123 | ssConfig << "install-mods-base-folder = " << preset.installBaseFolder << std::endl; 124 | ssConfig << "########################################" << std::endl; 125 | ssConfig << std::endl; 126 | ssConfig << std::endl; 127 | } 128 | ssConfig << "# DO NOT TOUCH THIS : used to recognise the last version of the program config" << std::endl; 129 | ssConfig << "last-program-version = " << Toolbox::getAppVersion() << std::endl; 130 | ssConfig << std::endl; 131 | 132 | GenericToolbox::dumpStringInFile(_config_.configFilePath, ssConfig.str()); 133 | } 134 | 135 | // preset selection 136 | void ConfigHandler::setCurrentConfigPresetId(int selectedPresetId_){ 137 | _config_.setSelectedPresetIndex( selectedPresetId_ ); 138 | // auto save last-preset-used 139 | this->dumpConfigToFile(); 140 | } 141 | void ConfigHandler::selectPresetWithName(const std::string &presetName_){ 142 | for( size_t iPreset = 0 ; iPreset < _config_.presetList.size() ; iPreset++ ){ 143 | if( _config_.presetList[iPreset].name == presetName_ ){ 144 | this->setCurrentConfigPresetId( int( iPreset ) ); 145 | return; 146 | } 147 | } 148 | } 149 | void ConfigHandler::selectNextPreset(){ 150 | _config_.setSelectedPresetIndex( _config_.selectedPresetIndex + 1 ); 151 | } 152 | void ConfigHandler::selectPreviousPreset(){ 153 | _config_.setSelectedPresetIndex( _config_.selectedPresetIndex - 1 ); 154 | } 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /src/ModManagerCore/src/GameBrowser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 03/09/2019. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | 9 | #include "GenericToolbox.Switch.h" 10 | #include "GenericToolbox.Vector.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | 20 | GameBrowser::GameBrowser(){ this->init(); } 21 | 22 | void GameBrowser::setIsGameSelected(bool isGameSelected) { 23 | _isGameSelected_ = isGameSelected; 24 | } 25 | 26 | // getters 27 | bool GameBrowser::isGameSelected() const { 28 | return _isGameSelected_; 29 | } 30 | const ConfigHandler &GameBrowser::getConfigHandler() const { 31 | return _configHandler_; 32 | } 33 | const Selector &GameBrowser::getSelector() const{ 34 | return _selector_; 35 | } 36 | Selector &GameBrowser::getSelector(){ 37 | return _selector_; 38 | } 39 | ModManager &GameBrowser::getModManager(){ 40 | return _modManager_; 41 | } 42 | ConfigHandler &GameBrowser::getConfigHandler(){ 43 | return _configHandler_; 44 | } 45 | ModsPresetHandler &GameBrowser::getModPresetHandler(){ 46 | return _modPresetHandler_; 47 | } 48 | 49 | // Browse 50 | void GameBrowser::selectGame(const std::string &gameName_) { 51 | _modManager_.setGameName( gameName_ ); 52 | _modManager_.setGameFolderPath( _configHandler_.getConfig().baseFolder + "/" + gameName_ ); 53 | _modPresetHandler_.setModFolder( _configHandler_.getConfig().baseFolder + "/" + gameName_ ); 54 | 55 | _isGameSelected_ = true; 56 | } 57 | 58 | 59 | // Terminal 60 | void GameBrowser::scanInputs(u64 kDown, u64 kHeld){ 61 | 62 | // nothing to do? 63 | if( kDown == 0 and kHeld == 0 ){ return; } 64 | 65 | if( _isGameSelected_ ){ 66 | // back button pressed? 67 | if( kDown & HidNpadButton_B ){ 68 | _isGameSelected_ = false; 69 | // will print the game browser 70 | } 71 | else{ 72 | _modManager_.scanInputs( kDown, kHeld ); 73 | } 74 | return; 75 | } 76 | 77 | // forward to the selector 78 | _selector_.scanInputs( kDown, kHeld ); 79 | 80 | 81 | if ( kDown & HidNpadButton_A ){ // select folder / apply mod 82 | this->selectGame( _selector_.getSelectedEntryTitle() ); 83 | } 84 | else if( kDown & HidNpadButton_Y ){ // switch between config preset 85 | _configHandler_.selectNextPreset(); 86 | } 87 | else if( kDown & HidNpadButton_ZL or kDown & HidNpadButton_ZR ){ 88 | // switch between config preset 89 | auto answer = Selector::askQuestion( 90 | "Do you want to switch back to the GUI ?", 91 | std::vector({"Yes", "No"}) 92 | ); 93 | if(answer == "Yes") { 94 | _configHandler_.getConfig().useGui = true; 95 | _configHandler_.dumpConfigToFile(); 96 | consoleExit(nullptr); 97 | exit( EXIT_SUCCESS ); 98 | // TODO QUIT? 99 | // GlobalObjects::set_quit_now_triggered(true); 100 | } 101 | } 102 | 103 | } 104 | void GameBrowser::printTerminal(){ 105 | 106 | if( _isGameSelected_ ){ 107 | _modManager_.printTerminal(); 108 | return; 109 | } 110 | 111 | if( _selector_.getFooter().empty() ){ 112 | // first build -> page numbering 113 | rebuildSelectorMenu(); 114 | } 115 | 116 | // update page 117 | rebuildSelectorMenu(); 118 | 119 | // print on screen 120 | _selector_.printTerminal(); 121 | } 122 | void GameBrowser::rebuildSelectorMenu(){ 123 | _selector_.clearMenu(); 124 | 125 | _selector_.getHeader() >> "SimpleModManager v" >> Toolbox::getAppVersion() << std::endl; 126 | _selector_.getHeader() << GenericToolbox::ColorCodes::redBackground << "Current Folder : "; 127 | _selector_.getHeader() << _configHandler_.getConfig().baseFolder << std::endl; 128 | _selector_.getHeader() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 129 | 130 | _selector_.getFooter() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 131 | _selector_.getFooter() << " Page (" << _selector_.getCursorPage() + 1 << "/" << _selector_.getNbPages() << ")" << std::endl; 132 | _selector_.getFooter() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 133 | _selector_.getFooter() << "Configuration preset : " << GenericToolbox::ColorCodes::greenBackground; 134 | _selector_.getFooter() << _configHandler_.getConfig().getCurrentPresetName() << GenericToolbox::ColorCodes::resetColor << std::endl; 135 | _selector_.getFooter() << "install-mods-base-folder = " + _configHandler_.getConfig().getCurrentPreset().installBaseFolder << std::endl; 136 | _selector_.getFooter() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 137 | _selector_.getFooter() << " A : Select folder" >> "Y : Change config preset " << std::endl; 138 | _selector_.getFooter() << " B : Quit" >> "ZL/ZR : Switch back to the GUI " << std::endl; 139 | _selector_.getFooter() << std::endl; 140 | _selector_.getFooter() << std::endl; 141 | _selector_.getFooter() << std::endl; 142 | 143 | _selector_.invalidatePageCache(); 144 | _selector_.refillPageEntryCache(); 145 | } 146 | 147 | uint8_t* GameBrowser::getFolderIcon(const std::string& gameFolder_){ 148 | if( _isGameSelected_ ){ return nullptr; } 149 | std::string game_folder_path = _configHandler_.getConfig().baseFolder + "/" + gameFolder_; 150 | uint8_t* icon = GenericToolbox::Switch::Utils::getIconFromTitleId( 151 | GenericToolbox::Switch::Utils::lookForTidInSubFolders(game_folder_path)); 152 | return icon; 153 | } 154 | 155 | // protected 156 | void GameBrowser::init(){ 157 | auto gameList = GenericToolbox::lsDirs( _configHandler_.getConfig().baseFolder ); 158 | 159 | std::vector nGameMod; 160 | nGameMod.reserve( gameList.size() ); 161 | for( auto& game : gameList ){ 162 | nGameMod.emplace_back( 163 | GenericToolbox::lsDirs( 164 | _configHandler_.getConfig().baseFolder + "/" + game 165 | ).size() 166 | ); 167 | } 168 | 169 | auto ordering = GenericToolbox::getSortPermutation(nGameMod, [](size_t a_, size_t b_){ return a_ > b_; }); 170 | GenericToolbox::applyPermutation(gameList, ordering); 171 | GenericToolbox::applyPermutation(nGameMod, ordering); 172 | 173 | _selector_.getEntryList().reserve( gameList.size() ); 174 | for( size_t iGame = 0 ; iGame < gameList.size() ; iGame++ ){ 175 | _selector_.getEntryList().emplace_back(); 176 | _selector_.getEntryList().back().title = gameList[iGame]; 177 | _selector_.getEntryList().back().tag = "(" + std::to_string(nGameMod[iGame]) + " mods)"; 178 | } 179 | } 180 | 181 | -------------------------------------------------------------------------------- /src/ModManagerCore/src/ModsPreseter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 13/02/2020. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "GenericToolbox.Vector.h" 10 | #include "GenericToolbox.Switch.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include "sstream" 18 | 19 | 20 | void ModsPresetHandler::setModFolder(const std::string &gameFolder_) { 21 | _gameFolder_ = gameFolder_; 22 | this->readConfigFile(); 23 | } 24 | 25 | const std::vector &ModsPresetHandler::getPresetList() const { 26 | return _presetList_; 27 | } 28 | std::vector &ModsPresetHandler::getPresetList() { 29 | return _presetList_; 30 | } 31 | 32 | Selector &ModsPresetHandler::getSelector() { 33 | return _selector_; 34 | } 35 | 36 | void ModsPresetHandler::selectModPreset() { 37 | 38 | 39 | auto drawSelectorPage = [&]{ 40 | using namespace GenericToolbox::Switch::Terminal; 41 | 42 | consoleClear(); 43 | printRight("SimpleModManager v" + Toolbox::getAppVersion()); 44 | std::cout << GenericToolbox::ColorCodes::redBackground << std::setw(GenericToolbox::getTerminalWidth()) << std::left; 45 | std::cout << "Select mod preset" << GenericToolbox::ColorCodes::resetColor; 46 | std::cout << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()); 47 | _selector_.printTerminal(); 48 | std::cout << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()); 49 | printLeft(" Page (" + std::to_string(_selector_.getCursorPage() + 1) + "/" + std::to_string( 50 | _selector_.getNbPages()) + ")"); 51 | std::cout << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()); 52 | printLeftRight(" A : Select mod preset", " X : Delete mod preset "); 53 | printLeftRight(" Y : Edit preset", "+ : Create a preset "); 54 | printLeft(" B : Go back"); 55 | consoleUpdate(nullptr); 56 | }; 57 | 58 | PadState pad; 59 | padInitializeAny(&pad); 60 | 61 | drawSelectorPage(); 62 | while(appletMainLoop()){ 63 | 64 | padUpdate(&pad);; 65 | u64 kDown = padGetButtonsDown(&pad); 66 | u64 kHeld = padGetButtons(&pad); 67 | _selector_.scanInputs(kDown, kHeld); 68 | if(kDown & HidNpadButton_B){ 69 | break; 70 | } 71 | else if( kDown & HidNpadButton_A and not _presetList_.empty() ){ 72 | return; 73 | } 74 | else if(kDown & HidNpadButton_X and not _presetList_.empty()){ 75 | std::string answer = Selector::askQuestion( 76 | "Are you sure you want to remove this preset ?", 77 | std::vector({"Yes", "No"}) 78 | ); 79 | if( answer == "Yes" ) this->deleteSelectedPreset(); 80 | } 81 | else if(kDown & HidNpadButton_Plus){ createNewPreset(); } 82 | else if(kDown & HidNpadButton_Y){ this->editPreset( _selector_.getCursorPage() ); } 83 | 84 | if( kDown != 0 or kHeld != 0 ){ drawSelectorPage(); } 85 | 86 | } 87 | } 88 | void ModsPresetHandler::createNewPreset(){ 89 | // generate a new default name 90 | int iPreset{1}; 91 | std::stringstream ss; 92 | auto presetNameList = this->generatePresetNameList(); 93 | do{ ss.str(""); ss << "preset-" << iPreset++; } 94 | while( GenericToolbox::doesElementIsInVector( ss.str(), presetNameList ) ); 95 | 96 | // create a new entry 97 | size_t presetIndex{_presetList_.size()}; 98 | _presetList_.emplace_back(); 99 | _presetList_.back().name = ss.str(); 100 | 101 | // start the editor 102 | this->editPreset( presetIndex ); 103 | } 104 | void ModsPresetHandler::deleteSelectedPreset(){ 105 | this->deletePreset( _selector_.getCursorPosition() ); 106 | } 107 | void ModsPresetHandler::editPreset( size_t entryIndex_ ) { 108 | 109 | auto& preset = _presetList_[entryIndex_]; 110 | 111 | std::vector modsList = GenericToolbox::lsFilesRecursive(_gameFolder_); 112 | std::sort( modsList.begin(), modsList.end() ); 113 | Selector sel; 114 | sel.setEntryList(modsList); 115 | 116 | auto reprocessSelectorTags = [&]{ 117 | // clear tags 118 | sel.clearTags(); 119 | 120 | for(size_t presetModIndex = 0 ; presetModIndex < preset.modList.size() ; presetModIndex++ ){ 121 | for( size_t jEntry = 0 ; jEntry < modsList.size() ; jEntry++ ){ 122 | 123 | if(preset.modList[presetModIndex] == modsList[jEntry] ){ 124 | // add selector tag to the given mod 125 | std::stringstream ss; 126 | ss << sel.getEntryList()[jEntry].tag; 127 | if( not ss.str().empty() ) ss << " & "; 128 | ss << "#" << presetModIndex; 129 | sel.setTag( jEntry, ss.str() ); 130 | break; 131 | } 132 | 133 | } 134 | } 135 | }; 136 | 137 | 138 | auto printSelector = [&]{ 139 | using namespace GenericToolbox::Switch::Terminal; 140 | 141 | consoleClear(); 142 | printRight("SimpleModManager v" + Toolbox::getAppVersion()); 143 | std::cout << GenericToolbox::ColorCodes::redBackground << std::setw(GenericToolbox::getTerminalWidth()) << std::left; 144 | std::string header_title = "Creating preset : " + preset.name + ". Select the mods you want."; 145 | std::cout << header_title << GenericToolbox::ColorCodes::resetColor; 146 | std::cout << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()); 147 | sel.printTerminal(); 148 | std::cout << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()); 149 | printLeftRight(" A : Add mod", "X : Cancel mod "); 150 | printLeftRight(" + : SAVE", "B : Abort / Go back "); 151 | consoleUpdate(nullptr); 152 | }; 153 | 154 | PadState pad; 155 | padInitializeAny(&pad); 156 | 157 | printSelector(); 158 | while(appletMainLoop()){ 159 | 160 | padUpdate(&pad); 161 | u64 kDown = padGetButtonsDown(&pad); 162 | u64 kHeld = padGetButtons(&pad); 163 | sel.scanInputs(kDown, kHeld); 164 | if(kDown & HidNpadButton_A){ 165 | preset.modList.emplace_back(sel.getSelectedEntryTitle() ); 166 | reprocessSelectorTags(); 167 | } 168 | else if(kDown & HidNpadButton_X){ 169 | preset.modList.pop_back(); 170 | reprocessSelectorTags(); 171 | } 172 | else if( kDown & HidNpadButton_Plus ){ 173 | // save changes 174 | break; 175 | } 176 | else if( kDown & HidNpadButton_B ){ 177 | // discard changes 178 | return; 179 | } 180 | 181 | if( kDown != 0 or kHeld != 0 ){ printSelector(); } 182 | } 183 | 184 | 185 | preset.name = GenericToolbox::Switch::UI::openKeyboardUi( preset.name ); 186 | 187 | // Check for conflicts 188 | this->showConflictingFiles( entryIndex_ ); 189 | this->writeConfigFile(); 190 | this->readConfigFile(); 191 | } 192 | void ModsPresetHandler::deletePreset( size_t entryIndex ){ 193 | _presetList_.erase( _presetList_.begin() + long( entryIndex ) ); 194 | this->writeConfigFile(); 195 | this->readConfigFile(); 196 | } 197 | void ModsPresetHandler::showConflictingFiles( size_t entryIndex_ ) { 198 | using namespace GenericToolbox::Switch::Terminal; 199 | 200 | auto& preset = _presetList_[entryIndex_]; 201 | 202 | consoleClear(); 203 | 204 | printLeft("Scanning preset files...", GenericToolbox::ColorCodes::magentaBackground); 205 | consoleUpdate(nullptr); 206 | 207 | struct ModFileEntry{ 208 | long finalFileSize{0}; 209 | std::vector fromModList{}; 210 | }; 211 | 212 | std::map installedFileList; 213 | 214 | for( auto& mod : preset.modList ){ 215 | printLeft(" > Getting files for the mod: " + mod, GenericToolbox::ColorCodes::magentaBackground); 216 | consoleUpdate(nullptr); 217 | 218 | auto filesList = GenericToolbox::lsFilesRecursive(_gameFolder_ + "/" + mod ); 219 | for( auto& file: filesList ){ 220 | std::stringstream ss; 221 | ss << _gameFolder_ << "/" << mod << "/" << file; 222 | installedFileList[file].finalFileSize = GenericToolbox::getFileSize( ss.str() ); 223 | installedFileList[file].fromModList.emplace_back( mod ); 224 | } 225 | } 226 | 227 | double presetSize{0}; 228 | for( auto& installedFile : installedFileList ){ 229 | presetSize += double( installedFile.second.finalFileSize ); 230 | } 231 | 232 | // std::string total_SD_size_str = GenericToolbox::parseSizeUnits(total_SD_size); 233 | 234 | std::vector conflictFileList; 235 | std::vector overridingModList; 236 | for( auto& installedFile : installedFileList ){ 237 | if( installedFile.second.fromModList.size() == 1 ){ continue; } 238 | conflictFileList.emplace_back( installedFile.first ); 239 | overridingModList.emplace_back( installedFile.second.fromModList.back() ); 240 | } 241 | 242 | Selector sel; 243 | sel.setEntryList(conflictFileList); 244 | sel.setTagList(overridingModList); 245 | 246 | auto rebuildHeader = [&]{ 247 | consoleClear(); 248 | 249 | sel.getHeader() >> "SimpleModManager v" >> Toolbox::getAppVersion() << std::endl; 250 | sel.getHeader() << GenericToolbox::ColorCodes::redBackground << "Conflicted files for the preset \"" << preset.name << "\":" << std::endl; 251 | sel.getHeader() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 252 | 253 | sel.getFooter() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 254 | sel.getFooter() << GenericToolbox::ColorCodes::greenBackground << "Total size of the preset:" + GenericToolbox::parseSizeUnits(presetSize) << std::endl; 255 | sel.getFooter() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 256 | sel.getFooter() << "Page (" << sel.getCursorPage() << "/" << sel.getNbPages() << ")" << std::endl; 257 | sel.getFooter() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 258 | sel.getFooter() << " A : OK" << std::endl; 259 | sel.getFooter() << " <- : Previous Page" >> "-> : Next Page " << std::endl; 260 | 261 | sel.invalidatePageCache(); 262 | sel.refillPageEntryCache(); 263 | }; 264 | 265 | 266 | auto printSelector = [&]{ 267 | // first nb page processing 268 | if( sel.getFooter().empty() ) rebuildHeader(); 269 | 270 | // update page number 271 | rebuildHeader(); 272 | 273 | consoleClear(); 274 | sel.printTerminal(); 275 | consoleUpdate(nullptr); 276 | }; 277 | 278 | printSelector(); 279 | 280 | PadState pad; 281 | padInitializeAny(&pad); 282 | 283 | // Main loop 284 | u64 kDown{0}, kHeld{0}; 285 | while(appletMainLoop()) { 286 | 287 | //Scan all the inputs. This should be done once for each frame 288 | padUpdate(&pad); 289 | 290 | //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame) 291 | kDown = padGetButtonsDown(&pad); 292 | kHeld = padGetButtons(&pad); 293 | 294 | if (kDown & HidNpadButton_A) { 295 | break; // break in order to return to hbmenu 296 | } 297 | 298 | sel.scanInputs(kDown, kHeld); 299 | 300 | if( kDown != 0 or kHeld != 0 ){ 301 | printSelector(); 302 | } 303 | } 304 | 305 | } 306 | 307 | void ModsPresetHandler::deletePreset( const std::string& presetName_ ){ 308 | int index = GenericToolbox::findElementIndex( presetName_, _selector_.getEntryList(), []( const SelectorEntry& e ){ return e.title; } ); 309 | if( index == -1 ){ return ; } 310 | this->deletePreset( index ); 311 | } 312 | 313 | std::vector ModsPresetHandler::generatePresetNameList() const{ 314 | std::vector out; 315 | out.reserve(_presetList_.size()); 316 | for( auto& preset : _presetList_ ){ 317 | out.emplace_back( preset.name ); 318 | } 319 | return out; 320 | } 321 | 322 | std::string ModsPresetHandler::getSelectedModPresetName() const { 323 | if( _presetList_.empty() ) return {}; 324 | return _presetList_[_selector_.getCursorPosition()].name; 325 | } 326 | const std::vector& ModsPresetHandler::getSelectedPresetModList() const{ 327 | return _presetList_[_selector_.getCursorPosition()].modList; 328 | } 329 | 330 | void ModsPresetHandler::readConfigFile() { 331 | _presetList_.clear(); 332 | 333 | // check if file exist 334 | auto lines = GenericToolbox::dumpFileAsVectorString(_gameFolder_ + "/mod_presets.conf", true ); 335 | 336 | for( auto &line : lines ){ 337 | if(line[0] == '#') continue; 338 | 339 | auto lineElements = GenericToolbox::splitString(line, "=", true); 340 | if( lineElements.size() != 2 ) continue; 341 | 342 | // clean up for extra spaces characters 343 | for(auto &element : lineElements){ GenericToolbox::trimInputString(element, " "); } 344 | 345 | if( lineElements[0] == "preset" ){ 346 | _presetList_.emplace_back(); 347 | _presetList_.back().name = lineElements[1]; 348 | } 349 | else { 350 | // cleaning possible garbage 351 | if( _presetList_.empty() ) continue; 352 | // element 0 is "mod7" for example. Irrelevant here 353 | _presetList_.back().modList.emplace_back( lineElements[1] ); 354 | } 355 | } 356 | 357 | this->fillSelector(); 358 | } 359 | void ModsPresetHandler::writeConfigFile() { 360 | 361 | std::stringstream ss; 362 | 363 | ss << "# This is a config file" << std::endl; 364 | ss << std::endl; 365 | ss << std::endl; 366 | 367 | for(auto const &preset : _presetList_ ){ 368 | ss << "########################################" << std::endl; 369 | ss << "# mods preset name" << std::endl; 370 | ss << "preset = " << preset.name << std::endl; 371 | ss << std::endl; 372 | ss << "# mods list" << std::endl; 373 | int iMod{0}; 374 | for( auto& mod : preset.modList ){ 375 | ss << "mod" << iMod++ << " = " << mod << std::endl; 376 | } 377 | ss << "########################################" << std::endl; 378 | ss << std::endl; 379 | } 380 | 381 | std::string data = ss.str(); 382 | GenericToolbox::dumpStringInFile(_gameFolder_ + "/mod_presets.conf", data); 383 | 384 | } 385 | void ModsPresetHandler::fillSelector(){ 386 | _selector_ = Selector(); 387 | 388 | if( _presetList_.empty() ){ 389 | _selector_.setEntryList({{"NO MODS PRESETS"}}); 390 | return; 391 | } 392 | 393 | auto presetNameList = this->generatePresetNameList(); 394 | std::vector> descriptionsList; 395 | descriptionsList.reserve( presetNameList.size() ); 396 | for( auto& preset: _presetList_ ){ 397 | descriptionsList.emplace_back(); 398 | descriptionsList.back().reserve( preset.modList.size() ); 399 | for( auto& mod : preset.modList ){ descriptionsList.back().emplace_back(" | " + mod ); } 400 | } 401 | 402 | _selector_.setEntryList(presetNameList); 403 | _selector_.setDescriptionList(descriptionsList); 404 | } 405 | 406 | 407 | 408 | 409 | 410 | -------------------------------------------------------------------------------- /src/ModManagerCore/src/Selector.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 12/09/2019. 3 | // 4 | 5 | #include "Selector.h" 6 | 7 | #include 8 | 9 | #include "GenericToolbox.Switch.h" 10 | #include "GenericToolbox.Vector.h" 11 | 12 | #include 13 | 14 | // non native setters 15 | void Selector::setEntryList(const std::vector& entryTitleList_) { 16 | this->invalidatePageCache(); 17 | _cursorPosition_ = 0; 18 | _entryList_.clear(); 19 | _entryList_.reserve( entryTitleList_.size() ); 20 | for( auto& title : entryTitleList_ ){ 21 | _entryList_.emplace_back(); 22 | _entryList_.back().title = title; 23 | } 24 | } 25 | void Selector::setTag(size_t entryIndex_, const std::string &tag_){ 26 | this->invalidatePageCache(); 27 | if( entryIndex_ >= _entryList_.size() ) return; 28 | _entryList_[entryIndex_].tag = tag_; 29 | } 30 | void Selector::setTagList(const std::vector& tagList_){ 31 | this->invalidatePageCache(); 32 | if( tagList_.size() != _entryList_.size() ) return; 33 | for( size_t iEntry = 0 ; iEntry < _entryList_.size() ; iEntry++ ){ 34 | _entryList_[iEntry].tag = tagList_[iEntry]; 35 | } 36 | } 37 | void Selector::setDescriptionList(const std::vector> &descriptionList_){ 38 | this->invalidatePageCache(); 39 | if(descriptionList_.size() != _entryList_.size()) return; 40 | for( size_t iEntry = 0 ; iEntry < _entryList_.size() ; iEntry++ ){ 41 | _entryList_[iEntry].description = descriptionList_[iEntry]; 42 | } 43 | } 44 | void Selector::clearTags(){ 45 | this->invalidatePageCache(); 46 | for( auto& entry : _entryList_ ){ entry.tag = ""; } 47 | } 48 | void Selector::clearDescriptions(){ 49 | this->invalidatePageCache(); 50 | for( auto& entry : _entryList_ ){ entry.description.clear(); } 51 | } 52 | 53 | // native getters 54 | size_t Selector::getCursorPosition() const { 55 | return _cursorPosition_; 56 | } 57 | const std::vector &Selector::getEntryList() const { 58 | return _entryList_; 59 | } 60 | MenuLineList &Selector::getHeader() { 61 | return _header_; 62 | } 63 | MenuLineList &Selector::getFooter() { 64 | return _footer_; 65 | } 66 | std::vector &Selector::getEntryList(){ 67 | return _entryList_; 68 | } 69 | 70 | 71 | // non native getters 72 | const SelectorEntry& Selector::getSelectedEntry() const { 73 | return _entryList_[_cursorPosition_]; 74 | } 75 | const std::string& Selector::getSelectedEntryTitle() const { 76 | return this->getSelectedEntry().title; 77 | } 78 | size_t Selector::getNbMenuLines() const{ 79 | return _header_.size() + _footer_.size(); 80 | } 81 | size_t Selector::getCursorPage() const{ 82 | this->refillPageEntryCache(); 83 | 84 | for( size_t iPage = 0 ; iPage < _pageEntryCache_.size() ; iPage++ ){ 85 | if( GenericToolbox::doesElementIsInVector( _cursorPosition_, _pageEntryCache_[iPage] ) ){ 86 | return iPage; 87 | } 88 | } 89 | 90 | // default if no entry is defined 91 | return 0; 92 | } 93 | size_t Selector::getNbPages() const { 94 | this->refillPageEntryCache(); 95 | return _pageEntryCache_.size(); 96 | } 97 | bool Selector::isSelectedEntry(const SelectorEntry& entry_) const{ 98 | return &entry_ == &(this->getSelectedEntry()); 99 | } 100 | 101 | // io 102 | void Selector::printTerminal() const { 103 | using namespace GenericToolbox::Switch::Terminal; 104 | 105 | // clear console 106 | consoleClear(); 107 | 108 | Selector::printMenu( _header_ ); 109 | 110 | // get the paging wrt headers/footers 111 | this->refillPageEntryCache(); 112 | 113 | // fetch the current page to print using the cursor position 114 | size_t currentPage{ this->getCursorPage() }; 115 | 116 | // print only the entries that 117 | std::stringstream ssLeft; 118 | for( auto& entryIndex : _pageEntryCache_[currentPage] ){ 119 | 120 | ssLeft.str(""); 121 | 122 | if( entryIndex == _cursorPosition_ ){ ssLeft << _cursorMarker_; } 123 | else{ ssLeft << " "; } 124 | ssLeft << " " << _entryList_[entryIndex].title; 125 | 126 | printLeftRight( 127 | ssLeft.str(), _entryList_[entryIndex].tag, 128 | (entryIndex == _cursorPosition_ ? GenericToolbox::ColorCodes::blueBackground : "") 129 | ); 130 | 131 | for( auto& descriptionLine : _entryList_[entryIndex].description ){ 132 | printLeft( 133 | descriptionLine, 134 | (entryIndex == _cursorPosition_ ? GenericToolbox::ColorCodes::blueBackground : "") 135 | ); 136 | } 137 | 138 | } 139 | 140 | Selector::printMenu( _footer_ ); 141 | 142 | consoleUpdate(nullptr); 143 | } 144 | void Selector::scanInputs( u64 kDown, u64 kHeld ){ 145 | 146 | // manage persistence 147 | if( kHeld != 0 and kHeld == _previousKheld_ ){ _holdingTiks_++; } 148 | else{ _holdingTiks_ = 0; } 149 | 150 | // cache last key 151 | _previousKheld_ = kHeld; 152 | 153 | if( kDown == 0 and kHeld == 0 ){ return; } 154 | 155 | if( kDown & HidNpadButton_AnyDown or 156 | ( kHeld & HidNpadButton_AnyDown and _holdingTiks_ > holdTickThreashold and _holdingTiks_ % repeatTick == 0 ) 157 | ){ 158 | this->selectNextEntry(); 159 | } 160 | else if( kDown & HidNpadButton_AnyUp or 161 | ( kHeld & HidNpadButton_AnyUp and _holdingTiks_ > holdTickThreashold and _holdingTiks_ % repeatTick == 0 ) 162 | ){ 163 | this->selectPrevious(); 164 | } 165 | else if( kDown & HidNpadButton_AnyLeft or 166 | ( kHeld & HidNpadButton_AnyLeft and _holdingTiks_ > holdTickThreashold and _holdingTiks_ % repeatTick == 0 ) 167 | ){ 168 | this->jumpToPreviousPage(); 169 | } 170 | else if( kDown & HidNpadButton_AnyRight or 171 | ( kHeld & HidNpadButton_AnyRight and _holdingTiks_ > holdTickThreashold and _holdingTiks_ % repeatTick == 0 ) 172 | ){ 173 | this->jumpToNextPage(); 174 | } 175 | 176 | } 177 | void Selector::clearMenu(){ 178 | _header_.clear(); 179 | _footer_.clear(); 180 | } 181 | 182 | // move cursor 183 | void Selector::moveCursorPosition(long cursorPosition_){ 184 | // if no entry, stay at 0 185 | if( _entryList_.empty() ){ _cursorPosition_ = 0; return; } 186 | 187 | // modulus to a valid index 188 | cursorPosition_ %= long( _entryList_.size() ); 189 | if( cursorPosition_ < 0 ){ cursorPosition_ += long( _entryList_.size() ); } 190 | 191 | // set the valid cursor position 192 | _cursorPosition_ = cursorPosition_; 193 | } 194 | void Selector::jumpToPage(long pageIndex_){ 195 | this->refillPageEntryCache(); 196 | 197 | // if no page is set, don't do anything 198 | if( _pageEntryCache_.empty() ){ return; } 199 | 200 | // make sure the page index is valid 201 | pageIndex_ %= long( _pageEntryCache_.size() ); 202 | if( pageIndex_ < 0 ){ pageIndex_ += long( _pageEntryCache_.size() ); } 203 | 204 | // if no entry in this page, don't do anything 205 | if( _pageEntryCache_[pageIndex_].empty() ){ return; } 206 | 207 | this->moveCursorPosition( long( _pageEntryCache_[pageIndex_][0] ) ); 208 | } 209 | void Selector::selectNextEntry(){ 210 | this->moveCursorPosition(long(_cursorPosition_) + 1); 211 | } 212 | void Selector::selectPrevious(){ 213 | this->moveCursorPosition(long(_cursorPosition_) - 1); 214 | } 215 | void Selector::jumpToNextPage(){ 216 | this->jumpToPage( long( this->getCursorPage() ) + 1 ); 217 | } 218 | void Selector::jumpToPreviousPage(){ 219 | this->jumpToPage( long( this->getCursorPage() ) - 1 ); 220 | } 221 | 222 | // paging 223 | void Selector::invalidatePageCache() const{ 224 | _isPageEntryCacheValid_ = false; 225 | } 226 | void Selector::refillPageEntryCache() const { 227 | if( _isPageEntryCacheValid_ ) return; 228 | 229 | // reset the cache 230 | _pageEntryCache_.clear(); 231 | _pageEntryCache_.emplace_back(); 232 | 233 | long nTotalLines{long(GenericToolbox::getTerminalHeight()) - long(this->getNbMenuLines())}; 234 | if( nTotalLines < 0 ) nTotalLines = 1; // ? 235 | long nLinesLeft{nTotalLines}; 236 | for( size_t iEntry = 0 ; iEntry < _entryList_.size() ; iEntry++ ){ 237 | 238 | // count how many lines would be left if the entry got printed 239 | nLinesLeft -= long( _entryList_[iEntry].getNbPrintLines() ); 240 | 241 | if( _pageEntryCache_.back().empty() ){ 242 | // add the entry even if it's too long 243 | } 244 | else if( nLinesLeft < 0 ){ 245 | // next page and reset counter 246 | _pageEntryCache_.emplace_back(); 247 | nLinesLeft = nTotalLines; 248 | 249 | // it's going to be printed on this new page. Count for it 250 | nLinesLeft -= long( _entryList_[iEntry].getNbPrintLines() ); 251 | } 252 | 253 | _pageEntryCache_.back().emplace_back( iEntry ); 254 | } 255 | 256 | _isPageEntryCacheValid_ = true; 257 | } 258 | 259 | // static 260 | void Selector::printMenu(const MenuLineList& menu_){ 261 | for( auto& menuLine : menu_.lineList ){ 262 | // if is last but empty, skip. It's just a trailing std::endl 263 | if( &menuLine == &menu_.lineList.back() and menuLine.empty() ){ break; } 264 | 265 | // printout to terminal 266 | menuLine.print(); 267 | } 268 | } 269 | std::string Selector::askQuestion(const std::string& question_, const std::vector& answers_, 270 | const std::vector>& descriptions_ ) { 271 | 272 | std::string answer; 273 | Selector sel; 274 | 275 | // set the layout with the question: 276 | sel.getHeader() >> "SimpleModManager v" >> Toolbox::getAppVersion() << std::endl; 277 | sel.getHeader() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 278 | sel.getHeader() << question_ << std::endl; 279 | sel.getHeader() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()); 280 | 281 | // possible answers 282 | sel.setEntryList( answers_ ); 283 | 284 | // fill optional description of the selectable items 285 | if( descriptions_.size() == answers_.size() ){ 286 | sel.setDescriptionList( descriptions_ ); 287 | } 288 | 289 | // footer 290 | sel.getFooter() << GenericToolbox::repeatString("*", GenericToolbox::getTerminalWidth()) << std::endl; 291 | sel.getFooter() << " A: Select" >> "B: back"; 292 | 293 | PadState pad; 294 | padInitializeAny(&pad); 295 | 296 | u64 kDown = 1; 297 | while( appletMainLoop() ){ 298 | 299 | // printout 300 | if( kDown != 0 ) { sel.printTerminal(); } 301 | 302 | //Scan all the inputs. This should be done once for each frame 303 | padUpdate( &pad ); 304 | 305 | //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame) 306 | kDown = padGetButtonsDown(&pad); 307 | 308 | if (kDown & HidNpadButton_AnyDown){ 309 | sel.selectNextEntry(); 310 | } 311 | else if(kDown & HidNpadButton_AnyUp){ 312 | sel.selectPrevious(); 313 | } 314 | else if(kDown & HidNpadButton_A){ 315 | answer = sel.getSelectedEntryTitle(); 316 | break; 317 | } 318 | else if(kDown & HidNpadButton_B){ 319 | break; 320 | } 321 | 322 | } 323 | consoleClear(); 324 | 325 | appletMainLoop(); 326 | 327 | return answer; 328 | } 329 | 330 | -------------------------------------------------------------------------------- /src/ModManagerCore/src/Toolbox.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 04/09/2019. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include "string" 12 | 13 | 14 | namespace Toolbox{ 15 | //! External function 16 | std::string getAppVersion(){ 17 | std::stringstream ss; 18 | ss << get_version_major() << "." << get_version_minor() << "." << get_version_micro() << get_version_tag(); 19 | return ss.str(); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/ModManagerGui/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Building Gui lib 3 | # 4 | 5 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/CoreExtension ) 6 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/FrameGameBrowser ) 7 | add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/FrameModBrowser ) 8 | -------------------------------------------------------------------------------- /src/ModManagerGui/CoreExtension/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Building CoreExtension app 3 | # 4 | 5 | set( SRC_FILES 6 | ${CMAKE_CURRENT_SOURCE_DIR}/src/GuiModManager.cpp 7 | ) 8 | 9 | add_library( CoreExtension STATIC ${SRC_FILES} ) 10 | install( TARGETS CoreExtension DESTINATION lib ) 11 | 12 | target_include_directories( CoreExtension PUBLIC 13 | ${CMAKE_CURRENT_SOURCE_DIR}/include 14 | ) 15 | 16 | target_link_libraries( CoreExtension PUBLIC 17 | Borealis 18 | ModManagerCore 19 | switch::libnx 20 | -L/opt/devkitpro/portlibs/switch/lib 21 | -L/opt/devkitpro/libnx/lib 22 | ${ZLIB_LIBRARIES} 23 | ${FREETYPE_LIBRARIES} 24 | -lglfw3 -lEGL -lglad -lglapi -ldrm_nouveau -lm -lnx 25 | ) 26 | -------------------------------------------------------------------------------- /src/ModManagerGui/CoreExtension/include/GuiModManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 28/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_GUIMODMANAGER_H 6 | #define SIMPLEMODMANAGER_GUIMODMANAGER_H 7 | 8 | 9 | #include "GameBrowser.h" 10 | #include "GenericToolbox.Borealis.h" 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | 18 | class GuiModManager { 19 | 20 | public: 21 | GuiModManager() = default; 22 | 23 | // setters 24 | void setTriggerUpdateModsDisplayedStatus(bool triggerUpdateModsDisplayedStatus); 25 | 26 | // getters 27 | [[nodiscard]] bool isTriggerUpdateModsDisplayedStatus() const; 28 | const GameBrowser &getGameBrowser() const; 29 | GameBrowser &getGameBrowser(); 30 | 31 | void startApplyModThread(const std::string& modName_); 32 | void startRemoveModThread(const std::string& modName_); 33 | void startCheckAllModsThread(); 34 | void startRemoveAllModsThread(); 35 | void startApplyModPresetThread(const std::string &modPresetName_); 36 | 37 | void applyMod(const std::string &modName_); 38 | void applyModsList(std::vector& modsList_); 39 | void removeMod(const std::string &modName_); 40 | void removeAllMods(); 41 | void checkAllMods(bool useCache_ = false); 42 | void getModStatus(const std::string &modName_, bool useCache_ = false); 43 | 44 | protected: 45 | bool applyModFunction(const std::string& modName_); 46 | bool applyModPresetFunction(const std::string& presetName_); 47 | bool removeModFunction(const std::string& modName_); 48 | bool checkAllModsFunction(); 49 | bool removeAllModsFunction(); 50 | 51 | bool leaveModAction(bool isSuccess_); 52 | 53 | 54 | 55 | private: 56 | // core 57 | GameBrowser _gameBrowser_{}; 58 | 59 | std::future _asyncResponse_{}; 60 | 61 | bool _triggeredOnCancel_{false}; 62 | bool _triggerUpdateModsDisplayedStatus_{false}; 63 | 64 | 65 | // monitors 66 | GenericToolbox::Borealis::PopupLoadingBox _loadingPopup_; 67 | struct ModApplyMonitor{ 68 | double progress{0}; 69 | std::string currentFile{}; 70 | }; ModApplyMonitor modApplyMonitor{}; 71 | 72 | struct ModApplyListMonitor{ 73 | double progress{0}; 74 | std::string currentMod{}; 75 | }; ModApplyListMonitor modApplyListMonitor{}; 76 | struct ModCheckMonitor{ 77 | double progress{0}; 78 | std::string currentFile{}; 79 | }; ModCheckMonitor modCheckMonitor{}; 80 | 81 | struct ModCheckAllMonitor{ 82 | double progress{0}; 83 | std::string currentMod{}; 84 | }; ModCheckAllMonitor modCheckAllMonitor{}; 85 | 86 | struct ModRemoveMonitor{ 87 | double progress{0}; 88 | std::string currentFile{}; 89 | }; ModRemoveMonitor modRemoveMonitor{}; 90 | 91 | struct ModRemoveAllMonitor{ 92 | double progress{0}; 93 | std::string currentMod{}; 94 | }; ModRemoveAllMonitor modRemoveAllMonitor{}; 95 | 96 | 97 | }; 98 | 99 | 100 | #endif //SIMPLEMODMANAGER_GUIMODMANAGER_H 101 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Building ModBrowser lib 2 | 3 | 4 | 5 | 6 | set( SRC_FILES 7 | ${CMAKE_CURRENT_SOURCE_DIR}/src/FrameRoot.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/src/TabAbout.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/src/TabGames.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/src/TabGeneralSettings.cpp 11 | ) 12 | 13 | 14 | 15 | 16 | add_library( FrameGameBrowser STATIC ${SRC_FILES} ) 17 | install( TARGETS FrameGameBrowser DESTINATION lib ) 18 | 19 | target_include_directories( FrameGameBrowser PUBLIC 20 | ${CMAKE_CURRENT_SOURCE_DIR}/include 21 | ) 22 | 23 | target_link_libraries( FrameGameBrowser PUBLIC 24 | Borealis 25 | FrameModBrowser 26 | switch::libnx 27 | -L/opt/devkitpro/portlibs/switch/lib 28 | -L/opt/devkitpro/libnx/lib 29 | ${ZLIB_LIBRARIES} 30 | ${FREETYPE_LIBRARIES} 31 | -lglfw3 -lEGL -lglad -lglapi -ldrm_nouveau -lm -lnx 32 | ) 33 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/include/FrameRoot.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 22/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_FRAMEROOT_H 6 | #define SIMPLEMODMANAGER_FRAMEROOT_H 7 | 8 | #include "GuiModManager.h" 9 | 10 | #include "borealis.hpp" 11 | 12 | 13 | class FrameRoot : public brls::TabFrame { 14 | 15 | public: 16 | FrameRoot(); 17 | 18 | bool onCancel() override; 19 | 20 | const GuiModManager &getGuiModManager() const { return _guiModManager_; } 21 | GuiModManager &getGuiModManager(){ return _guiModManager_; } 22 | 23 | private: 24 | GuiModManager _guiModManager_{}; 25 | 26 | }; 27 | 28 | 29 | #endif //SIMPLEMODMANAGER_FRAMEROOT_H 30 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/include/TabAbout.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 20/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_TABABOUT_H 6 | #define SIMPLEMODMANAGER_TABABOUT_H 7 | 8 | #include 9 | 10 | class TabAbout : public brls::List { 11 | 12 | public: 13 | TabAbout(); 14 | 15 | View* getDefaultFocus() override{ return nullptr; } 16 | 17 | }; 18 | 19 | 20 | #endif //SIMPLEMODMANAGER_TABABOUT_H 21 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/include/TabGames.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_TABGAMES_H 6 | #define SIMPLEMODMANAGER_TABGAMES_H 7 | 8 | 9 | #include "ConfigHandler.h" 10 | #include "GameBrowser.h" 11 | #include "Selector.h" 12 | 13 | #include "borealis.hpp" 14 | 15 | #include 16 | 17 | 18 | struct GameItem; 19 | class FrameRoot; 20 | 21 | class TabGames : public brls::List { 22 | 23 | public: 24 | explicit TabGames(FrameRoot* owner_); 25 | 26 | // non native getters 27 | [[nodiscard]] const GameBrowser& getGameBrowser() const; 28 | [[nodiscard]] const ConfigHolder& getConfig() const; 29 | 30 | GameBrowser& getGameBrowser(); 31 | ConfigHolder& getConfig(); 32 | 33 | private: 34 | FrameRoot* _owner_{}; 35 | std::vector _gameList_; 36 | 37 | }; 38 | 39 | struct GameItem{ 40 | std::string title{}; 41 | int nMods{-1}; 42 | 43 | // memory is fully handled by brls 44 | brls::ListItem* item{nullptr}; 45 | }; 46 | 47 | 48 | #endif //SIMPLEMODMANAGER_TABGAMES_H 49 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/include/TabGeneralSettings.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_TABGENERALSETTINGS_H 6 | #define SIMPLEMODMANAGER_TABGENERALSETTINGS_H 7 | 8 | #include "ConfigHandler.h" 9 | 10 | #include 11 | 12 | 13 | class FrameRoot; 14 | 15 | class TabGeneralSettings : public brls::List { 16 | 17 | public: 18 | explicit TabGeneralSettings(FrameRoot* owner_); 19 | 20 | void rebuildLayout(); 21 | 22 | brls::ListItem* itemInstallLocationPreset{nullptr}; 23 | 24 | [[nodiscard]] const ConfigHolder& getConfig() const; 25 | ConfigHolder& getConfig(); 26 | 27 | private: 28 | FrameRoot* _owner_{}; 29 | 30 | }; 31 | 32 | 33 | #endif //SIMPLEMODMANAGER_TABGENERALSETTINGS_H 34 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/src/FrameRoot.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 22/06/2020. 3 | // 4 | 5 | #include "FrameRoot.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | #include "Toolbox.h" 13 | 14 | #include "Logger.h" 15 | 16 | LoggerInit([]{ 17 | Logger::setUserHeaderStr("[FrameRoot]"); 18 | }); 19 | 20 | 21 | FrameRoot::FrameRoot() { 22 | LogWarning << "Build root frame..." << std::endl; 23 | 24 | this->setTitle("SimpleModManager"); 25 | this->setFooterText( "v" + Toolbox::getAppVersion() ); 26 | this->setIcon("romfs:/images/icon_corner.png"); 27 | this->addTab( "Game Browser", new TabGames(this) ); 28 | this->addSeparator(); 29 | this->addTab( "Settings", new TabGeneralSettings(this) ); 30 | this->addTab( "About", new TabAbout() ); 31 | 32 | LogInfo << "Root frame built." << std::endl; 33 | } 34 | 35 | bool FrameRoot::onCancel() { 36 | // fetch the current focus 37 | auto* lastFocus = brls::Application::getCurrentFocus(); 38 | 39 | // perform the cancel 40 | bool onCancel = TabFrame::onCancel(); 41 | 42 | // if the focus is the same, then quit the app 43 | if( lastFocus == brls::Application::getCurrentFocus() ){ brls::Application::quit(); } 44 | 45 | return onCancel; 46 | } 47 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/src/TabAbout.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 20/06/2020. 3 | // 4 | 5 | #include "TabAbout.h" 6 | #include 7 | 8 | #include "Logger.h" 9 | 10 | #include 11 | 12 | 13 | 14 | LoggerInit([]{ 15 | Logger::setUserHeaderStr("[TabAbout]"); 16 | }); 17 | 18 | TabAbout::TabAbout() { 19 | LogWarning << "Building about tab..." << std::endl; 20 | 21 | // Subtitle 22 | auto* shortDescription = new brls::Label( 23 | brls::LabelStyle::REGULAR, 24 | "SimpleModManager is an Nintendo Switch homebrew app that helps you to manage mods and config files on your SD card.", 25 | true 26 | ); 27 | shortDescription->setHorizontalAlign(NVG_ALIGN_CENTER); 28 | this->addView(shortDescription); 29 | 30 | auto* table = new brls::BoxLayout(brls::BoxLayoutOrientation::HORIZONTAL); 31 | table->setSpacing(22); 32 | table->setHeight(260); 33 | 34 | auto* leftBox = new brls::BoxLayout(brls::BoxLayoutOrientation::VERTICAL); 35 | leftBox->setSpacing(22); 36 | leftBox->setWidth(500); 37 | leftBox->setParent(table); 38 | 39 | leftBox->addView(new brls::Header("Version " + Toolbox::getAppVersion() + " - What's new ?")); 40 | auto *changelog = new brls::Label( 41 | brls::LabelStyle::DESCRIPTION, 42 | " - Fixing per game install preset\n - Stability improvements\n", 43 | true 44 | ); 45 | changelog->setHorizontalAlign(NVG_ALIGN_LEFT); 46 | leftBox->addView(changelog); 47 | 48 | leftBox->addView(new brls::Header("Copyright")); 49 | auto *copyright = new brls::Label( 50 | brls::LabelStyle::DESCRIPTION, 51 | "SimpleModManager is licensed under GPL-v3.0\n" \ 52 | "\u00A9 2019 - 2023 Nadrino", 53 | true 54 | ); 55 | copyright->setHorizontalAlign(NVG_ALIGN_CENTER); 56 | leftBox->addView(copyright); 57 | 58 | auto* rightBox = new brls::BoxLayout(brls::BoxLayoutOrientation::VERTICAL); 59 | rightBox->setSpacing(22); 60 | rightBox->setWidth(200); 61 | rightBox->setParent(table); 62 | 63 | rightBox->addView(new brls::Label(brls::LabelStyle::DESCRIPTION, " ")); 64 | 65 | auto* portrait = new brls::Image("romfs:/images/portrait.jpg"); 66 | portrait->setScaleType(brls::ImageScaleType::SCALE); 67 | portrait->setHeight(200); 68 | portrait->setParent(rightBox); 69 | rightBox->addView(portrait); 70 | auto* portraitText = new brls::Label(brls::LabelStyle::SMALL, "Author: Nadrino", true); 71 | portraitText->setHorizontalAlign(NVG_ALIGN_CENTER); 72 | rightBox->addView(portraitText); 73 | 74 | table->addView(leftBox); 75 | table->addView(rightBox); 76 | 77 | this->addView(table); 78 | 79 | this->addView(new brls::Header("Remerciements")); 80 | auto *links = new brls::Label( 81 | brls::LabelStyle::SMALL, 82 | "\uE017 SimpleModManager is powered by Borealis, an hardware accelerated UI library\n" \ 83 | "\uE017 Special thanks to the RetroNX team for their support with Borealis\n", 84 | true 85 | ); 86 | this->addView(links); 87 | 88 | LogInfo << "About tab built." << std::endl; 89 | } 90 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/src/TabGames.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #include "TabGames.h" 6 | #include "FrameModBrowser.h" 7 | #include "FrameRoot.h" 8 | 9 | #include "GenericToolbox.Switch.h" 10 | #include "GenericToolbox.Vector.h" 11 | #include "Logger.h" 12 | 13 | #include 14 | 15 | LoggerInit([]{ 16 | Logger::setUserHeaderStr("[TabGames]"); 17 | }); 18 | 19 | TabGames::TabGames(FrameRoot* owner_) : _owner_(owner_) { 20 | LogWarning << "Building game tab..." << std::endl; 21 | 22 | auto gameList = this->getGameBrowser().getSelector().getEntryList(); 23 | 24 | if( gameList.empty() ){ 25 | LogInfo << "No game found." << std::endl; 26 | 27 | std::stringstream ssTitle; 28 | std::stringstream ssSubTitle; 29 | 30 | auto baseFolder = this->getConfig().baseFolder; 31 | ssTitle << "No game folders have been found in " << baseFolder; 32 | 33 | ssSubTitle << "- To add mods, you need to copy them such as: "; 34 | ssSubTitle << baseFolder << "///." << std::endl; 35 | ssSubTitle << "- You can also change this folder (" + baseFolder; 36 | ssSubTitle << ") by editing the config file in /config/SimpleModManager/parameters.ini"; 37 | 38 | _gameList_.emplace_back(); 39 | _gameList_.back().item = new brls::ListItem( ssTitle.str(), ssSubTitle.str() ); 40 | _gameList_.back().item->show([](){}, false); 41 | } 42 | else{ 43 | LogInfo << "Adding " << gameList.size() << " game folders..." << std::endl; 44 | 45 | _gameList_.reserve( gameList.size() ); 46 | for( auto& gameEntry : gameList ){ 47 | LogScopeIndent; 48 | LogInfo << "Adding game folder: \"" << gameEntry.title << "\"" << std::endl; 49 | 50 | std::string gamePath{GenericToolbox::joinPath(this->getConfig().baseFolder, gameEntry.title)}; 51 | int nMods = int( GenericToolbox::lsDirs(gamePath).size() ); 52 | 53 | // memory allocation 54 | auto* item = new brls::ListItem(gameEntry.title, "", std::to_string(nMods) + " mod(s) available."); 55 | 56 | // looking for tid is quite slow... Slows down the boot up 57 | std::string _titleId_{ GenericToolbox::Switch::Utils::lookForTidInSubFolders(gamePath) }; 58 | auto* icon = GenericToolbox::Switch::Utils::getIconFromTitleId(_titleId_); 59 | if(icon != nullptr){ item->setThumbnail(icon, 0x20000); } 60 | item->getClickEvent()->subscribe([&, gameEntry](View* view) { 61 | LogWarning << "Opening \"" << gameEntry.title << "\"" << std::endl; 62 | getGameBrowser().selectGame( gameEntry.title ); 63 | auto* modsBrowser = new FrameModBrowser( &_owner_->getGuiModManager() ); 64 | brls::Application::pushView(modsBrowser, brls::ViewAnimation::SLIDE_LEFT); 65 | modsBrowser->registerAction("", brls::Key::PLUS, []{return true;}, true); 66 | modsBrowser->updateActionHint(brls::Key::PLUS, ""); // make the change visible 67 | }); 68 | item->updateActionHint(brls::Key::A, "Open"); 69 | 70 | 71 | _gameList_.emplace_back(); 72 | _gameList_.back().title = gameEntry.title; 73 | _gameList_.back().item = item; 74 | _gameList_.back().nMods = nMods; 75 | 76 | } 77 | } 78 | 79 | switch( this->getConfig().sortGameList.value ){ 80 | case ConfigHolder::SortGameList::Alphabetical: 81 | { 82 | LogInfo << "Sorting games wrt nb of mods..." << std::endl; 83 | GenericToolbox::sortVector(_gameList_, [](const GameItem& a_, const GameItem& b_){ 84 | return GenericToolbox::toLowerCase(a_.title) < GenericToolbox::toLowerCase(b_.title); // if true, then a_ goes first 85 | }); 86 | break; 87 | } 88 | case ConfigHolder::SortGameList::NbMods: 89 | { 90 | // "nb-mods" or default 91 | LogInfo << "Sorting games wrt nb of mods..." << std::endl; 92 | GenericToolbox::sortVector(_gameList_, [](const GameItem& a_, const GameItem& b_){ 93 | return a_.nMods > b_.nMods; // if true, then a_ goes first 94 | }); 95 | break; 96 | } 97 | case ConfigHolder::SortGameList::NoSort: 98 | { 99 | LogInfo << "No sort selected." << std::endl; 100 | break; 101 | } 102 | default: 103 | { 104 | LogError << "Invalid sort preset: " << this->getConfig().sortGameList << " / " << this->getConfig().sortGameList.toString() << std::endl; 105 | } 106 | } 107 | LogDebug << "Sort done." << std::endl; 108 | 109 | // add to the view 110 | for( auto& game : _gameList_ ){ this->addView( game.item ); } 111 | 112 | LogInfo << "Game tab build." << std::endl; 113 | } 114 | 115 | const GameBrowser& TabGames::getGameBrowser() const{ 116 | return _owner_->getGuiModManager().getGameBrowser(); 117 | } 118 | const ConfigHolder& TabGames::getConfig() const{ 119 | return getGameBrowser().getConfigHandler().getConfig(); 120 | } 121 | 122 | GameBrowser& TabGames::getGameBrowser(){ 123 | return _owner_->getGuiModManager().getGameBrowser(); 124 | } 125 | ConfigHolder& TabGames::getConfig(){ 126 | return getGameBrowser().getConfigHandler().getConfig(); 127 | } 128 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameGameBrowser/src/TabGeneralSettings.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #include 6 | 7 | #include "FrameRoot.h" 8 | 9 | 10 | #include "Logger.h" 11 | 12 | LoggerInit([]{ 13 | Logger::setUserHeaderStr("[TabGeneralSettings]"); 14 | }); 15 | 16 | TabGeneralSettings::TabGeneralSettings(FrameRoot* owner_) : _owner_(owner_) { 17 | LogWarning << "Building general settings tab..." << std::endl; 18 | this->rebuildLayout(); 19 | LogInfo << "General settings tab build." << std::endl; 20 | } 21 | 22 | void TabGeneralSettings::rebuildLayout() { 23 | 24 | itemInstallLocationPreset = new brls::ListItem( 25 | "\uE255 Install location:", 26 | "Specify from which base folder mods will be installed.\n"\ 27 | "- If you are using Atmosphere, mods have to be installed in /atmosphere/. "\ 28 | "This corresponds to the \"default\" preset. You need to take this path into account in your mod tree structure.\n"\ 29 | "- If you want to set a specific install folder for a given game, please refer to its Option tab and go to \"Attribute a config preset\".", 30 | "" 31 | ); 32 | itemInstallLocationPreset->setValue(this->getConfig().getCurrentPresetName() ); 33 | itemInstallLocationPreset->getClickEvent()->subscribe([this](View* view) { 34 | brls::ValueSelectedEvent::Callback valueCallback = [this](int result) { 35 | if (result == -1) { 36 | return; 37 | } 38 | 39 | this->getConfig().setSelectedPresetIndex( result ); 40 | _owner_->getGuiModManager().getGameBrowser().getConfigHandler().dumpConfigToFile(); 41 | LogInfo << "Selected: " << this->getConfig().getCurrentPreset().name << " -> " << this->getConfig().getCurrentPreset().installBaseFolder << std::endl; 42 | this->itemInstallLocationPreset->setValue(this->getConfig().getCurrentPresetName() ); 43 | }; 44 | 45 | std::vector presetNameList; 46 | presetNameList.reserve( this->getConfig().presetList.size() ); 47 | for( auto& preset : this->getConfig().presetList ){ 48 | presetNameList.emplace_back( preset.name + " \uE090 \"" + preset.installBaseFolder + "\"" ); 49 | } 50 | 51 | brls::Dropdown::open( 52 | "Current install preset:", 53 | presetNameList, 54 | valueCallback, 55 | this->getConfig().selectedPresetIndex 56 | ); 57 | }); 58 | this->addView(itemInstallLocationPreset); 59 | 60 | auto* itemStoredModsBaseFolder = new brls::ListItem( 61 | "\uE431 Stored mods location:", 62 | "This is the place where SimpleModManager will look for your mods. From this folder, the tree structure must look like this:\n"\ 63 | ".///.", 64 | ""); 65 | itemStoredModsBaseFolder->setValue( this->getConfig().baseFolder ); 66 | this->addView(itemStoredModsBaseFolder); 67 | 68 | 69 | auto* itemSortGames = new brls::ListItem( 70 | "\uE255 Sort games by", 71 | "Set which ordering of the games are displayed in the Game Browser list.\n", 72 | "" 73 | ); 74 | itemSortGames->setValue( this->getConfig().sortGameList.toString() ); 75 | 76 | 77 | // On click : show scrolling up menu 78 | itemSortGames->getClickEvent()->subscribe([this, itemSortGames](View* view) { 79 | LogInfo << "Opening itemSortGames selector..." << std::endl; 80 | 81 | // build the choice list + preselection 82 | int preSelection{0}; 83 | std::vector menuList; 84 | menuList.reserve( ConfigHolder::SortGameList::getEnumSize() ); 85 | for( int iEnum = 0 ; iEnum < ConfigHolder::SortGameList::getEnumSize() ; iEnum++ ){ 86 | menuList.emplace_back( ConfigHolder::SortGameList::toString(iEnum) ); 87 | if( menuList.back() == this->getConfig().sortGameList.toString() ){ preSelection = iEnum; } 88 | } 89 | 90 | // function that will set the config preset from the Dropdown menu selection (int result) 91 | brls::ValueSelectedEvent::Callback valueCallback = [this, itemSortGames, menuList](int result) { 92 | if( result == -1 ){ 93 | LogDebug << "Not selected. Return." << std::endl; 94 | // auto pop view 95 | return; 96 | } 97 | 98 | LogInfo << "Selected: " << menuList[result] << std::endl; 99 | this->getConfig().sortGameList = ConfigHolder::SortGameList::toEnum( menuList[result] ); 100 | _owner_->getGuiModManager().getGameBrowser().getConfigHandler().dumpConfigToFile(); 101 | itemSortGames->setValue( this->getConfig().sortGameList.toString() ); 102 | 103 | brls::Application::popView(); 104 | return; 105 | }; // Callback sequence 106 | 107 | brls::Dropdown::open( 108 | "Please select the sort preset you want", 109 | menuList, valueCallback, 110 | preSelection, 111 | true 112 | ); 113 | 114 | }); 115 | this->addView(itemSortGames); 116 | 117 | 118 | auto* itemUseUI = new brls::ListItem("\uE072 Disable the GUI", "If you want to go back on the old UI, select this option."); 119 | 120 | itemUseUI->updateActionHint(brls::Key::B, "Back"); 121 | itemUseUI->registerAction("Select", brls::Key::A, [&](){ 122 | 123 | auto* dialog = new brls::Dialog("Do you want to disable this GUI and switch back to the console-style UI ?"); 124 | 125 | dialog->addButton("Yes", [&](brls::View* view) { 126 | this->getConfig().useGui = false; 127 | _owner_->getGuiModManager().getGameBrowser().getConfigHandler().dumpConfigToFile(); 128 | brls::Application::quit(); 129 | }); 130 | dialog->addButton("No", [dialog](brls::View* view) { 131 | dialog->close(); 132 | }); 133 | 134 | dialog->setCancelable(true); 135 | dialog->open(); 136 | 137 | return true; 138 | }); 139 | this->addView(itemUseUI); 140 | 141 | } 142 | 143 | const ConfigHolder& TabGeneralSettings::getConfig() const{ 144 | return _owner_->getGuiModManager().getGameBrowser().getConfigHandler().getConfig(); 145 | } 146 | ConfigHolder& TabGeneralSettings::getConfig(){ 147 | return _owner_->getGuiModManager().getGameBrowser().getConfigHandler().getConfig(); 148 | } 149 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Building ModBrowser lib 2 | 3 | 4 | 5 | 6 | set( SRC_FILES 7 | ${CMAKE_CURRENT_SOURCE_DIR}/src/FrameModBrowser.cpp 8 | ${CMAKE_CURRENT_SOURCE_DIR}/src/TabModBrowser.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/src/TabModOptions.cpp 10 | ${CMAKE_CURRENT_SOURCE_DIR}/src/TabModPlugins.cpp 11 | ${CMAKE_CURRENT_SOURCE_DIR}/src/TabModPresets.cpp 12 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ThumbnailPresetEditor.cpp 13 | ) 14 | 15 | 16 | 17 | 18 | add_library( FrameModBrowser STATIC ${SRC_FILES} ) 19 | install( TARGETS FrameModBrowser DESTINATION lib ) 20 | 21 | target_include_directories( FrameModBrowser PUBLIC 22 | ${CMAKE_CURRENT_SOURCE_DIR}/include 23 | ) 24 | 25 | target_link_libraries( FrameModBrowser PUBLIC 26 | Borealis 27 | CoreExtension 28 | switch::libnx 29 | -L/opt/devkitpro/portlibs/switch/lib 30 | -L/opt/devkitpro/libnx/lib 31 | ${ZLIB_LIBRARIES} 32 | ${FREETYPE_LIBRARIES} 33 | -lglfw3 -lEGL -lglad -lglapi -ldrm_nouveau -lm -lnx 34 | ) 35 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/include/FrameModBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_FRAMEMODBROWSER_H 6 | #define SIMPLEMODMANAGER_FRAMEMODBROWSER_H 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "GuiModManager.h" 14 | #include "GameBrowser.h" 15 | 16 | #include 17 | 18 | #include "string" 19 | 20 | 21 | class FrameModBrowser : public brls::TabFrame { 22 | 23 | public: 24 | explicit FrameModBrowser(GuiModManager* guiModManagerPtr_); 25 | bool onCancel() override; 26 | 27 | uint8_t *getIcon(); 28 | std::string getTitleId(); 29 | TabModBrowser* getTabModBrowser(){ return _tabModBrowser_; } 30 | TabModPresets* getTabModPresets(){ return _tabModPresets_; } 31 | 32 | 33 | [[nodiscard]] const ConfigHolder& getConfig() const{ return _guiModManagerPtr_->getGameBrowser().getConfigHandler().getConfig(); } 34 | [[nodiscard]] const GuiModManager& getGuiModManager() const{ return *_guiModManagerPtr_; } 35 | [[nodiscard]] const GameBrowser& getGameBrowser() const{ return _guiModManagerPtr_->getGameBrowser(); } 36 | 37 | GuiModManager& getGuiModManager() { return *_guiModManagerPtr_; } 38 | GameBrowser& getGameBrowser(){ return _guiModManagerPtr_->getGameBrowser(); } 39 | 40 | 41 | private: 42 | GuiModManager* _guiModManagerPtr_{}; 43 | 44 | // memory handled by brls 45 | TabModBrowser* _tabModBrowser_{nullptr}; 46 | TabModOptions* _tabModOptions_{nullptr}; 47 | TabModPresets* _tabModPresets_{nullptr}; 48 | TabModPlugins* _tabModPlugins_{nullptr}; 49 | 50 | uint8_t* _icon_{nullptr}; 51 | std::string _titleId_{}; 52 | 53 | }; 54 | 55 | 56 | #endif //SIMPLEMODMANAGER_FRAMEMODBROWSER_H 57 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/include/TabModBrowser.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_TABMODBROWSER_H 6 | #define SIMPLEMODMANAGER_TABMODBROWSER_H 7 | 8 | 9 | #include 10 | #include "ModManager.h" 11 | 12 | #include 13 | 14 | #include "map" 15 | #include "string" 16 | 17 | 18 | struct ModItem; 19 | class FrameModBrowser; 20 | 21 | class TabModBrowser : public brls::List { 22 | 23 | public: 24 | explicit TabModBrowser(FrameModBrowser* owner_); 25 | 26 | void updateDisplayedModsStatus(); 27 | 28 | void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override; 29 | 30 | [[nodiscard]] const ModManager& getModManager() const; 31 | 32 | private: 33 | FrameModBrowser* _owner_{nullptr}; 34 | std::vector _modItemList_{}; 35 | }; 36 | 37 | 38 | struct ModItem{ 39 | int modIndex{-1}; 40 | 41 | // memory is handled by brls -> could be lost in the wild but handy to keep somewhere 42 | brls::ListItem* item{nullptr}; // deleted in BoxLayout::~BoxLayout() 43 | }; 44 | 45 | 46 | #endif //SIMPLEMODMANAGER_TABMODBROWSER_H 47 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/include/TabModOptions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 22/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_TABMODOPTIONS_H 6 | #define SIMPLEMODMANAGER_TABMODOPTIONS_H 7 | 8 | #include 9 | 10 | #include "ModManager.h" 11 | 12 | #include 13 | 14 | 15 | class FrameModBrowser; 16 | 17 | class TabModOptions : public brls::List { 18 | 19 | public: 20 | explicit TabModOptions(FrameModBrowser* owner_); 21 | 22 | [[nodiscard]] const ModManager& getModManager() const; 23 | ModManager& getModManager(); 24 | 25 | void initialize(); 26 | 27 | void buildFolderInstallPresetItem(); 28 | void buildResetModsCacheItem(); 29 | void buildDisableAllMods(); 30 | void buildGameIdentificationItem(); 31 | 32 | void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override; 33 | 34 | private: 35 | FrameModBrowser* _owner_{nullptr}; 36 | 37 | // memory handled by brls 38 | brls::ListItem* _itemConfigPreset_{nullptr}; 39 | brls::ListItem* _itemResetModsCache_{nullptr}; 40 | brls::ListItem* _itemDisableAllMods_{nullptr}; 41 | brls::ListItem* _itemGameIdentification_{nullptr}; 42 | 43 | }; 44 | 45 | 46 | #endif //SIMPLEMODMANAGER_TABMODOPTIONS_H 47 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/include/TabModPlugins.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLEMODMANAGER_TABMODPLUGINS_H 2 | #define SIMPLEMODMANAGER_TABMODPLUGINS_H 3 | 4 | #include 5 | 6 | 7 | class FrameModBrowser; 8 | 9 | class TabModPlugins : public brls::List 10 | { 11 | 12 | public: 13 | explicit TabModPlugins(FrameModBrowser* owner_); 14 | 15 | void draw(NVGcontext *vg, int x, int y, unsigned width, unsigned height, brls::Style *style, brls::FrameContext *ctx) override; 16 | 17 | std::string remove_extension(const std::string &filename); 18 | std::string get_extension(const std::string &filename); 19 | brls::Image *load_image_cache(const std::string& filename); 20 | 21 | std::string base64_encode(const std::string &in); 22 | std::string base64_decode(const std::string &in); 23 | 24 | private: 25 | FrameModBrowser* _owner_; 26 | 27 | brls::Dialog *dialog; 28 | std::map _modsListItems_; 29 | int frameCounter; 30 | 31 | }; 32 | extern std::map cached_thumbs; 33 | 34 | #endif //SIMPLEMODMANAGER_TABMODPLUGINS_H -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/include/TabModPresets.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 22/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_TABMODPRESETS_H 6 | #define SIMPLEMODMANAGER_TABMODPRESETS_H 7 | 8 | #include 9 | 10 | #include "vector" 11 | 12 | class FrameModBrowser; 13 | 14 | class TabModPresets : public brls::List { 15 | 16 | public: 17 | explicit TabModPresets(FrameModBrowser* owner_) : _owner_(owner_) { } 18 | 19 | void setTriggerUpdateItem(bool triggerUpdateItem){ _triggerUpdateItem_ = triggerUpdateItem; } 20 | 21 | void draw(NVGcontext *vg, int x, int y, unsigned width, unsigned height, brls::Style *style, brls::FrameContext *ctx) override; 22 | 23 | void assignButtons(brls::ListItem *item, bool isPreset_); 24 | 25 | 26 | protected: 27 | void updatePresetItems(); 28 | 29 | private: 30 | FrameModBrowser* _owner_{nullptr}; 31 | 32 | bool _triggerUpdateItem_{true}; 33 | 34 | // memory handled by brls 35 | brls::ListItem* _itemNewCreatePreset_{nullptr}; 36 | std::vector _itemList_{}; 37 | 38 | }; 39 | 40 | 41 | #endif //SIMPLEMODMANAGER_TABMODPRESETS_H 42 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/include/ThumbnailPresetEditor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 30/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_THUMBNAILPRESETEDITOR_H 6 | #define SIMPLEMODMANAGER_THUMBNAILPRESETEDITOR_H 7 | 8 | #include "ModsPresetHandler.h" 9 | 10 | #include 11 | 12 | #include "string" 13 | #include "vector" 14 | 15 | 16 | class FrameModBrowser; 17 | 18 | class ThumbnailPresetEditor : public brls::ThumbnailFrame { 19 | 20 | public: 21 | explicit ThumbnailPresetEditor(FrameModBrowser* owner_, const std::string& presetName_ = ""); 22 | 23 | void updateTags(); 24 | void save(); 25 | void autoAssignPresetName(); 26 | 27 | void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override; 28 | 29 | private: 30 | FrameModBrowser* _owner_{nullptr}; 31 | 32 | PresetData _bufferPreset_; 33 | 34 | // std::string _presetName_{"new-preset"}; 35 | // std::vector _selectedModsList_; 36 | 37 | std::vector _availableModItemList_; 38 | 39 | 40 | }; 41 | 42 | 43 | #endif //SIMPLEMODMANAGER_THUMBNAILPRESETEDITOR_H 44 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/src/FrameModBrowser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #include "FrameModBrowser.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "GenericToolbox.Switch.h" 13 | #include "Logger.h" 14 | 15 | 16 | LoggerInit([]{ 17 | Logger::setUserHeaderStr("[FrameModBrowser]"); 18 | }); 19 | 20 | 21 | FrameModBrowser::FrameModBrowser(GuiModManager* guiModManagerPtr_) : _guiModManagerPtr_(guiModManagerPtr_) { 22 | 23 | // fetch game title 24 | this->setTitle( getGameBrowser().getModManager().getGameName() ); 25 | 26 | std::string gamePath = getGameBrowser().getModManager().getGameFolderPath(); 27 | 28 | _titleId_ = GenericToolbox::Switch::Utils::lookForTidInSubFolders( gamePath, 5); 29 | _icon_ = GenericToolbox::Switch::Utils::getIconFromTitleId( _titleId_ ); 30 | if(_icon_ != nullptr){ this->setIcon(_icon_, 0x20000); } 31 | else{ this->setIcon("romfs:/images/icon_corner.png"); } 32 | 33 | this->setFooterText("SimpleModManager"); 34 | 35 | 36 | if( not getGameBrowser().getModManager().getModList().empty() ){ 37 | 38 | auto* parametersTabList = new brls::List(); 39 | 40 | auto* presetParameter = new brls::ListItem("Config preset", "", ""); 41 | presetParameter->setValue( getGameBrowser().getConfigHandler().getConfig().getCurrentPresetName() ); 42 | parametersTabList->addView(presetParameter); 43 | 44 | _tabModBrowser_ = new TabModBrowser( this ); 45 | _tabModPresets_ = new TabModPresets( this ); 46 | _tabModOptions_ = new TabModOptions( this ); 47 | _tabModPlugins_ = new TabModPlugins( this ); 48 | 49 | _tabModOptions_->initialize(); 50 | 51 | this->addTab("Mod Browser", _tabModBrowser_); 52 | this->addSeparator(); 53 | this->addTab("Mod Presets", _tabModPresets_); 54 | this->addTab("Options", _tabModOptions_); 55 | this->addTab("Plugins", _tabModPlugins_); 56 | 57 | } 58 | else{ 59 | auto* list = new brls::List(); 60 | LogError("Can't open: %s", gamePath.c_str()); 61 | auto* item = new brls::ListItem("Error: Can't open " + gamePath , "", ""); 62 | list->addView(item); 63 | this->addTab("Mod Browser", list); 64 | } 65 | 66 | } 67 | bool FrameModBrowser::onCancel() { 68 | 69 | // Go back to sidebar 70 | auto* lastFocus = brls::Application::getCurrentFocus(); 71 | brls::Application::onGamepadButtonPressed(GLFW_GAMEPAD_BUTTON_DPAD_LEFT, false); 72 | 73 | // If the sidebar was already there, the focus has not changed 74 | if(lastFocus == brls::Application::getCurrentFocus()){ 75 | LogInfo("Back on games screen..."); 76 | brls::Application::popView(brls::ViewAnimation::SLIDE_RIGHT); 77 | } 78 | return true; 79 | 80 | } 81 | 82 | uint8_t *FrameModBrowser::getIcon() { 83 | return _icon_; 84 | } 85 | std::string FrameModBrowser::getTitleId() { 86 | return _titleId_; 87 | } 88 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/src/TabModBrowser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | #include "TabModBrowser.h" 6 | 7 | #include "FrameModBrowser.h" 8 | 9 | 10 | #include "GenericToolbox.Macro.h" 11 | #include "Logger.h" 12 | 13 | 14 | #include 15 | 16 | 17 | LoggerInit([]{ 18 | Logger::setUserHeaderStr("[TabModBrowser]"); 19 | }); 20 | 21 | 22 | TabModBrowser::TabModBrowser(FrameModBrowser* owner_) : _owner_(owner_) { 23 | 24 | // Fetch the available mods 25 | auto modList = this->getModManager().getModList(); 26 | 27 | if( modList.empty() ){ 28 | LogInfo << "No mod found." << std::endl; 29 | 30 | _modItemList_.emplace_back(); 31 | _modItemList_.back().item = new brls::ListItem( 32 | "No mods have been found in " + this->getModManager().getGameFolderPath(), 33 | "There you need to put your mods such as: .//" 34 | ); 35 | _modItemList_.back().item->show([](){}, false ); 36 | } 37 | else{ 38 | LogInfo << "Adding " << modList.size() << " mods..." << std::endl; 39 | 40 | _modItemList_.reserve(modList.size()); 41 | for( auto& mod : modList ) { 42 | LogScopeIndent; 43 | LogInfo << "Adding mod: \"" << mod.modName << "\"" << std::endl; 44 | 45 | // memory allocation 46 | auto* item = new brls::ListItem(mod.modName, "", ""); 47 | 48 | // initialization 49 | item->getClickEvent()->subscribe([&, mod](View* view) { 50 | auto* dialog = new brls::Dialog("Do you want to install \"" + mod.modName + "\" ?"); 51 | 52 | dialog->addButton("Yes", [&, mod, dialog](brls::View* view) { 53 | // first, close the dialog box before the apply mod thread starts 54 | dialog->close(); 55 | 56 | // starts the async routine 57 | _owner_->getGuiModManager().startApplyModThread( mod.modName ); 58 | }); 59 | dialog->addButton("No", [dialog](brls::View* view) { dialog->close(); }); 60 | 61 | dialog->setCancelable(true); 62 | dialog->open(); 63 | 64 | return true; 65 | }); 66 | item->updateActionHint(brls::Key::A, "Apply"); 67 | 68 | item->registerAction("Disable", brls::Key::X, [&, mod]{ 69 | auto* dialog = new brls::Dialog("Do you want to disable \"" + mod.modName + "\" ?"); 70 | 71 | dialog->addButton("Yes", [&, dialog, mod](brls::View* view) { 72 | // first, close the dialog box before the async routine starts 73 | dialog->close(); 74 | 75 | // starts the async routine 76 | _owner_->getGuiModManager().startRemoveModThread( mod.modName ); 77 | }); 78 | dialog->addButton("No", [dialog](brls::View* view) { dialog->close(); }); 79 | 80 | dialog->setCancelable(true); 81 | dialog->open(); 82 | return true; 83 | }); 84 | 85 | // create the holding struct 86 | _modItemList_.emplace_back(); 87 | _modItemList_.back().modIndex = int(_modItemList_.size() ) - 1; 88 | _modItemList_.back().item = item; 89 | } 90 | } 91 | 92 | this->updateDisplayedModsStatus(); 93 | 94 | // add to view 95 | for( auto& modItem : _modItemList_ ){ 96 | this->addView( modItem.item ); 97 | } 98 | 99 | } 100 | 101 | void TabModBrowser::draw(NVGcontext *vg, int x, int y, unsigned int width, unsigned int height, brls::Style *style, 102 | brls::FrameContext *ctx) { 103 | 104 | ScrollView::draw(vg, x, y, width, height, style, ctx); 105 | 106 | if( _owner_->getGuiModManager().isTriggerUpdateModsDisplayedStatus() ){ 107 | LogDebug << "Updating mod status display..." << std::endl; 108 | updateDisplayedModsStatus(); 109 | _owner_->getGuiModManager().setTriggerUpdateModsDisplayedStatus( false ); 110 | } 111 | } 112 | 113 | void TabModBrowser::updateDisplayedModsStatus(){ 114 | LogInfo << __METHOD_NAME__ << std::endl; 115 | 116 | auto& modEntryList = _owner_->getGameBrowser().getModManager().getModList(); 117 | LogReturnIf( modEntryList.empty(), "No mod in this folder. Nothing to update." ); 118 | 119 | auto currentPreset = this->getModManager().fetchCurrentPreset().name; 120 | LogInfo << "Will display mod status with install preset: " << currentPreset << std::endl; 121 | 122 | for( size_t iMod = 0 ; iMod < modEntryList.size() ; iMod++ ){ 123 | 124 | // processing tag 125 | _modItemList_[iMod].item->setValue( modEntryList[iMod].getStatus(currentPreset ) ); 126 | double frac = modEntryList[iMod].getStatusFraction(currentPreset); 127 | 128 | NVGcolor color; 129 | // processing color 130 | if ( frac == 0 ){ 131 | // inactive color 132 | color = GenericToolbox::Borealis::grayNvgColor; 133 | } 134 | else if( frac == 1 ){ 135 | // applied color (less saturated green) 136 | color = nvgRGB(88, 195, 169); 137 | } 138 | else{ 139 | // partial color 140 | color = GenericToolbox::Borealis::orangeNvgColor; 141 | } 142 | _modItemList_[iMod].item->setValueActiveColor( color ); 143 | } 144 | } 145 | 146 | const ModManager& TabModBrowser::getModManager() const{ 147 | return _owner_->getGameBrowser().getModManager(); 148 | } 149 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/src/TabModOptions.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 22/06/2020. 3 | // 4 | 5 | #include "TabModOptions.h" 6 | #include "FrameModBrowser.h" 7 | 8 | 9 | 10 | 11 | #include "Logger.h" 12 | 13 | LoggerInit([]{ 14 | Logger::setUserHeaderStr("[TabModOptions]"); 15 | }); 16 | 17 | 18 | TabModOptions::TabModOptions(FrameModBrowser* owner_) : _owner_(owner_) { } 19 | 20 | void TabModOptions::buildFolderInstallPresetItem() { 21 | 22 | _itemConfigPreset_ = new brls::ListItem( 23 | "\uE255 Config preset", 24 | "Specify from which install folder mods from this subfolder (game) will be installed.\n", 25 | "" 26 | ); 27 | 28 | _itemConfigPreset_->setValue("Inherited from the main menu"); 29 | if( not this->getModManager().getCurrentPresetName().empty() ){ 30 | _itemConfigPreset_->setValue( this->getModManager().getCurrentPresetName() ); 31 | } 32 | 33 | // On click : show scrolling up menu 34 | _itemConfigPreset_->getClickEvent()->subscribe([this](View* view) { 35 | LogInfo << "Opening config preset selector..." << std::endl; 36 | 37 | // build the choice list + preselection 38 | int preSelection{0}; 39 | std::vector menuList; 40 | menuList.reserve( 1 + this->getModManager().getConfig().presetList.size() ); 41 | menuList.emplace_back( "Inherited from the main menu" ); 42 | int iPreset{-1}; 43 | for( auto& preset: this->getModManager().getConfig().presetList ){ 44 | iPreset++; 45 | menuList.emplace_back( preset.name + " \uE090 \"" + preset.installBaseFolder + "\"" ); 46 | if( preset.name == this->getModManager().getCurrentPresetName() ){ preSelection = 1 + iPreset; } 47 | } 48 | 49 | // function that will set the config preset from the Dropdown menu selection (int result) 50 | brls::ValueSelectedEvent::Callback valueCallback = [this](int result) { 51 | if( result == -1 ){ 52 | LogDebug << "Not selected. Return." << std::endl; 53 | // auto pop view 54 | return; 55 | } 56 | 57 | if( result == 0 ){ 58 | LogDebug << "Same as config selected. Deleting file..." << std::endl; 59 | this->getModManager().setCustomPreset(""); 60 | _itemConfigPreset_->setValue( "Inherited from the main menu" ); 61 | } 62 | else{ 63 | LogDebug << "Selected " << result - 1 << std::endl; 64 | this->getModManager().setCustomPreset( this->getModManager().getConfig().presetList[result - 1].name ); 65 | _itemConfigPreset_->setValue( this->getModManager().getCurrentPresetName() ); 66 | } 67 | 68 | _owner_->getGuiModManager().checkAllMods( true ); 69 | brls::Application::popView(); 70 | return; 71 | }; // Callback sequence 72 | 73 | brls::Dropdown::open( 74 | "Please select the config preset you want for this folder", 75 | menuList, valueCallback, 76 | preSelection, 77 | true 78 | ); 79 | 80 | }); 81 | 82 | } 83 | void TabModOptions::buildResetModsCacheItem() { 84 | 85 | _itemResetModsCache_ = new brls::ListItem( 86 | "\uE877 Recheck all mods", 87 | "\tThis option resets all mods cache status and recheck if each files is properly applied.\n" 88 | "\tWhen files where managed without SimpleModManager, the displayed mod status can be wrong. " 89 | "This typically happens when you modified some mod files, or other programs override some of the applied files.", 90 | "" 91 | ); 92 | 93 | _itemResetModsCache_->getClickEvent()->subscribe([this](View* view){ 94 | 95 | auto* dialog = new brls::Dialog("Do you want to reset mods cache status and recheck all mod files ?"); 96 | 97 | dialog->addButton("Yes", [&, dialog](brls::View* view) { 98 | // first, close the dialog box before the async routine starts 99 | dialog->close(); 100 | 101 | // starts the async routine 102 | _owner_->getGuiModManager().startCheckAllModsThread(); 103 | }); 104 | dialog->addButton("No", [dialog](brls::View* view) { 105 | dialog->close(); 106 | }); 107 | 108 | dialog->setCancelable(true); 109 | dialog->open(); 110 | 111 | }); 112 | 113 | } 114 | void TabModOptions::buildDisableAllMods() { 115 | 116 | _itemDisableAllMods_ = new brls::ListItem( 117 | "\uE872 Disable all installed mods", 118 | "This option will remove all installed mods files.\n" 119 | "Note: to be deleted, installed mod files need to be identical to the one present in this folder.", 120 | "" 121 | ); 122 | 123 | _itemDisableAllMods_->getClickEvent()->subscribe([this](View* view){ 124 | 125 | auto* dialog = new brls::Dialog("Do you want to disable all installed mods ?"); 126 | 127 | dialog->addButton("Yes", [&, dialog](brls::View* view) { 128 | // first, close the dialog box before the async routine starts 129 | dialog->close(); 130 | 131 | // starts the async routine 132 | _owner_->getGuiModManager().startRemoveAllModsThread(); 133 | }); 134 | dialog->addButton("No", [dialog](brls::View* view) { 135 | dialog->close(); 136 | }); 137 | 138 | dialog->setCancelable(true); 139 | dialog->open(); 140 | 141 | }); 142 | 143 | } 144 | void TabModOptions::buildGameIdentificationItem(){ 145 | 146 | _itemGameIdentification_ = new brls::ListItem( 147 | "Associated TitleID", 148 | "", 149 | "Current value: " 150 | ); 151 | 152 | if( _owner_->getIcon() != nullptr ){ 153 | _itemGameIdentification_->setThumbnail( _owner_->getIcon(), 0x20000 ); 154 | _itemGameIdentification_->setSubLabel( 155 | _itemGameIdentification_->getSubLabel() + _owner_->getTitleId() 156 | ); 157 | } 158 | else{ 159 | _itemGameIdentification_->setSubLabel( 160 | _itemGameIdentification_->getSubLabel() + "Not found." 161 | ); 162 | } 163 | 164 | } 165 | 166 | void TabModOptions::initialize() { 167 | 168 | this->buildFolderInstallPresetItem(); 169 | this->buildResetModsCacheItem(); 170 | this->buildDisableAllMods(); 171 | this->buildGameIdentificationItem(); 172 | 173 | // finally add to view 174 | this->addView(_itemResetModsCache_); 175 | this->addView(_itemConfigPreset_); 176 | this->addView(_itemDisableAllMods_); 177 | this->addView(_itemGameIdentification_); 178 | 179 | } 180 | 181 | void TabModOptions::draw(NVGcontext *vg, int x, int y, unsigned int width, unsigned int height, brls::Style *style, 182 | brls::FrameContext *ctx) { 183 | ScrollView::draw(vg, x, y, width, height, style, ctx); 184 | } 185 | 186 | const ModManager &TabModOptions::getModManager() const { 187 | return _owner_->getGameBrowser().getModManager(); 188 | } 189 | ModManager &TabModOptions::getModManager() { 190 | return _owner_->getGameBrowser().getModManager(); 191 | } 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/src/TabModPlugins.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 21/06/2020. 3 | // 4 | 5 | 6 | #include "TabModPlugins.h" 7 | 8 | #include "FrameModBrowser.h" 9 | 10 | #include "Logger.h" 11 | 12 | 13 | #include 14 | 15 | #include 16 | #include "string" 17 | 18 | 19 | LoggerInit([]{ 20 | Logger::setUserHeaderStr("[TabModPlugins]"); 21 | }); 22 | 23 | 24 | std::map cached_thumbs; 25 | TabModPlugins::TabModPlugins(FrameModBrowser* owner_) : _owner_(owner_) { 26 | 27 | this->frameCounter = -1; 28 | 29 | auto& modManager{_owner_->getGuiModManager().getGameBrowser().getModManager()}; 30 | 31 | // Set the list 32 | auto plugin_nros_list = GenericToolbox::ls( 33 | GenericToolbox::joinPath(modManager.getGameFolderPath(), ".plugins") 34 | ); 35 | plugin_nros_list.erase(std::remove_if(plugin_nros_list.begin(), plugin_nros_list.end(), [this](std::string &x) { 36 | return get_extension(x) != ".smm"; // put your condition here 37 | }), 38 | plugin_nros_list.end()); 39 | for (int i_nro = 0; i_nro < int(plugin_nros_list.size()); i_nro++) 40 | { 41 | std::string selected_plugin = remove_extension(plugin_nros_list[i_nro]); 42 | std::string selected_plugin_path = 43 | modManager.getGameFolderPath() + "/.plugins/" + plugin_nros_list[i_nro]; 44 | std::string selected_plugin_author; 45 | std::string selected_plugin_version; 46 | LogDebug("Adding plugin: %s", selected_plugin.c_str()); 47 | FILE *file = fopen(selected_plugin_path.c_str(), "rb"); 48 | if (file) 49 | { 50 | char name[513]; 51 | char author[257]; 52 | char version[17]; 53 | 54 | fseek(file, sizeof(NroStart), SEEK_SET); 55 | NroHeader header; 56 | fread(&header, sizeof(header), 1, file); 57 | fseek(file, header.size, SEEK_SET); 58 | NroAssetHeader asset_header; 59 | fread(&asset_header, sizeof(asset_header), 1, file); 60 | 61 | NacpStruct *nacp = (NacpStruct *)malloc(sizeof(NacpStruct)); 62 | if (nacp != NULL) 63 | { 64 | fseek(file, header.size + asset_header.nacp.offset, SEEK_SET); 65 | fread(nacp, sizeof(NacpStruct), 1, file); 66 | 67 | NacpLanguageEntry *langentry = NULL; 68 | Result rc = nacpGetLanguageEntry(nacp, &langentry); 69 | if (R_SUCCEEDED(rc) && langentry != NULL) 70 | { 71 | strncpy(name, langentry->name, sizeof(name) - 1); 72 | strncpy(author, langentry->author, sizeof(author) - 1); 73 | } 74 | strncpy(version, nacp->display_version, sizeof(version) - 1); 75 | 76 | free(nacp); 77 | nacp = NULL; 78 | } 79 | 80 | selected_plugin_author = author; 81 | selected_plugin_version = version; 82 | 83 | fclose(file); 84 | } 85 | auto *item = new brls::ListItem(selected_plugin, "", selected_plugin_author); 86 | item->setValue(selected_plugin_version); 87 | item->getClickEvent()->subscribe([this, selected_plugin, selected_plugin_path](View *view) { 88 | LogDebug << "selected_plugin_path = " << selected_plugin_path << std::endl; 89 | auto *dialog = new brls::Dialog("Do you want to start \"" + selected_plugin + "\" ?"); 90 | 91 | dialog->addButton("Yes", [selected_plugin_path, dialog](brls::View *view) { 92 | envSetNextLoad(selected_plugin_path.c_str(), selected_plugin_path.c_str()); 93 | brls::Application::quit(); 94 | dialog->close(); 95 | }); 96 | dialog->addButton("No", [dialog](brls::View *view) { 97 | dialog->close(); 98 | }); 99 | 100 | dialog->setCancelable(true); 101 | dialog->open(); 102 | 103 | return true; 104 | }); 105 | item->updateActionHint(brls::Key::A, "Start"); 106 | item->setValueActiveColor(nvgRGB(80, 128, 80)); 107 | brls::Image *icon = load_image_cache(selected_plugin_path); 108 | item->setThumbnail(icon); 109 | 110 | this->addView(item); 111 | _modsListItems_[selected_plugin] = item; 112 | } 113 | 114 | if (plugin_nros_list.empty()) 115 | { 116 | 117 | auto *emptyListLabel = new brls::ListItem( 118 | "No plugins have been found in " + modManager.getGameFolderPath() + "/.plugins", 119 | "There you need to put your plugins such as: ./.smm"); 120 | emptyListLabel->show([]() {}, false); 121 | this->addView(emptyListLabel); 122 | } 123 | } 124 | 125 | std::string TabModPlugins::remove_extension(const std::string &filename) 126 | { 127 | size_t lastdot = filename.find_last_of("."); 128 | if (lastdot == std::string::npos) 129 | return filename; 130 | return filename.substr(0, lastdot); 131 | } 132 | std::string TabModPlugins::get_extension(const std::string &filename) 133 | { 134 | size_t lastdot = filename.find_last_of("."); 135 | if (lastdot == std::string::npos) 136 | return filename; 137 | return filename.substr(lastdot); 138 | } 139 | brls::Image *TabModPlugins::load_image_cache(const std::string& filename) { 140 | LogDebug << "Requesting icon: " << filename << std::endl; 141 | 142 | brls::Image *image = nullptr; 143 | 144 | std::string filename_enc = base64_encode(filename); 145 | 146 | std::map::iterator it; 147 | 148 | it = cached_thumbs.find(filename_enc); 149 | // found 150 | if (it != cached_thumbs.end()) 151 | { 152 | LogDebug << "Icon Already Cached: " << filename << std::endl; 153 | image = cached_thumbs[filename_enc]; 154 | } 155 | else 156 | // not found 157 | { 158 | LogDebug << "Icon Not Yet Cached: " << filename << std::endl; 159 | 160 | FILE *file = fopen(filename.c_str(), "rb"); 161 | if (file) 162 | { 163 | fseek(file, sizeof(NroStart), SEEK_SET); 164 | NroHeader header; 165 | fread(&header, sizeof(header), 1, file); 166 | fseek(file, header.size, SEEK_SET); 167 | NroAssetHeader asset_header; 168 | fread(&asset_header, sizeof(asset_header), 1, file); 169 | 170 | size_t icon_size = asset_header.icon.size; 171 | uint8_t *icon = (uint8_t *)malloc(icon_size); 172 | if (icon != NULL && icon_size != 0) 173 | { 174 | memset(icon, 0, icon_size); 175 | fseek(file, header.size + asset_header.icon.offset, SEEK_SET); 176 | fread(icon, icon_size, 1, file); 177 | 178 | LogDebug << "Caching New Icon: " << filename << std::endl;; 179 | 180 | image = new brls::Image(icon, icon_size); 181 | 182 | cached_thumbs[filename_enc] = image; 183 | } 184 | else 185 | image = new brls::Image("romfs:/images/unknown.png"); 186 | 187 | free(icon); 188 | icon = nullptr; 189 | } 190 | else 191 | { 192 | LogDebug << "Using Unknown Icon For: " << filename << std::endl; 193 | image = new brls::Image("romfs:/images/unknown.png"); 194 | } 195 | } 196 | 197 | return image; 198 | } 199 | std::string TabModPlugins::base64_encode(const std::string &in) 200 | { 201 | std::string out; 202 | 203 | int val = 0, valb = -6; 204 | for (unsigned char c : in) 205 | { 206 | val = (val << 8) + c; 207 | valb += 8; 208 | while (valb >= 0) 209 | { 210 | out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(val >> valb) & 0x3F]); 211 | valb -= 6; 212 | } 213 | } 214 | if (valb > -6) 215 | out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((val << 8) >> (valb + 8)) & 0x3F]); 216 | while (out.size() % 4) 217 | out.push_back('='); 218 | return out; 219 | } 220 | std::string TabModPlugins::base64_decode(const std::string &in) 221 | { 222 | std::string out; 223 | 224 | std::vector T(256, -1); 225 | for (int i = 0; i < 64; i++) 226 | T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; 227 | 228 | int val = 0, valb = -8; 229 | for (unsigned char c : in) 230 | { 231 | if (T[c] == -1) 232 | break; 233 | val = (val << 6) + T[c]; 234 | valb += 6; 235 | if (valb >= 0) 236 | { 237 | out.push_back(char((val >> valb) & 0xFF)); 238 | valb -= 8; 239 | } 240 | } 241 | return out; 242 | } 243 | void TabModPlugins::draw(NVGcontext *vg, int x, int y, unsigned int width, unsigned int height, brls::Style *style, 244 | brls::FrameContext *ctx) 245 | { 246 | 247 | ScrollView::draw(vg, x, y, width, height, style, ctx); 248 | } 249 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/src/TabModPresets.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 22/06/2020. 3 | // 4 | 5 | #include "TabModPresets.h" 6 | 7 | #include "FrameModBrowser.h" 8 | 9 | #include 10 | 11 | #include 12 | #include "Logger.h" 13 | 14 | LoggerInit([]{ 15 | Logger::setUserHeaderStr("[TabModPresets]"); 16 | }); 17 | 18 | 19 | void TabModPresets::draw(NVGcontext *vg, int x, int y, unsigned width, unsigned height, brls::Style *style, brls::FrameContext *ctx) { 20 | 21 | if( _triggerUpdateItem_ ){ 22 | this->updatePresetItems(); 23 | } 24 | 25 | this->brls::List::draw( vg,x,y,width,height,style,ctx ); 26 | } 27 | 28 | void TabModPresets::assignButtons(brls::ListItem *item, bool isPreset_) { 29 | 30 | if( item == nullptr ){ 31 | LogError << "Can't assign buttons to nullptr item" << std::endl; 32 | return; 33 | } 34 | 35 | // reset 36 | item->clearActions(); 37 | // item->getClickEvent()->subscribe([](brls::View* view){}); 38 | // item->registerAction("", brls::Key::A, []{ return true; }); 39 | // item->registerAction("", brls::Key::X, []{ return true; }); 40 | // item->registerAction("", brls::Key::Y, []{ return true; }); 41 | // item->updateActionHint(brls::Key::A, ""); 42 | // item->updateActionHint(brls::Key::X, ""); 43 | // item->updateActionHint(brls::Key::Y, ""); 44 | // item->setActionAvailable(brls::Key::A, false); 45 | // item->setActionAvailable(brls::Key::X, false); 46 | // item->setActionAvailable(brls::Key::Y, false); 47 | 48 | auto* ownerPtr{_owner_}; 49 | 50 | if( isPreset_ ){ 51 | item->registerAction("Apply", brls::Key::A, [item, ownerPtr]{ 52 | auto* dialog = new brls::Dialog("Do you want disable all installed mods and apply the preset \"" + item->getLabel() + "\" ?"); 53 | 54 | dialog->addButton("Yes", [&, dialog, item](brls::View* view) { 55 | // first, close the dialog box before the apply mod thread starts 56 | dialog->close(); 57 | 58 | // starts the async routine 59 | ownerPtr->getGuiModManager().startApplyModPresetThread(item->getLabel()); 60 | }); 61 | dialog->addButton("No", [dialog](brls::View* view) { 62 | dialog->close(); 63 | }); 64 | 65 | dialog->setCancelable(true); 66 | dialog->open(); 67 | return true; 68 | }); 69 | item->registerAction("Remove", brls::Key::X, [this, ownerPtr, item]{ 70 | 71 | auto* dialog = new brls::Dialog("Do you want to delete the preset \"" + item->getLabel() + "\" ?"); 72 | 73 | dialog->addButton("Yes", [this, ownerPtr, item, dialog](brls::View* view) { 74 | ownerPtr->getGameBrowser().getModPresetHandler().deletePreset( item->getLabel() ); 75 | dialog->close(); 76 | this->setTriggerUpdateItem( true ); 77 | }); 78 | dialog->addButton("No", [dialog](brls::View* view) { dialog->close(); }); 79 | 80 | dialog->setCancelable(true); 81 | dialog->open(); 82 | return true; 83 | 84 | }); 85 | item->registerAction("Edit", brls::Key::Y, [ownerPtr, item]{ 86 | // open editor 87 | auto* editor = new ThumbnailPresetEditor( ownerPtr, item->getLabel() ); 88 | 89 | auto* icon = ownerPtr->getIcon(); 90 | if(icon != nullptr){ 91 | brls::PopupFrame::open( 92 | "Edit preset", icon, 0x20000, editor, 93 | "Please select the mods you want to install", 94 | "The mods will be applied in the same order." 95 | ); 96 | } 97 | else{ 98 | brls::PopupFrame::open( 99 | "Edit preset", editor, 100 | "Please select the mods you want to install", 101 | "The mods will be applied in the same order." 102 | ); 103 | } 104 | 105 | return true; 106 | }); 107 | 108 | item->updateActionHint(brls::Key::A, "Apply"); 109 | item->updateActionHint(brls::Key::X, "Remove"); 110 | item->updateActionHint(brls::Key::Y, "Edit"); 111 | 112 | item->setActionAvailable(brls::Key::A, true); 113 | item->setActionAvailable(brls::Key::X, true); 114 | item->setActionAvailable(brls::Key::Y, true); 115 | } 116 | else { 117 | // new preset 118 | item->registerAction("Create", brls::Key::A, [ownerPtr]{ 119 | // create new preset 120 | auto* editor = new ThumbnailPresetEditor( ownerPtr ); 121 | 122 | auto* icon = ownerPtr->getIcon(); 123 | if(icon != nullptr){ 124 | brls::PopupFrame::open( 125 | "New preset", icon, 0x20000, editor, 126 | "Please select the mods you want to install", 127 | "The mods will be applied in the same order." 128 | ); 129 | } 130 | else{ 131 | brls::PopupFrame::open( 132 | "New preset", editor, 133 | "Please select the mods you want to install", 134 | "The mods will be applied in the same order." 135 | ); 136 | } 137 | 138 | return true; 139 | }); 140 | item->updateActionHint(brls::Key::A, "Create"); 141 | 142 | item->setActionAvailable(brls::Key::A, true); 143 | } 144 | 145 | } 146 | 147 | void TabModPresets::updatePresetItems() { 148 | LogInfo << "Updating displayed preset items" << std::endl; 149 | 150 | _triggerUpdateItem_ = false; 151 | 152 | // adding presets to the list 153 | auto presetList = _owner_->getGameBrowser().getModPresetHandler().getPresetList(); 154 | 155 | // make sure it is the right size 156 | _itemList_.reserve( presetList.size() + 1 ); 157 | 158 | // keep the index out of the for loop 159 | size_t iPreset = 0; 160 | 161 | LogInfo << "Adding " << presetList.size() << " presets..." << std::endl; 162 | for( ; iPreset < presetList.size() ; iPreset++ ){ 163 | LogScopeIndent; 164 | LogInfo << "Adding mod preset: " << presetList[iPreset].name << std::endl; 165 | if( iPreset+1 > _itemList_.size() ){ 166 | // missing slot 167 | _itemList_.emplace_back( new brls::ListItem( presetList[iPreset].name ) ); 168 | 169 | // add it once 170 | this->addView( _itemList_[ iPreset ] ); 171 | } 172 | this->assignButtons( _itemList_[ iPreset ], true ); 173 | _itemList_[ iPreset ]->setLabel( presetList[iPreset].name ); 174 | _itemList_[ iPreset ]->setValue( std::to_string(presetList[iPreset].modList.size()) + " mods in this set" ); 175 | if( _itemList_[iPreset]->isCollapsed() ) _itemList_[iPreset]->expand(); 176 | } 177 | 178 | // need one more slot for create new preset 179 | if( iPreset+1 > _itemList_.size() ){ 180 | LogInfo << "(re)-Creating add button" << std::endl; 181 | _itemList_.emplace_back( new brls::ListItem( "\uE402 Create a new mod preset" ) ); 182 | 183 | this->addView( _itemList_[ iPreset ] ); 184 | } 185 | this->assignButtons( _itemList_[ iPreset ], false ); 186 | if( _itemList_[iPreset]->isCollapsed() ) _itemList_[iPreset]->expand(); 187 | _itemList_[ iPreset ]->setLabel( "\uE402 Create a new mod preset" ); 188 | _itemList_[ iPreset ]->setValue( "" ); 189 | iPreset++; 190 | 191 | // hide the slots that were already created 192 | for( ; iPreset < _itemList_.size() ; iPreset++ ){ 193 | _itemList_[iPreset]->collapse(); 194 | } 195 | 196 | LogTrace << GET_VAR_NAME_VALUE(getViewsCount()) << std::endl; 197 | LogTrace << GET_VAR_NAME_VALUE(_itemList_.size()) << std::endl; 198 | 199 | // focus is lost as the list has been cleared 200 | // brls::Application::giveFocus( this->getDefaultFocus() ); 201 | 202 | // does not work, focus is lost as 203 | // if(brls::Application::getCurrentFocus() != nullptr and brls::Application::getCurrentFocus()->isCollapsed()){ 204 | // brls::Application::getCurrentFocus()->onFocusLost(); 205 | // brls::Application::giveFocus(_itemNewCreatePreset_->getDefaultFocus()); 206 | // } 207 | 208 | brls::Application::unblockInputs(); 209 | LogInfo << "Leaving update..." << std::endl; 210 | } 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /src/ModManagerGui/FrameModBrowser/src/ThumbnailPresetEditor.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Adrien BLANCHET on 30/06/2020. 3 | // 4 | 5 | #include "ThumbnailPresetEditor.h" 6 | #include "FrameModBrowser.h" 7 | 8 | 9 | 10 | #include "GenericToolbox.Vector.h" 11 | #include "GenericToolbox.Switch.h" 12 | 13 | #include 14 | #include 15 | 16 | 17 | 18 | ThumbnailPresetEditor::ThumbnailPresetEditor(FrameModBrowser* owner_, const std::string& presetName_) : _owner_(owner_){ 19 | 20 | // fill the item list 21 | for( auto& availableMod : _owner_->getGameBrowser().getModManager().getModList() ){ 22 | auto* item = new brls::ListItem( availableMod.modName, "", "" ); 23 | 24 | // add mod 25 | item->getClickEvent()->subscribe([this,item](brls::View* view){ 26 | _bufferPreset_.modList.emplace_back( item->getLabel() ); 27 | this->updateTags(); 28 | return true; 29 | }); 30 | 31 | // remove mod 32 | item->registerAction("Remove", brls::Key::X, [this,item]{ 33 | 34 | if( _bufferPreset_.modList.empty() ){ 35 | // nothing to remove 36 | return true; 37 | } 38 | 39 | // loop backward to fetch the first appearing mod to be deleted 40 | for( auto modIterator = _bufferPreset_.modList.rbegin(); modIterator != _bufferPreset_.modList.rend(); ++modIterator ){ 41 | if( *modIterator == item->getLabel() ) { 42 | // We cannot directly erase with it because once we erase an element from a container, 43 | // the iterator to that element and all iterators after that element are invalidated. 44 | _bufferPreset_.modList.erase( std::next(modIterator).base() ); 45 | break; 46 | } 47 | } 48 | 49 | this->updateTags(); 50 | return true; 51 | }); 52 | 53 | // disable + to quit 54 | item->registerAction("", brls::Key::PLUS, []{ return true; }, true); 55 | 56 | // keep the pointer of the list in order to update the tags 57 | _availableModItemList_.emplace_back( item ); 58 | } 59 | 60 | // the list that will appear 61 | auto* modViewList = new brls::List(); 62 | for( auto& availableMod : _availableModItemList_ ){ modViewList->addView( availableMod ); } 63 | this->setContentView( modViewList ); 64 | this->registerAction("", brls::Key::PLUS, []{return true;}, true); 65 | 66 | 67 | int presetIndex{-1}; 68 | if( not presetName_.empty() ){ 69 | presetIndex = GenericToolbox::findElementIndex( 70 | presetName_, _owner_->getGameBrowser().getModPresetHandler().getPresetList(), 71 | [](const PresetData& p ){ return p.name; } 72 | ); 73 | } 74 | 75 | if( presetIndex == -1 ){ 76 | // preset not found, start from scratch 77 | this->autoAssignPresetName(); 78 | } 79 | else{ 80 | // copy existing preset 81 | _bufferPreset_ = _owner_->getGameBrowser().getModPresetHandler().getPresetList()[presetIndex]; 82 | } 83 | 84 | // sidebar and save button 85 | this->getSidebar()->setTitle( _bufferPreset_.name ); 86 | this->getSidebar()->getButton()->getClickEvent()->subscribe([this](brls::View* view){ 87 | this->save(); 88 | 89 | brls::Application::popView(brls::ViewAnimation::FADE); 90 | brls::Application::unblockInputs(); 91 | 92 | return true; 93 | }); 94 | this->getSidebar()->registerAction("", brls::Key::PLUS, []{return true;}, true); 95 | 96 | this->updateTags(); 97 | } 98 | 99 | 100 | 101 | void ThumbnailPresetEditor::updateTags() { 102 | 103 | // reset tags 104 | for( auto & availableMod : _availableModItemList_ ){ availableMod->setValue(""); } 105 | 106 | // set tags 107 | for( size_t iEntry = 0 ; iEntry < _bufferPreset_.modList.size() ; iEntry++ ){ 108 | // loop over selected mods 109 | 110 | for( auto & availableMod : _availableModItemList_ ){ 111 | // loop over available mods 112 | 113 | if( availableMod->getLabel() != _bufferPreset_.modList[iEntry] ){ 114 | // wrong mod, next 115 | continue; 116 | } 117 | 118 | // update the tag 119 | std::stringstream ss; 120 | if( availableMod->getValue().empty() ) ss << "#" << iEntry + 1; 121 | else ss << availableMod->getValue() << " & #" << iEntry + 1; 122 | availableMod->setValue( ss.str() ); 123 | 124 | } 125 | } 126 | 127 | std::stringstream ss; 128 | ss << this->_bufferPreset_.modList.size() << " mods have been selected."; 129 | this->getSidebar()->setSubtitle( ss.str() ); 130 | 131 | } 132 | void ThumbnailPresetEditor::save() { 133 | 134 | // is it an existing preset? 135 | int presetIndex{ 136 | GenericToolbox::findElementIndex( 137 | _bufferPreset_.name, _owner_->getGameBrowser().getModPresetHandler().getPresetList(), 138 | [](const PresetData& p ){ return p.name; } 139 | ) 140 | }; 141 | 142 | // let us edit the name 143 | _bufferPreset_.name = GenericToolbox::Switch::UI::openKeyboardUi( _bufferPreset_.name ); 144 | 145 | // insert into preset list 146 | if( presetIndex != -1 ){ 147 | _owner_->getGameBrowser().getModPresetHandler().getPresetList()[presetIndex] = _bufferPreset_; 148 | } 149 | else{ 150 | _owner_->getGameBrowser().getModPresetHandler().getPresetList().emplace_back( _bufferPreset_ ); 151 | } 152 | 153 | // TODO: Check for conflicts 154 | // showConflictingFiles(_presetName_); 155 | 156 | // save to file 157 | _owner_->getGameBrowser().getModPresetHandler().writeConfigFile(); 158 | _owner_->getGameBrowser().getModPresetHandler().readConfigFile(); 159 | 160 | // trigger backdrop list update 161 | _owner_->getTabModPresets()->setTriggerUpdateItem( true ); 162 | } 163 | void ThumbnailPresetEditor::autoAssignPresetName() { 164 | std::string autoName = "new-preset"; 165 | _bufferPreset_.name = autoName; 166 | int count{0}; 167 | while( GenericToolbox::doesElementIsInVector( 168 | _bufferPreset_.name, _owner_->getGameBrowser().getModPresetHandler().getPresetList(), 169 | [](const PresetData& p ){ return p.name; } 170 | )){ 171 | _bufferPreset_.name = autoName + "-" + std::to_string(count); 172 | count++; 173 | } 174 | } 175 | 176 | void ThumbnailPresetEditor::draw( 177 | NVGcontext *vg, int x, int y, 178 | unsigned int width, unsigned int height, 179 | brls::Style *style, brls::FrameContext *ctx) { 180 | 181 | if ( _bufferPreset_.modList.empty() ){ 182 | // save button should be hidden if nothing is selected 183 | this->getSidebar()->getButton()->setState(brls::ButtonState::DISABLED); 184 | } 185 | else if( this->getSidebar()->getButton()->getState() == brls::ButtonState::DISABLED ){ 186 | // re-enable it 187 | this->getSidebar()->getButton()->setState(brls::ButtonState::ENABLED); 188 | } 189 | 190 | // trigger the default draw 191 | this->ThumbnailFrame::draw(vg, x, y, width, height, style, ctx); 192 | } 193 | 194 | -------------------------------------------------------------------------------- /src/ModManagerOverlay/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Building Overlay lib 3 | # 4 | 5 | #set( SRC_FILES 6 | ## ${CMAKE_CURRENT_SOURCE_DIR}/src/ChangeConfigPresetGui.cpp 7 | ## ${CMAKE_CURRENT_SOURCE_DIR}/src/GameBrowserGui.cpp 8 | ## ${CMAKE_CURRENT_SOURCE_DIR}/src/ModBrowserGui.cpp 9 | # ) 10 | # 11 | # 12 | # 13 | #add_library( ModManagerOverlay STATIC ${SRC_FILES} ) 14 | #target_include_directories( ModManagerOverlay PUBLIC ${SUBMODULES_DIR}/libtesla/include ) 15 | #install( TARGETS ModManagerOverlay DESTINATION lib ) 16 | # 17 | #target_include_directories( ModManagerOverlay PUBLIC 18 | # ${CMAKE_CURRENT_SOURCE_DIR}/include 19 | # ) 20 | # 21 | #target_link_libraries( ModManagerOverlay PUBLIC 22 | # ModManagerCore 23 | # switch::libnx 24 | # -L/opt/devkitpro/portlibs/switch/lib 25 | # -L/opt/devkitpro/libnx/lib 26 | # ${ZLIB_LIBRARIES} 27 | # ${FREETYPE_LIBRARIES} 28 | # # -lglfw3 -lEGL -lglad -lglapi -ldrm_nouveau -lm -lnx 29 | # ) 30 | -------------------------------------------------------------------------------- /src/ModManagerOverlay/include/ChangeConfigPresetGui.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 06/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_CHANGECONFIGPRESETGUI_H 6 | #define SIMPLEMODMANAGER_CHANGECONFIGPRESETGUI_H 7 | 8 | #include 9 | 10 | class ChangeConfigPresetGui : public tsl::Gui { 11 | public: 12 | 13 | explicit ChangeConfigPresetGui(); 14 | 15 | tsl::elm::Element* createUI() override; 16 | 17 | // Called once every frame to handle inputs not handled by other UI elements 18 | bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) override; 19 | 20 | private: 21 | 22 | std::string _question_; 23 | std::vector _answers_; 24 | 25 | }; 26 | 27 | 28 | #endif //SIMPLEMODMANAGER_CHANGECONFIGPRESETGUI_H 29 | -------------------------------------------------------------------------------- /src/ModManagerOverlay/include/ModBrowserGui.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 06/06/2020. 3 | // 4 | 5 | #ifndef SIMPLEMODMANAGER_MODBROWSERGUI_H 6 | #define SIMPLEMODMANAGER_MODBROWSERGUI_H 7 | 8 | #include 9 | 10 | class ModBrowserGui : public tsl::Gui { 11 | public: 12 | 13 | explicit ModBrowserGui(const std::basic_string& current_sub_folder_); 14 | 15 | tsl::elm::Element* createUI() override; 16 | 17 | void fill_item_list(); 18 | void updateModStatusBars(); 19 | 20 | // Called once every frame to handle inputs not handled by other UI elements 21 | void update() override; 22 | bool handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) override; 23 | 24 | void set_trigger_item_list_update(bool trigger_item_list_update_); 25 | 26 | private: 27 | 28 | bool _trigger_item_list_update_; 29 | std::string _current_sub_folder_; 30 | tsl::elm::OverlayFrame* _frame_; 31 | tsl::elm::List* _list_; 32 | 33 | std::map _statusBarMap_; 34 | 35 | }; 36 | 37 | 38 | #endif //SIMPLEMODMANAGER_MODBROWSERGUI_H 39 | -------------------------------------------------------------------------------- /src/ModManagerOverlay/src/ChangeConfigPresetGui.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 06/06/2020. 3 | // 4 | 5 | #include "ChangeConfigPresetGui.h" 6 | 7 | 8 | ChangeConfigPresetGui::ChangeConfigPresetGui(){ 9 | 10 | _question_ = "Select the config preset"; 11 | _answers_ = GlobalObjects::gGameBrowser.getConfigHandler().get_presets_list(); 12 | 13 | } 14 | 15 | 16 | tsl::elm::Element* ChangeConfigPresetGui::createUI() { 17 | 18 | auto *rootFrame = new tsl::elm::OverlayFrame("SimpleModManager", GlobalObjects::_version_str_); 19 | 20 | // A list that can contain sub elements and handles scrolling 21 | auto list = new tsl::elm::List(); 22 | 23 | // List Items 24 | list->addItem(new tsl::elm::CategoryHeader(_question_)); 25 | 26 | for(int i_answer = 0 ; i_answer < int(_answers_.size()) ; i_answer++){ 27 | auto *clickableListItem = new tsl::elm::ListItem(_answers_[i_answer]); 28 | std::string selected_answer = _answers_[i_answer]; 29 | 30 | clickableListItem->setClickListener([selected_answer, this](u64 keys) { 31 | if (keys & HidNpadButton_A) { 32 | GlobalObjects::gGameBrowser.change_config_preset(selected_answer); 33 | tsl::goBack(); 34 | return true; 35 | } 36 | return false; 37 | }); 38 | list->addItem(clickableListItem); 39 | 40 | std::string install_path_display = "install-mods-base-folder: "+ GlobalObjects::gGameBrowser.getConfigHandler().get_parameter( 41 | _answers_[i_answer] + "-install-mods-base-folder" 42 | ); 43 | list->addItem(new tsl::elm::CustomDrawer([install_path_display](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { 44 | renderer->drawString(install_path_display.c_str(), false, x + 5, y + 21, 16, {255,255,255,255}); 45 | }), 30); 46 | } 47 | 48 | rootFrame->setContent(list); 49 | 50 | return rootFrame; 51 | 52 | } 53 | 54 | bool ChangeConfigPresetGui::handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) { 55 | if (keysDown & HidNpadButton_B) { 56 | tsl::goBack(); 57 | return true; 58 | } 59 | return false; 60 | } -------------------------------------------------------------------------------- /src/ModManagerOverlay/src/ModBrowserGui.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nadrino on 06/06/2020. 3 | // 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | 11 | 12 | 13 | ModBrowserGui::ModBrowserGui(const std::basic_string ¤t_sub_folder_) { 14 | _current_sub_folder_ = current_sub_folder_; 15 | _frame_ = nullptr; 16 | _list_ = nullptr; 17 | _trigger_item_list_update_ = false; 18 | } 19 | 20 | tsl::elm::Element *ModBrowserGui::createUI() { 21 | 22 | _frame_ = new tsl::elm::OverlayFrame("SimpleModManager", GlobalObjects::_version_str_); 23 | 24 | std::string new_path = GlobalObjects::gGameBrowser.get_current_directory() + "/" + _current_sub_folder_; 25 | new_path = GenericToolbox::removeRepeatedCharacters(new_path, "/"); 26 | 27 | GlobalObjects::gGameBrowser.change_directory(new_path); 28 | GlobalObjects::gGameBrowser.getModManager().setGameFolderPath(new_path); 29 | GlobalObjects::gGameBrowser.getModPresetHandler().readParameterFile(new_path); 30 | 31 | _list_ = new tsl::elm::List(); 32 | 33 | // A list that can contain sub elements and handles scrolling 34 | fill_item_list(); 35 | 36 | return _frame_; 37 | 38 | } 39 | 40 | void ModBrowserGui::fill_item_list() { 41 | 42 | // tsl::elm::Element* last_focused_element = this->getFocusedElement(); 43 | // int last_focus_index = 0; 44 | // tsl::elm::Element* last_element; 45 | // int element_index = 0; 46 | // do{ 47 | // 48 | // last_element = _list_->getItemAtIndex(element_index); 49 | // if(last_focused_element == last_element){ 50 | // last_focus_index = element_index; 51 | // break; 52 | // } 53 | // element_index++; 54 | // 55 | // } while(last_element != nullptr); 56 | 57 | // List Items 58 | _list_->addItem(new tsl::elm::CategoryHeader(_current_sub_folder_)); 59 | 60 | auto mods_list = GlobalObjects::gGameBrowser.getSelector().generateEntryTitleList(); 61 | for (int i_folder = 0; i_folder < int(mods_list.size()); i_folder++) { 62 | auto *clickableListItem = new tsl::elm::ListItem(mods_list[i_folder]); 63 | std::string selected_mod_name = mods_list[i_folder]; 64 | 65 | clickableListItem->setClickListener([selected_mod_name, this](u64 keys) { 66 | if (keys & HidNpadButton_A) { 67 | // apply mod... 68 | GlobalObjects::gGameBrowser.getModManager().applyMod(selected_mod_name, true); 69 | GlobalObjects::gGameBrowser.getSelector().setTag( 70 | GlobalObjects::gGameBrowser.getSelector().fetchEntryIndex(selected_mod_name), 71 | GlobalObjects::gGameBrowser.getModManager().generateStatusStr(selected_mod_name) 72 | ); 73 | this->set_trigger_item_list_update(true); 74 | return true; 75 | } else if (keys & HidNpadButton_X) { 76 | GlobalObjects::gGameBrowser.getModManager().removeMod(selected_mod_name, 0); 77 | GlobalObjects::gGameBrowser.getSelector().setTag( 78 | GlobalObjects::gGameBrowser.getSelector().fetchEntryIndex(selected_mod_name), 79 | GlobalObjects::gGameBrowser.getModManager().generateStatusStr(selected_mod_name) 80 | ); 81 | this->set_trigger_item_list_update(true); 82 | return true; 83 | } 84 | return false; 85 | }); 86 | _list_->addItem(clickableListItem); 87 | 88 | double mod_fraction = 0; // initialize at 0 89 | _statusBarMap_[mods_list[i_folder]] = new tsl::elm::CustomDrawer([mod_fraction](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { 90 | renderer->drawRect(x, y + 4, 400, 10, renderer->a(tsl::Color(100, 100, 100, 255))); 91 | }); 92 | 93 | _list_->addItem(_statusBarMap_[mods_list[i_folder]], 17); 94 | 95 | } 96 | 97 | _list_->addItem(new tsl::elm::CategoryHeader("Config Preset")); 98 | auto *buttonConfigPreset = new tsl::elm::ListItem( 99 | GlobalObjects::gGameBrowser.getConfigHandler().get_current_config_preset_name()); 100 | buttonConfigPreset->setClickListener([this](u64 keys) { 101 | if (keys & HidNpadButton_A) { 102 | // apply mod... 103 | this->set_trigger_item_list_update(true); 104 | tsl::changeTo(); 105 | return true; 106 | } 107 | return false; 108 | }); 109 | _list_->addItem(buttonConfigPreset); 110 | 111 | // Add the list to the frame for it to be drawn 112 | _frame_->setContent(_list_); // will delete previous list 113 | _frame_->setFooterTitle("\uE0E1 Back \uE0E0 Apply \uE0E2 Disable"); 114 | 115 | // this->restoreFocus(); 116 | // this->requestFocus(_list_->getItemAtIndex(last_focus_index), tsl::FocusDirection::None, true); 117 | 118 | updateModStatusBars(); 119 | 120 | } 121 | 122 | void ModBrowserGui::updateModStatusBars(){ 123 | 124 | auto mods_list = GlobalObjects::gGameBrowser.getSelector().generateEntryTitleList(); 125 | for (int i_folder = 0; i_folder < int(mods_list.size()); i_folder++) { 126 | std::string selected_mod_name = mods_list[i_folder]; 127 | double mod_fraction = GlobalObjects::gGameBrowser.getModManager().getModList()[i_folder].applyFraction; 128 | if (mod_fraction == -1) mod_fraction = 0; 129 | 130 | _statusBarMap_[selected_mod_name]->getMRenderFunc() = [mod_fraction](tsl::gfx::Renderer *renderer, s32 x, s32 y, s32 w, s32 h) { 131 | renderer->drawRect(x, y + 4, 400 * mod_fraction, 10, renderer->a(tsl::Color(100, 200, 100, 255))); 132 | renderer->drawRect(x + 400 * mod_fraction, y + 4, 400 * (1 - mod_fraction), 10, 133 | renderer->a(tsl::Color(100, 100, 100, 255))); 134 | }; 135 | } 136 | 137 | } 138 | 139 | void ModBrowserGui::update() { 140 | 141 | if (_trigger_item_list_update_) { 142 | _trigger_item_list_update_ = false; 143 | // this->removeFocus(); 144 | // this->_list_->clear(); 145 | // fill_item_list(); 146 | updateModStatusBars(); 147 | } 148 | 149 | } 150 | 151 | bool ModBrowserGui::handleInput(u64 keysDown, u64 keysHeld, const HidTouchState &touchPos, HidAnalogStickState leftJoyStick, HidAnalogStickState rightJoyStick) { 152 | if (keysDown & HidNpadButton_B) { 153 | GlobalObjects::getModBrowser().go_back(); 154 | tsl::goBack(); 155 | return true; 156 | } 157 | return false; // Return true here to singal the inputs have been consumed 158 | } 159 | 160 | void ModBrowserGui::set_trigger_item_list_update(bool trigger_item_list_update_) { 161 | _trigger_item_list_update_ = trigger_item_list_update_; 162 | } -------------------------------------------------------------------------------- /version_config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_CONFIG_H 2 | #define VERSION_CONFIG_H 3 | 4 | // define your version_libinterface 5 | #define VERSION_APP @APP_VERSION@ 6 | #define VERSION_MAJOR_APP @VERSION_MAJOR@ 7 | #define VERSION_MINOR_APP @VERSION_MINOR@ 8 | #define VERSION_MICRO_APP @VERSION_MICRO@ 9 | #define VERSION_TAG_APP @VERSION_TAG@ 10 | 11 | 12 | // alternatively you could add your global method getLibInterfaceVersion here 13 | unsigned int get_version_major() 14 | { 15 | return @VERSION_MAJOR@; 16 | } 17 | 18 | unsigned int get_version_minor() 19 | { 20 | return @VERSION_MINOR@; 21 | } 22 | 23 | unsigned int get_version_micro() 24 | { 25 | return @VERSION_MICRO@; 26 | } 27 | 28 | const char* get_version_tag(){ 29 | return @VERSION_TAG@; 30 | } 31 | 32 | #endif // VERSION_CONFIG_H --------------------------------------------------------------------------------