├── .gitattributes ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── icon.jpg ├── include ├── config.hpp ├── fs.hpp ├── gui.hpp ├── imgui_impl_switch.hpp ├── keyboard.hpp ├── language.hpp ├── log.hpp ├── net.hpp ├── popups.hpp ├── tabs.hpp ├── textures.hpp ├── usb.hpp ├── utils.hpp └── windows.hpp ├── libs ├── lib │ ├── liblwext4.a │ ├── libntfs-3g.a │ └── libusbhsfs.a ├── libnsbmp │ ├── COPYING │ ├── libnsbmp.c │ ├── libnsbmp.h │ └── utils │ │ └── log.h ├── stb_image.h └── usbhsfs.h ├── res ├── archive.png ├── check.png ├── file.png ├── folder.png ├── image.png ├── text.png └── uncheck.png └── source ├── config.cpp ├── fs.cpp ├── gui.cpp ├── image.cpp ├── imgui_impl_switch.cpp ├── keyboard.cpp ├── language.cpp ├── log.cpp ├── main.cpp ├── net.cpp ├── popups ├── delete.cpp ├── options.cpp ├── progressbar.cpp ├── properties.cpp ├── update.cpp └── usbprompt.cpp ├── tabs ├── filebrowser.cpp └── settings.cpp ├── textures.cpp ├── usb.cpp ├── utils.cpp └── window.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [ next-opengl3 ] 6 | pull_request: 7 | branches: [ next-opengl3 ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | container: devkitpro/devkita64:latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Build 21 | run: make 22 | 23 | - uses: actions/upload-artifact@v3 24 | with: 25 | name: NX-Shell.nro 26 | path: NX-Shell.nro 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # NX-Shell 35 | *.nro 36 | *.elf 37 | *.nacp 38 | build/ 39 | res/shaders 40 | !libs/lib/liblwext4.a 41 | !libs/lib/libntfs-3g.a 42 | !libs/lib/libusbhsfs.a 43 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/imgui"] 2 | path = libs/imgui 3 | url = https://github.com/ocornut/imgui.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITPRO)/libnx/switch_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 19 | # 20 | # NO_ICON: if set to anything, do not use icon. 21 | # NO_NACP: if set to anything, no .nacp file is generated. 22 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 23 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 24 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 25 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 26 | # ICON is the filename of the icon (.jpg), relative to the project folder. 27 | # If not set, it attempts to use one of the following (in this order): 28 | # - .jpg 29 | # - icon.jpg 30 | # - /default_icon.jpg 31 | # 32 | # CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder. 33 | # If not set, it attempts to use one of the following (in this order): 34 | # - .json 35 | # - config.json 36 | # If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead 37 | # of a homebrew executable (.nro). This is intended to be used for sysmodules. 38 | # NACP building is skipped as well. 39 | #--------------------------------------------------------------------------------- 40 | TARGET := $(notdir $(CURDIR)) 41 | BUILD := build 42 | SOURCES := libs/imgui libs/imgui/misc/freetype libs/libnsbmp source source/imgui_nx source/popups source/tabs 43 | DATA := data 44 | INCLUDES := include include/imgui_nx libs/imgui libs/imgui/misc/freetype libs/libnsbmp libs/ 45 | ROMFS := res 46 | 47 | # Output folders for autogenerated files in romfs 48 | OUT_SHADERS := shaders 49 | 50 | VERSION_MAJOR := 4 51 | VERSION_MINOR := 0 52 | VERSION_MICRO := 1 53 | 54 | APP_TITLE := NX-Shell 55 | APP_AUTHOR := Joel16 56 | APP_VERSION := ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_MICRO} 57 | 58 | EXT_LIBS := $(CURDIR)/libs 59 | 60 | #--------------------------------------------------------------------------------- 61 | # options for code generation 62 | #--------------------------------------------------------------------------------- 63 | ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE 64 | 65 | CFLAGS := -g -Wall -O3 -ffunction-sections -Wno-restrict $(ARCH) $(DEFINES) 66 | 67 | CFLAGS += $(INCLUDE) -D__SWITCH__ 68 | CFLAGS += `freetype-config --cflags` 69 | CFLAGS += -DVERSION_MAJOR=$(VERSION_MAJOR) -DVERSION_MINOR=$(VERSION_MINOR) -DVERSION_MICRO=$(VERSION_MICRO) 70 | CFLAGS += -DIMGUI_IMPL_OPENGL_LOADER_GLAD -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS -DIMGUI_DISABLE_DEMO_WINDOWS 71 | CFLAGS += -DIMGUI_DISABLE_DEBUG_TOOLS -DIMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS 72 | CFLAGS += -DIMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS -DIMGUI_DISABLE_WIN32_FUNCTIONS 73 | CFLAGS += -DIMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION -DIMGUI_ENABLE_FREETYPE 74 | 75 | CXXFLAGS := $(CFLAGS) -std=gnu++20 -fno-exceptions -fno-rtti 76 | 77 | ASFLAGS := -g $(ARCH) 78 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) 79 | 80 | LIBS := `curl-config --libs` `freetype-config --libs` -lgif -lturbojpeg -ljpeg -lpng -lwebp -ljansson \ 81 | -lglad -lEGL -lglapi -ldrm_nouveau -lusbhsfs -llwext4 -lntfs-3g -lnx -lm -lz 82 | 83 | #--------------------------------------------------------------------------------- 84 | # list of directories containing libraries, this must be the top level containing 85 | # include and lib 86 | #--------------------------------------------------------------------------------- 87 | LIBDIRS := $(PORTLIBS) $(LIBNX) $(EXT_LIBS) 88 | 89 | 90 | #--------------------------------------------------------------------------------- 91 | # no real need to edit anything past this point unless you need to add additional 92 | # rules for different file extensions 93 | #--------------------------------------------------------------------------------- 94 | ifneq ($(BUILD),$(notdir $(CURDIR))) 95 | #--------------------------------------------------------------------------------- 96 | 97 | export OUTPUT := $(CURDIR)/$(TARGET) 98 | export TOPDIR := $(CURDIR) 99 | 100 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 101 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 102 | 103 | export DEPSDIR := $(CURDIR)/$(BUILD) 104 | 105 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 106 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 107 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 108 | GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl))) 109 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 110 | 111 | #--------------------------------------------------------------------------------- 112 | # use CXX for linking C++ projects, CC for standard C 113 | #--------------------------------------------------------------------------------- 114 | ifeq ($(strip $(CPPFILES)),) 115 | #--------------------------------------------------------------------------------- 116 | export LD := $(CC) 117 | #--------------------------------------------------------------------------------- 118 | else 119 | #--------------------------------------------------------------------------------- 120 | export LD := $(CXX) 121 | #--------------------------------------------------------------------------------- 122 | endif 123 | #--------------------------------------------------------------------------------- 124 | 125 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 126 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 127 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 128 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 129 | 130 | ifneq ($(strip $(ROMFS)),) 131 | ROMFS_TARGETS := 132 | ROMFS_FOLDERS := 133 | ifneq ($(strip $(OUT_SHADERS)),) 134 | ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS) 135 | ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES)) 136 | ROMFS_FOLDERS += $(ROMFS_SHADERS) 137 | endif 138 | 139 | export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file)) 140 | endif 141 | 142 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 143 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 144 | -I$(CURDIR)/$(BUILD) 145 | 146 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 147 | 148 | ifeq ($(strip $(CONFIG_JSON)),) 149 | jsons := $(wildcard *.json) 150 | ifneq (,$(findstring $(TARGET).json,$(jsons))) 151 | export APP_JSON := $(TOPDIR)/$(TARGET).json 152 | else 153 | ifneq (,$(findstring config.json,$(jsons))) 154 | export APP_JSON := $(TOPDIR)/config.json 155 | endif 156 | endif 157 | else 158 | export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) 159 | endif 160 | 161 | ifeq ($(strip $(ICON)),) 162 | icons := $(wildcard *.jpg) 163 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 164 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 165 | else 166 | ifneq (,$(findstring icon.jpg,$(icons))) 167 | export APP_ICON := $(TOPDIR)/icon.jpg 168 | endif 169 | endif 170 | else 171 | export APP_ICON := $(TOPDIR)/$(ICON) 172 | endif 173 | 174 | ifeq ($(strip $(NO_ICON)),) 175 | export NROFLAGS += --icon=$(APP_ICON) 176 | endif 177 | 178 | ifeq ($(strip $(NO_NACP)),) 179 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 180 | endif 181 | 182 | ifneq ($(APP_TITLEID),) 183 | export NACPFLAGS += --titleid=$(APP_TITLEID) 184 | endif 185 | 186 | ifneq ($(ROMFS),) 187 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 188 | endif 189 | 190 | .PHONY: all clean 191 | 192 | #--------------------------------------------------------------------------------- 193 | all: $(ROMFS_TARGETS) | $(BUILD) 194 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 195 | 196 | $(BUILD): 197 | @mkdir -p $@ 198 | 199 | ifneq ($(strip $(ROMFS_TARGETS)),) 200 | 201 | $(ROMFS_TARGETS): | $(ROMFS_FOLDERS) 202 | 203 | $(ROMFS_FOLDERS): 204 | @mkdir -p $@ 205 | 206 | $(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl 207 | @echo {vert} $(notdir $<) 208 | @uam -s vert -o $@ $< 209 | 210 | $(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl 211 | @echo {tess_ctrl} $(notdir $<) 212 | @uam -s tess_ctrl -o $@ $< 213 | 214 | $(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl 215 | @echo {tess_eval} $(notdir $<) 216 | @uam -s tess_eval -o $@ $< 217 | 218 | $(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl 219 | @echo {geom} $(notdir $<) 220 | @uam -s geom -o $@ $< 221 | 222 | $(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl 223 | @echo {frag} $(notdir $<) 224 | @uam -s frag -o $@ $< 225 | 226 | $(ROMFS_SHADERS)/%.dksh: %.glsl 227 | @echo {comp} $(notdir $<) 228 | @uam -s comp -o $@ $< 229 | 230 | endif 231 | 232 | #--------------------------------------------------------------------------------- 233 | clean: 234 | @echo clean ... 235 | ifeq ($(strip $(APP_JSON)),) 236 | @rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nro $(TARGET).nacp $(TARGET).elf 237 | else 238 | @rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf 239 | endif 240 | 241 | 242 | #--------------------------------------------------------------------------------- 243 | else 244 | .PHONY: all 245 | 246 | DEPENDS := $(OFILES:.o=.d) 247 | 248 | #--------------------------------------------------------------------------------- 249 | # main targets 250 | #--------------------------------------------------------------------------------- 251 | ifeq ($(strip $(APP_JSON)),) 252 | 253 | all : $(OUTPUT).nro 254 | 255 | ifeq ($(strip $(NO_NACP)),) 256 | $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS) 257 | else 258 | $(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS) 259 | endif 260 | 261 | else 262 | 263 | all : $(OUTPUT).nsp 264 | 265 | $(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm 266 | 267 | $(OUTPUT).nso : $(OUTPUT).elf 268 | 269 | endif 270 | 271 | $(OUTPUT).elf : $(OFILES) 272 | 273 | $(OFILES_SRC) : $(HFILES_BIN) 274 | 275 | #--------------------------------------------------------------------------------- 276 | # you need a rule like this for each extension you use as binary data 277 | #--------------------------------------------------------------------------------- 278 | %.bin.o %_bin.h : %.bin 279 | #--------------------------------------------------------------------------------- 280 | @echo $(notdir $<) 281 | @$(bin2o) 282 | 283 | -include $(DEPENDS) 284 | 285 | #--------------------------------------------------------------------------------------- 286 | endif 287 | #--------------------------------------------------------------------------------------- 288 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NX-Shell ![Github latest downloads](https://img.shields.io/github/downloads/joel16/NX-Shell/total.svg) 2 | 3 | NX Shell is a multi-purpose file manager for the Nintendo Switch that aims towards handling various file types while keeping the basic necessities of a standard file manager. Initially, the project was inspired by LineageOS/CyanogenMod's file manager for android, and even had a similar design approach to that of the famous Android file manager. However, it has been re-written from scratch, now using more up to date tools and libraries. 4 | 5 |

6 | NX-Shell (Next) Screenshot 7 |

8 | 9 | # Features: 10 | 11 | - Delete files and folders. 12 | - Copy/Move files and folders. 13 | - Rename files and folders (standard switch keyboard). 14 | - Create files and folders (standard switch keyboard). 15 | - Displays file properties such as size, time created, modified and accessed. 16 | - Sorting options (Sort by name, date, size). 17 | - Display various images formats (BMP, GIF, JPG/JPEG, PGM, PPM, PNG, PSD, TGA and WEBP). 18 | - Browse devices such as safe, user, system and USB. 19 | 20 | # Credits: 21 | 22 | - **Preetisketch** for the banner. 23 | - **Dear ImGui developers and contributors** for the GUI. 24 | - **devkitPro maintainers and contributors** for libnx, devkitA64, and many other packages used by this project. 25 | - **DarkMatterCore** for libusbhsfs. 26 | -------------------------------------------------------------------------------- /icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/icon.jpg -------------------------------------------------------------------------------- /include/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef struct { 7 | int lang = 1; 8 | bool dev_options = false; 9 | bool image_filename = false; 10 | bool multi_lang = false; 11 | } config_t; 12 | 13 | extern config_t cfg; 14 | extern std::string cwd; 15 | extern std::string device; 16 | 17 | namespace Config { 18 | int Save(config_t &config); 19 | int Load(void); 20 | } 21 | -------------------------------------------------------------------------------- /include/fs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef enum FileType { 8 | FileTypeNone, 9 | FileTypeArchive, 10 | FileTypeImage, 11 | FileTypeText 12 | } FileType; 13 | 14 | typedef enum FileSystemDevices { 15 | FileSystemSDMC, 16 | FileSystemSafe, 17 | FileSystemUser, 18 | FileSystemSystem, 19 | FileSystemMax 20 | } FileSystemDevices; 21 | 22 | extern FsFileSystem *fs; 23 | extern FsFileSystem devices[FileSystemMax]; 24 | 25 | namespace FS { 26 | bool FileExists(const std::string &path); 27 | bool DirExists(const std::string &path); 28 | bool GetFileSize(const std::string &path, std::size_t &size); 29 | bool GetDirList(const std::string &device, const std::string &path, std::vector &entries); 30 | bool ChangeDirNext(const std::string &path, std::vector &entries); 31 | bool ChangeDirPrev(std::vector &entries); 32 | bool GetTimeStamp(FsDirectoryEntry &entry, FsTimeStampRaw ×tamp); 33 | bool Rename(FsDirectoryEntry &entry, const std::string &dest_path); 34 | bool Delete(FsDirectoryEntry &entry); 35 | void Copy(FsDirectoryEntry &entry, const std::string &path); 36 | bool Paste(void); 37 | bool Move(void); 38 | FileType GetFileType(const std::string &filename); 39 | Result SetArchiveBit(const std::string &path); 40 | Result GetFreeStorageSpace(s64 &size); 41 | Result GetTotalStorageSpace(s64 &size); 42 | Result GetUsedStorageSpace(s64 &size); 43 | std::string BuildPath(FsDirectoryEntry &entry); 44 | std::string BuildPath(const std::string &path, bool device_name); 45 | std::string GetFileExt(const std::string &filename); 46 | } 47 | -------------------------------------------------------------------------------- /include/gui.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace GUI { 4 | bool Init(void); 5 | bool SwapBuffers(void); 6 | bool Loop(u64 &key); 7 | void Render(void); 8 | void Exit(void); 9 | } 10 | -------------------------------------------------------------------------------- /include/imgui_impl_switch.hpp: -------------------------------------------------------------------------------- 1 | // dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline 2 | // - Desktop GL: 2.x 3.x 4.x 3 | // - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0) 4 | // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) 5 | 6 | // Implemented features: 7 | // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! 8 | // [x] Renderer: Desktop GL only: Support for large meshes (64k+ vertices) with 16-bit indices. 9 | 10 | // You can use unmodified imgui_impl_ *files in your project. See examples/ folder for examples of using this. 11 | // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. 12 | // If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp. 13 | // Read online: https://github.com/ocornut/imgui/tree/master/docs 14 | 15 | // About GLSL version: 16 | // The 'glsl_version' initialization parameter should be NULL (default) or a "#version XXX" string. 17 | // On computer platform the GLSL version default to "#version 130". On OpenGL ES 3 platform it defaults to "#version 300 es" 18 | // Only override if your GL version doesn't handle this GLSL version. See GLSL version table at the top of imgui_impl_opengl3.cpp. 19 | 20 | #pragma once 21 | #include 22 | 23 | #include "imgui.h" // IMGUI_IMPL_API 24 | 25 | // Backend API 26 | IMGUI_IMPL_API bool ImGui_ImplSwitch_Init(const char *glsl_version = nullptr); 27 | IMGUI_IMPL_API void ImGui_ImplSwitch_Shutdown(void); 28 | IMGUI_IMPL_API u64 ImGui_ImplSwitch_NewFrame(void); 29 | IMGUI_IMPL_API void ImGui_ImplSwitch_RenderDrawData(ImDrawData *draw_data); 30 | 31 | // (Optional) Called by Init/NewFrame/Shutdown 32 | IMGUI_IMPL_API bool ImGui_ImplSwitch_CreateFontsTexture(void); 33 | IMGUI_IMPL_API void ImGui_ImplSwitch_DestroyFontsTexture(void); 34 | IMGUI_IMPL_API bool ImGui_ImplSwitch_CreateDeviceObjects(void); 35 | IMGUI_IMPL_API void ImGui_ImplSwitch_DestroyDeviceObjects(void); 36 | 37 | // Specific OpenGL ES versions 38 | //#define IMGUI_IMPL_OPENGL_ES2 // Auto-detected on Emscripten 39 | //#define IMGUI_IMPL_OPENGL_ES3 // Auto-detected on iOS/Android 40 | 41 | // You can explicitly select GLES2 or GLES3 API by using one of the '#define IMGUI_IMPL_OPENGL_LOADER_XXX' in imconfig.h or compiler command-line. 42 | #if !defined(IMGUI_IMPL_OPENGL_ES2) \ 43 | && !defined(IMGUI_IMPL_OPENGL_ES3) 44 | 45 | // Try to detect GLES on matching platforms 46 | #if defined(__APPLE__) 47 | #include 48 | #endif 49 | #if (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_OS_TV)) || (defined(__ANDROID__)) 50 | #define IMGUI_IMPL_OPENGL_ES3 // iOS, Android -> GL ES 3, "#version 300 es" 51 | #elif defined(__EMSCRIPTEN__) || defined(__amigaos4__) 52 | #define IMGUI_IMPL_OPENGL_ES2 // Emscripten -> GL ES 2, "#version 100" 53 | #else 54 | // Otherwise imgui_impl_opengl3_loader.h will be used. 55 | #endif 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /include/keyboard.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Keyboard { 6 | std::string GetText(const std::string &guide_text, const std::string &initial_text); 7 | } 8 | -------------------------------------------------------------------------------- /include/language.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Lang { 4 | typedef enum { 5 | // Prompt/Message buttons 6 | ButtonOK = 0, 7 | ButtonCancel, 8 | 9 | // Options dialog 10 | OptionsTitle, 11 | OptionsSelectAll, 12 | OptionsClearAll, 13 | OptionsProperties, 14 | OptionsRename, 15 | OptionsNewFolder, 16 | OptionsNewFile, 17 | OptionsCopy, 18 | OptionsMove, 19 | OptionsPaste, 20 | OptionsDelete, 21 | OptionsSetArchiveBit, 22 | OptionsRenamePrompt, 23 | OptionsFolderPrompt, 24 | OptionsFilePrompt, 25 | OptionsCopying, 26 | 27 | // Properties dialog 28 | PropertiesName, 29 | PropertiesSize, 30 | PropertiesCreated, 31 | PropertiesModified, 32 | PropertiesAccessed, 33 | PropertiesWidth, 34 | PropertiesHeight, 35 | 36 | // Delete dialog 37 | DeleteMessage, 38 | DeleteMultiplePrompt, 39 | DeletePrompt, 40 | 41 | // Archive dialog 42 | ArchiveTitle, 43 | ArchiveMessage, 44 | ArchivePrompt, 45 | ArchiveExtracting, 46 | 47 | // SettingsWindow 48 | SettingsTitle, 49 | SettingsSortTitle, 50 | SettingsLanguageTitle, 51 | SettingsUSBTitle, 52 | SettingsUSBUnmount, 53 | SettingsImageViewTitle, 54 | SettingsDevOptsTitle, 55 | SettingsMultiLangTitle, 56 | SettingsAboutTitle, 57 | SettingsCheckForUpdates, 58 | SettingsImageViewFilenameToggle, 59 | SettingsDevOptsLogsToggle, 60 | SettingsMultiLangLogsToggle, 61 | SettingsAboutVersion, 62 | SettingsAboutAuthor, 63 | SettingsAboutBanner, 64 | 65 | // Updates Dialog 66 | UpdateTitle, 67 | UpdateNetworkError, 68 | UpdateAvailable, 69 | UpdatePrompt, 70 | UpdateSuccess, 71 | UpdateRestart, 72 | UpdateNotAvailable, 73 | 74 | // USB Dialog 75 | USBUnmountPrompt, 76 | USBUnmountSuccess, 77 | 78 | // Keyboard 79 | KeyboardEmpty, 80 | 81 | // Max 82 | Max 83 | } StringID; 84 | } 85 | 86 | extern const char **strings[Lang::Max]; 87 | -------------------------------------------------------------------------------- /include/log.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Log { 4 | void Init(void); 5 | void Error(const char *data, ...); 6 | void Exit(void); 7 | } 8 | -------------------------------------------------------------------------------- /include/net.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Net { 6 | bool GetNetworkStatus(void); 7 | bool GetAvailableUpdate(const std::string &tag); 8 | std::string GetLatestReleaseJSON(void); 9 | void GetLatestReleaseNRO(const std::string &tag); 10 | } 11 | -------------------------------------------------------------------------------- /include/popups.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "imgui.h" 6 | #include "windows.hpp" 7 | 8 | namespace Popups { 9 | inline void SetupPopup(const char *id) { 10 | ImGui::OpenPopup(id); 11 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(15, 15)); 12 | ImGui::SetNextWindowPos(ImVec2(640.0f, 360.0f), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); 13 | }; 14 | 15 | inline void ExitPopup(void) { 16 | ImGui::EndPopup(); 17 | ImGui::PopStyleVar(); 18 | }; 19 | 20 | void ArchivePopup(void); 21 | void DeletePopup(WindowData &data); 22 | void FilePropertiesPopup(WindowData &data, bool &file_stat); 23 | void ImageProperties(bool &state, Tex &texture, bool &file_stat); 24 | void OptionsPopup(WindowData &data); 25 | void UpdatePopup(bool &state, bool &connection_status, bool &available, const std::string &tag); 26 | void ProgressBar(float offset, float size, const std::string &title, const std::string &text); 27 | void USBPopup(bool &state); 28 | } 29 | -------------------------------------------------------------------------------- /include/tabs.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "imgui.h" 7 | #include "windows.hpp" 8 | 9 | namespace Tabs { 10 | void FileBrowser(WindowData &data); 11 | void Settings(WindowData &data); 12 | } 13 | -------------------------------------------------------------------------------- /include/textures.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | GLuint id = 0; 9 | int width = 0; 10 | int height = 0; 11 | int delay = 0; 12 | } Tex; 13 | 14 | extern std::vector file_icons; 15 | extern Tex folder_icon, check_icon, uncheck_icon; 16 | 17 | namespace Textures { 18 | bool LoadImageFile(const std::string &path, std::vector &textures); 19 | void Free(Tex &texture); 20 | void Init(void); 21 | void Exit(void); 22 | } 23 | -------------------------------------------------------------------------------- /include/usb.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace USB { 7 | Result Init(void); 8 | void Exit(void); 9 | void Unmount(void); 10 | bool Connected(void); 11 | } 12 | -------------------------------------------------------------------------------- /include/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern char __application_path[FS_MAX_PATH]; 6 | 7 | namespace Utils { 8 | void GetSizeString(char *string, double size); 9 | } 10 | -------------------------------------------------------------------------------- /include/windows.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "textures.hpp" 9 | 10 | enum WINDOW_STATES { 11 | WINDOW_STATE_FILEBROWSER = 0, 12 | WINDOW_STATE_SETTINGS, 13 | WINDOW_STATE_OPTIONS, 14 | WINDOW_STATE_DELETE, 15 | WINDOW_STATE_PROPERTIES, 16 | WINDOW_STATE_IMAGEVIEWER, 17 | WINDOW_STATE_ARCHIVEEXTRACT, 18 | WINDOW_STATE_TEXTREADER 19 | }; 20 | 21 | enum FS_SORT_STATE { 22 | FS_SORT_ALPHA_ASC = 0, 23 | FS_SORT_ALPHA_DESC, 24 | FS_SORT_SIZE_ASC, 25 | FS_SORT_SIZE_DESC 26 | }; 27 | 28 | typedef struct { 29 | std::vector checked; 30 | std::vector checked_copy; 31 | std::string cwd = ""; 32 | std::string device = ""; 33 | u64 count = 0; 34 | } WindowCheckboxData; 35 | 36 | typedef struct { 37 | WINDOW_STATES state = WINDOW_STATE_FILEBROWSER; 38 | u64 selected = 0; 39 | std::vector entries; 40 | WindowCheckboxData checkbox_data; 41 | s64 used_storage = 0; 42 | s64 total_storage = 0; 43 | std::vector textures; 44 | long unsigned int frame_count = 0; 45 | float zoom_factor = 1.0f; 46 | } WindowData; 47 | 48 | extern WindowData data; 49 | extern int sort; 50 | extern std::vector devices_list; 51 | extern std::recursive_mutex devices_list_mutex; 52 | 53 | namespace FileBrowser { 54 | bool Sort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB); 55 | bool TableSort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB); 56 | } 57 | 58 | namespace ImageViewer { 59 | void ClearTextures(void); 60 | bool HandleScroll(int index); 61 | bool HandlePrev(void); 62 | bool HandleNext(void); 63 | void HandleControls(u64 &key, bool &properties); 64 | } 65 | 66 | namespace Windows { 67 | void SetupWindow(void); 68 | void ExitWindow(void); 69 | void ResetCheckbox(WindowData &data); 70 | void MainWindow(WindowData &data, u64 &key, bool progress); 71 | void ImageViewer(bool &properties, bool &file_stat); 72 | } 73 | -------------------------------------------------------------------------------- /libs/lib/liblwext4.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/libs/lib/liblwext4.a -------------------------------------------------------------------------------- /libs/lib/libntfs-3g.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/libs/lib/libntfs-3g.a -------------------------------------------------------------------------------- /libs/lib/libusbhsfs.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/libs/lib/libusbhsfs.a -------------------------------------------------------------------------------- /libs/libnsbmp/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2006 Richard Wilson 2 | Copyright (C) 2008 Sean Fox 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | * The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /libs/libnsbmp/libnsbmp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2006 Richard Wilson 3 | * Copyright 2008 Sean Fox 4 | * 5 | * This file is part of NetSurf's libnsbmp, http://www.netsurf-browser.org/ 6 | * Licenced under the MIT License, 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | /** 11 | * \file 12 | * Bitmap file decoding interface. 13 | */ 14 | 15 | #ifndef libnsbmp_h_ 16 | #define libnsbmp_h_ 17 | 18 | #if defined (__cplusplus) 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | /* bmp flags */ 27 | #define BMP_NEW 0 28 | /** image is opaque (as opposed to having an alpha mask) */ 29 | #define BMP_OPAQUE (1 << 0) 30 | /** memory should be wiped */ 31 | #define BMP_CLEAR_MEMORY (1 << 1) 32 | 33 | /** 34 | * error return values 35 | */ 36 | typedef enum { 37 | BMP_OK = 0, 38 | BMP_INSUFFICIENT_MEMORY = 1, 39 | BMP_INSUFFICIENT_DATA = 2, 40 | BMP_DATA_ERROR = 3 41 | } bmp_result; 42 | 43 | /** 44 | * encoding types 45 | */ 46 | typedef enum { 47 | BMP_ENCODING_RGB = 0, 48 | BMP_ENCODING_RLE8 = 1, 49 | BMP_ENCODING_RLE4 = 2, 50 | BMP_ENCODING_BITFIELDS = 3 51 | } bmp_encoding; 52 | 53 | /* API for Bitmap callbacks */ 54 | typedef void* (*bmp_bitmap_cb_create)(int width, int height, unsigned int state); 55 | typedef void (*bmp_bitmap_cb_destroy)(void *bitmap); 56 | typedef unsigned char* (*bmp_bitmap_cb_get_buffer)(void *bitmap); 57 | typedef size_t (*bmp_bitmap_cb_get_bpp)(void *bitmap); 58 | 59 | /** 60 | * The Bitmap callbacks function table 61 | */ 62 | typedef struct bmp_bitmap_callback_vt_s { 63 | /** Callback to allocate bitmap storage. */ 64 | bmp_bitmap_cb_create bitmap_create; 65 | /** Called to free bitmap storage. */ 66 | bmp_bitmap_cb_destroy bitmap_destroy; 67 | /** Return a pointer to the pixel data in a bitmap. */ 68 | bmp_bitmap_cb_get_buffer bitmap_get_buffer; 69 | /** Find the width of a pixel row in bytes. */ 70 | bmp_bitmap_cb_get_bpp bitmap_get_bpp; 71 | } bmp_bitmap_callback_vt; 72 | 73 | /** 74 | * bitmap image 75 | */ 76 | typedef struct bmp_image { 77 | /** callbacks for bitmap functions */ 78 | bmp_bitmap_callback_vt bitmap_callbacks; 79 | /** pointer to BMP data */ 80 | uint8_t *bmp_data; 81 | /** width of BMP (valid after _analyse) */ 82 | uint32_t width; 83 | /** heigth of BMP (valid after _analyse) */ 84 | uint32_t height; 85 | /** whether the image has been decoded */ 86 | bool decoded; 87 | /** decoded image */ 88 | void *bitmap; 89 | 90 | /* Internal members are listed below */ 91 | /** total number of bytes of BMP data available */ 92 | uint32_t buffer_size; 93 | /** pixel encoding type */ 94 | bmp_encoding encoding; 95 | /** offset of bitmap data */ 96 | uint32_t bitmap_offset; 97 | /** bits per pixel */ 98 | uint16_t bpp; 99 | /** number of colours */ 100 | uint32_t colours; 101 | /** colour table */ 102 | uint32_t *colour_table; 103 | /** whether to use bmp's limited transparency */ 104 | bool limited_trans; 105 | /** colour to display for "transparent" pixels when using limited 106 | * transparency 107 | */ 108 | uint32_t trans_colour; 109 | /** scanlines are top to bottom */ 110 | bool reversed; 111 | /** image is part of an ICO, mask follows */ 112 | bool ico; 113 | /** true if the bitmap does not contain an alpha channel */ 114 | bool opaque; 115 | /** four bitwise mask */ 116 | uint32_t mask[4]; 117 | /** four bitwise shifts */ 118 | int32_t shift[4]; 119 | /** colour representing "transparency" in the bitmap */ 120 | uint32_t transparent_index; 121 | } bmp_image; 122 | 123 | typedef struct ico_image { 124 | bmp_image bmp; 125 | struct ico_image *next; 126 | } ico_image; 127 | 128 | /** 129 | * icon image collection 130 | */ 131 | typedef struct ico_collection { 132 | /** callbacks for bitmap functions */ 133 | bmp_bitmap_callback_vt bitmap_callbacks; 134 | /** width of largest BMP */ 135 | uint16_t width; 136 | /** heigth of largest BMP */ 137 | uint16_t height; 138 | 139 | /* Internal members are listed below */ 140 | /** pointer to ICO data */ 141 | uint8_t *ico_data; 142 | /** total number of bytes of ICO data available */ 143 | uint32_t buffer_size; 144 | /** root of linked list of images */ 145 | ico_image *first; 146 | } ico_collection; 147 | 148 | /** 149 | * Initialises bitmap ready for analysing the bitmap. 150 | * 151 | * \param bmp The Bitmap to initialise 152 | * \param callbacks The callbacks the library will call on operations. 153 | * \return BMP_OK on success or appropriate error code. 154 | */ 155 | bmp_result bmp_create(bmp_image *bmp, bmp_bitmap_callback_vt *callbacks); 156 | 157 | /** 158 | * Initialises icon ready for analysing the icon 159 | * 160 | * \param bmp The Bitmap to initialise 161 | * \param callbacks The callbacks the library will call on operations. 162 | * \return BMP_OK on success or appropriate error code. 163 | */ 164 | bmp_result ico_collection_create(ico_collection *ico, 165 | bmp_bitmap_callback_vt *callbacks); 166 | 167 | /** 168 | * Analyse a BMP prior to decoding. 169 | * 170 | * This will scan the data provided and perform checks to ensure the data is a 171 | * valid BMP and prepare the bitmap image structure ready for decode. 172 | * 173 | * This function must be called and resturn BMP_OK before bmp_decode() as it 174 | * prepares the bmp internal state for the decode process. 175 | * 176 | * \param bmp the BMP image to analyse. 177 | * \param size The size of data in cdata. 178 | * \param data The bitmap source data. 179 | * \return BMP_OK on success or error code on faliure. 180 | */ 181 | bmp_result bmp_analyse(bmp_image *bmp, size_t size, uint8_t *data); 182 | 183 | /** 184 | * Analyse an ICO prior to decoding. 185 | * 186 | * This function will scan the data provided and perform checks to ensure the 187 | * data is a valid ICO. 188 | * 189 | * This function must be called before ico_find(). 190 | * 191 | * \param ico the ICO image to analyse 192 | * \param size The size of data in cdata. 193 | * \param data The bitmap source data. 194 | * \return BMP_OK on success 195 | */ 196 | bmp_result ico_analyse(ico_collection *ico, size_t size, uint8_t *data); 197 | 198 | /** 199 | * Decode a BMP 200 | * 201 | * This function decodes the BMP data such that bmp->bitmap is a valid 202 | * image. The state of bmp->decoded is set to TRUE on exit such that it 203 | * can easily be identified which BMPs are in a fully decoded state. 204 | * 205 | * \param bmp the BMP image to decode 206 | * \return BMP_OK on success 207 | */ 208 | bmp_result bmp_decode(bmp_image *bmp); 209 | 210 | /** 211 | * Decode a BMP using "limited transparency" 212 | * 213 | * Bitmaps do not have native transparency support. However, there is a 214 | * "trick" that is used in some instances in which the first pixel of the 215 | * bitmap becomes the "transparency index". The decoding application can 216 | * replace this index with whatever background colour it chooses to 217 | * create the illusion of transparency. 218 | * 219 | * When to use transparency is at the discretion of the decoding 220 | * application. 221 | * 222 | * \param bmp the BMP image to decode 223 | * \param colour the colour to use as "transparent" 224 | * \return BMP_OK on success 225 | */ 226 | bmp_result bmp_decode_trans(bmp_image *bmp, uint32_t transparent_colour); 227 | 228 | /** 229 | * Finds the closest BMP within an ICO collection 230 | * 231 | * This function finds the BMP with dimensions as close to a specified set 232 | * as possible from the images in the collection. 233 | * 234 | * \param ico the ICO collection to examine 235 | * \param width the preferred width (0 to use ICO header width) 236 | * \param height the preferred height (0 to use ICO header height) 237 | */ 238 | bmp_image *ico_find(ico_collection *ico, uint16_t width, uint16_t height); 239 | 240 | /** 241 | * Finalise a BMP prior to destruction. 242 | * 243 | * \param bmp the BMP image to finalise. 244 | */ 245 | void bmp_finalise(bmp_image *bmp); 246 | 247 | /** 248 | * Finalise an ICO prior to destruction. 249 | * 250 | * \param ico the ICO image to finalise, 251 | */ 252 | void ico_finalise(ico_collection *ico); 253 | 254 | #if defined (__cplusplus) 255 | } 256 | #endif 257 | 258 | #endif 259 | -------------------------------------------------------------------------------- /libs/libnsbmp/utils/log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2003 James Bursa 3 | * Copyright 2004 John Tytgat 4 | * 5 | * This file is part of NetSurf, http://www.netsurf-browser.org/ 6 | * Licenced under the MIT License, 7 | * http://www.opensource.org/licenses/mit-license.php 8 | */ 9 | 10 | #include 11 | 12 | #ifndef _LIBNSBMP_LOG_H_ 13 | #define _LIBNSBMP_LOG_H_ 14 | 15 | #ifdef NDEBUG 16 | # define LOG(x) ((void) 0) 17 | #else 18 | # ifdef __GNUC__ 19 | # define LOG(x) do { printf x, fputc('\n', stdout)); } while (0) 20 | # elif defined(__CC_NORCROFT) 21 | # define LOG(x) do { printf x, fputc('\n', stdout)); } while (0) 22 | # else 23 | # define LOG(x) do { printf x, fputc('\n', stdout)); } while (0) 24 | # endif 25 | #endif 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /libs/usbhsfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * usbhsfs.h 3 | * 4 | * Copyright (c) 2020-2022, DarkMatterCore . 5 | * Copyright (c) 2020-2021, XorTroll. 6 | * Copyright (c) 2020-2021, Rhys Koedijk. 7 | * 8 | * This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs). 9 | */ 10 | 11 | #pragma once 12 | 13 | #ifndef __USBHSFS_H__ 14 | #define __USBHSFS_H__ 15 | 16 | #include 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | /// Library version. 23 | #define LIBUSBHSFS_VERSION_MAJOR 0 24 | #define LIBUSBHSFS_VERSION_MINOR 2 25 | #define LIBUSBHSFS_VERSION_MICRO 7 26 | 27 | /// Helper macro to generate a string based on a filesystem type value. 28 | #define LIBUSBHSFS_FS_TYPE_STR(x) ((x) == UsbHsFsDeviceFileSystemType_FAT12 ? "FAT12" : ((x) == UsbHsFsDeviceFileSystemType_FAT16 ? "FAT16" : ((x) == UsbHsFsDeviceFileSystemType_FAT32 ? "FAT32" : \ 29 | ((x) == UsbHsFsDeviceFileSystemType_exFAT ? "exFAT" : ((x) == UsbHsFsDeviceFileSystemType_NTFS ? "NTFS" : ((x) == UsbHsFsDeviceFileSystemType_EXT2 ? "EXT2" : \ 30 | ((x) == UsbHsFsDeviceFileSystemType_EXT3 ? "EXT3" : ((x) == UsbHsFsDeviceFileSystemType_EXT4 ? "EXT4" : "Invalid")))))))) 31 | 32 | /// Used to identify the filesystem type from a mounted filesystem (e.g. filesize limitations, etc.). 33 | typedef enum { 34 | UsbHsFsDeviceFileSystemType_Invalid = 0, 35 | UsbHsFsDeviceFileSystemType_FAT12 = 1, 36 | UsbHsFsDeviceFileSystemType_FAT16 = 2, 37 | UsbHsFsDeviceFileSystemType_FAT32 = 3, 38 | UsbHsFsDeviceFileSystemType_exFAT = 4, 39 | UsbHsFsDeviceFileSystemType_NTFS = 5, ///< Only returned by the GPL build of the library. 40 | UsbHsFsDeviceFileSystemType_EXT2 = 6, ///< Only returned by the GPL build of the library. 41 | UsbHsFsDeviceFileSystemType_EXT3 = 7, ///< Only returned by the GPL build of the library. 42 | UsbHsFsDeviceFileSystemType_EXT4 = 8 ///< Only returned by the GPL build of the library. 43 | } UsbHsFsDeviceFileSystemType; 44 | 45 | /// Filesystem mount flags. 46 | /// Not all supported filesystems are compatible with these flags. 47 | /// The default mount bitmask is `UsbHsFsMountFlags_UpdateAccessTimes | UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ReplayJournal`. 48 | /// It can be overriden via usbHsFsSetFileSystemMountFlags() (see below). 49 | typedef enum { 50 | UsbHsFsMountFlags_None = 0x00000000, ///< No special action is taken. 51 | UsbHsFsMountFlags_IgnoreCaseSensitivity = 0x00000001, ///< NTFS only. Case sensitivity is ignored for all filesystem operations. 52 | UsbHsFsMountFlags_UpdateAccessTimes = 0x00000002, ///< NTFS only. File/directory access times are updated after each successful R/W operation. 53 | UsbHsFsMountFlags_ShowHiddenFiles = 0x00000004, ///< NTFS only. Hidden file entries are returned while enumerating directories. 54 | UsbHsFsMountFlags_ShowSystemFiles = 0x00000008, ///< NTFS only. System file entries are returned while enumerating directories. 55 | UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute = 0x00000010, ///< NTFS only. Allows writing to files even if they are marked as read-only. 56 | UsbHsFsMountFlags_ReadOnly = 0x00000100, ///< NTFS and EXT only. Filesystem is mounted as read-only. 57 | UsbHsFsMountFlags_ReplayJournal = 0x00000200, ///< NTFS and EXT only. Replays the log/journal to restore filesystem consistency (e.g. fix unsafe device ejections). 58 | UsbHsFsMountFlags_IgnoreHibernation = 0x00010000, ///< NTFS only. Filesystem is mounted even if it's in a hibernated state. The saved Windows session is completely lost. 59 | 60 | ///< Pre-generated bitmasks provided for convenience. 61 | UsbHsFsMountFlags_SuperUser = (UsbHsFsMountFlags_ShowHiddenFiles | UsbHsFsMountFlags_ShowSystemFiles | UsbHsFsMountFlags_IgnoreFileReadOnlyAttribute), 62 | UsbHsFsMountFlags_Force = (UsbHsFsMountFlags_ReplayJournal | UsbHsFsMountFlags_IgnoreHibernation) 63 | } UsbHsFsMountFlags; 64 | 65 | /// Struct used to list mounted filesystems as devoptab devices. 66 | /// Everything but the manufacturer, product_name and name fields is empty/zeroed-out under SX OS. 67 | typedef struct { 68 | s32 usb_if_id; ///< USB interface ID. Internal use. 69 | u8 lun; ///< Logical unit. Internal use. 70 | u32 fs_idx; ///< Filesystem index. Internal use. 71 | bool write_protect; ///< Set to true if the logical unit is protected against write operations. 72 | u16 vid; ///< Vendor ID. Retrieved from the device descriptor. Useful if you wish to implement a filter in your application. 73 | u16 pid; ///< Product ID. Retrieved from the device descriptor. Useful if you wish to implement a filter in your application. 74 | char manufacturer[64]; ///< UTF-8 encoded manufacturer string. Retrieved from the device descriptor or SCSI Inquiry data. May be empty. 75 | char product_name[64]; ///< UTF-8 encoded product name string. Retrieved from the device descriptor or SCSI Inquiry data. May be empty. 76 | char serial_number[64]; ///< UTF-8 encoded serial number string. Retrieved from the device descriptor. May be empty. 77 | u64 capacity; ///< Raw capacity from the logical unit this filesystem belongs to. Use statvfs() to get the actual filesystem capacity. May be shared with other UsbHsFsDevice entries. 78 | char name[32]; ///< Mount name used by the devoptab virtual device interface (e.g. "ums0:"). Use it as a prefix in libcstd I/O calls to perform operations on this filesystem. 79 | u8 fs_type; ///< UsbHsFsDeviceFileSystemType. 80 | u32 flags; ///< UsbHsFsMountFlags bitmask used at mount time. 81 | } UsbHsFsDevice; 82 | 83 | /// Initializes the USB Mass Storage Host interface. 84 | /// event_idx represents the event index to use with usbHsCreateInterfaceAvailableEvent() / usbHsDestroyInterfaceAvailableEvent(). Must be within the 0 - 2 range (inclusive). 85 | /// If you're not using any usb:hs interface available events on your own, set this value to 0. If running under SX OS, this value will be ignored. 86 | /// This function will fail if the deprecated fsp-usb service is running in the background. 87 | Result usbHsFsInitialize(u8 event_idx); 88 | 89 | /// Closes the USB Mass Storage Host interface. 90 | /// If there are any UMS devices with mounted filesystems connected to the console when this function is called, their filesystems will be unmounted and their logical units will be stopped. 91 | void usbHsFsExit(void); 92 | 93 | /// Returns a pointer to the user-mode status change event (with autoclear enabled). 94 | /// Useful to wait for USB Mass Storage status changes without having to constantly poll the interface. 95 | /// Returns NULL if the USB Mass Storage Host interface hasn't been initialized. 96 | UEvent *usbHsFsGetStatusChangeUserEvent(void); 97 | 98 | /// Returns the mounted device count. 99 | u32 usbHsFsGetMountedDeviceCount(void); 100 | 101 | /// Lists up to max_count mounted devices and stores their information in the provided UsbHsFsDevice array. 102 | /// Returns the total number of written entries. 103 | u32 usbHsFsListMountedDevices(UsbHsFsDevice *out, u32 max_count); 104 | 105 | /// Unmounts all filesystems from the UMS device with a USB interface ID that matches the one from the provided UsbHsFsDevice, and stops all of its logical units. 106 | /// Can be used to safely unmount a UMS device at runtime, if that's needed for some reason. Calling this function before usbHsFsExit() isn't necessary. 107 | /// If multiple UsbHsFsDevice entries are returned for the same UMS device, any of them can be used as the input argument for this function. 108 | /// If successful, and signal_status_event is true, this will also fire the user-mode status change event from usbHsFsGetStatusChangeUserEvent(). 109 | /// This function has no effect at all under SX OS. 110 | bool usbHsFsUnmountDevice(UsbHsFsDevice *device, bool signal_status_event); 111 | 112 | /// Returns a bitmask with the current filesystem mount flags. 113 | /// Can be used even if the USB Mass Storage Host interface hasn't been initialized. 114 | /// This function has no effect at all under SX OS. 115 | u32 usbHsFsGetFileSystemMountFlags(void); 116 | 117 | /// Takes an input bitmask with the desired filesystem mount flags, which will be used for all mount operations. 118 | /// Can be used even if the USB Mass Storage Host interface hasn't been initialized. 119 | /// This function has no effect at all under SX OS. 120 | void usbHsFsSetFileSystemMountFlags(u32 flags); 121 | 122 | #ifdef __cplusplus 123 | } 124 | #endif 125 | 126 | #endif /* __USBHSFS_H__ */ 127 | -------------------------------------------------------------------------------- /res/archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/res/archive.png -------------------------------------------------------------------------------- /res/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/res/check.png -------------------------------------------------------------------------------- /res/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/res/file.png -------------------------------------------------------------------------------- /res/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/res/folder.png -------------------------------------------------------------------------------- /res/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/res/image.png -------------------------------------------------------------------------------- /res/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/res/text.png -------------------------------------------------------------------------------- /res/uncheck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joel16/NX-Shell/700aa486076c6353fd8245d930925a977612cf11/res/uncheck.png -------------------------------------------------------------------------------- /source/config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "config.hpp" 6 | #include "fs.hpp" 7 | #include "log.hpp" 8 | 9 | #define CONFIG_VERSION 5 10 | 11 | config_t cfg; 12 | 13 | namespace Config { 14 | static const char *config_path = "/switch/NX-Shell/config.json"; 15 | static const char *config_file = "{\n\t\"config_version\": %d,\n\t\"language\": %d,\n\t\"dev_options\": %d,\n\t\"image_filename\": %d,\n\t\"multi_lang\": %d\n}"; 16 | static int config_version_holder = 0; 17 | static const int buf_size = 128; 18 | 19 | int Save(config_t &config) { 20 | Result ret = 0; 21 | char *buf = new char[buf_size]; 22 | u64 len = std::snprintf(buf, buf_size, config_file, CONFIG_VERSION, config.lang, config.dev_options, config.image_filename, config.multi_lang); 23 | 24 | // Delete and re-create the file, we don't care about the return value here. 25 | fsFsDeleteFile(std::addressof(devices[FileSystemSDMC]), config_path); 26 | fsFsCreateFile(std::addressof(devices[FileSystemSDMC]), config_path, len, 0); 27 | 28 | FsFile file; 29 | if (R_FAILED(ret = fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), config_path, FsOpenMode_Write, std::addressof(file)))) { 30 | Log::Error("Config::Save fsFsOpenFile(%s) failed: 0x%x\n", config_path, ret); 31 | delete[] buf; 32 | return ret; 33 | } 34 | 35 | if (R_FAILED(ret = fsFileWrite(std::addressof(file), 0, buf, len, FsWriteOption_Flush))) { 36 | Log::Error("Config::Save fsFileWrite(%s) failed: 0x%x\n", config_path, ret); 37 | delete[] buf; 38 | fsFileClose(std::addressof(file)); 39 | return ret; 40 | } 41 | 42 | fsFileClose(std::addressof(file)); 43 | delete[] buf; 44 | return 0; 45 | } 46 | 47 | int Load(void) { 48 | Result ret = 0; 49 | 50 | if (!FS::DirExists("/switch/")) 51 | fsFsCreateDirectory(std::addressof(devices[FileSystemSDMC]), "/switch"); 52 | if (!FS::DirExists("/switch/NX-Shell/")) 53 | fsFsCreateDirectory(std::addressof(devices[FileSystemSDMC]), "/switch/NX-Shell"); 54 | 55 | if (!FS::FileExists(config_path)) { 56 | cfg = {}; 57 | return Config::Save(cfg); 58 | } 59 | 60 | FsFile file; 61 | if (R_FAILED(ret = fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), config_path, FsOpenMode_Read, std::addressof(file)))) 62 | return ret; 63 | 64 | s64 size = 0; 65 | if (R_FAILED(ret = fsFileGetSize(std::addressof(file), std::addressof(size)))) { 66 | fsFileClose(std::addressof(file)); 67 | return ret; 68 | } 69 | 70 | char *buf = new char[size + 1]; 71 | if (R_FAILED(ret = fsFileRead(std::addressof(file), 0, buf, static_cast(size) + 1, FsReadOption_None, nullptr))) { 72 | delete[] buf; 73 | fsFileClose(std::addressof(file)); 74 | return ret; 75 | } 76 | 77 | fsFileClose(std::addressof(file)); 78 | 79 | json_t *root; 80 | json_error_t error; 81 | root = json_loads(buf, 0, std::addressof(error)); 82 | delete[] buf; 83 | 84 | if (!root) { 85 | std::printf("error: on line %d: %s\n", error.line, error.text); 86 | return -1; 87 | } 88 | 89 | json_t *config_ver = json_object_get(root, "config_version"); 90 | config_version_holder = json_integer_value(config_ver); 91 | 92 | // Delete config file if config file is updated. This will rarely happen. 93 | if (config_version_holder < CONFIG_VERSION) { 94 | fsFsDeleteFile(std::addressof(devices[FileSystemSDMC]), config_path); 95 | cfg = {}; 96 | return Config::Save(cfg); 97 | } 98 | 99 | json_t *language = json_object_get(root, "language"); 100 | cfg.lang = json_integer_value(language); 101 | 102 | json_t *dev_options = json_object_get(root, "dev_options"); 103 | cfg.dev_options = json_integer_value(dev_options); 104 | 105 | json_t *image_filename = json_object_get(root, "image_filename"); 106 | cfg.image_filename = json_integer_value(image_filename); 107 | 108 | json_t *multi_lang = json_object_get(root, "multi_lang"); 109 | cfg.multi_lang = json_integer_value(multi_lang); 110 | 111 | json_decref(root); 112 | return 0; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /source/fs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "config.hpp" 8 | #include "fs.hpp" 9 | #include "language.hpp" 10 | #include "log.hpp" 11 | #include "popups.hpp" 12 | 13 | // Global vars 14 | FsFileSystem *fs; 15 | FsFileSystem devices[FileSystemMax]; 16 | std::string cwd = "/"; 17 | std::string device = "sdmc:"; 18 | 19 | namespace FS { 20 | 21 | typedef struct { 22 | std::string path; 23 | std::string filename; 24 | bool is_directory = false; 25 | } FSCopyEntry; 26 | 27 | FSCopyEntry fs_copy_entry; 28 | 29 | bool FileExists(const std::string &path) { 30 | struct stat file_stat = { 0 }; 31 | return (stat(path.c_str(), std::addressof(file_stat)) == 0 && S_ISREG(file_stat.st_mode)); 32 | } 33 | 34 | bool DirExists(const std::string &path) { 35 | struct stat dir_stat = { 0 }; 36 | return (stat(path.c_str(), &dir_stat) == 0); 37 | } 38 | 39 | bool GetFileSize(const std::string &path, std::size_t &size) { 40 | struct stat file_stat = { 0 }; 41 | std::string full_path = FS::BuildPath(path, true); 42 | 43 | if (stat(full_path.c_str(), std::addressof(file_stat)) != 0) { 44 | Log::Error("FS::GetFileSize(%s) failed to stat file.\n", full_path.c_str()); 45 | return false; 46 | } 47 | 48 | size = file_stat.st_size; 49 | return true; 50 | } 51 | 52 | bool GetDirList(const std::string &device, const std::string &path, std::vector &entries) { 53 | DIR *dir = nullptr; 54 | struct dirent *d_entry = nullptr; 55 | 56 | std::string full_path = device + path; 57 | dir = opendir(full_path.c_str()); 58 | entries.clear(); 59 | 60 | if (dir) { 61 | FsDirectoryEntry parent_entry; 62 | std::memset(std::addressof(parent_entry), 0, sizeof(FsDirectoryEntry)); 63 | std::snprintf(parent_entry.name, 3, ".."); 64 | parent_entry.type = FsDirEntryType_Dir; 65 | entries.push_back(parent_entry); 66 | 67 | while((d_entry = readdir(dir))) { 68 | FsDirectoryEntry entry; 69 | std::memset(std::addressof(entry), 0, sizeof(FsDirectoryEntry)); 70 | 71 | std::snprintf(entry.name, FS_MAX_PATH, d_entry->d_name); 72 | entry.type = (d_entry->d_type & DT_DIR)? FsDirEntryType_Dir : FsDirEntryType_File; 73 | entry.file_size = 0; 74 | 75 | entries.push_back(entry); 76 | } 77 | 78 | closedir(dir); 79 | } 80 | else { 81 | Log::Error("FS::GetDirList(%s) to open path.\n", full_path.c_str()); 82 | return false; 83 | } 84 | 85 | return true; 86 | } 87 | 88 | static bool ChangeDir(const std::string &path, std::vector &entries) { 89 | std::vector new_entries; 90 | const std::string new_path = path; 91 | cwd = path; 92 | 93 | bool ret = FS::GetDirList(device, new_path, new_entries); 94 | 95 | entries.clear(); 96 | entries = new_entries; 97 | return ret; 98 | } 99 | 100 | bool ChangeDirNext(const std::string &path, std::vector &entries) { 101 | return FS::ChangeDir(FS::BuildPath(path, false), entries); 102 | } 103 | 104 | bool ChangeDirPrev(std::vector &entries) { 105 | // We are already at the root. 106 | if (cwd.compare("/") == 0) 107 | return false; 108 | 109 | std::filesystem::path path = cwd; 110 | std::string parent_path = path.parent_path(); 111 | return FS::ChangeDir(parent_path.empty()? cwd : parent_path, entries); 112 | } 113 | 114 | bool GetTimeStamp(FsDirectoryEntry &entry, FsTimeStampRaw ×tamp) { 115 | struct stat file_stat = { 0 }; 116 | std::string full_path = FS::BuildPath(entry); 117 | 118 | if (stat(full_path.c_str(), std::addressof(file_stat)) != 0) { 119 | Log::Error("FS::GetTimeStamp(%s) failed to stat file.\n", full_path.c_str()); 120 | return false; 121 | } 122 | 123 | timestamp.is_valid = 1; 124 | timestamp.created = file_stat.st_ctime; 125 | timestamp.modified = file_stat.st_mtime; 126 | timestamp.accessed = file_stat.st_atime; 127 | return true; 128 | } 129 | 130 | bool Rename(FsDirectoryEntry &entry, const std::string &dest_path) { 131 | std::string src_path = FS::BuildPath(entry); 132 | std::string full_dest_path = FS::BuildPath(dest_path, true); 133 | 134 | if (rename(src_path.c_str(), full_dest_path.c_str()) != 0) { 135 | Log::Error("FS::Rename(%s, %s) failed.\n", src_path.c_str(), dest_path.c_str()); 136 | return false; 137 | } 138 | 139 | return true; 140 | } 141 | 142 | bool DeleteRecursive(const std::string &path) { 143 | DIR *dir = nullptr; 144 | struct dirent *entry = nullptr; 145 | dir = opendir(path.c_str()); 146 | 147 | if (dir) { 148 | while((entry = readdir(dir))) { 149 | std::string filename = entry->d_name; 150 | if ((filename.compare(".") == 0) || (filename.compare("..") == 0)) 151 | continue; 152 | 153 | std::string file_path = path; 154 | file_path.append(path.compare("/") == 0? "" : "/"); 155 | file_path.append(filename); 156 | 157 | if (entry->d_type & DT_DIR) { 158 | FS::DeleteRecursive(file_path); 159 | } 160 | else { 161 | if (remove(file_path.c_str()) != 0) { 162 | Log::Error("FS::DeleteRecursive(%s) failed to delete file.\n", file_path.c_str()); 163 | return false; 164 | } 165 | } 166 | } 167 | 168 | closedir(dir); 169 | } 170 | else { 171 | Log::Error("FS::DeleteRecursive(%s) failed to open path.\n", path.c_str()); 172 | return false; 173 | } 174 | 175 | return (rmdir(path.c_str()) == 0); 176 | } 177 | 178 | bool Delete(FsDirectoryEntry &entry) { 179 | std::string full_path = FS::BuildPath(entry); 180 | 181 | if (entry.type == FsDirEntryType_Dir) { 182 | if (!FS::DeleteRecursive(full_path)) { 183 | Log::Error("FS::Delete(%s) failed to delete folder.\n", full_path.c_str()); 184 | return false; 185 | } 186 | } 187 | else { 188 | if (remove(full_path.c_str()) != 0) { 189 | Log::Error("FS::Delete(%s) failed to delete file.\n", full_path.c_str()); 190 | return false; 191 | } 192 | } 193 | 194 | return true; 195 | } 196 | 197 | static bool CopyFile(const std::string &src_path, const std::string &dest_path) { 198 | FILE *src = fopen(src_path.c_str(), "rb"); 199 | if (!src) { 200 | Log::Error("FS::CopyFile (%s) failed to open src file.\n", src_path.c_str()); 201 | return false; 202 | } 203 | 204 | struct stat file_stat = { 0 }; 205 | if (stat(src_path.c_str(), std::addressof(file_stat)) != 0) { 206 | Log::Error("FS::CopyFile (%s) failed to get src file size.\n", src_path.c_str()); 207 | return false; 208 | } 209 | 210 | std::size_t size = file_stat.st_size; 211 | 212 | FILE *dest = fopen(dest_path.c_str(), "wb"); 213 | if (!dest) { 214 | Log::Error("FS::CopyFile (%s) failed to open dest file.\n", dest_path.c_str()); 215 | fclose(src); 216 | return false; 217 | } 218 | 219 | std::size_t bytes_read = 0, offset = 0; 220 | const std::size_t buf_size = 0x10000; 221 | unsigned char *buf = new unsigned char[buf_size]; 222 | std::string filename = std::filesystem::path(src_path).filename(); 223 | 224 | do { 225 | std::memset(buf, 0, buf_size); 226 | 227 | bytes_read = fread(buf, sizeof(unsigned char), buf_size, src); 228 | if (bytes_read < 0) { 229 | Log::Error("FS::CopyFile (%s) failed to read src file.\n", src_path.c_str()); 230 | delete[] buf; 231 | fclose(src); 232 | fclose(dest); 233 | return false; 234 | } 235 | 236 | std::size_t bytes_written = fwrite(buf, sizeof(unsigned char), bytes_read, dest); 237 | if (bytes_written != bytes_read) { 238 | Log::Error("FS::CopyFile (%s) failed to write to dest file.\n", dest_path.c_str()); 239 | delete[] buf; 240 | fclose(src); 241 | fclose(dest); 242 | return false; 243 | } 244 | 245 | offset += bytes_read; 246 | Popups::ProgressBar(static_cast(offset), static_cast(size), strings[cfg.lang][Lang::OptionsCopying], filename.c_str()); 247 | } while (offset < size); 248 | 249 | delete[] buf; 250 | fclose(src); 251 | fclose(dest); 252 | return true; 253 | return 0; 254 | } 255 | 256 | static bool CopyDir(const std::string &src_path, const std::string &dest_path) { 257 | DIR *dir = nullptr; 258 | struct dirent *entry = nullptr; 259 | dir = opendir(src_path.c_str()); 260 | 261 | if (dir) { 262 | // This may fail or not, but we don't care -> make the dir if it doesn't exist, otherwise continue. 263 | mkdir(dest_path.c_str(), 0700); 264 | 265 | while((entry = readdir(dir))) { 266 | std::string filename = entry->d_name; 267 | if ((filename.compare(".") == 0) || (filename.compare("..") == 0)) 268 | continue; 269 | 270 | std::string src = src_path; 271 | src.append("/"); 272 | src.append(filename); 273 | 274 | std::string dest = dest_path; 275 | dest.append("/"); 276 | dest.append(filename); 277 | 278 | if (entry->d_type & DT_DIR) 279 | FS::CopyDir(src.c_str(), dest.c_str()); // Copy Folder (via recursion) 280 | else 281 | FS::CopyFile(src.c_str(), dest.c_str()); // Copy File 282 | } 283 | 284 | closedir(dir); 285 | } 286 | else { 287 | Log::Error("FS::CopyDir(%s) failed to open path.\n", src_path.c_str()); 288 | return false; 289 | } 290 | 291 | return true; 292 | } 293 | 294 | void Copy(FsDirectoryEntry &entry, const std::string &path) { 295 | std::string full_path = path; 296 | full_path.append(path.compare("/") == 0? "" : "/"); 297 | full_path.append(entry.name); 298 | 299 | if ((std::strncmp(entry.name, "..", 2)) != 0) { 300 | fs_copy_entry.path = full_path; 301 | fs_copy_entry.filename = entry.name; 302 | 303 | if (entry.type == FsDirEntryType_Dir) 304 | fs_copy_entry.is_directory = true; 305 | } 306 | } 307 | 308 | bool Paste(void) { 309 | bool ret = false; 310 | std::string path = FS::BuildPath(fs_copy_entry.filename, true); 311 | 312 | if (fs_copy_entry.is_directory) 313 | ret = FS::CopyDir(fs_copy_entry.path, path); 314 | else 315 | ret = FS::CopyFile(fs_copy_entry.path, path); 316 | 317 | fs_copy_entry = {}; 318 | return ret; 319 | } 320 | 321 | bool Move(void) { 322 | std::string path = FS::BuildPath(fs_copy_entry.filename, true); 323 | 324 | if (rename(fs_copy_entry.path.c_str(), path.c_str()) != 0) { 325 | Log::Error("FS::Move(%s, %s) failed.\n", fs_copy_entry.path.c_str(), path.c_str()); 326 | return false; 327 | } 328 | 329 | fs_copy_entry = {}; 330 | return true; 331 | } 332 | 333 | FileType GetFileType(const std::string &filename) { 334 | std::string ext = FS::GetFileExt(filename); 335 | 336 | if ((!ext.compare(".ZIP")) || (!ext.compare(".RAR")) || (!ext.compare(".7Z"))) 337 | return FileTypeArchive; 338 | else if ((!ext.compare(".BMP")) || (!ext.compare(".GIF")) || (!ext.compare(".JPG")) || (!ext.compare(".JPEG")) || (!ext.compare(".PGM")) 339 | || (!ext.compare(".PPM")) || (!ext.compare(".PNG")) || (!ext.compare(".PSD")) || (!ext.compare(".TGA")) || (!ext.compare(".WEBP"))) 340 | return FileTypeImage; 341 | else if ((!ext.compare(".JSON")) || (!ext.compare(".LOG")) || (!ext.compare(".TXT")) || (!ext.compare(".CFG")) || (!ext.compare(".INI"))) 342 | return FileTypeText; 343 | 344 | return FileTypeNone; 345 | } 346 | 347 | Result SetArchiveBit(const std::string &path) { 348 | Result ret = 0; 349 | 350 | char fs_path[FS_MAX_PATH]; 351 | std::snprintf(fs_path, FS_MAX_PATH, path.c_str()); 352 | 353 | if (R_FAILED(ret = fsFsSetConcatenationFileAttribute(std::addressof(devices[FileSystemSDMC]), fs_path))) { 354 | Log::Error("fsFsSetConcatenationFileAttribute(%s) failed: 0x%x\n", path.c_str(), ret); 355 | return ret; 356 | } 357 | 358 | return 0; 359 | } 360 | 361 | Result GetFreeStorageSpace(s64 &size) { 362 | Result ret = 0; 363 | 364 | if (R_FAILED(ret = fsFsGetFreeSpace(fs, "/", std::addressof(size)))) { 365 | Log::Error("fsFsGetFreeSpace() failed: 0x%x\n", ret); 366 | return ret; 367 | } 368 | 369 | return 0; 370 | } 371 | 372 | Result GetTotalStorageSpace(s64 &size) { 373 | Result ret = 0; 374 | 375 | if (R_FAILED(ret = fsFsGetTotalSpace(fs, "/", std::addressof(size)))) { 376 | Log::Error("fsFsGetTotalSpace() failed: 0x%x\n", ret); 377 | return ret; 378 | } 379 | 380 | return 0; 381 | } 382 | 383 | Result GetUsedStorageSpace(s64 &size) { 384 | Result ret = 0; 385 | s64 free_size = 0, total_size = 0; 386 | 387 | if (R_FAILED(ret = FS::GetFreeStorageSpace(free_size))) 388 | return ret; 389 | 390 | if (R_FAILED(ret = FS::GetTotalStorageSpace(total_size))) 391 | return ret; 392 | 393 | size = (total_size - free_size); 394 | return 0; 395 | } 396 | 397 | std::string GetFileExt(const std::string &filename) { 398 | std::string ext = std::filesystem::path(filename).extension(); 399 | std::transform(ext.begin(), ext.end(), ext.begin(), ::toupper); 400 | return ext; 401 | } 402 | 403 | std::string BuildPath(FsDirectoryEntry &entry) { 404 | std::string path_next = device; 405 | path_next.append(cwd); 406 | path_next.append((cwd.compare("/") == 0)? "" : "/"); 407 | path_next.append(entry.name); 408 | return path_next; 409 | } 410 | 411 | std::string BuildPath(const std::string &path, bool device_name) { 412 | std::string path_next = ""; 413 | 414 | if (device_name) 415 | path_next.append(device); 416 | 417 | path_next.append(cwd); 418 | path_next.append((cwd.compare("/") == 0)? "" : "/"); 419 | path_next.append(path); 420 | return path_next; 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /source/gui.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "config.hpp" 9 | #include "gui.hpp" 10 | #include "imgui_impl_switch.hpp" 11 | #include "log.hpp" 12 | 13 | namespace GUI { 14 | static EGLDisplay s_display = EGL_NO_DISPLAY; 15 | static EGLContext s_context = EGL_NO_CONTEXT; 16 | static EGLSurface s_surface = EGL_NO_SURFACE; 17 | 18 | static bool InitEGL(NWindow* win) { 19 | s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 20 | 21 | if (!s_display) { 22 | Log::Error("Could not connect to display! error: %d", eglGetError()); 23 | return false; 24 | } 25 | 26 | eglInitialize(s_display, nullptr, nullptr); 27 | 28 | if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { 29 | Log::Error("Could not set API! error: %d", eglGetError()); 30 | eglTerminate(s_display); 31 | s_display = nullptr; 32 | } 33 | 34 | EGLConfig config; 35 | EGLint num_configs; 36 | static const EGLint framebuffer_attr_list[] = { 37 | EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, 38 | EGL_RED_SIZE, 8, 39 | EGL_GREEN_SIZE, 8, 40 | EGL_BLUE_SIZE, 8, 41 | EGL_ALPHA_SIZE, 8, 42 | EGL_DEPTH_SIZE, 24, 43 | EGL_STENCIL_SIZE, 8, 44 | EGL_NONE 45 | }; 46 | 47 | eglChooseConfig(s_display, framebuffer_attr_list, std::addressof(config), 1, std::addressof(num_configs)); 48 | if (num_configs == 0) { 49 | Log::Error("No config found! error: %d", eglGetError()); 50 | eglTerminate(s_display); 51 | s_display = nullptr; 52 | } 53 | 54 | s_surface = eglCreateWindowSurface(s_display, config, win, nullptr); 55 | if (!s_surface) { 56 | Log::Error("Surface creation failed! error: %d", eglGetError()); 57 | eglTerminate(s_display); 58 | s_display = nullptr; 59 | } 60 | 61 | static const EGLint context_attr_list[] = { 62 | EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, 63 | EGL_CONTEXT_MAJOR_VERSION_KHR, 4, 64 | EGL_CONTEXT_MINOR_VERSION_KHR, 3, 65 | EGL_NONE 66 | }; 67 | 68 | s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, context_attr_list); 69 | if (!s_context) { 70 | Log::Error("Context creation failed! error: %d", eglGetError()); 71 | eglDestroySurface(s_display, s_surface); 72 | s_surface = nullptr; 73 | } 74 | 75 | eglMakeCurrent(s_display, s_surface, s_surface, s_context); 76 | return true; 77 | } 78 | 79 | static void ExitEGL(void) { 80 | if (s_display) { 81 | eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 82 | 83 | if (s_context) { 84 | eglDestroyContext(s_display, s_context); 85 | s_context = nullptr; 86 | } 87 | 88 | if (s_surface) { 89 | eglDestroySurface(s_display, s_surface); 90 | s_surface = nullptr; 91 | } 92 | 93 | eglTerminate(s_display); 94 | s_display = nullptr; 95 | } 96 | } 97 | 98 | bool SwapBuffers(void) { 99 | return eglSwapBuffers(s_display, s_surface); 100 | } 101 | 102 | void SetDefaultTheme(void) { 103 | ImGui::GetStyle().FrameRounding = 4.0f; 104 | ImGui::GetStyle().GrabRounding = 4.0f; 105 | 106 | ImVec4 *colors = ImGui::GetStyle().Colors; 107 | colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f); 108 | colors[ImGuiCol_TextDisabled] = ImVec4(0.36f, 0.42f, 0.47f, 1.00f); 109 | colors[ImGuiCol_WindowBg] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); 110 | colors[ImGuiCol_ChildBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); 111 | colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f); 112 | colors[ImGuiCol_Border] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); 113 | colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); 114 | colors[ImGuiCol_FrameBg] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); 115 | colors[ImGuiCol_FrameBgHovered] = ImVec4(0.12f, 0.20f, 0.28f, 1.00f); 116 | colors[ImGuiCol_FrameBgActive] = ImVec4(0.09f, 0.12f, 0.14f, 1.00f); 117 | colors[ImGuiCol_TitleBg] = ImVec4(0.09f, 0.12f, 0.14f, 0.65f); 118 | colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.10f, 0.12f, 1.00f); 119 | colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 0.51f); 120 | colors[ImGuiCol_MenuBarBg] = ImVec4(0.15f, 0.18f, 0.22f, 1.00f); 121 | colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.39f); 122 | colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); 123 | colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.18f, 0.22f, 0.25f, 1.00f); 124 | colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.09f, 0.21f, 0.31f, 1.00f); 125 | colors[ImGuiCol_CheckMark] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 126 | colors[ImGuiCol_SliderGrab] = ImVec4(0.28f, 0.56f, 1.00f, 1.00f); 127 | colors[ImGuiCol_SliderGrabActive] = ImVec4(0.37f, 0.61f, 1.00f, 1.00f); 128 | colors[ImGuiCol_Button] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); 129 | colors[ImGuiCol_ButtonHovered] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 130 | colors[ImGuiCol_ButtonActive] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 131 | colors[ImGuiCol_Header] = ImVec4(0.20f, 0.25f, 0.29f, 0.55f); 132 | colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 133 | colors[ImGuiCol_HeaderActive] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 134 | colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.25f, 0.29f, 1.00f); 135 | colors[ImGuiCol_SeparatorHovered] = ImVec4(0.10f, 0.40f, 0.75f, 0.78f); 136 | colors[ImGuiCol_SeparatorActive] = ImVec4(0.10f, 0.40f, 0.75f, 1.00f); 137 | colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); 138 | colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); 139 | colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); 140 | colors[ImGuiCol_Tab] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); 141 | colors[ImGuiCol_TabHovered] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 142 | colors[ImGuiCol_TabActive] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 143 | colors[ImGuiCol_TabUnfocused] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); 144 | colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.11f, 0.15f, 0.17f, 1.00f); 145 | colors[ImGuiCol_PlotLines] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 146 | colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); 147 | colors[ImGuiCol_PlotHistogram] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 148 | colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); 149 | colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f); 150 | colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); 151 | colors[ImGuiCol_NavHighlight] = ImVec4(0.00f, 0.50f, 0.50f, 1.0f); 152 | colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); 153 | colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); 154 | colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); 155 | } 156 | 157 | bool Init(void) { 158 | IMGUI_CHECKVERSION(); 159 | ImGui::CreateContext(); 160 | ImGuiIO &io = ImGui::GetIO(); 161 | (void)io; 162 | 163 | io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; 164 | 165 | if (!GUI::InitEGL(nwindowGetDefault())) 166 | return false; 167 | 168 | gladLoadGL(); 169 | 170 | ImGui_ImplSwitch_Init("#version 130"); 171 | 172 | // Load nintendo font 173 | PlFontData standard, extended, chinese, korean; 174 | static ImWchar extended_range[] = {0xE000, 0xE152}; 175 | 176 | if ((R_SUCCEEDED(plGetSharedFontByType(std::addressof(standard), PlSharedFontType_Standard))) && 177 | R_SUCCEEDED(plGetSharedFontByType(std::addressof(extended), PlSharedFontType_NintendoExt)) && 178 | R_SUCCEEDED(plGetSharedFontByType(std::addressof(chinese), PlSharedFontType_ChineseSimplified)) && 179 | R_SUCCEEDED(plGetSharedFontByType(std::addressof(korean), PlSharedFontType_KO))) { 180 | 181 | u8 *px = nullptr; 182 | int w = 0, h = 0, bpp = 0; 183 | ImFontConfig font_cfg; 184 | 185 | font_cfg.FontDataOwnedByAtlas = false; 186 | io.Fonts->AddFontFromMemoryTTF(standard.address, standard.size, 20.f, std::addressof(font_cfg), io.Fonts->GetGlyphRangesDefault()); 187 | 188 | if (cfg.multi_lang) { 189 | font_cfg.MergeMode = true; 190 | io.Fonts->AddFontFromMemoryTTF(extended.address, extended.size, 20.f, std::addressof(font_cfg), extended_range); 191 | io.Fonts->AddFontFromMemoryTTF(chinese.address, chinese.size, 20.f, std::addressof(font_cfg), io.Fonts->GetGlyphRangesChineseFull()); 192 | io.Fonts->AddFontFromMemoryTTF(korean.address, korean.size, 20.f, std::addressof(font_cfg), io.Fonts->GetGlyphRangesKorean()); 193 | } 194 | 195 | // build font atlas 196 | io.Fonts->GetTexDataAsAlpha8(std::addressof(px), std::addressof(w), std::addressof(h), std::addressof(bpp)); 197 | io.Fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; 198 | io.Fonts->Build(); 199 | } 200 | 201 | GUI::SetDefaultTheme(); 202 | return true; 203 | } 204 | 205 | bool Loop(u64 &key) { 206 | if (!appletMainLoop()) 207 | return false; 208 | 209 | key = ImGui_ImplSwitch_NewFrame(); 210 | ImGui::NewFrame(); 211 | return !(key & HidNpadButton_Plus); 212 | } 213 | 214 | void Render(void) { 215 | ImGui::Render(); 216 | ImGuiIO &io = ImGui::GetIO(); (void)io; 217 | glViewport(0, 0, static_cast(io.DisplaySize.x), static_cast(io.DisplaySize.y)); 218 | glClearColor(0.00f, 0.00f, 0.00f, 1.00f); 219 | glClear(GL_COLOR_BUFFER_BIT); 220 | ImGui_ImplSwitch_RenderDrawData(ImGui::GetDrawData()); 221 | GUI::SwapBuffers(); 222 | } 223 | 224 | void Exit(void) { 225 | ImGui_ImplSwitch_Shutdown(); 226 | GUI::ExitEGL(); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /source/image.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "config.hpp" 4 | #include "gui.hpp" 5 | #include "imgui.h" 6 | #include "popups.hpp" 7 | #include "windows.hpp" 8 | 9 | #define IMGUI_DEFINE_MATH_OPERATORS 10 | #include "imgui_internal.h" 11 | 12 | namespace ImageViewer { 13 | void ClearTextures(void) { 14 | data.textures.clear(); 15 | data.frame_count = 0; 16 | } 17 | 18 | bool HandleScroll(int index) { 19 | if (data.entries[index].type == FsDirEntryType_Dir) 20 | return false; 21 | else { 22 | data.selected = index; 23 | 24 | char fs_path[FS_MAX_PATH + 1]; 25 | if ((std::snprintf(fs_path, FS_MAX_PATH, "%s/%s", cwd.c_str(), data.entries[index].name)) > 0) { 26 | bool ret = Textures::LoadImageFile(fs_path, data.textures); 27 | IM_ASSERT(ret); 28 | return ret; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | 35 | bool HandlePrev(void) { 36 | bool ret = false; 37 | 38 | for (int i = data.selected - 1; i > 0; i--) { 39 | std::string filename = data.entries[i].name; 40 | if (filename.empty()) 41 | continue; 42 | 43 | if (!(ret = ImageViewer::HandleScroll(i))) 44 | continue; 45 | else 46 | break; 47 | } 48 | 49 | return ret; 50 | } 51 | 52 | bool HandleNext(void) { 53 | bool ret = false; 54 | 55 | if (data.selected == data.entries.size()) 56 | return ret; 57 | 58 | for (unsigned int i = data.selected + 1; i < data.entries.size(); i++) { 59 | if (!(ret = ImageViewer::HandleScroll(i))) 60 | continue; 61 | else 62 | break; 63 | } 64 | 65 | return ret; 66 | } 67 | 68 | void HandleControls(u64 &key, bool &properties) { 69 | if (key & HidNpadButton_X) 70 | properties = true; 71 | 72 | if (ImGui::IsKeyDown(ImGuiKey_GamepadDpadDown)) { 73 | data.zoom_factor -= 0.5f * ImGui::GetIO().DeltaTime; 74 | 75 | if (data.zoom_factor < 0.1f) 76 | data.zoom_factor = 0.1f; 77 | } 78 | else if (ImGui::IsKeyDown(ImGuiKey_GamepadDpadUp)) { 79 | data.zoom_factor += 0.5f * ImGui::GetIO().DeltaTime; 80 | 81 | if (data.zoom_factor > 5.0f) 82 | data.zoom_factor = 5.0f; 83 | } 84 | 85 | if (!properties) { 86 | if (key & HidNpadButton_B) { 87 | ImageViewer::ClearTextures(); 88 | data.zoom_factor = 1.0f; 89 | data.state = WINDOW_STATE_FILEBROWSER; 90 | } 91 | 92 | if (key & HidNpadButton_L) { 93 | ImageViewer::ClearTextures(); 94 | 95 | if (!ImageViewer::HandlePrev()) 96 | data.state = WINDOW_STATE_FILEBROWSER; 97 | } 98 | else if (key & HidNpadButton_R) { 99 | ImageViewer::ClearTextures(); 100 | 101 | if (!ImageViewer::HandleNext()) 102 | data.state = WINDOW_STATE_FILEBROWSER; 103 | } 104 | } 105 | } 106 | } 107 | 108 | namespace Windows { 109 | void ImageViewer(bool &properties, bool &file_stat) { 110 | Windows::SetupWindow(); 111 | 112 | ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); 113 | ImGuiWindowFlags_ filename_flag = !cfg.image_filename? ImGuiWindowFlags_NoTitleBar : ImGuiWindowFlags_None; 114 | 115 | if (ImGui::Begin(data.entries[data.selected].name, nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_HorizontalScrollbar | filename_flag)) { 116 | if (((data.textures[0].width * data.zoom_factor) <= 1280) && ((data.textures[0].height * data.zoom_factor) <= 720)) 117 | ImGui::SetCursorPos((ImGui::GetWindowSize() - ImVec2((data.textures[0].width * data.zoom_factor), (data.textures[0].height * data.zoom_factor))) * 0.5f); 118 | 119 | if (data.textures.size() > 1) { 120 | svcSleepThread(data.textures[data.frame_count].delay); 121 | ImGui::Image(reinterpret_cast(data.textures[data.frame_count].id), (ImVec2((data.textures[data.frame_count].width * data.zoom_factor), 122 | (data.textures[data.frame_count].height * data.zoom_factor)))); 123 | data.frame_count++; 124 | 125 | // Reset frame counter 126 | if (data.frame_count == data.textures.size() - 1) 127 | data.frame_count = 0; 128 | } 129 | else 130 | ImGui::Image(reinterpret_cast(data.textures[0].id), ImVec2((data.textures[0].width * data.zoom_factor), (data.textures[0].height * data.zoom_factor))); 131 | } 132 | 133 | if (properties) 134 | Popups::ImageProperties(properties, data.textures[0], file_stat); 135 | 136 | Windows::ExitWindow(); 137 | ImGui::PopStyleVar(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /source/keyboard.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.hpp" 5 | #include "keyboard.hpp" 6 | #include "language.hpp" 7 | #include "log.hpp" 8 | 9 | namespace Keyboard { 10 | // Empty strings are invalid. 11 | SwkbdTextCheckResult ValidateText(char *string, size_t size) { 12 | if (std::strcmp(string, "") == 0) { 13 | std::strncpy(string, strings[cfg.lang][Lang::KeyboardEmpty], size); 14 | return SwkbdTextCheckResult_Bad; 15 | } 16 | 17 | return SwkbdTextCheckResult_OK; 18 | } 19 | 20 | std::string GetText(const std::string &guide_text, const std::string &initial_text) { 21 | Result ret = 0; 22 | SwkbdConfig swkbd; 23 | static char input_string[256]; 24 | 25 | if (R_FAILED(ret = swkbdCreate(std::addressof(swkbd), 0))) { 26 | Log::Error("swkbdCreate() failed: 0x%x\n", ret); 27 | swkbdClose(std::addressof(swkbd)); 28 | return std::string(); 29 | } 30 | 31 | swkbdConfigMakePresetDefault(std::addressof(swkbd)); 32 | if (!guide_text.empty()) 33 | swkbdConfigSetGuideText(std::addressof(swkbd), guide_text.c_str()); 34 | 35 | if (!initial_text.empty()) 36 | swkbdConfigSetInitialText(std::addressof(swkbd), initial_text.c_str()); 37 | 38 | swkbdConfigSetTextCheckCallback(std::addressof(swkbd), Keyboard::ValidateText); 39 | if (R_FAILED(ret = swkbdShow(std::addressof(swkbd), input_string, sizeof(input_string)))) { 40 | Log::Error("swkbdShow() failed: 0x%x\n", ret); 41 | swkbdClose(std::addressof(swkbd)); 42 | return std::string(); 43 | } 44 | 45 | swkbdClose(std::addressof(swkbd)); 46 | return input_string; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /source/language.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "language.hpp" 4 | 5 | // TODO: Japanese 6 | static const char *strings_jp[] { 7 | "OK", 8 | "Cancel", 9 | 10 | "Options", 11 | "Select All", 12 | "Clear All", 13 | "Properties", 14 | "Rename", 15 | "New Folder", 16 | "New File", 17 | "Copy", 18 | "Move", 19 | "Paste", 20 | "Delete", 21 | "Set Archive Bit", 22 | "Enter name", 23 | "Enter folder name", 24 | "Enter file name", 25 | "Copying:", 26 | 27 | "Name: ", 28 | "Size: ", 29 | "Created: ", 30 | "Modified: ", 31 | "Accessed: ", 32 | "Width: ", 33 | "Height: ", 34 | 35 | "This action cannot be undone.", 36 | "Do you wish to delete the following:", 37 | "Do you wish to delete ", 38 | 39 | "Extract archive", 40 | "This action may take a while.", 41 | "Do you wish to extract ", 42 | "Extracting:", 43 | 44 | "Settings", 45 | "Sort Settings", 46 | "Language", 47 | "USB", 48 | "Unmount USB devices", 49 | "Image Viewer", 50 | "Developer Options", 51 | "Multiple Character Set (Improves boot speed when disabled)", 52 | "About", 53 | "Check for Updates", 54 | " Display filename", 55 | " Enable logs", 56 | " Enable support for special symbols/characters", 57 | "version", 58 | "Author", 59 | "Banner", 60 | 61 | "Update", 62 | "Could not connect to network.", 63 | "An update is available.", 64 | "Do you wish to download and install NX-Shell version ", 65 | "Update was successful.", 66 | "Please exit and rerun the application.", 67 | "You are on the latest version.", 68 | 69 | "Do you wish to unmount all the connected USB devices?", 70 | "The USB device can now be safely removed.", 71 | 72 | "The name cannot be empty." 73 | }; 74 | 75 | static const char *strings_en[] { 76 | "OK", 77 | "Cancel", 78 | 79 | "Options", 80 | "Select All", 81 | "Clear All", 82 | "Properties", 83 | "Rename", 84 | "New Folder", 85 | "New File", 86 | "Copy", 87 | "Move", 88 | "Paste", 89 | "Delete", 90 | "Set Archive Bit", 91 | "Enter name", 92 | "Enter folder name", 93 | "Enter file name", 94 | "Copying:", 95 | 96 | "Name: ", 97 | "Size: ", 98 | "Created: ", 99 | "Modified: ", 100 | "Accessed: ", 101 | "Width: ", 102 | "Height: ", 103 | 104 | "This action cannot be undone.", 105 | "Do you wish to delete the following:", 106 | "Do you wish to delete ", 107 | 108 | "Extract archive", 109 | "This action may take a while.", 110 | "Do you wish to extract ", 111 | "Extracting:", 112 | 113 | "Settings", 114 | "Sort Settings", 115 | "Language", 116 | "USB", 117 | "Unmount USB devices", 118 | "Image Viewer", 119 | "Developer Options", 120 | "Multiple Character Set (Improves boot speed when disabled)", 121 | "About", 122 | "Check for Updates", 123 | " Display filename", 124 | " Enable logs", 125 | " Enable support for special symbols/characters", 126 | "version", 127 | "Author", 128 | "Banner", 129 | 130 | "Update", 131 | "Could not connect to network.", 132 | "An update is available.", 133 | "Do you wish to download and install NX-Shell version ", 134 | "Update was successful.", 135 | "Please exit and rerun the application.", 136 | "You are on the latest version.", 137 | 138 | "Do you wish to unmount all the connected USB devices?", 139 | "The USB device can now be safely removed.", 140 | 141 | "The name cannot be empty." 142 | }; 143 | 144 | // TODO: French 145 | static const char *strings_fr[] { 146 | "OK", 147 | "Cancel", 148 | 149 | "Options", 150 | "Select All", 151 | "Clear All", 152 | "Properties", 153 | "Rename", 154 | "New Folder", 155 | "New File", 156 | "Copy", 157 | "Move", 158 | "Paste", 159 | "Delete", 160 | "Set Archive Bit", 161 | "Enter name", 162 | "Enter folder name", 163 | "Enter file name", 164 | "Copying:", 165 | 166 | "Name: ", 167 | "Size: ", 168 | "Created: ", 169 | "Modified: ", 170 | "Accessed: ", 171 | "Width: ", 172 | "Height: ", 173 | 174 | "This action cannot be undone.", 175 | "Do you wish to delete the following:", 176 | "Do you wish to delete ", 177 | 178 | "Extract archive", 179 | "This action may take a while.", 180 | "Do you wish to extract ", 181 | "Extracting:", 182 | 183 | "Settings", 184 | "Sort Settings", 185 | "Language", 186 | "USB", 187 | "Unmount USB devices", 188 | "Image Viewer", 189 | "Developer Options", 190 | "Multiple Character Set (Improves boot speed when disabled)", 191 | "About", 192 | "Check for Updates", 193 | " Display filename", 194 | " Enable logs", 195 | " Enable support for special symbols/characters", 196 | "version", 197 | "Author", 198 | "Banner", 199 | 200 | "Update", 201 | "Could not connect to network.", 202 | "An update is available.", 203 | "Do you wish to download and install NX-Shell version ", 204 | "Update was successful.", 205 | "Please exit and rerun the application.", 206 | "You are on the latest version.", 207 | 208 | "Do you wish to unmount all the connected USB devices?", 209 | "The USB device can now be safely removed.", 210 | 211 | "The name cannot be empty." 212 | }; 213 | 214 | static const char *strings_de[] { 215 | "OK", 216 | "Abbrechen", 217 | 218 | "Optionen", 219 | "Alle auswählen", 220 | "Alle entfernen", 221 | "Eigenschaften", 222 | "Umbenennen", 223 | "Neuer Ordner", 224 | "Neue Datei", 225 | "Kopieren", 226 | "Verschieben", 227 | "Einfügen", 228 | "Löschen", 229 | "\"Archive Bit\" setzen", 230 | "Name eingeben", 231 | "Ordnername eingeben", 232 | "Dateiname eingeben", 233 | "Kopiere:", 234 | 235 | "Name: ", 236 | "Größe: ", 237 | "Erstellt: ", 238 | "Geändert: ", 239 | "Zugegriffen: ", 240 | "Breite: ", 241 | "Höhe: ", 242 | 243 | "Dies kann nicht rückgängig gemacht werden.", 244 | "Möchten Sie Folgendes löschen:", 245 | "Möchten Sie Folgendes löschen:", 246 | 247 | "Archiv entpacken", 248 | "Dies kann eine Weile dauern.", 249 | "Möchten Sie Folgendes extrahieren:", 250 | "Extrahiere:", 251 | 252 | "Einstellungen", 253 | "Sortiereinstellung", 254 | "Sprache", 255 | "USB", 256 | "Unmount USB devices", 257 | "Bildanzeige", 258 | "Entwickleroptionen", 259 | "Multiple Character Set (Improves boot speed when disabled)", 260 | "Über", 261 | "Nach Updates suchen", 262 | " Dateiname anzeigen", 263 | " Log aktivieren", 264 | " Enable support for special symbols/characters", 265 | "Version", 266 | "Autor", 267 | "Banner", 268 | 269 | "Update", 270 | "Es konnte keine Verbindung zum Netzwerk herstellt werden.", 271 | "Ein Update ist verfügbar.", 272 | "Möchten Sie die folgende NX-Shell-Version herunterladen und installieren:", 273 | "Update war erfolgreich.", 274 | "Bitte beenden Sie die Anwendung und starten Sie sie erneut.", 275 | "Sie sind bereits auf der neusten Version.", 276 | 277 | "Do you wish to unmount all the connected USB devices?", 278 | "The USB device can now be safely removed.", 279 | 280 | "Der Name darf nicht leer sein." 281 | }; 282 | 283 | // TODO: Italian 284 | static const char *strings_it[] { 285 | "OK", 286 | "Cancel", 287 | 288 | "Options", 289 | "Select All", 290 | "Clear All", 291 | "Properties", 292 | "Rename", 293 | "New Folder", 294 | "New File", 295 | "Copy", 296 | "Move", 297 | "Paste", 298 | "Delete", 299 | "Set Archive Bit", 300 | "Enter name", 301 | "Enter folder name", 302 | "Enter file name", 303 | "Copying:", 304 | 305 | "Name: ", 306 | "Size: ", 307 | "Created: ", 308 | "Modified: ", 309 | "Accessed: ", 310 | "Width: ", 311 | "Height: ", 312 | 313 | "This action cannot be undone.", 314 | "Do you wish to delete the following:", 315 | "Do you wish to delete ", 316 | 317 | "Extract archive", 318 | "This action may take a while.", 319 | "Do you wish to extract ", 320 | "Extracting:", 321 | 322 | "Settings", 323 | "Sort Settings", 324 | "Language", 325 | "USB", 326 | "Unmount USB devices", 327 | "Image Viewer", 328 | "Developer Options", 329 | "Multiple Character Set (Improves boot speed when disabled)", 330 | "About", 331 | "Check for Updates", 332 | " Display filename", 333 | " Enable logs", 334 | " Enable support for special symbols/characters", 335 | "version", 336 | "Author", 337 | "Banner", 338 | 339 | "Update", 340 | "Could not connect to network.", 341 | "An update is available.", 342 | "Do you wish to download and install NX-Shell version ", 343 | "Update was successful.", 344 | "Please exit and rerun the application.", 345 | "You are on the latest version.", 346 | 347 | "Do you wish to unmount all the connected USB devices?", 348 | "The USB device can now be safely removed.", 349 | 350 | "The name cannot be empty." 351 | }; 352 | 353 | // Spanish 354 | static const char *strings_es[] { 355 | "Aceptar", 356 | "Cancelar", 357 | 358 | "Opciones", 359 | "Seleccionar Todo", 360 | "Limpiar Todo", 361 | "Propiedades", 362 | "Renombrar", 363 | "Nueva Carpeta", 364 | "Nuevo Archivo", 365 | "Copiar", 366 | "Mover", 367 | "Pegar", 368 | "Eliminar", 369 | "Establecer Bit de Archivo", 370 | "Ingresar Nombre", 371 | "Ingresar Nombre de Carpeta", 372 | "Ingresar Nombre de Archivo", 373 | "Copiando:", 374 | 375 | "Nombre: ", 376 | "Tamaño: ", 377 | "Creado: ", 378 | "Modificado: ", 379 | "Accedido: ", 380 | "Ancho: ", 381 | "Alto: ", 382 | 383 | "Esta acción no se puede deshacer.", 384 | "Deseas eliminar lo siguiente:", 385 | "Deseas eliminar ", 386 | 387 | "Extraer archivo", 388 | "Esta acción puede tomar un tiempo.", 389 | "Deseas extraer ", 390 | "Extrayendo:", 391 | 392 | "Ajustes", 393 | "Ajustes de organización", 394 | "Idioma", 395 | "USB", 396 | "Unmount USB devices", 397 | "Visualizador de Imagen", 398 | "Opciones de Desarrollador", 399 | "Múltiples Juegos de Caracteres (arranque más rápido si se desactiva)", 400 | "Acerca de", 401 | "Buscar Actualizaciones", 402 | " Mostrar nombre de archivo", 403 | " Habilitar logs", 404 | " Enable support for special symbols/characters", 405 | "versión", 406 | "Autor", 407 | "Banner", 408 | 409 | "Actualizar", 410 | "No se puede conectar a la red.", 411 | "Una actualización está disponible.", 412 | "Deseas descargar e instalar la versión de NX-Shell ", 413 | "Actualización exitosa.", 414 | "Por favor cerrar y reiniciar la aplicación.", 415 | "Estás en la última versión.", 416 | 417 | "¿Quieres desmontar todos los dispositivos USB conectados?", 418 | "El dispositivo USB ahora puede ser removido de forma segura.", 419 | 420 | "El nombre no puede estar vacío." 421 | }; 422 | 423 | // Simplified Chinese ("Chinese") 424 | static const char *strings_sc[] { 425 | "确定", 426 | "取消", 427 | 428 | "选项", 429 | "选择全部", 430 | "清除全部", 431 | "属性", 432 | "重命名", 433 | "新建文件夹", 434 | "新建文件", 435 | "复制", 436 | "移动", 437 | "粘贴", 438 | "删除", 439 | "设置存档位", 440 | "输入名字", 441 | "输入文件夹名", 442 | "输入文件名", 443 | "复制: ", 444 | 445 | "文件名: ", 446 | "大小: ", 447 | "创建日期: ", 448 | "最后修改: ", 449 | "最后访问: ", 450 | "宽度: ", 451 | "高度: ", 452 | 453 | "本操作不可逆.", 454 | "确定删除下列文件吗:", 455 | "确定删除吗 ", 456 | 457 | "提取归档", 458 | "本功能需要花费一点时间.", 459 | "确定提取吗 ", 460 | "提取中:", 461 | 462 | "设置", 463 | "排序方式", 464 | "语言", 465 | "USB", 466 | "卸载USB设备", 467 | "图片查看器", 468 | "开发人员选项", 469 | "多字符集(禁用时提高启动速度)", 470 | "关于", 471 | "检查更新", 472 | " 显示文件名", 473 | " 打开日志", 474 | " 启用对特殊符号/字符的支持", 475 | "版本", 476 | "作者", 477 | "横幅", 478 | 479 | "更新", 480 | "连接网络失败.", 481 | "有新版本的更新可用.", 482 | "您希望下载并安装NX-Shell版本吗 ", 483 | "更新成功.", 484 | "请退出并重新运行应用程序.", 485 | "你使用的是最新版本.", 486 | 487 | "您想卸载所有连接的 USB 设备吗?", 488 | "现在可以安全地移除 USB 设备。", 489 | 490 | "名称不能为空." 491 | }; 492 | 493 | // TODO: Korean 494 | static const char *strings_ko[] { 495 | "확인", 496 | "취소", 497 | 498 | "옵션", 499 | "모두 선택", 500 | "모두 지움", 501 | "속성", 502 | "이름 바꾸기", 503 | "새 폴더", 504 | "새 파일", 505 | "복사", 506 | "이동", 507 | "붙여넣기", 508 | "삭제", 509 | "아카이브 비트 설정", 510 | "이름 입력", 511 | "폴더 이름 입력", 512 | "파일 이름 입력", 513 | "복사 중:", 514 | 515 | "이름: ", 516 | "크기: ", 517 | "제작: ", 518 | "수정: ", 519 | "접속: ", 520 | "너비: ", 521 | "높이: ", 522 | 523 | "이 작업은 취소할 수 없습니다.", 524 | "다음을 삭제하겠습니까:", 525 | "삭제하겠습니까 ", 526 | 527 | "파일 해제", 528 | "이 작업은 시간이 걸릴 수 있습니다.", 529 | "해제하겠습니까? ", 530 | "해제 중:", 531 | 532 | "설정", 533 | "정렬 설정", 534 | "언어", 535 | "USB", 536 | "USB 장치 마운트 해제", 537 | "이미지 뷰어", 538 | "개발자 옵션", 539 | "다중 문자 세트 (비활성화 시 부팅 속도 향상)", 540 | "정보", 541 | "업데이트 확인", 542 | " 파일 이름 표시", 543 | " 로그 활성화", 544 | " 특수 기호/문자 지원 활성화", 545 | "버전", 546 | "제작자", 547 | "배너", 548 | 549 | "업데이트", 550 | "네트워크에 연결할 수 없습니다.", 551 | "업데이트가 가능합니다.", 552 | "NX-Shell 버전을 다운로드하여 설치하겠습니까? ", 553 | "업데이트에 성공했습니다.", 554 | "응용 프로그램을 종료하고 다시 실행하십시오.", 555 | "최신 버전을 사용 중입니다.", 556 | 557 | "연결된 모든 USB 장치를 마운트 해제하겠습니까?", 558 | "이제 USB 장치를 안전하게 제거할 수 있습니다.", 559 | 560 | "이름은 공백이 될 수 없습니다." 561 | }; 562 | 563 | // TODO: Dutch 564 | static const char *strings_nl[] { 565 | "OK", 566 | "Cancel", 567 | 568 | "Options", 569 | "Select All", 570 | "Clear All", 571 | "Properties", 572 | "Rename", 573 | "New Folder", 574 | "New File", 575 | "Copy", 576 | "Move", 577 | "Paste", 578 | "Delete", 579 | "Set Archive Bit", 580 | "Enter name", 581 | "Enter folder name", 582 | "Enter file name", 583 | "Copying:", 584 | 585 | "Name: ", 586 | "Size: ", 587 | "Created: ", 588 | "Modified: ", 589 | "Accessed: ", 590 | "Width: ", 591 | "Height: ", 592 | 593 | "This action cannot be undone.", 594 | "Do you wish to delete the following:", 595 | "Do you wish to delete ", 596 | 597 | "Extract archive", 598 | "This action may take a while.", 599 | "Do you wish to extract ", 600 | "Extracting:", 601 | 602 | "Settings", 603 | "Sort Settings", 604 | "Language", 605 | "USB", 606 | "Unmount USB devices", 607 | "Image Viewer", 608 | "Developer Options", 609 | "Multiple Character Set (Improves boot speed when disabled)", 610 | "About", 611 | "Check for Updates", 612 | " Display filename", 613 | " Enable logs", 614 | " Enable support for special symbols/characters", 615 | "version", 616 | "Author", 617 | "Banner", 618 | 619 | "Update", 620 | "Could not connect to network.", 621 | "An update is available.", 622 | "Do you wish to download and install NX-Shell version ", 623 | "Update was successful.", 624 | "Please exit and rerun the application.", 625 | "You are on the latest version.", 626 | 627 | "Do you wish to unmount all the connected USB devices?", 628 | "The USB device can now be safely removed.", 629 | 630 | "The name cannot be empty." 631 | }; 632 | 633 | // Portuguese 634 | static const char *strings_pt[] { 635 | "OK", 636 | "Cancelar", 637 | 638 | "Opções", 639 | "Selecionar Tudo", 640 | "Limpar Tudo", 641 | "Propriedades", 642 | "Renomear", 643 | "Nova Pasta", 644 | "Novo Arquivo", 645 | "Copiar", 646 | "Mover", 647 | "Colar", 648 | "Deletar", 649 | "Definir Bit de Arquivo", 650 | "Insira o nome", 651 | "Insira o nome da pasta", 652 | "Insira o nome do arquivo", 653 | "Copiando:", 654 | 655 | "Nome: ", 656 | "Tamanho: ", 657 | "Criado: ", 658 | "Modificado: ", 659 | "Acessado: ", 660 | "Largura: ", 661 | "Altura: ", 662 | 663 | "Essa ação não pode ser desfeita.", 664 | "Você deseja deletar os seguintes:", 665 | "Você deseja deletar ", 666 | 667 | "Extrair arquivo", 668 | "Essa ação pode demorar um pouco.", 669 | "Você deseja extrair ", 670 | "Extraindo:", 671 | 672 | "Configurações", 673 | "Configurações de Organização", 674 | "Idioma", 675 | "USB", 676 | "Desmontar dispositivos USB", 677 | "Visualizador de Imagens", 678 | "Opções de Desenvolvedor", 679 | "Múltiplos Conjuntos de Caracteres (Melhora a velocidade de inicialização quando desabilitado)", 680 | "Sobre", 681 | "Verificar se há Atualizações", 682 | " Exibir nome de arquivo", 683 | " Habilitar logs", 684 | " Habilitar suporte para símbolos/caracteres especiais", 685 | "versão", 686 | "Autor", 687 | "Banner", 688 | 689 | "Atualizar", 690 | "Não foi possível se conectar à internet.", 691 | "Uma atualização está disponível.", 692 | "Você deseja baixar e instalar NX-Shell versão ", 693 | "Atualização feita com sucesso.", 694 | "Por favor, saia e reinicie a aplicação.", 695 | "Você está na versão mais recente.", 696 | 697 | "Você deseja desmontar todos os dispositivos USB conectados?", 698 | "O dispositivo USB pode ser removido com segurança.", 699 | 700 | "O nome não pode estar vazio." 701 | }; 702 | 703 | // TODO: Russian 704 | static const char *strings_ru[] { 705 | "OK", 706 | "Cancel", 707 | 708 | "Options", 709 | "Select All", 710 | "Clear All", 711 | "Properties", 712 | "Rename", 713 | "New Folder", 714 | "New File", 715 | "Copy", 716 | "Move", 717 | "Paste", 718 | "Delete", 719 | "Set Archive Bit", 720 | "Enter name", 721 | "Enter folder name", 722 | "Enter file name", 723 | "Copying:", 724 | 725 | "Name: ", 726 | "Size: ", 727 | "Created: ", 728 | "Modified: ", 729 | "Accessed: ", 730 | "Width: ", 731 | "Height: ", 732 | 733 | "This action cannot be undone.", 734 | "Do you wish to delete the following:", 735 | "Do you wish to delete ", 736 | 737 | "Extract archive", 738 | "This action may take a while.", 739 | "Do you wish to extract ", 740 | "Extracting:", 741 | 742 | "Settings", 743 | "Sort Settings", 744 | "Language", 745 | "USB", 746 | "Unmount USB devices", 747 | "Image Viewer", 748 | "Developer Options", 749 | "Multiple Character Set (Improves boot speed when disabled)", 750 | "About", 751 | "Check for Updates", 752 | " Display filename", 753 | " Enable logs", 754 | " Enable support for special symbols/characters", 755 | "version", 756 | "Author", 757 | "Banner", 758 | 759 | "Update", 760 | "Could not connect to network.", 761 | "An update is available.", 762 | "Do you wish to download and install NX-Shell version ", 763 | "Update was successful.", 764 | "Please exit and rerun the application.", 765 | "You are on the latest version.", 766 | 767 | "Do you wish to unmount all the connected USB devices?", 768 | "The USB device can now be safely removed.", 769 | 770 | "The name cannot be empty." 771 | }; 772 | 773 | // Traditional Chinese ("Taiwanese") 774 | static const char *strings_tw[] { 775 | "確定", 776 | "取消", 777 | 778 | "選項", 779 | "選擇全部", 780 | "清除全部", 781 | "屬性", 782 | "重命名", 783 | "新建文件夾", 784 | "新建文件", 785 | "復制", 786 | "移動", 787 | "粘貼", 788 | "刪除", 789 | "設置存檔位", 790 | "輸入名字", 791 | "輸入文件夾名", 792 | "輸入文件名", 793 | "復制:", 794 | 795 | "文件名: ", 796 | "大小: ", 797 | "創建日期: ", 798 | "最後修改: ", 799 | "最後訪問: ", 800 | "寬度: ", 801 | "高度: ", 802 | 803 | "本操作不可逆.", 804 | "確定刪除下列文件嗎:", 805 | "確定刪除嗎 ", 806 | 807 | "提取歸檔", 808 | "本功能需要花費壹點時間.", 809 | "確定提取嗎 ", 810 | "提取中:", 811 | 812 | "設置", 813 | "排序方式", 814 | "語言", 815 | "USB", 816 | "卸載 USB 設備", 817 | "圖片查看器", 818 | "開發人員選項", 819 | "多字符集(禁用時提高啟動速度)", 820 | "關於", 821 | "檢查更新", 822 | " 顯示文件名", 823 | " 打開日誌", 824 | " 啟用對特殊符號/字符的支持", 825 | "版本", 826 | "作者", 827 | "橫幅", 828 | 829 | "更新", 830 | "連接網絡失敗.", 831 | "有新版本的更新可用.", 832 | "您希望下載並安裝NX-Shell版本嗎 ", 833 | "更新成功.", 834 | "請退出並重新運行應用程序.", 835 | "妳使用的是最新版本.", 836 | 837 | "您想卸載所有連接的 USB 設備嗎?", 838 | "現在可以安全地移除 USB 設備。", 839 | 840 | "名稱不能為空." 841 | }; 842 | 843 | const char **strings[Lang::Max] = { 844 | strings_jp, 845 | strings_en, 846 | strings_fr, 847 | strings_de, 848 | strings_it, 849 | strings_es, 850 | strings_sc, 851 | strings_ko, 852 | strings_nl, 853 | strings_pt, 854 | strings_ru, 855 | strings_tw 856 | }; 857 | -------------------------------------------------------------------------------- /source/log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "config.hpp" 4 | #include "fs.hpp" 5 | 6 | namespace Log { 7 | static FsFile file; 8 | static s64 offset = 0; 9 | 10 | void Init(void) { 11 | const char *log_path = "/switch/NX-Shell/debug.log"; 12 | 13 | if (!cfg.dev_options) 14 | return; 15 | 16 | if (!FS::FileExists(log_path)) 17 | fsFsCreateFile(std::addressof(devices[FileSystemSDMC]), log_path, 0, 0); 18 | 19 | if (R_FAILED(fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), log_path, (FsOpenMode_Read | FsOpenMode_Write | FsOpenMode_Append), std::addressof(file)))) 20 | return; 21 | 22 | s64 size = 0; 23 | if (R_FAILED(fsFileGetSize(std::addressof(file), std::addressof(size)))) 24 | return; 25 | 26 | unsigned char *buffer = new unsigned char[size]; 27 | 28 | u64 bytes_read = 0; 29 | if (R_FAILED(fsFileRead(std::addressof(file), offset, buffer, size, FsReadOption_None, std::addressof(bytes_read)))) { 30 | delete[] buffer; 31 | return; 32 | } 33 | 34 | delete[] buffer; 35 | offset += bytes_read; 36 | } 37 | 38 | void Error(const char *data, ...) { 39 | if (!cfg.dev_options) 40 | return; 41 | 42 | char buf[256 + FS_MAX_PATH]; 43 | va_list args; 44 | va_start(args, data); 45 | std::vsnprintf(buf, sizeof(buf), data, args); 46 | va_end(args); 47 | 48 | std::string error_string = "[ERROR] "; 49 | error_string.append(buf); 50 | 51 | std::printf("%s", error_string.c_str()); 52 | 53 | if (R_FAILED(fsFileWrite(std::addressof(file), offset, error_string.data(), error_string.length(), FsWriteOption_None))) 54 | return; 55 | 56 | offset += error_string.length(); 57 | } 58 | 59 | void Exit(void) { 60 | if (!cfg.dev_options) 61 | return; 62 | 63 | fsFileClose(std::addressof(file)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.hpp" 5 | #include "fs.hpp" 6 | #include "gui.hpp" 7 | #include "imgui.h" 8 | #include "log.hpp" 9 | #include "textures.hpp" 10 | #include "windows.hpp" 11 | #include "usb.hpp" 12 | 13 | char __application_path[FS_MAX_PATH]; 14 | 15 | namespace Services { 16 | int Init(void) { 17 | Result ret = 0; 18 | 19 | devices[FileSystemSDMC] = *fsdevGetDeviceFileSystem("sdmc"); 20 | fs = std::addressof(devices[FileSystemSDMC]); 21 | 22 | fsOpenBisFileSystem(std::addressof(devices[FileSystemSafe]), FsBisPartitionId_SafeMode, ""); 23 | fsdevMountDevice("safe", devices[FileSystemSafe]); 24 | 25 | fsOpenBisFileSystem(std::addressof(devices[FileSystemUser]), FsBisPartitionId_User, ""); 26 | fsdevMountDevice("user", devices[FileSystemUser]); 27 | 28 | fsOpenBisFileSystem(std::addressof(devices[FileSystemSystem]), FsBisPartitionId_System, ""); 29 | fsdevMountDevice("system", devices[FileSystemSystem]); 30 | 31 | Config::Load(); 32 | Log::Init(); 33 | socketInitializeDefault(); 34 | nxlinkStdio(); 35 | 36 | if (R_FAILED(ret = romfsInit())) { 37 | Log::Error("romfsInit() failed: 0x%x\n", ret); 38 | return ret; 39 | } 40 | 41 | if (R_FAILED(ret = nifmInitialize(NifmServiceType_User))) { 42 | Log::Error("nifmInitialize(NifmServiceType_User) failed: 0x%x\n", ret); 43 | return ret; 44 | } 45 | 46 | if (R_FAILED(ret = plInitialize(PlServiceType_User))) { 47 | Log::Error("plInitialize(PlServiceType_User) failed: 0x%x\n", ret); 48 | return ret; 49 | } 50 | 51 | if (R_FAILED(ret = USB::Init())) { 52 | Log::Error("usbHsFsInitialize(0) failed: 0x%x\n", ret); 53 | return ret; 54 | } 55 | 56 | if (!GUI::Init()) 57 | Log::Error("GUI::Init() failed: 0x%x\n", ret); 58 | 59 | Textures::Init(); 60 | plExit(); 61 | romfsExit(); 62 | return 0; 63 | } 64 | 65 | void Exit(void) { 66 | Textures::Exit(); 67 | GUI::Exit(); 68 | USB::Exit(); 69 | nifmExit(); 70 | socketExit(); 71 | Log::Exit(); 72 | 73 | fsdevUnmountDevice("system"); 74 | fsdevUnmountDevice("user"); 75 | fsdevUnmountDevice("safe"); 76 | } 77 | } 78 | 79 | int main(int argc, char* argv[]) { 80 | u64 key = 0; 81 | 82 | Services::Init(); 83 | 84 | if (!FS::GetDirList(device, cwd, data.entries)) { 85 | Services::Exit(); 86 | return 0; 87 | } 88 | 89 | data.checkbox_data.checked.resize(data.entries.size()); 90 | FS::GetUsedStorageSpace(data.used_storage); 91 | FS::GetTotalStorageSpace(data.total_storage); 92 | 93 | while (GUI::Loop(key)) { 94 | Windows::MainWindow(data, key, false); 95 | GUI::Render(); 96 | } 97 | 98 | data.entries.clear(); 99 | Services::Exit(); 100 | return 0; 101 | } 102 | -------------------------------------------------------------------------------- /source/net.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "fs.hpp" 9 | #include "log.hpp" 10 | #include "net.hpp" 11 | 12 | namespace Net { 13 | static s64 offset = 0; 14 | 15 | bool GetNetworkStatus(void) { 16 | Result ret = 0; 17 | NifmInternetConnectionStatus status; 18 | if (R_FAILED(ret = nifmGetInternetConnectionStatus(nullptr, nullptr, std::addressof(status)))) { 19 | Log::Error("nifmGetInternetConnectionStatus() failed: 0x%x\n", ret); 20 | return false; 21 | } 22 | 23 | return (status == NifmInternetConnectionStatus_Connected); 24 | } 25 | 26 | bool GetAvailableUpdate(const std::string &tag) { 27 | if (tag.empty()) 28 | return false; 29 | 30 | int current_ver = ((VERSION_MAJOR * 100) + (VERSION_MINOR * 10) + VERSION_MICRO); 31 | 32 | std::string tag_name = tag; 33 | tag_name.erase(std::remove_if(tag_name.begin(), tag_name.end(), [](char c) { return c == '.'; }), tag_name.end()); 34 | int available_ver = std::stoi(tag_name); 35 | return (available_ver > current_ver); 36 | } 37 | 38 | size_t WriteJSONData(const char *ptr, size_t size, size_t nmemb, void *userdata) { 39 | const size_t total_size(size * nmemb); 40 | reinterpret_cast(userdata)->append(ptr, total_size); 41 | return total_size; 42 | } 43 | 44 | std::string GetLatestReleaseJSON(void) { 45 | std::string json = std::string(); 46 | CURL *handle = curl_easy_init(); 47 | 48 | curl_slist *header_data = nullptr; 49 | header_data = curl_slist_append(header_data, "Content-Type: application/json"); 50 | header_data = curl_slist_append(header_data, "Accept: application/json"); 51 | curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_data); 52 | 53 | curl_easy_setopt(handle, CURLOPT_URL, "https://api.github.com/repos/joel16/NX-Shell/releases/latest"); 54 | curl_easy_setopt(handle, CURLOPT_USERAGENT, "NX-Shell"); 55 | curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); 56 | curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); 57 | curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); 58 | curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, Net::WriteJSONData); 59 | curl_easy_setopt(handle, CURLOPT_WRITEDATA, std::addressof(json)); 60 | curl_easy_perform(handle); 61 | curl_easy_cleanup(handle); 62 | 63 | json_t *root; 64 | json_error_t error; 65 | root = json_loads(json.c_str(), JSON_DECODE_ANY, std::addressof(error)); 66 | 67 | if (!root) { 68 | Log::Error("json_loads failed on line %d: %s\n", error.line, error.text); 69 | return std::string(); 70 | } 71 | 72 | json_t *tag = json_object_get(root, "tag_name"); 73 | std::string tag_name = json_string_value(tag); 74 | return tag_name; 75 | } 76 | 77 | size_t WriteNROData(const char *ptr, size_t size, size_t nmemb, void *userdata) { 78 | if (R_SUCCEEDED(fsFileWrite(reinterpret_cast(userdata), offset, ptr, (size * nmemb), FsWriteOption_Flush))) 79 | offset += (size * nmemb); 80 | 81 | return (size * nmemb); 82 | } 83 | 84 | void GetLatestReleaseNRO(const std::string &tag) { 85 | Result ret = 0; 86 | FsFile file; 87 | const char path[FS_MAX_PATH] = "/switch/NX-Shell/NX-Shell_UPDATE.nro"; 88 | 89 | if (!FS::FileExists(path)) 90 | fsFsCreateFile(std::addressof(devices[FileSystemSDMC]), path, 0, 0); 91 | 92 | if (R_FAILED(ret = fsFsOpenFile(std::addressof(devices[FileSystemSDMC]), path, FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)))) { 93 | Log::Error("fsFsOpenFile(%s) failed: 0x%x\n", path, ret); 94 | return; 95 | } 96 | 97 | CURL *handle = curl_easy_init(); 98 | if (handle) { 99 | std::string URL = "https://github.com/joel16/NX-Shell/releases/download/" + tag + "/NX-Shell.nro"; 100 | curl_easy_setopt(handle, CURLOPT_URL, URL.c_str()); 101 | curl_easy_setopt(handle, CURLOPT_USERAGENT, "NX-Shell"); 102 | curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L); 103 | curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L); 104 | curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L); 105 | curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, Net::WriteNROData); 106 | curl_easy_setopt(handle, CURLOPT_WRITEDATA, std::addressof(file)); 107 | curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L); 108 | curl_easy_perform(handle); 109 | curl_easy_cleanup(handle); 110 | } 111 | 112 | fsFileClose(std::addressof(file)); 113 | offset = 0; 114 | return; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /source/popups/delete.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.hpp" 5 | #include "fs.hpp" 6 | #include "imgui.h" 7 | #include "language.hpp" 8 | #include "log.hpp" 9 | #include "popups.hpp" 10 | 11 | namespace Popups { 12 | void DeletePopup(WindowData &data) { 13 | Popups::SetupPopup(strings[cfg.lang][Lang::OptionsDelete]); 14 | 15 | if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::OptionsDelete], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { 16 | ImGui::Text(strings[cfg.lang][Lang::DeleteMessage]); 17 | if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd == cwd)) { 18 | ImGui::Text(strings[cfg.lang][Lang::DeleteMultiplePrompt]); 19 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 20 | ImGui::BeginChild("Scrolling", ImVec2(0, 100)); 21 | for (std::size_t i = 0; i < data.checkbox_data.checked.size(); i++) { 22 | if (data.checkbox_data.checked[i]) 23 | ImGui::Text(data.entries[i].name); 24 | } 25 | ImGui::EndChild(); 26 | } 27 | else { 28 | std::string text = strings[cfg.lang][Lang::DeletePrompt] + std::string(data.entries[data.selected].name) + "?"; 29 | ImGui::Text(text.c_str()); 30 | } 31 | 32 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 33 | 34 | if (ImGui::Button(strings[cfg.lang][Lang::ButtonOK], ImVec2(120, 0))) { 35 | bool ret = false; 36 | 37 | if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd == cwd)) { 38 | std::vector entries; 39 | if (!FS::GetDirList(data.checkbox_data.device, data.checkbox_data.cwd, entries)) 40 | return; 41 | 42 | std::sort(entries.begin(), entries.end(), FileBrowser::Sort); 43 | Log::Exit(); 44 | 45 | for (std::size_t i = 0; i < data.checkbox_data.checked.size(); i++) { 46 | if (std::strncmp(entries[i].name, "..", 2) == 0) 47 | continue; 48 | 49 | if (data.checkbox_data.checked[i]) { 50 | if (!(ret = FS::Delete(entries[i]))) { 51 | FS::GetDirList(device, cwd, data.entries); 52 | Windows::ResetCheckbox(data); 53 | break; 54 | } 55 | } 56 | } 57 | } 58 | else { 59 | if ((std::strncmp(data.entries[data.selected].name, "..", 2)) != 0) 60 | ret = FS::Delete(data.entries[data.selected]); 61 | } 62 | 63 | if (ret) { 64 | FS::GetDirList(device, cwd, data.entries); 65 | Windows::ResetCheckbox(data); 66 | } 67 | 68 | sort = -1; 69 | Log::Init(); 70 | ImGui::CloseCurrentPopup(); 71 | data.state = WINDOW_STATE_FILEBROWSER; 72 | } 73 | 74 | ImGui::SameLine(0.0f, 15.0f); 75 | 76 | if (ImGui::Button(strings[cfg.lang][Lang::ButtonCancel], ImVec2(120, 0))) { 77 | ImGui::CloseCurrentPopup(); 78 | data.state = WINDOW_STATE_OPTIONS; 79 | } 80 | } 81 | 82 | Popups::ExitPopup(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /source/popups/options.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "config.hpp" 7 | #include "fs.hpp" 8 | #include "gui.hpp" 9 | #include "imgui_impl_switch.hpp" 10 | #include "imgui_internal.h" 11 | #include "keyboard.hpp" 12 | #include "language.hpp" 13 | #include "popups.hpp" 14 | 15 | namespace Options { 16 | static void RefreshEntries(bool reset_checkbox_data) { 17 | FS::GetDirList(device, cwd, data.entries); 18 | 19 | if (reset_checkbox_data) 20 | Windows::ResetCheckbox(data); 21 | } 22 | 23 | static void HandleMultipleCopy(WindowData &data, bool (*func)()) { 24 | std::vector entries; 25 | if (!FS::GetDirList(data.checkbox_data.device, data.checkbox_data.cwd, entries)) 26 | return; 27 | 28 | std::sort(entries.begin(), entries.end(), FileBrowser::Sort); 29 | 30 | for (std::size_t i = 0; i < data.checkbox_data.checked_copy.size(); i++) { 31 | if (std::strncmp(entries[i].name, "..", 2) == 0) 32 | continue; 33 | 34 | if (data.checkbox_data.checked_copy[i]) { 35 | std::string path = data.checkbox_data.device + data.checkbox_data.cwd; 36 | FS::Copy(entries[i], path); 37 | 38 | if (!(*func)()) { 39 | Options::RefreshEntries(true); 40 | break; 41 | } 42 | } 43 | } 44 | 45 | Options::RefreshEntries(true); 46 | sort = -1; 47 | entries.clear(); 48 | } 49 | } 50 | 51 | namespace Popups { 52 | static bool copy = false, move = false; 53 | 54 | void OptionsPopup(WindowData &data) { 55 | Popups::SetupPopup(strings[cfg.lang][Lang::OptionsTitle]); 56 | 57 | if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::OptionsTitle], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { 58 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsSelectAll], ImVec2(200, 50))) { 59 | if ((data.checkbox_data.cwd.length() != 0) && (data.checkbox_data.cwd != cwd)) 60 | Windows::ResetCheckbox(data); 61 | 62 | data.checkbox_data.cwd = cwd; 63 | data.checkbox_data.device = device; 64 | std::fill(data.checkbox_data.checked.begin() + 1, data.checkbox_data.checked.end(), true); 65 | data.checkbox_data.count = data.checkbox_data.checked.size(); 66 | } 67 | 68 | ImGui::SameLine(0.0f, 15.0f); 69 | 70 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsClearAll], ImVec2(200, 50))) { 71 | Windows::ResetCheckbox(data); 72 | copy = false; 73 | move = false; 74 | } 75 | 76 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 77 | 78 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsProperties], ImVec2(200, 50))) { 79 | ImGui::CloseCurrentPopup(); 80 | data.state = WINDOW_STATE_PROPERTIES; 81 | } 82 | 83 | ImGui::SameLine(0.0f, 15.0f); 84 | 85 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsRename], ImVec2(200, 50))) { 86 | std::string path = Keyboard::GetText(strings[cfg.lang][Lang::OptionsRenamePrompt], data.entries[data.selected].name); 87 | 88 | if (FS::Rename(data.entries[data.selected], path.c_str())) { 89 | Options::RefreshEntries(false); 90 | sort = -1; 91 | } 92 | 93 | ImGui::CloseCurrentPopup(); 94 | data.state = WINDOW_STATE_FILEBROWSER; 95 | } 96 | 97 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 98 | 99 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsNewFolder], ImVec2(200, 50))) { 100 | std::string name = Keyboard::GetText(strings[cfg.lang][Lang::OptionsFolderPrompt], strings[cfg.lang][Lang::OptionsNewFolder]); 101 | std::string path = FS::BuildPath(name, true); 102 | 103 | if (R_SUCCEEDED(mkdir(path.c_str(), 0700))) { 104 | Options::RefreshEntries(true); 105 | sort = -1; 106 | } 107 | 108 | ImGui::CloseCurrentPopup(); 109 | data.state = WINDOW_STATE_FILEBROWSER; 110 | } 111 | 112 | ImGui::SameLine(0.0f, 15.0f); 113 | 114 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsNewFile], ImVec2(200, 50))) { 115 | std::string name = Keyboard::GetText(strings[cfg.lang][Lang::OptionsFilePrompt], strings[cfg.lang][Lang::OptionsNewFile]); 116 | std::string path = FS::BuildPath(name, true); 117 | 118 | FILE *file = fopen(path.c_str(), "w"); 119 | fclose(file); 120 | 121 | if (FS::FileExists(path)) { 122 | Options::RefreshEntries(true); 123 | sort = -1; 124 | } 125 | 126 | ImGui::CloseCurrentPopup(); 127 | data.state = WINDOW_STATE_FILEBROWSER; 128 | } 129 | 130 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 131 | 132 | if (ImGui::Button(!copy? strings[cfg.lang][Lang::OptionsCopy] : strings[cfg.lang][Lang::OptionsPaste], ImVec2(200, 50))) { 133 | if (!copy) { 134 | if ((data.checkbox_data.count >= 1) && (data.checkbox_data.cwd != cwd)) 135 | Windows::ResetCheckbox(data); 136 | if (data.checkbox_data.count <= 1) { 137 | std::string path = device + cwd; 138 | FS::Copy(data.entries[data.selected], path); 139 | } 140 | 141 | copy = !copy; 142 | data.state = WINDOW_STATE_FILEBROWSER; 143 | } 144 | else { 145 | ImGui::EndPopup(); 146 | ImGui::PopStyleVar(); 147 | ImGui::Render(); 148 | 149 | if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd != cwd)) 150 | Options::HandleMultipleCopy(data, std::addressof(FS::Paste)); 151 | else { 152 | if (FS::Paste()) { 153 | Options::RefreshEntries(true); 154 | sort = -1; 155 | } 156 | } 157 | 158 | copy = !copy; 159 | //ImGui::CloseCurrentPopup(); 160 | data.state = WINDOW_STATE_FILEBROWSER; 161 | return; 162 | } 163 | } 164 | 165 | ImGui::SameLine(0.0f, 15.0f); 166 | 167 | if (ImGui::Button(!move? strings[cfg.lang][Lang::OptionsMove] : strings[cfg.lang][Lang::OptionsPaste], ImVec2(200, 50))) { 168 | if (!move) { 169 | if ((data.checkbox_data.count >= 1) && (data.checkbox_data.cwd != cwd)) 170 | Windows::ResetCheckbox(data); 171 | if (data.checkbox_data.count <= 1) { 172 | std::string path = device + cwd; 173 | FS::Copy(data.entries[data.selected], path); 174 | } 175 | } 176 | else { 177 | if ((data.checkbox_data.count > 1) && (data.checkbox_data.cwd != cwd)) 178 | Options::HandleMultipleCopy(data, std::addressof(FS::Move)); 179 | else { 180 | if (FS::Move()) { 181 | Options::RefreshEntries(true); 182 | sort = -1; 183 | } 184 | } 185 | } 186 | 187 | move = !move; 188 | ImGui::CloseCurrentPopup(); 189 | data.state = WINDOW_STATE_FILEBROWSER; 190 | } 191 | 192 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 193 | 194 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsDelete], ImVec2(200, 50))) { 195 | ImGui::CloseCurrentPopup(); 196 | data.state = WINDOW_STATE_DELETE; 197 | } 198 | 199 | ImGui::SameLine(0.0f, 15.0f); 200 | 201 | if (ImGui::Button(strings[cfg.lang][Lang::OptionsSetArchiveBit], ImVec2(200, 50))) { 202 | std::string path = FS::BuildPath(data.entries[data.selected]); 203 | 204 | if (FS::SetArchiveBit(path)) { 205 | Options::RefreshEntries(true); 206 | sort = -1; 207 | } 208 | 209 | ImGui::CloseCurrentPopup(); 210 | data.state = WINDOW_STATE_FILEBROWSER; 211 | } 212 | } 213 | 214 | Popups::ExitPopup(); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /source/popups/progressbar.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "gui.hpp" 5 | #include "imgui_impl_switch.hpp" 6 | #include "log.hpp" 7 | #include "popups.hpp" 8 | #include "windows.hpp" 9 | 10 | // Todo maybe use a thread to run this? 11 | namespace Popups { 12 | 13 | void ProgressBar(float offset, float size, const std::string &title, const std::string &text) { 14 | u64 key = ImGui_ImplSwitch_NewFrame(); 15 | ImGui::NewFrame(); 16 | 17 | Windows::MainWindow(data, key, true); 18 | Popups::SetupPopup(title.c_str()); 19 | 20 | if (ImGui::BeginPopupModal(title.c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { 21 | ImGui::Text(text.c_str()); 22 | ImGui::ProgressBar(offset/size, ImVec2(0.0f, 0.0f)); 23 | } 24 | 25 | Popups::ExitPopup(); 26 | 27 | ImGui::Render(); 28 | glViewport(0, 0, static_cast(ImGui::GetIO().DisplaySize.x), static_cast(ImGui::GetIO().DisplaySize.y)); 29 | glClearColor(0.00f, 0.00f, 0.00f, 1.00f); 30 | glClear(GL_COLOR_BUFFER_BIT); 31 | ImGui_ImplSwitch_RenderDrawData(ImGui::GetDrawData()); 32 | GUI::SwapBuffers(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /source/popups/properties.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "fs.hpp" 3 | #include "gui.hpp" 4 | #include "imgui.h" 5 | #include "language.hpp" 6 | #include "popups.hpp" 7 | #include "utils.hpp" 8 | 9 | namespace Popups { 10 | static std::size_t size = 0; 11 | 12 | static char *FormatDate(char *string, time_t timestamp) { 13 | strftime(string, 36, "%Y/%m/%d %H:%M:%S", localtime(std::addressof(timestamp))); 14 | return string; 15 | } 16 | 17 | void FilePropertiesPopup(WindowData &data, bool &file_stat) { 18 | Popups::SetupPopup(strings[cfg.lang][Lang::OptionsProperties]); 19 | 20 | if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::OptionsProperties], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { 21 | std::string name_text = strings[cfg.lang][Lang::PropertiesName] + std::string(data.entries[data.selected].name); 22 | ImGui::Text(name_text.c_str()); 23 | 24 | if (data.entries[data.selected].type == FsDirEntryType_File) { 25 | if (!file_stat) { 26 | FS::GetFileSize(data.entries[data.selected].name, size); 27 | file_stat = true; 28 | } 29 | 30 | char size_str[16]; 31 | Utils::GetSizeString(size_str, static_cast(size)); 32 | std::string size_text = strings[cfg.lang][Lang::PropertiesSize]; 33 | size_text.append(size_str); 34 | ImGui::Text(size_text.c_str()); 35 | } 36 | 37 | FsTimeStampRaw timestamp; 38 | if (FS::GetTimeStamp(data.entries[data.selected], timestamp)) { 39 | if (timestamp.is_valid == 1) { // Confirm valid timestamp 40 | char date[3][36]; 41 | 42 | std::string created_time = strings[cfg.lang][Lang::PropertiesCreated]; 43 | created_time.append(Popups::FormatDate(date[0], timestamp.created)); 44 | ImGui::Text(created_time.c_str()); 45 | 46 | std::string modified_time = strings[cfg.lang][Lang::PropertiesModified]; 47 | modified_time.append(Popups::FormatDate(date[1], timestamp.modified)); 48 | ImGui::Text(modified_time.c_str()); 49 | 50 | std::string accessed_time = strings[cfg.lang][Lang::PropertiesAccessed]; 51 | accessed_time.append(Popups::FormatDate(date[2], timestamp.accessed)); 52 | ImGui::Text(accessed_time.c_str()); 53 | } 54 | } 55 | 56 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 57 | 58 | if (ImGui::Button(strings[cfg.lang][Lang::ButtonOK], ImVec2(120, 0))) { 59 | file_stat = false; 60 | ImGui::CloseCurrentPopup(); 61 | data.state = WINDOW_STATE_OPTIONS; 62 | } 63 | } 64 | 65 | Popups::ExitPopup(); 66 | } 67 | 68 | void ImageProperties(bool &state, Tex &texture, bool &file_stat) { 69 | Popups::SetupPopup(strings[cfg.lang][Lang::OptionsProperties]); 70 | 71 | std::string new_width, new_height; 72 | if (ImGui::BeginPopupModal("Properties", std::addressof(state), ImGuiWindowFlags_AlwaysAutoResize)) { 73 | std::string parent_text = "Parent: "; 74 | parent_text.append(device); 75 | parent_text.append(cwd); 76 | ImGui::Text(parent_text.c_str()); 77 | 78 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 79 | 80 | std::string name_text = "Name: "; 81 | name_text.append(data.entries[data.selected].name); 82 | ImGui::Text(name_text.c_str()); 83 | 84 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 85 | 86 | if (!file_stat) { 87 | FS::GetFileSize(data.entries[data.selected].name, size); 88 | file_stat = true; 89 | } 90 | 91 | char size_str[16]; 92 | Utils::GetSizeString(size_str, static_cast(size)); 93 | std::string size_text = "Size: "; 94 | size_text.append(size_str); 95 | ImGui::Text(size_text.c_str()); 96 | 97 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 98 | 99 | std::string width_text = "Width: "; 100 | width_text.append(std::to_string(texture.width)); 101 | width_text.append("px"); 102 | ImGui::Text(width_text.c_str()); 103 | /*ImGui::SameLine(0.0f, 10.0f); 104 | if (ImGui::Button("Edit width")) 105 | new_width = Keyboard::GetText("Enter width", std::to_string(texture.width));*/ 106 | 107 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 108 | 109 | std::string height_text = "Height: "; 110 | height_text.append(std::to_string(texture.height)); 111 | height_text.append("px"); 112 | ImGui::Text(height_text.c_str()); 113 | 114 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 115 | 116 | if (ImGui::Button("OK", ImVec2(120, 0))) { 117 | state = false; 118 | file_stat = false; 119 | ImGui::CloseCurrentPopup(); 120 | } 121 | } 122 | 123 | Popups::ExitPopup(); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /source/popups/update.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "fs.hpp" 3 | #include "gui.hpp" 4 | #include "imgui.h" 5 | #include "language.hpp" 6 | #include "log.hpp" 7 | #include "net.hpp" 8 | #include "popups.hpp" 9 | #include "utils.hpp" 10 | #include "windows.hpp" 11 | 12 | namespace Popups { 13 | static bool done = false; 14 | 15 | void UpdatePopup(bool &state, bool &connection_status, bool &available, const std::string &tag) { 16 | Popups::SetupPopup(strings[cfg.lang][Lang::UpdateTitle]); 17 | 18 | if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::UpdateTitle], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { 19 | if (!connection_status) 20 | ImGui::Text(strings[cfg.lang][Lang::UpdateNetworkError]); 21 | else if ((connection_status) && (available) && (!tag.empty()) && (!done)) { 22 | ImGui::Text(strings[cfg.lang][Lang::UpdateAvailable]); 23 | std::string text = strings[cfg.lang][Lang::UpdatePrompt] + tag + "?"; 24 | ImGui::Text(text.c_str()); 25 | } 26 | else if (done) { 27 | ImGui::Text(strings[cfg.lang][Lang::UpdateSuccess]); 28 | ImGui::Text(strings[cfg.lang][Lang::UpdateRestart]); 29 | } 30 | else 31 | ImGui::Text(strings[cfg.lang][Lang::UpdateNotAvailable]); 32 | 33 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 34 | 35 | if (ImGui::Button(strings[cfg.lang][Lang::ButtonOK], ImVec2(120, 0))) { 36 | if ((connection_status) && (available) && (!tag.empty()) && (!done)) { 37 | Net::GetLatestReleaseNRO(tag); 38 | 39 | Result ret = 0; 40 | if (R_FAILED(ret = fsFsDeleteFile(std::addressof(devices[FileSystemSDMC]), __application_path))) 41 | Log::Error("fsFsDeleteFile(%s) failed: 0x%x\n", __application_path, ret); 42 | 43 | if (R_FAILED(ret = fsFsRenameFile(std::addressof(devices[FileSystemSDMC]), "/switch/NX-Shell/NX-Shell_UPDATE.nro", __application_path))) 44 | Log::Error("fsFsRenameFile(update) failed: 0x%x\n", ret); 45 | 46 | done = true; 47 | } 48 | else { 49 | ImGui::CloseCurrentPopup(); 50 | state = false; 51 | } 52 | } 53 | 54 | if ((connection_status) && (available) && (!done)) { 55 | ImGui::SameLine(0.0f, 15.0f); 56 | 57 | if (ImGui::Button(strings[cfg.lang][Lang::ButtonCancel], ImVec2(120, 0))) { 58 | ImGui::CloseCurrentPopup(); 59 | state = false; 60 | } 61 | } 62 | } 63 | 64 | Popups::ExitPopup(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /source/popups/usbprompt.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "fs.hpp" 3 | #include "imgui.h" 4 | #include "language.hpp" 5 | #include "popups.hpp" 6 | #include "usb.hpp" 7 | #include "windows.hpp" 8 | 9 | namespace Popups { 10 | static bool done = false; 11 | 12 | void USBPopup(bool &state) { 13 | Popups::SetupPopup(strings[cfg.lang][Lang::SettingsUSBTitle]); 14 | 15 | if (ImGui::BeginPopupModal(strings[cfg.lang][Lang::SettingsUSBTitle], nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { 16 | if (!done) 17 | ImGui::Text(strings[cfg.lang][Lang::USBUnmountPrompt]); 18 | else 19 | ImGui::Text(strings[cfg.lang][Lang::USBUnmountSuccess]); 20 | 21 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 22 | 23 | if (ImGui::Button(strings[cfg.lang][Lang::ButtonOK], ImVec2(120, 0))) { 24 | if (!done) { 25 | USB::Unmount(); 26 | 27 | // Reset device back to sdmc 28 | device = "sdmc:"; 29 | fs = std::addressof(devices[FileSystemSDMC]); 30 | 31 | cwd = "/"; 32 | data.entries.clear(); 33 | FS::GetDirList(device, cwd, data.entries); 34 | 35 | data.checkbox_data.checked.resize(data.entries.size()); 36 | FS::GetUsedStorageSpace(data.used_storage); 37 | FS::GetTotalStorageSpace(data.total_storage); 38 | sort = -1; 39 | 40 | ImGui::CloseCurrentPopup(); 41 | done = true; 42 | } 43 | else { 44 | ImGui::CloseCurrentPopup(); 45 | done = false; 46 | state = false; 47 | } 48 | } 49 | 50 | ImGui::SameLine(0.0f, 15.0f); 51 | 52 | if (!done) { 53 | if (ImGui::Button(strings[cfg.lang][Lang::ButtonCancel], ImVec2(120, 0))) { 54 | ImGui::CloseCurrentPopup(); 55 | state = false; 56 | } 57 | } 58 | } 59 | 60 | Popups::ExitPopup(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /source/tabs/filebrowser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.hpp" 5 | #include "fs.hpp" 6 | #include "imgui.h" 7 | #include "imgui_internal.h" 8 | #include "tabs.hpp" 9 | #include "textures.hpp" 10 | #include "utils.hpp" 11 | 12 | int sort = 0; 13 | std::vector devices_list = { "sdmc:", "safe:", "user:", "system:" }; 14 | std::recursive_mutex devices_list_mutex; 15 | 16 | namespace FileBrowser { 17 | // Sort without using ImGuiTableSortSpecs 18 | bool Sort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB) { 19 | // Make sure ".." stays at the top regardless of sort direction 20 | if (strcasecmp(entryA.name, "..") == 0) 21 | return true; 22 | 23 | if (strcasecmp(entryB.name, "..") == 0) 24 | return false; 25 | 26 | if ((entryA.type == FsDirEntryType_Dir) && !(entryB.type == FsDirEntryType_Dir)) 27 | return true; 28 | else if (!(entryA.type == FsDirEntryType_Dir) && (entryB.type == FsDirEntryType_Dir)) 29 | return false; 30 | 31 | switch(sort) { 32 | case FS_SORT_ALPHA_ASC: 33 | return (strcasecmp(entryA.name, entryB.name) < 0); 34 | break; 35 | 36 | case FS_SORT_ALPHA_DESC: 37 | return (strcasecmp(entryB.name, entryA.name) < 0); 38 | break; 39 | } 40 | 41 | return false; 42 | } 43 | 44 | // Sort using ImGuiTableSortSpecs 45 | bool TableSort(const FsDirectoryEntry &entryA, const FsDirectoryEntry &entryB) { 46 | bool descending = false; 47 | ImGuiTableSortSpecs *table_sort_specs = ImGui::TableGetSortSpecs(); 48 | 49 | for (int i = 0; i < table_sort_specs->SpecsCount; ++i) { 50 | const ImGuiTableColumnSortSpecs *column_sort_spec = std::addressof(table_sort_specs->Specs[i]); 51 | descending = (column_sort_spec->SortDirection == ImGuiSortDirection_Descending); 52 | 53 | // Make sure ".." stays at the top regardless of sort direction 54 | if (strcasecmp(entryA.name, "..") == 0) 55 | return true; 56 | 57 | if (strcasecmp(entryB.name, "..") == 0) 58 | return false; 59 | 60 | if ((entryA.type == FsDirEntryType_Dir) && !(entryB.type == FsDirEntryType_Dir)) 61 | return true; 62 | else if (!(entryA.type == FsDirEntryType_Dir) && (entryB.type == FsDirEntryType_Dir)) 63 | return false; 64 | else { 65 | switch (column_sort_spec->ColumnIndex) { 66 | case 1: // filename 67 | sort = descending? FS_SORT_ALPHA_DESC : FS_SORT_ALPHA_ASC; 68 | return descending? (strcasecmp(entryB.name, entryA.name) < 0) : (strcasecmp(entryA.name, entryB.name) < 0); 69 | break; 70 | 71 | default: 72 | break; 73 | } 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | } 80 | 81 | namespace Tabs { 82 | static const ImVec2 tex_size = ImVec2(21, 21); 83 | 84 | void FileBrowser(WindowData &data) { 85 | if (ImGui::BeginTabItem("File Browser")) { 86 | ImGui::Dummy(ImVec2(0.0f, 1.0f)); // Spacing 87 | 88 | ImGui::PushID("device_list"); 89 | ImGui::PushItemWidth(160.f); 90 | if (ImGui::BeginCombo("", device.c_str())) { 91 | std::scoped_lock lock(devices_list_mutex); 92 | 93 | for (std::size_t i = 0; i < devices_list.size(); i++) { 94 | const bool is_selected = (device == devices_list[i]); 95 | 96 | if (ImGui::Selectable(devices_list[i].c_str(), is_selected)) { 97 | device = devices_list[i]; 98 | fs = std::addressof(devices[i]); 99 | 100 | cwd = "/"; 101 | data.entries.clear(); 102 | FS::GetDirList(device, cwd, data.entries); 103 | 104 | data.checkbox_data.checked.resize(data.entries.size()); 105 | FS::GetUsedStorageSpace(data.used_storage); 106 | FS::GetTotalStorageSpace(data.total_storage); 107 | sort = -1; 108 | } 109 | 110 | if (is_selected) 111 | ImGui::SetItemDefaultFocus(); 112 | } 113 | 114 | ImGui::EndCombo(); 115 | } 116 | ImGui::PopItemWidth(); 117 | ImGui::PopID(); 118 | 119 | ImGui::SameLine(); 120 | 121 | // Display current working directory 122 | ImGui::Text(cwd.c_str()); 123 | 124 | // Draw storage bar 125 | ImGui::Dummy(ImVec2(0.0f, 1.0f)); // Spacing 126 | ImGui::ProgressBar(static_cast(data.used_storage) / static_cast(data.total_storage), ImVec2(1265.0f, 6.0f), ""); 127 | ImGui::Dummy(ImVec2(0.0f, 1.0f)); // Spacing 128 | 129 | ImGuiTableFlags tableFlags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_BordersInner | 130 | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_ScrollY; 131 | 132 | if (ImGui::BeginTable("Directory List", 2, tableFlags)) { 133 | // Make header always visible 134 | // ImGui::TableSetupScrollFreeze(0, 1); 135 | 136 | ImGui::TableSetupColumn("", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_NoHeaderLabel | ImGuiTableColumnFlags_WidthFixed); 137 | ImGui::TableSetupColumn("Filename", ImGuiTableColumnFlags_DefaultSort); 138 | ImGui::TableHeadersRow(); 139 | 140 | if (ImGuiTableSortSpecs *sorts_specs = ImGui::TableGetSortSpecs()) { 141 | if (sort == -1) 142 | sorts_specs->SpecsDirty = true; 143 | 144 | if (sorts_specs->SpecsDirty) { 145 | std::sort(data.entries.begin(), data.entries.end(), FileBrowser::TableSort); 146 | sorts_specs->SpecsDirty = false; 147 | } 148 | } 149 | 150 | for (u64 i = 0; i < data.entries.size(); i++) { 151 | ImGui::TableNextRow(); 152 | 153 | ImGui::TableNextColumn(); 154 | ImGui::PushID(i); 155 | 156 | if ((data.checkbox_data.checked[i]) && (data.checkbox_data.cwd.compare(cwd) == 0) && (data.checkbox_data.device.compare(device) == 0)) 157 | ImGui::Image(reinterpret_cast(check_icon.id), tex_size); 158 | else 159 | ImGui::Image(reinterpret_cast(uncheck_icon.id), tex_size); 160 | 161 | ImGui::PopID(); 162 | 163 | ImGui::TableNextColumn(); 164 | FileType file_type = FS::GetFileType(data.entries[i].name); 165 | 166 | if (data.entries[i].type == FsDirEntryType_Dir) 167 | ImGui::Image(reinterpret_cast(folder_icon.id), tex_size); 168 | else 169 | ImGui::Image(reinterpret_cast(file_icons[file_type].id), tex_size); 170 | 171 | ImGui::SameLine(); 172 | 173 | if (ImGui::Selectable(data.entries[i].name, false)) { 174 | if (data.entries[i].type == FsDirEntryType_Dir) { 175 | if (std::strncmp(data.entries[i].name, "..", 2) == 0) { 176 | if (FS::ChangeDirPrev(data.entries)) { 177 | if ((data.checkbox_data.count > 1) && (data.checkbox_data.checked_copy.empty())) 178 | data.checkbox_data.checked_copy = data.checkbox_data.checked; 179 | 180 | data.checkbox_data.checked.resize(data.entries.size()); 181 | } 182 | } 183 | else if (FS::ChangeDirNext(data.entries[i].name, data.entries)) { 184 | if ((data.checkbox_data.count > 1) && (data.checkbox_data.checked_copy.empty())) 185 | data.checkbox_data.checked_copy = data.checkbox_data.checked; 186 | 187 | data.checkbox_data.checked.resize(data.entries.size()); 188 | } 189 | 190 | // Reset navigation ID -- TODO: Scroll to top 191 | ImGuiContext& g = *GImGui; 192 | ImGui::SetNavID(ImGui::GetID(data.entries[0].name, 0), g.NavLayer, 0, ImRect()); 193 | 194 | // Reapply sort 195 | ImGuiTableSortSpecs *sorts_specs = ImGui::TableGetSortSpecs(); 196 | sorts_specs->SpecsDirty = true; 197 | } 198 | else { 199 | std::string path = FS::BuildPath(data.entries[i]); 200 | 201 | switch (file_type) { 202 | case FileTypeImage: 203 | if (Textures::LoadImageFile(path, data.textures)) 204 | data.state = WINDOW_STATE_IMAGEVIEWER; 205 | break; 206 | 207 | default: 208 | break; 209 | } 210 | } 211 | } 212 | 213 | if (ImGui::IsItemHovered()) 214 | data.selected = i; 215 | } 216 | 217 | ImGui::EndTable(); 218 | } 219 | 220 | ImGui::EndTabItem(); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /source/tabs/settings.cpp: -------------------------------------------------------------------------------- 1 | #include "config.hpp" 2 | #include "fs.hpp" 3 | #include "gui.hpp" 4 | #include "imgui.h" 5 | #define IMGUI_DEFINE_MATH_OPERATORS 6 | #include "imgui_internal.h" 7 | #include "language.hpp" 8 | #include "net.hpp" 9 | #include "popups.hpp" 10 | #include "tabs.hpp" 11 | #include "usb.hpp" 12 | 13 | namespace Tabs { 14 | static bool update_popup = false, network_status = false, update_available = false, unmount_popup = false; 15 | static std::string tag_name = std::string(); 16 | 17 | static void Indent(const std::string &title) { 18 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 19 | ImGui::TextColored(ImGui::GetStyle().Colors[ImGuiCol_CheckMark], title.c_str()); 20 | ImGui::Indent(20.f); 21 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 22 | } 23 | 24 | static void Separator(void) { 25 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 26 | ImGui::Unindent(); 27 | ImGui::Separator(); 28 | } 29 | 30 | void Settings(WindowData &data) { 31 | if (ImGui::BeginTabItem("Settings")) { 32 | // Disable language settings for now (At least until it;s complete) 33 | // if (ImGui::TreeNode(strings[cfg.lang][Lang::SettingsLanguageTitle])) { 34 | // const char *languages[] = { 35 | // " Japanese", 36 | // " English", 37 | // " French", 38 | // " German", 39 | // " Italian", 40 | // " Spanish", 41 | // " Simplified Chinese", 42 | // " Korean", 43 | // " Dutch", 44 | // " Portuguese", 45 | // " Russian", 46 | // " Traditional Chinese" 47 | // }; 48 | 49 | // ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 50 | 51 | // const int max_lang = 12; 52 | // for (int i = 0; i < max_lang; i++) { 53 | // if (ImGui::RadioButton(languages[i], &cfg.lang, i)) 54 | // Config::Save(cfg); 55 | 56 | // if (i != (max_lang - 1)) 57 | // ImGui::Dummy(ImVec2(0.0f, 15.0f)); // Spacing 58 | // } 59 | 60 | // ImGui::TreePop(); 61 | // } 62 | 63 | // ImGui::Separator(); 64 | 65 | // USB unmount 66 | ImGui::Indent(10.f); 67 | Tabs::Indent(strings[cfg.lang][Lang::SettingsUSBTitle]); 68 | 69 | if (!USB::Connected()) { 70 | ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); 71 | ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); 72 | } 73 | 74 | if (ImGui::Button(strings[cfg.lang][Lang::SettingsUSBUnmount], ImVec2(250, 50))) 75 | unmount_popup = true; 76 | 77 | if (!USB::Connected()) { 78 | ImGui::PopItemFlag(); 79 | ImGui::PopStyleVar(); 80 | } 81 | 82 | Tabs::Separator(); 83 | 84 | // Image filename checkbox 85 | Tabs::Indent(strings[cfg.lang][Lang::SettingsImageViewTitle]); 86 | 87 | if (ImGui::Checkbox(strings[cfg.lang][Lang::SettingsImageViewFilenameToggle], std::addressof(cfg.image_filename))) 88 | Config::Save(cfg); 89 | 90 | Tabs::Separator(); 91 | 92 | // Developer Options Checkbox 93 | Tabs::Indent(strings[cfg.lang][Lang::SettingsDevOptsTitle]); 94 | 95 | if (ImGui::Checkbox(strings[cfg.lang][Lang::SettingsDevOptsLogsToggle], std::addressof(cfg.dev_options))) 96 | Config::Save(cfg); 97 | 98 | Tabs::Separator(); 99 | 100 | // Multi lang Checkbox 101 | Tabs::Indent(strings[cfg.lang][Lang::SettingsMultiLangTitle]); 102 | 103 | if (ImGui::Checkbox(strings[cfg.lang][Lang::SettingsMultiLangLogsToggle], std::addressof(cfg.multi_lang))) 104 | Config::Save(cfg); 105 | 106 | Tabs::Separator(); 107 | 108 | // About 109 | Tabs::Indent(strings[cfg.lang][Lang::SettingsAboutTitle]); 110 | 111 | ImGui::Text("NX-Shell %s: v%d.%d.%d", strings[cfg.lang][Lang::SettingsAboutVersion], VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO); 112 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 113 | ImGui::Text("Dear ImGui %s: %s", strings[cfg.lang][Lang::SettingsAboutVersion], ImGui::GetVersion()); 114 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 115 | ImGui::Text("%s: Joel16", strings[cfg.lang][Lang::SettingsAboutAuthor]); 116 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 117 | ImGui::Text("%s: Preetisketch", strings[cfg.lang][Lang::SettingsAboutBanner]); 118 | ImGui::Dummy(ImVec2(0.0f, 5.0f)); // Spacing 119 | 120 | if (ImGui::Button(strings[cfg.lang][Lang::SettingsCheckForUpdates], ImVec2(250, 50))) { 121 | tag_name = Net::GetLatestReleaseJSON(); 122 | network_status = Net::GetNetworkStatus(); 123 | update_available = Net::GetAvailableUpdate(tag_name); 124 | update_popup = true; 125 | } 126 | 127 | ImGui::EndTabItem(); 128 | } 129 | 130 | if (update_popup) 131 | Popups::UpdatePopup(update_popup, network_status, update_available, tag_name); 132 | 133 | if (unmount_popup) 134 | Popups::USBPopup(unmount_popup); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /source/textures.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // BMP 7 | #include "libnsbmp.h" 8 | 9 | // GIF 10 | #include 11 | 12 | // JPEG 13 | #include 14 | 15 | // STB 16 | #define STB_IMAGE_IMPLEMENTATION 17 | #define STBI_NO_BMP 18 | #define STBI_NO_HDR 19 | #define STBI_NO_JPEG 20 | #define STBI_NO_PIC 21 | #define STBI_NO_PNG 22 | #define STBI_ONLY_GIF 23 | #define STBI_ONLY_PNM 24 | #define STBI_ONLY_PSD 25 | #define STBI_ONLY_TGA 26 | #include "stb_image.h" 27 | 28 | // PNG 29 | #include 30 | 31 | // WEBP 32 | #include 33 | 34 | #include 35 | 36 | #include "fs.hpp" 37 | #include "gui.hpp" 38 | #include "imgui_impl_switch.hpp" 39 | #include "log.hpp" 40 | #include "textures.hpp" 41 | #include "windows.hpp" 42 | 43 | #define BYTES_PER_PIXEL 4 44 | #define MAX_IMAGE_BYTES (48 * 1024 * 1024) 45 | 46 | std::vector file_icons; 47 | Tex folder_icon, check_icon, uncheck_icon; 48 | 49 | namespace BMP { 50 | static void *bitmap_create(int width, int height, [[maybe_unused]] unsigned int state) { 51 | /* ensure a stupidly large (>50Megs or so) bitmap is not created */ 52 | if ((static_cast(width) * static_cast(height)) > (MAX_IMAGE_BYTES/BYTES_PER_PIXEL)) 53 | return nullptr; 54 | 55 | return std::calloc(width * height, BYTES_PER_PIXEL); 56 | } 57 | 58 | static unsigned char *bitmap_get_buffer(void *bitmap) { 59 | assert(bitmap); 60 | return static_cast(bitmap); 61 | } 62 | 63 | static size_t bitmap_get_bpp([[maybe_unused]] void *bitmap) { 64 | return BYTES_PER_PIXEL; 65 | } 66 | 67 | static void bitmap_destroy(void *bitmap) { 68 | assert(bitmap); 69 | std::free(bitmap); 70 | } 71 | } 72 | 73 | namespace Textures { 74 | typedef enum ImageType { 75 | ImageTypeBMP, 76 | ImageTypeGIF, 77 | ImageTypeJPEG, 78 | ImageTypePNG, 79 | ImageTypeWEBP, 80 | ImageTypeOther 81 | } ImageType; 82 | 83 | static bool ReadFile(const std::string &path, unsigned char **buffer, std::size_t &size) { 84 | FILE *file = fopen(path.c_str(), "rb"); 85 | if (!file) { 86 | Log::Error("Textures::ReadFile (%s) failed to open file.\n", path.c_str()); 87 | return false; 88 | } 89 | 90 | struct stat file_stat = { 0 }; 91 | if (stat(path.c_str(), std::addressof(file_stat)) != 0) { 92 | Log::Error("Textures::ReadFile (%s) failed to get file size.\n", path.c_str()); 93 | return false; 94 | } 95 | 96 | size = file_stat.st_size; 97 | *buffer = new unsigned char[size + 1]; 98 | std::size_t bytes_read = fread(*buffer, sizeof(unsigned char), size, file); 99 | 100 | if (bytes_read != size) { 101 | Log::Error("Textures::ReadFile (%s) failed to read file.\n", path.c_str()); 102 | fclose(file); 103 | return false; 104 | } 105 | 106 | fclose(file); 107 | return true; 108 | } 109 | 110 | static bool Create(unsigned char *data, GLint format, Tex &texture) { 111 | glGenTextures(1, std::addressof(texture.id)); 112 | glBindTexture(GL_TEXTURE_2D, texture.id); 113 | 114 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 115 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 116 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // This is required on WebGL for non power-of-two textures 117 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Same 118 | 119 | // Upload pixels into texture 120 | #if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__) 121 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); 122 | #endif 123 | glTexImage2D(GL_TEXTURE_2D, 0, format, texture.width, texture.height, 0, format, GL_UNSIGNED_BYTE, data); 124 | return true; 125 | } 126 | 127 | static bool LoadImagePNG(const std::string &path, Tex &texture) { 128 | bool ret = false; 129 | png_image image; 130 | std::memset(std::addressof(image), 0, (sizeof image)); 131 | image.version = PNG_IMAGE_VERSION; 132 | 133 | if (png_image_begin_read_from_file(std::addressof(image), path.c_str()) != 0) { 134 | png_bytep buffer; 135 | image.format = PNG_FORMAT_RGBA; 136 | buffer = new png_byte[PNG_IMAGE_SIZE(image)]; 137 | 138 | if (buffer != nullptr && png_image_finish_read(std::addressof(image), nullptr, buffer, 0, nullptr) != 0) { 139 | texture.width = image.width; 140 | texture.height = image.height; 141 | ret = Textures::Create(buffer, GL_RGBA, texture); 142 | delete[] buffer; 143 | png_image_free(std::addressof(image)); 144 | } 145 | else { 146 | if (buffer == nullptr) 147 | png_image_free(std::addressof(image)); 148 | else 149 | delete[] buffer; 150 | } 151 | } 152 | 153 | return ret; 154 | } 155 | 156 | static bool LoadImageBMP(unsigned char **data, std::size_t &size, Tex &texture) { 157 | bmp_bitmap_callback_vt bitmap_callbacks = { 158 | BMP::bitmap_create, 159 | BMP::bitmap_destroy, 160 | BMP::bitmap_get_buffer, 161 | BMP::bitmap_get_bpp 162 | }; 163 | 164 | bmp_result code = BMP_OK; 165 | bmp_image bmp; 166 | bmp_create(std::addressof(bmp), std::addressof(bitmap_callbacks)); 167 | 168 | code = bmp_analyse(std::addressof(bmp), size, *data); 169 | if (code != BMP_OK) { 170 | bmp_finalise(std::addressof(bmp)); 171 | return false; 172 | } 173 | 174 | code = bmp_decode(std::addressof(bmp)); 175 | if (code != BMP_OK) { 176 | if ((code != BMP_INSUFFICIENT_DATA) && (code != BMP_DATA_ERROR)) { 177 | bmp_finalise(std::addressof(bmp)); 178 | return false; 179 | } 180 | 181 | /* skip if the decoded image would be ridiculously large */ 182 | if ((bmp.width * bmp.height) > 200000) { 183 | bmp_finalise(std::addressof(bmp)); 184 | return false; 185 | } 186 | } 187 | 188 | texture.width = bmp.width; 189 | texture.height = bmp.height; 190 | bool ret = Textures::Create(static_cast(bmp.bitmap), GL_RGBA, texture); 191 | bmp_finalise(std::addressof(bmp)); 192 | return ret; 193 | } 194 | 195 | static bool LoadImageGIF(const std::string &path, std::vector &textures) { 196 | bool ret = false; 197 | int error = 0; 198 | GifFileType *gif = DGifOpenFileName(path.c_str(), std::addressof(error)); 199 | 200 | if (!gif) { 201 | Log::Error("DGifOpenFileName failed: %d\n", error); 202 | return ret; 203 | } 204 | 205 | if (DGifSlurp(gif) != GIF_OK) { 206 | Log::Error("DGifSlurp failed: %d\n", gif->Error); 207 | return ret; 208 | } 209 | 210 | if (gif->ImageCount <= 0) { 211 | Log::Error("Gif does not contain any images.\n"); 212 | return ret; 213 | } 214 | 215 | textures.resize(gif->ImageCount); 216 | 217 | // seiken's example code from: 218 | // https://forums.somethingawful.com/showthread.php?threadid=2773485&userid=0&perpage=40&pagenumber=487#post465199820 219 | textures[0].width = gif->SWidth; 220 | textures[0].height = gif->SHeight; 221 | std::unique_ptr pixels(new u32[textures[0].width * textures[0].height]); 222 | 223 | for (int i = 0; i < textures[0].width * textures[0].height; ++i) 224 | pixels[i] = gif->SBackGroundColor; 225 | 226 | for (int i = 0; i < gif->ImageCount; ++i) { 227 | const SavedImage &frame = gif->SavedImages[i]; 228 | bool transparency = false; 229 | unsigned char transparency_byte = 0; 230 | 231 | // Delay time in hundredths of a second. 232 | int delay_time = 1; 233 | for (int j = 0; j < frame.ExtensionBlockCount; ++j) { 234 | const ExtensionBlock &block = frame.ExtensionBlocks[j]; 235 | 236 | if (block.Function != GRAPHICS_EXT_FUNC_CODE) 237 | continue; 238 | 239 | // Here's the metadata for this frame. 240 | char dispose = (block.Bytes[0] >> 2) & 7; 241 | transparency = block.Bytes[0] & 1; 242 | delay_time = block.Bytes[1] + (block.Bytes[2] << 8); 243 | transparency_byte = block.Bytes[3]; 244 | 245 | if (dispose == 2) { 246 | // Clear the canvas. 247 | for (int k = 0; k < textures[0].width * textures[0].height; ++k) 248 | pixels[k] = gif->SBackGroundColor; 249 | } 250 | } 251 | 252 | // Colour map for this frame. 253 | ColorMapObject *map = frame.ImageDesc.ColorMap ? frame.ImageDesc.ColorMap : gif->SColorMap; 254 | 255 | // Region this frame draws to. 256 | int fw = frame.ImageDesc.Width; 257 | int fh = frame.ImageDesc.Height; 258 | int fl = frame.ImageDesc.Left; 259 | int ft = frame.ImageDesc.Top; 260 | 261 | for (int y = 0; y < std::min(textures[0].height, fh); ++y) { 262 | for (int x = 0; x < std::min(textures[0].width, fw); ++x) { 263 | unsigned char byte = frame.RasterBits[x + y * fw]; 264 | 265 | // Transparent pixel. 266 | if (transparency && byte == transparency_byte) 267 | continue; 268 | 269 | // Draw to canvas. 270 | const GifColorType &c = map->Colors[byte]; 271 | pixels[fl + x + (ft + y) * textures[0].width] = c.Red | (c.Green << 8) | (c.Blue << 16) | (0xff << 24); 272 | } 273 | } 274 | 275 | textures[i].delay = delay_time * 10000000; 276 | textures[i].width = textures[0].width; 277 | textures[i].height = textures[0].height; 278 | 279 | // Here's the actual frame, pixels.get() is now a pointer to the 32-bit RGBA 280 | // data for this frame you might expect. 281 | ret = Textures::Create(reinterpret_cast(pixels.get()), GL_RGBA, textures[i]); 282 | } 283 | 284 | if (DGifCloseFile(gif, std::addressof(error)) != GIF_OK) { 285 | Log::Error("DGifCloseFile failed: %d\n", error); 286 | return false; 287 | } 288 | 289 | return true; 290 | } 291 | 292 | static bool LoadImageJPEG(unsigned char **data, std::size_t &size, Tex &texture) { 293 | tjhandle jpeg = tjInitDecompress(); 294 | int jpegsubsamp = 0; 295 | tjDecompressHeader2(jpeg, *data, size, std::addressof(texture.width), std::addressof(texture.height), std::addressof(jpegsubsamp)); 296 | unsigned char *buffer = new unsigned char[texture.width * texture.height * 4]; 297 | tjDecompress2(jpeg, *data, size, buffer, texture.width, 0, texture.height, TJPF_RGBA, TJFLAG_FASTDCT); 298 | bool ret = Textures::Create(buffer, GL_RGBA, texture); 299 | tjDestroy(jpeg); 300 | delete[] buffer; 301 | return ret; 302 | } 303 | 304 | static bool LoadImageOther(const std::string &path, Tex &texture) { 305 | unsigned char *image = stbi_load(path.c_str(), std::addressof(texture.width), std::addressof(texture.height), nullptr, STBI_rgb_alpha); 306 | bool ret = Textures::Create(image, GL_RGBA, texture); 307 | return ret; 308 | } 309 | 310 | static bool LoadImageWEBP(unsigned char **data, std::size_t &size, Tex &texture) { 311 | *data = WebPDecodeRGBA(*data, size, std::addressof(texture.width), std::addressof(texture.height)); 312 | bool ret = Textures::Create(*data, GL_RGBA, texture); 313 | return ret; 314 | } 315 | 316 | ImageType GetImageType(const std::string &filename) { 317 | std::string ext = FS::GetFileExt(filename); 318 | 319 | if (!ext.compare(".BMP")) 320 | return ImageTypeBMP; 321 | else if (!ext.compare(".GIF")) 322 | return ImageTypeGIF; 323 | else if ((!ext.compare(".JPG")) || (!ext.compare(".JPEG"))) 324 | return ImageTypeJPEG; 325 | else if (!ext.compare(".PNG")) 326 | return ImageTypePNG; 327 | else if (!ext.compare(".WEBP")) 328 | return ImageTypeWEBP; 329 | 330 | return ImageTypeOther; 331 | } 332 | 333 | bool LoadImageFile(const std::string &path, std::vector &textures) { 334 | bool ret = false; 335 | 336 | // Resize to 1 initially. If the file is a GIF it will be resized accordingly. 337 | textures.resize(1); 338 | 339 | ImageType type = Textures::GetImageType(path); 340 | 341 | if (type == ImageTypeGIF) 342 | ret = Textures::LoadImageGIF(path, textures); 343 | else if (type == ImageTypePNG) 344 | ret = Textures::LoadImagePNG(path, textures[0]); 345 | else if (type == ImageTypeOther) 346 | ret = Textures::LoadImageOther(path, textures[0]); 347 | else { 348 | unsigned char *data = nullptr; 349 | std::size_t size = 0; 350 | 351 | if (!Textures::ReadFile(path, std::addressof(data), size)) { 352 | delete[] data; 353 | return ret; 354 | } 355 | 356 | switch(type) { 357 | case ImageTypeBMP: 358 | ret = Textures::LoadImageBMP(std::addressof(data), size, textures[0]); 359 | break; 360 | 361 | case ImageTypeJPEG: 362 | ret = Textures::LoadImageJPEG(std::addressof(data), size, textures[0]); 363 | break; 364 | 365 | case ImageTypeWEBP: 366 | ret = Textures::LoadImageWEBP(std::addressof(data), size, textures[0]); 367 | break; 368 | 369 | default: 370 | break; 371 | } 372 | 373 | delete[] data; 374 | } 375 | return ret; 376 | } 377 | 378 | void Init(void) { 379 | const int num_icons = 4; 380 | 381 | const std::string paths[num_icons] { 382 | "romfs:/file.png", 383 | "romfs:/archive.png", 384 | "romfs:/image.png", 385 | "romfs:/text.png" 386 | }; 387 | 388 | bool image_ret = Textures::LoadImagePNG("romfs:/folder.png", folder_icon); 389 | IM_ASSERT(image_ret); 390 | 391 | image_ret = Textures::LoadImagePNG("romfs:/check.png", check_icon); 392 | IM_ASSERT(image_ret); 393 | 394 | image_ret = Textures::LoadImagePNG("romfs:/uncheck.png", uncheck_icon); 395 | IM_ASSERT(image_ret); 396 | 397 | file_icons.resize(num_icons); 398 | 399 | for (int i = 0; i < num_icons; i++) { 400 | bool ret = Textures::LoadImagePNG(paths[i], file_icons[i]); 401 | IM_ASSERT(ret); 402 | } 403 | } 404 | 405 | void Free(Tex &texture) { 406 | glDeleteTextures(1, std::addressof(texture.id)); 407 | } 408 | 409 | void Exit(void) { 410 | for (unsigned int i = 0; i < file_icons.size(); i++) 411 | Textures::Free(file_icons[i]); 412 | 413 | Textures::Free(uncheck_icon); 414 | Textures::Free(check_icon); 415 | Textures::Free(folder_icon); 416 | 417 | for (unsigned int i = 0; i < data.textures.size(); i++) 418 | Textures::Free(data.textures[i]); 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /source/usb.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "usb.hpp" 4 | #include "usbhsfs.h" 5 | #include "windows.hpp" 6 | 7 | namespace USB { 8 | static UEvent *status_change_event = nullptr, exit_event = {0}; 9 | static u32 usb_device_count = 0; 10 | static UsbHsFsDevice *usb_devices = nullptr; 11 | static Thread thread = {0}; 12 | static u32 listed_device_count = 0; 13 | static bool thread_created = false; 14 | 15 | // This function is heavily based off the example provided by DarkMatterCore 16 | // https://github.com/DarkMatterCore/libusbhsfs/blob/main/example/source/main.c 17 | static void usbMscThreadFunc(void *arg) { 18 | (void)arg; 19 | 20 | Result ret = 0; 21 | int idx = 0; 22 | 23 | /* Generate waiters for our user events. */ 24 | Waiter status_change_event_waiter = waiterForUEvent(status_change_event); 25 | Waiter exit_event_waiter = waiterForUEvent(&exit_event); 26 | 27 | while(true) { 28 | /* Wait until an event is triggered. */ 29 | if (R_FAILED(ret = waitMulti(&idx, -1, status_change_event_waiter, exit_event_waiter))) 30 | continue; 31 | 32 | /* Exit event triggered. */ 33 | if (idx == 1) 34 | break; 35 | 36 | /* Free USB Mass Storage device data. */ 37 | USB::Unmount(); 38 | 39 | { 40 | std::scoped_lock lock(devices_list_mutex); 41 | 42 | /* Get mounted device count. */ 43 | usb_device_count = usbHsFsGetMountedDeviceCount(); 44 | if (!usb_device_count) 45 | continue; 46 | 47 | /* Allocate mounted devices buffer. */ 48 | usb_devices = new UsbHsFsDevice[usb_device_count]; 49 | if (!usb_devices) 50 | continue; 51 | 52 | /* List mounted devices. */ 53 | if (!(listed_device_count = usbHsFsListMountedDevices(usb_devices, usb_device_count))) { 54 | delete[] usb_devices; 55 | usb_devices = nullptr; 56 | continue; 57 | } 58 | 59 | /* Print info from mounted devices. */ 60 | for(u32 i = 0; i < listed_device_count; i++) { 61 | UsbHsFsDevice *device = std::addressof(usb_devices[i]); 62 | devices_list.push_back(device->name); 63 | } 64 | } 65 | } 66 | 67 | /* Exit thread. */ 68 | return; 69 | } 70 | 71 | Result Init(void) { 72 | std::scoped_lock lock(devices_list_mutex); 73 | 74 | Result ret = usbHsFsInitialize(0); 75 | if (R_SUCCEEDED(ret)) { 76 | /* Get USB Mass Storage status change event. */ 77 | status_change_event = usbHsFsGetStatusChangeUserEvent(); 78 | 79 | /* Create usermode thread exit event. */ 80 | ueventCreate(&exit_event, true); 81 | 82 | /* Create thread. */ 83 | if (R_SUCCEEDED(ret = threadCreate(&thread, usbMscThreadFunc, nullptr, nullptr, 0x10000, 0x2C, -2))) { 84 | if (R_SUCCEEDED(ret = threadStart(&thread))) 85 | thread_created = true; 86 | } 87 | } 88 | 89 | return ret; 90 | } 91 | 92 | void Exit(void) { 93 | std::scoped_lock lock(devices_list_mutex); 94 | 95 | if (thread_created) { 96 | /* Signal background thread. */ 97 | ueventSignal(&exit_event); 98 | 99 | /* Wait for the background thread to exit on its own. */ 100 | threadWaitForExit(&thread); 101 | threadClose(&thread); 102 | 103 | thread_created = false; 104 | } 105 | 106 | /* Clean up and exit. */ 107 | USB::Unmount(); 108 | usbHsFsExit(); 109 | } 110 | 111 | bool Connected(void) { 112 | std::scoped_lock lock(devices_list_mutex); 113 | return (listed_device_count > 0); 114 | } 115 | 116 | void Unmount(void) { 117 | std::scoped_lock lock(devices_list_mutex); 118 | 119 | /* Unmount devices. */ 120 | if (usb_devices) { 121 | for(u32 i = 0; i < listed_device_count; i++) { 122 | UsbHsFsDevice *device = std::addressof(usb_devices[i]); 123 | devices_list.pop_back(); 124 | usbHsFsUnmountDevice(device, false); 125 | } 126 | 127 | delete[] usb_devices; 128 | usb_devices = nullptr; 129 | } 130 | 131 | listed_device_count = 0; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /source/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace Utils { 4 | void GetSizeString(char *string, double size) { 5 | int i = 0; 6 | const char *units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}; 7 | 8 | while (size >= 1024.0f) { 9 | size /= 1024.0f; 10 | i++; 11 | } 12 | 13 | std::sprintf(string, "%.*f %s", (i == 0) ? 0 : 2, size, units[i]); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /source/window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.hpp" 5 | #include "imgui.h" 6 | #include "popups.hpp" 7 | #include "tabs.hpp" 8 | #include "windows.hpp" 9 | 10 | WindowData data; 11 | 12 | namespace Windows { 13 | static bool image_properties = false, file_stat = false; 14 | 15 | void SetupWindow(void) { 16 | ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f), ImGuiCond_Once); 17 | ImGui::SetNextWindowSize(ImVec2(1280.0f, 720.0f), ImGuiCond_Once); 18 | ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); 19 | }; 20 | 21 | void ExitWindow(void) { 22 | ImGui::End(); 23 | ImGui::PopStyleVar(); 24 | }; 25 | 26 | void ResetCheckbox(WindowData &data) { 27 | data.checkbox_data.checked.clear(); 28 | data.checkbox_data.checked_copy.clear(); 29 | data.checkbox_data.checked.resize(data.entries.size()); 30 | data.checkbox_data.checked.assign(data.checkbox_data.checked.size(), false); 31 | data.checkbox_data.cwd = ""; 32 | data.checkbox_data.count = 0; 33 | }; 34 | 35 | void MainWindow(WindowData &data, u64 &key, bool progress) { 36 | Windows::SetupWindow(); 37 | if (ImGui::Begin("NX-Shell", nullptr, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) { 38 | if (ImGui::BeginTabBar("NX-Shell-tabs")) { 39 | Tabs::FileBrowser(data); 40 | Tabs::Settings(data); 41 | ImGui::EndTabBar(); 42 | } 43 | } 44 | Windows::ExitWindow(); 45 | 46 | if (progress) 47 | return; 48 | 49 | switch (data.state) { 50 | case WINDOW_STATE_OPTIONS: 51 | Popups::OptionsPopup(data); 52 | break; 53 | 54 | case WINDOW_STATE_PROPERTIES: 55 | Popups::FilePropertiesPopup(data, file_stat); 56 | break; 57 | 58 | case WINDOW_STATE_DELETE: 59 | Popups::DeletePopup(data); 60 | break; 61 | 62 | case WINDOW_STATE_IMAGEVIEWER: 63 | Windows::ImageViewer(image_properties, file_stat); 64 | ImageViewer::HandleControls(key, image_properties); 65 | break; 66 | 67 | default: 68 | break; 69 | } 70 | 71 | if ((key & HidNpadButton_X) && (data.state == WINDOW_STATE_FILEBROWSER)) 72 | data.state = WINDOW_STATE_OPTIONS; 73 | 74 | if (key & HidNpadButton_Y) { 75 | if ((data.checkbox_data.cwd.length() != 0) && (data.checkbox_data.cwd != cwd)) 76 | Windows::ResetCheckbox(data); 77 | 78 | if ((std::strncmp(data.entries[data.selected].name, "..", 2)) != 0) { 79 | data.checkbox_data.cwd = cwd; 80 | data.checkbox_data.device = device; 81 | data.checkbox_data.checked.at(data.selected) = !data.checkbox_data.checked.at(data.selected); 82 | data.checkbox_data.count = std::count(data.checkbox_data.checked.begin(), data.checkbox_data.checked.end(), true); 83 | } 84 | } 85 | 86 | if (key & HidNpadButton_B) { 87 | switch(data.state) { 88 | case WINDOW_STATE_OPTIONS: 89 | data.state = WINDOW_STATE_FILEBROWSER; 90 | break; 91 | 92 | case WINDOW_STATE_PROPERTIES: 93 | data.state = WINDOW_STATE_OPTIONS; 94 | file_stat = false; 95 | break; 96 | 97 | case WINDOW_STATE_DELETE: 98 | data.state = WINDOW_STATE_OPTIONS; 99 | break; 100 | 101 | case WINDOW_STATE_IMAGEVIEWER: 102 | if (image_properties) { 103 | image_properties = false; 104 | file_stat = false; 105 | } 106 | else { 107 | ImageViewer::ClearTextures(); 108 | data.state = WINDOW_STATE_FILEBROWSER; 109 | } 110 | 111 | break; 112 | 113 | default: 114 | break; 115 | } 116 | } 117 | } 118 | } 119 | --------------------------------------------------------------------------------