commit 31953464e70f0fc4009dfca02923bb16f71c51c4 Author: Nikolai Fedorov Date: Sun Jul 14 20:20:03 2024 +0300 initial commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..b8a22bf Binary files /dev/null and b/.DS_Store differ diff --git a/.cache/clangd/index/Main.cpp.685062F8D32EB8FC.idx b/.cache/clangd/index/Main.cpp.685062F8D32EB8FC.idx new file mode 100644 index 0000000..0da004a Binary files /dev/null and b/.cache/clangd/index/Main.cpp.685062F8D32EB8FC.idx differ diff --git a/.cache/clangd/index/MainApplication.cpp.79A72745C41D6043.idx b/.cache/clangd/index/MainApplication.cpp.79A72745C41D6043.idx new file mode 100644 index 0000000..3c7e1ab Binary files /dev/null and b/.cache/clangd/index/MainApplication.cpp.79A72745C41D6043.idx differ diff --git a/.cache/clangd/index/MainApplication.hpp.221A9A34681ED2E1.idx b/.cache/clangd/index/MainApplication.hpp.221A9A34681ED2E1.idx new file mode 100644 index 0000000..a71b2ca Binary files /dev/null and b/.cache/clangd/index/MainApplication.hpp.221A9A34681ED2E1.idx differ diff --git a/.cache/clangd/index/Plutonium.176CA4CACA9A1290.idx b/.cache/clangd/index/Plutonium.176CA4CACA9A1290.idx new file mode 100644 index 0000000..1e8ad35 Binary files /dev/null and b/.cache/clangd/index/Plutonium.176CA4CACA9A1290.idx differ diff --git a/.cache/clangd/index/TitlesLayout.cpp.45180DB29A164B8B.idx b/.cache/clangd/index/TitlesLayout.cpp.45180DB29A164B8B.idx new file mode 100644 index 0000000..296e136 Binary files /dev/null and b/.cache/clangd/index/TitlesLayout.cpp.45180DB29A164B8B.idx differ diff --git a/.cache/clangd/index/TitlesLayout.hpp.75FC133FF2EF41D2.idx b/.cache/clangd/index/TitlesLayout.hpp.75FC133FF2EF41D2.idx new file mode 100644 index 0000000..0416f1d Binary files /dev/null and b/.cache/clangd/index/TitlesLayout.hpp.75FC133FF2EF41D2.idx differ diff --git a/.cache/clangd/index/UsersLayout.cpp.88C1C105F3F43467.idx b/.cache/clangd/index/UsersLayout.cpp.88C1C105F3F43467.idx new file mode 100644 index 0000000..14527b7 Binary files /dev/null and b/.cache/clangd/index/UsersLayout.cpp.88C1C105F3F43467.idx differ diff --git a/.cache/clangd/index/UsersLayout.hpp.1712702588294560.idx b/.cache/clangd/index/UsersLayout.hpp.1712702588294560.idx new file mode 100644 index 0000000..66a84d7 Binary files /dev/null and b/.cache/clangd/index/UsersLayout.hpp.1712702588294560.idx differ diff --git a/.cache/clangd/index/audio_Music.hpp.8B2BCABF9BE0D597.idx b/.cache/clangd/index/audio_Music.hpp.8B2BCABF9BE0D597.idx new file mode 100644 index 0000000..e1bc31f Binary files /dev/null and b/.cache/clangd/index/audio_Music.hpp.8B2BCABF9BE0D597.idx differ diff --git a/.cache/clangd/index/audio_Sfx.hpp.20BB10D088C7D73C.idx b/.cache/clangd/index/audio_Sfx.hpp.20BB10D088C7D73C.idx new file mode 100644 index 0000000..229f104 Binary files /dev/null and b/.cache/clangd/index/audio_Sfx.hpp.20BB10D088C7D73C.idx differ diff --git a/.cache/clangd/index/const.h.BA34DD522C225A5C.idx b/.cache/clangd/index/const.h.BA34DD522C225A5C.idx new file mode 100644 index 0000000..436dc3f Binary files /dev/null and b/.cache/clangd/index/const.h.BA34DD522C225A5C.idx differ diff --git a/.cache/clangd/index/data.cpp.92E1D4C612A67155.idx b/.cache/clangd/index/data.cpp.92E1D4C612A67155.idx new file mode 100644 index 0000000..f4b8421 Binary files /dev/null and b/.cache/clangd/index/data.cpp.92E1D4C612A67155.idx differ diff --git a/.cache/clangd/index/data.h.FB9692DD59DD0CA9.idx b/.cache/clangd/index/data.h.FB9692DD59DD0CA9.idx new file mode 100644 index 0000000..f72c8d3 Binary files /dev/null and b/.cache/clangd/index/data.h.FB9692DD59DD0CA9.idx differ diff --git a/.cache/clangd/index/dir.cpp.84282E8F15A0C3E7.idx b/.cache/clangd/index/dir.cpp.84282E8F15A0C3E7.idx new file mode 100644 index 0000000..cbd16e0 Binary files /dev/null and b/.cache/clangd/index/dir.cpp.84282E8F15A0C3E7.idx differ diff --git a/.cache/clangd/index/dir.h.8134312F1E0EEB8F.idx b/.cache/clangd/index/dir.h.8134312F1E0EEB8F.idx new file mode 100644 index 0000000..7daac4a Binary files /dev/null and b/.cache/clangd/index/dir.h.8134312F1E0EEB8F.idx differ diff --git a/.cache/clangd/index/elm_Button.hpp.018512E7528A66C7.idx b/.cache/clangd/index/elm_Button.hpp.018512E7528A66C7.idx new file mode 100644 index 0000000..63bb953 Binary files /dev/null and b/.cache/clangd/index/elm_Button.hpp.018512E7528A66C7.idx differ diff --git a/.cache/clangd/index/elm_Element.hpp.14E03F7E0D260C63.idx b/.cache/clangd/index/elm_Element.hpp.14E03F7E0D260C63.idx new file mode 100644 index 0000000..fd2b076 Binary files /dev/null and b/.cache/clangd/index/elm_Element.hpp.14E03F7E0D260C63.idx differ diff --git a/.cache/clangd/index/elm_Image.hpp.FAD5FE2703E71E60.idx b/.cache/clangd/index/elm_Image.hpp.FAD5FE2703E71E60.idx new file mode 100644 index 0000000..625d0ef Binary files /dev/null and b/.cache/clangd/index/elm_Image.hpp.FAD5FE2703E71E60.idx differ diff --git a/.cache/clangd/index/elm_Menu.hpp.52144C9486AEB8A4.idx b/.cache/clangd/index/elm_Menu.hpp.52144C9486AEB8A4.idx new file mode 100644 index 0000000..f958d1d Binary files /dev/null and b/.cache/clangd/index/elm_Menu.hpp.52144C9486AEB8A4.idx differ diff --git a/.cache/clangd/index/elm_ProgressBar.hpp.5BE7F064B955686E.idx b/.cache/clangd/index/elm_ProgressBar.hpp.5BE7F064B955686E.idx new file mode 100644 index 0000000..ccf5285 Binary files /dev/null and b/.cache/clangd/index/elm_ProgressBar.hpp.5BE7F064B955686E.idx differ diff --git a/.cache/clangd/index/elm_Rectangle.hpp.E8436387021D3723.idx b/.cache/clangd/index/elm_Rectangle.hpp.E8436387021D3723.idx new file mode 100644 index 0000000..b0fd7c1 Binary files /dev/null and b/.cache/clangd/index/elm_Rectangle.hpp.E8436387021D3723.idx differ diff --git a/.cache/clangd/index/elm_TextBlock.hpp.B8CCDCBE3879D2C2.idx b/.cache/clangd/index/elm_TextBlock.hpp.B8CCDCBE3879D2C2.idx new file mode 100644 index 0000000..c35e700 Binary files /dev/null and b/.cache/clangd/index/elm_TextBlock.hpp.B8CCDCBE3879D2C2.idx differ diff --git a/.cache/clangd/index/elm_Toggle.hpp.F643B22B4EBE2394.idx b/.cache/clangd/index/elm_Toggle.hpp.F643B22B4EBE2394.idx new file mode 100644 index 0000000..e588c34 Binary files /dev/null and b/.cache/clangd/index/elm_Toggle.hpp.F643B22B4EBE2394.idx differ diff --git a/.cache/clangd/index/extras_Toast.hpp.1CB990ED4FA33D5B.idx b/.cache/clangd/index/extras_Toast.hpp.1CB990ED4FA33D5B.idx new file mode 100644 index 0000000..abb66ef Binary files /dev/null and b/.cache/clangd/index/extras_Toast.hpp.1CB990ED4FA33D5B.idx differ diff --git a/.cache/clangd/index/file.cpp.5237F76D2447B003.idx b/.cache/clangd/index/file.cpp.5237F76D2447B003.idx new file mode 100644 index 0000000..a0e56d5 Binary files /dev/null and b/.cache/clangd/index/file.cpp.5237F76D2447B003.idx differ diff --git a/.cache/clangd/index/file.h.ECED60B58854FB92.idx b/.cache/clangd/index/file.h.ECED60B58854FB92.idx new file mode 100644 index 0000000..18e999a Binary files /dev/null and b/.cache/clangd/index/file.h.ECED60B58854FB92.idx differ diff --git a/.cache/clangd/index/fs.cpp.2C750D58CF93396A.idx b/.cache/clangd/index/fs.cpp.2C750D58CF93396A.idx new file mode 100644 index 0000000..fb70b75 Binary files /dev/null and b/.cache/clangd/index/fs.cpp.2C750D58CF93396A.idx differ diff --git a/.cache/clangd/index/fs.h.B7E897F33D9D7EE5.idx b/.cache/clangd/index/fs.h.B7E897F33D9D7EE5.idx new file mode 100644 index 0000000..d74e017 Binary files /dev/null and b/.cache/clangd/index/fs.h.B7E897F33D9D7EE5.idx differ diff --git a/.cache/clangd/index/fsfile.c.867FA10C150715C4.idx b/.cache/clangd/index/fsfile.c.867FA10C150715C4.idx new file mode 100644 index 0000000..4cf7d01 Binary files /dev/null and b/.cache/clangd/index/fsfile.c.867FA10C150715C4.idx differ diff --git a/.cache/clangd/index/fsfile.h.35B524E0BEF369EF.idx b/.cache/clangd/index/fsfile.h.35B524E0BEF369EF.idx new file mode 100644 index 0000000..0a4a006 Binary files /dev/null and b/.cache/clangd/index/fsfile.h.35B524E0BEF369EF.idx differ diff --git a/.cache/clangd/index/fstype.h.DEBE4E158EC53C0F.idx b/.cache/clangd/index/fstype.h.DEBE4E158EC53C0F.idx new file mode 100644 index 0000000..0999c49 Binary files /dev/null and b/.cache/clangd/index/fstype.h.DEBE4E158EC53C0F.idx differ diff --git a/.cache/clangd/index/ldn.cpp.393680BBDDA86121.idx b/.cache/clangd/index/ldn.cpp.393680BBDDA86121.idx new file mode 100644 index 0000000..b21627a Binary files /dev/null and b/.cache/clangd/index/ldn.cpp.393680BBDDA86121.idx differ diff --git a/.cache/clangd/index/ldn.h.1D557AF101A575C7.idx b/.cache/clangd/index/ldn.h.1D557AF101A575C7.idx new file mode 100644 index 0000000..8479681 Binary files /dev/null and b/.cache/clangd/index/ldn.h.1D557AF101A575C7.idx differ diff --git a/.cache/clangd/index/pu_Include.hpp.C523922F5EC060FC.idx b/.cache/clangd/index/pu_Include.hpp.C523922F5EC060FC.idx new file mode 100644 index 0000000..b0c8ec7 Binary files /dev/null and b/.cache/clangd/index/pu_Include.hpp.C523922F5EC060FC.idx differ diff --git a/.cache/clangd/index/render_Renderer.hpp.6683E79ACA4A54CC.idx b/.cache/clangd/index/render_Renderer.hpp.6683E79ACA4A54CC.idx new file mode 100644 index 0000000..3151c93 Binary files /dev/null and b/.cache/clangd/index/render_Renderer.hpp.6683E79ACA4A54CC.idx differ diff --git a/.cache/clangd/index/render_SDL2.hpp.18A6D5991D9EFEE8.idx b/.cache/clangd/index/render_SDL2.hpp.18A6D5991D9EFEE8.idx new file mode 100644 index 0000000..e5ba05e Binary files /dev/null and b/.cache/clangd/index/render_SDL2.hpp.18A6D5991D9EFEE8.idx differ diff --git a/.cache/clangd/index/sdl2_CustomTtf.h.DA6EB9766D3A2A8C.idx b/.cache/clangd/index/sdl2_CustomTtf.h.DA6EB9766D3A2A8C.idx new file mode 100644 index 0000000..8b1bb26 Binary files /dev/null and b/.cache/clangd/index/sdl2_CustomTtf.h.DA6EB9766D3A2A8C.idx differ diff --git a/.cache/clangd/index/sdl2_Types.hpp.14E8D52331FEA768.idx b/.cache/clangd/index/sdl2_Types.hpp.14E8D52331FEA768.idx new file mode 100644 index 0000000..54941a3 Binary files /dev/null and b/.cache/clangd/index/sdl2_Types.hpp.14E8D52331FEA768.idx differ diff --git a/.cache/clangd/index/threads.cpp.3D4AF726B9B95CFB.idx b/.cache/clangd/index/threads.cpp.3D4AF726B9B95CFB.idx new file mode 100644 index 0000000..195e416 Binary files /dev/null and b/.cache/clangd/index/threads.cpp.3D4AF726B9B95CFB.idx differ diff --git a/.cache/clangd/index/threads.h.BB90B2C1D3A0674C.idx b/.cache/clangd/index/threads.h.BB90B2C1D3A0674C.idx new file mode 100644 index 0000000..5c3e736 Binary files /dev/null and b/.cache/clangd/index/threads.h.BB90B2C1D3A0674C.idx differ diff --git a/.cache/clangd/index/type.h.3039A978BD63D81E.idx b/.cache/clangd/index/type.h.3039A978BD63D81E.idx new file mode 100644 index 0000000..fbf3bf8 Binary files /dev/null and b/.cache/clangd/index/type.h.3039A978BD63D81E.idx differ diff --git a/.cache/clangd/index/ui_Application.hpp.C3BE54443DABA7BF.idx b/.cache/clangd/index/ui_Application.hpp.C3BE54443DABA7BF.idx new file mode 100644 index 0000000..56db13c Binary files /dev/null and b/.cache/clangd/index/ui_Application.hpp.C3BE54443DABA7BF.idx differ diff --git a/.cache/clangd/index/ui_Container.hpp.DBA564B6F57F0254.idx b/.cache/clangd/index/ui_Container.hpp.DBA564B6F57F0254.idx new file mode 100644 index 0000000..c44e358 Binary files /dev/null and b/.cache/clangd/index/ui_Container.hpp.DBA564B6F57F0254.idx differ diff --git a/.cache/clangd/index/ui_Dialog.hpp.BA5E55C427BB5263.idx b/.cache/clangd/index/ui_Dialog.hpp.BA5E55C427BB5263.idx new file mode 100644 index 0000000..a333316 Binary files /dev/null and b/.cache/clangd/index/ui_Dialog.hpp.BA5E55C427BB5263.idx differ diff --git a/.cache/clangd/index/ui_Layout.hpp.30029BE965429247.idx b/.cache/clangd/index/ui_Layout.hpp.30029BE965429247.idx new file mode 100644 index 0000000..4ba4ad4 Binary files /dev/null and b/.cache/clangd/index/ui_Layout.hpp.30029BE965429247.idx differ diff --git a/.cache/clangd/index/ui_Overlay.hpp.177672D215C52C40.idx b/.cache/clangd/index/ui_Overlay.hpp.177672D215C52C40.idx new file mode 100644 index 0000000..44f18c4 Binary files /dev/null and b/.cache/clangd/index/ui_Overlay.hpp.177672D215C52C40.idx differ diff --git a/.cache/clangd/index/ui_Types.hpp.D33AC0C2311B451E.idx b/.cache/clangd/index/ui_Types.hpp.D33AC0C2311B451E.idx new file mode 100644 index 0000000..88e3fa2 Binary files /dev/null and b/.cache/clangd/index/ui_Types.hpp.D33AC0C2311B451E.idx differ diff --git a/.cache/clangd/index/util.cpp.7798728B5E2F0E42.idx b/.cache/clangd/index/util.cpp.7798728B5E2F0E42.idx new file mode 100644 index 0000000..0b7254c Binary files /dev/null and b/.cache/clangd/index/util.cpp.7798728B5E2F0E42.idx differ diff --git a/.cache/clangd/index/util.h.4B818D27750FB9A3.idx b/.cache/clangd/index/util.h.4B818D27750FB9A3.idx new file mode 100644 index 0000000..74af237 Binary files /dev/null and b/.cache/clangd/index/util.h.4B818D27750FB9A3.idx differ diff --git a/.cache/clangd/index/zip.cpp.BDB97FF96BC149B8.idx b/.cache/clangd/index/zip.cpp.BDB97FF96BC149B8.idx new file mode 100644 index 0000000..62ef634 Binary files /dev/null and b/.cache/clangd/index/zip.cpp.BDB97FF96BC149B8.idx differ diff --git a/.cache/clangd/index/zip.h.74C41B03268B991E.idx b/.cache/clangd/index/zip.h.74C41B03268B991E.idx new file mode 100644 index 0000000..2f617a6 Binary files /dev/null and b/.cache/clangd/index/zip.h.74C41B03268B991E.idx differ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4312018 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib"] + path = lib + url = https://github.com/XorTroll/Plutonium.git diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..53624c9 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..e6bbd32 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..d59c8e2 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "DKP aarch64", + "includePath": [ + "${env:DEVKITPRO}/devkitA64/aarch64-none-elf/include/**", + "${env:DEVKITPRO}/devkitARM/arm-none-eabi/include/**", + "${env:DEVKITPRO}/devkitA64/lib/gcc/aarch64-none-elf/13.2.0/include/**", + "${env:DEVKITPRO}/libnx/include/**", + "${env:DEVKITPRO}/portlibs/switch/include/**", + "${workspaceFolder}/include/**", + "${workspaceFolder}/lib/Plutonium/include/**", + "/opt/homebrew/opt/lz4/include/**" + ], + "defines": ["SWITCH", "__SWITCH__", "DEBUG", "__BSD_VISIBLE"], + "compilerPath": "${env:DEVKITPRO}/devkitA64/bin/aarch64-none-elf-g++", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-gcc-arm64" + } + ], + "version": 4 +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..67b7939 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,101 @@ +{ + "files.associations": { + "string.h": "c", + "stdlib.h": "c", + "switch.h": "c", + "psm.h": "c", + "vector": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "barrier": "cpp", + "bit": "cpp", + "*.tcc": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "cfenv": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "csetjmp": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cuchar": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "exception": "cpp", + "expected": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "latch": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "scoped_allocator": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "spanstream": "cpp", + "sstream": "cpp", + "stacktrace": "cpp", + "stdexcept": "cpp", + "stdfloat": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "syncstream": "cpp", + "thread": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp", + "filesystem": "cpp", + "plutonium": "cpp" + } +} \ No newline at end of file diff --git a/Iridium_Icon256.png b/Iridium_Icon256.png new file mode 100644 index 0000000..5ba7b0c Binary files /dev/null and b/Iridium_Icon256.png differ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c894e4d --- /dev/null +++ b/Makefile @@ -0,0 +1,203 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +#--------------------------------------------------------------------------------- +TARGET := NXST +BUILD := build +SOURCES := source source/fs lib/Plutonium/source +INCLUDES := include include/fs lib/Plutonium/include +EXEFS_SRC := exefs_src +APP_TITLE := NXST +APP_AUTHOR := DragonSpirit +APP_VERSION := 01.28.2024 +ICON := icon.jpg + + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS += -g -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS:= $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lpu -lz -lminizip -lfreetype -lSDL2_mixer -lopusfile -lopus -lmodplug -lmpg123 -lvorbisidec -logg -lSDL2_ttf -lSDL2_gfx -lSDL2_image -lSDL2 -lEGL -lGLESv2 -lglapi -ldrm_nouveau -lwebp -lpng -ljpeg `sdl2-config --libs` `freetype-config --libs` -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/lib/Plutonium + + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf + + +#--------------------------------------------------------------------------------- +send: $(BUILD) + @nxlink $(TARGET).nro + +debug: $(BUILD) + @nxlink -s $(TARGET).nro + + +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).pfs0 $(OUTPUT).nro + +$(OUTPUT).pfs0 : $(OUTPUT).nso + +$(OUTPUT).nso : $(OUTPUT).elf + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/NXST.lst b/NXST.lst new file mode 100644 index 0000000..3ad15de --- /dev/null +++ b/NXST.lst @@ -0,0 +1,2 @@ +-CSn +/Users/n.fedorov/dev/NXST/NXST.elf diff --git a/cleanup b/cleanup new file mode 100755 index 0000000..344a357 --- /dev/null +++ b/cleanup @@ -0,0 +1,10 @@ +rm build/ -r || true + +# TRY to remove Iridium.nsp, ignore if it fails + +rm Iridium.nro || true +rm Iridium.nacp || true +rm Iridium.elf || true +rm Iridium.lst || true + + diff --git a/compile_commands.json b/compile_commands.json new file mode 100644 index 0000000..3f01e6a --- /dev/null +++ b/compile_commands.json @@ -0,0 +1,428 @@ +[ + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/Main.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/Main.cpp", + "-o", + "Main.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/Main.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/MainApplication.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/MainApplication.cpp", + "-o", + "MainApplication.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/MainApplication.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/TitlesLayout.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/TitlesLayout.cpp", + "-o", + "TitlesLayout.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/TitlesLayout.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/UsersLayout.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/UsersLayout.cpp", + "-o", + "UsersLayout.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/UsersLayout.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/data.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/data.cpp", + "-o", + "data.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/data.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/fs.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/fs.cpp", + "-o", + "fs.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/fs.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/ldn.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/ldn.cpp", + "-o", + "ldn.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/ldn.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/threads.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/threads.cpp", + "-o", + "threads.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/threads.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/util.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/util.cpp", + "-o", + "util.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/util.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/dir.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/fs/dir.cpp", + "-o", + "dir.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/fs/dir.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/file.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/fs/file.cpp", + "-o", + "file.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/fs/file.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-g++", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/zip.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-fno-rtti", + "-fno-exceptions", + "-std=gnu++17", + "-c", + "/Users/n.fedorov/dev/NXST/source/fs/zip.cpp", + "-o", + "zip.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/fs/zip.cpp" + }, + { + "directory": "/Users/n.fedorov/dev/NXST", + "arguments": [ + "aarch64-none-elf-gcc", + "-MMD", + "-MP", + "-MF", + "/Users/n.fedorov/dev/NXST/build/fsfile.d", + "-g", + "-O2", + "-ffunction-sections", + "-march=armv8-a+crc+crypto", + "-mtune=cortex-a57", + "-mtp=soft", + "-fPIE", + "-I/Users/n.fedorov/dev/NXST/include", + "-I/Users/n.fedorov/dev/NXST/include/fs", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/opt/devkitpro/portlibs/switch/include", + "-I/opt/devkitpro/libnx/include", + "-I/Users/n.fedorov/dev/NXST/lib/Plutonium/include", + "-I/Users/n.fedorov/dev/NXST/build", + "-D__SWITCH__", + "-c", + "/Users/n.fedorov/dev/NXST/source/fs/fsfile.c", + "-o", + "fsfile.o" + ], + "file": "/Users/n.fedorov/dev/NXST/source/fs/fsfile.c" + } +] diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 0000000..a060b1d Binary files /dev/null and b/icon.jpg differ diff --git a/include/.DS_Store b/include/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/include/.DS_Store differ diff --git a/include/MainApplication.hpp b/include/MainApplication.hpp new file mode 100644 index 0000000..c510dea --- /dev/null +++ b/include/MainApplication.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace ui { + + class MainApplication : public pu::ui::Application { + + public: + using Application::Application; + PU_SMART_CTOR(MainApplication) + + void OnLoad() override; + + // Layout instance + UsersLayout::Ref usersLayout; + TitlesLayout::Ref titlesLayout; + }; +} \ No newline at end of file diff --git a/include/TitlesLayout.hpp b/include/TitlesLayout.hpp new file mode 100644 index 0000000..747a1a2 --- /dev/null +++ b/include/TitlesLayout.hpp @@ -0,0 +1,19 @@ +#include +#include +#include + +namespace ui { + class TitlesLayout : public pu::ui::Layout { + private: + + pu::ui::elm::Menu::Ref titlesMenu; + + public: + + void InitTitles(); + + void onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos); + + PU_SMART_CTOR(TitlesLayout) + }; +} \ No newline at end of file diff --git a/include/UsersLayout.hpp b/include/UsersLayout.hpp new file mode 100644 index 0000000..f714f6a --- /dev/null +++ b/include/UsersLayout.hpp @@ -0,0 +1,22 @@ +#include +#include + +namespace ui { + + class UsersLayout : public pu::ui::Layout { + private: + + pu::ui::elm::Menu::Ref usersMenu; + + public: + + UsersLayout(); + + void onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos); + + int32_t GetCurrentIndex(); + + PU_SMART_CTOR(UsersLayout) + + }; +} diff --git a/include/const.h b/include/const.h new file mode 100644 index 0000000..8dda9ed --- /dev/null +++ b/include/const.h @@ -0,0 +1,2 @@ +#define BACKGROUND_COLOR COLOR("00FFFFFF") +#define COLOR(hex) pu::ui::Color::FromHex(hex) \ No newline at end of file diff --git a/include/data.h b/include/data.h new file mode 100644 index 0000000..17336de --- /dev/null +++ b/include/data.h @@ -0,0 +1,91 @@ +#pragma once +#include + +#include +#include +#include + +#define BLD_MON 02 +#define BLD_DAY 23 +#define BLD_YEAR 2023 + +namespace data +{ + //Loads user + title info + void init(); + void exit(); + bool loadUsersTitles(bool clearUsers); + void sortUserTitles(); + + //Draws some stats to the upper left corner + void dispStats(); + + //Global stuff for all titles/saves + typedef struct + { + NacpStruct nacp; + std::string title, safeTitle, author;//Shortcuts sorta. + bool fav; + } titleInfo; + + //Holds stuff specific to user's titles/saves + typedef struct + { + //Makes it easier to grab id + uint64_t tid; + FsSaveDataInfo saveInfo; + PdmPlayStatistics playStats; + } userTitleInfo; + + //Class to store user info + titles + class user + { + public: + user() = default; + user(const AccountUid& _id, const std::string& _backupName, const std::string& _safeBackupName); + + //Sets ID + void setUID(const AccountUid& _id); + + //Returns user ID + AccountUid getUID() const { return userID; } + u128 getUID128() const { return uID128; } + + //Returns username + std::string getUsername() const { return username; } + std::string getUsernameSafe() const { return userSafe; } + + std::vector titleInfo; + void addUserTitleInfo(const uint64_t& _tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats); + + private: + AccountUid userID; + u128 uID128; + std::string username, userSafe; + //User icon + }; + + //User vector + extern std::vector users; + //Title data/info map + extern std::unordered_map titles; + + //Sets/Retrieves current user/title + void setUserIndex(unsigned _sUser); + data::user *getCurrentUser(); + unsigned getCurrentUserIndex(); + + void setTitleIndex(unsigned _sTitle); + data::userTitleInfo *getCurrentUserTitleInfo(); + unsigned getCurrentUserTitleInfoIndex(); + + //Gets pointer to info that also has title + nacp + data::titleInfo *getTitleInfoByTID(const uint64_t& tid); + + //More shortcut functions + std::string getTitleNameByTID(const uint64_t& tid); + std::string getTitleSafeNameByTID(const uint64_t& tid); + int getTitleIndexInUser(const data::user& u, const uint64_t& tid); + extern SetLanguage sysLang; + +} \ No newline at end of file diff --git a/include/fs.h b/include/fs.h new file mode 100644 index 0000000..a582ba5 --- /dev/null +++ b/include/fs.h @@ -0,0 +1,54 @@ +#pragma once +#include + +#include +#include + +#include "fs/fstype.h" +#include "fs/file.h" +#include "fs/dir.h" +#include "fs/fsfile.h" +#include "fs/zip.h" + +#define BUFF_SIZE 0x4000 +#define ZIP_BUFF_SIZE 0x20000 +#define TRANSFER_BUFFER_LIMIT 0xC00000 + +namespace fs +{ + copyArgs *copyArgsCreate(const std::string& src, const std::string& dst, const std::string& dev, zipFile z, unzFile unz, bool _cleanup, bool _trimZipPath, uint8_t _trimPlaces); + void copyArgsDestroy(copyArgs *c); + + void init(); + bool mountSave(const FsSaveDataInfo& _m); + inline bool unmountSave() { return fsdevUnmountDevice("sv") == 0; } + bool commitToDevice(const std::string& dev); + std::string getWorkDir(); + void setWorkDir(const std::string& _w); + + //Loads paths to filter from backup/deletion + void loadPathFilters(const uint64_t& tid); + bool pathIsFiltered(const std::string& _path); + void freePathFilters(); + + void createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _uid, threadInfo *t); + void createSaveDataThreaded(FsSaveDataType _type, uint64_t _tid, AccountUid _uid); + bool extendSaveData(const data::userTitleInfo *tinfo, uint64_t extSize, threadInfo *t); + void extendSaveDataThreaded(const data::userTitleInfo *tinfo, uint64_t extSize); + uint64_t getJournalSize(const data::userTitleInfo *tinfo); + uint64_t getJournalSizeMax(const data::userTitleInfo *tinfo); + + //Always threaded + void wipeSave(); + + void createNewBackup(void *a); + void overwriteBackup(void *a); + void restoreBackup(void *a); + void deleteBackup(void *a); + + void dumpAllUserSaves(void *a); + void dumpAllUsersAllSaves(void *a); + + void logOpen(); + void logWrite(const char *fmt, ...); +} diff --git a/include/fs/dir.h b/include/fs/dir.h new file mode 100644 index 0000000..8dc83b3 --- /dev/null +++ b/include/fs/dir.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include "type.h" + +namespace fs +{ + void mkDir(const std::string& _p); + void mkDirRec(const std::string& _p); + void delDir(const std::string& _p); + bool dirNotEmpty(const std::string& _dir); + bool isDir(const std::string& _path); + + //threadInfo is optional. Only for updating task status. + void copyDirToDir(const std::string& src, const std::string& dst, threadInfo *t); + void copyDirToDirThreaded(const std::string& src, const std::string& dst); + void copyDirToDirCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t); + void copyDirToDirCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev); + void getDirProps(const std::string& path, unsigned& dirCount, unsigned& fileCount, uint64_t& totalSize); + + class dirItem + { + public: + dirItem(const std::string& pathTo, const std::string& sItem); + std::string getItm() const { return itm; } + std::string getName() const; + std::string getExt() const; + bool isDir() const { return dir; } + + private: + std::string itm; + bool dir = false; + }; + + //Just retrieves a listing for _path and stores it in item vector + class dirList + { + public: + dirList() = default; + dirList(const std::string& _path); + void reassign(const std::string& _path); + void rescan(); + + std::string getItem(int index) const { return item[index].getItm(); } + std::string getItemExt(int index) const { return item[index].getExt(); } + bool isDir(int index) const { return item[index].isDir(); } + unsigned getCount() const { return item.size(); } + fs::dirItem *getDirItemAt(unsigned int _ind) { return &item[_ind]; } + + private: + std::string path; + std::vector item; + }; +} diff --git a/include/fs/file.h b/include/fs/file.h new file mode 100644 index 0000000..08ed43a --- /dev/null +++ b/include/fs/file.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "fs.h" +#include "data.h" + +namespace fs +{ + //Copy args are optional and only used if passed and threaded + void copyFile(const std::string& src, const std::string& dst, threadInfo *t); + void copyFileThreaded(const std::string& src, const std::string& dst); + void copyFileCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t); + void copyFileCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev); + void fileDrawFunc(void *a); + + //deletes file + void delfile(const std::string& _p); + + //Dumps all titles for current user + void dumpAllUserSaves(); + + void getShowFileProps(const std::string& _path); + void getShowDirProps(const std::string& _path); + + bool fileExists(const std::string& _path); + //Returns file size + size_t fsize(const std::string& _f); + + class dataFile + { + public: + dataFile(const std::string& _path); + ~dataFile(); + void close(){ fclose(f); } + + bool isOpen() const { return opened; } + + bool readNextLine(bool proc); + //Finds where variable name ends. When a '(' or '=' is hit. Strips spaces + void procLine(); + std::string getLine() const { return line; } + std::string getName() const { return name; } + //Reads until ';', ',', or '\n' is hit and returns as string. + std::string getNextValueStr(); + int getNextValueInt(); + + private: + FILE *f; + std::string line, name; + size_t lPos = 0; + bool opened = false; + }; + + void logOpen(); + void logWrite(const char *fmt, ...); + void logClose(); +} diff --git a/include/fs/fsfile.h b/include/fs/fsfile.h new file mode 100644 index 0000000..c84be82 --- /dev/null +++ b/include/fs/fsfile.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include + +//Bare minimum wrapper around switch fs for JKSV +#define FS_SEEK_SET 0 +#define FS_SEEK_CUR 1 +#define FS_SEEK_END 2 + +#ifdef __cplusplus +extern "C" +{ +#endif +typedef struct +{ + FsFile _f; + Result error; + s64 offset, fsize; +} FSFILE; + +int fsremove(const char *_p); +Result fsDelDirRec(const char *_p); + +char *getDeviceFromPath(char *dev, size_t _max, const char *path); +char *getFilePath(char *pathOut, size_t _max, const char *path); + +bool fsMkDir(const char *_p); + +/*Opens file. Device is fetched from path. Libnx romfs doesn't work with this. +Mode needs to be: + FsOpenMode_Read + FsOpenMode_Write + FsOpenMode_Append +*/ +bool fsfcreate(const char *_p, int64_t crSize); + +FSFILE *fsfopen(const char *_p, uint32_t mode); + +/*Same as above, but FsFileSystem _s is used. Path cannot have device in it*/ +FSFILE *fsfopenWithSystem(FsFileSystem *_s, const char *_p, uint32_t mode); + +//Closes _f +inline void fsfclose(FSFILE *_f) +{ + if(_f != NULL) + { + fsFileClose(&_f->_f); + free(_f); + } +} + +//Seeks like stdio +inline void fsfseek(FSFILE *_f, int offset, int origin) +{ + switch(origin) + { + case FS_SEEK_SET: + _f->offset = offset; + break; + + case FS_SEEK_CUR: + _f->offset += offset; + break; + + case FS_SEEK_END: + _f->offset = offset + _f->fsize; + break; + } +} + +//Returns offset +inline size_t fsftell(FSFILE *_f) { return _f->offset; } + +//Writes buf to file. Automatically resizes _f to fit buf +size_t fsfwrite(const void *buf, size_t sz, size_t count, FSFILE *_f); + +//Reads to buff +inline size_t fsfread(void *buf, size_t sz, size_t count, FSFILE *_f) +{ + uint64_t read = 0; + _f->error = fsFileRead(&_f->_f, _f->offset, buf, sz * count, 0, &read); + _f->offset += read; + return read; +} + +//Gets byte from file +inline char fsfgetc(FSFILE *_f) +{ + char ret = 0; + uint64_t read = 0; + _f->error = fsFileRead(&_f->_f, _f->offset++, &ret, 1, 0, &read); + return ret; +} + +//Writes byte to file +inline void fsfputc(int ch, FSFILE *_f) { fsfwrite(&ch, 1, 1, _f); } +#ifdef __cplusplus +} +#endif diff --git a/include/fs/fstype.h b/include/fs/fstype.h new file mode 100644 index 0000000..347f05d --- /dev/null +++ b/include/fs/fstype.h @@ -0,0 +1,46 @@ +#pragma once + +#include "data.h" +#include "type.h" +#include "ldn.h" + +namespace fs +{ + typedef struct + { + std::string src, dst, dev; + zipFile z; + unzFile unz; + LDN::LDNCommunicate *comm; + bool cleanup = false, trimZipPath = false; + uint8_t trimZipPlaces = 0; + uint64_t offset = 0; + threadStatus *thrdStatus; + Mutex arglck = 0; + void argLock() { mutexLock(&arglck); } + void argUnlock() { mutexUnlock(&arglck); } + } copyArgs; + + typedef struct + { + FsSaveDataType type; + uint64_t tid; + AccountUid account; + uint16_t index; + } svCreateArgs; + + typedef struct + { + const data::userTitleInfo *tinfo; + uint64_t extSize; + } svExtendArgs; + + typedef struct + { + std::string path; + bool origin = false; + unsigned dirCount = 0; + unsigned fileCount = 0; + uint64_t totalSize = 0; + } dirCountArgs; +} diff --git a/include/fs/ldn.h b/include/fs/ldn.h new file mode 100644 index 0000000..16b74fb --- /dev/null +++ b/include/fs/ldn.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "type.h" + +#define SAVE_DATA_SERVER_PORT 25789 +#define SOCKET_BUFFER_SIZE 4096 +#define LENGTH_OF_LISTEN_QUEUE 20 + +enum LDN_COMMUNICATE_TYPE { + UPDATE_FILE, + UPDATE_ABORT, + UPDATE_OK, + UPDATE_DONE, +}; + +struct LZ4_readFile_s { + LZ4F_dctx *dctxPtr; + int socket; + LZ4_byte *srcBuf; + size_t srcBufNext; + size_t srcBufSize; + size_t srcBufMaxSize; +}; + +struct LZ4_writeFile_s { + LZ4F_cctx *cctxPtr; + int socket; + LZ4_byte *dstBuf; + size_t maxWriteSize; + size_t dstBufMaxSize; + LZ4F_errorCode_t errCode; +}; + +namespace LDN { + typedef struct { + // point to server's socket fd; + int serverFD; + // point to communicate socket fd, send meta data + int commFD; + // for client to bind with server, communicate create file socket fd + struct sockaddr_in serverAddr; + } LDNCommunicate; + + typedef struct { + LDNCommunicate *comm; + unsigned int filesize = 0, writeLimit = 0; + std::string fullPath; + std::mutex bufferLock; + std::condition_variable cond; + std::vector sharedBuffer; + bool bufferIsFull = false; + std::string dst, dev; + LZ4_writeFile_s* lz4fWrite; + } LDNfcopyArgs; + + typedef struct { + u32 type; + size_t fsz; + } commMeta; + + void destroyLDN(); + + LDN::LDNCommunicate *createCommunicate(void); + + void destroyCommunicate(LDNCommunicate *comm); + + Result createLDNServer(LDNCommunicate *comm); + + Result createLDNClient(LDNCommunicate *comm); + + int bindClient(int serverFD); + + int bindServer(sockaddr_in *serverAddr); + + bool waitForOK(int socketfd); + bool waitForDONE(int socketfd); + void sendOK(int socket_fd); + void sendDONE(int socket_fd); + void sendAbort(int socket_fd); + void reciveMeta(commMeta *meta, int socketfd); + void sendMeta(commMeta *meta, int socketfd); + void copySaveFileToRemote(const std::string &local, threadInfo *t); + void copyRemoteSaveFile(threadInfo *t); +}; \ No newline at end of file diff --git a/include/fs/zip.h b/include/fs/zip.h new file mode 100644 index 0000000..4f8afb5 --- /dev/null +++ b/include/fs/zip.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include +#include + +#include "type.h" + +namespace fs +{ + //threadInfo is optional and only used when threaded versions are used + void copyDirToZip(const std::string& src, zipFile dst, bool trimPath, int trimPlaces, threadInfo *t); + void copyDirToZipThreaded(const std::string& src, zipFile dst, bool trimPath, int trimPlaces); + void copyZipToDir(unzFile src, const std::string& dst, const std::string& dev, threadInfo *t); + void copyZipToDirThreaded(unzFile src, const std::string& dst, const std::string& dev); + uint64_t getZipTotalSize(unzFile unz); + bool zipNotEmpty(unzFile unz); +} diff --git a/include/threads.h b/include/threads.h new file mode 100644 index 0000000..a384904 --- /dev/null +++ b/include/threads.h @@ -0,0 +1,44 @@ +#include +#include "type.h" + +namespace threads { + + //pad data cause i don't know where else to put it + extern PadState pad; + extern HidTouchScreenState touchState; + + static inline void updateInput() { + touchState = {0}; + padUpdate(&pad); + hidGetTouchScreenStates(&touchState, 1); + } + + inline uint64_t padKeysDown() { return padGetButtonsDown(&pad); } + + inline uint64_t padKeysHeld() { return padGetButtons(&pad); } + + inline uint64_t padKeysUp() { return padGetButtonsUp(&pad); } + + threadInfo *newThread(ThreadFunc func, void *args, funcPtr _drawFunc); + + class threadProcMngr { + public: + ~threadProcMngr(); + + //Draw function is used and called to draw on overlay + threadInfo *newThread(ThreadFunc func, void *args, funcPtr _drawFunc); + + void update(); + + void draw(); + + bool empty() { return threads.empty(); } + + private: + std::vector threads; + uint8_t lgFrame = 0, clrShft = 0; + bool clrAdd = true; + unsigned frameCount = 0; + Mutex threadLock = 0; + }; +} diff --git a/include/type.h b/include/type.h new file mode 100644 index 0000000..4f73986 --- /dev/null +++ b/include/type.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include +#include +#include + +//Misc stuff for new menu code +typedef void (*funcPtr)(void *); + +class threadStatus +{ + public: + void setStatus(const char *fmt, ...); + void getStatus(std::string& statusOut); + + private: + Mutex statusLock = 0; + std::string status; +}; + +typedef struct +{ + bool running = false, finished = false; + Thread thrd; + ThreadFunc thrdFunc; + void *argPtr = NULL; + funcPtr drawFunc = NULL;//Draw func is passed threadInfo pointer too + threadStatus *status; +} threadInfo; diff --git a/include/util.h b/include/util.h new file mode 100644 index 0000000..72548cc --- /dev/null +++ b/include/util.h @@ -0,0 +1,152 @@ +#pragma once + +#include "data.h" +//#include "ui.h" +#include "fs/file.h" +//#include "gfx.h" + +namespace util +{ + enum + { + DATE_FMT_YMD, + DATE_FMT_YDM, + DATE_FMT_HOYSTE, + DATE_FMT_JHK, + DATE_FMT_ASC + }; + + typedef enum + { + CPU_SPEED_204MHz = 204000000, + CPU_SPEED_306MHz = 306000000, + CPU_SPEED_408MHz = 408000000, + CPU_SPEED_510MHz = 510000000, + CPU_SPEED_612MHz = 612000000, + CPU_SPEED_714MHz = 714000000, + CPU_SPEED_816MHz = 816000000, + CPU_SPEED_918MHz = 918000000, + CPU_SPEED_1020MHz = 1020000000, //Default + CPU_SPEED_1122MHz = 1122000000, + CPU_SPEED_1224MHz = 1224000000, + CPU_SPEED_1326MHz = 1326000000, + CPU_SPEED_1428MHz = 1428000000, + CPU_SPEED_1581MHz = 1581000000, + CPU_SPEED_1683MHz = 1683000000, + CPU_SPEED_1785MHz = 1785000000 + } cpuSpds; + + typedef enum + { + GPU_SPEED_0MHz = 0, + GPU_SPEED_76MHz = 76800000, + GPU_SPEED_153MHz = 153600000, + GPU_SPEED_203MHz = 230400000, + GPU_SPEED_307MHz = 307200000, //Handheld 1 + GPU_SPEED_384MHz = 384000000, //Handheld 2 + GPU_SPEED_460MHz = 460800000, + GPU_SPEED_537MHz = 537600000, + GPU_SPEED_614MHz = 614400000, + GPU_SPEED_768MHz = 768000000, //Docked + GPU_SPEED_844MHz = 844800000, + GPU_SPEED_921MHZ = 921600000 + } gpuSpds; + + typedef enum + { + RAM_SPEED_0MHz = 0, + RAM_SPEED_40MHz = 40800000, + RAM_SPEED_68MHz = 68000000, + RAM_SPEED_102MHz = 102000000, + RAM_SPEED_204MHz = 204000000, + RAM_SPEED_408MHz = 408000000, + RAM_SPEED_665MHz = 665600000, + RAM_SPEED_800MHz = 800000000, + RAM_SPEED_1065MHz = 1065600000, + RAM_SPEED_1331MHz = 1331200000, + RAM_SPEED_1600MHz = 1600000000 + } ramSpds; + + //Returns string with date S+ time + std::string getDateTime(int fmt); + + //Removes last folder from '_path' + void removeLastFolderFromString(std::string& _path); + size_t getTotalPlacesInPath(const std::string& _path); + void trimPath(std::string& _path, uint8_t _places); + + inline bool isASCII(const uint32_t& t) + { + return t > 30 && t < 127; + } + + std::string safeString(const std::string& s); + + std::string getStringInput(SwkbdType _type, const std::string& def, const std::string& head, size_t maxLength, unsigned dictCnt, const std::string dictWords[]); + + std::string getExtensionFromString(const std::string& get); + std::string getFilenameFromPath(const std::string& get); + + std::string generateAbbrev(const uint64_t& tid); + + //removes char from C++ string + void stripChar(char _c, std::string& _s); + + void replaceStr(std::string& _str, const std::string& _find, const std::string& _rep); + + //For future external translation support. Replaces [button] with button chars + void replaceButtonsInString(std::string& rep); + + inline u128 accountUIDToU128(AccountUid uid) + { + return ((u128)uid.uid[0] << 64 | uid.uid[1]); + } + + inline AccountUid u128ToAccountUID(u128 id) + { + AccountUid ret; + ret.uid[0] = id >> 64; + ret.uid[1] = id; + return ret; + } + + inline std::string getIDStr(const uint64_t& _id) + { + char tmp[18]; + sprintf(tmp, "%016lX", _id); + return std::string(tmp); + } + + inline std::string getIDStrLower(const uint64_t& _id) + { + char tmp[18]; + sprintf(tmp, "%08X", (uint32_t)_id); + return std::string(tmp); + } + + inline std::string generatePathByTID(const uint64_t& tid) + { + return fs::getWorkDir() + data::getTitleSafeNameByTID(tid) + "/"; + } + + std::string getSizeString(const uint64_t& _size); + + inline void createTitleDirectoryByTID(const uint64_t& tid) + { + std::string makePath = fs::getWorkDir() + data::getTitleSafeNameByTID(tid); + mkdir(makePath.c_str(), 777); + } + + Result accountDeleteUser(AccountUid *uid); + + void sysBoost(); + void sysNormal(); + + inline bool isApplet() + { + AppletType type = appletGetAppletType(); + return type == AppletType_LibraryApplet; + } + + void checkForUpdate(void *a); +} \ No newline at end of file diff --git a/lib b/lib new file mode 160000 index 0000000..b56564b --- /dev/null +++ b/lib @@ -0,0 +1 @@ +Subproject commit b56564b70d038c59eef875f2c3cf436859c827f2 diff --git a/source/.DS_Store b/source/.DS_Store new file mode 100644 index 0000000..4cf2dca Binary files /dev/null and b/source/.DS_Store differ diff --git a/source/Main.cpp b/source/Main.cpp new file mode 100644 index 0000000..ac54e21 --- /dev/null +++ b/source/Main.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +static int nxlink_sock = -1; + +extern "C" void userAppInit() { + appletInitialize(); + hidInitialize(); + nsInitialize(); + setsysInitialize(); + setInitialize(); + accountInitialize(AccountServiceType_Administrator); + pmshellInitialize(); + socketInitializeDefault(); + pdmqryInitialize(); + nxlink_sock = nxlinkStdio(); + printf("userAppInit\n"); +} + +extern "C" void userAppExit() { + if (nxlink_sock != -1) { + close(nxlink_sock); + } + appletExit(); + hidExit(); + nsExit(); + setsysExit(); + setExit(); + accountExit(); + pmshellExit(); + socketExit(); + pdmqryExit(); +} + +// Main entrypoint +int main() { + printf("main\n"); + // First create our renderer, where one can customize SDL or other stuff's + // initialization. + auto renderer_opts = pu::ui::render::RendererInitOptions( + SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags); + renderer_opts.UseImage(pu::ui::render::IMGAllFlags); + renderer_opts.UseAudio(pu::ui::render::MixerAllFlags); + renderer_opts.UseTTF(); + + auto renderer = pu::ui::render::Renderer::New(renderer_opts); + + data::init(); + fs::init(); + // Create our main application from the renderer + auto main = ui::MainApplication::New(renderer); + + main->Prepare(); + + main->Show(); + + return 0; +} diff --git a/source/MainApplication.cpp b/source/MainApplication.cpp new file mode 100644 index 0000000..c2fa9de --- /dev/null +++ b/source/MainApplication.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include + +namespace ui { + MainApplication *mainApp; + + void MainApplication::OnLoad() { + mainApp = this; + this->usersLayout = UsersLayout::New(); + this->titlesLayout = TitlesLayout::New(); + this->usersLayout->SetOnInput( + std::bind(&UsersLayout::onInput, this->usersLayout, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); + this->titlesLayout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titlesLayout,std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); + this->LoadLayout(this->usersLayout); + } +} \ No newline at end of file diff --git a/source/TitlesLayout.cpp b/source/TitlesLayout.cpp new file mode 100644 index 0000000..52822c2 --- /dev/null +++ b/source/TitlesLayout.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +static std::vector accSids, devSids, bcatSids, cacheSids; + +//Sort save create tids alphabetically by title from data +static struct +{ + bool operator()(const uint64_t& tid1, const uint64_t& tid2) + { + std::string tid1Title = data::getTitleNameByTID(tid1); + std::string tid2Title = data::getTitleNameByTID(tid2); + + uint32_t pointA = 0, pointB = 0; + for(unsigned i = 0, j = 0; i < tid1Title.length(); ) + { + ssize_t tid1Cnt = decode_utf8(&pointA, (const uint8_t *)&tid1Title.c_str()[i]); + ssize_t tid2Cnt = decode_utf8(&pointB, (const uint8_t *)&tid2Title.c_str()[j]); + + pointA = tolower(pointA), pointB = tolower(pointB); + if(pointA != pointB) + return pointA < pointB; + + i += tid1Cnt, j += tid2Cnt; + } + return false; + } +} sortCreateTIDs; + +namespace ui { + extern MainApplication *mainApp; + void TitlesLayout::InitTitles() { + this->titlesMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7); + const data::user *u = data::getCurrentUser(); + for(const data::userTitleInfo& t : u->titleInfo) { + auto titleItem = pu::ui::elm::MenuItem::New(data::getTitleNameByTID(t.tid).c_str()); + titleItem->SetColor(COLOR("#FFFFFFFF")); + titlesMenu->AddItem(titleItem); + } + + this->Add(this->titlesMenu); + + this->SetBackgroundColor(BACKGROUND_COLOR); + } + + void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) { + if (Down & HidNpadButton_Plus) { + mainApp->Close(); + return; + } + + if (Down & HidNpadButton_A) { + printf("current game index is %i\n", this->titlesMenu->GetSelectedIndex()); + data::setTitleIndex(this->titlesMenu->GetSelectedIndex()); + int opt = mainApp->CreateShowDialog("", "What do you want?", { "Transfer", "Receive" }, true); + printf("opt is %d\n", opt); + switch (opt) { + case 0: { + // Transfert selected + break; + } + case 1: { + // Receive selected + break; + } + } + } + } +} diff --git a/source/UsersLayout.cpp b/source/UsersLayout.cpp new file mode 100644 index 0000000..7c5629c --- /dev/null +++ b/source/UsersLayout.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + +namespace ui { + extern MainApplication *mainApp; + + UsersLayout::UsersLayout() : Layout::Layout() { + this->usersMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, + 7); + this->usersMenu->SetScrollbarColor(COLOR("#170909FF")); + + for (data::user &u: data::users) { + auto username = pu::ui::elm::MenuItem::New(u.getUsername() + ": " + std::to_string(u.titleInfo.size())); + username->SetColor(COLOR("#FFFFFFFF")); + this->usersMenu->AddItem(username); + } + this->SetBackgroundColor(BACKGROUND_COLOR); + this->Add(this->usersMenu); + } + + int32_t UsersLayout::GetCurrentIndex() { + return this->usersMenu->GetSelectedIndex(); + } + + void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) { + if (Down & HidNpadButton_Plus) { + mainApp->Close(); + return; + } + + if (Down & HidNpadButton_A) { + printf("current index is %i\n", this->usersMenu->GetSelectedIndex()); + data::setUserIndex(this->usersMenu->GetSelectedIndex()); + mainApp->titlesLayout->InitTitles(); + mainApp->LoadLayout(mainApp->titlesLayout); + } + } +} diff --git a/source/data.cpp b/source/data.cpp new file mode 100644 index 0000000..9048f8d --- /dev/null +++ b/source/data.cpp @@ -0,0 +1,352 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "data.h" +#include "fs/file.h" +#include "util.h" +#include "type.h" + +//FsSaveDataSpaceId_All doesn't work for SD +static const unsigned saveOrder[] = {0, 1, 2, 3, 4, 100, 101}; + +int selUser = 0, selData = 0; + +//User vector +std::vector data::users; + +//For other save types +static bool sysBCATPushed = false, tempPushed = false; +std::unordered_map data::titles; + +//Sorts titles by sortType +static struct { + bool operator()(const data::userTitleInfo &a, const data::userTitleInfo &b) { + std::string titleA = data::getTitleNameByTID(a.tid); + std::string titleB = data::getTitleNameByTID(b.tid); + uint32_t pointA, pointB; + for (unsigned i = 0, j = 0; i < titleA.length();) { + ssize_t aCnt = decode_utf8(&pointA, (const uint8_t *) &titleA.data()[i]); + ssize_t bCnt = decode_utf8(&pointB, (const uint8_t *) &titleB.data()[j]); + pointA = tolower(pointA), pointB = tolower(pointB); + if (pointA != pointB) + return pointA < pointB; + + i += aCnt; + j += bCnt; + } + + return false; + } +} sortTitles; + +//Returns -1 for new +static int getUserIndex(const AccountUid &id) { + u128 nId = util::accountUIDToU128(id); + for (unsigned i = 0; i < data::users.size(); i++) + if (data::users[i].getUID128() == nId) return i; + + return -1; +} + +static inline bool accountSystemSaveCheck(const FsSaveDataInfo &_inf) { + if (_inf.save_data_type == FsSaveDataType_System && util::accountUIDToU128(_inf.uid) != 0) + return false; + + return true; +} + +//Minimal init/test to avoid loading and creating things I don't need +static bool testMount(const FsSaveDataInfo &_inf) { + bool ret = false; + + if ((ret = fs::mountSave(_inf))) + fs::unmountSave(); + + return ret; +} + +static inline void addTitleToList(const uint64_t &tid) { + uint64_t outSize = 0; + NsApplicationControlData *ctrlData = new NsApplicationControlData; + NacpLanguageEntry *ent; + Result ctrlRes = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, ctrlData, + sizeof(NsApplicationControlData), &outSize); + Result nacpRes = nacpGetLanguageEntry(&ctrlData->nacp, &ent); + size_t iconSize = outSize - sizeof(ctrlData->nacp); + + if (R_SUCCEEDED(ctrlRes) && !(outSize < sizeof(ctrlData->nacp)) && R_SUCCEEDED(nacpRes) && iconSize > 0) { + //Copy nacp + memcpy(&data::titles[tid].nacp, &ctrlData->nacp, sizeof(NacpStruct)); + + //Setup 'shortcuts' to strings + NacpLanguageEntry *ent; + nacpGetLanguageEntry(&data::titles[tid].nacp, &ent); + if (strlen(ent->name) == 0) + data::titles[tid].title = ctrlData->nacp.lang[SetLanguage_ENUS].name; + else + data::titles[tid].title = ent->name; + + data::titles[tid].author = ent->author; + + if ((data::titles[tid].safeTitle = util::safeString(ent->name)) == "") + data::titles[tid].safeTitle = util::getIDStr(tid); + } else { + memset(&data::titles[tid].nacp, 0, sizeof(NacpStruct)); + data::titles[tid].title = util::getIDStr(tid); + data::titles[tid].author = "Someone?"; + data::titles[tid].safeTitle = util::getIDStr(tid); + } + delete ctrlData; +} + +static inline bool titleIsLoaded(const uint64_t &tid) { + auto findTid = data::titles.find(tid); + + return findTid == data::titles.end() ? false : true; +} + +static void loadUserAccounts() { + s32 total = 0; + AccountUid *uids = new AccountUid[8]; + if (R_SUCCEEDED(accountListAllUsers(uids, 8, &total))) { + for (int i = 0; i < total; i++) + data::users.emplace_back(uids[i], "", ""); + } + delete[] uids; +} + +//This can load titles installed without having save data +static void loadTitlesFromRecords() { + NsApplicationRecord nsRecord; + int32_t entryCount = 0, recordOffset = 0; + while (R_SUCCEEDED(nsListApplicationRecord(&nsRecord, 1, recordOffset++, &entryCount)) && entryCount > 0) { + if (!titleIsLoaded(nsRecord.application_id)) + addTitleToList(nsRecord.application_id); + } +} + +bool data::loadUsersTitles(bool clearUsers) { + static unsigned systemUserCount = 4; + FsSaveDataInfoReader it; + FsSaveDataInfo info; + s64 total = 0; + + //Clear titles + for (data::user &u: data::users) + u.titleInfo.clear(); + if (clearUsers) { + systemUserCount = 4; + data::users.clear(); + + loadUserAccounts(); + sysBCATPushed = false; + tempPushed = false; + + users.emplace_back(util::u128ToAccountUID(3), "Device", "Device"); + users.emplace_back(util::u128ToAccountUID(2), "BCAT", "BCAT"); + users.emplace_back(util::u128ToAccountUID(5), "Cache", "Cache"); + users.emplace_back(util::u128ToAccountUID(0), "System", "System"); + } + + for (unsigned i = 0; i < 7; i++) { + if (R_FAILED(fsOpenSaveDataInfoReader(&it, (FsSaveDataSpaceId) saveOrder[i]))) + continue; + + while (R_SUCCEEDED(fsSaveDataInfoReaderRead(&it, &info, 1, &total)) && total != 0) { + uint64_t tid = 0; + if (info.save_data_type == FsSaveDataType_System || info.save_data_type == FsSaveDataType_SystemBcat) + tid = info.system_save_data_id; + else + tid = info.application_id; + + if (!titleIsLoaded(tid)) + addTitleToList(tid); + + //Don't bother with this stuff + if (!accountSystemSaveCheck(info) || !testMount(info)) + continue; + + switch (info.save_data_type) { + case FsSaveDataType_Bcat: + info.uid = util::u128ToAccountUID(2); + break; + + case FsSaveDataType_Device: + info.uid = util::u128ToAccountUID(3); + break; + + case FsSaveDataType_SystemBcat: + info.uid = util::u128ToAccountUID(4); + if (!sysBCATPushed) { + ++systemUserCount; + sysBCATPushed = true; + users.emplace_back(util::u128ToAccountUID(4), "System BCAT", + "System BCAT"); + } + break; + + case FsSaveDataType_Cache: + info.uid = util::u128ToAccountUID(5); + break; + + case FsSaveDataType_Temporary: + info.uid = util::u128ToAccountUID(6); + if (!tempPushed) { + ++systemUserCount; + tempPushed = true; + users.emplace_back(util::u128ToAccountUID(6), "Temporary", + "Temporary"); + } + break; + } + + int u = getUserIndex(info.uid); + if (u == -1) { + users.emplace(data::users.end() - systemUserCount, info.uid, "", ""); + u = getUserIndex(info.uid); + } + + PdmPlayStatistics playStats; + if (info.save_data_type == FsSaveDataType_Account || info.save_data_type == FsSaveDataType_Device) + pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(info.application_id, info.uid, false, + &playStats); + else + memset(&playStats, 0, sizeof(PdmPlayStatistics)); + users[u].addUserTitleInfo(tid, &info, &playStats); + } + fsSaveDataInfoReaderClose(&it); + } + + //Get reference to device save user + unsigned devPos = getUserIndex(util::u128ToAccountUID(3)); + data::user &dev = data::users[devPos]; + for (unsigned i = 0; i < devPos; i++) { + //Not needed but makes this easier to read + data::user &u = data::users[i]; + u.titleInfo.insert(u.titleInfo.end(), dev.titleInfo.begin(), dev.titleInfo.end()); + } + + data::sortUserTitles(); + + return true; +} + +void data::sortUserTitles() { + + for (data::user &u: data::users) + std::sort(u.titleInfo.begin(), u.titleInfo.end(), sortTitles); +} + +void data::init() { + data::loadUsersTitles(true); +} + +void data::exit() { + /*Still needed for planned future revisions*/ +} + +void data::setUserIndex(unsigned _sUser) { + selUser = _sUser; +} + +data::user *data::getCurrentUser() { + return &users[selUser]; +} + +unsigned data::getCurrentUserIndex() { + return selUser; +} + +void data::setTitleIndex(unsigned _sTitle) { + selData = _sTitle; +} + +data::userTitleInfo *data::getCurrentUserTitleInfo() { + return &users[selUser].titleInfo[selData]; +} + +unsigned data::getCurrentUserTitleInfoIndex() { + return selData; +} + +data::titleInfo *data::getTitleInfoByTID(const uint64_t &tid) { + if (titles.find(tid) != titles.end()) + return &titles[tid]; + return NULL; +} + +std::string data::getTitleNameByTID(const uint64_t &tid) { + return titles[tid].title; +} + +std::string data::getTitleSafeNameByTID(const uint64_t &tid) { + return titles[tid].safeTitle; +} + +int data::getTitleIndexInUser(const data::user &u, const uint64_t &tid) { + for (unsigned i = 0; i < u.titleInfo.size(); i++) { + if (u.titleInfo[i].tid == tid) + return i; + } + return -1; +} + +data::user::user(const AccountUid &_id, const std::string &_backupName, const std::string &_safeBackupName) { + userID = _id; + uID128 = util::accountUIDToU128(_id); + + AccountProfile prof; + AccountProfileBase base; + + if (R_SUCCEEDED(accountGetProfile(&prof, userID)) && R_SUCCEEDED(accountProfileGet(&prof, NULL, &base))) { + username = base.nickname; + userSafe = util::safeString(username); + if (userSafe.empty()) { + char tmp[32]; + sprintf(tmp, "Acc%08X", (uint32_t) uID128); + userSafe = tmp; + } + + uint32_t jpgSize = 0; + accountProfileGetImageSize(&prof, &jpgSize); + uint8_t *jpegData = new uint8_t[jpgSize]; + accountProfileLoadImage(&prof, jpegData, jpgSize, &jpgSize); + delete[] jpegData; + + accountProfileClose(&prof); + } else { + username = _backupName.empty() ? util::getIDStr((uint64_t) uID128) : _backupName; + userSafe = _safeBackupName.empty() ? util::getIDStr((uint64_t) uID128) : _safeBackupName; + } + titles.reserve(64); +} + +void data::user::setUID(const AccountUid &_id) { + userID = _id; + uID128 = util::accountUIDToU128(_id); +} + +void data::user::addUserTitleInfo(const uint64_t &tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats) { + data::userTitleInfo newInfo; + newInfo.tid = tid; + memcpy(&newInfo.saveInfo, _saveInfo, sizeof(FsSaveDataInfo)); + memcpy(&newInfo.playStats, _stats, sizeof(PdmPlayStatistics)); + titleInfo.push_back(newInfo); +} + +void data::dispStats() { + data::user *cu = data::getCurrentUser(); + data::userTitleInfo *d = data::getCurrentUserTitleInfo(); + + //Easiest/laziest way to do this + std::string stats = std::to_string(users.size()) + "\n"; + for (data::user &u: data::users) { + stats += u.getUsername() + ": " + std::to_string(u.titleInfo.size()) + "\n"; + } +} diff --git a/source/fs.cpp b/source/fs.cpp new file mode 100644 index 0000000..13df28e --- /dev/null +++ b/source/fs.cpp @@ -0,0 +1,519 @@ +#include + +#include "fs.h" +#include "threads.h" +#include "util.h" + +static std::string wd = "sdmc:/NXST/"; + +static FSFILE *debLog; + +static FsFileSystem sv; + +static std::vector pathFilter; + +void fs::init() { + mkDirRec(wd); + fs::logOpen(); +} + +bool fs::mountSave(const FsSaveDataInfo &_m) { + Result svOpen; + FsSaveDataAttribute attr = {0}; + switch (_m.save_data_type) { + case FsSaveDataType_System: + case FsSaveDataType_SystemBcat: { + attr.uid = _m.uid; + attr.system_save_data_id = _m.system_save_data_id; + attr.save_data_type = _m.save_data_type; + svOpen = fsOpenSaveDataFileSystemBySystemSaveDataId(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); + } + break; + + case FsSaveDataType_Account: { + attr.uid = _m.uid; + attr.application_id = _m.application_id; + attr.save_data_type = _m.save_data_type; + attr.save_data_rank = _m.save_data_rank; + attr.save_data_index = _m.save_data_index; + svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); + } + break; + + case FsSaveDataType_Device: { + attr.application_id = _m.application_id; + attr.save_data_type = FsSaveDataType_Device; + svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); + } + break; + + case FsSaveDataType_Bcat: { + attr.application_id = _m.application_id; + attr.save_data_type = FsSaveDataType_Bcat; + svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); + } + break; + + case FsSaveDataType_Cache: { + attr.application_id = _m.application_id; + attr.save_data_type = FsSaveDataType_Cache; + attr.save_data_index = _m.save_data_index; + svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); + } + break; + + case FsSaveDataType_Temporary: { + attr.application_id = _m.application_id; + attr.save_data_type = _m.save_data_type; + svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); + } + break; + + default: + svOpen = 1; + break; + } + + return R_SUCCEEDED(svOpen) && fsdevMountDevice("sv", sv) != -1; +} + +bool fs::commitToDevice(const std::string &dev) { + bool ret = true; + Result res = fsdevCommitDevice(dev.c_str()); + if (R_FAILED(res)) { + fs::logWrite("Error committing file to device -> 0x%X\n", res); + ret = false; + } + return ret; +} + +std::string fs::getWorkDir() { return wd; } + +void fs::setWorkDir(const std::string &_w) { wd = _w; } + + +void fs::loadPathFilters(const uint64_t &tid) { + char path[256]; + // sprintf(path, "sdmc:/config/JKSV/0x%016lX_filter.txt", tid); + if (fs::fileExists(path)) { + fs::dataFile filter(path); + while (filter.readNextLine(false)) + pathFilter.push_back(filter.getLine()); + } +} + +bool fs::pathIsFiltered(const std::string &_path) { + if (pathFilter.empty()) + return false; + + for (std::string &_p: pathFilter) { + if (_path == _p) + return true; + } + + return false; +} + +void fs::freePathFilters() { + pathFilter.clear(); +} + +void fs::createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _uid, threadInfo *t) { + data::titleInfo *tinfo = data::getTitleInfoByTID(_tid); + if (t) + t->status->setStatus("Creating Save Data for #%s#...", tinfo->title.c_str()); + + uint16_t cacheIndex = 0; + std::string indexStr; + if (_type == FsSaveDataType_Cache && + !(indexStr = util::getStringInput(SwkbdType_NumPad, "0", "Enter cache index", 2, 0, NULL)).empty()) + cacheIndex = strtoul(indexStr.c_str(), NULL, 10); + else if (_type == FsSaveDataType_Cache && indexStr.empty()) { + if (t) + t->finished = true; + return; + } + + FsSaveDataAttribute attr; + memset(&attr, 0, sizeof(FsSaveDataAttribute)); + attr.application_id = _tid; + attr.uid = _uid; + attr.system_save_data_id = 0; + attr.save_data_type = _type; + attr.save_data_rank = 0; + attr.save_data_index = cacheIndex; + + FsSaveDataCreationInfo crt; + memset(&crt, 0, sizeof(FsSaveDataCreationInfo)); + int64_t saveSize = 0, journalSize = 0; + switch (_type) { + case FsSaveDataType_Account: + saveSize = tinfo->nacp.user_account_save_data_size; + journalSize = tinfo->nacp.user_account_save_data_journal_size; + break; + + case FsSaveDataType_Device: + saveSize = tinfo->nacp.device_save_data_size; + journalSize = tinfo->nacp.device_save_data_journal_size; + break; + + case FsSaveDataType_Bcat: + saveSize = tinfo->nacp.bcat_delivery_cache_storage_size; + journalSize = tinfo->nacp.bcat_delivery_cache_storage_size; + break; + + case FsSaveDataType_Cache: + saveSize = 32 * 1024 * 1024; + if (tinfo->nacp.cache_storage_journal_size > tinfo->nacp.cache_storage_data_and_journal_size_max) + journalSize = tinfo->nacp.cache_storage_journal_size; + else + journalSize = tinfo->nacp.cache_storage_data_and_journal_size_max; + break; + + default: + if (t) + t->finished = true; + return; + break; + } + crt.save_data_size = saveSize; + crt.journal_size = journalSize; + crt.available_size = 0x4000; + crt.owner_id = _type == FsSaveDataType_Bcat ? 0x010000000000000C : tinfo->nacp.save_data_owner_id; + crt.flags = 0; + crt.save_data_space_id = FsSaveDataSpaceId_User; + + FsSaveDataMetaInfo meta; + memset(&meta, 0, sizeof(FsSaveDataMetaInfo)); + if (_type != FsSaveDataType_Bcat) { + meta.size = 0x40060; + meta.type = FsSaveDataMetaType_Thumbnail; + } + + Result res = 0; + if (R_SUCCEEDED(res = fsCreateSaveDataFileSystem(&attr, &crt, &meta))) { + util::createTitleDirectoryByTID(_tid); + data::loadUsersTitles(false); + } else { + fs::logWrite("SaveCreate Failed -> %X\n", res); + } +} + +static void createSaveData_t(void *a) { + threadInfo *t = (threadInfo *) a; + fs::svCreateArgs *crt = (fs::svCreateArgs *) t->argPtr; + fs::createSaveData(crt->type, crt->tid, crt->account, t); + delete crt; + t->finished = true; +} + +void fs::createSaveDataThreaded(FsSaveDataType _type, uint64_t _tid, AccountUid _uid) { + fs::svCreateArgs *send = new fs::svCreateArgs; + send->type = _type; + send->tid = _tid; + send->account = _uid; + threads::newThread(createSaveData_t, send, NULL); +} + +bool fs::extendSaveData(const data::userTitleInfo *tinfo, uint64_t extSize, threadInfo *t) { + if (t) + t->status->setStatus("Extending Save Data for #%s#...", data::getTitleNameByTID(tinfo->tid).c_str()); + + uint64_t journal = fs::getJournalSizeMax(tinfo); + uint64_t saveID = tinfo->saveInfo.save_data_id; + FsSaveDataSpaceId space = (FsSaveDataSpaceId) tinfo->saveInfo.save_data_space_id; + Result res = 0; + if (R_FAILED((res = fsExtendSaveDataFileSystem(space, saveID, extSize, journal)))) { + int64_t totalSize = 0; + fs::mountSave(tinfo->saveInfo); + fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &totalSize); + fs::unmountSave(); + + fs::logWrite("Extend Failed: %uMB to %uMB -> %X\n", totalSize / 1024 / 1024, extSize / 1024 / 1024, res); +// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataExtendFailed", 0)); + return false; + } + return true; +} + +static void extendSaveData_t(void *a) { + threadInfo *t = (threadInfo *) a; + fs::svExtendArgs *e = (fs::svExtendArgs *) t->argPtr; + fs::extendSaveData(e->tinfo, e->extSize, t); + delete e; + t->finished = true; +} + +void fs::extendSaveDataThreaded(const data::userTitleInfo *tinfo, uint64_t extSize) { + fs::svExtendArgs *send = new fs::svExtendArgs; + send->tinfo = tinfo; + send->extSize = extSize; + threads::newThread(extendSaveData_t, send, NULL); +} + +uint64_t fs::getJournalSize(const data::userTitleInfo *tinfo) { + uint64_t ret = 0; + data::titleInfo *t = data::getTitleInfoByTID(tinfo->tid); + switch (tinfo->saveInfo.save_data_type) { + case FsSaveDataType_Account: + ret = t->nacp.user_account_save_data_journal_size; + break; + + case FsSaveDataType_Device: + ret = t->nacp.device_save_data_journal_size; + break; + + case FsSaveDataType_Bcat: + ret = t->nacp.bcat_delivery_cache_storage_size; + break; + + case FsSaveDataType_Cache: + if (t->nacp.cache_storage_journal_size > 0) + ret = t->nacp.cache_storage_journal_size; + else + ret = t->nacp.cache_storage_data_and_journal_size_max; + break; + + default: + ret = BUFF_SIZE; + break; + } + return ret; +} + +uint64_t fs::getJournalSizeMax(const data::userTitleInfo *tinfo) { + uint64_t ret = 0; + data::titleInfo *extend = data::getTitleInfoByTID(tinfo->tid); + switch (tinfo->saveInfo.save_data_type) { + case FsSaveDataType_Account: + if (extend->nacp.user_account_save_data_journal_size_max > extend->nacp.user_account_save_data_journal_size) + ret = extend->nacp.user_account_save_data_journal_size_max; + else + ret = extend->nacp.user_account_save_data_journal_size; + break; + + case FsSaveDataType_Bcat: + ret = extend->nacp.bcat_delivery_cache_storage_size; + break; + + case FsSaveDataType_Cache: + if (extend->nacp.cache_storage_data_and_journal_size_max > extend->nacp.cache_storage_journal_size) + ret = extend->nacp.cache_storage_data_and_journal_size_max; + else + ret = extend->nacp.cache_storage_journal_size; + break; + + case FsSaveDataType_Device: + if (extend->nacp.device_save_data_journal_size_max > extend->nacp.device_save_data_journal_size) + ret = extend->nacp.device_save_data_journal_size_max; + else + ret = extend->nacp.device_save_data_journal_size; + break; + + default: + //will just fail + ret = 0; + break; + } + return ret; +} + +static void wipeSave_t(void *a) { + threadInfo *t = (threadInfo *) a; + t->status->setStatus("Resetting save data..."); + fs::delDir("sv:/"); + fs::commitToDevice("sv"); + t->finished = true; +} + +void fs::wipeSave() { + threads::newThread(wipeSave_t, NULL, NULL); +} + +void fs::createNewBackup(void *a) { + if (!fs::dirNotEmpty("sv:/")) { +// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popSaveIsEmpty", 0)); + return; + } + + uint64_t held = threads::padKeysHeld(); + + data::user *u = data::getCurrentUser(); + data::userTitleInfo *d = data::getCurrentUserTitleInfo(); + data::titleInfo *t = data::getTitleInfoByTID(d->tid); + + std::string out; + + const std::string dict[] = + { + util::getDateTime(util::DATE_FMT_YMD), + util::getDateTime(util::DATE_FMT_YDM), + util::getDateTime(util::DATE_FMT_HOYSTE), + util::getDateTime(util::DATE_FMT_JHK), + util::getDateTime(util::DATE_FMT_ASC), + u->getUsernameSafe(), + t->safeTitle, + util::generateAbbrev(d->tid), + ".zip" + }; + std::string defaultText = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD); + out = util::getStringInput(SwkbdType_QWERTY, defaultText, "Enter a new name", 64, 9, dict); + out = util::safeString(out); + + if (!out.empty()) { + std::string ext = util::getExtensionFromString(out); + std::string path = util::generatePathByTID(d->tid) + out; + + fs::mkDir(path); + path += "/"; + fs::copyDirToDirThreaded("sv:/", path); +// ui::fldRefreshMenu(); + } +} + +void fs::overwriteBackup(void *a) { + threadInfo *t = (threadInfo *) a; + std::string *dst = (std::string *) t->argPtr; + bool saveHasFiles = fs::dirNotEmpty("sv:/"); + if (fs::isDir(*dst) && saveHasFiles) { + fs::delDir(*dst); + fs::mkDir(*dst); + dst->append("/"); + fs::copyDirToDirThreaded("sv:/", *dst); + } + delete dst; + t->finished = true; +} + +void fs::restoreBackup(void *a) { + threadInfo *t = (threadInfo *) a; + std::string *restore = (std::string *) t->argPtr; + data::user *u = data::getCurrentUser(); + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + if ((utinfo->saveInfo.save_data_type != FsSaveDataType_System)) { + bool saveHasFiles = fs::dirNotEmpty("sv:/"); + if (saveHasFiles) { + std::string autoFolder = util::generatePathByTID(utinfo->tid) + "/AUTO - " + u->getUsernameSafe() + " - " + + util::getDateTime(util::DATE_FMT_YMD) + "/"; + fs::mkDir(autoFolder.substr(0, autoFolder.length() - 1)); + fs::copyDirToDirThreaded("sv:/", autoFolder); + } + + if (fs::isDir(*restore)) { + restore->append("/"); + if (fs::dirNotEmpty(*restore)) { + t->status->setStatus("Calculating save data size..."); + unsigned dirCount = 0, fileCount = 0; + uint64_t saveSize = 0; + int64_t availSize = 0; + fs::getDirProps(*restore, dirCount, fileCount, saveSize); + fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &availSize); + if ((int) saveSize > availSize) { + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + fs::unmountSave(); + fs::extendSaveData(utinfo, saveSize + 0x500000, t); + fs::mountSave(utinfo->saveInfo); + } + + fs::wipeSave(); + fs::copyDirToDirCommitThreaded(*restore, "sv:/", "sv"); + } +// else +// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popFolderIsEmpty", 0)); + } else { + std::string dstPath = "sv:/" + util::getFilenameFromPath(*restore); + fs::copyFileCommitThreaded(*restore, dstPath, "sv"); + } + } + + delete restore; + t->finished = true; +} + +void fs::deleteBackup(void *a) { + threadInfo *t = (threadInfo *) a; + std::string *deletePath = (std::string *) t->argPtr; + std::string backupName = util::getFilenameFromPath(*deletePath); + + t->status->setStatus("Deleting..."); + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + + if (fs::isDir(*deletePath)) { + *deletePath += "/"; + fs::delDir(*deletePath); +// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str()); + } else { + fs::delfile(*deletePath); +// ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str()); + } +// ui::fldRefreshMenu(); + delete deletePath; + t->finished = true; +} + +void fs::dumpAllUserSaves(void *a) { + threadInfo *t = (threadInfo *) a; + fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0); + t->argPtr = c; + data::user *u = data::getCurrentUser(); + + for (unsigned i = 0; i < u->titleInfo.size(); i++) { + bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo); + util::createTitleDirectoryByTID(u->titleInfo[i].tid); + if (saveMounted && fs::dirNotEmpty("sv:/")) { + fs::loadPathFilters(u->titleInfo[i].tid); + std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " + + util::getDateTime(util::DATE_FMT_YMD) + "/"; + fs::mkDir(dst.substr(0, dst.length() - 1)); + fs::copyDirToDir("sv:/", dst, t); + fs::freePathFilters(); + } + fs::unmountSave(); + } + fs::copyArgsDestroy(c); + t->finished = true; +} + +void fs::dumpAllUsersAllSaves(void *a) { + threadInfo *t = (threadInfo *) a; + fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0); + t->argPtr = c; + unsigned curUser = 0; + while (data::users[curUser].getUID128() != 2) { + data::user *u = &data::users[curUser++]; + for (unsigned i = 0; i < u->titleInfo.size(); i++) { + bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo); + util::createTitleDirectoryByTID(u->titleInfo[i].tid); + if (saveMounted && fs::dirNotEmpty("sv:/")) { + fs::loadPathFilters(u->titleInfo[i].tid); + std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " + + util::getDateTime(util::DATE_FMT_YMD) + "/"; + fs::mkDir(dst.substr(0, dst.length() - 1)); + fs::copyDirToDir("sv:/", dst, t); + fs::freePathFilters(); + } + fs::unmountSave(); + } + } + fs::copyArgsDestroy(c); + t->finished = true; +} + +void fs::logOpen() { + std::string logPath = wd + "log.txt"; + debLog = fsfopen(logPath.c_str(), FsOpenMode_Write); + fsfclose(debLog); +} + +void fs::logWrite(const char *fmt, ...) { + std::string logPath = wd + "log.txt"; + debLog = fsfopen(logPath.c_str(), FsOpenMode_Append | FsOpenMode_Write); + char tmp[256]; + va_list args; + va_start(args, fmt); + vsprintf(tmp, fmt, args); + va_end(args); + fsfwrite(tmp, 1, strlen(tmp), debLog); + fsfclose(debLog); +} + diff --git a/source/fs/dir.cpp b/source/fs/dir.cpp new file mode 100644 index 0000000..20b3164 --- /dev/null +++ b/source/fs/dir.cpp @@ -0,0 +1,266 @@ +#include +#include + +#include "fs.h" +#include "util.h" +#include + +static struct +{ + bool operator()(const fs::dirItem& a, const fs::dirItem& b) + { + if(a.isDir() != b.isDir()) + return a.isDir(); + + for(unsigned i = 0; i < a.getItm().length(); i++) + { + char charA = tolower(a.getItm()[i]); + char charB = tolower(b.getItm()[i]); + if(charA != charB) + return charA < charB; + } + return false; + } +} sortDirList; + +void fs::mkDir(const std::string& _p) +{ + mkdir(_p.c_str(), 777); +} + +void fs::mkDirRec(const std::string& _p) +{ + //skip first slash + size_t pos = _p.find('/', 0) + 1; + while((pos = _p.find('/', pos)) != _p.npos) + { + fs::mkDir(_p.substr(0, pos).c_str()); + ++pos; + } +} + +void fs::delDir(const std::string& path) +{ + dirList list(path); + for(unsigned i = 0; i < list.getCount(); i++) + { + if(pathIsFiltered(path + list.getItem(i))) + continue; + + if(list.isDir(i)) + { + std::string newPath = path + list.getItem(i) + "/"; + delDir(newPath); + + std::string delPath = path + list.getItem(i); + // rmdir(delPath.c_str()); + } + else + { + std::string delPath = path + list.getItem(i); + std::remove(delPath.c_str()); + } + } + // rmdir(path.c_str()); +} + +bool fs::dirNotEmpty(const std::string& _dir) +{ + fs::dirList tmp(_dir); + return tmp.getCount() > 0; +} + +bool fs::isDir(const std::string& _path) +{ + struct stat s; + return stat(_path.c_str(), &s) == 0 && S_ISDIR(s.st_mode); +} + +void fs::copyDirToDir(const std::string& src, const std::string& dst, threadInfo *t) +{ + if(t) + t->status->setStatus("Opening '#%s#'..."); + + fs::dirList *list = new fs::dirList(src); + for(unsigned i = 0; i < list->getCount(); i++) + { + if(pathIsFiltered(src + list->getItem(i))) + continue; + + if(list->isDir(i)) + { + std::string newSrc = src + list->getItem(i) + "/"; + std::string newDst = dst + list->getItem(i) + "/"; + fs::mkDir(newDst.substr(0, newDst.length() - 1)); + fs::copyDirToDir(newSrc, newDst, t); + } + else + { + std::string fullSrc = src + list->getItem(i); + std::string fullDst = dst + list->getItem(i); + + if(t) + t->status->setStatus("Copying '#%s#'..."); + + fs::copyFile(fullSrc, fullDst, t); + } + } + delete list; +} + +static void copyDirToDir_t(void *a) +{ + threadInfo *t = (threadInfo *)a; + fs::copyArgs *in = (fs::copyArgs *)t->argPtr; + fs::copyDirToDir(in->src, in->dst, t); + if(in->cleanup) + fs::copyArgsDestroy(in); + t->finished = true; +} + +void fs::copyDirToDirThreaded(const std::string& src, const std::string& dst) +{ + fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0); + threads::newThread(copyDirToDir_t, send, fs::fileDrawFunc); +} + +void fs::copyDirToDirCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t) +{ + if(t) + t->status->setStatus("Opening '#%s#'..."); + + fs::dirList *list = new fs::dirList(src); + for(unsigned i = 0; i < list->getCount(); i++) + { + if(pathIsFiltered(src + list->getItem(i))) + continue; + + if(list->isDir(i)) + { + std::string newSrc = src + list->getItem(i) + "/"; + std::string newDst = dst + list->getItem(i) + "/"; + fs::mkDir(newDst.substr(0, newDst.length() - 1)); + fs::copyDirToDirCommit(newSrc, newDst, dev, t); + } + else + { + std::string fullSrc = src + list->getItem(i); + std::string fullDst = dst + list->getItem(i); + + if(t) + t->status->setStatus("Copying '#%s#'..."); + + fs::copyFileCommit(fullSrc, fullDst, dev, t); + } + } + delete list; +} + +static void copyDirToDirCommit_t(void *a) +{ + threadInfo *t = (threadInfo *)a; + fs::copyArgs *in = (fs::copyArgs *)t->argPtr; + fs::copyDirToDirCommit(in->src, in->dst, in->dev, t); + if(in->cleanup) + fs::copyArgsDestroy(in); + t->finished = true; +} + +void fs::copyDirToDirCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev) +{ + fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0); + threads::newThread(copyDirToDirCommit_t, send, fs::fileDrawFunc); +} + +void fs::getDirProps(const std::string& path, unsigned& dirCount, unsigned& fileCount, uint64_t& totalSize) +{ + fs::dirList *d = new fs::dirList(path); + for(unsigned i = 0; i < d->getCount(); i++) + { + if(d->isDir(i)) + { + ++dirCount; + std::string newPath = path + d->getItem(i) + "/"; + fs::getDirProps(newPath, dirCount, fileCount, totalSize); + } + else + { + ++fileCount; + std::string filePath = path + d->getItem(i); + totalSize += fs::fsize(filePath); + } + } + delete d; +} + +fs::dirItem::dirItem(const std::string& pathTo, const std::string& sItem) +{ + itm = sItem; + + std::string fullPath = pathTo + sItem; + struct stat s; + if(stat(fullPath.c_str(), &s) == 0 && S_ISDIR(s.st_mode)) + dir = true; +} + +std::string fs::dirItem::getName() const +{ + size_t extPos = itm.find_last_of('.'), slPos = itm.find_last_of('/'); + if(extPos == itm.npos) + return ""; + + return itm.substr(slPos + 1, extPos); +} + +std::string fs::dirItem::getExt() const +{ + return util::getExtensionFromString(itm); +} + +fs::dirList::dirList(const std::string& _path) +{ + DIR *d; + struct dirent *ent; + path = _path; + d = opendir(path.c_str()); + + while((ent = readdir(d))) + item.emplace_back(path, ent->d_name); + + closedir(d); + + std::sort(item.begin(), item.end(), sortDirList); +} + +void fs::dirList::reassign(const std::string& _path) +{ + DIR *d; + struct dirent *ent; + path = _path; + + d = opendir(path.c_str()); + + item.clear(); + + while((ent = readdir(d))) + item.emplace_back(path, ent->d_name); + + closedir(d); + + std::sort(item.begin(), item.end(), sortDirList); +} + +void fs::dirList::rescan() +{ + item.clear(); + DIR *d; + struct dirent *ent; + d = opendir(path.c_str()); + + while((ent = readdir(d))) + item.emplace_back(path, ent->d_name); + + closedir(d); + + std::sort(item.begin(), item.end(), sortDirList); +} diff --git a/source/fs/file.cpp b/source/fs/file.cpp new file mode 100644 index 0000000..4911d7d --- /dev/null +++ b/source/fs/file.cpp @@ -0,0 +1,383 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fs.h" +#include "util.h" +#include "data.h" +#include + +static std::string wd = "sdmc:/JKSV/"; + +typedef struct +{ + std::mutex bufferLock; + std::condition_variable cond; + std::vector sharedBuffer; + std::string dst, dev; + bool bufferIsFull = false; + unsigned int filesize = 0, writeLimit = 0; +} fileCpyThreadArgs; + +static void writeFile_t(void *a) +{ + fileCpyThreadArgs *in = (fileCpyThreadArgs *)a; + size_t written = 0; + std::vector localBuffer; + std::FILE *out = std::fopen(in->dst.c_str(), "wb"); + + while(written < in->filesize) + { + std::unique_lock buffLock(in->bufferLock); + in->cond.wait(buffLock, [in]{ return in->bufferIsFull;}); + localBuffer.clear(); + localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end()); + in->sharedBuffer.clear(); + in->bufferIsFull = false; + buffLock.unlock(); + in->cond.notify_one(); + written += fwrite(localBuffer.data(), 1, localBuffer.size(), out); + } + fclose(out); +} + +static void writeFileCommit_t(void *a) +{ + fileCpyThreadArgs *in = (fileCpyThreadArgs *)a; + size_t written = 0, journalCount = 0; + std::vector localBuffer; + FILE *out = fopen(in->dst.c_str(), "wb"); + + while(written < in->filesize) + {` + std::unique_lock buffLock(in->bufferLock); + in->cond.wait(buffLock, [in]{ return in->bufferIsFull; }); + localBuffer.clear(); + localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end()); + in->sharedBuffer.clear(); + in->bufferIsFull = false; + buffLock.unlock(); + in->cond.notify_one(); + + written += fwrite(localBuffer.data(), 1, localBuffer.size(), out); + + journalCount += written; + if(journalCount >= in->writeLimit) + { + journalCount = 0; + fclose(out); + fs::commitToDevice(in->dev.c_str()); + out = fopen(in->dst.c_str(), "ab"); + } + } + fclose(out); +} + +fs::copyArgs *fs::copyArgsCreate(const std::string& src, const std::string& dst, const std::string& dev, zipFile z, unzFile unz, bool _cleanup, bool _trimZipPath, uint8_t _trimPlaces) +{ + copyArgs *ret = new copyArgs; + ret->src = src; + ret->dst = dst; + ret->dev = dev; + ret->z = z; + ret->unz = unz; + ret->cleanup = _cleanup; + ret->offset = 0; + ret->trimZipPath = _trimZipPath; + ret->trimZipPlaces = _trimPlaces; + return ret; +} + +void fs::copyArgsDestroy(copyArgs *c) +{ + // delete c->prog; + delete c; + c = NULL; +} + +fs::dataFile::dataFile(const std::string& _path) +{ + f = fopen(_path.c_str(), "r"); + if(f != NULL) + opened = true; +} + +fs::dataFile::~dataFile() +{ + fclose(f); +} + +bool fs::dataFile::readNextLine(bool proc) +{ + bool ret = false; + char tmp[1024]; + while(fgets(tmp, 1024, f)) + { + if(tmp[0] != '#' && tmp[0] != '\n' && tmp[0] != '\r') + { + line = tmp; + ret = true; + break; + } + } + util::stripChar('\n', line); + util::stripChar('\r', line); + if(proc) + procLine(); + + return ret; +} + +void fs::dataFile::procLine() +{ + size_t pPos = line.find_first_of("(=,"); + if(pPos != line.npos) + { + lPos = pPos; + name.assign(line.begin(), line.begin() + lPos); + } + else + name = line; + + util::stripChar(' ', name); + ++lPos; +} + +std::string fs::dataFile::getNextValueStr() +{ + std::string ret = ""; + //Skip all spaces until we hit actual text + size_t pos1 = line.find_first_not_of(", ", lPos); + //If reading from quotes + if(line[pos1] == '"') + lPos = line.find_first_of('"', ++pos1); + else + lPos = line.find_first_of(",;\n", pos1);//Set lPos to end of string we want. This should just set lPos to the end of the line if it fails, which is ok + + ret = line.substr(pos1, lPos++ - pos1); + + util::replaceStr(ret, "\\n", "\n"); + + return ret; +} + +int fs::dataFile::getNextValueInt() +{ + int ret = 0; + std::string no = getNextValueStr(); + if(no[0] == '0' && tolower(no[1]) == 'x') + ret = strtoul(no.c_str(), NULL, 16); + else + ret = strtoul(no.c_str(), NULL, 10); + + return ret; +} + +void fs::copyFile(const std::string& src, const std::string& dst, threadInfo *t) +{ + fs::copyArgs *c = NULL; + size_t filesize = fs::fsize(src); + if(t) + { + c = (fs::copyArgs *)t->argPtr; + c->offset = 0; + } + + FILE *fsrc = fopen(src.c_str(), "rb"); + if(!fsrc) + { + fclose(fsrc); + return; + } + + fileCpyThreadArgs thrdArgs; + thrdArgs.dst = dst; + thrdArgs.filesize = filesize; + + uint8_t *buff = new uint8_t[BUFF_SIZE]; + std::vector transferBuffer; + Thread writeThread; + threadCreate(&writeThread, writeFile_t, &thrdArgs, NULL, 0x40000, 0x2E, 2); + threadStart(&writeThread); + size_t readIn = 0; + uint64_t readCount = 0; + while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0) + { + transferBuffer.insert(transferBuffer.end(), buff, buff + readIn); + readCount += readIn; + + if(c) + c->offset = readCount; + + if(transferBuffer.size() >= TRANSFER_BUFFER_LIMIT || readCount == filesize) + { + std::unique_lock buffLock(thrdArgs.bufferLock); + thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; }); + thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end()); + transferBuffer.clear(); + thrdArgs.bufferIsFull = true; + buffLock.unlock(); + thrdArgs.cond.notify_one(); + } + } + threadWaitForExit(&writeThread); + threadClose(&writeThread); + fclose(fsrc); + delete[] buff; +} + + +static void copyFileThreaded_t(void *a) +{ + threadInfo *t = (threadInfo *)a; + fs::copyArgs *in = (fs::copyArgs *)t->argPtr; + + t->status->setStatus("Copy file", in->src.c_str()); + + fs::copyFile(in->src, in->dst, t); + if(in->cleanup) + fs::copyArgsDestroy(in); + t->finished = true; +} + +void fs::copyFileThreaded(const std::string& src, const std::string& dst) +{ + fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0); + threads::newThread(copyFileThreaded_t, send, fs::fileDrawFunc); +} + +void fs::copyFileCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t) +{ + fs::copyArgs *c = NULL; + size_t filesize = fs::fsize(src); + if(t) + { + c = (fs::copyArgs *)t->argPtr; + c->offset = 0; + // c->prog->setMax(filesize); + // c->prog->update(0); + } + + FILE *fsrc = fopen(src.c_str(), "rb"); + if(!fsrc) + { + fclose(fsrc); + return; + } + + fileCpyThreadArgs thrdArgs; + thrdArgs.dst = dst; + thrdArgs.dev = dev; + thrdArgs.filesize = filesize; + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + uint64_t journalSpace = fs::getJournalSize(utinfo); + thrdArgs.writeLimit = (journalSpace - 0x100000) < TRANSFER_BUFFER_LIMIT ? journalSpace - 0x100000 : TRANSFER_BUFFER_LIMIT; + + Thread writeThread; + threadCreate(&writeThread, writeFileCommit_t, &thrdArgs, NULL, 0x040000, 0x2E, 2); + + uint8_t *buff = new uint8_t[BUFF_SIZE]; + size_t readIn = 0; + uint64_t readCount = 0; + std::vector transferBuffer; + threadStart(&writeThread); + while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0) + { + transferBuffer.insert(transferBuffer.end(), buff, buff + readIn); + readCount += readIn; + if(c) + c->offset = readCount; + + if(transferBuffer.size() >= thrdArgs.writeLimit || readCount == filesize) + { + std::unique_lock buffLock(thrdArgs.bufferLock); + thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; }); + thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end()); + transferBuffer.clear(); + thrdArgs.bufferIsFull = true; + buffLock.unlock(); + thrdArgs.cond.notify_one(); + } + } + threadWaitForExit(&writeThread); + threadClose(&writeThread); + + fclose(fsrc); + fs::commitToDevice(dev); + delete[] buff; +} + +static void copyFileCommit_t(void *a) +{ + threadInfo *t = (threadInfo *)a; + fs::copyArgs *in = (fs::copyArgs *)t->argPtr; + + t->status->setStatus("Copy file", in->src.c_str()); + + fs::copyFileCommit(in->src, in->dst, in->dev, t); + if(in->cleanup) + fs::copyArgsDestroy(in); + + t->finished = true; +} + +void fs::copyFileCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev) +{ + fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0); + threads::newThread(copyFileCommit_t, send, fs::fileDrawFunc); +} + +void fs::fileDrawFunc(void *a) +{ + threadInfo *t = (threadInfo *)a; + if(!t->finished && t->argPtr) + { + copyArgs *c = (copyArgs *)t->argPtr; + std::string tmp; + t->status->getStatus(tmp); + c->argLock(); + // c->prog->update(c->offset); + // c->prog->draw(tmp); + c->argUnlock(); + } +} + +void fs::delfile(const std::string& path) +{ + remove(path.c_str()); +} + +void fs::getShowFileProps(const std::string& _path) +{ + size_t size = fs::fsize(_path); + // ui::showMessage(ui::getUICString("fileModeFileProperties", 0), _path.c_str(), util::getSizeString(size).c_str()); +} + +bool fs::fileExists(const std::string& path) +{ + bool ret = false; + FILE *test = fopen(path.c_str(), "rb"); + if(test != NULL) + ret = true; + fclose(test); + return ret; +} + +size_t fs::fsize(const std::string& _f) +{ + size_t ret = 0; + FILE *get = fopen(_f.c_str(), "rb"); + if(get != NULL) + { + fseek(get, 0, SEEK_END); + ret = ftell(get); + } + fclose(get); + return ret; +} diff --git a/source/fs/fsfile.c b/source/fs/fsfile.c new file mode 100644 index 0000000..d0b7d52 --- /dev/null +++ b/source/fs/fsfile.c @@ -0,0 +1,150 @@ +#include +#include +#include +#include + +#include "fs/fsfile.h" + +char *getDeviceFromPath(char *dev, size_t _max, const char *path) +{ + memset(dev, 0, _max); + char *c = strchr(path, ':'); + if(c - path > _max) + return NULL; + + //probably not good? idk + memcpy(dev, path, c - path); + + return dev; +} + +char *getFilePath(char *pathOut, size_t _max, const char *path) +{ + memset(pathOut, 0, _max); + char *c = strchr(path, '/'); + size_t pLength = strlen(c); + if(pLength > _max) + return NULL; + + memcpy(pathOut, c, pLength); + + return pathOut; +} + +bool fsMkDir(const char *_p) +{ + char devStr[16]; + char path[FS_MAX_PATH]; + if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p)) + return false; + + Result res = fsFsCreateDirectory(fsdevGetDeviceFileSystem(devStr), path); + return res == 0; +} + +int fsremove(const char *_p) +{ + char devStr[16]; + char path[FS_MAX_PATH]; + if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p)) + return -1; + + Result res = fsFsDeleteFile(fsdevGetDeviceFileSystem(devStr), path); + return res; +} + +Result fsDelDirRec(const char *_p) +{ + char devStr[16]; + char path[FS_MAX_PATH]; + if(!getDeviceFromPath(devStr, 16, _p) || ! getFilePath(path, FS_MAX_PATH, _p)) + return 1; + + return fsFsDeleteDirectoryRecursively(fsdevGetDeviceFileSystem(devStr), path); +} + +bool fsfcreate(const char *_p, int64_t crSize) +{ + char devStr[16]; + char filePath[FS_MAX_PATH]; + if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p)) + return false; + + FsFileSystem *s = fsdevGetDeviceFileSystem(devStr); + if(s == NULL) + return false; + + Result res = fsFsCreateFile(s, filePath, crSize, 0); + if(R_SUCCEEDED(res)) + res = fsdevCommitDevice(devStr); + + return R_SUCCEEDED(res) ? true : false; +} + +FSFILE *fsfopen(const char *_p, uint32_t mode) +{ + char devStr[16]; + char filePath[FS_MAX_PATH]; + if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p)) + return NULL; + + FsFileSystem *s = fsdevGetDeviceFileSystem(devStr); + if(s == NULL) + return NULL; + + if(mode == FsOpenMode_Write) + { + fsFsDeleteFile(s, filePath); + fsFsCreateFile(s, filePath, 0, 0); + } + + FSFILE *ret = malloc(sizeof(FSFILE)); + ret->error = fsFsOpenFile(s, filePath, mode, &ret->_f); + if(R_FAILED(ret->error)) + { + free(ret); + return NULL; + } + fsFileGetSize(&ret->_f, &ret->fsize); + ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0; + + return ret; +} + +FSFILE *fsfopenWithSystem(FsFileSystem *_s, const char *_p, uint32_t mode) +{ + if(mode & FsOpenMode_Write) + { + fsFsDeleteFile(_s, _p); + fsFsCreateFile(_s, _p, 0, 0); + } + else if(mode & FsOpenMode_Append) + fsFsCreateFile(_s, _p, 0, 0); + + FSFILE *ret = malloc(sizeof(FSFILE)); + ret->error = fsFsOpenFile(_s, _p, mode, &ret->_f); + if(R_FAILED(ret->error)) + { + free(ret); + return NULL; + } + fsFileGetSize(&ret->_f, &ret->fsize); + ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0; + + return ret; +} + +size_t fsfwrite(const void *buf, size_t sz, size_t count, FSFILE *_f) +{ + size_t fullSize = sz * count; + if(_f->offset + fullSize > _f->fsize) + { + s64 newSize = (_f->fsize + fullSize) - (_f->fsize - _f->offset); + fsFileSetSize(&_f->_f, newSize); + _f->fsize = newSize; + } + _f->error = fsFileWrite(&_f->_f, _f->offset, buf, fullSize, FsWriteOption_Flush); + _f->offset += fullSize; + + return fullSize; +} diff --git a/source/fs/zip.cpp b/source/fs/zip.cpp new file mode 100644 index 0000000..1795b2b --- /dev/null +++ b/source/fs/zip.cpp @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include + +#include "fs.h" +#include "util.h" +#include "threads.h" + +typedef struct +{ + std::mutex buffLock; + std::condition_variable cond; + std::vector sharedBuffer; + std::string dst, dev; + bool bufferIsFull = false; + unzFile unz; + unsigned int fileSize, writeLimit = 0; +} unzThrdArgs; + +static void writeFileFromZip_t(void *a) +{ + unzThrdArgs *in = (unzThrdArgs *)a; + std::vector localBuffer; + unsigned int written = 0, journalCount = 0; + + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + uint64_t journalSpace = fs::getJournalSize(utinfo); + + FILE *out = fopen(in->dst.c_str(), "wb"); + while(written < in->fileSize) + { + std::unique_lock buffLock(in->buffLock); + in->cond.wait(buffLock, [in]{ return in->bufferIsFull; }); + localBuffer.clear(); + localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end()); + in->sharedBuffer.clear(); + in->bufferIsFull = false; + buffLock.unlock(); + in->cond.notify_one(); + + written += fwrite(localBuffer.data(), 1, localBuffer.size(), out); + journalCount += written; + if(journalCount >= in->writeLimit) + { + journalCount = 0; + fclose(out); + fs::commitToDevice(in->dev); + out = fopen(in->dst.c_str(), "ab"); + } + } + fclose(out); +} + +void fs::copyDirToZip(const std::string& src, zipFile dst, bool trimPath, int trimPlaces, threadInfo *t) +{ + fs::copyArgs *c = NULL; + if(t) + { + t->status->setStatus("threadStatusOpeningFolder"); + c = (fs::copyArgs *)t->argPtr; + } + + fs::dirList *list = new fs::dirList(src); + for(unsigned i = 0; i < list->getCount(); i++) + { + std::string itm = list->getItem(i); + if(fs::pathIsFiltered(src + itm)) + continue; + + if(list->isDir(i)) + { + std::string newSrc = src + itm + "/"; + fs::copyDirToZip(newSrc, dst, trimPath, trimPlaces, t); + } + else + { + time_t raw; + time(&raw); + tm *locTime = localtime(&raw); + zip_fileinfo inf = { (unsigned)locTime->tm_sec, (unsigned)locTime->tm_min, (unsigned)locTime->tm_hour, + (unsigned)locTime->tm_mday, (unsigned)locTime->tm_mon, (unsigned)(1900 + locTime->tm_year), 0, 0, 0 }; + + std::string filename = src + itm; + size_t zipNameStart = 0; + if(trimPath) + util::trimPath(filename, trimPlaces); + else + zipNameStart = filename.find_first_of('/') + 1; + + if(t) + t->status->setStatus("threadStatusAddingFileToZip"); + + int zipOpenFile = zipOpenNewFileInZip64(dst, filename.substr(zipNameStart, filename.npos).c_str(), &inf, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0); + if(zipOpenFile == ZIP_OK) + { + std::string fullSrc = src + itm; + if(c) + { + c->offset = 0; + // c->prog->setMax(fs::fsize(fullSrc)); + // c->prog->update(0); + } + + FILE *fsrc = fopen(fullSrc.c_str(), "rb"); + size_t readIn = 0; + uint8_t *buff = new uint8_t[ZIP_BUFF_SIZE]; + while((readIn = fread(buff, 1, ZIP_BUFF_SIZE, fsrc)) > 0) + { + zipWriteInFileInZip(dst, buff, readIn); + if(c) + c->offset += readIn; + } + delete[] buff; + fclose(fsrc); + } + } + } +} + +void copyDirToZip_t(void *a) +{ + threadInfo *t = (threadInfo *)a; + fs::copyArgs *c = (fs::copyArgs *)t->argPtr; + + fs::copyDirToZip(c->src, c->z, c->trimZipPath, c->trimZipPlaces, t); + + if(c->cleanup) + { + zipClose(c->z, NULL); + delete c; + } + t->finished = true; +} + +void fs::copyDirToZipThreaded(const std::string& src, zipFile dst, bool trimPath, int trimPlaces) +{ + fs::copyArgs *send = fs::copyArgsCreate(src, "", "", dst, NULL, true, false, 0); + threads::newThread(copyDirToZip_t, send, fs::fileDrawFunc); +} + +void fs::copyZipToDir(unzFile src, const std::string& dst, const std::string& dev, threadInfo *t) +{ + fs::copyArgs *c = NULL; + if(t) + c = (fs::copyArgs *)t->argPtr; + + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + uint64_t journalSize = getJournalSize(utinfo), writeCount = 0; + char filename[FS_MAX_PATH]; + uint8_t *buff = new uint8_t[BUFF_SIZE]; + int readIn = 0; + unz_file_info64 info; + do + { + unzGetCurrentFileInfo64(src, &info, filename, FS_MAX_PATH, NULL, 0, NULL, 0); + if(unzOpenCurrentFile(src) == UNZ_OK) + { + if(t) + t->status->setStatus("threadStatusDecompressingFile"); + + if(c) + { + // c->prog->setMax(info.uncompressed_size); + // c->prog->update(0); + c->offset = 0; + } + + std::string fullDst = dst + filename; + fs::mkDirRec(fullDst.substr(0, fullDst.find_last_of('/') + 1)); + + unzThrdArgs unzThrd; + unzThrd.dst = fullDst; + unzThrd.fileSize = info.uncompressed_size; + unzThrd.dev = dev; + unzThrd.writeLimit = (journalSize - 0x100000) < TRANSFER_BUFFER_LIMIT ? (journalSize - 0x100000) : TRANSFER_BUFFER_LIMIT; + + Thread writeThread; + threadCreate(&writeThread, writeFileFromZip_t, &unzThrd, NULL, 0x8000, 0x2B, 2); + threadStart(&writeThread); + + std::vector transferBuffer; + uint64_t readCount = 0; + while((readIn = unzReadCurrentFile(src, buff, BUFF_SIZE)) > 0) + { + transferBuffer.insert(transferBuffer.end(), buff, buff + readIn); + readCount += readIn; + + if(c) + c->offset += readIn; + + if(transferBuffer.size() >= unzThrd.writeLimit || readCount == info.uncompressed_size) + { + std::unique_lock buffLock(unzThrd.buffLock); + unzThrd.cond.wait(buffLock, [&unzThrd]{ return unzThrd.bufferIsFull == false; }); + unzThrd.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end()); + transferBuffer.clear(); + unzThrd.bufferIsFull = true; + unzThrd.cond.notify_one(); + } + } + threadWaitForExit(&writeThread); + threadClose(&writeThread); + fs::commitToDevice(dev); + } + } + while(unzGoToNextFile(src) != UNZ_END_OF_LIST_OF_FILE); + delete[] buff; +} + +static void copyZipToDir_t(void *a) +{ + threadInfo *t = (threadInfo *)a; + fs::copyArgs *c = (fs::copyArgs *)t->argPtr; + fs::copyZipToDir(c->unz, c->dst, c->dev, t); + if(c->cleanup) + { + unzClose(c->unz); + delete c; + } + t->finished = true; +} + +void fs::copyZipToDirThreaded(unzFile src, const std::string& dst, const std::string& dev) +{ + fs::copyArgs *send = fs::copyArgsCreate("", dst, dev, NULL, src, true, false, 0); + threads::newThread(copyZipToDir_t, send, fs::fileDrawFunc); +} + +uint64_t fs::getZipTotalSize(unzFile unz) +{ + uint64_t ret = 0; + if(unzGoToFirstFile(unz) == UNZ_OK) + { + unz_file_info64 finfo; + char filename[FS_MAX_PATH]; + do + { + unzGetCurrentFileInfo64(unz, &finfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0); + ret += finfo.uncompressed_size; + } while(unzGoToNextFile(unz) != UNZ_END_OF_LIST_OF_FILE); + unzGoToFirstFile(unz); + } + return ret; +} + +bool fs::zipNotEmpty(unzFile unz) +{ + return unzGoToFirstFile(unz) == UNZ_OK; +} diff --git a/source/ldn.cpp b/source/ldn.cpp new file mode 100644 index 0000000..5001df0 --- /dev/null +++ b/source/ldn.cpp @@ -0,0 +1,543 @@ +#include "ldn.h" + +#include +#include +#include +#include +#include +#include + +#include "fs.h" +#include "file.h" +#include "util.h" + +static const u8 sec_data[0x10] = {0x04, 0xb9, 0x9d, 0x4d, 0x58, 0xbc, + 0x65, 0xe1, 0x77, 0x13, 0xc2, 0xb8, + 0xd1, 0xb8, 0xec, 0xf6}; + +Result create_network(const LdnSecurityConfig *sec_config, + const LdnUserConfig *user_config, + const LdnNetworkConfig *netconfig, const void *advert, + size_t advert_size) { + Result rc = 0; + + rc = ldnOpenAccessPoint(); + if (R_FAILED(rc)) { + return rc; + } + + rc = ldnCreateNetwork(sec_config, user_config, netconfig); + if (R_FAILED(rc)) { + goto error_close; + } + + rc = ldnSetAdvertiseData(advert, advert_size); + if (R_FAILED(rc)) + goto error_close; + + return rc; +error_close: + ldnCloseAccessPoint(); + return rc; +} + +Result connect_network(const LdnScanFilter *filter, + const LdnSecurityConfig *sec_config, + const LdnUserConfig *user_config, const void *advert, + size_t advert_size) { + Result rc = 0; + s32 total_out = 0; + LdnNetworkInfo netinfo_list[0x18]; + + rc = ldnOpenStation(); + + if (R_SUCCEEDED(rc)) { + rc = ldnScan(0, filter, netinfo_list, 0x18, &total_out); + } + + // In an actual app you'd display the output netinfo_list and let the user + // select which one to connect to, however in this example we'll just + // connect to the first one. + + if (R_SUCCEEDED(rc) && !total_out) { + rc = MAKERESULT(Module_Libnx, LibnxError_NotFound); + } + + if (R_SUCCEEDED(rc)) { // Handle this / parse it with any method you want. + if (netinfo_list[0].advertise_data_size != advert_size || + memcmp(netinfo_list[0].advertise_data, advert, advert_size) != 0) { + rc = MAKERESULT(Module_Libnx, LibnxError_NotFound); + } + } + + if (R_SUCCEEDED(rc)) { + rc = ldnConnect(sec_config, user_config, 0, 0, &netinfo_list[0]); + } + + if (R_FAILED(rc)) + ldnCloseStation(); + + return rc; +} + +static void leave_network(void) { + Result rc = 0; + LdnState state; + + rc = ldnGetState(&state); + if (R_SUCCEEDED(rc)) { + if (state == LdnState_AccessPointOpened || + state == LdnState_AccessPointCreated) { + if (state == LdnState_AccessPointCreated) { + rc = ldnDestroyNetwork(); + } + rc = ldnCloseAccessPoint(); + } + + if (state == LdnState_StationOpened || state == LdnState_StationConnected) { + if (state == LdnState_StationConnected) { + rc = ldnDisconnect(); + } + rc = ldnCloseStation(); + } + } +} + +void LDN::destroyLDN() { + leave_network(); + ldnExit(); +} + +LDN::LDNCommunicate* LDN::createCommunicate(void) +{ + LDN::LDNCommunicate* comm = new LDN::LDNCommunicate; + comm->serverFD = -1; + comm->commFD = -1; + return comm; +} + +void LDN::destroyCommunicate(LDNCommunicate *comm) { + if (comm->commFD >= 0) + close(comm->commFD); + if (comm->serverFD >= 0) + close(comm->serverFD); + delete comm; +} + +Result LDN::createLDNServer(LDNCommunicate *comm) { + Result rc; + LdnSecurityConfig sec_config = {0}; + LdnUserConfig user_config = {0}; + LdnNetworkConfig netconfig = {0}; + LdnState state; + data::user *user = data::getCurrentUser(); + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + /* + * Use Title ID to the advert, so if advert not match + * LDN connect between client and server will bind fail + */ + std::string advertStr = std::to_string(utinfo->tid); + int serverSocket; + + // send nickname to make sure + strncpy(user_config.nickname, user->getUsername().c_str(), 0x20 - 1); + + netconfig.local_communication_id = -1; + netconfig.participant_max = 2; // Adjust as needed. + + sec_config.type = 1; + sec_config.data_size = sizeof(sec_data); + memcpy(sec_config.data, sec_data, sizeof(sec_data)); + + rc = ldnInitialize(LdnServiceType_User); + if (R_FAILED(rc)) { + goto out; + } + + rc = ldnGetState(&state); + if (!R_SUCCEEDED(rc) || state != LdnState_Initialized) { + goto ldn_out; + } + + rc = create_network(&sec_config, &user_config, &netconfig, advertStr.c_str(), + advertStr.length()); + if (R_FAILED(rc)) { + goto ldn_out; + } + + return rc; + +ldn_out: + ldnExit(); +out: + return rc; +} + +Result LDN::createLDNClient(LDNCommunicate *comm) { + Result rc; + LdnUserConfig user_config = {0}; + LdnSecurityConfig sec_config = {0}; + LdnNetworkConfig netconfig = {0}; + LdnScanFilter filter = {0}; + LdnState state; + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + /* + * Use Title ID to the advert, so if advert not match + * LDN connect between client and server will bind fail + */ + std::string advertStr = std::to_string(utinfo->tid); + data::user *user = data::getCurrentUser(); + + strncpy(user_config.nickname, user->getUsername().c_str(), 0x20 - 1); + + rc = ldnInitialize(LdnServiceType_User); + if (R_FAILED(rc)) { + goto out; + } + + netconfig.local_communication_id = -1; + netconfig.participant_max = 2; // Adjust as needed. + + sec_config.type = 1; + sec_config.data_size = sizeof(sec_data); + memcpy(sec_config.data, sec_data, sizeof(sec_data)); + + filter.local_communication_id = -1; + filter.userdata_filter = netconfig.userdata_filter; + filter.flags = + LdnScanFilterFlags_LocalCommunicationId | LdnScanFilterFlags_UserData; + + rc = ldnGetState(&state); + if (!R_SUCCEEDED(rc) || state != LdnState_Initialized) { + goto ldnOut; + } + + rc = connect_network(&filter, &sec_config, &user_config, advertStr.c_str(), + advertStr.length()); + +out: + return rc; +ldnOut: + ldnExit(); + return rc; +} + +int LDN::bindClient(int serverFD) { + struct sockaddr_in clientAddr; + socklen_t length = sizeof(clientAddr); + int cSocket = + accept(serverFD, (struct sockaddr *)&clientAddr, &length); + if (cSocket < 0) { + return -1; + } + + return cSocket; +} + +int LDN::bindServer(struct sockaddr_in *serverAddr) { + /// sockfd + int sock_cli = socket(AF_INET, SOCK_STREAM, 0); + int ret; + if ((ret = connect(sock_cli, (struct sockaddr *)serverAddr, + sizeof(*serverAddr))) < 0) { + close(sock_cli); + return -1; + } + + return sock_cli; +} + +static void reciveBuf(char *buf, ssize_t size, int socketfd) { + ssize_t offset = 0, ssize; + while (offset < size) { + ssize = + size - offset < SOCKET_BUFFER_SIZE ? size - offset : SOCKET_BUFFER_SIZE; + ssize_t done = recv(socketfd, buf + offset, ssize, 0); + if (done == -1) { + break; + } + offset += done; + } +} + +static void sendBuf(const char *buf, ssize_t size, int socketfd) { + ssize_t offset = 0, ssize; + while (offset < size) { + ssize = + size - offset < SOCKET_BUFFER_SIZE ? size - offset : SOCKET_BUFFER_SIZE; + ssize_t done = send(socketfd, buf + offset, ssize, 0); + if (done == -1) { + break; + } + offset += done; + } +} + +bool LDN::waitForOK(int socketfd) { + commMeta meta; + reciveBuf((char *)&meta, sizeof(meta), socketfd); + if (meta.type == UPDATE_OK) + return true; + return false; +} + +bool LDN::waitForDONE(int socketfd) { + commMeta meta; + reciveBuf((char *)&meta, sizeof(meta), socketfd); + if (meta.type == UPDATE_DONE) + return true; + return false; +} + +void LDN::sendOK(int socket_fd) { + commMeta meta; + meta.type = UPDATE_OK; + + sendBuf((char *)&meta, sizeof(meta), socket_fd); +} + +void LDN::sendDONE(int socket_fd) { + commMeta meta; + meta.type = UPDATE_DONE; + + sendBuf((char *)&meta, sizeof(meta), socket_fd); +} + +void LDN::sendAbort(int socket_fd) { + commMeta meta; + meta.type = UPDATE_ABORT; + + sendBuf((char *)&meta, sizeof(meta), socket_fd); +} + +void LDN::reciveMeta(commMeta *meta, int socketfd) { + bzero(meta, sizeof(commMeta)); + reciveBuf((char *)meta, sizeof(commMeta), socketfd); + sendOK(socketfd); +} + +void LDN::sendMeta(commMeta *meta, int socketfd) { + sendBuf((char *)meta, sizeof(commMeta), socketfd); + waitForOK(socketfd); +} + +static void copySaveFileToRemote_t(void *a) { + LDN::LDNfcopyArgs *in = (LDN::LDNfcopyArgs *)a; + LDN::LDNCommunicate *comm = in->comm; + size_t written = 0; + std::vector localBuffer; + + int fSocket = LDN::bindClient(comm->serverFD); + if (fSocket < 0) + return; + + while (written < in->filesize) { + std::unique_lock buffLock(in->bufferLock); + in->cond.wait(buffLock, [in] { return in->bufferIsFull; }); + localBuffer.clear(); + localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end()); + in->sharedBuffer.clear(); + in->bufferIsFull = false; + buffLock.unlock(); + in->cond.notify_one(); + + written += send(fSocket, localBuffer.data(), localBuffer.size(), 0); + } + + close(fSocket); +} + +void LDN::copySaveFileToRemote(const std::string &local, threadInfo *t) { + fs::copyArgs *c = NULL; + c = (fs::copyArgs *)t->argPtr; + LDN::LDNCommunicate *comm = c->comm; + commMeta cm; + std::string zipPath = fs::getWorkDir() + "tempSave.zip"; + + //Trim this to unzip direct in direct sv:/ + int zipTrim = util::getTotalPlacesInPath("sv:/"); + + t->status->setStatus("LDNStatus"); + + + zipFile zip = zipOpen64(zipPath.c_str(), 0); + fs::copyDirToZip(local, zip, true, zipTrim, t); + zipClose(zip, NULL); + + + size_t filesize = fs::fsize(zipPath); + t->status->setStatus("LDNStatus"); + c->offset = 0; + + FILE *fsrc = fopen(zipPath.c_str(), "rb"); + if(!fsrc) + { + sendAbort(comm->commFD); + fclose(fsrc); + return; + } + + cm.type = UPDATE_FILE; + cm.fsz = filesize; + sendMeta(&cm, comm->commFD); + + LDNfcopyArgs thrdArgs; + thrdArgs.comm = comm; + thrdArgs.filesize = filesize; + + uint8_t *buff = new uint8_t[BUFF_SIZE]; + std::vector transferBuffer; + Thread writeThread; + threadCreate(&writeThread, copySaveFileToRemote_t, &thrdArgs, NULL, + 0x40000, 0x2E, 2); + threadStart(&writeThread); + size_t readIn = 0; + uint64_t readCount = 0; + + while ((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0) { + transferBuffer.insert(transferBuffer.end(), buff, buff + readIn); + readCount += readIn; + + if (c) + c->offset = readCount; + + if (transferBuffer.size() >= TRANSFER_BUFFER_LIMIT || + readCount == filesize) { + std::unique_lock buffLock(thrdArgs.bufferLock); + thrdArgs.cond.wait( + buffLock, [&thrdArgs] { return thrdArgs.bufferIsFull == false; }); + thrdArgs.sharedBuffer.assign(transferBuffer.begin(), + transferBuffer.end()); + transferBuffer.clear(); + thrdArgs.bufferIsFull = true; + buffLock.unlock(); + thrdArgs.cond.notify_one(); + } + } + threadWaitForExit(&writeThread); + threadClose(&writeThread); + fclose(fsrc); + fs::delfile(zipPath); + delete[] buff; + + t->status->setStatus("LDNStatus"); + // wait for client recived sure + // otherwise, may lost package + LDN::sendDONE(comm->commFD); + LDN::waitForDONE(comm->commFD); +} + +static void copyRemoteFileWrite_t(void *a) { + LDN::LDNfcopyArgs *in = (LDN::LDNfcopyArgs *)a; + size_t written = 0; + std::vector localBuffer; + FILE *out = fopen(in->dst.c_str(), "wb"); + + while (written < in->filesize) { + std::unique_lock buffLock(in->bufferLock); + in->cond.wait(buffLock, [in] { return in->bufferIsFull; }); + localBuffer.clear(); + localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end()); + in->sharedBuffer.clear(); + in->bufferIsFull = false; + buffLock.unlock(); + in->cond.notify_one(); + written += fwrite(localBuffer.data(), 1, localBuffer.size(), out); + } + fclose(out); +} + +static void copyRemoteSaveFileCommit(LDN::commMeta *meta, threadInfo *t) { + fs::copyArgs *c = (fs::copyArgs *)t->argPtr; + LDN::LDNCommunicate *comm = c->comm; + + std::string fullPath = fs::getWorkDir() + "tempSave.zip"; + size_t filesize = meta->fsz; + + c->offset = 0; + + int fSocket = LDN::bindServer(&comm->serverAddr); + if (fSocket < 0) + return; + + t->status->setStatus("LDNStatus"); + LDN::LDNfcopyArgs thrdArgs; + thrdArgs.dst = fullPath; + thrdArgs.filesize = filesize; + + uint8_t *buff = new uint8_t[BUFF_SIZE]; + std::vector transferBuffer; + Thread writeThread; + threadCreate(&writeThread, copyRemoteFileWrite_t, &thrdArgs, NULL, 0x40000, + 0x2E, 2); + threadStart(&writeThread); + size_t readIn = 0; + uint64_t readCount = 0; + while ((readIn = recv(fSocket, buff, BUFF_SIZE, 0)) > 0) { + transferBuffer.insert(transferBuffer.end(), buff, buff + readIn); + readCount += readIn; + + if (c) + c->offset = readCount; + + if (transferBuffer.size() >= TRANSFER_BUFFER_LIMIT || + readCount == filesize) { + std::unique_lock buffLock(thrdArgs.bufferLock); + thrdArgs.cond.wait( + buffLock, [&thrdArgs] { return thrdArgs.bufferIsFull == false; }); + thrdArgs.sharedBuffer.assign(transferBuffer.begin(), + transferBuffer.end()); + transferBuffer.clear(); + thrdArgs.bufferIsFull = true; + buffLock.unlock(); + thrdArgs.cond.notify_one(); + } + } + threadWaitForExit(&writeThread); + threadClose(&writeThread); + + t->status->setStatus("LDNStatus"); + + unzFile unz = unzOpen64(fullPath.c_str()); + if(unz && fs::zipNotEmpty(unz)) { + t->status->setStatus("threadStatusCalculatingSaveSize"); + uint64_t saveSize = fs::getZipTotalSize(unz); + int64_t availSize = 0; + fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &availSize); + if ((int)saveSize > availSize) { + data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); + fs::unmountSave(); + fs::extendSaveData(utinfo, saveSize + 0x500000, t); + fs::mountSave(utinfo->saveInfo); + } + + fs::delDir("sv:/"); + fs::commitToDevice("sv"); + // fs::copyZipToDir(unz, "sv:/", "sv", t); + } + + // if (unz) + // unzClose(unz); + close(fSocket); + + delete[] buff; + fs::delfile(fullPath); + + LDN::waitForDONE(comm->commFD); + LDN::sendDONE(comm->commFD); +} + +void LDN::copyRemoteSaveFile(threadInfo *t) { + fs::copyArgs *c = (fs::copyArgs *)t->argPtr; + LDN::LDNCommunicate *comm = (LDN::LDNCommunicate *)c->comm; + LDN::commMeta meta; + + t->status->setStatus("LDNStatus"); + reciveMeta(&meta, comm->commFD); + if (meta.type == UPDATE_ABORT) + return; + + if (meta.type == UPDATE_FILE) { + copyRemoteSaveFileCommit(&meta, t); + } +} \ No newline at end of file diff --git a/source/threads.cpp b/source/threads.cpp new file mode 100644 index 0000000..7a29759 --- /dev/null +++ b/source/threads.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include +#include +#include +#include +#include "threads.h" +#include "util.h" + +static threads::threadProcMngr *threadMngr; + +threads::threadProcMngr::~threadProcMngr() +{ + for(threadInfo *t : threads) + { + threadWaitForExit(&t->thrd); + threadClose(&t->thrd); + delete t->status; + delete t; + } +} + +threadInfo *threads::threadProcMngr::newThread(ThreadFunc func, void *args, funcPtr _drawfunc) +{ + threadInfo *t = new threadInfo; + t->status = new threadStatus; + t->running = false; + t->finished = false; + t->thrdFunc = func; + t->drawFunc = _drawfunc; + t->argPtr = args; + + mutexLock(&threadLock); + threads.push_back(t); + mutexUnlock(&threadLock); + return threads[threads.size() - 1]; +} + +void threads::threadProcMngr::update() +{ + if(!threads.empty()) + { + Result res = 0; + threadInfo *t = threads[0]; + if(!t->running && R_SUCCEEDED((res = threadCreate(&t->thrd, t->thrdFunc, t, NULL, 0x80000, 0x2B, 1)))) + { + threadStart(&t->thrd); + t->running = true; + } + else if(!t->running && R_FAILED(res))//Should kill the thread that failed. + t->finished = true; + else if(t->finished) + { + threadWaitForExit(&t->thrd); + threadClose(&t->thrd); + delete t->status; + delete t; + mutexLock(&threadLock); + threads.erase(threads.begin()); + mutexUnlock(&threadLock); + } + } +} + +threadInfo *threads::newThread(ThreadFunc func, void *args, funcPtr _drawFunc) +{ + return threadMngr->newThread(func, args, _drawFunc); +} diff --git a/source/util.cpp b/source/util.cpp new file mode 100644 index 0000000..f8dd4fb --- /dev/null +++ b/source/util.cpp @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include + +#include "fs/file.h" +#include "data.h" +#include "util.h" +#include "type.h" + +static const uint32_t verboten[] = { L',', L'/', L'\\', L'<', L'>', L':', L'"', L'|', L'?', L'*', L'™', L'©', L'®'}; + +static bool isVerboten(const uint32_t& t) +{ + for(unsigned i = 0; i < 13; i++) + { + if(t == verboten[i]) + return true; + } + + return false; +} + +void util::replaceStr(std::string& _str, const std::string& _find, const std::string& _rep) +{ + size_t pos = 0; + while((pos = _str.find(_find)) != _str.npos) + _str.replace(pos, _find.length(), _rep); +} + +//Used to split version tag git +static void getVersionFromTag(const std::string& tag, unsigned& _year, unsigned& _month, unsigned& _day) +{ + _month = strtoul(tag.substr(0, 2).c_str(), NULL, 10); + _day = strtoul(tag.substr(3, 5).c_str(), NULL, 10); + _year = strtoul(tag.substr(6, 10).c_str(), NULL, 10); +} + +//Missing swkbd config funcs for now +typedef enum +{ + SwkbdPosStart = 0, + SwkbdPosEnd = 1 +} SwkbdInitPos; + +typedef struct +{ + uint16_t read[0x32 / sizeof(uint16_t)]; + uint16_t word[0x32 / sizeof(uint16_t)]; +} dictWord; + +void swkbdDictWordCreate(dictWord *w, const char *read, const char *word) +{ + memset(w, 0, sizeof(*w)); + + utf8_to_utf16(w->read, (uint8_t *)read, (sizeof(w->read) / sizeof(uint16_t)) - 1); + utf8_to_utf16(w->word, (uint8_t *)word, (sizeof(w->word) / sizeof(uint16_t)) - 1); +} + +uint32_t replaceChar(uint32_t c) +{ + switch(c) + { + case L'é': + return 'e'; + break; + } + + return c; +} + +static inline void replaceCharCStr(char *_s, char _find, char _rep) +{ + size_t strLength = strlen(_s); + for(unsigned i = 0; i < strLength; i++) + { + if(_s[i] == _find) + _s[i] = _rep; + } +} + +std::string util::getDateTime(int fmt) +{ + char ret[128]; + + time_t raw; + time(&raw); + tm *Time = localtime(&raw); + + switch(fmt) + { + case DATE_FMT_YMD: + sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min, Time->tm_sec); + break; + + case DATE_FMT_YDM: + sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mday, Time->tm_mon + 1, Time->tm_hour, Time->tm_min, Time->tm_sec); + break; + + case DATE_FMT_HOYSTE: + sprintf(ret, "%02d.%02d.%04d", Time->tm_mday, Time->tm_mon + 1, Time->tm_year + 1900); + break; + + case DATE_FMT_JHK: + sprintf(ret, "%04d%02d%02d_%02d%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min); + break; + + case DATE_FMT_ASC: + strcpy(ret, asctime(Time)); + replaceCharCStr(ret, ':', '_'); + replaceCharCStr(ret, '\n', 0x00); + break; + } + + return std::string(ret); +} + +//void util::copyDirListToMenu(const fs::dirList& d, ui::menu& m) +//{ +// m.reset(); +// m.addOpt(NULL, "."); +// m.addOpt(NULL, ".."); +// for(unsigned i = 0; i < d.getCount(); i++) +// { +// if(d.isDir(i)) +// m.addOpt(NULL, "D " + d.getItem(i)); +// else +// m.addOpt(NULL, "F " + d.getItem(i)); +// } +//} + +void util::removeLastFolderFromString(std::string& _path) +{ + unsigned last = _path.find_last_of('/', _path.length() - 2); + _path.erase(last + 1, _path.length()); +} + +size_t util::getTotalPlacesInPath(const std::string& _path) +{ + //Skip device + size_t pos = _path.find_first_of('/'), ret = 0; + while((pos = _path.find_first_of('/', ++pos)) != _path.npos) + ++ret; + return ret; +} + +void util::trimPath(std::string& _path, uint8_t _places) +{ + size_t pos = _path.find_first_of('/'); + for(int i = 0; i < _places; i++) + pos = _path.find_first_of('/', ++pos); + _path = _path.substr(++pos, _path.npos); +} + +std::string util::safeString(const std::string& s) +{ + std::string ret = ""; + for(unsigned i = 0; i < s.length(); ) + { + uint32_t tmpChr = 0; + ssize_t untCnt = decode_utf8(&tmpChr, (uint8_t *)&s.data()[i]); + + i += untCnt; + + tmpChr = replaceChar(tmpChr); + + if(isVerboten(tmpChr)) + ret += ' '; + else if(!isASCII(tmpChr)) + return ""; //return empty string so titledata::init defaults to titleID + else + ret += (char)tmpChr; + } + + //Check for spaces at end + while(ret[ret.length() - 1] == ' ' || ret[ret.length() - 1] == '.') + ret.erase(ret.length() - 1, 1); + + return ret; +} + +static inline std::string getTimeString(const uint32_t& _h, const uint32_t& _m) +{ + char tmp[32]; + sprintf(tmp, "%02d:%02d", _h, _m); + return std::string(tmp); +} + +std::string util::getStringInput(SwkbdType _type, const std::string& def, const std::string& head, size_t maxLength, unsigned dictCnt, const std::string dictWords[]) +{ + SwkbdConfig swkbd; + swkbdCreate(&swkbd, dictCnt); + swkbdConfigSetBlurBackground(&swkbd, true); + swkbdConfigSetInitialText(&swkbd, def.c_str()); + swkbdConfigSetHeaderText(&swkbd, head.c_str()); + swkbdConfigSetGuideText(&swkbd, head.c_str()); + swkbdConfigSetInitialCursorPos(&swkbd, SwkbdPosEnd); + swkbdConfigSetType(&swkbd, _type); + swkbdConfigSetStringLenMax(&swkbd, maxLength); + swkbdConfigSetKeySetDisableBitmask(&swkbd, SwkbdKeyDisableBitmask_Backslash | SwkbdKeyDisableBitmask_Percent); + swkbdConfigSetDicFlag(&swkbd, 1); + + if(dictCnt > 0) + { + dictWord words[dictCnt]; + for(unsigned i = 0; i < dictCnt; i++) + swkbdDictWordCreate(&words[i], dictWords[i].c_str(), dictWords[i].c_str()); + + swkbdConfigSetDictionary(&swkbd, (SwkbdDictWord *)words, dictCnt); + } + + char out[maxLength + 1]; + memset(out, 0, maxLength + 1); + swkbdShow(&swkbd, out, maxLength + 1); + swkbdClose(&swkbd); + + return std::string(out); +} + +std::string util::getExtensionFromString(const std::string& get) +{ + size_t ext = get.find_last_of('.'); + if(ext != get.npos) + return get.substr(ext + 1, get.npos); + else + return ""; +} + +std::string util::getFilenameFromPath(const std::string& get) +{ + size_t nameStart = get.find_last_of('/'); + if(nameStart != get.npos) + return get.substr(nameStart + 1, get.npos); + else + return ""; +} + +std::string util::generateAbbrev(const uint64_t& tid) +{ + data::titleInfo *tmp = data::getTitleInfoByTID(tid); + size_t titleLength = tmp->safeTitle.length(); + + char temp[titleLength + 1]; + memset(temp, 0, titleLength + 1); + memcpy(temp, tmp->safeTitle.c_str(), titleLength); + + std::string ret; + char *tok = strtok(temp, " "); + while(tok) + { + if(isASCII(tok[0])) + ret += tok[0]; + tok = strtok(NULL, " "); + } + return ret; +} + +void util::stripChar(char _c, std::string& _s) +{ + size_t pos = 0; + while((pos = _s.find(_c)) != _s.npos) + _s.erase(pos, 1); +} + +void util::replaceButtonsInString(std::string& rep) +{ + replaceStr(rep, "[A]", "\ue0e0"); + replaceStr(rep, "[B]", "\ue0e1"); + replaceStr(rep, "[X]", "\ue0e2"); + replaceStr(rep, "[Y]", "\ue0e3"); + replaceStr(rep, "[L]", "\ue0e4"); + replaceStr(rep, "[R]", "\ue0e5"); + replaceStr(rep, "[ZL]", "\ue0e6"); + replaceStr(rep, "[ZR]", "\ue0e7"); + replaceStr(rep, "[SL]", "\ue0e8"); + replaceStr(rep, "[SR]", "\ue0e9"); + replaceStr(rep, "[DPAD]", "\ue0ea"); + replaceStr(rep, "[DUP]", "\ue0eb"); + replaceStr(rep, "[DDOWN]", "\ue0ec"); + replaceStr(rep, "[DLEFT]", "\ue0ed"); + replaceStr(rep, "[DRIGHT]", "\ue0ee"); + replaceStr(rep, "[+]", "\ue0ef"); + replaceStr(rep, "[-]", "\ue0f0"); +} + +void util::sysBoost() +{ + if(R_FAILED(clkrstInitialize())) + return; + + ClkrstSession cpu, gpu, ram; + clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3); + clkrstOpenSession(&gpu, PcvModuleId_GPU, 3); + clkrstOpenSession(&ram, PcvModuleId_EMC, 3); + + clkrstSetClockRate(&cpu, util::CPU_SPEED_1785MHz); + clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz); + clkrstSetClockRate(&ram, util::RAM_SPEED_1600MHz); + + clkrstCloseSession(&cpu); + clkrstCloseSession(&gpu); + clkrstCloseSession(&ram); + clkrstExit(); +} + +void util::sysNormal() +{ + if(R_FAILED(clkrstInitialize())) + return; + + ClkrstSession cpu, gpu, ram; + clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3); + clkrstOpenSession(&gpu, PcvModuleId_GPU, 3); + clkrstOpenSession(&ram, PcvModuleId_EMC, 3); + + clkrstSetClockRate(&cpu, util::CPU_SPEED_1020MHz); + clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz); + clkrstSetClockRate(&ram, util::RAM_SPEED_1331MHz); + + clkrstCloseSession(&cpu); + clkrstCloseSession(&gpu); + clkrstCloseSession(&ram); + clkrstExit(); + +} + +std::string util::getSizeString(const uint64_t& _size) +{ + char sizeStr[32]; + if(_size >= 0x40000000) + sprintf(sizeStr, "%.2fGB", (float)_size / 1024.0f / 1024.0f / 1024.0f); + else if(_size >= 0x100000) + sprintf(sizeStr, "%.2fMB", (float)_size / 1024.0f / 1024.0f); + else if(_size >= 0x400) + sprintf(sizeStr, "%.2fKB", (float)_size / 1024.0f); + else + sprintf(sizeStr, "%lu Bytes", _size); + return std::string(sizeStr); +} + +Result util::accountDeleteUser(AccountUid *uid) +{ + Service *account = accountGetServiceSession(); + struct + { + AccountUid uid; + } in = {*uid}; + + return serviceDispatchIn(account, 203, in); +}