├── .gitattributes ├── dist ├── themes │ └── default │ │ ├── icons │ │ ├── 24x24 │ │ │ ├── app.png │ │ │ ├── dlc.png │ │ │ ├── hos.png │ │ │ ├── update.png │ │ │ ├── save_data.png │ │ │ ├── unknown.png │ │ │ ├── system_data.png │ │ │ └── system_archive.png │ │ └── index.theme │ │ └── default.qrc └── threeSDumper.gm9 ├── .lgtm.yml ├── .ci ├── macos │ ├── deps.sh │ ├── build.sh │ └── upload.sh ├── linux-fresh │ ├── docker.sh │ └── upload.sh ├── linux-mingw │ ├── upload.sh │ ├── docker.sh │ └── scan_dll.py ├── windows-msvc │ ├── build.sh │ └── deps.sh ├── common │ ├── pre-upload.sh │ └── post-upload.sh └── linux-clang-format │ └── docker.sh ├── externals ├── CMakeLists.txt ├── inih │ └── CMakeLists.txt └── qdevicewatcher │ └── CMakeLists.txt ├── src ├── frontend │ ├── helpers │ │ ├── frontend_common.h │ │ ├── rate_limited_progress_dialog.h │ │ ├── frontend_common.cpp │ │ ├── simple_job.h │ │ ├── dpi_aware_dialog.h │ │ ├── rate_limited_progress_dialog.cpp │ │ ├── multi_job.h │ │ ├── simple_job.cpp │ │ ├── multi_job.cpp │ │ └── dpi_aware_dialog.cpp │ ├── select_nand_dialog.h │ ├── main.h │ ├── cia_build_dialog.h │ ├── select_files_dialog.h │ ├── Info.plist │ ├── title_info_dialog.h │ ├── utilities.h │ ├── select_nand_dialog.ui │ ├── select_nand_dialog.cpp │ ├── main.ui │ ├── select_files_dialog.cpp │ ├── select_files_dialog.ui │ ├── import_dialog.ui │ ├── import_dialog.h │ ├── CMakeLists.txt │ ├── cia_build_dialog.ui │ └── cia_build_dialog.cpp ├── common │ ├── CMakeLists.txt │ ├── alignment.h │ ├── progress_callback.h │ ├── progress_callback.cpp │ ├── scope_exit.h │ ├── misc.cpp │ ├── common_paths.h │ ├── logging │ │ ├── log.cpp │ │ └── log.h │ ├── common_types.h │ ├── thread.h │ ├── assert.h │ ├── string_util.h │ ├── common_funcs.h │ └── string_util.cpp ├── core │ ├── key │ │ ├── arithmetic128.h │ │ ├── arithmetic128.cpp │ │ └── key.h │ ├── db │ │ ├── seed_db.h │ │ ├── title_keys_bin.h │ │ ├── title_keys_bin.cpp │ │ ├── title_db.h │ │ ├── seed_db.cpp │ │ └── title_db.cpp │ ├── file_sys │ │ ├── cia_common.h │ │ ├── data │ │ │ ├── savegame.h │ │ │ ├── extdata.h │ │ │ ├── savegame.cpp │ │ │ ├── extdata.cpp │ │ │ └── data_container.h │ │ ├── signature.h │ │ ├── config_savegame.cpp │ │ ├── certificate.h │ │ ├── ticket.h │ │ ├── config_savegame.h │ │ ├── smdh.h │ │ ├── signature.cpp │ │ ├── smdh.cpp │ │ ├── title_metadata.h │ │ ├── ticket.cpp │ │ └── certificate.cpp │ ├── CMakeLists.txt │ ├── sdmc_decryptor.h │ ├── file_decryptor.h │ ├── cia_builder.h │ ├── sdmc_decryptor.cpp │ └── file_decryptor.cpp ├── CMakeLists.txt └── .clang-format ├── .gitmodules ├── CMakeModules ├── MSVCCache.cmake ├── DownloadExternals.cmake ├── WindowsCopyFiles.cmake ├── CopyQt5Deps.cmake └── MinGWCross.cmake ├── .gitignore ├── README.md ├── appveyor.yml ├── .github └── workflows │ └── ci.yml └── CMakeLists.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/app.png -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/dlc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/dlc.png -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/hos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/hos.png -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/update.png -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/save_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/save_data.png -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/unknown.png -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/system_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/system_data.png -------------------------------------------------------------------------------- /dist/themes/default/icons/index.theme: -------------------------------------------------------------------------------- 1 | [Icon Theme] 2 | Name=default 3 | Comment=default theme 4 | Directories=24x24 5 | 6 | [24x24] 7 | Size=24 8 | -------------------------------------------------------------------------------- /dist/themes/default/icons/24x24/system_archive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaowenlan1779/threeSD/HEAD/dist/themes/default/icons/24x24/system_archive.png -------------------------------------------------------------------------------- /.lgtm.yml: -------------------------------------------------------------------------------- 1 | path_classifiers: 2 | library: "externals" 3 | queries: 4 | - include: "*" 5 | - exclude: cpp/array-in-interface 6 | - exclude: cpp/world-writable-file-creation 7 | -------------------------------------------------------------------------------- /.ci/macos/deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | brew update 4 | brew unlink python@2 || true 5 | rm '/usr/local/bin/2to3' || true 6 | brew install qt5 sdl2 p7zip ccache llvm ninja || true 7 | pip3 install macpack 8 | -------------------------------------------------------------------------------- /.ci/linux-fresh/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | mkdir build && cd build 4 | cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ 5 | ninja 6 | -------------------------------------------------------------------------------- /externals/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Crypto++ 2 | add_subdirectory(cryptopp) 3 | 4 | # fmt 5 | add_subdirectory(fmt) 6 | 7 | # inih 8 | add_subdirectory(inih) 9 | 10 | # QDeviceWatcher 11 | add_subdirectory(qdevicewatcher) 12 | -------------------------------------------------------------------------------- /.ci/linux-fresh/upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | . .ci/common/pre-upload.sh 4 | 5 | REV_NAME="threeSD-linux-${GITNAME}" 6 | 7 | mkdir "$REV_NAME" 8 | cp build/bin/threeSD "$REV_NAME" 9 | 10 | mkdir "$REV_NAME/dist" 11 | 12 | . .ci/common/post-upload.sh 13 | -------------------------------------------------------------------------------- /externals/inih/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(inih 2 | inih/ini.c 3 | inih/ini.h 4 | inih/cpp/INIReader.cpp 5 | inih/cpp/INIReader.h 6 | ) 7 | target_include_directories(inih INTERFACE .) 8 | 9 | target_compile_definitions(inih PRIVATE -DINI_MAX_LINE=1000) 10 | -------------------------------------------------------------------------------- /src/frontend/helpers/frontend_common.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | QString ReadableByteSize(qulonglong size); 10 | -------------------------------------------------------------------------------- /.ci/macos/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | set -o pipefail 4 | 5 | export MACOSX_DEPLOYMENT_TARGET=10.13 6 | export Qt5_DIR=$(brew --prefix)/opt/qt5 7 | export PATH="/usr/local/opt/ccache/libexec:$PATH" 8 | 9 | mkdir build && cd build 10 | cmake .. -DCMAKE_BUILD_TYPE=Release 11 | make -j4 12 | -------------------------------------------------------------------------------- /.ci/linux-mingw/upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | . .ci/common/pre-upload.sh 4 | 5 | REV_NAME="threeSD-windows-mingw-${GITNAME}" 6 | 7 | mkdir "$REV_NAME" 8 | # get around the permission issues 9 | cp -r package/* "$REV_NAME" 10 | 11 | mkdir "$REV_NAME/dist" 12 | 13 | . .ci/common/post-upload.sh 14 | -------------------------------------------------------------------------------- /.ci/windows-msvc/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | mkdir build && cd build 4 | cmake .. -DCMAKE_BUILD_TYPE=Release -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DWARNINGS_AS_ERRORS=OFF -DUSE_BUNDLED_QT=1 5 | 6 | ninja 7 | # show the caching efficiency 8 | buildcache -s 9 | -------------------------------------------------------------------------------- /.ci/common/pre-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`" 4 | GITREV="`git show -s --format='%h'`" 5 | if [[ $GITHUB_REF == refs/tags/* ]]; then 6 | GITNAME="${GITHUB_REF:10}" 7 | else 8 | GITNAME="${GITDATE}-${GITREV}" 9 | fi 10 | 11 | mkdir -p artifacts 12 | -------------------------------------------------------------------------------- /.ci/common/post-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Copy documentation 4 | cp license.txt "$REV_NAME" 5 | cp README.md "$REV_NAME" 6 | 7 | cp dist/threeSDumper.gm9 "$REV_NAME/dist" 8 | 9 | 7z a "$REV_NAME.zip" $REV_NAME 10 | 11 | # move the compiled archive into the artifacts directory to be uploaded by gh action releases 12 | mv "$REV_NAME.zip" artifacts/ 13 | -------------------------------------------------------------------------------- /.ci/windows-msvc/deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | BUILDCACHE_VERSION="0.22.3" 4 | 5 | choco install wget ninja 6 | # Install buildcache 7 | wget "https://github.com/mbitsnbites/buildcache/releases/download/v${BUILDCACHE_VERSION}/buildcache-win-mingw.zip" 8 | 7z x 'buildcache-win-mingw.zip' 9 | mv ./buildcache/bin/buildcache.exe "/c/ProgramData/chocolatey/bin" 10 | rm -rf ./buildcache/ 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "fmt"] 2 | path = externals/fmt 3 | url = https://github.com/fmtlib/fmt.git 4 | [submodule "cryptopp"] 5 | path = externals/cryptopp/cryptopp 6 | url = https://github.com/weidai11/cryptopp.git 7 | [submodule "qdevicewatcher"] 8 | path = externals/qdevicewatcher/qdevicewatcher 9 | url = https://github.com/wang-bin/qdevicewatcher.git 10 | [submodule "inih"] 11 | path = externals/inih/inih 12 | url = https://github.com/benhoyt/inih.git 13 | -------------------------------------------------------------------------------- /src/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(common STATIC 2 | alignment.h 3 | assert.h 4 | bit_field.h 5 | common_funcs.h 6 | common_paths.h 7 | common_types.h 8 | file_util.cpp 9 | file_util.h 10 | logging/log.cpp 11 | logging/log.h 12 | misc.cpp 13 | progress_callback.cpp 14 | progress_callback.h 15 | scope_exit.h 16 | string_util.cpp 17 | string_util.h 18 | swap.h 19 | thread.h 20 | ) 21 | 22 | target_link_libraries(common PUBLIC fmt inih) 23 | -------------------------------------------------------------------------------- /CMakeModules/MSVCCache.cmake: -------------------------------------------------------------------------------- 1 | # buildcache wrapper 2 | OPTION(USE_CCACHE "Use buildcache for compilation" OFF) 3 | IF(USE_CCACHE) 4 | FIND_PROGRAM(CCACHE buildcache) 5 | IF (CCACHE) 6 | MESSAGE(STATUS "Using buildcache found in PATH") 7 | SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE}) 8 | SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE}) 9 | ELSE(CCACHE) 10 | MESSAGE(WARNING "USE_CCACHE enabled, but no buildcache executable found") 11 | ENDIF(CCACHE) 12 | ENDIF(USE_CCACHE) 13 | -------------------------------------------------------------------------------- /src/core/key/arithmetic128.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Citra Emulator Project / 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include "common/common_types.h" 8 | #include "core/key/key.h" 9 | 10 | namespace Core::Key { 11 | 12 | AESKey Lrot128(const AESKey& in, u32 rot); 13 | AESKey Add128(const AESKey& a, const AESKey& b); 14 | AESKey Add128(const AESKey& a, u64 b); 15 | AESKey Xor128(const AESKey& a, const AESKey& b); 16 | 17 | } // namespace Core::Key 18 | -------------------------------------------------------------------------------- /src/common/alignment.h: -------------------------------------------------------------------------------- 1 | // This file is under the public domain. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace Common { 9 | 10 | template 11 | constexpr T AlignUp(T value, std::size_t size) { 12 | static_assert(std::is_unsigned_v, "T must be an unsigned value."); 13 | return static_cast(value + (size - value % size) % size); 14 | } 15 | 16 | template 17 | constexpr T AlignDown(T value, std::size_t size) { 18 | static_assert(std::is_unsigned_v, "T must be an unsigned value."); 19 | return static_cast(value - value % size); 20 | } 21 | 22 | } // namespace Common 23 | -------------------------------------------------------------------------------- /src/frontend/select_nand_dialog.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "core/importer.h" 10 | 11 | namespace Ui { 12 | class SelectNandDialog; 13 | } 14 | 15 | class SelectNandDialog final : public QDialog { 16 | public: 17 | explicit SelectNandDialog(QWidget* parent, const std::vector& nands); 18 | ~SelectNandDialog(); 19 | 20 | int GetResult() const; 21 | 22 | private: 23 | std::unique_ptr ui; 24 | 25 | const std::vector& nands; 26 | int result = 0; 27 | }; 28 | -------------------------------------------------------------------------------- /.ci/macos/upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | . .ci/common/pre-upload.sh 4 | 5 | REV_NAME="threeSD-macos-${GITNAME}" 6 | 7 | mkdir "$REV_NAME" 8 | cp -r build/bin/threeSD.app "$REV_NAME" 9 | 10 | # move libs into folder for deployment 11 | macpack "${REV_NAME}/threeSD.app/Contents/MacOS/threeSD" -d "../Frameworks" 12 | # move qt frameworks into app bundle for deployment 13 | $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/threeSD.app" -executable="${REV_NAME}/threeSD.app/Contents/MacOS/threeSD" 14 | 15 | # Make the launching script executable 16 | chmod +x ${REV_NAME}/threeSD.app/Contents/MacOS/threeSD 17 | 18 | # Verify loader instructions 19 | find "$REV_NAME" -exec otool -L {} \; 20 | 21 | mkdir "$REV_NAME/dist" 22 | 23 | . .ci/common/post-upload.sh 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build directory 2 | [Bb]uild/ 3 | doc-build/ 4 | 5 | # Generated source files 6 | src/common/scm_rev.cpp 7 | .travis.descriptor.json 8 | 9 | # Project/editor files 10 | *.swp 11 | .idea/ 12 | .vs/ 13 | .vscode/ 14 | CMakeLists.txt.user* 15 | 16 | # *nix related 17 | # Common convention for backup or temporary files 18 | *~ 19 | 20 | # Visual Studio CMake settings 21 | CMakeSettings.json 22 | 23 | # OSX global filetypes 24 | # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) 25 | .DS_Store 26 | .AppleDouble 27 | .LSOverride 28 | .Spotlight-V100 29 | .Trashes 30 | 31 | # Windows global filetypes 32 | Thumbs.db 33 | 34 | # Python files 35 | *.pyc 36 | 37 | # Flatpak generated files 38 | .flatpak-builder/ 39 | repo/ 40 | -------------------------------------------------------------------------------- /src/frontend/main.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "core/importer.h" 9 | #include "frontend/helpers/dpi_aware_dialog.h" 10 | 11 | namespace Ui { 12 | class MainDialog; 13 | } 14 | 15 | class MainDialog final : public DPIAwareDialog { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit MainDialog(QWidget* parent = nullptr); 20 | ~MainDialog() override; 21 | 22 | private: 23 | void SetContentSizes(int previous_width, int previous_height) override; 24 | 25 | void LoadPresetConfig(); 26 | void LaunchImportDialog(); 27 | 28 | std::vector preset_config_list; 29 | std::unique_ptr ui; 30 | }; 31 | -------------------------------------------------------------------------------- /src/core/db/seed_db.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Citra Emulator Project / 2020 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_types.h" 10 | 11 | namespace Core { 12 | 13 | constexpr std::size_t SEEDDB_PADDING_BYTES{12}; 14 | constexpr std::size_t SEEDDB_ENTRY_PADDING_BYTES{8}; 15 | constexpr std::size_t SEEDDB_ENTRY_SIZE{32}; 16 | 17 | using Seed = std::array; 18 | class SeedDB { 19 | public: 20 | bool AddFromFile(const std::string& path); 21 | bool Save(const std::string& path) const; 22 | std::size_t GetSize() const; 23 | 24 | std::unordered_map seeds; 25 | }; 26 | 27 | inline SeedDB g_seed_db; 28 | 29 | } // namespace Core 30 | -------------------------------------------------------------------------------- /externals/qdevicewatcher/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_AUTOMOC ON) 2 | 3 | add_definitions(-DUNICODE -D_UNICODE) 4 | 5 | add_library(qdevicewatcher STATIC 6 | qdevicewatcher/src/qdevicewatcher_p.h 7 | qdevicewatcher/src/qdevicewatcher.cpp 8 | qdevicewatcher/src/qdevicewatcher.h 9 | ) 10 | 11 | target_link_libraries(qdevicewatcher PRIVATE Qt5::Core) 12 | target_include_directories(qdevicewatcher INTERFACE qdevicewatcher/src) 13 | 14 | if (WIN32) 15 | target_sources(qdevicewatcher PRIVATE 16 | qdevicewatcher/src/qdevicewatcher_win32.cpp 17 | ) 18 | elseif(APPLE) 19 | target_sources(qdevicewatcher PRIVATE 20 | qdevicewatcher/src/qdevicewatcher_mac.cpp 21 | ) 22 | elseif(UNIX) 23 | target_sources(qdevicewatcher PRIVATE 24 | qdevicewatcher/src/qdevicewatcher_linux.cpp 25 | ) 26 | endif() 27 | -------------------------------------------------------------------------------- /src/frontend/cia_build_dialog.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "core/file_sys/cia_common.h" 10 | #include "frontend/helpers/dpi_aware_dialog.h" 11 | 12 | namespace Ui { 13 | class CIABuildDialog; 14 | } 15 | 16 | class CIABuildDialog : public DPIAwareDialog { 17 | Q_OBJECT 18 | 19 | public: 20 | explicit CIABuildDialog(QWidget* parent, bool is_dir, bool is_nand, bool enable_legit, 21 | const QString& default_path); 22 | ~CIABuildDialog(); 23 | 24 | std::pair GetResults() const; 25 | 26 | private: 27 | std::unique_ptr ui; 28 | bool is_dir; 29 | }; 30 | -------------------------------------------------------------------------------- /src/frontend/select_files_dialog.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "frontend/helpers/dpi_aware_dialog.h" 9 | 10 | namespace Ui { 11 | class SelectFilesDialog; 12 | } 13 | 14 | class SelectFilesDialog : public DPIAwareDialog { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit SelectFilesDialog(QWidget* parent, bool source_is_dir, bool destination_is_dir); 19 | ~SelectFilesDialog() override; 20 | 21 | std::pair GetResults() const; 22 | 23 | private: 24 | std::unique_ptr ui; 25 | bool source_is_dir; // Whether Source should be a directory 26 | bool destination_is_dir; // Whether Destination should be a directory 27 | }; 28 | -------------------------------------------------------------------------------- /src/core/file_sys/cia_common.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Citra Emulator Project / 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "common/common_types.h" 9 | 10 | namespace Core { 11 | 12 | /// Full names of the certificates contained in a CIA. 13 | constexpr std::array CIACertNames{{ 14 | "Root-CA00000003", 15 | "Root-CA00000003-XS0000000c", 16 | "Root-CA00000003-CP0000000b", 17 | }}; 18 | 19 | enum class CIABuildType { 20 | Standard, /// Decrypted CIA with generalized ticket 21 | PirateLegit, /// Uses legit TMD and encryption, but with generalized ticket 22 | Legit, /// Fully legit, with personal ticket containing console ID and eshop account 23 | }; 24 | 25 | } // namespace Core 26 | -------------------------------------------------------------------------------- /src/frontend/helpers/rate_limited_progress_dialog.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | class RateLimitedProgressDialog : public QProgressDialog { 11 | public: 12 | explicit RateLimitedProgressDialog(const QString& label_text, const QString& cancel_button_text, 13 | int minimum, int maximum, QWidget* parent = nullptr); 14 | ~RateLimitedProgressDialog() override; 15 | 16 | void Update(int progress, const QString& label_text); 17 | 18 | private: 19 | std::chrono::steady_clock::time_point last_update_time = std::chrono::steady_clock::now(); 20 | static constexpr auto MinimumInterval = std::chrono::milliseconds{100}; 21 | }; 22 | -------------------------------------------------------------------------------- /dist/themes/default/default.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/index.theme 4 | 5 | icons/24x24/app.png 6 | 7 | icons/24x24/dlc.png 8 | 9 | icons/24x24/hos.png 10 | 11 | icons/24x24/save_data.png 12 | 13 | icons/24x24/system_archive.png 14 | 15 | icons/24x24/system_data.png 16 | 17 | icons/24x24/unknown.png 18 | 19 | icons/24x24/update.png 20 | 21 | 22 | -------------------------------------------------------------------------------- /.ci/linux-mingw/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # override CI ccache size 4 | mkdir -p "$HOME/.ccache/" 5 | echo 'max_size = 3.0G' > "$HOME/.ccache/ccache.conf" 6 | 7 | mkdir build && cd build 8 | cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DCMAKE_BUILD_TYPE=Release -DCOMPILE_WITH_DWARF=OFF 9 | ninja 10 | 11 | ccache -s 12 | 13 | echo 'Prepare binaries...' 14 | cd .. 15 | mkdir package 16 | 17 | QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/' 18 | find build/ -name "threeSD.exe" -exec cp {} 'package' \; 19 | 20 | # copy Qt plugins 21 | mkdir package/platforms 22 | cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/ 23 | cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/ 24 | 25 | python3 .ci/linux-mingw/scan_dll.py package/*.exe package/imageformats/*.dll "package/" 26 | -------------------------------------------------------------------------------- /.ci/linux-clang-format/docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \ 4 | dist/*.svg dist/*.xml; then 5 | echo Trailing whitespace found, aborting 6 | exit 1 7 | fi 8 | 9 | # Default clang-format points to default 3.5 version one 10 | CLANG_FORMAT=clang-format-10 11 | $CLANG_FORMAT --version 12 | 13 | # Check everything 14 | files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')" 15 | 16 | # Turn off tracing for this because it's too verbose 17 | set +x 18 | 19 | for f in $files_to_lint; do 20 | d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true) 21 | if ! [ -z "$d" ]; then 22 | echo "!!! $f not compliant to coding style, here is the fix:" 23 | echo "$d" 24 | fail=1 25 | fi 26 | done 27 | 28 | set -x 29 | 30 | if [ "$fail" = 1 ]; then 31 | exit 1 32 | fi 33 | -------------------------------------------------------------------------------- /src/core/file_sys/data/savegame.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include "core/file_sys/data/inner_fat.hpp" 8 | 9 | namespace Core { 10 | 11 | class Savegame final : public Archive { 12 | public: 13 | /// @param data Data of the DISA archive 14 | explicit Savegame(std::vector data); 15 | ~Savegame(); 16 | 17 | bool IsGood() const; 18 | bool Extract(std::string path) const; 19 | 20 | private: 21 | bool Init(std::vector data); 22 | bool CheckMagic() const; 23 | bool ExtractFile(const std::string& path, std::size_t index) const; 24 | ArchiveFormatInfo GetFormatInfo() const; 25 | 26 | bool is_good = false; 27 | 28 | friend class Archive; 29 | friend class InnerFAT; 30 | friend class SDMCImporter; // Needed to read config savegame 31 | }; 32 | 33 | } // namespace Core 34 | -------------------------------------------------------------------------------- /CMakeModules/DownloadExternals.cmake: -------------------------------------------------------------------------------- 1 | 2 | # This function downloads a binary library package from our external repo. 3 | # Params: 4 | # remote_path: path to the file to download, relative to the remote repository root 5 | # prefix_var: name of a variable which will be set with the path to the extracted contents 6 | function(download_bundled_external remote_path lib_name prefix_var) 7 | set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") 8 | if (NOT EXISTS "${prefix}") 9 | message(STATUS "Downloading binaries for ${lib_name}...") 10 | file(DOWNLOAD 11 | https://github.com/yuzu-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z 12 | "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS) 13 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" 14 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") 15 | endif() 16 | message(STATUS "Using bundled binaries at ${prefix}") 17 | set(${prefix_var} "${prefix}" PARENT_SCOPE) 18 | endfunction() -------------------------------------------------------------------------------- /src/frontend/helpers/frontend_common.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include "frontend/helpers/frontend_common.h" 9 | 10 | QString ReadableByteSize(qulonglong size) { 11 | static const std::array units = {QT_TR_NOOP("B"), QT_TR_NOOP("KiB"), 12 | QT_TR_NOOP("MiB"), QT_TR_NOOP("GiB"), 13 | QT_TR_NOOP("TiB"), QT_TR_NOOP("PiB")}; 14 | if (size == 0) 15 | return QStringLiteral("0"); 16 | int digit_groups = std::min(static_cast(std::log10(size) / std::log10(1024)), 17 | static_cast(units.size() - 1)); 18 | return QStringLiteral("%L1 %2") 19 | .arg(size / std::pow(1024, digit_groups), 0, 'f', 1) 20 | .arg(QObject::tr(units[digit_groups], "FrontendCommon")); 21 | } 22 | -------------------------------------------------------------------------------- /src/core/db/title_keys_bin.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_funcs.h" 10 | #include "common/common_types.h" 11 | #include "common/swap.h" 12 | 13 | namespace Core { 14 | 15 | struct TitleKeysBinHeader { 16 | u32_le num_entries; 17 | INSERT_PADDING_BYTES(12); 18 | }; 19 | static_assert(sizeof(TitleKeysBinHeader) == 16); 20 | 21 | struct TitleKeysBinEntry { 22 | u32_be common_key_index; 23 | INSERT_PADDING_BYTES(4); 24 | u64_be title_id; 25 | std::array title_key; 26 | }; 27 | static_assert(sizeof(TitleKeysBinEntry) == 32); 28 | 29 | using TitleKeysMap = std::unordered_map; 30 | class EncTitleKeysBin : public TitleKeysMap {}; 31 | 32 | // GM9 support files encTitleKeys.bin and decTitleKeys.bin. 33 | bool LoadTitleKeysBin(TitleKeysMap& out, const std::string& path); 34 | 35 | } // namespace Core 36 | -------------------------------------------------------------------------------- /src/common/progress_callback.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "common/common_types.h" 9 | 10 | namespace Common { 11 | 12 | // (current_size, total_size) 13 | using ProgressCallback = std::function; 14 | 15 | // Note on using this: it is important to ensure that the progress callback is at least 16 | // invoked once. Typically a `callback(total, total)` at the end will work fine. 17 | class ProgressCallbackWrapper { 18 | public: 19 | // (total imported size, total size) 20 | ProgressCallback Wrap(const ProgressCallback& callback); 21 | 22 | // (current content imported size, total imported size, total size) 23 | ProgressCallback Wrap(const std::function& callback); 24 | 25 | void SetCurrent(u64 current); 26 | 27 | std::size_t total_size{}; 28 | std::size_t current_done_size{}; 29 | std::size_t current_pending_size{}; 30 | }; 31 | 32 | } // namespace Common 33 | -------------------------------------------------------------------------------- /src/frontend/helpers/simple_job.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_types.h" 10 | #include "common/progress_callback.h" 11 | 12 | /** 13 | * Lightweight wrapper around QThread, for easy use with progressive jobs. 14 | */ 15 | class SimpleJob : public QThread { 16 | Q_OBJECT 17 | 18 | public: 19 | using ExecuteFunc = std::function; 20 | using AbortFunc = std::function; 21 | 22 | explicit SimpleJob(QObject* parent, ExecuteFunc execute, AbortFunc abort); 23 | ~SimpleJob() override; 24 | 25 | void run() override; 26 | void Cancel(); 27 | 28 | void StartWithProgressDialog(QWidget* widget); 29 | 30 | signals: 31 | void ProgressUpdated(u64 current, u64 total); 32 | void Completed(bool canceled); 33 | void ErrorOccured(); 34 | 35 | private: 36 | ExecuteFunc execute; 37 | AbortFunc abort; 38 | bool canceled{}; 39 | }; 40 | -------------------------------------------------------------------------------- /src/core/file_sys/signature.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_types.h" 10 | #include "common/swap.h" 11 | 12 | namespace CryptoPP { 13 | class PK_MessageAccumulator; 14 | } 15 | 16 | namespace FileUtil { 17 | class IOFile; 18 | } 19 | 20 | namespace Core { 21 | 22 | /// Consists of a signature type, a signature, and alignment to 0x40. 23 | class Signature { 24 | public: 25 | bool Load(const std::vector& file_data, std::size_t offset = 0); 26 | 27 | /// Writes signature to file. Includes the alignment 28 | bool Save(FileUtil::IOFile& file) const; 29 | 30 | std::size_t GetSize() const; 31 | 32 | /// Verifies the signature. Accepts a functor which should add the message to the accumulator 33 | bool Verify(const std::string& issuer, 34 | const std::function& func) const; 35 | 36 | u32_be type; 37 | std::vector data; 38 | }; 39 | 40 | } // namespace Core 41 | -------------------------------------------------------------------------------- /src/common/progress_callback.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include "common/progress_callback.h" 6 | 7 | namespace Common { 8 | 9 | ProgressCallback ProgressCallbackWrapper::Wrap(const ProgressCallback& callback) { 10 | current_done_size += current_pending_size; // Last content finished 11 | return [this, callback](std::size_t current, std::size_t total) { 12 | current_pending_size = total; 13 | callback(current + current_done_size, total_size); 14 | }; 15 | } 16 | 17 | ProgressCallback ProgressCallbackWrapper::Wrap(const std::function& callback) { 18 | current_done_size += current_pending_size; // Last content finished 19 | return [this, callback](std::size_t current, std::size_t total) { 20 | current_pending_size = total; 21 | callback(current, current + current_done_size, total_size); 22 | }; 23 | } 24 | 25 | void ProgressCallbackWrapper::SetCurrent(u64 current) { 26 | current_done_size = current; 27 | current_pending_size = 0; 28 | } 29 | 30 | } // namespace Common 31 | -------------------------------------------------------------------------------- /src/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(core STATIC 2 | cia_builder.cpp 3 | cia_builder.h 4 | db/seed_db.cpp 5 | db/seed_db.h 6 | db/title_db.cpp 7 | db/title_db.h 8 | db/title_keys_bin.cpp 9 | db/title_keys_bin.h 10 | file_decryptor.cpp 11 | file_decryptor.h 12 | file_sys/certificate.cpp 13 | file_sys/certificate.h 14 | file_sys/cia_common.h 15 | file_sys/config_savegame.cpp 16 | file_sys/config_savegame.h 17 | file_sys/data/data_container.cpp 18 | file_sys/data/data_container.h 19 | file_sys/data/extdata.cpp 20 | file_sys/data/extdata.h 21 | file_sys/data/inner_fat.hpp 22 | file_sys/data/savegame.cpp 23 | file_sys/data/savegame.h 24 | file_sys/ncch_container.cpp 25 | file_sys/ncch_container.h 26 | file_sys/signature.cpp 27 | file_sys/signature.h 28 | file_sys/smdh.cpp 29 | file_sys/smdh.h 30 | file_sys/ticket.cpp 31 | file_sys/ticket.h 32 | file_sys/title_metadata.cpp 33 | file_sys/title_metadata.h 34 | importer.cpp 35 | importer.h 36 | key/arithmetic128.cpp 37 | key/arithmetic128.h 38 | key/key.cpp 39 | key/key.h 40 | sdmc_decryptor.cpp 41 | sdmc_decryptor.h 42 | ) 43 | 44 | target_link_libraries(core PRIVATE common cryptopp) 45 | -------------------------------------------------------------------------------- /CMakeModules/WindowsCopyFiles.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Citra Emulator Project 2 | # Licensed under GPLv2 or any later version 3 | # Refer to the license.txt file included. 4 | 5 | # This file provides the function windows_copy_files. 6 | # This is only valid on Windows. 7 | 8 | # Include guard 9 | if(__windows_copy_files) 10 | return() 11 | endif() 12 | set(__windows_copy_files YES) 13 | 14 | # Any number of files to copy from SOURCE_DIR to DEST_DIR can be specified after DEST_DIR. 15 | # This copying happens post-build. 16 | function(windows_copy_files TARGET SOURCE_DIR DEST_DIR) 17 | # windows commandline expects the / to be \ so switch them 18 | string(REPLACE "/" "\\\\" SOURCE_DIR ${SOURCE_DIR}) 19 | string(REPLACE "/" "\\\\" DEST_DIR ${DEST_DIR}) 20 | 21 | # /NJH /NJS /NDL /NFL /NC /NS /NP - Silence any output 22 | # cmake adds an extra check for command success which doesn't work too well with robocopy 23 | # so trick it into thinking the command was successful with the || cmd /c "exit /b 0" 24 | add_custom_command(TARGET ${TARGET} POST_BUILD 25 | COMMAND if not exist ${DEST_DIR} mkdir ${DEST_DIR} 2> nul 26 | COMMAND robocopy ${SOURCE_DIR} ${DEST_DIR} ${ARGN} /NJH /NJS /NDL /NFL /NC /NS /NP || cmd /c "exit /b 0" 27 | ) 28 | endfunction() -------------------------------------------------------------------------------- /src/frontend/helpers/dpi_aware_dialog.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | class DPIAwareDialog : public QDialog { 10 | public: 11 | explicit DPIAwareDialog(QWidget* parent, int width, int height); 12 | ~DPIAwareDialog() override; 13 | 14 | protected: 15 | void showEvent(QShowEvent* event) override; 16 | 17 | // Called with two zeroes to set up content sizes that are relative to dialog size. Also called 18 | // when screen is changed, to update those sizes. 19 | virtual void SetContentSizes([[maybe_unused]] int previous_width = 0, 20 | [[maybe_unused]] int previous_height = 0){}; 21 | 22 | private: 23 | QWindow* window_handle{}; 24 | const int original_width{}; 25 | const int original_height{}; 26 | 27 | #ifndef __APPLE__ 28 | protected: 29 | void resizeEvent(QResizeEvent* event) override; 30 | 31 | private: 32 | void OnScreenChanged(); 33 | 34 | bool resized = false; // whether this dialog has been manually resized 35 | double prev_scaleX{}; 36 | double prev_scaleY{}; 37 | int prev_width{}; 38 | int prev_height{}; 39 | #endif 40 | }; 41 | -------------------------------------------------------------------------------- /src/core/db/title_keys_bin.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include "common/file_util.h" 6 | #include "core/db/title_keys_bin.h" 7 | 8 | namespace Core { 9 | 10 | bool LoadTitleKeysBin(TitleKeysMap& out, const std::string& path) { 11 | FileUtil::IOFile file(path, "rb"); 12 | if (!file) { 13 | LOG_ERROR(Core, "Could not open file {}", path); 14 | return false; 15 | } 16 | 17 | TitleKeysBinHeader header; 18 | if (file.ReadBytes(&header, sizeof(header)) != sizeof(header)) { 19 | LOG_ERROR(Core, "Could not read header from {}", path); 20 | return false; 21 | } 22 | 23 | for (std::size_t i = 0; i < header.num_entries; ++i) { 24 | TitleKeysBinEntry entry; 25 | if (file.ReadBytes(&entry, sizeof(entry)) != sizeof(entry)) { 26 | LOG_ERROR(Core, "Could not read entry {} from {}", i, path); 27 | return false; 28 | } 29 | out.emplace(entry.title_id, entry); 30 | } 31 | 32 | if (file.Tell() != file.GetSize()) { 33 | LOG_ERROR(Core, "File {} has redundant data, may be corrupted", path); 34 | return false; 35 | } 36 | return true; 37 | } 38 | 39 | } // namespace Core 40 | -------------------------------------------------------------------------------- /src/core/file_sys/config_savegame.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include "common/logging/log.h" 8 | #include "core/file_sys/config_savegame.h" 9 | 10 | namespace Core { 11 | 12 | ConfigSavegame::ConfigSavegame() = default; 13 | ConfigSavegame::~ConfigSavegame() = default; 14 | 15 | bool ConfigSavegame::Init(std::vector data) { 16 | if (data.size() != ConfigSavegameSize) { 17 | LOG_ERROR(Core, "Config savegame is of incorrect size"); 18 | return false; 19 | } 20 | std::memcpy(&header, data.data(), sizeof(header)); 21 | return true; 22 | } 23 | 24 | u8 ConfigSavegame::GetSystemLanguage() const { 25 | static constexpr u32 LanguageBlockID = 0x000A0002; 26 | 27 | const auto iter = std::find_if( 28 | header.block_entries.begin(), header.block_entries.end(), 29 | [](const ConfigSavegameBlockEntry& entry) { return entry.block_id == LanguageBlockID; }); 30 | if (iter == header.block_entries.end()) { 31 | LOG_ERROR(Core, "Cannot find Language config block, returning default (English)"); 32 | return 1; 33 | } 34 | return static_cast(iter->offset_or_data); 35 | } 36 | 37 | } // namespace Core 38 | -------------------------------------------------------------------------------- /src/common/scope_exit.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Citra Emulator Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "common/common_funcs.h" 9 | 10 | namespace detail { 11 | template 12 | struct ScopeExitHelper { 13 | explicit ScopeExitHelper(Func&& func) : func(std::move(func)) {} 14 | ~ScopeExitHelper() { 15 | func(); 16 | } 17 | 18 | Func func; 19 | }; 20 | 21 | template 22 | ScopeExitHelper ScopeExit(Func&& func) { 23 | return ScopeExitHelper(std::forward(func)); 24 | } 25 | } // namespace detail 26 | 27 | /** 28 | * This macro allows you to conveniently specify a block of code that will run on scope exit. Handy 29 | * for doing ad-hoc clean-up tasks in a function with multiple returns. 30 | * 31 | * Example usage: 32 | * \code 33 | * const int saved_val = g_foo; 34 | * g_foo = 55; 35 | * SCOPE_EXIT({ g_foo = saved_val; }); 36 | * 37 | * if (Bar()) { 38 | * return 0; 39 | * } else { 40 | * return 20; 41 | * } 42 | * \endcode 43 | */ 44 | #define SCOPE_EXIT(body) \ 45 | auto CONCAT2(scope_exit_helper_, __LINE__) = ::detail::ScopeExit([&]() body) 46 | -------------------------------------------------------------------------------- /src/frontend/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | 11 | 13 | CFBundleIdentifier 14 | cn.com.zhupengfei.threeSD 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | 19 | CFBundleName 20 | threeSD 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 29 | CSResourcesFileMapped 30 | 31 | LSRequiresCarbon 32 | 33 | NSHumanReadableCopyright 34 | 35 | NSPrincipalClass 36 | NSApplication 37 | NSHighResolutionCapable 38 | True 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/common/misc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef _WIN32 10 | #include 11 | #else 12 | #include 13 | #endif 14 | 15 | #include "common/common_funcs.h" 16 | 17 | // Generic function to get last error message. 18 | // Call directly after the command or use the error num. 19 | // This function might change the error code. 20 | std::string GetLastErrorMsg() { 21 | static const std::size_t buff_size = 255; 22 | char err_str[buff_size]; 23 | 24 | #if defined(_WIN32) 25 | FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), 26 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); 27 | #else 28 | auto ret = strerror_r(errno, err_str, buff_size); 29 | if constexpr (std::is_same_v) { 30 | // GNU specific 31 | // This is a workaround for XSI-compliant variant; this should always be safe. 32 | const char* str = reinterpret_cast(ret); 33 | return std::string(str, strlen(str)); 34 | } 35 | #endif 36 | 37 | return std::string(err_str, strnlen(err_str, buff_size)); 38 | } 39 | -------------------------------------------------------------------------------- /src/frontend/title_info_dialog.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "core/file_sys/smdh.h" 9 | #include "frontend/helpers/dpi_aware_dialog.h" 10 | 11 | namespace Core { 12 | struct Config; 13 | struct ContentSpecifier; 14 | class NCCHContainer; 15 | class SDMCImporter; 16 | class TitleMetadata; 17 | } // namespace Core 18 | 19 | namespace Ui { 20 | class TitleInfoDialog; 21 | } 22 | 23 | class TitleInfoDialog : public DPIAwareDialog { 24 | Q_OBJECT 25 | 26 | public: 27 | explicit TitleInfoDialog(QWidget* parent, Core::SDMCImporter& importer, 28 | Core::ContentSpecifier specifier); 29 | ~TitleInfoDialog(); 30 | 31 | private: 32 | void LoadInfo(); 33 | 34 | void LoadEncryption(Core::NCCHContainer& ncch); 35 | void LoadIcons(); 36 | void InitializeLanguageComboBox(); 37 | void InitializeChecks(Core::TitleMetadata& tmd); 38 | 39 | void SaveIcon(bool large); 40 | void UpdateNames(); 41 | void ExecuteContentsCheck(); 42 | 43 | std::unique_ptr ui; 44 | 45 | Core::SDMCImporter& importer; 46 | const Core::ContentSpecifier specifier; 47 | Core::SMDH smdh{}; 48 | bool contents_check_result = false; 49 | }; 50 | -------------------------------------------------------------------------------- /src/frontend/helpers/rate_limited_progress_dialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include "frontend/helpers/rate_limited_progress_dialog.h" 6 | 7 | RateLimitedProgressDialog::RateLimitedProgressDialog(const QString& label_text, 8 | const QString& cancel_button_text, int minimum, 9 | int maximum, QWidget* parent) 10 | : QProgressDialog(label_text, cancel_button_text, minimum, maximum, parent) { 11 | 12 | setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); 13 | setWindowModality(Qt::WindowModal); 14 | setMinimumDuration(0); 15 | setValue(0); 16 | } 17 | 18 | RateLimitedProgressDialog::~RateLimitedProgressDialog() = default; 19 | 20 | void RateLimitedProgressDialog::Update(int progress, const QString& label_text) { 21 | if (progress == maximum()) { // always set the maximum 22 | setValue(progress); 23 | return; 24 | } 25 | 26 | const auto current_time = std::chrono::steady_clock::now(); 27 | if (current_time - last_update_time < MinimumInterval) { 28 | return; 29 | } 30 | 31 | setValue(progress); 32 | setLabelText(label_text); 33 | last_update_time = current_time; 34 | } 35 | -------------------------------------------------------------------------------- /src/common/common_paths.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 2 | // 2019 threeSD Project 3 | // Licensed under GPLv2 or any later version 4 | // Refer to the license.txt file included. 5 | 6 | #pragma once 7 | 8 | // Directory separators, do we need this? 9 | #define DIR_SEP "/" 10 | #define DIR_SEP_CHR '/' 11 | 12 | // Citra's path names 13 | 14 | // The user data dir 15 | #define ROOT_DIR "." 16 | #define USERDATA_DIR "user" 17 | #ifdef USER_DIR 18 | #define EMU_DATA_DIR USER_DIR 19 | #else 20 | #ifdef _WIN32 21 | #define EMU_DATA_DIR "Citra" 22 | #else 23 | #define EMU_DATA_DIR "citra-emu" 24 | #endif 25 | #endif 26 | #define CITRA_EXECUTABLE "citra-qt" 27 | 28 | #define LOG_FILE "threeSD_log.txt" 29 | 30 | // Subdirs in the User dir returned by GetUserPath(UserPath::UserDir) 31 | #define CONFIG_DIR "config" 32 | #define CACHE_DIR "cache" 33 | #define SDMC_DIR "sdmc" 34 | #define NAND_DIR "nand" 35 | #define SYSDATA_DIR "sysdata" 36 | #define LOG_DIR "log" 37 | #define CHEATS_DIR "cheats" 38 | #define DLL_DIR "external_dlls" 39 | 40 | #define BOOTROM9 "boot9.bin" 41 | #define SECRET_SECTOR "sector0x96.bin" 42 | #define MOVABLE_SED "movable.sed" 43 | #define SEED_DB "seeddb.bin" 44 | #define AES_KEYS "aes_keys.txt" 45 | #define CERTS_DB "certs.db" 46 | #define TITLE_DB "title.db" 47 | #define TICKET_DB "ticket.db" 48 | #define ENC_TITLE_KEYS_BIN "encTitleKeys.bin" 49 | -------------------------------------------------------------------------------- /src/frontend/utilities.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "frontend/helpers/dpi_aware_dialog.h" 10 | 11 | class QWidget; 12 | 13 | namespace Ui { 14 | class UtilitiesDialog; 15 | } 16 | 17 | class UtilitiesDialog : public DPIAwareDialog { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit UtilitiesDialog(QWidget* parent); 22 | ~UtilitiesDialog() override; 23 | 24 | private: 25 | /** 26 | * Open a dialog to ask the user for source and destination paths. 27 | * @return {source, destination} 28 | */ 29 | std::pair GetFilePaths(bool source_is_dir, bool destination_is_dir); 30 | 31 | bool LoadSDKeys(); 32 | 33 | void ShowProgressDialog(std::function operation); 34 | 35 | /** 36 | * Gets SDMC root, and relative source path. 37 | * @return {success, sdmc root, relative source path} 38 | */ 39 | std::tuple GetSDMCRoot(const QString& source); 40 | 41 | void SDDecryptionTool(); 42 | void SaveDataExtractionTool(); 43 | void ExtdataExtractionTool(); 44 | void RomFSExtractionTool(); 45 | void ShowResult(); 46 | 47 | bool result = false; /// Result of the last operation. 48 | std::unique_ptr ui; 49 | }; 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | threeSD 2 | ======== 3 | 4 | threeSD is a tool to help you import data from a Nintendo 3DS SD Card for [Citra](https://citra-emu.org), or dump CXIs and build CIAs, all directly on your PC! 5 | 6 | ## Advantages 7 | 8 | Compared with the previous method of using [GodMode9](https://github.com/d0k3/GodMode9) to dump games, and [Checkpoint](https://github.com/FlagBrew/Checkpoint) to dump saves, threeSD offers the following advantages: 9 | 10 | * Simple to use. You can import everything at once, including applications, updates, DLCs, saves, extra datas as well as necessary system datas. The UI is very simple, but usable and intutive. On your 3DS you will only need to run a GM9 script and everything is ready. 11 | * Fast. A PC's processing power and I/O speeds are obviously much better than a 3DS. In my test, importing all 20+ GiB of content only took about 20 minutes. 12 | * Does not require additional SD card space. `Dumping` a content requires space on your SD card. `Importing` it doesn't. 13 | 14 | ## Usage Instructions 15 | 16 | Please refer to the [wiki](https://github.com/zhaowenlan1779/threeSD/wiki/Quickstart-Guide). 17 | 18 | ## TODO 19 | 20 | * Clean up core/importer.cpp by removing those 00000000...000000s there with FileUtil functions 21 | * UI improvements 22 | * Better error messages 23 | * Beautiful icons 24 | * Bug fixes 25 | * Clear all the `TODO`s in the code 26 | * Wireless transfer (probably FTP?) 27 | * but: slow, complex 28 | -------------------------------------------------------------------------------- /src/core/file_sys/data/extdata.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include "core/file_sys/data/inner_fat.hpp" 8 | 9 | namespace Core { 10 | 11 | class SDMCDecryptor; 12 | 13 | class Extdata final : public Archive { 14 | public: 15 | /** 16 | * Loads an SD extdata folder. 17 | * @param data_path Path to the ENCRYPTED SD extdata folder, relative to decryptor root 18 | * @param decryptor Const reference to the SDMCDecryptor. 19 | */ 20 | explicit Extdata(std::string data_path, const SDMCDecryptor& decryptor); 21 | 22 | /** 23 | * Loads an Extdata folder without encryption. 24 | * @param data_path Path to the DECRYPTED extdata folder 25 | */ 26 | explicit Extdata(std::string data_path); 27 | 28 | ~Extdata(); 29 | 30 | bool IsGood() const; 31 | bool Extract(std::string path) const; 32 | 33 | private: 34 | bool Init(); 35 | bool CheckMagic() const; 36 | std::vector ReadFile(const std::string& path) const; 37 | bool ExtractFile(const std::string& path, u32 index) const; 38 | ArchiveFormatInfo GetFormatInfo() const; 39 | 40 | bool is_good = false; 41 | std::string data_path; 42 | const SDMCDecryptor* decryptor = nullptr; 43 | bool use_decryptor = true; 44 | 45 | friend class Archive; 46 | friend class InnerFAT; 47 | }; 48 | 49 | } // namespace Core 50 | -------------------------------------------------------------------------------- /src/frontend/select_nand_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SelectNandDialog 4 | 5 | 6 | Select NAND 7 | 8 | 9 | 10 | 0 11 | 0 12 | 400 13 | 0 14 | 15 | 16 | 17 | 18 | 19 | 20 | Select primary NAND: 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | true 31 | 32 | 33 | This determines which NAND's System Titles and System Data will be used. 34 | 35 | 36 | 37 | 38 | 39 | 40 | Qt::Vertical 41 | 42 | 43 | 44 | 45 | 46 | 47 | QDialogButtonBox::Ok|QDialogButtonBox::Cancel 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /CMakeModules/CopyQt5Deps.cmake: -------------------------------------------------------------------------------- 1 | function(copy_Qt5_deps target_dir) 2 | include(WindowsCopyFiles) 3 | set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$/") 4 | set(Qt5_DLL_DIR "${Qt5_DIR}/../../../bin") 5 | set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/") 6 | set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/") 7 | # set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/") 8 | set(PLATFORMS ${DLL_DEST}platforms/) 9 | set(STYLES ${DLL_DEST}styles/) 10 | # set(IMAGEFORMATS ${DLL_DEST}imageformats/) 11 | windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST} 12 | icudt*.dll 13 | icuin*.dll 14 | icuuc*.dll 15 | Qt5Core$<$:d>.* 16 | Qt5Gui$<$:d>.* 17 | Qt5Widgets$<$:d>.* 18 | ) 19 | windows_copy_files(threeSD ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$:d>.*) 20 | windows_copy_files(threeSD ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$:d>.*) 21 | # windows_copy_files(${target_dir} ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} 22 | # qgif$<$:d>.dll 23 | # qicns$<$:d>.dll 24 | # qico$<$:d>.dll 25 | # qjpeg$<$:d>.dll 26 | # qsvg$<$:d>.dll 27 | # qtga$<$:d>.dll 28 | # qtiff$<$:d>.dll 29 | # qwbmp$<$:d>.dll 30 | # qwebp$<$:d>.dll 31 | # ) 32 | endfunction(copy_Qt5_deps) 33 | -------------------------------------------------------------------------------- /src/core/file_sys/certificate.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "common/common_funcs.h" 11 | #include "common/swap.h" 12 | #include "core/file_sys/signature.h" 13 | 14 | namespace CryptoPP { 15 | class Integer; 16 | } 17 | 18 | namespace FileUtil { 19 | class IOFile; 20 | } 21 | 22 | namespace Core { 23 | 24 | enum PublicKeyType : u32 { 25 | RSA_4096 = 0, 26 | RSA_2048 = 1, 27 | ECC = 2, 28 | }; 29 | 30 | class Certificate { 31 | public: 32 | struct Body { 33 | std::array issuer; 34 | u32_be key_type; 35 | std::array name; 36 | u32_be expiration_time; 37 | }; 38 | static_assert(sizeof(Body) == 0x88); 39 | 40 | bool Load(std::vector file_data, std::size_t offset = 0); 41 | bool Save(FileUtil::IOFile& file) const; 42 | std::size_t GetSize() const; 43 | 44 | /// (modulus, exponent) 45 | std::pair GetRSAPublicKey() const; 46 | 47 | Signature signature; 48 | Body body; 49 | std::vector public_key; 50 | }; 51 | 52 | struct CertsDBHeader { 53 | u32_le magic; 54 | INSERT_PADDING_BYTES(4); 55 | u32_le size; 56 | INSERT_PADDING_BYTES(4); 57 | }; 58 | static_assert(sizeof(CertsDBHeader) == 0x10); 59 | 60 | namespace Certs { 61 | 62 | bool Load(const std::string& path); 63 | bool IsLoaded(); 64 | void Clear(); 65 | const Certificate& Get(const std::string& name); 66 | bool Exists(const std::string& name); 67 | 68 | } // namespace Certs 69 | 70 | } // namespace Core 71 | -------------------------------------------------------------------------------- /src/core/key/arithmetic128.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Citra Emulator Project / 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include "core/key/arithmetic128.h" 8 | 9 | namespace Core::Key { 10 | 11 | AESKey Lrot128(const AESKey& in, u32 rot) { 12 | AESKey out; 13 | rot %= 128; 14 | const u32 byte_shift = rot / 8; 15 | const u32 bit_shift = rot % 8; 16 | 17 | for (u32 i = 0; i < 16; i++) { 18 | const u32 wrap_index_a = (i + byte_shift) % 16; 19 | const u32 wrap_index_b = (i + byte_shift + 1) % 16; 20 | out[i] = ((in[wrap_index_a] << bit_shift) | (in[wrap_index_b] >> (8 - bit_shift))) & 0xFF; 21 | } 22 | return out; 23 | } 24 | 25 | AESKey Add128(const AESKey& a, const AESKey& b) { 26 | AESKey out; 27 | u32 carry = 0; 28 | u32 sum = 0; 29 | 30 | for (int i = 15; i >= 0; i--) { 31 | sum = a[i] + b[i] + carry; 32 | carry = sum >> 8; 33 | out[i] = static_cast(sum & 0xff); 34 | } 35 | 36 | return out; 37 | } 38 | 39 | AESKey Add128(const AESKey& a, u64 b) { 40 | AESKey out = a; 41 | u32 carry = 0; 42 | u32 sum = 0; 43 | 44 | for (int i = 15; i >= 8; i--) { 45 | sum = a[i] + static_cast((b >> ((15 - i) * 8)) & 0xff) + carry; 46 | carry = sum >> 8; 47 | out[i] = static_cast(sum & 0xff); 48 | } 49 | 50 | return out; 51 | } 52 | 53 | AESKey Xor128(const AESKey& a, const AESKey& b) { 54 | AESKey out; 55 | std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>()); 56 | return out; 57 | } 58 | 59 | } // namespace Core::Key 60 | -------------------------------------------------------------------------------- /src/core/file_sys/ticket.h: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Citra Emulator Project / 2020 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_funcs.h" 10 | #include "common/common_types.h" 11 | #include "common/swap.h" 12 | #include "core/file_sys/signature.h" 13 | 14 | namespace FileUtil { 15 | class IOFile; 16 | } 17 | 18 | namespace Core { 19 | 20 | class Ticket { 21 | public: 22 | #pragma pack(push, 1) 23 | struct Body { 24 | std::array issuer; 25 | std::array ecc_public_key; 26 | u8 version; 27 | u8 ca_crl_version; 28 | u8 signer_crl_version; 29 | std::array title_key; 30 | INSERT_PADDING_BYTES(1); 31 | u64_be ticket_id; 32 | u32_be console_id; 33 | u64_be title_id; 34 | INSERT_PADDING_BYTES(2); 35 | u16_be ticket_title_version; 36 | INSERT_PADDING_BYTES(8); 37 | u8 license_type; 38 | u8 common_key_index; 39 | INSERT_PADDING_BYTES(0x2A); 40 | u32_be eshop_account_id; 41 | INSERT_PADDING_BYTES(1); 42 | u8 audit; 43 | INSERT_PADDING_BYTES(0x42); 44 | std::array limits; 45 | }; 46 | static_assert(sizeof(Body) == 0x164, "Ticket body structure size is wrong"); 47 | #pragma pack(pop) 48 | 49 | bool Load(const std::vector file_data, std::size_t offset = 0); 50 | bool Save(FileUtil::IOFile& file) const; 51 | bool ValidateSignature() const; 52 | std::size_t GetSize() const; 53 | 54 | Signature signature; 55 | Body body; 56 | std::vector content_index; 57 | }; 58 | 59 | Ticket BuildFakeTicket(u64 title_id); 60 | 61 | } // namespace Core 62 | -------------------------------------------------------------------------------- /src/frontend/select_nand_dialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include "frontend/select_nand_dialog.h" 8 | #include "ui_select_nand_dialog.h" 9 | 10 | SelectNandDialog::SelectNandDialog(QWidget* parent, 11 | const std::vector& nands_) 12 | : QDialog(parent), ui(std::make_unique()), nands(nands_) { 13 | 14 | ui->setupUi(this); 15 | setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); 16 | 17 | // Initialize radio buttons 18 | for (std::size_t i = 0; i < nands.size(); ++i) { 19 | const auto& nand = nands[i]; 20 | 21 | // TODO: this is currently hardcoded 22 | QString display_name; 23 | if (nand.nand_name == Core::SysNANDName) { 24 | display_name = tr("SysNAND"); 25 | } else { 26 | const std::string emu_offset = nand.nand_name.substr(Core::EmuNANDPrefix.size()); 27 | display_name = tr("EmuNAND at 0x%1").arg(QString::fromStdString(emu_offset)); 28 | } 29 | 30 | QRadioButton* button = new QRadioButton(display_name); 31 | if (i == 0) { // Select one by default 32 | button->setChecked(true); 33 | } 34 | connect(button, &QRadioButton::toggled, this, [this, i](bool checked) { 35 | if (checked) { 36 | result = static_cast(i); 37 | } 38 | }); 39 | ui->contentLayout->addWidget(button); 40 | } 41 | 42 | connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 43 | connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 44 | } 45 | 46 | SelectNandDialog::~SelectNandDialog() = default; 47 | 48 | int SelectNandDialog::GetResult() const { 49 | return result; 50 | } 51 | -------------------------------------------------------------------------------- /src/core/file_sys/config_savegame.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Citra Emulator Project / 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/swap.h" 10 | 11 | namespace Core { 12 | 13 | constexpr u32 ConfigSavegameSize = 0x8000; 14 | /// The maximum number of block entries that can exist in the config file 15 | constexpr u32 ConfigSavegameMaxEntries = 1479; 16 | 17 | /// Block header in the config savedata file 18 | struct ConfigSavegameBlockEntry { 19 | u32_le block_id; ///< The id of the current block 20 | u32_le offset_or_data; ///< This is the absolute offset to the block data if the size is greater 21 | /// than 4 bytes, otherwise it contains the data itself 22 | u16_le size; ///< The size of the block 23 | u16_le flags; ///< The flags of the block, possibly used for access control 24 | }; 25 | 26 | struct ConfigSavegameHeader { 27 | u16_le total_entries; ///< The total number of set entries in the config file 28 | u16_le data_entries_offset; ///< The offset where the data for the blocks start 29 | /// The block headers, the maximum possible value is 1479 as per hardware 30 | std::array block_entries; 31 | u32_le unknown; ///< This field is unknown, possibly padding, 0 has been observed in hardware 32 | }; 33 | static_assert(sizeof(ConfigSavegameHeader) == 0x455C, 34 | "Config Savegame header must be exactly 0x455C bytes"); 35 | 36 | // This is not the config savegame itself, but rather the `config` file inside. 37 | class ConfigSavegame { 38 | public: 39 | explicit ConfigSavegame(); 40 | ~ConfigSavegame(); 41 | 42 | bool Init(std::vector data); 43 | u8 GetSystemLanguage() const; 44 | 45 | private: 46 | ConfigSavegameHeader header; 47 | }; 48 | 49 | } // namespace Core 50 | -------------------------------------------------------------------------------- /src/frontend/main.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainDialog 4 | 5 | 6 | threeSD 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Select your SD card root, or manually browse when not detected. 15 | 16 | 17 | 18 | 19 | 20 | 21 | Qt::Horizontal 22 | 23 | 24 | 25 | 26 | 27 | 28 | Utilities... 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Path 39 | 40 | 41 | 42 | 43 | ID 44 | 45 | 46 | 47 | 48 | Status 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Qt::Horizontal 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | QDialogButtonBox::Ok|QDialogButtonBox::Reset 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/frontend/select_files_dialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include "frontend/select_files_dialog.h" 9 | #include "ui_select_files_dialog.h" 10 | 11 | SelectFilesDialog::SelectFilesDialog(QWidget* parent, bool source_is_dir_, bool destination_is_dir_) 12 | : DPIAwareDialog(parent, 480, 96), ui(std::make_unique()), 13 | source_is_dir(source_is_dir_), destination_is_dir(destination_is_dir_) { 14 | 15 | ui->setupUi(this); 16 | 17 | connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { 18 | if (ui->source->text().isEmpty() || ui->destination->text().isEmpty()) { 19 | QMessageBox::warning(this, tr("Warning"), tr("Please fill in all the fields.")); 20 | return; 21 | } 22 | accept(); 23 | }); 24 | connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &SelectFilesDialog::reject); 25 | 26 | connect(ui->sourceExplore, &QToolButton::clicked, [this] { 27 | QString path; 28 | if (source_is_dir) { 29 | path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); 30 | } else { 31 | path = QFileDialog::getOpenFileName(this, tr("Select File")); 32 | } 33 | if (!path.isEmpty()) { 34 | ui->source->setText(path); 35 | } 36 | }); 37 | connect(ui->destinationExplore, &QToolButton::clicked, [this] { 38 | QString path; 39 | if (destination_is_dir) { 40 | path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); 41 | } else { 42 | path = QFileDialog::getSaveFileName(this, tr("Select File")); 43 | } 44 | if (!path.isEmpty()) { 45 | ui->destination->setText(path); 46 | } 47 | }); 48 | } 49 | 50 | SelectFilesDialog::~SelectFilesDialog() = default; 51 | 52 | std::pair SelectFilesDialog::GetResults() const { 53 | return {ui->source->text(), ui->destination->text()}; 54 | } 55 | -------------------------------------------------------------------------------- /src/frontend/helpers/multi_job.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "common/progress_callback.h" 11 | #include "core/importer.h" 12 | 13 | class MultiJob : public QThread { 14 | Q_OBJECT 15 | 16 | public: 17 | using ExecuteFunc = std::function; 19 | using AbortFunc = std::function; 20 | // (content, error log) 21 | using FailedContentList = std::vector>; 22 | 23 | explicit MultiJob(QObject* parent, Core::SDMCImporter& importer, 24 | std::vector contents, ExecuteFunc execute_func, 25 | AbortFunc abort_func); 26 | ~MultiJob() override; 27 | 28 | void run() override; 29 | void Cancel(); 30 | 31 | FailedContentList GetFailedContents() const; 32 | 33 | signals: 34 | /** 35 | * Called when progress is updated on the current content. 36 | * @param current_imported_size Imported size of the current content. 37 | * @param total_imported_size Total imported size taking all previous contents into 38 | * consideration. 39 | * @param eta ETA in seconds, 0 when not determined. 40 | */ 41 | void ProgressUpdated(u64 current_imported_size, u64 total_imported_size, int eta); 42 | 43 | /// Dumping of a content has been finished, go on to the next. Called at start as well. 44 | void NextContent(std::size_t count, u64 total_imported_size, 45 | const Core::ContentSpecifier& next_content, int eta); 46 | 47 | void Completed(); 48 | 49 | private: 50 | std::atomic_bool cancelled{false}; 51 | Core::SDMCImporter& importer; 52 | std::vector contents; 53 | FailedContentList failed_contents; 54 | ExecuteFunc execute_func; 55 | AbortFunc abort_func; 56 | }; 57 | 58 | Q_DECLARE_METATYPE(Core::ContentSpecifier) 59 | -------------------------------------------------------------------------------- /src/frontend/select_files_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SelectFilesDialog 4 | 5 | 6 | Select Files 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Source: 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 0 26 | 0 27 | 28 | 29 | 30 | ... 31 | 32 | 33 | 34 | 35 | 36 | 37 | Destination: 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 0 49 | 0 50 | 51 | 52 | 53 | ... 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | QDialogButtonBox::Ok|QDialogButtonBox::Cancel 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/common/logging/log.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #ifdef _WIN32 8 | #include // For _SH_DENYWR 9 | #endif 10 | 11 | #include "common/common_paths.h" 12 | #include "common/file_util.h" 13 | #include "common/logging/log.h" 14 | #include "common/string_util.h" 15 | 16 | namespace Common::Logging { 17 | 18 | std::uint64_t GetLoggingTime() { 19 | static auto time_origin = std::chrono::steady_clock::now(); 20 | return std::chrono::duration_cast(std::chrono::steady_clock::now() - 21 | time_origin) 22 | .count(); 23 | } 24 | 25 | static FileUtil::IOFile g_log_file; 26 | void InitializeLogging() { 27 | #ifdef __WIN32 28 | g_log_file.Open(FileUtil::GetExeDirectory() + DIR_SEP LOG_FILE, "w", _SH_DENYWR); 29 | #elif __APPLE__ 30 | g_log_file.Open(FileUtil::GetXDGDirectory("XDG_DATA_HOME") + DIR_SEP LOG_FILE, "w"); 31 | #else 32 | g_log_file.Open(ROOT_DIR DIR_SEP LOG_FILE, "w"); 33 | #endif 34 | } 35 | 36 | static std::array g_error_buffer{}; 37 | static int g_error_buffer_pos = 0; 38 | 39 | void WriteLog(Entry entry) { 40 | // stderr 41 | fmt::print(stderr, entry.style, entry.message); 42 | 43 | // log file 44 | if (g_log_file.IsOpen()) { 45 | g_log_file.WriteString(entry.message); 46 | if (entry.level >= Level::Error) { 47 | g_log_file.Flush(); // Do not flush the file too often 48 | } 49 | } 50 | 51 | // log buffer 52 | if (entry.level >= Level::Error) { 53 | g_error_buffer[g_error_buffer_pos] = std::move(entry); 54 | g_error_buffer_pos = (g_error_buffer_pos + 1) % g_error_buffer.size(); 55 | } 56 | } 57 | 58 | std::string GetLastErrors() { 59 | std::string output; 60 | for (std::size_t i = 0; i < g_error_buffer.size(); ++i) { 61 | const std::size_t pos = (g_error_buffer_pos + i) % g_error_buffer.size(); 62 | if (g_error_buffer[pos].level != Level::Invalid) { 63 | output.append(g_error_buffer[pos].message); 64 | } 65 | } 66 | return output; 67 | } 68 | 69 | } // namespace Common::Logging 70 | -------------------------------------------------------------------------------- /src/core/key/key.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Citra Emulator Project / 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "common/common_types.h" 11 | 12 | namespace Core::Key { 13 | 14 | enum KeySlotID : std::size_t { 15 | 16 | // Used to decrypt the SSL client cert/private-key stored in ClCertA. 17 | SSLKey = 0x0D, 18 | 19 | // AES keyslots used to decrypt NCCH 20 | NCCHSecure1 = 0x2C, 21 | NCCHSecure2 = 0x25, 22 | NCCHSecure3 = 0x18, 23 | NCCHSecure4 = 0x1B, 24 | 25 | // AES Keyslot used to generate the UDS data frame CCMP key. 26 | UDSDataKey = 0x2D, 27 | 28 | // AES Keyslot used to encrypt the BOSS container data. 29 | BOSSDataKey = 0x38, 30 | 31 | // AES Keyslot used to calculate DLP data frame checksum. 32 | DLPDataKey = 0x39, 33 | 34 | // AES Keyslot used to generate the StreetPass CCMP key. 35 | CECDDataKey = 0x2E, 36 | 37 | // AES Keyslot used by the friends module. 38 | FRDKey = 0x36, 39 | 40 | // AES Keyslot used by the NFC module. 41 | NFCKey = 0x39, 42 | 43 | // AES keyslot used for APT:Wrap/Unwrap functions 44 | APTWrap = 0x31, 45 | 46 | // Console-unique AES keyslot used to encrypt all data in the "Nintendo 3DS//" folder 47 | SDKey = 0x34, 48 | 49 | // AES keyslot used for decrypting ticket title key 50 | TicketCommonKey = 0x3D, 51 | 52 | MaxKeySlotID = 0x40, 53 | }; 54 | 55 | constexpr std::size_t AES_BLOCK_SIZE = 16; 56 | 57 | using AESKey = std::array; 58 | 59 | std::string KeyToString(const AESKey& key); 60 | 61 | void LoadBootromKeys(const std::string& path); 62 | void LoadMovableSedKeys(const std::string& path); 63 | void ClearKeys(); 64 | 65 | void SetKeyX(std::size_t slot_id, const AESKey& key); 66 | void SetKeyY(std::size_t slot_id, const AESKey& key); 67 | void SetNormalKey(std::size_t slot_id, const AESKey& key); 68 | 69 | bool IsNormalKeyAvailable(std::size_t slot_id); 70 | AESKey GetNormalKey(std::size_t slot_id); 71 | 72 | // For importing aes_keys.txt 73 | AESKey GetKeyX(std::size_t slot_id); 74 | 75 | void SelectCommonKeyIndex(u8 index); 76 | 77 | } // namespace Core::Key 78 | -------------------------------------------------------------------------------- /src/core/sdmc_decryptor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_types.h" 10 | #include "common/file_util.h" 11 | #include "common/logging/log.h" 12 | #include "core/file_decryptor.h" 13 | 14 | namespace Core { 15 | 16 | class SDMCDecryptor { 17 | public: 18 | /** 19 | * Initializes the decryptor. 20 | * @param root_folder Path to the "Nintendo 3DS//" folder. 21 | */ 22 | explicit SDMCDecryptor(const std::string& root_folder); 23 | 24 | ~SDMCDecryptor(); 25 | 26 | /** 27 | * Decrypts a file from the SD card and writes it into another file. 28 | * This function blocks, but can be aborted with the Abort() function (would return false) 29 | * 30 | * @param source Path to the file relative to the root folder, starting with "/". 31 | * @param destination Path to the destination file. 32 | * @return true on success, false otherwise 33 | */ 34 | bool DecryptAndWriteFile( 35 | const std::string& source, const std::string& destination, 36 | const Common::ProgressCallback& callback = [](u64, u64) {}); 37 | 38 | void Abort(); 39 | 40 | /** 41 | * Decrypts a file and reads it into a vector. 42 | * @param source Path to the file relative to the root folder, starting with "/". 43 | */ 44 | std::vector DecryptFile(const std::string& source) const; 45 | 46 | private: 47 | std::string root_folder; 48 | FileDecryptor file_decryptor; 49 | }; 50 | 51 | /// Interface for reading an SDMC file like a normal IOFile. This is read-only. 52 | class SDMCFile : public FileUtil::IOFile { 53 | public: 54 | SDMCFile(std::string root_folder, const std::string& filename, const char openmode[], 55 | int flags = 0); 56 | 57 | ~SDMCFile() override; 58 | 59 | std::size_t Read(char* data, std::size_t length) override; 60 | std::size_t Write(const char* data, std::size_t length) override; 61 | bool Seek(s64 off, int origin) override; 62 | 63 | private: 64 | void DecryptData(u8* data, std::size_t size); 65 | 66 | struct Impl; 67 | std::unique_ptr impl; 68 | }; 69 | 70 | } // namespace Core 71 | -------------------------------------------------------------------------------- /src/common/common_types.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2005-2012 Gekko Emulator 3 | * 4 | * @file common_types.h 5 | * @author ShizZy 6 | * @date 2012-02-11 7 | * @brief Common types used throughout the project 8 | * 9 | * @section LICENSE 10 | * This program is free software; you can redistribute it and/or 11 | * modify it under the terms of the GNU General Public License as 12 | * published by the Free Software Foundation; either version 2 of 13 | * the License, or (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, but 16 | * WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | * General Public License for more details at 19 | * http://www.gnu.org/copyleft/gpl.html 20 | * 21 | * Official project repository can be found at: 22 | * http://code.google.com/p/gekko-gc-emu/ 23 | */ 24 | 25 | #pragma once 26 | 27 | #include 28 | 29 | #ifdef _MSC_VER 30 | #ifndef __func__ 31 | #define __func__ __FUNCTION__ 32 | #endif 33 | #endif 34 | 35 | typedef std::uint8_t u8; ///< 8-bit unsigned byte 36 | typedef std::uint16_t u16; ///< 16-bit unsigned short 37 | typedef std::uint32_t u32; ///< 32-bit unsigned word 38 | typedef std::uint64_t u64; ///< 64-bit unsigned int 39 | 40 | typedef std::int8_t s8; ///< 8-bit signed byte 41 | typedef std::int16_t s16; ///< 16-bit signed short 42 | typedef std::int32_t s32; ///< 32-bit signed word 43 | typedef std::int64_t s64; ///< 64-bit signed int 44 | 45 | typedef float f32; ///< 32-bit floating point 46 | typedef double f64; ///< 64-bit floating point 47 | 48 | // TODO: It would be nice to eventually replace these with strong types that prevent accidental 49 | // conversion between each other. 50 | typedef u32 VAddr; ///< Represents a pointer in the userspace virtual address space. 51 | typedef u32 PAddr; ///< Represents a pointer in the ARM11 physical address space. 52 | 53 | // An inheritable class to disallow the copy constructor and operator= functions 54 | class NonCopyable { 55 | protected: 56 | constexpr NonCopyable() = default; 57 | ~NonCopyable() = default; 58 | 59 | NonCopyable(const NonCopyable&) = delete; 60 | NonCopyable& operator=(const NonCopyable&) = delete; 61 | }; 62 | -------------------------------------------------------------------------------- /CMakeModules/MinGWCross.cmake: -------------------------------------------------------------------------------- 1 | SET(MINGW_PREFIX /usr/x86_64-w64-mingw32/) 2 | SET(CMAKE_SYSTEM_NAME Windows) 3 | SET(CMAKE_SYSTEM_PROCESSOR x86_64) 4 | # Actually a hack, w/o this will cause some strange errors 5 | SET(CMAKE_HOST_WIN32 TRUE) 6 | 7 | 8 | SET(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX}) 9 | SET(SDL2_PATH ${MINGW_PREFIX}) 10 | SET(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-) 11 | 12 | # Specify the cross compiler 13 | SET(CMAKE_C_COMPILER ${MINGW_TOOL_PREFIX}gcc-posix) 14 | SET(CMAKE_CXX_COMPILER ${MINGW_TOOL_PREFIX}g++-posix) 15 | SET(CMAKE_RC_COMPILER ${MINGW_TOOL_PREFIX}windres) 16 | 17 | # Mingw tools 18 | SET(STRIP ${MINGW_TOOL_PREFIX}strip) 19 | SET(WINDRES ${MINGW_TOOL_PREFIX}windres) 20 | SET(ENV{PKG_CONFIG} ${MINGW_TOOL_PREFIX}pkg-config) 21 | 22 | # ccache wrapper 23 | OPTION(USE_CCACHE "Use ccache for compilation" OFF) 24 | IF(USE_CCACHE) 25 | FIND_PROGRAM(CCACHE ccache) 26 | IF (CCACHE) 27 | MESSAGE(STATUS "Using ccache found in PATH") 28 | SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE}) 29 | SET_PROPERTY(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE}) 30 | ELSE(CCACHE) 31 | MESSAGE(WARNING "USE_CCACHE enabled, but no ccache found") 32 | ENDIF(CCACHE) 33 | ENDIF(USE_CCACHE) 34 | 35 | # Search for programs in the build host directories 36 | SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 37 | 38 | 39 | # Echo modified cmake vars to screen for debugging purposes 40 | IF(NOT DEFINED ENV{MINGW_DEBUG_INFO}) 41 | MESSAGE("") 42 | MESSAGE("Custom cmake vars: (blank = system default)") 43 | MESSAGE("-----------------------------------------") 44 | MESSAGE("* CMAKE_C_COMPILER : ${CMAKE_C_COMPILER}") 45 | MESSAGE("* CMAKE_CXX_COMPILER : ${CMAKE_CXX_COMPILER}") 46 | MESSAGE("* CMAKE_RC_COMPILER : ${CMAKE_RC_COMPILER}") 47 | MESSAGE("* WINDRES : ${WINDRES}") 48 | MESSAGE("* ENV{PKG_CONFIG} : $ENV{PKG_CONFIG}") 49 | MESSAGE("* STRIP : ${STRIP}") 50 | MESSAGE("* USE_CCACHE : ${USE_CCACHE}") 51 | MESSAGE("") 52 | # So that the debug info only appears once 53 | SET(ENV{MINGW_DEBUG_INFO} SHOWN) 54 | ENDIF() 55 | -------------------------------------------------------------------------------- /src/frontend/helpers/simple_job.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include "common/logging/log.h" 7 | #include "frontend/helpers/frontend_common.h" 8 | #include "frontend/helpers/rate_limited_progress_dialog.h" 9 | #include "frontend/helpers/simple_job.h" 10 | 11 | SimpleJob::SimpleJob(QObject* parent, ExecuteFunc execute_, AbortFunc abort_) 12 | : QThread(parent), execute(std::move(execute_)), abort(std::move(abort_)) {} 13 | 14 | SimpleJob::~SimpleJob() = default; 15 | 16 | void SimpleJob::run() { 17 | const bool ret = 18 | execute([this](u64 current, u64 total) { emit ProgressUpdated(current, total); }); 19 | 20 | if (ret || canceled) { 21 | emit Completed(canceled); 22 | } else { 23 | emit ErrorOccured(); 24 | } 25 | } 26 | 27 | void SimpleJob::Cancel() { 28 | canceled = true; 29 | abort(); 30 | } 31 | 32 | void SimpleJob::StartWithProgressDialog(QWidget* widget) { 33 | auto* dialog = new RateLimitedProgressDialog(tr("Initializing..."), tr("Cancel"), 0, 0, widget); 34 | connect(this, &SimpleJob::ProgressUpdated, this, [dialog](u64 current, u64 total) { 35 | if (dialog->wasCanceled()) { 36 | return; 37 | } 38 | // Try to map total to int range 39 | // This is equal to ceil(total / INT_MAX) 40 | const u64 multiplier = 41 | (total + std::numeric_limits::max() - 1) / std::numeric_limits::max(); 42 | dialog->setMaximum(static_cast(total / multiplier)); 43 | dialog->Update(static_cast(current / multiplier), 44 | tr("%1 / %2").arg(ReadableByteSize(current), ReadableByteSize(total))); 45 | }); 46 | connect(this, &SimpleJob::ErrorOccured, this, [widget, dialog] { 47 | QMessageBox message_box(QMessageBox::Critical, tr("threeSD"), 48 | tr("Operation failed. Refer to the log for details."), 49 | QMessageBox::Ok, widget); 50 | message_box.setDetailedText(QString::fromStdString(Common::Logging::GetLastErrors())); 51 | message_box.exec(); 52 | dialog->hide(); 53 | }); 54 | connect(this, &SimpleJob::Completed, dialog, &QProgressDialog::hide); 55 | connect(dialog, &QProgressDialog::canceled, this, &SimpleJob::Cancel); 56 | 57 | start(); 58 | } 59 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # shallow clone 2 | clone_depth: 5 3 | 4 | os: Visual Studio 2019 5 | 6 | environment: 7 | # Tell msys2 to add mingw64 to the path 8 | MSYSTEM: MINGW64 9 | # Tell msys2 to inherit the current directory when starting the shell 10 | CHERE_INVOKING: 1 11 | matrix: 12 | - BUILD_TYPE: mingw 13 | 14 | platform: 15 | - x64 16 | 17 | configuration: 18 | - Release 19 | 20 | install: 21 | - git submodule update --init --recursive 22 | - ps: | 23 | $dependencies = "mingw64/mingw-w64-x86_64-cmake mingw64/mingw-w64-x86_64-qt5-static" 24 | C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S $dependencies" 25 | # (HACK) Link these libs to really static link qt 26 | C:\msys64\usr\bin\bash -lc "rm /mingw64/lib/libzstd.dll.a && link /mingw64/lib/libzstd.a /mingw64/lib/libzstd.dll.a" 27 | C:\msys64\usr\bin\bash -lc "rm /mingw64/lib/libz.dll.a && link /mingw64/lib/libz.a /mingw64/lib/libz.dll.a" 28 | # (HACK) ignore errors 29 | 0 30 | before_build: 31 | - mkdir %BUILD_TYPE%_build 32 | - cd %BUILD_TYPE%_build 33 | - C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=Release -DMINGW_STATIC_BUILD=ON -DCOMPILE_WITH_DWARF=OFF .. 2>&1" 34 | - cd .. 35 | 36 | build_script: 37 | - C:\msys64\usr\bin\bash.exe -lc 'mingw32-make -j4 -C mingw_build/ 2>&1' 38 | after_build: 39 | - ps: | 40 | mkdir release 41 | mkdir release\dist 42 | 43 | C:\msys64\usr\bin\bash.exe -lc 'strip ./mingw_build/bin/threeSD.exe' 44 | Copy-Item .\mingw_build\bin\threeSD.exe -Destination release 45 | Copy-Item .\license.txt -Destination release 46 | Copy-Item .\README.md -Destination release 47 | Copy-Item .\dist\threeSDumper.gm9 -Destination release\dist 48 | $GITDATE = $(git show -s --date=short --format='%ad') -replace "-","" 49 | $GITREV = $(git show -s --format='%h') 50 | if ($env:APPVEYOR_REPO_TAG_NAME) { 51 | $BUILD_ZIP = "threeSD-windows-$env:APPVEYOR_REPO_TAG_NAME.zip" -replace " ", "" 52 | } else { 53 | $BUILD_ZIP = "threeSD-windows-$GITDATE-$GITREV.zip" -replace " ", "" 54 | } 55 | $env:BUILD_ZIP = $BUILD_ZIP 56 | 7z a $BUILD_ZIP release 57 | artifacts: 58 | - path: $(BUILD_ZIP) 59 | name: build 60 | 61 | deploy: 62 | provider: GitHub 63 | auth_token: 64 | secure: 4xdt1ZdE/ZgP2amG5Jr073yvbitMmdV0ts48wKBKEWpR6PJwDG3bR0Attvm9Mgv8 65 | artifact: build 66 | on: 67 | branch: master 68 | APPVEYOR_REPO_NAME: zhaowenlan1779/threeSD 69 | APPVEYOR_REPO_TAG: true 70 | -------------------------------------------------------------------------------- /src/core/file_sys/smdh.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Citra Emulator Project / 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_funcs.h" 10 | #include "common/common_types.h" 11 | #include "common/swap.h" 12 | 13 | namespace Core { 14 | 15 | /** 16 | * Tests if data is a valid SMDH by its length and magic number. 17 | * @param smdh_data data buffer to test 18 | * @return bool test result 19 | */ 20 | bool IsValidSMDH(const std::vector& smdh_data); 21 | 22 | /// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH 23 | struct SMDH { 24 | u32_le magic; 25 | u16_le version; 26 | INSERT_PADDING_BYTES(2); 27 | 28 | struct Title { 29 | std::array short_title; 30 | std::array long_title; 31 | std::array publisher; 32 | }; 33 | std::array titles; 34 | 35 | std::array ratings; 36 | u32_le region_lockout; 37 | u32_le match_maker_id; 38 | u64_le match_maker_bit_id; 39 | u32_le flags; 40 | u16_le eula_version; 41 | INSERT_PADDING_BYTES(2); 42 | float_le banner_animation_frame; 43 | u32_le cec_id; 44 | INSERT_PADDING_BYTES(8); 45 | 46 | std::array small_icon; 47 | std::array large_icon; 48 | 49 | /// indicates the language used for each title entry 50 | enum class TitleLanguage { 51 | Japanese = 0, 52 | English = 1, 53 | French = 2, 54 | German = 3, 55 | Italian = 4, 56 | Spanish = 5, 57 | SimplifiedChinese = 6, 58 | Korean = 7, 59 | Dutch = 8, 60 | Portuguese = 9, 61 | Russian = 10, 62 | TraditionalChinese = 11 63 | }; 64 | 65 | /** 66 | * Gets game icon from SMDH 67 | * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) 68 | * @return vector of RGB565 data 69 | */ 70 | std::vector GetIcon(bool large) const; 71 | 72 | /** 73 | * Gets the short game title from SMDH 74 | * @param language title language 75 | * @return UTF-16 array of the short title 76 | */ 77 | std::array GetShortTitle(Core::SMDH::TitleLanguage language) const; 78 | 79 | /// Gets a string representing the supported regions. 80 | std::string GetRegionString() const; 81 | }; 82 | static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong"); 83 | 84 | } // namespace Core 85 | -------------------------------------------------------------------------------- /src/frontend/helpers/multi_job.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include "common/logging/log.h" 7 | #include "frontend/helpers/multi_job.h" 8 | 9 | MultiJob::MultiJob(QObject* parent, Core::SDMCImporter& importer_, 10 | std::vector contents_, ExecuteFunc execute_func_, 11 | AbortFunc abort_func_) 12 | : QThread(parent), importer(importer_), contents(std::move(contents_)), 13 | execute_func(std::move(execute_func_)), abort_func(abort_func_) {} 14 | 15 | MultiJob::~MultiJob() = default; 16 | 17 | void MultiJob::run() { 18 | u64 total_size = 0; 19 | for (const auto& content : contents) { 20 | total_size += content.maximum_size; 21 | } 22 | 23 | std::size_t count = 0; 24 | int eta = -1; 25 | 26 | const auto initial_time = std::chrono::steady_clock::now(); 27 | const auto UpdateETA = [total_size, &eta, initial_time](u64 size_imported) { 28 | if (size_imported < 10 * 1024 * 1024) { // 10M Threshold 29 | return; 30 | } 31 | using namespace std::chrono; 32 | const u64 time_elapsed = 33 | duration_cast(steady_clock::now() - initial_time).count(); 34 | eta = 35 | static_cast(time_elapsed * (total_size - size_imported) / (size_imported) / 1000); 36 | }; 37 | const auto Callback = [this, &eta, &UpdateETA](u64 current_imported_size, 38 | u64 total_imported_size, u64 /*total_size*/) { 39 | UpdateETA(total_imported_size); 40 | emit ProgressUpdated(current_imported_size, total_imported_size, eta); 41 | }; 42 | 43 | Common::ProgressCallbackWrapper wrapper{total_size}; 44 | for (const auto& content : contents) { 45 | emit NextContent(count + 1, wrapper.current_done_size + wrapper.current_pending_size, 46 | content, eta); 47 | if (!execute_func(importer, content, wrapper.Wrap(Callback))) { 48 | if (!cancelled) { 49 | failed_contents.emplace_back(content, Common::Logging::GetLastErrors()); 50 | } 51 | } 52 | count++; 53 | 54 | if (cancelled) { 55 | break; 56 | } 57 | } 58 | emit Completed(); 59 | } 60 | 61 | void MultiJob::Cancel() { 62 | cancelled.store(true); 63 | abort_func(importer); 64 | } 65 | 66 | MultiJob::FailedContentList MultiJob::GetFailedContents() const { 67 | return failed_contents; 68 | } 69 | -------------------------------------------------------------------------------- /src/frontend/helpers/dpi_aware_dialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include "common/logging/log.h" 8 | #include "frontend/helpers/dpi_aware_dialog.h" 9 | 10 | DPIAwareDialog::DPIAwareDialog(QWidget* parent, int width, int height) 11 | : QDialog(parent), original_width(width), original_height(height) { 12 | 13 | setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint)); 14 | } 15 | 16 | DPIAwareDialog::~DPIAwareDialog() = default; 17 | 18 | void DPIAwareDialog::showEvent(QShowEvent* event) { 19 | QDialog::showEvent(event); 20 | if (window_handle) { 21 | return; 22 | } 23 | 24 | // Initialize window_handle and connections 25 | window_handle = windowHandle(); 26 | if (!window_handle) { 27 | return; 28 | } 29 | 30 | #ifdef __APPLE__ 31 | // Note: macOS implements system level virtualization, so there's no need to connect here. 32 | // but we still need to call SetContentSizes() at least once to set up the UI 33 | // macOS style has more padding. Make the dialog larger for compensation. 34 | resize(original_width * 1.25, original_height * 1.25); 35 | SetContentSizes(); 36 | #else 37 | resized = false; 38 | connect(window_handle, &QWindow::screenChanged, this, &DPIAwareDialog::OnScreenChanged); 39 | OnScreenChanged(); 40 | #endif 41 | } 42 | 43 | #ifndef __APPLE__ 44 | void DPIAwareDialog::resizeEvent(QResizeEvent* event) { 45 | QDialog::resizeEvent(event); 46 | resized = true; 47 | } 48 | 49 | void DPIAwareDialog::OnScreenChanged() { 50 | // Resize according to DPI 51 | const double scaleX = window_handle->screen()->logicalDotsPerInchX() / 96.0; 52 | const double scaleY = window_handle->screen()->logicalDotsPerInchY() / 96.0; 53 | if (resized) { 54 | const int new_width = static_cast(scaleX * width() / prev_scaleX); 55 | const int new_height = static_cast(scaleY * height() / prev_scaleY); 56 | setMinimumSize(0, 0); // Enforce this resize 57 | resize(new_width, new_height); 58 | } else { 59 | const int new_width = static_cast(original_width * scaleX); 60 | const int new_height = static_cast(original_height * scaleY); 61 | setMinimumSize(new_width, new_height); 62 | adjustSize(); 63 | resized = false; // This resize isn't user-initiated 64 | } 65 | 66 | SetContentSizes(prev_width, prev_height); 67 | prev_scaleX = scaleX; 68 | prev_scaleY = scaleY; 69 | prev_width = width(); 70 | prev_height = height(); 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /src/core/file_decryptor.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "common/common_types.h" 12 | #include "common/progress_callback.h" 13 | #include "common/thread.h" 14 | #include "core/key/key.h" 15 | 16 | namespace FileUtil { 17 | class IOFile; 18 | } 19 | 20 | namespace Core { 21 | 22 | class CryptoFunc; 23 | 24 | /** 25 | * Generalized file decryptor. 26 | * Helper that reads, decrypts and writes data. This uses three threads to process the data 27 | * and call progress callbacks occasionally. 28 | */ 29 | class FileDecryptor { 30 | public: 31 | explicit FileDecryptor(); 32 | ~FileDecryptor(); 33 | 34 | /** 35 | * Set up the crypto to use. 36 | * Default / nullptr is plain copy. 37 | */ 38 | void SetCrypto(std::shared_ptr crypto); 39 | 40 | /** 41 | * Crypts and writes a file. 42 | * 43 | * @param source Source file 44 | * @param size Size to read, decrypt and write 45 | * @param destination Destination file 46 | * @param callback Progress callback. default for nothing. 47 | */ 48 | bool CryptAndWriteFile( 49 | std::shared_ptr source, std::size_t size, 50 | std::shared_ptr destination, 51 | const Common::ProgressCallback& callback = [](u64, u64) {}); 52 | 53 | void DataReadLoop(); 54 | void DataDecryptLoop(); 55 | void DataWriteLoop(); 56 | 57 | void Abort(); 58 | 59 | private: 60 | static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB 61 | 62 | std::shared_ptr source; 63 | std::shared_ptr destination; 64 | std::shared_ptr crypto; 65 | 66 | std::size_t total_size{}; 67 | 68 | std::array, 3> buffers; 69 | std::array data_read_event; 70 | std::array data_decrypted_event; 71 | std::array data_written_event; 72 | 73 | std::unique_ptr read_thread; 74 | std::unique_ptr decrypt_thread; 75 | std::unique_ptr write_thread; 76 | 77 | Common::ProgressCallback callback; 78 | 79 | Common::Event completion_event; 80 | bool is_good{true}; 81 | std::atomic_bool is_running{false}; 82 | }; 83 | 84 | class CryptoFunc { 85 | public: 86 | virtual ~CryptoFunc(); 87 | virtual void ProcessData(u8* data, std::size_t size) = 0; 88 | }; 89 | 90 | std::shared_ptr CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr, 91 | std::size_t seek_pos = 0); 92 | 93 | } // namespace Core 94 | -------------------------------------------------------------------------------- /src/common/thread.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 2 | // 2019 threeSD Project 3 | // Licensed under GPLv2 or any later version 4 | // Refer to the license.txt file included. 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace Common { 15 | 16 | class Event { 17 | public: 18 | void Set() { 19 | std::lock_guard lk{mutex}; 20 | if (!is_set) { 21 | is_set = true; 22 | condvar.notify_one(); 23 | } 24 | } 25 | 26 | void Wait() { 27 | std::unique_lock lk{mutex}; 28 | condvar.wait(lk, [&] { return is_set; }); 29 | is_set = false; 30 | } 31 | 32 | template 33 | bool WaitFor(const std::chrono::duration& time) { 34 | std::unique_lock lk(mutex); 35 | if (!condvar.wait_for(lk, time, [this] { return is_set; })) 36 | return false; 37 | is_set = false; 38 | return true; 39 | } 40 | 41 | template 42 | bool WaitUntil(const std::chrono::time_point& time) { 43 | std::unique_lock lk{mutex}; 44 | if (!condvar.wait_until(lk, time, [this] { return is_set; })) 45 | return false; 46 | is_set = false; 47 | return true; 48 | } 49 | 50 | void Reset() { 51 | std::unique_lock lk{mutex}; 52 | // no other action required, since wait loops on the predicate and any lingering signal will 53 | // get cleared on the first iteration 54 | is_set = false; 55 | } 56 | 57 | private: 58 | bool is_set = false; 59 | std::condition_variable condvar; 60 | std::mutex mutex; 61 | }; 62 | 63 | class Barrier { 64 | public: 65 | explicit Barrier(std::size_t count_) : count(count_) {} 66 | 67 | /// Blocks until all "count" threads have called Sync() 68 | void Sync() { 69 | std::unique_lock lk{mutex}; 70 | const std::size_t current_generation = generation; 71 | 72 | if (++waiting == count) { 73 | generation++; 74 | waiting = 0; 75 | condvar.notify_all(); 76 | } else { 77 | condvar.wait(lk, 78 | [this, current_generation] { return current_generation != generation; }); 79 | } 80 | } 81 | 82 | std::size_t Generation() const { 83 | std::unique_lock lk(mutex); 84 | return generation; 85 | } 86 | 87 | private: 88 | std::condition_variable condvar; 89 | mutable std::mutex mutex; 90 | std::size_t count; 91 | std::size_t waiting = 0; 92 | std::size_t generation = 0; // Incremented once each time the barrier is used 93 | }; 94 | 95 | } // namespace Common 96 | -------------------------------------------------------------------------------- /src/common/assert.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "common/common_funcs.h" 9 | #include "common/logging/log.h" 10 | 11 | // For asserts we'd like to keep all the junk executed when an assert happens away from the 12 | // important code in the function. One way of doing this is to put all the relevant code inside a 13 | // lambda and force the compiler to not inline it. Unfortunately, MSVC seems to have no syntax to 14 | // specify __declspec on lambda functions, so what we do instead is define a noinline wrapper 15 | // template that calls the lambda. This seems to generate an extra instruction at the call-site 16 | // compared to the ideal implementation (which wouldn't support ASSERT_MSG parameters), but is good 17 | // enough for our purposes. 18 | template 19 | #if defined(_MSC_VER) 20 | __declspec(noinline, noreturn) 21 | #elif defined(__GNUC__) 22 | __attribute__((noinline, noreturn, cold)) 23 | #endif 24 | static void assert_noinline_call(const Fn& fn) { 25 | fn(); 26 | Crash(); 27 | exit(1); // Keeps GCC's mouth shut about this actually returning 28 | } 29 | 30 | #define ASSERT(_a_) \ 31 | do \ 32 | if (!(_a_)) { \ 33 | assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \ 34 | } \ 35 | while (0) 36 | 37 | #define ASSERT_MSG(_a_, ...) \ 38 | do \ 39 | if (!(_a_)) { \ 40 | assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \ 41 | } \ 42 | while (0) 43 | 44 | #define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!") 45 | #define UNREACHABLE_MSG(...) ASSERT_MSG(false, __VA_ARGS__) 46 | 47 | #ifdef _DEBUG 48 | #define DEBUG_ASSERT(_a_) ASSERT(_a_) 49 | #define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__) 50 | #else // not debug 51 | #define DEBUG_ASSERT(_a_) 52 | #define DEBUG_ASSERT_MSG(_a_, _desc_, ...) 53 | #endif 54 | 55 | #define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!") 56 | #define UNIMPLEMENTED_MSG(_a_, ...) ASSERT_MSG(false, _a_, __VA_ARGS__) 57 | -------------------------------------------------------------------------------- /src/core/file_sys/data/savegame.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include "core/file_sys/data/data_container.h" 6 | #include "core/file_sys/data/savegame.h" 7 | 8 | namespace Core { 9 | 10 | Savegame::Savegame(std::vector data) { 11 | is_good = Init(std::move(data)); 12 | } 13 | 14 | Savegame::~Savegame() = default; 15 | 16 | bool Savegame::Init(std::vector data) { 17 | if (data.empty()) { 18 | return false; 19 | } 20 | 21 | DataContainer container(std::move(data)); 22 | 23 | std::vector> partitions; 24 | if (!container.IsGood() || !container.GetIVFCLevel4Data(partitions)) { 25 | return false; 26 | } 27 | 28 | return Archive::Init(std::move(partitions)); 29 | } 30 | 31 | bool Savegame::CheckMagic() const { 32 | if (header.fat_header.magic != MakeMagic('S', 'A', 'V', 'E') || 33 | header.fat_header.version != 0x40000) { 34 | 35 | LOG_ERROR(Core, "File is invalid, decryption errors may have happened."); 36 | return false; 37 | } 38 | return true; 39 | } 40 | 41 | bool Savegame::IsGood() const { 42 | return is_good; 43 | } 44 | 45 | bool Savegame::ExtractFile(const std::string& path, std::size_t index) const { 46 | std::vector data; 47 | if (!GetFileData(data, index)) { 48 | LOG_ERROR(Core, "Could not get file data for index {}", index); 49 | return false; 50 | } 51 | return FileUtil::WriteBytesToFile(path, data.data(), data.size()); 52 | } 53 | 54 | bool Savegame::Extract(std::string path) const { 55 | if (path.back() != '/' && path.back() != '\\') { 56 | path += '/'; 57 | } 58 | 59 | // All saves on a physical 3DS are called 00000001.sav 60 | if (!ExtractDirectory(path + "00000001/", 1)) { // Directory 1 = root 61 | return false; 62 | } 63 | 64 | // Write format info 65 | const auto format_info = GetFormatInfo(); 66 | return FileUtil::WriteBytesToFile(path + "00000001.metadata", &format_info, 67 | sizeof(format_info)); 68 | } 69 | 70 | ArchiveFormatInfo Savegame::GetFormatInfo() const { 71 | // Tests on a physical 3DS shows that the `total_size` field seems to always be 0 72 | // when requested with the UserSaveData archive, and 134328448 when requested with 73 | // the SaveData archive. More investigation is required to tell whether this is a fixed value. 74 | ArchiveFormatInfo format_info = {/* total_size */ 0x40000, 75 | /* number_directories */ fs_info.maximum_directory_count, 76 | /* number_files */ fs_info.maximum_file_count, 77 | /* duplicate_data */ duplicate_data}; 78 | 79 | return format_info; 80 | } 81 | 82 | } // namespace Core 83 | -------------------------------------------------------------------------------- /src/frontend/import_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ImportDialog 4 | 5 | 6 | Select Contents 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Advanced... 15 | 16 | 17 | 18 | 19 | 20 | 21 | Qt::Horizontal 22 | 23 | 24 | 25 | 26 | 27 | 28 | Title View 29 | 30 | 31 | 32 | 33 | 34 | 35 | Group View 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | Qt::CustomContextMenu 45 | 46 | 47 | 48 | Name 49 | 50 | 51 | 52 | 53 | Size 54 | 55 | 56 | 57 | 58 | Exists 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Available Space: 69 | 70 | 71 | 72 | 73 | 74 | 75 | Qt::Horizontal 76 | 77 | 78 | 79 | 80 | 81 | 82 | Total Size: 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Reset 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/core/file_sys/signature.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include "common/alignment.h" 7 | #include "common/common_funcs.h" 8 | #include "common/file_util.h" 9 | #include "common/logging/log.h" 10 | #include "core/file_sys/certificate.h" 11 | #include "core/file_sys/signature.h" 12 | 13 | namespace Core { 14 | 15 | enum SignatureType : u32 { 16 | Rsa4096Sha1 = 0x10000, 17 | Rsa2048Sha1 = 0x10001, 18 | EllipticSha1 = 0x10002, 19 | Rsa4096Sha256 = 0x10003, 20 | Rsa2048Sha256 = 0x10004, 21 | EcdsaSha256 = 0x10005 22 | }; 23 | 24 | static u32 GetSignatureSize(u32 type) { 25 | switch (type) { 26 | case Rsa4096Sha1: 27 | case Rsa4096Sha256: 28 | return 0x200; 29 | 30 | case Rsa2048Sha1: 31 | case Rsa2048Sha256: 32 | return 0x100; 33 | 34 | case EllipticSha1: 35 | case EcdsaSha256: 36 | return 0x3C; 37 | } 38 | 39 | LOG_ERROR(Common_Filesystem, "Invalid signature type {}", type); 40 | return 0; 41 | } 42 | 43 | bool Signature::Load(const std::vector& file_data, std::size_t offset) { 44 | TRY_MEMCPY(&type, file_data, offset, sizeof(type)); 45 | 46 | const auto data_size = GetSignatureSize(type); 47 | if (data_size == 0) { 48 | return false; 49 | } 50 | 51 | data.resize(data_size); 52 | TRY_MEMCPY(data.data(), file_data, offset + sizeof(u32), data_size); 53 | return true; 54 | } 55 | 56 | bool Signature::Save(FileUtil::IOFile& file) const { 57 | if (file.WriteBytes(&type, sizeof(type)) != sizeof(type)) { 58 | LOG_ERROR(Core, "Could not write to file"); 59 | return false; 60 | } 61 | if (file.WriteBytes(data.data(), data.size()) != data.size()) { 62 | LOG_ERROR(Core, "Could not write to file"); 63 | return false; 64 | } 65 | return file.Seek(GetSize() - data.size() - sizeof(type), SEEK_CUR); 66 | } 67 | 68 | std::size_t Signature::GetSize() const { 69 | return Common::AlignUp(data.size() + sizeof(type), 0x40); 70 | } 71 | 72 | bool Signature::Verify(const std::string& issuer, 73 | const std::function& func) const { 74 | 75 | const auto& cert = Certs::Get(issuer); 76 | if (type != SignatureType::Rsa2048Sha256 || cert.body.key_type != PublicKeyType::RSA_2048) { 77 | 78 | LOG_ERROR(Core, "Unsupported signature type or cert public key type"); 79 | return false; 80 | } 81 | 82 | const auto [modulus, exponent] = cert.GetRSAPublicKey(); 83 | CryptoPP::RSASS::Verifier verifier(modulus, exponent); 84 | 85 | auto* message = verifier.NewVerificationAccumulator(); 86 | func(message); 87 | verifier.InputSignature(*message, data.data(), data.size()); 88 | return verifier.Verify(message); 89 | } 90 | 91 | } // namespace Core 92 | -------------------------------------------------------------------------------- /src/common/logging/log.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Citra Emulator Project / 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | // This is simplified version of Citra's logging system. 6 | // Only stderr/stderr output is enabled and color is implemented 7 | // with fmt tools. 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace Common::Logging { 16 | 17 | std::uint64_t GetLoggingTime(); 18 | 19 | enum Level { Invalid = 0, Trace, Debug, Info, Warning, Error, Critical }; 20 | struct Entry { 21 | Level level; 22 | fmt::text_style style; 23 | std::string message; 24 | }; 25 | 26 | void InitializeLogging(); 27 | void WriteLog(Entry entry); 28 | 29 | // Returns up to 3 latest error messages 30 | std::string GetLastErrors(); 31 | 32 | } // namespace Common::Logging 33 | 34 | #define HELPER_STR(line) #line 35 | #define HELPER_STR2(line) HELPER_STR(line) 36 | #define LOG_PRINT(log_class, level, text_style, format_str, ...) \ 37 | Common::Logging::WriteLog( \ 38 | Common::Logging::Entry{Common::Logging::Level::level, text_style, \ 39 | fmt::format("[{:12.6f}] " log_class " <" #level "> " __FILE__ \ 40 | ":" HELPER_STR2(__LINE__) ":{}: " format_str "\n", \ 41 | Common::Logging::GetLoggingTime() / 1000000.0, \ 42 | __func__ __VA_OPT__(, ) __VA_ARGS__)}); 43 | 44 | #ifdef _DEBUG 45 | #define LOG_TRACE(log_class, ...) \ 46 | LOG_PRINT(#log_class, Trace, fmt::fg(fmt::terminal_color::bright_black), __VA_ARGS__) 47 | #else 48 | #define LOG_TRACE(log_class, fmt, ...) (void(0)) 49 | #endif 50 | 51 | #define LOG_DEBUG(log_class, ...) \ 52 | LOG_PRINT(#log_class, Debug, fmt::fg(fmt::terminal_color::cyan), __VA_ARGS__) 53 | #define LOG_INFO(log_class, ...) \ 54 | LOG_PRINT(#log_class, Info, fmt::fg(fmt::terminal_color::white), __VA_ARGS__) 55 | #define LOG_WARNING(log_class, ...) \ 56 | LOG_PRINT(#log_class, Warning, fmt::fg(fmt::terminal_color::bright_yellow), __VA_ARGS__) 57 | #define LOG_ERROR(log_class, ...) \ 58 | LOG_PRINT(#log_class, Error, fmt::fg(fmt::terminal_color::bright_red), __VA_ARGS__) 59 | #define LOG_CRITICAL(log_class, ...) \ 60 | LOG_PRINT(#log_class, Critical, fmt::fg(fmt::terminal_color::bright_magenta), __VA_ARGS__) 61 | -------------------------------------------------------------------------------- /src/common/string_util.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "common/common_types.h" 12 | #include "common/swap.h" 13 | 14 | namespace Common { 15 | 16 | /// Make a string lowercase 17 | std::string ToLower(std::string str); 18 | 19 | /// Make a string uppercase 20 | std::string ToUpper(std::string str); 21 | 22 | std::string StripSpaces(const std::string& s); 23 | std::string StripQuotes(const std::string& s); 24 | 25 | std::string StringFromBool(bool value); 26 | 27 | std::string TabsToSpaces(int tab_size, std::string in); 28 | 29 | void SplitString(const std::string& str, char delim, std::vector& output); 30 | 31 | // "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe" 32 | bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, 33 | std::string* _pExtension); 34 | 35 | void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, 36 | const std::string& _Filename); 37 | std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest); 38 | 39 | std::string UTF16ToUTF8(const std::u16string& input); 40 | std::u16string UTF8ToUTF16(const std::string& input); 41 | 42 | #ifdef _WIN32 43 | std::string UTF16ToUTF8(const std::wstring& input); 44 | std::wstring UTF8ToUTF16W(const std::string& str); 45 | 46 | #endif 47 | 48 | /** 49 | * Compares the string defined by the range [`begin`, `end`) to the null-terminated C-string 50 | * `other` for equality. 51 | */ 52 | template 53 | bool ComparePartialString(InIt begin, InIt end, const char* other) { 54 | for (; begin != end && *other != '\0'; ++begin, ++other) { 55 | if (*begin != *other) { 56 | return false; 57 | } 58 | } 59 | // Only return true if both strings finished at the same point 60 | return (begin == end) == (*other == '\0'); 61 | } 62 | 63 | /** 64 | * Converts a UTF-16 text in a container to a UTF-8 std::string. 65 | */ 66 | template 67 | std::string UTF16BufferToUTF8(const T& text) { 68 | const auto text_end = std::find(text.begin(), text.end(), u'\0'); 69 | const std::size_t text_size = std::distance(text.begin(), text_end); 70 | std::u16string buffer(text_size, 0); 71 | std::transform(text.begin(), text_end, buffer.begin(), [](u16_le character) { 72 | return static_cast(static_cast(character)); 73 | }); 74 | return UTF16ToUTF8(buffer); 75 | } 76 | 77 | /** 78 | * Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't 79 | * NUL-terminated then the string ends at max_len characters. 80 | */ 81 | std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len); 82 | 83 | } // namespace Common 84 | -------------------------------------------------------------------------------- /src/frontend/import_dialog.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "core/file_sys/ncch_container.h" 12 | #include "core/importer.h" 13 | #include "helpers/dpi_aware_dialog.h" 14 | 15 | class AdvancedMenu; 16 | class MultiJob; 17 | class SimpleJob; 18 | class QTreeWidgetItem; 19 | 20 | namespace Ui { 21 | class ImportDialog; 22 | } 23 | 24 | class ImportDialog final : public DPIAwareDialog { 25 | Q_OBJECT 26 | 27 | public: 28 | explicit ImportDialog(QWidget* parent, const Core::Config& config); 29 | ~ImportDialog() override; 30 | 31 | private: 32 | void SetContentSizes(int previous_width, int previous_height) override; 33 | 34 | void RelistContent(); 35 | void RepopulateContent(); 36 | void UpdateSizeDisplay(); 37 | std::vector GetSelectedContentList(); 38 | 39 | void InsertTopLevelItem(QString text, QPixmap icon = {}); 40 | void InsertTopLevelItem(QString text, QPixmap icon, u64 total_size, QString exists); 41 | // When replace_name and replace_icon are present they are used instead of those in `content`. 42 | void InsertSecondLevelItem(std::size_t row, const Core::ContentSpecifier& content, 43 | std::size_t id, QString replace_name = {}, 44 | QPixmap replace_icon = {}); 45 | 46 | Core::ContentSpecifier SpecifierFromItem(QTreeWidgetItem* item) const; 47 | 48 | void OnContextMenu(const QPoint& point); 49 | void ShowAdvancedMenu(); 50 | 51 | void OnItemChanged(QTreeWidgetItem* item, int column); 52 | 53 | void RunMultiJob(MultiJob* job, std::size_t total_count, u64 total_size); 54 | 55 | void StartImporting(); 56 | 57 | void StartDumpingCXISingle(const Core::ContentSpecifier& content); 58 | QString last_dump_cxi_path; // Used for recording last path in StartDumpingCXISingle 59 | void StartBatchDumpingCXI(); 60 | QString last_batch_dump_cxi_path; // Used for recording last path in StartBatchDumpingCXI 61 | 62 | void StartBuildingCIASingle(const Core::ContentSpecifier& content); 63 | QString last_build_cia_path; // Used for recording last path in StartBuildingCIASingle 64 | void StartBatchBuildingCIA(); 65 | QString last_batch_build_cia_path; // Used for recording last path in StartBatchBuildingCIA 66 | 67 | std::unique_ptr ui; 68 | 69 | std::unique_ptr importer; 70 | const Core::Config config; 71 | 72 | std::vector contents; 73 | u64 total_selected_size = 0; 74 | 75 | // HACK: Block advanced menu trigger once. 76 | bool block_advanced_menu = false; 77 | friend class AdvancedMenu; 78 | 79 | // Whether the System Archive / System Data warning has been shown 80 | bool system_warning_shown = false; 81 | // Whether the Applets warning has been shown 82 | bool applet_warning_shown = false; 83 | 84 | // TODO: Why this won't work as locals? 85 | Core::ContentSpecifier current_content = {}; 86 | std::size_t current_count = 0; 87 | }; 88 | -------------------------------------------------------------------------------- /src/frontend/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_AUTOMOC ON) 2 | set(CMAKE_AUTORCC ON) 3 | set(CMAKE_AUTOUIC ON) 4 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 5 | if (POLICY CMP0071) 6 | cmake_policy(SET CMP0071 NEW) 7 | endif() 8 | 9 | file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/themes/*) 10 | 11 | add_executable(threeSD 12 | helpers/dpi_aware_dialog.cpp 13 | helpers/dpi_aware_dialog.h 14 | helpers/frontend_common.cpp 15 | helpers/frontend_common.h 16 | helpers/multi_job.cpp 17 | helpers/multi_job.h 18 | helpers/rate_limited_progress_dialog.cpp 19 | helpers/rate_limited_progress_dialog.h 20 | helpers/simple_job.cpp 21 | helpers/simple_job.h 22 | cia_build_dialog.cpp 23 | cia_build_dialog.h 24 | cia_build_dialog.ui 25 | import_dialog.cpp 26 | import_dialog.h 27 | import_dialog.ui 28 | main.cpp 29 | main.h 30 | main.ui 31 | select_files_dialog.cpp 32 | select_files_dialog.h 33 | select_files_dialog.ui 34 | select_nand_dialog.cpp 35 | select_nand_dialog.h 36 | select_nand_dialog.ui 37 | title_info_dialog.cpp 38 | title_info_dialog.h 39 | title_info_dialog.ui 40 | utilities.cpp 41 | utilities.h 42 | utilities.ui 43 | ${THEMES} 44 | ) 45 | 46 | target_link_libraries(threeSD PRIVATE common core) 47 | target_link_libraries(threeSD PRIVATE Qt5::Widgets) 48 | target_link_libraries(threeSD PRIVATE qdevicewatcher) 49 | target_link_libraries(threeSD PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) 50 | 51 | if (APPLE) 52 | # set(MACOSX_ICON "../../dist/citra.icns") 53 | # set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) 54 | # target_sources(threeSD PRIVATE ${MACOSX_ICON}) 55 | set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE TRUE) 56 | set_target_properties(threeSD PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist) 57 | elseif(WIN32) 58 | # compile as a win32 gui application instead of a console application 59 | target_link_libraries(threeSD PRIVATE Qt5::WinMain) 60 | if(MSVC) 61 | set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS") 62 | elseif(MINGW) 63 | set_target_properties(threeSD PROPERTIES LINK_FLAGS_RELEASE "-mwindows") 64 | endif() 65 | elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") 66 | # In Ubuntu, the executable would be recognized as a shared library otherwise. 67 | set_target_properties(threeSD PROPERTIES LINK_FLAGS "-no-pie") 68 | endif() 69 | 70 | target_compile_definitions(threeSD PRIVATE 71 | # Use QStringBuilder for string concatenation to reduce 72 | # the overall number of temporary strings created. 73 | -DQT_USE_QSTRINGBUILDER 74 | 75 | # Disable implicit type narrowing in signal/slot connect() calls. 76 | -DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT 77 | 78 | # Disable unsafe overloads of QProcess' start() function. 79 | -DQT_NO_PROCESS_COMBINED_ARGUMENT_START 80 | 81 | # Disable implicit QString->QUrl conversions to enforce use of proper resolving functions. 82 | -DQT_NO_URL_CAST_FROM_STRING 83 | 84 | # Disable automatic conversions from 8-bit strings (char *) to unicode QStrings 85 | -DQT_NO_CAST_FROM_ASCII 86 | ) 87 | 88 | if (MSVC) 89 | include(CopyQt5Deps) 90 | copy_Qt5_deps(threeSD) 91 | endif() 92 | -------------------------------------------------------------------------------- /src/common/common_funcs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #if !defined(ARCHITECTURE_x86_64) 12 | #include // for exit 13 | #endif 14 | #include "common/common_types.h" 15 | 16 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) 17 | 18 | /// Textually concatenates two tokens. The double-expansion is required by the C preprocessor. 19 | #define CONCAT2(x, y) DO_CONCAT2(x, y) 20 | #define DO_CONCAT2(x, y) x##y 21 | 22 | #define TRY(x, ...) \ 23 | if (!(x)) { \ 24 | __VA_ARGS__; \ 25 | return false; \ 26 | } 27 | 28 | // helper macro to properly align structure members. 29 | // Calling INSERT_PADDING_BYTES will add a new member variable with a name like "pad121", 30 | // depending on the current source line to make sure variable names are unique. 31 | #define INSERT_PADDING_BYTES(num_bytes) u8 CONCAT2(pad, __LINE__)[(num_bytes)] 32 | #define INSERT_PADDING_WORDS(num_words) u32 CONCAT2(pad, __LINE__)[(num_words)] 33 | 34 | // Inlining 35 | #ifdef _WIN32 36 | #define FORCE_INLINE __forceinline 37 | #else 38 | #define FORCE_INLINE inline __attribute__((always_inline)) 39 | #endif 40 | 41 | #ifndef _MSC_VER 42 | 43 | #ifdef ARCHITECTURE_x86_64 44 | #define Crash() __asm__ __volatile__("int $3") 45 | #else 46 | #define Crash() exit(1) 47 | #endif 48 | 49 | #else // _MSC_VER 50 | 51 | #if (_MSC_VER < 1900) 52 | // Function Cross-Compatibility 53 | #define snprintf _snprintf 54 | #endif 55 | 56 | // Locale Cross-Compatibility 57 | #define locale_t _locale_t 58 | 59 | extern "C" { 60 | __declspec(dllimport) void __stdcall DebugBreak(void); 61 | } 62 | #define Crash() DebugBreak() 63 | 64 | #endif // _MSC_VER ndef 65 | 66 | // Generic function to get last error message. 67 | // Call directly after the command or use the error num. 68 | // This function might change the error code. 69 | // Defined in Misc.cpp. 70 | std::string GetLastErrorMsg(); 71 | 72 | template 73 | bool CheckedMemcpy(void* dest, T& container, std::ptrdiff_t offset, std::size_t size) { 74 | static_assert(std::is_same_v, "Only works with u8"); 75 | if (container.size() < offset + size) { 76 | return false; 77 | } 78 | std::memcpy(dest, container.data() + offset, size); 79 | return true; 80 | } 81 | 82 | constexpr u32 MakeMagic(char a, char b, char c, char d) { 83 | return a | b << 8 | c << 16 | d << 24; 84 | } 85 | 86 | constexpr u64 MakeMagic(char a, char b, char c, char d, char e, char f, char g, char h) { 87 | return u64(a) | u64(b) << 8 | u64(c) << 16 | u64(d) << 24 | u64(e) << 32 | u64(f) << 40 | 88 | u64(g) << 48 | u64(h) << 56; 89 | } 90 | 91 | #define TRY_MEMCPY(dest, container, offset, size) \ 92 | TRY(CheckedMemcpy(dest, container, offset, size), LOG_ERROR(Core, "File size is too small")) 93 | -------------------------------------------------------------------------------- /dist/threeSDumper.gm9: -------------------------------------------------------------------------------- 1 | # Copyright 2019 threeSD Project 2 | # Licensed under GPLv2 or any later version 3 | # Refer to the license.txt file included. 4 | 5 | # GM9 Script for dumping necessary files automatically. 6 | 7 | set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779" 8 | if not ask "Execute threeSD Dumper?\n \nRequires GodMode9 v2.0.0\nYou are on $[GM9VER]\n \nRequired Space: ~400MB for each NAND" 9 | goto Exit 10 | end 11 | 12 | set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nWorking..." 13 | 14 | set OUT "0:/threeSD" 15 | if exist $[OUT] 16 | rm $[OUT] 17 | end 18 | mkdir $[OUT] 19 | 20 | # === General data (independent of NANDs) 21 | 22 | # Version 23 | dumptxt $[OUT]/version.txt 4 24 | 25 | # bootrom 26 | if exist "M:/boot9.bin" 27 | cp -w -n "M:/boot9.bin" $[OUT]/boot9.bin 28 | elif exist "0:/3DS/boot9.bin" 29 | cp -w -n "0:/3DS/boot9.bin" $[OUT]/boot9.bin 30 | else 31 | echo "ERROR: \nboot9.bin not found. \nIf you use fastboot3ds, hold HOME while booting, \nand go to Miscellaneous... > Dump bootroms & OTP. \nWhen finished, simply execute this script again." 32 | goto Exit 33 | end 34 | 35 | # Secret sector (N3DS only) 36 | if chk $[ONTYPE] "N3DS" 37 | cp -w -n "S:/sector0x96.bin" $[OUT]/sector0x96.bin 38 | end 39 | 40 | # === NANDs 41 | 42 | # Start with SysNAND 43 | set NAND "1:" 44 | set NAND_NAME "Sys" 45 | set ID0 $[SYSID0] 46 | 47 | @Loop 48 | set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nWorking ($[NAND_NAME])..." 49 | set OUT "0:/threeSD/$[NAND_NAME]" 50 | mkdir $[OUT] 51 | 52 | # movable.sed 53 | cp -w -n $[NAND]/private/movable.sed $[OUT]/movable.sed 54 | 55 | # certs.db 56 | if chk $[RDTYPE] "devkit" 57 | echo "WARNING: \nDev kit detected. \nCIA building will not be usable." 58 | else 59 | cp -w -n $[NAND]/dbs/certs.db $[OUT]/certs.db 60 | end 61 | 62 | # ticket.db 63 | cp -w -n $[NAND]/dbs/ticket.db $[OUT]/ticket.db 64 | 65 | # title.db 66 | cp -w -n $[NAND]/dbs/title.db $[OUT]/title.db 67 | 68 | # seeddb.bin 69 | # Note: this contains both SysNAND and EmuNAND seeds when built, but only the current EmuNAND 70 | if exist 0:/gm9/out/seedd.bin 71 | rm 0:/gm9/out/seeddb.bin 72 | end 73 | sdump -o -s -w seeddb.bin 74 | if not exist 0:/gm9/out/seeddb.bin 75 | echo "WARNING: \nseeddb.bin couldn't be built. \nThis may be because your system \ndoes not have any seeds. \nOtherwise, imported games may fail \nto run if they use seed encryption." 76 | else 77 | cp -w -n "0:/gm9/out/seeddb.bin" $[OUT]/seeddb.bin 78 | rm "0:/gm9/out/seeddb.bin" 79 | end 80 | 81 | # data 82 | cp -w -n $[NAND]/data/$[ID0] $[OUT]/data 83 | 84 | # title 85 | cp -w -n $[NAND]/title $[OUT]/title 86 | 87 | # Loop Control 88 | if chk $[NAND] "1:" 89 | # Start EmuNAND 90 | if not exist "4:/title" 91 | goto Finish 92 | end 93 | set NAND "4:" 94 | else 95 | # Next EmuNAND 96 | set LASTEMU $[EMUBASE] 97 | nextemu 98 | if chk $[EMUBASE] $[LASTEMU] 99 | # All EmuNANDs done 100 | goto Finish 101 | end 102 | end 103 | set NAND_NAME "Emu$[EMUBASE]" 104 | set ID0 $[EMUID0] 105 | goto Loop 106 | 107 | @Finish 108 | set PREVIEW_MODE "threeSD Dumper\nby zhaowenlan1779\n \nSuccess!" 109 | if ask "Successfully dumped necessary\nfiles for threeSD.\n \nPower off now?" 110 | poweroff 111 | end 112 | 113 | @Exit 114 | -------------------------------------------------------------------------------- /src/core/db/title_db.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "common/common_funcs.h" 11 | #include "common/common_types.h" 12 | #include "common/swap.h" 13 | #include "core/file_sys/data/inner_fat.hpp" 14 | #include "core/file_sys/ticket.h" 15 | 16 | namespace Core { 17 | 18 | struct TitleDBPreheader { 19 | u64_le db_magic; 20 | INSERT_PADDING_BYTES(0x78); 21 | }; 22 | static_assert(sizeof(TitleDBPreheader) == 0x80, "TitleDB pre-header has incorrect size"); 23 | 24 | #pragma pack(push, 1) 25 | struct TitleDBDirectoryEntryTableEntry { 26 | u32_le parent_directory_index; 27 | u32_le next_sibling_index; 28 | u32_le first_subdirectory_index; 29 | u32_le first_file_index; 30 | INSERT_PADDING_BYTES(12); 31 | u32_le next_hash_bucket_entry; 32 | }; 33 | static_assert(sizeof(TitleDBDirectoryEntryTableEntry) == 0x20, 34 | "TitleDBDirectoryEntryTableEntry has incorrect size"); 35 | 36 | struct TitleDBFileEntryTableEntry { 37 | u32_le parent_directory_index; 38 | u64_le title_id; 39 | u32_le next_sibling_index; 40 | INSERT_PADDING_BYTES(4); 41 | u32_le data_block_index; 42 | u64_le file_size; 43 | INSERT_PADDING_BYTES(8); 44 | u32_le next_hash_bucket_entry; 45 | }; 46 | static_assert(sizeof(TitleDBFileEntryTableEntry) == 0x2c, 47 | "TitleDBFileEntryTableEntry has incorrect size"); 48 | #pragma pack(pop) 49 | 50 | struct TitleInfoEntry { 51 | u64_le title_size; 52 | u32_le title_type; 53 | u32_le title_version; 54 | u32_le flags0; 55 | u32_le tmd_content_id; 56 | u32_le cmd_content_id; 57 | u32_le flags1; 58 | u32_le extdata_id_low; 59 | INSERT_PADDING_BYTES(4); 60 | u64_le flags2; 61 | std::array product_code; 62 | INSERT_PADDING_BYTES(0x40); 63 | }; 64 | static_assert(sizeof(TitleInfoEntry) == 0x80, "TitleInfoEntry has incorrect size"); 65 | 66 | class TitleDB; 67 | using InnerFAT_TitleDB = InnerFAT; 69 | 70 | class TitleDB final : public InnerFAT_TitleDB { 71 | public: 72 | bool AddFromData(std::vector data); 73 | bool AddFromFile(const std::string& path); 74 | ~TitleDB(); 75 | 76 | std::unordered_map titles; 77 | 78 | private: 79 | bool Init(std::vector data); 80 | bool CheckMagic() const; 81 | bool LoadTitleInfo(u32 index); 82 | 83 | friend InnerFAT_TitleDB; 84 | }; 85 | 86 | struct TicketDBPreheader { 87 | u32_le db_magic; 88 | INSERT_PADDING_BYTES(0x0C); 89 | }; 90 | static_assert(sizeof(TicketDBPreheader) == 0x10, "TicketDB pre-header has incorrect size"); 91 | 92 | class TicketDB; 93 | using InnerFAT_TicketDB = InnerFAT; 95 | class TicketDB final : public InnerFAT_TicketDB { 96 | public: 97 | bool AddFromFile(const std::string& path); 98 | ~TicketDB(); 99 | 100 | std::unordered_map tickets; 101 | 102 | private: 103 | bool Init(std::vector data); 104 | bool CheckMagic() const; 105 | bool LoadTicket(u32 index); 106 | 107 | friend InnerFAT_TicketDB; 108 | }; 109 | 110 | } // namespace Core 111 | -------------------------------------------------------------------------------- /src/frontend/cia_build_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CIABuildDialog 4 | 5 | 6 | Build CIA 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Destination: 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 0 26 | 0 27 | 28 | 29 | 30 | ... 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Build Type 40 | 41 | 42 | 43 | 44 | 45 | Standard 46 | 47 | 48 | true 49 | 50 | 51 | 52 | 53 | 54 | 55 | Recommended for general use.<br>Decrypted CIA with decrypted contents and standard ticket. 56 | 57 | 58 | 59 | 60 | 61 | 62 | Legit with standard ticket ("pirate legit") 63 | 64 | 65 | 66 | 67 | 68 | 69 | Encrypted CIA with legit TMD, encrypted contents and standard ticket. 70 | 71 | 72 | 73 | 74 | 75 | 76 | Legit 77 | 78 | 79 | 80 | 81 | 82 | 83 | Encrypted CIA with legit TMD, encrypted contents and legit ticket.<br>WARNING: Legit ticket may include console identifying information! 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | Qt::Vertical 94 | 95 | 96 | 97 | 98 | 99 | 100 | QDialogButtonBox::Ok|QDialogButtonBox::Cancel 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: threeSD-ci 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | tags: [ "*" ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | image: ["linux-clang-format", "linux-fresh", "linux-mingw"] 16 | container: citraemu/build-environments:${{ matrix.image }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | with: 20 | submodules: recursive 21 | - name: Set up cache 22 | uses: actions/cache@v2 23 | with: 24 | path: ~/.ccache 25 | key: ${{ runner.os }}-${{ matrix.image }}-${{ github.sha }} 26 | restore-keys: | 27 | ${{ runner.os }}-${{ matrix.image }}- 28 | - name: Query tag name 29 | uses: little-core-labs/get-git-tag@v3.0.2 30 | id: tagName 31 | - name: Build 32 | run: ./.ci/${{ matrix.image }}/docker.sh 33 | - name: Pack 34 | run: ./.ci/${{ matrix.image }}/upload.sh 35 | if: ${{ matrix.image != 'linux-clang-format' }} 36 | env: 37 | NAME: ${{ matrix.image }} 38 | - name: Upload 39 | uses: actions/upload-artifact@v2 40 | if: ${{ matrix.image != 'linux-mingw' && matrix.image != 'linux-clang-format' }} 41 | with: 42 | name: ${{ matrix.image }} 43 | path: artifacts/ 44 | macos: 45 | runs-on: macos-latest 46 | steps: 47 | - uses: actions/checkout@v2 48 | with: 49 | submodules: recursive 50 | - name: Set up cache 51 | uses: actions/cache@v2 52 | with: 53 | path: ~/Library/Caches/ccache 54 | key: ${{ runner.os }}-macos-${{ github.sha }} 55 | restore-keys: | 56 | ${{ runner.os }}-macos- 57 | - name: Query tag name 58 | uses: little-core-labs/get-git-tag@v3.0.2 59 | id: tagName 60 | - name: Install dependencies 61 | run: ./.ci/macos/deps.sh 62 | - name: Build 63 | run: ./.ci/macos/build.sh 64 | env: 65 | MACOSX_DEPLOYMENT_TARGET: "10.13" 66 | - name: Pack 67 | run: ./.ci/macos/upload.sh 68 | - name: Upload 69 | uses: actions/upload-artifact@v2 70 | with: 71 | name: macos 72 | path: artifacts/ 73 | windows: 74 | runs-on: windows-latest 75 | steps: 76 | - uses: actions/checkout@v2 77 | with: 78 | submodules: recursive 79 | - name: Set up cache 80 | uses: actions/cache@v2 81 | with: 82 | path: ~/.buildcache 83 | key: ${{ runner.os }}-win-${{ github.sha }} 84 | restore-keys: | 85 | ${{ runner.os }}-win- 86 | - name: Install dependencies 87 | run: ./.ci/windows-msvc/deps.sh 88 | shell: bash 89 | - name: Set up MSVC 90 | uses: ilammy/msvc-dev-cmd@v1 91 | - name: Build 92 | run: ./.ci/windows-msvc/build.sh 93 | shell: bash 94 | env: 95 | ENABLE_COMPATIBILITY_REPORTING: "ON" 96 | release: 97 | runs-on: ubuntu-latest 98 | needs: [build, macos] 99 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 100 | steps: 101 | - uses: actions/download-artifact@v2 102 | - name: Query tag name 103 | uses: little-core-labs/get-git-tag@v3.0.2 104 | id: tagName 105 | - name: Upload artifacts 106 | uses: alexellis/upload-assets@0.2.3 107 | env: 108 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 109 | with: 110 | asset_paths: '["./**/*.zip"]' 111 | -------------------------------------------------------------------------------- /src/core/db/seed_db.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Citra Emulator Project / 2020 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include "common/file_util.h" 6 | #include "common/logging/log.h" 7 | #include "common/swap.h" 8 | #include "core/db/seed_db.h" 9 | 10 | namespace Core { 11 | 12 | bool SeedDB::AddFromFile(const std::string& path) { 13 | if (!FileUtil::Exists(path)) { 14 | LOG_WARNING(Service_FS, "Seed database does not exist"); 15 | return true; 16 | } 17 | FileUtil::IOFile file{path, "rb"}; 18 | if (!file.IsOpen()) { 19 | LOG_ERROR(Service_FS, "Failed to open seed database"); 20 | return false; 21 | } 22 | u32_le count; 23 | if (file.ReadBytes(&count, sizeof(count)) != sizeof(count)) { 24 | LOG_ERROR(Service_FS, "Failed to read seed database count fully"); 25 | return false; 26 | } 27 | if (!file.Seek(SEEDDB_PADDING_BYTES, SEEK_CUR)) { 28 | LOG_ERROR(Service_FS, "Failed to skip seed database padding"); 29 | return false; 30 | } 31 | for (u32 i = 0; i < count; ++i) { 32 | u64_le title_id; 33 | if (!file.ReadBytes(&title_id, sizeof(title_id))) { 34 | LOG_ERROR(Service_FS, "Failed to read seed {} title ID", i); 35 | return false; 36 | } 37 | Seed seed; 38 | if (!file.ReadBytes(seed.data(), seed.size())) { 39 | LOG_ERROR(Service_FS, "Failed to read seed {} data", i); 40 | return false; 41 | } 42 | if (!file.Seek(SEEDDB_ENTRY_PADDING_BYTES, SEEK_CUR)) { 43 | LOG_ERROR(Service_FS, "Failed to skip past seed {} padding", i); 44 | return false; 45 | } 46 | seeds.emplace(title_id, std::move(seed)); 47 | } 48 | return true; 49 | } 50 | 51 | bool SeedDB::Save(const std::string& path) const { 52 | if (!FileUtil::CreateFullPath(path)) { 53 | LOG_ERROR(Service_FS, "Failed to create seed database"); 54 | return false; 55 | } 56 | FileUtil::IOFile file{path, "wb"}; 57 | if (!file.IsOpen()) { 58 | LOG_ERROR(Service_FS, "Failed to open seed database"); 59 | return false; 60 | } 61 | u32_le count{static_cast(seeds.size())}; 62 | if (file.WriteBytes(&count, sizeof(count)) != sizeof(count)) { 63 | LOG_ERROR(Service_FS, "Failed to write seed database count fully"); 64 | return false; 65 | } 66 | std::array padding{}; 67 | if (file.WriteBytes(padding.data(), padding.size()) != padding.size()) { 68 | LOG_ERROR(Service_FS, "Failed to write seed database padding fully"); 69 | return false; 70 | } 71 | for (const auto& [title_id, seed] : seeds) { 72 | const u64_le raw_title_id{title_id}; // for endianess 73 | if (file.WriteBytes(&raw_title_id, sizeof(raw_title_id)) != sizeof(raw_title_id)) { 74 | LOG_ERROR(Service_FS, "Failed to write seed {:016x} title ID fully", title_id); 75 | return false; 76 | } 77 | 78 | if (file.WriteBytes(seed.data(), seed.size()) != seed.size()) { 79 | LOG_ERROR(Service_FS, "Failed to write seed {:016x} data fully", title_id); 80 | return false; 81 | } 82 | static constexpr std::array Padding{}; 83 | if (file.WriteBytes(Padding.data(), Padding.size()) != Padding.size()) { 84 | LOG_ERROR(Service_FS, "Failed to write seed {:016x} padding fully", title_id); 85 | return false; 86 | } 87 | } 88 | return true; 89 | } 90 | 91 | std::size_t SeedDB::GetSize() const { 92 | return sizeof(u32) + SEEDDB_PADDING_BYTES + seeds.size() * SEEDDB_ENTRY_SIZE; 93 | } 94 | 95 | } // namespace Core 96 | -------------------------------------------------------------------------------- /src/frontend/cia_build_dialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "common/assert.h" 10 | #include "frontend/cia_build_dialog.h" 11 | #include "ui_cia_build_dialog.h" 12 | 13 | CIABuildDialog::CIABuildDialog(QWidget* parent, bool is_dir_, bool is_nand, bool enable_legit, 14 | const QString& default_path) 15 | : DPIAwareDialog(parent, 510, 260), ui(std::make_unique()), 16 | is_dir(is_dir_) { 17 | 18 | ui->setupUi(this); 19 | 20 | if (is_dir) { 21 | setWindowTitle(tr("Batch Build CIA")); 22 | } 23 | if (is_nand) { 24 | ui->pirateLegitButton->setVisible(false); 25 | ui->pirateLegitLabel->setVisible(false); 26 | 27 | auto message = tr("Encrypted CIA with legit TMD, encrypted contents and legit ticket.
"); 28 | if (is_dir) { 29 | message.append(tr( 30 | "Legit tickets for these titles do not include console-identifying information.")); 31 | } else { 32 | message.append(tr( 33 | "Legit ticket for this title does not include console-identifying information.")); 34 | } 35 | ui->legitLabel->setText(message); 36 | } 37 | if (!enable_legit) { 38 | const auto message = 39 | is_dir ? tr("This option is not available as some of the titles are not legit.") 40 | : tr("This option is not available as the title is not legit."); 41 | ui->pirateLegitButton->setEnabled(false); 42 | ui->pirateLegitLabel->setText(message); 43 | ui->legitButton->setEnabled(false); 44 | ui->legitLabel->setText(message); 45 | } 46 | 47 | connect(ui->buttonBox, &QDialogButtonBox::accepted, [this] { 48 | if (ui->destination->text().isEmpty()) { 49 | const QString message = is_dir ? tr("Please specify destination folder.") 50 | : tr("Please specify destination file."); 51 | QMessageBox::warning(this, tr("threeSD"), message); 52 | return; 53 | } 54 | accept(); 55 | }); 56 | connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &CIABuildDialog::reject); 57 | 58 | if (is_dir) { 59 | ui->destination->setText(default_path); 60 | } 61 | connect(ui->destinationExplore, &QToolButton::clicked, [this, default_path] { 62 | QString path; 63 | if (is_dir) { 64 | path = QFileDialog::getExistingDirectory(this, tr("Batch Build CIA"), 65 | ui->destination->text()); 66 | } else { 67 | const auto cur = ui->destination->text().isEmpty() 68 | ? default_path 69 | : QFileInfo(ui->destination->text()).path(); 70 | path = QFileDialog::getSaveFileName(this, tr("Build CIA"), cur, 71 | tr("CTR Importable Archive (*.cia)")); 72 | } 73 | if (!path.isEmpty()) { 74 | ui->destination->setText(path); 75 | } 76 | }); 77 | } 78 | 79 | CIABuildDialog::~CIABuildDialog() = default; 80 | 81 | std::pair CIABuildDialog::GetResults() const { 82 | Core::CIABuildType type; 83 | if (ui->standardButton->isChecked()) { 84 | type = Core::CIABuildType::Standard; 85 | } else if (ui->pirateLegitButton->isChecked()) { 86 | type = Core::CIABuildType::PirateLegit; 87 | } else if (ui->legitButton->isChecked()) { 88 | type = Core::CIABuildType::Legit; 89 | } else { 90 | UNREACHABLE(); 91 | } 92 | return {ui->destination->text(), type}; 93 | } 94 | -------------------------------------------------------------------------------- /src/core/file_sys/smdh.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Citra Emulator Project / 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include "common/common_funcs.h" 8 | #include "common/common_types.h" 9 | #include "core/file_sys/smdh.h" 10 | 11 | namespace Core { 12 | 13 | // 8x8 Z-Order coordinate from 2D coordinates 14 | static constexpr u32 MortonInterleave(u32 x, u32 y) { 15 | constexpr u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15}; 16 | constexpr u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a}; 17 | return xlut[x % 8] + ylut[y % 8]; 18 | } 19 | 20 | /** 21 | * Calculates the offset of the position of the pixel in Morton order 22 | */ 23 | static inline u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) { 24 | // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each 25 | // of which is composed of four 2x2 subtiles each of which is composed of four texels. 26 | // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g. 27 | // texels are laid out in a 2x2 subtile like this: 28 | // 2 3 29 | // 0 1 30 | // 31 | // The full 8x8 tile has the texels arranged like this: 32 | // 33 | // 42 43 46 47 58 59 62 63 34 | // 40 41 44 45 56 57 60 61 35 | // 34 35 38 39 50 51 54 55 36 | // 32 33 36 37 48 49 52 53 37 | // 10 11 14 15 26 27 30 31 38 | // 08 09 12 13 24 25 28 29 39 | // 02 03 06 07 18 19 22 23 40 | // 00 01 04 05 16 17 20 21 41 | // 42 | // This pattern is what's called Z-order curve, or Morton order. 43 | 44 | const unsigned int block_height = 8; 45 | const unsigned int coarse_x = x & ~7; 46 | 47 | u32 i = MortonInterleave(x, y); 48 | 49 | const unsigned int offset = coarse_x * block_height; 50 | 51 | return (i + offset) * bytes_per_pixel; 52 | } 53 | 54 | bool IsValidSMDH(const std::vector& smdh_data) { 55 | if (smdh_data.size() < sizeof(Core::SMDH)) 56 | return false; 57 | 58 | u32 magic; 59 | memcpy(&magic, smdh_data.data(), sizeof(u32)); 60 | 61 | return MakeMagic('S', 'M', 'D', 'H') == magic; 62 | } 63 | 64 | std::vector SMDH::GetIcon(bool large) const { 65 | u32 size; 66 | const u8* icon_data; 67 | 68 | if (large) { 69 | size = 48; 70 | icon_data = large_icon.data(); 71 | } else { 72 | size = 24; 73 | icon_data = small_icon.data(); 74 | } 75 | 76 | std::vector icon(size * size); 77 | for (u32 x = 0; x < size; ++x) { 78 | for (u32 y = 0; y < size; ++y) { 79 | u32 coarse_y = y & ~7; 80 | const u8* pixel = icon_data + GetMortonOffset(x, y, 2) + coarse_y * size * 2; 81 | icon[x + size * y] = (pixel[1] << 8) + pixel[0]; 82 | } 83 | } 84 | return icon; 85 | } 86 | 87 | std::array SMDH::GetShortTitle(Core::SMDH::TitleLanguage language) const { 88 | return titles[static_cast(language)].short_title; 89 | } 90 | 91 | std::string SMDH::GetRegionString() const { 92 | constexpr u32 REGION_COUNT = 7; 93 | 94 | // JPN/USA/EUR/Australia/CHN/KOR/TWN 95 | // Australia does not have a symbol because it's practically the same as Europe 96 | static const std::array RegionSymbols{ 97 | {"J", "U", "E", "", "C", "K", "T"}}; 98 | 99 | std::string region_string; 100 | for (u32 region = 0; region < REGION_COUNT; ++region) { 101 | if (region_lockout & (1 << region)) { 102 | region_string.append(RegionSymbols[region]); 103 | } 104 | } 105 | 106 | if (region_string == "JUECKT") { 107 | return "W"; 108 | } 109 | return region_string; 110 | } 111 | 112 | } // namespace Core 113 | -------------------------------------------------------------------------------- /src/core/file_sys/title_metadata.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Citra Emulator Project / 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "common/common_types.h" 11 | #include "common/swap.h" 12 | #include "core/file_sys/signature.h" 13 | 14 | namespace Core { 15 | 16 | enum TMDContentTypeFlag : u16 { 17 | Encrypted = 1 << 0, 18 | Disc = 1 << 2, 19 | CFM = 1 << 3, 20 | Optional = 1 << 14, 21 | Shared = 1 << 15 22 | }; 23 | 24 | enum TMDContentIndex { Main = 0, Manual = 1, DLP = 2 }; 25 | 26 | /** 27 | * Helper which implements an interface to read and write Title Metadata (TMD) files. 28 | * If a file path is provided and the file exists, it can be parsed and used, otherwise 29 | * it must be created. The TMD file can then be interpreted, modified and/or saved. 30 | */ 31 | class TitleMetadata { 32 | public: 33 | struct ContentChunk { 34 | u32_be id; 35 | u16_be index; 36 | u16_be type; 37 | u64_be size; 38 | std::array hash; 39 | }; 40 | 41 | static_assert(sizeof(ContentChunk) == 0x30, "TMD ContentChunk structure size is wrong"); 42 | 43 | struct ContentInfo { 44 | u16_be index; 45 | u16_be command_count; 46 | std::array hash; 47 | }; 48 | 49 | static_assert(sizeof(ContentInfo) == 0x24, "TMD ContentInfo structure size is wrong"); 50 | 51 | #pragma pack(push, 1) 52 | 53 | struct Body { 54 | std::array issuer; 55 | u8 version; 56 | u8 ca_crl_version; 57 | u8 signer_crl_version; 58 | u8 reserved; 59 | u64_be system_version; 60 | u64_be title_id; 61 | u32_be title_type; 62 | u16_be group_id; 63 | u32_be savedata_size; 64 | u32_be srl_private_savedata_size; 65 | std::array reserved_2; 66 | u8 srl_flag; 67 | std::array reserved_3; 68 | u32_be access_rights; 69 | u16_be title_version; 70 | u16_be content_count; 71 | u16_be boot_content; 72 | std::array reserved_4; 73 | std::array contentinfo_hash; 74 | std::array contentinfo; 75 | }; 76 | 77 | static_assert(sizeof(Body) == 0x9C4, "TMD body structure size is wrong"); 78 | 79 | #pragma pack(pop) 80 | 81 | bool Load(const std::vector file_data, std::size_t offset = 0); 82 | bool Save(FileUtil::IOFile& file); 83 | bool Save(const std::string& file_path); 84 | 85 | void FixHashes(); 86 | bool VerifyHashes() const; 87 | bool ValidateSignature() const; 88 | 89 | std::size_t GetSize() const; 90 | u64 GetTitleID() const; 91 | u32 GetTitleType() const; 92 | u16 GetTitleVersion() const; 93 | std::string GetTitleVersionString() const; 94 | u64 GetSystemVersion() const; 95 | std::size_t GetContentCount() const; 96 | u32 GetBootContentID() const; 97 | u32 GetManualContentID() const; 98 | u32 GetDLPContentID() const; 99 | u32 GetContentIDByIndex(u16 index) const; 100 | u16 GetContentTypeByIndex(u16 index) const; 101 | u64 GetContentSizeByIndex(u16 index) const; 102 | std::array GetContentCTRByIndex(u16 index) const; 103 | 104 | ContentChunk& GetContentChunkByID(u32 content_id); 105 | const ContentChunk& GetContentChunkByID(u32 content_id) const; 106 | bool HasContentID(u32 content_id) const; 107 | 108 | void SetTitleID(u64 title_id); 109 | void SetTitleType(u32 type); 110 | void SetTitleVersion(u16 version); 111 | void SetSystemVersion(u64 version); 112 | void AddContentChunk(const ContentChunk& chunk); 113 | 114 | void Print() const; 115 | 116 | Signature signature; 117 | Body tmd_body; 118 | std::vector tmd_chunks; 119 | }; 120 | 121 | } // namespace Core 122 | -------------------------------------------------------------------------------- /.ci/linux-mingw/scan_dll.py: -------------------------------------------------------------------------------- 1 | try: 2 | import lief 3 | except ImportError: 4 | import pefile 5 | import sys 6 | import re 7 | import os 8 | import queue 9 | import shutil 10 | 11 | # constant definitions 12 | KNOWN_SYS_DLLS = ['WINMM.DLL', 'MSVCRT.DLL', 'VERSION.DLL', 'MPR.DLL', 13 | 'DWMAPI.DLL', 'UXTHEME.DLL', 'DNSAPI.DLL', 'IPHLPAPI.DLL'] 14 | # below is for Ubuntu 18.04 with specified PPA enabled, if you are using 15 | # other distro or different repositories, change the following accordingly 16 | DLL_PATH = [ 17 | '/usr/x86_64-w64-mingw32/bin/', 18 | '/usr/x86_64-w64-mingw32/lib/', 19 | '/usr/lib/gcc/x86_64-w64-mingw32/9.3-posix/' 20 | ] 21 | 22 | missing = [] 23 | 24 | 25 | def parse_imports_lief(filename): 26 | results = [] 27 | pe = lief.parse(filename) 28 | for entry in pe.imports: 29 | name = entry.name 30 | if name.upper() not in KNOWN_SYS_DLLS and not re.match(string=name, pattern=r'.*32\.DLL'): 31 | results.append(name) 32 | return results 33 | 34 | 35 | def parse_imports(file_name): 36 | if globals().get('lief'): 37 | return parse_imports_lief(file_name) 38 | 39 | results = [] 40 | pe = pefile.PE(file_name, fast_load=True) 41 | pe.parse_data_directories() 42 | 43 | for entry in pe.DIRECTORY_ENTRY_IMPORT: 44 | current = entry.dll.decode() 45 | current_u = current.upper() # b/c Windows is often case insensitive 46 | # here we filter out system dlls 47 | # dll w/ names like *32.dll are likely to be system dlls 48 | if current_u.upper() not in KNOWN_SYS_DLLS and not re.match(string=current_u, pattern=r'.*32\.DLL'): 49 | results.append(current) 50 | 51 | return results 52 | 53 | 54 | def parse_imports_recursive(file_name, path_list=[]): 55 | q = queue.Queue() # create a FIFO queue 56 | # file_name can be a string or a list for the convience 57 | if isinstance(file_name, str): 58 | q.put(file_name) 59 | elif isinstance(file_name, list): 60 | for i in file_name: 61 | q.put(i) 62 | full_list = [] 63 | while q.qsize(): 64 | current = q.get_nowait() 65 | print('> %s' % current) 66 | deps = parse_imports(current) 67 | # if this dll does not have any import, ignore it 68 | if not deps: 69 | continue 70 | for dep in deps: 71 | # the dependency already included in the list, skip 72 | if dep in full_list: 73 | continue 74 | # find the requested dll in the provided paths 75 | full_path = find_dll(dep) 76 | if not full_path: 77 | missing.append(dep) 78 | continue 79 | full_list.append(dep) 80 | q.put(full_path) 81 | path_list.append(full_path) 82 | return full_list 83 | 84 | 85 | def find_dll(name): 86 | for path in DLL_PATH: 87 | for root, _, files in os.walk(path): 88 | for f in files: 89 | if name.lower() == f.lower(): 90 | return os.path.join(root, f) 91 | 92 | 93 | def deploy(name, dst, dry_run=False): 94 | dlls_path = [] 95 | parse_imports_recursive(name, dlls_path) 96 | for dll_entry in dlls_path: 97 | if not dry_run: 98 | shutil.copy(dll_entry, dst) 99 | else: 100 | print('[Dry-Run] Copy %s to %s' % (dll_entry, dst)) 101 | print('Deploy completed.') 102 | return dlls_path 103 | 104 | 105 | def main(): 106 | if len(sys.argv) < 3: 107 | print('Usage: %s [files to examine ...] [target deploy directory]') 108 | return 1 109 | to_deploy = sys.argv[1:-1] 110 | tgt_dir = sys.argv[-1] 111 | if not os.path.isdir(tgt_dir): 112 | print('%s is not a directory.' % tgt_dir) 113 | return 1 114 | print('Scanning dependencies...') 115 | deploy(to_deploy, tgt_dir) 116 | if missing: 117 | print('Following DLLs are not found: %s' % ('\n'.join(missing))) 118 | return 0 119 | 120 | 121 | if __name__ == '__main__': 122 | main() 123 | -------------------------------------------------------------------------------- /src/core/file_sys/data/extdata.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include "core/file_sys/data/data_container.h" 6 | #include "core/file_sys/data/extdata.h" 7 | #include "core/sdmc_decryptor.h" 8 | 9 | namespace Core { 10 | 11 | Extdata::Extdata(std::string data_path_, const SDMCDecryptor& decryptor_) 12 | : data_path(std::move(data_path_)), decryptor(&decryptor_) { 13 | 14 | if (data_path.back() != '/' && data_path.back() != '\\') { 15 | data_path += '/'; 16 | } 17 | 18 | use_decryptor = true; 19 | is_good = Init(); 20 | } 21 | 22 | Extdata::Extdata(std::string data_path_) : data_path(std::move(data_path_)) { 23 | if (data_path.back() != '/' && data_path.back() != '\\') { 24 | data_path += '/'; 25 | } 26 | 27 | use_decryptor = false; 28 | is_good = Init(); 29 | } 30 | 31 | Extdata::~Extdata() = default; 32 | 33 | bool Extdata::CheckMagic() const { 34 | if (header.fat_header.magic != MakeMagic('V', 'S', 'X', 'E') || 35 | header.fat_header.version != 0x30000) { 36 | 37 | LOG_ERROR(Core, "File is invalid, decryption errors may have happened."); 38 | return false; 39 | } 40 | return true; 41 | } 42 | 43 | bool Extdata::IsGood() const { 44 | return is_good; 45 | } 46 | 47 | bool Extdata::Extract(std::string path) const { 48 | if (path.back() != '/' && path.back() != '\\') { 49 | path += '/'; 50 | } 51 | 52 | if (!ExtractDirectory(path, 1)) { 53 | return false; 54 | } 55 | 56 | // Write format info 57 | const auto format_info = GetFormatInfo(); 58 | return FileUtil::WriteBytesToFile(path + "metadata", &format_info, sizeof(format_info)); 59 | } 60 | 61 | std::vector Extdata::ReadFile(const std::string& path) const { 62 | if (use_decryptor) { 63 | return decryptor->DecryptFile(path); 64 | } else { 65 | FileUtil::IOFile file(path, "rb"); 66 | return file.GetData(); 67 | } 68 | } 69 | 70 | bool Extdata::Init() { 71 | // Read VSXE file 72 | auto vsxe_raw = ReadFile(data_path + "00000000/00000001"); 73 | if (vsxe_raw.empty()) { 74 | LOG_ERROR(Core, "Failed to load or decrypt VSXE"); 75 | return false; 76 | } 77 | 78 | const DataContainer vsxe_container(std::move(vsxe_raw)); 79 | if (!vsxe_container.IsGood()) { 80 | return false; 81 | } 82 | 83 | std::vector> data; 84 | if (!vsxe_container.GetIVFCLevel4Data(data)) { 85 | return false; 86 | } 87 | 88 | return Archive::Init(std::move(data)); 89 | } 90 | 91 | bool Extdata::ExtractFile(const std::string& path, u32 index) const { 92 | /// Maximum amount of device files a device directory can hold. 93 | constexpr u32 DeviceDirCapacity = 126; 94 | 95 | const u32 file_index = index + 1; 96 | const u32 sub_directory_id = file_index / DeviceDirCapacity; 97 | const u32 sub_file_id = file_index % DeviceDirCapacity; 98 | const std::string device_file_path = 99 | fmt::format("{}{:08x}/{:08x}", data_path, sub_directory_id, sub_file_id); 100 | 101 | auto container_data = ReadFile(device_file_path); 102 | if (container_data.empty()) { // File does not exist? 103 | LOG_WARNING(Core, "Ignoring file {}", device_file_path); 104 | return true; 105 | } 106 | 107 | const DataContainer container(std::move(container_data)); 108 | if (!container.IsGood()) { 109 | return false; 110 | } 111 | 112 | std::vector> data; 113 | if (!container.GetIVFCLevel4Data(data)) { 114 | return false; 115 | } 116 | 117 | return FileUtil::WriteBytesToFile(path, data[0].data(), data[0].size()); 118 | } 119 | 120 | ArchiveFormatInfo Extdata::GetFormatInfo() const { 121 | // This information is based on how Citra created the metadata in FS 122 | return {/* total_size */ 0, 123 | /* number_directories */ fs_info.maximum_directory_count, 124 | /* number_files */ fs_info.maximum_file_count, 125 | /* duplicate_data */ false}; 126 | } 127 | 128 | } // namespace Core 129 | -------------------------------------------------------------------------------- /src/core/db/title_db.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include "common/file_util.h" 6 | #include "common/logging/log.h" 7 | #include "core/db/title_db.h" 8 | #include "core/file_sys/data/data_container.h" 9 | 10 | namespace Core { 11 | 12 | bool TitleDB::AddFromData(std::vector data) { 13 | return Init(std::move(data)); 14 | } 15 | 16 | bool TitleDB::AddFromFile(const std::string& path) { 17 | FileUtil::IOFile file(path, "rb"); 18 | DataContainer container(file.GetData()); 19 | std::vector> data; 20 | if (container.IsGood() && container.GetIVFCLevel4Data(data)) { 21 | return Init(std::move(data[0])); 22 | } else { 23 | return false; 24 | } 25 | } 26 | 27 | TitleDB::~TitleDB() = default; 28 | 29 | bool TitleDB::Init(std::vector data) { 30 | if (!InnerFAT_TitleDB::Init({std::move(data)})) { 31 | return false; 32 | } 33 | 34 | u32 cur = directory_entry_table[1].first_file_index; 35 | while (cur != 0) { 36 | if (!LoadTitleInfo(cur)) { 37 | return false; 38 | } 39 | cur = file_entry_table[cur].next_sibling_index; 40 | } 41 | return true; 42 | } 43 | 44 | bool TitleDB::CheckMagic() const { 45 | if (header.pre_header.db_magic != MakeMagic('N', 'A', 'N', 'D', 'T', 'D', 'B', 0) && 46 | header.pre_header.db_magic != MakeMagic('T', 'E', 'M', 'P', 'T', 'D', 'B', 0)) { 47 | 48 | LOG_ERROR(Core, "File is invalid, decryption errors may have happened."); 49 | return false; 50 | } 51 | 52 | if (header.fat_header.magic != MakeMagic('B', 'D', 'R', 'I') || 53 | header.fat_header.version != 0x30000) { 54 | 55 | LOG_ERROR(Core, "File is invalid, decryption errors may have happened."); 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | bool TitleDB::LoadTitleInfo(u32 index) { 62 | std::vector data; 63 | if (!GetFileData(data, index)) { 64 | return false; 65 | } 66 | 67 | TitleInfoEntry title; 68 | if (data.size() != sizeof(TitleInfoEntry)) { 69 | LOG_ERROR(Core, "Entry {} has incorrect size", index); 70 | } 71 | std::memcpy(&title, data.data(), data.size()); 72 | 73 | titles.emplace(file_entry_table[index].title_id, title); 74 | return true; 75 | } 76 | 77 | bool TicketDB::AddFromFile(const std::string& path) { 78 | FileUtil::IOFile file(path, "rb"); 79 | DataContainer container(file.GetData()); 80 | std::vector> data; 81 | if (container.IsGood() && container.GetIVFCLevel4Data(data)) { 82 | return Init(std::move(data[0])); 83 | } else { 84 | return false; 85 | } 86 | } 87 | 88 | TicketDB::~TicketDB() = default; 89 | 90 | bool TicketDB::Init(std::vector data) { 91 | if (!InnerFAT_TicketDB::Init({std::move(data)})) { 92 | return false; 93 | } 94 | 95 | u32 cur = directory_entry_table[1].first_file_index; 96 | while (cur != 0) { 97 | if (!LoadTicket(cur)) { 98 | return false; 99 | } 100 | cur = file_entry_table[cur].next_sibling_index; 101 | } 102 | return true; 103 | } 104 | 105 | bool TicketDB::CheckMagic() const { 106 | if (header.pre_header.db_magic != MakeMagic('T', 'I', 'C', 'K')) { 107 | LOG_ERROR(Core, "File is invalid, decryption errors may have happened."); 108 | return false; 109 | } 110 | 111 | if (header.fat_header.magic != MakeMagic('B', 'D', 'R', 'I') || 112 | header.fat_header.version != 0x30000) { 113 | 114 | LOG_ERROR(Core, "File is invalid, decryption errors may have happened."); 115 | return false; 116 | } 117 | return true; 118 | } 119 | 120 | bool TicketDB::LoadTicket(u32 index) { 121 | std::vector data; 122 | if (!GetFileData(data, index)) { 123 | return false; 124 | } 125 | 126 | Ticket ticket; 127 | if (!ticket.Load(std::move(data), 8)) { // there is a 8-byte header 128 | return false; 129 | } 130 | tickets.emplace(file_entry_table[index].title_id, ticket); 131 | return true; 132 | } 133 | 134 | } // namespace Core 135 | -------------------------------------------------------------------------------- /src/core/file_sys/ticket.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include "common/alignment.h" 9 | #include "common/assert.h" 10 | #include "common/common_funcs.h" 11 | #include "common/file_util.h" 12 | #include "common/string_util.h" 13 | #include "core/file_sys/cia_common.h" 14 | #include "core/file_sys/ticket.h" 15 | 16 | namespace Core { 17 | 18 | bool Ticket::Load(const std::vector file_data, std::size_t offset) { 19 | if (!signature.Load(file_data, offset)) { 20 | return false; 21 | } 22 | TRY_MEMCPY(&body, file_data, offset + signature.GetSize(), sizeof(Body)); 23 | 24 | // Load content index 25 | const std::size_t content_index_offset = offset + signature.GetSize() + sizeof(Body); 26 | 27 | struct ContentIndexHeader { 28 | INSERT_PADDING_BYTES(4); 29 | u32_be content_index_size; 30 | }; 31 | ContentIndexHeader header; 32 | 33 | TRY_MEMCPY(&header, file_data, content_index_offset, sizeof(header)); 34 | if (static_cast(header.content_index_size) > 0x10000) { // sanity limit 35 | LOG_ERROR(Core, "Content index size too big"); 36 | return false; 37 | } 38 | 39 | content_index.resize(static_cast(header.content_index_size)); 40 | TRY_MEMCPY(content_index.data(), file_data, content_index_offset, content_index.size()); 41 | return true; 42 | } 43 | 44 | bool Ticket::Save(FileUtil::IOFile& file) const { 45 | // signature 46 | if (!signature.Save(file)) { 47 | return false; 48 | } 49 | 50 | // body 51 | if (file.WriteBytes(&body, sizeof(body)) != sizeof(body)) { 52 | LOG_ERROR(Core, "Failed to write body"); 53 | return false; 54 | } 55 | 56 | // content index 57 | if (file.WriteBytes(content_index.data(), content_index.size()) != content_index.size()) { 58 | LOG_ERROR(Core, "Failed to save content index"); 59 | return false; 60 | } 61 | 62 | return true; 63 | } 64 | 65 | bool Ticket::ValidateSignature() const { 66 | const auto issuer = 67 | Common::StringFromFixedZeroTerminatedBuffer(body.issuer.data(), body.issuer.size()); 68 | return signature.Verify(issuer, [this](CryptoPP::PK_MessageAccumulator* message) { 69 | message->Update(reinterpret_cast(&body), sizeof(body)); 70 | message->Update(content_index.data(), content_index.size()); 71 | }); 72 | } 73 | 74 | std::size_t Ticket::GetSize() const { 75 | return signature.GetSize() + sizeof(body) + content_index.size(); 76 | } 77 | 78 | constexpr std::string_view TicketIssuer = "Root-CA00000003-XS0000000c"; 79 | 80 | // TODO: Make use of this? 81 | constexpr std::string_view TicketIssuerDev = "Root-CA00000004-XS00000009"; 82 | 83 | // From GodMode9 84 | constexpr std::array TicketContentIndex{ 85 | {0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 86 | 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 87 | 0x00, 0x84, 0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; 88 | 89 | // Values taken from GodMode9 90 | Ticket BuildFakeTicket(u64 title_id) { 91 | Ticket ticket{}; 92 | 93 | ticket.signature.type = 0x10004; // RSA_2048 SHA256 94 | ticket.signature.data.resize(0x100); // Size of RSA_2048 signature 95 | std::memset(ticket.signature.data.data(), 0xFF, ticket.signature.data.size()); 96 | 97 | auto& body = ticket.body; 98 | std::memcpy(body.issuer.data(), TicketIssuer.data(), TicketIssuer.size()); 99 | std::memset(body.ecc_public_key.data(), 0xFF, body.ecc_public_key.size()); 100 | body.version = 0x01; 101 | std::memset(body.title_key.data(), 0xFF, body.title_key.size()); 102 | body.title_id = title_id; 103 | body.common_key_index = 0x00; 104 | body.audit = 0x01; 105 | std::memcpy(ticket.content_index.data(), TicketContentIndex.data(), TicketContentIndex.size()); 106 | // GodMode9 by default sets all remaining 0x80 bytes to 0xFF 107 | std::memset(ticket.content_index.data() + TicketContentIndex.size(), 0xFF, 0x80); 108 | return ticket; 109 | } 110 | 111 | } // namespace Core 112 | -------------------------------------------------------------------------------- /src/core/file_sys/data/data_container.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include "common/common_funcs.h" 10 | #include "common/common_types.h" 11 | #include "common/swap.h" 12 | 13 | namespace Core { 14 | 15 | #pragma pack(push, 1) 16 | struct DataDescriptor { 17 | u64_le offset; 18 | u64_le size; 19 | }; 20 | 21 | struct DISAHeader { 22 | u32_le magic; 23 | u32_le version; 24 | u32_le partition_count; 25 | INSERT_PADDING_BYTES(4); 26 | u64_le secondary_partition_table_offset; 27 | u64_le primary_partition_table_offset; 28 | u64_le partition_table_size; 29 | std::array partition_descriptors; 30 | std::array partitions; 31 | u8 active_partition_table; 32 | INSERT_PADDING_BYTES(3); 33 | std::array sha_hash; 34 | INSERT_PADDING_BYTES(0x74); 35 | }; 36 | static_assert(sizeof(DISAHeader) == 0x100, "Size of DISA header is incorrect"); 37 | 38 | struct DIFFHeader { 39 | u32_le magic; 40 | u32_le version; 41 | u64_le secondary_partition_table_offset; 42 | u64_le primary_partition_table_offset; 43 | u64_le partition_table_size; 44 | DataDescriptor partition_A; 45 | u8 active_partition_table; 46 | INSERT_PADDING_BYTES(3); 47 | std::array sha_hash; 48 | u64_le unique_identifier; 49 | INSERT_PADDING_BYTES(0xA4); 50 | }; 51 | static_assert(sizeof(DIFFHeader) == 0x100, "Size of DIFF header is incorrect"); 52 | 53 | struct DIFIHeader { 54 | u32_le magic; 55 | u32_le version; 56 | DataDescriptor ivfc; 57 | DataDescriptor dpfs; 58 | DataDescriptor partition_hash; 59 | u8 enable_external_IVFC_level_4; 60 | u8 dpfs_level1_selector; 61 | INSERT_PADDING_BYTES(2); 62 | u64_le external_IVFC_level_4_offset; 63 | }; 64 | static_assert(sizeof(DIFIHeader) == 0x44, "Size of DIFI header is incorrect"); 65 | 66 | /// Descriptor for both IVFC and DPFS levels 67 | struct LevelDescriptor { 68 | u64_le offset; 69 | u64_le size; 70 | u32_le block_size; // In log2 71 | INSERT_PADDING_BYTES(4); 72 | }; 73 | static_assert(sizeof(LevelDescriptor) == 0x18, "Size of level descriptor is incorrect"); 74 | 75 | struct IVFCDescriptor { 76 | u32_le magic; 77 | u32_le version; 78 | u64_le master_hash_size; 79 | std::array levels; 80 | u64_le descriptor_size; 81 | }; 82 | static_assert(sizeof(IVFCDescriptor) == 0x78, "Size of IVFC descriptor is incorrect"); 83 | 84 | struct DPFSDescriptor { 85 | u32_le magic; 86 | u32_le version; 87 | std::array levels; 88 | }; 89 | static_assert(sizeof(DPFSDescriptor) == 0x50, "Size of DPFS descriptor is incorrect"); 90 | #pragma pack(pop) 91 | 92 | class DPFSContainer { 93 | public: 94 | explicit DPFSContainer(DPFSDescriptor descriptor, u8 level1_selector, std::vector data); 95 | 96 | /// Unwraps the DPFS Tree, returning actual data in Level3. 97 | bool GetLevel3Data(std::vector& out) const; 98 | 99 | private: 100 | bool GetBit(u8& out, u8 level, u8 selector, u64 index) const; 101 | bool GetByte(u8& out, u8 level, u8 selector, u64 index) const; 102 | 103 | DPFSDescriptor descriptor; 104 | u8 level1_selector; 105 | std::vector data; 106 | }; 107 | 108 | /** 109 | * DISA/DIFF Container. 110 | */ 111 | class DataContainer { 112 | public: 113 | explicit DataContainer(std::vector data); 114 | ~DataContainer(); 115 | 116 | /// Unwraps the whole container, returning the data in IVFC Level 4 of all partitions. 117 | bool GetIVFCLevel4Data(std::vector>& out) const; 118 | 119 | bool IsGood() const; 120 | 121 | private: 122 | bool InitAsDISA(); 123 | bool InitAsDIFF(); 124 | 125 | /// Unwraps the whole container, returning the data in IVFC Level 4 of a partition. 126 | bool GetPartitionData(std::vector& out, u8 index) const; 127 | 128 | bool is_good = false; 129 | std::vector data; 130 | u32 partition_count; 131 | u64_le partition_table_offset; 132 | std::vector partition_descriptors; 133 | std::vector partitions; 134 | }; 135 | 136 | } // namespace Core 137 | -------------------------------------------------------------------------------- /src/core/cia_builder.h: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Citra Emulator Project / 2020 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "common/file_util.h" 11 | #include "common/progress_callback.h" 12 | #include "common/swap.h" 13 | #include "core/file_decryptor.h" 14 | #include "core/file_sys/cia_common.h" 15 | #include "core/file_sys/ncch_container.h" 16 | #include "core/file_sys/title_metadata.h" 17 | #include "core/key/key.h" 18 | 19 | namespace Core { 20 | 21 | constexpr std::size_t CIA_CONTENT_MAX_COUNT = 0x10000; 22 | constexpr std::size_t CIA_CONTENT_BITS_SIZE = (CIA_CONTENT_MAX_COUNT / 8); 23 | constexpr std::size_t CIA_HEADER_SIZE = 0x2020; 24 | constexpr std::size_t CIA_CERT_SIZE = 0xA00; 25 | constexpr std::size_t CIA_METADATA_SIZE = 0x3AC0; 26 | 27 | struct Config; 28 | class EncTitleKeysBin; 29 | class HashedFile; 30 | class Ticket; 31 | class TicketDB; 32 | 33 | class CIABuilder { 34 | public: 35 | explicit CIABuilder(const Config& config, std::shared_ptr ticket_db); 36 | ~CIABuilder(); 37 | 38 | /** 39 | * Initializes the building of the CIA. 40 | * @return true on success, false otherwise 41 | */ 42 | bool Init(CIABuildType type, const std::string& destination, TitleMetadata tmd, 43 | std::size_t total_size, const Common::ProgressCallback& callback); 44 | 45 | void Cleanup(); 46 | 47 | /** 48 | * Adds an NCCH content to the CIA. 49 | * @return true on success, false otherwise 50 | */ 51 | bool AddContent(u16 content_id, NCCHContainer& ncch); 52 | 53 | /** 54 | * Finalizes this CIA and write remaining data. 55 | * @return true on success, false otherwise 56 | */ 57 | bool Finalize(); 58 | 59 | /** 60 | * Aborts the current work. In fact, only usable during AddContent. 61 | */ 62 | void Abort(); 63 | 64 | private: 65 | struct Header { 66 | u32_le header_size; 67 | u16_le type; 68 | u16_le version; 69 | u32_le cert_size; 70 | u32_le tik_size; 71 | u32_le tmd_size; 72 | u32_le meta_size; 73 | u64_le content_size; 74 | std::array content_present; 75 | 76 | bool IsContentPresent(u16 index) const { 77 | // The content_present is a bit array which defines which content in the TMD 78 | // is included in the CIA, so check the bit for this index and add if set. 79 | // The bits in the content index are arranged w/ index 0 as the MSB, 7 as the LSB, etc. 80 | return (content_present[index >> 3] & (0x80 >> (index & 7))); 81 | } 82 | 83 | void SetContentPresent(u16 index) { 84 | content_present[index >> 3] |= (0x80 >> (index & 7)); 85 | } 86 | }; 87 | 88 | static_assert(sizeof(Header) == CIA_HEADER_SIZE, "CIA Header structure size is wrong"); 89 | 90 | struct Metadata { 91 | std::array dependencies; 92 | std::array reserved; 93 | u32_le core_version; 94 | std::array reserved_2; 95 | std::array icon_data; 96 | }; 97 | 98 | static_assert(sizeof(Metadata) == CIA_METADATA_SIZE, "CIA Metadata structure size is wrong"); 99 | 100 | bool WriteCert(); 101 | 102 | bool FindLegitTicket(Ticket& ticket, u64 title_id) const; 103 | Ticket BuildStandardTicket(u64 title_id) const; 104 | bool WriteTicket(); 105 | 106 | // Persistent state 107 | const std::shared_ptr ticket_db; 108 | std::unique_ptr enc_title_keys_bin; 109 | 110 | // State of a single task 111 | CIABuildType type; 112 | 113 | Header header{}; 114 | Metadata meta{}; 115 | 116 | TitleMetadata tmd; 117 | Key::AESKey title_key{}; 118 | 119 | std::size_t cert_offset{}; 120 | std::size_t ticket_offset{}; 121 | std::size_t tmd_offset{}; 122 | std::size_t content_offset{}; 123 | 124 | std::shared_ptr file; 125 | std::size_t written{}; // size written (with alignment) 126 | std::size_t total_size{}; 127 | Common::ProgressCallback callback; 128 | Common::ProgressCallbackWrapper wrapper; 129 | 130 | // The NCCH to abort on 131 | std::mutex abort_ncch_mutex; 132 | NCCHContainer* abort_ncch{}; 133 | 134 | FileDecryptor decryptor; 135 | }; 136 | 137 | } // namespace Core 138 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Enable modules to include each other's files 2 | include_directories(.) 3 | 4 | # CMake seems to only define _DEBUG on Windows 5 | set_property(DIRECTORY APPEND PROPERTY 6 | COMPILE_DEFINITIONS $<$:_DEBUG> $<$>:NDEBUG>) 7 | 8 | # Ensure that projects build with Unicode support. 9 | add_definitions(-DUNICODE -D_UNICODE) 10 | 11 | # Set compilation flags 12 | if (MSVC) 13 | set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) 14 | 15 | # Silence "deprecation" warnings 16 | add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS) 17 | 18 | # Avoid windows.h junk 19 | add_definitions(-DNOMINMAX) 20 | 21 | # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. 22 | add_definitions(-DWIN32_LEAN_AND_MEAN) 23 | 24 | # /W4 - Level 4 warnings 25 | # /w34263 - Non-virtual member function hides base class virtual function 26 | # /w44265 - Class has virtual functions, but destructor is not virtual 27 | # /w34456 - Declaration of 'var' hides previous local declaration 28 | # /w34457 - Declaration of 'var' hides function parameter 29 | # /w34458 - Declaration of 'var' hides class member 30 | # /w34459 - Declaration of 'var' hides global definition 31 | # /w34946 - Reinterpret-cast between related types 32 | # /wd4592 - Symbol will be dynamically initialized (implementation limitation) 33 | # /MP - Multi-threaded compilation 34 | # /Zi - Output debugging information 35 | # /Zo - Enhanced debug info for optimized builds 36 | # /permissive- - Enables stricter C++ standards conformance checks 37 | # /EHsc - C++-only exception handling semantics 38 | # /volatile:iso - Use strict standards-compliant volatile semantics. 39 | # /Zc:externConstexpr - Allow extern constexpr variables to have external linkage, like the standard mandates 40 | # /Zc:inline - Let codegen omit inline functions in object files 41 | # /Zc:throwingNew - Let codegen assume `operator new` (without std::nothrow) will never return null 42 | # /Zc:preprocessor - Use std-conforming MSVC preprocessor 43 | add_compile_options( 44 | /W4 45 | /w34263 46 | /w44265 47 | /w34456 48 | /w34457 49 | /w34458 50 | /w34459 51 | /w34946 52 | /wd4592 53 | /MP 54 | /Zi 55 | /Zo 56 | /permissive- 57 | /EHsc 58 | /std:c++latest 59 | /volatile:iso 60 | /Zc:externConstexpr 61 | /Zc:inline 62 | /Zc:throwingNew 63 | /Zc:preprocessor 64 | ) 65 | 66 | # /GS- - No stack buffer overflow checks 67 | add_compile_options("$<$:/GS->") 68 | 69 | if (WARNINGS_AS_ERRORS) 70 | add_compile_options(/WX) 71 | endif() 72 | 73 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE) 74 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) 75 | else() 76 | add_compile_options( 77 | -Wall 78 | -Wno-attributes 79 | -Wno-unused-variable 80 | ) 81 | 82 | if (WARNINGS_AS_ERRORS) 83 | add_compile_options(-Werror -Wfatal-errors) 84 | endif() 85 | 86 | if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang) 87 | add_compile_options("-stdlib=libc++") 88 | endif() 89 | 90 | # Set file offset size to 64 bits. 91 | # 92 | # On modern Unixes, this is typically already the case. The lone exception is 93 | # glibc, which may default to 32 bits. glibc allows this to be configured 94 | # by setting _FILE_OFFSET_BITS. 95 | if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) 96 | add_definitions(-D_FILE_OFFSET_BITS=64) 97 | endif() 98 | 99 | if (MINGW) 100 | add_definitions(-DMINGW_HAS_SECURE_API) 101 | if (COMPILE_WITH_DWARF) 102 | add_compile_options("-gdwarf") 103 | endif() 104 | 105 | if (MINGW_STATIC_BUILD) 106 | add_definitions(-DQT_STATICPLUGIN) 107 | add_compile_options("-static") 108 | endif() 109 | endif() 110 | 111 | # if (NOT DEBUG) 112 | # add_compile_options("-flto") 113 | # endif() 114 | endif() 115 | 116 | add_subdirectory(common) 117 | add_subdirectory(core) 118 | add_subdirectory(frontend) 119 | -------------------------------------------------------------------------------- /src/core/sdmc_decryptor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "common/assert.h" 13 | #include "common/file_util.h" 14 | #include "common/string_util.h" 15 | #include "core/key/key.h" 16 | #include "core/sdmc_decryptor.h" 17 | 18 | namespace Core { 19 | 20 | SDMCDecryptor::SDMCDecryptor(const std::string& root_folder_) : root_folder(root_folder_) { 21 | 22 | ASSERT_MSG(Key::IsNormalKeyAvailable(Key::SDKey), 23 | "SD Key must be available in order to decrypt"); 24 | 25 | if (root_folder.back() == '/' || root_folder.back() == '\\') { 26 | // Remove '/' or '\' character at the end as we will add them back when combining path 27 | root_folder.erase(root_folder.size() - 1); 28 | } 29 | } 30 | 31 | SDMCDecryptor::~SDMCDecryptor() = default; 32 | 33 | namespace { 34 | std::array GetFileCTR(const std::string& path) { 35 | auto path_utf16 = Common::UTF8ToUTF16(path); 36 | std::vector path_data(path_utf16.size() * 2 + 2, 0); // Add the '\0' character 37 | std::memcpy(path_data.data(), path_utf16.data(), path_utf16.size() * 2); 38 | 39 | CryptoPP::SHA256 sha; 40 | std::array hash; 41 | sha.CalculateDigest(hash.data(), path_data.data(), path_data.size()); 42 | 43 | std::array ctr; 44 | for (int i = 0; i < 16; i++) { 45 | ctr[i] = hash[i] ^ hash[16 + i]; 46 | } 47 | return ctr; 48 | } 49 | } // namespace 50 | 51 | bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source, const std::string& destination, 52 | const Common::ProgressCallback& callback) { 53 | if (!FileUtil::CreateFullPath(destination)) { 54 | LOG_ERROR(Core, "Could not create path {}", destination); 55 | return false; 56 | } 57 | 58 | auto key = Key::GetNormalKey(Key::SDKey); 59 | auto ctr = GetFileCTR(source); 60 | file_decryptor.SetCrypto(CreateCTRCrypto(key, ctr)); 61 | 62 | auto source_file = std::make_shared(root_folder + source, "rb"); 63 | auto size = source_file->GetSize(); 64 | auto destination_file = std::make_shared(destination, "wb"); 65 | return file_decryptor.CryptAndWriteFile(std::move(source_file), size, 66 | std::move(destination_file), callback); 67 | } 68 | 69 | void SDMCDecryptor::Abort() { 70 | file_decryptor.Abort(); 71 | } 72 | 73 | std::vector SDMCDecryptor::DecryptFile(const std::string& source) const { 74 | auto ctr = GetFileCTR(source); 75 | auto key = Key::GetNormalKey(Key::SDKey); 76 | CryptoPP::CTR_Mode::Decryption aes; 77 | aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); 78 | 79 | FileUtil::IOFile file(root_folder + source, "rb"); 80 | std::vector encrypted_data = file.GetData(); 81 | if (encrypted_data.empty()) { 82 | LOG_ERROR(Core, "Failed to read from {}", root_folder + source); 83 | return {}; 84 | } 85 | 86 | std::vector data(file.GetSize()); 87 | aes.ProcessData(data.data(), encrypted_data.data(), encrypted_data.size()); 88 | return data; 89 | } 90 | 91 | struct SDMCFile::Impl { 92 | CryptoPP::CTR_Mode::Decryption aes; 93 | std::array original_ctr; 94 | std::array key; 95 | }; 96 | 97 | SDMCFile::SDMCFile(std::string root_folder, const std::string& filename, const char openmode[], 98 | int flags) { 99 | 100 | impl = std::make_unique(); 101 | 102 | if (root_folder.back() == '/' || root_folder.back() == '\\') { 103 | // Remove '/' or '\' character at the end as we will add them back when combining path 104 | root_folder.erase(root_folder.size() - 1); 105 | } 106 | 107 | impl->original_ctr = GetFileCTR(filename); 108 | impl->key = Key::GetNormalKey(Key::SDKey); 109 | impl->aes.SetKeyWithIV(impl->key.data(), impl->key.size(), impl->original_ctr.data()); 110 | 111 | Open(root_folder + filename, openmode, flags); 112 | } 113 | 114 | SDMCFile::~SDMCFile() { 115 | Close(); 116 | } 117 | 118 | std::size_t SDMCFile::Read(char* data, std::size_t length) { 119 | const std::size_t length_read = FileUtil::IOFile::Read(data, length); 120 | DecryptData(reinterpret_cast(data), length_read); 121 | return length_read; 122 | } 123 | 124 | std::size_t SDMCFile::Write([[maybe_unused]] const char* data, 125 | [[maybe_unused]] std::size_t length) { 126 | UNREACHABLE_MSG("Cannot write to a SDMCFile"); 127 | } 128 | 129 | bool SDMCFile::Seek(s64 off, int origin) { 130 | if (!FileUtil::IOFile::Seek(off, origin)) { 131 | return false; 132 | } 133 | impl->aes.Seek(Tell()); 134 | return true; 135 | } 136 | 137 | void SDMCFile::DecryptData(u8* data, std::size_t size) { 138 | impl->aes.ProcessData(data, data, size); 139 | } 140 | 141 | } // namespace Core 142 | -------------------------------------------------------------------------------- /src/core/file_sys/certificate.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Pengfei Zhu 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include "common/alignment.h" 9 | #include "common/assert.h" 10 | #include "common/common_funcs.h" 11 | #include "common/file_util.h" 12 | #include "common/logging/log.h" 13 | #include "common/string_util.h" 14 | #include "core/file_sys/certificate.h" 15 | #include "core/file_sys/cia_common.h" 16 | #include "core/file_sys/data/data_container.h" 17 | 18 | namespace Core { 19 | 20 | // Sizes include padding (0x34 for RSA, 0x3C for ECC) 21 | inline std::size_t GetPublicKeySize(u32 public_key_type) { 22 | switch (public_key_type) { 23 | case PublicKeyType::RSA_4096: 24 | return 0x238; 25 | case PublicKeyType::RSA_2048: 26 | return 0x138; 27 | case PublicKeyType::ECC: 28 | return 0x78; 29 | } 30 | 31 | LOG_ERROR(Common_Filesystem, "Tried to read cert with bad public key {}", public_key_type); 32 | return 0; 33 | } 34 | 35 | bool Certificate::Load(std::vector file_data, std::size_t offset) { 36 | if (!signature.Load(file_data, offset)) { 37 | return false; 38 | } 39 | // certificate body 40 | const auto signature_size = signature.GetSize(); 41 | TRY_MEMCPY(&body, file_data, offset + signature_size, sizeof(Body)); 42 | 43 | // Public key lengths are variable 44 | const auto public_key_size = GetPublicKeySize(body.key_type); 45 | if (public_key_size == 0) { 46 | return false; 47 | } 48 | public_key.resize(public_key_size); 49 | 50 | const auto public_key_offset = offset + signature_size + sizeof(Body); 51 | TRY_MEMCPY(public_key.data(), file_data, public_key_offset, public_key.size()); 52 | return true; 53 | } 54 | 55 | bool Certificate::Save(FileUtil::IOFile& file) const { 56 | // signature 57 | if (!signature.Save(file)) { 58 | return false; 59 | } 60 | 61 | // body 62 | if (file.WriteBytes(&body, sizeof(body)) != sizeof(body)) { 63 | LOG_ERROR(Core, "Failed to write body"); 64 | return false; 65 | } 66 | 67 | // public key 68 | if (file.WriteBytes(public_key.data(), public_key.size()) != public_key.size()) { 69 | LOG_ERROR(Core, "Failed to write public key"); 70 | return false; 71 | } 72 | 73 | return true; 74 | } 75 | 76 | std::size_t Certificate::GetSize() const { 77 | return signature.GetSize() + sizeof(Body) + public_key.size(); 78 | } 79 | 80 | std::pair Certificate::GetRSAPublicKey() const { 81 | if (body.key_type == PublicKeyType::RSA_2048) { 82 | return {CryptoPP::Integer(public_key.data(), 0x100), 83 | CryptoPP::Integer(public_key.data() + 0x100, 0x4)}; 84 | } else if (body.key_type == PublicKeyType::RSA_4096) { 85 | return {CryptoPP::Integer(public_key.data(), 0x200), 86 | CryptoPP::Integer(public_key.data() + 0x200, 0x4)}; 87 | } else { 88 | UNREACHABLE_MSG("Certificate is not RSA"); 89 | } 90 | } 91 | 92 | namespace Certs { 93 | 94 | static std::unordered_map g_certs; 95 | static bool g_is_loaded = false; 96 | 97 | bool Load(const std::string& path) { 98 | Clear(); 99 | 100 | FileUtil::IOFile file(path, "rb"); 101 | DataContainer container(file.GetData()); 102 | std::vector> data; 103 | if (!container.IsGood() || !container.GetIVFCLevel4Data(data)) { 104 | return false; 105 | } 106 | 107 | CertsDBHeader header; 108 | TRY_MEMCPY(&header, data[0], 0, sizeof(header)); 109 | 110 | if (header.magic != MakeMagic('C', 'E', 'R', 'T')) { 111 | LOG_ERROR(Core, "File is invalid {}", path); 112 | return false; 113 | } 114 | 115 | const auto total_size = header.size + sizeof(header); 116 | if (data[0].size() < total_size) { 117 | LOG_ERROR(Core, "File {} header reports invalid size, may be corrupted", path); 118 | return false; 119 | } 120 | 121 | std::size_t pos = sizeof(header); 122 | while (pos < total_size) { 123 | Certificate cert; 124 | if (!cert.Load(data[0], pos)) { // Failed to load 125 | return false; 126 | } 127 | pos += cert.GetSize(); 128 | 129 | const auto issuer = Common::StringFromFixedZeroTerminatedBuffer(cert.body.issuer.data(), 130 | cert.body.issuer.size()); 131 | const auto name = Common::StringFromFixedZeroTerminatedBuffer(cert.body.name.data(), 132 | cert.body.name.size()); 133 | const auto full_name = issuer + "-" + name; 134 | g_certs.emplace(full_name, std::move(cert)); 135 | } 136 | 137 | for (const auto& cert : CIACertNames) { 138 | if (!g_certs.count(cert)) { 139 | LOG_ERROR(Core, "Cert {} required for CIA building but does not exist", cert); 140 | return false; 141 | } 142 | } 143 | 144 | g_is_loaded = true; 145 | return true; 146 | } 147 | 148 | bool IsLoaded() { 149 | return g_is_loaded; 150 | } 151 | 152 | void Clear() { 153 | g_is_loaded = false; 154 | g_certs.clear(); 155 | } 156 | 157 | const Certificate& Get(const std::string& name) { 158 | return g_certs.at(name); 159 | } 160 | 161 | bool Exists(const std::string& name) { 162 | return g_certs.count(name); 163 | } 164 | 165 | } // namespace Certs 166 | 167 | } // namespace Core 168 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMake 3.12.4 required for 20 to be a valid value for CXX_STANDARD 2 | cmake_minimum_required(VERSION 3.12.4) 3 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.15) 4 | # Add MSVC runtime library selection flags automatically: 5 | cmake_policy(SET CMP0091 OLD) 6 | # Don't override the warning flags in MSVC: 7 | cmake_policy(SET CMP0092 NEW) 8 | endif () 9 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") 10 | include(DownloadExternals) 11 | include(CMakeDependentOption) 12 | 13 | if (POLICY CMP0076) 14 | cmake_policy(SET CMP0076 NEW) 15 | endif() 16 | 17 | project(threeSD) 18 | 19 | option(WARNINGS_AS_ERRORS "Treat warnings as errors" ON) 20 | CMAKE_DEPENDENT_OPTION(USE_BUNDLED_QT "Download bundled Qt binaries" ON "MSVC" OFF) 21 | CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ON "MINGW" OFF) 22 | 23 | # Sanity check : Check that all submodules are present 24 | # ======================================================================= 25 | function(check_submodules_present) 26 | file(READ "${PROJECT_SOURCE_DIR}/.gitmodules" gitmodules) 27 | string(REGEX MATCHALL "path *= *[^ \t\r\n]*" gitmodules ${gitmodules}) 28 | foreach(module ${gitmodules}) 29 | string(REGEX REPLACE "path *= *" "" module ${module}) 30 | if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git") 31 | message(SEND_ERROR "Git submodule ${module} not found." 32 | "Please run: git submodule update --init --recursive") 33 | endif() 34 | endforeach() 35 | endfunction() 36 | check_submodules_present() 37 | 38 | # Detect current compilation architecture and create standard definitions 39 | # ======================================================================= 40 | 41 | include(CheckSymbolExists) 42 | function(detect_architecture symbol arch) 43 | if (NOT DEFINED ARCHITECTURE) 44 | set(CMAKE_REQUIRED_QUIET 1) 45 | check_symbol_exists("${symbol}" "" ARCHITECTURE_${arch}) 46 | unset(CMAKE_REQUIRED_QUIET) 47 | 48 | # The output variable needs to be unique across invocations otherwise 49 | # CMake's crazy scope rules will keep it defined 50 | if (ARCHITECTURE_${arch}) 51 | set(ARCHITECTURE "${arch}" PARENT_SCOPE) 52 | set(ARCHITECTURE_${arch} 1 PARENT_SCOPE) 53 | add_definitions(-DARCHITECTURE_${arch}=1) 54 | endif() 55 | endif() 56 | endfunction() 57 | 58 | if (NOT ENABLE_GENERIC) 59 | if (MSVC) 60 | detect_architecture("_M_AMD64" x86_64) 61 | detect_architecture("_M_IX86" x86) 62 | detect_architecture("_M_ARM" ARM) 63 | detect_architecture("_M_ARM64" ARM64) 64 | else() 65 | detect_architecture("__x86_64__" x86_64) 66 | detect_architecture("__i386__" x86) 67 | detect_architecture("__arm__" ARM) 68 | detect_architecture("__aarch64__" ARM64) 69 | endif() 70 | endif() 71 | if (NOT DEFINED ARCHITECTURE) 72 | set(ARCHITECTURE "GENERIC") 73 | set(ARCHITECTURE_GENERIC 1) 74 | add_definitions(-DARCHITECTURE_GENERIC=1) 75 | endif() 76 | message(STATUS "Target architecture: ${ARCHITECTURE}") 77 | 78 | 79 | # Configure C++ standard 80 | # =========================== 81 | if (MSVC) 82 | add_compile_options(/std:c++latest) 83 | add_definitions(-D_HAS_DEPRECATED_RESULT_OF) 84 | else() 85 | set(CMAKE_CXX_STANDARD 20) 86 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 87 | endif() 88 | 89 | # set up output paths for executable binaries 90 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 91 | 92 | # System imported libraries 93 | # ====================== 94 | 95 | # TODO: Is this necessary? 96 | 97 | # Prefer the -pthread flag on Linux. 98 | set(THREADS_PREFER_PTHREAD_FLAG ON) 99 | find_package(Threads REQUIRED) 100 | 101 | if (USE_BUNDLED_QT) 102 | if ((MSVC_VERSION GREATER_EQUAL 1920 AND MSVC_VERSION LESS 1940) AND ARCHITECTURE_x86_64) 103 | set(QT_VER qt-5.15.2-msvc2019_64) 104 | else() 105 | message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable USE_BUNDLED_QT and provide your own.") 106 | endif() 107 | 108 | if (DEFINED QT_VER) 109 | download_bundled_external("qt/" ${QT_VER} QT_PREFIX) 110 | endif() 111 | 112 | set(QT_PREFIX_HINT HINTS "${QT_PREFIX}") 113 | else() 114 | # Passing an empty HINTS seems to cause default system paths to get ignored in CMake 2.8 so 115 | # make sure to not pass anything if we don't have one. 116 | set(QT_PREFIX_HINT) 117 | endif() 118 | 119 | find_package(Qt5 REQUIRED COMPONENTS Widgets ${QT_PREFIX_HINT}) 120 | 121 | # Platform-specific library requirements 122 | # ====================================== 123 | # TODO: Check the necessity of these 124 | 125 | if (APPLE) 126 | # Umbrella framework for everything GUI-related 127 | find_library(COCOA_LIBRARY Cocoa) 128 | # For qdevicewatcher 129 | find_library(DISK_ARBITRATION_LIBRARY DiskArbitration) 130 | set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${DISK_ARBITRATION_LIBRARY}) 131 | elseif (WIN32) 132 | # WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista) 133 | add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600) 134 | set(PLATFORM_LIBRARIES winmm ws2_32) 135 | if (MINGW) 136 | # PSAPI is the Process Status API 137 | set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) 138 | endif() 139 | elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") 140 | set(PLATFORM_LIBRARIES rt) 141 | endif() 142 | 143 | # Include source code 144 | # =================== 145 | add_subdirectory(externals) 146 | add_subdirectory(src) 147 | -------------------------------------------------------------------------------- /src/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: None 36 | BreakBeforeBraces: Attach 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: false 39 | ColumnLimit: 100 40 | CommentPragmas: '^ IWYU pragma:' 41 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 42 | ConstructorInitializerIndentWidth: 4 43 | ContinuationIndentWidth: 4 44 | Cpp11BracedListStyle: true 45 | DerivePointerAlignment: false 46 | DisableFormat: false 47 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 48 | IncludeCategories: 49 | - Regex: '^\<[^Q][^/.>]*\>' 50 | Priority: -2 51 | - Regex: '^\<' 52 | Priority: -1 53 | - Regex: '^\"' 54 | Priority: 0 55 | IndentCaseLabels: false 56 | IndentWidth: 4 57 | IndentWrappedFunctionNames: false 58 | KeepEmptyLinesAtTheStartOfBlocks: true 59 | MacroBlockBegin: '' 60 | MacroBlockEnd: '' 61 | MaxEmptyLinesToKeep: 1 62 | NamespaceIndentation: None 63 | ObjCBlockIndentWidth: 2 64 | ObjCSpaceAfterProperty: false 65 | ObjCSpaceBeforeProtocolList: true 66 | PenaltyBreakBeforeFirstCallParameter: 19 67 | PenaltyBreakComment: 300 68 | PenaltyBreakFirstLessLess: 120 69 | PenaltyBreakString: 1000 70 | PenaltyExcessCharacter: 1000000 71 | PenaltyReturnTypeOnItsOwnLine: 150 72 | PointerAlignment: Left 73 | ReflowComments: true 74 | SortIncludes: true 75 | SpaceAfterCStyleCast: false 76 | SpaceBeforeAssignmentOperators: true 77 | SpaceBeforeParens: ControlStatements 78 | SpaceInEmptyParentheses: false 79 | SpacesBeforeTrailingComments: 1 80 | SpacesInAngles: false 81 | SpacesInContainerLiterals: true 82 | SpacesInCStyleCastParentheses: false 83 | SpacesInParentheses: false 84 | SpacesInSquareBrackets: false 85 | Standard: Cpp11 86 | TabWidth: 4 87 | UseTab: Never 88 | --- 89 | Language: Java 90 | # BasedOnStyle: LLVM 91 | AccessModifierOffset: -4 92 | AlignAfterOpenBracket: Align 93 | AlignConsecutiveAssignments: false 94 | AlignConsecutiveDeclarations: false 95 | AlignEscapedNewlinesLeft: false 96 | AlignOperands: true 97 | AlignTrailingComments: true 98 | AllowAllParametersOfDeclarationOnNextLine: true 99 | AllowShortBlocksOnASingleLine: false 100 | AllowShortCaseLabelsOnASingleLine: false 101 | AllowShortFunctionsOnASingleLine: Empty 102 | AllowShortIfStatementsOnASingleLine: false 103 | AllowShortLoopsOnASingleLine: false 104 | AlwaysBreakAfterDefinitionReturnType: None 105 | AlwaysBreakAfterReturnType: None 106 | AlwaysBreakBeforeMultilineStrings: false 107 | AlwaysBreakTemplateDeclarations: true 108 | BinPackArguments: true 109 | BinPackParameters: true 110 | BraceWrapping: 111 | AfterClass: false 112 | AfterControlStatement: false 113 | AfterEnum: false 114 | AfterFunction: false 115 | AfterNamespace: false 116 | AfterObjCDeclaration: false 117 | AfterStruct: false 118 | AfterUnion: false 119 | BeforeCatch: false 120 | BeforeElse: false 121 | IndentBraces: false 122 | BreakBeforeBinaryOperators: None 123 | BreakBeforeBraces: Attach 124 | BreakBeforeTernaryOperators: true 125 | BreakConstructorInitializersBeforeComma: false 126 | ColumnLimit: 100 127 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 128 | ConstructorInitializerIndentWidth: 4 129 | ContinuationIndentWidth: 4 130 | Cpp11BracedListStyle: true 131 | DerivePointerAlignment: false 132 | DisableFormat: false 133 | IncludeCategories: 134 | - Regex: '^\<[^Q][^/.>]*\>' 135 | Priority: -2 136 | - Regex: '^\<' 137 | Priority: -1 138 | - Regex: '^\"' 139 | Priority: 0 140 | IndentCaseLabels: false 141 | IndentWidth: 4 142 | IndentWrappedFunctionNames: false 143 | KeepEmptyLinesAtTheStartOfBlocks: true 144 | MacroBlockBegin: '' 145 | MacroBlockEnd: '' 146 | MaxEmptyLinesToKeep: 1 147 | NamespaceIndentation: None 148 | ObjCBlockIndentWidth: 2 149 | ObjCSpaceAfterProperty: false 150 | ObjCSpaceBeforeProtocolList: true 151 | PenaltyBreakBeforeFirstCallParameter: 19 152 | PenaltyBreakComment: 300 153 | PenaltyBreakFirstLessLess: 120 154 | PenaltyBreakString: 1000 155 | PenaltyExcessCharacter: 1000000 156 | PenaltyReturnTypeOnItsOwnLine: 150 157 | PointerAlignment: Left 158 | ReflowComments: true 159 | SortIncludes: true 160 | SpaceAfterCStyleCast: false 161 | SpaceBeforeAssignmentOperators: true 162 | SpaceBeforeParens: ControlStatements 163 | SpaceInEmptyParentheses: false 164 | SpacesBeforeTrailingComments: 1 165 | SpacesInAngles: false 166 | SpacesInContainerLiterals: true 167 | SpacesInCStyleCastParentheses: false 168 | SpacesInParentheses: false 169 | SpacesInSquareBrackets: false 170 | TabWidth: 4 171 | UseTab: Never 172 | ... 173 | -------------------------------------------------------------------------------- /src/core/file_decryptor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 threeSD Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "common/assert.h" 12 | #include "common/file_util.h" 13 | #include "common/string_util.h" 14 | #include "core/file_decryptor.h" 15 | 16 | namespace Core { 17 | 18 | FileDecryptor::FileDecryptor() = default; 19 | 20 | FileDecryptor::~FileDecryptor() = default; 21 | 22 | void FileDecryptor::SetCrypto(std::shared_ptr crypto_) { 23 | crypto = std::move(crypto_); 24 | } 25 | 26 | bool FileDecryptor::CryptAndWriteFile(std::shared_ptr source_, std::size_t size, 27 | std::shared_ptr destination_, 28 | const Common::ProgressCallback& callback_) { 29 | if (is_running) { 30 | LOG_ERROR(Core, "Decryptor is running"); 31 | return false; 32 | } 33 | 34 | if (size == 0) { 35 | return true; 36 | } 37 | 38 | for (auto& event : data_read_event) { 39 | event.Reset(); 40 | } 41 | for (auto& event : data_decrypted_event) { 42 | event.Reset(); 43 | } 44 | for (auto& event : data_written_event) { 45 | event.Reset(); 46 | } 47 | completion_event.Reset(); 48 | 49 | source = std::move(source_); 50 | destination = std::move(destination_); 51 | callback = callback_; 52 | 53 | total_size = size; 54 | 55 | is_good = is_running = true; 56 | 57 | read_thread = std::make_unique(&FileDecryptor::DataReadLoop, this); 58 | write_thread = std::make_unique(&FileDecryptor::DataWriteLoop, this); 59 | if (crypto) { 60 | decrypt_thread = std::make_unique(&FileDecryptor::DataDecryptLoop, this); 61 | } 62 | 63 | completion_event.Wait(); 64 | is_running = false; 65 | 66 | read_thread->join(); 67 | write_thread->join(); 68 | if (crypto) { 69 | decrypt_thread->join(); 70 | } 71 | 72 | // Release the files 73 | source.reset(); 74 | destination.reset(); 75 | 76 | bool ret = is_good; 77 | is_good = true; 78 | return ret; 79 | } 80 | 81 | void FileDecryptor::DataReadLoop() { 82 | std::size_t current_buffer = 0; 83 | bool is_first_run = true; 84 | 85 | if (!*source) { 86 | is_good = false; 87 | completion_event.Set(); 88 | return; 89 | } 90 | 91 | std::size_t file_size = total_size; 92 | 93 | while (is_running && file_size > 0) { 94 | if (is_first_run) { 95 | if (current_buffer == buffers.size() - 1) { 96 | is_first_run = false; 97 | } 98 | } else { 99 | data_written_event[current_buffer].Wait(); 100 | } 101 | 102 | const auto bytes_to_read = std::min(BufferSize, file_size); 103 | if (source->ReadBytes(buffers[current_buffer].data(), bytes_to_read) != bytes_to_read) { 104 | is_good = false; 105 | completion_event.Set(); 106 | return; 107 | } 108 | file_size -= bytes_to_read; 109 | 110 | data_read_event[current_buffer].Set(); 111 | current_buffer = (current_buffer + 1) % buffers.size(); 112 | } 113 | } 114 | 115 | void FileDecryptor::DataDecryptLoop() { 116 | std::size_t current_buffer = 0; 117 | std::size_t file_size = total_size; 118 | 119 | while (is_running && file_size > 0) { 120 | data_read_event[current_buffer].Wait(); 121 | 122 | const auto bytes_to_process = std::min(BufferSize, file_size); 123 | crypto->ProcessData(buffers[current_buffer].data(), bytes_to_process); 124 | 125 | file_size -= bytes_to_process; 126 | 127 | data_decrypted_event[current_buffer].Set(); 128 | current_buffer = (current_buffer + 1) % buffers.size(); 129 | } 130 | } 131 | 132 | void FileDecryptor::DataWriteLoop() { 133 | std::size_t current_buffer = 0; 134 | 135 | if (!*destination) { 136 | is_good = false; 137 | completion_event.Set(); 138 | return; 139 | } 140 | 141 | std::size_t file_size = total_size; 142 | std::size_t imported_size = 0; 143 | std::size_t iteration = 0; 144 | /// The number of iterations each progress report covers. 32 * 16K = 512K 145 | constexpr std::size_t ProgressReportFreq = 32; 146 | 147 | while (is_running && file_size > 0) { 148 | if (iteration % ProgressReportFreq == 0) { 149 | callback(imported_size, total_size); 150 | } 151 | 152 | iteration++; 153 | 154 | if (crypto) { 155 | data_decrypted_event[current_buffer].Wait(); 156 | } else { 157 | data_read_event[current_buffer].Wait(); 158 | } 159 | 160 | const auto bytes_to_write = std::min(BufferSize, file_size); 161 | if (destination->WriteBytes(buffers[current_buffer].data(), bytes_to_write) != 162 | bytes_to_write) { 163 | is_good = false; 164 | completion_event.Set(); 165 | return; 166 | } 167 | file_size -= bytes_to_write; 168 | imported_size += bytes_to_write; 169 | 170 | data_written_event[current_buffer].Set(); 171 | current_buffer = (current_buffer + 1) % buffers.size(); 172 | } 173 | 174 | if (imported_size == total_size) { // Completed 175 | callback(total_size, total_size); 176 | } 177 | completion_event.Set(); 178 | } 179 | 180 | void FileDecryptor::Abort() { 181 | if (is_running.exchange(false)) { 182 | is_good = false; 183 | completion_event.Set(); 184 | } 185 | } 186 | 187 | CryptoFunc::~CryptoFunc() = default; 188 | 189 | class CryptoFunc_AES_CTR final : public CryptoFunc { 190 | public: 191 | explicit CryptoFunc_AES_CTR(const Key::AESKey& key, const Key::AESKey& ctr, 192 | std::size_t seek_pos = 0) { 193 | 194 | aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); 195 | aes.Seek(seek_pos); 196 | } 197 | 198 | ~CryptoFunc_AES_CTR() override = default; 199 | 200 | void ProcessData(u8* data, std::size_t size) override { 201 | aes.ProcessData(data, data, size); 202 | } 203 | 204 | private: 205 | CryptoPP::CTR_Mode::Decryption aes; 206 | }; 207 | 208 | std::shared_ptr CreateCTRCrypto(const Key::AESKey& key, const Key::AESKey& ctr, 209 | std::size_t seek_pos) { 210 | return std::make_shared(key, ctr, seek_pos); 211 | } 212 | 213 | } // namespace Core 214 | -------------------------------------------------------------------------------- /src/common/string_util.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project 2 | // Licensed under GPLv2 or any later version 3 | // Refer to the license.txt file included. 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "common/common_paths.h" 12 | #include "common/logging/log.h" 13 | #include "common/string_util.h" 14 | 15 | #ifdef _WIN32 16 | #include 17 | #endif 18 | 19 | namespace Common { 20 | 21 | /// Make a string lowercase 22 | std::string ToLower(std::string str) { 23 | std::transform(str.begin(), str.end(), str.begin(), 24 | [](unsigned char c) { return std::tolower(c); }); 25 | return str; 26 | } 27 | 28 | /// Make a string uppercase 29 | std::string ToUpper(std::string str) { 30 | std::transform(str.begin(), str.end(), str.begin(), 31 | [](unsigned char c) { return std::toupper(c); }); 32 | return str; 33 | } 34 | 35 | // Turns " hej " into "hej". Also handles tabs. 36 | std::string StripSpaces(const std::string& str) { 37 | const std::size_t s = str.find_first_not_of(" \t\r\n"); 38 | 39 | if (str.npos != s) 40 | return str.substr(s, str.find_last_not_of(" \t\r\n") - s + 1); 41 | else 42 | return ""; 43 | } 44 | 45 | // "\"hello\"" is turned to "hello" 46 | // This one assumes that the string has already been space stripped in both 47 | // ends, as done by StripSpaces above, for example. 48 | std::string StripQuotes(const std::string& s) { 49 | if (s.size() && '\"' == s[0] && '\"' == *s.rbegin()) 50 | return s.substr(1, s.size() - 2); 51 | else 52 | return s; 53 | } 54 | 55 | std::string StringFromBool(bool value) { 56 | return value ? "True" : "False"; 57 | } 58 | 59 | bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, 60 | std::string* _pExtension) { 61 | if (full_path.empty()) 62 | return false; 63 | 64 | std::size_t dir_end = full_path.find_last_of("/" 65 | // windows needs the : included for something like just "C:" to be considered a directory 66 | #ifdef _WIN32 67 | ":" 68 | #endif 69 | ); 70 | if (std::string::npos == dir_end) 71 | dir_end = 0; 72 | else 73 | dir_end += 1; 74 | 75 | std::size_t fname_end = full_path.rfind('.'); 76 | if (fname_end < dir_end || std::string::npos == fname_end) 77 | fname_end = full_path.size(); 78 | 79 | if (_pPath) 80 | *_pPath = full_path.substr(0, dir_end); 81 | 82 | if (_pFilename) 83 | *_pFilename = full_path.substr(dir_end, fname_end - dir_end); 84 | 85 | if (_pExtension) 86 | *_pExtension = full_path.substr(fname_end); 87 | 88 | return true; 89 | } 90 | 91 | void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, 92 | const std::string& _Filename) { 93 | _CompleteFilename = _Path; 94 | 95 | // check for seperator 96 | if (DIR_SEP_CHR != *_CompleteFilename.rbegin()) 97 | _CompleteFilename += DIR_SEP_CHR; 98 | 99 | // add the filename 100 | _CompleteFilename += _Filename; 101 | } 102 | 103 | void SplitString(const std::string& str, const char delim, std::vector& output) { 104 | std::istringstream iss(str); 105 | output.resize(1); 106 | 107 | while (std::getline(iss, *output.rbegin(), delim)) { 108 | output.emplace_back(); 109 | } 110 | 111 | output.pop_back(); 112 | } 113 | 114 | std::string TabsToSpaces(int tab_size, std::string in) { 115 | std::size_t i = 0; 116 | 117 | while ((i = in.find('\t')) != std::string::npos) { 118 | in.replace(i, 1, tab_size, ' '); 119 | } 120 | 121 | return in; 122 | } 123 | 124 | std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) { 125 | std::size_t pos = 0; 126 | 127 | if (src == dest) 128 | return result; 129 | 130 | while ((pos = result.find(src, pos)) != std::string::npos) { 131 | result.replace(pos, src.size(), dest); 132 | pos += dest.length(); 133 | } 134 | 135 | return result; 136 | } 137 | 138 | std::string UTF16ToUTF8(const std::u16string& input) { 139 | #ifdef _MSC_VER 140 | // Workaround for missing char16_t/char32_t instantiations in MSVC2017 141 | std::wstring_convert, __int16> convert; 142 | std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend()); 143 | return convert.to_bytes(tmp_buffer); 144 | #else 145 | std::wstring_convert, char16_t> convert; 146 | return convert.to_bytes(input); 147 | #endif 148 | } 149 | 150 | std::u16string UTF8ToUTF16(const std::string& input) { 151 | #ifdef _MSC_VER 152 | // Workaround for missing char16_t/char32_t instantiations in MSVC2017 153 | std::wstring_convert, __int16> convert; 154 | auto tmp_buffer = convert.from_bytes(input); 155 | return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend()); 156 | #else 157 | std::wstring_convert, char16_t> convert; 158 | return convert.from_bytes(input); 159 | #endif 160 | } 161 | 162 | #ifdef _WIN32 163 | static std::wstring CPToUTF16(u32 code_page, const std::string& input) { 164 | const auto size = 165 | MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), nullptr, 0); 166 | 167 | if (size == 0) { 168 | return L""; 169 | } 170 | 171 | std::wstring output(size, L'\0'); 172 | 173 | if (size != MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), 174 | &output[0], static_cast(output.size()))) { 175 | output.clear(); 176 | } 177 | 178 | return output; 179 | } 180 | 181 | std::string UTF16ToUTF8(const std::wstring& input) { 182 | const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), 183 | nullptr, 0, nullptr, nullptr); 184 | if (size == 0) { 185 | return ""; 186 | } 187 | 188 | std::string output(size, '\0'); 189 | 190 | if (size != WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), 191 | &output[0], static_cast(output.size()), nullptr, 192 | nullptr)) { 193 | output.clear(); 194 | } 195 | 196 | return output; 197 | } 198 | 199 | std::wstring UTF8ToUTF16W(const std::string& input) { 200 | return CPToUTF16(CP_UTF8, input); 201 | } 202 | 203 | #endif 204 | 205 | std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) { 206 | std::size_t len = 0; 207 | while (len < max_len && buffer[len] != '\0') 208 | ++len; 209 | 210 | return std::string(buffer, len); 211 | } 212 | } // namespace Common 213 | --------------------------------------------------------------------------------