Compare commits
178 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54ddb37b3c | ||
|
|
df5a44a40b | ||
|
|
fb65076b0f | ||
|
|
3ef134a092 | ||
|
|
2ea45fe75b | ||
|
|
a0e51d8b98 | ||
|
|
8e7497d5bb | ||
|
|
2ec9fbc2d4 | ||
|
|
a42376dfad | ||
|
|
ce97d8ef6c | ||
|
|
60899b80f0 | ||
|
|
938aa5779c | ||
|
|
926dd41587 | ||
|
|
49c4fe1f2f | ||
|
|
4a43fb7e1d | ||
|
|
d9e21eebe8 | ||
|
|
cc50857460 | ||
|
|
957ddab679 | ||
|
|
a99d9db32f | ||
|
|
79217f9870 | ||
|
|
0821a210c4 | ||
|
|
44fac34697 | ||
|
|
9f2bcdbb76 | ||
|
|
fbe462099b | ||
|
|
3c5c292592 | ||
|
|
9b3bc0b282 | ||
|
|
04d723baf9 | ||
|
|
e89c22c147 | ||
|
|
a9b953e6d4 | ||
|
|
3a2567c97c | ||
|
|
475222a496 | ||
|
|
3ee4fa557f | ||
|
|
c245150439 | ||
|
|
89825766ee | ||
|
|
522a11a11f | ||
|
|
4a9acc87f9 | ||
|
|
7bb226f22d | ||
|
|
0e61e8362f | ||
|
|
7fe10dea3e | ||
|
|
c243bc09d4 | ||
|
|
bad035e9a3 | ||
|
|
c061e27155 | ||
|
|
429217248f | ||
|
|
325c259fc5 | ||
|
|
cdddd71d08 | ||
|
|
3e973bc4c6 | ||
|
|
5c676dc884 | ||
|
|
3f0922715a | ||
|
|
1470b85af9 | ||
|
|
2f0ff4d25b | ||
|
|
143525dcb9 | ||
|
|
46ba1bc40f | ||
|
|
6ac955a0b4 | ||
|
|
4c3bd33be2 | ||
|
|
3bac3051fc | ||
|
|
804115b2a4 | ||
|
|
d6e8e16a66 | ||
|
|
12445b476d | ||
|
|
d884e805c5 | ||
|
|
4bea6657ef | ||
|
|
ae0c95efcc | ||
|
|
c1b8cd9058 | ||
|
|
0cfb0bacb2 | ||
|
|
d2f788762a | ||
|
|
17f8059fea | ||
|
|
c560043581 | ||
|
|
4c0b1cc1ae | ||
|
|
035e6bd407 | ||
|
|
1c34498368 | ||
|
|
ac959799e4 | ||
|
|
49b15af054 | ||
|
|
f9e468d891 | ||
|
|
7ddd5b765d | ||
|
|
50c191439d | ||
|
|
3b8a0bc146 | ||
|
|
136040ee15 | ||
|
|
e58855c7a4 | ||
|
|
00131e752d | ||
|
|
223ddb2008 | ||
|
|
fcf81147e7 | ||
|
|
73a2d71f44 | ||
|
|
bd8065295c | ||
|
|
0acf9b351f | ||
|
|
073a21ac0b | ||
|
|
64e45b04e0 | ||
|
|
55af5bda55 | ||
|
|
3dcedb36b4 | ||
|
|
6d64ecf359 | ||
|
|
500e81429a | ||
|
|
5ff72a48a7 | ||
|
|
82a313a14c | ||
|
|
fdb199290b | ||
|
|
af074ee422 | ||
|
|
deff28d3c0 | ||
|
|
3d9776f36a | ||
|
|
1aa195a9c0 | ||
|
|
e51bd49f87 | ||
|
|
7e697ab7ff | ||
|
|
6d9dd1dc6d | ||
|
|
112351d557 | ||
|
|
a6efff8b02 | ||
|
|
a76f0d5d06 | ||
|
|
4048b54ef7 | ||
|
|
9cd79c25ed | ||
|
|
2515d2433b | ||
|
|
8b08cb925b | ||
|
|
975226e7ff | ||
|
|
a8974f0556 | ||
|
|
23ae7cf9db | ||
|
|
fdd5c97a14 | ||
|
|
f165a85398 | ||
|
|
0731383124 | ||
|
|
05f6f59ffb | ||
|
|
ce8291f6c5 | ||
|
|
9dccf7e1fa | ||
|
|
030676b95d | ||
|
|
a439f7b6e1 | ||
|
|
b56e5edafc | ||
|
|
460ebc8187 | ||
|
|
6ac1bd9f5d | ||
|
|
7e9b79955f | ||
|
|
564b7fdc9c | ||
|
|
c08c5d346a | ||
|
|
9382414b20 | ||
|
|
e3af341d5b | ||
|
|
3f17fe7133 | ||
|
|
a164b413fa | ||
|
|
9273c02427 | ||
|
|
b89dda2b98 | ||
|
|
9947c6ad59 | ||
|
|
9b50dca2bb | ||
|
|
009a2cc9cc | ||
|
|
6faf1b0972 | ||
|
|
820f646458 | ||
|
|
948f6c0738 | ||
|
|
ddcdbce067 | ||
|
|
8d685a29bc | ||
|
|
14230fe2af | ||
|
|
68296d9474 | ||
|
|
8f4e09ba07 | ||
|
|
56ab608044 | ||
|
|
54724fe918 | ||
|
|
b155b3ef81 | ||
|
|
a859a35ec8 | ||
|
|
fbaefc47a0 | ||
|
|
742f895f8b | ||
|
|
a781042700 | ||
|
|
77554ac773 | ||
|
|
6f09c5b128 | ||
|
|
94f193af65 | ||
|
|
a6ae765410 | ||
|
|
aba988f71c | ||
|
|
7f15306f78 | ||
|
|
6bd6beee20 | ||
|
|
d3ad9469a1 | ||
|
|
c0b7ed8b58 | ||
|
|
527e362a83 | ||
|
|
50a806ea67 | ||
|
|
c913136eb2 | ||
|
|
7d5d781b20 | ||
|
|
23a16c1720 | ||
|
|
92e26df00f | ||
|
|
c91b60a421 | ||
|
|
cbd517d8cc | ||
|
|
2814ca3624 | ||
|
|
a6e75cd45b | ||
|
|
9664ce255d | ||
|
|
f92b3512e0 | ||
|
|
8e150c46b9 | ||
|
|
f5e03b9173 | ||
|
|
08fcb4694f | ||
|
|
97bf83bc56 | ||
|
|
8e900a301a | ||
|
|
54e7ddb93a | ||
|
|
1efe5a76b1 | ||
|
|
9951f6d054 | ||
|
|
d2caf4af7d | ||
|
|
99fbcb3bf2 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -31,3 +31,6 @@
|
||||
[submodule "opus"]
|
||||
path = externals/opus
|
||||
url = https://github.com/ogniK5377/opus.git
|
||||
[submodule "soundtouch"]
|
||||
path = externals/soundtouch
|
||||
url = https://github.com/citra-emu/ext-soundtouch.git
|
||||
|
||||
@@ -431,6 +431,9 @@ enable_testing()
|
||||
add_subdirectory(externals)
|
||||
add_subdirectory(src)
|
||||
|
||||
# Set yuzu project as default StartUp Project in Visual Studio
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT yuzu)
|
||||
|
||||
|
||||
# Installation instructions
|
||||
# =========================
|
||||
|
||||
6
externals/CMakeLists.txt
vendored
6
externals/CMakeLists.txt
vendored
@@ -43,10 +43,16 @@ target_include_directories(mbedtls PUBLIC ./mbedtls/include)
|
||||
add_library(microprofile INTERFACE)
|
||||
target_include_directories(microprofile INTERFACE ./microprofile)
|
||||
|
||||
# Open Source Archives
|
||||
add_subdirectory(open_source_archives EXCLUDE_FROM_ALL)
|
||||
|
||||
# Unicorn
|
||||
add_library(unicorn-headers INTERFACE)
|
||||
target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
|
||||
|
||||
# SoundTouch
|
||||
add_subdirectory(soundtouch)
|
||||
|
||||
# Xbyak
|
||||
if (ARCHITECTURE_x86_64)
|
||||
# Defined before "dynarmic" above
|
||||
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 0435ac2d80...959446573f
16
externals/open_source_archives/CMakeLists.txt
vendored
Normal file
16
externals/open_source_archives/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
add_library(open_source_archives
|
||||
src/FontChineseSimplified.cpp
|
||||
src/FontChineseTraditional.cpp
|
||||
src/FontExtendedChineseSimplified.cpp
|
||||
src/FontKorean.cpp
|
||||
src/FontNintendoExtended.cpp
|
||||
src/FontStandard.cpp
|
||||
include/FontChineseSimplified.h
|
||||
include/FontChineseTraditional.h
|
||||
include/FontExtendedChineseSimplified.h
|
||||
include/FontKorean.h
|
||||
include/FontNintendoExtended.h
|
||||
include/FontStandard.h
|
||||
)
|
||||
|
||||
target_include_directories(open_source_archives PUBLIC include)
|
||||
4
externals/open_source_archives/Readme.md
vendored
Normal file
4
externals/open_source_archives/Readme.md
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
These files were generated by https://github.com/FearlessTobi/yuzu_system_archives at git commit 0a24b0c9f38d71fb2c4bba5645a39029e539a5ec. To generate the files use the run.sh inside that repository.
|
||||
|
||||
The follwing system archives are currently included:
|
||||
- JPN/EUR/USA System Font
|
||||
6
externals/open_source_archives/include/FontChineseSimplified.h
vendored
Normal file
6
externals/open_source_archives/include/FontChineseSimplified.h
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
extern const std::array<unsigned char, 217276> FontChineseSimplified;
|
||||
|
||||
6
externals/open_source_archives/include/FontChineseTraditional.h
vendored
Normal file
6
externals/open_source_archives/include/FontChineseTraditional.h
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
extern const std::array<unsigned char, 222236> FontChineseTraditional;
|
||||
|
||||
6
externals/open_source_archives/include/FontExtendedChineseSimplified.h
vendored
Normal file
6
externals/open_source_archives/include/FontExtendedChineseSimplified.h
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
extern const std::array<unsigned char, 293516> FontExtendedChineseSimplified;
|
||||
|
||||
6
externals/open_source_archives/include/FontKorean.h
vendored
Normal file
6
externals/open_source_archives/include/FontKorean.h
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
extern const std::array<unsigned char, 217276> FontKorean;
|
||||
|
||||
6
externals/open_source_archives/include/FontNintendoExtended.h
vendored
Normal file
6
externals/open_source_archives/include/FontNintendoExtended.h
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
extern const std::array<unsigned char, 172064> FontNintendoExtended;
|
||||
|
||||
6
externals/open_source_archives/include/FontStandard.h
vendored
Normal file
6
externals/open_source_archives/include/FontStandard.h
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
extern const std::array<unsigned char, 217276> FontStandard;
|
||||
|
||||
18112
externals/open_source_archives/src/FontChineseSimplified.cpp
vendored
Normal file
18112
externals/open_source_archives/src/FontChineseSimplified.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18525
externals/open_source_archives/src/FontChineseTraditional.cpp
vendored
Normal file
18525
externals/open_source_archives/src/FontChineseTraditional.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
24465
externals/open_source_archives/src/FontExtendedChineseSimplified.cpp
vendored
Normal file
24465
externals/open_source_archives/src/FontExtendedChineseSimplified.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18112
externals/open_source_archives/src/FontKorean.cpp
vendored
Normal file
18112
externals/open_source_archives/src/FontKorean.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14344
externals/open_source_archives/src/FontNintendoExtended.cpp
vendored
Normal file
14344
externals/open_source_archives/src/FontNintendoExtended.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
18112
externals/open_source_archives/src/FontStandard.cpp
vendored
Normal file
18112
externals/open_source_archives/src/FontStandard.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
externals/soundtouch
vendored
Submodule
1
externals/soundtouch
vendored
Submodule
Submodule externals/soundtouch added at 060181eaf2
2
externals/xbyak
vendored
2
externals/xbyak
vendored
Submodule externals/xbyak updated: 71b75f653f...1de435ed04
@@ -17,6 +17,8 @@ add_library(audio_core STATIC
|
||||
sink_stream.h
|
||||
stream.cpp
|
||||
stream.h
|
||||
time_stretch.cpp
|
||||
time_stretch.h
|
||||
|
||||
$<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
|
||||
)
|
||||
@@ -24,6 +26,7 @@ add_library(audio_core STATIC
|
||||
create_target_directory_groups(audio_core)
|
||||
|
||||
target_link_libraries(audio_core PUBLIC common core)
|
||||
target_link_libraries(audio_core PRIVATE SoundTouch)
|
||||
|
||||
if(ENABLE_CUBEB)
|
||||
target_link_libraries(audio_core PRIVATE cubeb)
|
||||
|
||||
@@ -17,10 +17,10 @@ AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
|
||||
|
||||
audio_core = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_core->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_core->StartStream(stream);
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
audio_out->StartStream(stream);
|
||||
|
||||
QueueMixedBuffer(0);
|
||||
QueueMixedBuffer(1);
|
||||
@@ -236,11 +236,11 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
}
|
||||
}
|
||||
}
|
||||
audio_core->QueueBuffer(stream, tag, std::move(buffer));
|
||||
audio_out->QueueBuffer(stream, tag, std::move(buffer));
|
||||
}
|
||||
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||
const auto released_buffers{audio_core->GetTagsAndReleaseBuffers(stream, 2)};
|
||||
const auto released_buffers{audio_out->GetTagsAndReleaseBuffers(stream, 2)};
|
||||
for (const auto& tag : released_buffers) {
|
||||
QueueMixedBuffer(tag);
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ private:
|
||||
AudioRendererParameter worker_params;
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_event;
|
||||
std::vector<VoiceState> voices;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_core;
|
||||
std::unique_ptr<AudioCore::AudioOut> audio_out;
|
||||
AudioCore::StreamPtr stream;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,27 +3,23 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_core/cubeb_sink.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "audio_core/time_stretch.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/ring_buffer.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class SinkStreamImpl final : public SinkStream {
|
||||
class CubebSinkStream final : public SinkStream {
|
||||
public:
|
||||
SinkStreamImpl(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
|
||||
const std::string& name)
|
||||
: ctx{ctx}, num_channels{num_channels_} {
|
||||
|
||||
if (num_channels == 6) {
|
||||
// 6-channel audio does not seem to work with cubeb + SDL, so we downsample this to 2
|
||||
// channel for now
|
||||
is_6_channel = true;
|
||||
num_channels = 2;
|
||||
}
|
||||
CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
|
||||
const std::string& name)
|
||||
: ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
|
||||
num_channels} {
|
||||
|
||||
cubeb_stream_params params{};
|
||||
params.rate = sample_rate;
|
||||
@@ -38,7 +34,7 @@ public:
|
||||
|
||||
if (cubeb_stream_init(ctx, &stream_backend, name.c_str(), nullptr, nullptr, output_device,
|
||||
¶ms, std::max(512u, minimum_latency),
|
||||
&SinkStreamImpl::DataCallback, &SinkStreamImpl::StateCallback,
|
||||
&CubebSinkStream::DataCallback, &CubebSinkStream::StateCallback,
|
||||
this) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream");
|
||||
return;
|
||||
@@ -50,7 +46,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
~SinkStreamImpl() {
|
||||
~CubebSinkStream() {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
@@ -62,27 +58,32 @@ public:
|
||||
cubeb_stream_destroy(stream_backend);
|
||||
}
|
||||
|
||||
void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) override {
|
||||
if (!ctx) {
|
||||
void EnqueueSamples(u32 source_num_channels, const std::vector<s16>& samples) override {
|
||||
if (source_num_channels > num_channels) {
|
||||
// Downsample 6 channels to 2
|
||||
std::vector<s16> buf;
|
||||
buf.reserve(samples.size() * num_channels / source_num_channels);
|
||||
for (size_t i = 0; i < samples.size(); i += source_num_channels) {
|
||||
for (size_t ch = 0; ch < num_channels; ch++) {
|
||||
buf.push_back(samples[i + ch]);
|
||||
}
|
||||
}
|
||||
queue.Push(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock{queue_mutex};
|
||||
queue.Push(samples);
|
||||
}
|
||||
|
||||
queue.reserve(queue.size() + samples.size() * GetNumChannels());
|
||||
size_t SamplesInQueue(u32 num_channels) const override {
|
||||
if (!ctx)
|
||||
return 0;
|
||||
|
||||
if (is_6_channel) {
|
||||
// Downsample 6 channels to 2
|
||||
const size_t sample_count_copy_size = samples.size() * 2;
|
||||
queue.reserve(sample_count_copy_size);
|
||||
for (size_t i = 0; i < samples.size(); i += num_channels) {
|
||||
queue.push_back(samples[i]);
|
||||
queue.push_back(samples[i + 1]);
|
||||
}
|
||||
} else {
|
||||
// Copy as-is
|
||||
std::copy(samples.begin(), samples.end(), std::back_inserter(queue));
|
||||
}
|
||||
return queue.Size() / num_channels;
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
should_flush = true;
|
||||
}
|
||||
|
||||
u32 GetNumChannels() const {
|
||||
@@ -95,10 +96,11 @@ private:
|
||||
cubeb* ctx{};
|
||||
cubeb_stream* stream_backend{};
|
||||
u32 num_channels{};
|
||||
bool is_6_channel{};
|
||||
|
||||
std::mutex queue_mutex;
|
||||
std::vector<s16> queue;
|
||||
Common::RingBuffer<s16, 0x10000> queue;
|
||||
std::array<s16, 2> last_frame;
|
||||
std::atomic<bool> should_flush{};
|
||||
TimeStretcher time_stretch;
|
||||
|
||||
static long DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* output_buffer, long num_frames);
|
||||
@@ -144,38 +146,52 @@ CubebSink::~CubebSink() {
|
||||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
|
||||
const std::string& name) {
|
||||
sink_streams.push_back(
|
||||
std::make_unique<SinkStreamImpl>(ctx, sample_rate, num_channels, output_device, name));
|
||||
std::make_unique<CubebSinkStream>(ctx, sample_rate, num_channels, output_device, name));
|
||||
return *sink_streams.back();
|
||||
}
|
||||
|
||||
long SinkStreamImpl::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* output_buffer, long num_frames) {
|
||||
SinkStreamImpl* impl = static_cast<SinkStreamImpl*>(user_data);
|
||||
long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const void* input_buffer,
|
||||
void* output_buffer, long num_frames) {
|
||||
CubebSinkStream* impl = static_cast<CubebSinkStream*>(user_data);
|
||||
u8* buffer = reinterpret_cast<u8*>(output_buffer);
|
||||
|
||||
if (!impl) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::lock_guard lock{impl->queue_mutex};
|
||||
const size_t num_channels = impl->GetNumChannels();
|
||||
const size_t samples_to_write = num_channels * num_frames;
|
||||
size_t samples_written;
|
||||
|
||||
const size_t frames_to_write{
|
||||
std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))};
|
||||
if (Settings::values.enable_audio_stretching) {
|
||||
const std::vector<s16> in{impl->queue.Pop()};
|
||||
const size_t num_in{in.size() / num_channels};
|
||||
s16* const out{reinterpret_cast<s16*>(buffer)};
|
||||
const size_t out_frames = impl->time_stretch.Process(in.data(), num_in, out, num_frames);
|
||||
samples_written = out_frames * num_channels;
|
||||
|
||||
memcpy(buffer, impl->queue.data(), frames_to_write * sizeof(s16) * impl->GetNumChannels());
|
||||
impl->queue.erase(impl->queue.begin(),
|
||||
impl->queue.begin() + frames_to_write * impl->GetNumChannels());
|
||||
if (impl->should_flush) {
|
||||
impl->time_stretch.Flush();
|
||||
impl->should_flush = false;
|
||||
}
|
||||
} else {
|
||||
samples_written = impl->queue.Pop(buffer, samples_to_write);
|
||||
}
|
||||
|
||||
if (frames_to_write < num_frames) {
|
||||
// Fill the rest of the frames with silence
|
||||
memset(buffer + frames_to_write * sizeof(s16) * impl->GetNumChannels(), 0,
|
||||
(num_frames - frames_to_write) * sizeof(s16) * impl->GetNumChannels());
|
||||
if (samples_written >= num_channels) {
|
||||
std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16),
|
||||
num_channels * sizeof(s16));
|
||||
}
|
||||
|
||||
// Fill the rest of the frames with last_frame
|
||||
for (size_t i = samples_written; i < samples_to_write; i += num_channels) {
|
||||
std::memcpy(buffer + i * sizeof(s16), &impl->last_frame[0], num_channels * sizeof(s16));
|
||||
}
|
||||
|
||||
return num_frames;
|
||||
}
|
||||
|
||||
void SinkStreamImpl::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
|
||||
void CubebSinkStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state) {}
|
||||
|
||||
std::vector<std::string> ListCubebSinkDevices() {
|
||||
std::vector<std::string> device_list;
|
||||
|
||||
@@ -21,6 +21,12 @@ public:
|
||||
private:
|
||||
struct NullSinkStreamImpl final : SinkStream {
|
||||
void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {}
|
||||
|
||||
size_t SamplesInQueue(u32 /*num_channels*/) const override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Flush() override {}
|
||||
} null_sink_stream;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ const std::vector<SinkDetails> g_sink_details = {
|
||||
[] { return std::vector<std::string>{"null"}; }},
|
||||
};
|
||||
|
||||
const SinkDetails& GetSinkDetails(std::string sink_id) {
|
||||
const SinkDetails& GetSinkDetails(std::string_view sink_id) {
|
||||
auto iter =
|
||||
std::find_if(g_sink_details.begin(), g_sink_details.end(),
|
||||
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -30,6 +32,6 @@ struct SinkDetails {
|
||||
|
||||
extern const std::vector<SinkDetails> g_sink_details;
|
||||
|
||||
const SinkDetails& GetSinkDetails(std::string sink_id);
|
||||
const SinkDetails& GetSinkDetails(std::string_view sink_id);
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -25,6 +25,10 @@ public:
|
||||
* @param samples Samples in interleaved stereo PCM16 format.
|
||||
*/
|
||||
virtual void EnqueueSamples(u32 num_channels, const std::vector<s16>& samples) = 0;
|
||||
|
||||
virtual std::size_t SamplesInQueue(u32 num_channels) const = 0;
|
||||
|
||||
virtual void Flush() = 0;
|
||||
};
|
||||
|
||||
using SinkStreamPtr = std::unique_ptr<SinkStream>;
|
||||
|
||||
@@ -73,6 +73,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples) {
|
||||
void Stream::PlayNextBuffer() {
|
||||
if (!IsPlaying()) {
|
||||
// Ensure we are in playing state before playing the next buffer
|
||||
sink_stream.Flush();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -83,6 +84,7 @@ void Stream::PlayNextBuffer() {
|
||||
|
||||
if (queued_buffers.empty()) {
|
||||
// No queued buffers - we are effectively paused
|
||||
sink_stream.Flush();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -90,6 +92,7 @@ void Stream::PlayNextBuffer() {
|
||||
queued_buffers.pop();
|
||||
|
||||
VolumeAdjustSamples(active_buffer->Samples());
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
|
||||
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
|
||||
68
src/audio_core/time_stretch.cpp
Normal file
68
src/audio_core/time_stretch.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include "audio_core/time_stretch.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count)
|
||||
: m_sample_rate(sample_rate), m_channel_count(channel_count) {
|
||||
m_sound_touch.setChannels(channel_count);
|
||||
m_sound_touch.setSampleRate(sample_rate);
|
||||
m_sound_touch.setPitch(1.0);
|
||||
m_sound_touch.setTempo(1.0);
|
||||
}
|
||||
|
||||
void TimeStretcher::Clear() {
|
||||
m_sound_touch.clear();
|
||||
}
|
||||
|
||||
void TimeStretcher::Flush() {
|
||||
m_sound_touch.flush();
|
||||
}
|
||||
|
||||
size_t TimeStretcher::Process(const s16* in, size_t num_in, s16* out, size_t num_out) {
|
||||
const double time_delta = static_cast<double>(num_out) / m_sample_rate; // seconds
|
||||
|
||||
// We were given actual_samples number of samples, and num_samples were requested from us.
|
||||
double current_ratio = static_cast<double>(num_in) / static_cast<double>(num_out);
|
||||
|
||||
const double max_latency = 1.0; // seconds
|
||||
const double max_backlog = m_sample_rate * max_latency;
|
||||
const double backlog_fullness = m_sound_touch.numSamples() / max_backlog;
|
||||
if (backlog_fullness > 5.0) {
|
||||
// Too many samples in backlog: Don't push anymore on
|
||||
num_in = 0;
|
||||
}
|
||||
|
||||
// We ideally want the backlog to be about 50% full.
|
||||
// This gives some headroom both ways to prevent underflow and overflow.
|
||||
// We tweak current_ratio to encourage this.
|
||||
constexpr double tweak_time_scale = 0.05; // seconds
|
||||
const double tweak_correction = (backlog_fullness - 0.5) * (time_delta / tweak_time_scale);
|
||||
current_ratio *= std::pow(1.0 + 2.0 * tweak_correction, tweak_correction < 0 ? 3.0 : 1.0);
|
||||
|
||||
// This low-pass filter smoothes out variance in the calculated stretch ratio.
|
||||
// The time-scale determines how responsive this filter is.
|
||||
constexpr double lpf_time_scale = 2.0; // seconds
|
||||
const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
|
||||
m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
|
||||
|
||||
// Place a lower limit of 5% speed. When a game boots up, there will be
|
||||
// many silence samples. These do not need to be timestretched.
|
||||
m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
|
||||
m_sound_touch.setTempo(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, num_in);
|
||||
return m_sound_touch.receiveSamples(out, num_out);
|
||||
}
|
||||
|
||||
} // namespace AudioCore
|
||||
36
src/audio_core/time_stretch.h
Normal file
36
src/audio_core/time_stretch.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2018 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <SoundTouch.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class TimeStretcher {
|
||||
public:
|
||||
TimeStretcher(u32 sample_rate, u32 channel_count);
|
||||
|
||||
/// @param in Input sample buffer
|
||||
/// @param num_in Number of input frames in `in`
|
||||
/// @param out Output sample buffer
|
||||
/// @param num_out Desired number of output frames in `out`
|
||||
/// @returns Actual number of frames written to `out`
|
||||
size_t Process(const s16* in, size_t num_in, s16* out, size_t num_out);
|
||||
|
||||
void Clear();
|
||||
|
||||
void Flush();
|
||||
|
||||
private:
|
||||
u32 m_sample_rate;
|
||||
u32 m_channel_count;
|
||||
soundtouch::SoundTouch m_sound_touch;
|
||||
double m_stretch_ratio = 1.0;
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
@@ -1,13 +1,16 @@
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Bleeding Edge) to the scm_rev file as well
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
set(REPO_NAME "")
|
||||
set(BUILD_VERSION "0")
|
||||
if ($ENV{CI})
|
||||
if ($ENV{TRAVIS})
|
||||
set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG})
|
||||
set(BUILD_TAG $ENV{TRAVIS_TAG})
|
||||
elseif($ENV{APPVEYOR})
|
||||
set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
|
||||
set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
|
||||
endif()
|
||||
# regex capture the string nightly or bleeding-edge into CMAKE_MATCH_1
|
||||
# regex capture the string nightly or canary into CMAKE_MATCH_1
|
||||
string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
# capitalize the first letter of each word in the repo name.
|
||||
@@ -16,10 +19,21 @@ if ($ENV{CI})
|
||||
string(SUBSTRING ${WORD} 0 1 FIRST_LETTER)
|
||||
string(SUBSTRING ${WORD} 1 -1 REMAINDER)
|
||||
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
|
||||
# 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(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ")
|
||||
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}")
|
||||
endforeach()
|
||||
if (BUILD_TAG)
|
||||
string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
set(BUILD_VERSION ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
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} ")
|
||||
else()
|
||||
set(BUILD_FULLNAME "")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY)
|
||||
@@ -57,6 +71,7 @@ add_library(common STATIC
|
||||
param_package.cpp
|
||||
param_package.h
|
||||
quaternion.h
|
||||
ring_buffer.h
|
||||
scm_rev.cpp
|
||||
scm_rev.h
|
||||
scope_exit.h
|
||||
|
||||
111
src/common/ring_buffer.h
Normal file
111
src/common/ring_buffer.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
/// SPSC ring buffer
|
||||
/// @tparam T Element type
|
||||
/// @tparam capacity Number of slots in ring buffer
|
||||
/// @tparam granularity Slot size in terms of number of elements
|
||||
template <typename T, size_t capacity, size_t granularity = 1>
|
||||
class RingBuffer {
|
||||
/// A "slot" is made of `granularity` elements of `T`.
|
||||
static constexpr size_t slot_size = granularity * sizeof(T);
|
||||
// T must be safely memcpy-able and have a trivial default constructor.
|
||||
static_assert(std::is_trivial_v<T>);
|
||||
// Ensure capacity is sensible.
|
||||
static_assert(capacity < std::numeric_limits<size_t>::max() / 2 / granularity);
|
||||
static_assert((capacity & (capacity - 1)) == 0, "capacity must be a power of two");
|
||||
// Ensure lock-free.
|
||||
static_assert(std::atomic<size_t>::is_always_lock_free);
|
||||
|
||||
public:
|
||||
/// Pushes slots into the ring buffer
|
||||
/// @param new_slots Pointer to the slots to push
|
||||
/// @param slot_count Number of slots to push
|
||||
/// @returns The number of slots actually pushed
|
||||
size_t Push(const void* new_slots, size_t slot_count) {
|
||||
const size_t write_index = m_write_index.load();
|
||||
const size_t slots_free = capacity + m_read_index.load() - write_index;
|
||||
const size_t push_count = std::min(slot_count, slots_free);
|
||||
|
||||
const size_t pos = write_index % capacity;
|
||||
const size_t first_copy = std::min(capacity - pos, push_count);
|
||||
const size_t second_copy = push_count - first_copy;
|
||||
|
||||
const char* in = static_cast<const char*>(new_slots);
|
||||
std::memcpy(m_data.data() + pos * granularity, in, first_copy * slot_size);
|
||||
in += first_copy * slot_size;
|
||||
std::memcpy(m_data.data(), in, second_copy * slot_size);
|
||||
|
||||
m_write_index.store(write_index + push_count);
|
||||
|
||||
return push_count;
|
||||
}
|
||||
|
||||
size_t Push(const std::vector<T>& input) {
|
||||
return Push(input.data(), input.size());
|
||||
}
|
||||
|
||||
/// Pops slots from the ring buffer
|
||||
/// @param output Where to store the popped slots
|
||||
/// @param max_slots Maximum number of slots to pop
|
||||
/// @returns The number of slots actually popped
|
||||
size_t Pop(void* output, size_t max_slots = ~size_t(0)) {
|
||||
const size_t read_index = m_read_index.load();
|
||||
const size_t slots_filled = m_write_index.load() - read_index;
|
||||
const size_t pop_count = std::min(slots_filled, max_slots);
|
||||
|
||||
const size_t pos = read_index % capacity;
|
||||
const size_t first_copy = std::min(capacity - pos, pop_count);
|
||||
const size_t second_copy = pop_count - first_copy;
|
||||
|
||||
char* out = static_cast<char*>(output);
|
||||
std::memcpy(out, m_data.data() + pos * granularity, first_copy * slot_size);
|
||||
out += first_copy * slot_size;
|
||||
std::memcpy(out, m_data.data(), second_copy * slot_size);
|
||||
|
||||
m_read_index.store(read_index + pop_count);
|
||||
|
||||
return pop_count;
|
||||
}
|
||||
|
||||
std::vector<T> Pop(size_t max_slots = ~size_t(0)) {
|
||||
std::vector<T> out(std::min(max_slots, capacity) * granularity);
|
||||
const size_t count = Pop(out.data(), out.size() / granularity);
|
||||
out.resize(count * granularity);
|
||||
return out;
|
||||
}
|
||||
|
||||
/// @returns Number of slots used
|
||||
size_t Size() const {
|
||||
return m_write_index.load() - m_read_index.load();
|
||||
}
|
||||
|
||||
/// @returns Maximum size of ring buffer
|
||||
constexpr size_t Capacity() const {
|
||||
return capacity;
|
||||
}
|
||||
|
||||
private:
|
||||
// It is important to align the below variables for performance reasons:
|
||||
// Having them on the same cache-line would result in false-sharing between them.
|
||||
alignas(128) std::atomic<size_t> m_read_index{0};
|
||||
alignas(128) std::atomic<size_t> m_write_index{0};
|
||||
|
||||
std::array<T, granularity * capacity> m_data;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -9,6 +9,8 @@
|
||||
#define GIT_DESC "@GIT_DESC@"
|
||||
#define BUILD_NAME "@REPO_NAME@"
|
||||
#define BUILD_DATE "@BUILD_DATE@"
|
||||
#define BUILD_FULLNAME "@BUILD_FULLNAME@"
|
||||
#define BUILD_VERSION "@BUILD_VERSION@"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -17,6 +19,8 @@ const char g_scm_branch[] = GIT_BRANCH;
|
||||
const char g_scm_desc[] = GIT_DESC;
|
||||
const char g_build_name[] = BUILD_NAME;
|
||||
const char g_build_date[] = BUILD_DATE;
|
||||
const char g_build_fullname[] = BUILD_FULLNAME;
|
||||
const char g_build_version[] = BUILD_VERSION;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -11,5 +11,7 @@ extern const char g_scm_branch[];
|
||||
extern const char g_scm_desc[];
|
||||
extern const char g_build_name[];
|
||||
extern const char g_build_date[];
|
||||
extern const char g_build_fullname[];
|
||||
extern const char g_build_version[];
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -35,8 +35,12 @@ add_library(core STATIC
|
||||
file_sys/mode.h
|
||||
file_sys/nca_metadata.cpp
|
||||
file_sys/nca_metadata.h
|
||||
file_sys/nca_patch.cpp
|
||||
file_sys/nca_patch.h
|
||||
file_sys/partition_filesystem.cpp
|
||||
file_sys/partition_filesystem.h
|
||||
file_sys/patch_manager.cpp
|
||||
file_sys/patch_manager.h
|
||||
file_sys/program_metadata.cpp
|
||||
file_sys/program_metadata.h
|
||||
file_sys/registered_cache.cpp
|
||||
@@ -384,7 +388,7 @@ add_library(core STATIC
|
||||
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)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
target_sources(core PRIVATE
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
@@ -137,7 +136,7 @@ struct System::Impl {
|
||||
if (virtual_filesystem == nullptr)
|
||||
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
|
||||
current_process = Kernel::Process::Create(kernel, "main");
|
||||
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(current_process)};
|
||||
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();
|
||||
@@ -282,7 +281,6 @@ struct System::Impl {
|
||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||
std::shared_ptr<Tegra::DebugContext> debug_context;
|
||||
Kernel::SharedPtr<Kernel::Process> current_process;
|
||||
std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
|
||||
std::shared_ptr<CpuBarrier> cpu_barrier;
|
||||
std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
|
||||
@@ -364,7 +362,11 @@ const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) {
|
||||
}
|
||||
|
||||
Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
|
||||
return impl->kernel.CurrentProcess();
|
||||
}
|
||||
|
||||
ARM_Interface& System::ArmInterface(size_t core_index) {
|
||||
|
||||
@@ -174,9 +174,12 @@ public:
|
||||
/// Gets the scheduler for the CPU core with the specified index
|
||||
const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index);
|
||||
|
||||
/// Gets the current process
|
||||
/// Provides a reference to the current process
|
||||
Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
|
||||
|
||||
/// 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();
|
||||
|
||||
|
||||
@@ -82,11 +82,25 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
|
||||
}
|
||||
} else {
|
||||
const auto block_size = mbedtls_cipher_get_block_size(context);
|
||||
if (size < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src, size);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest, block.data(), size);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t offset = 0; offset < size; offset += block_size) {
|
||||
auto length = std::min<size_t>(block_size, size - offset);
|
||||
mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
|
||||
if (written != length) {
|
||||
if (length < block_size) {
|
||||
std::vector<u8> block(block_size);
|
||||
std::memcpy(block.data(), src + offset, length);
|
||||
Transcode(block.data(), block.size(), block.data(), op);
|
||||
std::memcpy(dest + offset, block.data(), length);
|
||||
return;
|
||||
}
|
||||
LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
|
||||
length, written);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
|
||||
UpdateIV(base_offset + offset);
|
||||
std::vector<u8> raw = base->ReadBytes(length, offset);
|
||||
cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt);
|
||||
return raw.size();
|
||||
return length;
|
||||
}
|
||||
|
||||
// offset does not fall on block boundary (0x10)
|
||||
|
||||
@@ -52,11 +52,11 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
|
||||
const auto secure_ncas = secure_partition->GetNCAsCollapsed();
|
||||
std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
|
||||
|
||||
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
program =
|
||||
secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
|
||||
if (program != nullptr)
|
||||
program_nca_status = program->GetStatus();
|
||||
program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
|
||||
if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA)
|
||||
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
|
||||
|
||||
auto result = AddNCAFromPartition(XCIPartition::Update);
|
||||
if (result != Loader::ResultStatus::Success) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/crypto/ctr_encryption_layer.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_patch.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_offset.h"
|
||||
@@ -68,10 +69,31 @@ struct RomFSSuperblock {
|
||||
};
|
||||
static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
|
||||
|
||||
struct BKTRHeader {
|
||||
u64_le offset;
|
||||
u64_le size;
|
||||
u32_le magic;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u32_le number_entries;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
|
||||
|
||||
struct BKTRSuperblock {
|
||||
NCASectionHeaderBlock header_block;
|
||||
IVFCHeader ivfc;
|
||||
INSERT_PADDING_BYTES(0x18);
|
||||
BKTRHeader relocation;
|
||||
BKTRHeader subsection;
|
||||
INSERT_PADDING_BYTES(0xC0);
|
||||
};
|
||||
static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
|
||||
|
||||
union NCASectionHeader {
|
||||
NCASectionRaw raw;
|
||||
PFS0Superblock pfs0;
|
||||
RomFSSuperblock romfs;
|
||||
BKTRSuperblock bktr;
|
||||
};
|
||||
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
|
||||
|
||||
@@ -104,7 +126,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
|
||||
Core::Crypto::Key128 out;
|
||||
if (type == NCASectionCryptoType::XTS)
|
||||
std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
|
||||
else if (type == NCASectionCryptoType::CTR)
|
||||
else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR)
|
||||
std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
|
||||
else
|
||||
LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
|
||||
@@ -154,6 +176,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
LOG_DEBUG(Crypto, "called with mode=NONE");
|
||||
return in;
|
||||
case NCASectionCryptoType::CTR:
|
||||
// During normal BKTR decryption, this entire function is skipped. This is for the metadata,
|
||||
// which uses the same CTR as usual.
|
||||
case NCASectionCryptoType::BKTR:
|
||||
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
|
||||
{
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
@@ -190,7 +215,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
|
||||
}
|
||||
}
|
||||
|
||||
NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
|
||||
: file(std::move(file_)),
|
||||
bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
|
||||
if (file == nullptr) {
|
||||
@@ -265,22 +292,21 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
|
||||
return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
|
||||
}) != sections.end();
|
||||
ivfc_offset = 0;
|
||||
|
||||
for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
|
||||
auto section = sections[i];
|
||||
|
||||
if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
|
||||
const size_t romfs_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER +
|
||||
section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t base_offset =
|
||||
header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
|
||||
ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
const size_t romfs_offset = base_offset + ivfc_offset;
|
||||
const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
|
||||
auto dec =
|
||||
Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset),
|
||||
romfs_offset);
|
||||
if (dec != nullptr) {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
|
||||
auto dec = Decrypt(section, raw, romfs_offset);
|
||||
|
||||
if (dec == nullptr) {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return;
|
||||
if (has_rights_id)
|
||||
@@ -289,6 +315,117 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
|
||||
if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
|
||||
section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
|
||||
status = Loader::ResultStatus::ErrorBadBKTRHeader;
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.bktr.relocation.offset + section.bktr.relocation.size !=
|
||||
section.bktr.subsection.offset) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 size =
|
||||
MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
|
||||
header.section_tables[i].media_offset);
|
||||
if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
|
||||
status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
|
||||
return;
|
||||
}
|
||||
|
||||
const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
|
||||
RelocationBlock relocation_block{};
|
||||
if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBlock;
|
||||
return;
|
||||
}
|
||||
SubsectionBlock subsection_block{};
|
||||
if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
|
||||
sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBlock;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucketRaw> relocation_buckets_raw(
|
||||
(section.bktr.relocation.size - sizeof(RelocationBlock)) /
|
||||
sizeof(RelocationBucketRaw));
|
||||
if (dec->ReadBytes(relocation_buckets_raw.data(),
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock),
|
||||
section.bktr.relocation.offset + sizeof(RelocationBlock) -
|
||||
offset) !=
|
||||
section.bktr.relocation.size - sizeof(RelocationBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadRelocationBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<SubsectionBucketRaw> subsection_buckets_raw(
|
||||
(section.bktr.subsection.size - sizeof(SubsectionBlock)) /
|
||||
sizeof(SubsectionBucketRaw));
|
||||
if (dec->ReadBytes(subsection_buckets_raw.data(),
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock),
|
||||
section.bktr.subsection.offset + sizeof(SubsectionBlock) -
|
||||
offset) !=
|
||||
section.bktr.subsection.size - sizeof(SubsectionBlock)) {
|
||||
status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
|
||||
std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
|
||||
relocation_buckets.begin(), &ConvertRelocationBucketRaw);
|
||||
std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
|
||||
std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
|
||||
subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
|
||||
|
||||
u32 ctr_low;
|
||||
std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
|
||||
subsection_buckets.back().entries.push_back(
|
||||
{section.bktr.relocation.offset, {0}, ctr_low});
|
||||
subsection_buckets.back().entries.push_back({size, {0}, 0});
|
||||
|
||||
boost::optional<Core::Crypto::Key128> key = boost::none;
|
||||
if (encrypted) {
|
||||
if (has_rights_id) {
|
||||
status = Loader::ResultStatus::Success;
|
||||
key = GetTitlekey();
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingTitlekey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
|
||||
if (key == boost::none) {
|
||||
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bktr_base_romfs == nullptr) {
|
||||
status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
|
||||
return;
|
||||
}
|
||||
|
||||
auto bktr = std::make_shared<BKTR>(
|
||||
bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
|
||||
relocation_block, relocation_buckets, subsection_block, subsection_buckets,
|
||||
encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
|
||||
bktr_base_ivfc_offset, section.raw.section_ctr);
|
||||
|
||||
// BKTR applies to entire IVFC, so make an offset version to level 6
|
||||
|
||||
files.push_back(std::make_shared<OffsetVfsFile>(
|
||||
bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
|
||||
romfs = files.back();
|
||||
} else {
|
||||
files.push_back(std::move(dec));
|
||||
romfs = files.back();
|
||||
}
|
||||
} else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
|
||||
u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
|
||||
MEDIA_OFFSET_MULTIPLIER) +
|
||||
@@ -304,6 +441,12 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
} else {
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
else
|
||||
status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
@@ -349,11 +492,15 @@ NCAContentType NCA::GetType() const {
|
||||
}
|
||||
|
||||
u64 NCA::GetTitleId() const {
|
||||
if (status != Loader::ResultStatus::Success)
|
||||
return {};
|
||||
if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS)
|
||||
return header.title_id | 0x800;
|
||||
return header.title_id;
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
}
|
||||
|
||||
VirtualFile NCA::GetRomFS() const {
|
||||
return romfs;
|
||||
}
|
||||
@@ -366,8 +513,8 @@ VirtualFile NCA::GetBaseFile() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
bool NCA::IsUpdate() const {
|
||||
return is_update;
|
||||
u64 NCA::GetBaseIVFCOffset() const {
|
||||
return ivfc_offset;
|
||||
}
|
||||
|
||||
bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
|
||||
|
||||
@@ -79,7 +79,8 @@ bool IsValidNCA(const NCAHeader& header);
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NCA(VirtualFile file);
|
||||
explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
|
||||
u64 bktr_base_ivfc_offset = 0);
|
||||
Loader::ResultStatus GetStatus() const;
|
||||
|
||||
std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
|
||||
@@ -89,13 +90,15 @@ public:
|
||||
|
||||
NCAContentType GetType() const;
|
||||
u64 GetTitleId() const;
|
||||
bool IsUpdate() const;
|
||||
|
||||
VirtualFile GetRomFS() const;
|
||||
VirtualDir GetExeFS() const;
|
||||
|
||||
VirtualFile GetBaseFile() const;
|
||||
|
||||
bool IsUpdate() const;
|
||||
// Returns the base ivfc offset used in BKTR patching.
|
||||
u64 GetBaseIVFCOffset() const;
|
||||
|
||||
protected:
|
||||
bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
|
||||
@@ -112,14 +115,16 @@ private:
|
||||
VirtualFile romfs = nullptr;
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualFile file;
|
||||
VirtualFile bktr_base_romfs;
|
||||
u64 ivfc_offset;
|
||||
|
||||
NCAHeader header{};
|
||||
bool has_rights_id{};
|
||||
bool is_update{};
|
||||
|
||||
Loader::ResultStatus status{};
|
||||
|
||||
bool encrypted;
|
||||
bool is_update;
|
||||
|
||||
Core::Crypto::KeyManager keys;
|
||||
};
|
||||
|
||||
210
src/core/file_sys/nca_patch.cpp
Normal file
210
src/core/file_sys/nca_patch.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/crypto/aes_util.h"
|
||||
#include "core/file_sys/nca_patch.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
|
||||
std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
|
||||
std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
|
||||
Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
|
||||
std::array<u8, 8> section_ctr_)
|
||||
: relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
|
||||
subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
|
||||
base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
|
||||
encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
|
||||
section_ctr(section_ctr_) {
|
||||
for (size_t i = 0; i < relocation.number_buckets - 1; ++i) {
|
||||
relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < subsection.number_buckets - 1; ++i) {
|
||||
subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch,
|
||||
{0},
|
||||
subsection_buckets[i + 1].entries[0].ctr});
|
||||
}
|
||||
|
||||
relocation_buckets.back().entries.push_back({relocation.size, 0, 0});
|
||||
}
|
||||
|
||||
BKTR::~BKTR() = default;
|
||||
|
||||
size_t BKTR::Read(u8* data, size_t length, size_t offset) const {
|
||||
// Read out of bounds.
|
||||
if (offset >= relocation.size)
|
||||
return 0;
|
||||
const auto relocation = GetRelocationEntry(offset);
|
||||
const auto section_offset = offset - relocation.address_patch + relocation.address_source;
|
||||
const auto bktr_read = relocation.from_patch;
|
||||
|
||||
const auto next_relocation = GetNextRelocationEntry(offset);
|
||||
|
||||
if (offset + length > next_relocation.address_patch) {
|
||||
const u64 partition = next_relocation.address_patch - offset;
|
||||
return Read(data, partition, offset) +
|
||||
Read(data + partition, length - partition, offset + partition);
|
||||
}
|
||||
|
||||
if (!bktr_read) {
|
||||
ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative.");
|
||||
return base_romfs->Read(data, length, section_offset - ivfc_offset);
|
||||
}
|
||||
|
||||
if (!encrypted) {
|
||||
return bktr_romfs->Read(data, length, section_offset);
|
||||
}
|
||||
|
||||
const auto subsection = GetSubsectionEntry(section_offset);
|
||||
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
|
||||
|
||||
// Calculate AES IV
|
||||
std::vector<u8> iv(16);
|
||||
auto subsection_ctr = subsection.ctr;
|
||||
auto offset_iv = section_offset + base_offset;
|
||||
for (size_t i = 0; i < section_ctr.size(); ++i)
|
||||
iv[i] = section_ctr[0x8 - i - 1];
|
||||
offset_iv >>= 4;
|
||||
for (size_t i = 0; i < sizeof(u64); ++i) {
|
||||
iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
|
||||
offset_iv >>= 8;
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(u32); ++i) {
|
||||
iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
|
||||
subsection_ctr >>= 8;
|
||||
}
|
||||
cipher.SetIV(iv);
|
||||
|
||||
const auto next_subsection = GetNextSubsectionEntry(section_offset);
|
||||
|
||||
if (section_offset + length > next_subsection.address_patch) {
|
||||
const u64 partition = next_subsection.address_patch - section_offset;
|
||||
return Read(data, partition, offset) +
|
||||
Read(data + partition, length - partition, offset + partition);
|
||||
}
|
||||
|
||||
const auto block_offset = section_offset & 0xF;
|
||||
if (block_offset != 0) {
|
||||
auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
|
||||
cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt);
|
||||
if (length + block_offset < 0x10) {
|
||||
std::memcpy(data, block.data() + block_offset, std::min(length, block.size()));
|
||||
return std::min(length, block.size());
|
||||
}
|
||||
|
||||
const auto read = 0x10 - block_offset;
|
||||
std::memcpy(data, block.data() + block_offset, read);
|
||||
return read + Read(data + read, length - read, offset + read);
|
||||
}
|
||||
|
||||
const auto raw_read = bktr_romfs->Read(data, length, section_offset);
|
||||
cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
|
||||
return raw_read;
|
||||
}
|
||||
|
||||
template <bool Subsection, typename BlockType, typename BucketType>
|
||||
std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
|
||||
BucketType buckets) const {
|
||||
if constexpr (Subsection) {
|
||||
const auto last_bucket = buckets[block.number_buckets - 1];
|
||||
if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
|
||||
return {block.number_buckets - 1, last_bucket.number_entries};
|
||||
} else {
|
||||
ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
|
||||
}
|
||||
|
||||
size_t bucket_id = std::count_if(block.base_offsets.begin() + 1,
|
||||
block.base_offsets.begin() + block.number_buckets,
|
||||
[&offset](u64 base_offset) { return base_offset <= offset; });
|
||||
|
||||
const auto bucket = buckets[bucket_id];
|
||||
|
||||
if (bucket.number_entries == 1)
|
||||
return {bucket_id, 0};
|
||||
|
||||
size_t low = 0;
|
||||
size_t mid = 0;
|
||||
size_t high = bucket.number_entries - 1;
|
||||
while (low <= high) {
|
||||
mid = (low + high) / 2;
|
||||
if (bucket.entries[mid].address_patch > offset) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
if (mid == bucket.number_entries - 1 ||
|
||||
bucket.entries[mid + 1].address_patch > offset) {
|
||||
return {bucket_id, mid};
|
||||
}
|
||||
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
UNREACHABLE_MSG("Offset could not be found in BKTR block.");
|
||||
}
|
||||
|
||||
RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
|
||||
return relocation_buckets[res.first].entries[res.second];
|
||||
}
|
||||
|
||||
RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
|
||||
const auto bucket = relocation_buckets[res.first];
|
||||
if (res.second + 1 < bucket.entries.size())
|
||||
return bucket.entries[res.second + 1];
|
||||
return relocation_buckets[res.first + 1].entries[0];
|
||||
}
|
||||
|
||||
SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
|
||||
return subsection_buckets[res.first].entries[res.second];
|
||||
}
|
||||
|
||||
SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
|
||||
const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
|
||||
const auto bucket = subsection_buckets[res.first];
|
||||
if (res.second + 1 < bucket.entries.size())
|
||||
return bucket.entries[res.second + 1];
|
||||
return subsection_buckets[res.first + 1].entries[0];
|
||||
}
|
||||
|
||||
std::string BKTR::GetName() const {
|
||||
return base_romfs->GetName();
|
||||
}
|
||||
|
||||
size_t BKTR::GetSize() const {
|
||||
return relocation.size;
|
||||
}
|
||||
|
||||
bool BKTR::Resize(size_t new_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
|
||||
return base_romfs->GetContainingDirectory();
|
||||
}
|
||||
|
||||
bool BKTR::IsWritable() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BKTR::IsReadable() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t BKTR::Write(const u8* data, size_t length, size_t offset) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool BKTR::Rename(std::string_view name) {
|
||||
return base_romfs->Rename(name);
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
150
src/core/file_sys/nca_patch.h
Normal file
150
src/core/file_sys/nca_patch.h
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct RelocationEntry {
|
||||
u64_le address_patch;
|
||||
u64_le address_source;
|
||||
u32 from_patch;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
|
||||
|
||||
struct RelocationBucketRaw {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_entries;
|
||||
u64_le end_offset;
|
||||
std::array<RelocationEntry, 0x332> relocation_entries;
|
||||
INSERT_PADDING_BYTES(8);
|
||||
};
|
||||
static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
|
||||
|
||||
// Vector version of RelocationBucketRaw
|
||||
struct RelocationBucket {
|
||||
u32 number_entries;
|
||||
u64 end_offset;
|
||||
std::vector<RelocationEntry> entries;
|
||||
};
|
||||
|
||||
struct RelocationBlock {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_buckets;
|
||||
u64_le size;
|
||||
std::array<u64, 0x7FE> base_offsets;
|
||||
};
|
||||
static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
|
||||
|
||||
struct SubsectionEntry {
|
||||
u64_le address_patch;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
u32_le ctr;
|
||||
};
|
||||
static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
|
||||
|
||||
struct SubsectionBucketRaw {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_entries;
|
||||
u64_le end_offset;
|
||||
std::array<SubsectionEntry, 0x3FF> subsection_entries;
|
||||
};
|
||||
static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
|
||||
|
||||
// Vector version of SubsectionBucketRaw
|
||||
struct SubsectionBucket {
|
||||
u32 number_entries;
|
||||
u64 end_offset;
|
||||
std::vector<SubsectionEntry> entries;
|
||||
};
|
||||
|
||||
struct SubsectionBlock {
|
||||
INSERT_PADDING_BYTES(4);
|
||||
u32_le number_buckets;
|
||||
u64_le size;
|
||||
std::array<u64, 0x7FE> base_offsets;
|
||||
};
|
||||
static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
|
||||
|
||||
inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
|
||||
return {raw.number_entries,
|
||||
raw.end_offset,
|
||||
{raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
|
||||
}
|
||||
|
||||
inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
|
||||
return {raw.number_entries,
|
||||
raw.end_offset,
|
||||
{raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
|
||||
}
|
||||
|
||||
class BKTR : public VfsFile {
|
||||
public:
|
||||
BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
|
||||
std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
|
||||
std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
|
||||
Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
|
||||
~BKTR() override;
|
||||
|
||||
size_t Read(u8* data, size_t length, size_t offset) const override;
|
||||
|
||||
std::string GetName() const override;
|
||||
|
||||
size_t GetSize() const override;
|
||||
|
||||
bool Resize(size_t new_size) override;
|
||||
|
||||
std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
|
||||
|
||||
bool IsWritable() const override;
|
||||
|
||||
bool IsReadable() const override;
|
||||
|
||||
size_t Write(const u8* data, size_t length, size_t offset) override;
|
||||
|
||||
bool Rename(std::string_view name) override;
|
||||
|
||||
private:
|
||||
template <bool Subsection, typename BlockType, typename BucketType>
|
||||
std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block,
|
||||
BucketType buckets) const;
|
||||
|
||||
RelocationEntry GetRelocationEntry(u64 offset) const;
|
||||
RelocationEntry GetNextRelocationEntry(u64 offset) const;
|
||||
|
||||
SubsectionEntry GetSubsectionEntry(u64 offset) const;
|
||||
SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
|
||||
|
||||
RelocationBlock relocation;
|
||||
std::vector<RelocationBucket> relocation_buckets;
|
||||
SubsectionBlock subsection;
|
||||
std::vector<SubsectionBucket> subsection_buckets;
|
||||
|
||||
// Should be the raw base romfs, decrypted.
|
||||
VirtualFile base_romfs;
|
||||
// Should be the raw BKTR romfs, (located at media_offset with size media_size).
|
||||
VirtualFile bktr_romfs;
|
||||
|
||||
bool encrypted;
|
||||
Core::Crypto::Key128 key;
|
||||
|
||||
// Base offset into NCA, used for IV calculation.
|
||||
u64 base_offset;
|
||||
// Distance between IVFC start and RomFS start, used for base reads
|
||||
u64 ivfc_offset;
|
||||
std::array<u8, 8> section_ctr;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
157
src/core/file_sys/patch_manager.cpp
Normal file
157
src/core/file_sys/patch_manager.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
|
||||
|
||||
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
|
||||
std::array<u8, sizeof(u32)> bytes{};
|
||||
bytes[0] = version % SINGLE_BYTE_MODULUS;
|
||||
for (size_t i = 1; i < bytes.size(); ++i) {
|
||||
version /= SINGLE_BYTE_MODULUS;
|
||||
bytes[i] = version % SINGLE_BYTE_MODULUS;
|
||||
}
|
||||
|
||||
if (format == TitleVersionFormat::FourElements)
|
||||
return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
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<size_t>(type));
|
||||
}
|
||||
|
||||
PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
|
||||
|
||||
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
|
||||
|
||||
if (exefs == nullptr)
|
||||
return exefs;
|
||||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
|
||||
if (update != nullptr) {
|
||||
if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
|
||||
update->GetExeFS() != nullptr) {
|
||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
exefs = update->GetExeFS();
|
||||
}
|
||||
}
|
||||
|
||||
return exefs;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (romfs == nullptr)
|
||||
return romfs;
|
||||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = installed->GetEntryRaw(update_tid, type);
|
||||
if (update != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
}
|
||||
|
||||
return romfs;
|
||||
}
|
||||
|
||||
std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
|
||||
std::map<PatchType, std::string> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
PatchManager update{update_tid};
|
||||
auto [nacp, discard_icon_file] = update.GetControlMetadata();
|
||||
|
||||
if (nacp != nullptr) {
|
||||
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[PatchType::Update] = "";
|
||||
} else {
|
||||
out[PatchType::Update] =
|
||||
FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
if (romfs == nullptr)
|
||||
return {};
|
||||
|
||||
const auto extracted = ExtractRomFS(romfs);
|
||||
if (extracted == nullptr)
|
||||
return {};
|
||||
|
||||
auto nacp_file = extracted->GetFile("control.nacp");
|
||||
if (nacp_file == nullptr)
|
||||
nacp_file = extracted->GetFile("Control.nacp");
|
||||
|
||||
const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
|
||||
|
||||
VirtualFile icon_file;
|
||||
for (const auto& language : FileSys::LANGUAGE_NAMES) {
|
||||
icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
|
||||
if (icon_file != nullptr)
|
||||
break;
|
||||
}
|
||||
|
||||
return {nacp, icon_file};
|
||||
}
|
||||
} // namespace FileSys
|
||||
63
src/core/file_sys/patch_manager.h
Normal file
63
src/core/file_sys/patch_manager.h
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 "common/common_types.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
class NACP;
|
||||
|
||||
enum class TitleVersionFormat : u8 {
|
||||
ThreeElements, ///< vX.Y.Z
|
||||
FourElements, ///< vX.Y.Z.W
|
||||
};
|
||||
|
||||
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:
|
||||
explicit PatchManager(u64 title_id);
|
||||
|
||||
// Currently tracked ExeFS patches:
|
||||
// - Game Updates
|
||||
VirtualDir PatchExeFS(VirtualDir exefs) const;
|
||||
|
||||
// Currently tracked RomFS patches:
|
||||
// - Game Updates
|
||||
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
|
||||
ContentRecordType type = ContentRecordType::Program) const;
|
||||
|
||||
// Returns a vector of pairs between patch names and patch versions.
|
||||
// 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::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
|
||||
|
||||
// Version of GetControlMetadata that takes an arbitrary NCA
|
||||
std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
|
||||
const std::shared_ptr<NCA>& nca) const;
|
||||
|
||||
private:
|
||||
u64 title_id;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
@@ -280,6 +280,18 @@ VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
|
||||
const auto meta_iter = meta.find(title_id);
|
||||
if (meta_iter != meta.end())
|
||||
return meta_iter->second.GetTitleVersion();
|
||||
|
||||
const auto yuzu_meta_iter = yuzu_meta.find(title_id);
|
||||
if (yuzu_meta_iter != yuzu_meta.end())
|
||||
return yuzu_meta_iter->second.GetTitleVersion();
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
const auto id = GetNcaIDFromMetadata(title_id, type);
|
||||
if (id == boost::none)
|
||||
@@ -498,4 +510,107 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
|
||||
kv.second.GetTitleID() == cnmt.GetTitleID();
|
||||
}) != yuzu_meta.end();
|
||||
}
|
||||
|
||||
RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
|
||||
: caches(std::move(caches)) {}
|
||||
|
||||
void RegisteredCacheUnion::Refresh() {
|
||||
for (const auto& c : caches)
|
||||
c->Refresh();
|
||||
}
|
||||
|
||||
bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const {
|
||||
return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) {
|
||||
return cache->HasEntry(title_id, type);
|
||||
});
|
||||
}
|
||||
|
||||
bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
|
||||
return HasEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryVersion(title_id);
|
||||
if (res != boost::none)
|
||||
return res;
|
||||
}
|
||||
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryUnparsed(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const {
|
||||
return GetEntryUnparsed(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
|
||||
for (const auto& c : caches) {
|
||||
const auto res = c->GetEntryRaw(title_id, type);
|
||||
if (res != nullptr)
|
||||
return res;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const {
|
||||
return GetEntryRaw(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
|
||||
const auto raw = GetEntryRaw(title_id, type);
|
||||
if (raw == nullptr)
|
||||
return nullptr;
|
||||
return std::make_shared<NCA>(raw);
|
||||
}
|
||||
|
||||
std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
|
||||
return GetEntry(entry.title_id, entry.type);
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[](const CNMT& c, const ContentRecord& r) { return true; });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
|
||||
boost::optional<u64> title_id) const {
|
||||
std::vector<RegisteredCacheEntry> out;
|
||||
for (const auto& c : caches) {
|
||||
c->IterateAllMetadata<RegisteredCacheEntry>(
|
||||
out,
|
||||
[](const CNMT& c, const ContentRecord& r) {
|
||||
return RegisteredCacheEntry{c.GetTitleID(), r.type};
|
||||
},
|
||||
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
|
||||
if (title_type != boost::none && title_type.get() != c.GetType())
|
||||
return false;
|
||||
if (record_type != boost::none && record_type.get() != r.type)
|
||||
return false;
|
||||
if (title_id != boost::none && title_id.get() != c.GetTitleID())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -43,6 +43,10 @@ struct RegisteredCacheEntry {
|
||||
std::string DebugInfo() const;
|
||||
};
|
||||
|
||||
constexpr u64 GetUpdateTitleID(u64 base_title_id) {
|
||||
return base_title_id | 0x800;
|
||||
}
|
||||
|
||||
// boost flat_map requires operator< for O(log(n)) lookups.
|
||||
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
|
||||
|
||||
@@ -60,6 +64,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
|
||||
* 4GB splitting can be ignored.)
|
||||
*/
|
||||
class RegisteredCache {
|
||||
friend class RegisteredCacheUnion;
|
||||
|
||||
public:
|
||||
// Parsing function defines the conversion from raw file to NCA. If there are other steps
|
||||
// besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
|
||||
@@ -74,6 +80,8 @@ public:
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
boost::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
|
||||
@@ -131,4 +139,36 @@ private:
|
||||
boost::container::flat_map<u64, CNMT> yuzu_meta;
|
||||
};
|
||||
|
||||
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
|
||||
class RegisteredCacheUnion {
|
||||
public:
|
||||
explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
|
||||
|
||||
void Refresh();
|
||||
|
||||
bool HasEntry(u64 title_id, ContentRecordType type) const;
|
||||
bool HasEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
boost::optional<u32> GetEntryVersion(u64 title_id) const;
|
||||
|
||||
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
|
||||
|
||||
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
|
||||
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
|
||||
std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
|
||||
|
||||
std::vector<RegisteredCacheEntry> ListEntries() const;
|
||||
// If a parameter is not boost::none, it will be filtered for from all entries.
|
||||
std::vector<RegisteredCacheEntry> ListEntriesFilter(
|
||||
boost::optional<TitleType> title_type = boost::none,
|
||||
boost::optional<ContentRecordType> record_type = boost::none,
|
||||
boost::optional<u64> title_id = boost::none) const;
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<RegisteredCache>> caches;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
@@ -19,10 +23,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
|
||||
if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
|
||||
LOG_ERROR(Service_FS, "Unable to read RomFS!");
|
||||
}
|
||||
|
||||
updatable = app_loader.IsRomFSUpdatable();
|
||||
ivfc_offset = app_loader.ReadRomFSIVFCOffset();
|
||||
}
|
||||
|
||||
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
|
||||
return MakeResult<VirtualFile>(file);
|
||||
if (!updatable)
|
||||
return MakeResult<VirtualFile>(file);
|
||||
|
||||
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) {
|
||||
|
||||
@@ -36,6 +36,8 @@ public:
|
||||
|
||||
private:
|
||||
VirtualFile file;
|
||||
bool updatable;
|
||||
u64 ivfc_offset;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/ostream.h>
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/partition_filesystem.h"
|
||||
@@ -13,8 +19,8 @@
|
||||
|
||||
namespace FileSys {
|
||||
NSP::NSP(VirtualFile file_)
|
||||
: file(std::move(file_)),
|
||||
pfs(std::make_shared<PartitionFilesystem>(file)), status{Loader::ResultStatus::Success} {
|
||||
: file(std::move(file_)), status{Loader::ResultStatus::Success},
|
||||
pfs(std::make_shared<PartitionFilesystem>(file)) {
|
||||
if (pfs->GetStatus() != Loader::ResultStatus::Success) {
|
||||
status = pfs->GetStatus();
|
||||
return;
|
||||
@@ -60,8 +66,11 @@ NSP::NSP(VirtualFile file_)
|
||||
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)
|
||||
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()) {
|
||||
|
||||
@@ -4,20 +4,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/romfs_factory.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Loader {
|
||||
enum class ResultStatus : u16;
|
||||
}
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
class NCA;
|
||||
class PartitionFilesystem;
|
||||
|
||||
enum class ContentRecordType : u8;
|
||||
|
||||
class NSP : public ReadOnlyVfsDirectory {
|
||||
public:
|
||||
explicit NSP(VirtualFile file);
|
||||
|
||||
@@ -153,7 +153,7 @@ struct DataPayloadHeader {
|
||||
u32_le magic;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
};
|
||||
static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadRequest size is incorrect");
|
||||
static_assert(sizeof(DataPayloadHeader) == 8, "DataPayloadHeader size is incorrect");
|
||||
|
||||
struct DomainMessageHeader {
|
||||
enum class CommandType : u32_le {
|
||||
|
||||
@@ -21,6 +21,7 @@ enum {
|
||||
HandleTableFull = 105,
|
||||
InvalidMemoryState = 106,
|
||||
InvalidMemoryPermissions = 108,
|
||||
InvalidThreadPriority = 112,
|
||||
InvalidProcessorId = 113,
|
||||
InvalidHandle = 114,
|
||||
InvalidCombination = 116,
|
||||
@@ -36,7 +37,7 @@ enum {
|
||||
// WARNING: The kernel is quite inconsistent in it's usage of errors code. Make sure to always
|
||||
// double check that the code matches before re-using the constant.
|
||||
|
||||
// TODO(bunnei): Replace these with correct errors for Switch OS
|
||||
// TODO(bunnei): Replace -1 with correct errors for Switch OS
|
||||
constexpr ResultCode ERR_HANDLE_TABLE_FULL(ErrorModule::Kernel, ErrCodes::HandleTableFull);
|
||||
constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);
|
||||
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrorModule::Kernel, ErrCodes::TooLarge);
|
||||
@@ -53,15 +54,16 @@ constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::In
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidMemoryPermissions);
|
||||
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
|
||||
constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
|
||||
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
|
||||
constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel,
|
||||
ErrCodes::InvalidThreadPriority);
|
||||
constexpr ResultCode ERR_INVALID_POINTER(-1);
|
||||
constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1);
|
||||
constexpr ResultCode ERR_NOT_AUTHORIZED(-1);
|
||||
/// Alternate code returned instead of ERR_INVALID_HANDLE in some code paths.
|
||||
constexpr ResultCode ERR_INVALID_HANDLE_OS(-1);
|
||||
constexpr ResultCode ERR_NOT_FOUND(-1);
|
||||
constexpr ResultCode ERR_OUT_OF_RANGE(-1);
|
||||
constexpr ResultCode ERR_OUT_OF_RANGE_KERNEL(-1);
|
||||
constexpr ResultCode RESULT_TIMEOUT(ErrorModule::Kernel, ErrCodes::Timeout);
|
||||
/// Returned when Accept() is called on a port with no sessions to be accepted.
|
||||
constexpr ResultCode ERR_NO_PENDING_SESSIONS(-1);
|
||||
|
||||
@@ -116,6 +116,7 @@ struct KernelCore::Impl {
|
||||
next_thread_id = 1;
|
||||
|
||||
process_list.clear();
|
||||
current_process.reset();
|
||||
|
||||
handle_table.Clear();
|
||||
resource_limits.fill(nullptr);
|
||||
@@ -206,6 +207,7 @@ struct KernelCore::Impl {
|
||||
|
||||
// Lists all processes that exist in the current session.
|
||||
std::vector<SharedPtr<Process>> process_list;
|
||||
SharedPtr<Process> current_process;
|
||||
|
||||
Kernel::HandleTable handle_table;
|
||||
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
|
||||
@@ -264,6 +266,18 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
|
||||
impl->process_list.push_back(std::move(process));
|
||||
}
|
||||
|
||||
void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) {
|
||||
impl->current_process = std::move(process);
|
||||
}
|
||||
|
||||
SharedPtr<Process>& KernelCore::CurrentProcess() {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
const SharedPtr<Process>& KernelCore::CurrentProcess() const {
|
||||
return impl->current_process;
|
||||
}
|
||||
|
||||
void KernelCore::AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
impl->named_ports.emplace(std::move(name), std::move(port));
|
||||
}
|
||||
|
||||
@@ -65,6 +65,15 @@ public:
|
||||
/// Adds the given shared pointer to an internal list of active processes.
|
||||
void AppendNewProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Makes the given process the new current process.
|
||||
void MakeCurrentProcess(SharedPtr<Process> process);
|
||||
|
||||
/// Retrieves a reference to the current process.
|
||||
SharedPtr<Process>& CurrentProcess();
|
||||
|
||||
/// Retrieves a const reference to the current process.
|
||||
const SharedPtr<Process>& CurrentProcess() const;
|
||||
|
||||
/// Adds a port to the named port table
|
||||
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
|
||||
|
||||
|
||||
@@ -273,7 +273,11 @@ static void Break(u64 reason, u64 info1, u64 info2) {
|
||||
}
|
||||
|
||||
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
|
||||
static void OutputDebugString(VAddr address, s32 len) {
|
||||
static void OutputDebugString(VAddr address, u64 len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string str(len, '\0');
|
||||
Memory::ReadBlock(address, str.data(), str.size());
|
||||
LOG_DEBUG(Debug_Emulated, "{}", str);
|
||||
@@ -378,7 +382,7 @@ static ResultCode GetThreadPriority(u32* priority, Handle handle) {
|
||||
/// Sets the priority for the specified thread
|
||||
static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
if (priority > THREADPRIO_LOWEST) {
|
||||
return ERR_OUT_OF_RANGE;
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
@@ -520,10 +524,10 @@ static void ExitProcess() {
|
||||
/// Creates a new thread
|
||||
static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top,
|
||||
u32 priority, s32 processor_id) {
|
||||
std::string name = fmt::format("unknown-{:X}", entry_point);
|
||||
std::string name = fmt::format("thread-{:X}", entry_point);
|
||||
|
||||
if (priority > THREADPRIO_LOWEST) {
|
||||
return ERR_OUT_OF_RANGE;
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
|
||||
@@ -544,8 +548,8 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
case THREADPROCESSORID_3:
|
||||
break;
|
||||
default:
|
||||
ASSERT_MSG(false, "Unsupported thread processor ID: {}", processor_id);
|
||||
break;
|
||||
LOG_ERROR(Kernel_SVC, "Invalid thread processor ID: {}", processor_id);
|
||||
return ERR_INVALID_PROCESSOR_ID;
|
||||
}
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
|
||||
@@ -222,9 +222,9 @@ void SvcWrap() {
|
||||
func((s64)PARAM(0));
|
||||
}
|
||||
|
||||
template <void func(u64, s32 len)>
|
||||
template <void func(u64, u64 len)>
|
||||
void SvcWrap() {
|
||||
func(PARAM(0), (s32)(PARAM(1) & 0xFFFFFFFF));
|
||||
func(PARAM(0), PARAM(1));
|
||||
}
|
||||
|
||||
template <void func(u64, u64, u64)>
|
||||
|
||||
@@ -227,12 +227,12 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
// Check if priority is in ranged. Lowest priority -> highest priority id.
|
||||
if (priority > THREADPRIO_LOWEST) {
|
||||
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
|
||||
return ERR_OUT_OF_RANGE;
|
||||
return ERR_INVALID_THREAD_PRIORITY;
|
||||
}
|
||||
|
||||
if (processor_id > THREADPROCESSORID_MAX) {
|
||||
LOG_ERROR(Kernel_SVC, "Invalid processor id: {}", processor_id);
|
||||
return ERR_OUT_OF_RANGE_KERNEL;
|
||||
return ERR_INVALID_PROCESSOR_ID;
|
||||
}
|
||||
|
||||
// TODO(yuriks): Other checks, returning 0xD9001BEA
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
|
||||
enum ThreadPriority : u32 {
|
||||
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
|
||||
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
|
||||
@@ -54,12 +60,6 @@ enum class ThreadWakeupReason {
|
||||
Timeout // The thread was woken up due to a wait timeout.
|
||||
};
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
|
||||
class Thread final : public WaitObject {
|
||||
public:
|
||||
/**
|
||||
|
||||
@@ -18,4 +18,6 @@ ACC_AA::ACC_AA(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_AA::~ACC_AA() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -12,6 +12,7 @@ class ACC_AA final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_AA(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
~ACC_AA() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -51,4 +51,6 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_SU::~ACC_SU() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -6,14 +6,13 @@
|
||||
|
||||
#include "core/hle/service/acc/acc.h"
|
||||
|
||||
namespace Service {
|
||||
namespace Account {
|
||||
namespace Service::Account {
|
||||
|
||||
class ACC_SU final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_SU(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
~ACC_SU() override;
|
||||
};
|
||||
|
||||
} // namespace Account
|
||||
} // namespace Service
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -31,4 +31,6 @@ ACC_U0::ACC_U0(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_U0::~ACC_U0() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -12,6 +12,7 @@ class ACC_U0 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U0(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
~ACC_U0() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -38,4 +38,6 @@ ACC_U1::ACC_U1(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ACC_U1::~ACC_U1() = default;
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -12,6 +12,7 @@ class ACC_U1 final : public Module::Interface {
|
||||
public:
|
||||
explicit ACC_U1(std::shared_ptr<Module> module,
|
||||
std::shared_ptr<ProfileManager> profile_manager);
|
||||
~ACC_U1() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Account
|
||||
|
||||
@@ -29,6 +29,8 @@ ProfileManager::ProfileManager() {
|
||||
OpenUser(user_uuid);
|
||||
}
|
||||
|
||||
ProfileManager::~ProfileManager() = default;
|
||||
|
||||
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
|
||||
/// internal management of the users profiles
|
||||
boost::optional<size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) {
|
||||
|
||||
@@ -82,6 +82,8 @@ static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size");
|
||||
class ProfileManager {
|
||||
public:
|
||||
ProfileManager(); // TODO(ogniK): Load from system save
|
||||
~ProfileManager();
|
||||
|
||||
ResultCode AddUser(const ProfileInfo& user);
|
||||
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
|
||||
ResultCode CreateNewUser(UUID uuid, const std::string& username);
|
||||
|
||||
@@ -35,6 +35,8 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IWindowController::~IWindowController() = default;
|
||||
|
||||
void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
@@ -61,6 +63,8 @@ IAudioController::IAudioController() : ServiceFramework("IAudioController") {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IAudioController::~IAudioController() = default;
|
||||
|
||||
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -116,7 +120,10 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IDisplayController::~IDisplayController() = default;
|
||||
|
||||
IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
|
||||
IDebugFunctions::~IDebugFunctions() = default;
|
||||
|
||||
ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
: ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) {
|
||||
@@ -165,6 +172,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
|
||||
Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "ISelfController:LaunchableEvent");
|
||||
}
|
||||
|
||||
ISelfController::~ISelfController() = default;
|
||||
|
||||
void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) {
|
||||
// Takes 3 input u8s with each field located immediately after the previous u8, these are
|
||||
// bool flags. No output.
|
||||
@@ -337,6 +346,8 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
|
||||
event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "ICommonStateGetter:Event");
|
||||
}
|
||||
|
||||
ICommonStateGetter::~ICommonStateGetter() = default;
|
||||
|
||||
void ICommonStateGetter::GetBootMode(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -573,6 +584,8 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
ILibraryAppletCreator::~ILibraryAppletCreator() = default;
|
||||
|
||||
void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
@@ -638,6 +651,8 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IApplicationFunctions::~IApplicationFunctions() = default;
|
||||
|
||||
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
|
||||
constexpr std::array<u8, 0x88> data{{
|
||||
0xca, 0x97, 0x94, 0xc7, // Magic
|
||||
@@ -760,6 +775,8 @@ IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions"
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IHomeMenuFunctions::~IHomeMenuFunctions() = default;
|
||||
|
||||
void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -783,6 +800,8 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IGlobalStateController::~IGlobalStateController() = default;
|
||||
|
||||
IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "CreateApplication"},
|
||||
@@ -793,6 +812,8 @@ IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreat
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IApplicationCreator::~IApplicationCreator() = default;
|
||||
|
||||
IProcessWindingController::IProcessWindingController()
|
||||
: ServiceFramework("IProcessWindingController") {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -807,4 +828,6 @@ IProcessWindingController::IProcessWindingController()
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IProcessWindingController::~IProcessWindingController() = default;
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -42,6 +42,7 @@ enum SystemLanguage {
|
||||
class IWindowController final : public ServiceFramework<IWindowController> {
|
||||
public:
|
||||
IWindowController();
|
||||
~IWindowController() override;
|
||||
|
||||
private:
|
||||
void GetAppletResourceUserId(Kernel::HLERequestContext& ctx);
|
||||
@@ -51,6 +52,7 @@ private:
|
||||
class IAudioController final : public ServiceFramework<IAudioController> {
|
||||
public:
|
||||
IAudioController();
|
||||
~IAudioController() override;
|
||||
|
||||
private:
|
||||
void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
|
||||
@@ -63,16 +65,19 @@ private:
|
||||
class IDisplayController final : public ServiceFramework<IDisplayController> {
|
||||
public:
|
||||
IDisplayController();
|
||||
~IDisplayController() override;
|
||||
};
|
||||
|
||||
class IDebugFunctions final : public ServiceFramework<IDebugFunctions> {
|
||||
public:
|
||||
IDebugFunctions();
|
||||
~IDebugFunctions() override;
|
||||
};
|
||||
|
||||
class ISelfController final : public ServiceFramework<ISelfController> {
|
||||
public:
|
||||
explicit ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
|
||||
~ISelfController() override;
|
||||
|
||||
private:
|
||||
void SetFocusHandlingMode(Kernel::HLERequestContext& ctx);
|
||||
@@ -98,6 +103,7 @@ private:
|
||||
class ICommonStateGetter final : public ServiceFramework<ICommonStateGetter> {
|
||||
public:
|
||||
ICommonStateGetter();
|
||||
~ICommonStateGetter() override;
|
||||
|
||||
private:
|
||||
enum class FocusState : u8 {
|
||||
@@ -124,6 +130,7 @@ private:
|
||||
class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
|
||||
public:
|
||||
ILibraryAppletCreator();
|
||||
~ILibraryAppletCreator() override;
|
||||
|
||||
private:
|
||||
void CreateLibraryApplet(Kernel::HLERequestContext& ctx);
|
||||
@@ -133,6 +140,7 @@ private:
|
||||
class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
|
||||
public:
|
||||
IApplicationFunctions();
|
||||
~IApplicationFunctions() override;
|
||||
|
||||
private:
|
||||
void PopLaunchParameter(Kernel::HLERequestContext& ctx);
|
||||
@@ -150,6 +158,7 @@ private:
|
||||
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
|
||||
public:
|
||||
IHomeMenuFunctions();
|
||||
~IHomeMenuFunctions() override;
|
||||
|
||||
private:
|
||||
void RequestToGetForeground(Kernel::HLERequestContext& ctx);
|
||||
@@ -158,16 +167,19 @@ private:
|
||||
class IGlobalStateController final : public ServiceFramework<IGlobalStateController> {
|
||||
public:
|
||||
IGlobalStateController();
|
||||
~IGlobalStateController() override;
|
||||
};
|
||||
|
||||
class IApplicationCreator final : public ServiceFramework<IApplicationCreator> {
|
||||
public:
|
||||
IApplicationCreator();
|
||||
~IApplicationCreator() override;
|
||||
};
|
||||
|
||||
class IProcessWindingController final : public ServiceFramework<IProcessWindingController> {
|
||||
public:
|
||||
IProcessWindingController();
|
||||
~IProcessWindingController() override;
|
||||
};
|
||||
|
||||
/// Registers all AM services with the specified service manager.
|
||||
|
||||
@@ -222,4 +222,6 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AppletAE::~AppletAE() = default;
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace AM {
|
||||
class AppletAE final : public ServiceFramework<AppletAE> {
|
||||
public:
|
||||
explicit AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
|
||||
~AppletAE() = default;
|
||||
~AppletAE() override;
|
||||
|
||||
private:
|
||||
void OpenSystemAppletProxy(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -103,4 +103,6 @@ AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AppletOE::~AppletOE() = default;
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace AM {
|
||||
class AppletOE final : public ServiceFramework<AppletOE> {
|
||||
public:
|
||||
explicit AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger);
|
||||
~AppletOE() = default;
|
||||
~AppletOE() override;
|
||||
|
||||
private:
|
||||
void OpenApplicationProxy(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -21,4 +21,6 @@ IdleSys::IdleSys() : ServiceFramework{"idle:sys"} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IdleSys::~IdleSys() = default;
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Service::AM {
|
||||
class IdleSys final : public ServiceFramework<IdleSys> {
|
||||
public:
|
||||
explicit IdleSys();
|
||||
~IdleSys() override;
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -39,4 +39,6 @@ OMM::OMM() : ServiceFramework{"omm"} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
OMM::~OMM() = default;
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Service::AM {
|
||||
class OMM final : public ServiceFramework<OMM> {
|
||||
public:
|
||||
explicit OMM();
|
||||
~OMM() override;
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -27,4 +27,6 @@ SPSM::SPSM() : ServiceFramework{"spsm"} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
SPSM::~SPSM() = default;
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Service::AM {
|
||||
class SPSM final : public ServiceFramework<SPSM> {
|
||||
public:
|
||||
explicit SPSM();
|
||||
~SPSM() override;
|
||||
};
|
||||
|
||||
} // namespace Service::AM
|
||||
|
||||
@@ -23,6 +23,8 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u") {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AOC_U::~AOC_U() = default;
|
||||
|
||||
void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Service::AOC {
|
||||
class AOC_U final : public ServiceFramework<AOC_U> {
|
||||
public:
|
||||
AOC_U();
|
||||
~AOC_U() = default;
|
||||
~AOC_U() override;
|
||||
|
||||
private:
|
||||
void CountAddOnContent(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
namespace Service::APM {
|
||||
|
||||
Module::Module() = default;
|
||||
Module::~Module() = default;
|
||||
|
||||
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||
auto module_ = std::make_shared<Module>();
|
||||
std::make_shared<APM>(module_, "apm")->InstallAsService(service_manager);
|
||||
|
||||
@@ -15,8 +15,8 @@ enum class PerformanceMode : u8 {
|
||||
|
||||
class Module final {
|
||||
public:
|
||||
Module() = default;
|
||||
~Module() = default;
|
||||
Module();
|
||||
~Module();
|
||||
};
|
||||
|
||||
/// Registers all AM services with the specified service manager.
|
||||
|
||||
@@ -70,6 +70,8 @@ APM::APM(std::shared_ptr<Module> apm, const char* name)
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
APM::~APM() = default;
|
||||
|
||||
void APM::OpenSession(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
@@ -93,6 +95,8 @@ APM_Sys::APM_Sys() : ServiceFramework{"apm:sys"} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
APM_Sys::~APM_Sys() = default;
|
||||
|
||||
void APM_Sys::GetPerformanceEvent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Service::APM {
|
||||
class APM final : public ServiceFramework<APM> {
|
||||
public:
|
||||
explicit APM(std::shared_ptr<Module> apm, const char* name);
|
||||
~APM() = default;
|
||||
~APM() override;
|
||||
|
||||
private:
|
||||
void OpenSession(Kernel::HLERequestContext& ctx);
|
||||
@@ -22,6 +22,7 @@ private:
|
||||
class APM_Sys final : public ServiceFramework<APM_Sys> {
|
||||
public:
|
||||
explicit APM_Sys();
|
||||
~APM_Sys() override;
|
||||
|
||||
private:
|
||||
void GetPerformanceEvent(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -42,4 +42,6 @@ AudCtl::AudCtl() : ServiceFramework{"audctl"} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudCtl::~AudCtl() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Service::Audio {
|
||||
class AudCtl final : public ServiceFramework<AudCtl> {
|
||||
public:
|
||||
explicit AudCtl();
|
||||
~AudCtl() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -17,4 +17,6 @@ AudDbg::AudDbg(const char* name) : ServiceFramework{name} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudDbg::~AudDbg() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Service::Audio {
|
||||
class AudDbg final : public ServiceFramework<AudDbg> {
|
||||
public:
|
||||
explicit AudDbg(const char* name);
|
||||
~AudDbg() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -19,4 +19,6 @@ AudInA::AudInA() : ServiceFramework{"audin:a"} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudInA::~AudInA() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Service::Audio {
|
||||
class AudInA final : public ServiceFramework<AudInA> {
|
||||
public:
|
||||
explicit AudInA();
|
||||
~AudInA() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -41,4 +41,6 @@ AudInU::AudInU() : ServiceFramework("audin:u") {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudInU::~AudInU() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Service::Audio {
|
||||
class AudInU final : public ServiceFramework<AudInU> {
|
||||
public:
|
||||
explicit AudInU();
|
||||
~AudInU() = default;
|
||||
~AudInU() override;
|
||||
};
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "core/hle/service/audio/audren_u.h"
|
||||
#include "core/hle/service/audio/codecctl.h"
|
||||
#include "core/hle/service/audio/hwopus.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Service::Audio {
|
||||
|
||||
|
||||
@@ -21,4 +21,6 @@ AudOutA::AudOutA() : ServiceFramework{"audout:a"} {
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
AudOutA::~AudOutA() = default;
|
||||
|
||||
} // namespace Service::Audio
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user