Initial commit

This commit is contained in:
zhupengfei
2019-08-24 23:30:22 +08:00
commit 4f5a3effd8
38 changed files with 4939 additions and 0 deletions
+172
View File
@@ -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
...
+9
View File
@@ -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)
+14
View File
@@ -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
)
+56
View File
@@ -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__)
+201
View File
@@ -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>;
+62
View File
@@ -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();
+39
View File
@@ -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"
+61
View File
@@ -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;
};
+864
View File
@@ -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 users 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 isnt defined, and the current user cant 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 {} musnt 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
+280
View File
@@ -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
}
+47
View File
@@ -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__)
+31
View File
@@ -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);
}
+212
View File
@@ -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
+83
View File
@@ -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
+718
View File
@@ -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;
+14
View File
@@ -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
)
+152
View File
@@ -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)};
}
}
+129
View File
@@ -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;
};
+91
View File
@@ -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;
}
+37
View File
@@ -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;
};
+13
View File
@@ -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;
+74
View File
@@ -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;
};
+336
View File
@@ -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;
}
+196
View File
@@ -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;
};
+59
View File
@@ -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
+17
View File
@@ -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
+213
View File
@@ -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
+71
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
target_sources(threeSD PRIVATE
frontend/main.cpp
)
+14
View File
@@ -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;
}