Compare commits

..

16 Commits

Author SHA1 Message Date
Subv
51d4c772a2 RasterizerCache: Swizzle to a staging buffer before copying it to memory when flushing a texture.
This gives us slightly increased memory safety, writing to out of bounds memory will be caught at the point of write instead of several calls down the line as memory corruption
2018-09-21 13:53:13 -05:00
Subv
2fd06acc8f GPU/DMA: Fixed Tiled->Linear transfers.
We no longer write to out of bounds memory anymore.
2018-09-21 13:53:12 -05:00
Subv
bdb3920753 GPU/DMA: Copy the requested amount of data when doing Linear->Linear 2D transfers. 2018-09-21 13:53:12 -05:00
Subv
bb9eeba670 GPU/DMA: Fixed the Linear->Tiled and Linear->Linear transfer modes.
This fixes the loading bar in Has-Been Heroes.

The Tiled->Tiled transfer mode is not implemented yet and will assert.
2018-09-21 13:53:12 -05:00
Subv
47826fd090 RasterizerGL: Flush dirty cached objects before removing them from the cache.
There might be a case where we draw to a framebuffer (thus making it dirty) and then proceed to overwrite only a portion of it from the CPU. The current code would cause the rest of the modified framebuffer to be discarded.
2018-09-21 13:53:11 -05:00
Subv
b87b8db879 GPU/Engines: Flush and invalidate source/dest regions in Fermi2D and KeplerMemory copies. 2018-09-21 13:53:11 -05:00
Subv
23e1dd3c4d RasterizerCache: Do not flush surfaces that have not been modified.
This should bring the performance closer to what it was before flushing was implemented.
2018-09-21 13:53:10 -05:00
Subv
acfc8d7fc9 RasterizerCache: Convert the S8Z24 format from Z24S8 back to S8Z24 when flushing. 2018-09-21 13:53:10 -05:00
Subv
81afcf3f4e RasterizerCache: Allow flushing compressed formats. 2018-09-21 13:53:10 -05:00
Subv
b27ba353d4 RasterizerCache: Log problematic formats when flushing a surface. 2018-09-21 13:53:09 -05:00
Subv
8474a5adf7 GPU/DMA: Implemented source offset copies for unswizzling Tiled->Linear transfers.
This is used by nouveau to implement glReadPixels.
2018-09-21 13:53:09 -05:00
Subv
ca7eb39b86 RasterizerCache: Remove the glGetError call after a surface flush 2018-09-21 13:53:08 -05:00
Subv
393f2418c5 GPU/DMA: Flush the source memory region before a DMA transfer and invalidate the destination region after the transfer. 2018-09-21 13:53:08 -05:00
Subv
d0a814eaca GPU/DMA: Pass the current rasterizer as a variable when constructing the DMA engine. 2018-09-21 13:53:06 -05:00
Subv
047bbe0881 RasterizerCache: Re-introduced the code to flush the various resource caches. 2018-09-21 13:53:06 -05:00
Subv
181aca9af0 RasterizerCache: Reintroduced code for flushing OpenGL surfaces back to memory.
This is required for a proper implementation of the DMA and 2D engines.
2018-09-21 13:53:05 -05:00
248 changed files with 2276 additions and 30173 deletions

4
.gitattributes vendored
View File

@@ -1,4 +0,0 @@
dist/languages/* linguist-vendored
dist/qt_themes/* linguist-vendored
externals/* linguist-vendored
*.h linguist-language=cpp

6
.gitmodules vendored
View File

@@ -34,9 +34,3 @@
[submodule "soundtouch"]
path = externals/soundtouch
url = https://github.com/citra-emu/ext-soundtouch.git
[submodule "libressl"]
path = externals/libressl
url = https://github.com/citra-emu/ext-libressl-portable.git
[submodule "discord-rpc"]
path = externals/discord-rpc
url = https://github.com/discordapp/discord-rpc.git

View File

@@ -24,7 +24,7 @@ matrix:
- os: osx
env: NAME="macos build"
sudo: false
osx_image: xcode10
osx_image: xcode9.3
install: "./.travis/macos/deps.sh"
script: "./.travis/macos/build.sh"
after_success: "./.travis/macos/upload.sh"

View File

@@ -10,7 +10,3 @@ TRAVIS_JOB_ID
TRAVIS_JOB_NUMBER
TRAVIS_REPO_SLUG
TRAVIS_TAG
# yuzu specific flags
ENABLE_COMPATIBILITY_REPORTING
USE_DISCORD_PRESENCE

View File

@@ -1,4 +1,4 @@
#!/bin/bash -ex
mkdir -p "$HOME/.ccache"
docker run -e ENABLE_COMPATIBILITY_REPORTING --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh
docker run --env-file .travis/common/travis-ci.env -v $(pwd):/yuzu -v "$HOME/.ccache":/root/.ccache ubuntu:18.04 /bin/bash /yuzu/.travis/linux/docker.sh

View File

@@ -6,7 +6,7 @@ apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev
cd /yuzu
mkdir build && cd build
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -G Ninja
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -G Ninja
ninja
ccache -s

View File

@@ -2,14 +2,14 @@
set -o pipefail
export MACOSX_DEPLOYMENT_TARGET=10.13
export MACOSX_DEPLOYMENT_TARGET=10.12
export Qt5_DIR=$(brew --prefix)/opt/qt5
export UNICORNDIR=$(pwd)/externals/unicorn
export PATH="/usr/local/opt/ccache/libexec:$PATH"
mkdir build && cd build
cmake --version
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON
make -j4
ccache -s

View File

@@ -15,14 +15,10 @@ CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON
option(ENABLE_QT "Enable the Qt frontend" ON)
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook")
file(COPY hooks/pre-commit
@@ -127,6 +123,8 @@ else()
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
add_definitions(/DWIN32_LEAN_AND_MEAN)
# set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
# Tweak optimization settings
@@ -348,6 +346,14 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
set(PLATFORM_LIBRARIES rt)
ENDIF (APPLE)
# MINGW: GCC does not support codecvt, so use iconv instead
if (UNIX OR MINGW)
find_library(ICONV_LIBRARY NAMES iconv)
if (ICONV_LIBRARY)
list(APPEND PLATFORM_LIBRARIES ${ICONV_LIBRARY})
endif()
endif()
# Setup a custom clang-format target (if clang-format can be found) that will run
# against all the src files. This should be used before making a pull request.
# =======================================================================
@@ -434,12 +440,8 @@ enable_testing()
add_subdirectory(externals)
add_subdirectory(src)
# Set yuzu project or yuzu-cmd project as default StartUp Project in Visual Studio depending on whether QT is enabled or not
if(ENABLE_QT)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu)
else()
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu-cmd)
endif()
# Set yuzu project as default StartUp Project in Visual Studio
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu)
# Installation instructions

View File

@@ -22,7 +22,7 @@ If clang format is found, then cmake will add a custom build target that can be
* Don't ever introduce new external dependencies into Core
* Don't use any platform specific code in Core
* Use namespaces often
* Avoid the use of C-style casts and instead prefer C++-style `static_cast` and `reinterpret_cast`. Try to avoid using `dynamic_cast`. Never use `const_cast`.
* Avoid the use of C-style casts and instead prefer C++-style `static_cast` and `reinterpret_cast`. Try to avoid using `dynamic_cast`. Never use `const_cast`. The only exception to this rule is for casting between two numeric types, where C-style casts are encouraged for brevity and readability.
### Naming Rules
* Functions: `PascalCase`

View File

@@ -39,12 +39,11 @@ before_build:
- mkdir %BUILD_TYPE%_build
- cd %BUILD_TYPE%_build
- ps: |
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
if ($env:BUILD_TYPE -eq 'msvc') {
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1 && exit 0'
} else {
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON .. 2>&1"
}
- cd ..
@@ -164,13 +163,3 @@ artifacts:
name: build
type: zip
deploy:
provider: GitHub
release: $(appveyor_repo_tag_name)
auth_token:
secure: QqePPnXbkzmXct5c8hZ2X5AbsthbI6cS1Sr+VBzcD8oUOIjfWJJKXVAQGUbQAbb0
artifact: update,build
draft: false
prerelease: false
on:
appveyor_repo_tag: true

View File

@@ -70,28 +70,3 @@ if(ENABLE_CUBEB)
set(BUILD_TESTS OFF CACHE BOOL "")
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
endif()
# DiscordRPC
if (USE_DISCORD_PRESENCE)
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif()
if (ENABLE_WEB_SERVICE)
# LibreSSL
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
add_subdirectory(libressl EXCLUDE_FROM_ALL)
target_include_directories(ssl INTERFACE ./libressl/include)
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
# lurlparser
add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
# httplib
add_library(httplib INTERFACE)
target_include_directories(httplib INTERFACE ./httplib)
# JSON
add_library(json-headers INTERFACE)
target_include_directories(json-headers INTERFACE ./json)
endif()

Submodule externals/discord-rpc deleted from e32d001809

2
externals/fmt vendored

View File

@@ -1,15 +0,0 @@
From https://github.com/yhirose/cpp-httplib/commit/d9479bc0b12e8a1e8bce2d34da4feeef488581f3
MIT License
===
cpp-httplib
A C++11 header-only HTTP library.
It's extremely easy to setup. Just include httplib.h file in your code!
Inspired by Sinatra and express.
© 2017 Yuji Hirose

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +0,0 @@
JSON for Modern C++
===================
v3.1.2
This is a mirror providing the single required header file.
The original repository can be found at:
https://github.com/nlohmann/json/commit/d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe

17300
externals/json/json.hpp vendored

File diff suppressed because it is too large Load Diff

1
externals/libressl vendored

Submodule externals/libressl deleted from 7d01cb01cb

View File

@@ -1,8 +0,0 @@
add_library(lurlparser
LUrlParser.cpp
LUrlParser.h
)
create_target_directory_groups(lurlparser)
target_include_directories(lurlparser INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@@ -1,265 +0,0 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "LUrlParser.h"
#include <algorithm>
#include <cstring>
#include <stdlib.h>
// check if the scheme name is valid
static bool IsSchemeValid( const std::string& SchemeName )
{
for ( auto c : SchemeName )
{
if ( !isalpha( c ) && c != '+' && c != '-' && c != '.' ) return false;
}
return true;
}
bool LUrlParser::clParseURL::GetPort( int* OutPort ) const
{
if ( !IsValid() ) { return false; }
int Port = atoi( m_Port.c_str() );
if ( Port <= 0 || Port > 65535 ) { return false; }
if ( OutPort ) { *OutPort = Port; }
return true;
}
// based on RFC 1738 and RFC 3986
LUrlParser::clParseURL LUrlParser::clParseURL::ParseURL( const std::string& URL )
{
LUrlParser::clParseURL Result;
const char* CurrentString = URL.c_str();
/*
* <scheme>:<scheme-specific-part>
* <scheme> := [a-z\+\-\.]+
* For resiliency, programs interpreting URLs should treat upper case letters as equivalent to lower case in scheme names
*/
// try to read scheme
{
const char* LocalString = strchr( CurrentString, ':' );
if ( !LocalString )
{
return clParseURL( LUrlParserError_NoUrlCharacter );
}
// save the scheme name
Result.m_Scheme = std::string( CurrentString, LocalString - CurrentString );
if ( !IsSchemeValid( Result.m_Scheme ) )
{
return clParseURL( LUrlParserError_InvalidSchemeName );
}
// scheme should be lowercase
std::transform( Result.m_Scheme.begin(), Result.m_Scheme.end(), Result.m_Scheme.begin(), ::tolower );
// skip ':'
CurrentString = LocalString+1;
}
/*
* //<user>:<password>@<host>:<port>/<url-path>
* any ":", "@" and "/" must be normalized
*/
// skip "//"
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
if ( *CurrentString++ != '/' ) return clParseURL( LUrlParserError_NoDoubleSlash );
// check if the user name and password are specified
bool bHasUserName = false;
const char* LocalString = CurrentString;
while ( *LocalString )
{
if ( *LocalString == '@' )
{
// user name and password are specified
bHasUserName = true;
break;
}
else if ( *LocalString == '/' )
{
// end of <host>:<port> specification
bHasUserName = false;
break;
}
LocalString++;
}
// user name and password
LocalString = CurrentString;
if ( bHasUserName )
{
// read user name
while ( *LocalString && *LocalString != ':' && *LocalString != '@' ) LocalString++;
Result.m_UserName = std::string( CurrentString, LocalString - CurrentString );
// proceed with the current pointer
CurrentString = LocalString;
if ( *CurrentString == ':' )
{
// skip ':'
CurrentString++;
// read password
LocalString = CurrentString;
while ( *LocalString && *LocalString != '@' ) LocalString++;
Result.m_Password = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// skip '@'
if ( *CurrentString != '@' )
{
return clParseURL( LUrlParserError_NoAtSign );
}
CurrentString++;
}
bool bHasBracket = ( *CurrentString == '[' );
// go ahead, read the host name
LocalString = CurrentString;
while ( *LocalString )
{
if ( bHasBracket && *LocalString == ']' )
{
// end of IPv6 address
LocalString++;
break;
}
else if ( !bHasBracket && ( *LocalString == ':' || *LocalString == '/' ) )
{
// port number is specified
break;
}
LocalString++;
}
Result.m_Host = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// is port number specified?
if ( *CurrentString == ':' )
{
CurrentString++;
// read port number
LocalString = CurrentString;
while ( *LocalString && *LocalString != '/' ) LocalString++;
Result.m_Port = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// end of string
if ( !*CurrentString )
{
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}
// skip '/'
if ( *CurrentString != '/' )
{
return clParseURL( LUrlParserError_NoSlash );
}
CurrentString++;
// parse the path
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' && *LocalString != '?' ) LocalString++;
Result.m_Path = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
// check for query
if ( *CurrentString == '?' )
{
// skip '?'
CurrentString++;
// read query
LocalString = CurrentString;
while ( *LocalString && *LocalString != '#' ) LocalString++;
Result.m_Query = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
// check for fragment
if ( *CurrentString == '#' )
{
// skip '#'
CurrentString++;
// read fragment
LocalString = CurrentString;
while ( *LocalString ) LocalString++;
Result.m_Fragment = std::string( CurrentString, LocalString - CurrentString );
CurrentString = LocalString;
}
Result.m_ErrorCode = LUrlParserError_Ok;
return Result;
}

View File

@@ -1,78 +0,0 @@
/*
* Lightweight URL & URI parser (RFC 1738, RFC 3986)
* https://github.com/corporateshark/LUrlParser
*
* The MIT License (MIT)
*
* Copyright (C) 2015 Sergey Kosarevsky (sk@linderdaum.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <string>
namespace LUrlParser
{
enum LUrlParserError
{
LUrlParserError_Ok = 0,
LUrlParserError_Uninitialized = 1,
LUrlParserError_NoUrlCharacter = 2,
LUrlParserError_InvalidSchemeName = 3,
LUrlParserError_NoDoubleSlash = 4,
LUrlParserError_NoAtSign = 5,
LUrlParserError_UnexpectedEndOfLine = 6,
LUrlParserError_NoSlash = 7,
};
class clParseURL
{
public:
LUrlParserError m_ErrorCode;
std::string m_Scheme;
std::string m_Host;
std::string m_Port;
std::string m_Path;
std::string m_Query;
std::string m_Fragment;
std::string m_UserName;
std::string m_Password;
clParseURL()
: m_ErrorCode( LUrlParserError_Uninitialized )
{}
/// return 'true' if the parsing was successful
bool IsValid() const { return m_ErrorCode == LUrlParserError_Ok; }
/// helper to convert the port number to int, return 'true' if the port is valid (within the 0..65535 range)
bool GetPort( int* OutPort ) const;
/// parse the URL
static clParseURL ParseURL( const std::string& URL );
private:
explicit clParseURL( LUrlParserError ErrorCode )
: m_ErrorCode( ErrorCode )
{}
};
} // namespace LUrlParser

View File

@@ -1,19 +0,0 @@
From https://github.com/corporateshark/LUrlParser/commit/455d5e2d27e3946f11ad0328fee9ee2628e6a8e2
MIT License
===
Lightweight URL & URI parser (RFC 1738, RFC 3986)
(C) Sergey Kosarevsky, 2015
@corporateshark sk@linderdaum.com
http://www.linderdaum.com
http://blog.linderdaum.com
=============================
A tiny and lightweight URL & URI parser (RFC 1738, RFC 3986) written in C++.

0
git
View File

View File

@@ -13,6 +13,3 @@ endif()
if (ENABLE_QT)
add_subdirectory(yuzu)
endif()
if (ENABLE_WEB_SERVICE)
add_subdirectory(web_service)
endif()

View File

@@ -30,7 +30,7 @@ public:
return info;
}
VoiceInfo& GetInfo() {
VoiceInfo& Info() {
return info;
}
@@ -51,30 +51,9 @@ private:
VoiceInfo info{};
};
class AudioRenderer::EffectState {
public:
const EffectOutStatus& GetOutStatus() const {
return out_status;
}
const EffectInStatus& GetInfo() const {
return info;
}
EffectInStatus& GetInfo() {
return info;
}
void UpdateState();
private:
EffectOutStatus out_status{};
EffectInStatus info{};
};
AudioRenderer::AudioRenderer(AudioRendererParameter params,
Kernel::SharedPtr<Kernel::Event> buffer_event)
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
effects(params.effect_count) {
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
audio_out = std::make_unique<AudioCore::AudioOut>();
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
@@ -100,10 +79,6 @@ u32 AudioRenderer::GetMixBufferCount() const {
return worker_params.mix_buffer_count;
}
Stream::State AudioRenderer::GetStreamState() const {
return stream->GetState();
}
std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
// Copy UpdateDataHeader struct
UpdateDataHeader config{};
@@ -117,29 +92,11 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
memory_pool_count * sizeof(MemoryPoolInfo));
// Copy VoiceInfo structs
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size + config.voice_resource_size};
std::size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size +
config.voice_resource_size};
for (auto& voice : voices) {
std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
voice_offset += sizeof(VoiceInfo);
}
std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
config.memory_pools_size + config.voice_resource_size +
config.voices_size};
for (auto& effect : effects) {
std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
effect_offset += sizeof(EffectInStatus);
}
// Update memory pool state
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
memory_pool[index].state = MemoryPoolStates::Attached;
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
memory_pool[index].state = MemoryPoolStates::Detached;
}
std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo));
offset += sizeof(VoiceInfo);
}
// Update voices
@@ -153,8 +110,14 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
}
}
for (auto& effect : effects) {
effect.UpdateState();
// Update memory pool state
std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
for (std::size_t index = 0; index < memory_pool.size(); ++index) {
if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
memory_pool[index].state = MemoryPoolStates::Attached;
} else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
memory_pool[index].state = MemoryPoolStates::Detached;
}
}
// Release previous buffers and queue next ones for playback
@@ -177,14 +140,6 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
voice_out_status_offset += sizeof(VoiceOutStatus);
}
std::size_t effect_out_status_offset{
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
response_data.voice_resource_size};
for (const auto& effect : effects) {
std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
sizeof(EffectOutStatus));
effect_out_status_offset += sizeof(EffectOutStatus);
}
return output_params;
}
@@ -285,29 +240,11 @@ void AudioRenderer::VoiceState::RefreshBuffer() {
break;
}
samples =
Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE);
samples = Interpolate(interp_state, std::move(samples), Info().sample_rate, STREAM_SAMPLE_RATE);
is_refresh_pending = false;
}
void AudioRenderer::EffectState::UpdateState() {
if (info.is_new) {
out_status.state = EffectStatus::New;
} else {
if (info.type == Effect::Aux) {
ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_info) == 0,
"Aux buffers tried to update");
ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_info) == 0,
"Aux buffers tried to update");
ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_base) == 0,
"Aux buffers tried to update");
ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_base) == 0,
"Aux buffers tried to update");
}
}
}
static constexpr s16 ClampToS16(s32 value) {
return static_cast<s16>(std::clamp(value, -32768, 32767));
}

View File

@@ -28,16 +28,6 @@ enum class PlayState : u8 {
Paused = 2,
};
enum class Effect : u8 {
None = 0,
Aux = 2,
};
enum class EffectStatus : u8 {
None = 0,
New = 1,
};
struct AudioRendererParameter {
u32_le sample_rate;
u32_le sample_count;
@@ -138,43 +128,6 @@ struct VoiceOutStatus {
};
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
struct AuxInfo {
std::array<u8, 24> input_mix_buffers;
std::array<u8, 24> output_mix_buffers;
u32_le mix_buffer_count;
u32_le sample_rate; // Stored in the aux buffer currently
u32_le sampe_count;
u64_le send_buffer_info;
u64_le send_buffer_base;
u64_le return_buffer_info;
u64_le return_buffer_base;
};
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
struct EffectInStatus {
Effect type;
u8 is_new;
u8 is_enabled;
INSERT_PADDING_BYTES(1);
u32_le mix_id;
u64_le buffer_base;
u64_le buffer_sz;
s32_le priority;
INSERT_PADDING_BYTES(4);
union {
std::array<u8, 0xa0> raw;
AuxInfo aux_info;
};
};
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
struct EffectOutStatus {
EffectStatus state;
INSERT_PADDING_BYTES(0xf);
};
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
struct UpdateDataHeader {
UpdateDataHeader() {}
@@ -217,16 +170,13 @@ public:
u32 GetSampleRate() const;
u32 GetSampleCount() const;
u32 GetMixBufferCount() const;
Stream::State GetStreamState() const;
private:
class EffectState;
class VoiceState;
AudioRendererParameter worker_params;
Kernel::SharedPtr<Kernel::Event> buffer_event;
std::vector<VoiceState> voices;
std::vector<EffectState> effects;
std::unique_ptr<AudioOut> audio_out;
AudioCore::StreamPtr stream;
};

View File

@@ -49,14 +49,9 @@ void Stream::Play() {
}
void Stream::Stop() {
state = State::Stopped;
ASSERT_MSG(false, "Unimplemented");
}
Stream::State Stream::GetState() const {
return state;
}
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);

View File

@@ -33,12 +33,6 @@ public:
Multi51Channel16,
};
/// Current state of the stream
enum class State {
Stopped,
Playing,
};
/// Callback function type, used to change guest state on a buffer being released
using ReleaseCallback = std::function<void()>;
@@ -78,10 +72,13 @@ public:
/// Gets the number of channels
u32 GetNumChannels() const;
/// Get the state
State GetState() const;
private:
/// Current state of the stream
enum class State {
Stopped,
Playing,
};
/// Plays the next queued buffer in the audio stream, starting playback if necessary
void PlayNextBuffer();

View File

@@ -59,7 +59,7 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
m_sound_touch.setTempo(m_stretch_ratio);
LOG_TRACE(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio,
LOG_DEBUG(Audio, "{:5}/{:5} ratio:{:0.6f} backlog:{:0.6f}", num_in, num_out, m_stretch_ratio,
backlog_fullness);
m_sound_touch.putSamples(in, static_cast<u32>(num_in));

View File

@@ -29,7 +29,7 @@ if ($ENV{CI})
if (BUILD_VERSION)
# This leaves a trailing space on the last word, but we actually want that
# because of how it's styled in the title bar.
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ")
else()
set(BUILD_FULLNAME "")
endif()
@@ -41,8 +41,6 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU
add_library(common STATIC
alignment.h
assert.h
detached_tasks.cpp
detached_tasks.h
bit_field.h
bit_set.h
cityhash.cpp
@@ -89,7 +87,6 @@ add_library(common STATIC
timer.cpp
timer.h
vector_math.h
web_result.h
)
if(ARCHITECTURE_x86_64)

View File

@@ -33,8 +33,6 @@
#define NAND_DIR "nand"
#define SYSDATA_DIR "sysdata"
#define KEYS_DIR "keys"
#define LOAD_DIR "load"
#define DUMP_DIR "dump"
#define LOG_DIR "log"
// Filenames

View File

@@ -1,41 +0,0 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <thread>
#include "common/assert.h"
#include "common/detached_tasks.h"
namespace Common {
DetachedTasks* DetachedTasks::instance = nullptr;
DetachedTasks::DetachedTasks() {
ASSERT(instance == nullptr);
instance = this;
}
void DetachedTasks::WaitForAllTasks() {
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [this]() { return count == 0; });
}
DetachedTasks::~DetachedTasks() {
std::unique_lock<std::mutex> lock(mutex);
ASSERT(count == 0);
instance = nullptr;
}
void DetachedTasks::AddTask(std::function<void()> task) {
std::unique_lock<std::mutex> lock(instance->mutex);
++instance->count;
std::thread([task{std::move(task)}]() {
task();
std::unique_lock<std::mutex> lock(instance->mutex);
--instance->count;
std::notify_all_at_thread_exit(instance->cv, std::move(lock));
})
.detach();
}
} // namespace Common

View File

@@ -1,40 +0,0 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <condition_variable>
#include <functional>
namespace Common {
/**
* A background manager which ensures that all detached task is finished before program exits.
*
* Some tasks, telemetry submission for example, prefer executing asynchronously and don't care
* about the result. These tasks are suitable for std::thread::detach(). However, this is unsafe if
* the task is launched just before the program exits (which is a common case for telemetry), so we
* need to block on these tasks on program exit.
*
* To make detached task safe, a single DetachedTasks object should be placed in the main(), and
* call WaitForAllTasks() after all program execution but before global/static variable destruction.
* Any potentially unsafe detached task should be executed via DetachedTasks::AddTask.
*/
class DetachedTasks {
public:
DetachedTasks();
~DetachedTasks();
void WaitForAllTasks();
static void AddTask(std::function<void()> task);
private:
static DetachedTasks* instance;
std::condition_variable cv;
std::mutex mutex;
int count = 0;
};
} // namespace Common

View File

@@ -705,8 +705,6 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
#endif
paths.emplace(UserPath::SDMCDir, user_path + SDMC_DIR DIR_SEP);
paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
// TODO: Put the logs in a better location for each OS

View File

@@ -29,8 +29,6 @@ enum class UserPath {
NANDDir,
RootDir,
SDMCDir,
LoadDir,
DumpDir,
SysDataDir,
UserDir,
};
@@ -285,7 +283,7 @@ private:
template <typename T>
void OpenFStream(T& fstream, const std::string& filename, std::ios_base::openmode openmode) {
#ifdef _MSC_VER
fstream.open(Common::UTF8ToUTF16W(filename).c_str(), openmode);
fstream.open(Common::UTF8ToTStr(filename).c_str(), openmode);
#else
fstream.open(filename.c_str(), openmode);
#endif

View File

@@ -18,25 +18,6 @@ u8 ToHexNibble(char c1) {
return 0;
}
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
std::vector<u8> out(str.size() / 2);
if (little_endian) {
for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
} else {
for (std::size_t i = 0; i < str.size(); i += 2)
out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
}
return out;
}
std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
std::string out;
for (u8 c : vector)
out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
return out;
}
std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
if (len != 32) {
LOG_ERROR(Common,

View File

@@ -7,7 +7,6 @@
#include <array>
#include <cstddef>
#include <string>
#include <vector>
#include <fmt/format.h>
#include "common/common_types.h"
@@ -15,8 +14,6 @@ namespace Common {
u8 ToHexNibble(char c1);
std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
template <std::size_t Size, bool le = false>
std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
@@ -30,8 +27,6 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
return out;
}
std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
template <std::size_t Size>
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
std::string out;

View File

@@ -183,7 +183,6 @@ void FileBackend::Write(const Entry& entry) {
SUB(Service, FS) \
SUB(Service, GRC) \
SUB(Service, HID) \
SUB(Service, IRS) \
SUB(Service, LBL) \
SUB(Service, LDN) \
SUB(Service, LDR) \

View File

@@ -70,7 +70,6 @@ enum class Class : ClassType {
Service_FS, ///< The FS (Filesystem) service
Service_GRC, ///< The game recording service
Service_HID, ///< The HID (Human interface device) service
Service_IRS, ///< The IRS service
Service_LBL, ///< The LBL (LCD backlight) service
Service_LDN, ///< The LDN (Local domain network) service
Service_LDR, ///< The loader service

View File

@@ -31,7 +31,7 @@ std::string FormatLogMessage(const Entry& entry) {
}
void PrintMessage(const Entry& entry) {
const auto str = FormatLogMessage(entry).append(1, '\n');
auto str = FormatLogMessage(entry) + '\n';
fputs(str.c_str(), stderr);
}

View File

@@ -20,15 +20,7 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
/// A placeholder for empty param packages to avoid empty strings
/// (they may be recognized as "not set" by some frontend libraries like qt)
constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
ParamPackage::ParamPackage(const std::string& serialized) {
if (serialized == EMPTY_PLACEHOLDER) {
return;
}
std::vector<std::string> pairs;
Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
@@ -54,7 +46,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
std::string ParamPackage::Serialize() const {
if (data.empty())
return EMPTY_PLACEHOLDER;
return "";
std::string result;
@@ -128,12 +120,4 @@ bool ParamPackage::Has(const std::string& key) const {
return data.find(key) != data.end();
}
void ParamPackage::Erase(const std::string& key) {
data.erase(key);
}
void ParamPackage::Clear() {
data.clear();
}
} // namespace Common

View File

@@ -32,8 +32,6 @@ public:
void Set(const std::string& key, int value);
void Set(const std::string& key, float value);
bool Has(const std::string& key) const;
void Erase(const std::string& key);
void Clear();
private:
DataType data;

View File

@@ -5,7 +5,6 @@
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <codecvt>
#include <cstdio>
#include <cstdlib>
#include <cstring>
@@ -14,7 +13,11 @@
#include "common/string_util.h"
#ifdef _WIN32
#include <codecvt>
#include <windows.h>
#include "common/common_funcs.h"
#else
#include <iconv.h>
#endif
namespace Common {
@@ -192,9 +195,11 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
return result;
}
#ifdef _WIN32
std::string UTF16ToUTF8(const std::u16string& input) {
#ifdef _MSC_VER
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
#if _MSC_VER >= 1900
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
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);
@@ -205,8 +210,8 @@ std::string UTF16ToUTF8(const std::u16string& input) {
}
std::u16string UTF8ToUTF16(const std::string& input) {
#ifdef _MSC_VER
// Workaround for missing char16_t/char32_t instantiations in MSVC2017
#if _MSC_VER >= 1900
// Workaround for missing char16_t/char32_t instantiations in MSVC2015
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());
@@ -216,7 +221,6 @@ std::u16string UTF8ToUTF16(const std::string& 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);
@@ -257,6 +261,124 @@ std::wstring UTF8ToUTF16W(const std::string& input) {
return CPToUTF16(CP_UTF8, input);
}
std::string SHIFTJISToUTF8(const std::string& input) {
return UTF16ToUTF8(CPToUTF16(932, input));
}
std::string CP1252ToUTF8(const std::string& input) {
return UTF16ToUTF8(CPToUTF16(1252, input));
}
#else
template <typename T>
static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& input) {
iconv_t const conv_desc = iconv_open("UTF-8", fromcode);
if ((iconv_t)(-1) == conv_desc) {
LOG_ERROR(Common, "Iconv initialization failure [{}]: {}", fromcode, strerror(errno));
iconv_close(conv_desc);
return {};
}
const std::size_t in_bytes = sizeof(T) * input.size();
// Multiply by 4, which is the max number of bytes to encode a codepoint
const std::size_t out_buffer_size = 4 * in_bytes;
std::string out_buffer(out_buffer_size, '\0');
auto src_buffer = &input[0];
std::size_t src_bytes = in_bytes;
auto dst_buffer = &out_buffer[0];
std::size_t dst_bytes = out_buffer.size();
while (0 != src_bytes) {
std::size_t const iconv_result =
iconv(conv_desc, (char**)(&src_buffer), &src_bytes, &dst_buffer, &dst_bytes);
if (static_cast<std::size_t>(-1) == iconv_result) {
if (EILSEQ == errno || EINVAL == errno) {
// Try to skip the bad character
if (0 != src_bytes) {
--src_bytes;
++src_buffer;
}
} else {
LOG_ERROR(Common, "iconv failure [{}]: {}", fromcode, strerror(errno));
break;
}
}
}
std::string result;
out_buffer.resize(out_buffer_size - dst_bytes);
out_buffer.swap(result);
iconv_close(conv_desc);
return result;
}
std::u16string UTF8ToUTF16(const std::string& input) {
iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8");
if ((iconv_t)(-1) == conv_desc) {
LOG_ERROR(Common, "Iconv initialization failure [UTF-8]: {}", strerror(errno));
iconv_close(conv_desc);
return {};
}
const std::size_t in_bytes = sizeof(char) * input.size();
// Multiply by 4, which is the max number of bytes to encode a codepoint
const std::size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes;
std::u16string out_buffer(out_buffer_size, char16_t{});
char* src_buffer = const_cast<char*>(&input[0]);
std::size_t src_bytes = in_bytes;
char* dst_buffer = (char*)(&out_buffer[0]);
std::size_t dst_bytes = out_buffer.size();
while (0 != src_bytes) {
std::size_t const iconv_result =
iconv(conv_desc, &src_buffer, &src_bytes, &dst_buffer, &dst_bytes);
if (static_cast<std::size_t>(-1) == iconv_result) {
if (EILSEQ == errno || EINVAL == errno) {
// Try to skip the bad character
if (0 != src_bytes) {
--src_bytes;
++src_buffer;
}
} else {
LOG_ERROR(Common, "iconv failure [UTF-8]: {}", strerror(errno));
break;
}
}
}
std::u16string result;
out_buffer.resize(out_buffer_size - dst_bytes);
out_buffer.swap(result);
iconv_close(conv_desc);
return result;
}
std::string UTF16ToUTF8(const std::u16string& input) {
return CodeToUTF8("UTF-16LE", input);
}
std::string CP1252ToUTF8(const std::string& input) {
// return CodeToUTF8("CP1252//TRANSLIT", input);
// return CodeToUTF8("CP1252//IGNORE", input);
return CodeToUTF8("CP1252", input);
}
std::string SHIFTJISToUTF8(const std::string& input) {
// return CodeToUTF8("CP932", input);
return CodeToUTF8("SJIS", input);
}
#endif
std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) {

View File

@@ -72,10 +72,31 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
std::string UTF16ToUTF8(const std::u16string& input);
std::u16string UTF8ToUTF16(const std::string& input);
std::string CP1252ToUTF8(const std::string& str);
std::string SHIFTJISToUTF8(const std::string& str);
#ifdef _WIN32
std::string UTF16ToUTF8(const std::wstring& input);
std::wstring UTF8ToUTF16W(const std::string& str);
#ifdef _UNICODE
inline std::string TStrToUTF8(const std::wstring& str) {
return UTF16ToUTF8(str);
}
inline std::wstring UTF8ToTStr(const std::string& str) {
return UTF8ToUTF16W(str);
}
#else
inline std::string TStrToUTF8(const std::string& str) {
return str;
}
inline std::string UTF8ToTStr(const std::string& str) {
return str;
}
#endif
#endif
/**

View File

@@ -87,6 +87,14 @@ private:
void SleepCurrentThread(int ms);
void SwitchCurrentThread(); // On Linux, this is equal to sleep 1ms
// Use this function during a spin-wait to make the current thread
// relax while another thread is working. This may be more efficient
// than using events because event functions use kernel calls.
inline void YieldCPU() {
std::this_thread::yield();
}
void SetCurrentThreadName(const char* name);
} // namespace Common

View File

@@ -1,24 +0,0 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
namespace Common {
struct WebResult {
enum class Code : u32 {
Success,
InvalidURL,
CredentialsMissing,
LibError,
HttpError,
WrongContent,
NoWebservice,
};
Code result_code;
std::string result_string;
std::string returned_data;
};
} // namespace Common

View File

@@ -18,8 +18,6 @@ add_library(core STATIC
crypto/encryption_layer.h
crypto/key_manager.cpp
crypto/key_manager.h
crypto/partition_data_manager.cpp
crypto/partition_data_manager.h
crypto/ctr_encryption_layer.cpp
crypto/ctr_encryption_layer.h
crypto/xts_encryption_layer.cpp
@@ -34,10 +32,6 @@ add_library(core STATIC
file_sys/control_metadata.h
file_sys/directory.h
file_sys/errors.h
file_sys/fsmitm_romfsbuild.cpp
file_sys/fsmitm_romfsbuild.h
file_sys/ips_layer.cpp
file_sys/ips_layer.h
file_sys/mode.h
file_sys/nca_metadata.cpp
file_sys/nca_metadata.h
@@ -65,14 +59,10 @@ add_library(core STATIC
file_sys/vfs.h
file_sys/vfs_concat.cpp
file_sys/vfs_concat.h
file_sys/vfs_layered.cpp
file_sys/vfs_layered.h
file_sys/vfs_offset.cpp
file_sys/vfs_offset.h
file_sys/vfs_real.cpp
file_sys/vfs_real.h
file_sys/vfs_static.h
file_sys/vfs_types.h
file_sys/vfs_vector.cpp
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
@@ -399,10 +389,6 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
if (ENABLE_WEB_SERVICE)
add_definitions(-DENABLE_WEB_SERVICE)
target_link_libraries(core PUBLIC json-headers web_service)
endif()
if (ARCHITECTURE_x86_64)
target_sources(core PRIVATE

View File

@@ -22,16 +22,10 @@ public:
std::array<u64, 31> cpu_registers;
u64 sp;
u64 pc;
u32 pstate;
std::array<u8, 4> padding;
u64 pstate;
std::array<u128, 32> vector_registers;
u32 fpcr;
u32 fpsr;
u64 tpidr;
u64 fpcr;
};
// Internally within the kernel, it expects the AArch64 version of the
// thread context to be 800 bytes in size.
static_assert(sizeof(ThreadContext) == 0x320);
/// Runs the CPU until an event happens
virtual void Run() = 0;

View File

@@ -86,7 +86,7 @@ public:
parent.jit->HaltExecution();
parent.SetPC(pc);
Kernel::Thread* thread = Kernel::GetCurrentThread();
parent.SaveContext(thread->GetContext());
parent.SaveContext(thread->context);
GDBStub::Break();
GDBStub::SendTrap(thread, 5);
return;
@@ -129,8 +129,7 @@ public:
};
std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
auto* current_process = Core::CurrentProcess();
auto** const page_table = current_process->VMManager().page_table.pointers.data();
auto** const page_table = Core::CurrentProcess()->vm_manager.page_table.pointers.data();
Dynarmic::A64::UserConfig config;
@@ -139,7 +138,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
// Memory
config.page_table = reinterpret_cast<void**>(page_table);
config.page_table_address_space_bits = current_process->VMManager().GetAddressSpaceWidth();
config.page_table_address_space_bits = Memory::ADDRESS_SPACE_BITS;
config.silently_mirror_page_table = false;
// Multi-process state
@@ -175,7 +174,7 @@ ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
std::size_t core_index)
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
ThreadContext ctx{};
ThreadContext ctx;
inner_unicorn.SaveContext(ctx);
PageTableChanged();
LoadContext(ctx);
@@ -247,19 +246,15 @@ void ARM_Dynarmic::SaveContext(ThreadContext& ctx) {
ctx.pstate = jit->GetPstate();
ctx.vector_registers = jit->GetVectors();
ctx.fpcr = jit->GetFpcr();
ctx.fpsr = jit->GetFpsr();
ctx.tpidr = cb->tpidr_el0;
}
void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) {
jit->SetRegisters(ctx.cpu_registers);
jit->SetSP(ctx.sp);
jit->SetPC(ctx.pc);
jit->SetPstate(ctx.pstate);
jit->SetPstate(static_cast<u32>(ctx.pstate));
jit->SetVectors(ctx.vector_registers);
jit->SetFpcr(ctx.fpcr);
jit->SetFpsr(ctx.fpsr);
SetTPIDR_EL0(ctx.tpidr);
jit->SetFpcr(static_cast<u32>(ctx.fpcr));
}
void ARM_Dynarmic::PrepareReschedule() {

View File

@@ -195,7 +195,7 @@ void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
}
Kernel::Thread* thread = Kernel::GetCurrentThread();
SaveContext(thread->GetContext());
SaveContext(thread->context);
if (last_bkpt_hit || GDBStub::GetCpuStepFlag()) {
last_bkpt_hit = false;
GDBStub::Break();

View File

@@ -64,7 +64,7 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
if (concat.empty())
return nullptr;
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
return FileSys::ConcatenateFiles(concat, dir->GetName());
}
return vfs->OpenFile(path, FileSys::Mode::Read);
@@ -136,8 +136,7 @@ struct System::Impl {
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
auto main_process = Kernel::Process::Create(kernel, "main");
kernel.MakeCurrentProcess(main_process.get());
kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main"));
cpu_barrier = std::make_shared<CpuBarrier>();
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
@@ -203,7 +202,7 @@ struct System::Impl {
return init_result;
}
const Loader::ResultStatus load_result{app_loader->Load(*kernel.CurrentProcess())};
const Loader::ResultStatus load_result{app_loader->Load(kernel.CurrentProcess())};
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", static_cast<int>(load_result));
Shutdown();
@@ -362,11 +361,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_ind
return impl->cpu_cores[core_index]->Scheduler();
}
Kernel::Process* System::CurrentProcess() {
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
return impl->kernel.CurrentProcess();
}
const Kernel::Process* System::CurrentProcess() const {
const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
return impl->kernel.CurrentProcess();
}

View File

@@ -174,11 +174,11 @@ public:
/// Gets the scheduler for the CPU core with the specified index
const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index);
/// Provides a pointer to the current process
Kernel::Process* CurrentProcess();
/// Provides a reference to the current process
Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
/// Provides a constant pointer to the current process.
const Kernel::Process* CurrentProcess() const;
/// Provides a constant reference to the current process.
const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const;
/// Provides a reference to the kernel instance.
Kernel::KernelCore& Kernel();
@@ -246,7 +246,7 @@ inline TelemetrySession& Telemetry() {
return System::GetInstance().TelemetrySession();
}
inline Kernel::Process* CurrentProcess() {
inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
return System::GetInstance().CurrentProcess();
}

View File

@@ -55,16 +55,16 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
arm_interface = std::make_shared<ARM_Dynarmic>(exclusive_monitor, core_index);
#else
arm_interface = std::make_unique<ARM_Unicorn>();
arm_interface = std::make_shared<ARM_Unicorn>();
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
} else {
arm_interface = std::make_unique<ARM_Unicorn>();
arm_interface = std::make_shared<ARM_Unicorn>();
}
scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface);
scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get());
}
Cpu::~Cpu() = default;

View File

@@ -76,7 +76,7 @@ public:
private:
void Reschedule();
std::unique_ptr<ARM_Interface> arm_interface;
std::shared_ptr<ARM_Interface> arm_interface;
std::shared_ptr<CpuBarrier> cpu_barrier;
std::shared_ptr<Kernel::Scheduler> scheduler;

View File

@@ -4,56 +4,23 @@
#include <algorithm>
#include <array>
#include <bitset>
#include <cctype>
#include <fstream>
#include <locale>
#include <map>
#include <sstream>
#include <string_view>
#include <tuple>
#include <vector>
#include <mbedtls/bignum.h>
#include <mbedtls/cipher.h>
#include <mbedtls/cmac.h>
#include <mbedtls/sha256.h>
#include "common/common_funcs.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Core::Crypto {
constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
using namespace Common;
const std::array<SHA256Hash, 2> eticket_source_hashes{
"B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
"E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
};
const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
{{S128KeyType::Master, 0}, "master_key_"},
{{S128KeyType::Package1, 0}, "package1_key_"},
{{S128KeyType::Package2, 0}, "package2_key_"},
{{S128KeyType::Titlekek, 0}, "titlekek_"},
{{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
{{S128KeyType::Keyblob, 0}, "keyblob_key_"},
{{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
Key128 out{};
@@ -70,77 +37,6 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K
return out;
}
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) {
AESCipher<Key128> sbk_cipher(sbk, Mode::ECB);
AESCipher<Key128> tsec_cipher(tsec, Mode::ECB);
tsec_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
sbk_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
return source;
}
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source) {
Key128 master_root;
std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128));
AESCipher<Key128> master_cipher(master_root, Mode::ECB);
Key128 master{};
master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt);
return master;
}
std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob,
const Key128& key) {
std::array<u8, 0x90> keyblob;
AESCipher<Key128> cipher(key, Mode::CTR);
cipher.SetIV(std::vector<u8>(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20));
cipher.Transcode(encrypted_keyblob.data() + 0x20, keyblob.size(), keyblob.data(), Op::Decrypt);
return keyblob;
}
void KeyManager::DeriveGeneralPurposeKeys(u8 crypto_revision) {
const auto kek_generation_source =
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
const auto key_generation_source =
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
if (HasKey(S128KeyType::Master, crypto_revision)) {
for (auto kak_type :
{KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) {
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(kak_type))) {
const auto source =
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(kak_type));
const auto kek =
GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision),
kek_generation_source, key_generation_source);
SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast<u64>(kak_type));
}
}
AESCipher<Key128> master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB);
for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) {
if (HasKey(S128KeyType::Source, static_cast<u64>(key_type))) {
Key128 key{};
master_cipher.Transcode(
GetKey(S128KeyType::Source, static_cast<u64>(key_type)).data(), key.size(),
key.data(), Op::Decrypt);
SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek
: S128KeyType::Package2,
key, crypto_revision);
}
}
}
}
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
Key128 mac_key{};
mac_cipher.Transcode(mac_source.data(), mac_key.size(), mac_key.data(), Op::Decrypt);
return mac_key;
}
boost::optional<Key128> DeriveSDSeed() {
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000043",
@@ -175,24 +71,23 @@ boost::optional<Key128> DeriveSDSeed() {
return seed;
}
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) {
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)))
return Loader::ResultStatus::ErrorMissingSDKEKSource;
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)))
return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
const auto sd_kek_source =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK));
const auto aes_kek_gen =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration));
const auto aes_key_gen =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
const auto master_00 = keys.GetKey(S128KeyType::Master);
const auto sd_kek =
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
keys.SetKey(S128KeyType::SDKek, sd_kek);
if (!keys.HasKey(S128KeyType::SDSeed))
return Loader::ResultStatus::ErrorMissingSDSeed;
@@ -223,141 +118,9 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
return source; ///< Return unaltered source to satisfy output requirement.
});
keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save));
keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA));
return Loader::ResultStatus::Success;
}
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
if (!ticket_save.IsOpen())
return {};
std::vector<u8> buffer(ticket_save.GetSize());
ticket_save.ReadBytes(buffer.data(), buffer.size());
std::vector<TicketRaw> out;
u32 magic{};
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {
out.emplace_back();
auto& next = out.back();
std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
offset += next.size();
}
}
return out;
}
template <size_t size>
static std::array<u8, size> operator^(const std::array<u8, size>& lhs,
const std::array<u8, size>& rhs) {
std::array<u8, size> out{};
std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), std::bit_xor<>());
return out;
}
template <size_t target_size, size_t in_size>
static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
std::array<u8, in_size + 4> seed_exp{};
std::memcpy(seed_exp.data(), seed.data(), in_size);
std::vector<u8> out;
size_t i = 0;
while (out.size() < target_size) {
out.resize(out.size() + 0x20);
seed_exp[in_size + 3] = i;
mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0);
++i;
}
std::array<u8, target_size> target;
std::memcpy(target.data(), out.data(), target_size);
return target;
}
template <size_t size>
static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
u64 offset = 0;
for (size_t i = 0x20; i < data.size() - 0x10; ++i) {
if (data[i] == 0x1) {
offset = i + 1;
break;
} else if (data[i] != 0x0) {
return boost::none;
}
}
return offset;
}
boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
const RSAKeyPair<2048>& key) {
u32 cert_authority;
std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
if (cert_authority == 0)
return boost::none;
if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't'))
LOG_INFO(Crypto,
"Attempting to parse ticket with non-standard certificate authority {:08X}.",
cert_authority);
Key128 rights_id;
std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
if (rights_id == Key128{})
return boost::none;
Key128 key_temp{};
if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
return std::make_pair(rights_id, key_temp);
}
mbedtls_mpi D; // RSA Private Exponent
mbedtls_mpi N; // RSA Modulus
mbedtls_mpi S; // Input
mbedtls_mpi M; // Output
mbedtls_mpi_init(&D);
mbedtls_mpi_init(&N);
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&M);
mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
std::array<u8, 0x100> rsa_step;
mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size());
u8 m_0 = rsa_step[0];
std::array<u8, 0x20> m_1;
std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size());
std::array<u8, 0xDF> m_2;
std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
if (m_0 != 0)
return boost::none;
m_1 = m_1 ^ MGF1<0x20>(m_2);
m_2 = m_2 ^ MGF1<0xDF>(m_1);
const auto offset = FindTicketOffset(m_2);
if (offset == boost::none)
return boost::none;
ASSERT(offset.get() > 0);
std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size());
return std::make_pair(rights_id, key_temp);
}
KeyManager::KeyManager() {
// Initialize keys
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
@@ -374,15 +137,6 @@ KeyManager::KeyManager() {
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false);
}
static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
if (base.size() < begin + length)
return false;
return std::all_of(base.begin() + begin, base.begin() + begin + length,
[](u8 c) { return std::isdigit(c); });
}
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
@@ -404,9 +158,6 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
if (out[0].compare(0, 1, "#") == 0)
continue;
if (is_title_keys) {
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
u128 rights_id{};
@@ -423,50 +174,6 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
const auto index = s256_file_id.at(out[0]);
Key256 key = Common::HexStringToArray<32>(out[1]);
s256_keys[{index.type, index.field1, index.field2}] = key;
} else if (out[0].compare(0, 8, "keyblob_") == 0 &&
out[0].compare(0, 9, "keyblob_k") != 0) {
if (!ValidCryptoRevisionString(out[0], 8, 2))
continue;
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
if (!ValidCryptoRevisionString(out[0], 18, 2))
continue;
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
} else {
for (const auto& kv : KEYS_VARIABLE_LENGTH) {
if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
continue;
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
const auto index =
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
const auto sub = kv.first.second;
if (sub == 0) {
s128_keys[{kv.first.first, index, 0}] =
Common::HexStringToArray<16>(out[1]);
} else {
s128_keys[{kv.first.first, kv.first.second, index}] =
Common::HexStringToArray<16>(out[1]);
}
break;
}
}
static constexpr std::array<const char*, 3> kak_names = {
"key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"};
for (size_t j = 0; j < kak_names.size(); ++j) {
const auto& match = kak_names[j];
if (out[0].compare(0, std::strlen(match), match) == 0) {
const auto index =
std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
s128_keys[{S128KeyType::KeyArea, index, j}] =
Common::HexStringToArray<16>(out[1]);
}
}
}
}
}
@@ -480,28 +187,6 @@ void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string&
LoadFromFile(dir2 + DIR_SEP + filename, title);
}
bool KeyManager::BaseDeriveNecessary() const {
const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
return !HasKey(key_type, index1, index2);
};
if (check_key_existence(S256KeyType::Header))
return true;
for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
if (check_key_existence(S128KeyType::Master, i) ||
check_key_existence(S128KeyType::KeyArea, i,
static_cast<u64>(KeyAreaKeyType::Application)) ||
check_key_existence(S128KeyType::KeyArea, i, static_cast<u64>(KeyAreaKeyType::Ocean)) ||
check_key_existence(S128KeyType::KeyArea, i,
static_cast<u64>(KeyAreaKeyType::System)) ||
check_key_existence(S128KeyType::Titlekek, i))
return true;
}
return false;
}
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
return s128_keys.find({id, field1, field2}) != s128_keys.end();
}
@@ -522,30 +207,13 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
return s256_keys.at({id, field1, field2});
}
Key256 KeyManager::GetBISKey(u8 partition_id) const {
Key256 out{};
for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) {
if (HasKey(S128KeyType::BIS, partition_id, static_cast<u64>(bis_type))) {
std::memcpy(
out.data() + sizeof(Key128) * static_cast<u64>(bis_type),
s128_keys.at({S128KeyType::BIS, partition_id, static_cast<u64>(bis_type)}).data(),
sizeof(Key128));
}
}
return out;
}
template <size_t Size>
void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
template <std::size_t Size>
void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
const std::array<u8, Size>& key) {
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
std::string filename = "title.keys_autogenerated";
if (category == KeyCategory::Standard)
if (!title_key)
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
else if (category == KeyCategory::Console)
filename = "console.keys_autogenerated";
const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename);
FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename);
std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app);
@@ -559,7 +227,7 @@ void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
}
file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key);
}
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
@@ -569,15 +237,8 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
Key128 rights_id;
std::memcpy(rights_id.data(), &field2, sizeof(u64));
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);
WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
}
auto category = KeyCategory::Standard;
if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC ||
id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) {
category = KeyCategory::Console;
}
const auto iter2 = std::find_if(
s128_file_id.begin(), s128_file_id.end(),
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
@@ -585,30 +246,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
std::tie(id, field1, field2);
});
if (iter2 != s128_file_id.end())
WriteKeyToFile(category, iter2->first, key);
// Variable cases
if (id == S128KeyType::KeyArea) {
static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}",
"key_area_key_ocean_{:02X}",
"key_area_key_system_{:02X}"};
WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
} else if (id == S128KeyType::Master) {
WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
} else if (id == S128KeyType::Package1) {
WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key);
} else if (id == S128KeyType::Package2) {
WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key);
} else if (id == S128KeyType::Titlekek) {
WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key);
} else if (id == S128KeyType::Keyblob) {
WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key);
} else if (id == S128KeyType::KeyblobMAC) {
WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key);
} else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) {
WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key);
}
WriteKeyToFile(false, iter2->first, key);
s128_keys[{id, field1, field2}] = key;
}
@@ -622,7 +260,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
std::tie(id, field1, field2);
});
if (iter != s256_file_id.end())
WriteKeyToFile(KeyCategory::Standard, iter->first, key);
WriteKeyToFile(false, iter->first, key);
s256_keys[{id, field1, field2}] = key;
}
@@ -652,388 +290,59 @@ void KeyManager::DeriveSDSeedLazy() {
SetKey(S128KeyType::SDSeed, res.get());
}
static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
Key128 out{};
mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(),
key.size() * 8, source, size, out.data());
return out;
}
void KeyManager::DeriveBase() {
if (!BaseDeriveNecessary())
return;
if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC))
return;
const auto has_bis = [this](u64 id) {
return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Tweak));
};
const auto copy_bis = [this](u64 id_from, u64 id_to) {
SetKey(S128KeyType::BIS,
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Crypto)), id_to,
static_cast<u64>(BISKeyType::Crypto));
SetKey(S128KeyType::BIS,
GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Tweak)), id_to,
static_cast<u64>(BISKeyType::Tweak));
};
if (has_bis(2) && !has_bis(3))
copy_bis(2, 3);
else if (has_bis(3) && !has_bis(2))
copy_bis(3, 2);
std::bitset<32> revisions(0xFFFFFFFF);
for (size_t i = 0; i < revisions.size(); ++i) {
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i) ||
encrypted_keyblobs[i] == std::array<u8, 0xB0>{}) {
revisions.reset(i);
}
}
if (!revisions.any())
return;
const auto sbk = GetKey(S128KeyType::SecureBoot);
const auto tsec = GetKey(S128KeyType::TSEC);
const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master));
for (size_t i = 0; i < revisions.size(); ++i) {
if (!revisions[i])
continue;
// Derive keyblob key
const auto key = DeriveKeyblobKey(
sbk, tsec, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i));
SetKey(S128KeyType::Keyblob, key, i);
// Derive keyblob MAC key
if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)))
continue;
const auto mac_key = DeriveKeyblobMACKey(
key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
SetKey(S128KeyType::KeyblobMAC, mac_key, i);
Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0)
continue;
// Decrypt keyblob
if (keyblobs[i] == std::array<u8, 0x90>{}) {
keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i),
keyblobs[i]);
}
Key128 package1;
std::memcpy(package1.data(), keyblobs[i].data() + 0x80, sizeof(Key128));
SetKey(S128KeyType::Package1, package1, i);
// Derive master key
if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master))) {
SetKey(S128KeyType::Master,
DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source,
static_cast<u64>(SourceKeyType::Master))),
i);
}
}
revisions.set();
for (size_t i = 0; i < revisions.size(); ++i) {
if (!HasKey(S128KeyType::Master, i))
revisions.reset(i);
}
if (!revisions.any())
return;
for (size_t i = 0; i < revisions.size(); ++i) {
if (!revisions[i])
continue;
// Derive general purpose keys
DeriveGeneralPurposeKeys(i);
}
if (HasKey(S128KeyType::Master, 0) &&
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)) &&
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)) &&
HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)) &&
HasKey(S256KeyType::HeaderSource)) {
const auto header_kek = GenerateKeyEncryptionKey(
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)),
GetKey(S128KeyType::Master, 0),
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)),
GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)));
SetKey(S128KeyType::HeaderKek, header_kek);
AESCipher<Key128> header_cipher(header_kek, Mode::ECB);
Key256 out = GetKey(S256KeyType::HeaderSource);
header_cipher.Transcode(out.data(), out.size(), out.data(), Op::Decrypt);
SetKey(S256KeyType::Header, out);
}
}
void KeyManager::DeriveETicket(PartitionDataManager& data) {
// ETicket keys
const auto es = Service::FileSystem::GetUnionContents()->GetEntry(
0x0100000000000033, FileSys::ContentRecordType::Program);
if (es == nullptr)
return;
const auto exefs = es->GetExeFS();
if (exefs == nullptr)
return;
const auto main = exefs->GetFile("main");
if (main == nullptr)
return;
const auto bytes = main->ReadAllBytes();
const auto eticket_kek = FindKeyFromHex16(bytes, eticket_source_hashes[0]);
const auto eticket_kekek = FindKeyFromHex16(bytes, eticket_source_hashes[1]);
const auto seed3 = data.GetRSAKekSeed3();
const auto mask0 = data.GetRSAKekMask0();
if (eticket_kek != Key128{})
SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
if (eticket_kekek != Key128{}) {
SetKey(S128KeyType::Source, eticket_kekek,
static_cast<size_t>(SourceKeyType::ETicketKekek));
}
if (seed3 != Key128{})
SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
if (mask0 != Key128{})
SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
mask0 == Key128{}) {
return;
}
Key128 rsa_oaep_kek{};
std::transform(seed3.begin(), seed3.end(), mask0.begin(), rsa_oaep_kek.begin(),
std::bit_xor<>());
if (rsa_oaep_kek == Key128{})
return;
SetKey(S128KeyType::Source, rsa_oaep_kek,
static_cast<u64>(SourceKeyType::RSAOaepKekGeneration));
Key128 temp_kek{};
Key128 temp_kekek{};
Key128 eticket_final{};
// Derive ETicket RSA Kek
AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB);
es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt);
AESCipher<Key128> es_kekek(temp_kek, Mode::ECB);
es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt);
AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
if (eticket_final == Key128{})
return;
SetKey(S128KeyType::ETicketRSAKek, eticket_final);
// Titlekeys
data.DecryptProdInfo(GetBISKey(0));
const auto eticket_extended_kek = data.GetETicketExtendedKek();
std::vector<u8> extended_iv(0x10);
std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
std::array<u8, 0x230> extended_dec{};
AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
rsa_1.SetIV(extended_iv);
rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
extended_dec.data(), Op::Decrypt);
RSAKeyPair<2048> rsa_key{};
std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/80000000000000e1",
"rb+");
const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/80000000000000e2",
"rb+");
auto res = GetTicketblob(save1);
const auto res2 = GetTicketblob(save2);
std::copy(res2.begin(), res2.end(), std::back_inserter(res));
for (const auto& raw : res) {
const auto pair = ParseTicket(raw, rsa_key);
if (pair == boost::none)
continue;
const auto& [rid, key] = pair.value();
u128 rights_id;
std::memcpy(rights_id.data(), rid.data(), rid.size());
SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
if (key == Key128{})
return;
SetKey(id, key, field1, field2);
}
void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
if (key == Key256{})
return;
SetKey(id, key, field1, field2);
}
void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
if (!BaseDeriveNecessary())
return;
if (!data.HasBoot0())
return;
for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{})
continue;
encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
encrypted_keyblobs[i]);
}
SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
static_cast<u64>(SourceKeyType::Package2));
SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(),
static_cast<u64>(SourceKeyType::AESKekGeneration));
SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(),
static_cast<u64>(SourceKeyType::Titlekek));
SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(),
static_cast<u64>(SourceKeyType::Master));
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(),
static_cast<u64>(SourceKeyType::KeyblobMAC));
for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) {
SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i),
static_cast<u64>(SourceKeyType::Keyblob), i);
}
if (data.HasFuses())
SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
DeriveBase();
Key128 latest_master{};
for (s8 i = 0x1F; i >= 0; --i) {
if (GetKey(S128KeyType::Master, static_cast<u8>(i)) != Key128{}) {
latest_master = GetKey(S128KeyType::Master, static_cast<u8>(i));
break;
}
}
const auto masters = data.GetTZMasterKeys(latest_master);
for (size_t i = 0; i < masters.size(); ++i) {
if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i))
SetKey(S128KeyType::Master, masters[i], i);
}
DeriveBase();
if (!data.HasPackage2())
return;
std::array<Key128, 0x20> package2_keys{};
for (size_t i = 0; i < package2_keys.size(); ++i) {
if (HasKey(S128KeyType::Package2, i))
package2_keys[i] = GetKey(S128KeyType::Package2, i);
}
data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyApplicationSource(),
static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Application));
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(),
static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Ocean));
SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(),
static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::System));
SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(),
static_cast<u64>(SourceKeyType::SDKek));
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(),
static_cast<u64>(SDKeyType::Save));
SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(),
static_cast<u64>(SDKeyType::NCA));
SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(),
static_cast<u64>(SourceKeyType::HeaderKek));
SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource());
SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(),
static_cast<u64>(SourceKeyType::AESKeyGeneration));
DeriveBase();
}
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
{"master_key_00", {S128KeyType::Master, 0, 0}},
{"master_key_01", {S128KeyType::Master, 1, 0}},
{"master_key_02", {S128KeyType::Master, 2, 0}},
{"master_key_03", {S128KeyType::Master, 3, 0}},
{"master_key_04", {S128KeyType::Master, 4, 0}},
{"package1_key_00", {S128KeyType::Package1, 0, 0}},
{"package1_key_01", {S128KeyType::Package1, 1, 0}},
{"package1_key_02", {S128KeyType::Package1, 2, 0}},
{"package1_key_03", {S128KeyType::Package1, 3, 0}},
{"package1_key_04", {S128KeyType::Package1, 4, 0}},
{"package2_key_00", {S128KeyType::Package2, 0, 0}},
{"package2_key_01", {S128KeyType::Package2, 1, 0}},
{"package2_key_02", {S128KeyType::Package2, 2, 0}},
{"package2_key_03", {S128KeyType::Package2, 3, 0}},
{"package2_key_04", {S128KeyType::Package2, 4, 0}},
{"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
{"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
{"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
{"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
{"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
{"eticket_rsa_kek_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
{"eticket_rsa_kekek_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
{"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
{"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
{"rsa_oaep_kek_generation_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
{"key_area_key_application_00",
{S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_01",
{S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_02",
{S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_03",
{S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_application_04",
{S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
{"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
{"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}},
{"aes_kek_generation_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}},
{"aes_key_generation_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
{"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
{"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
{"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
{"key_area_key_application_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Application)}},
{"key_area_key_ocean_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::Ocean)}},
{"key_area_key_system_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
static_cast<u64>(KeyAreaKeyType::System)}},
{"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
{"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}},
{"tsec_key", {S128KeyType::TSEC, 0, 0}},
{"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
{"sd_seed", {S128KeyType::SDSeed, 0, 0}},
{"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
{"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
{"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
{"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
{"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
{"header_kek", {S128KeyType::HeaderKek, 0, 0}},
{"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
};
const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
{"header_key", {S256KeyType::Header, 0, 0}},
{"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
{"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
{"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
{"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
{"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
};
} // namespace Core::Crypto

View File

@@ -5,18 +5,11 @@
#pragma once
#include <array>
#include <map>
#include <string>
#include <boost/container/flat_map.hpp>
#include <boost/optional.hpp>
#include <fmt/format.h>
#include "common/common_types.h"
#include "core/crypto/partition_data_manager.h"
#include "core/file_sys/vfs_types.h"
namespace FileUtil {
class IOFile;
}
namespace Loader {
enum class ResultStatus : u16;
@@ -29,30 +22,13 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;
using TicketRaw = std::array<u8, 0x400>;
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
template <size_t bit_size, size_t byte_size = (bit_size >> 3)>
struct RSAKeyPair {
std::array<u8, byte_size> encryption_key;
std::array<u8, byte_size> decryption_key;
std::array<u8, byte_size> modulus;
std::array<u8, 4> exponent;
};
enum class KeyCategory : u8 {
Standard,
Title,
Console,
};
static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
enum class S256KeyType : u64 {
SDKey, // f1=SDKeyType
Header, //
SDKeySource, // f1=SDKeyType
HeaderSource, //
Header, //
SDKeySource, // f1=SDKeyType
};
enum class S128KeyType : u64 {
@@ -65,14 +41,6 @@ enum class S128KeyType : u64 {
SDSeed, //
Titlekey, // f1=rights id LSB f2=rights id MSB
Source, // f1=source type, f2= sub id
Keyblob, // f1=crypto revision
KeyblobMAC, // f1=crypto revision
TSEC, //
SecureBoot, //
BIS, // f1=partition (0-3), f2=type {crypt, tweak}
HeaderKek, //
SDKek, //
RSAKek, //
};
enum class KeyAreaKeyType : u8 {
@@ -82,19 +50,9 @@ enum class KeyAreaKeyType : u8 {
};
enum class SourceKeyType : u8 {
SDKek, //
AESKekGeneration, //
AESKeyGeneration, //
RSAOaepKekGeneration, //
Master, //
Keyblob, // f2=crypto revision
KeyAreaKey, // f2=KeyAreaKeyType
Titlekek, //
Package2, //
HeaderKek, //
KeyblobMAC, //
ETicketKek, //
ETicketKekek, //
SDKEK,
AESKEKGeneration,
AESKeyGeneration,
};
enum class SDKeyType : u8 {
@@ -102,16 +60,6 @@ enum class SDKeyType : u8 {
NCA,
};
enum class BISKeyType : u8 {
Crypto,
Tweak,
};
enum class RSAKekType : u8 {
Mask0,
Seed3,
};
template <typename KeyType>
struct KeyIndex {
KeyType type;
@@ -143,8 +91,6 @@ public:
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
Key256 GetBISKey(u8 partition_id) const;
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
@@ -154,51 +100,23 @@ public:
// 8*43 and the private file to exist.
void DeriveSDSeedLazy();
bool BaseDeriveNecessary() const;
void DeriveBase();
void DeriveETicket(PartitionDataManager& data);
void PopulateFromPartitionData(PartitionDataManager& data);
private:
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
bool dev_mode;
void LoadFromFile(const std::string& filename, bool is_title_keys);
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title);
template <size_t Size>
void WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key);
void DeriveGeneralPurposeKeys(u8 crypto_revision);
void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
template <std::size_t Size>
void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key);
static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source);
Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source);
Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source);
std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob,
const Key128& key);
boost::optional<Key128> DeriveSDSeed();
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
// 0x140-0x144 is zero)
boost::optional<std::pair<Key128, Key128>> ParseTicket(
const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key);
Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys);
} // namespace Core::Crypto

View File

@@ -1,601 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// NOTE TO FUTURE MAINTAINERS:
// When a new version of switch cryptography is released,
// hash the new keyblob source and master key and add the hashes to
// the arrays below.
#include <algorithm>
#include <array>
#include <cctype>
#include <cstring>
#include <boost/optional/optional.hpp>
#include <mbedtls/sha256.h>
#include "common/assert.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/crypto/ctr_encryption_layer.h"
#include "core/crypto/key_manager.h"
#include "core/crypto/partition_data_manager.h"
#include "core/crypto/xts_encryption_layer.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_offset.h"
using namespace Common;
namespace Core::Crypto {
struct Package2Header {
std::array<u8, 0x100> signature;
Key128 header_ctr;
std::array<Key128, 4> section_ctr;
u32_le magic;
u32_le base_offset;
INSERT_PADDING_BYTES(4);
u8 version_max;
u8 version_min;
INSERT_PADDING_BYTES(2);
std::array<u32_le, 4> section_size;
std::array<u32_le, 4> section_offset;
std::array<SHA256Hash, 4> section_hash;
};
static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
struct INIHeader {
u32_le magic;
u32_le size;
u32_le process_count;
INSERT_PADDING_BYTES(4);
};
static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
struct SectionHeader {
u32_le offset;
u32_le size_decompressed;
u32_le size_compressed;
u32_le attribute;
};
static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size.");
struct KIPHeader {
u32_le magic;
std::array<char, 12> name;
u64_le title_id;
u32_le category;
u8 priority;
u8 core;
INSERT_PADDING_BYTES(1);
u8 flags;
std::array<SectionHeader, 6> sections;
std::array<u32, 0x20> capabilities;
};
static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
const std::array<SHA256Hash, 0x10> source_hashes{
"B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
"7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
"21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source
"FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source
"C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source
"04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source
"FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source
"1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source
"6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source
"D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source
"2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source
"1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source
"8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source
"D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3
"FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0
};
const std::array<SHA256Hash, 0x20> keyblob_source_hashes{
"8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00
"2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01
"61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02
"8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03
"95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04
"3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F
};
const std::array<SHA256Hash, 0x20> master_key_hashes{
"0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00
"4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01
"79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02
"4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03
"75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04
"EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E
"0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
};
static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) {
const auto data_size = in.size() - 0xC;
u32 compressed_size{};
u32 init_index{};
u32 additional_size{};
std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32));
std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32));
std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32));
std::vector<u8> out(in.size() + additional_size);
if (compressed_size == in.size())
std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size);
else
std::memcpy(out.data(), in.data(), compressed_size);
auto index = in.size() - init_index;
auto out_index = out.size();
while (out_index > 0) {
--index;
auto control = in[index];
for (size_t i = 0; i < 8; ++i) {
if ((control & 0x80) > 0) {
ASSERT(index >= 2);
index -= 2;
u64 segment_offset = in[index] | in[index + 1] << 8;
u64 segment_size = ((segment_offset >> 12) & 0xF) + 3;
segment_offset &= 0xFFF;
segment_offset += 3;
if (out_index < segment_size)
segment_size = out_index;
ASSERT(out_index >= segment_size);
out_index -= segment_size;
for (size_t j = 0; j < segment_size; ++j) {
ASSERT(out_index + j + segment_offset < out.size());
out[out_index + j] = out[out_index + j + segment_offset];
}
} else {
ASSERT(out_index >= 1);
--out_index;
--index;
out[out_index] = in[index];
}
control <<= 1;
if (out_index == 0)
return out;
}
}
return out;
}
static u8 CalculateMaxKeyblobSourceHash() {
for (s8 i = 0x1F; i >= 0; --i) {
if (keyblob_source_hashes[i] != SHA256Hash{})
return static_cast<u8>(i + 1);
}
return 0;
}
const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = CalculateMaxKeyblobSourceHash();
template <size_t key_size = 0x10>
std::array<u8, key_size> FindKeyFromHex(const std::vector<u8>& binary,
const std::array<u8, 0x20>& hash) {
if (binary.size() < key_size)
return {};
std::array<u8, 0x20> temp{};
for (size_t i = 0; i < binary.size() - key_size; ++i) {
mbedtls_sha256(binary.data() + i, key_size, temp.data(), 0);
if (temp != hash)
continue;
std::array<u8, key_size> out{};
std::memcpy(out.data(), binary.data() + i, key_size);
return out;
}
return {};
}
std::array<u8, 16> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 32> hash) {
return FindKeyFromHex<0x10>(binary, hash);
}
static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<u8>& binary,
const Key128& key) {
if (binary.size() < 0x10)
return {};
SHA256Hash temp{};
Key128 dec_temp{};
std::array<Key128, 0x20> out{};
AESCipher<Key128> cipher(key, Mode::ECB);
for (size_t i = 0; i < binary.size() - 0x10; ++i) {
cipher.Transcode(binary.data() + i, dec_temp.size(), dec_temp.data(), Op::Decrypt);
mbedtls_sha256(dec_temp.data(), dec_temp.size(), temp.data(), 0);
for (size_t k = 0; k < out.size(); ++k) {
if (temp == master_key_hashes[k]) {
out[k] = dec_temp;
break;
}
}
}
return out;
}
FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
const std::string& name) {
auto upper = name;
std::transform(upper.begin(), upper.end(), upper.begin(), [](u8 c) { return std::toupper(c); });
for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) {
if (dir->GetFile(fname) != nullptr)
return dir->GetFile(fname);
}
return nullptr;
}
PartitionDataManager::PartitionDataManager(FileSys::VirtualDir sysdata_dir)
: boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")),
fuses(FindFileInDirWithNames(sysdata_dir, "fuse")),
kfuses(FindFileInDirWithNames(sysdata_dir, "kfuse")),
package2({
FindFileInDirWithNames(sysdata_dir, "BCPKG2-1-Normal-Main"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-2-Normal-Sub"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-3-SafeMode-Main"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-4-SafeMode-Sub"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-5-Repair-Main"),
FindFileInDirWithNames(sysdata_dir, "BCPKG2-6-Repair-Sub"),
}),
secure_monitor(FindFileInDirWithNames(sysdata_dir, "secmon")),
package1_decrypted(FindFileInDirWithNames(sysdata_dir, "pkg1_decr")),
secure_monitor_bytes(secure_monitor == nullptr ? std::vector<u8>{}
: secure_monitor->ReadAllBytes()),
package1_decrypted_bytes(package1_decrypted == nullptr ? std::vector<u8>{}
: package1_decrypted->ReadAllBytes()),
prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")) {}
PartitionDataManager::~PartitionDataManager() = default;
bool PartitionDataManager::HasBoot0() const {
return boot0 != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const {
return boot0;
}
std::array<u8, 176> PartitionDataManager::GetEncryptedKeyblob(u8 index) const {
if (HasBoot0() && index < 32)
return GetEncryptedKeyblobs()[index];
return {};
}
std::array<std::array<u8, 176>, 32> PartitionDataManager::GetEncryptedKeyblobs() const {
if (!HasBoot0())
return {};
std::array<std::array<u8, 176>, 32> out{};
for (size_t i = 0; i < 0x20; ++i)
boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200);
return out;
}
std::vector<u8> PartitionDataManager::GetSecureMonitor() const {
return secure_monitor_bytes;
}
std::array<u8, 16> PartitionDataManager::GetPackage2KeySource() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[2]);
}
std::array<u8, 16> PartitionDataManager::GetAESKekGenerationSource() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[3]);
}
std::array<u8, 16> PartitionDataManager::GetTitlekekSource() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[5]);
}
std::array<std::array<u8, 16>, 32> PartitionDataManager::GetTZMasterKeys(
std::array<u8, 0x10> master_key) const {
return FindEncryptedMasterKeyFromHex(secure_monitor_bytes, master_key);
}
std::array<u8, 16> PartitionDataManager::GetRSAKekSeed3() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[14]);
}
std::array<u8, 16> PartitionDataManager::GetRSAKekMask0() const {
return FindKeyFromHex(secure_monitor_bytes, source_hashes[15]);
}
std::vector<u8> PartitionDataManager::GetPackage1Decrypted() const {
return package1_decrypted_bytes;
}
std::array<u8, 16> PartitionDataManager::GetMasterKeySource() const {
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[1]);
}
std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const {
return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]);
}
std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(u8 revision) const {
if (keyblob_source_hashes[revision] == SHA256Hash{}) {
LOG_WARNING(Crypto,
"No keyblob source hash for crypto revision {:02X}! Cannot derive keys...",
revision);
}
return FindKeyFromHex(package1_decrypted_bytes, keyblob_source_hashes[revision]);
}
bool PartitionDataManager::HasFuses() const {
return fuses != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetFusesRaw() const {
return fuses;
}
std::array<u8, 16> PartitionDataManager::GetSecureBootKey() const {
if (!HasFuses())
return {};
Key128 out{};
fuses->Read(out.data(), out.size(), 0xA4);
return out;
}
bool PartitionDataManager::HasKFuses() const {
return kfuses != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetKFusesRaw() const {
return kfuses;
}
bool PartitionDataManager::HasPackage2(Package2Type type) const {
return package2.at(static_cast<size_t>(type)) != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) const {
return package2.at(static_cast<size_t>(type));
}
bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end());
Package2Header temp = header;
AESCipher<Key128> cipher(key, Mode::CTR);
cipher.SetIV(iv);
cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr,
Op::Decrypt);
if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
header = temp;
return true;
}
return false;
}
void PartitionDataManager::DecryptPackage2(std::array<std::array<u8, 16>, 0x20> package2_keys,
Package2Type type) {
FileSys::VirtualFile file = std::make_shared<FileSys::OffsetVfsFile>(
package2[static_cast<size_t>(type)],
package2[static_cast<size_t>(type)]->GetSize() - 0x4000, 0x4000);
Package2Header header{};
if (file->ReadObject(&header) != sizeof(Package2Header))
return;
u8 revision = 0xFF;
if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) {
for (size_t i = 0; i < package2_keys.size(); ++i) {
if (AttemptDecrypt(package2_keys[i], header))
revision = i;
}
}
if (header.magic != Common::MakeMagic('P', 'K', '2', '1'))
return;
const std::vector<u8> s1_iv(header.section_ctr[1].begin(), header.section_ctr[1].end());
const auto a = std::make_shared<FileSys::OffsetVfsFile>(
file, header.section_size[1], header.section_size[0] + sizeof(Package2Header));
auto c = a->ReadAllBytes();
AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR);
cipher.SetIV(s1_iv);
cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
// package2_decrypted[static_cast<size_t>(type)] = s1;
INIHeader ini;
std::memcpy(&ini, c.data(), sizeof(INIHeader));
if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
return;
std::map<u64, KIPHeader> kips{};
u64 offset = sizeof(INIHeader);
for (size_t i = 0; i < ini.process_count; ++i) {
KIPHeader kip;
std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader));
if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1'))
return;
kips.emplace(offset, kip);
const auto name =
Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size());
if (name != "FS" && name != "spl") {
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
continue;
}
std::vector<u8> text(kip.sections[0].size_compressed);
std::vector<u8> rodata(kip.sections[1].size_compressed);
std::vector<u8> data(kip.sections[2].size_compressed);
u64 offset_sec = sizeof(KIPHeader) + offset;
std::memcpy(text.data(), c.data() + offset_sec, text.size());
offset_sec += text.size();
std::memcpy(rodata.data(), c.data() + offset_sec, rodata.size());
offset_sec += rodata.size();
std::memcpy(data.data(), c.data() + offset_sec, data.size());
offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
kip.sections[1].size_compressed + kip.sections[2].size_compressed;
text = DecompressBLZ(text);
rodata = DecompressBLZ(rodata);
data = DecompressBLZ(data);
std::vector<u8> out(text.size() + rodata.size() + data.size());
std::memcpy(out.data(), text.data(), text.size());
std::memcpy(out.data() + text.size(), rodata.data(), rodata.size());
std::memcpy(out.data() + text.size() + rodata.size(), data.data(), data.size());
if (name == "FS")
package2_fs[static_cast<size_t>(type)] = out;
else if (name == "spl")
package2_spl[static_cast<size_t>(type)] = out;
}
}
const std::vector<u8>& PartitionDataManager::GetPackage2FSDecompressed(Package2Type type) const {
return package2_fs.at(static_cast<size_t>(type));
}
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyApplicationSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[6]);
}
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyOceanSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[7]);
}
std::array<u8, 16> PartitionDataManager::GetKeyAreaKeySystemSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[8]);
}
std::array<u8, 16> PartitionDataManager::GetSDKekSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[9]);
}
std::array<u8, 32> PartitionDataManager::GetSDSaveKeySource(Package2Type type) const {
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[10]);
}
std::array<u8, 32> PartitionDataManager::GetSDNCAKeySource(Package2Type type) const {
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[11]);
}
std::array<u8, 16> PartitionDataManager::GetHeaderKekSource(Package2Type type) const {
return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[12]);
}
std::array<u8, 32> PartitionDataManager::GetHeaderKeySource(Package2Type type) const {
return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[13]);
}
const std::vector<u8>& PartitionDataManager::GetPackage2SPLDecompressed(Package2Type type) const {
return package2_spl.at(static_cast<size_t>(type));
}
std::array<u8, 16> PartitionDataManager::GetAESKeyGenerationSource(Package2Type type) const {
return FindKeyFromHex(package2_spl.at(static_cast<size_t>(type)), source_hashes[4]);
}
bool PartitionDataManager::HasProdInfo() const {
return prodinfo != nullptr;
}
FileSys::VirtualFile PartitionDataManager::GetProdInfoRaw() const {
return prodinfo;
}
void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
if (prodinfo == nullptr)
return;
prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
}
std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
std::array<u8, 0x240> out{};
if (prodinfo_decrypted != nullptr)
prodinfo_decrypted->Read(out.data(), out.size(), 0x3890);
return out;
}
} // namespace Core::Crypto

View File

@@ -1,105 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs_types.h"
namespace Core::Crypto {
enum class Package2Type {
NormalMain,
NormalSub,
SafeModeMain,
SafeModeSub,
RepairMain,
RepairSub,
};
class PartitionDataManager {
public:
static const u8 MAX_KEYBLOB_SOURCE_HASH;
explicit PartitionDataManager(FileSys::VirtualDir sysdata_dir);
~PartitionDataManager();
// BOOT0
bool HasBoot0() const;
FileSys::VirtualFile GetBoot0Raw() const;
std::array<u8, 0xB0> GetEncryptedKeyblob(u8 index) const;
std::array<std::array<u8, 0xB0>, 0x20> GetEncryptedKeyblobs() const;
std::vector<u8> GetSecureMonitor() const;
std::array<u8, 0x10> GetPackage2KeySource() const;
std::array<u8, 0x10> GetAESKekGenerationSource() const;
std::array<u8, 0x10> GetTitlekekSource() const;
std::array<std::array<u8, 0x10>, 0x20> GetTZMasterKeys(std::array<u8, 0x10> master_key) const;
std::array<u8, 0x10> GetRSAKekSeed3() const;
std::array<u8, 0x10> GetRSAKekMask0() const;
std::vector<u8> GetPackage1Decrypted() const;
std::array<u8, 0x10> GetMasterKeySource() const;
std::array<u8, 0x10> GetKeyblobMACKeySource() const;
std::array<u8, 0x10> GetKeyblobKeySource(u8 revision) const;
// Fuses
bool HasFuses() const;
FileSys::VirtualFile GetFusesRaw() const;
std::array<u8, 0x10> GetSecureBootKey() const;
// K-Fuses
bool HasKFuses() const;
FileSys::VirtualFile GetKFusesRaw() const;
// Package2
bool HasPackage2(Package2Type type = Package2Type::NormalMain) const;
FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const;
void DecryptPackage2(std::array<std::array<u8, 16>, 0x20> package2, Package2Type type);
const std::vector<u8>& GetPackage2FSDecompressed(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetKeyAreaKeyApplicationSource(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetKeyAreaKeyOceanSource(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetKeyAreaKeySystemSource(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetSDKekSource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x20> GetSDSaveKeySource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x20> GetSDNCAKeySource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetHeaderKekSource(Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x20> GetHeaderKeySource(Package2Type type = Package2Type::NormalMain) const;
const std::vector<u8>& GetPackage2SPLDecompressed(
Package2Type type = Package2Type::NormalMain) const;
std::array<u8, 0x10> GetAESKeyGenerationSource(
Package2Type type = Package2Type::NormalMain) const;
// PRODINFO
bool HasProdInfo() const;
FileSys::VirtualFile GetProdInfoRaw() const;
void DecryptProdInfo(std::array<u8, 0x20> bis_key);
std::array<u8, 0x240> GetETicketExtendedKek() const;
private:
FileSys::VirtualFile boot0;
FileSys::VirtualFile fuses;
FileSys::VirtualFile kfuses;
std::array<FileSys::VirtualFile, 6> package2;
FileSys::VirtualFile prodinfo;
FileSys::VirtualFile secure_monitor;
FileSys::VirtualFile package1_decrypted;
// Processed
std::array<FileSys::VirtualFile, 6> package2_decrypted;
FileSys::VirtualFile prodinfo_decrypted;
std::vector<u8> secure_monitor_bytes;
std::vector<u8> package1_decrypted_bytes;
std::array<std::vector<u8>, 6> package2_fs;
std::array<std::vector<u8>, 6> package2_spl;
};
std::array<u8, 0x10> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 0x20> hash);
} // namespace Core::Crypto

View File

@@ -2,14 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <fmt/format.h>
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/registered_cache.h"
namespace FileSys {
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
BISFactory::BISFactory(VirtualDir nand_root_)
: nand_root(std::move(nand_root_)),
sysnand_cache(std::make_shared<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
usrnand_cache(std::make_shared<RegisteredCache>(
@@ -25,11 +24,4 @@ std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
return usrnand_cache;
}
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
// LayeredFS doesn't work on updates and title id-less homebrew
if (title_id == 0 || (title_id & 0x800) > 0)
return nullptr;
return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
}
} // namespace FileSys

View File

@@ -17,17 +17,14 @@ class RegisteredCache;
/// registered caches.
class BISFactory {
public:
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
explicit BISFactory(VirtualDir nand_root);
~BISFactory();
std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
VirtualDir GetModificationLoadRoot(u64 title_id) const;
private:
VirtualDir nand_root;
VirtualDir load_root;
std::shared_ptr<RegisteredCache> sysnand_cache;
std::shared_ptr<RegisteredCache> usrnand_cache;

View File

@@ -20,9 +20,7 @@ namespace FileSys {
constexpr std::array<const char*, 0x4> partition_names = {"update", "normal", "secure", "logo"};
XCI::XCI(VirtualFile file_)
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
partitions(0x4) {
XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
status = Loader::ResultStatus::ErrorBadXCIHeader;
return;

View File

@@ -56,10 +56,6 @@ u64 NACP::GetTitleId() const {
return raw->title_id;
}
u64 NACP::GetDLCBaseTitleId() const {
return raw->dlc_base_title_id;
}
std::string NACP::GetVersionString() const {
return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10);
}

View File

@@ -79,7 +79,6 @@ public:
std::string GetApplicationName(Language language = Language::Default) const;
std::string GetDeveloperName(Language language = Language::Default) const;
u64 GetTitleId() const;
u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
private:

View File

@@ -1,384 +0,0 @@
/*
* Copyright (c) 2018 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Adapted by DarkLordZach for use/interaction with yuzu
*
* Modifications Copyright 2018 yuzu emulator team
* Licensed under GPLv2 or any later version
* Refer to the license.txt file included.
*/
#include <cstring>
#include "common/alignment.h"
#include "common/assert.h"
#include "core/file_sys/fsmitm_romfsbuild.h"
#include "core/file_sys/ips_layer.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
constexpr u64 FS_MAX_PATH = 0x301;
constexpr u32 ROMFS_ENTRY_EMPTY = 0xFFFFFFFF;
constexpr u32 ROMFS_FILEPARTITION_OFS = 0x200;
// Types for building a RomFS.
struct RomFSHeader {
u64 header_size;
u64 dir_hash_table_ofs;
u64 dir_hash_table_size;
u64 dir_table_ofs;
u64 dir_table_size;
u64 file_hash_table_ofs;
u64 file_hash_table_size;
u64 file_table_ofs;
u64 file_table_size;
u64 file_partition_ofs;
};
static_assert(sizeof(RomFSHeader) == 0x50, "RomFSHeader has incorrect size.");
struct RomFSDirectoryEntry {
u32 parent;
u32 sibling;
u32 child;
u32 file;
u32 hash;
u32 name_size;
};
static_assert(sizeof(RomFSDirectoryEntry) == 0x18, "RomFSDirectoryEntry has incorrect size.");
struct RomFSFileEntry {
u32 parent;
u32 sibling;
u64 offset;
u64 size;
u32 hash;
u32 name_size;
};
static_assert(sizeof(RomFSFileEntry) == 0x20, "RomFSFileEntry has incorrect size.");
struct RomFSBuildFileContext;
struct RomFSBuildDirectoryContext {
std::string path;
u32 cur_path_ofs = 0;
u32 path_len = 0;
u32 entry_offset = 0;
std::shared_ptr<RomFSBuildDirectoryContext> parent;
std::shared_ptr<RomFSBuildDirectoryContext> child;
std::shared_ptr<RomFSBuildDirectoryContext> sibling;
std::shared_ptr<RomFSBuildFileContext> file;
};
struct RomFSBuildFileContext {
std::string path;
u32 cur_path_ofs = 0;
u32 path_len = 0;
u32 entry_offset = 0;
u64 offset = 0;
u64 size = 0;
std::shared_ptr<RomFSBuildDirectoryContext> parent;
std::shared_ptr<RomFSBuildFileContext> sibling;
VirtualFile source;
};
static u32 romfs_calc_path_hash(u32 parent, std::string path, u32 start, std::size_t path_len) {
u32 hash = parent ^ 123456789;
for (u32 i = 0; i < path_len; i++) {
hash = (hash >> 5) | (hash << 27);
hash ^= path[start + i];
}
return hash;
}
static u64 romfs_get_hash_table_count(u64 num_entries) {
if (num_entries < 3) {
return 3;
}
if (num_entries < 19) {
return num_entries | 1;
}
u64 count = num_entries;
while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 ||
count % 11 == 0 || count % 13 == 0 || count % 17 == 0) {
count++;
}
return count;
}
void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext,
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
VirtualDir dir;
if (parent->path_len == 0)
dir = root_romfs;
else
dir = root_romfs->GetDirectoryRelative(parent->path);
const auto entries = dir->GetEntries();
for (const auto& kv : entries) {
if (kv.second == VfsEntryType::Directory) {
const auto child = std::make_shared<RomFSBuildDirectoryContext>();
// Set child's path.
child->cur_path_ofs = parent->path_len + 1;
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
continue;
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
if (AddDirectory(parent, child)) {
child_dirs.push_back(child);
}
} else {
const auto child = std::make_shared<RomFSBuildFileContext>();
// Set child's path.
child->cur_path_ofs = parent->path_len + 1;
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
continue;
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
child->source = root_romfs->GetFileRelative(child->path);
if (ext != nullptr) {
const auto ips = ext->GetFileRelative(child->path + ".ips");
if (ips != nullptr) {
auto patched = PatchIPS(child->source, ips);
if (patched != nullptr)
child->source = std::move(patched);
}
}
child->size = child->source->GetSize();
AddFile(parent, child);
}
}
for (auto& child : child_dirs) {
this->VisitDirectory(root_romfs, ext, child);
}
}
bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
// Check whether it's already in the known directories.
const auto existing = directories.find(dir_ctx->path);
if (existing != directories.end())
return false;
// Add a new directory.
num_dirs++;
dir_table_size +=
sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4);
dir_ctx->parent = parent_dir_ctx;
directories.emplace(dir_ctx->path, dir_ctx);
return true;
}
bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
std::shared_ptr<RomFSBuildFileContext> file_ctx) {
// Check whether it's already in the known files.
const auto existing = files.find(file_ctx->path);
if (existing != files.end()) {
return false;
}
// Add a new file.
num_files++;
file_table_size +=
sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4);
file_ctx->parent = parent_dir_ctx;
files.emplace(file_ctx->path, file_ctx);
return true;
}
RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
: base(std::move(base_)), ext(std::move(ext_)) {
root = std::make_shared<RomFSBuildDirectoryContext>();
root->path = "\0";
directories.emplace(root->path, root);
num_dirs = 1;
dir_table_size = 0x18;
VisitDirectory(base, ext, root);
}
RomFSBuildContext::~RomFSBuildContext() = default;
std::map<u64, VirtualFile> RomFSBuildContext::Build() {
const u64 dir_hash_table_entry_count = romfs_get_hash_table_count(num_dirs);
const u64 file_hash_table_entry_count = romfs_get_hash_table_count(num_files);
dir_hash_table_size = 4 * dir_hash_table_entry_count;
file_hash_table_size = 4 * file_hash_table_entry_count;
// Assign metadata pointers
RomFSHeader header{};
std::vector<u32> dir_hash_table(dir_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
std::vector<u32> file_hash_table(file_hash_table_entry_count, ROMFS_ENTRY_EMPTY);
std::vector<u8> dir_table(dir_table_size);
std::vector<u8> file_table(file_table_size);
std::shared_ptr<RomFSBuildFileContext> cur_file;
// Determine file offsets.
u32 entry_offset = 0;
std::shared_ptr<RomFSBuildFileContext> prev_file = nullptr;
for (const auto& it : files) {
cur_file = it.second;
file_partition_size = Common::AlignUp(file_partition_size, 16);
cur_file->offset = file_partition_size;
file_partition_size += cur_file->size;
cur_file->entry_offset = entry_offset;
entry_offset += sizeof(RomFSFileEntry) +
Common::AlignUp(cur_file->path_len - cur_file->cur_path_ofs, 4);
prev_file = cur_file;
}
// Assign deferred parent/sibling ownership.
for (auto it = files.rbegin(); it != files.rend(); ++it) {
cur_file = it->second;
cur_file->sibling = cur_file->parent->file;
cur_file->parent->file = cur_file;
}
std::shared_ptr<RomFSBuildDirectoryContext> cur_dir;
// Determine directory offsets.
entry_offset = 0;
for (const auto& it : directories) {
cur_dir = it.second;
cur_dir->entry_offset = entry_offset;
entry_offset += sizeof(RomFSDirectoryEntry) +
Common::AlignUp(cur_dir->path_len - cur_dir->cur_path_ofs, 4);
}
// Assign deferred parent/sibling ownership.
for (auto it = directories.rbegin(); it->second != root; ++it) {
cur_dir = it->second;
cur_dir->sibling = cur_dir->parent->child;
cur_dir->parent->child = cur_dir;
}
std::map<u64, VirtualFile> out;
// Populate file tables.
for (const auto& it : files) {
cur_file = it.second;
RomFSFileEntry cur_entry{};
cur_entry.parent = cur_file->parent->entry_offset;
cur_entry.sibling =
cur_file->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_file->sibling->entry_offset;
cur_entry.offset = cur_file->offset;
cur_entry.size = cur_file->size;
const auto name_size = cur_file->path_len - cur_file->cur_path_ofs;
const auto hash = romfs_calc_path_hash(cur_file->parent->entry_offset, cur_file->path,
cur_file->cur_path_ofs, name_size);
cur_entry.hash = file_hash_table[hash % file_hash_table_entry_count];
file_hash_table[hash % file_hash_table_entry_count] = cur_file->entry_offset;
cur_entry.name_size = name_size;
out.emplace(cur_file->offset + ROMFS_FILEPARTITION_OFS, cur_file->source);
std::memcpy(file_table.data() + cur_file->entry_offset, &cur_entry, sizeof(RomFSFileEntry));
std::memset(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry), 0,
Common::AlignUp(cur_entry.name_size, 4));
std::memcpy(file_table.data() + cur_file->entry_offset + sizeof(RomFSFileEntry),
cur_file->path.data() + cur_file->cur_path_ofs, name_size);
}
// Populate dir tables.
for (const auto& it : directories) {
cur_dir = it.second;
RomFSDirectoryEntry cur_entry{};
cur_entry.parent = cur_dir == root ? 0 : cur_dir->parent->entry_offset;
cur_entry.sibling =
cur_dir->sibling == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->sibling->entry_offset;
cur_entry.child =
cur_dir->child == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->child->entry_offset;
cur_entry.file = cur_dir->file == nullptr ? ROMFS_ENTRY_EMPTY : cur_dir->file->entry_offset;
const auto name_size = cur_dir->path_len - cur_dir->cur_path_ofs;
const auto hash = romfs_calc_path_hash(cur_dir == root ? 0 : cur_dir->parent->entry_offset,
cur_dir->path, cur_dir->cur_path_ofs, name_size);
cur_entry.hash = dir_hash_table[hash % dir_hash_table_entry_count];
dir_hash_table[hash % dir_hash_table_entry_count] = cur_dir->entry_offset;
cur_entry.name_size = name_size;
std::memcpy(dir_table.data() + cur_dir->entry_offset, &cur_entry,
sizeof(RomFSDirectoryEntry));
std::memset(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry), 0,
Common::AlignUp(cur_entry.name_size, 4));
std::memcpy(dir_table.data() + cur_dir->entry_offset + sizeof(RomFSDirectoryEntry),
cur_dir->path.data() + cur_dir->cur_path_ofs, name_size);
}
// Set header fields.
header.header_size = sizeof(RomFSHeader);
header.file_hash_table_size = file_hash_table_size;
header.file_table_size = file_table_size;
header.dir_hash_table_size = dir_hash_table_size;
header.dir_table_size = dir_table_size;
header.file_partition_ofs = ROMFS_FILEPARTITION_OFS;
header.dir_hash_table_ofs = Common::AlignUp(header.file_partition_ofs + file_partition_size, 4);
header.dir_table_ofs = header.dir_hash_table_ofs + header.dir_hash_table_size;
header.file_hash_table_ofs = header.dir_table_ofs + header.dir_table_size;
header.file_table_ofs = header.file_hash_table_ofs + header.file_hash_table_size;
std::vector<u8> header_data(sizeof(RomFSHeader));
std::memcpy(header_data.data(), &header, header_data.size());
out.emplace(0, std::make_shared<VectorVfsFile>(std::move(header_data)));
std::vector<u8> metadata(file_hash_table_size + file_table_size + dir_hash_table_size +
dir_table_size);
std::size_t index = 0;
std::memcpy(metadata.data(), dir_hash_table.data(), dir_hash_table.size() * sizeof(u32));
index += dir_hash_table.size() * sizeof(u32);
std::memcpy(metadata.data() + index, dir_table.data(), dir_table.size());
index += dir_table.size();
std::memcpy(metadata.data() + index, file_hash_table.data(),
file_hash_table.size() * sizeof(u32));
index += file_hash_table.size() * sizeof(u32);
std::memcpy(metadata.data() + index, file_table.data(), file_table.size());
out.emplace(header.dir_hash_table_ofs, std::make_shared<VectorVfsFile>(std::move(metadata)));
return out;
}
} // namespace FileSys

View File

@@ -1,72 +0,0 @@
/*
* Copyright (c) 2018 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Adapted by DarkLordZach for use/interaction with yuzu
*
* Modifications Copyright 2018 yuzu emulator team
* Licensed under GPLv2 or any later version
* Refer to the license.txt file included.
*/
#pragma once
#include <map>
#include <memory>
#include <string>
#include <boost/detail/container_fwd.hpp>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
struct RomFSBuildDirectoryContext;
struct RomFSBuildFileContext;
struct RomFSDirectoryEntry;
struct RomFSFileEntry;
class RomFSBuildContext {
public:
explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
~RomFSBuildContext();
// This finalizes the context.
std::map<u64, VirtualFile> Build();
private:
VirtualDir base;
VirtualDir ext;
std::shared_ptr<RomFSBuildDirectoryContext> root;
std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
u64 num_dirs = 0;
u64 num_files = 0;
u64 dir_table_size = 0;
u64 file_table_size = 0;
u64 dir_hash_table_size = 0;
u64 file_hash_table_size = 0;
u64 file_partition_size = 0;
void VisitDirectory(VirtualDir filesys, VirtualDir ext,
std::shared_ptr<RomFSBuildDirectoryContext> parent);
bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
bool AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
std::shared_ptr<RomFSBuildFileContext> file_ctx);
};
} // namespace FileSys

View File

@@ -1,338 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/file_sys/ips_layer.h"
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
enum class IPSFileType {
IPS,
IPS32,
Error,
};
constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
{"\\a", "\a"},
{"\\b", "\b"},
{"\\f", "\f"},
{"\\n", "\n"},
{"\\r", "\r"},
{"\\t", "\t"},
{"\\v", "\v"},
{"\\\\", "\\"},
{"\\\'", "\'"},
{"\\\"", "\""},
{"\\\?", "\?"},
}};
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
if (magic.size() != 5) {
return IPSFileType::Error;
}
constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
return IPSFileType::IPS;
}
constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
return IPSFileType::IPS32;
}
return IPSFileType::Error;
}
static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
return true;
}
constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
}
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
if (in == nullptr || ips == nullptr)
return nullptr;
const auto type = IdentifyMagic(ips->ReadBytes(0x5));
if (type == IPSFileType::Error)
return nullptr;
auto in_data = in->ReadAllBytes();
std::vector<u8> temp(type == IPSFileType::IPS ? 3 : 4);
u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
offset += temp.size();
if (IsEOF(type, temp)) {
break;
}
u32 real_offset{};
if (type == IPSFileType::IPS32)
real_offset = (temp[0] << 24) | (temp[1] << 16) | (temp[2] << 8) | temp[3];
else
real_offset = (temp[0] << 16) | (temp[1] << 8) | temp[2];
u16 data_size{};
if (ips->ReadObject(&data_size, offset) != sizeof(u16))
return nullptr;
data_size = Common::swap16(data_size);
offset += sizeof(u16);
if (data_size == 0) { // RLE
u16 rle_size{};
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
return nullptr;
rle_size = Common::swap16(data_size);
offset += sizeof(u16);
const auto data = ips->ReadByte(offset++);
if (data == boost::none)
return nullptr;
if (real_offset + rle_size > in_data.size())
rle_size = static_cast<u16>(in_data.size() - real_offset);
std::memset(in_data.data() + real_offset, data.get(), rle_size);
} else { // Standard Patch
auto read = data_size;
if (real_offset + read > in_data.size())
read = static_cast<u16>(in_data.size() - real_offset);
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
return nullptr;
offset += data_size;
}
}
if (!IsEOF(type, temp)) {
return nullptr;
}
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
}
struct IPSwitchCompiler::IPSwitchPatch {
std::string name;
bool enabled;
std::map<u32, std::vector<u8>> records;
};
IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
Parse();
}
IPSwitchCompiler::~IPSwitchCompiler() = default;
std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
return nso_build_id;
}
bool IPSwitchCompiler::IsValid() const {
return valid;
}
static bool StartsWith(std::string_view base, std::string_view check) {
return base.size() >= check.size() && base.substr(0, check.size()) == check;
}
static std::string EscapeStringSequences(std::string in) {
for (const auto& seq : ESCAPE_CHARACTER_MAP) {
for (auto index = in.find(seq.first); index != std::string::npos;
index = in.find(seq.first, index)) {
in.replace(index, std::strlen(seq.first), seq.second);
index += std::strlen(seq.second);
}
}
return in;
}
void IPSwitchCompiler::ParseFlag(const std::string& line) {
if (StartsWith(line, "@flag offset_shift ")) {
// Offset Shift Flag
offset_shift = std::stoll(line.substr(19), nullptr, 0);
} else if (StartsWith(line, "@little-endian")) {
// Set values to read as little endian
is_little_endian = true;
} else if (StartsWith(line, "@big-endian")) {
// Set values to read as big endian
is_little_endian = false;
} else if (StartsWith(line, "@flag print_values")) {
// Force printing of applied values
print_values = true;
}
}
void IPSwitchCompiler::Parse() {
const auto bytes = patch_text->ReadAllBytes();
std::stringstream s;
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
std::vector<std::string> lines;
std::string stream_line;
while (std::getline(s, stream_line)) {
// Remove a trailing \r
if (!stream_line.empty() && stream_line.back() == '\r')
stream_line.pop_back();
lines.push_back(std::move(stream_line));
}
for (std::size_t i = 0; i < lines.size(); ++i) {
auto line = lines[i];
// Remove midline comments
std::size_t comment_index = std::string::npos;
bool within_string = false;
for (std::size_t k = 0; k < line.size(); ++k) {
if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
within_string = !within_string;
} else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
comment_index = k;
break;
}
}
if (!StartsWith(line, "//") && comment_index != std::string::npos) {
last_comment = line.substr(comment_index + 2);
line = line.substr(0, comment_index);
}
if (StartsWith(line, "@stop")) {
// Force stop
break;
} else if (StartsWith(line, "@nsobid-")) {
// NSO Build ID Specifier
auto raw_build_id = line.substr(8);
if (raw_build_id.size() != 0x40)
raw_build_id.resize(0x40, '0');
nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
} else if (StartsWith(line, "#")) {
// Mandatory Comment
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
patch_text->GetName(), line.substr(1));
} else if (StartsWith(line, "//")) {
// Normal Comment
last_comment = line.substr(2);
if (last_comment.find_first_not_of(' ') == std::string::npos)
continue;
if (last_comment.find_first_not_of(' ') != 0)
last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
} else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
// Start of patch
const auto enabled = StartsWith(line, "@enabled");
if (i == 0)
return;
LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
patch_text->GetName(), last_comment, line.substr(1));
IPSwitchPatch patch{last_comment, enabled, {}};
// Read rest of patch
while (true) {
if (i + 1 >= lines.size())
break;
const auto patch_line = lines[++i];
// Start of new patch
if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
--i;
break;
}
// Check for a flag
if (StartsWith(patch_line, "@")) {
ParseFlag(patch_line);
continue;
}
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
if (patch_line.length() < 11)
break;
auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
offset += static_cast<unsigned long>(offset_shift);
std::vector<u8> replace;
// 9 - first char of replacement val
if (patch_line[9] == '\"') {
// string replacement
auto end_index = patch_line.find('\"', 10);
if (end_index == std::string::npos || end_index < 10)
return;
while (patch_line[end_index - 1] == '\\') {
end_index = patch_line.find('\"', end_index + 1);
if (end_index == std::string::npos || end_index < 10)
return;
}
auto value = patch_line.substr(10, end_index - 10);
value = EscapeStringSequences(value);
replace.reserve(value.size());
std::copy(value.begin(), value.end(), std::back_inserter(replace));
} else {
// hex replacement
const auto value = patch_line.substr(9);
replace.reserve(value.size() / 2);
replace = Common::HexStringToVector(value, is_little_endian);
}
if (print_values) {
LOG_INFO(Loader,
"[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
"with byte string '{}'",
patch_text->GetName(), offset, Common::HexVectorToString(replace));
}
patch.records.insert_or_assign(offset, std::move(replace));
}
patches.push_back(std::move(patch));
} else if (StartsWith(line, "@")) {
ParseFlag(line);
}
}
valid = true;
}
VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
if (in == nullptr || !valid)
return nullptr;
auto in_data = in->ReadAllBytes();
for (const auto& patch : patches) {
if (!patch.enabled)
continue;
for (const auto& record : patch.records) {
if (record.first >= in_data.size())
continue;
auto replace_size = record.second.size();
if (record.first + replace_size > in_data.size())
replace_size = in_data.size() - record.first;
for (std::size_t i = 0; i < replace_size; ++i)
in_data[i + record.first] = record.second[i];
}
}
return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
in->GetContainingDirectory());
}
} // namespace FileSys

View File

@@ -1,44 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <vector>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
class IPSwitchCompiler {
public:
explicit IPSwitchCompiler(VirtualFile patch_text);
~IPSwitchCompiler();
std::array<u8, 0x20> GetBuildID() const;
bool IsValid() const;
VirtualFile Apply(const VirtualFile& in) const;
private:
struct IPSwitchPatch;
void ParseFlag(const std::string& flag);
void Parse();
bool valid = false;
VirtualFile patch_text;
std::vector<IPSwitchPatch> patches;
std::array<u8, 0x20> nso_build_id{};
bool is_little_endian = false;
s64 offset_shift = 0;
bool print_values = false;
std::string last_comment = "";
};
} // namespace FileSys

View File

@@ -2,36 +2,21 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstring>
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/ips_layer.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs_layered.h"
#include "core/file_sys/vfs_vector.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
namespace FileSys {
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
struct NSOBuildHeader {
u32_le magic;
INSERT_PADDING_BYTES(0x3C);
std::array<u8, 0x20> build_id;
INSERT_PADDING_BYTES(0xA0);
};
static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
std::array<u8, sizeof(u32)> bytes{};
@@ -46,6 +31,14 @@ std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
}
constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{
"Update",
};
std::string FormatPatchTypeName(PatchType type) {
return PATCH_TYPE_NAMES.at(static_cast<std::size_t>(type));
}
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
PatchManager::~PatchManager() = default;
@@ -73,147 +66,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs;
}
static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
const std::string& build_id) {
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips") {
auto name = file->GetName();
const auto p1 = name.substr(0, name.find('.'));
const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
if (build_id == this_build_id)
out.push_back(file);
} else if (file->GetExtension() == "pchtxt") {
IPSwitchCompiler compiler{file};
if (!compiler.IsValid())
continue;
auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
this_build_id =
this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
if (build_id == this_build_id)
out.push_back(file);
}
}
}
}
return out;
}
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
if (nso.size() < 0x100)
return nso;
NSOBuildHeader header;
std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
return nso;
const auto build_id_raw = Common::HexArrayToString(header.build_id);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
const auto patches = CollectPatches(patch_dirs, build_id);
auto out = nso;
for (const auto& patch_file : patches) {
if (patch_file->GetExtension() == "ips") {
LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
if (patched != nullptr)
out = patched->ReadAllBytes();
} else if (patch_file->GetExtension() == "pchtxt") {
LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
const IPSwitchCompiler compiler{patch_file};
const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
if (patched != nullptr)
out = patched->ReadAllBytes();
}
}
if (out.size() < 0x100)
return nso;
std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
return out;
}
bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
const auto build_id_raw = Common::HexArrayToString(build_id_);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
return !CollectPatches(patch_dirs, build_id).empty();
}
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
return;
}
auto extracted = ExtractRomFS(romfs);
if (extracted == nullptr) {
return;
}
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
std::vector<VirtualDir> layers;
std::vector<VirtualDir> layers_ext;
layers.reserve(patch_dirs.size() + 1);
layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
auto ext_dir = subdir->GetSubdirectory("romfs_ext");
if (ext_dir != nullptr)
layers_ext.push_back(std::move(ext_dir));
}
layers.push_back(std::move(extracted));
auto layered = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers));
if (layered == nullptr) {
return;
}
auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
if (packed == nullptr) {
return;
}
LOG_INFO(Loader, " RomFS: LayeredFS patches applied successfully");
romfs = std::move(packed);
}
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
ContentRecordType type) const {
LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
static_cast<u8>(type));
@@ -233,134 +87,53 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
romfs = new_nca->GetRomFS();
}
} else if (update_raw != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
romfs = new_nca->GetRomFS();
}
}
// LayeredFS
ApplyLayeredFS(romfs, title_id, type);
return romfs;
}
static void AppendCommaIfNotEmpty(std::string& to, const std::string& with) {
if (to.empty())
to += with;
else
to += ", " + with;
}
static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
std::map<PatchType, std::string> out;
const auto installed = Service::FileSystem::GetUnionContents();
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
PatchManager update{update_tid};
auto [nacp, discard_icon_file] = update.GetControlMetadata();
if (nacp != nullptr) {
out.insert_or_assign("Update", nacp->GetVersionString());
out[PatchType::Update] = nacp->GetVersionString();
} else {
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = installed->GetEntryVersion(update_tid);
if (meta_ver == boost::none || meta_ver.get() == 0) {
out.insert_or_assign("Update", "");
out[PatchType::Update] = "";
} else {
out.insert_or_assign(
"Update",
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
out[PatchType::Update] =
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements);
}
} else if (update_raw != nullptr) {
out.insert_or_assign("Update", "PACKED");
}
}
// General Mods (LayeredFS and IPS)
const auto mod_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
for (const auto& mod : mod_dir->GetSubdirectories()) {
std::string types;
const auto exefs_dir = mod->GetSubdirectory("exefs");
if (IsDirValidAndNonEmpty(exefs_dir)) {
bool ips = false;
bool ipswitch = false;
for (const auto& file : exefs_dir->GetFiles()) {
if (file->GetExtension() == "ips")
ips = true;
else if (file->GetExtension() == "pchtxt")
ipswitch = true;
}
if (ips)
AppendCommaIfNotEmpty(types, "IPS");
if (ipswitch)
AppendCommaIfNotEmpty(types, "IPSwitch");
}
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
if (types.empty())
continue;
out.insert_or_assign(mod->GetName(), types);
}
}
// DLC
const auto dlc_entries = installed->ListEntriesFilter(TitleType::AOC, ContentRecordType::Data);
std::vector<RegisteredCacheEntry> dlc_match;
dlc_match.reserve(dlc_entries.size());
std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
[this, &installed](const RegisteredCacheEntry& entry) {
return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == title_id &&
installed->GetEntry(entry)->GetStatus() ==
Loader::ResultStatus::Success;
});
if (!dlc_match.empty()) {
// Ensure sorted so DLC IDs show in order.
std::sort(dlc_match.begin(), dlc_match.end());
std::string list;
for (size_t i = 0; i < dlc_match.size() - 1; ++i)
list += fmt::format("{}, ", dlc_match[i].title_id & 0x7FF);
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
out.insert_or_assign("DLC", std::move(list));
}
return out;
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
const auto& installed{Service::FileSystem::GetUnionContents()};
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr)
return {};
return ParseControlNCA(*base_control_nca);
return ParseControlNCA(base_control_nca);
}
std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
const auto base_romfs = nca.GetRomFS();
std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
const std::shared_ptr<NCA>& nca) const {
const auto base_romfs = nca->GetRomFS();
if (base_romfs == nullptr)
return {};
const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control);
if (romfs == nullptr)
return {};
@@ -372,7 +145,7 @@ std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(cons
if (nacp_file == nullptr)
nacp_file = extracted->GetFile("Control.nacp");
auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
VirtualFile icon_file;
for (const auto& language : FileSys::LANGUAGE_NAMES) {
@@ -381,6 +154,6 @@ std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(cons
break;
}
return {std::move(nacp), icon_file};
return {nacp, icon_file};
}
} // namespace FileSys

View File

@@ -24,6 +24,12 @@ enum class TitleVersionFormat : u8 {
std::string FormatTitleVersion(u32 version,
TitleVersionFormat format = TitleVersionFormat::ThreeElements);
enum class PatchType {
Update,
};
std::string FormatPatchTypeName(PatchType type);
// A centralized class to manage patches to games.
class PatchManager {
public:
@@ -34,33 +40,22 @@ public:
// - Game Updates
VirtualDir PatchExeFS(VirtualDir exefs) const;
// Currently tracked NSO patches:
// - IPS
// - IPSwitch
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
// Used to prevent expensive copies in NSO loader.
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
ContentRecordType type = ContentRecordType::Program,
VirtualFile update_raw = nullptr) const;
ContentRecordType type = ContentRecordType::Program) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
VirtualFile update_raw = nullptr) const;
// i.e. Update v80 will return {Update, 80}
std::map<PatchType, std::string> GetPatchVersionNames() const;
// Given title_id of the program, attempts to get the control data of the update and parse it,
// falling back to the base control data.
std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
const std::shared_ptr<NCA>& nca) const;
private:
u64 title_id;

View File

@@ -83,12 +83,10 @@ void ProgramMetadata::Print() const {
auto address_space = "Unknown";
switch (npdm_header.address_space_type) {
case ProgramAddressSpaceType::Is36Bit:
case ProgramAddressSpaceType::Is39Bit:
case ProgramAddressSpaceType::Is64Bit:
address_space = "64-bit";
break;
case ProgramAddressSpaceType::Is32Bit:
case ProgramAddressSpaceType::Is32BitNoMap:
address_space = "32-bit";
break;
}

View File

@@ -17,10 +17,8 @@ enum class ResultStatus : u16;
namespace FileSys {
enum class ProgramAddressSpaceType : u8 {
Is32Bit = 0,
Is36Bit = 1,
Is32BitNoMap = 2,
Is39Bit = 3,
Is64Bit = 1,
Is32Bit = 2,
};
enum class ProgramFilePermission : u64 {

View File

@@ -18,10 +18,6 @@
#include "core/loader/loader.h"
namespace FileSys {
// The size of blocks to use when vfs raw copying into nand.
constexpr size_t VFS_RC_LARGE_COPY_BLOCK = 0x400000;
std::string RegisteredCacheEntry::DebugInfo() const {
return fmt::format("title_id={:016X}, content_type={:02X}", title_id, static_cast<u8>(type));
}
@@ -125,7 +121,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
if (concat.empty())
return nullptr;
file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
file = FileSys::ConcatenateFiles(concat);
}
return file;
@@ -484,8 +480,7 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
auto out = dir->CreateFileRelative(path);
if (out == nullptr)
return InstallResult::ErrorCopyFailed;
return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success
: InstallResult::ErrorCopyFailed;
return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed;
}
bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {

View File

@@ -27,7 +27,7 @@ struct ContentRecord;
using NcaID = std::array<u8, 0x10>;
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;
using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>;
enum class InstallResult {
Success,

View File

@@ -4,10 +4,8 @@
#include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/fsmitm_romfsbuild.h"
#include "core/file_sys/romfs.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_offset.h"
#include "core/file_sys/vfs_vector.h"
@@ -100,7 +98,7 @@ void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file
}
}
VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
VirtualDir ExtractRomFS(VirtualFile file) {
RomFSHeader header{};
if (file->ReadObject(&header) != sizeof(RomFSHeader))
return nullptr;
@@ -119,22 +117,9 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
VirtualDir out = std::move(root);
while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
if (out->GetSubdirectories().front()->GetName() == "data" &&
type == RomFSExtractionType::Truncated)
break;
out = out->GetSubdirectories().front();
}
while (out->GetSubdirectory("") != nullptr)
out = out->GetSubdirectory("");
return out;
}
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
if (dir == nullptr)
return nullptr;
RomFSBuildContext ctx{dir, ext};
return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
}
} // namespace FileSys

View File

@@ -5,7 +5,6 @@
#pragma once
#include <array>
#include <map>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -13,8 +12,6 @@
namespace FileSys {
struct RomFSHeader;
struct IVFCLevel {
u64_le offset;
u64_le size;
@@ -32,18 +29,8 @@ struct IVFCHeader {
};
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
enum class RomFSExtractionType {
Full, // Includes data directory
Truncated, // Traverses into data directory
};
// Converts a RomFS binary blob to VFS Filesystem
// Returns nullptr on failure
VirtualDir ExtractRomFS(VirtualFile file,
RomFSExtractionType type = RomFSExtractionType::Truncated);
// Converts a VFS filesystem into a RomFS binary
// Returns nullptr on failure
VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
VirtualDir ExtractRomFS(VirtualFile file);
} // namespace FileSys

View File

@@ -30,49 +30,45 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
RomFSFactory::~RomFSFactory() = default;
void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
this->update_raw = std::move(update_raw);
}
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
if (!updatable)
return MakeResult<VirtualFile>(file);
const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
return MakeResult<VirtualFile>(
patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
const PatchManager patch_manager(Core::CurrentProcess()->program_id);
return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
std::shared_ptr<NCA> res;
switch (storage) {
case StorageId::None:
res = Service::FileSystem::GetUnionContents()->GetEntry(title_id, type);
break;
case StorageId::NandSystem:
res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
break;
case StorageId::NandUser:
res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
break;
case StorageId::SdCard:
res = Service::FileSystem::GetSDMCContents()->GetEntry(title_id, type);
break;
case StorageId::NandSystem: {
const auto res = Service::FileSystem::GetSystemNANDContents()->GetEntry(title_id, type);
if (res == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
const auto romfs = res->GetRomFS();
if (romfs == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
return MakeResult<VirtualFile>(romfs);
}
case StorageId::NandUser: {
const auto res = Service::FileSystem::GetUserNANDContents()->GetEntry(title_id, type);
if (res == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
const auto romfs = res->GetRomFS();
if (romfs == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
return MakeResult<VirtualFile>(romfs);
}
default:
UNIMPLEMENTED_MSG("Unimplemented storage_id={:02X}", static_cast<u8>(storage));
}
if (res == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
const auto romfs = res->GetRomFS();
if (romfs == nullptr) {
// TODO(DarkLordZach): Find the right error code to use here
return ResultCode(-1);
}
return MakeResult<VirtualFile>(romfs);
}
} // namespace FileSys

View File

@@ -32,13 +32,11 @@ public:
explicit RomFSFactory(Loader::AppLoader& app_loader);
~RomFSFactory();
void SetPackedUpdate(VirtualFile update_raw);
ResultVal<VirtualFile> OpenCurrentProcess();
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
private:
VirtualFile file;
VirtualFile update_raw;
bool updatable;
u64 ivfc_offset;
};

View File

@@ -81,7 +81,7 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
// According to switchbrew, if a save is of type SaveData and the title id field is 0, it should
// be interpreted as the title id of the current process.
if (type == SaveDataType::SaveData && title_id == 0)
title_id = Core::CurrentProcess()->GetTitleID();
title_id = Core::CurrentProcess()->program_id;
std::string out;

View File

@@ -18,39 +18,6 @@
#include "core/loader/loader.h"
namespace FileSys {
namespace {
void SetTicketKeys(const std::vector<VirtualFile>& files) {
Core::Crypto::KeyManager keys;
for (const auto& ticket_file : files) {
if (ticket_file == nullptr) {
continue;
}
if (ticket_file->GetExtension() != "tik") {
continue;
}
if (ticket_file->GetSize() <
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
continue;
}
Core::Crypto::Key128 key{};
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
// We get the name without the extension in order to create the rights ID.
std::string name_only(ticket_file->GetName());
name_only.erase(name_only.size() - 4);
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
u128 rights_id;
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
} // Anonymous namespace
NSP::NSP(VirtualFile file_)
: file(std::move(file_)), status{Loader::ResultStatus::Success},
pfs(std::make_shared<PartitionFilesystem>(file)) {
@@ -59,16 +26,83 @@ NSP::NSP(VirtualFile file_)
return;
}
const auto files = pfs->GetFiles();
if (IsDirectoryExeFS(pfs)) {
extracted = true;
InitializeExeFSAndRomFS(files);
exefs = pfs;
const auto& files = pfs->GetFiles();
const auto romfs_iter =
std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
return file->GetName().find(".romfs") != std::string::npos;
});
if (romfs_iter != files.end())
romfs = *romfs_iter;
return;
}
SetTicketKeys(files);
ReadNCAs(files);
extracted = false;
const auto files = pfs->GetFiles();
Core::Crypto::KeyManager keys;
for (const auto& ticket_file : files) {
if (ticket_file->GetExtension() == "tik") {
if (ticket_file == nullptr ||
ticket_file->GetSize() <
Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
continue;
}
Core::Crypto::Key128 key{};
ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
std::string_view name_only(ticket_file->GetName());
name_only.remove_suffix(4);
const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
u128 rights_id;
std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
}
}
for (const auto& outer_file : files) {
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
const auto nca = std::make_shared<NCA>(outer_file);
if (nca->GetStatus() != Loader::ResultStatus::Success) {
program_status[nca->GetTitleId()] = nca->GetStatus();
continue;
}
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& inner_file : section0->GetFiles()) {
if (inner_file->GetExtension() != "cnmt")
continue;
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
ncas_title[ContentRecordType::Meta] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
if (next_file == nullptr) {
LOG_WARNING(Service_FS,
"NCA with ID {}.nca is listed in content metadata, but cannot "
"be found in PFS. NSP appears to be corrupted.",
id_string);
continue;
}
auto next_nca = std::make_shared<NCA>(next_file);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
if (next_nca->GetStatus() == Loader::ResultStatus::Success)
ncas_title[rec.type] = std::move(next_nca);
}
break;
}
}
}
}
NSP::~NSP() = default;
@@ -208,66 +242,4 @@ VirtualDir NSP::GetParentDirectory() const {
bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
exefs = pfs;
const auto romfs_iter = std::find_if(files.begin(), files.end(), [](const VirtualFile& file) {
return file->GetName().rfind(".romfs") != std::string::npos;
});
if (romfs_iter == files.end()) {
return;
}
romfs = *romfs_iter;
}
void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
for (const auto& outer_file : files) {
if (outer_file->GetName().substr(outer_file->GetName().size() - 9) != ".cnmt.nca") {
continue;
}
const auto nca = std::make_shared<NCA>(outer_file);
if (nca->GetStatus() != Loader::ResultStatus::Success) {
program_status[nca->GetTitleId()] = nca->GetStatus();
continue;
}
const auto section0 = nca->GetSubdirectories()[0];
for (const auto& inner_file : section0->GetFiles()) {
if (inner_file->GetExtension() != "cnmt")
continue;
const CNMT cnmt(inner_file);
auto& ncas_title = ncas[cnmt.GetTitleID()];
ncas_title[ContentRecordType::Meta] = nca;
for (const auto& rec : cnmt.GetContentRecords()) {
const auto id_string = Common::HexArrayToString(rec.nca_id, false);
const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
if (next_file == nullptr) {
LOG_WARNING(Service_FS,
"NCA with ID {}.nca is listed in content metadata, but cannot "
"be found in PFS. NSP appears to be corrupted.",
id_string);
continue;
}
auto next_nca = std::make_shared<NCA>(next_file);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
(next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
(cnmt.GetTitleID() & 0x800) != 0)) {
ncas_title[rec.type] = std::move(next_nca);
}
}
break;
}
}
}
} // namespace FileSys

View File

@@ -59,12 +59,9 @@ protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
void ReadNCAs(const std::vector<VirtualFile>& files);
VirtualFile file;
bool extracted = false;
bool extracted;
Loader::ResultStatus status;
std::map<u64, Loader::ResultStatus> program_status;

View File

@@ -399,15 +399,6 @@ bool VfsDirectory::Copy(std::string_view src, std::string_view dest) {
return f2->WriteBytes(f1->ReadAllBytes()) == f1->GetSize();
}
std::map<std::string, VfsEntryType, std::less<>> VfsDirectory::GetEntries() const {
std::map<std::string, VfsEntryType, std::less<>> out;
for (const auto& dir : GetSubdirectories())
out.emplace(dir->GetName(), VfsEntryType::Directory);
for (const auto& file : GetFiles())
out.emplace(file->GetName(), VfsEntryType::File);
return out;
}
std::string VfsDirectory::GetFullPath() const {
if (IsRoot())
return GetName();
@@ -463,41 +454,13 @@ bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t
return true;
}
bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size) {
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
bool VfsRawCopy(VirtualFile src, VirtualFile dest) {
if (src == nullptr || dest == nullptr)
return false;
if (!dest->Resize(src->GetSize()))
return false;
std::vector<u8> temp(std::min(block_size, src->GetSize()));
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
const auto read = std::min(block_size, src->GetSize() - i);
const auto block = src->Read(temp.data(), read, i);
if (dest->Write(temp.data(), read, i) != read)
return false;
}
return true;
}
bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size) {
if (src == nullptr || dest == nullptr || !src->IsReadable() || !dest->IsWritable())
return false;
for (const auto& file : src->GetFiles()) {
const auto out = dest->CreateFile(file->GetName());
if (!VfsRawCopy(file, out, block_size))
return false;
}
for (const auto& dir : src->GetSubdirectories()) {
const auto out = dest->CreateSubdirectory(dir->GetName());
if (!VfsRawCopyD(dir, out, block_size))
return false;
}
return true;
std::vector<u8> data = src->ReadAllBytes();
return dest->WriteBytes(data, 0) == data.size();
}
VirtualDir GetOrCreateDirectoryRelative(const VirtualDir& rel, std::string_view path) {

View File

@@ -4,7 +4,6 @@
#pragma once
#include <map>
#include <memory>
#include <string>
#include <string_view>
@@ -12,12 +11,20 @@
#include <vector>
#include <boost/optional.hpp>
#include "common/common_types.h"
#include "core/file_sys/vfs_types.h"
namespace FileSys {
class VfsDirectory;
class VfsFile;
class VfsFilesystem;
enum class Mode : u32;
// Convenience typedefs to use Vfs* interfaces
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
using VirtualDir = std::shared_ptr<VfsDirectory>;
using VirtualFile = std::shared_ptr<VfsFile>;
// An enumeration representing what can be at the end of a path in a VfsFilesystem
enum class VfsEntryType {
None,
@@ -258,10 +265,6 @@ public:
// dest.
virtual bool Copy(std::string_view src, std::string_view dest);
// Gets all of the entries directly in the directory (files and dirs), returning a map between
// item name -> type.
virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
// Interprets the file with name file instead as a directory of type directory.
// The directory must have a constructor that takes a single argument of type
// std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
@@ -307,19 +310,13 @@ public:
bool Rename(std::string_view name) override;
};
// Compare the two files, byte-for-byte, in increments specified by block_size
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2,
std::size_t block_size = 0x1000);
// Compare the two files, byte-for-byte, in increments specificed by block_size
bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200);
// A method that copies the raw data between two different implementations of VirtualFile. If you
// are using the same implementation, it is probably better to use the Copy method in the parent
// directory of src/dest.
bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t block_size = 0x1000);
// A method that performs a similar function to VfsRawCopy above, but instead copies entire
// directories. It suffers the same performance penalties as above and an implementation-specific
// Copy should always be preferred.
bool VfsRawCopyD(const VirtualDir& src, const VirtualDir& dest, std::size_t block_size = 0x1000);
bool VfsRawCopy(VirtualFile src, VirtualFile dest);
// Checks if the directory at path relative to rel exists. If it does, returns that. If it does not
// it attempts to create it and returns the new dir or nullptr on failure.

View File

@@ -5,22 +5,17 @@
#include <algorithm>
#include <utility>
#include "common/assert.h"
#include "core/file_sys/vfs_concat.h"
#include "core/file_sys/vfs_static.h"
namespace FileSys {
static bool VerifyConcatenationMapContinuity(const std::map<u64, VirtualFile>& map) {
const auto last_valid = --map.end();
for (auto iter = map.begin(); iter != last_valid;) {
const auto old = iter++;
if (old->first + old->second->GetSize() != iter->first) {
return false;
}
}
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) {
if (files.empty())
return nullptr;
if (files.size() == 1)
return files[0];
return map.begin()->first == 0;
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name)
@@ -32,48 +27,8 @@ ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::s
}
}
ConcatenatedVfsFile::ConcatenatedVfsFile(std::map<u64, VirtualFile> files_, std::string name)
: files(std::move(files_)), name(std::move(name)) {
ASSERT(VerifyConcatenationMapContinuity(files));
}
ConcatenatedVfsFile::~ConcatenatedVfsFile() = default;
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(std::vector<VirtualFile> files,
std::string name) {
if (files.empty())
return nullptr;
if (files.size() == 1)
return files[0];
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
VirtualFile ConcatenatedVfsFile::MakeConcatenatedFile(u8 filler_byte,
std::map<u64, VirtualFile> files,
std::string name) {
if (files.empty())
return nullptr;
if (files.size() == 1)
return files.begin()->second;
const auto last_valid = --files.end();
for (auto iter = files.begin(); iter != last_valid;) {
const auto old = iter++;
if (old->first + old->second->GetSize() != iter->first) {
files.emplace(old->first + old->second->GetSize(),
std::make_shared<StaticVfsFile>(filler_byte, iter->first - old->first -
old->second->GetSize()));
}
}
// Ensure the map starts at offset 0 (start of file), otherwise pad to fill.
if (files.begin()->first != 0)
files.emplace(0, std::make_shared<StaticVfsFile>(filler_byte, files.begin()->first));
return std::shared_ptr<VfsFile>(new ConcatenatedVfsFile(std::move(files), std::move(name)));
}
std::string ConcatenatedVfsFile::GetName() const {
if (files.empty())
return "";
@@ -107,7 +62,7 @@ bool ConcatenatedVfsFile::IsReadable() const {
}
std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const {
auto entry = --files.end();
auto entry = files.end();
for (auto iter = files.begin(); iter != files.end(); ++iter) {
if (iter->first > offset) {
entry = --iter;
@@ -115,17 +70,20 @@ std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t
}
}
if (entry->first + entry->second->GetSize() <= offset)
// Check if the entry should be the last one. The loop above will make it end().
if (entry == files.end() && offset < files.rbegin()->first + files.rbegin()->second->GetSize())
--entry;
if (entry == files.end())
return 0;
const auto read_in =
std::min<u64>(entry->first + entry->second->GetSize() - offset, entry->second->GetSize());
if (length > read_in) {
return entry->second->Read(data, read_in, offset - entry->first) +
Read(data + read_in, length - read_in, offset + read_in);
const auto remaining = entry->second->GetSize() + offset - entry->first;
if (length > remaining) {
return entry->second->Read(data, remaining, offset - entry->first) +
Read(data + remaining, length - remaining, offset + remaining);
}
return entry->second->Read(data, std::min<u64>(read_in, length), offset - entry->first);
return entry->second->Read(data, length, offset - entry->first);
}
std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) {
@@ -135,5 +93,4 @@ std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::
bool ConcatenatedVfsFile::Rename(std::string_view name) {
return false;
}
} // namespace FileSys

View File

@@ -4,30 +4,26 @@
#pragma once
#include <map>
#include <memory>
#include <string_view>
#include <boost/container/flat_map.hpp>
#include "core/file_sys/vfs.h"
namespace FileSys {
// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name = "");
// Class that wraps multiple vfs files and concatenates them, making reads seamless. Currently
// read-only.
class ConcatenatedVfsFile : public VfsFile {
friend VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name);
ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name);
ConcatenatedVfsFile(std::map<u64, VirtualFile> files, std::string name);
public:
~ConcatenatedVfsFile() override;
/// Wrapper function to allow for more efficient handling of files.size() == 0, 1 cases.
static VirtualFile MakeConcatenatedFile(std::vector<VirtualFile> files, std::string name);
/// Convenience function that turns a map of offsets to files into a concatenated file, filling
/// gaps with a given filler byte.
static VirtualFile MakeConcatenatedFile(u8 filler_byte, std::map<u64, VirtualFile> files,
std::string name);
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
@@ -40,7 +36,7 @@ public:
private:
// Maps starting offset to file -- more efficient.
std::map<u64, VirtualFile> files;
boost::container::flat_map<u64, VirtualFile> files;
std::string name;
};

View File

@@ -1,132 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <utility>
#include "core/file_sys/vfs_layered.h"
namespace FileSys {
LayeredVfsDirectory::LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name)
: dirs(std::move(dirs)), name(std::move(name)) {}
LayeredVfsDirectory::~LayeredVfsDirectory() = default;
VirtualDir LayeredVfsDirectory::MakeLayeredDirectory(std::vector<VirtualDir> dirs,
std::string name) {
if (dirs.empty())
return nullptr;
if (dirs.size() == 1)
return dirs[0];
return std::shared_ptr<VfsDirectory>(new LayeredVfsDirectory(std::move(dirs), std::move(name)));
}
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFileRelative(std::string_view path) const {
for (const auto& layer : dirs) {
const auto file = layer->GetFileRelative(path);
if (file != nullptr)
return file;
}
return nullptr;
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetDirectoryRelative(
std::string_view path) const {
std::vector<VirtualDir> out;
for (const auto& layer : dirs) {
auto dir = layer->GetDirectoryRelative(path);
if (dir != nullptr)
out.push_back(std::move(dir));
}
return MakeLayeredDirectory(std::move(out));
}
std::shared_ptr<VfsFile> LayeredVfsDirectory::GetFile(std::string_view name) const {
return GetFileRelative(name);
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetSubdirectory(std::string_view name) const {
return GetDirectoryRelative(name);
}
std::string LayeredVfsDirectory::GetFullPath() const {
return dirs[0]->GetFullPath();
}
std::vector<std::shared_ptr<VfsFile>> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualFile> out;
for (const auto& layer : dirs) {
for (const auto& file : layer->GetFiles()) {
if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) {
return comp->GetName() == file->GetName();
}) == out.end()) {
out.push_back(file);
}
}
}
return out;
}
std::vector<std::shared_ptr<VfsDirectory>> LayeredVfsDirectory::GetSubdirectories() const {
std::vector<std::string> names;
for (const auto& layer : dirs) {
for (const auto& sd : layer->GetSubdirectories()) {
if (std::find(names.begin(), names.end(), sd->GetName()) == names.end())
names.push_back(sd->GetName());
}
}
std::vector<VirtualDir> out;
out.reserve(names.size());
for (const auto& subdir : names)
out.push_back(GetSubdirectory(subdir));
return out;
}
bool LayeredVfsDirectory::IsWritable() const {
return false;
}
bool LayeredVfsDirectory::IsReadable() const {
return true;
}
std::string LayeredVfsDirectory::GetName() const {
return name.empty() ? dirs[0]->GetName() : name;
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::GetParentDirectory() const {
return dirs[0]->GetParentDirectory();
}
std::shared_ptr<VfsDirectory> LayeredVfsDirectory::CreateSubdirectory(std::string_view name) {
return nullptr;
}
std::shared_ptr<VfsFile> LayeredVfsDirectory::CreateFile(std::string_view name) {
return nullptr;
}
bool LayeredVfsDirectory::DeleteSubdirectory(std::string_view name) {
return false;
}
bool LayeredVfsDirectory::DeleteFile(std::string_view name) {
return false;
}
bool LayeredVfsDirectory::Rename(std::string_view name_) {
name = name_;
return true;
}
bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}
} // namespace FileSys

View File

@@ -1,50 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "core/file_sys/vfs.h"
namespace FileSys {
// Class that stacks multiple VfsDirectories on top of each other, attempting to read from the first
// one and falling back to the one after. The highest priority directory (overwrites all others)
// should be element 0 in the dirs vector.
class LayeredVfsDirectory : public VfsDirectory {
LayeredVfsDirectory(std::vector<VirtualDir> dirs, std::string name);
public:
~LayeredVfsDirectory() override;
/// Wrapper function to allow for more efficient handling of dirs.size() == 0, 1 cases.
static VirtualDir MakeLayeredDirectory(std::vector<VirtualDir> dirs, std::string name = "");
std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override;
std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override;
std::shared_ptr<VfsFile> GetFile(std::string_view name) const override;
std::shared_ptr<VfsDirectory> GetSubdirectory(std::string_view name) const override;
std::string GetFullPath() const override;
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::string GetName() const override;
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
std::shared_ptr<VfsDirectory> CreateSubdirectory(std::string_view name) override;
std::shared_ptr<VfsFile> CreateFile(std::string_view name) override;
bool DeleteSubdirectory(std::string_view name) override;
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
private:
std::vector<VirtualDir> dirs;
std::string name;
};
} // namespace FileSys

View File

@@ -413,23 +413,6 @@ std::string RealVfsDirectory::GetFullPath() const {
return out;
}
std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries() const {
if (perms == Mode::Append)
return {};
std::map<std::string, VfsEntryType, std::less<>> out;
FileUtil::ForeachDirectoryEntry(
nullptr, path,
[&out](u64* entries_out, const std::string& directory, const std::string& filename) {
const std::string full_path = directory + DIR_SEP + filename;
out.emplace(filename, FileUtil::IsDirectory(full_path) ? VfsEntryType::Directory
: VfsEntryType::File);
return true;
});
return out;
}
bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
return false;
}

View File

@@ -98,7 +98,6 @@ public:
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
std::string GetFullPath() const override;
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
protected:
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;

View File

@@ -1,79 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <memory>
#include <string_view>
#include "core/file_sys/vfs.h"
namespace FileSys {
class StaticVfsFile : public VfsFile {
public:
explicit StaticVfsFile(u8 value, std::size_t size = 0, std::string name = "",
VirtualDir parent = nullptr)
: value{value}, size{size}, name{std::move(name)}, parent{std::move(parent)} {}
std::string GetName() const override {
return name;
}
std::size_t GetSize() const override {
return size;
}
bool Resize(std::size_t new_size) override {
size = new_size;
return true;
}
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
return parent;
}
bool IsWritable() const override {
return false;
}
bool IsReadable() const override {
return true;
}
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override {
const auto read = std::min(length, size - offset);
std::fill(data, data + read, value);
return read;
}
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
return 0;
}
boost::optional<u8> ReadByte(std::size_t offset) const override {
if (offset < size)
return value;
return boost::none;
}
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
const auto read = std::min(length, size - offset);
return std::vector<u8>(read, value);
}
bool Rename(std::string_view new_name) override {
name = new_name;
return true;
}
private:
u8 value;
std::size_t size;
std::string name;
VirtualDir parent;
};
} // namespace FileSys

View File

@@ -1,21 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
namespace FileSys {
class VfsDirectory;
class VfsFile;
class VfsFilesystem;
// Declarations for Vfs* pointer types
using VirtualDir = std::shared_ptr<VfsDirectory>;
using VirtualFile = std::shared_ptr<VfsFile>;
using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
} // namespace FileSys

View File

@@ -3,64 +3,10 @@
// Refer to the license.txt file included.
#include <algorithm>
#include <cstring>
#include <utility>
#include "core/file_sys/vfs_vector.h"
namespace FileSys {
VectorVfsFile::VectorVfsFile(std::vector<u8> initial_data, std::string name, VirtualDir parent)
: data(std::move(initial_data)), parent(std::move(parent)), name(std::move(name)) {}
VectorVfsFile::~VectorVfsFile() = default;
std::string VectorVfsFile::GetName() const {
return name;
}
size_t VectorVfsFile::GetSize() const {
return data.size();
}
bool VectorVfsFile::Resize(size_t new_size) {
data.resize(new_size);
return true;
}
std::shared_ptr<VfsDirectory> VectorVfsFile::GetContainingDirectory() const {
return parent;
}
bool VectorVfsFile::IsWritable() const {
return true;
}
bool VectorVfsFile::IsReadable() const {
return true;
}
std::size_t VectorVfsFile::Read(u8* data_, std::size_t length, std::size_t offset) const {
const auto read = std::min(length, data.size() - offset);
std::memcpy(data_, data.data() + offset, read);
return read;
}
std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_t offset) {
if (offset + length > data.size())
data.resize(offset + length);
const auto write = std::min(length, data.size() - offset);
std::memcpy(data.data(), data_, write);
return write;
}
bool VectorVfsFile::Rename(std::string_view name_) {
name = name_;
return true;
}
void VectorVfsFile::Assign(std::vector<u8> new_data) {
data = std::move(new_data);
}
VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
std::vector<VirtualDir> dirs_, std::string name_,
VirtualDir parent_)

View File

@@ -8,31 +8,6 @@
namespace FileSys {
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
class VectorVfsFile : public VfsFile {
public:
explicit VectorVfsFile(std::vector<u8> initial_data = {}, std::string name = "",
VirtualDir parent = nullptr);
~VectorVfsFile() override;
std::string GetName() const override;
std::size_t GetSize() const override;
bool Resize(std::size_t new_size) override;
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
bool IsWritable() const override;
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
bool Rename(std::string_view name) override;
virtual void Assign(std::vector<u8> new_data);
private:
std::vector<u8> data;
VirtualDir parent;
std::string name;
};
// An implementation of VfsDirectory that maintains two vectors for subdirectories and files.
// Vector data is supplied upon construction.
class VectorVfsDirectory : public VfsDirectory {

View File

@@ -37,9 +37,7 @@
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/loader.h"
#include "core/memory.h"
@@ -209,7 +207,7 @@ static Kernel::Thread* FindThreadById(int id) {
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadID() == static_cast<u32>(id)) {
if (thread->GetThreadId() == static_cast<u32>(id)) {
current_core = core;
return thread.get();
}
@@ -223,18 +221,16 @@ static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) {
return 0;
}
const auto& thread_context = thread->GetContext();
if (id < SP_REGISTER) {
return thread_context.cpu_registers[id];
return thread->context.cpu_registers[id];
} else if (id == SP_REGISTER) {
return thread_context.sp;
return thread->context.sp;
} else if (id == PC_REGISTER) {
return thread_context.pc;
return thread->context.pc;
} else if (id == PSTATE_REGISTER) {
return thread_context.pstate;
return thread->context.pstate;
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
return thread_context.vector_registers[id - UC_ARM64_REG_Q0][0];
return thread->context.vector_registers[id - UC_ARM64_REG_Q0][0];
} else {
return 0;
}
@@ -245,18 +241,16 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr)
return;
}
auto& thread_context = thread->GetContext();
if (id < SP_REGISTER) {
thread_context.cpu_registers[id] = val;
thread->context.cpu_registers[id] = val;
} else if (id == SP_REGISTER) {
thread_context.sp = val;
thread->context.sp = val;
} else if (id == PC_REGISTER) {
thread_context.pc = val;
thread->context.pc = val;
} else if (id == PSTATE_REGISTER) {
thread_context.pstate = static_cast<u32>(val);
thread->context.pstate = val;
} else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) {
thread_context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
thread->context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val;
}
}
@@ -591,15 +585,14 @@ static void HandleQuery() {
strlen("Xfer:features:read:target.xml:")) == 0) {
SendReply(target_xml);
} else if (strncmp(query, "Offsets", strlen("Offsets")) == 0) {
const VAddr base_address = Core::CurrentProcess()->VMManager().GetCodeRegionBaseAddress();
std::string buffer = fmt::format("TextSeg={:0x}", base_address);
std::string buffer = fmt::format("TextSeg={:0x}", Memory::PROCESS_IMAGE_VADDR);
SendReply(buffer.c_str());
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
std::string val = "m";
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x}", thread->GetThreadID());
val += fmt::format("{:x}", thread->GetThreadId());
val += ",";
}
}
@@ -616,7 +609,7 @@ static void HandleQuery() {
for (const auto& thread : threads) {
buffer +=
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
thread->GetThreadID(), core, thread->GetThreadID());
thread->GetThreadId(), core, thread->GetThreadId());
}
}
buffer += "</threads>";
@@ -697,7 +690,7 @@ static void SendSignal(Kernel::Thread* thread, u32 signal, bool full = true) {
}
if (thread) {
buffer += fmt::format(";thread:{:x};", thread->GetThreadID());
buffer += fmt::format(";thread:{:x};", thread->GetThreadId());
}
SendReply(buffer.c_str());
@@ -861,9 +854,7 @@ static void WriteRegister() {
}
// Update Unicorn context skipping scheduler, no running threads at this point
Core::System::GetInstance()
.ArmInterface(current_core)
.LoadContext(current_thread->GetContext());
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
SendReply("OK");
}
@@ -892,9 +883,7 @@ static void WriteRegisters() {
}
// Update Unicorn context skipping scheduler, no running threads at this point
Core::System::GetInstance()
.ArmInterface(current_core)
.LoadContext(current_thread->GetContext());
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
SendReply("OK");
}
@@ -904,11 +893,11 @@ static void ReadMemory() {
static u8 reply[GDB_BUFFER_SIZE - 4];
auto start_offset = command_buffer + 1;
const auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
const VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
VAddr addr = HexToLong(start_offset, static_cast<u64>(addr_pos - start_offset));
start_offset = addr_pos + 1;
const u64 len =
u64 len =
HexToLong(start_offset, static_cast<u64>((command_buffer + command_length) - start_offset));
LOG_DEBUG(Debug_GDBStub, "gdb: addr: {:016X} len: {:016X}", addr, len);
@@ -917,9 +906,7 @@ static void ReadMemory() {
SendReply("E01");
}
const auto& vm_manager = Core::CurrentProcess()->VMManager();
if (addr < vm_manager.GetCodeRegionBaseAddress() ||
addr >= vm_manager.GetMapRegionEndAddress()) {
if (addr < Memory::PROCESS_IMAGE_VADDR || addr >= Memory::MAP_REGION_VADDR_END) {
return SendReply("E00");
}
@@ -968,9 +955,7 @@ static void Step() {
if (command_length > 1) {
RegWrite(PC_REGISTER, GdbHexToLong(command_buffer + 1), current_thread);
// Update Unicorn context skipping scheduler, no running threads at this point
Core::System::GetInstance()
.ArmInterface(current_core)
.LoadContext(current_thread->GetContext());
Core::System::GetInstance().ArmInterface(current_core).LoadContext(current_thread->context);
}
step_loop = true;
halt_loop = true;

View File

@@ -23,13 +23,13 @@ namespace AddressArbiter {
// Performs actual address waiting logic.
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
SharedPtr<Thread> current_thread = GetCurrentThread();
current_thread->SetArbiterWaitAddress(address);
current_thread->SetStatus(ThreadStatus::WaitArb);
current_thread->InvalidateWakeupCallback();
current_thread->arb_wait_address = address;
current_thread->status = ThreadStatus::WaitArb;
current_thread->wakeup_callback = nullptr;
current_thread->WakeAfterDelay(timeout);
Core::System::GetInstance().CpuCore(current_thread->GetProcessorID()).PrepareReschedule();
Core::System::GetInstance().CpuCore(current_thread->processor_id).PrepareReschedule();
return RESULT_TIMEOUT;
}
@@ -39,10 +39,10 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr arb_addr) {
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
const auto& thread_list = scheduler->GetThreadList();
auto& thread_list = scheduler->GetThreadList();
for (const auto& thread : thread_list) {
if (thread->GetArbiterWaitAddress() == arb_addr)
for (auto& thread : thread_list) {
if (thread->arb_wait_address == arb_addr)
waiting_threads.push_back(thread);
}
};
@@ -57,7 +57,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
// Sort them by priority, such that the highest priority ones come first.
std::sort(threads.begin(), threads.end(),
[](const SharedPtr<Thread>& lhs, const SharedPtr<Thread>& rhs) {
return lhs->GetPriority() < rhs->GetPriority();
return lhs->current_priority < rhs->current_priority;
});
return threads;
@@ -73,9 +73,9 @@ static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num
// Signal the waiting threads.
for (std::size_t i = 0; i < last; i++) {
ASSERT(waiting_threads[i]->GetStatus() == ThreadStatus::WaitArb);
ASSERT(waiting_threads[i]->status == ThreadStatus::WaitArb);
waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS);
waiting_threads[i]->SetArbiterWaitAddress(0);
waiting_threads[i]->arb_wait_address = 0;
waiting_threads[i]->ResumeFromWait();
}
}

View File

@@ -17,10 +17,6 @@ namespace Kernel {
ClientPort::ClientPort(KernelCore& kernel) : Object{kernel} {}
ClientPort::~ClientPort() = default;
SharedPtr<ServerPort> ClientPort::GetServerPort() const {
return server_port;
}
ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
// Note: Threads do not wait for the server endpoint to call
// AcceptSession before returning from this call.

View File

@@ -30,8 +30,6 @@ public:
return HANDLE_TYPE;
}
SharedPtr<ServerPort> GetServerPort() const;
/**
* Creates a new Session pair, adds the created ServerSession to the associated ServerPort's
* list of pending sessions, and signals the ServerPort, causing any threads

View File

@@ -22,7 +22,6 @@ enum {
HandleTableFull = 105,
InvalidMemoryState = 106,
InvalidMemoryPermissions = 108,
InvalidMemoryRange = 110,
InvalidThreadPriority = 112,
InvalidProcessorId = 113,
InvalidHandle = 114,
@@ -32,7 +31,6 @@ enum {
TooLarge = 119,
InvalidEnumValue = 120,
NoSuchEntry = 121,
AlreadyRegistered = 122,
InvalidState = 125,
ResourceLimitExceeded = 132,
};
@@ -57,11 +55,9 @@ constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidA
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
ErrCodes::InvalidMemoryPermissions);
constexpr ResultCode ERR_INVALID_MEMORY_RANGE(ErrorModule::Kernel, ErrCodes::InvalidMemoryRange);
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
constexpr ResultCode ERR_INVALID_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize);
constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::Kernel, ErrCodes::AlreadyRegistered);
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel,
ErrCodes::InvalidThreadPriority);

View File

@@ -42,14 +42,14 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
Kernel::SharedPtr<Kernel::Event> event) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
thread->SetWakeupCallback([context = *this, callback](
thread->wakeup_callback = [context = *this, callback](
ThreadWakeupReason reason, SharedPtr<Thread> thread,
SharedPtr<WaitObject> object, std::size_t index) mutable -> bool {
ASSERT(thread->GetStatus() == ThreadStatus::WaitHLEEvent);
ASSERT(thread->status == ThreadStatus::WaitHLEEvent);
callback(thread, context, reason);
context.WriteToOutgoingCommandBuffer(*thread);
return true;
});
};
if (!event) {
// Create event if not provided
@@ -59,8 +59,8 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread,
}
event->Clear();
thread->SetStatus(ThreadStatus::WaitHLEEvent);
thread->SetWaitObjects({event});
thread->status = ThreadStatus::WaitHLEEvent;
thread->wait_objects = {event};
event->AddWaitingThread(thread);
if (timeout > 0) {
@@ -209,7 +209,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
Memory::ReadBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
// The header was already built in the internal command buffer. Attempt to parse it to verify
@@ -268,7 +268,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
}
// Copy the translated command buffer back into the thread's command buffer area.
Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
Memory::WriteBlock(*thread.owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
return RESULT_SUCCESS;

Some files were not shown because too many files have changed in this diff Show More