diff --git a/.gitignore b/.gitignore index 56e48da..f86b775 100755 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,7 @@ #.gitignore # ################### +.devcontainer/ + build/ out/ diff --git a/.gitmodules b/.gitmodules index 72a87b8..792f82d 100755 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "inipp"] path = inipp url = https://github.com/mcmtroffaes/inipp +[submodule "borealis"] + path = borealis + url = https://github.com/withertech/borealis diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 74d81f0..2d615a5 100755 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,20 +1,25 @@ { "configurations": [ { - "name": "Linux", + "name": "DKP aarch64", "includePath": [ "${workspaceFolder}/**", "${workspaceFolder}/inipp/", + "${workspaceFolder}/borealis/library/include/", "/opt/devkitpro/devkitA64/aarch64-none-elf/include/", "/opt/devkitpro/devkitA64/lib/gcc/aarch64-none-elf/**", "/opt/devkitpro/libnx/include/", "/opt/devkitpro/portlibs/switch/include/" ], - "defines": [], - "compilerPath": "/usr/bin/gcc", + "defines": [ + "SWITCH", + "__SWITCH__", + "DEBUG" + ], + "compilerPath": "/opt/devkitpro/devkitA64/bin/aarch64-none-elf-gcc", "cStandard": "c11", "cppStandard": "c++17", - "intelliSenseMode": "gcc-x64" + "intelliSenseMode": "gcc-arm64" } ], "version": 4 diff --git a/.vscode/settings.json b/.vscode/settings.json index da9369d..2303c0c 100755 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,84 +1,94 @@ { "files.associations": { - "stdio.h": "c", - "cartridge.h": "c", - "stdint.h": "c", - "type_traits": "c", - "cpu.h": "c", - "cpu_tester.h": "c", - "instrs.h": "c", - "stddef.h": "c", - "string.h": "c", - "util.h": "c", - "standard_controller.h": "c", - "input_device.h": "c", - "renderer.h": "c", - "system.h": "c", - "sdl_audio.h": "c", - "hotkeys.h": "c", - "mappers.h": "c", - "nrom.h": "c", - "plutonium": "cpp", - "random": "cpp", - "vector": "cpp", - "map": "cpp", - "array": "cpp", - "atomic": "cpp", - "strstream": "cpp", - "*.tcc": "cpp", - "bitset": "cpp", - "cctype": "cpp", - "chrono": "cpp", - "clocale": "cpp", - "cmath": "cpp", - "complex": "cpp", - "condition_variable": "cpp", - "csignal": "cpp", - "cstdarg": "cpp", - "cstddef": "cpp", - "cstdint": "cpp", - "cstdio": "cpp", - "cstdlib": "cpp", - "cstring": "cpp", - "ctime": "cpp", - "cwchar": "cpp", - "cwctype": "cpp", - "deque": "cpp", - "list": "cpp", - "unordered_map": "cpp", - "exception": "cpp", - "algorithm": "cpp", - "functional": "cpp", - "iterator": "cpp", - "memory": "cpp", - "memory_resource": "cpp", - "numeric": "cpp", - "optional": "cpp", - "ratio": "cpp", - "set": "cpp", - "string": "cpp", - "string_view": "cpp", - "system_error": "cpp", - "tuple": "cpp", - "utility": "cpp", - "fstream": "cpp", - "initializer_list": "cpp", - "iomanip": "cpp", - "iosfwd": "cpp", - "iostream": "cpp", - "istream": "cpp", - "limits": "cpp", - "mutex": "cpp", - "new": "cpp", - "ostream": "cpp", - "sstream": "cpp", - "stdexcept": "cpp", - "streambuf": "cpp", - "thread": "cpp", - "cfenv": "cpp", - "cinttypes": "cpp", - "typeindex": "cpp", - "typeinfo": "cpp", - "*.ipp": "cpp" - } + "stdio.h": "c", + "cartridge.h": "c", + "stdint.h": "c", + "type_traits": "c", + "cpu.h": "c", + "cpu_tester.h": "c", + "instrs.h": "c", + "stddef.h": "c", + "string.h": "c", + "util.h": "c", + "standard_controller.h": "c", + "input_device.h": "c", + "renderer.h": "c", + "system.h": "c", + "sdl_audio.h": "c", + "hotkeys.h": "c", + "mappers.h": "c", + "nrom.h": "c", + "plutonium": "cpp", + "random": "cpp", + "vector": "cpp", + "map": "cpp", + "array": "cpp", + "atomic": "cpp", + "strstream": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "complex": "cpp", + "condition_variable": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "list": "cpp", + "unordered_map": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ratio": "cpp", + "set": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "utility": "cpp", + "fstream": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "thread": "cpp", + "cfenv": "cpp", + "cinttypes": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "*.ipp": "cpp", + "hash_map": "cpp", + "bit": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "concepts": "cpp", + "regex": "cpp", + "ranges": "cpp", + "shared_mutex": "cpp", + "stop_token": "cpp", + "variant": "cpp" + } } \ No newline at end of file diff --git a/Makefile b/Makefile index 2c482a7..40fe584 100755 --- a/Makefile +++ b/Makefile @@ -25,6 +25,10 @@ EXEFS_SRC := exefs_src #ROMFS := romfs ICON := res/icon.jpg +ROMFS := resources +BOREALIS_PATH := borealis +BOREALIS_RESOURCES := romfs:/ + ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE CFLAGS := -Wall -O3 -ffunction-sections \ @@ -34,17 +38,19 @@ CFLAGS := -Wall -O3 -ffunction-sections \ -D__VERSION_MICRO=${VERSION_MICRO} \ -D__VERSION="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO}" -CFLAGS += $(INCLUDE) -D__SWITCH__ - -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 +CFLAGS += $(INCLUDE) -D__SWITCH__ -DBOREALIS_RESOURCES="\"$(BOREALIS_RESOURCES)\"" +CXXFLAGS := $(CFLAGS) -std=c++17 +#-fno-rtti -fno-exceptions ASFLAGS := $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs $(ARCH) -Wl,-no-as-needed,-Map,$(notdir $*.map) -LIBS := -lnx +LIBS := -lsmm -lnx LIBDIRS := $(PORTLIBS) $(LIBNX) +include $(TOPDIR)/borealis/library/borealis.mk + ifneq ($(BUILD),$(notdir $(CURDIR))) export OUTPUT := $(CURDIR)/$(OUTDIR)/$(TARGET) diff --git a/borealis b/borealis new file mode 160000 index 0000000..c35d3e5 --- /dev/null +++ b/borealis @@ -0,0 +1 @@ +Subproject commit c35d3e5a546fe4dcab230a7a60f2278ca78578c3 diff --git a/include/error_defs.hpp b/include/error_defs.hpp index 68de082..18ad0a4 100755 --- a/include/error_defs.hpp +++ b/include/error_defs.hpp @@ -26,32 +26,51 @@ #pragma once #include "console_helper.hpp" +#include "main.hpp" +#include -#define FATAL(fmt, ...) CONSOLE_CLEAR_SCREEN(); \ - CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_RED); \ - printf("Fatal error: " ); \ - CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_YELLOW); \ - printf(fmt, ##__VA_ARGS__); \ - CONSOLE_MOVE_DOWN(3); \ - CONSOLE_MOVE_LEFT(99); \ - CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_GREEN); \ - printf("Press (+) to exit.\n"); \ - g_fatal_occurred = true +#define FATAL(fmt, ...) \ + if (g_gui) \ + { \ + char msg[100] = "Fatal Error: "; \ + char err[100]; \ + sprintf(err, fmt, ##__VA_ARGS__); \ + strcat(msg, err); \ + brls::Application::crash(msg); \ + g_fatal_occurred = true; \ + } \ + else \ + { \ + CONSOLE_CLEAR_SCREEN(); \ + CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_RED); \ + printf("Fatal error: " ); \ + CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_YELLOW); \ + printf(fmt, ##__VA_ARGS__); \ + CONSOLE_MOVE_DOWN(3); \ + CONSOLE_MOVE_LEFT(99); \ + CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_GREEN); \ + printf("Press (+) to exit.\n"); \ + g_fatal_occurred = true; \ + } + #define FATAL_CODE(code, fmt, ...) FATAL(fmt " (code %d)", ##__VA_ARGS__, code) -#define PANIC() FATAL("Panic @ %s:%d\n\nPlease contact the developer", __FILE__, __LINE__) +#define PANIC() FATAL(("sky/fatal/panic"_i18n).c_str(), __FILE__, __LINE__) #define RC_SUCCESS(rc) (rc == 0) #define RC_FAILURE(rc) (rc != 0) -#define DO_OR_DIE(rc, task, fail_fmt, ...) if ((rc = RC_FAILURE((task)))) { \ - FATAL_CODE(rc, fail_fmt, ##__VA_ARGS__); \ - return -1; \ - } +#define DO_OR_DIE(rc, task, fail_fmt, ...) \ + if ((rc = RC_FAILURE((task)))) \ + { \ + FATAL_CODE(rc, fail_fmt, ##__VA_ARGS__); \ + return -1; \ + } static bool g_fatal_occurred = false; -inline bool fatal_occurred(void) { - return g_fatal_occurred; +inline bool fatal_occurred(void) +{ + return g_fatal_occurred; } diff --git a/include/frame_root.hpp b/include/frame_root.hpp new file mode 100644 index 0000000..236d321 --- /dev/null +++ b/include/frame_root.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include +#include "mod.hpp" + +class frame_root : public brls::TabFrame +{ + +public: + frame_root(); + + bool onCancel() override; +}; diff --git a/include/global.hpp b/include/global.hpp new file mode 100644 index 0000000..ab49b56 --- /dev/null +++ b/include/global.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +class tab_mods; + +void setModsTabPtr(tab_mods *currentTabModBrowserPtr_); + +tab_mods *getModsTabPtr(void); diff --git a/include/item_mod.hpp b/include/item_mod.hpp new file mode 100644 index 0000000..7b81680 --- /dev/null +++ b/include/item_mod.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include "mod.hpp" +#include "main.hpp" +#include "global.hpp" +using namespace brls; + +class ModListItem : public ListItem +{ + +public: + ModListItem(std::string label, std::string description = "", std::string subLabel = "") : ListItem(label, description, subLabel){}; + void onFocusLost(); + void onFocusGained(); +}; diff --git a/include/main.hpp b/include/main.hpp new file mode 100644 index 0000000..e128798 --- /dev/null +++ b/include/main.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include "console_helper.hpp" +#include "error_defs.hpp" +#include "gui.hpp" +#include "frame_root.hpp" +#include "ini_helper.hpp" +#include "mod.hpp" +#include "path_helper.hpp" +#include "string_helper.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace brls::i18n::literals; +#define Q(x) #x +#define QUOTE(x) Q(x) +#define VERSION QUOTE(__VERSION) + +extern bool g_dirty; +extern bool g_dirty_warned; + +extern std::string g_status_msg; +extern bool g_tmp_status; + +extern ModList g_mod_list_tmp; + +extern std::string g_plugins_header; + +extern int g_scroll_dir; +extern u64 g_last_scroll_time; +extern bool g_scroll_initial_cooldown; + +extern bool g_edit_load_order; +extern bool g_gui; + +extern std::string g_sel_mod; +extern std::string g_prev_mod; + +extern bool g_dialog_open; + +void clearTempEffects(void); +u64 _nanotime(void); \ No newline at end of file diff --git a/include/path_helper.hpp b/include/path_helper.hpp index 2ca2a58..7ea73ce 100755 --- a/include/path_helper.hpp +++ b/include/path_helper.hpp @@ -27,6 +27,8 @@ #include +#define CONFIG_DIR "sdmc:/config/SkyMM-NX" + #define SKYRIM_ROMFS_DIR "sdmc:/atmosphere/contents/01000A10041EA000/romfs" #define SKYRIM_ROMFS_DIR_OLD "sdmc:/atmosphere/titles/01000A10041EA000/romfs" #define SKYRIM_DATA_DIR "Data" @@ -36,6 +38,12 @@ #define LANG_CODE_MAX_LEN 6 +std::string getConfigPath(std::string &partial); + +std::string getConfigPath(const char *partial); + +const char *getBaseConfigPath(void); + std::string getRomfsPath(std::string &partial); std::string getRomfsPath(const char *partial); diff --git a/include/tab_mods.hpp b/include/tab_mods.hpp new file mode 100644 index 0000000..0e4a76b --- /dev/null +++ b/include/tab_mods.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include "mod.hpp" +#include "main.hpp" +#include "global.hpp" +#include +#include +class ModListItem; +class tab_mods : public brls::List +{ + +public: + tab_mods(); + std::vector &getModsListItems(); + + void setTriggerUpdateModsDisplayedStatus(bool triggerUpdateModsDisplayedStatus_); + void setTriggerUpdateListItems(bool triggerUpdateListItems_); + void updateDisplayedModsStatus(); + void updateListItems(); + void draw(NVGcontext *vg, int x, int y, unsigned width, unsigned height, brls::Style *style, brls::FrameContext *ctx) override; + +private: + brls::Dialog *dialog; + std::vector _modsListItems_; + bool triggerUpdateModsDisplayedStatus; + bool triggerUpdateListItems; + int frameCounter; + void onChildFocusLost(View *child); + void onChildFocusGained(View *child); +}; \ No newline at end of file diff --git a/resources/.gitignore b/resources/.gitignore new file mode 100644 index 0000000..17dfeba --- /dev/null +++ b/resources/.gitignore @@ -0,0 +1,2 @@ +Illegal-Font.ttf +Wingdings.ttf diff --git a/resources/i18n/en-US/brls.json b/resources/i18n/en-US/brls.json new file mode 100644 index 0000000..03f2ec9 --- /dev/null +++ b/resources/i18n/en-US/brls.json @@ -0,0 +1,15 @@ +{ + "hints": { + "ok": "OK", + "back": "Back", + "exit": "Exit" + }, + + "crash_frame": { + "button": "OK" + }, + + "thumbnail_sidebar": { + "save": "Save" + } +} diff --git a/resources/i18n/en-US/sky.json b/resources/i18n/en-US/sky.json new file mode 100644 index 0000000..e9df832 --- /dev/null +++ b/resources/i18n/en-US/sky.json @@ -0,0 +1,44 @@ +{ + "hints": { + "save": "Save", + "edit": "Edit", + "disable": "Disable", + "enable": "Enable" + }, + "dialog": { + "yes": "Yes", + "no": "No", + "enable": "Do you want to enable \"{}\" ?", + "disable": "Do you want to disable \"{}\" ?" + }, + "status": { + "enabled": "Enabled", + "partial": "Partial", + "disabled": "Disabled" + }, + "msg": { + "edit": "Editing load order", + "saving": "Saving changes...", + "save": "Wrote changes to SDMC!", + "warning": "Press ({}) to exit without saving changes", + "missing": "No mods have been found in {} There you need to put your mods" + }, + "tab": { + "mods": "Mods" + }, + "console": { + "footer1": "(Up/Down) Navigate | (A) Toggle Mod | (Y) (hold) Change Load Order", + "footer2": "(-) Save Changes | (+) Exit" + }, + "fatal": { + "brls_init": "Unable to init Borealis application", + "no_data": "No Skyrim data folder found!\nSearched in %s", + "plugin": "Failed to read Plugins file", + "plugin_atm": "Failed to open ATM Plugins file", + "plugin_smm": "Failed to open SMM Plugins file", + "ini": "Failed to read file at %s", + "ini_atm": "Failed to open %s", + "ini_smm": "Failed to open %s", + "panic": "Panic @ %s:%d\n\nPlease contact the developer" + } +} \ No newline at end of file diff --git a/resources/images/icon.jpg b/resources/images/icon.jpg new file mode 100755 index 0000000..44c83ed Binary files /dev/null and b/resources/images/icon.jpg differ diff --git a/resources/inter/Inter-Switch.ttf b/resources/inter/Inter-Switch.ttf new file mode 100644 index 0000000..d73ca67 Binary files /dev/null and b/resources/inter/Inter-Switch.ttf differ diff --git a/resources/inter/LICENSE.txt b/resources/inter/LICENSE.txt new file mode 100644 index 0000000..3983080 --- /dev/null +++ b/resources/inter/LICENSE.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016-2018 The Inter Project Authors (me@rsms.me) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/resources/material/LICENSE.txt b/resources/material/LICENSE.txt new file mode 100644 index 0000000..7a4a3ea --- /dev/null +++ b/resources/material/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/resources/material/MaterialIcons-Regular.ttf b/resources/material/MaterialIcons-Regular.ttf new file mode 100644 index 0000000..7015564 Binary files /dev/null and b/resources/material/MaterialIcons-Regular.ttf differ diff --git a/src/frame_root.cpp b/src/frame_root.cpp new file mode 100644 index 0000000..5dcfa27 --- /dev/null +++ b/src/frame_root.cpp @@ -0,0 +1,37 @@ +#include + +frame_root::frame_root() +{ + this->setTitle("SkyMM-NX"); + this->setFooterText(VERSION); + this->setIcon("romfs:/images/icon.jpg"); + + this->addTab("sky/tab/mods"_i18n, new tab_mods()); + // this->addSeparator(); + // this->addTab("Settings", new tab_general_settings()); + // this->addTab("About", new tab_about()); +} + +bool frame_root::onCancel() +{ + + auto *lastFocus = brls::Application::getCurrentFocus(); + + bool onCancel = TabFrame::onCancel(); + + if (lastFocus == brls::Application::getCurrentFocus()) + { + if (g_dirty && !g_dirty_warned) + { + g_status_msg = fmt::format("sky/msg/warning"_i18n, "B"); + g_tmp_status = true; + g_dirty_warned = true; + } + else + { + brls::Application::quit(); + } + } + + return onCancel; +} \ No newline at end of file diff --git a/src/global.cpp b/src/global.cpp new file mode 100644 index 0000000..a31b5ac --- /dev/null +++ b/src/global.cpp @@ -0,0 +1,14 @@ +#include "global.hpp" +#include +#include +static tab_mods *_currentTabModBrowserPtr_; + +void setModsTabPtr(tab_mods *currentTabModBrowserPtr_) +{ + _currentTabModBrowserPtr_ = currentTabModBrowserPtr_; +} + +tab_mods *getModsTabPtr() +{ + return _currentTabModBrowserPtr_; +} diff --git a/src/ini_helper.cpp b/src/ini_helper.cpp index 6171c90..0684dc1 100755 --- a/src/ini_helper.cpp +++ b/src/ini_helper.cpp @@ -28,6 +28,7 @@ #include "mod.hpp" #include "path_helper.hpp" #include "string_helper.hpp" +#include "main.hpp" #include #include @@ -36,6 +37,8 @@ #include #include +#include + static const std::vector g_archive_types_1 = {"", "Animations", "Meshes", "Sounds"}; static const std::vector g_archive_types_2 = {"Textures", "Voices"}; static const std::vector g_archive_types_3 = {"Animations"}; @@ -43,221 +46,257 @@ static const std::vector g_archive_types_3 = {"Animations"}; static StdIni g_skyrim_ini; static StdIni g_skyrim_lang_ini; -static inline const char *get_language_code(SetLanguage &lang) { - switch (lang) { - case SetLanguage_JA: - return "ja"; - case SetLanguage_ENUS: - return "en"; - case SetLanguage_FR: - return "fr"; - case SetLanguage_DE: - return "de"; - case SetLanguage_IT: - return "it"; - case SetLanguage_ES: - return "es"; - case SetLanguage_ZHCN: - return "zhhant"; - case SetLanguage_KO: - return "en"; - case SetLanguage_NL: - return "en"; - case SetLanguage_PT: - return "en"; - case SetLanguage_RU: - return "ru"; - case SetLanguage_ZHTW: - return "zhhant"; - case SetLanguage_ENGB: - return "en"; - case SetLanguage_FRCA: - return "fr"; - case SetLanguage_ES419: - return "es"; - case 15: - return "zhhant"; - case 16: - return "zhhant"; - default: - return "en"; - } +static inline const char *get_language_code(SetLanguage &lang) +{ + switch (lang) + { + case SetLanguage_JA: + return "ja"; + case SetLanguage_ENUS: + return "en"; + case SetLanguage_FR: + return "fr"; + case SetLanguage_DE: + return "de"; + case SetLanguage_IT: + return "it"; + case SetLanguage_ES: + return "es"; + case SetLanguage_ZHCN: + return "zhhant"; + case SetLanguage_KO: + return "en"; + case SetLanguage_NL: + return "en"; + case SetLanguage_PT: + return "en"; + case SetLanguage_RU: + return "ru"; + case SetLanguage_ZHTW: + return "zhhant"; + case SetLanguage_ENGB: + return "en"; + case SetLanguage_FRCA: + return "fr"; + case SetLanguage_ES419: + return "es"; + case 15: + return "zhhant"; + case 16: + return "zhhant"; + default: + return "en"; + } } -static std::string getString(StdIni &ini, std::string section, std::string key) { - auto sec = ini.sections.find(section); - if (sec != ini.sections.cend()) { - auto val_it = sec->second.find(key); - if (val_it != sec->second.cend()) { - return val_it->second; - } - } - return ""; +static std::string getString(StdIni &ini, std::string section, std::string key) +{ + auto sec = ini.sections.find(section); + if (sec != ini.sections.cend()) + { + auto val_it = sec->second.find(key); + if (val_it != sec->second.cend()) + { + return val_it->second; + } + } + return ""; } -static int getLanguage(SetLanguage *lang) { - int rc; - u64 lang_code; - DO_OR_DIE(rc, setInitialize(), "Failed to initialize settings"); - DO_OR_DIE(rc, setGetSystemLanguage(&lang_code), "Failed to get system language"); - DO_OR_DIE(rc, setMakeLanguage(lang_code, lang), "Failed to convert language code"); - return 0; +static int getLanguage(SetLanguage *lang) +{ + int rc; + u64 lang_code; + DO_OR_DIE(rc, setInitialize(), "Failed to initialize settings"); + DO_OR_DIE(rc, setGetSystemLanguage(&lang_code), "Failed to get system language"); + DO_OR_DIE(rc, setMakeLanguage(lang_code, lang), "Failed to convert language code"); + return 0; } -int readIniFile(std::string &path, StdIni &ini) { - std::ifstream ini_stream = std::ifstream(path, std::ios::in); - if (!ini_stream.good()) { - FATAL("Failed to read file at %s", path.c_str()); - return -1; - } +int readIniFile(std::string &path, StdIni &ini) +{ + std::ifstream ini_stream = std::ifstream(path, std::ios::in); + if (!ini_stream.good()) + { + FATAL(("sky/fatal/ini"_i18n).c_str(), path.c_str()); + return -1; + } - ini.parse(ini_stream); + ini.parse(ini_stream); - return 0; + return 0; } -int readIniFile(const char *path, StdIni &ini) { - std::string path_str = std::string(path); - return readIniFile(path_str, ini); +int readIniFile(const char *path, StdIni &ini) +{ + std::string path_str = std::string(path); + return readIniFile(path_str, ini); } int processIniDefs(ModList &final_mod_list, ModList &temp_mod_list, StdIni &ini, const char *key, - const std::vector &expected_suffixes) { - std::string archive_list_str = getString(ini, INI_SECTION_ARCHIVE, key); - std::vector archive_list = split(archive_list_str, ","); - - for (std::string archive_file : archive_list) { - ModFile mod_file = ModFile::fromFileName(archive_file); - if (mod_file.type != ModFileType::BSA) { - continue; - } - - if (mod_file.base_name == "Skyrim") { - continue; - } - - bool good_suffix = false; - for (std::string expected_suffix : expected_suffixes) { - if (mod_file.suffix.find_last_of(expected_suffix, expected_suffix.size())) { - good_suffix = true; - break; - } - } - if (!good_suffix) { - continue; - } - - std::shared_ptr mod = find_mod(final_mod_list, mod_file.base_name); - if (!mod) { - mod = find_mod(temp_mod_list, mod_file.base_name); - if (mod) { - final_mod_list.insert(final_mod_list.end(), mod); - } else { - continue; - } - } - - mod->enabled_bsas[mod_file.suffix] += 1; - } - - return 0; + const std::vector &expected_suffixes) +{ + std::string archive_list_str = getString(ini, INI_SECTION_ARCHIVE, key); + std::vector archive_list = split(archive_list_str, ","); + + for (std::string archive_file : archive_list) + { + ModFile mod_file = ModFile::fromFileName(archive_file); + if (mod_file.type != ModFileType::BSA) + { + continue; + } + + if (mod_file.base_name == "Skyrim") + { + continue; + } + + bool good_suffix = false; + for (std::string expected_suffix : expected_suffixes) + { + if (mod_file.suffix == expected_suffix) + { + good_suffix = true; + break; + } + } + if (!good_suffix) + { + continue; + } + + std::shared_ptr mod = find_mod(final_mod_list, mod_file.base_name); + if (!mod) + { + mod = find_mod(temp_mod_list, mod_file.base_name); + if (mod) + { + final_mod_list.insert(final_mod_list.end(), mod); + } + else + { + continue; + } + } + + mod->enabled_bsas[mod_file.suffix] += 1; + } + + return 0; } -int parseInis(ModList &final_mod_list, ModList &temp_mod_list) { - int rc; - SetLanguage lang; - if (RC_FAILURE(getLanguage(&lang))) { - return -1; - } - const char *skyrim_lang_code = get_language_code(lang); +int parseInis(ModList &final_mod_list, ModList &temp_mod_list) +{ + int rc; + SetLanguage lang; + if (RC_FAILURE(getLanguage(&lang))) + { + return -1; + } + const char *skyrim_lang_code = get_language_code(lang); - std::string ini_lang_base = std::string(SKYRIM_INI_LANG_FILE_PREFIX) + skyrim_lang_code + ".ini"; - std::string ini_lang_file = getRomfsPath(ini_lang_base); + std::string ini_lang_base = std::string(SKYRIM_INI_LANG_FILE_PREFIX) + skyrim_lang_code + ".ini"; + std::string ini_lang_file = getRomfsPath(ini_lang_base); - DO_OR_DIE(rc, readIniFile(getRomfsPath(SKYRIM_INI_FILE).c_str(), g_skyrim_ini), "Failed to read Skyrim.ini"); - DO_OR_DIE(rc, readIniFile(ini_lang_file, g_skyrim_lang_ini), "Failed to read Skyrim_%s.ini", skyrim_lang_code); + DO_OR_DIE(rc, readIniFile(getRomfsPath(SKYRIM_INI_FILE).c_str(), g_skyrim_ini), "Failed to read Skyrim.ini"); + DO_OR_DIE(rc, readIniFile(ini_lang_file, g_skyrim_lang_ini), "Failed to read Skyrim_%s.ini", skyrim_lang_code); - processIniDefs(final_mod_list, temp_mod_list, g_skyrim_ini, INI_ARCHIVE_LIST_1, g_archive_types_1); - processIniDefs(final_mod_list, temp_mod_list, g_skyrim_ini, INI_ARCHIVE_LIST_3, g_archive_types_3); - processIniDefs(final_mod_list, temp_mod_list, g_skyrim_lang_ini, INI_ARCHIVE_LIST_2, g_archive_types_2); + processIniDefs(final_mod_list, temp_mod_list, g_skyrim_ini, INI_ARCHIVE_LIST_1, g_archive_types_1); + processIniDefs(final_mod_list, temp_mod_list, g_skyrim_ini, INI_ARCHIVE_LIST_3, g_archive_types_3); + processIniDefs(final_mod_list, temp_mod_list, g_skyrim_lang_ini, INI_ARCHIVE_LIST_2, g_archive_types_2); - return 0; + return 0; } static int writeFileList(const char *path, StdIni &ini, std::string key, - std::vector const &expected_suffixes) { - std::vector file_list; - - std::string archive_list_str = getString(ini, INI_SECTION_ARCHIVE, key); - std::vector archive_list = split(archive_list_str, ","); - std::vector cur_file_list; - std::transform(archive_list.cbegin(), archive_list.cend(), std::back_inserter(cur_file_list), ModFile::fromFileName); - - for (ModFile file : cur_file_list) { - if (file.base_name == "Skyrim") { - file_list.insert(file_list.end(), file); - } - } - - for (std::shared_ptr mod : getGlobalModList()) { - for (std::pair suffix_pair : mod->enabled_bsas) { - for (std::string expected_suffix : expected_suffixes) { - if (suffix_pair.first.find(expected_suffix) == 0) { - file_list.insert(file_list.end(), {mod->is_master ? ModFileType::ESM : ModFileType::ESP, mod->base_name, suffix_pair.first}); - break; - } - } - } - } - - std::stringstream ss; - for (auto mf_it = file_list.cbegin(); mf_it != file_list.cend(); mf_it++) { - ss << mf_it->base_name; - if (!mf_it->suffix.empty()) { - ss << " - " << mf_it->suffix; - } - ss << ".bsa"; - - if (mf_it != file_list.cend() - 1) { - ss << ", "; - } - } - - std::string out_list_str = ss.str(); - - auto sec_it = ini.sections.find(INI_SECTION_ARCHIVE); - std::map sec_map; - if (sec_it != ini.sections.cend()) { - sec_map = sec_it->second; - } - - sec_map.insert_or_assign(key, out_list_str); - - ini.sections.insert_or_assign(INI_SECTION_ARCHIVE, sec_map); - - std::ofstream ini_stream(path, std::ios::out | std::ios::trunc | std::ios::binary); - if (!ini_stream.good()) { - FATAL("Failed to open %s", path); - return -1; - } - - ini.generate(ini_stream); - return 0; + std::vector const &expected_suffixes) +{ + std::vector file_list; + + std::string archive_list_str = getString(ini, INI_SECTION_ARCHIVE, key); + std::vector archive_list = split(archive_list_str, ","); + std::vector cur_file_list; + std::transform(archive_list.cbegin(), archive_list.cend(), std::back_inserter(cur_file_list), ModFile::fromFileName); + + for (ModFile file : cur_file_list) + { + if (file.base_name == "Skyrim") + { + file_list.insert(file_list.end(), file); + } + } + + for (std::shared_ptr mod : getGlobalModList()) + { + for (std::pair suffix_pair : mod->enabled_bsas) + { + for (std::string expected_suffix : expected_suffixes) + { + if (suffix_pair.first == expected_suffix) + { + file_list.insert(file_list.end(), {mod->is_master ? ModFileType::ESM : ModFileType::ESP, mod->base_name, suffix_pair.first}); + break; + } + } + } + } + + std::stringstream ss; + for (auto mf_it = file_list.cbegin(); mf_it != file_list.cend(); mf_it++) + { + ss << mf_it->base_name; + if (!mf_it->suffix.empty()) + { + ss << " - " << mf_it->suffix; + } + ss << ".bsa"; + + if (mf_it != file_list.cend() - 1) + { + ss << ", "; + } + } + + std::string out_list_str = ss.str(); + + auto sec_it = ini.sections.find(INI_SECTION_ARCHIVE); + std::map sec_map; + if (sec_it != ini.sections.cend()) + { + sec_map = sec_it->second; + } + + sec_map.insert_or_assign(key, out_list_str); + + ini.sections.insert_or_assign(INI_SECTION_ARCHIVE, sec_map); + + std::ofstream ini_stream(path, std::ios::out | std::ios::trunc | std::ios::binary); + if (!ini_stream.good()) + { + FATAL(("sky/fatal/ini_atm"_i18n).c_str(), path); + return -1; + } + ini.generate(ini_stream); + + return 0; } -int writeIniChanges(void) { - SetLanguage lang; - if (RC_FAILURE(getLanguage(&lang))) { - return -1; - } - const char *skyrim_lang_code = get_language_code(lang); +int writeIniChanges(void) +{ + SetLanguage lang; + if (RC_FAILURE(getLanguage(&lang))) + { + return -1; + } + const char *skyrim_lang_code = get_language_code(lang); - std::string ini_lang_base = std::string(SKYRIM_INI_LANG_FILE_PREFIX) + skyrim_lang_code + ".ini"; - std::string ini_lang_file = getRomfsPath(ini_lang_base); + std::string ini_lang_base = std::string(SKYRIM_INI_LANG_FILE_PREFIX) + skyrim_lang_code + ".ini"; + std::string ini_lang_file = getRomfsPath(ini_lang_base); - writeFileList(getRomfsPath(SKYRIM_INI_FILE).c_str(), g_skyrim_ini, INI_ARCHIVE_LIST_1, g_archive_types_1); - writeFileList(ini_lang_file.c_str(), g_skyrim_lang_ini, INI_ARCHIVE_LIST_2, g_archive_types_2); - writeFileList(getRomfsPath(SKYRIM_INI_FILE).c_str(), g_skyrim_ini, INI_ARCHIVE_LIST_3, g_archive_types_3); + writeFileList(getRomfsPath(SKYRIM_INI_FILE).c_str(), g_skyrim_ini, INI_ARCHIVE_LIST_1, g_archive_types_1); + writeFileList(ini_lang_file.c_str(), g_skyrim_lang_ini, INI_ARCHIVE_LIST_2, g_archive_types_2); + writeFileList(getRomfsPath(SKYRIM_INI_FILE).c_str(), g_skyrim_ini, INI_ARCHIVE_LIST_3, g_archive_types_3); - return 0; + return 0; } diff --git a/src/item_mod.cpp b/src/item_mod.cpp new file mode 100644 index 0000000..d638c9c --- /dev/null +++ b/src/item_mod.cpp @@ -0,0 +1,13 @@ +#include "item_mod.hpp" +#include + +void ModListItem::onFocusLost() +{ + g_prev_mod = this->getLabel(); + View::onFocusLost(); +} +void ModListItem::onFocusGained() +{ + g_sel_mod = this->getLabel(); + View::onFocusGained(); +} diff --git a/src/main.cpp b/src/main.cpp index 597692d..5e28700 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,35 +23,11 @@ * THE SOFTWARE. */ -#include "console_helper.hpp" -#include "error_defs.hpp" -#include "gui.hpp" -#include "ini_helper.hpp" -#include "mod.hpp" -#include "path_helper.hpp" -#include "string_helper.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include +#include "main.hpp" #define STRINGIZE0(x) #x #define STRINGIZE(x) STRINGIZE0(x) -#ifndef __VERSION -#define __VERSION "Unknown" -#endif - #define HEADER_HEIGHT 3 #define FOOTER_HEIGHT 5 @@ -62,209 +38,273 @@ static HidNpadButton g_key_edit_lo = HidNpadButton_Y; -static bool g_dirty = false; -static bool g_dirty_warned = false; - -static std::string g_status_msg = ""; -static bool g_tmp_status = false; - -static ModList g_mod_list_tmp; - -static std::string g_plugins_header; - -static int g_scroll_dir = 0; -static u64 g_last_scroll_time = 0; -static bool g_scroll_initial_cooldown; - -static bool g_edit_load_order = false; - -static u64 _nanotime(void) { - return armTicksToNs(armGetSystemTick()); -} - -int discoverMods() { - DIR *dir = opendir(getRomfsPath(SKYRIM_DATA_DIR).c_str()); - - if (!dir) { - FATAL("No Skyrim data folder found!\nSearched in %s", getBaseRomfsPath()); - return -1; - } - - std::vector files; - - struct dirent *ent; - while ((ent = readdir(dir))) { - if (ent->d_type != DT_REG) { - continue; - } +bool g_dirty = false; +bool g_dirty_warned = false; - files.insert(files.end(), ent->d_name); - } +std::string g_status_msg = VERSION; +std::string g_status_msg_old = VERSION; +bool g_tmp_status = false; - closedir(dir); +ModList g_mod_list_tmp; - printf("Found %lu mod files\n", files.size()); +std::string g_plugins_header; - for (std::string file_name : files) { - ModFile mod_file = ModFile::fromFileName(file_name); +int g_scroll_dir = 0; +u64 g_last_scroll_time = 0; +bool g_scroll_initial_cooldown; - if (mod_file.type == ModFileType::UNKNOWN) { - continue; - } +bool g_edit_load_order = false; - std::shared_ptr mod = find_mod(g_mod_list_tmp, mod_file.base_name); - if (!mod) { - mod = std::shared_ptr(new SkyrimMod(mod_file.base_name)); - // everything gets loaded into a temp buffer so we can rebuild it with the proper order later - g_mod_list_tmp.insert(g_mod_list_tmp.end(), mod); - } +bool g_gui = true; - if (mod_file.type == ModFileType::ESP) { - mod->has_esp = true; - } else if (mod_file.type == ModFileType::ESM) { - mod->has_esp = true; - mod->is_master = true; - } else if (mod_file.type == ModFileType::BSA) { - mod->bsa_suffixes.insert(mod->bsa_suffixes.end(), mod_file.suffix); - } else { - PANIC(); - return -1; - } - } - - return 0; +u64 _nanotime(void) +{ + return armTicksToNs(armGetSystemTick()); } -int processPluginsFile() { - std::ifstream plugins_stream = std::ifstream(getRomfsPath(SKYRIM_PLUGINS_FILE), std::ios::in); - if (!plugins_stream.good()) { - FATAL("Failed to open Plugins file"); - return -1; - } - - bool in_header = true; - std::stringstream header_stream; - std::string line; - while (std::getline(plugins_stream, line)) { - if (line.length() == 0 || line.at(0) == '#') { - if (in_header) { - header_stream << line << '\n'; - } - continue; - } - - if (in_header) { - g_plugins_header = header_stream.str(); - in_header = false; - } - - bool enable = line.at(0) == '*'; - - std::string file_name = enable ? line.substr(1) : line; - ModFile file_def = ModFile::fromFileName(file_name); - if (file_def.type != ModFileType::ESP && file_def.type != ModFileType::ESM) { - continue; - } - - std::shared_ptr mod = find_mod(getGlobalModList(), file_def.base_name); - if (!mod) { - mod = find_mod(g_mod_list_tmp, file_def.base_name); - if (mod) { - getGlobalModList().insert(getGlobalModList().end(), mod); - } else { - continue; - } - } - - mod->esp_enabled = enable; - } - - return 0; +int discoverMods() +{ + DIR *dir = opendir(getRomfsPath(SKYRIM_DATA_DIR).c_str()); + + if (!dir) + { + FATAL(("sky/fatal/no_data"_i18n).c_str(), getBaseRomfsPath()); + return -1; + } + + std::vector files; + + struct dirent *ent; + while ((ent = readdir(dir))) + { + if (ent->d_type != DT_REG) + { + continue; + } + + files.insert(files.end(), ent->d_name); + } + + closedir(dir); + brls::Logger::debug("Found {} mod files\n", files.size()); + + for (std::string file_name : files) + { + ModFile mod_file = ModFile::fromFileName(file_name); + + if (mod_file.type == ModFileType::UNKNOWN) + { + continue; + } + + std::shared_ptr mod = find_mod(g_mod_list_tmp, mod_file.base_name); + if (!mod) + { + mod = std::shared_ptr(new SkyrimMod(mod_file.base_name)); + // everything gets loaded into a temp buffer so we can rebuild it with the proper order later + g_mod_list_tmp.insert(g_mod_list_tmp.end(), mod); + } + + if (mod_file.type == ModFileType::ESP) + { + mod->has_esp = true; + } + else if (mod_file.type == ModFileType::ESM) + { + mod->has_esp = true; + mod->is_master = true; + } + else if (mod_file.type == ModFileType::BSA) + { + mod->bsa_suffixes.insert(mod->bsa_suffixes.end(), mod_file.suffix); + } + else + { + PANIC(); + return -1; + } + } + + return 0; } -int writePluginsFile(void) { - std::ofstream plugins_stream = std::ofstream(getRomfsPath(SKYRIM_PLUGINS_FILE), - std::ios::out | std::ios::trunc | std::ios::binary); - if (!plugins_stream.good()) { - FATAL("Failed to open Plugins file"); - return -1; - } - - // write header that we loaded earlier - plugins_stream << g_plugins_header; - - for (std::shared_ptr mod : getGlobalModList()) { - if (mod->has_esp) { - if (mod->esp_enabled) { - plugins_stream << '*'; - } - if (mod->is_master) { - plugins_stream << mod->base_name << ".esm"; - } else { - plugins_stream << mod->base_name << ".esp"; - } - plugins_stream << '\n'; - } - } - - return 0; +int processPluginsFile() +{ + std::ifstream plugins_stream = std::ifstream(getRomfsPath(SKYRIM_PLUGINS_FILE), std::ios::in); + if (!plugins_stream.good()) + { + FATAL(("sky/fatal/plugin"_i18n).c_str()); + return -1; + } + + bool in_header = true; + std::stringstream header_stream; + std::string line; + while (std::getline(plugins_stream, line)) + { + if (line.length() == 0 || line.at(0) == '#') + { + if (in_header) + { + header_stream << line << '\n'; + } + continue; + } + + if (in_header) + { + g_plugins_header = header_stream.str(); + in_header = false; + } + + bool enable = line.at(0) == '*'; + + std::string file_name = enable ? line.substr(1) : line; + ModFile file_def = ModFile::fromFileName(file_name); + if (file_def.type != ModFileType::ESP && file_def.type != ModFileType::ESM) + { + continue; + } + + std::shared_ptr mod = find_mod(getGlobalModList(), file_def.base_name); + if (!mod) + { + mod = find_mod(g_mod_list_tmp, file_def.base_name); + if (mod) + { + getGlobalModList().insert(getGlobalModList().end(), mod); + } + else + { + continue; + } + } + + mod->esp_enabled = enable; + } + + return 0; } -int initialize(void) { - int rc; - - CONSOLE_SET_POS(0, 0); - CONSOLE_CLEAR_SCREEN(); - CONSOLE_SET_ATTRS(CONSOLE_ATTR_BOLD); - printf("Discovering available mods...\n"); - - if (RC_FAILURE(rc = discoverMods())) { - return rc; - } - - if (RC_FAILURE(rc = processPluginsFile())) { - return rc; - } - - if (RC_FAILURE(rc = parseInis(getGlobalModList(), g_mod_list_tmp))) { - return rc; - } - - for (std::shared_ptr mod : g_mod_list_tmp) { - std::string mod_name = mod->base_name; - if (!find_mod(getGlobalModList(), mod_name)) { - getGlobalModList().insert(getGlobalModList().end(), mod); - } - } - - printf("Identified %lu mods\n", getGlobalModList().size()); - - CONSOLE_MOVE_DOWN(3); - printf("Mod listing:\n\n"); - for (auto it = getGlobalModList().cbegin(); it != getGlobalModList().cend(); it++) { - ModStatus status = (*it)->getStatus(); - const char *status_str; - switch (status) { - case ModStatus::ENABLED: - status_str = "Enabled"; - break; - case ModStatus::DISABLED: - status_str = "Disabled"; - break; - case ModStatus::PARTIAL: - status_str = "Partial"; - break; - default: - PANIC(); - return -1; - } - printf(" - %s (%s)\n", (*it)->base_name.c_str(), status_str); - } +int writePluginsFile(void) +{ + std::ofstream plugins_stream = std::ofstream(getRomfsPath(SKYRIM_PLUGINS_FILE), + std::ios::out | std::ios::trunc | std::ios::binary); + + if (!plugins_stream.good()) + { + FATAL(("sky/fatal/plugin_atm"_i18n).c_str()); + return -1; + } + + // write header that we loaded earlier + plugins_stream << g_plugins_header; + + for (std::shared_ptr mod : getGlobalModList()) + { + if (mod->has_esp) + { + if (mod->esp_enabled) + { + plugins_stream << '*'; + } + if (mod->is_master) + { + plugins_stream << mod->base_name << ".esm"; + } + else + { + plugins_stream << mod->base_name << ".esp"; + } + plugins_stream << '\n'; + } + } + + return 0; +} - return 0; +int initialize(void) +{ + int rc; + if (g_gui) + { + brls::Logger::debug("Discovering available mods...\n"); + } + else + { + CONSOLE_SET_POS(0, 0); + CONSOLE_CLEAR_SCREEN(); + CONSOLE_SET_ATTRS(CONSOLE_ATTR_BOLD); + printf("Discovering available mods...\n"); + } + + if (RC_FAILURE(rc = discoverMods())) + { + return rc; + } + + if (RC_FAILURE(rc = processPluginsFile())) + { + return rc; + } + + if (RC_FAILURE(rc = parseInis(getGlobalModList(), g_mod_list_tmp))) + { + return rc; + } + + for (std::shared_ptr mod : g_mod_list_tmp) + { + std::string mod_name = mod->base_name; + if (!find_mod(getGlobalModList(), mod_name)) + { + getGlobalModList().insert(getGlobalModList().end(), mod); + } + } + if (g_gui) + { + brls::Logger::debug("Identified {} mods\n", getGlobalModList().size()); + brls::Logger::debug("Mod listing:\n\n"); } + else + { + printf("Identified %lu mods\n", getGlobalModList().size()); + CONSOLE_MOVE_DOWN(3); + printf("Mod listing:\n\n"); + } + for (auto it = getGlobalModList().cbegin(); it != getGlobalModList().cend(); it++) + { + ModStatus status = (*it)->getStatus(); + const char *status_str; + switch (status) + { + case ModStatus::ENABLED: + status_str = "Enabled"; + break; + case ModStatus::DISABLED: + status_str = "Disabled"; + break; + case ModStatus::PARTIAL: + status_str = "Partial"; + break; + default: + PANIC(); + return -1; + } + if (g_gui) + { + brls::Logger::debug(" - {} ({})\n", (*it)->base_name.c_str(), status_str); + } + else + { + printf(" - %s (%s)\n", (*it)->base_name.c_str(), status_str); + } + } + + return 0; } -static void redrawHeader(void) { +static void redrawHeader(void) +{ CONSOLE_SET_POS(0, 0); CONSOLE_CLEAR_LINE(); CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_CYAN); @@ -276,7 +316,8 @@ static void redrawHeader(void) { printf(HRULE); } -static void redrawFooter() { +static void redrawFooter() +{ CONSOLE_SET_POS(39, 0); CONSOLE_CLEAR_LINE(); printf(HRULE); @@ -285,7 +326,8 @@ static void redrawFooter() { CONSOLE_MOVE_DOWN(1); CONSOLE_CLEAR_LINE(); - if (!g_status_msg.empty()) { + if (!g_status_msg.empty()) + { CONSOLE_SET_ATTRS(CONSOLE_ATTR_NONE); CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_YELLOW); printf(g_status_msg.c_str()); @@ -300,33 +342,39 @@ static void redrawFooter() { CONSOLE_MOVE_DOWN(1); CONSOLE_CLEAR_LINE(); CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_GREEN); - printf("(Up/Down) Navigate | (A) Toggle Mod | (Y) (hold) Change Load Order"); + printf(("sky/console/footer1"_i18n).c_str()); CONSOLE_MOVE_LEFT(255); CONSOLE_MOVE_DOWN(1); - printf("(-) Save Changes | (+) Exit"); + printf(("sky/console/footer2"_i18n).c_str()); CONSOLE_SET_COLOR(CONSOLE_COLOR_FG_WHITE); } -static void clearTempEffects(void) { - g_dirty_warned = false; +void clearTempEffects(void) +{ + g_dirty_warned = false; - if (g_tmp_status) { - g_status_msg = ""; - g_tmp_status = false; - redrawFooter(); - } + if (g_tmp_status) + { + g_status_msg = VERSION; + g_tmp_status = false; + } } -void handleScrollHold(u64 kDown, u64 kHeld, HidNpadButton key, ModGui &gui) { - if (kHeld & key && !(kDown & key)) { +void handleScrollHold(u64 kDown, u64 kHeld, HidNpadButton key, ModGui &gui) +{ + if (kHeld & key && !(kDown & key)) + { u64 period = g_scroll_initial_cooldown ? SCROLL_INITIAL_DELAY : SCROLL_INTERVAL; - if (_nanotime() - g_last_scroll_time >= period) { + if (_nanotime() - g_last_scroll_time >= period) + { g_last_scroll_time = _nanotime(); g_scroll_initial_cooldown = false; - if (g_edit_load_order) { - if (gui.getSelectedIndex() < getGlobalModList().size() - 1) { + if (g_edit_load_order) + { + if (gui.getSelectedIndex() < getGlobalModList().size() - 1) + { if (g_scroll_dir == 1) { gui.getSelectedMod()->loadLater(); } else { @@ -343,143 +391,260 @@ void handleScrollHold(u64 kDown, u64 kHeld, HidNpadButton key, ModGui &gui) { } } -int main(int argc, char **argv) { - consoleInit(NULL); - - ModGui gui = ModGui(getGlobalModList(), HEADER_HEIGHT, CONSOLE_LINES - HEADER_HEIGHT - FOOTER_HEIGHT); - - padConfigureInput(1, HidNpadStyleSet_NpadStandard); - PadState defaultPad; - padInitializeDefault(&defaultPad); - - int init_status = initialize(); - if (RC_SUCCESS(init_status)) { - CONSOLE_CLEAR_SCREEN(); - - redrawHeader(); - gui.redraw(); - redrawFooter(); - } - - while (appletMainLoop()) { - padUpdate(&defaultPad); - - u64 kDown = padGetButtonsDown(&defaultPad); - u64 kUp = padGetButtonsUp(&defaultPad); - u64 kHeld = padGetButtons(&defaultPad); - - if (kDown & HidNpadButton_Plus) { - if (g_dirty && !g_dirty_warned) { - g_status_msg = "Press (+) to exit without saving changes"; - g_tmp_status = true; - g_dirty_warned = true; - redrawFooter(); - } else { - break; - } - } - - if (RC_FAILURE(init_status) || fatal_occurred()) { - consoleUpdate(NULL); - continue; - } - - if (kDown & g_key_edit_lo) { - g_edit_load_order = true; - g_status_msg = "Editing load order"; - redrawFooter(); - } - - if (kUp & g_key_edit_lo) { - g_edit_load_order = false; - g_status_msg = ""; - redrawFooter(); - } - - if ((kUp & HidNpadButton_AnyDown) && g_scroll_dir == 1) { - g_scroll_dir = 0; - } else if ((kUp & HidNpadButton_AnyUp) && g_scroll_dir == -1) { - g_scroll_dir = 0; - } - - switch (g_scroll_dir) { - case -1: - handleScrollHold(kDown, kHeld, HidNpadButton_AnyUp, gui); - break; - case 1: - handleScrollHold(kDown, kHeld, HidNpadButton_AnyDown, gui); - break; - default: - break; - } - - if (kDown & HidNpadButton_AnyDown) { - if (g_edit_load_order) { - if (gui.getSelectedIndex() < getGlobalModList().size() - 1) { - gui.getSelectedMod()->loadLater(); - g_dirty = true; - } - } - - g_last_scroll_time = _nanotime(); - g_scroll_initial_cooldown = true; - g_scroll_dir = 1; - - gui.scrollSelection(1); - - clearTempEffects(); - } else if (kDown & HidNpadButton_AnyUp) { - if (g_edit_load_order) { - if (gui.getSelectedIndex() > 0) { - gui.getSelectedMod()->loadSooner(); - g_dirty = true; - } - } - - g_last_scroll_time = _nanotime(); - g_scroll_initial_cooldown = true; - g_scroll_dir = -1; - - gui.scrollSelection(-1); - - clearTempEffects(); - } else if (kDown & HidNpadButton_A) { - std::shared_ptr mod = gui.getSelectedMod(); - switch (mod->getStatus()) { - case ModStatus::ENABLED: - mod->disable(); - break; - case ModStatus::PARTIAL: - case ModStatus::DISABLED: - mod->enable(); - break; - default: - PANIC(); - } - g_dirty = true; - - gui.redrawCurrentRow(); - - clearTempEffects(); - } - - if (kDown & HidNpadButton_Minus) { - g_status_msg = "Saving changes..."; - redrawFooter(); - consoleUpdate(NULL); - - writePluginsFile(); - writeIniChanges(); - g_dirty = false; - - g_status_msg = "Wrote changes to SDMC!"; - g_tmp_status = true; - redrawFooter(); - } +static std::string getString(StdIni &ini, std::string section, std::string key) +{ + auto sec = ini.sections.find(section); + if (sec != ini.sections.cend()) + { + auto val_it = sec->second.find(key); + if (val_it != sec->second.cend()) + { + return val_it->second; + } + } + return ""; +} - consoleUpdate(NULL); - } +static bool getBoolean(StdIni &ini, std::string section, std::string key) +{ + auto sec = ini.sections.find(section); + if (sec != ini.sections.cend()) + { + auto val_it = sec->second.find(key); + if (val_it != sec->second.cend()) + { + if (val_it->second == "true") + { + return true; + } + else + { + return false; + } + + } + } + return false; +} - consoleExit(NULL); - return 0; +int main(int argc, char **argv) +{ + StdIni ini; + if (std::filesystem::exists(std::filesystem::path(CONFIG_DIR) / "config.ini")) + { + std::ifstream is(getConfigPath("config.ini")); + ini.parse(is); + g_gui = getBoolean(ini, "Config", "enable_gui"); + } + else + { + if (!std::filesystem::exists(getBaseConfigPath())) + { + std::filesystem::create_directories(getBaseConfigPath()); + } + auto sec_it = ini.sections.find("Config"); + std::map sec_map; + if (sec_it != ini.sections.cend()) + { + sec_map = sec_it->second; + } + sec_map.insert_or_assign("enable_gui", "true"); + ini.sections.insert_or_assign("Config", sec_map); + std::ofstream of(getConfigPath("config.ini")); + ini.generate(of); + } + + if (g_gui) + { + Result rc; + rc = nsInitialize(); + if (R_FAILED(rc)) + { + brls::Logger::debug("nsInitialize Failed"); + } + brls::i18n::loadTranslations(); + brls::Logger::setLogLevel(brls::LogLevel::DEBUG); + if (not brls::Application::init("SkyMM-NX")) + { + FATAL(("sky/fatal/brls_init"_i18n).c_str()); + } + frame_root *gui; + + int init_status = initialize(); + if (RC_SUCCESS(init_status)) + { + gui = new frame_root(); + brls::Application::pushView(gui); + } + else + { + gui = new frame_root(); + } + gui->registerAction( + "sky/hints/save"_i18n, brls::Key::PLUS, [gui] { + g_status_msg = "sky/msg/saving"_i18n; + if (g_status_msg_old != g_status_msg) + { + g_status_msg_old = g_status_msg; + gui->setFooterText(g_status_msg); + } + writePluginsFile(); + writeIniChanges(); + g_dirty = false; + + g_status_msg = "sky/msg/save"_i18n; + g_tmp_status = true; + return true; + }); + while (brls::Application::mainLoop()) + { + if (g_status_msg_old != g_status_msg) + { + g_status_msg_old = g_status_msg; + gui->setFooterText(g_status_msg); + } + } + } + else + { + consoleInit(NULL); + brls::i18n::loadTranslations(); + + ModGui gui = ModGui(getGlobalModList(), HEADER_HEIGHT, CONSOLE_LINES - HEADER_HEIGHT - FOOTER_HEIGHT); + + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + PadState defaultPad; + padInitializeDefault(&defaultPad); + + int init_status = initialize(); + if (RC_SUCCESS(init_status)) { + CONSOLE_CLEAR_SCREEN(); + + redrawHeader(); + gui.redraw(); + redrawFooter(); + } + + while (appletMainLoop()) { + padUpdate(&defaultPad); + + u64 kDown = padGetButtonsDown(&defaultPad); + u64 kUp = padGetButtonsUp(&defaultPad); + u64 kHeld = padGetButtons(&defaultPad); + + if (kDown & HidNpadButton_Plus) { + if (g_dirty && !g_dirty_warned) { + g_status_msg = fmt::format("sky/msg/warning"_i18n, "+"); + g_tmp_status = true; + g_dirty_warned = true; + redrawFooter(); + } else { + break; + } + } + + if (RC_FAILURE(init_status) || fatal_occurred()) { + consoleUpdate(NULL); + continue; + } + + if (kDown & g_key_edit_lo) { + g_edit_load_order = true; + g_status_msg = "sky/msg/edit"_i18n; + redrawFooter(); + } + + if (kUp & g_key_edit_lo) { + g_edit_load_order = false; + g_status_msg = ""; + redrawFooter(); + } + + if ((kUp & HidNpadButton_AnyDown) && g_scroll_dir == 1) { + g_scroll_dir = 0; + } else if ((kUp & HidNpadButton_AnyUp) && g_scroll_dir == -1) { + g_scroll_dir = 0; + } + + switch (g_scroll_dir) { + case -1: + handleScrollHold(kDown, kHeld, HidNpadButton_AnyUp, gui); + break; + case 1: + handleScrollHold(kDown, kHeld, HidNpadButton_AnyDown, gui); + break; + default: + break; + } + + if (kDown & HidNpadButton_AnyDown) { + if (g_edit_load_order) { + if (gui.getSelectedIndex() < getGlobalModList().size() - 1) { + gui.getSelectedMod()->loadLater(); + g_dirty = true; + } + } + + g_last_scroll_time = _nanotime(); + g_scroll_initial_cooldown = true; + g_scroll_dir = 1; + + gui.scrollSelection(1); + + clearTempEffects(); + } else if (kDown & HidNpadButton_AnyUp) { + if (g_edit_load_order) { + if (gui.getSelectedIndex() > 0) { + gui.getSelectedMod()->loadSooner(); + g_dirty = true; + } + } + + g_last_scroll_time = _nanotime(); + g_scroll_initial_cooldown = true; + g_scroll_dir = -1; + + gui.scrollSelection(-1); + + clearTempEffects(); + } else if (kDown & HidNpadButton_A) { + std::shared_ptr mod = gui.getSelectedMod(); + switch (mod->getStatus()) { + case ModStatus::ENABLED: + mod->disable(); + break; + case ModStatus::PARTIAL: + case ModStatus::DISABLED: + mod->enable(); + break; + default: + PANIC(); + } + g_dirty = true; + + gui.redrawCurrentRow(); + + clearTempEffects(); + } + + if (kDown & HidNpadButton_Minus) { + g_status_msg = "sky/msg/saving"_i18n; + redrawFooter(); + consoleUpdate(NULL); + + writePluginsFile(); + writeIniChanges(); + g_dirty = false; + + g_status_msg = "sky/msg/save"_i18n; + g_tmp_status = true; + redrawFooter(); + } + + consoleUpdate(NULL); + } + + consoleExit(NULL); + } + return 0; } diff --git a/src/mod.cpp b/src/mod.cpp index 5d84565..d68f850 100755 --- a/src/mod.cpp +++ b/src/mod.cpp @@ -34,141 +34,188 @@ static ModList g_mod_list; -ModFile ModFile::fromFileName(std::string const &file_name) { - size_t dot_index = file_name.find_last_of('.'); - if (dot_index == std::string::npos) { - return {ModFileType::UNKNOWN}; - } - - std::string base = file_name.substr(0, dot_index); - std::string ext = file_name.substr(dot_index + 1); - std::string suffix; - - base = trim(base); - ext = trim(ext); - - if (ext == EXT_BSA) { - size_t dash_index = base.rfind(" - "); - if (dash_index == std::string::npos) { - suffix = ""; - } else { - suffix = base.substr(dash_index + 3); - base = base.substr(0, dash_index); - } - } else if (ext == EXT_ESP || ext == EXT_ESM) { - suffix = ""; - } else { - return {ModFileType::UNKNOWN}; - } - - ModFileType type; - if (ext == EXT_ESM) { - type = ModFileType::ESM; - } else if (ext == EXT_ESP) { - type = ModFileType::ESP; - } else if (ext == EXT_BSA) { - type = ModFileType::BSA; - } else { - type = ModFileType::UNKNOWN; - } - - return {type, base, suffix}; +ModFile ModFile::fromFileName(std::string const &file_name) +{ + size_t dot_index = file_name.find_last_of('.'); + if (dot_index == std::string::npos) + { + return {ModFileType::UNKNOWN}; + } + + std::string base = file_name.substr(0, dot_index); + std::string ext = file_name.substr(dot_index + 1); + std::string suffix; + + base = trim(base); + ext = trim(ext); + + if (ext == EXT_BSA) + { + size_t dash_index = base.rfind(" - "); + if (dash_index == std::string::npos) + { + suffix = ""; + } + else + { + suffix = base.substr(dash_index + 3); + base = base.substr(0, dash_index); + } + } + else if (ext == EXT_ESP || ext == EXT_ESM) + { + suffix = ""; + } + else + { + return {ModFileType::UNKNOWN}; + } + + ModFileType type; + if (ext == EXT_ESM) + { + type = ModFileType::ESM; + } + else if (ext == EXT_ESP) + { + type = ModFileType::ESP; + } + else if (ext == EXT_BSA) + { + type = ModFileType::BSA; + } + else + { + type = ModFileType::UNKNOWN; + } + + return {type, base, suffix}; } -ModStatus SkyrimMod::getStatus(void) { - bool esp_status = has_esp ? esp_enabled : true; - - ModStatus bsa_status; - if (bsa_suffixes.size() > 0 && enabled_bsas.size() == 0) { - bsa_status = ModStatus::DISABLED; - } else if (bsa_suffixes.size() != enabled_bsas.size()) { - bsa_status = ModStatus::PARTIAL; - } else { - bool bad_anims = false; - for (auto bsa_pair : enabled_bsas) { - if (bsa_pair.first.find("Animations") == 0 && bsa_pair.second != 2) { - bsa_status = ModStatus::PARTIAL; - bad_anims = true; - break; - } - } - - if (!bad_anims) { - bsa_status = ModStatus::ENABLED; - } - } - - switch (bsa_status) { - case ModStatus::PARTIAL: - return ModStatus::PARTIAL; - case ModStatus::DISABLED: - return (esp_status && has_esp) ? ModStatus::PARTIAL : ModStatus::DISABLED; - case ModStatus::ENABLED: - return esp_status ? ModStatus::ENABLED : (bsa_suffixes.empty() ? ModStatus::DISABLED : ModStatus::PARTIAL); - default: - PANIC(); - return ModStatus::DISABLED; - } +ModStatus SkyrimMod::getStatus(void) +{ + bool esp_status = has_esp ? esp_enabled : true; + + ModStatus bsa_status; + if (bsa_suffixes.size() > 0 && enabled_bsas.size() == 0) + { + bsa_status = ModStatus::DISABLED; + } + else if (bsa_suffixes.size() != enabled_bsas.size()) + { + bsa_status = ModStatus::PARTIAL; + } + else + { + bool bad_anims = false; + for (auto bsa_pair : enabled_bsas) + { + if (bsa_pair.first.find("Animations") == 0 && bsa_pair.second != 2) + { + bsa_status = ModStatus::PARTIAL; + bad_anims = true; + break; + } + } + + if (!bad_anims) + { + bsa_status = ModStatus::ENABLED; + } + } + + switch (bsa_status) + { + case ModStatus::PARTIAL: + return ModStatus::PARTIAL; + case ModStatus::DISABLED: + return (esp_status && has_esp) ? ModStatus::PARTIAL : ModStatus::DISABLED; + case ModStatus::ENABLED: + return esp_status ? ModStatus::ENABLED : (bsa_suffixes.empty() ? ModStatus::DISABLED : ModStatus::PARTIAL); + default: + PANIC(); + return ModStatus::DISABLED; + } } -void SkyrimMod::enable(void) { - enabled_bsas.clear(); - for (std::string bsa : bsa_suffixes) { - int count = bsa.find("Animations") == 0 ? 2 : 1; - enabled_bsas.insert(std::pair(bsa, count)); - } - - if (has_esp) { - esp_enabled = true; - } +void SkyrimMod::enable(void) +{ + enabled_bsas.clear(); + for (std::string bsa : bsa_suffixes) + { + int count = bsa.find("Animations") == 0 ? 2 : 1; + enabled_bsas.insert(std::pair(bsa, count)); + } + + if (has_esp) + { + esp_enabled = true; + } } -void SkyrimMod::disable(void) { - enabled_bsas.clear(); - esp_enabled = false; +void SkyrimMod::disable(void) +{ + enabled_bsas.clear(); + esp_enabled = false; } -void SkyrimMod::loadSooner(void) { - for (auto it = g_mod_list.begin(); it != g_mod_list.end(); it++) { - if ((*it)->base_name == this->base_name) { - if (it == g_mod_list.begin()) { - // mod is already first, nothing to do - return; - } else { - // swap it with the previous mod - std::iter_swap(it, it - 1); - return; - } - } - } +void SkyrimMod::loadSooner(void) +{ + for (auto it = g_mod_list.begin(); it != g_mod_list.end(); it++) + { + if ((*it)->base_name == this->base_name) + { + if (it == g_mod_list.begin()) + { + // mod is already first, nothing to do + return; + } + else + { + // swap it with the previous mod + std::iter_swap(it, it - 1); + return; + } + } + } } -void SkyrimMod::loadLater(void) { - for (auto it = g_mod_list.begin(); it != g_mod_list.end(); it++) { - if ((*it)->base_name == this->base_name) { - if (it == g_mod_list.end() - 1) { - // mod is already last, nothing to do - return; - } else { - // swap it with the next mod - std::iter_swap(it, it + 1); - return; - } - } - } +void SkyrimMod::loadLater(void) +{ + for (auto it = g_mod_list.begin(); it != g_mod_list.end(); it++) + { + if ((*it)->base_name == this->base_name) + { + if (it == g_mod_list.end() - 1) + { + // mod is already last, nothing to do + return; + } + else + { + // swap it with the next mod + std::iter_swap(it, it + 1); + return; + } + } + } } -ModList &getGlobalModList(void) { - return g_mod_list; +ModList &getGlobalModList(void) +{ + return g_mod_list; } -std::shared_ptr find_mod(ModList const &mod_list, std::string const &name) { - std::shared_ptr mod; - for (std::shared_ptr entry : mod_list) { - if (entry->base_name == name) { - mod = entry; - break; - } - } - return mod; +std::shared_ptr find_mod(ModList const &mod_list, std::string const &name) +{ + std::shared_ptr mod; + for (std::shared_ptr entry : mod_list) + { + if (entry->base_name == name) + { + mod = entry; + break; + } + } + return mod; } diff --git a/src/path_helper.cpp b/src/path_helper.cpp index b280cd6..885b7fa 100755 --- a/src/path_helper.cpp +++ b/src/path_helper.cpp @@ -5,53 +5,94 @@ #include -#define SPL_CONFIG_EXO_VERSION ((SplConfigItem) 65000) +#define SPL_CONFIG_EXO_VERSION ((SplConfigItem)65000) static bool initted = false; static bool newRomfsPath = false; -static void _init(void) { - splInitialize(); - u64 ver = 0; - splGetConfig(SPL_CONFIG_EXO_VERSION, &ver); - splExit(); - - u32 exoMajor = (ver >> 56) & 0xFF; - u32 exoMinor = (ver >> 48) & 0xFF; - u32 exoMicro = (ver >> 40) & 0xFF; - - // AMS 0.10.0 changed the RomFS directory - if (exoMajor > 0 || (exoMinor >= 10)) { - newRomfsPath = true; - } else { - newRomfsPath = false; - } +static void _init(void) +{ + splInitialize(); + u64 ver = 0; + splGetConfig(SPL_CONFIG_EXO_VERSION, &ver); + splExit(); + + u32 exoMajor = (ver >> 56) & 0xFF; + u32 exoMinor = (ver >> 48) & 0xFF; + u32 exoMicro = (ver >> 40) & 0xFF; + + // AMS 0.10.0 changed the RomFS directory + if (exoMajor > 0 || (exoMinor >= 10)) + { + newRomfsPath = true; + } + else + { + newRomfsPath = false; + } +} + +const char *getBaseConfigPath(void) +{ + if (!initted) + { + _init(); + } + + return CONFIG_DIR; + +} + +std::string getConfigPath(std::string &partial) +{ + return getConfigPath(partial.c_str()); +} + +std::string getConfigPath(const char *partial) +{ + if (!initted) + { + _init(); + } + + return std::string(CONFIG_DIR) + "/" + partial; } -const char *getBaseRomfsPath(void) { - if (!initted) { - _init(); - } +const char *getBaseRomfsPath(void) +{ + if (!initted) + { + _init(); + } - if (newRomfsPath) { - return SKYRIM_ROMFS_DIR; - } else { - return SKYRIM_ROMFS_DIR_OLD; - } + if (newRomfsPath) + { + return SKYRIM_ROMFS_DIR; + } + else + { + return SKYRIM_ROMFS_DIR_OLD; + } } -std::string getRomfsPath(std::string &partial) { - return getRomfsPath(partial.c_str()); +std::string getRomfsPath(std::string &partial) +{ + return getRomfsPath(partial.c_str()); } -std::string getRomfsPath(const char *partial) { - if (!initted) { - _init(); - } +std::string getRomfsPath(const char *partial) +{ + if (!initted) + { + _init(); + } - if (newRomfsPath) { - return std::string(SKYRIM_ROMFS_DIR) + "/" + partial; - } else { - return std::string(SKYRIM_ROMFS_DIR_OLD) + "/" + partial; - } + if (newRomfsPath) + { + return std::string(SKYRIM_ROMFS_DIR) + "/" + partial; + } + else + { + return std::string(SKYRIM_ROMFS_DIR_OLD) + "/" + partial; + } } diff --git a/src/tab_mods.cpp b/src/tab_mods.cpp new file mode 100644 index 0000000..2079035 --- /dev/null +++ b/src/tab_mods.cpp @@ -0,0 +1,247 @@ +#include "tab_mods.hpp" +std::string g_prev_mod; +std::string g_sel_mod; +bool g_dialog_open; +tab_mods::tab_mods() +{ + setModsTabPtr(this); + this->triggerUpdateListItems = false; + this->triggerUpdateModsDisplayedStatus = false; + this->frameCounter = -1; + this->clear(); + // Setup the list + auto mod_items_list = getGlobalModList(); + for (int i_mod = 0; i_mod < int(mod_items_list.size()); i_mod++) + { + std::string selected_mod = mod_items_list[i_mod].get()->base_name; + brls::Logger::debug("Adding mod: {}", selected_mod.c_str()); + ModListItem *item = new ModListItem(selected_mod, "", ""); + item->getClickEvent()->subscribe([this, item](View *view) { + if (!g_dialog_open) + { + auto *dialog = new brls::Dialog(fmt::format("sky/dialog/enable"_i18n, item->getLabel())); + + dialog->addButton("sky/dialog/yes"_i18n, [item, dialog, this](brls::View *view) { + std::shared_ptr mod = find_mod(getGlobalModList(), item->getLabel()); + switch (mod->getStatus()) + { + case ModStatus::ENABLED: + { + mod->enable(); + g_dialog_open = false; + dialog->close(); + break; + } + case ModStatus::PARTIAL: + { + mod->enable(); + g_dialog_open = false; + dialog->close(); + break; + } + case ModStatus::DISABLED: + { + mod->enable(); + g_dialog_open = false; + dialog->close(); + break; + } + default: + break; + } + this->triggerUpdateModsDisplayedStatus = true; + g_dirty = true; + clearTempEffects(); + }); + dialog->addButton("sky/dialog/no"_i18n, [dialog](brls::View *view) { + g_dialog_open = false; + dialog->close(); + }); + dialog->registerAction("brls/hints/back"_i18n, Key::B, [dialog] { g_dialog_open = false; return dialog->onCancel(); }); + dialog->setCancelable(true); + g_dialog_open = true; + dialog->open(); + } + + return true; + }); + item->updateActionHint(brls::Key::A, "sky/hints/enable"_i18n); + + item->registerAction("sky/hints/disable"_i18n, brls::Key::X, [this, item] { + if (!g_dialog_open) + { + auto *dialog = new brls::Dialog("sky/dialog/disable1"_i18n + item->getLabel() + "sky/dialog/disable2"_i18n); + + dialog->addButton("sky/dialog/yes"_i18n, [item, dialog, this](brls::View *view) { + std::shared_ptr mod = find_mod(getGlobalModList(), item->getLabel()); + switch (mod->getStatus()) + { + case ModStatus::ENABLED: + { + mod->disable(); + g_dialog_open = false; + dialog->close(); + break; + } + case ModStatus::PARTIAL: + { + mod->disable(); + g_dialog_open = false; + dialog->close(); + break; + } + case ModStatus::DISABLED: + { + mod->disable(); + g_dialog_open = false; + dialog->close(); + break; + } + default: + break; + } + this->triggerUpdateModsDisplayedStatus = true; + g_dirty = true; + clearTempEffects(); + }); + dialog->addButton("sky/dialog/no"_i18n, [dialog](brls::View *view) { + g_dialog_open = false; + dialog->close(); + }); + dialog->registerAction("brls/hints/back"_i18n, Key::B, [dialog] { g_dialog_open = false; return dialog->onCancel(); }); + dialog->setCancelable(true); + g_dialog_open = true; + dialog->open(); + } + return true; + }); + + this->addView(item); + _modsListItems_.push_back(item); + this->setTriggerUpdateModsDisplayedStatus(true); + } + + if (mod_items_list.empty()) + { + + auto *emptyListLabel = new brls::ListItem(fmt::format("sky/msg/missing"_i18n, getRomfsPath("Data"))); + emptyListLabel->show([]() {}, false); + this->addView(emptyListLabel); + } + else + { + this->registerAction( + "sky/hints/edit"_i18n, brls::Key::Y, [] { + if (g_edit_load_order) + { + g_status_msg = VERSION; + g_edit_load_order = false; + } + else + { + g_status_msg = "sky/msg/edit"_i18n; + g_edit_load_order = true; + } + return true; + }); + } +} +void tab_mods::updateListItems() +{ + for (auto it = getModsListItems().begin(); it != getModsListItems().end(); it++) + { + (*it)->setLabel(getGlobalModList().at(std::distance(getModsListItems().begin(), it)).get()->base_name); + } + this->setTriggerUpdateModsDisplayedStatus(true); +} +std::vector &tab_mods::getModsListItems() +{ + return _modsListItems_; +} +void tab_mods::onChildFocusLost(View *child) +{ + View::onChildFocusLost(child); +} + +void tab_mods::onChildFocusGained(View *child) +{ + if (g_edit_load_order) + { + ModList::iterator old_i = getGlobalModList().end(); + ModList::iterator new_i = getGlobalModList().end(); + for (auto it = getGlobalModList().begin(); it != getGlobalModList().end(); it++) + { + if ((*it)->base_name == g_prev_mod) + { + old_i = it; + } + if ((*it)->base_name == g_sel_mod) + { + new_i = it; + } + if (old_i != getGlobalModList().end() && new_i != getGlobalModList().end()) + { + break; + } + } + std::iter_swap(old_i, new_i); + g_dirty = true; + this->setTriggerUpdateListItems(true); + g_edit_load_order = false; + g_status_msg = VERSION; + } + ScrollView::onChildFocusGained(child); +} +void tab_mods::draw(NVGcontext *vg, int x, int y, unsigned int width, unsigned int height, brls::Style *style, + brls::FrameContext *ctx) +{ + + ScrollView::draw(vg, x, y, width, height, style, ctx); + + if (this->triggerUpdateModsDisplayedStatus) + { + this->updateDisplayedModsStatus(); + this->triggerUpdateModsDisplayedStatus = false; + } + + if (this->triggerUpdateListItems) + { + this->updateListItems(); + this->triggerUpdateListItems = false; + } +} + +void tab_mods::setTriggerUpdateModsDisplayedStatus(bool triggerUpdateModsDisplayedStatus_) +{ + tab_mods::triggerUpdateModsDisplayedStatus = triggerUpdateModsDisplayedStatus_; +} + +void tab_mods::setTriggerUpdateListItems(bool triggerUpdateListItems_) +{ + tab_mods::triggerUpdateListItems = triggerUpdateListItems_; +} +void tab_mods::updateDisplayedModsStatus() +{ + for (auto it = getModsListItems().begin(); it != getModsListItems().end(); it++) + { + std::string reference_str = (*it)->getLabel(); + std::shared_ptr mod = find_mod(getGlobalModList(), reference_str); + NVGcolor color; + switch (mod->getStatus()) + { + case ModStatus::ENABLED: + (*it)->setValue("sky/status/enabled"_i18n); + color = nvgRGB(88, 195, 169); + break; + case ModStatus::PARTIAL: + (*it)->setValue("sky/status/partial"_i18n); + color = nvgRGB(245 * 0.85, 198 * 0.85, 59 * 0.85); + break; + case ModStatus::DISABLED: + (*it)->setValue("sky/status/disabled"_i18n); + color = nvgRGB(80, 80, 80); + break; + } + (*it)->setValueActiveColor(color); + } +} \ No newline at end of file