commit 4f5a3effd86f2d4c535dd92d0397da9fa7e2f649 Author: zhupengfei Date: Sat Aug 24 23:30:22 2019 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3759136 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Build directory +[Bb]uild/ +doc-build/ + +# Generated source files +src/common/scm_rev.cpp +.travis.descriptor.json + +# Project/editor files +*.swp +.idea/ +.vs/ +.vscode/ +CMakeLists.txt.user* + +# *nix related +# Common convention for backup or temporary files +*~ + +# Visual Studio CMake settings +CMakeSettings.json + +# OSX global filetypes +# Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) +.DS_Store +.AppleDouble +.LSOverride +.Spotlight-V100 +.Trashes + +# Windows global filetypes +Thumbs.db + +# Python files +*.pyc + +# Flatpak generated files +.flatpak-builder/ +repo/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e7c38da --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "fmt"] + path = externals/fmt + url = https://github.com/fmtlib/fmt.git +[submodule "cryptopp"] + path = externals/cryptopp/cryptopp + url = https://github.com/weidai11/cryptopp.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bd3198e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +# CMake 3.8 required for 17 to be a valid value for CXX_STANDARD +cmake_minimum_required(VERSION 3.8) + +project(threeSD) + +# Sanity check : Check that all submodules are present +# ======================================================================= +function(check_submodules_present) + file(READ "${PROJECT_SOURCE_DIR}/.gitmodules" gitmodules) + string(REGEX MATCHALL "path *= *[^ \t\r\n]*" gitmodules ${gitmodules}) + foreach(module ${gitmodules}) + string(REGEX REPLACE "path *= *" "" module ${module}) + if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git") + message(SEND_ERROR "Git submodule ${module} not found." + "Please run: git submodule update --init --recursive") + endif() + endforeach() +endfunction() +check_submodules_present() + +# Configure C++ standard +# =========================== +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# set up output paths for executable binaries +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) + +# Include source code +# =================== +add_subdirectory(externals) +add_subdirectory(src) diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt new file mode 100644 index 0000000..818c4fd --- /dev/null +++ b/externals/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(cryptopp) +add_subdirectory(fmt) diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt new file mode 100644 index 0000000..974f02b --- /dev/null +++ b/externals/cryptopp/CMakeLists.txt @@ -0,0 +1,251 @@ +# The CMakeLists.txt shipped with cryptopp pollutes our option list and installation list, +# so we made our own one. This is basically a trimmed down version of the shipped CMakeLists.txt +# The differences are: +# - removed support for legacy CMake versions +# - removed support for 32-bit +# - removed -march=native flag +# - removed rdrand module.asm as a workaround for an issue (see below) +# - added prefix "CRYPTOPP_" to all option names +# - disabled testing +# - disabled installation +# - disabled documentation +# - configured to build a static library only +# - adds include directories to the library target + +include(TestBigEndian) +include(CheckCXXCompilerFlag) + +#============================================================================ +# Settable options +#============================================================================ + +option(CRYPTOPP_DISABLE_ASM "Disable ASM" OFF) +option(CRYPTOPP_DISABLE_SSSE3 "Disable SSSE3" OFF) +option(CRYPTOPP_DISABLE_AESNI "Disable AES-NI" OFF) +option(CRYPTOPP_DISABLE_CXXFLAGS_OPTIMIZATIONS "Disable CXXFLAGS optimizations" OFF) + +#============================================================================ +# Internal compiler options +#============================================================================ + +# Only set when cross-compiling, http://www.vtk.org/Wiki/CMake_Cross_Compiling +if (NOT (CMAKE_SYSTEM_VERSION AND CMAKE_SYSTEM_PROCESSOR)) + set(CRYPTOPP_CROSS_COMPILE 1) +else() + set(CRYPTOPP_CROSS_COMPILE 0) +endif() + +# Don't use RPATH's. The resulting binary could fail a security audit. +if (NOT CMAKE_VERSION VERSION_LESS 2.8.12) + set(CMAKE_MACOSX_RPATH 0) +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "Intel") + add_definitions(-wd68 -wd186 -wd279 -wd327 -wd161 -wd3180) +endif() + +if(MSVC) + # Disable C4390: empty controlled statement found: is this the intent? + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4390") +endif() + +# Endianness +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) +if(IS_BIG_ENDIAN) + add_definitions(-DIS_BIG_ENDIAN) +endif() + +if(CRYPTOPP_DISABLE_ASM) + add_definitions(-DCRYPTOPP_DISABLE_ASM) +endif() +if(CRYPTOPP_DISABLE_SSSE3) + add_definitions(-DCRYPTOPP_DISABLE_SSSE3) +endif() +if(CRYPTOPP_DISABLE_AESNI) + add_definitions(-DCRYPTOPP_DISABLE_AESNI) +endif() + +# We need the output 'uname -s' for Unix and Linux system detection +if (NOT CRYPTOPP_CROSS_COMPILE) + set (UNAME_CMD "uname") + set (UNAME_ARG "-s") + execute_process(COMMAND ${UNAME_CMD} ${UNAME_ARG} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE UNAME_RESULT + OUTPUT_VARIABLE UNAME_SYSTEM) + string(REGEX REPLACE "\n$" "" UNAME_SYSTEM "${UNAME_SYSTEM}") +endif() + +# We need the output 'uname -m' for Unix and Linux platform detection +if (NOT CRYPTOPP_CROSS_COMPILE) + set (UNAME_CMD "uname") + set (UNAME_ARG "-m") + execute_process(COMMAND ${UNAME_CMD} ${UNAME_ARG} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE UNAME_RESULT + OUTPUT_VARIABLE UNAME_MACHINE) + string(REGEX REPLACE "\n$" "" UNAME_MACHINE "${UNAME_MACHINE}") +endif() + +if(WINDOWS_STORE OR WINDOWS_PHONE) + if("${CMAKE_SYSTEM_VERSION}" MATCHES "10\\.0.*") + SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D\"_WIN32_WINNT=0x0A00\"" ) + endif() + SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FI\"winapifamily.h\"" ) +endif() + +# Enable PIC for all targets except Windows and 32-bit x86. +# Avoid on 32-bit x86 due to register pressures. +if ((NOT CRYPTOPP_CROSS_COMPILE) AND (NOT (WINDOWS OR WINDOWS_STORE OR WINDOWS_PHONE))) + # Use Regex; match i386, i486, i586 and i686 + if (NOT (${UNAME_MACHINE} MATCHES "i.86")) + SET(CMAKE_POSITION_INDEPENDENT_CODE 1) + endif() +endif() + +# Link is driven through the compiler, but CXXFLAGS are not used. Also see +# http://public.kitware.com/pipermail/cmake/2003-June/003967.html +if (NOT (WINDOWS OR WINDOWS_STORE OR WINDOWS_PHONE)) + SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_FLAGS}") +endif() + +#============================================================================ +# Sources & headers +#============================================================================ + +# Library headers +file(GLOB cryptopp_HEADERS cryptopp/*.h) + +# Library sources. +# These have been trimmed to include only things Citra uses. This speeds up +# compiles and reduces the amount of compilation breakage. +set(cryptopp_SOURCES + # The Crypto++ readme says you should put these 3 object files first, + # to avoid "problems associated with C++ static initialization order", + # but doesn't actually tell what could go wrong. Better safe than sorry + # I guess... + cryptopp/cryptlib.cpp + cryptopp/cpu.cpp + cryptopp/integer.cpp + + cryptopp/algparam.cpp + cryptopp/asn.cpp + cryptopp/authenc.cpp + cryptopp/base64.cpp + cryptopp/basecode.cpp + cryptopp/ccm.cpp + cryptopp/crc-simd.cpp + cryptopp/des.cpp + cryptopp/dessp.cpp + cryptopp/dll.cpp + cryptopp/ec2n.cpp + cryptopp/ecp.cpp + cryptopp/files.cpp + cryptopp/filters.cpp + cryptopp/fips140.cpp + cryptopp/gcm-simd.cpp + cryptopp/gf2n.cpp + cryptopp/gfpcrypt.cpp + cryptopp/hex.cpp + cryptopp/hmac.cpp + cryptopp/hrtimer.cpp + cryptopp/iterhash.cpp + cryptopp/md5.cpp + cryptopp/misc.cpp + cryptopp/modes.cpp + cryptopp/mqueue.cpp + cryptopp/nbtheory.cpp + cryptopp/neon-simd.cpp + cryptopp/oaep.cpp + cryptopp/osrng.cpp + cryptopp/pubkey.cpp + cryptopp/queue.cpp + cryptopp/randpool.cpp + cryptopp/rdtables.cpp + cryptopp/rijndael-simd.cpp + cryptopp/rijndael.cpp + cryptopp/rng.cpp + cryptopp/sha-simd.cpp + cryptopp/sha.cpp + cryptopp/sse-simd.cpp + ) + +if (MINGW OR WIN32) + list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/winpipes.cpp) +endif () + +if(MSVC AND NOT CRYPTOPP_DISABLE_ASM) + if(${CMAKE_GENERATOR} MATCHES ".*ARM") + message(STATUS "Disabling ASM because ARM is specified as target platform.") + else() + # Note that we removed rdrand.asm. This is a workaround for the issue that rdrand.asm cannnot compiled properly + # on MSVC. Because there is also a rdrand.S file in the submodule, CMake will specify the target path for + # rdrand.asm as "/crytopp.dir/{Debug|Release}/cryptopp/rdrand.asm.obj". The additional target folder "cryptopp" + # is specified because the file rdrand.asm is in the source folder "cryptopp". But MSVC assembler can't build + # target file to an non-existing folder("cryptopp"). + list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64dll.asm) + list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64masm.asm) + # list(APPEND cryptopp_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/rdrand.asm) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64dll.asm PROPERTIES COMPILE_FLAGS "/D_M_X64") + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/x64masm.asm PROPERTIES COMPILE_FLAGS "/D_M_X64") + # set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/rdrand.asm PROPERTIES COMPILE_FLAGS "/D_M_X64") + enable_language(ASM_MASM) + endif() +endif() + +if ((CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND NOT CRYPTOPP_DISABLE_ASM) + check_cxx_compiler_flag(-msse2 CRYPTOPP_HAS_MSSE2) + check_cxx_compiler_flag(-mssse3 CRYPTOPP_HAS_MSSSE3) + check_cxx_compiler_flag(-msse4.1 CRYPTOPP_HAS_MSSE41) + check_cxx_compiler_flag(-msse4.2 CRYPTOPP_HAS_MSSE42) + check_cxx_compiler_flag(-maes CRYPTOPP_HAS_MAES) + check_cxx_compiler_flag(-mpclmul CRYPTOPP_HAS_PCLMUL) + check_cxx_compiler_flag(-msha CRYPTOPP_HAS_MSHA) + check_cxx_compiler_flag(-march=armv8-a+crc CRYPTOPP_HAS_ARMV8_CRC32) + check_cxx_compiler_flag(-march=armv8-a+crypto CRYPTOPP_HAS_ARMV8_CRYPTO) + if (CRYPTOPP_HAS_MSSE2) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/sse-simd.cpp PROPERTIES COMPILE_FLAGS "-msse2") + endif() + if (CRYPTOPP_HAS_MSSSE3 AND CRYPTOPP_HAS_MAES AND CRYPTOPP_HAS_PCLMUL) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/gcm-simd.cpp + PROPERTIES COMPILE_FLAGS "-mssse3 -maes -mpclmul") + endif() + if (CRYPTOPP_HAS_MSSE41 AND CRYPTOPP_HAS_MAES) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/rijndael-simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.1 -maes") + endif() + if (CRYPTOPP_HAS_MSSE42) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/crc-simd.cpp + PROPERTIES COMPILE_FLAGS "-msse4.2") + endif() + if (CRYPTOPP_HAS_MSSE42 AND CRYPTOPP_HAS_MSHA) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/sha-simd.cpp PROPERTIES COMPILE_FLAGS "-msse4.2 -msha") + endif() + if (CRYPTOPP_HAS_ARMV8_CRC32) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/crc-simd.cpp + PROPERTIES COMPILE_FLAGS "-march=armv8-a+crc") + endif() + if (CRYPTOPP_HAS_ARMV8_CRYPTO) + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/rijndael-simd.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cryptopp/sha-simd.cpp + PROPERTIES COMPILE_FLAGS "-march=armv8-a+crypto") + endif() +endif() + +#============================================================================ +# Compile targets +#============================================================================ +add_library(cryptopp STATIC ${cryptopp_SOURCES}) +target_include_directories(cryptopp INTERFACE .) + +#============================================================================ +# Third-party libraries +#============================================================================ + +find_package(Threads) +target_link_libraries(cryptopp PRIVATE ${CMAKE_THREAD_LIBS_INIT}) + +if(ANDROID) + include(AndroidNdkModules) + android_ndk_import_module_cpufeatures() + target_link_libraries(cryptopp PRIVATE cpufeatures) +endif() diff --git a/externals/cryptopp/cryptopp b/externals/cryptopp/cryptopp new file mode 160000 index 0000000..f320e7d --- /dev/null +++ b/externals/cryptopp/cryptopp @@ -0,0 +1 @@ +Subproject commit f320e7d92a33ee80ae42deef79da78cfc30868af diff --git a/externals/fmt b/externals/fmt new file mode 160000 index 0000000..5a4b246 --- /dev/null +++ b/externals/fmt @@ -0,0 +1 @@ +Subproject commit 5a4b24613ba16cc689977c3b5bd8274a3ba1dd1f diff --git a/license.txt b/license.txt new file mode 100644 index 0000000..d511905 --- /dev/null +++ b/license.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/src/.clang-format b/src/.clang-format new file mode 100644 index 0000000..bf88726 --- /dev/null +++ b/src/.clang-format @@ -0,0 +1,172 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^\<[^Q][^/.>]*\>' + Priority: -2 + - Regex: '^\<' + Priority: -1 + - Regex: '^\"' + Priority: 0 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 150 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +--- +Language: Java +# BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 100 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +IncludeCategories: + - Regex: '^\<[^Q][^/.>]*\>' + Priority: -2 + - Regex: '^\<' + Priority: -1 + - Regex: '^\"' + Priority: 0 +IndentCaseLabels: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 150 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never +... diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..fbb37c0 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,9 @@ +include_directories(.) + +add_executable(threeSD) + +add_subdirectory(common) +add_subdirectory(core) +add_subdirectory(frontend) + +target_link_libraries(threeSD PRIVATE cryptopp fmt) diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt new file mode 100644 index 0000000..aabc5b8 --- /dev/null +++ b/src/common/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(threeSD PRIVATE + common/assert.h + common/bit_field.h + common/common_funcs.h + common/common_paths.h + common/common_types.h + common/file_util.cpp + common/file_util.h + common/logging/log.h + common/misc.cpp + common/string_util.cpp + common/string_util.h + common/swap.h +) diff --git a/src/common/assert.h b/src/common/assert.h new file mode 100644 index 0000000..5c479f5 --- /dev/null +++ b/src/common/assert.h @@ -0,0 +1,56 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include "common/common_funcs.h" +#include "common/logging/log.h" + +// For asserts we'd like to keep all the junk executed when an assert happens away from the +// important code in the function. One way of doing this is to put all the relevant code inside a +// lambda and force the compiler to not inline it. Unfortunately, MSVC seems to have no syntax to +// specify __declspec on lambda functions, so what we do instead is define a noinline wrapper +// template that calls the lambda. This seems to generate an extra instruction at the call-site +// compared to the ideal implementation (which wouldn't support ASSERT_MSG parameters), but is good +// enough for our purposes. +template +#if defined(_MSC_VER) +__declspec(noinline, noreturn) +#elif defined(__GNUC__) + __attribute__((noinline, noreturn, cold)) +#endif + static void assert_noinline_call(const Fn& fn) { + fn(); + Crash(); + exit(1); // Keeps GCC's mouth shut about this actually returning +} + +#define ASSERT(_a_) \ + do \ + if (!(_a_)) { \ + assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \ + } \ + while (0) + +#define ASSERT_MSG(_a_, ...) \ + do \ + if (!(_a_)) { \ + assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \ + } \ + while (0) + +#define UNREACHABLE() ASSERT_MSG(false, "Unreachable code!") +#define UNREACHABLE_MSG(...) ASSERT_MSG(false, __VA_ARGS__) + +#ifdef _DEBUG +#define DEBUG_ASSERT(_a_) ASSERT(_a_) +#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__) +#else // not debug +#define DEBUG_ASSERT(_a_) +#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) +#endif + +#define UNIMPLEMENTED() LOG_CRITICAL(Debug, "Unimplemented code!") +#define UNIMPLEMENTED_MSG(_a_, ...) ASSERT_MSG(false, _a_, __VA_ARGS__) diff --git a/src/common/bit_field.h b/src/common/bit_field.h new file mode 100644 index 0000000..8131d1f --- /dev/null +++ b/src/common/bit_field.h @@ -0,0 +1,201 @@ +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// Copyright 2014 Tony Wasserka +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the owner nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include +#include +#include +#include "common/common_funcs.h" +#include "common/swap.h" + +/* + * Abstract bitfield class + * + * Allows endianness-independent access to individual bitfields within some raw + * integer value. The assembly generated by this class is identical to the + * usage of raw bitfields, so it's a perfectly fine replacement. + * + * For BitField, X is the distance of the bitfield to the LSB of the + * raw value, Y is the length in bits of the bitfield. Z is an integer type + * which determines the sign of the bitfield. Z must have the same size as the + * raw integer. + * + * + * General usage: + * + * Create a new union with the raw integer value as a member. + * Then for each bitfield you want to expose, add a BitField member + * in the union. The template parameters are the bit offset and the number + * of desired bits. + * + * Changes in the bitfield members will then get reflected in the raw integer + * value and vice-versa. + * + * + * Sample usage: + * + * union SomeRegister + * { + * u32 hex; + * + * BitField<0,7,u32> first_seven_bits; // unsigned + * BitField<7,8,u32> next_eight_bits; // unsigned + * BitField<3,15,s32> some_signed_fields; // signed + * }; + * + * This is equivalent to the little-endian specific code: + * + * union SomeRegister + * { + * u32 hex; + * + * struct + * { + * u32 first_seven_bits : 7; + * u32 next_eight_bits : 8; + * }; + * struct + * { + * u32 : 3; // padding + * s32 some_signed_fields : 15; + * }; + * }; + * + * + * Caveats: + * + * 1) + * BitField provides automatic casting from and to the storage type where + * appropriate. However, when using non-typesafe functions like printf, an + * explicit cast must be performed on the BitField object to make sure it gets + * passed correctly, e.g.: + * printf("Value: %d", (s32)some_register.some_signed_fields); + * + * 2) + * Not really a caveat, but potentially irritating: This class is used in some + * packed structures that do not guarantee proper alignment. Therefore we have + * to use #pragma pack here not to pack the members of the class, but instead + * to break GCC's assumption that the members of the class are aligned on + * sizeof(StorageType). + * TODO(neobrain): Confirm that this is a proper fix and not just masking + * symptoms. + */ +#pragma pack(1) +template +struct BitField { +private: + // UnderlyingType is T for non-enum types and the underlying type of T if + // T is an enumeration. Note that T is wrapped within an enable_if in the + // former case to workaround compile errors which arise when using + // std::underlying_type::type directly. + using UnderlyingType = typename std::conditional_t, std::underlying_type, + std::enable_if>::type; + + // We store the value as the unsigned type to avoid undefined behaviour on value shifting + using StorageType = std::make_unsigned_t; + + using StorageTypeWithEndian = typename AddEndian::type; + +public: + /// Constants to allow limited introspection of fields if needed + static constexpr std::size_t position = Position; + static constexpr std::size_t bits = Bits; + static constexpr StorageType mask = (((StorageType)~0) >> (8 * sizeof(T) - bits)) << position; + + /** + * Formats a value by masking and shifting it according to the field parameters. A value + * containing several bitfields can be assembled by formatting each of their values and ORing + * the results together. + */ + static constexpr FORCE_INLINE StorageType FormatValue(const T& value) { + return ((StorageType)value << position) & mask; + } + + /** + * Extracts a value from the passed storage. In most situations prefer use the member functions + * (such as Value() or operator T), but this can be used to extract a value from a bitfield + * union in a constexpr context. + */ + static constexpr FORCE_INLINE T ExtractValue(const StorageType& storage) { + if constexpr (std::numeric_limits::is_signed) { + std::size_t shift = 8 * sizeof(T) - bits; + return static_cast(static_cast(storage << (shift - position)) >> + shift); + } else { + return static_cast((storage & mask) >> position); + } + } + + // This constructor and assignment operator might be considered ambiguous: + // Would they initialize the storage or just the bitfield? + // Hence, delete them. Use the Assign method to set bitfield values! + BitField(T val) = delete; + BitField& operator=(T val) = delete; + + constexpr BitField() noexcept = default; + + constexpr BitField(const BitField&) noexcept = default; + constexpr BitField& operator=(const BitField&) noexcept = default; + + constexpr BitField(BitField&&) noexcept = default; + constexpr BitField& operator=(BitField&&) noexcept = default; + + constexpr operator T() const { + return Value(); + } + + constexpr void Assign(const T& value) { + storage = (static_cast(storage) & ~mask) | FormatValue(value); + } + + constexpr T Value() const { + return ExtractValue(storage); + } + + constexpr explicit operator bool() const { + return Value() != 0; + } + +private: + StorageTypeWithEndian storage; + + static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range"); + + // And, you know, just in case people specify something stupid like bits=position=0x80000000 + static_assert(position < 8 * sizeof(T), "Invalid position"); + static_assert(bits <= 8 * sizeof(T), "Invalid number of bits"); + static_assert(bits > 0, "Invalid number of bits"); + static_assert(std::is_trivially_copyable_v, "T must be trivially copyable in a BitField"); +}; +#pragma pack() + +template +using BitFieldBE = BitField; diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h new file mode 100644 index 0000000..71a0ec0 --- /dev/null +++ b/src/common/common_funcs.h @@ -0,0 +1,62 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#if !defined(ARCHITECTURE_x86_64) +#include // for exit +#endif +#include "common/common_types.h" + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +/// Textually concatenates two tokens. The double-expansion is required by the C preprocessor. +#define CONCAT2(x, y) DO_CONCAT2(x, y) +#define DO_CONCAT2(x, y) x##y + +// helper macro to properly align structure members. +// Calling INSERT_PADDING_BYTES will add a new member variable with a name like "pad121", +// depending on the current source line to make sure variable names are unique. +#define INSERT_PADDING_BYTES(num_bytes) u8 CONCAT2(pad, __LINE__)[(num_bytes)] +#define INSERT_PADDING_WORDS(num_words) u32 CONCAT2(pad, __LINE__)[(num_words)] + +// Inlining +#ifdef _WIN32 +#define FORCE_INLINE __forceinline +#else +#define FORCE_INLINE inline __attribute__((always_inline)) +#endif + +#ifndef _MSC_VER + +#ifdef ARCHITECTURE_x86_64 +#define Crash() __asm__ __volatile__("int $3") +#else +#define Crash() exit(1) +#endif + +#else // _MSC_VER + +#if (_MSC_VER < 1900) +// Function Cross-Compatibility +#define snprintf _snprintf +#endif + +// Locale Cross-Compatibility +#define locale_t _locale_t + +extern "C" { +__declspec(dllimport) void __stdcall DebugBreak(void); +} +#define Crash() DebugBreak() + +#endif // _MSC_VER ndef + +// Generic function to get last error message. +// Call directly after the command or use the error num. +// This function might change the error code. +// Defined in Misc.cpp. +std::string GetLastErrorMsg(); diff --git a/src/common/common_paths.h b/src/common/common_paths.h new file mode 100644 index 0000000..591f04d --- /dev/null +++ b/src/common/common_paths.h @@ -0,0 +1,39 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +// Directory separators, do we need this? +#define DIR_SEP "/" +#define DIR_SEP_CHR '/' + +// Citra's path names + +// The user data dir +#define ROOT_DIR "." +#define USERDATA_DIR "user" +#ifdef USER_DIR +#define EMU_DATA_DIR USER_DIR +#else +#ifdef _WIN32 +#define EMU_DATA_DIR "Citra" +#else +#define EMU_DATA_DIR "citra-emu" +#endif +#endif + +// Subdirs in the User dir returned by GetUserPath(UserPath::UserDir) +#define CONFIG_DIR "config" +#define CACHE_DIR "cache" +#define SDMC_DIR "sdmc" +#define NAND_DIR "nand" +#define SYSDATA_DIR "sysdata" +#define LOG_DIR "log" +#define CHEATS_DIR "cheats" +#define DLL_DIR "external_dlls" + +#define BOOTROM9 "boot9.bin" +#define SECRET_SECTOR "sector0x96.bin" +#define MOVABLE_SED "movable.sed" diff --git a/src/common/common_types.h b/src/common/common_types.h new file mode 100644 index 0000000..ee18eac --- /dev/null +++ b/src/common/common_types.h @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2005-2012 Gekko Emulator + * + * @file common_types.h + * @author ShizZy + * @date 2012-02-11 + * @brief Common types used throughout the project + * + * @section LICENSE + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details at + * http://www.gnu.org/copyleft/gpl.html + * + * Official project repository can be found at: + * http://code.google.com/p/gekko-gc-emu/ + */ + +#pragma once + +#include + +#ifdef _MSC_VER +#ifndef __func__ +#define __func__ __FUNCTION__ +#endif +#endif + +typedef std::uint8_t u8; ///< 8-bit unsigned byte +typedef std::uint16_t u16; ///< 16-bit unsigned short +typedef std::uint32_t u32; ///< 32-bit unsigned word +typedef std::uint64_t u64; ///< 64-bit unsigned int + +typedef std::int8_t s8; ///< 8-bit signed byte +typedef std::int16_t s16; ///< 16-bit signed short +typedef std::int32_t s32; ///< 32-bit signed word +typedef std::int64_t s64; ///< 64-bit signed int + +typedef float f32; ///< 32-bit floating point +typedef double f64; ///< 64-bit floating point + +// TODO: It would be nice to eventually replace these with strong types that prevent accidental +// conversion between each other. +typedef u32 VAddr; ///< Represents a pointer in the userspace virtual address space. +typedef u32 PAddr; ///< Represents a pointer in the ARM11 physical address space. + +// An inheritable class to disallow the copy constructor and operator= functions +class NonCopyable { +protected: + constexpr NonCopyable() = default; + ~NonCopyable() = default; + + NonCopyable(const NonCopyable&) = delete; + NonCopyable& operator=(const NonCopyable&) = delete; +}; diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp new file mode 100644 index 0000000..39f078c --- /dev/null +++ b/src/common/file_util.cpp @@ -0,0 +1,864 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/logging/log.h" + +#ifdef _WIN32 +#include +// windows.h needs to be included before other windows headers +#include // getcwd +#include +#include +#include // for SHGetFolderPath +#include +#include "common/string_util.h" + +#ifdef _MSC_VER +// 64 bit offsets for MSVC +#define fseeko _fseeki64 +#define ftello _ftelli64 +#define fileno _fileno +#endif + +// 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64 +#define stat _stat64 +#define fstat _fstat64 + +#else +#ifdef __APPLE__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(__APPLE__) +// CFURL contains __attribute__ directives that gcc does not know how to parse, so we need to just +// ignore them if we're not using clang. The macro is only used to prevent linking against +// functions that don't exist on older versions of macOS, and the worst case scenario is a linker +// error, so this is perfectly safe, just inconvenient. +#ifndef __clang__ +#define availability(...) +#endif +#include +#include +#include +#ifdef availability +#undef availability +#endif + +#endif + +#include +#include + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) +#endif + +// This namespace has various generic functions related to files and paths. +// The code still needs a ton of cleanup. +// REMEMBER: strdup considered harmful! +namespace FileUtil { + +// Remove any ending forward slashes from directory paths +// Modifies argument. +static void StripTailDirSlashes(std::string& fname) { + if (fname.length() <= 1) { + return; + } + + std::size_t i = fname.length(); + while (i > 0 && fname[i - 1] == DIR_SEP_CHR) { + --i; + } + fname.resize(i); +} + +bool Exists(const std::string& filename) { + struct stat file_info; + + std::string copy(filename); + StripTailDirSlashes(copy); + +#ifdef _WIN32 + // Windows needs a slash to identify a driver root + if (copy.size() != 0 && copy.back() == ':') + copy += DIR_SEP_CHR; + + int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); +#else + int result = stat(copy.c_str(), &file_info); +#endif + + return (result == 0); +} + +bool IsDirectory(const std::string& filename) { + struct stat file_info; + + std::string copy(filename); + StripTailDirSlashes(copy); + +#ifdef _WIN32 + // Windows needs a slash to identify a driver root + if (copy.size() != 0 && copy.back() == ':') + copy += DIR_SEP_CHR; + + int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); +#else + int result = stat(copy.c_str(), &file_info); +#endif + + if (result < 0) { + LOG_DEBUG(Common_Filesystem, "stat failed on {}: {}", filename, GetLastErrorMsg()); + return false; + } + + return S_ISDIR(file_info.st_mode); +} + +bool Delete(const std::string& filename) { + LOG_TRACE(Common_Filesystem, "file {}", filename); + + // Return true because we care about the file no + // being there, not the actual delete. + if (!Exists(filename)) { + LOG_DEBUG(Common_Filesystem, "{} does not exist", filename); + return true; + } + + // We can't delete a directory + if (IsDirectory(filename)) { + LOG_ERROR(Common_Filesystem, "Failed: {} is a directory", filename); + return false; + } + +#ifdef _WIN32 + if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) { + LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg()); + return false; + } +#else + if (unlink(filename.c_str()) == -1) { + LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg()); + return false; + } +#endif + + return true; +} + +bool CreateDir(const std::string& path) { + LOG_TRACE(Common_Filesystem, "directory {}", path); +#ifdef _WIN32 + if (::CreateDirectoryW(Common::UTF8ToUTF16W(path).c_str(), nullptr)) + return true; + DWORD error = GetLastError(); + if (error == ERROR_ALREADY_EXISTS) { + LOG_DEBUG(Common_Filesystem, "CreateDirectory failed on {}: already exists", path); + return true; + } + LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error); + return false; +#else + if (mkdir(path.c_str(), 0755) == 0) + return true; + + int err = errno; + + if (err == EEXIST) { + LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path); + return true; + } + + LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err)); + return false; +#endif +} + +bool CreateFullPath(const std::string& fullPath) { + int panicCounter = 100; + LOG_TRACE(Common_Filesystem, "path {}", fullPath); + + if (FileUtil::Exists(fullPath)) { + LOG_DEBUG(Common_Filesystem, "path exists {}", fullPath); + return true; + } + + std::size_t position = 0; + while (true) { + // Find next sub path + position = fullPath.find(DIR_SEP_CHR, position); + + // we're done, yay! + if (position == fullPath.npos) + return true; + + // Include the '/' so the first call is CreateDir("/") rather than CreateDir("") + std::string const subPath(fullPath.substr(0, position + 1)); + if (!FileUtil::IsDirectory(subPath) && !FileUtil::CreateDir(subPath)) { + LOG_ERROR(Common, "CreateFullPath: directory creation failed"); + return false; + } + + // A safety check + panicCounter--; + if (panicCounter <= 0) { + LOG_ERROR(Common, "CreateFullPath: directory structure is too deep"); + return false; + } + position++; + } +} + +bool DeleteDir(const std::string& filename) { + LOG_TRACE(Common_Filesystem, "directory {}", filename); + + // check if a directory + if (!FileUtil::IsDirectory(filename)) { + LOG_ERROR(Common_Filesystem, "Not a directory {}", filename); + return false; + } + +#ifdef _WIN32 + if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str())) + return true; +#else + if (rmdir(filename.c_str()) == 0) + return true; +#endif + LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); + + return false; +} + +bool Rename(const std::string& srcFilename, const std::string& destFilename) { + LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); +#ifdef _WIN32 + if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(), + Common::UTF8ToUTF16W(destFilename).c_str()) == 0) + return true; +#else + if (rename(srcFilename.c_str(), destFilename.c_str()) == 0) + return true; +#endif + LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, + GetLastErrorMsg()); + return false; +} + +bool Copy(const std::string& srcFilename, const std::string& destFilename) { + LOG_TRACE(Common_Filesystem, "{} --> {}", srcFilename, destFilename); +#ifdef _WIN32 + if (CopyFileW(Common::UTF8ToUTF16W(srcFilename).c_str(), + Common::UTF8ToUTF16W(destFilename).c_str(), FALSE)) + return true; + + LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, + GetLastErrorMsg()); + return false; +#else + using CFilePointer = std::unique_ptr; + + // Open input file + CFilePointer input{fopen(srcFilename.c_str(), "rb"), std::fclose}; + if (!input) { + LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename, + destFilename, GetLastErrorMsg()); + return false; + } + + // open output file + CFilePointer output{fopen(destFilename.c_str(), "wb"), std::fclose}; + if (!output) { + LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename, + destFilename, GetLastErrorMsg()); + return false; + } + + // copy loop + std::array buffer; + while (!feof(input.get())) { + // read input + std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get()); + if (rnum != buffer.size()) { + if (ferror(input.get()) != 0) { + LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}", + srcFilename, destFilename, GetLastErrorMsg()); + return false; + } + } + + // write output + std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get()); + if (wnum != rnum) { + LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename, + destFilename, GetLastErrorMsg()); + return false; + } + } + + return true; +#endif +} + +u64 GetSize(const std::string& filename) { + if (!Exists(filename)) { + LOG_ERROR(Common_Filesystem, "failed {}: No such file", filename); + return 0; + } + + if (IsDirectory(filename)) { + LOG_ERROR(Common_Filesystem, "failed {}: is a directory", filename); + return 0; + } + + struct stat buf; +#ifdef _WIN32 + if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0) +#else + if (stat(filename.c_str(), &buf) == 0) +#endif + { + LOG_TRACE(Common_Filesystem, "{}: {}", filename, buf.st_size); + return buf.st_size; + } + + LOG_ERROR(Common_Filesystem, "Stat failed {}: {}", filename, GetLastErrorMsg()); + return 0; +} + +u64 GetSize(const int fd) { + struct stat buf; + if (fstat(fd, &buf) != 0) { + LOG_ERROR(Common_Filesystem, "GetSize: stat failed {}: {}", fd, GetLastErrorMsg()); + return 0; + } + return buf.st_size; +} + +u64 GetSize(FILE* f) { + // can't use off_t here because it can be 32-bit + u64 pos = ftello(f); + if (fseeko(f, 0, SEEK_END) != 0) { + LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", (void*)f, GetLastErrorMsg()); + return 0; + } + u64 size = ftello(f); + if ((size != pos) && (fseeko(f, pos, SEEK_SET) != 0)) { + LOG_ERROR(Common_Filesystem, "GetSize: seek failed {}: {}", (void*)f, GetLastErrorMsg()); + return 0; + } + return size; +} + +bool CreateEmptyFile(const std::string& filename) { + LOG_TRACE(Common_Filesystem, "{}", filename); + + if (!FileUtil::IOFile(filename, "wb")) { + LOG_ERROR(Common_Filesystem, "failed {}: {}", filename, GetLastErrorMsg()); + return false; + } + + return true; +} + +bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory, + DirectoryEntryCallable callback) { + LOG_TRACE(Common_Filesystem, "directory {}", directory); + + // How many files + directories we found + u64 found_entries = 0; + + // Save the status of callback function + bool callback_error = false; + +#ifdef _WIN32 + // Find the first file in the directory. + WIN32_FIND_DATAW ffd; + + HANDLE handle_find = FindFirstFileW(Common::UTF8ToUTF16W(directory + "\\*").c_str(), &ffd); + if (handle_find == INVALID_HANDLE_VALUE) { + FindClose(handle_find); + return false; + } + // windows loop + do { + const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName)); +#else + DIR* dirp = opendir(directory.c_str()); + if (!dirp) + return false; + + // non windows loop + while (struct dirent* result = readdir(dirp)) { + const std::string virtual_name(result->d_name); +#endif + + if (virtual_name == "." || virtual_name == "..") + continue; + + u64 ret_entries = 0; + if (!callback(&ret_entries, directory, virtual_name)) { + callback_error = true; + break; + } + found_entries += ret_entries; + +#ifdef _WIN32 + } while (FindNextFileW(handle_find, &ffd) != 0); + FindClose(handle_find); +#else + } + closedir(dirp); +#endif + + if (callback_error) + return false; + + // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it + if (num_entries_out != nullptr) + *num_entries_out = found_entries; + return true; +} + +u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, + unsigned int recursion) { + const auto callback = [recursion, &parent_entry](u64* num_entries_out, + const std::string& directory, + const std::string& virtual_name) -> bool { + FSTEntry entry; + entry.virtualName = virtual_name; + entry.physicalName = directory + DIR_SEP + virtual_name; + + if (IsDirectory(entry.physicalName)) { + entry.isDirectory = true; + // is a directory, lets go inside if we didn't recurse to often + if (recursion > 0) { + entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1); + *num_entries_out += entry.size; + } else { + entry.size = 0; + } + } else { // is a file + entry.isDirectory = false; + entry.size = GetSize(entry.physicalName); + } + (*num_entries_out)++; + + // Push into the tree + parent_entry.children.push_back(std::move(entry)); + return true; + }; + + u64 num_entries; + return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0; +} + +bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) { + const auto callback = [recursion](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string new_path = directory + DIR_SEP_CHR + virtual_name; + + if (IsDirectory(new_path)) { + if (recursion == 0) + return false; + return DeleteDirRecursively(new_path, recursion - 1); + } + return Delete(new_path); + }; + + if (!ForeachDirectoryEntry(nullptr, directory, callback)) + return false; + + // Delete the outermost directory + FileUtil::DeleteDir(directory); + return true; +} + +void CopyDir(const std::string& source_path, const std::string& dest_path) { +#ifndef _WIN32 + if (source_path == dest_path) + return; + if (!FileUtil::Exists(source_path)) + return; + if (!FileUtil::Exists(dest_path)) + FileUtil::CreateFullPath(dest_path); + + DIR* dirp = opendir(source_path.c_str()); + if (!dirp) + return; + + while (struct dirent* result = readdir(dirp)) { + const std::string virtualName(result->d_name); + // check for "." and ".." + if (((virtualName[0] == '.') && (virtualName[1] == '\0')) || + ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) + continue; + + std::string source, dest; + source = source_path + virtualName; + dest = dest_path + virtualName; + if (IsDirectory(source)) { + source += '/'; + dest += '/'; + if (!FileUtil::Exists(dest)) + FileUtil::CreateFullPath(dest); + CopyDir(source, dest); + } else if (!FileUtil::Exists(dest)) + FileUtil::Copy(source, dest); + } + closedir(dirp); +#endif +} + +std::optional GetCurrentDir() { +// Get the current working directory (getcwd uses malloc) +#ifdef _WIN32 + wchar_t* dir; + if (!(dir = _wgetcwd(nullptr, 0))) +#else + char* dir; + if (!(dir = getcwd(nullptr, 0))) +#endif + { + LOG_ERROR(Common_Filesystem, "GetCurrentDirectory failed: {}", GetLastErrorMsg()); + return {}; + } +#ifdef _WIN32 + std::string strDir = Common::UTF16ToUTF8(dir); +#else + std::string strDir = dir; +#endif + free(dir); + return strDir; +} + +bool SetCurrentDir(const std::string& directory) { +#ifdef _WIN32 + return _wchdir(Common::UTF8ToUTF16W(directory).c_str()) == 0; +#else + return chdir(directory.c_str()) == 0; +#endif +} + +#if defined(__APPLE__) +std::string GetBundleDirectory() { + CFURLRef BundleRef; + char AppBundlePath[MAXPATHLEN]; + // Get the main bundle for the app + BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle); + CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath)); + CFRelease(BundleRef); + CFRelease(BundlePath); + + return AppBundlePath; +} +#endif + +#ifdef _WIN32 +const std::string& GetExeDirectory() { + static std::string exe_path; + if (exe_path.empty()) { + wchar_t wchar_exe_path[2048]; + GetModuleFileNameW(nullptr, wchar_exe_path, 2048); + exe_path = Common::UTF16ToUTF8(wchar_exe_path); + exe_path = exe_path.substr(0, exe_path.find_last_of('\\')); + } + return exe_path; +} + +std::string AppDataRoamingDirectory() { + PWSTR pw_local_path = nullptr; + // Only supported by Windows Vista or later + SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pw_local_path); + std::string local_path = Common::UTF16ToUTF8(pw_local_path); + CoTaskMemFree(pw_local_path); + return local_path; +} +#else +/** + * @return The user’s home directory on POSIX systems + */ +static const std::string& GetHomeDirectory() { + static std::string home_path; + if (home_path.empty()) { + const char* envvar = getenv("HOME"); + if (envvar) { + home_path = envvar; + } else { + auto pw = getpwuid(getuid()); + ASSERT_MSG(pw, + "$HOME isn’t defined, and the current user can’t be found in /etc/passwd."); + home_path = pw->pw_dir; + } + } + return home_path; +} + +/** + * Follows the XDG Base Directory Specification to get a directory path + * @param envvar The XDG environment variable to get the value from + * @return The directory path + * @sa http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html + */ +static const std::string GetUserDirectory(const std::string& envvar) { + const char* directory = getenv(envvar.c_str()); + + std::string user_dir; + if (directory) { + user_dir = directory; + } else { + std::string subdirectory; + if (envvar == "XDG_DATA_HOME") + subdirectory = DIR_SEP ".local" DIR_SEP "share"; + else if (envvar == "XDG_CONFIG_HOME") + subdirectory = DIR_SEP ".config"; + else if (envvar == "XDG_CACHE_HOME") + subdirectory = DIR_SEP ".cache"; + else + ASSERT_MSG(false, "Unknown XDG variable {}.", envvar); + user_dir = GetHomeDirectory() + subdirectory; + } + + ASSERT_MSG(!user_dir.empty(), "User directory {} musn’t be empty.", envvar); + ASSERT_MSG(user_dir[0] == '/', "User directory {} must be absolute.", envvar); + + return user_dir; +} +#endif + +std::string GetSysDirectory() { + std::string sysDir; + +#if defined(__APPLE__) + sysDir = GetBundleDirectory(); + sysDir += DIR_SEP; + sysDir += SYSDATA_DIR; +#else + sysDir = SYSDATA_DIR; +#endif + sysDir += DIR_SEP; + + LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir); + return sysDir; +} + +namespace { +std::unordered_map g_paths; +} + +void SetUserPath(const std::string& path) { + std::string& user_path = g_paths[UserPath::UserDir]; + + if (!path.empty() && CreateFullPath(path)) { + LOG_INFO(Common_Filesystem, "Using {} as the user directory", path); + user_path = path; + g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); + g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); + } else { +#ifdef _WIN32 + user_path = GetExeDirectory() + DIR_SEP USERDATA_DIR DIR_SEP; + if (!FileUtil::IsDirectory(user_path)) { + user_path = AppDataRoamingDirectory() + DIR_SEP EMU_DATA_DIR DIR_SEP; + } else { + LOG_INFO(Common_Filesystem, "Using the local user directory"); + } + + g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); + g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); +#elif ANDROID + ASSERT_MSG(false, "Specified path {} is not valid", path); +#else + if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { + user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; + g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); + g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); + } else { + std::string data_dir = GetUserDirectory("XDG_DATA_HOME"); + std::string config_dir = GetUserDirectory("XDG_CONFIG_HOME"); + std::string cache_dir = GetUserDirectory("XDG_CACHE_HOME"); + + user_path = data_dir + DIR_SEP EMU_DATA_DIR DIR_SEP; + g_paths.emplace(UserPath::ConfigDir, config_dir + DIR_SEP EMU_DATA_DIR DIR_SEP); + g_paths.emplace(UserPath::CacheDir, cache_dir + DIR_SEP EMU_DATA_DIR DIR_SEP); + } +#endif + } + g_paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP); + g_paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP); + g_paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP); + // TODO: Put the logs in a better location for each OS + g_paths.emplace(UserPath::LogDir, user_path + LOG_DIR DIR_SEP); + g_paths.emplace(UserPath::CheatsDir, user_path + CHEATS_DIR DIR_SEP); + g_paths.emplace(UserPath::DLLDir, user_path + DLL_DIR DIR_SEP); +} + +const std::string& GetUserPath(UserPath path) { + // Set up all paths and files on the first run + if (g_paths.empty()) + SetUserPath(); + return g_paths[path]; +} + + +std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str) { + return IOFile(filename, text_file ? "w" : "wb").WriteString(str); +} + +std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str) { + IOFile file(filename, text_file ? "r" : "rb"); + + if (!file) + return false; + + str.resize(static_cast(file.GetSize())); + return file.ReadArray(&str[0], str.size()); +} + +void SplitFilename83(const std::string& filename, std::array& short_name, + std::array& extension) { + const std::string forbidden_characters = ".\"/\\[]:;=, "; + + // On a FAT32 partition, 8.3 names are stored as a 11 bytes array, filled with spaces. + short_name = {{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\0'}}; + extension = {{' ', ' ', ' ', '\0'}}; + + std::string::size_type point = filename.rfind('.'); + if (point == filename.size() - 1) + point = filename.rfind('.', point); + + // Get short name. + int j = 0; + for (char letter : filename.substr(0, point)) { + if (forbidden_characters.find(letter, 0) != std::string::npos) + continue; + if (j == 8) { + // TODO(Link Mauve): also do that for filenames containing a space. + // TODO(Link Mauve): handle multiple files having the same short name. + short_name[6] = '~'; + short_name[7] = '1'; + break; + } + short_name[j++] = toupper(letter); + } + + // Get extension. + if (point != std::string::npos) { + j = 0; + for (char letter : filename.substr(point + 1, 3)) + extension[j++] = toupper(letter); + } +} + +IOFile::IOFile() {} + +IOFile::IOFile(const std::string& filename, const char openmode[], int flags) { + Open(filename, openmode, flags); +} + +IOFile::~IOFile() { + Close(); +} + +IOFile::IOFile(IOFile&& other) { + Swap(other); +} + +IOFile& IOFile::operator=(IOFile&& other) { + Swap(other); + return *this; +} + +void IOFile::Swap(IOFile& other) { + std::swap(m_file, other.m_file); + std::swap(m_good, other.m_good); +} + +bool IOFile::Open(const std::string& filename, const char openmode[], int flags) { + Close(); +#ifdef _WIN32 + if (flags != 0) { + m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(), + Common::UTF8ToUTF16W(openmode).c_str(), flags); + } else { + _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(), + Common::UTF8ToUTF16W(openmode).c_str()); + } +#else + m_file = fopen(filename.c_str(), openmode); +#endif + + m_good = IsOpen(); + return m_good; +} + +bool IOFile::Close() { + if (!IsOpen() || 0 != std::fclose(m_file)) + m_good = false; + + m_file = nullptr; + return m_good; +} + +u64 IOFile::GetSize() const { + if (IsOpen()) + return FileUtil::GetSize(m_file); + + return 0; +} + +bool IOFile::Seek(s64 off, int origin) { + if (!IsOpen() || 0 != fseeko(m_file, off, origin)) + m_good = false; + + return m_good; +} + +u64 IOFile::Tell() const { + if (IsOpen()) + return ftello(m_file); + + return -1; +} + +bool IOFile::Flush() { + if (!IsOpen() || 0 != std::fflush(m_file)) + m_good = false; + + return m_good; +} + +bool IOFile::Resize(u64 size) { + if (!IsOpen() || 0 != +#ifdef _WIN32 + // ector: _chsize sucks, not 64-bit safe + // F|RES: changed to _chsize_s. i think it is 64-bit safe + _chsize_s(_fileno(m_file), size) +#else + // TODO: handle 64bit and growing + ftruncate(fileno(m_file), size) +#endif + ) + m_good = false; + + return m_good; +} + +} // namespace FileUtil diff --git a/src/common/file_util.h b/src/common/file_util.h new file mode 100644 index 0000000..ad44c0f --- /dev/null +++ b/src/common/file_util.h @@ -0,0 +1,280 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/common_types.h" +#ifdef _MSC_VER +#include "common/string_util.h" +#endif + +namespace FileUtil { + +// User paths for GetUserPath +enum class UserPath { + CacheDir, + CheatsDir, + ConfigDir, + DLLDir, + LogDir, + NANDDir, + RootDir, + SDMCDir, + SysDataDir, + UserDir, +}; + +// FileSystem tree node/ +struct FSTEntry { + bool isDirectory; + u64 size; // file length or number of entries from children + std::string physicalName; // name on disk + std::string virtualName; // name in FST names table + std::vector children; +}; + +// Returns true if file filename exists +bool Exists(const std::string& filename); + +// Returns true if filename is a directory +bool IsDirectory(const std::string& filename); + +// Returns the size of filename (64bit) +u64 GetSize(const std::string& filename); + +// Overloaded GetSize, accepts file descriptor +u64 GetSize(const int fd); + +// Overloaded GetSize, accepts FILE* +u64 GetSize(FILE* f); + +// Returns true if successful, or path already exists. +bool CreateDir(const std::string& filename); + +// Creates the full path of fullPath returns true on success +bool CreateFullPath(const std::string& fullPath); + +// Deletes a given filename, return true on success +// Doesn't supports deleting a directory +bool Delete(const std::string& filename); + +// Deletes a directory filename, returns true on success +bool DeleteDir(const std::string& filename); + +// renames file srcFilename to destFilename, returns true on success +bool Rename(const std::string& srcFilename, const std::string& destFilename); + +// copies file srcFilename to destFilename, returns true on success +bool Copy(const std::string& srcFilename, const std::string& destFilename); + +// creates an empty file filename, returns true on success +bool CreateEmptyFile(const std::string& filename); + +/** + * @param num_entries_out to be assigned by the callable with the number of iterated directory + * entries, never null + * @param directory the path to the enclosing directory + * @param virtual_name the entry name, without any preceding directory info + * @return whether handling the entry succeeded + */ +using DirectoryEntryCallable = std::function; + +/** + * Scans a directory, calling the callback for each file/directory contained within. + * If the callback returns failure, scanning halts and this function returns failure as well + * @param num_entries_out assigned by the function with the number of iterated directory entries, + * can be null + * @param directory the directory to scan + * @param callback The callback which will be called for each entry + * @return whether scanning the directory succeeded + */ +bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory, + DirectoryEntryCallable callback); + +/** + * Scans the directory tree, storing the results. + * @param directory the parent directory to start scanning from + * @param parent_entry FSTEntry where the filesystem tree results will be stored. + * @param recursion Number of children directories to read before giving up. + * @return the total number of files/directories found + */ +u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry, + unsigned int recursion = 0); + +// deletes the given directory and anything under it. Returns true on success. +bool DeleteDirRecursively(const std::string& directory, unsigned int recursion = 256); + +// Returns the current directory +std::optional GetCurrentDir(); + +// Create directory and copy contents (does not overwrite existing files) +void CopyDir(const std::string& source_path, const std::string& dest_path); + +// Set the current directory to given directory +bool SetCurrentDir(const std::string& directory); + +// User paths are not directly used by us, and are only preserved here to match Citra's +// user folder and tell where to copy the files to. + +void SetUserPath(const std::string& path = ""); + +// Returns a pointer to a string with a Citra data dir in the user's home +// directory. To be used in "multi-user" mode (that is, installed). +const std::string& GetUserPath(UserPath path); + +// Returns the path to where the sys file are +std::string GetSysDirectory(); + +#ifdef __APPLE__ +std::string GetBundleDirectory(); +#endif + +#ifdef _WIN32 +const std::string& GetExeDirectory(); +std::string AppDataRoamingDirectory(); +#endif + +std::size_t WriteStringToFile(bool text_file, const std::string& filename, std::string_view str); + +std::size_t ReadFileToString(bool text_file, const std::string& filename, std::string& str); + +/** + * Splits the filename into 8.3 format + * Loosely implemented following https://en.wikipedia.org/wiki/8.3_filename + * @param filename The normal filename to use + * @param short_name A 9-char array in which the short name will be written + * @param extension A 4-char array in which the extension will be written + */ +void SplitFilename83(const std::string& filename, std::array& short_name, + std::array& extension); + +// simple wrapper for cstdlib file functions to +// hopefully will make error checking easier +// and make forgetting an fclose() harder +class IOFile : public NonCopyable { +public: + IOFile(); + + // flags is used for windows specific file open mode flags, which + // allows citra to open the logs in shared write mode, so that the file + // isn't considered "locked" while citra is open and people can open the log file and view it + IOFile(const std::string& filename, const char openmode[], int flags = 0); + + ~IOFile(); + + IOFile(IOFile&& other); + IOFile& operator=(IOFile&& other); + + void Swap(IOFile& other); + + bool Open(const std::string& filename, const char openmode[], int flags = 0); + bool Close(); + + template + std::size_t ReadArray(T* data, std::size_t length) { + static_assert(std::is_trivially_copyable_v, + "Given array does not consist of trivially copyable objects"); + + if (!IsOpen()) { + m_good = false; + return std::numeric_limits::max(); + } + + std::size_t items_read = std::fread(data, sizeof(T), length, m_file); + if (items_read != length) + m_good = false; + + return items_read; + } + + template + std::size_t WriteArray(const T* data, std::size_t length) { + static_assert(std::is_trivially_copyable_v, + "Given array does not consist of trivially copyable objects"); + + if (!IsOpen()) { + m_good = false; + return std::numeric_limits::max(); + } + + std::size_t items_written = std::fwrite(data, sizeof(T), length, m_file); + if (items_written != length) + m_good = false; + + return items_written; + } + + template + std::size_t ReadBytes(T* data, std::size_t length) { + static_assert(std::is_trivially_copyable_v, "T must be trivially copyable"); + return ReadArray(reinterpret_cast(data), length); + } + + template + std::size_t WriteBytes(const T* data, std::size_t length) { + static_assert(std::is_trivially_copyable_v, "T must be trivially copyable"); + return WriteArray(reinterpret_cast(data), length); + } + + template + std::size_t WriteObject(const T& object) { + static_assert(!std::is_pointer_v, "WriteObject arguments must not be a pointer"); + return WriteArray(&object, 1); + } + + std::size_t WriteString(std::string_view str) { + return WriteArray(str.data(), str.length()); + } + + bool IsOpen() const { + return nullptr != m_file; + } + + // m_good is set to false when a read, write or other function fails + bool IsGood() const { + return m_good; + } + explicit operator bool() const { + return IsGood(); + } + + bool Seek(s64 off, int origin); + u64 Tell() const; + u64 GetSize() const; + bool Resize(u64 size); + bool Flush(); + + // clear error state + void Clear() { + m_good = true; + std::clearerr(m_file); + } + +private: + std::FILE* m_file = nullptr; + bool m_good = true; +}; + +} // namespace FileUtil + +// To deal with Windows being dumb at unicode: +template +void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) { +#ifdef _MSC_VER + fstream.open(Common::UTF8ToUTF16W(filename), openmode); +#else + fstream.open(filename, openmode); +#endif +} diff --git a/src/common/logging/log.h b/src/common/logging/log.h new file mode 100644 index 0000000..bdb1a15 --- /dev/null +++ b/src/common/logging/log.h @@ -0,0 +1,47 @@ +// Copyright 2014 Citra Emulator Project / 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +// This is simplified version of Citra's logging system. +// Only stderr/stderr output is enabled and color is implemented +// for the UNIX-like. + +#pragma once + +#include +#include +#include +#include +#include "common/string_util.h" + +template +void PrintLog(std::FILE* f, const std::string& log_class, const std::string& level, + const std::string& color, const std::string& file, int line, const std::string& func, + const std::string& format, Args&&... args) { + static auto time_origin = std::chrono::steady_clock::now(); + const u64 us = std::chrono::duration_cast( + std::chrono::steady_clock::now() - time_origin) + .count(); + const auto real_class = Common::ReplaceAll(log_class, "_", "."); + fmt::print(f, "\x1b{}[{:12.6f}] {} <{}> {}:{}:{}: " + format + "\x1b[0m\n", color, + us / 1000000.0, log_class, level, file, line, func, args...); + fflush(stderr); +} + +#ifdef _DEBUG +#define LOG_TRACE(log_class, ...) \ + PrintLog(stderr, #log_class, "Trace", "[1;30m", __FILE__, __LINE__, __func__, __VA_ARGS__) +#else +#define LOG_TRACE(log_class, fmt, ...) (void(0)) +#endif + +#define LOG_DEBUG(log_class, ...) \ + PrintLog(stderr, #log_class, "Debug", "[0;36m", __FILE__, __LINE__, __func__, __VA_ARGS__) +#define LOG_INFO(log_class, ...) \ + PrintLog(stderr, #log_class, "Info", "[0;37m", __FILE__, __LINE__, __func__, __VA_ARGS__) +#define LOG_WARNING(log_class, ...) \ + PrintLog(stderr, #log_class, "Warning", "[1;33m", __FILE__, __LINE__, __func__, __VA_ARGS__) +#define LOG_ERROR(log_class, ...) \ + PrintLog(stderr, #log_class, "Error", "[1;31m", __FILE__, __LINE__, __func__, __VA_ARGS__) +#define LOG_CRITICAL(log_class, ...) \ + PrintLog(stderr, #log_class, "Critical", "[1;35m", __FILE__, __LINE__, __func__, __VA_ARGS__) diff --git a/src/common/misc.cpp b/src/common/misc.cpp new file mode 100644 index 0000000..68cb86c --- /dev/null +++ b/src/common/misc.cpp @@ -0,0 +1,31 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#include "common/common_funcs.h" + +// Generic function to get last error message. +// Call directly after the command or use the error num. +// This function might change the error code. +std::string GetLastErrorMsg() { + static const std::size_t buff_size = 255; + char err_str[buff_size]; + +#ifdef _WIN32 + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), err_str, buff_size, nullptr); +#else + // Thread safe (XSI-compliant) + strerror_r(errno, err_str, buff_size); +#endif + + return std::string(err_str, buff_size); +} diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp new file mode 100644 index 0000000..f3c6488 --- /dev/null +++ b/src/common/string_util.cpp @@ -0,0 +1,212 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include "common/common_paths.h" +#include "common/logging/log.h" +#include "common/string_util.h" + +#ifdef _WIN32 +#include +#endif + +namespace Common { + +/// Make a string lowercase +std::string ToLower(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::tolower(c); }); + return str; +} + +/// Make a string uppercase +std::string ToUpper(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), + [](unsigned char c) { return std::toupper(c); }); + return str; +} + +// Turns " hej " into "hej". Also handles tabs. +std::string StripSpaces(const std::string& str) { + const std::size_t s = str.find_first_not_of(" \t\r\n"); + + if (str.npos != s) + return str.substr(s, str.find_last_not_of(" \t\r\n") - s + 1); + else + return ""; +} + +// "\"hello\"" is turned to "hello" +// This one assumes that the string has already been space stripped in both +// ends, as done by StripSpaces above, for example. +std::string StripQuotes(const std::string& s) { + if (s.size() && '\"' == s[0] && '\"' == *s.rbegin()) + return s.substr(1, s.size() - 2); + else + return s; +} + +std::string StringFromBool(bool value) { + return value ? "True" : "False"; +} + +bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, + std::string* _pExtension) { + if (full_path.empty()) + return false; + + std::size_t dir_end = full_path.find_last_of("/" +// windows needs the : included for something like just "C:" to be considered a directory +#ifdef _WIN32 + ":" +#endif + ); + if (std::string::npos == dir_end) + dir_end = 0; + else + dir_end += 1; + + std::size_t fname_end = full_path.rfind('.'); + if (fname_end < dir_end || std::string::npos == fname_end) + fname_end = full_path.size(); + + if (_pPath) + *_pPath = full_path.substr(0, dir_end); + + if (_pFilename) + *_pFilename = full_path.substr(dir_end, fname_end - dir_end); + + if (_pExtension) + *_pExtension = full_path.substr(fname_end); + + return true; +} + +void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, + const std::string& _Filename) { + _CompleteFilename = _Path; + + // check for seperator + if (DIR_SEP_CHR != *_CompleteFilename.rbegin()) + _CompleteFilename += DIR_SEP_CHR; + + // add the filename + _CompleteFilename += _Filename; +} + +void SplitString(const std::string& str, const char delim, std::vector& output) { + std::istringstream iss(str); + output.resize(1); + + while (std::getline(iss, *output.rbegin(), delim)) { + output.emplace_back(); + } + + output.pop_back(); +} + +std::string TabsToSpaces(int tab_size, std::string in) { + std::size_t i = 0; + + while ((i = in.find('\t')) != std::string::npos) { + in.replace(i, 1, tab_size, ' '); + } + + return in; +} + +std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) { + std::size_t pos = 0; + + if (src == dest) + return result; + + while ((pos = result.find(src, pos)) != std::string::npos) { + result.replace(pos, src.size(), dest); + pos += dest.length(); + } + + return result; +} + +std::string UTF16ToUTF8(const std::u16string& input) { +#ifdef _MSC_VER + // Workaround for missing char16_t/char32_t instantiations in MSVC2017 + std::wstring_convert, __int16> convert; + std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend()); + return convert.to_bytes(tmp_buffer); +#else + std::wstring_convert, char16_t> convert; + return convert.to_bytes(input); +#endif +} + +std::u16string UTF8ToUTF16(const std::string& input) { +#ifdef _MSC_VER + // Workaround for missing char16_t/char32_t instantiations in MSVC2017 + std::wstring_convert, __int16> convert; + auto tmp_buffer = convert.from_bytes(input); + return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend()); +#else + std::wstring_convert, char16_t> convert; + return convert.from_bytes(input); +#endif +} + +#ifdef _WIN32 +static std::wstring CPToUTF16(u32 code_page, const std::string& input) { + const auto size = + MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), nullptr, 0); + + if (size == 0) { + return L""; + } + + std::wstring output(size, L'\0'); + + if (size != MultiByteToWideChar(code_page, 0, input.data(), static_cast(input.size()), + &output[0], static_cast(output.size()))) { + output.clear(); + } + + return output; +} + +std::string UTF16ToUTF8(const std::wstring& input) { + const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), + nullptr, 0, nullptr, nullptr); + if (size == 0) { + return ""; + } + + std::string output(size, '\0'); + + if (size != WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast(input.size()), + &output[0], static_cast(output.size()), nullptr, + nullptr)) { + output.clear(); + } + + return output; +} + +std::wstring UTF8ToUTF16W(const std::string& input) { + return CPToUTF16(CP_UTF8, input); +} + +#endif + +std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) { + std::size_t len = 0; + while (len < max_len && buffer[len] != '\0') + ++len; + + return std::string(buffer, len); +} +} // namespace Common diff --git a/src/common/string_util.h b/src/common/string_util.h new file mode 100644 index 0000000..8c18946 --- /dev/null +++ b/src/common/string_util.h @@ -0,0 +1,83 @@ +// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/swap.h" + +namespace Common { + +/// Make a string lowercase +std::string ToLower(std::string str); + +/// Make a string uppercase +std::string ToUpper(std::string str); + +std::string StripSpaces(const std::string& s); +std::string StripQuotes(const std::string& s); + +std::string StringFromBool(bool value); + +std::string TabsToSpaces(int tab_size, std::string in); + +void SplitString(const std::string& str, char delim, std::vector& output); + +// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe" +bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _pFilename, + std::string* _pExtension); + +void BuildCompleteFilename(std::string& _CompleteFilename, const std::string& _Path, + const std::string& _Filename); +std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest); + +std::string UTF16ToUTF8(const std::u16string& input); +std::u16string UTF8ToUTF16(const std::string& input); + +#ifdef _WIN32 +std::string UTF16ToUTF8(const std::wstring& input); +std::wstring UTF8ToUTF16W(const std::string& str); + +#endif + +/** + * Compares the string defined by the range [`begin`, `end`) to the null-terminated C-string + * `other` for equality. + */ +template +bool ComparePartialString(InIt begin, InIt end, const char* other) { + for (; begin != end && *other != '\0'; ++begin, ++other) { + if (*begin != *other) { + return false; + } + } + // Only return true if both strings finished at the same point + return (begin == end) == (*other == '\0'); +} + +/** + * Converts a UTF-16 text in a container to a UTF-8 std::string. + */ +template +std::string UTF16BufferToUTF8(const T& text) { + const auto text_end = std::find(text.begin(), text.end(), u'\0'); + const std::size_t text_size = std::distance(text.begin(), text_end); + std::u16string buffer(text_size, 0); + std::transform(text.begin(), text_end, buffer.begin(), [](u16_le character) { + return static_cast(static_cast(character)); + }); + return UTF16ToUTF8(buffer); +} + +/** + * Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't + * NUL-terminated then the string ends at max_len characters. + */ +std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len); + +} // namespace Common diff --git a/src/common/swap.h b/src/common/swap.h new file mode 100644 index 0000000..71932c2 --- /dev/null +++ b/src/common/swap.h @@ -0,0 +1,718 @@ +// Copyright (c) 2012- PPSSPP Project / Dolphin Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include + +#if defined(_MSC_VER) +#include +#endif +#include +#include "common/common_types.h" + +// GCC +#ifdef __GNUC__ + +#if __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) && !defined(COMMON_LITTLE_ENDIAN) +#define COMMON_LITTLE_ENDIAN 1 +#elif __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) && !defined(COMMON_BIG_ENDIAN) +#define COMMON_BIG_ENDIAN 1 +#endif + +// LLVM/clang +#elif defined(__clang__) + +#if __LITTLE_ENDIAN__ && !defined(COMMON_LITTLE_ENDIAN) +#define COMMON_LITTLE_ENDIAN 1 +#elif __BIG_ENDIAN__ && !defined(COMMON_BIG_ENDIAN) +#define COMMON_BIG_ENDIAN 1 +#endif + +// MSVC +#elif defined(_MSC_VER) && !defined(COMMON_BIG_ENDIAN) && !defined(COMMON_LITTLE_ENDIAN) + +#define COMMON_LITTLE_ENDIAN 1 +#endif + +// Worst case, default to little endian. +#if !COMMON_BIG_ENDIAN && !COMMON_LITTLE_ENDIAN +#define COMMON_LITTLE_ENDIAN 1 +#endif + +namespace Common { + +#ifdef _MSC_VER +[[nodiscard]] inline u16 swap16(u16 data) noexcept { + return _byteswap_ushort(data); +} +[[nodiscard]] inline u32 swap32(u32 data) noexcept { + return _byteswap_ulong(data); +} +[[nodiscard]] inline u64 swap64(u64 data) noexcept { + return _byteswap_uint64(data); +} +#elif defined(__clang__) || defined(__GNUC__) +#if defined(__Bitrig__) || defined(__OpenBSD__) +// redefine swap16, swap32, swap64 as inline functions +#undef swap16 +#undef swap32 +#undef swap64 +#endif +[[nodiscard]] inline u16 swap16(u16 data) noexcept { + return __builtin_bswap16(data); +} +[[nodiscard]] inline u32 swap32(u32 data) noexcept { + return __builtin_bswap32(data); +} +[[nodiscard]] inline u64 swap64(u64 data) noexcept { + return __builtin_bswap64(data); +} +#else +// Generic implementation. +[[nodiscard]] inline u16 swap16(u16 data) noexcept { + return (data >> 8) | (data << 8); +} +[[nodiscard]] inline u32 swap32(u32 data) noexcept { + return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) | + ((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24); +} +[[nodiscard]] inline u64 swap64(u64 data) noexcept { + return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) | + ((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) | + ((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) | + ((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56); +} +#endif + +[[nodiscard]] inline float swapf(float f) noexcept { + static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t."); + + u32 value; + std::memcpy(&value, &f, sizeof(u32)); + + value = swap32(value); + std::memcpy(&f, &value, sizeof(u32)); + + return f; +} + +[[nodiscard]] inline double swapd(double f) noexcept { + static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t."); + + u64 value; + std::memcpy(&value, &f, sizeof(u64)); + + value = swap64(value); + std::memcpy(&f, &value, sizeof(u64)); + + return f; +} + +} // Namespace Common + +template +struct swap_struct_t { + using swapped_t = swap_struct_t; + +protected: + T value; + + static T swap(T v) { + return F::swap(v); + } + +public: + T swap() const { + return swap(value); + } + swap_struct_t() = default; + swap_struct_t(const T& v) : value(swap(v)) {} + + template + swapped_t& operator=(const S& source) { + value = swap(static_cast(source)); + return *this; + } + + operator s8() const { + return static_cast(swap()); + } + operator u8() const { + return static_cast(swap()); + } + operator s16() const { + return static_cast(swap()); + } + operator u16() const { + return static_cast(swap()); + } + operator s32() const { + return static_cast(swap()); + } + operator u32() const { + return static_cast(swap()); + } + operator s64() const { + return static_cast(swap()); + } + operator u64() const { + return static_cast(swap()); + } + operator float() const { + return static_cast(swap()); + } + operator double() const { + return static_cast(swap()); + } + + // +v + swapped_t operator+() const { + return +swap(); + } + // -v + swapped_t operator-() const { + return -swap(); + } + + // v / 5 + swapped_t operator/(const swapped_t& i) const { + return swap() / i.swap(); + } + template + swapped_t operator/(const S& i) const { + return swap() / i; + } + + // v * 5 + swapped_t operator*(const swapped_t& i) const { + return swap() * i.swap(); + } + template + swapped_t operator*(const S& i) const { + return swap() * i; + } + + // v + 5 + swapped_t operator+(const swapped_t& i) const { + return swap() + i.swap(); + } + template + swapped_t operator+(const S& i) const { + return swap() + static_cast(i); + } + // v - 5 + swapped_t operator-(const swapped_t& i) const { + return swap() - i.swap(); + } + template + swapped_t operator-(const S& i) const { + return swap() - static_cast(i); + } + + // v += 5 + swapped_t& operator+=(const swapped_t& i) { + value = swap(swap() + i.swap()); + return *this; + } + template + swapped_t& operator+=(const S& i) { + value = swap(swap() + static_cast(i)); + return *this; + } + // v -= 5 + swapped_t& operator-=(const swapped_t& i) { + value = swap(swap() - i.swap()); + return *this; + } + template + swapped_t& operator-=(const S& i) { + value = swap(swap() - static_cast(i)); + return *this; + } + + // ++v + swapped_t& operator++() { + value = swap(swap() + 1); + return *this; + } + // --v + swapped_t& operator--() { + value = swap(swap() - 1); + return *this; + } + + // v++ + swapped_t operator++(int) { + swapped_t old = *this; + value = swap(swap() + 1); + return old; + } + // v-- + swapped_t operator--(int) { + swapped_t old = *this; + value = swap(swap() - 1); + return old; + } + // Comparaison + // v == i + bool operator==(const swapped_t& i) const { + return swap() == i.swap(); + } + template + bool operator==(const S& i) const { + return swap() == i; + } + + // v != i + bool operator!=(const swapped_t& i) const { + return swap() != i.swap(); + } + template + bool operator!=(const S& i) const { + return swap() != i; + } + + // v > i + bool operator>(const swapped_t& i) const { + return swap() > i.swap(); + } + template + bool operator>(const S& i) const { + return swap() > i; + } + + // v < i + bool operator<(const swapped_t& i) const { + return swap() < i.swap(); + } + template + bool operator<(const S& i) const { + return swap() < i; + } + + // v >= i + bool operator>=(const swapped_t& i) const { + return swap() >= i.swap(); + } + template + bool operator>=(const S& i) const { + return swap() >= i; + } + + // v <= i + bool operator<=(const swapped_t& i) const { + return swap() <= i.swap(); + } + template + bool operator<=(const S& i) const { + return swap() <= i; + } + + // logical + swapped_t operator!() const { + return !swap(); + } + + // bitmath + swapped_t operator~() const { + return ~swap(); + } + + swapped_t operator&(const swapped_t& b) const { + return swap() & b.swap(); + } + template + swapped_t operator&(const S& b) const { + return swap() & b; + } + swapped_t& operator&=(const swapped_t& b) { + value = swap(swap() & b.swap()); + return *this; + } + template + swapped_t& operator&=(const S b) { + value = swap(swap() & b); + return *this; + } + + swapped_t operator|(const swapped_t& b) const { + return swap() | b.swap(); + } + template + swapped_t operator|(const S& b) const { + return swap() | b; + } + swapped_t& operator|=(const swapped_t& b) { + value = swap(swap() | b.swap()); + return *this; + } + template + swapped_t& operator|=(const S& b) { + value = swap(swap() | b); + return *this; + } + + swapped_t operator^(const swapped_t& b) const { + return swap() ^ b.swap(); + } + template + swapped_t operator^(const S& b) const { + return swap() ^ b; + } + swapped_t& operator^=(const swapped_t& b) { + value = swap(swap() ^ b.swap()); + return *this; + } + template + swapped_t& operator^=(const S& b) { + value = swap(swap() ^ b); + return *this; + } + + template + swapped_t operator<<(const S& b) const { + return swap() << b; + } + template + swapped_t& operator<<=(const S& b) const { + value = swap(swap() << b); + return *this; + } + + template + swapped_t operator>>(const S& b) const { + return swap() >> b; + } + template + swapped_t& operator>>=(const S& b) const { + value = swap(swap() >> b); + return *this; + } + + // Member + /** todo **/ + + // Arithmetics + template + friend S operator+(const S& p, const swapped_t v); + + template + friend S operator-(const S& p, const swapped_t v); + + template + friend S operator/(const S& p, const swapped_t v); + + template + friend S operator*(const S& p, const swapped_t v); + + template + friend S operator%(const S& p, const swapped_t v); + + // Arithmetics + assignements + template + friend S operator+=(const S& p, const swapped_t v); + + template + friend S operator-=(const S& p, const swapped_t v); + + // Bitmath + template + friend S operator&(const S& p, const swapped_t v); + + // Comparison + template + friend bool operator<(const S& p, const swapped_t v); + + template + friend bool operator>(const S& p, const swapped_t v); + + template + friend bool operator<=(const S& p, const swapped_t v); + + template + friend bool operator>=(const S& p, const swapped_t v); + + template + friend bool operator!=(const S& p, const swapped_t v); + + template + friend bool operator==(const S& p, const swapped_t v); +}; + +// Arithmetics +template +S operator+(const S& i, const swap_struct_t v) { + return i + v.swap(); +} + +template +S operator-(const S& i, const swap_struct_t v) { + return i - v.swap(); +} + +template +S operator/(const S& i, const swap_struct_t v) { + return i / v.swap(); +} + +template +S operator*(const S& i, const swap_struct_t v) { + return i * v.swap(); +} + +template +S operator%(const S& i, const swap_struct_t v) { + return i % v.swap(); +} + +// Arithmetics + assignements +template +S& operator+=(S& i, const swap_struct_t v) { + i += v.swap(); + return i; +} + +template +S& operator-=(S& i, const swap_struct_t v) { + i -= v.swap(); + return i; +} + +// Logical +template +S operator&(const S& i, const swap_struct_t v) { + return i & v.swap(); +} + +template +S operator&(const swap_struct_t v, const S& i) { + return static_cast(v.swap() & i); +} + +// Comparaison +template +bool operator<(const S& p, const swap_struct_t v) { + return p < v.swap(); +} +template +bool operator>(const S& p, const swap_struct_t v) { + return p > v.swap(); +} +template +bool operator<=(const S& p, const swap_struct_t v) { + return p <= v.swap(); +} +template +bool operator>=(const S& p, const swap_struct_t v) { + return p >= v.swap(); +} +template +bool operator!=(const S& p, const swap_struct_t v) { + return p != v.swap(); +} +template +bool operator==(const S& p, const swap_struct_t v) { + return p == v.swap(); +} + +template +struct swap_64_t { + static T swap(T x) { + return static_cast(Common::swap64(x)); + } +}; + +template +struct swap_32_t { + static T swap(T x) { + return static_cast(Common::swap32(x)); + } +}; + +template +struct swap_16_t { + static T swap(T x) { + return static_cast(Common::swap16(x)); + } +}; + +template +struct swap_float_t { + static T swap(T x) { + return static_cast(Common::swapf(x)); + } +}; + +template +struct swap_double_t { + static T swap(T x) { + return static_cast(Common::swapd(x)); + } +}; + +template +struct swap_enum_t { + static_assert(std::is_enum_v); + using base = std::underlying_type_t; + +public: + swap_enum_t() = default; + swap_enum_t(const T& v) : value(swap(v)) {} + + swap_enum_t& operator=(const T& v) { + value = swap(v); + return *this; + } + + operator T() const { + return swap(value); + } + + explicit operator base() const { + return static_cast(swap(value)); + } + +protected: + T value{}; + // clang-format off + using swap_t = std::conditional_t< + std::is_same_v, swap_16_t, std::conditional_t< + std::is_same_v, swap_16_t, std::conditional_t< + std::is_same_v, swap_32_t, std::conditional_t< + std::is_same_v, swap_32_t, std::conditional_t< + std::is_same_v, swap_64_t, std::conditional_t< + std::is_same_v, swap_64_t, void>>>>>>; + // clang-format on + static T swap(T x) { + return static_cast(swap_t::swap(static_cast(x))); + } +}; + +struct SwapTag {}; // Use the different endianness from the system +struct KeepTag {}; // Use the same endianness as the system + +template +struct AddEndian; + +// KeepTag specializations + +template +struct AddEndian { + using type = T; +}; + +// SwapTag specializations + +template <> +struct AddEndian { + using type = u8; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = s8; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template <> +struct AddEndian { + using type = swap_struct_t>; +}; + +template +struct AddEndian { + static_assert(std::is_enum_v); + using type = swap_enum_t; +}; + +// Alias LETag/BETag as KeepTag/SwapTag depending on the system +#if COMMON_LITTLE_ENDIAN + +using LETag = KeepTag; +using BETag = SwapTag; + +#else + +using BETag = KeepTag; +using LETag = SwapTag; + +#endif + +// Aliases for LE types +using u16_le = AddEndian::type; +using u32_le = AddEndian::type; +using u64_le = AddEndian::type; + +using s16_le = AddEndian::type; +using s32_le = AddEndian::type; +using s64_le = AddEndian::type; + +template +using enum_le = std::enable_if_t, typename AddEndian::type>; + +using float_le = AddEndian::type; +using double_le = AddEndian::type; + +// Aliases for BE types +using u16_be = AddEndian::type; +using u32_be = AddEndian::type; +using u64_be = AddEndian::type; + +using s16_be = AddEndian::type; +using s32_be = AddEndian::type; +using s64_be = AddEndian::type; + +template +using enum_be = std::enable_if_t, typename AddEndian::type>; + +using float_be = AddEndian::type; +using double_be = AddEndian::type; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..7f35085 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,14 @@ +target_sources(threeSD PRIVATE + core/data_container.cpp + core/data_container.h + core/decryptor.cpp + core/decryptor.h + core/importer.cpp + core/importer.h + core/inner_fat.cpp + core/inner_fat.h + core/key/arithmetic128.cpp + core/key/arithmetic128.h + core/key/key.cpp + core/key/key.h +) diff --git a/src/core/data_container.cpp b/src/core/data_container.cpp new file mode 100644 index 0000000..087a595 --- /dev/null +++ b/src/core/data_container.cpp @@ -0,0 +1,152 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/assert.h" +#include "core/data_container.h" + +constexpr u32 MakeMagic(char a, char b, char c, char d) { + return a | b << 8 | c << 16 | d << 24; +} + +DPFSContainer::DPFSContainer(DPFSDescriptor descriptor_, u8 level1_selector_, + std::vector data_) + : descriptor(descriptor_), level1_selector(level1_selector_), data(std::move(data_)) { + + ASSERT_MSG(descriptor.magic == MakeMagic('D', 'P', 'F', 'S'), "DPFS Magic is not correct"); + ASSERT_MSG(descriptor.version == 0x10000, "DPFS Version is not correct"); +} + +u8 DPFSContainer::GetBit(u8 level, u8 selector, u64 index) const { + ASSERT_MSG(level <= 2 && selector <= 1, "Level or selector invalid"); + return (data[(descriptor.levels[level].offset + selector * descriptor.levels[level].size) / 4 + + index / 32] >> + (31 - (index % 32))) & + 1; +} + +u8 DPFSContainer::GetByte(u8 level, u8 selector, u64 index) const { + ASSERT_MSG(level <= 2 && selector <= 1, "Level or selector invalid"); + return reinterpret_cast( + data.data())[descriptor.levels[level].offset + selector * descriptor.levels[level].size + + index]; +} + +std::vector DPFSContainer::GetLevel3Data() const { + std::vector level3_data(descriptor.levels[2].size); + for (std::size_t i = 0; i < level3_data.size(); i++) { + auto level2_bit_index = i / std::pow(2, descriptor.levels[1].block_size); + auto level1_bit_index = + (level2_bit_index / 8) / std::pow(2, descriptor.levels[0].block_size); + auto level2_selector = GetBit(0, level1_selector, level1_bit_index); + auto level3_selector = GetBit(1, level2_selector, level2_bit_index); + level3_data[i] = GetByte(2, level3_selector, i); + } + return level3_data; +} + +DataContainer::DataContainer(std::vector data_) : data(std::move(data_)) { + ASSERT_MSG(data.size() >= 0x200, "Data size is too small"); + + u32_le magic; + std::memcpy(&magic, data.data() + 0x100, sizeof(u32_le)); + if (magic == MakeMagic('D', 'I', 'S', 'A')) { + InitAsDISA(); + } else if (magic == MakeMagic('D', 'I', 'F', 'F')) { + InitAsDIFF(); + } else { + // TODO: Add error handling + UNREACHABLE_MSG("Unknown magic"); + } +} + +DataContainer::~DataContainer() = default; + +void DataContainer::InitAsDISA() { + DISAHeader header; + std::memcpy(&header, data.data() + 0x100, sizeof(header)); + + ASSERT_MSG(header.version == 0x40000, "DISA Version is not correct"); + + if (header.active_partition_table == 0) { // primary + partition_table_offset = header.primary_partition_table_offset; + } else { + partition_table_offset = header.secondary_partition_table_offset; + } + + partition_count = header.partition_count; + + if (header.partition_count == 2) { + partition_descriptors = {header.partition_descriptors[0], header.partition_descriptors[1]}; + partitions = {header.partitions[0], header.partitions[1]}; + } else { + partition_descriptors = {header.partition_descriptors[0]}; + partitions = {header.partitions[0]}; + } +} + +void DataContainer::InitAsDIFF() { + DIFFHeader header; + std::memcpy(&header, data.data() + 0x100, sizeof(header)); + + ASSERT_MSG(header.version == 0x30000, "DIFF Version is not correct"); + + if (header.active_partition_table == 0) { // primary + partition_table_offset = header.primary_partition_table_offset; + } else { + partition_table_offset = header.secondary_partition_table_offset; + } + + partition_count = 1; + partition_descriptors = {{/* offset */ 0, /* size */ header.partition_table_size}}; + partitions = {header.partition_A}; +} + +std::vector DataContainer::GetPartitionData(u8 index) const { + auto partition_descriptor_offset = partition_table_offset + partition_descriptors[index].offset; + + DIFIHeader difi; + std::memcpy(&difi, data.data() + partition_descriptor_offset, sizeof(difi)); + ASSERT_MSG(difi.magic == MakeMagic('D', 'I', 'F', 'I'), "DIFI Magic is not correct"); + ASSERT_MSG(difi.version == 0x10000, "DIFI Version is not correct"); + + ASSERT_MSG(difi.ivfc.size >= sizeof(IVFCDescriptor), "IVFC descriptor size is too small"); + IVFCDescriptor ivfc_descriptor; + std::memcpy(&ivfc_descriptor, data.data() + partition_descriptor_offset + difi.ivfc.offset, + sizeof(ivfc_descriptor)); + + if (difi.enable_external_IVFC_level_4) { + std::vector result( + data.data() + partitions[index].offset + difi.external_IVFC_level_4_offset, + data.data() + partitions[index].offset + difi.external_IVFC_level_4_offset + + ivfc_descriptor.levels[3].size); + return result; + } + + // Unwrap DPFS Tree + ASSERT_MSG(difi.dpfs.size >= sizeof(DPFSDescriptor), "DPFS descriptor size is too small"); + DPFSDescriptor dpfs_descriptor; + std::memcpy(&dpfs_descriptor, data.data() + partition_descriptor_offset + difi.dpfs.offset, + sizeof(dpfs_descriptor)); + + std::vector partition_data(partitions[index].size / 4); + std::memcpy(partition_data.data(), data.data() + partitions[index].offset, + partitions[index].size); + + DPFSContainer dpfs_container(dpfs_descriptor, difi.dpfs_level1_selector, partition_data); + auto ivfc_data = dpfs_container.GetLevel3Data(); + + std::vector result(ivfc_data.data() + ivfc_descriptor.levels[3].offset, + ivfc_data.data() + ivfc_descriptor.levels[3].offset + + ivfc_descriptor.levels[3].size); + return result; +} + +std::vector> DataContainer::GetIVFCLevel4Data() const { + if (partition_count == 1) { + return {GetPartitionData(0)}; + } else { + return {GetPartitionData(0), GetPartitionData(1)}; + } +} diff --git a/src/core/data_container.h b/src/core/data_container.h new file mode 100644 index 0000000..9cf36cd --- /dev/null +++ b/src/core/data_container.h @@ -0,0 +1,129 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +#pragma pack(push, 1) +struct DataDescriptor { + u64_le offset; + u64_le size; +}; + +struct DISAHeader { + u32_le magic; + u32_le version; + u32_le partition_count; + INSERT_PADDING_BYTES(4); + u64_le secondary_partition_table_offset; + u64_le primary_partition_table_offset; + u64_le partition_table_size; + std::array partition_descriptors; + std::array partitions; + u8 active_partition_table; + INSERT_PADDING_BYTES(3); + std::array sha_hash; + INSERT_PADDING_BYTES(0x74); +}; +static_assert(sizeof(DISAHeader) == 0x100, "Size of DISA header is incorrect"); + +struct DIFFHeader { + u32_le magic; + u32_le version; + u64_le secondary_partition_table_offset; + u64_le primary_partition_table_offset; + u64_le partition_table_size; + DataDescriptor partition_A; + u8 active_partition_table; + INSERT_PADDING_BYTES(3); + std::array sha_hash; + u64_le unique_identifier; + INSERT_PADDING_BYTES(0xA4); +}; +static_assert(sizeof(DIFFHeader) == 0x100, "Size of DIFF header is incorrect"); + +struct DIFIHeader { + u32_le magic; + u32_le version; + DataDescriptor ivfc; + DataDescriptor dpfs; + DataDescriptor partition_hash; + u8 enable_external_IVFC_level_4; + u8 dpfs_level1_selector; + INSERT_PADDING_BYTES(2); + u64_le external_IVFC_level_4_offset; +}; +static_assert(sizeof(DIFIHeader) == 0x44, "Size of DIFI header is incorrect"); + +/// Descriptor for both IVFC and DPFS levels +struct LevelDescriptor { + u64_le offset; + u64_le size; + u32_le block_size; // In log2 + INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(LevelDescriptor) == 0x18, "Size of level descriptor is incorrect"); + +struct IVFCDescriptor { + u32_le magic; + u32_le version; + u64_le master_hash_size; + std::array levels; + u64_le descriptor_size; +}; +static_assert(sizeof(IVFCDescriptor) == 0x78, "Size of IVFC descriptor is incorrect"); + +struct DPFSDescriptor { + u32_le magic; + u32_le version; + std::array levels; +}; +static_assert(sizeof(DPFSDescriptor) == 0x50, "Size of DPFS descriptor is incorrect"); +#pragma pack(pop) + +class DPFSContainer { +public: + explicit DPFSContainer(DPFSDescriptor descriptor, u8 level1_selector, std::vector data); + + /// Unwraps the DPFS Tree, returning actual data in Level3. + std::vector GetLevel3Data() const; + +private: + u8 GetBit(u8 level, u8 selector, u64 index) const; + u8 GetByte(u8 level, u8 selector, u64 index) const; + + DPFSDescriptor descriptor; + u8 level1_selector; + std::vector data; +}; + +/** + * DISA/DIFF Container. + */ +class DataContainer { +public: + explicit DataContainer(std::vector data); + ~DataContainer(); + + /// Unwraps the whole container, returning the data in IVFC Level 4 of all partitions. + std::vector> GetIVFCLevel4Data() const; + +private: + void InitAsDISA(); + void InitAsDIFF(); + + /// Unwraps the whole container, returning the data in IVFC Level 4 of a partition. + std::vector GetPartitionData(u8 index) const; + + std::vector data; + u32 partition_count; + u64_le partition_table_offset; + std::vector partition_descriptors; + std::vector partitions; +}; diff --git a/src/core/decryptor.cpp b/src/core/decryptor.cpp new file mode 100644 index 0000000..9521cbe --- /dev/null +++ b/src/core/decryptor.cpp @@ -0,0 +1,91 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include "common/assert.h" +#include "common/file_util.h" +#include "common/string_util.h" +#include "core/decryptor.h" +#include "core/key/key.h" + +SDMCDecryptor::SDMCDecryptor(const std::string& root_folder_) : root_folder(root_folder_) { + ASSERT_MSG(Key::IsNormalKeyAvailable(Key::SDKey), + "SD Key must be available in order to decrypt"); + + if (root_folder.back() == '/' || root_folder.back() == '\\') { + // Remove '/' or '\' character at the end as we will add them back when combining path + root_folder.erase(root_folder.size() - 1); + } +} + +SDMCDecryptor::~SDMCDecryptor() = default; + +namespace { +std::array GetFileCTR(const std::string& path) { + auto path_utf16 = Common::UTF8ToUTF16(path); + std::vector path_data(path_utf16.size() * 2 + 2, 0); // Add the '\0' character + std::memcpy(path_data.data(), path_utf16.data(), path_utf16.size() * 2); + + CryptoPP::SHA256 sha; + std::array hash; + sha.CalculateDigest(hash.data(), path_data.data(), path_data.size()); + + std::array ctr; + for (int i = 0; i < 16; i++) { + ctr[i] = hash[i] ^ hash[16 + i]; + } + return ctr; +} +} // namespace + +bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source, + const std::string& destination) const { + auto ctr = GetFileCTR(source); + auto key = Key::GetNormalKey(Key::SDKey); + CryptoPP::CTR_Mode::Decryption aes; + aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); + + std::string absolute_source = root_folder + source; + try { + CryptoPP::FileSource(absolute_source.c_str(), true, + new CryptoPP::StreamTransformationFilter( + aes, new CryptoPP::FileSink(destination.c_str(), true)), + true); + } catch (CryptoPP::Exception& e) { + LOG_ERROR(Frontend, "Error decrypting and writing file: {}", e.what()); + return false; + } + return true; +} + +std::vector SDMCDecryptor::DecryptFile(const std::string& source) const { + auto ctr = GetFileCTR(source); + auto key = Key::GetNormalKey(Key::SDKey); + CryptoPP::CTR_Mode::Decryption aes; + aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); + + FileUtil::IOFile file(root_folder + source, "rb"); + if (!file) { + LOG_ERROR(Frontend, "Could not open {}", root_folder + source); + return {}; + } + + auto size = file.GetSize(); + + std::vector encrypted_data(size); + if (file.ReadBytes(encrypted_data.data(), size) != size) { + LOG_ERROR(Frontend, "Could not read file {}", root_folder + source); + return {}; + } + + std::vector data(size); + aes.ProcessData(data.data(), encrypted_data.data(), encrypted_data.size()); + return data; +} diff --git a/src/core/decryptor.h b/src/core/decryptor.h new file mode 100644 index 0000000..c4d4540 --- /dev/null +++ b/src/core/decryptor.h @@ -0,0 +1,37 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" + +class SDMCDecryptor { +public: + /** + * Initializes the decryptor. + * @param root_folder Path to the "Nintendo 3DS//" folder. + */ + explicit SDMCDecryptor(const std::string& root_folder); + + ~SDMCDecryptor(); + + /** + * Decrypts a file from the SD card and writes it into another file. + * @param source Path to the file relative to the root folder, starting with "/". + * @param destination Path to the destination file. + * @return true on success, false otherwise + */ + bool DecryptAndWriteFile(const std::string& source, const std::string& destination) const; + + /** + * Decrypts a file and reads it into a vector. + * @param source Path to the file relative to the root folder, starting with "/". + */ + std::vector DecryptFile(const std::string& source) const; + +private: + std::string root_folder; +}; diff --git a/src/core/importer.cpp b/src/core/importer.cpp new file mode 100644 index 0000000..585103e --- /dev/null +++ b/src/core/importer.cpp @@ -0,0 +1,13 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/importer.h" +#include "core/key/key.h" + +SDMCImporter::SDMCImporter(const Config& config_) : config(config_) { + Key::LoadBootromKeys(config.bootrom_path); + Key::LoadMovableSedKeys(config.movable_sed_path); +} + +SDMCImporter::~SDMCImporter() = default; diff --git a/src/core/importer.h b/src/core/importer.h new file mode 100644 index 0000000..7752a2b --- /dev/null +++ b/src/core/importer.h @@ -0,0 +1,74 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" + +/** + * Type of an importable content. + * Applications, updates and DLCs are all considered titles. + */ +enum class ContentType { + Application, + Update, + DLC, + Savegame, + Extdata, +}; + +/** + * Struct that specifies an importable content. + */ +struct ContentSpecifier { + ContentType type; + u64 id; +}; + +/** + * A set of values that are used to initialize the importer. + */ +struct Config { + std::string sdmc_path; ///< SDMC root path ("Nintendo 3DS//") + + // Necessary system files keys are loaded from. + std::string movable_sed_path; ///< Path to movable.sed + std::string bootrom_path; ///< Path to bootrom (boot9.bin) + + // The following system files are optional for importing and are only copied so that Citra + // will be able to decrypt imported encrypted ROMs. + std::string safe_mode_firm_path; ///< Path to safe mode firm + std::string secret_sector_path; ///< Path to secret sector (New3DS only) +}; + +class SDMCImporter { +public: + /** + * Initializes the importer. + * @param root_folder Path to the "Nintendo 3DS//" folder. + */ + explicit SDMCImporter(const Config& config); + + ~SDMCImporter(); + + /** + * Dumps a specific content by its specifier. + * @return true on success, false otherwise + */ + bool ImportContent(const ContentSpecifier& specifier); + + /** + * Gets a list of dumpable content specifiers. + */ + std::vector ListContent() const; + +private: + bool ImportTitle(u64 id); + bool ImportSavegame(u64 id); + bool ImportExtdata(u64 id); + + Config config; +}; diff --git a/src/core/inner_fat.cpp b/src/core/inner_fat.cpp new file mode 100644 index 0000000..fdde9d6 --- /dev/null +++ b/src/core/inner_fat.cpp @@ -0,0 +1,336 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/assert.h" +#include "common/file_util.h" +#include "core/data_container.h" +#include "core/decryptor.h" +#include "core/inner_fat.h" + +constexpr u32 MakeMagic(char a, char b, char c, char d) { + return a | b << 8 | c << 16 | d << 24; +} + +InnerFAT::~InnerFAT() = default; + +bool InnerFAT::IsGood() const { + return is_good; +} + +bool InnerFAT::ExtractDirectory(const std::string& path, std::size_t index) const { + auto entry = directory_entry_table[index]; + + std::array name_data = {}; // Append a null terminator + std::memcpy(name_data.data(), entry.name.data(), entry.name.size()); + + std::string name = name_data.data(); + std::string new_path = name.empty() ? path : path + name + "/"; // Name is empty for root + + if (!FileUtil::CreateFullPath(new_path)) { + LOG_ERROR(Frontend, "Could not create path {}", new_path); + return false; + } + + // Files + u32 cur = entry.first_file_index; + while (cur != 0) { + if (!ExtractFile(new_path, cur)) + return false; + cur = file_entry_table[cur].next_sibling_index; + } + + // Subdirectories + cur = entry.first_subdirectory_index; + while (cur != 0) { + if (!ExtractDirectory(new_path, cur)) + return false; + cur = directory_entry_table[cur].next_sibling_index; + } + + return true; +} + +bool InnerFAT::WriteMetadata(const std::string& path) const { + if (!FileUtil::CreateFullPath(path)) { + LOG_ERROR(Frontend, "Could not create path {}", path); + return false; + } + + auto format_info = GetFormatInfo(); + + FileUtil::IOFile file(path, "wb"); + if (!file.IsOpen()) { + LOG_ERROR(Frontend, "Could not open file {}", path); + return false; + } + if (file.WriteBytes(&format_info, sizeof(format_info)) != sizeof(format_info)) { + LOG_ERROR(Frontend, "Write data failed (file: {})", path); + return false; + } + return true; +} + +SDSavegame::SDSavegame(std::vector data_) : duplicate_data(true), data(std::move(data_)) {} + +SDSavegame::SDSavegame(std::vector partitionA_, std::vector partitionB_) + : duplicate_data(false), partitionA(std::move(partitionA_)), + partitionB(std::move(partitionB_)) { + is_good = Init(); +} + +SDSavegame::~SDSavegame() = default; + +bool SDSavegame::Init() { + auto header_iter = duplicate_data ? data.data() : partitionA.data(); + + // Read header + std::memcpy(&header, header_iter, sizeof(header)); + if (header.magic != MakeMagic('S', 'A', 'V', 'E') || header.version != 0x40000) { + LOG_ERROR(Frontend, "File is invalid, decryption errors may have happened."); + return false; + } + + // Read filesystem information + std::memcpy(&fs_info, header_iter + header.filesystem_information_offset, sizeof(fs_info)); + + // Read data region + if (duplicate_data) { + data_region.resize(fs_info.data_region_block_count * fs_info.data_region_block_size); + std::memcpy(data_region.data(), data.data() + fs_info.data_region_offset, + data_region.size()); + } else { + data_region = std::move(partitionB); + } + + // Directory & file entry tables are allocated in the data region as if they were normal + // files. However, only continuous allocation has been observed so far according to 3DBrew, + // so it should be safe to directly read the bytes. + + // Read directory entry table + auto directory_entry_table_iter = + header_iter + (duplicate_data ? fs_info.data_region_offset + + fs_info.directory_entry_table.duplicate.block_index * + fs_info.data_region_block_size + : fs_info.directory_entry_table.non_duplicate); + + directory_entry_table.resize(fs_info.maximum_directory_count + 2); // including head and root + std::memcpy(directory_entry_table.data(), directory_entry_table_iter, + directory_entry_table.size() * sizeof(DirectoryEntryTableEntry)); + + // Read file entry table + auto file_entry_table_iter = + header_iter + (duplicate_data ? fs_info.data_region_offset + + fs_info.file_entry_table.duplicate.block_index * + fs_info.data_region_block_size + : fs_info.file_entry_table.non_duplicate); + + file_entry_table.resize(fs_info.maximum_file_count + 1); // including head + std::memcpy(file_entry_table.data(), file_entry_table_iter, + file_entry_table.size() * sizeof(FileEntryTableEntry)); + + // Read file allocation table + fat.resize(fs_info.file_allocation_table_entry_count); + std::memcpy(fat.data(), header_iter + fs_info.file_allocation_table_offset, + fat.size() * sizeof(FATNode)); + + return true; +} + +bool SDSavegame::ExtractFile(const std::string& path, std::size_t index) const { + if (!FileUtil::CreateFullPath(path)) { + LOG_ERROR(Frontend, "Could not create path {}", path); + return false; + } + + auto entry = file_entry_table[index]; + + std::array name_data = {}; // Append a null terminator + std::memcpy(name_data.data(), entry.name.data(), entry.name.size()); + + std::string name{name_data.data()}; + FileUtil::IOFile file(path + name, "wb"); + if (!file.IsOpen()) { + LOG_ERROR(Frontend, "Could not open file {}", path + name); + return false; + } + + u32 block = entry.data_block_index; + if (block == 0x80000000) { // empty file + return true; + } + + while (true) { + // Entry index is block index + 1 + auto block_data = fat[block + 1]; + + u32 last_block = block; + if (block_data.v.flag) { // This node has multiple entries + last_block = fat[block + 2].v.index - 1; + } + + std::size_t size = fs_info.data_region_block_size * (last_block - block + 1); + if (file.WriteBytes(data_region.data() + fs_info.data_region_block_size * block, size) != + size) { + LOG_ERROR(Frontend, "Write data failed (file: {})", path + name); + return false; + } + + if (block_data.v.index == 0) // last node + break; + + block = block_data.v.index - 1; + } + + return true; +} + +bool SDSavegame::Extract(std::string path) const { + if (path.back() != '/' && path.back() != '\\') { + path += '/'; + } + + // All saves on a physical 3DS are called 00000001.sav + if (!ExtractDirectory(path + "00000001/", 1)) { // Directory 1 = root + return false; + } + + if (!WriteMetadata(path + "00000001.metadata")) { + return false; + } + + return true; +} + +ArchiveFormatInfo SDSavegame::GetFormatInfo() const { + // Tests on a physical 3DS shows that the `total_size` field seems to always be 0 + // when requested with the UserSaveData archive, and 134328448 when requested with + // the SaveData archive. More investigation is required to tell whether this is a fixed value. + ArchiveFormatInfo format_info = {/* total_size */ 134328448, + /* number_directories */ fs_info.maximum_directory_count, + /* number_files */ fs_info.maximum_file_count, + /* duplicate_data */ duplicate_data}; + + return format_info; +} + +SDExtdata::SDExtdata(std::string data_path_, const SDMCDecryptor& decryptor_) + : data_path(std::move(data_path_)), decryptor(decryptor_) { + + if (data_path.back() != '/' && data_path.back() != '\\') { + data_path += '/'; + } + + is_good = Init(); +} + +SDExtdata::~SDExtdata() = default; + +bool SDExtdata::Init() { + // Read VSXE file + auto vsxe_raw = decryptor.DecryptFile(data_path + "00000000/00000001"); + if (vsxe_raw.empty()) { + LOG_ERROR(Frontend, "Failed to load or decrypt VSXE"); + return false; + } + DataContainer vsxe_container(vsxe_raw); + auto vsxe = vsxe_container.GetIVFCLevel4Data()[0]; + + // Read header + std::memcpy(&header, vsxe.data(), sizeof(header)); + if (header.magic != MakeMagic('V', 'S', 'X', 'E') || header.version != 0x30000) { + LOG_ERROR(Frontend, "File is invalid, decryption errors may have happened."); + return false; + } + + // Read filesystem information + std::memcpy(&fs_info, vsxe.data() + header.filesystem_information_offset, sizeof(fs_info)); + + // Read data region + data_region.resize(fs_info.data_region_block_count * fs_info.data_region_block_size); + std::memcpy(data_region.data(), vsxe.data() + fs_info.data_region_offset, data_region.size()); + + // Read directory entry table + directory_entry_table.resize(fs_info.maximum_directory_count + 2); // including head and root + std::memcpy(directory_entry_table.data(), + vsxe.data() + fs_info.data_region_offset + + fs_info.directory_entry_table.duplicate.block_index * + fs_info.data_region_block_size, + directory_entry_table.size() * sizeof(DirectoryEntryTableEntry)); + + // Read file entry table + file_entry_table.resize(fs_info.maximum_file_count + 1); // including head + std::memcpy(file_entry_table.data(), + vsxe.data() + fs_info.data_region_offset + + fs_info.file_entry_table.duplicate.block_index * fs_info.data_region_block_size, + file_entry_table.size() * sizeof(FileEntryTableEntry)); + + // File allocation table isn't needed here, as the only files allocated by them are + // directory/file entry tables which we already read above. + return true; +} + +bool SDExtdata::Extract(std::string path) const { + if (path.back() != '/' && path.back() != '\\') { + path += '/'; + } + + if (!ExtractDirectory(path, 1)) { + return false; + } + + if (!WriteMetadata(path + "metadata")) { + return false; + } + + return true; +} + +bool SDExtdata::ExtractFile(const std::string& path, std::size_t index) const { + /// Maximum amount of device files a device directory can hold. + constexpr u32 DeviceDirCapacity = 126; + + auto entry = file_entry_table[index]; + + std::array name_data = {}; // Append a null terminator + std::memcpy(name_data.data(), entry.name.data(), entry.name.size()); + + std::string name{name_data.data()}; + FileUtil::IOFile file(path + name, "wb"); + if (!file) { + LOG_ERROR(Frontend, "Could not open file {}", path + name); + return false; + } + + u32 file_index = index + 1; + u32 sub_directory_id = file_index / DeviceDirCapacity; + u32 sub_file_id = file_index % DeviceDirCapacity; + std::string device_file_path = + fmt::format("{}{:08x}/{:08x}", data_path, sub_directory_id, sub_file_id); + + auto container_data = decryptor.DecryptFile(device_file_path); + if (container_data.empty()) { // File does not exist? + LOG_WARNING(Frontend, "Ignoring file {}", device_file_path); + return true; + } + + DataContainer container(container_data); + auto data = container.GetIVFCLevel4Data()[0]; + if (file.WriteBytes(data.data(), data.size()) != data.size()) { + LOG_ERROR(Frontend, "Write data failed (file: {})", path + name); + return false; + } + + return true; +} + +ArchiveFormatInfo SDExtdata::GetFormatInfo() const { + // This information is based on how Citra created the metadata in FS + ArchiveFormatInfo format_info = {/* total_size */ 0, + /* number_directories */ fs_info.maximum_directory_count, + /* number_files */ fs_info.maximum_file_count, + /* duplicate_data */ false}; + + return format_info; +} diff --git a/src/core/inner_fat.h b/src/core/inner_fat.h new file mode 100644 index 0000000..7fc5d1c --- /dev/null +++ b/src/core/inner_fat.h @@ -0,0 +1,196 @@ +// Copyright 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" + +class SDMCDecryptor; + +/// Parameters of the archive, as specified in the Create or Format call. +struct ArchiveFormatInfo { + u32_le total_size; ///< The pre-defined size of the archive. + u32_le number_directories; ///< The pre-defined number of directories in the archive. + u32_le number_files; ///< The pre-defined number of files in the archive. + u8 duplicate_data; ///< Whether the archive should duplicate the data. +}; +static_assert(std::is_pod::value, "ArchiveFormatInfo is not POD"); + +union TableOffset { + // This has different meanings for different savegame layouts + struct { // duplicate data = true + u32_le block_index; + u32_le block_count; + } duplicate; + + u64_le non_duplicate; // duplicate data = false +}; + +struct FATHeader { + u32_le magic; + u32_le version; + u64_le filesystem_information_offset; + u64_le image_size; + u32_le image_block_size; + INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(FATHeader) == 0x20, "FATHeader has incorrect size"); + +struct FileSystemInformation { + INSERT_PADDING_BYTES(4); + u32_le data_region_block_size; + u64_le directory_hash_table_offset; + u32_le directory_hash_table_bucket_count; + INSERT_PADDING_BYTES(4); + u64_le file_hash_table_offset; + u32_le file_hash_table_bucket_count; + INSERT_PADDING_BYTES(4); + u64_le file_allocation_table_offset; + u32_le file_allocation_table_entry_count; + INSERT_PADDING_BYTES(4); + u64_le data_region_offset; + u32_le data_region_block_count; + INSERT_PADDING_BYTES(4); + TableOffset directory_entry_table; + u32_le maximum_directory_count; + INSERT_PADDING_BYTES(4); + TableOffset file_entry_table; + u32_le maximum_file_count; + INSERT_PADDING_BYTES(4); +}; +static_assert(sizeof(FileSystemInformation) == 0x68, "FileSystemInformation has incorrect size"); + +struct DirectoryEntryTableEntry { + u32_le parent_directory_index; + std::array name; + u32_le next_sibling_index; + u32_le first_subdirectory_index; + u32_le first_file_index; + INSERT_PADDING_BYTES(4); + u32_le next_hash_bucket_entry; +}; +static_assert(sizeof(DirectoryEntryTableEntry) == 0x28, + "DirectoryEntryTableEntry has incorrect size"); + +struct FileEntryTableEntry { + u32_le parent_directory_index; + std::array name; + u32_le next_sibling_index; + INSERT_PADDING_BYTES(4); + u32_le data_block_index; + u64_le file_size; + INSERT_PADDING_BYTES(4); + u32_le next_hash_bucket_entry; +}; +static_assert(sizeof(FileEntryTableEntry) == 0x30, "FileEntryTableEntry has incorrect size"); + +struct FATNode { + union { + BitField<0, 31, u32> index; + BitField<31, 1, u32> flag; + + u32_le raw; + } u, v; +}; + +/** + * Virtual interface for the inner FAT filesystem of SD Savegames/Extdata/TitleDB. + */ +class InnerFAT { +public: + virtual ~InnerFAT(); + + /** + * Returns whether the filesystem is in "good" state, i.e. successfully initialized. + */ + bool IsGood() const; + + /** + * Completely extracts everything from this filesystem, including files, directories + * and metadata used by Citra. + * @return true on success, false otherwise + */ + virtual bool Extract(std::string path) const = 0; + +protected: + /** + * Gets the ArchiveFormatInfo of this archive, used for writing the archive metadata. + */ + virtual ArchiveFormatInfo GetFormatInfo() const = 0; + + /** + * Extracts the index-th file in the file entry table to a certain path. (The path does not + * contain the file name). + * @return true on success, false otherwise + */ + virtual bool ExtractFile(const std::string& path, std::size_t index) const = 0; + + /** + * Recursively extracts the index-th directory in the directory entry table. + * @return true on success, false otherwise + */ + bool ExtractDirectory(const std::string& path, std::size_t index) const; + + /** + * Writes the corresponding archive metadata to a certain path. + * @return true on success, false otherwise + */ + bool WriteMetadata(const std::string& path) const; + + bool is_good = false; + FATHeader header; + FileSystemInformation fs_info; + std::vector directory_entry_table; + std::vector file_entry_table; + std::vector data_region; +}; + +class SDSavegame : public InnerFAT { +public: + explicit SDSavegame(std::vector data); + explicit SDSavegame(std::vector partitionA, std::vector partitionB); + ~SDSavegame() override; + + bool Extract(std::string path) const override; + +private: + bool Init(); + bool ExtractFile(const std::string& path, std::size_t index) const override; + ArchiveFormatInfo GetFormatInfo() const override; + + std::vector fat; + bool duplicate_data; // Layout variant + + // Temporary storage for construction data + std::vector data; + std::vector partitionA; + std::vector partitionB; +}; + +class SDExtdata : public InnerFAT { +public: + /** + * Loads an SD extdata folder. + * @param data_path Path to the ENCRYPTED SD extdata folder, relative to decryptor root + * @param decryptor Const reference to the SDMCDecryptor. + */ + explicit SDExtdata(std::string data_path, const SDMCDecryptor& decryptor); + ~SDExtdata() override; + + bool Extract(std::string path) const override; + +private: + bool Init(); + bool ExtractFile(const std::string& path, std::size_t index) const override; + ArchiveFormatInfo GetFormatInfo() const override; + + std::string data_path; + const SDMCDecryptor& decryptor; +}; diff --git a/src/core/key/arithmetic128.cpp b/src/core/key/arithmetic128.cpp new file mode 100644 index 0000000..9bd6104 --- /dev/null +++ b/src/core/key/arithmetic128.cpp @@ -0,0 +1,59 @@ +// Copyright 2017 Citra Emulator Project / 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "core/key/arithmetic128.h" + +namespace Key { + +AESKey Lrot128(const AESKey& in, u32 rot) { + AESKey out; + rot %= 128; + const u32 byte_shift = rot / 8; + const u32 bit_shift = rot % 8; + + for (u32 i = 0; i < 16; i++) { + const u32 wrap_index_a = (i + byte_shift) % 16; + const u32 wrap_index_b = (i + byte_shift + 1) % 16; + out[i] = ((in[wrap_index_a] << bit_shift) | (in[wrap_index_b] >> (8 - bit_shift))) & 0xFF; + } + return out; +} + +AESKey Add128(const AESKey& a, const AESKey& b) { + AESKey out; + u32 carry = 0; + u32 sum = 0; + + for (int i = 15; i >= 0; i--) { + sum = a[i] + b[i] + carry; + carry = sum >> 8; + out[i] = static_cast(sum & 0xff); + } + + return out; +} + +AESKey Add128(const AESKey& a, u64 b) { + AESKey out = a; + u32 carry = 0; + u32 sum = 0; + + for (int i = 15; i >= 8; i--) { + sum = a[i] + static_cast((b >> ((15 - i) * 8)) & 0xff) + carry; + carry = sum >> 8; + out[i] = static_cast(sum & 0xff); + } + + return out; +} + +AESKey Xor128(const AESKey& a, const AESKey& b) { + AESKey out; + std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>()); + return out; +} + +} // namespace Key diff --git a/src/core/key/arithmetic128.h b/src/core/key/arithmetic128.h new file mode 100644 index 0000000..3d3f2ee --- /dev/null +++ b/src/core/key/arithmetic128.h @@ -0,0 +1,17 @@ +// Copyright 2017 Citra Emulator Project / 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/key/key.h" + +namespace Key { + +AESKey Lrot128(const AESKey& in, u32 rot); +AESKey Add128(const AESKey& a, const AESKey& b); +AESKey Add128(const AESKey& a, u64 b); +AESKey Xor128(const AESKey& a, const AESKey& b); + +} // namespace Key diff --git a/src/core/key/key.cpp b/src/core/key/key.cpp new file mode 100644 index 0000000..c5b9613 --- /dev/null +++ b/src/core/key/key.cpp @@ -0,0 +1,213 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "common/file_util.h" +#include "common/logging/log.h" +#include "core/key/arithmetic128.h" +#include "core/key/key.h" + +namespace Key { + +namespace { + +// The generator constant was calculated using the 0x39 KeyX and KeyY retrieved from a 3DS and the +// normal key dumped from a Wii U solving the equation: +// NormalKey = (((KeyX ROL 2) XOR KeyY) + constant) ROL 87 +// On a real 3DS the generation for the normal key is hardware based, and thus the constant can't +// get dumped . generated normal keys are also not accesible on a 3DS. The used formula for +// calculating the constant is a software implementation of what the hardware generator does. +constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, + 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}}; + +struct KeyDesc { + char key_type; + std::size_t slot_id; + // This key is identical to the key with the same key_type and slot_id -1 + bool same_as_before; +}; + +AESKey HexToKey(const std::string& hex) { + if (hex.size() < 32) { + throw std::invalid_argument("hex string is too short"); + } + + AESKey key; + for (std::size_t i = 0; i < key.size(); ++i) { + key[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return key; +} + +struct KeySlot { + std::optional x; + std::optional y; + std::optional normal; + + void SetKeyX(std::optional key) { + x = key; + GenerateNormalKey(); + } + + void SetKeyY(std::optional key) { + y = key; + GenerateNormalKey(); + } + + void SetNormalKey(std::optional key) { + normal = key; + } + + void GenerateNormalKey() { + if (x && y) { + normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), generator_constant), 87); + } else { + normal = {}; + } + } + + void Clear() { + x.reset(); + y.reset(); + normal.reset(); + } +}; + +std::array key_slots; +std::array, 6> common_key_y_slots; + +std::string KeyToString(AESKey& key) { + std::string s; + for (auto pos : key) { + s += fmt::format("{:02X}", pos); + } + return s; +} +} // namespace + +void LoadBootromKeys(const std::string& path) { + constexpr std::array keys = { + {{'X', 0x2C, false}, {'X', 0x2D, true}, {'X', 0x2E, true}, {'X', 0x2F, true}, + {'X', 0x30, false}, {'X', 0x31, true}, {'X', 0x32, true}, {'X', 0x33, true}, + {'X', 0x34, false}, {'X', 0x35, true}, {'X', 0x36, true}, {'X', 0x37, true}, + {'X', 0x38, false}, {'X', 0x39, true}, {'X', 0x3A, true}, {'X', 0x3B, true}, + {'X', 0x3C, false}, {'X', 0x3D, false}, {'X', 0x3E, false}, {'X', 0x3F, false}, + {'Y', 0x4, false}, {'Y', 0x5, false}, {'Y', 0x6, false}, {'Y', 0x7, false}, + {'Y', 0x8, false}, {'Y', 0x9, false}, {'Y', 0xA, false}, {'Y', 0xB, false}, + {'N', 0xC, false}, {'N', 0xD, true}, {'N', 0xE, true}, {'N', 0xF, true}, + {'N', 0x10, false}, {'N', 0x11, true}, {'N', 0x12, true}, {'N', 0x13, true}, + {'N', 0x14, false}, {'N', 0x15, false}, {'N', 0x16, false}, {'N', 0x17, false}, + {'N', 0x18, false}, {'N', 0x19, true}, {'N', 0x1A, true}, {'N', 0x1B, true}, + {'N', 0x1C, false}, {'N', 0x1D, true}, {'N', 0x1E, true}, {'N', 0x1F, true}, + {'N', 0x20, false}, {'N', 0x21, true}, {'N', 0x22, true}, {'N', 0x23, true}, + {'N', 0x24, false}, {'N', 0x25, true}, {'N', 0x26, true}, {'N', 0x27, true}, + {'N', 0x28, true}, {'N', 0x29, false}, {'N', 0x2A, false}, {'N', 0x2B, false}, + {'N', 0x2C, false}, {'N', 0x2D, true}, {'N', 0x2E, true}, {'N', 0x2F, true}, + {'N', 0x30, false}, {'N', 0x31, true}, {'N', 0x32, true}, {'N', 0x33, true}, + {'N', 0x34, false}, {'N', 0x35, true}, {'N', 0x36, true}, {'N', 0x37, true}, + {'N', 0x38, false}, {'N', 0x39, true}, {'N', 0x3A, true}, {'N', 0x3B, true}, + {'N', 0x3C, true}, {'N', 0x3D, false}, {'N', 0x3E, false}, {'N', 0x3F, false}}}; + + // Bootrom sets all these keys when executed, but later some of the normal keys get overwritten + // by other applications e.g. process9. These normal keys thus aren't used by any application + // and have no value for emulation + + auto file = FileUtil::IOFile(path, "rb"); + if (!file) { + return; + } + + const std::size_t length = file.GetSize(); + if (length != 65536) { + LOG_ERROR(Key, "Bootrom9 size is wrong: {}", length); + return; + } + + constexpr std::size_t KEY_SECTION_START = 55760; + file.Seek(KEY_SECTION_START, SEEK_SET); // Jump to the key section + + AESKey new_key; + for (const auto& key : keys) { + if (!key.same_as_before) { + file.ReadArray(new_key.data(), new_key.size()); + if (!file) { + LOG_ERROR(Key, "Reading from Bootrom9 failed"); + return; + } + } + + LOG_DEBUG(Key, "Loaded Slot{:#02x} Key{}: {}", key.slot_id, key.key_type, + KeyToString(new_key)); + + switch (key.key_type) { + case 'X': + key_slots.at(key.slot_id).SetKeyX(new_key); + break; + case 'Y': + key_slots.at(key.slot_id).SetKeyY(new_key); + break; + case 'N': + key_slots.at(key.slot_id).SetNormalKey(new_key); + break; + default: + LOG_ERROR(Key, "Invalid key type {}", key.key_type); + break; + } + } +} + +void LoadMovableSedKeys(const std::string& path) { + auto file = FileUtil::IOFile(path, "rb"); + if (!file) { + return; + } + + const std::size_t length = file.GetSize(); + if (length < 0x120) { + LOG_ERROR(Key, "movable.sed size is too small: {}", length); + return; + } + + constexpr std::size_t KEY_SECTION_START = 0x118; + file.Seek(KEY_SECTION_START, SEEK_SET); // Jump to the key section + + AESKey key; + file.ReadArray(key.data(), key.size()); + if (!file) { + LOG_ERROR(Key, "Reading from movable.sed failed"); + return; + } + + SetKeyY(0x26, key); +} + +void SetKeyX(std::size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyX(key); +} + +void SetKeyY(std::size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyY(key); +} + +void SetNormalKey(std::size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetNormalKey(key); +} + +bool IsNormalKeyAvailable(std::size_t slot_id) { + return key_slots.at(slot_id).normal.has_value(); +} + +AESKey GetNormalKey(std::size_t slot_id) { + return key_slots.at(slot_id).normal.value_or(AESKey{}); +} + +void SelectCommonKeyIndex(u8 index) { + key_slots[KeySlotID::TicketCommonKey].SetKeyY(common_key_y_slots.at(index)); +} + +} // namespace Key diff --git a/src/core/key/key.h b/src/core/key/key.h new file mode 100644 index 0000000..78a83ec --- /dev/null +++ b/src/core/key/key.h @@ -0,0 +1,71 @@ +// Copyright 2017 Citra Emulator Project / 2019 threeSD Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/common_types.h" + +namespace Key { + +enum KeySlotID : std::size_t { + + // Used to decrypt the SSL client cert/private-key stored in ClCertA. + SSLKey = 0x0D, + + // AES keyslots used to decrypt NCCH + NCCHSecure1 = 0x2C, + NCCHSecure2 = 0x25, + NCCHSecure3 = 0x18, + NCCHSecure4 = 0x1B, + + // AES Keyslot used to generate the UDS data frame CCMP key. + UDSDataKey = 0x2D, + + // AES Keyslot used to encrypt the BOSS container data. + BOSSDataKey = 0x38, + + // AES Keyslot used to calculate DLP data frame checksum. + DLPDataKey = 0x39, + + // AES Keyslot used to generate the StreetPass CCMP key. + CECDDataKey = 0x2E, + + // AES Keyslot used by the friends module. + FRDKey = 0x36, + + // AES Keyslot used by the NFC module. + NFCKey = 0x39, + + // AES keyslot used for APT:Wrap/Unwrap functions + APTWrap = 0x31, + + // Console-unique AES keyslot used to encrypt all data in the "Nintendo 3DS//" folder + SDKey = 0x34, + + // AES keyslot used for decrypting ticket title key + TicketCommonKey = 0x3D, + + MaxKeySlotID = 0x40, +}; + +constexpr std::size_t AES_BLOCK_SIZE = 16; + +using AESKey = std::array; + +void LoadBootromKeys(const std::string& path); +void LoadMovableSedKeys(const std::string& path); + +void SetKeyX(std::size_t slot_id, const AESKey& key); +void SetKeyY(std::size_t slot_id, const AESKey& key); +void SetNormalKey(std::size_t slot_id, const AESKey& key); + +bool IsNormalKeyAvailable(std::size_t slot_id); +AESKey GetNormalKey(std::size_t slot_id); + +void SelectCommonKeyIndex(u8 index); + +} // namespace Key diff --git a/src/frontend/CMakeLists.txt b/src/frontend/CMakeLists.txt new file mode 100644 index 0000000..b6ed1d3 --- /dev/null +++ b/src/frontend/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(threeSD PRIVATE + frontend/main.cpp +) diff --git a/src/frontend/main.cpp b/src/frontend/main.cpp new file mode 100644 index 0000000..4745cc8 --- /dev/null +++ b/src/frontend/main.cpp @@ -0,0 +1,14 @@ +// Dummy + +#include +#include "common/logging/log.h" + +int main() { + + LOG_ERROR(Frontend, "test"); + _sleep(1000); + LOG_WARNING(Frontend, "test2"); + system("pause"); + + return 0; +} \ No newline at end of file