mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-02 16:49:04 +00:00
Initial commit
This commit is contained in:
+39
@@ -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/
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
add_subdirectory(cryptopp)
|
||||||
|
add_subdirectory(fmt)
|
||||||
Vendored
+251
@@ -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()
|
||||||
+1
Submodule externals/cryptopp/cryptopp added at f320e7d92a
+1
Submodule externals/fmt added at 5a4b24613b
+339
@@ -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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 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.
|
||||||
@@ -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
|
||||||
|
...
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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 <cstdlib>
|
||||||
|
#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 <typename Fn>
|
||||||
|
#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__)
|
||||||
@@ -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 <cstddef>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
#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,Y,Z>, 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 <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
|
||||||
|
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<T>::type directly.
|
||||||
|
using UnderlyingType = typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>,
|
||||||
|
std::enable_if<true, T>>::type;
|
||||||
|
|
||||||
|
// We store the value as the unsigned type to avoid undefined behaviour on value shifting
|
||||||
|
using StorageType = std::make_unsigned_t<UnderlyingType>;
|
||||||
|
|
||||||
|
using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::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<UnderlyingType>::is_signed) {
|
||||||
|
std::size_t shift = 8 * sizeof(T) - bits;
|
||||||
|
return static_cast<T>(static_cast<UnderlyingType>(storage << (shift - position)) >>
|
||||||
|
shift);
|
||||||
|
} else {
|
||||||
|
return static_cast<T>((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<StorageType>(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>, "T must be trivially copyable in a BitField");
|
||||||
|
};
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
template <std::size_t Position, std::size_t Bits, typename T>
|
||||||
|
using BitFieldBE = BitField<Position, Bits, T, BETag>;
|
||||||
@@ -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 <string>
|
||||||
|
|
||||||
|
#if !defined(ARCHITECTURE_x86_64)
|
||||||
|
#include <cstdlib> // 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();
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (C) 2005-2012 Gekko Emulator
|
||||||
|
*
|
||||||
|
* @file common_types.h
|
||||||
|
* @author ShizZy <shizzy247@gmail.com>
|
||||||
|
* @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 <cstdint>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
};
|
||||||
@@ -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 <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#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>
|
||||||
|
// windows.h needs to be included before other windows headers
|
||||||
|
#include <direct.h> // getcwd
|
||||||
|
#include <io.h>
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <shlobj.h> // for SHGetFolderPath
|
||||||
|
#include <tchar.h>
|
||||||
|
#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 <sys/param.h>
|
||||||
|
#endif
|
||||||
|
#include <cctype>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#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 <CoreFoundation/CFBundle.h>
|
||||||
|
#include <CoreFoundation/CFString.h>
|
||||||
|
#include <CoreFoundation/CFURL.h>
|
||||||
|
#ifdef availability
|
||||||
|
#undef availability
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#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<FILE, decltype(&std::fclose)>;
|
||||||
|
|
||||||
|
// 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<char, 1024> 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<std::string> 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<UserPath, std::string> 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<u32>(file.GetSize()));
|
||||||
|
return file.ReadArray(&str[0], str.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SplitFilename83(const std::string& filename, std::array<char, 9>& short_name,
|
||||||
|
std::array<char, 4>& 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
|
||||||
@@ -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 <array>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
#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<FSTEntry> 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<bool(
|
||||||
|
u64* num_entries_out, const std::string& directory, const std::string& virtual_name)>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<std::string> 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<char, 9>& short_name,
|
||||||
|
std::array<char, 4>& 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 <typename T>
|
||||||
|
std::size_t ReadArray(T* data, std::size_t length) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>,
|
||||||
|
"Given array does not consist of trivially copyable objects");
|
||||||
|
|
||||||
|
if (!IsOpen()) {
|
||||||
|
m_good = false;
|
||||||
|
return std::numeric_limits<std::size_t>::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 <typename T>
|
||||||
|
std::size_t WriteArray(const T* data, std::size_t length) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>,
|
||||||
|
"Given array does not consist of trivially copyable objects");
|
||||||
|
|
||||||
|
if (!IsOpen()) {
|
||||||
|
m_good = false;
|
||||||
|
return std::numeric_limits<std::size_t>::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 <typename T>
|
||||||
|
std::size_t ReadBytes(T* data, std::size_t length) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||||
|
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::size_t WriteBytes(const T* data, std::size_t length) {
|
||||||
|
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||||
|
return WriteArray(reinterpret_cast<const char*>(data), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::size_t WriteObject(const T& object) {
|
||||||
|
static_assert(!std::is_pointer_v<T>, "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 <typename T>
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -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 <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <string>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
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::microseconds>(
|
||||||
|
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__)
|
||||||
@@ -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 <cstddef>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
@@ -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 <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <codecvt>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <locale>
|
||||||
|
#include <sstream>
|
||||||
|
#include "common/common_paths.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#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<std::string>& 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<std::codecvt_utf8_utf16<__int16>, __int16> convert;
|
||||||
|
std::basic_string<__int16> tmp_buffer(input.cbegin(), input.cend());
|
||||||
|
return convert.to_bytes(tmp_buffer);
|
||||||
|
#else
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, 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<std::codecvt_utf8_utf16<__int16>, __int16> convert;
|
||||||
|
auto tmp_buffer = convert.from_bytes(input);
|
||||||
|
return std::u16string(tmp_buffer.cbegin(), tmp_buffer.cend());
|
||||||
|
#else
|
||||||
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, 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<int>(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<int>(input.size()),
|
||||||
|
&output[0], static_cast<int>(output.size()))) {
|
||||||
|
output.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string UTF16ToUTF8(const std::wstring& input) {
|
||||||
|
const auto size = WideCharToMultiByte(CP_UTF8, 0, input.data(), static_cast<int>(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<int>(input.size()),
|
||||||
|
&output[0], static_cast<int>(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
|
||||||
@@ -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 <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#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<std::string>& 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 <typename InIt>
|
||||||
|
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 <typename T>
|
||||||
|
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<char16_t>(static_cast<u16>(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
|
||||||
@@ -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 <type_traits>
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#include <cstdlib>
|
||||||
|
#endif
|
||||||
|
#include <cstring>
|
||||||
|
#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 <typename T, typename F>
|
||||||
|
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 <typename S>
|
||||||
|
swapped_t& operator=(const S& source) {
|
||||||
|
value = swap(static_cast<T>(source));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator s8() const {
|
||||||
|
return static_cast<s8>(swap());
|
||||||
|
}
|
||||||
|
operator u8() const {
|
||||||
|
return static_cast<u8>(swap());
|
||||||
|
}
|
||||||
|
operator s16() const {
|
||||||
|
return static_cast<s16>(swap());
|
||||||
|
}
|
||||||
|
operator u16() const {
|
||||||
|
return static_cast<u16>(swap());
|
||||||
|
}
|
||||||
|
operator s32() const {
|
||||||
|
return static_cast<s32>(swap());
|
||||||
|
}
|
||||||
|
operator u32() const {
|
||||||
|
return static_cast<u32>(swap());
|
||||||
|
}
|
||||||
|
operator s64() const {
|
||||||
|
return static_cast<s64>(swap());
|
||||||
|
}
|
||||||
|
operator u64() const {
|
||||||
|
return static_cast<u64>(swap());
|
||||||
|
}
|
||||||
|
operator float() const {
|
||||||
|
return static_cast<float>(swap());
|
||||||
|
}
|
||||||
|
operator double() const {
|
||||||
|
return static_cast<double>(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 <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
swapped_t operator+(const S& i) const {
|
||||||
|
return swap() + static_cast<T>(i);
|
||||||
|
}
|
||||||
|
// v - 5
|
||||||
|
swapped_t operator-(const swapped_t& i) const {
|
||||||
|
return swap() - i.swap();
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
swapped_t operator-(const S& i) const {
|
||||||
|
return swap() - static_cast<T>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// v += 5
|
||||||
|
swapped_t& operator+=(const swapped_t& i) {
|
||||||
|
value = swap(swap() + i.swap());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
swapped_t& operator+=(const S& i) {
|
||||||
|
value = swap(swap() + static_cast<T>(i));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
// v -= 5
|
||||||
|
swapped_t& operator-=(const swapped_t& i) {
|
||||||
|
value = swap(swap() - i.swap());
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
swapped_t& operator-=(const S& i) {
|
||||||
|
value = swap(swap() - static_cast<T>(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 <typename S>
|
||||||
|
bool operator==(const S& i) const {
|
||||||
|
return swap() == i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v != i
|
||||||
|
bool operator!=(const swapped_t& i) const {
|
||||||
|
return swap() != i.swap();
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
bool operator!=(const S& i) const {
|
||||||
|
return swap() != i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v > i
|
||||||
|
bool operator>(const swapped_t& i) const {
|
||||||
|
return swap() > i.swap();
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
bool operator>(const S& i) const {
|
||||||
|
return swap() > i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v < i
|
||||||
|
bool operator<(const swapped_t& i) const {
|
||||||
|
return swap() < i.swap();
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
bool operator<(const S& i) const {
|
||||||
|
return swap() < i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v >= i
|
||||||
|
bool operator>=(const swapped_t& i) const {
|
||||||
|
return swap() >= i.swap();
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
bool operator>=(const S& i) const {
|
||||||
|
return swap() >= i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// v <= i
|
||||||
|
bool operator<=(const swapped_t& i) const {
|
||||||
|
return swap() <= i.swap();
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
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 <typename S>
|
||||||
|
swapped_t& operator^=(const S& b) {
|
||||||
|
value = swap(swap() ^ b);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
swapped_t operator<<(const S& b) const {
|
||||||
|
return swap() << b;
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
swapped_t& operator<<=(const S& b) const {
|
||||||
|
value = swap(swap() << b);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
swapped_t operator>>(const S& b) const {
|
||||||
|
return swap() >> b;
|
||||||
|
}
|
||||||
|
template <typename S>
|
||||||
|
swapped_t& operator>>=(const S& b) const {
|
||||||
|
value = swap(swap() >> b);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member
|
||||||
|
/** todo **/
|
||||||
|
|
||||||
|
// Arithmetics
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator+(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator-(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator/(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator*(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator%(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
// Arithmetics + assignements
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator+=(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator-=(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
// Bitmath
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend S operator&(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
// Comparison
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend bool operator<(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend bool operator>(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend bool operator<=(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend bool operator>=(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend bool operator!=(const S& p, const swapped_t v);
|
||||||
|
|
||||||
|
template <typename S, typename T2, typename F2>
|
||||||
|
friend bool operator==(const S& p, const swapped_t v);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Arithmetics
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S operator+(const S& i, const swap_struct_t<T, F> v) {
|
||||||
|
return i + v.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S operator-(const S& i, const swap_struct_t<T, F> v) {
|
||||||
|
return i - v.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S operator/(const S& i, const swap_struct_t<T, F> v) {
|
||||||
|
return i / v.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S operator*(const S& i, const swap_struct_t<T, F> v) {
|
||||||
|
return i * v.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S operator%(const S& i, const swap_struct_t<T, F> v) {
|
||||||
|
return i % v.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arithmetics + assignements
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S& operator+=(S& i, const swap_struct_t<T, F> v) {
|
||||||
|
i += v.swap();
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S& operator-=(S& i, const swap_struct_t<T, F> v) {
|
||||||
|
i -= v.swap();
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logical
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S operator&(const S& i, const swap_struct_t<T, F> v) {
|
||||||
|
return i & v.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
S operator&(const swap_struct_t<T, F> v, const S& i) {
|
||||||
|
return static_cast<S>(v.swap() & i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comparaison
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
bool operator<(const S& p, const swap_struct_t<T, F> v) {
|
||||||
|
return p < v.swap();
|
||||||
|
}
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
bool operator>(const S& p, const swap_struct_t<T, F> v) {
|
||||||
|
return p > v.swap();
|
||||||
|
}
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
bool operator<=(const S& p, const swap_struct_t<T, F> v) {
|
||||||
|
return p <= v.swap();
|
||||||
|
}
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
bool operator>=(const S& p, const swap_struct_t<T, F> v) {
|
||||||
|
return p >= v.swap();
|
||||||
|
}
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
bool operator!=(const S& p, const swap_struct_t<T, F> v) {
|
||||||
|
return p != v.swap();
|
||||||
|
}
|
||||||
|
template <typename S, typename T, typename F>
|
||||||
|
bool operator==(const S& p, const swap_struct_t<T, F> v) {
|
||||||
|
return p == v.swap();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct swap_64_t {
|
||||||
|
static T swap(T x) {
|
||||||
|
return static_cast<T>(Common::swap64(x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct swap_32_t {
|
||||||
|
static T swap(T x) {
|
||||||
|
return static_cast<T>(Common::swap32(x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct swap_16_t {
|
||||||
|
static T swap(T x) {
|
||||||
|
return static_cast<T>(Common::swap16(x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct swap_float_t {
|
||||||
|
static T swap(T x) {
|
||||||
|
return static_cast<T>(Common::swapf(x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct swap_double_t {
|
||||||
|
static T swap(T x) {
|
||||||
|
return static_cast<T>(Common::swapd(x));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct swap_enum_t {
|
||||||
|
static_assert(std::is_enum_v<T>);
|
||||||
|
using base = std::underlying_type_t<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<base>(swap(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
T value{};
|
||||||
|
// clang-format off
|
||||||
|
using swap_t = std::conditional_t<
|
||||||
|
std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
|
||||||
|
std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
|
||||||
|
std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t<
|
||||||
|
std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
|
||||||
|
std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
|
||||||
|
std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
|
||||||
|
// clang-format on
|
||||||
|
static T swap(T x) {
|
||||||
|
return static_cast<T>(swap_t::swap(static_cast<base>(x)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SwapTag {}; // Use the different endianness from the system
|
||||||
|
struct KeepTag {}; // Use the same endianness as the system
|
||||||
|
|
||||||
|
template <typename T, typename Tag>
|
||||||
|
struct AddEndian;
|
||||||
|
|
||||||
|
// KeepTag specializations
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct AddEndian<T, KeepTag> {
|
||||||
|
using type = T;
|
||||||
|
};
|
||||||
|
|
||||||
|
// SwapTag specializations
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<u8, SwapTag> {
|
||||||
|
using type = u8;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<u16, SwapTag> {
|
||||||
|
using type = swap_struct_t<u16, swap_16_t<u16>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<u32, SwapTag> {
|
||||||
|
using type = swap_struct_t<u32, swap_32_t<u32>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<u64, SwapTag> {
|
||||||
|
using type = swap_struct_t<u64, swap_64_t<u64>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<s8, SwapTag> {
|
||||||
|
using type = s8;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<s16, SwapTag> {
|
||||||
|
using type = swap_struct_t<s16, swap_16_t<s16>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<s32, SwapTag> {
|
||||||
|
using type = swap_struct_t<s32, swap_32_t<s32>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<s64, SwapTag> {
|
||||||
|
using type = swap_struct_t<s64, swap_64_t<s64>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<float, SwapTag> {
|
||||||
|
using type = swap_struct_t<float, swap_float_t<float>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct AddEndian<double, SwapTag> {
|
||||||
|
using type = swap_struct_t<double, swap_double_t<double>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct AddEndian<T, SwapTag> {
|
||||||
|
static_assert(std::is_enum_v<T>);
|
||||||
|
using type = swap_enum_t<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<u16, LETag>::type;
|
||||||
|
using u32_le = AddEndian<u32, LETag>::type;
|
||||||
|
using u64_le = AddEndian<u64, LETag>::type;
|
||||||
|
|
||||||
|
using s16_le = AddEndian<s16, LETag>::type;
|
||||||
|
using s32_le = AddEndian<s32, LETag>::type;
|
||||||
|
using s64_le = AddEndian<s64, LETag>::type;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>;
|
||||||
|
|
||||||
|
using float_le = AddEndian<float, LETag>::type;
|
||||||
|
using double_le = AddEndian<double, LETag>::type;
|
||||||
|
|
||||||
|
// Aliases for BE types
|
||||||
|
using u16_be = AddEndian<u16, BETag>::type;
|
||||||
|
using u32_be = AddEndian<u32, BETag>::type;
|
||||||
|
using u64_be = AddEndian<u64, BETag>::type;
|
||||||
|
|
||||||
|
using s16_be = AddEndian<s16, BETag>::type;
|
||||||
|
using s32_be = AddEndian<s32, BETag>::type;
|
||||||
|
using s64_be = AddEndian<s64, BETag>::type;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>;
|
||||||
|
|
||||||
|
using float_be = AddEndian<float, BETag>::type;
|
||||||
|
using double_be = AddEndian<double, BETag>::type;
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
// Copyright 2019 threeSD Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#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<u32_le> 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<const u8*>(
|
||||||
|
data.data())[descriptor.levels[level].offset + selector * descriptor.levels[level].size +
|
||||||
|
index];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> DPFSContainer::GetLevel3Data() const {
|
||||||
|
std::vector<u8> 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<u8> 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<u8> 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<u8> 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<u32> 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<u8> 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<std::vector<u8>> DataContainer::GetIVFCLevel4Data() const {
|
||||||
|
if (partition_count == 1) {
|
||||||
|
return {GetPartitionData(0)};
|
||||||
|
} else {
|
||||||
|
return {GetPartitionData(0), GetPartitionData(1)};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 <array>
|
||||||
|
#include <vector>
|
||||||
|
#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<DataDescriptor, 2> partition_descriptors;
|
||||||
|
std::array<DataDescriptor, 2> partitions;
|
||||||
|
u8 active_partition_table;
|
||||||
|
INSERT_PADDING_BYTES(3);
|
||||||
|
std::array<u8, 0x20> 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<u8, 0x20> 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<LevelDescriptor, 4> 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<LevelDescriptor, 3> 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<u32_le> data);
|
||||||
|
|
||||||
|
/// Unwraps the DPFS Tree, returning actual data in Level3.
|
||||||
|
std::vector<u8> 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<u32_le> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DISA/DIFF Container.
|
||||||
|
*/
|
||||||
|
class DataContainer {
|
||||||
|
public:
|
||||||
|
explicit DataContainer(std::vector<u8> data);
|
||||||
|
~DataContainer();
|
||||||
|
|
||||||
|
/// Unwraps the whole container, returning the data in IVFC Level 4 of all partitions.
|
||||||
|
std::vector<std::vector<u8>> GetIVFCLevel4Data() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void InitAsDISA();
|
||||||
|
void InitAsDIFF();
|
||||||
|
|
||||||
|
/// Unwraps the whole container, returning the data in IVFC Level 4 of a partition.
|
||||||
|
std::vector<u8> GetPartitionData(u8 index) const;
|
||||||
|
|
||||||
|
std::vector<u8> data;
|
||||||
|
u32 partition_count;
|
||||||
|
u64_le partition_table_offset;
|
||||||
|
std::vector<DataDescriptor> partition_descriptors;
|
||||||
|
std::vector<DataDescriptor> partitions;
|
||||||
|
};
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2019 threeSD Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <cryptopp/aes.h>
|
||||||
|
#include <cryptopp/files.h>
|
||||||
|
#include <cryptopp/filters.h>
|
||||||
|
#include <cryptopp/modes.h>
|
||||||
|
#include <cryptopp/sha.h>
|
||||||
|
#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<u8, 16> GetFileCTR(const std::string& path) {
|
||||||
|
auto path_utf16 = Common::UTF8ToUTF16(path);
|
||||||
|
std::vector<u8> 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<u8, CryptoPP::SHA256::DIGESTSIZE> hash;
|
||||||
|
sha.CalculateDigest(hash.data(), path_data.data(), path_data.size());
|
||||||
|
|
||||||
|
std::array<u8, 16> 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<CryptoPP::AES>::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<u8> SDMCDecryptor::DecryptFile(const std::string& source) const {
|
||||||
|
auto ctr = GetFileCTR(source);
|
||||||
|
auto key = Key::GetNormalKey(Key::SDKey);
|
||||||
|
CryptoPP::CTR_Mode<CryptoPP::AES>::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<u8> encrypted_data(size);
|
||||||
|
if (file.ReadBytes(encrypted_data.data(), size) != size) {
|
||||||
|
LOG_ERROR(Frontend, "Could not read file {}", root_folder + source);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> data(size);
|
||||||
|
aes.ProcessData(data.data(), encrypted_data.data(), encrypted_data.size());
|
||||||
|
return data;
|
||||||
|
}
|
||||||
@@ -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 <string>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
class SDMCDecryptor {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Initializes the decryptor.
|
||||||
|
* @param root_folder Path to the "Nintendo 3DS/<ID0>/<ID1>" 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<u8> DecryptFile(const std::string& source) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string root_folder;
|
||||||
|
};
|
||||||
@@ -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;
|
||||||
@@ -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 <string>
|
||||||
|
#include <vector>
|
||||||
|
#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/<ID0>/<ID1>")
|
||||||
|
|
||||||
|
// 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/<ID0>/<ID1>" 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<ContentSpecifier> ListContent() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool ImportTitle(u64 id);
|
||||||
|
bool ImportSavegame(u64 id);
|
||||||
|
bool ImportExtdata(u64 id);
|
||||||
|
|
||||||
|
Config config;
|
||||||
|
};
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
// Copyright 2019 threeSD Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#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<char, 17> 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<u8> data_) : duplicate_data(true), data(std::move(data_)) {}
|
||||||
|
|
||||||
|
SDSavegame::SDSavegame(std::vector<u8> partitionA_, std::vector<u8> 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<char, 17> 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<char, 17> 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;
|
||||||
|
}
|
||||||
@@ -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 <array>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <vector>
|
||||||
|
#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<ArchiveFormatInfo>::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<char, 16> 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<char, 16> 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<DirectoryEntryTableEntry> directory_entry_table;
|
||||||
|
std::vector<FileEntryTableEntry> file_entry_table;
|
||||||
|
std::vector<u8> data_region;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SDSavegame : public InnerFAT {
|
||||||
|
public:
|
||||||
|
explicit SDSavegame(std::vector<u8> data);
|
||||||
|
explicit SDSavegame(std::vector<u8> partitionA, std::vector<u8> 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<FATNode> fat;
|
||||||
|
bool duplicate_data; // Layout variant
|
||||||
|
|
||||||
|
// Temporary storage for construction data
|
||||||
|
std::vector<u8> data;
|
||||||
|
std::vector<u8> partitionA;
|
||||||
|
std::vector<u8> 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;
|
||||||
|
};
|
||||||
@@ -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 <algorithm>
|
||||||
|
#include <functional>
|
||||||
|
#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<u8>(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<u8>((b >> ((15 - i) * 8)) & 0xff) + carry;
|
||||||
|
carry = sum >> 8;
|
||||||
|
out[i] = static_cast<u8>(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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#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<u8>(std::stoi(hex.substr(i * 2, 2), 0, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KeySlot {
|
||||||
|
std::optional<AESKey> x;
|
||||||
|
std::optional<AESKey> y;
|
||||||
|
std::optional<AESKey> normal;
|
||||||
|
|
||||||
|
void SetKeyX(std::optional<AESKey> key) {
|
||||||
|
x = key;
|
||||||
|
GenerateNormalKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetKeyY(std::optional<AESKey> key) {
|
||||||
|
y = key;
|
||||||
|
GenerateNormalKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNormalKey(std::optional<AESKey> 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<KeySlot, KeySlotID::MaxKeySlotID> key_slots;
|
||||||
|
std::array<std::optional<AESKey>, 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<KeyDesc, 80> 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
|
||||||
@@ -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 <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#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/<ID0>/<ID1>" 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<u8, AES_BLOCK_SIZE>;
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
target_sources(threeSD PRIVATE
|
||||||
|
frontend/main.cpp
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// Dummy
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
LOG_ERROR(Frontend, "test");
|
||||||
|
_sleep(1000);
|
||||||
|
LOG_WARNING(Frontend, "test2");
|
||||||
|
system("pause");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user