diff options
Diffstat (limited to 'src')
396 files changed, 7589 insertions, 2835 deletions
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 82e4850f7..c381dbe1d 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -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) diff --git a/src/audio_core/algorithm/filter.cpp b/src/audio_core/algorithm/filter.cpp index 9fcd0614d..f65bf64f7 100644 --- a/src/audio_core/algorithm/filter.cpp +++ b/src/audio_core/algorithm/filter.cpp @@ -35,12 +35,12 @@ Filter::Filter(double a0, double a1, double a2, double b0, double b1, double b2) : a1(a1 / a0), a2(a2 / a0), b0(b0 / a0), b1(b1 / a0), b2(b2 / a0) {} void Filter::Process(std::vector<s16>& signal) { - const size_t num_frames = signal.size() / 2; - for (size_t i = 0; i < num_frames; i++) { + const std::size_t num_frames = signal.size() / 2; + for (std::size_t i = 0; i < num_frames; i++) { std::rotate(in.begin(), in.end() - 1, in.end()); std::rotate(out.begin(), out.end() - 1, out.end()); - for (size_t ch = 0; ch < channel_count; ch++) { + for (std::size_t ch = 0; ch < channel_count; ch++) { in[0][ch] = signal[i * channel_count + ch]; out[0][ch] = b0 * in[0][ch] + b1 * in[1][ch] + b2 * in[2][ch] - a1 * out[1][ch] - @@ -54,14 +54,14 @@ void Filter::Process(std::vector<s16>& signal) { /// Calculates the appropriate Q for each biquad in a cascading filter. /// @param total_count The total number of biquads to be cascaded. /// @param index 0-index of the biquad to calculate the Q value for. -static double CascadingBiquadQ(size_t total_count, size_t index) { +static double CascadingBiquadQ(std::size_t total_count, std::size_t index) { const double pole = M_PI * (2 * index + 1) / (4.0 * total_count); return 1.0 / (2.0 * std::cos(pole)); } -CascadingFilter CascadingFilter::LowPass(double cutoff, size_t cascade_size) { +CascadingFilter CascadingFilter::LowPass(double cutoff, std::size_t cascade_size) { std::vector<Filter> cascade(cascade_size); - for (size_t i = 0; i < cascade_size; i++) { + for (std::size_t i = 0; i < cascade_size; i++) { cascade[i] = Filter::LowPass(cutoff, CascadingBiquadQ(cascade_size, i)); } return CascadingFilter{std::move(cascade)}; diff --git a/src/audio_core/algorithm/filter.h b/src/audio_core/algorithm/filter.h index a41beef98..3546d149b 100644 --- a/src/audio_core/algorithm/filter.h +++ b/src/audio_core/algorithm/filter.h @@ -30,7 +30,7 @@ public: void Process(std::vector<s16>& signal); private: - static constexpr size_t channel_count = 2; + static constexpr std::size_t channel_count = 2; /// Coefficients are in normalized form (a0 = 1.0). double a1, a2, b0, b1, b2; @@ -46,7 +46,7 @@ public: /// Creates a cascading low-pass filter. /// @param cutoff Determines the cutoff frequency. A value from 0.0 to 1.0. /// @param cascade_size Number of biquads in cascade. - static CascadingFilter LowPass(double cutoff, size_t cascade_size); + static CascadingFilter LowPass(double cutoff, std::size_t cascade_size); /// Passthrough. CascadingFilter(); diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp index 11459821f..3aea9b0f2 100644 --- a/src/audio_core/algorithm/interpolate.cpp +++ b/src/audio_core/algorithm/interpolate.cpp @@ -14,7 +14,7 @@ namespace AudioCore { /// The Lanczos kernel -static double Lanczos(size_t a, double x) { +static double Lanczos(std::size_t a, double x) { if (x == 0.0) return 1.0; const double px = M_PI * x; @@ -37,15 +37,15 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, } state.nyquist.Process(input); - constexpr size_t taps = InterpolationState::lanczos_taps; - const size_t num_frames = input.size() / 2; + constexpr std::size_t taps = InterpolationState::lanczos_taps; + const std::size_t num_frames = input.size() / 2; std::vector<s16> output; - output.reserve(static_cast<size_t>(input.size() / ratio + 4)); + output.reserve(static_cast<std::size_t>(input.size() / ratio + 4)); double& pos = state.position; auto& h = state.history; - for (size_t i = 0; i < num_frames; ++i) { + for (std::size_t i = 0; i < num_frames; ++i) { std::rotate(h.begin(), h.end() - 1, h.end()); h[0][0] = input[i * 2 + 0]; h[0][1] = input[i * 2 + 1]; @@ -53,7 +53,7 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input, while (pos <= 1.0) { double l = 0.0; double r = 0.0; - for (size_t j = 0; j < h.size(); j++) { + for (std::size_t j = 0; j < h.size(); j++) { l += Lanczos(taps, pos + j - taps + 1) * h[j][0]; r += Lanczos(taps, pos + j - taps + 1) * h[j][1]; } diff --git a/src/audio_core/algorithm/interpolate.h b/src/audio_core/algorithm/interpolate.h index c79c2eef4..edbd6460f 100644 --- a/src/audio_core/algorithm/interpolate.h +++ b/src/audio_core/algorithm/interpolate.h @@ -12,8 +12,8 @@ namespace AudioCore { struct InterpolationState { - static constexpr size_t lanczos_taps = 4; - static constexpr size_t history_size = lanczos_taps * 2 - 1; + static constexpr std::size_t lanczos_taps = 4; + static constexpr std::size_t history_size = lanczos_taps * 2 - 1; double current_ratio = 0.0; CascadingFilter nyquist; diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp index 12632a95c..0c8f5b18e 100644 --- a/src/audio_core/audio_out.cpp +++ b/src/audio_core/audio_out.cpp @@ -39,7 +39,8 @@ StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name)); } -std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count) { +std::vector<Buffer::Tag> AudioOut::GetTagsAndReleaseBuffers(StreamPtr stream, + std::size_t max_count) { return stream->GetTagsAndReleaseBuffers(max_count); } diff --git a/src/audio_core/audio_out.h b/src/audio_core/audio_out.h index 39b7e656b..df9607ac7 100644 --- a/src/audio_core/audio_out.h +++ b/src/audio_core/audio_out.h @@ -25,7 +25,7 @@ public: Stream::ReleaseCallback&& release_callback); /// Returns a vector of recently released buffers specified by tag for the specified stream - std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, size_t max_count); + std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count); /// Starts an audio stream for playback void StartStream(StreamPtr stream); diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index 397b107f5..83b75e61f 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -3,9 +3,12 @@ // Refer to the license.txt file included. #include "audio_core/algorithm/interpolate.h" +#include "audio_core/audio_out.h" #include "audio_core/audio_renderer.h" +#include "audio_core/codec.h" #include "common/assert.h" #include "common/logging/log.h" +#include "core/hle/kernel/event.h" #include "core/memory.h" namespace AudioCore { @@ -13,20 +16,57 @@ namespace AudioCore { constexpr u32 STREAM_SAMPLE_RATE{48000}; constexpr u32 STREAM_NUM_CHANNELS{2}; +class AudioRenderer::VoiceState { +public: + bool IsPlaying() const { + return is_in_use && info.play_state == PlayState::Started; + } + + const VoiceOutStatus& GetOutStatus() const { + return out_status; + } + + const VoiceInfo& GetInfo() const { + return info; + } + + VoiceInfo& Info() { + return info; + } + + void SetWaveIndex(std::size_t index); + std::vector<s16> DequeueSamples(std::size_t sample_count); + void UpdateState(); + void RefreshBuffer(); + +private: + bool is_in_use{}; + bool is_refresh_pending{}; + std::size_t wave_index{}; + std::size_t offset{}; + Codec::ADPCMState adpcm_state{}; + InterpolationState interp_state{}; + std::vector<s16> samples; + VoiceOutStatus out_status{}; + VoiceInfo info{}; +}; + 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); QueueMixedBuffer(2); } +AudioRenderer::~AudioRenderer() = default; + u32 AudioRenderer::GetSampleRate() const { return worker_params.sample_rate; } @@ -52,8 +92,8 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ memory_pool_count * sizeof(MemoryPoolInfo)); // Copy VoiceInfo structs - size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size + - config.voice_resource_size}; + std::size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size + + config.voice_resource_size}; for (auto& voice : voices) { std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo)); offset += sizeof(VoiceInfo); @@ -72,7 +112,7 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ // Update memory pool state std::vector<MemoryPoolEntry> memory_pool(memory_pool_count); - for (size_t index = 0; index < memory_pool.size(); ++index) { + for (std::size_t index = 0; index < memory_pool.size(); ++index) { if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) { memory_pool[index].state = MemoryPoolStates::Attached; } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) { @@ -93,7 +133,7 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ response_data.memory_pools_size); // Copy output voice status - size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size}; + std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size}; for (const auto& voice : voices) { std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(), sizeof(VoiceOutStatus)); @@ -103,12 +143,12 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_ return output_params; } -void AudioRenderer::VoiceState::SetWaveIndex(size_t index) { +void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) { wave_index = index & 3; is_refresh_pending = true; } -std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(size_t sample_count) { +std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(std::size_t sample_count) { if (!IsPlaying()) { return {}; } @@ -117,9 +157,9 @@ std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(size_t sample_count) RefreshBuffer(); } - const size_t max_size{samples.size() - offset}; - const size_t dequeue_offset{offset}; - size_t size{sample_count * STREAM_NUM_CHANNELS}; + const std::size_t max_size{samples.size() - offset}; + const std::size_t dequeue_offset{offset}; + std::size_t size{sample_count * STREAM_NUM_CHANNELS}; if (size > max_size) { size = max_size; } @@ -184,7 +224,7 @@ void AudioRenderer::VoiceState::RefreshBuffer() { case 1: // 1 channel is upsampled to 2 channel samples.resize(new_samples.size() * 2); - for (size_t index = 0; index < new_samples.size(); ++index) { + for (std::size_t index = 0; index < new_samples.size(); ++index) { samples[index * 2] = new_samples[index]; samples[index * 2 + 1] = new_samples[index]; } @@ -210,7 +250,7 @@ static constexpr s16 ClampToS16(s32 value) { } void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { - constexpr size_t BUFFER_SIZE{512}; + constexpr std::size_t BUFFER_SIZE{512}; std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels()); for (auto& voice : voices) { @@ -218,7 +258,7 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { continue; } - size_t offset{}; + std::size_t offset{}; s64 samples_remaining{BUFFER_SIZE}; while (samples_remaining > 0) { const std::vector<s16> samples{voice.DequeueSamples(samples_remaining)}; @@ -236,11 +276,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); } diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h index eba67f28e..2c4f5ab75 100644 --- a/src/audio_core/audio_renderer.h +++ b/src/audio_core/audio_renderer.h @@ -8,16 +8,20 @@ #include <memory> #include <vector> -#include "audio_core/algorithm/interpolate.h" -#include "audio_core/audio_out.h" -#include "audio_core/codec.h" #include "audio_core/stream.h" +#include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "core/hle/kernel/event.h" +#include "core/hle/kernel/object.h" + +namespace Kernel { +class Event; +} namespace AudioCore { +class AudioOut; + enum class PlayState : u8 { Started = 0, Stopped = 1, @@ -158,6 +162,8 @@ static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size class AudioRenderer { public: AudioRenderer(AudioRendererParameter params, Kernel::SharedPtr<Kernel::Event> buffer_event); + ~AudioRenderer(); + std::vector<u8> UpdateAudioRenderer(const std::vector<u8>& input_params); void QueueMixedBuffer(Buffer::Tag tag); void ReleaseAndQueueBuffers(); @@ -166,45 +172,12 @@ public: u32 GetMixBufferCount() const; private: - class VoiceState { - public: - bool IsPlaying() const { - return is_in_use && info.play_state == PlayState::Started; - } - - const VoiceOutStatus& GetOutStatus() const { - return out_status; - } - - const VoiceInfo& GetInfo() const { - return info; - } - - VoiceInfo& Info() { - return info; - } - - void SetWaveIndex(size_t index); - std::vector<s16> DequeueSamples(size_t sample_count); - void UpdateState(); - void RefreshBuffer(); - - private: - bool is_in_use{}; - bool is_refresh_pending{}; - size_t wave_index{}; - size_t offset{}; - Codec::ADPCMState adpcm_state{}; - InterpolationState interp_state{}; - std::vector<s16> samples; - VoiceOutStatus out_status{}; - VoiceInfo info{}; - }; + class VoiceState; AudioRendererParameter worker_params; Kernel::SharedPtr<Kernel::Event> buffer_event; std::vector<VoiceState> voices; - std::unique_ptr<AudioCore::AudioOut> audio_core; + std::unique_ptr<AudioOut> audio_out; AudioCore::StreamPtr stream; }; diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp index c3021403f..454de798b 100644 --- a/src/audio_core/codec.cpp +++ b/src/audio_core/codec.cpp @@ -8,27 +8,27 @@ namespace AudioCore::Codec { -std::vector<s16> DecodeADPCM(const u8* const data, size_t size, const ADPCM_Coeff& coeff, +std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff, ADPCMState& state) { // GC-ADPCM with scale factor and variable coefficients. // Frames are 8 bytes long containing 14 samples each. // Samples are 4 bits (one nibble) long. - constexpr size_t FRAME_LEN = 8; - constexpr size_t SAMPLES_PER_FRAME = 14; + constexpr std::size_t FRAME_LEN = 8; + constexpr std::size_t SAMPLES_PER_FRAME = 14; constexpr std::array<int, 16> SIGNED_NIBBLES = { {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}}; - const size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; - const size_t ret_size = + const std::size_t sample_count = (size / FRAME_LEN) * SAMPLES_PER_FRAME; + const std::size_t ret_size = sample_count % 2 == 0 ? sample_count : sample_count + 1; // Ensure multiple of two. std::vector<s16> ret(ret_size); int yn1 = state.yn1, yn2 = state.yn2; - const size_t NUM_FRAMES = + const std::size_t NUM_FRAMES = (sample_count + (SAMPLES_PER_FRAME - 1)) / SAMPLES_PER_FRAME; // Round up. - for (size_t framei = 0; framei < NUM_FRAMES; framei++) { + for (std::size_t framei = 0; framei < NUM_FRAMES; framei++) { const int frame_header = data[framei * FRAME_LEN]; const int scale = 1 << (frame_header & 0xF); const int idx = (frame_header >> 4) & 0x7; @@ -53,9 +53,9 @@ std::vector<s16> DecodeADPCM(const u8* const data, size_t size, const ADPCM_Coef return static_cast<s16>(val); }; - size_t outputi = framei * SAMPLES_PER_FRAME; - size_t datai = framei * FRAME_LEN + 1; - for (size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) { + std::size_t outputi = framei * SAMPLES_PER_FRAME; + std::size_t datai = framei * FRAME_LEN + 1; + for (std::size_t i = 0; i < SAMPLES_PER_FRAME && outputi < sample_count; i += 2) { const s16 sample1 = decode_sample(SIGNED_NIBBLES[data[datai] >> 4]); ret[outputi] = sample1; outputi++; diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h index 3f845c42c..ef2ce01a8 100644 --- a/src/audio_core/codec.h +++ b/src/audio_core/codec.h @@ -38,7 +38,7 @@ using ADPCM_Coeff = std::array<s16, 16>; * @param state ADPCM state, this is updated with new state * @return Decoded stereo signed PCM16 data, sample_count in length */ -std::vector<s16> DecodeADPCM(const u8* const data, size_t size, const ADPCM_Coeff& coeff, +std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM_Coeff& coeff, ADPCMState& state); }; // namespace AudioCore::Codec diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp index 5a1177d0c..392039688 100644 --- a/src/audio_core/cubeb_sink.cpp +++ b/src/audio_core/cubeb_sink.cpp @@ -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 (std::size_t i = 0; i < samples.size(); i += source_num_channels) { + for (std::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()); + std::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); @@ -117,10 +119,10 @@ CubebSink::CubebSink(std::string target_device_name) { LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); } else { const auto collection_end{collection.device + collection.count}; - const auto device{std::find_if(collection.device, collection_end, - [&](const cubeb_device_info& device) { - return target_device_name == device.friendly_name; - })}; + const auto device{ + std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) { + return target_device_name == info.friendly_name; + })}; if (device != collection_end) { output_device = device->devid; } @@ -144,44 +146,59 @@ 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 frames_to_write{ - std::min(impl->queue.size() / impl->GetNumChannels(), static_cast<size_t>(num_frames))}; + const std::size_t num_channels = impl->GetNumChannels(); + const std::size_t samples_to_write = num_channels * num_frames; + std::size_t samples_written; + + if (Settings::values.enable_audio_stretching) { + const std::vector<s16> in{impl->queue.Pop()}; + const std::size_t num_in{in.size() / num_channels}; + s16* const out{reinterpret_cast<s16*>(buffer)}; + const std::size_t out_frames = + impl->time_stretch.Process(in.data(), num_in, out, num_frames); + samples_written = out_frames * num_channels; + + if (impl->should_flush) { + impl->time_stretch.Flush(); + impl->should_flush = false; + } + } else { + samples_written = impl->queue.Pop(buffer, samples_to_write); + } - 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 (samples_written >= num_channels) { + std::memcpy(&impl->last_frame[0], buffer + (samples_written - num_channels) * sizeof(s16), + num_channels * sizeof(s16)); + } - 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()); + // Fill the rest of the frames with last_frame + for (std::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; cubeb* ctx; - if (cubeb_init(&ctx, "Citra Device Enumerator", nullptr) != CUBEB_OK) { + if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "cubeb_init failed"); return {}; } @@ -190,7 +207,7 @@ std::vector<std::string> ListCubebSinkDevices() { if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) { LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported"); } else { - for (size_t i = 0; i < collection.count; i++) { + for (std::size_t i = 0; i < collection.count; i++) { const cubeb_device_info& device = collection.device[i]; if (device.friendly_name) { device_list.emplace_back(device.friendly_name); diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h index f235d93e5..a78d78893 100644 --- a/src/audio_core/null_sink.h +++ b/src/audio_core/null_sink.h @@ -21,6 +21,12 @@ public: private: struct NullSinkStreamImpl final : SinkStream { void EnqueueSamples(u32 /*num_channels*/, const std::vector<s16>& /*samples*/) override {} + + std::size_t SamplesInQueue(u32 /*num_channels*/) const override { + return 0; + } + + void Flush() override {} } null_sink_stream; }; diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp index 955ba20fb..67cf1f3b2 100644 --- a/src/audio_core/sink_details.cpp +++ b/src/audio_core/sink_details.cpp @@ -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; }); diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h index ea666c554..03534b187 100644 --- a/src/audio_core/sink_details.h +++ b/src/audio_core/sink_details.h @@ -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 diff --git a/src/audio_core/sink_stream.h b/src/audio_core/sink_stream.h index 41b6736d8..4309ad094 100644 --- a/src/audio_core/sink_stream.h +++ b/src/audio_core/sink_stream.h @@ -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>; diff --git a/src/audio_core/stream.cpp b/src/audio_core/stream.cpp index ad9e2915c..449db2416 100644 --- a/src/audio_core/stream.cpp +++ b/src/audio_core/stream.cpp @@ -7,16 +7,18 @@ #include "audio_core/sink.h" #include "audio_core/sink_details.h" +#include "audio_core/sink_stream.h" #include "audio_core/stream.h" #include "common/assert.h" #include "common/logging/log.h" +#include "common/microprofile.h" #include "core/core_timing.h" #include "core/core_timing_util.h" #include "core/settings.h" namespace AudioCore { -constexpr size_t MaxAudioBufferCount{32}; +constexpr std::size_t MaxAudioBufferCount{32}; u32 Stream::GetNumChannels() const { switch (format) { @@ -51,7 +53,7 @@ void Stream::Stop() { } s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const { - const size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; + const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()}; return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate); } @@ -72,6 +74,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; } @@ -82,6 +85,7 @@ void Stream::PlayNextBuffer() { if (queued_buffers.empty()) { // No queued buffers - we are effectively paused + sink_stream.Flush(); return; } @@ -89,12 +93,16 @@ void Stream::PlayNextBuffer() { queued_buffers.pop(); VolumeAdjustSamples(active_buffer->Samples()); + sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples()); CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {}); } +MICROPROFILE_DEFINE(AudioOutput, "Audio", "ReleaseActiveBuffer", MP_RGB(100, 100, 255)); + void Stream::ReleaseActiveBuffer() { + MICROPROFILE_SCOPE(AudioOutput); ASSERT(active_buffer); released_buffers.push(std::move(active_buffer)); release_callback(); @@ -115,9 +123,9 @@ bool Stream::ContainsBuffer(Buffer::Tag tag) const { return {}; } -std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(size_t max_count) { +std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) { std::vector<Buffer::Tag> tags; - for (size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { + for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { tags.push_back(released_buffers.front()->GetTag()); released_buffers.pop(); } diff --git a/src/audio_core/stream.h b/src/audio_core/stream.h index 049b92ca9..27db1112f 100644 --- a/src/audio_core/stream.h +++ b/src/audio_core/stream.h @@ -11,13 +11,16 @@ #include <queue> #include "audio_core/buffer.h" -#include "audio_core/sink_stream.h" -#include "common/assert.h" #include "common/common_types.h" -#include "core/core_timing.h" + +namespace CoreTiming { +struct EventType; +} namespace AudioCore { +class SinkStream; + /** * Represents an audio stream, which is a sequence of queued buffers, to be outputed by AudioOut */ @@ -49,7 +52,7 @@ public: bool ContainsBuffer(Buffer::Tag tag) const; /// Returns a vector of recently released buffers specified by tag - std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(size_t max_count); + std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(std::size_t max_count); /// Returns true if the stream is currently playing bool IsPlaying() const { @@ -57,7 +60,7 @@ public: } /// Returns the number of queued buffers - size_t GetQueueSize() const { + std::size_t GetQueueSize() const { return queued_buffers.size(); } diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp new file mode 100644 index 000000000..fc14151da --- /dev/null +++ b/src/audio_core/time_stretch.cpp @@ -0,0 +1,69 @@ +// 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(); +} + +std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out, + std::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, static_cast<u32>(num_in)); + return m_sound_touch.receiveSamples(out, static_cast<u32>(num_out)); +} + +} // namespace AudioCore diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h new file mode 100644 index 000000000..decd760f1 --- /dev/null +++ b/src/audio_core/time_stretch.h @@ -0,0 +1,35 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#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` + std::size_t Process(const s16* in, std::size_t num_in, s16* out, std::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 diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d9424ea91..6a3f1fe08 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -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 diff --git a/src/common/alignment.h b/src/common/alignment.h index b9dd38746..225770fab 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -8,13 +8,13 @@ namespace Common { template <typename T> -constexpr T AlignUp(T value, size_t size) { +constexpr T AlignUp(T value, std::size_t size) { static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); return static_cast<T>(value + (size - value % size) % size); } template <typename T> -constexpr T AlignDown(T value, size_t size) { +constexpr T AlignDown(T value, std::size_t size) { static_assert(std::is_unsigned_v<T>, "T must be an unsigned value."); return static_cast<T>(value - value % size); } diff --git a/src/common/bit_field.h b/src/common/bit_field.h index 732201de7..bf803da8d 100644 --- a/src/common/bit_field.h +++ b/src/common/bit_field.h @@ -129,8 +129,8 @@ private: public: /// Constants to allow limited introspection of fields if needed - static constexpr size_t position = Position; - static constexpr size_t bits = Bits; + static constexpr std::size_t position = Position; + static constexpr std::size_t bits = Bits; static constexpr StorageType mask = (((StorageTypeU)~0) >> (8 * sizeof(T) - bits)) << position; /** diff --git a/src/common/bit_set.h b/src/common/bit_set.h index 5a197d8c1..5cd1352b2 100644 --- a/src/common/bit_set.h +++ b/src/common/bit_set.h @@ -170,14 +170,14 @@ public: m_val |= (IntTy)1 << bit; } - static BitSet AllTrue(size_t count) { + static BitSet AllTrue(std::size_t count) { return BitSet(count == sizeof(IntTy) * 8 ? ~(IntTy)0 : (((IntTy)1 << count) - 1)); } - Ref operator[](size_t bit) { + Ref operator[](std::size_t bit) { return Ref(this, (IntTy)1 << bit); } - const Ref operator[](size_t bit) const { + const Ref operator[](std::size_t bit) const { return (*const_cast<BitSet*>(this))[bit]; } bool operator==(BitSet other) const { diff --git a/src/common/cityhash.cpp b/src/common/cityhash.cpp index de31ffbd8..4e1d874b5 100644 --- a/src/common/cityhash.cpp +++ b/src/common/cityhash.cpp @@ -114,7 +114,7 @@ static uint64 HashLen16(uint64 u, uint64 v, uint64 mul) { return b; } -static uint64 HashLen0to16(const char* s, size_t len) { +static uint64 HashLen0to16(const char* s, std::size_t len) { if (len >= 8) { uint64 mul = k2 + len * 2; uint64 a = Fetch64(s) + k2; @@ -141,7 +141,7 @@ static uint64 HashLen0to16(const char* s, size_t len) { // This probably works well for 16-byte strings as well, but it may be overkill // in that case. -static uint64 HashLen17to32(const char* s, size_t len) { +static uint64 HashLen17to32(const char* s, std::size_t len) { uint64 mul = k2 + len * 2; uint64 a = Fetch64(s) * k1; uint64 b = Fetch64(s + 8); @@ -170,7 +170,7 @@ static pair<uint64, uint64> WeakHashLen32WithSeeds(const char* s, uint64 a, uint } // Return an 8-byte hash for 33 to 64 bytes. -static uint64 HashLen33to64(const char* s, size_t len) { +static uint64 HashLen33to64(const char* s, std::size_t len) { uint64 mul = k2 + len * 2; uint64 a = Fetch64(s) * k2; uint64 b = Fetch64(s + 8); @@ -191,7 +191,7 @@ static uint64 HashLen33to64(const char* s, size_t len) { return b + x; } -uint64 CityHash64(const char* s, size_t len) { +uint64 CityHash64(const char* s, std::size_t len) { if (len <= 32) { if (len <= 16) { return HashLen0to16(s, len); @@ -212,7 +212,7 @@ uint64 CityHash64(const char* s, size_t len) { x = x * k1 + Fetch64(s); // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. - len = (len - 1) & ~static_cast<size_t>(63); + len = (len - 1) & ~static_cast<std::size_t>(63); do { x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; @@ -229,17 +229,17 @@ uint64 CityHash64(const char* s, size_t len) { HashLen16(v.second, w.second) + x); } -uint64 CityHash64WithSeed(const char* s, size_t len, uint64 seed) { +uint64 CityHash64WithSeed(const char* s, std::size_t len, uint64 seed) { return CityHash64WithSeeds(s, len, k2, seed); } -uint64 CityHash64WithSeeds(const char* s, size_t len, uint64 seed0, uint64 seed1) { +uint64 CityHash64WithSeeds(const char* s, std::size_t len, uint64 seed0, uint64 seed1) { return HashLen16(CityHash64(s, len) - seed0, seed1); } // A subroutine for CityHash128(). Returns a decent 128-bit hash for strings // of any length representable in signed long. Based on City and Murmur. -static uint128 CityMurmur(const char* s, size_t len, uint128 seed) { +static uint128 CityMurmur(const char* s, std::size_t len, uint128 seed) { uint64 a = Uint128Low64(seed); uint64 b = Uint128High64(seed); uint64 c = 0; @@ -269,7 +269,7 @@ static uint128 CityMurmur(const char* s, size_t len, uint128 seed) { return uint128(a ^ b, HashLen16(b, a)); } -uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed) { +uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed) { if (len < 128) { return CityMurmur(s, len, seed); } @@ -313,7 +313,7 @@ uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed) { w.first *= 9; v.first *= k0; // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. - for (size_t tail_done = 0; tail_done < len;) { + for (std::size_t tail_done = 0; tail_done < len;) { tail_done += 32; y = Rotate(x + y, 42) * k0 + v.second; w.first += Fetch64(s + len - tail_done + 16); @@ -331,7 +331,7 @@ uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed) { return uint128(HashLen16(x + v.second, w.second) + y, HashLen16(x + w.second, y + v.second)); } -uint128 CityHash128(const char* s, size_t len) { +uint128 CityHash128(const char* s, std::size_t len) { return len >= 16 ? CityHash128WithSeed(s + 16, len - 16, uint128(Fetch64(s), Fetch64(s + 8) + k0)) : CityHash128WithSeed(s, len, uint128(k0, k1)); diff --git a/src/common/cityhash.h b/src/common/cityhash.h index bcebdb150..4b94f8e18 100644 --- a/src/common/cityhash.h +++ b/src/common/cityhash.h @@ -63,7 +63,7 @@ #include <utility> #include <stdint.h> -#include <stdlib.h> // for size_t. +#include <stdlib.h> // for std::size_t. namespace Common { @@ -77,22 +77,22 @@ inline uint64_t Uint128High64(const uint128& x) { } // Hash function for a byte array. -uint64_t CityHash64(const char* buf, size_t len); +uint64_t CityHash64(const char* buf, std::size_t len); // Hash function for a byte array. For convenience, a 64-bit seed is also // hashed into the result. -uint64_t CityHash64WithSeed(const char* buf, size_t len, uint64_t seed); +uint64_t CityHash64WithSeed(const char* buf, std::size_t len, uint64_t seed); // Hash function for a byte array. For convenience, two seeds are also // hashed into the result. -uint64_t CityHash64WithSeeds(const char* buf, size_t len, uint64_t seed0, uint64_t seed1); +uint64_t CityHash64WithSeeds(const char* buf, std::size_t len, uint64_t seed0, uint64_t seed1); // Hash function for a byte array. -uint128 CityHash128(const char* s, size_t len); +uint128 CityHash128(const char* s, std::size_t len); // Hash function for a byte array. For convenience, a 128-bit seed is also // hashed into the result. -uint128 CityHash128WithSeed(const char* s, size_t len, uint128 seed); +uint128 CityHash128WithSeed(const char* s, std::size_t len, uint128 seed); // Hash 128 input bits down to 64 bits of output. // This is intended to be a reasonably good hash function. diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index baa721481..21a0b9738 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -76,7 +76,7 @@ namespace FileUtil { // Modifies argument. static void StripTailDirSlashes(std::string& fname) { if (fname.length() > 1) { - size_t i = fname.length(); + std::size_t i = fname.length(); while (i > 0 && fname[i - 1] == DIR_SEP_CHR) --i; fname.resize(i); @@ -201,7 +201,7 @@ bool CreateFullPath(const std::string& fullPath) { return true; } - size_t position = 0; + std::size_t position = 0; while (true) { // Find next sub path position = fullPath.find(DIR_SEP_CHR, position); @@ -299,7 +299,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { std::array<char, 1024> buffer; while (!feof(input.get())) { // read input - size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get()); + std::size_t rnum = fread(buffer.data(), sizeof(char), buffer.size(), input.get()); if (rnum != buffer.size()) { if (ferror(input.get()) != 0) { LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}", @@ -309,7 +309,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { } // write output - size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get()); + std::size_t wnum = fwrite(buffer.data(), sizeof(char), rnum, output.get()); if (wnum != rnum) { LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename, destFilename, GetLastErrorMsg()); @@ -756,11 +756,11 @@ std::string GetNANDRegistrationDir(bool system) { return GetUserPath(UserPath::NANDDir) + "user/Contents/registered/"; } -size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { +std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename) { return FileUtil::IOFile(filename, text_file ? "w" : "wb").WriteBytes(str.data(), str.size()); } -size_t ReadFileToString(bool text_file, const char* filename, std::string& str) { +std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str) { IOFile file(filename, text_file ? "r" : "rb"); if (!file.IsOpen()) @@ -829,7 +829,7 @@ std::vector<std::string> SplitPathComponents(std::string_view filename) { std::string_view GetParentPath(std::string_view path) { const auto name_bck_index = path.rfind('\\'); const auto name_fwd_index = path.rfind('/'); - size_t name_index; + std::size_t name_index; if (name_bck_index == std::string_view::npos || name_fwd_index == std::string_view::npos) { name_index = std::min(name_bck_index, name_fwd_index); @@ -868,7 +868,7 @@ std::string_view GetFilename(std::string_view path) { } std::string_view GetExtensionFromFilename(std::string_view name) { - const size_t index = name.rfind('.'); + const std::size_t index = name.rfind('.'); if (index == std::string_view::npos) { return {}; diff --git a/src/common/file_util.h b/src/common/file_util.h index 2f13d0b6b..24c1e413c 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -143,8 +143,9 @@ const std::string& GetExeDirectory(); std::string AppDataRoamingDirectory(); #endif -size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename); -size_t ReadFileToString(bool text_file, const char* filename, std::string& str); +std::size_t WriteStringToFile(bool text_file, const std::string& str, const char* filename); + +std::size_t ReadFileToString(bool text_file, const char* filename, std::string& str); /** * Splits the filename into 8.3 format @@ -177,10 +178,10 @@ std::string_view RemoveTrailingSlash(std::string_view path); // Creates a new vector containing indices [first, last) from the original. template <typename T> -std::vector<T> SliceVector(const std::vector<T>& vector, size_t first, size_t last) { +std::vector<T> SliceVector(const std::vector<T>& vector, std::size_t first, std::size_t last) { if (first >= last) return {}; - last = std::min<size_t>(last, vector.size()); + last = std::min<std::size_t>(last, vector.size()); return std::vector<T>(vector.begin() + first, vector.begin() + first + last); } @@ -213,47 +214,47 @@ public: bool Close(); template <typename T> - size_t ReadArray(T* data, size_t length) const { + std::size_t ReadArray(T* data, std::size_t length) const { static_assert(std::is_trivially_copyable_v<T>, "Given array does not consist of trivially copyable objects"); if (!IsOpen()) { - return std::numeric_limits<size_t>::max(); + return std::numeric_limits<std::size_t>::max(); } return std::fread(data, sizeof(T), length, m_file); } template <typename T> - size_t WriteArray(const T* data, size_t length) { + std::size_t WriteArray(const T* data, std::size_t length) { static_assert(std::is_trivially_copyable_v<T>, "Given array does not consist of trivially copyable objects"); if (!IsOpen()) { - return std::numeric_limits<size_t>::max(); + return std::numeric_limits<std::size_t>::max(); } return std::fwrite(data, sizeof(T), length, m_file); } template <typename T> - size_t ReadBytes(T* data, size_t length) const { + std::size_t ReadBytes(T* data, std::size_t length) const { static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); return ReadArray(reinterpret_cast<char*>(data), length); } template <typename T> - size_t WriteBytes(const T* data, size_t length) { + std::size_t WriteBytes(const T* data, std::size_t length) { static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); return WriteArray(reinterpret_cast<const char*>(data), length); } template <typename T> - size_t WriteObject(const T& object) { + std::size_t WriteObject(const T& object) { static_assert(!std::is_pointer_v<T>, "WriteObject arguments must not be a pointer"); return WriteArray(&object, 1); } - size_t WriteString(const std::string& str) { + std::size_t WriteString(const std::string& str) { return WriteArray(str.c_str(), str.length()); } diff --git a/src/common/hash.h b/src/common/hash.h index 2c761e545..40194d1ee 100644 --- a/src/common/hash.h +++ b/src/common/hash.h @@ -17,7 +17,7 @@ namespace Common { * @param len Length of data (in bytes) to compute hash over * @returns 64-bit hash value that was computed over the data block */ -static inline u64 ComputeHash64(const void* data, size_t len) { +static inline u64 ComputeHash64(const void* data, std::size_t len) { return CityHash64(static_cast<const char*>(data), len); } @@ -63,7 +63,7 @@ struct HashableStruct { return !(*this == o); }; - size_t Hash() const { + std::size_t Hash() const { return Common::ComputeStructHash64(state); } }; diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp index 8e0a9e46f..589ae5cbf 100644 --- a/src/common/hex_util.cpp +++ b/src/common/hex_util.cpp @@ -18,7 +18,7 @@ u8 ToHexNibble(char c1) { return 0; } -std::array<u8, 16> operator""_array16(const char* str, size_t len) { +std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { if (len != 32) { LOG_ERROR(Common, "Attempting to parse string to array that is not of correct size (expected=32, " @@ -29,7 +29,7 @@ std::array<u8, 16> operator""_array16(const char* str, size_t len) { return HexStringToArray<16>(str); } -std::array<u8, 32> operator""_array32(const char* str, size_t len) { +std::array<u8, 32> operator""_array32(const char* str, std::size_t len) { if (len != 64) { LOG_ERROR(Common, "Attempting to parse string to array that is not of correct size (expected=64, " diff --git a/src/common/hex_util.h b/src/common/hex_util.h index 5fb79bb72..863a5ccd9 100644 --- a/src/common/hex_util.h +++ b/src/common/hex_util.h @@ -14,20 +14,20 @@ namespace Common { u8 ToHexNibble(char c1); -template <size_t Size, bool le = false> +template <std::size_t Size, bool le = false> std::array<u8, Size> HexStringToArray(std::string_view str) { std::array<u8, Size> out{}; if constexpr (le) { - for (size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) + for (std::size_t i = 2 * Size - 2; i <= 2 * Size; i -= 2) out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); } else { - for (size_t i = 0; i < 2 * Size; i += 2) + for (std::size_t i = 0; i < 2 * Size; i += 2) out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]); } return out; } -template <size_t Size> +template <std::size_t Size> std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { std::string out; for (u8 c : array) @@ -35,7 +35,7 @@ std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { return out; } -std::array<u8, 0x10> operator"" _array16(const char* str, size_t len); -std::array<u8, 0x20> operator"" _array32(const char* str, size_t len); +std::array<u8, 0x10> operator"" _array16(const char* str, std::size_t len); +std::array<u8, 0x20> operator"" _array32(const char* str, std::size_t len); } // namespace Common diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 1323f8d0f..efd776db6 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -135,7 +135,7 @@ FileBackend::FileBackend(const std::string& filename) void FileBackend::Write(const Entry& entry) { // prevent logs from going over the maximum size (in case its spamming and the user doesn't // know) - constexpr size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; + constexpr std::size_t MAX_BYTES_WRITTEN = 50 * 1024L * 1024L; if (!file.IsOpen() || bytes_written > MAX_BYTES_WRITTEN) { return; } diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index b3f4b9cef..11edbf1b6 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h @@ -100,7 +100,7 @@ public: private: FileUtil::IOFile file; - size_t bytes_written; + std::size_t bytes_written; }; void AddBackend(std::unique_ptr<Backend> backend); diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 2dd331152..2eccbcd8d 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -71,7 +71,7 @@ void Filter::ResetAll(Level level) { } void Filter::SetClassLevel(Class log_class, Level level) { - class_levels[static_cast<size_t>(log_class)] = level; + class_levels[static_cast<std::size_t>(log_class)] = level; } void Filter::ParseFilterString(std::string_view filter_view) { @@ -93,7 +93,8 @@ void Filter::ParseFilterString(std::string_view filter_view) { } bool Filter::CheckMessage(Class log_class, Level level) const { - return static_cast<u8>(level) >= static_cast<u8>(class_levels[static_cast<size_t>(log_class)]); + return static_cast<u8>(level) >= + static_cast<u8>(class_levels[static_cast<std::size_t>(log_class)]); } bool Filter::IsDebug() const { diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index d5ffc5a58..773df6f2c 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h @@ -19,7 +19,7 @@ namespace Log { class Filter { public: /// Initializes the filter with all classes having `default_level` as the minimum level. - Filter(Level default_level = Level::Info); + explicit Filter(Level default_level = Level::Info); /// Resets the filter so that all classes have `level` as the minimum displayed level. void ResetAll(Level level); @@ -49,6 +49,6 @@ public: bool IsDebug() const; private: - std::array<Level, (size_t)Class::Count> class_levels; + std::array<Level, static_cast<std::size_t>(Class::Count)> class_levels; }; } // namespace Log diff --git a/src/common/logging/log.h b/src/common/logging/log.h index e12f47f8f..4d577524f 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -12,14 +12,14 @@ namespace Log { /// Specifies the severity or level of detail of the log message. enum class Level : u8 { Trace, ///< Extremely detailed and repetitive debugging information that is likely to - /// pollute logs. + ///< pollute logs. Debug, ///< Less detailed debugging information. Info, ///< Status information from important points during execution. Warning, ///< Minor or potential problems found during execution of a task. Error, ///< Major problems found during execution of a task that prevent it from being - /// completed. - Critical, ///< Major problems during execution that threathen the stability of the entire - /// application. + ///< completed. + Critical, ///< Major problems during execution that threaten the stability of the entire + ///< application. Count ///< Total number of logging levels }; @@ -49,7 +49,7 @@ enum class Class : ClassType { Kernel, ///< The HLE implementation of the CTR kernel Kernel_SVC, ///< Kernel system calls Service, ///< HLE implementation of system services. Each major service - /// should have its own subclass. + ///< should have its own subclass. Service_ACC, ///< The ACC (Accounts) service Service_AM, ///< The AM (Applet manager) service Service_AOC, ///< The AOC (AddOn Content) service diff --git a/src/common/logging/text_formatter.h b/src/common/logging/text_formatter.h index 9609cec7c..b6d9e57c8 100644 --- a/src/common/logging/text_formatter.h +++ b/src/common/logging/text_formatter.h @@ -15,6 +15,6 @@ struct Entry; std::string FormatLogMessage(const Entry& entry); /// Formats and prints a log entry to stderr. void PrintMessage(const Entry& entry); -/// Prints the same message as `PrintMessage`, but colored acoording to the severity level. +/// Prints the same message as `PrintMessage`, but colored according to the severity level. void PrintColoredMessage(const Entry& entry); } // namespace Log diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp index 09462ccee..9736fb12a 100644 --- a/src/common/memory_util.cpp +++ b/src/common/memory_util.cpp @@ -25,7 +25,7 @@ // This is purposely not a full wrapper for virtualalloc/mmap, but it // provides exactly the primitive operations that Dolphin needs. -void* AllocateExecutableMemory(size_t size, bool low) { +void* AllocateExecutableMemory(std::size_t size, bool low) { #if defined(_WIN32) void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); #else @@ -74,7 +74,7 @@ void* AllocateExecutableMemory(size_t size, bool low) { return ptr; } -void* AllocateMemoryPages(size_t size) { +void* AllocateMemoryPages(std::size_t size) { #ifdef _WIN32 void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE); #else @@ -90,7 +90,7 @@ void* AllocateMemoryPages(size_t size) { return ptr; } -void* AllocateAlignedMemory(size_t size, size_t alignment) { +void* AllocateAlignedMemory(std::size_t size, std::size_t alignment) { #ifdef _WIN32 void* ptr = _aligned_malloc(size, alignment); #else @@ -109,7 +109,7 @@ void* AllocateAlignedMemory(size_t size, size_t alignment) { return ptr; } -void FreeMemoryPages(void* ptr, size_t size) { +void FreeMemoryPages(void* ptr, std::size_t size) { if (ptr) { #ifdef _WIN32 if (!VirtualFree(ptr, 0, MEM_RELEASE)) @@ -130,7 +130,7 @@ void FreeAlignedMemory(void* ptr) { } } -void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) { +void WriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) { #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue)) @@ -140,7 +140,7 @@ void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) { #endif } -void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) { +void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) { #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, diff --git a/src/common/memory_util.h b/src/common/memory_util.h index 76ca5a30c..aad071979 100644 --- a/src/common/memory_util.h +++ b/src/common/memory_util.h @@ -7,13 +7,13 @@ #include <cstddef> #include <string> -void* AllocateExecutableMemory(size_t size, bool low = true); -void* AllocateMemoryPages(size_t size); -void FreeMemoryPages(void* ptr, size_t size); -void* AllocateAlignedMemory(size_t size, size_t alignment); +void* AllocateExecutableMemory(std::size_t size, bool low = true); +void* AllocateMemoryPages(std::size_t size); +void FreeMemoryPages(void* ptr, std::size_t size); +void* AllocateAlignedMemory(std::size_t size, std::size_t alignment); void FreeAlignedMemory(void* ptr); -void WriteProtectMemory(void* ptr, size_t size, bool executable = false); -void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute = false); +void WriteProtectMemory(void* ptr, std::size_t size, bool executable = false); +void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute = false); std::string MemUsage(); inline int GetPageSize() { diff --git a/src/common/misc.cpp b/src/common/misc.cpp index 3fa8a3bc4..68cb86cd1 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -16,7 +16,7 @@ // Call directly after the command or use the error num. // This function might change the error code. std::string GetLastErrorMsg() { - static const size_t buff_size = 255; + static const std::size_t buff_size = 255; char err_str[buff_size]; #ifdef _WIN32 diff --git a/src/common/ring_buffer.h b/src/common/ring_buffer.h new file mode 100644 index 000000000..abe3b4dc2 --- /dev/null +++ b/src/common/ring_buffer.h @@ -0,0 +1,119 @@ +// 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 <new> +#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, std::size_t capacity, std::size_t granularity = 1> +class RingBuffer { + /// A "slot" is made of `granularity` elements of `T`. + static constexpr std::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<std::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 + std::size_t Push(const void* new_slots, std::size_t slot_count) { + const std::size_t write_index = m_write_index.load(); + const std::size_t slots_free = capacity + m_read_index.load() - write_index; + const std::size_t push_count = std::min(slot_count, slots_free); + + const std::size_t pos = write_index % capacity; + const std::size_t first_copy = std::min(capacity - pos, push_count); + const std::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; + } + + std::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 + std::size_t Pop(void* output, std::size_t max_slots = ~std::size_t(0)) { + const std::size_t read_index = m_read_index.load(); + const std::size_t slots_filled = m_write_index.load() - read_index; + const std::size_t pop_count = std::min(slots_filled, max_slots); + + const std::size_t pos = read_index % capacity; + const std::size_t first_copy = std::min(capacity - pos, pop_count); + const std::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(std::size_t max_slots = ~std::size_t(0)) { + std::vector<T> out(std::min(max_slots, capacity) * granularity); + const std::size_t count = Pop(out.data(), out.size() / granularity); + out.resize(count * granularity); + return out; + } + + /// @returns Number of slots used + std::size_t Size() const { + return m_write_index.load() - m_read_index.load(); + } + + /// @returns Maximum size of ring buffer + constexpr std::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. + // TODO: Remove this ifdef whenever clang and GCC support + // std::hardware_destructive_interference_size. +#if defined(_MSC_VER) && _MSC_VER >= 1911 + alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_read_index{0}; + alignas(std::hardware_destructive_interference_size) std::atomic_size_t m_write_index{0}; +#else + alignas(128) std::atomic_size_t m_read_index{0}; + alignas(128) std::atomic_size_t m_write_index{0}; +#endif + + std::array<T, granularity * capacity> m_data; +}; + +} // namespace Common diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in index 4083095d5..2b1727769 100644 --- a/src/common/scm_rev.cpp.in +++ b/src/common/scm_rev.cpp.in @@ -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 diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index db0f4a947..af9a9daed 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -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 diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 0ca663032..c9a5425a7 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -37,7 +37,7 @@ std::string ToUpper(std::string str) { } // For Debugging. Read out an u8 array. -std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces) { +std::string ArrayToString(const u8* data, std::size_t size, int line_len, bool spaces) { std::ostringstream oss; oss << std::setfill('0') << std::hex; @@ -60,7 +60,7 @@ std::string StringFromBuffer(const std::vector<u8>& data) { // Turns " hej " into "hej". Also handles tabs. std::string StripSpaces(const std::string& str) { - const size_t s = str.find_first_not_of(" \t\r\n"); + const std::size_t s = str.find_first_not_of(" \t\r\n"); if (str.npos != s) return str.substr(s, str.find_last_not_of(" \t\r\n") - s + 1); @@ -121,10 +121,10 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ if (full_path.empty()) return false; - size_t dir_end = full_path.find_last_of("/" + std::size_t dir_end = full_path.find_last_of("/" // windows needs the : included for something like just "C:" to be considered a directory #ifdef _WIN32 - "\\:" + "\\:" #endif ); if (std::string::npos == dir_end) @@ -132,7 +132,7 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ else dir_end += 1; - size_t fname_end = full_path.rfind('.'); + std::size_t fname_end = full_path.rfind('.'); if (fname_end < dir_end || std::string::npos == fname_end) fname_end = full_path.size(); @@ -172,7 +172,7 @@ void SplitString(const std::string& str, const char delim, std::vector<std::stri } std::string TabsToSpaces(int tab_size, std::string in) { - size_t i = 0; + std::size_t i = 0; while ((i = in.find('\t')) != std::string::npos) { in.replace(i, 1, tab_size, ' '); @@ -182,7 +182,7 @@ std::string TabsToSpaces(int tab_size, std::string in) { } std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) { - size_t pos = 0; + std::size_t pos = 0; if (src == dest) return result; @@ -280,22 +280,22 @@ static std::string CodeToUTF8(const char* fromcode, const std::basic_string<T>& return {}; } - const size_t in_bytes = sizeof(T) * input.size(); + const std::size_t in_bytes = sizeof(T) * input.size(); // Multiply by 4, which is the max number of bytes to encode a codepoint - const size_t out_buffer_size = 4 * in_bytes; + const std::size_t out_buffer_size = 4 * in_bytes; std::string out_buffer(out_buffer_size, '\0'); auto src_buffer = &input[0]; - size_t src_bytes = in_bytes; + std::size_t src_bytes = in_bytes; auto dst_buffer = &out_buffer[0]; - size_t dst_bytes = out_buffer.size(); + std::size_t dst_bytes = out_buffer.size(); while (0 != src_bytes) { - size_t const iconv_result = + std::size_t const iconv_result = iconv(conv_desc, (char**)(&src_buffer), &src_bytes, &dst_buffer, &dst_bytes); - if (static_cast<size_t>(-1) == iconv_result) { + if (static_cast<std::size_t>(-1) == iconv_result) { if (EILSEQ == errno || EINVAL == errno) { // Try to skip the bad character if (0 != src_bytes) { @@ -326,22 +326,22 @@ std::u16string UTF8ToUTF16(const std::string& input) { return {}; } - const size_t in_bytes = sizeof(char) * input.size(); + const std::size_t in_bytes = sizeof(char) * input.size(); // Multiply by 4, which is the max number of bytes to encode a codepoint - const size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes; + const std::size_t out_buffer_size = 4 * sizeof(char16_t) * in_bytes; std::u16string out_buffer(out_buffer_size, char16_t{}); char* src_buffer = const_cast<char*>(&input[0]); - size_t src_bytes = in_bytes; + std::size_t src_bytes = in_bytes; char* dst_buffer = (char*)(&out_buffer[0]); - size_t dst_bytes = out_buffer.size(); + std::size_t dst_bytes = out_buffer.size(); while (0 != src_bytes) { - size_t const iconv_result = + std::size_t const iconv_result = iconv(conv_desc, &src_buffer, &src_bytes, &dst_buffer, &dst_bytes); - if (static_cast<size_t>(-1) == iconv_result) { + if (static_cast<std::size_t>(-1) == iconv_result) { if (EILSEQ == errno || EINVAL == errno) { // Try to skip the bad character if (0 != src_bytes) { @@ -381,8 +381,8 @@ std::string SHIFTJISToUTF8(const std::string& input) { #endif -std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len) { - size_t len = 0; +std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len) { + std::size_t len = 0; while (len < max_len && buffer[len] != '\0') ++len; diff --git a/src/common/string_util.h b/src/common/string_util.h index 4a2143b59..dcca6bc38 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h @@ -19,7 +19,7 @@ std::string ToLower(std::string str); /// Make a string uppercase std::string ToUpper(std::string str); -std::string ArrayToString(const u8* data, size_t size, int line_len = 20, bool spaces = true); +std::string ArrayToString(const u8* data, std::size_t size, int line_len = 20, bool spaces = true); std::string StringFromBuffer(const std::vector<u8>& data); @@ -118,7 +118,7 @@ bool ComparePartialString(InIt begin, InIt end, const char* other) { * Creates a std::string from a fixed-size NUL-terminated char buffer. If the buffer isn't * NUL-terminated then the string ends at max_len characters. */ -std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, size_t max_len); +std::string StringFromFixedZeroTerminatedBuffer(const char* buffer, std::size_t max_len); /** * Attempts to trim an arbitrary prefix from `path`, leaving only the part starting at `root`. It's diff --git a/src/common/thread.h b/src/common/thread.h index 9465e1de7..12a1c095c 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -60,12 +60,12 @@ private: class Barrier { public: - explicit Barrier(size_t count_) : count(count_), waiting(0), generation(0) {} + explicit Barrier(std::size_t count_) : count(count_), waiting(0), generation(0) {} /// Blocks until all "count" threads have called Sync() void Sync() { std::unique_lock<std::mutex> lk(mutex); - const size_t current_generation = generation; + const std::size_t current_generation = generation; if (++waiting == count) { generation++; @@ -80,9 +80,9 @@ public: private: std::condition_variable condvar; std::mutex mutex; - const size_t count; - size_t waiting; - size_t generation; // Incremented once each time the barrier is used + const std::size_t count; + std::size_t waiting; + std::size_t generation; // Incremented once each time the barrier is used }; void SleepCurrentThread(int ms); diff --git a/src/common/x64/xbyak_abi.h b/src/common/x64/xbyak_abi.h index 927da9187..636a5c0f9 100644 --- a/src/common/x64/xbyak_abi.h +++ b/src/common/x64/xbyak_abi.h @@ -97,7 +97,7 @@ const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ Xbyak::util::xmm15, }); -constexpr size_t ABI_SHADOW_SPACE = 0x20; +constexpr std::size_t ABI_SHADOW_SPACE = 0x20; #else @@ -147,22 +147,23 @@ const BitSet32 ABI_ALL_CALLEE_SAVED = BuildRegSet({ Xbyak::util::r15, }); -constexpr size_t ABI_SHADOW_SPACE = 0; +constexpr std::size_t ABI_SHADOW_SPACE = 0; #endif -inline void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t needed_frame_size, - s32* out_subtraction, s32* out_xmm_offset) { +inline void ABI_CalculateFrameSize(BitSet32 regs, std::size_t rsp_alignment, + std::size_t needed_frame_size, s32* out_subtraction, + s32* out_xmm_offset) { int count = (regs & ABI_ALL_GPRS).Count(); rsp_alignment -= count * 8; - size_t subtraction = 0; + std::size_t subtraction = 0; int xmm_count = (regs & ABI_ALL_XMMS).Count(); if (xmm_count) { // If we have any XMMs to save, we must align the stack here. subtraction = rsp_alignment & 0xF; } subtraction += 0x10 * xmm_count; - size_t xmm_base_subtraction = subtraction; + std::size_t xmm_base_subtraction = subtraction; subtraction += needed_frame_size; subtraction += ABI_SHADOW_SPACE; // Final alignment. @@ -173,8 +174,9 @@ inline void ABI_CalculateFrameSize(BitSet32 regs, size_t rsp_alignment, size_t n *out_xmm_offset = (s32)(subtraction - xmm_base_subtraction); } -inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, - size_t rsp_alignment, size_t needed_frame_size = 0) { +inline std::size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, + std::size_t rsp_alignment, + std::size_t needed_frame_size = 0) { s32 subtraction, xmm_offset; ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); @@ -195,7 +197,8 @@ inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet } inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, BitSet32 regs, - size_t rsp_alignment, size_t needed_frame_size = 0) { + std::size_t rsp_alignment, + std::size_t needed_frame_size = 0) { s32 subtraction, xmm_offset; ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size, &subtraction, &xmm_offset); diff --git a/src/common/x64/xbyak_util.h b/src/common/x64/xbyak_util.h index 02323a017..5cc8a8c76 100644 --- a/src/common/x64/xbyak_util.h +++ b/src/common/x64/xbyak_util.h @@ -34,7 +34,7 @@ inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) { template <typename T> inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) { static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer."); - size_t addr = reinterpret_cast<size_t>(f); + std::size_t addr = reinterpret_cast<std::size_t>(f); if (IsWithin2G(code, addr)) { code.call(f); } else { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a74270a0f..26f727d96 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 @@ -49,6 +53,8 @@ add_library(core STATIC file_sys/savedata_factory.h file_sys/sdmc_factory.cpp file_sys/sdmc_factory.h + file_sys/submission_package.cpp + file_sys/submission_package.h file_sys/vfs.cpp file_sys/vfs.h file_sys/vfs_concat.cpp @@ -359,6 +365,8 @@ add_library(core STATIC loader/nro.h loader/nso.cpp loader/nso.h + loader/nsp.cpp + loader/nsp.h loader/xci.cpp loader/xci.h memory.cpp @@ -380,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 diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index c368745b1..16d528994 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h @@ -6,11 +6,14 @@ #include <array> #include "common/common_types.h" -#include "core/hle/kernel/vm_manager.h" + +namespace Kernel { +enum class VMAPermission : u8; +} namespace Core { -/// Generic ARM11 CPU interface +/// Generic ARMv8 CPU interface class ARM_Interface : NonCopyable { public: virtual ~ARM_Interface() {} @@ -19,9 +22,9 @@ public: std::array<u64, 31> cpu_registers; u64 sp; u64 pc; - u64 cpsr; - std::array<u128, 32> fpu_registers; - u64 fpscr; + u64 pstate; + std::array<u128, 32> vector_registers; + u64 fpcr; }; /// Runs the CPU until an event happens @@ -31,11 +34,11 @@ public: virtual void Step() = 0; /// Maps a backing memory region for the CPU - virtual void MapBackingMemory(VAddr address, size_t size, u8* memory, + virtual void MapBackingMemory(VAddr address, std::size_t size, u8* memory, Kernel::VMAPermission perms) = 0; /// Unmaps a region of memory that was previously mapped using MapBackingMemory - virtual void UnmapMemory(VAddr address, size_t size) = 0; + virtual void UnmapMemory(VAddr address, std::size_t size) = 0; /// Clear all instruction cache virtual void ClearInstructionCache() = 0; @@ -69,42 +72,50 @@ public: */ virtual void SetReg(int index, u64 value) = 0; - virtual u128 GetExtReg(int index) const = 0; - - virtual void SetExtReg(int index, u128 value) = 0; - /** - * Gets the value of a VFP register - * @param index Register index (0-31) - * @return Returns the value in the register + * Gets the value of a specified vector register. + * + * @param index The index of the vector register. + * @return the value within the vector register. */ - virtual u32 GetVFPReg(int index) const = 0; + virtual u128 GetVectorReg(int index) const = 0; /** - * Sets a VFP register to the given value - * @param index Register index (0-31) - * @param value Value to set register to + * Sets a given value into a vector register. + * + * @param index The index of the vector register. + * @param value The new value to place in the register. */ - virtual void SetVFPReg(int index, u32 value) = 0; + virtual void SetVectorReg(int index, u128 value) = 0; /** - * Get the current CPSR register - * @return Returns the value of the CPSR register + * Get the current PSTATE register + * @return Returns the value of the PSTATE register */ - virtual u32 GetCPSR() const = 0; + virtual u32 GetPSTATE() const = 0; /** - * Set the current CPSR register - * @param cpsr Value to set CPSR to + * Set the current PSTATE register + * @param pstate Value to set PSTATE to */ - virtual void SetCPSR(u32 cpsr) = 0; + virtual void SetPSTATE(u32 pstate) = 0; virtual VAddr GetTlsAddress() const = 0; virtual void SetTlsAddress(VAddr address) = 0; + /** + * Gets the value within the TPIDR_EL0 (read/write software thread ID) register. + * + * @return the value within the register. + */ virtual u64 GetTPIDR_EL0() const = 0; + /** + * Sets a new value within the TPIDR_EL0 (read/write software thread ID) register. + * + * @param value The new value to place in the register. + */ virtual void SetTPIDR_EL0(u64 value) = 0; /** @@ -119,6 +130,7 @@ public: */ virtual void LoadContext(const ThreadContext& ctx) = 0; + /// Clears the exclusive monitor's state. virtual void ClearExclusiveState() = 0; /// Prepare core for thread reschedule (if needed to correctly handle state) diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index de44ccebd..7be5a38de 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp @@ -7,12 +7,15 @@ #include <dynarmic/A64/a64.h> #include <dynarmic/A64/config.h> #include "common/logging/log.h" +#include "common/microprofile.h" #include "core/arm/dynarmic/arm_dynarmic.h" #include "core/core.h" #include "core/core_cpu.h" #include "core/core_timing.h" +#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/svc.h" +#include "core/hle/kernel/vm_manager.h" #include "core/memory.h" namespace Core { @@ -57,7 +60,7 @@ public: Memory::Write64(vaddr + 8, value[1]); } - void InterpreterFallback(u64 pc, size_t num_instructions) override { + void InterpreterFallback(u64 pc, std::size_t num_instructions) override { LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc, num_instructions, MemoryReadCode(pc)); @@ -78,9 +81,20 @@ public: case Dynarmic::A64::Exception::SendEventLocal: case Dynarmic::A64::Exception::Yield: return; + case Dynarmic::A64::Exception::Breakpoint: + if (GDBStub::IsServerEnabled()) { + parent.jit->HaltExecution(); + parent.SetPC(pc); + Kernel::Thread* thread = Kernel::GetCurrentThread(); + parent.SaveContext(thread->context); + GDBStub::Break(); + GDBStub::SendTrap(thread, 5); + return; + } + [[fallthrough]]; default: ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})", - static_cast<size_t>(exception), pc); + static_cast<std::size_t>(exception), pc); } } @@ -109,7 +123,7 @@ public: } ARM_Dynarmic& parent; - size_t num_interpreted_instructions = 0; + std::size_t num_interpreted_instructions = 0; u64 tpidrro_el0 = 0; u64 tpidr_el0 = 0; }; @@ -143,7 +157,10 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const { return std::make_unique<Dynarmic::A64::Jit>(config); } +MICROPROFILE_DEFINE(ARM_Jit_Dynarmic, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64)); + void ARM_Dynarmic::Run() { + MICROPROFILE_SCOPE(ARM_Jit_Dynarmic); ASSERT(Memory::GetCurrentPageTable() == current_page_table); jit->Run(); @@ -153,7 +170,8 @@ void ARM_Dynarmic::Step() { cb->InterpreterFallback(jit->GetPC(), 1); } -ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, size_t core_index) +ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, + std::size_t core_index) : cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index}, exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} { ThreadContext ctx; @@ -164,12 +182,12 @@ ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, ARM_Dynarmic::~ARM_Dynarmic() = default; -void ARM_Dynarmic::MapBackingMemory(u64 address, size_t size, u8* memory, +void ARM_Dynarmic::MapBackingMemory(u64 address, std::size_t size, u8* memory, Kernel::VMAPermission perms) { inner_unicorn.MapBackingMemory(address, size, memory, perms); } -void ARM_Dynarmic::UnmapMemory(u64 address, size_t size) { +void ARM_Dynarmic::UnmapMemory(u64 address, std::size_t size) { inner_unicorn.UnmapMemory(address, size); } @@ -189,29 +207,20 @@ void ARM_Dynarmic::SetReg(int index, u64 value) { jit->SetRegister(index, value); } -u128 ARM_Dynarmic::GetExtReg(int index) const { +u128 ARM_Dynarmic::GetVectorReg(int index) const { return jit->GetVector(index); } -void ARM_Dynarmic::SetExtReg(int index, u128 value) { +void ARM_Dynarmic::SetVectorReg(int index, u128 value) { jit->SetVector(index, value); } -u32 ARM_Dynarmic::GetVFPReg(int /*index*/) const { - UNIMPLEMENTED(); - return {}; -} - -void ARM_Dynarmic::SetVFPReg(int /*index*/, u32 /*value*/) { - UNIMPLEMENTED(); -} - -u32 ARM_Dynarmic::GetCPSR() const { +u32 ARM_Dynarmic::GetPSTATE() const { return jit->GetPstate(); } -void ARM_Dynarmic::SetCPSR(u32 cpsr) { - jit->SetPstate(cpsr); +void ARM_Dynarmic::SetPSTATE(u32 pstate) { + jit->SetPstate(pstate); } u64 ARM_Dynarmic::GetTlsAddress() const { @@ -234,18 +243,18 @@ void ARM_Dynarmic::SaveContext(ThreadContext& ctx) { ctx.cpu_registers = jit->GetRegisters(); ctx.sp = jit->GetSP(); ctx.pc = jit->GetPC(); - ctx.cpsr = jit->GetPstate(); - ctx.fpu_registers = jit->GetVectors(); - ctx.fpscr = jit->GetFpcr(); + ctx.pstate = jit->GetPstate(); + ctx.vector_registers = jit->GetVectors(); + ctx.fpcr = jit->GetFpcr(); } void ARM_Dynarmic::LoadContext(const ThreadContext& ctx) { jit->SetRegisters(ctx.cpu_registers); jit->SetSP(ctx.sp); jit->SetPC(ctx.pc); - jit->SetPstate(static_cast<u32>(ctx.cpsr)); - jit->SetVectors(ctx.fpu_registers); - jit->SetFpcr(static_cast<u32>(ctx.fpscr)); + jit->SetPstate(static_cast<u32>(ctx.pstate)); + jit->SetVectors(ctx.vector_registers); + jit->SetFpcr(static_cast<u32>(ctx.fpcr)); } void ARM_Dynarmic::PrepareReschedule() { @@ -265,10 +274,10 @@ void ARM_Dynarmic::PageTableChanged() { current_page_table = Memory::GetCurrentPageTable(); } -DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(size_t core_count) : monitor(core_count) {} +DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(std::size_t core_count) : monitor(core_count) {} DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default; -void DynarmicExclusiveMonitor::SetExclusive(size_t core_index, VAddr addr) { +void DynarmicExclusiveMonitor::SetExclusive(std::size_t core_index, VAddr addr) { // Size doesn't actually matter. monitor.Mark(core_index, addr, 16); } @@ -277,30 +286,30 @@ void DynarmicExclusiveMonitor::ClearExclusive() { monitor.Clear(); } -bool DynarmicExclusiveMonitor::ExclusiveWrite8(size_t core_index, VAddr vaddr, u8 value) { +bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) { return monitor.DoExclusiveOperation(core_index, vaddr, 1, [&] { Memory::Write8(vaddr, value); }); } -bool DynarmicExclusiveMonitor::ExclusiveWrite16(size_t core_index, VAddr vaddr, u16 value) { +bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) { return monitor.DoExclusiveOperation(core_index, vaddr, 2, [&] { Memory::Write16(vaddr, value); }); } -bool DynarmicExclusiveMonitor::ExclusiveWrite32(size_t core_index, VAddr vaddr, u32 value) { +bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) { return monitor.DoExclusiveOperation(core_index, vaddr, 4, [&] { Memory::Write32(vaddr, value); }); } -bool DynarmicExclusiveMonitor::ExclusiveWrite64(size_t core_index, VAddr vaddr, u64 value) { +bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) { return monitor.DoExclusiveOperation(core_index, vaddr, 8, [&] { Memory::Write64(vaddr, value); }); } -bool DynarmicExclusiveMonitor::ExclusiveWrite128(size_t core_index, VAddr vaddr, u128 value) { +bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) { return monitor.DoExclusiveOperation(core_index, vaddr, 16, [&] { - Memory::Write64(vaddr, value[0]); - Memory::Write64(vaddr, value[1]); + Memory::Write64(vaddr + 0, value[0]); + Memory::Write64(vaddr + 8, value[1]); }); } diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 3bdfd8cd9..4ee92ee27 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h @@ -12,6 +12,10 @@ #include "core/arm/exclusive_monitor.h" #include "core/arm/unicorn/arm_unicorn.h" +namespace Memory { +struct PageTable; +} + namespace Core { class ARM_Dynarmic_Callbacks; @@ -19,24 +23,22 @@ class DynarmicExclusiveMonitor; class ARM_Dynarmic final : public ARM_Interface { public: - ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, size_t core_index); + ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, std::size_t core_index); ~ARM_Dynarmic(); - void MapBackingMemory(VAddr address, size_t size, u8* memory, + void MapBackingMemory(VAddr address, std::size_t size, u8* memory, Kernel::VMAPermission perms) override; - void UnmapMemory(u64 address, size_t size) override; + void UnmapMemory(u64 address, std::size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; void SetReg(int index, u64 value) override; - u128 GetExtReg(int index) const override; - void SetExtReg(int index, u128 value) override; - u32 GetVFPReg(int index) const override; - void SetVFPReg(int index, u32 value) override; - u32 GetCPSR() const override; + u128 GetVectorReg(int index) const override; + void SetVectorReg(int index, u128 value) override; + u32 GetPSTATE() const override; + void SetPSTATE(u32 pstate) override; void Run() override; void Step() override; - void SetCPSR(u32 cpsr) override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; void SetTPIDR_EL0(u64 value) override; @@ -59,7 +61,7 @@ private: std::unique_ptr<Dynarmic::A64::Jit> jit; ARM_Unicorn inner_unicorn; - size_t core_index; + std::size_t core_index; std::shared_ptr<DynarmicExclusiveMonitor> exclusive_monitor; Memory::PageTable* current_page_table = nullptr; @@ -67,17 +69,17 @@ private: class DynarmicExclusiveMonitor final : public ExclusiveMonitor { public: - explicit DynarmicExclusiveMonitor(size_t core_count); + explicit DynarmicExclusiveMonitor(std::size_t core_count); ~DynarmicExclusiveMonitor(); - void SetExclusive(size_t core_index, VAddr addr) override; + void SetExclusive(std::size_t core_index, VAddr addr) override; void ClearExclusive() override; - bool ExclusiveWrite8(size_t core_index, VAddr vaddr, u8 value) override; - bool ExclusiveWrite16(size_t core_index, VAddr vaddr, u16 value) override; - bool ExclusiveWrite32(size_t core_index, VAddr vaddr, u32 value) override; - bool ExclusiveWrite64(size_t core_index, VAddr vaddr, u64 value) override; - bool ExclusiveWrite128(size_t core_index, VAddr vaddr, u128 value) override; + bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override; + bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override; + bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override; + bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override; + bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) override; private: friend class ARM_Dynarmic; diff --git a/src/core/arm/exclusive_monitor.h b/src/core/arm/exclusive_monitor.h index 6f9b51573..f59aca667 100644 --- a/src/core/arm/exclusive_monitor.h +++ b/src/core/arm/exclusive_monitor.h @@ -12,14 +12,14 @@ class ExclusiveMonitor { public: virtual ~ExclusiveMonitor(); - virtual void SetExclusive(size_t core_index, VAddr addr) = 0; + virtual void SetExclusive(std::size_t core_index, VAddr addr) = 0; virtual void ClearExclusive() = 0; - virtual bool ExclusiveWrite8(size_t core_index, VAddr vaddr, u8 value) = 0; - virtual bool ExclusiveWrite16(size_t core_index, VAddr vaddr, u16 value) = 0; - virtual bool ExclusiveWrite32(size_t core_index, VAddr vaddr, u32 value) = 0; - virtual bool ExclusiveWrite64(size_t core_index, VAddr vaddr, u64 value) = 0; - virtual bool ExclusiveWrite128(size_t core_index, VAddr vaddr, u128 value) = 0; + virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0; + virtual bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) = 0; + virtual bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) = 0; + virtual bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) = 0; + virtual bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) = 0; }; } // namespace Core diff --git a/src/core/arm/unicorn/arm_unicorn.cpp b/src/core/arm/unicorn/arm_unicorn.cpp index 307f12198..e218a0b15 100644 --- a/src/core/arm/unicorn/arm_unicorn.cpp +++ b/src/core/arm/unicorn/arm_unicorn.cpp @@ -90,12 +90,12 @@ ARM_Unicorn::~ARM_Unicorn() { CHECKED(uc_close(uc)); } -void ARM_Unicorn::MapBackingMemory(VAddr address, size_t size, u8* memory, +void ARM_Unicorn::MapBackingMemory(VAddr address, std::size_t size, u8* memory, Kernel::VMAPermission perms) { CHECKED(uc_mem_map_ptr(uc, address, size, static_cast<u32>(perms), memory)); } -void ARM_Unicorn::UnmapMemory(VAddr address, size_t size) { +void ARM_Unicorn::UnmapMemory(VAddr address, std::size_t size) { CHECKED(uc_mem_unmap(uc, address, size)); } @@ -131,33 +131,24 @@ void ARM_Unicorn::SetReg(int regn, u64 val) { CHECKED(uc_reg_write(uc, treg, &val)); } -u128 ARM_Unicorn::GetExtReg(int /*index*/) const { +u128 ARM_Unicorn::GetVectorReg(int /*index*/) const { UNIMPLEMENTED(); static constexpr u128 res{}; return res; } -void ARM_Unicorn::SetExtReg(int /*index*/, u128 /*value*/) { +void ARM_Unicorn::SetVectorReg(int /*index*/, u128 /*value*/) { UNIMPLEMENTED(); } -u32 ARM_Unicorn::GetVFPReg(int /*index*/) const { - UNIMPLEMENTED(); - return {}; -} - -void ARM_Unicorn::SetVFPReg(int /*index*/, u32 /*value*/) { - UNIMPLEMENTED(); -} - -u32 ARM_Unicorn::GetCPSR() const { +u32 ARM_Unicorn::GetPSTATE() const { u64 nzcv{}; CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &nzcv)); return static_cast<u32>(nzcv); } -void ARM_Unicorn::SetCPSR(u32 cpsr) { - u64 nzcv = cpsr; +void ARM_Unicorn::SetPSTATE(u32 pstate) { + u64 nzcv = pstate; CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &nzcv)); } @@ -193,10 +184,10 @@ void ARM_Unicorn::Step() { ExecuteInstructions(1); } -MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64)); +MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64)); void ARM_Unicorn::ExecuteInstructions(int num_instructions) { - MICROPROFILE_SCOPE(ARM_Jit); + MICROPROFILE_SCOPE(ARM_Jit_Unicorn); CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions)); CoreTiming::AddTicks(num_instructions); if (GDBStub::IsServerEnabled()) { @@ -219,7 +210,7 @@ void ARM_Unicorn::SaveContext(ThreadContext& ctx) { CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &ctx.sp)); CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &ctx.pc)); - CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &ctx.cpsr)); + CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &ctx.pstate)); for (auto i = 0; i < 29; ++i) { uregs[i] = UC_ARM64_REG_X0 + i; @@ -234,7 +225,7 @@ void ARM_Unicorn::SaveContext(ThreadContext& ctx) { for (int i = 0; i < 32; ++i) { uregs[i] = UC_ARM64_REG_Q0 + i; - tregs[i] = &ctx.fpu_registers[i]; + tregs[i] = &ctx.vector_registers[i]; } CHECKED(uc_reg_read_batch(uc, uregs, tregs, 32)); @@ -246,7 +237,7 @@ void ARM_Unicorn::LoadContext(const ThreadContext& ctx) { CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &ctx.sp)); CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &ctx.pc)); - CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &ctx.cpsr)); + CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &ctx.pstate)); for (int i = 0; i < 29; ++i) { uregs[i] = UC_ARM64_REG_X0 + i; @@ -261,7 +252,7 @@ void ARM_Unicorn::LoadContext(const ThreadContext& ctx) { for (auto i = 0; i < 32; ++i) { uregs[i] = UC_ARM64_REG_Q0 + i; - tregs[i] = (void*)&ctx.fpu_registers[i]; + tregs[i] = (void*)&ctx.vector_registers[i]; } CHECKED(uc_reg_write_batch(uc, uregs, tregs, 32)); diff --git a/src/core/arm/unicorn/arm_unicorn.h b/src/core/arm/unicorn/arm_unicorn.h index bd6b2f723..75761950b 100644 --- a/src/core/arm/unicorn/arm_unicorn.h +++ b/src/core/arm/unicorn/arm_unicorn.h @@ -15,19 +15,17 @@ class ARM_Unicorn final : public ARM_Interface { public: ARM_Unicorn(); ~ARM_Unicorn(); - void MapBackingMemory(VAddr address, size_t size, u8* memory, + void MapBackingMemory(VAddr address, std::size_t size, u8* memory, Kernel::VMAPermission perms) override; - void UnmapMemory(VAddr address, size_t size) override; + void UnmapMemory(VAddr address, std::size_t size) override; void SetPC(u64 pc) override; u64 GetPC() const override; u64 GetReg(int index) const override; void SetReg(int index, u64 value) override; - u128 GetExtReg(int index) const override; - void SetExtReg(int index, u128 value) override; - u32 GetVFPReg(int index) const override; - void SetVFPReg(int index, u32 value) override; - u32 GetCPSR() const override; - void SetCPSR(u32 cpsr) override; + u128 GetVectorReg(int index) const override; + void SetVectorReg(int index, u128 value) override; + u32 GetPSTATE() const override; + void SetPSTATE(u32 pstate) override; VAddr GetTlsAddress() const override; void SetTlsAddress(VAddr address) override; void SetTPIDR_EL0(u64 value) override; diff --git a/src/core/core.cpp b/src/core/core.cpp index 2cfae18df..50f0a42fb 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -14,6 +14,9 @@ #include "core/core.h" #include "core/core_cpu.h" #include "core/core_timing.h" +#include "core/file_sys/mode.h" +#include "core/file_sys/vfs_concat.h" +#include "core/file_sys/vfs_real.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/kernel.h" @@ -21,14 +24,11 @@ #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" #include "core/settings.h" #include "core/telemetry_session.h" -#include "file_sys/vfs_concat.h" -#include "file_sys/vfs_real.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" @@ -136,11 +136,11 @@ 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()); - for (size_t index = 0; index < cpu_cores.size(); ++index) { + for (std::size_t index = 0; index < cpu_cores.size(); ++index) { cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index); } @@ -161,7 +161,7 @@ struct System::Impl { // CPU core 0 is run on the main thread thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0]; if (Settings::values.use_multi_core) { - for (size_t index = 0; index < cpu_core_threads.size(); ++index) { + for (std::size_t index = 0; index < cpu_core_threads.size(); ++index) { cpu_core_threads[index] = std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]); thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1]; @@ -202,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(); @@ -281,12 +281,11 @@ 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; std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads; - size_t active_core{}; ///< Active core, only used in single thread mode + std::size_t active_core{}; ///< Active core, only used in single thread mode /// Service manager std::shared_ptr<Service::SM::ServiceManager> service_manager; @@ -349,7 +348,7 @@ ARM_Interface& System::CurrentArmInterface() { return CurrentCpuCore().ArmInterface(); } -size_t System::CurrentCoreIndex() { +std::size_t System::CurrentCoreIndex() { return CurrentCpuCore().CoreIndex(); } @@ -357,21 +356,25 @@ Kernel::Scheduler& System::CurrentScheduler() { return *CurrentCpuCore().Scheduler(); } -const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(size_t core_index) { +const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_index) { ASSERT(core_index < NUM_CPU_CORES); return impl->cpu_cores[core_index]->Scheduler(); } Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() { - return impl->current_process; + return impl->kernel.CurrentProcess(); } -ARM_Interface& System::ArmInterface(size_t core_index) { +const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const { + return impl->kernel.CurrentProcess(); +} + +ARM_Interface& System::ArmInterface(std::size_t core_index) { ASSERT(core_index < NUM_CPU_CORES); return impl->cpu_cores[core_index]->ArmInterface(); } -Cpu& System::CpuCore(size_t core_index) { +Cpu& System::CpuCore(std::size_t core_index) { ASSERT(core_index < NUM_CPU_CORES); return *impl->cpu_cores[core_index]; } @@ -440,8 +443,8 @@ void System::SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) { impl->debug_context = std::move(context); } -std::shared_ptr<Tegra::DebugContext> System::GetGPUDebugContext() const { - return impl->debug_context; +Tegra::DebugContext* System::GetGPUDebugContext() const { + return impl->debug_context.get(); } void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { diff --git a/src/core/core.h b/src/core/core.h index eee1fecc1..f9a3e97e3 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -145,16 +145,16 @@ public: ARM_Interface& CurrentArmInterface(); /// Gets the index of the currently running CPU core - size_t CurrentCoreIndex(); + std::size_t CurrentCoreIndex(); /// Gets the scheduler for the CPU core that is currently running Kernel::Scheduler& CurrentScheduler(); /// Gets an ARM interface to the CPU core with the specified index - ARM_Interface& ArmInterface(size_t core_index); + ARM_Interface& ArmInterface(std::size_t core_index); /// Gets a CPU interface to the CPU core with the specified index - Cpu& CpuCore(size_t core_index); + Cpu& CpuCore(std::size_t core_index); /// Gets the exclusive monitor ExclusiveMonitor& Monitor(); @@ -172,11 +172,14 @@ public: const VideoCore::RendererBase& Renderer() const; /// Gets the scheduler for the CPU core with the specified index - const std::shared_ptr<Kernel::Scheduler>& Scheduler(size_t core_index); + const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::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(); @@ -209,7 +212,7 @@ public: void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context); - std::shared_ptr<Tegra::DebugContext> GetGPUDebugContext() const; + Tegra::DebugContext* GetGPUDebugContext() const; void SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs); diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp index b042ee02b..21568ad50 100644 --- a/src/core/core_cpu.cpp +++ b/src/core/core_cpu.cpp @@ -9,6 +9,7 @@ #ifdef ARCHITECTURE_x86_64 #include "core/arm/dynarmic/arm_dynarmic.h" #endif +#include "core/arm/exclusive_monitor.h" #include "core/arm/unicorn/arm_unicorn.h" #include "core/core_cpu.h" #include "core/core_timing.h" @@ -49,7 +50,7 @@ bool CpuBarrier::Rendezvous() { } Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, - std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index) + std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index) : cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} { if (Settings::values.use_cpu_jit) { @@ -66,7 +67,9 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, scheduler = std::make_shared<Kernel::Scheduler>(arm_interface.get()); } -std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(size_t num_cores) { +Cpu::~Cpu() = default; + +std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) { if (Settings::values.use_cpu_jit) { #ifdef ARCHITECTURE_x86_64 return std::make_shared<DynarmicExclusiveMonitor>(num_cores); diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h index 40ed34b47..685532965 100644 --- a/src/core/core_cpu.h +++ b/src/core/core_cpu.h @@ -6,11 +6,10 @@ #include <atomic> #include <condition_variable> +#include <cstddef> #include <memory> #include <mutex> -#include <string> #include "common/common_types.h" -#include "core/arm/exclusive_monitor.h" namespace Kernel { class Scheduler; @@ -19,6 +18,7 @@ class Scheduler; namespace Core { class ARM_Interface; +class ExclusiveMonitor; constexpr unsigned NUM_CPU_CORES{4}; @@ -42,7 +42,8 @@ private: class Cpu { public: Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, - std::shared_ptr<CpuBarrier> cpu_barrier, size_t core_index); + std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index); + ~Cpu(); void RunLoop(bool tight_loop = true); @@ -66,11 +67,11 @@ public: return core_index == 0; } - size_t CoreIndex() const { + std::size_t CoreIndex() const { return core_index; } - static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(size_t num_cores); + static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores); private: void Reschedule(); @@ -80,7 +81,7 @@ private: std::shared_ptr<Kernel::Scheduler> scheduler; std::atomic<bool> reschedule_pending = false; - size_t core_index; + std::size_t core_index; }; } // namespace Core diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp index 72e4bed67..4be76bb43 100644 --- a/src/core/crypto/aes_util.cpp +++ b/src/core/crypto/aes_util.cpp @@ -10,9 +10,9 @@ namespace Core::Crypto { namespace { -std::vector<u8> CalculateNintendoTweak(size_t sector_id) { +std::vector<u8> CalculateNintendoTweak(std::size_t sector_id) { std::vector<u8> out(0x10); - for (size_t i = 0xF; i <= 0xF; --i) { + for (std::size_t i = 0xF; i <= 0xF; --i) { out[i] = sector_id & 0xFF; sector_id >>= 8; } @@ -20,11 +20,14 @@ std::vector<u8> CalculateNintendoTweak(size_t sector_id) { } } // Anonymous namespace -static_assert(static_cast<size_t>(Mode::CTR) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_CTR), +static_assert(static_cast<std::size_t>(Mode::CTR) == + static_cast<std::size_t>(MBEDTLS_CIPHER_AES_128_CTR), "CTR has incorrect value."); -static_assert(static_cast<size_t>(Mode::ECB) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_ECB), +static_assert(static_cast<std::size_t>(Mode::ECB) == + static_cast<std::size_t>(MBEDTLS_CIPHER_AES_128_ECB), "ECB has incorrect value."); -static_assert(static_cast<size_t>(Mode::XTS) == static_cast<size_t>(MBEDTLS_CIPHER_AES_128_XTS), +static_assert(static_cast<std::size_t>(Mode::XTS) == + static_cast<std::size_t>(MBEDTLS_CIPHER_AES_128_XTS), "XTS has incorrect value."); // Structure to hide mbedtls types from header file @@ -33,7 +36,7 @@ struct CipherContext { mbedtls_cipher_context_t decryption_context; }; -template <typename Key, size_t KeySize> +template <typename Key, std::size_t KeySize> Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) : ctx(std::make_unique<CipherContext>()) { mbedtls_cipher_init(&ctx->encryption_context); @@ -54,26 +57,26 @@ Crypto::AESCipher<Key, KeySize>::AESCipher(Key key, Mode mode) //"Failed to set key on mbedtls ciphers."); } -template <typename Key, size_t KeySize> +template <typename Key, std::size_t KeySize> AESCipher<Key, KeySize>::~AESCipher() { mbedtls_cipher_free(&ctx->encryption_context); mbedtls_cipher_free(&ctx->decryption_context); } -template <typename Key, size_t KeySize> +template <typename Key, std::size_t KeySize> void AESCipher<Key, KeySize>::SetIV(std::vector<u8> iv) { ASSERT_MSG((mbedtls_cipher_set_iv(&ctx->encryption_context, iv.data(), iv.size()) || mbedtls_cipher_set_iv(&ctx->decryption_context, iv.data(), iv.size())) == 0, "Failed to set IV on mbedtls ciphers."); } -template <typename Key, size_t KeySize> -void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op op) const { +template <typename Key, std::size_t KeySize> +void AESCipher<Key, KeySize>::Transcode(const u8* src, std::size_t size, u8* dest, Op op) const { auto* const context = op == Op::Encrypt ? &ctx->encryption_context : &ctx->decryption_context; mbedtls_cipher_reset(context); - size_t written = 0; + std::size_t written = 0; if (mbedtls_cipher_get_cipher_mode(context) == MBEDTLS_MODE_XTS) { mbedtls_cipher_update(context, src, size, dest, &written); if (written != size) { @@ -82,11 +85,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); + for (std::size_t offset = 0; offset < size; offset += block_size) { + auto length = std::min<std::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); } @@ -96,12 +113,12 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op mbedtls_cipher_finish(context, nullptr, nullptr); } -template <typename Key, size_t KeySize> -void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, - size_t sector_size, Op op) { +template <typename Key, std::size_t KeySize> +void AESCipher<Key, KeySize>::XTSTranscode(const u8* src, std::size_t size, u8* dest, + std::size_t sector_id, std::size_t sector_size, Op op) { ASSERT_MSG(size % sector_size == 0, "XTS decryption size must be a multiple of sector size."); - for (size_t i = 0; i < size; i += sector_size) { + for (std::size_t i = 0; i < size; i += sector_size) { SetIV(CalculateNintendoTweak(sector_id++)); Transcode<u8, u8>(src + i, sector_size, dest + i, op); } diff --git a/src/core/crypto/aes_util.h b/src/core/crypto/aes_util.h index 8ce9d6612..edc4ab910 100644 --- a/src/core/crypto/aes_util.h +++ b/src/core/crypto/aes_util.h @@ -25,7 +25,7 @@ enum class Op { Decrypt, }; -template <typename Key, size_t KeySize = sizeof(Key)> +template <typename Key, std::size_t KeySize = sizeof(Key)> class AESCipher { static_assert(std::is_same_v<Key, std::array<u8, KeySize>>, "Key must be std::array of u8."); static_assert(KeySize == 0x10 || KeySize == 0x20, "KeySize must be 128 or 256."); @@ -38,25 +38,25 @@ public: void SetIV(std::vector<u8> iv); template <typename Source, typename Dest> - void Transcode(const Source* src, size_t size, Dest* dest, Op op) const { + void Transcode(const Source* src, std::size_t size, Dest* dest, Op op) const { static_assert(std::is_trivially_copyable_v<Source> && std::is_trivially_copyable_v<Dest>, "Transcode source and destination types must be trivially copyable."); Transcode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), op); } - void Transcode(const u8* src, size_t size, u8* dest, Op op) const; + void Transcode(const u8* src, std::size_t size, u8* dest, Op op) const; template <typename Source, typename Dest> - void XTSTranscode(const Source* src, size_t size, Dest* dest, size_t sector_id, - size_t sector_size, Op op) { + void XTSTranscode(const Source* src, std::size_t size, Dest* dest, std::size_t sector_id, + std::size_t sector_size, Op op) { static_assert(std::is_trivially_copyable_v<Source> && std::is_trivially_copyable_v<Dest>, "XTSTranscode source and destination types must be trivially copyable."); XTSTranscode(reinterpret_cast<const u8*>(src), size, reinterpret_cast<u8*>(dest), sector_id, sector_size, op); } - void XTSTranscode(const u8* src, size_t size, u8* dest, size_t sector_id, size_t sector_size, - Op op); + void XTSTranscode(const u8* src, std::size_t size, u8* dest, std::size_t sector_id, + std::size_t sector_size, Op op); private: std::unique_ptr<CipherContext> ctx; diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp index 3ea60dbd0..902841c77 100644 --- a/src/core/crypto/ctr_encryption_layer.cpp +++ b/src/core/crypto/ctr_encryption_layer.cpp @@ -8,11 +8,12 @@ namespace Core::Crypto { -CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, size_t base_offset) +CTREncryptionLayer::CTREncryptionLayer(FileSys::VirtualFile base_, Key128 key_, + std::size_t base_offset) : EncryptionLayer(std::move(base_)), base_offset(base_offset), cipher(key_, Mode::CTR), iv(16, 0) {} -size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const { +std::size_t CTREncryptionLayer::Read(u8* data, std::size_t length, std::size_t offset) const { if (length == 0) return 0; @@ -21,14 +22,14 @@ 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) std::vector<u8> block = base->ReadBytes(0x10, offset - sector_offset); UpdateIV(base_offset + offset - sector_offset); cipher.Transcode(block.data(), block.size(), block.data(), Op::Decrypt); - size_t read = 0x10 - sector_offset; + std::size_t read = 0x10 - sector_offset; if (length + sector_offset < 0x10) { std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); @@ -43,9 +44,9 @@ void CTREncryptionLayer::SetIV(const std::vector<u8>& iv_) { iv.assign(iv_.cbegin(), iv_.cbegin() + length); } -void CTREncryptionLayer::UpdateIV(size_t offset) const { +void CTREncryptionLayer::UpdateIV(std::size_t offset) const { offset >>= 4; - for (size_t i = 0; i < 8; ++i) { + for (std::size_t i = 0; i < 8; ++i) { iv[16 - i - 1] = offset & 0xFF; offset >>= 8; } diff --git a/src/core/crypto/ctr_encryption_layer.h b/src/core/crypto/ctr_encryption_layer.h index 11b8683c7..a7bf810f4 100644 --- a/src/core/crypto/ctr_encryption_layer.h +++ b/src/core/crypto/ctr_encryption_layer.h @@ -14,20 +14,20 @@ namespace Core::Crypto { // Sits on top of a VirtualFile and provides CTR-mode AES decription. class CTREncryptionLayer : public EncryptionLayer { public: - CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, size_t base_offset); + CTREncryptionLayer(FileSys::VirtualFile base, Key128 key, std::size_t base_offset); - size_t Read(u8* data, size_t length, size_t offset) const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; void SetIV(const std::vector<u8>& iv); private: - size_t base_offset; + std::size_t base_offset; // Must be mutable as operations modify cipher contexts. mutable AESCipher<Key128> cipher; mutable std::vector<u8> iv; - void UpdateIV(size_t offset) const; + void UpdateIV(std::size_t offset) const; }; } // namespace Core::Crypto diff --git a/src/core/crypto/encryption_layer.cpp b/src/core/crypto/encryption_layer.cpp index 4204527e3..4c377d7d4 100644 --- a/src/core/crypto/encryption_layer.cpp +++ b/src/core/crypto/encryption_layer.cpp @@ -12,11 +12,11 @@ std::string EncryptionLayer::GetName() const { return base->GetName(); } -size_t EncryptionLayer::GetSize() const { +std::size_t EncryptionLayer::GetSize() const { return base->GetSize(); } -bool EncryptionLayer::Resize(size_t new_size) { +bool EncryptionLayer::Resize(std::size_t new_size) { return false; } @@ -32,7 +32,7 @@ bool EncryptionLayer::IsReadable() const { return true; } -size_t EncryptionLayer::Write(const u8* data, size_t length, size_t offset) { +std::size_t EncryptionLayer::Write(const u8* data, std::size_t length, std::size_t offset) { return 0; } diff --git a/src/core/crypto/encryption_layer.h b/src/core/crypto/encryption_layer.h index 7f05af9b4..53619cb38 100644 --- a/src/core/crypto/encryption_layer.h +++ b/src/core/crypto/encryption_layer.h @@ -15,15 +15,15 @@ class EncryptionLayer : public FileSys::VfsFile { public: explicit EncryptionLayer(FileSys::VirtualFile base); - size_t Read(u8* data, size_t length, size_t offset) const override = 0; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override = 0; std::string GetName() const override; - size_t GetSize() const override; - bool Resize(size_t new_size) override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; std::shared_ptr<FileSys::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; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; bool Rename(std::string_view name) override; protected: diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index 0b6c07de8..bf3a70944 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -8,12 +8,15 @@ #include <locale> #include <sstream> #include <string_view> +#include <tuple> +#include <vector> #include "common/common_paths.h" #include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #include "core/crypto/aes_util.h" #include "core/crypto/key_manager.h" +#include "core/loader/loader.h" #include "core/settings.h" namespace Core::Crypto { @@ -51,7 +54,7 @@ boost::optional<Key128> DeriveSDSeed() { return boost::none; std::array<u8, 0x10> buffer{}; - size_t offset = 0; + std::size_t offset = 0; for (; offset + 0x10 < save_43.GetSize(); ++offset) { save_43.Seek(offset, SEEK_SET); save_43.ReadBytes(buffer.data(), buffer.size()); @@ -102,7 +105,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManag // Combine sources and seed for (auto& source : sd_key_sources) { - for (size_t i = 0; i < source.size(); ++i) + for (std::size_t i = 0; i < source.size(); ++i) source[i] ^= sd_seed[i & 0xF]; } @@ -204,7 +207,7 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const { return s256_keys.at({id, field1, field2}); } -template <size_t Size> +template <std::size_t Size> void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key) { const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir); @@ -228,18 +231,28 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, } void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { - const auto iter = std::find_if( + if (s128_keys.find({id, field1, field2}) != s128_keys.end()) + return; + if (id == S128KeyType::Titlekey) { + Key128 rights_id; + std::memcpy(rights_id.data(), &field2, sizeof(u64)); + std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64)); + WriteKeyToFile(true, Common::HexArrayToString(rights_id), key); + } + const auto iter2 = std::find_if( s128_file_id.begin(), s128_file_id.end(), [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == std::tie(id, field1, field2); }); - if (iter != s128_file_id.end()) - WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key); + if (iter2 != s128_file_id.end()) + WriteKeyToFile(false, iter2->first, key); s128_keys[{id, field1, field2}] = key; } void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { + if (s256_keys.find({id, field1, field2}) != s256_keys.end()) + return; const auto iter = std::find_if( s256_file_id.begin(), s256_file_id.end(), [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) { diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h index 7ca3e6cbc..978eec8dc 100644 --- a/src/core/crypto/key_manager.h +++ b/src/core/crypto/key_manager.h @@ -6,16 +6,19 @@ #include <array> #include <string> -#include <string_view> -#include <type_traits> -#include <vector> #include <boost/container/flat_map.hpp> +#include <boost/optional.hpp> #include <fmt/format.h> #include "common/common_types.h" -#include "core/loader/loader.h" + +namespace Loader { +enum class ResultStatus : u16; +} namespace Core::Crypto { +constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; + using Key128 = std::array<u8, 0x10>; using Key256 = std::array<u8, 0x20>; using SHA256Hash = std::array<u8, 0x20>; @@ -105,7 +108,7 @@ private: void LoadFromFile(const std::string& filename, bool is_title_keys); void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2, const std::string& filename, bool title); - template <size_t Size> + template <std::size_t Size> void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key); static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id; diff --git a/src/core/crypto/xts_encryption_layer.cpp b/src/core/crypto/xts_encryption_layer.cpp index c10832cfe..8f0ba4ee7 100644 --- a/src/core/crypto/xts_encryption_layer.cpp +++ b/src/core/crypto/xts_encryption_layer.cpp @@ -14,7 +14,7 @@ constexpr u64 XTS_SECTOR_SIZE = 0x4000; XTSEncryptionLayer::XTSEncryptionLayer(FileSys::VirtualFile base_, Key256 key_) : EncryptionLayer(std::move(base_)), cipher(key_, Mode::XTS) {} -size_t XTSEncryptionLayer::Read(u8* data, size_t length, size_t offset) const { +std::size_t XTSEncryptionLayer::Read(u8* data, std::size_t length, std::size_t offset) const { if (length == 0) return 0; @@ -46,7 +46,7 @@ size_t XTSEncryptionLayer::Read(u8* data, size_t length, size_t offset) const { block.resize(XTS_SECTOR_SIZE); cipher.XTSTranscode(block.data(), block.size(), block.data(), (offset - sector_offset) / XTS_SECTOR_SIZE, XTS_SECTOR_SIZE, Op::Decrypt); - const size_t read = XTS_SECTOR_SIZE - sector_offset; + const std::size_t read = XTS_SECTOR_SIZE - sector_offset; if (length + sector_offset < XTS_SECTOR_SIZE) { std::memcpy(data, block.data() + sector_offset, std::min<u64>(length, read)); diff --git a/src/core/crypto/xts_encryption_layer.h b/src/core/crypto/xts_encryption_layer.h index 7a1f1dc64..5f8f00fe7 100644 --- a/src/core/crypto/xts_encryption_layer.h +++ b/src/core/crypto/xts_encryption_layer.h @@ -15,7 +15,7 @@ class XTSEncryptionLayer : public EncryptionLayer { public: XTSEncryptionLayer(FileSys::VirtualFile base, Key256 key); - size_t Read(u8* data, size_t length, size_t offset) const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; private: // Must be mutable as operations modify cipher contexts. diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp index 08a7cea5a..205492897 100644 --- a/src/core/file_sys/bis_factory.cpp +++ b/src/core/file_sys/bis_factory.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include "core/file_sys/bis_factory.h" +#include "core/file_sys/registered_cache.h" namespace FileSys { @@ -13,6 +14,8 @@ BISFactory::BISFactory(VirtualDir nand_root_) usrnand_cache(std::make_shared<RegisteredCache>( GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {} +BISFactory::~BISFactory() = default; + std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const { return sysnand_cache; } diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h index a970a5e2e..9523dd864 100644 --- a/src/core/file_sys/bis_factory.h +++ b/src/core/file_sys/bis_factory.h @@ -5,17 +5,20 @@ #pragma once #include <memory> -#include "core/loader/loader.h" -#include "registered_cache.h" + +#include "core/file_sys/vfs.h" namespace FileSys { +class RegisteredCache; + /// File system interface to the Built-In Storage /// This is currently missing accessors to BIS partitions, but seemed like a good place for the NAND /// registered caches. class BISFactory { public: explicit BISFactory(VirtualDir nand_root); + ~BISFactory(); std::shared_ptr<RegisteredCache> GetSystemNANDContents() const; std::shared_ptr<RegisteredCache> GetUserNANDContents() const; diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp index d61a2ebe1..edfc1bbd4 100644 --- a/src/core/file_sys/card_image.cpp +++ b/src/core/file_sys/card_image.cpp @@ -9,7 +9,10 @@ #include "common/logging/log.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" #include "core/file_sys/partition_filesystem.h" +#include "core/file_sys/submission_package.h" #include "core/file_sys/vfs_offset.h" #include "core/loader/loader.h" @@ -38,20 +41,25 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { for (XCIPartition partition : {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) { - auto raw = main_hfs.GetFile(partition_names[static_cast<size_t>(partition)]); + auto raw = main_hfs.GetFile(partition_names[static_cast<std::size_t>(partition)]); if (raw != nullptr) - partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); + partitions[static_cast<std::size_t>(partition)] = + std::make_shared<PartitionFilesystem>(raw); } - program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; + secure_partition = std::make_shared<NSP>( + main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)])); - auto result = AddNCAFromPartition(XCIPartition::Secure); - if (result != Loader::ResultStatus::Success) { - status = result; - return; - } + const auto secure_ncas = secure_partition->GetNCAsCollapsed(); + std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas)); - result = AddNCAFromPartition(XCIPartition::Update); + program = + secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program); + 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) { status = result; return; @@ -74,6 +82,8 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { status = Loader::ResultStatus::Success; } +XCI::~XCI() = default; + Loader::ResultStatus XCI::GetStatus() const { return status; } @@ -83,7 +93,11 @@ Loader::ResultStatus XCI::GetProgramNCAStatus() const { } VirtualDir XCI::GetPartition(XCIPartition partition) const { - return partitions[static_cast<size_t>(partition)]; + return partitions[static_cast<std::size_t>(partition)]; +} + +std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const { + return secure_partition; } VirtualDir XCI::GetSecurePartition() const { @@ -102,6 +116,20 @@ VirtualDir XCI::GetLogoPartition() const { return GetPartition(XCIPartition::Logo); } +u64 XCI::GetProgramTitleID() const { + return secure_partition->GetProgramTitleID(); +} + +std::shared_ptr<NCA> XCI::GetProgramNCA() const { + return program; +} + +VirtualFile XCI::GetProgramNCAFile() const { + if (GetProgramNCA() == nullptr) + return nullptr; + return GetProgramNCA()->GetBaseFile(); +} + const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const { return ncas; } @@ -141,11 +169,11 @@ bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { } Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { - if (partitions[static_cast<size_t>(part)] == nullptr) { + if (partitions[static_cast<std::size_t>(part)] == nullptr) { return Loader::ResultStatus::ErrorXCIMissingPartition; } - for (const VirtualFile& file : partitions[static_cast<size_t>(part)]->GetFiles()) { + for (const VirtualFile& file : partitions[static_cast<std::size_t>(part)]->GetFiles()) { if (file->GetExtension() != "nca") continue; auto nca = std::make_shared<NCA>(file); @@ -160,7 +188,7 @@ Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) { } else { const u16 error_id = static_cast<u16>(nca->GetStatus()); LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})", - partition_names[static_cast<size_t>(part)], nca->GetName(), error_id, + partition_names[static_cast<std::size_t>(part)], nca->GetName(), error_id, nca->GetStatus()); } } diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h index 54ab828d1..ce514dfa0 100644 --- a/src/core/file_sys/card_image.h +++ b/src/core/file_sys/card_image.h @@ -5,15 +5,22 @@ #pragma once #include <array> +#include <memory> #include <vector> #include "common/common_types.h" #include "common/swap.h" -#include "core/file_sys/content_archive.h" #include "core/file_sys/vfs.h" -#include "core/loader/loader.h" + +namespace Loader { +enum class ResultStatus : u16; +} namespace FileSys { +class NCA; +enum class NCAContentType : u8; +class NSP; + enum class GamecardSize : u8 { S_1GB = 0xFA, S_2GB = 0xF8, @@ -57,6 +64,7 @@ enum class XCIPartition : u8 { Update, Normal, Secure, Logo }; class XCI : public ReadOnlyVfsDirectory { public: explicit XCI(VirtualFile file); + ~XCI() override; Loader::ResultStatus GetStatus() const; Loader::ResultStatus GetProgramNCAStatus() const; @@ -64,11 +72,16 @@ public: u8 GetFormatVersion() const; VirtualDir GetPartition(XCIPartition partition) const; + std::shared_ptr<NSP> GetSecurePartitionNSP() const; VirtualDir GetSecurePartition() const; VirtualDir GetNormalPartition() const; VirtualDir GetUpdatePartition() const; VirtualDir GetLogoPartition() const; + u64 GetProgramTitleID() const; + + std::shared_ptr<NCA> GetProgramNCA() const; + VirtualFile GetProgramNCAFile() const; const std::vector<std::shared_ptr<NCA>>& GetNCAs() const; std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; VirtualFile GetNCAFileByType(NCAContentType type) const; @@ -94,6 +107,8 @@ private: Loader::ResultStatus program_nca_status; std::vector<VirtualDir> partitions; + std::shared_ptr<NSP> secure_partition; + std::shared_ptr<NCA> program; std::vector<std::shared_ptr<NCA>> ncas; }; } // namespace FileSys diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp index e8b5d6ece..aa1b3c17d 100644 --- a/src/core/file_sys/content_archive.cpp +++ b/src/core/file_sys/content_archive.cpp @@ -3,12 +3,17 @@ // Refer to the license.txt file included. #include <algorithm> +#include <cstring> #include <utility> + #include <boost/optional.hpp> + #include "common/logging/log.h" #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" #include "core/loader/loader.h" @@ -64,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."); @@ -100,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}", @@ -150,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; @@ -186,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) { @@ -261,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 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 { + const std::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 std::size_t romfs_offset = base_offset + ivfc_offset; + const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; + 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) @@ -285,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) + @@ -300,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) @@ -316,6 +463,8 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) { status = Loader::ResultStatus::Success; } +NCA::~NCA() = default; + Loader::ResultStatus NCA::GetStatus() const { return status; } @@ -345,11 +494,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; } @@ -362,8 +515,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) { diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h index b961cfde7..f9f66cae9 100644 --- a/src/core/file_sys/content_archive.h +++ b/src/core/file_sys/content_archive.h @@ -12,10 +12,12 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" -#include "control_metadata.h" #include "core/crypto/key_manager.h" -#include "core/file_sys/partition_filesystem.h" -#include "core/loader/loader.h" +#include "core/file_sys/vfs.h" + +namespace Loader { +enum class ResultStatus : u16; +} namespace FileSys { @@ -77,7 +79,10 @@ 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); + ~NCA() override; + Loader::ResultStatus GetStatus() const; std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; @@ -87,13 +92,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; @@ -110,14 +117,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; }; diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp index ae21ad5b9..5b1177a03 100644 --- a/src/core/file_sys/control_metadata.cpp +++ b/src/core/file_sys/control_metadata.cpp @@ -8,6 +8,14 @@ namespace FileSys { +const std::array<const char*, 15> LANGUAGE_NAMES = { + "AmericanEnglish", "BritishEnglish", "Japanese", + "French", "German", "LatinAmericanSpanish", + "Spanish", "Italian", "Dutch", + "CanadianFrench", "Portugese", "Russian", + "Korean", "Taiwanese", "Chinese", +}; + std::string LanguageEntry::GetApplicationName() const { return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200); } @@ -20,8 +28,20 @@ NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) { file->ReadObject(raw.get()); } +NACP::~NACP() = default; + const LanguageEntry& NACP::GetLanguageEntry(Language language) const { - return raw->language_entries.at(static_cast<u8>(language)); + if (language != Language::Default) { + return raw->language_entries.at(static_cast<u8>(language)); + } + + for (const auto& language_entry : raw->language_entries) { + if (!language_entry.GetApplicationName().empty()) + return language_entry; + } + + // Fallback to English + return GetLanguageEntry(Language::AmericanEnglish); } std::string NACP::GetApplicationName(Language language) const { diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h index 8c2cc1a2a..43d6f0719 100644 --- a/src/core/file_sys/control_metadata.h +++ b/src/core/file_sys/control_metadata.h @@ -8,6 +8,8 @@ #include <memory> #include <string> #include "common/common_funcs.h" +#include "common/common_types.h" +#include "common/swap.h" #include "core/file_sys/vfs.h" namespace FileSys { @@ -60,23 +62,22 @@ enum class Language : u8 { Korean = 12, Taiwanese = 13, Chinese = 14, + + Default = 255, }; -static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { - "AmericanEnglish", "BritishEnglish", "Japanese", - "French", "German", "LatinAmericanSpanish", - "Spanish", "Italian", "Dutch", - "CanadianFrench", "Portugese", "Russian", - "Korean", "Taiwanese", "Chinese"}; +extern const std::array<const char*, 15> LANGUAGE_NAMES; // A class representing the format used by NX metadata files, typically named Control.nacp. // These store application name, dev name, title id, and other miscellaneous data. class NACP { public: explicit NACP(VirtualFile file); - const LanguageEntry& GetLanguageEntry(Language language = Language::AmericanEnglish) const; - std::string GetApplicationName(Language language = Language::AmericanEnglish) const; - std::string GetDeveloperName(Language language = Language::AmericanEnglish) const; + ~NACP(); + + const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const; + std::string GetApplicationName(Language language = Language::Default) const; + std::string GetDeveloperName(Language language = Language::Default) const; u64 GetTitleId() const; std::string GetVersionString() const; diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index 3759e743a..12bb90ec8 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -25,7 +25,7 @@ enum EntryType : u8 { struct Entry { Entry(std::string_view view, EntryType entry_type, u64 entry_size) : type{entry_type}, file_size{entry_size} { - const size_t copy_size = view.copy(filename, std::size(filename) - 1); + const std::size_t copy_size = view.copy(filename, std::size(filename) - 1); filename[copy_size] = '\0'; } diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 449244444..6f34b7836 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp @@ -3,20 +3,19 @@ // Refer to the license.txt file included. #include <cstring> -#include "common/common_funcs.h" +#include "common/common_types.h" #include "common/logging/log.h" #include "common/swap.h" -#include "content_archive.h" #include "core/file_sys/nca_metadata.h" namespace FileSys { bool operator>=(TitleType lhs, TitleType rhs) { - return static_cast<size_t>(lhs) >= static_cast<size_t>(rhs); + return static_cast<std::size_t>(lhs) >= static_cast<std::size_t>(rhs); } bool operator<=(TitleType lhs, TitleType rhs) { - return static_cast<size_t>(lhs) <= static_cast<size_t>(rhs); + return static_cast<std::size_t>(lhs) <= static_cast<std::size_t>(rhs); } CNMT::CNMT(VirtualFile file) { @@ -52,6 +51,8 @@ CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentReco : header(std::move(header)), opt_header(std::move(opt_header)), content_records(std::move(content_records)), meta_records(std::move(meta_records)) {} +CNMT::~CNMT() = default; + u64 CNMT::GetTitleID() const { return header.title_id; } diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index ce05b4c1d..a05d155f4 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -4,7 +4,6 @@ #pragma once -#include <cstring> #include <memory> #include <vector> #include "common/common_funcs.h" @@ -88,6 +87,7 @@ public: explicit CNMT(VirtualFile file); CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, std::vector<MetaRecord> meta_records); + ~CNMT(); u64 GetTitleID() const; u32 GetTitleVersion() const; diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp new file mode 100644 index 000000000..0090cc6c4 --- /dev/null +++ b/src/core/file_sys/nca_patch.cpp @@ -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 (std::size_t i = 0; i < relocation.number_buckets - 1; ++i) { + relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); + } + + for (std::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; + +std::size_t BKTR::Read(u8* data, std::size_t length, std::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 (std::size_t i = 0; i < section_ctr.size(); ++i) + iv[i] = section_ctr[0x8 - i - 1]; + offset_iv >>= 4; + for (std::size_t i = 0; i < sizeof(u64); ++i) { + iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); + offset_iv >>= 8; + } + for (std::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<std::size_t, std::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."); + } + + std::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}; + + std::size_t low = 0; + std::size_t mid = 0; + std::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(); +} + +std::size_t BKTR::GetSize() const { + return relocation.size; +} + +bool BKTR::Resize(std::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; +} + +std::size_t BKTR::Write(const u8* data, std::size_t length, std::size_t offset) { + return 0; +} + +bool BKTR::Rename(std::string_view name) { + return base_romfs->Rename(name); +} + +} // namespace FileSys diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h new file mode 100644 index 000000000..8e64e8378 --- /dev/null +++ b/src/core/file_sys/nca_patch.h @@ -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; + + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + + std::string GetName() const override; + + std::size_t GetSize() const override; + + bool Resize(std::size_t new_size) override; + + std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; + + bool IsWritable() const override; + + bool IsReadable() const override; + + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + + bool Rename(std::string_view name) override; + +private: + template <bool Subsection, typename BlockType, typename BucketType> + std::pair<std::size_t, std::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 diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp index c377edc9c..5791c76ff 100644 --- a/src/core/file_sys/partition_filesystem.cpp +++ b/src/core/file_sys/partition_filesystem.cpp @@ -42,21 +42,21 @@ PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) { is_hfs = pfs_header.magic == Common::MakeMagic('H', 'F', 'S', '0'); - size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); - size_t metadata_size = + std::size_t entry_size = is_hfs ? sizeof(HFSEntry) : sizeof(PFSEntry); + std::size_t metadata_size = sizeof(Header) + (pfs_header.num_entries * entry_size) + pfs_header.strtab_size; // Actually read in now... std::vector<u8> file_data = file->ReadBytes(metadata_size); - const size_t total_size = file_data.size(); + const std::size_t total_size = file_data.size(); if (total_size != metadata_size) { status = Loader::ResultStatus::ErrorIncorrectPFSFileSize; return; } - size_t entries_offset = sizeof(Header); - size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); + std::size_t entries_offset = sizeof(Header); + std::size_t strtab_offset = entries_offset + (pfs_header.num_entries * entry_size); content_offset = strtab_offset + pfs_header.strtab_size; for (u16 i = 0; i < pfs_header.num_entries; i++) { FSEntry entry; @@ -72,6 +72,8 @@ PartitionFilesystem::PartitionFilesystem(std::shared_ptr<VfsFile> file) { status = Loader::ResultStatus::Success; } +PartitionFilesystem::~PartitionFilesystem() = default; + Loader::ResultStatus PartitionFilesystem::GetStatus() const { return status; } diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h index be7bc32a8..739c63a7f 100644 --- a/src/core/file_sys/partition_filesystem.h +++ b/src/core/file_sys/partition_filesystem.h @@ -25,6 +25,8 @@ namespace FileSys { class PartitionFilesystem : public ReadOnlyVfsDirectory { public: explicit PartitionFilesystem(std::shared_ptr<VfsFile> file); + ~PartitionFilesystem() override; + Loader::ResultStatus GetStatus() const; std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; @@ -79,7 +81,7 @@ private: Header pfs_header{}; bool is_hfs = false; - size_t content_offset = 0; + std::size_t content_offset = 0; std::vector<VirtualFile> pfs_files; std::vector<VirtualDir> pfs_dirs; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp new file mode 100644 index 000000000..aebc69d52 --- /dev/null +++ b/src/core/file_sys/patch_manager.cpp @@ -0,0 +1,159 @@ +// 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 (std::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<std::size_t>(type)); +} + +PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} + +PatchManager::~PatchManager() = default; + +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 diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h new file mode 100644 index 000000000..209cab1dc --- /dev/null +++ b/src/core/file_sys/patch_manager.h @@ -0,0 +1,64 @@ +// 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); + ~PatchManager(); + + // 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 diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 279f987d4..02319ce0f 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp @@ -2,15 +2,22 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/file_util.h" +#include <cstddef> +#include <cstring> +#include <vector> + #include "common/logging/log.h" #include "core/file_sys/program_metadata.h" #include "core/loader/loader.h" namespace FileSys { +ProgramMetadata::ProgramMetadata() = default; + +ProgramMetadata::~ProgramMetadata() = default; + Loader::ResultStatus ProgramMetadata::Load(VirtualFile file) { - size_t total_size = static_cast<size_t>(file->GetSize()); + std::size_t total_size = static_cast<std::size_t>(file->GetSize()); if (total_size < sizeof(Header)) return Loader::ResultStatus::ErrorBadNPDMHeader; diff --git a/src/core/file_sys/program_metadata.h b/src/core/file_sys/program_metadata.h index 74a91052b..1143e36c4 100644 --- a/src/core/file_sys/program_metadata.h +++ b/src/core/file_sys/program_metadata.h @@ -5,12 +5,10 @@ #pragma once #include <array> -#include <string> -#include <vector> #include "common/bit_field.h" #include "common/common_types.h" #include "common/swap.h" -#include "partition_filesystem.h" +#include "core/file_sys/vfs.h" namespace Loader { enum class ResultStatus : u16; @@ -38,6 +36,9 @@ enum class ProgramFilePermission : u64 { */ class ProgramMetadata { public: + ProgramMetadata(); + ~ProgramMetadata(); + Loader::ResultStatus Load(VirtualFile file); bool Is64BitProgram() const; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index dacf8568b..dad7ae10b 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -5,13 +5,17 @@ #include <regex> #include <mbedtls/sha256.h> #include "common/assert.h" +#include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" -#include "core/crypto/encryption_layer.h" +#include "core/crypto/key_manager.h" #include "core/file_sys/card_image.h" +#include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/registered_cache.h" +#include "core/file_sys/submission_package.h" #include "core/file_sys/vfs_concat.h" +#include "core/loader/loader.h" namespace FileSys { std::string RegisteredCacheEntry::DebugInfo() const { @@ -58,11 +62,11 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { "" ///< Currently unknown 'DeltaTitle' }; - auto index = static_cast<size_t>(type); + auto index = static_cast<std::size_t>(type); // If the index is after the jump in TitleType, subtract it out. - if (index >= static_cast<size_t>(TitleType::Application)) { - index -= static_cast<size_t>(TitleType::Application) - - static_cast<size_t>(TitleType::FirmwarePackageB); + if (index >= static_cast<std::size_t>(TitleType::Application)) { + index -= static_cast<std::size_t>(TitleType::Application) - + static_cast<std::size_t>(TitleType::FirmwarePackageB); } return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); } @@ -101,7 +105,7 @@ VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir, } else { std::vector<VirtualFile> concat; // Since the files are a two-digit hex number, max is FF. - for (size_t i = 0; i < 0x100; ++i) { + for (std::size_t i = 0; i < 0x100; ++i) { auto next = nca_dir->GetFile(fmt::format("{:02X}", i)); if (next != nullptr) { concat.push_back(std::move(next)); @@ -276,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) @@ -355,17 +371,21 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter( return out; } -static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) { - const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false)); - const auto iter = - std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(), - [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; }); - return iter == xci->GetNCAs().end() ? nullptr : *iter; +static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) { + const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false))); + if (file == nullptr) + return nullptr; + return std::make_shared<NCA>(file); } InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, const VfsCopyFunction& copy) { - const auto& ncas = xci->GetNCAs(); + return InstallEntry(xci->GetSecurePartitionNSP(), overwrite_if_exists, copy); +} + +InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists, + const VfsCopyFunction& copy) { + const auto& ncas = nsp->GetNCAsCollapsed(); const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { return nca->GetType() == NCAContentType::Meta; }); @@ -389,7 +409,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overw const auto cnmt_file = section0->GetFiles()[0]; const CNMT cnmt(cnmt_file); for (const auto& record : cnmt.GetContentRecords()) { - const auto nca = GetNCAFromXCIForID(xci, record.nca_id); + const auto nca = GetNCAFromNSPForID(nsp, record.nca_id); if (nca == nullptr) return InstallResult::ErrorCopyFailed; const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); @@ -490,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 diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 7b8955dfa..f487b0cf0 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -11,15 +11,19 @@ #include <string> #include <vector> #include <boost/container/flat_map.hpp> -#include "common/common_funcs.h" #include "common/common_types.h" -#include "content_archive.h" -#include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" namespace FileSys { -class XCI; class CNMT; +class NCA; +class NSP; +class XCI; + +enum class ContentRecordType : u8; +enum class TitleType : u8; + +struct ContentRecord; using NcaID = std::array<u8, 0x10>; using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; @@ -39,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); @@ -56,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 @@ -70,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; @@ -86,10 +98,12 @@ public: boost::optional<ContentRecordType> record_type = boost::none, boost::optional<u64> title_id = boost::none) const; - // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there - // is a meta NCA and all of them are accessible. + // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure + // there is a meta NCA and all of them are accessible. InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, const VfsCopyFunction& copy = &VfsRawCopy); + InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false, + const VfsCopyFunction& copy = &VfsRawCopy); // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a @@ -125,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 diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp index e490c8ace..9f6e41cdf 100644 --- a/src/core/file_sys/romfs.cpp +++ b/src/core/file_sys/romfs.cpp @@ -49,7 +49,7 @@ struct FileEntry { static_assert(sizeof(FileEntry) == 0x20, "FileEntry has incorrect size."); template <typename Entry> -static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, size_t offset) { +static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, std::size_t offset) { Entry entry{}; if (file->ReadObject(&entry, offset) != sizeof(Entry)) return {}; @@ -59,8 +59,8 @@ static std::pair<Entry, std::string> GetEntry(const VirtualFile& file, size_t of return {entry, string}; } -void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 this_file_offset, - std::shared_ptr<VectorVfsDirectory> parent) { +void ProcessFile(VirtualFile file, std::size_t file_offset, std::size_t data_offset, + u32 this_file_offset, std::shared_ptr<VectorVfsDirectory> parent) { while (true) { auto entry = GetEntry<FileEntry>(file, file_offset + this_file_offset); @@ -74,8 +74,9 @@ void ProcessFile(VirtualFile file, size_t file_offset, size_t data_offset, u32 t } } -void ProcessDirectory(VirtualFile file, size_t dir_offset, size_t file_offset, size_t data_offset, - u32 this_dir_offset, std::shared_ptr<VectorVfsDirectory> parent) { +void ProcessDirectory(VirtualFile file, std::size_t dir_offset, std::size_t file_offset, + std::size_t data_offset, u32 this_dir_offset, + std::shared_ptr<VectorVfsDirectory> parent) { while (true) { auto entry = GetEntry<DirectoryEntry>(file, dir_offset + this_dir_offset); auto current = std::make_shared<VectorVfsDirectory>( diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h index 03a876d22..e54a7d7a9 100644 --- a/src/core/file_sys/romfs.h +++ b/src/core/file_sys/romfs.h @@ -6,6 +6,7 @@ #include <array> #include "common/common_funcs.h" +#include "common/common_types.h" #include "common/swap.h" #include "core/file_sys/vfs.h" diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp index eb4e6c865..3d1a3685e 100644 --- a/src/core/file_sys/romfs_factory.cpp +++ b/src/core/file_sys/romfs_factory.cpp @@ -2,11 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> #include <memory> +#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" @@ -20,10 +23,19 @@ 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(); } +RomFSFactory::~RomFSFactory() = default; + 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) { diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h index f38ddc4f7..2cace8180 100644 --- a/src/core/file_sys/romfs_factory.h +++ b/src/core/file_sys/romfs_factory.h @@ -30,12 +30,15 @@ enum class StorageId : u8 { class RomFSFactory { public: explicit RomFSFactory(Loader::AppLoader& app_loader); + ~RomFSFactory(); ResultVal<VirtualFile> OpenCurrentProcess(); ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type); private: VirtualFile file; + bool updatable; + u64 ivfc_offset; }; } // namespace FileSys diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index 952bd74b3..9b2c51bbd 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <memory> +#include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" #include "core/core.h" @@ -19,6 +20,8 @@ std::string SaveDataDescriptor::DebugInfo() const { SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {} +SaveDataFactory::~SaveDataFactory() = default; + ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescriptor meta) { if (meta.type == SaveDataType::SystemSaveData || meta.type == SaveDataType::SaveData) { if (meta.zero_1 != 0) { @@ -84,10 +87,10 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ switch (space) { case SaveDataSpaceId::NandSystem: - out = "/system/save/"; + out = "/system/"; break; case SaveDataSpaceId::NandUser: - out = "/user/save/"; + out = "/user/"; break; default: ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space)); @@ -95,9 +98,12 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ switch (type) { case SaveDataType::SystemSaveData: - return fmt::format("{}{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]); + return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]); case SaveDataType::SaveData: - return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], + return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], + title_id); + case SaveDataType::TemporaryStorage: + return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0], title_id); default: ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type)); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index c6f9549f0..d69ef6741 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -6,6 +6,7 @@ #include <memory> #include <string> +#include "common/common_funcs.h" #include "common/common_types.h" #include "common/swap.h" #include "core/file_sys/vfs.h" @@ -47,6 +48,7 @@ static_assert(sizeof(SaveDataDescriptor) == 0x40, "SaveDataDescriptor has incorr class SaveDataFactory { public: explicit SaveDataFactory(VirtualDir dir); + ~SaveDataFactory(); ResultVal<VirtualDir> Open(SaveDataSpaceId space, SaveDataDescriptor meta); diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp new file mode 100644 index 000000000..11264878d --- /dev/null +++ b/src/core/file_sys/submission_package.cpp @@ -0,0 +1,245 @@ +// Copyright 2018 yuzu emulator team +// 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/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" +#include "core/file_sys/submission_package.h" +#include "core/loader/loader.h" + +namespace FileSys { +NSP::NSP(VirtualFile file_) + : file(std::move(file_)), status{Loader::ResultStatus::Success}, + pfs(std::make_shared<PartitionFilesystem>(file)) { + if (pfs->GetStatus() != Loader::ResultStatus::Success) { + status = pfs->GetStatus(); + return; + } + + if (IsDirectoryExeFS(pfs)) { + extracted = true; + exefs = pfs; + + const auto& files = pfs->GetFiles(); + const auto romfs_iter = + std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) { + return file->GetName().find(".romfs") != std::string::npos; + }); + if (romfs_iter != files.end()) + romfs = *romfs_iter; + return; + } + + extracted = false; + const auto files = pfs->GetFiles(); + + Core::Crypto::KeyManager keys; + for (const auto& ticket_file : files) { + if (ticket_file->GetExtension() == "tik") { + if (ticket_file == nullptr || + ticket_file->GetSize() < + Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { + continue; + } + + Core::Crypto::Key128 key{}; + ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); + std::string_view name_only(ticket_file->GetName()); + name_only.remove_suffix(4); + const auto rights_id_raw = Common::HexStringToArray<16>(name_only); + u128 rights_id; + std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); + keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); + } + } + + for (const auto& outer_file : files) { + if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") { + const auto nca = std::make_shared<NCA>(outer_file); + if (nca->GetStatus() != Loader::ResultStatus::Success) { + program_status[nca->GetTitleId()] = nca->GetStatus(); + continue; + } + + const auto section0 = nca->GetSubdirectories()[0]; + + for (const auto& inner_file : section0->GetFiles()) { + if (inner_file->GetExtension() != "cnmt") + continue; + + const CNMT cnmt(inner_file); + auto& ncas_title = ncas[cnmt.GetTitleID()]; + + ncas_title[ContentRecordType::Meta] = nca; + for (const auto& rec : cnmt.GetContentRecords()) { + const auto id_string = Common::HexArrayToString(rec.nca_id, false); + const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); + if (next_file == nullptr) { + LOG_WARNING(Service_FS, + "NCA with ID {}.nca is listed in content metadata, but cannot " + "be found in PFS. NSP appears to be corrupted.", + id_string); + continue; + } + + auto next_nca = std::make_shared<NCA>(next_file); + if (next_nca->GetType() == NCAContentType::Program) + program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); + if (next_nca->GetStatus() == Loader::ResultStatus::Success) + ncas_title[rec.type] = std::move(next_nca); + } + + break; + } + } + } +} + +NSP::~NSP() = default; + +Loader::ResultStatus NSP::GetStatus() const { + return status; +} + +Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { + const auto iter = program_status.find(title_id); + if (iter == program_status.end()) + return Loader::ResultStatus::ErrorNSPMissingProgramNCA; + return iter->second; +} + +u64 NSP::GetFirstTitleID() const { + if (program_status.empty()) + return 0; + return program_status.begin()->first; +} + +u64 NSP::GetProgramTitleID() const { + const auto out = GetFirstTitleID(); + if ((out & 0x800) == 0) + return out; + + const auto ids = GetTitleIDs(); + const auto iter = + std::find_if(ids.begin(), ids.end(), [](u64 tid) { return (tid & 0x800) == 0; }); + return iter == ids.end() ? out : *iter; +} + +std::vector<u64> NSP::GetTitleIDs() const { + std::vector<u64> out; + out.reserve(ncas.size()); + for (const auto& kv : ncas) + out.push_back(kv.first); + return out; +} + +bool NSP::IsExtractedType() const { + return extracted; +} + +VirtualFile NSP::GetRomFS() const { + return romfs; +} + +VirtualDir NSP::GetExeFS() const { + return exefs; +} + +std::vector<std::shared_ptr<NCA>> NSP::GetNCAsCollapsed() const { + if (extracted) + LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); + std::vector<std::shared_ptr<NCA>> out; + for (const auto& map : ncas) { + for (const auto& inner_map : map.second) + out.push_back(inner_map.second); + } + return out; +} + +std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const { + if (extracted) + LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); + std::multimap<u64, std::shared_ptr<NCA>> out; + for (const auto& map : ncas) { + for (const auto& inner_map : map.second) + out.emplace(map.first, inner_map.second); + } + return out; +} + +std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const { + return ncas; +} + +std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const { + if (extracted) + LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); + + const auto title_id_iter = ncas.find(title_id); + if (title_id_iter == ncas.end()) + return nullptr; + + const auto type_iter = title_id_iter->second.find(type); + if (type_iter == title_id_iter->second.end()) + return nullptr; + + return type_iter->second; +} + +VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const { + if (extracted) + LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); + const auto nca = GetNCA(title_id, type); + if (nca != nullptr) + return nca->GetBaseFile(); + return nullptr; +} + +std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const { + if (extracted) + LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); + std::vector<Core::Crypto::Key128> out; + for (const auto& ticket_file : ticket_files) { + if (ticket_file == nullptr || + ticket_file->GetSize() < + Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { + continue; + } + + out.emplace_back(); + ticket_file->Read(out.back().data(), out.back().size(), + Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); + } + return out; +} + +std::vector<VirtualFile> NSP::GetFiles() const { + return pfs->GetFiles(); +} + +std::vector<VirtualDir> NSP::GetSubdirectories() const { + return pfs->GetSubdirectories(); +} + +std::string NSP::GetName() const { + return file->GetName(); +} + +VirtualDir NSP::GetParentDirectory() const { + return file->GetContainingDirectory(); +} + +bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { + return false; +} +} // namespace FileSys diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h new file mode 100644 index 000000000..e85a2b76e --- /dev/null +++ b/src/core/file_sys/submission_package.h @@ -0,0 +1,76 @@ +// 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 <vector> +#include "common/common_types.h" +#include "core/file_sys/vfs.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); + ~NSP() override; + + Loader::ResultStatus GetStatus() const; + Loader::ResultStatus GetProgramStatus(u64 title_id) const; + // Should only be used when one title id can be assured. + u64 GetFirstTitleID() const; + u64 GetProgramTitleID() const; + std::vector<u64> GetTitleIDs() const; + + bool IsExtractedType() const; + + // Common (Can be safely called on both types) + VirtualFile GetRomFS() const; + VirtualDir GetExeFS() const; + + // Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML) + std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const; + std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const; + std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const; + std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const; + VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const; + std::vector<Core::Crypto::Key128> GetTitlekey() const; + + std::vector<VirtualFile> GetFiles() const override; + + std::vector<VirtualDir> GetSubdirectories() const override; + + std::string GetName() const override; + + VirtualDir GetParentDirectory() const override; + +protected: + bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; + +private: + VirtualFile file; + + bool extracted; + Loader::ResultStatus status; + std::map<u64, Loader::ResultStatus> program_status; + + std::shared_ptr<PartitionFilesystem> pfs; + // Map title id -> {map type -> NCA} + std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas; + std::vector<VirtualFile> ticket_files; + + VirtualFile romfs; + VirtualDir exefs; +}; +} // namespace FileSys diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 146c839f4..d7b52abfd 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -167,18 +167,18 @@ std::string VfsFile::GetExtension() const { VfsDirectory::~VfsDirectory() = default; -boost::optional<u8> VfsFile::ReadByte(size_t offset) const { +boost::optional<u8> VfsFile::ReadByte(std::size_t offset) const { u8 out{}; - size_t size = Read(&out, 1, offset); + std::size_t size = Read(&out, 1, offset); if (size == 1) return out; return boost::none; } -std::vector<u8> VfsFile::ReadBytes(size_t size, size_t offset) const { +std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const { std::vector<u8> out(size); - size_t read_size = Read(out.data(), size, offset); + std::size_t read_size = Read(out.data(), size, offset); out.resize(read_size); return out; } @@ -187,11 +187,11 @@ std::vector<u8> VfsFile::ReadAllBytes() const { return ReadBytes(GetSize()); } -bool VfsFile::WriteByte(u8 data, size_t offset) { +bool VfsFile::WriteByte(u8 data, std::size_t offset) { return Write(&data, 1, offset) == 1; } -size_t VfsFile::WriteBytes(const std::vector<u8>& data, size_t offset) { +std::size_t VfsFile::WriteBytes(const std::vector<u8>& data, std::size_t offset) { return Write(data.data(), data.size(), offset); } @@ -215,7 +215,7 @@ std::shared_ptr<VfsFile> VfsDirectory::GetFileRelative(std::string_view path) co } auto dir = GetSubdirectory(vec[0]); - for (size_t component = 1; component < vec.size() - 1; ++component) { + for (std::size_t component = 1; component < vec.size() - 1; ++component) { if (dir == nullptr) { return nullptr; } @@ -249,7 +249,7 @@ std::shared_ptr<VfsDirectory> VfsDirectory::GetDirectoryRelative(std::string_vie } auto dir = GetSubdirectory(vec[0]); - for (size_t component = 1; component < vec.size(); ++component) { + for (std::size_t component = 1; component < vec.size(); ++component) { if (dir == nullptr) { return nullptr; } @@ -286,7 +286,7 @@ bool VfsDirectory::IsRoot() const { return GetParentDirectory() == nullptr; } -size_t VfsDirectory::GetSize() const { +std::size_t VfsDirectory::GetSize() const { const auto& files = GetFiles(); const auto sum_sizes = [](const auto& range) { return std::accumulate(range.begin(), range.end(), 0ULL, @@ -434,13 +434,13 @@ bool ReadOnlyVfsDirectory::Rename(std::string_view name) { return false; } -bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size) { +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size) { if (file1->GetSize() != file2->GetSize()) return false; std::vector<u8> f1_v(block_size); std::vector<u8> f2_v(block_size); - for (size_t i = 0; i < file1->GetSize(); i += block_size) { + for (std::size_t i = 0; i < file1->GetSize(); i += block_size) { auto f1_vs = file1->Read(f1_v.data(), block_size, i); auto f2_vs = file2->Read(f2_v.data(), block_size, i); diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h index 5142a3e86..74489b452 100644 --- a/src/core/file_sys/vfs.h +++ b/src/core/file_sys/vfs.h @@ -92,9 +92,9 @@ public: // Retrieves the extension of the file name. virtual std::string GetExtension() const; // Retrieves the size of the file. - virtual size_t GetSize() const = 0; + virtual std::size_t GetSize() const = 0; // Resizes the file to new_size. Returns whether or not the operation was successful. - virtual bool Resize(size_t new_size) = 0; + virtual bool Resize(std::size_t new_size) = 0; // Gets a pointer to the directory containing this file, returning nullptr if there is none. virtual std::shared_ptr<VfsDirectory> GetContainingDirectory() const = 0; @@ -105,15 +105,15 @@ public: // The primary method of reading from the file. Reads length bytes into data starting at offset // into file. Returns number of bytes successfully read. - virtual size_t Read(u8* data, size_t length, size_t offset = 0) const = 0; + virtual std::size_t Read(u8* data, std::size_t length, std::size_t offset = 0) const = 0; // The primary method of writing to the file. Writes length bytes from data starting at offset // into file. Returns number of bytes successfully written. - virtual size_t Write(const u8* data, size_t length, size_t offset = 0) = 0; + virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0; // Reads exactly one byte at the offset provided, returning boost::none on error. - virtual boost::optional<u8> ReadByte(size_t offset = 0) const; + virtual boost::optional<u8> ReadByte(std::size_t offset = 0) const; // Reads size bytes starting at offset in file into a vector. - virtual std::vector<u8> ReadBytes(size_t size, size_t offset = 0) const; + virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const; // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(), // 0)' virtual std::vector<u8> ReadAllBytes() const; @@ -121,7 +121,7 @@ public: // Reads an array of type T, size number_elements starting at offset. // Returns the number of bytes (sizeof(T)*number_elements) read successfully. template <typename T> - size_t ReadArray(T* data, size_t number_elements, size_t offset = 0) const { + std::size_t ReadArray(T* data, std::size_t number_elements, std::size_t offset = 0) const { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Read(reinterpret_cast<u8*>(data), number_elements * sizeof(T), offset); @@ -130,7 +130,7 @@ public: // Reads size bytes into the memory starting at data starting at offset into the file. // Returns the number of bytes read successfully. template <typename T> - size_t ReadBytes(T* data, size_t size, size_t offset = 0) const { + std::size_t ReadBytes(T* data, std::size_t size, std::size_t offset = 0) const { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Read(reinterpret_cast<u8*>(data), size, offset); } @@ -138,22 +138,22 @@ public: // Reads one object of type T starting at offset in file. // Returns the number of bytes read successfully (sizeof(T)). template <typename T> - size_t ReadObject(T* data, size_t offset = 0) const { + std::size_t ReadObject(T* data, std::size_t offset = 0) const { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Read(reinterpret_cast<u8*>(data), sizeof(T), offset); } // Writes exactly one byte to offset in file and retuns whether or not the byte was written // successfully. - virtual bool WriteByte(u8 data, size_t offset = 0); + virtual bool WriteByte(u8 data, std::size_t offset = 0); // Writes a vector of bytes to offset in file and returns the number of bytes successfully // written. - virtual size_t WriteBytes(const std::vector<u8>& data, size_t offset = 0); + virtual std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset = 0); // Writes an array of type T, size number_elements to offset in file. // Returns the number of bytes (sizeof(T)*number_elements) written successfully. template <typename T> - size_t WriteArray(const T* data, size_t number_elements, size_t offset = 0) { + std::size_t WriteArray(const T* data, std::size_t number_elements, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Write(data, number_elements * sizeof(T), offset); } @@ -161,7 +161,7 @@ public: // Writes size bytes starting at memory location data to offset in file. // Returns the number of bytes written successfully. template <typename T> - size_t WriteBytes(const T* data, size_t size, size_t offset = 0) { + std::size_t WriteBytes(const T* data, std::size_t size, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Write(reinterpret_cast<const u8*>(data), size, offset); } @@ -169,7 +169,7 @@ public: // Writes one object of type T to offset in file. // Returns the number of bytes written successfully (sizeof(T)). template <typename T> - size_t WriteObject(const T& data, size_t offset = 0) { + std::size_t WriteObject(const T& data, std::size_t offset = 0) { static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable."); return Write(&data, sizeof(T), offset); } @@ -221,7 +221,7 @@ public: // Returns the name of the directory. virtual std::string GetName() const = 0; // Returns the total size of all files and subdirectories in this directory. - virtual size_t GetSize() const; + virtual std::size_t GetSize() const; // Returns the parent directory of this directory. Returns nullptr if this directory is root or // has no parent. virtual std::shared_ptr<VfsDirectory> GetParentDirectory() const = 0; @@ -311,7 +311,7 @@ public: }; // Compare the two files, byte-for-byte, in increments specificed by block_size -bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, size_t block_size = 0x200); +bool DeepEquals(const VirtualFile& file1, const VirtualFile& file2, std::size_t block_size = 0x200); // A method that copies the raw data between two different implementations of VirtualFile. If you // are using the same implementation, it is probably better to use the Copy method in the parent diff --git a/src/core/file_sys/vfs_concat.cpp b/src/core/file_sys/vfs_concat.cpp index e6bf586a3..dc7a279a9 100644 --- a/src/core/file_sys/vfs_concat.cpp +++ b/src/core/file_sys/vfs_concat.cpp @@ -20,13 +20,15 @@ VirtualFile ConcatenateFiles(std::vector<VirtualFile> files, std::string name) { ConcatenatedVfsFile::ConcatenatedVfsFile(std::vector<VirtualFile> files_, std::string name) : name(std::move(name)) { - size_t next_offset = 0; + std::size_t next_offset = 0; for (const auto& file : files_) { files[next_offset] = file; next_offset += file->GetSize(); } } +ConcatenatedVfsFile::~ConcatenatedVfsFile() = default; + std::string ConcatenatedVfsFile::GetName() const { if (files.empty()) return ""; @@ -35,13 +37,13 @@ std::string ConcatenatedVfsFile::GetName() const { return files.begin()->second->GetName(); } -size_t ConcatenatedVfsFile::GetSize() const { +std::size_t ConcatenatedVfsFile::GetSize() const { if (files.empty()) return 0; return files.rbegin()->first + files.rbegin()->second->GetSize(); } -bool ConcatenatedVfsFile::Resize(size_t new_size) { +bool ConcatenatedVfsFile::Resize(std::size_t new_size) { return false; } @@ -59,7 +61,7 @@ bool ConcatenatedVfsFile::IsReadable() const { return true; } -size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const { +std::size_t ConcatenatedVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { auto entry = files.end(); for (auto iter = files.begin(); iter != files.end(); ++iter) { if (iter->first > offset) { @@ -84,7 +86,7 @@ size_t ConcatenatedVfsFile::Read(u8* data, size_t length, size_t offset) const { return entry->second->Read(data, length, offset - entry->first); } -size_t ConcatenatedVfsFile::Write(const u8* data, size_t length, size_t offset) { +std::size_t ConcatenatedVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { return 0; } diff --git a/src/core/file_sys/vfs_concat.h b/src/core/file_sys/vfs_concat.h index 686d32515..717d04bdc 100644 --- a/src/core/file_sys/vfs_concat.h +++ b/src/core/file_sys/vfs_concat.h @@ -22,14 +22,16 @@ class ConcatenatedVfsFile : public VfsFile { ConcatenatedVfsFile(std::vector<VirtualFile> files, std::string name); public: + ~ConcatenatedVfsFile() override; + std::string GetName() const override; - size_t GetSize() const override; - bool Resize(size_t new_size) override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; - size_t Read(u8* data, size_t length, size_t offset) const override; - size_t Write(const u8* data, size_t length, size_t offset) override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; bool Rename(std::string_view name) override; private: diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp index 847cde2f5..a4c6719a0 100644 --- a/src/core/file_sys/vfs_offset.cpp +++ b/src/core/file_sys/vfs_offset.cpp @@ -9,20 +9,22 @@ namespace FileSys { -OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, size_t size_, size_t offset_, +OffsetVfsFile::OffsetVfsFile(std::shared_ptr<VfsFile> file_, std::size_t size_, std::size_t offset_, std::string name_, VirtualDir parent_) : file(file_), offset(offset_), size(size_), name(std::move(name_)), parent(parent_ == nullptr ? file->GetContainingDirectory() : std::move(parent_)) {} +OffsetVfsFile::~OffsetVfsFile() = default; + std::string OffsetVfsFile::GetName() const { return name.empty() ? file->GetName() : name; } -size_t OffsetVfsFile::GetSize() const { +std::size_t OffsetVfsFile::GetSize() const { return size; } -bool OffsetVfsFile::Resize(size_t new_size) { +bool OffsetVfsFile::Resize(std::size_t new_size) { if (offset + new_size < file->GetSize()) { size = new_size; } else { @@ -47,22 +49,22 @@ bool OffsetVfsFile::IsReadable() const { return file->IsReadable(); } -size_t OffsetVfsFile::Read(u8* data, size_t length, size_t r_offset) const { +std::size_t OffsetVfsFile::Read(u8* data, std::size_t length, std::size_t r_offset) const { return file->Read(data, TrimToFit(length, r_offset), offset + r_offset); } -size_t OffsetVfsFile::Write(const u8* data, size_t length, size_t r_offset) { +std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t r_offset) { return file->Write(data, TrimToFit(length, r_offset), offset + r_offset); } -boost::optional<u8> OffsetVfsFile::ReadByte(size_t r_offset) const { +boost::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const { if (r_offset < size) return file->ReadByte(offset + r_offset); return boost::none; } -std::vector<u8> OffsetVfsFile::ReadBytes(size_t r_size, size_t r_offset) const { +std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const { return file->ReadBytes(TrimToFit(r_size, r_offset), offset + r_offset); } @@ -70,14 +72,14 @@ std::vector<u8> OffsetVfsFile::ReadAllBytes() const { return file->ReadBytes(size, offset); } -bool OffsetVfsFile::WriteByte(u8 data, size_t r_offset) { +bool OffsetVfsFile::WriteByte(u8 data, std::size_t r_offset) { if (r_offset < size) return file->WriteByte(data, offset + r_offset); return false; } -size_t OffsetVfsFile::WriteBytes(const std::vector<u8>& data, size_t r_offset) { +std::size_t OffsetVfsFile::WriteBytes(const std::vector<u8>& data, std::size_t r_offset) { return file->Write(data.data(), TrimToFit(data.size(), r_offset), offset + r_offset); } @@ -85,12 +87,12 @@ bool OffsetVfsFile::Rename(std::string_view name) { return file->Rename(name); } -size_t OffsetVfsFile::GetOffset() const { +std::size_t OffsetVfsFile::GetOffset() const { return offset; } -size_t OffsetVfsFile::TrimToFit(size_t r_size, size_t r_offset) const { - return std::clamp(r_size, size_t{0}, size - r_offset); +std::size_t OffsetVfsFile::TrimToFit(std::size_t r_size, std::size_t r_offset) const { + return std::clamp(r_size, std::size_t{0}, size - r_offset); } } // namespace FileSys diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h index cb92d1570..8062702a7 100644 --- a/src/core/file_sys/vfs_offset.h +++ b/src/core/file_sys/vfs_offset.h @@ -17,33 +17,34 @@ namespace FileSys { // the size of this wrapper. class OffsetVfsFile : public VfsFile { public: - OffsetVfsFile(std::shared_ptr<VfsFile> file, size_t size, size_t offset = 0, + OffsetVfsFile(std::shared_ptr<VfsFile> file, std::size_t size, std::size_t offset = 0, std::string new_name = "", VirtualDir new_parent = nullptr); + ~OffsetVfsFile() override; std::string GetName() const override; - size_t GetSize() const override; - bool Resize(size_t new_size) override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; - size_t Read(u8* data, size_t length, size_t offset) const override; - size_t Write(const u8* data, size_t length, size_t offset) override; - boost::optional<u8> ReadByte(size_t offset) const override; - std::vector<u8> ReadBytes(size_t size, size_t offset) const override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; + boost::optional<u8> ReadByte(std::size_t offset) const override; + std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override; std::vector<u8> ReadAllBytes() const override; - bool WriteByte(u8 data, size_t offset) override; - size_t WriteBytes(const std::vector<u8>& data, size_t offset) override; + bool WriteByte(u8 data, std::size_t offset) override; + std::size_t WriteBytes(const std::vector<u8>& data, std::size_t offset) override; bool Rename(std::string_view name) override; - size_t GetOffset() const; + std::size_t GetOffset() const; private: - size_t TrimToFit(size_t r_size, size_t r_offset) const; + std::size_t TrimToFit(std::size_t r_size, std::size_t r_offset) const; std::shared_ptr<VfsFile> file; - size_t offset; - size_t size; + std::size_t offset; + std::size_t size; std::string name; VirtualDir parent; }; diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 2b8ac7103..5e242e20f 100644 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -8,6 +8,7 @@ #include <utility> #include "common/assert.h" #include "common/common_paths.h" +#include "common/file_util.h" #include "common/logging/log.h" #include "core/file_sys/vfs_real.h" @@ -39,6 +40,7 @@ static std::string ModeFlagsToString(Mode mode) { } RealVfsFilesystem::RealVfsFilesystem() : VfsFilesystem(nullptr) {} +RealVfsFilesystem::~RealVfsFilesystem() = default; std::string RealVfsFilesystem::GetName() const { return "Real"; @@ -219,15 +221,17 @@ RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::shared_ptr<FileUtil::IOF parent_components(FileUtil::SliceVector(path_components, 0, path_components.size() - 1)), perms(perms_) {} +RealVfsFile::~RealVfsFile() = default; + std::string RealVfsFile::GetName() const { return path_components.back(); } -size_t RealVfsFile::GetSize() const { +std::size_t RealVfsFile::GetSize() const { return backing->GetSize(); } -bool RealVfsFile::Resize(size_t new_size) { +bool RealVfsFile::Resize(std::size_t new_size) { return backing->Resize(new_size); } @@ -243,13 +247,13 @@ bool RealVfsFile::IsReadable() const { return (perms & Mode::ReadWrite) != 0; } -size_t RealVfsFile::Read(u8* data, size_t length, size_t offset) const { +std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) const { if (!backing->Seek(offset, SEEK_SET)) return 0; return backing->ReadBytes(data, length); } -size_t RealVfsFile::Write(const u8* data, size_t length, size_t offset) { +std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { if (!backing->Seek(offset, SEEK_SET)) return 0; return backing->WriteBytes(data, length); @@ -312,6 +316,8 @@ RealVfsDirectory::RealVfsDirectory(RealVfsFilesystem& base_, const std::string& FileUtil::CreateDir(path); } +RealVfsDirectory::~RealVfsDirectory() = default; + std::shared_ptr<VfsFile> RealVfsDirectory::GetFileRelative(std::string_view path) const { const auto full_path = FileUtil::SanitizePath(this->path + DIR_SEP + std::string(path)); if (!FileUtil::Exists(full_path) || FileUtil::IsDirectory(full_path)) diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index 989803d43..681c43e82 100644 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -6,15 +6,19 @@ #include <string_view> #include <boost/container/flat_map.hpp> -#include "common/file_util.h" #include "core/file_sys/mode.h" #include "core/file_sys/vfs.h" +namespace FileUtil { +class IOFile; +} + namespace FileSys { class RealVfsFilesystem : public VfsFilesystem { public: RealVfsFilesystem(); + ~RealVfsFilesystem() override; std::string GetName() const override; bool IsReadable() const override; @@ -40,21 +44,23 @@ class RealVfsFile : public VfsFile { friend class RealVfsDirectory; friend class RealVfsFilesystem; - RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing, - const std::string& path, Mode perms = Mode::Read); - public: + ~RealVfsFile() override; + std::string GetName() const override; - size_t GetSize() const override; - bool Resize(size_t new_size) override; + std::size_t GetSize() const override; + bool Resize(std::size_t new_size) override; std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; bool IsWritable() const override; bool IsReadable() const override; - size_t Read(u8* data, size_t length, size_t offset) const override; - size_t Write(const u8* data, size_t length, size_t offset) override; + std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override; + std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override; bool Rename(std::string_view name) override; private: + RealVfsFile(RealVfsFilesystem& base, std::shared_ptr<FileUtil::IOFile> backing, + const std::string& path, Mode perms = Mode::Read); + bool Close(); RealVfsFilesystem& base; @@ -70,9 +76,9 @@ private: class RealVfsDirectory : public VfsDirectory { friend class RealVfsFilesystem; - RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read); - public: + ~RealVfsDirectory() override; + std::shared_ptr<VfsFile> GetFileRelative(std::string_view path) const override; std::shared_ptr<VfsDirectory> GetDirectoryRelative(std::string_view path) const override; std::shared_ptr<VfsFile> GetFile(std::string_view name) const override; @@ -97,6 +103,8 @@ protected: bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; private: + RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read); + template <typename T, typename R> std::vector<std::shared_ptr<R>> IterateEntries() const; diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp index 98e7c4598..ec7f735b5 100644 --- a/src/core/file_sys/vfs_vector.cpp +++ b/src/core/file_sys/vfs_vector.cpp @@ -13,6 +13,8 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_, : files(std::move(files_)), dirs(std::move(dirs_)), parent(std::move(parent_)), name(std::move(name_)) {} +VectorVfsDirectory::~VectorVfsDirectory() = default; + std::vector<std::shared_ptr<VfsFile>> VectorVfsDirectory::GetFiles() const { return files; } diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h index 179f62e4b..cba44a7a6 100644 --- a/src/core/file_sys/vfs_vector.h +++ b/src/core/file_sys/vfs_vector.h @@ -15,6 +15,7 @@ public: explicit VectorVfsDirectory(std::vector<VirtualFile> files = {}, std::vector<VirtualDir> dirs = {}, std::string name = "", VirtualDir parent = nullptr); + ~VectorVfsDirectory() override; std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; std::vector<std::shared_ptr<VfsDirectory>> GetSubdirectories() const override; diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp index 552835738..b2b164368 100644 --- a/src/core/file_sys/xts_archive.cpp +++ b/src/core/file_sys/xts_archive.cpp @@ -10,6 +10,7 @@ #include <mbedtls/md.h> #include <mbedtls/sha256.h> #include "common/assert.h" +#include "common/file_util.h" #include "common/hex_util.h" #include "common/logging/log.h" #include "core/crypto/aes_util.h" @@ -24,14 +25,11 @@ namespace FileSys { constexpr u64 NAX_HEADER_PADDING_SIZE = 0x4000; template <typename SourceData, typename SourceKey, typename Destination> -static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_length, - const SourceData* data, size_t data_length) { +static bool CalculateHMAC256(Destination* out, const SourceKey* key, std::size_t key_length, + const SourceData* data, std::size_t data_length) { mbedtls_md_context_t context; mbedtls_md_init(&context); - const auto key_f = reinterpret_cast<const u8*>(key); - const std::vector<u8> key_v(key_f, key_f + key_length); - if (mbedtls_md_setup(&context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), 1) || mbedtls_md_hmac_starts(&context, reinterpret_cast<const u8*>(key), key_length) || mbedtls_md_hmac_update(&context, reinterpret_cast<const u8*>(data), data_length) || @@ -44,7 +42,7 @@ static bool CalculateHMAC256(Destination* out, const SourceKey* key, size_t key_ return true; } -NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<NAXHeader>()) { +NAX::NAX(VirtualFile file_) : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { std::string path = FileUtil::SanitizePath(file->GetFullPath()); static const std::regex nax_path_regex("/registered/(000000[0-9A-F]{2})/([0-9A-F]{32})\\.nca", std::regex_constants::ECMAScript | @@ -64,13 +62,15 @@ NAX::NAX(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<NA } NAX::NAX(VirtualFile file_, std::array<u8, 0x10> nca_id) - : file(std::move(file_)), header(std::make_unique<NAXHeader>()) { + : header(std::make_unique<NAXHeader>()), file(std::move(file_)) { Core::Crypto::SHA256Hash hash{}; mbedtls_sha256(nca_id.data(), nca_id.size(), hash.data(), 0); status = Parse(fmt::format("/registered/000000{:02X}/{}.nca", hash[0], Common::HexArrayToString(nca_id, false))); } +NAX::~NAX() = default; + Loader::ResultStatus NAX::Parse(std::string_view path) { if (file->ReadObject(header.get()) != sizeof(NAXHeader)) return Loader::ResultStatus::ErrorBadNAXHeader; @@ -90,7 +90,7 @@ Loader::ResultStatus NAX::Parse(std::string_view path) { const auto enc_keys = header->key_area; - size_t i = 0; + std::size_t i = 0; for (; i < sd_keys.size(); ++i) { std::array<Core::Crypto::Key128, 2> nax_keys{}; if (!CalculateHMAC256(nax_keys.data(), sd_keys[i].data(), 0x10, std::string(path).c_str(), @@ -98,7 +98,7 @@ Loader::ResultStatus NAX::Parse(std::string_view path) { return Loader::ResultStatus::ErrorNAXKeyHMACFailed; } - for (size_t j = 0; j < nax_keys.size(); ++j) { + for (std::size_t j = 0; j < nax_keys.size(); ++j) { Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(nax_keys[j], Core::Crypto::Mode::ECB); cipher.Transcode(enc_keys[j].data(), 0x10, header->key_area[j].data(), @@ -137,9 +137,9 @@ VirtualFile NAX::GetDecrypted() const { return dec_file; } -std::shared_ptr<NCA> NAX::AsNCA() const { +std::unique_ptr<NCA> NAX::AsNCA() const { if (type == NAXContentType::NCA) - return std::make_shared<NCA>(GetDecrypted()); + return std::make_unique<NCA>(GetDecrypted()); return nullptr; } diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h index 55d2154a6..8fedd8585 100644 --- a/src/core/file_sys/xts_archive.h +++ b/src/core/file_sys/xts_archive.h @@ -33,12 +33,13 @@ class NAX : public ReadOnlyVfsDirectory { public: explicit NAX(VirtualFile file); explicit NAX(VirtualFile file, std::array<u8, 0x10> nca_id); + ~NAX() override; Loader::ResultStatus GetStatus() const; VirtualFile GetDecrypted() const; - std::shared_ptr<NCA> AsNCA() const; + std::unique_ptr<NCA> AsNCA() const; NAXContentType GetContentType() const; @@ -60,7 +61,7 @@ private: VirtualFile file; Loader::ResultStatus status; - NAXContentType type; + NAXContentType type{}; VirtualFile dec_file; diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 332e5c3d0..0ecdd9f82 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -65,9 +65,9 @@ constexpr u32 MSG_WAITALL = 8; constexpr u32 LR_REGISTER = 30; constexpr u32 SP_REGISTER = 31; constexpr u32 PC_REGISTER = 32; -constexpr u32 CPSR_REGISTER = 33; +constexpr u32 PSTATE_REGISTER = 33; constexpr u32 UC_ARM64_REG_Q0 = 34; -constexpr u32 FPSCR_REGISTER = 66; +constexpr u32 FPCR_REGISTER = 66; // TODO/WiP - Used while working on support for FPU constexpr u32 TODO_DUMMY_REG_997 = 997; @@ -116,7 +116,7 @@ constexpr char target_xml[] = <reg name="pc" bitsize="64" type="code_ptr"/> - <flags id="cpsr_flags" size="4"> + <flags id="pstate_flags" size="4"> <field name="SP" start="0" end="0"/> <field name="" start="1" end="1"/> <field name="EL" start="2" end="3"/> @@ -135,7 +135,7 @@ constexpr char target_xml[] = <field name="Z" start="30" end="30"/> <field name="N" start="31" end="31"/> </flags> - <reg name="cpsr" bitsize="32" type="cpsr_flags"/> + <reg name="pstate" bitsize="32" type="pstate_flags"/> </feature> <feature name="org.gnu.gdb.aarch64.fpu"> </feature> @@ -227,10 +227,10 @@ static u64 RegRead(std::size_t id, Kernel::Thread* thread = nullptr) { return thread->context.sp; } else if (id == PC_REGISTER) { return thread->context.pc; - } else if (id == CPSR_REGISTER) { - return thread->context.cpsr; - } else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) { - return thread->context.fpu_registers[id - UC_ARM64_REG_Q0][0]; + } else if (id == PSTATE_REGISTER) { + return thread->context.pstate; + } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { + return thread->context.vector_registers[id - UC_ARM64_REG_Q0][0]; } else { return 0; } @@ -247,10 +247,10 @@ static void RegWrite(std::size_t id, u64 val, Kernel::Thread* thread = nullptr) thread->context.sp = val; } else if (id == PC_REGISTER) { thread->context.pc = val; - } else if (id == CPSR_REGISTER) { - thread->context.cpsr = val; - } else if (id > CPSR_REGISTER && id < FPSCR_REGISTER) { - thread->context.fpu_registers[id - (CPSR_REGISTER + 1)][0] = val; + } else if (id == PSTATE_REGISTER) { + thread->context.pstate = val; + } else if (id > PSTATE_REGISTER && id < FPCR_REGISTER) { + thread->context.vector_registers[id - (PSTATE_REGISTER + 1)][0] = val; } } @@ -292,7 +292,7 @@ static u8 NibbleToHex(u8 n) { * @param src Pointer to array of output hex string characters. * @param len Length of src array. */ -static u32 HexToInt(const u8* src, size_t len) { +static u32 HexToInt(const u8* src, std::size_t len) { u32 output = 0; while (len-- > 0) { output = (output << 4) | HexCharToValue(src[0]); @@ -307,7 +307,7 @@ static u32 HexToInt(const u8* src, size_t len) { * @param src Pointer to array of output hex string characters. * @param len Length of src array. */ -static u64 HexToLong(const u8* src, size_t len) { +static u64 HexToLong(const u8* src, std::size_t len) { u64 output = 0; while (len-- > 0) { output = (output << 4) | HexCharToValue(src[0]); @@ -323,7 +323,7 @@ static u64 HexToLong(const u8* src, size_t len) { * @param src Pointer to array of u8 bytes. * @param len Length of src array. */ -static void MemToGdbHex(u8* dest, const u8* src, size_t len) { +static void MemToGdbHex(u8* dest, const u8* src, std::size_t len) { while (len-- > 0) { u8 tmp = *src++; *dest++ = NibbleToHex(tmp >> 4); @@ -338,7 +338,7 @@ static void MemToGdbHex(u8* dest, const u8* src, size_t len) { * @param src Pointer to array of output hex string characters. * @param len Length of src array. */ -static void GdbHexToMem(u8* dest, const u8* src, size_t len) { +static void GdbHexToMem(u8* dest, const u8* src, std::size_t len) { while (len-- > 0) { *dest++ = (HexCharToValue(src[0]) << 4) | HexCharToValue(src[1]); src += 2; @@ -406,7 +406,7 @@ static u64 GdbHexToLong(const u8* src) { /// Read a byte from the gdb client. static u8 ReadByte() { u8 c; - size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL); + std::size_t received_size = recv(gdbserver_socket, reinterpret_cast<char*>(&c), 1, MSG_WAITALL); if (received_size != 1) { LOG_ERROR(Debug_GDBStub, "recv failed: {}", received_size); Shutdown(); @@ -416,7 +416,7 @@ static u8 ReadByte() { } /// Calculate the checksum of the current command buffer. -static u8 CalculateChecksum(const u8* buffer, size_t length) { +static u8 CalculateChecksum(const u8* buffer, std::size_t length) { return static_cast<u8>(std::accumulate(buffer, buffer + length, 0, std::plus<u8>())); } @@ -518,7 +518,7 @@ bool CheckBreakpoint(VAddr addr, BreakpointType type) { * @param packet Packet to be sent to client. */ static void SendPacket(const char packet) { - size_t sent_size = send(gdbserver_socket, &packet, 1, 0); + std::size_t sent_size = send(gdbserver_socket, &packet, 1, 0); if (sent_size != 1) { LOG_ERROR(Debug_GDBStub, "send failed"); } @@ -781,11 +781,11 @@ static void ReadRegister() { LongToGdbHex(reply, RegRead(id, current_thread)); } else if (id == PC_REGISTER) { LongToGdbHex(reply, RegRead(id, current_thread)); - } else if (id == CPSR_REGISTER) { - IntToGdbHex(reply, (u32)RegRead(id, current_thread)); - } else if (id >= UC_ARM64_REG_Q0 && id < FPSCR_REGISTER) { + } else if (id == PSTATE_REGISTER) { + IntToGdbHex(reply, static_cast<u32>(RegRead(id, current_thread))); + } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { LongToGdbHex(reply, RegRead(id, current_thread)); - } else if (id == FPSCR_REGISTER) { + } else if (id == FPCR_REGISTER) { LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_998, current_thread)); } else { LongToGdbHex(reply, RegRead(TODO_DUMMY_REG_997, current_thread)); @@ -811,7 +811,7 @@ static void ReadRegisters() { bufptr += 16; - IntToGdbHex(bufptr, (u32)RegRead(CPSR_REGISTER, current_thread)); + IntToGdbHex(bufptr, static_cast<u32>(RegRead(PSTATE_REGISTER, current_thread))); bufptr += 8; @@ -843,11 +843,11 @@ static void WriteRegister() { RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); } else if (id == PC_REGISTER) { RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); - } else if (id == CPSR_REGISTER) { + } else if (id == PSTATE_REGISTER) { RegWrite(id, GdbHexToInt(buffer_ptr), current_thread); - } else if (id >= UC_ARM64_REG_Q0 && id < FPSCR_REGISTER) { + } else if (id >= UC_ARM64_REG_Q0 && id < FPCR_REGISTER) { RegWrite(id, GdbHexToLong(buffer_ptr), current_thread); - } else if (id == FPSCR_REGISTER) { + } else if (id == FPCR_REGISTER) { RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr), current_thread); } else { RegWrite(TODO_DUMMY_REG_997, GdbHexToLong(buffer_ptr), current_thread); @@ -866,16 +866,16 @@ static void WriteRegisters() { if (command_buffer[0] != 'G') return SendReply("E01"); - for (u32 i = 0, reg = 0; reg <= FPSCR_REGISTER; i++, reg++) { + for (u32 i = 0, reg = 0; reg <= FPCR_REGISTER; i++, reg++) { if (reg <= SP_REGISTER) { RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); } else if (reg == PC_REGISTER) { RegWrite(PC_REGISTER, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == CPSR_REGISTER) { - RegWrite(CPSR_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread); - } else if (reg >= UC_ARM64_REG_Q0 && reg < FPSCR_REGISTER) { + } else if (reg == PSTATE_REGISTER) { + RegWrite(PSTATE_REGISTER, GdbHexToInt(buffer_ptr + i * 16), current_thread); + } else if (reg >= UC_ARM64_REG_Q0 && reg < FPCR_REGISTER) { RegWrite(reg, GdbHexToLong(buffer_ptr + i * 16), current_thread); - } else if (reg == FPSCR_REGISTER) { + } else if (reg == FPCR_REGISTER) { RegWrite(TODO_DUMMY_REG_998, GdbHexToLong(buffer_ptr + i * 16), current_thread); } else { UNIMPLEMENTED(); @@ -995,7 +995,7 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) { breakpoint.addr = addr; breakpoint.len = len; Memory::ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size()); - static constexpr std::array<u8, 4> btrap{{0xd4, 0x20, 0x7d, 0x0}}; + static constexpr std::array<u8, 4> btrap{{0x00, 0x7d, 0x20, 0xd4}}; Memory::WriteBlock(addr, btrap.data(), btrap.size()); Core::System::GetInstance().InvalidateCpuInstructionCaches(); p.insert({addr, breakpoint}); diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index eaa5395ac..419f45896 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -12,7 +12,7 @@ namespace IPC { /// Size of the command buffer area, in 32-bit words. -constexpr size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32); +constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32); // These errors are commonly returned by invalid IPC translations, so alias them here for // convenience. @@ -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 { diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index 0f3ffdb60..a4bfe2eb0 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -152,8 +152,8 @@ public: } void ValidateHeader() { - const size_t num_domain_objects = context->NumDomainObjects(); - const size_t num_move_objects = context->NumMoveObjects(); + const std::size_t num_domain_objects = context->NumDomainObjects(); + const std::size_t num_move_objects = context->NumMoveObjects(); ASSERT_MSG(!num_domain_objects || !num_move_objects, "cannot move normal handles and domain objects"); ASSERT_MSG((index - datapayload_index) == normal_params_size, @@ -290,13 +290,6 @@ public: Skip(CommandIdSize, false); } - ResponseBuilder MakeBuilder(u32 normal_params_size, u32 num_handles_to_copy, - u32 num_handles_to_move, - ResponseBuilder::Flags flags = ResponseBuilder::Flags::None) const { - return ResponseBuilder{*context, normal_params_size, num_handles_to_copy, - num_handles_to_move, flags}; - } - template <typename T> T Pop(); @@ -329,10 +322,10 @@ public: T PopRaw(); template <typename T> - Kernel::SharedPtr<T> GetMoveObject(size_t index); + Kernel::SharedPtr<T> GetMoveObject(std::size_t index); template <typename T> - Kernel::SharedPtr<T> GetCopyObject(size_t index); + Kernel::SharedPtr<T> GetCopyObject(std::size_t index); template <class T> std::shared_ptr<T> PopIpcInterface() { @@ -406,12 +399,12 @@ void RequestParser::Pop(First& first_value, Other&... other_values) { } template <typename T> -Kernel::SharedPtr<T> RequestParser::GetMoveObject(size_t index) { +Kernel::SharedPtr<T> RequestParser::GetMoveObject(std::size_t index) { return context->GetMoveObject<T>(index); } template <typename T> -Kernel::SharedPtr<T> RequestParser::GetCopyObject(size_t index) { +Kernel::SharedPtr<T> RequestParser::GetCopyObject(std::size_t index) { return context->GetCopyObject<T>(index); } diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index 6657accd5..93577591f 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -35,16 +35,17 @@ static ResultCode WaitForAddress(VAddr address, s64 timeout) { // Gets the threads waiting on an address. static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) { - const auto RetrieveWaitingThreads = - [](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr arb_addr) { - const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); - auto& thread_list = scheduler->GetThreadList(); - - for (auto& thread : thread_list) { - if (thread->arb_wait_address == arb_addr) - waiting_threads.push_back(thread); - } - }; + const auto RetrieveWaitingThreads = [](std::size_t core_index, + std::vector<SharedPtr<Thread>>& waiting_threads, + VAddr arb_addr) { + const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); + auto& thread_list = scheduler->GetThreadList(); + + for (auto& thread : thread_list) { + if (thread->arb_wait_address == arb_addr) + waiting_threads.push_back(thread); + } + }; // Retrieve all threads that are waiting for this address. std::vector<SharedPtr<Thread>> threads; @@ -66,12 +67,12 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address) static void WakeThreads(std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_to_wake) { // Only process up to 'target' threads, unless 'target' is <= 0, in which case process // them all. - size_t last = waiting_threads.size(); + std::size_t last = waiting_threads.size(); if (num_to_wake > 0) last = num_to_wake; // Signal the waiting threads. - for (size_t i = 0; i < last; i++) { + for (std::size_t i = 0; i < last; i++) { ASSERT(waiting_threads[i]->status == ThreadStatus::WaitArb); waiting_threads[i]->SetWaitSynchronizationResult(RESULT_SUCCESS); waiting_threads[i]->arb_wait_address = 0; diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index 4054d5db6..8c2be2681 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -17,10 +17,12 @@ enum { // Confirmed Switch OS error codes MaxConnectionsReached = 7, + InvalidSize = 101, InvalidAddress = 102, HandleTableFull = 105, InvalidMemoryState = 106, InvalidMemoryPermissions = 108, + InvalidThreadPriority = 112, InvalidProcessorId = 113, InvalidHandle = 114, InvalidCombination = 116, @@ -28,6 +30,7 @@ enum { SynchronizationCanceled = 118, TooLarge = 119, InvalidEnumValue = 120, + NoSuchEntry = 121, InvalidState = 125, ResourceLimitExceeded = 132, }; @@ -36,7 +39,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 +56,17 @@ 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_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize); 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 ERR_NOT_FOUND(ErrorModule::Kernel, ErrCodes::NoSuchEntry); 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); diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp index 3a079b9a9..5ee5c05e3 100644 --- a/src/core/hle/kernel/handle_table.cpp +++ b/src/core/hle/kernel/handle_table.cpp @@ -65,7 +65,7 @@ ResultCode HandleTable::Close(Handle handle) { } bool HandleTable::IsValid(Handle handle) const { - size_t slot = GetSlot(handle); + std::size_t slot = GetSlot(handle); u16 generation = GetGeneration(handle); return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation; diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h index cac928adb..9e2f33e8a 100644 --- a/src/core/hle/kernel/handle_table.h +++ b/src/core/hle/kernel/handle_table.h @@ -93,7 +93,7 @@ private: * This is the maximum limit of handles allowed per process in CTR-OS. It can be further * reduced by ExHeader values, but this is not emulated here. */ - static const size_t MAX_COUNT = 4096; + static const std::size_t MAX_COUNT = 4096; static u16 GetSlot(Handle handle) { return handle >> 15; diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 7264be906..72fb9d250 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -42,9 +42,9 @@ SharedPtr<Event> HLERequestContext::SleepClientThread(SharedPtr<Thread> thread, Kernel::SharedPtr<Kernel::Event> event) { // Put the client thread to sleep until the wait event is signaled or the timeout expires. - thread->wakeup_callback = - [context = *this, callback](ThreadWakeupReason reason, SharedPtr<Thread> thread, - SharedPtr<WaitObject> object, size_t index) mutable -> bool { + thread->wakeup_callback = [context = *this, callback]( + ThreadWakeupReason reason, SharedPtr<Thread> thread, + SharedPtr<WaitObject> object, std::size_t index) mutable -> bool { ASSERT(thread->status == ThreadStatus::WaitHLEEvent); callback(thread, context, reason); context.WriteToOutgoingCommandBuffer(*thread); @@ -199,8 +199,8 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb } // The data_size already includes the payload header, the padding and the domain header. - size_t size = data_payload_offset + command_header->data_size - - sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4; + std::size_t size = data_payload_offset + command_header->data_size - + sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4; if (domain_message_header) size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32); std::copy_n(src_cmdbuf, size, cmd_buf.begin()); @@ -217,8 +217,8 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) ParseCommandBuffer(cmd_buf.data(), false); // The data_size already includes the payload header, the padding and the domain header. - size_t size = data_payload_offset + command_header->data_size - - sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4; + std::size_t size = data_payload_offset + command_header->data_size - + sizeof(IPC::DataPayloadHeader) / sizeof(u32) - 4; if (domain_message_header) size -= sizeof(IPC::DomainMessageHeader) / sizeof(u32); @@ -229,7 +229,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) "Handle descriptor bit set but no handles to translate"); // We write the translated handles at a specific offset in the command buffer, this space // was already reserved when writing the header. - size_t current_offset = + std::size_t current_offset = (sizeof(IPC::CommandHeader) + sizeof(IPC::HandleDescriptorHeader)) / sizeof(u32); ASSERT_MSG(!handle_descriptor_header->send_current_pid, "Sending PID is not implemented"); @@ -258,7 +258,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) ASSERT(domain_message_header->num_objects == domain_objects.size()); // Write the domain objects to the command buffer, these go after the raw untranslated data. // TODO(Subv): This completely ignores C buffers. - size_t domain_offset = size - domain_message_header->num_objects; + std::size_t domain_offset = size - domain_message_header->num_objects; auto& request_handlers = server_session->domain_request_handlers; for (auto& object : domain_objects) { @@ -291,14 +291,15 @@ std::vector<u8> HLERequestContext::ReadBuffer(int buffer_index) const { return buffer; } -size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size, int buffer_index) const { +std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size, + int buffer_index) const { if (size == 0) { LOG_WARNING(Core, "skip empty buffer write"); return 0; } const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()}; - const size_t buffer_size{GetWriteBufferSize(buffer_index)}; + const std::size_t buffer_size{GetWriteBufferSize(buffer_index)}; if (size > buffer_size) { LOG_CRITICAL(Core, "size ({:016X}) is greater than buffer_size ({:016X})", size, buffer_size); @@ -314,13 +315,13 @@ size_t HLERequestContext::WriteBuffer(const void* buffer, size_t size, int buffe return size; } -size_t HLERequestContext::GetReadBufferSize(int buffer_index) const { +std::size_t HLERequestContext::GetReadBufferSize(int buffer_index) const { const bool is_buffer_a{BufferDescriptorA().size() && BufferDescriptorA()[buffer_index].Size()}; return is_buffer_a ? BufferDescriptorA()[buffer_index].Size() : BufferDescriptorX()[buffer_index].Size(); } -size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const { +std::size_t HLERequestContext::GetWriteBufferSize(int buffer_index) const { const bool is_buffer_b{BufferDescriptorB().size() && BufferDescriptorB()[buffer_index].Size()}; return is_buffer_b ? BufferDescriptorB()[buffer_index].Size() : BufferDescriptorC()[buffer_index].Size(); diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index f0d07f1b6..894479ee0 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -170,7 +170,7 @@ public: std::vector<u8> ReadBuffer(int buffer_index = 0) const; /// Helper function to write a buffer using the appropriate buffer descriptor - size_t WriteBuffer(const void* buffer, size_t size, int buffer_index = 0) const; + std::size_t WriteBuffer(const void* buffer, std::size_t size, int buffer_index = 0) const; /* Helper function to write a buffer using the appropriate buffer descriptor * @@ -182,7 +182,7 @@ public: */ template <typename ContiguousContainer, typename = std::enable_if_t<!std::is_pointer_v<ContiguousContainer>>> - size_t WriteBuffer(const ContiguousContainer& container, int buffer_index = 0) const { + std::size_t WriteBuffer(const ContiguousContainer& container, int buffer_index = 0) const { using ContiguousType = typename ContiguousContainer::value_type; static_assert(std::is_trivially_copyable_v<ContiguousType>, @@ -193,19 +193,19 @@ public: } /// Helper function to get the size of the input buffer - size_t GetReadBufferSize(int buffer_index = 0) const; + std::size_t GetReadBufferSize(int buffer_index = 0) const; /// Helper function to get the size of the output buffer - size_t GetWriteBufferSize(int buffer_index = 0) const; + std::size_t GetWriteBufferSize(int buffer_index = 0) const; template <typename T> - SharedPtr<T> GetCopyObject(size_t index) { + SharedPtr<T> GetCopyObject(std::size_t index) { ASSERT(index < copy_objects.size()); return DynamicObjectCast<T>(copy_objects[index]); } template <typename T> - SharedPtr<T> GetMoveObject(size_t index) { + SharedPtr<T> GetMoveObject(std::size_t index) { ASSERT(index < move_objects.size()); return DynamicObjectCast<T>(move_objects[index]); } @@ -223,7 +223,7 @@ public: } template <typename T> - std::shared_ptr<T> GetDomainRequestHandler(size_t index) const { + std::shared_ptr<T> GetDomainRequestHandler(std::size_t index) const { return std::static_pointer_cast<T>(domain_request_handlers[index]); } @@ -240,15 +240,15 @@ public: domain_objects.clear(); } - size_t NumMoveObjects() const { + std::size_t NumMoveObjects() const { return move_objects.size(); } - size_t NumCopyObjects() const { + std::size_t NumCopyObjects() const { return copy_objects.size(); } - size_t NumDomainObjects() const { + std::size_t NumDomainObjects() const { return domain_objects.size(); } diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 615d7901a..3e0800a71 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -13,6 +13,7 @@ #include "core/core.h" #include "core/core_timing.h" +#include "core/hle/kernel/client_port.h" #include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" @@ -115,6 +116,7 @@ struct KernelCore::Impl { next_thread_id = 1; process_list.clear(); + current_process.reset(); handle_table.Clear(); resource_limits.fill(nullptr); @@ -124,6 +126,8 @@ struct KernelCore::Impl { timer_callback_handle_table.Clear(); timer_callback_event_type = nullptr; + + named_ports.clear(); } void InitializeResourceLimits(KernelCore& kernel) { @@ -203,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; @@ -217,6 +222,10 @@ struct KernelCore::Impl { // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, // allowing us to simply use a pool index or similar. Kernel::HandleTable thread_wakeup_callback_handle_table; + + /// Map of named ports managed by the kernel, which can be retrieved using + /// the ConnectToPort SVC. + NamedPortTable named_ports; }; KernelCore::KernelCore() : impl{std::make_unique<Impl>()} {} @@ -257,6 +266,35 @@ 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)); +} + +KernelCore::NamedPortTable::iterator KernelCore::FindNamedPort(const std::string& name) { + return impl->named_ports.find(name); +} + +KernelCore::NamedPortTable::const_iterator KernelCore::FindNamedPort( + const std::string& name) const { + return impl->named_ports.find(name); +} + +bool KernelCore::IsValidNamedPort(NamedPortTable::const_iterator port) const { + return port != impl->named_ports.cend(); +} + u32 KernelCore::CreateNewObjectID() { return impl->next_object_id++; } diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 089e959ac..c0771ecf0 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -4,6 +4,8 @@ #pragma once +#include <string> +#include <unordered_map> #include "core/hle/kernel/object.h" template <typename T> @@ -15,6 +17,7 @@ struct EventType; namespace Kernel { +class ClientPort; class HandleTable; class Process; class ResourceLimit; @@ -25,6 +28,9 @@ enum class ResourceLimitCategory : u8; /// Represents a single instance of the kernel. class KernelCore { +private: + using NamedPortTable = std::unordered_map<std::string, SharedPtr<ClientPort>>; + public: KernelCore(); ~KernelCore(); @@ -59,6 +65,27 @@ 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); + + /// Finds a port within the named port table with the given name. + NamedPortTable::iterator FindNamedPort(const std::string& name); + + /// Finds a port within the named port table with the given name. + NamedPortTable::const_iterator FindNamedPort(const std::string& name) const; + + /// Determines whether or not the given port is a valid named port. + bool IsValidNamedPort(NamedPortTable::const_iterator port) const; + private: friend class Object; friend class Process; diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index 36bf0b677..81675eac5 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp @@ -16,6 +16,7 @@ #include "core/hle/kernel/object.h" #include "core/hle/kernel/thread.h" #include "core/hle/result.h" +#include "core/memory.h" namespace Kernel { @@ -62,7 +63,7 @@ ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle ho Handle requesting_thread_handle) { // The mutex address must be 4-byte aligned if ((address % sizeof(u32)) != 0) { - return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress); + return ERR_INVALID_ADDRESS; } SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle); @@ -100,7 +101,7 @@ ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle ho ResultCode Mutex::Release(VAddr address) { // The mutex address must be 4-byte aligned if ((address % sizeof(u32)) != 0) { - return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidAddress); + return ERR_INVALID_ADDRESS; } auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address); diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index b025e323f..914bbe0a1 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -40,8 +40,8 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) { return process; } -void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { - for (size_t i = 0; i < len; ++i) { +void Process::ParseKernelCaps(const u32* kernel_caps, std::size_t len) { + for (std::size_t i = 0; i < len; ++i) { u32 descriptor = kernel_caps[i]; u32 type = descriptor >> 20; @@ -125,7 +125,7 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) { vm_manager.LogLayout(); status = ProcessStatus::Running; - Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, this); + Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this); } void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) { @@ -211,7 +211,7 @@ ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { "Shared memory exceeds bounds of mapped block"); const std::shared_ptr<std::vector<u8>>& backing_block = vma->second.backing_block; - size_t backing_block_offset = vma->second.offset + vma_offset; + std::size_t backing_block_offset = vma->second.offset + vma_offset; CASCADE_RESULT(auto new_vma, vm_manager.MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index 1587d40c1..81538f70c 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -59,7 +59,7 @@ class ResourceLimit; struct CodeSet final : public Object { struct Segment { - size_t offset = 0; + std::size_t offset = 0; VAddr addr = 0; u32 size = 0; }; @@ -164,7 +164,7 @@ public: * Parses a list of kernel capability descriptors (as found in the ExHeader) and applies them * to this process. */ - void ParseKernelCaps(const u32* kernel_caps, size_t len); + void ParseKernelCaps(const u32* kernel_caps, std::size_t len); /** * Applies address space changes and launches the process main thread. diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 2c729afe3..2c06bb7ce 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h @@ -119,7 +119,7 @@ public: /// Backing memory for this shared memory block. std::shared_ptr<std::vector<u8>> backing_block; /// Offset into the backing block for this shared memory. - size_t backing_block_offset; + std::size_t backing_block_offset; /// Size of the memory block. Page-aligned. u64 size; /// Permission restrictions applied to the process which created the block. diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 5da71cff0..371fc439e 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -35,10 +35,21 @@ #include "core/hle/service/service.h" namespace Kernel { +namespace { +constexpr bool Is4KBAligned(VAddr address) { + return (address & 0xFFF) == 0; +} +} // Anonymous namespace /// Set the process heap to a given Size. It can both extend and shrink the heap. static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) { LOG_TRACE(Kernel_SVC, "called, heap_size=0x{:X}", heap_size); + + // Size must be a multiple of 0x200000 (2MB) and be equal to or less than 4GB. + if ((heap_size & 0xFFFFFFFE001FFFFF) != 0) { + return ERR_INVALID_SIZE; + } + auto& process = *Core::CurrentProcess(); CASCADE_RESULT(*heap_addr, process.HeapAllocate(Memory::HEAP_VADDR, heap_size, VMAPermission::ReadWrite)); @@ -56,6 +67,15 @@ static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, src_addr, size); + + if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) { + return ERR_INVALID_ADDRESS; + } + + if (size == 0 || !Is4KBAligned(size)) { + return ERR_INVALID_SIZE; + } + return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size); } @@ -63,24 +83,36 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) { LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr, src_addr, size); + + if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) { + return ERR_INVALID_ADDRESS; + } + + if (size == 0 || !Is4KBAligned(size)) { + return ERR_INVALID_SIZE; + } + return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size); } /// Connect to an OS service given the port name, returns the handle to the port to out static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address) { - if (!Memory::IsValidVirtualAddress(port_name_address)) + if (!Memory::IsValidVirtualAddress(port_name_address)) { return ERR_NOT_FOUND; + } static constexpr std::size_t PortNameMaxLength = 11; // Read 1 char beyond the max allowed port name to detect names that are too long. std::string port_name = Memory::ReadCString(port_name_address, PortNameMaxLength + 1); - if (port_name.size() > PortNameMaxLength) + if (port_name.size() > PortNameMaxLength) { return ERR_PORT_NAME_TOO_LONG; + } LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); - auto it = Service::g_kernel_named_ports.find(port_name); - if (it == Service::g_kernel_named_ports.end()) { + auto& kernel = Core::System::GetInstance().Kernel(); + auto it = kernel.FindNamedPort(port_name); + if (!kernel.IsValidNamedPort(it)) { LOG_WARNING(Kernel_SVC, "tried to connect to unknown port: {}", port_name); return ERR_NOT_FOUND; } @@ -91,7 +123,6 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address CASCADE_RESULT(client_session, client_port->Connect()); // Return the client session - auto& kernel = Core::System::GetInstance().Kernel(); CASCADE_RESULT(*out_handle, kernel.HandleTable().Create(client_session)); return RESULT_SUCCESS; } @@ -144,7 +175,7 @@ static ResultCode GetProcessId(u32* process_id, Handle process_handle) { /// Default thread wakeup callback for WaitSynchronization static bool DefaultThreadWakeupCallback(ThreadWakeupReason reason, SharedPtr<Thread> thread, - SharedPtr<WaitObject> object, size_t index) { + SharedPtr<WaitObject> object, std::size_t index) { ASSERT(thread->status == ThreadStatus::WaitSynchAny); if (reason == ThreadWakeupReason::Timeout) { @@ -249,6 +280,10 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, "requesting_current_thread_handle=0x{:08X}", holding_thread_handle, mutex_addr, requesting_thread_handle); + if (Memory::IsKernelVirtualAddress(mutex_addr)) { + return ERR_INVALID_ADDRESS_STATE; + } + auto& handle_table = Core::System::GetInstance().Kernel().HandleTable(); return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle, requesting_thread_handle); @@ -258,6 +293,10 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, static ResultCode ArbitrateUnlock(VAddr mutex_addr) { LOG_TRACE(Kernel_SVC, "called mutex_addr=0x{:X}", mutex_addr); + if (Memory::IsKernelVirtualAddress(mutex_addr)) { + return ERR_INVALID_ADDRESS_STATE; + } + return Mutex::Release(mutex_addr); } @@ -271,7 +310,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); @@ -376,7 +419,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(); @@ -409,35 +452,43 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s "called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}", shared_memory_handle, addr, size, permissions); + if (!Is4KBAligned(addr)) { + return ERR_INVALID_ADDRESS; + } + + if (size == 0 || !Is4KBAligned(size)) { + return ERR_INVALID_SIZE; + } + + const auto permissions_type = static_cast<MemoryPermission>(permissions); + if (permissions_type != MemoryPermission::Read && + permissions_type != MemoryPermission::ReadWrite) { + LOG_ERROR(Kernel_SVC, "Invalid permissions=0x{:08X}", permissions); + return ERR_INVALID_MEMORY_PERMISSIONS; + } + auto& kernel = Core::System::GetInstance().Kernel(); auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); if (!shared_memory) { return ERR_INVALID_HANDLE; } - MemoryPermission permissions_type = static_cast<MemoryPermission>(permissions); - switch (permissions_type) { - case MemoryPermission::Read: - case MemoryPermission::Write: - case MemoryPermission::ReadWrite: - case MemoryPermission::Execute: - case MemoryPermission::ReadExecute: - case MemoryPermission::WriteExecute: - case MemoryPermission::ReadWriteExecute: - case MemoryPermission::DontCare: - return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type, - MemoryPermission::DontCare); - default: - LOG_ERROR(Kernel_SVC, "unknown permissions=0x{:08X}", permissions); - } - - return RESULT_SUCCESS; + return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type, + MemoryPermission::DontCare); } static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) { LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}", shared_memory_handle, addr, size); + if (!Is4KBAligned(addr)) { + return ERR_INVALID_ADDRESS; + } + + if (size == 0 || !Is4KBAligned(size)) { + return ERR_INVALID_SIZE; + } + auto& kernel = Core::System::GetInstance().Kernel(); auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); @@ -518,10 +569,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; @@ -542,8 +593,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(); @@ -641,16 +692,17 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x{:X}, target=0x{:08X}", condition_variable_addr, target); - auto RetrieveWaitingThreads = - [](size_t core_index, std::vector<SharedPtr<Thread>>& waiting_threads, VAddr condvar_addr) { - const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); - auto& thread_list = scheduler->GetThreadList(); + auto RetrieveWaitingThreads = [](std::size_t core_index, + std::vector<SharedPtr<Thread>>& waiting_threads, + VAddr condvar_addr) { + const auto& scheduler = Core::System::GetInstance().Scheduler(core_index); + auto& thread_list = scheduler->GetThreadList(); - for (auto& thread : thread_list) { - if (thread->condvar_wait_address == condvar_addr) - waiting_threads.push_back(thread); - } - }; + for (auto& thread : thread_list) { + if (thread->condvar_wait_address == condvar_addr) + waiting_threads.push_back(thread); + } + }; // Retrieve a list of all threads that are waiting for this condition variable. std::vector<SharedPtr<Thread>> waiting_threads; @@ -666,7 +718,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target // Only process up to 'target' threads, unless 'target' is -1, in which case process // them all. - size_t last = waiting_threads.size(); + std::size_t last = waiting_threads.size(); if (target != -1) last = target; @@ -674,12 +726,12 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target if (last > waiting_threads.size()) return RESULT_SUCCESS; - for (size_t index = 0; index < last; ++index) { + for (std::size_t index = 0; index < last; ++index) { auto& thread = waiting_threads[index]; ASSERT(thread->condvar_wait_address == condition_variable_addr); - size_t current_core = Core::System::GetInstance().CurrentCoreIndex(); + std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex(); auto& monitor = Core::System::GetInstance().Monitor(); @@ -892,12 +944,28 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss LOG_TRACE(Kernel_SVC, "called, size=0x{:X}, localPerms=0x{:08X}, remotePerms=0x{:08X}", size, local_permissions, remote_permissions); + // Size must be a multiple of 4KB and be less than or equal to + // approx. 8 GB (actually (1GB - 512B) * 8) + if (size == 0 || (size & 0xFFFFFFFE00000FFF) != 0) { + return ERR_INVALID_SIZE; + } + + const auto local_perms = static_cast<MemoryPermission>(local_permissions); + if (local_perms != MemoryPermission::Read && local_perms != MemoryPermission::ReadWrite) { + return ERR_INVALID_MEMORY_PERMISSIONS; + } + + const auto remote_perms = static_cast<MemoryPermission>(remote_permissions); + if (remote_perms != MemoryPermission::Read && remote_perms != MemoryPermission::ReadWrite && + remote_perms != MemoryPermission::DontCare) { + return ERR_INVALID_MEMORY_PERMISSIONS; + } + auto& kernel = Core::System::GetInstance().Kernel(); auto& handle_table = kernel.HandleTable(); auto shared_mem_handle = SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size, - static_cast<MemoryPermission>(local_permissions), - static_cast<MemoryPermission>(remote_permissions)); + local_perms, remote_perms); CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle)); return RESULT_SUCCESS; diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h index 79c3fe31b..fea9ba5ea 100644 --- a/src/core/hle/kernel/svc_wrap.h +++ b/src/core/hle/kernel/svc_wrap.h @@ -13,7 +13,9 @@ namespace Kernel { -#define PARAM(n) Core::CurrentArmInterface().GetReg(n) +static inline u64 Param(int n) { + return Core::CurrentArmInterface().GetReg(n); +} /** * HLE a function return from the current ARM userland process @@ -28,23 +30,23 @@ static inline void FuncReturn(u64 res) { template <ResultCode func(u64)> void SvcWrap() { - FuncReturn(func(PARAM(0)).raw); + FuncReturn(func(Param(0)).raw); } template <ResultCode func(u32)> void SvcWrap() { - FuncReturn(func((u32)PARAM(0)).raw); + FuncReturn(func((u32)Param(0)).raw); } template <ResultCode func(u32, u32)> void SvcWrap() { - FuncReturn(func((u32)PARAM(0), (u32)PARAM(1)).raw); + FuncReturn(func((u32)Param(0), (u32)Param(1)).raw); } template <ResultCode func(u32*, u32)> void SvcWrap() { u32 param_1 = 0; - u32 retval = func(¶m_1, (u32)PARAM(1)).raw; + u32 retval = func(¶m_1, (u32)Param(1)).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -52,39 +54,39 @@ void SvcWrap() { template <ResultCode func(u32*, u64)> void SvcWrap() { u32 param_1 = 0; - u32 retval = func(¶m_1, PARAM(1)).raw; + u32 retval = func(¶m_1, Param(1)).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } template <ResultCode func(u64, s32)> void SvcWrap() { - FuncReturn(func(PARAM(0), (s32)PARAM(1)).raw); + FuncReturn(func(Param(0), (s32)Param(1)).raw); } template <ResultCode func(u64*, u64)> void SvcWrap() { u64 param_1 = 0; - u32 retval = func(¶m_1, PARAM(1)).raw; + u32 retval = func(¶m_1, Param(1)).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } template <ResultCode func(u32, u64)> void SvcWrap() { - FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), PARAM(1)).raw); + FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), Param(1)).raw); } template <ResultCode func(u32, u32, u64)> void SvcWrap() { - FuncReturn(func((u32)(PARAM(0) & 0xFFFFFFFF), (u32)(PARAM(1) & 0xFFFFFFFF), PARAM(2)).raw); + FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), (u32)(Param(1) & 0xFFFFFFFF), Param(2)).raw); } template <ResultCode func(u32, u32*, u64*)> void SvcWrap() { u32 param_1 = 0; u64 param_2 = 0; - ResultCode retval = func((u32)(PARAM(2) & 0xFFFFFFFF), ¶m_1, ¶m_2); + ResultCode retval = func((u32)(Param(2) & 0xFFFFFFFF), ¶m_1, ¶m_2); Core::CurrentArmInterface().SetReg(1, param_1); Core::CurrentArmInterface().SetReg(2, param_2); FuncReturn(retval.raw); @@ -93,46 +95,46 @@ void SvcWrap() { template <ResultCode func(u64, u64, u32, u32)> void SvcWrap() { FuncReturn( - func(PARAM(0), PARAM(1), (u32)(PARAM(3) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw); + func(Param(0), Param(1), (u32)(Param(3) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw); } template <ResultCode func(u32, u64, u32)> void SvcWrap() { - FuncReturn(func((u32)PARAM(0), PARAM(1), (u32)PARAM(2)).raw); + FuncReturn(func((u32)Param(0), Param(1), (u32)Param(2)).raw); } template <ResultCode func(u64, u64, u64)> void SvcWrap() { - FuncReturn(func(PARAM(0), PARAM(1), PARAM(2)).raw); + FuncReturn(func(Param(0), Param(1), Param(2)).raw); } template <ResultCode func(u32, u64, u64, u32)> void SvcWrap() { - FuncReturn(func((u32)PARAM(0), PARAM(1), PARAM(2), (u32)PARAM(3)).raw); + FuncReturn(func((u32)Param(0), Param(1), Param(2), (u32)Param(3)).raw); } template <ResultCode func(u32, u64, u64)> void SvcWrap() { - FuncReturn(func((u32)PARAM(0), PARAM(1), PARAM(2)).raw); + FuncReturn(func((u32)Param(0), Param(1), Param(2)).raw); } template <ResultCode func(u32*, u64, u64, s64)> void SvcWrap() { u32 param_1 = 0; - ResultCode retval = func(¶m_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3)); + ResultCode retval = func(¶m_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (s64)Param(3)); Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval.raw); } template <ResultCode func(u64, u64, u32, s64)> void SvcWrap() { - FuncReturn(func(PARAM(0), PARAM(1), (u32)PARAM(2), (s64)PARAM(3)).raw); + FuncReturn(func(Param(0), Param(1), (u32)Param(2), (s64)Param(3)).raw); } template <ResultCode func(u64*, u64, u64, u64)> void SvcWrap() { u64 param_1 = 0; - u32 retval = func(¶m_1, PARAM(1), PARAM(2), PARAM(3)).raw; + u32 retval = func(¶m_1, Param(1), Param(2), Param(3)).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -141,7 +143,7 @@ template <ResultCode func(u32*, u64, u64, u64, u32, s32)> void SvcWrap() { u32 param_1 = 0; u32 retval = - func(¶m_1, PARAM(1), PARAM(2), PARAM(3), (u32)PARAM(4), (s32)(PARAM(5) & 0xFFFFFFFF)) + func(¶m_1, Param(1), Param(2), Param(3), (u32)Param(4), (s32)(Param(5) & 0xFFFFFFFF)) .raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); @@ -151,13 +153,13 @@ template <ResultCode func(MemoryInfo*, PageInfo*, u64)> void SvcWrap() { MemoryInfo memory_info = {}; PageInfo page_info = {}; - u32 retval = func(&memory_info, &page_info, PARAM(2)).raw; + u32 retval = func(&memory_info, &page_info, Param(2)).raw; - Memory::Write64(PARAM(0), memory_info.base_address); - Memory::Write64(PARAM(0) + 8, memory_info.size); - Memory::Write32(PARAM(0) + 16, memory_info.type); - Memory::Write32(PARAM(0) + 20, memory_info.attributes); - Memory::Write32(PARAM(0) + 24, memory_info.permission); + Memory::Write64(Param(0), memory_info.base_address); + Memory::Write64(Param(0) + 8, memory_info.size); + Memory::Write32(Param(0) + 16, memory_info.type); + Memory::Write32(Param(0) + 20, memory_info.attributes); + Memory::Write32(Param(0) + 24, memory_info.permission); FuncReturn(retval); } @@ -165,7 +167,7 @@ void SvcWrap() { template <ResultCode func(u32*, u64, u64, u32)> void SvcWrap() { u32 param_1 = 0; - u32 retval = func(¶m_1, PARAM(1), PARAM(2), (u32)(PARAM(3) & 0xFFFFFFFF)).raw; + u32 retval = func(¶m_1, Param(1), Param(2), (u32)(Param(3) & 0xFFFFFFFF)).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -174,7 +176,7 @@ template <ResultCode func(Handle*, u64, u32, u32)> void SvcWrap() { u32 param_1 = 0; u32 retval = - func(¶m_1, PARAM(1), (u32)(PARAM(2) & 0xFFFFFFFF), (u32)(PARAM(3) & 0xFFFFFFFF)).raw; + func(¶m_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw; Core::CurrentArmInterface().SetReg(1, param_1); FuncReturn(retval); } @@ -182,14 +184,14 @@ void SvcWrap() { template <ResultCode func(u64, u32, s32, s64)> void SvcWrap() { FuncReturn( - func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF), (s64)PARAM(3)) + func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF), (s64)Param(3)) .raw); } template <ResultCode func(u64, u32, s32, s32)> void SvcWrap() { - FuncReturn(func(PARAM(0), (u32)(PARAM(1) & 0xFFFFFFFF), (s32)(PARAM(2) & 0xFFFFFFFF), - (s32)(PARAM(3) & 0xFFFFFFFF)) + FuncReturn(func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF), + (s32)(Param(3) & 0xFFFFFFFF)) .raw); } @@ -219,20 +221,17 @@ void SvcWrap() { template <void func(s64)> void SvcWrap() { - func((s64)PARAM(0)); + 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)> void SvcWrap() { - func(PARAM(0), PARAM(1), PARAM(2)); + func(Param(0), Param(1), Param(2)); } -#undef PARAM -#undef FuncReturn - } // namespace Kernel diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 3d10d9af2..c2d7535c9 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -217,8 +217,8 @@ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAdd context.cpu_registers[0] = arg; context.pc = entry_point; context.sp = stack_top; - context.cpsr = 0; - context.fpscr = 0; + context.pstate = 0; + context.fpcr = 0; } ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point, @@ -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 @@ -275,7 +275,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name available_slot = 0; // Use the first slot in the new page // Allocate some memory from the end of the linear heap for this region. - const size_t offset = thread->tls_memory->size(); + const std::size_t offset = thread->tls_memory->size(); thread->tls_memory->insert(thread->tls_memory->end(), Memory::PAGE_SIZE, 0); auto& vm_manager = owner_process->vm_manager; @@ -311,13 +311,13 @@ void Thread::BoostPriority(u32 priority) { } SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 priority, - SharedPtr<Process> owner_process) { + Process& owner_process) { // Setup page table so we can write to memory - SetCurrentPageTable(&Core::CurrentProcess()->vm_manager.page_table); + SetCurrentPageTable(&owner_process.vm_manager.page_table); // Initialize new "main" thread auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0, - Memory::STACK_AREA_VADDR_END, std::move(owner_process)); + Memory::STACK_AREA_VADDR_END, &owner_process); SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 20f50458b..91e9b79ec 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -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: /** @@ -254,7 +254,7 @@ public: Handle callback_handle; using WakeupCallback = bool(ThreadWakeupReason reason, SharedPtr<Thread> thread, - SharedPtr<WaitObject> object, size_t index); + SharedPtr<WaitObject> object, std::size_t index); // Callback that will be invoked when the thread is resumed from a waiting state. If the thread // was waiting via WaitSynchronizationN then the object will be the last object that became // available. In case of a timeout, the object will be nullptr. @@ -281,7 +281,7 @@ private: * @return A shared pointer to the main thread */ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 priority, - SharedPtr<Process> owner_process); + Process& owner_process); /** * Gets the current thread diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index 479cacb62..608cbd57b 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -86,7 +86,7 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const { ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block, - size_t offset, u64 size, + std::size_t offset, u64 size, MemoryState state) { ASSERT(block != nullptr); ASSERT(offset + size <= block->size()); diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 98bd04bea..de75036c0 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -81,7 +81,7 @@ struct VirtualMemoryArea { /// Memory block backing this VMA. std::shared_ptr<std::vector<u8>> backing_block = nullptr; /// Offset into the backing_memory the mapping starts from. - size_t offset = 0; + std::size_t offset = 0; // Settings for type = BackingMemory /// Pointer backing this VMA. It will not be destroyed or freed when the VMA is removed. @@ -147,7 +147,7 @@ public: * @param state MemoryState tag to attach to the VMA. */ ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block, - size_t offset, u64 size, MemoryState state); + std::size_t offset, u64 size, MemoryState state); /** * Maps an unmanaged host memory pointer at a given address. diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp index eef00b729..b190ceb98 100644 --- a/src/core/hle/kernel/wait_object.cpp +++ b/src/core/hle/kernel/wait_object.cpp @@ -81,7 +81,7 @@ void WaitObject::WakeupWaitingThread(SharedPtr<Thread> thread) { } } - size_t index = thread->GetWaitObjectIndex(this); + std::size_t index = thread->GetWaitObjectIndex(this); for (auto& object : thread->wait_objects) object->RemoveWaitingThread(thread.get()); diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index 1502dbf55..e61748ca3 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -34,7 +34,7 @@ public: static const FunctionInfo functions[] = { {0, &IProfile::Get, "Get"}, {1, &IProfile::GetBase, "GetBase"}, - {10, nullptr, "GetImageSize"}, + {10, &IProfile::GetImageSize, "GetImageSize"}, {11, &IProfile::LoadImage, "LoadImage"}, }; RegisterHandlers(functions); @@ -93,6 +93,14 @@ private: rb.Push<u32>(jpeg_size); } + void GetImageSize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_ACC, "(STUBBED) called"); + constexpr u32 jpeg_size = 107; + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(jpeg_size); + } + const ProfileManager& profile_manager; UUID user_id; ///< The user id this profile refers to. }; @@ -122,11 +130,10 @@ private: void GetAccountId(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_ACC, "(STUBBED) called"); - // TODO(Subv): Find out what this actually does and implement it. Stub it as an error for - // now since we do not implement NNID. Returning a bogus id here will cause games to send - // invalid IPC requests after ListOpenUsers is called. - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(ResultCode(-1)); + // Should return a nintendo account ID + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u64>(1); } }; diff --git a/src/core/hle/service/acc/acc_aa.cpp b/src/core/hle/service/acc/acc_aa.cpp index 9bd595a37..e84d9f7cf 100644 --- a/src/core/hle/service/acc/acc_aa.cpp +++ b/src/core/hle/service/acc/acc_aa.cpp @@ -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 diff --git a/src/core/hle/service/acc/acc_aa.h b/src/core/hle/service/acc/acc_aa.h index 2e08c781a..9edb0421b 100644 --- a/src/core/hle/service/acc/acc_aa.h +++ b/src/core/hle/service/acc/acc_aa.h @@ -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 diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp index 0218ee859..ad455c3a7 100644 --- a/src/core/hle/service/acc/acc_su.cpp +++ b/src/core/hle/service/acc/acc_su.cpp @@ -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 diff --git a/src/core/hle/service/acc/acc_su.h b/src/core/hle/service/acc/acc_su.h index 79a47d88d..fcced063a 100644 --- a/src/core/hle/service/acc/acc_su.h +++ b/src/core/hle/service/acc/acc_su.h @@ -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 diff --git a/src/core/hle/service/acc/acc_u0.cpp b/src/core/hle/service/acc/acc_u0.cpp index 84a4d05b8..72d4adf35 100644 --- a/src/core/hle/service/acc/acc_u0.cpp +++ b/src/core/hle/service/acc/acc_u0.cpp @@ -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 diff --git a/src/core/hle/service/acc/acc_u0.h b/src/core/hle/service/acc/acc_u0.h index e8a114f99..a1290e0bd 100644 --- a/src/core/hle/service/acc/acc_u0.h +++ b/src/core/hle/service/acc/acc_u0.h @@ -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 diff --git a/src/core/hle/service/acc/acc_u1.cpp b/src/core/hle/service/acc/acc_u1.cpp index 495693949..d480f08e5 100644 --- a/src/core/hle/service/acc/acc_u1.cpp +++ b/src/core/hle/service/acc/acc_u1.cpp @@ -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 diff --git a/src/core/hle/service/acc/acc_u1.h b/src/core/hle/service/acc/acc_u1.h index a77520e6f..9e79daee3 100644 --- a/src/core/hle/service/acc/acc_u1.h +++ b/src/core/hle/service/acc/acc_u1.h @@ -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 diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index e0b03d763..bcb3475db 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -25,13 +25,15 @@ const UUID& UUID::Generate() { ProfileManager::ProfileManager() { // TODO(ogniK): Create the default user we have for now until loading/saving users is added auto user_uuid = UUID{1, 0}; - CreateNewUser(user_uuid, Settings::values.username); + ASSERT(CreateNewUser(user_uuid, Settings::values.username).IsSuccess()); 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) { +boost::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) { if (user_count >= MAX_USERS) { return boost::none; } @@ -40,7 +42,7 @@ boost::optional<size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) { } /// Deletes a specific profile based on it's profile index -bool ProfileManager::RemoveProfileAtIndex(size_t index) { +bool ProfileManager::RemoveProfileAtIndex(std::size_t index) { if (index >= MAX_USERS || index >= user_count) { return false; } @@ -89,7 +91,8 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const ProfileUsername& usern /// specifically by allowing an std::string for the username. This is required specifically since /// we're loading a string straight from the config ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) { - ProfileUsername username_output; + ProfileUsername username_output{}; + if (username.size() > username_output.size()) { std::copy_n(username.begin(), username_output.size(), username_output.begin()); } else { @@ -99,7 +102,7 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) } /// Returns a users profile index based on their user id. -boost::optional<size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { +boost::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { if (!uuid) { return boost::none; } @@ -108,16 +111,17 @@ boost::optional<size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { if (iter == profiles.end()) { return boost::none; } - return static_cast<size_t>(std::distance(profiles.begin(), iter)); + return static_cast<std::size_t>(std::distance(profiles.begin(), iter)); } /// Returns a users profile index based on their profile -boost::optional<size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const { +boost::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const { return GetUserIndex(user.user_uuid); } /// Returns the data structure used by the switch when GetProfileBase is called on acc:* -bool ProfileManager::GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const { +bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index, + ProfileBase& profile) const { if (index == boost::none || index >= MAX_USERS) { return false; } @@ -141,14 +145,16 @@ bool ProfileManager::GetProfileBase(const ProfileInfo& user, ProfileBase& profil /// Returns the current user count on the system. We keep a variable which tracks the count so we /// don't have to loop the internal profile array every call. -size_t ProfileManager::GetUserCount() const { + +std::size_t ProfileManager::GetUserCount() const { return user_count; } /// Lists the current "opened" users on the system. Users are typically not open until they sign /// into something or pick a profile. As of right now users should all be open until qlaunch is /// booting -size_t ProfileManager::GetOpenUserCount() const { + +std::size_t ProfileManager::GetOpenUserCount() const { return std::count_if(profiles.begin(), profiles.end(), [](const ProfileInfo& p) { return p.is_open; }); } @@ -204,7 +210,7 @@ UUID ProfileManager::GetLastOpenedUser() const { } /// Return the users profile base and the unknown arbitary data. -bool ProfileManager::GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, +bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile, ProfileData& data) const { if (GetProfileBase(index, profile)) { data = profiles[index.get()].data; diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index 52967844d..bffd4cf4d 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -12,8 +12,8 @@ #include "core/hle/result.h" namespace Service::Account { -constexpr size_t MAX_USERS = 8; -constexpr size_t MAX_DATA = 128; +constexpr std::size_t MAX_USERS = 8; +constexpr std::size_t MAX_DATA = 128; constexpr u128 INVALID_UUID{{0, 0}}; struct UUID { @@ -82,21 +82,23 @@ 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); - boost::optional<size_t> GetUserIndex(const UUID& uuid) const; - boost::optional<size_t> GetUserIndex(const ProfileInfo& user) const; - bool GetProfileBase(boost::optional<size_t> index, ProfileBase& profile) const; + boost::optional<std::size_t> GetUserIndex(const UUID& uuid) const; + boost::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const; + bool GetProfileBase(boost::optional<std::size_t> index, ProfileBase& profile) const; bool GetProfileBase(UUID uuid, ProfileBase& profile) const; bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const; - bool GetProfileBaseAndData(boost::optional<size_t> index, ProfileBase& profile, + bool GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile, ProfileData& data) const; bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const; bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile, ProfileData& data) const; - size_t GetUserCount() const; - size_t GetOpenUserCount() const; + std::size_t GetUserCount() const; + std::size_t GetOpenUserCount() const; bool UserExists(UUID uuid) const; void OpenUser(UUID uuid); void CloseUser(UUID uuid); @@ -108,9 +110,9 @@ public: private: std::array<ProfileInfo, MAX_USERS> profiles{}; - size_t user_count = 0; - boost::optional<size_t> AddToProfiles(const ProfileInfo& profile); - bool RemoveProfileAtIndex(size_t index); + std::size_t user_count = 0; + boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); + bool RemoveProfileAtIndex(std::size_t index); UUID last_opened_user{INVALID_UUID}; }; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 818c03e0f..69bfce1c1 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -20,6 +20,7 @@ #include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/pm/pm.h" #include "core/hle/service/set/set.h" +#include "core/hle/service/vi/vi.h" #include "core/settings.h" namespace Service::AM { @@ -35,6 +36,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 +64,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 +121,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 +173,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. @@ -325,7 +335,7 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter" {51, nullptr, "SetVrModeEnabled"}, {52, nullptr, "SwitchLcdBacklight"}, {55, nullptr, "IsInControllerFirmwareUpdateSection"}, - {60, nullptr, "GetDefaultDisplayResolution"}, + {60, &ICommonStateGetter::GetDefaultDisplayResolution, "GetDefaultDisplayResolution"}, {61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent, "GetDefaultDisplayResolutionChangeEvent"}, {62, nullptr, "GetHdcpAuthenticationState"}, @@ -337,6 +347,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); @@ -382,6 +394,21 @@ void ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent(Kernel::HLEReque LOG_WARNING(Service_AM, "(STUBBED) called"); } +void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + + if (Settings::values.use_docked_mode) { + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedWidth)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::DockedHeight)); + } else { + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedWidth)); + rb.Push(static_cast<u32>(Service::VI::DisplayResolution::UndockedHeight)); + } + + LOG_DEBUG(Service_AM, "called"); +} + void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; @@ -435,7 +462,7 @@ private: std::memcpy(&buffer[offset], data.data(), data.size()); - IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_AM, "called, offset={}", offset); @@ -445,13 +472,13 @@ private: IPC::RequestParser rp{ctx}; const u64 offset{rp.Pop<u64>()}; - const size_t size{ctx.GetWriteBufferSize()}; + const std::size_t size{ctx.GetWriteBufferSize()}; ASSERT(offset + size <= buffer.size()); ctx.WriteBuffer(buffer.data() + offset, size); - IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_AM, "called, offset={}", offset); @@ -541,7 +568,7 @@ private: IPC::RequestParser rp{ctx}; storage_stack.push(rp.PopIpcInterface<AM::IStorage>()); - IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 0)}; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_DEBUG(Service_AM, "called"); @@ -573,6 +600,8 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple RegisterHandlers(functions); } +ILibraryAppletCreator::~ILibraryAppletCreator() = default; + void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -587,7 +616,7 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { const u64 size{rp.Pop<u64>()}; std::vector<u8> buffer(size); - IPC::ResponseBuilder rb{rp.MakeBuilder(2, 0, 1)}; + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<AM::IStorage>(std::move(buffer)); @@ -638,6 +667,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 +791,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 +816,8 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat RegisterHandlers(functions); } +IGlobalStateController::~IGlobalStateController() = default; + IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") { static const FunctionInfo functions[] = { {0, nullptr, "CreateApplication"}, @@ -793,6 +828,8 @@ IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreat RegisterHandlers(functions); } +IApplicationCreator::~IApplicationCreator() = default; + IProcessWindingController::IProcessWindingController() : ServiceFramework("IProcessWindingController") { static const FunctionInfo functions[] = { @@ -807,4 +844,6 @@ IProcessWindingController::IProcessWindingController() }; RegisterHandlers(functions); } + +IProcessWindingController::~IProcessWindingController() = default; } // namespace Service::AM diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 9e8bb4e43..b39b0d838 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -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 { @@ -117,6 +123,7 @@ private: void GetOperationMode(Kernel::HLERequestContext& ctx); void GetPerformanceMode(Kernel::HLERequestContext& ctx); void GetBootMode(Kernel::HLERequestContext& ctx); + void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx); Kernel::SharedPtr<Kernel::Event> event; }; @@ -124,6 +131,7 @@ private: class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { public: ILibraryAppletCreator(); + ~ILibraryAppletCreator() override; private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); @@ -133,6 +141,7 @@ private: class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { public: IApplicationFunctions(); + ~IApplicationFunctions() override; private: void PopLaunchParameter(Kernel::HLERequestContext& ctx); @@ -150,6 +159,7 @@ private: class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { public: IHomeMenuFunctions(); + ~IHomeMenuFunctions() override; private: void RequestToGetForeground(Kernel::HLERequestContext& ctx); @@ -158,16 +168,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. diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 7cebc918a..4296c255e 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -222,4 +222,6 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) RegisterHandlers(functions); } +AppletAE::~AppletAE() = default; + } // namespace Service::AM diff --git a/src/core/hle/service/am/applet_ae.h b/src/core/hle/service/am/applet_ae.h index bdc57b9bc..1ed77baa4 100644 --- a/src/core/hle/service/am/applet_ae.h +++ b/src/core/hle/service/am/applet_ae.h @@ -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); diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index beea7d19b..e45cf6e20 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -103,4 +103,6 @@ AppletOE::AppletOE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) RegisterHandlers(functions); } +AppletOE::~AppletOE() = default; + } // namespace Service::AM diff --git a/src/core/hle/service/am/applet_oe.h b/src/core/hle/service/am/applet_oe.h index c52e2a322..60cfdfd9d 100644 --- a/src/core/hle/service/am/applet_oe.h +++ b/src/core/hle/service/am/applet_oe.h @@ -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); diff --git a/src/core/hle/service/am/idle.cpp b/src/core/hle/service/am/idle.cpp index af46e9494..0e3088bc8 100644 --- a/src/core/hle/service/am/idle.cpp +++ b/src/core/hle/service/am/idle.cpp @@ -21,4 +21,6 @@ IdleSys::IdleSys() : ServiceFramework{"idle:sys"} { RegisterHandlers(functions); } +IdleSys::~IdleSys() = default; + } // namespace Service::AM diff --git a/src/core/hle/service/am/idle.h b/src/core/hle/service/am/idle.h index 1eb68d2c9..c44e856b1 100644 --- a/src/core/hle/service/am/idle.h +++ b/src/core/hle/service/am/idle.h @@ -11,6 +11,7 @@ namespace Service::AM { class IdleSys final : public ServiceFramework<IdleSys> { public: explicit IdleSys(); + ~IdleSys() override; }; } // namespace Service::AM diff --git a/src/core/hle/service/am/omm.cpp b/src/core/hle/service/am/omm.cpp index 447fe8669..1c37f849f 100644 --- a/src/core/hle/service/am/omm.cpp +++ b/src/core/hle/service/am/omm.cpp @@ -39,4 +39,6 @@ OMM::OMM() : ServiceFramework{"omm"} { RegisterHandlers(functions); } +OMM::~OMM() = default; + } // namespace Service::AM diff --git a/src/core/hle/service/am/omm.h b/src/core/hle/service/am/omm.h index 49e5d331c..59dc91b72 100644 --- a/src/core/hle/service/am/omm.h +++ b/src/core/hle/service/am/omm.h @@ -11,6 +11,7 @@ namespace Service::AM { class OMM final : public ServiceFramework<OMM> { public: explicit OMM(); + ~OMM() override; }; } // namespace Service::AM diff --git a/src/core/hle/service/am/spsm.cpp b/src/core/hle/service/am/spsm.cpp index a05d433d0..003ee8667 100644 --- a/src/core/hle/service/am/spsm.cpp +++ b/src/core/hle/service/am/spsm.cpp @@ -27,4 +27,6 @@ SPSM::SPSM() : ServiceFramework{"spsm"} { RegisterHandlers(functions); } +SPSM::~SPSM() = default; + } // namespace Service::AM diff --git a/src/core/hle/service/am/spsm.h b/src/core/hle/service/am/spsm.h index 57dde62e1..3a0b979fa 100644 --- a/src/core/hle/service/am/spsm.h +++ b/src/core/hle/service/am/spsm.h @@ -11,6 +11,7 @@ namespace Service::AM { class SPSM final : public ServiceFramework<SPSM> { public: explicit SPSM(); + ~SPSM() override; }; } // namespace Service::AM diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 6e7438580..d9eeac9ec 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -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); diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h index 17d48ef30..29ce8f488 100644 --- a/src/core/hle/service/aoc/aoc_u.h +++ b/src/core/hle/service/aoc/aoc_u.h @@ -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); diff --git a/src/core/hle/service/apm/apm.cpp b/src/core/hle/service/apm/apm.cpp index 4109cb7f7..f3c09bbb1 100644 --- a/src/core/hle/service/apm/apm.cpp +++ b/src/core/hle/service/apm/apm.cpp @@ -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); diff --git a/src/core/hle/service/apm/apm.h b/src/core/hle/service/apm/apm.h index 90a80d51b..4d7d5bb7c 100644 --- a/src/core/hle/service/apm/apm.h +++ b/src/core/hle/service/apm/apm.h @@ -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. diff --git a/src/core/hle/service/apm/interface.cpp b/src/core/hle/service/apm/interface.cpp index 4cd8132f5..c22bd3859 100644 --- a/src/core/hle/service/apm/interface.cpp +++ b/src/core/hle/service/apm/interface.cpp @@ -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); diff --git a/src/core/hle/service/apm/interface.h b/src/core/hle/service/apm/interface.h index d14264ad7..773541aa4 100644 --- a/src/core/hle/service/apm/interface.h +++ b/src/core/hle/service/apm/interface.h @@ -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); diff --git a/src/core/hle/service/audio/audctl.cpp b/src/core/hle/service/audio/audctl.cpp index 37c3fdcac..b6b71f966 100644 --- a/src/core/hle/service/audio/audctl.cpp +++ b/src/core/hle/service/audio/audctl.cpp @@ -42,4 +42,6 @@ AudCtl::AudCtl() : ServiceFramework{"audctl"} { RegisterHandlers(functions); } +AudCtl::~AudCtl() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audctl.h b/src/core/hle/service/audio/audctl.h index ed837bdf2..9d2d9e83b 100644 --- a/src/core/hle/service/audio/audctl.h +++ b/src/core/hle/service/audio/audctl.h @@ -11,6 +11,7 @@ namespace Service::Audio { class AudCtl final : public ServiceFramework<AudCtl> { public: explicit AudCtl(); + ~AudCtl() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/auddbg.cpp b/src/core/hle/service/audio/auddbg.cpp index b08c21a20..8fff3e4b4 100644 --- a/src/core/hle/service/audio/auddbg.cpp +++ b/src/core/hle/service/audio/auddbg.cpp @@ -17,4 +17,6 @@ AudDbg::AudDbg(const char* name) : ServiceFramework{name} { RegisterHandlers(functions); } +AudDbg::~AudDbg() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/auddbg.h b/src/core/hle/service/audio/auddbg.h index a2f540b75..6689f4759 100644 --- a/src/core/hle/service/audio/auddbg.h +++ b/src/core/hle/service/audio/auddbg.h @@ -11,6 +11,7 @@ namespace Service::Audio { class AudDbg final : public ServiceFramework<AudDbg> { public: explicit AudDbg(const char* name); + ~AudDbg() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_a.cpp b/src/core/hle/service/audio/audin_a.cpp index a70d5bca4..ddd12f35e 100644 --- a/src/core/hle/service/audio/audin_a.cpp +++ b/src/core/hle/service/audio/audin_a.cpp @@ -19,4 +19,6 @@ AudInA::AudInA() : ServiceFramework{"audin:a"} { RegisterHandlers(functions); } +AudInA::~AudInA() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_a.h b/src/core/hle/service/audio/audin_a.h index e4c75510f..e7623bc29 100644 --- a/src/core/hle/service/audio/audin_a.h +++ b/src/core/hle/service/audio/audin_a.h @@ -11,6 +11,7 @@ namespace Service::Audio { class AudInA final : public ServiceFramework<AudInA> { public: explicit AudInA(); + ~AudInA() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp index cbc49e55e..657010312 100644 --- a/src/core/hle/service/audio/audin_u.cpp +++ b/src/core/hle/service/audio/audin_u.cpp @@ -41,4 +41,6 @@ AudInU::AudInU() : ServiceFramework("audin:u") { RegisterHandlers(functions); } +AudInU::~AudInU() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audin_u.h b/src/core/hle/service/audio/audin_u.h index 2e65efb5b..0538b9560 100644 --- a/src/core/hle/service/audio/audin_u.h +++ b/src/core/hle/service/audio/audin_u.h @@ -15,7 +15,7 @@ namespace Service::Audio { class AudInU final : public ServiceFramework<AudInU> { public: explicit AudInU(); - ~AudInU() = default; + ~AudInU() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audio.cpp b/src/core/hle/service/audio/audio.cpp index 6b5e15633..128df7db5 100644 --- a/src/core/hle/service/audio/audio.cpp +++ b/src/core/hle/service/audio/audio.cpp @@ -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 { diff --git a/src/core/hle/service/audio/audio.h b/src/core/hle/service/audio/audio.h index 95e5691f7..f5bd3bf5f 100644 --- a/src/core/hle/service/audio/audio.h +++ b/src/core/hle/service/audio/audio.h @@ -4,7 +4,9 @@ #pragma once -#include "core/hle/service/service.h" +namespace Service::SM { +class ServiceManager; +} namespace Service::Audio { diff --git a/src/core/hle/service/audio/audout_a.cpp b/src/core/hle/service/audio/audout_a.cpp index bf8d40157..85febbca3 100644 --- a/src/core/hle/service/audio/audout_a.cpp +++ b/src/core/hle/service/audio/audout_a.cpp @@ -21,4 +21,6 @@ AudOutA::AudOutA() : ServiceFramework{"audout:a"} { RegisterHandlers(functions); } +AudOutA::~AudOutA() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_a.h b/src/core/hle/service/audio/audout_a.h index 91a069152..d65b66e8e 100644 --- a/src/core/hle/service/audio/audout_a.h +++ b/src/core/hle/service/audio/audout_a.h @@ -11,6 +11,7 @@ namespace Service::Audio { class AudOutA final : public ServiceFramework<AudOutA> { public: explicit AudOutA(); + ~AudOutA() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 5f370bbdf..ff1edefbb 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -3,15 +3,20 @@ // Refer to the license.txt file included. #include <array> +#include <cstring> #include <vector> +#include "audio_core/audio_out.h" #include "audio_core/codec.h" +#include "common/common_funcs.h" #include "common/logging/log.h" +#include "common/swap.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/audio/audout_u.h" +#include "core/memory.h" namespace Service::Audio { @@ -25,6 +30,18 @@ enum { constexpr std::array<char, 10> DefaultDevice{{"DeviceOut"}}; constexpr int DefaultSampleRate{48000}; +struct AudoutParams { + s32_le sample_rate; + u16_le channel_count; + INSERT_PADDING_BYTES(2); +}; +static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); + +enum class AudioState : u32 { + Started, + Stopped, +}; + class IAudioOut final : public ServiceFramework<IAudioOut> { public: IAudioOut(AudoutParams audio_params, AudioCore::AudioOut& audio_core) @@ -173,7 +190,7 @@ void AudOutU::ListAudioOutsImpl(Kernel::HLERequestContext& ctx) { ctx.WriteBuffer(DefaultDevice); - IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); // Amount of audio devices @@ -218,4 +235,6 @@ AudOutU::AudOutU() : ServiceFramework("audout:u") { audio_core = std::make_unique<AudioCore::AudioOut>(); } +AudOutU::~AudOutU() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audout_u.h b/src/core/hle/service/audio/audout_u.h index fd491f65d..dcaf64708 100644 --- a/src/core/hle/service/audio/audout_u.h +++ b/src/core/hle/service/audio/audout_u.h @@ -4,33 +4,24 @@ #pragma once -#include "audio_core/audio_out.h" #include "core/hle/service/service.h" +namespace AudioCore { +class AudioOut; +} + namespace Kernel { class HLERequestContext; } namespace Service::Audio { -struct AudoutParams { - s32_le sample_rate; - u16_le channel_count; - INSERT_PADDING_BYTES(2); -}; -static_assert(sizeof(AudoutParams) == 0x8, "AudoutParams is an invalid size"); - -enum class AudioState : u32 { - Started, - Stopped, -}; - class IAudioOut; class AudOutU final : public ServiceFramework<AudOutU> { public: AudOutU(); - ~AudOutU() = default; + ~AudOutU() override; private: std::shared_ptr<IAudioOut> audio_out_interface; diff --git a/src/core/hle/service/audio/audrec_a.cpp b/src/core/hle/service/audio/audrec_a.cpp index 016eabf53..ce1bfb48d 100644 --- a/src/core/hle/service/audio/audrec_a.cpp +++ b/src/core/hle/service/audio/audrec_a.cpp @@ -17,4 +17,6 @@ AudRecA::AudRecA() : ServiceFramework{"audrec:a"} { RegisterHandlers(functions); } +AudRecA::~AudRecA() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audrec_a.h b/src/core/hle/service/audio/audrec_a.h index 9685047f2..384d24c69 100644 --- a/src/core/hle/service/audio/audrec_a.h +++ b/src/core/hle/service/audio/audrec_a.h @@ -11,6 +11,7 @@ namespace Service::Audio { class AudRecA final : public ServiceFramework<AudRecA> { public: explicit AudRecA(); + ~AudRecA() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audrec_u.cpp b/src/core/hle/service/audio/audrec_u.cpp index 74909415c..34974afa9 100644 --- a/src/core/hle/service/audio/audrec_u.cpp +++ b/src/core/hle/service/audio/audrec_u.cpp @@ -36,4 +36,6 @@ AudRecU::AudRecU() : ServiceFramework("audrec:u") { RegisterHandlers(functions); } +AudRecU::~AudRecU() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audrec_u.h b/src/core/hle/service/audio/audrec_u.h index 46daa33a4..ca3d638e8 100644 --- a/src/core/hle/service/audio/audrec_u.h +++ b/src/core/hle/service/audio/audrec_u.h @@ -15,7 +15,7 @@ namespace Service::Audio { class AudRecU final : public ServiceFramework<AudRecU> { public: explicit AudRecU(); - ~AudRecU() = default; + ~AudRecU() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_a.cpp b/src/core/hle/service/audio/audren_a.cpp index 616ff3dc4..edb66d985 100644 --- a/src/core/hle/service/audio/audren_a.cpp +++ b/src/core/hle/service/audio/audren_a.cpp @@ -23,4 +23,6 @@ AudRenA::AudRenA() : ServiceFramework{"audren:a"} { RegisterHandlers(functions); } +AudRenA::~AudRenA() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_a.h b/src/core/hle/service/audio/audren_a.h index 5ecf2e184..81fef0ffe 100644 --- a/src/core/hle/service/audio/audren_a.h +++ b/src/core/hle/service/audio/audren_a.h @@ -11,6 +11,7 @@ namespace Service::Audio { class AudRenA final : public ServiceFramework<AudRenA> { public: explicit AudRenA(); + ~AudRenA() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index 016db7c82..06ac6372d 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -2,12 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> +#include <memory> +#include "audio_core/audio_renderer.h" #include "common/alignment.h" +#include "common/common_funcs.h" #include "common/logging/log.h" -#include "core/core_timing.h" -#include "core/core_timing_util.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" @@ -135,7 +137,7 @@ private: constexpr std::array<char, 15> audio_interface{{"AudioInterface"}}; ctx.WriteBuffer(audio_interface); - IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); } @@ -149,7 +151,7 @@ private: auto file_buffer = ctx.ReadBuffer(); auto end = std::find(file_buffer.begin(), file_buffer.end(), '\0'); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -160,7 +162,7 @@ private: constexpr std::array<char, 12> audio_interface{{"AudioDevice"}}; ctx.WriteBuffer(audio_interface); - IPC::ResponseBuilder rb = rp.MakeBuilder(3, 0, 0); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(1); } @@ -198,6 +200,8 @@ AudRenU::AudRenU() : ServiceFramework("audren:u") { RegisterHandlers(functions); } +AudRenU::~AudRenU() = default; + void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; auto params = rp.PopRaw<AudioCore::AudioRendererParameter>(); diff --git a/src/core/hle/service/audio/audren_u.h b/src/core/hle/service/audio/audren_u.h index 8600ac6e4..c6bc3a90a 100644 --- a/src/core/hle/service/audio/audren_u.h +++ b/src/core/hle/service/audio/audren_u.h @@ -4,7 +4,6 @@ #pragma once -#include "audio_core/audio_renderer.h" #include "core/hle/service/service.h" namespace Kernel { @@ -16,7 +15,7 @@ namespace Service::Audio { class AudRenU final : public ServiceFramework<AudRenU> { public: explicit AudRenU(); - ~AudRenU() = default; + ~AudRenU() override; private: void OpenAudioRenderer(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/audio/codecctl.cpp b/src/core/hle/service/audio/codecctl.cpp index 212c8d448..c6864146d 100644 --- a/src/core/hle/service/audio/codecctl.cpp +++ b/src/core/hle/service/audio/codecctl.cpp @@ -28,4 +28,6 @@ CodecCtl::CodecCtl() : ServiceFramework("codecctl") { RegisterHandlers(functions); } +CodecCtl::~CodecCtl() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/codecctl.h b/src/core/hle/service/audio/codecctl.h index d9ac29b67..2fe75b6e2 100644 --- a/src/core/hle/service/audio/codecctl.h +++ b/src/core/hle/service/audio/codecctl.h @@ -15,7 +15,7 @@ namespace Service::Audio { class CodecCtl final : public ServiceFramework<CodecCtl> { public: explicit CodecCtl(); - ~CodecCtl() = default; + ~CodecCtl() override; }; } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 371cd4997..fc6067e59 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -3,7 +3,12 @@ // Refer to the license.txt file included. #include <cstring> +#include <memory> +#include <vector> + #include <opus.h> + +#include "common/common_funcs.h" #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/hle_ipc.h" @@ -56,7 +61,7 @@ private: bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input, std::vector<opus_int16>& output) { - size_t raw_output_sz = output.size() * sizeof(opus_int16); + std::size_t raw_output_sz = output.size() * sizeof(opus_int16); if (sizeof(OpusHeader) > input.size()) return false; OpusHeader hdr{}; @@ -91,7 +96,7 @@ private: u32 channel_count; }; -static size_t WorkerBufferSize(u32 channel_count) { +static std::size_t WorkerBufferSize(u32 channel_count) { ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); return opus_decoder_get_size(static_cast<int>(channel_count)); } @@ -124,7 +129,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) { "Invalid sample rate"); ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count"); - size_t worker_sz = WorkerBufferSize(channel_count); + std::size_t worker_sz = WorkerBufferSize(channel_count); ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large"); std::unique_ptr<OpusDecoder, OpusDeleter> decoder{ static_cast<OpusDecoder*>(operator new(worker_sz))}; @@ -151,4 +156,6 @@ HwOpus::HwOpus() : ServiceFramework("hwopus") { RegisterHandlers(functions); } +HwOpus::~HwOpus() = default; + } // namespace Service::Audio diff --git a/src/core/hle/service/audio/hwopus.h b/src/core/hle/service/audio/hwopus.h index 5258d59f3..602ede8ba 100644 --- a/src/core/hle/service/audio/hwopus.h +++ b/src/core/hle/service/audio/hwopus.h @@ -11,7 +11,7 @@ namespace Service::Audio { class HwOpus final : public ServiceFramework<HwOpus> { public: explicit HwOpus(); - ~HwOpus() = default; + ~HwOpus() override; private: void OpenOpusDecoder(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/bcat/bcat.cpp b/src/core/hle/service/bcat/bcat.cpp index 20ce692dc..179aa4949 100644 --- a/src/core/hle/service/bcat/bcat.cpp +++ b/src/core/hle/service/bcat/bcat.cpp @@ -13,4 +13,6 @@ BCAT::BCAT(std::shared_ptr<Module> module, const char* name) }; RegisterHandlers(functions); } + +BCAT::~BCAT() = default; } // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/bcat.h b/src/core/hle/service/bcat/bcat.h index 6632996a0..802bd689a 100644 --- a/src/core/hle/service/bcat/bcat.h +++ b/src/core/hle/service/bcat/bcat.h @@ -11,6 +11,7 @@ namespace Service::BCAT { class BCAT final : public Module::Interface { public: explicit BCAT(std::shared_ptr<Module> module, const char* name); + ~BCAT() override; }; } // namespace Service::BCAT diff --git a/src/core/hle/service/bcat/module.cpp b/src/core/hle/service/bcat/module.cpp index 35e024c3d..6e7b795fb 100644 --- a/src/core/hle/service/bcat/module.cpp +++ b/src/core/hle/service/bcat/module.cpp @@ -42,6 +42,8 @@ void Module::Interface::CreateBcatService(Kernel::HLERequestContext& ctx) { Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::~Interface() = default; + void InstallInterfaces(SM::ServiceManager& service_manager) { auto module = std::make_shared<Module>(); std::make_shared<BCAT>(module, "bcat:a")->InstallAsService(service_manager); diff --git a/src/core/hle/service/bcat/module.h b/src/core/hle/service/bcat/module.h index 62f6f5f9d..f0d63cab0 100644 --- a/src/core/hle/service/bcat/module.h +++ b/src/core/hle/service/bcat/module.h @@ -13,6 +13,7 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, const char* name); + ~Interface() override; void CreateBcatService(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp index 299b9474f..b436ce4e6 100644 --- a/src/core/hle/service/fatal/fatal.cpp +++ b/src/core/hle/service/fatal/fatal.cpp @@ -13,6 +13,8 @@ namespace Service::Fatal { Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::~Interface() = default; + void Module::Interface::ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); u32 error_code = rp.Pop<u32>(); diff --git a/src/core/hle/service/fatal/fatal.h b/src/core/hle/service/fatal/fatal.h index ca607e236..4d9a5be52 100644 --- a/src/core/hle/service/fatal/fatal.h +++ b/src/core/hle/service/fatal/fatal.h @@ -13,6 +13,7 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, const char* name); + ~Interface() override; void ThrowFatalWithPolicy(Kernel::HLERequestContext& ctx); void ThrowFatalWithCpuContext(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/fatal/fatal_p.cpp b/src/core/hle/service/fatal/fatal_p.cpp index a5254ac2f..9e5f872ff 100644 --- a/src/core/hle/service/fatal/fatal_p.cpp +++ b/src/core/hle/service/fatal/fatal_p.cpp @@ -9,4 +9,6 @@ namespace Service::Fatal { Fatal_P::Fatal_P(std::shared_ptr<Module> module) : Module::Interface(std::move(module), "fatal:p") {} +Fatal_P::~Fatal_P() = default; + } // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_p.h b/src/core/hle/service/fatal/fatal_p.h index bfd8c8b74..6e9c5979f 100644 --- a/src/core/hle/service/fatal/fatal_p.h +++ b/src/core/hle/service/fatal/fatal_p.h @@ -11,6 +11,7 @@ namespace Service::Fatal { class Fatal_P final : public Module::Interface { public: explicit Fatal_P(std::shared_ptr<Module> module); + ~Fatal_P() override; }; } // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_u.cpp b/src/core/hle/service/fatal/fatal_u.cpp index f0631329e..befc307cf 100644 --- a/src/core/hle/service/fatal/fatal_u.cpp +++ b/src/core/hle/service/fatal/fatal_u.cpp @@ -15,4 +15,6 @@ Fatal_U::Fatal_U(std::shared_ptr<Module> module) : Module::Interface(std::move(m RegisterHandlers(functions); } +Fatal_U::~Fatal_U() = default; + } // namespace Service::Fatal diff --git a/src/core/hle/service/fatal/fatal_u.h b/src/core/hle/service/fatal/fatal_u.h index 9b1a9e97a..72cb6d076 100644 --- a/src/core/hle/service/fatal/fatal_u.h +++ b/src/core/hle/service/fatal/fatal_u.h @@ -11,6 +11,7 @@ namespace Service::Fatal { class Fatal_U final : public Module::Interface { public: explicit Fatal_U(std::shared_ptr<Module> module); + ~Fatal_U() override; }; } // namespace Service::Fatal diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp index 881c39e31..d349ee686 100644 --- a/src/core/hle/service/filesystem/filesystem.cpp +++ b/src/core/hle/service/filesystem/filesystem.cpp @@ -10,6 +10,7 @@ #include "core/file_sys/bis_factory.h" #include "core/file_sys/errors.h" #include "core/file_sys/mode.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/sdmc_factory.h" @@ -39,6 +40,8 @@ static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base, VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_) : backing(std::move(backing_)) {} +VfsDirectoryServiceWrapper::~VfsDirectoryServiceWrapper() = default; + std::string VfsDirectoryServiceWrapper::GetName() const { return backing->GetName(); } @@ -60,17 +63,20 @@ ResultCode VfsDirectoryServiceWrapper::CreateFile(const std::string& path_, u64 ResultCode VfsDirectoryServiceWrapper::DeleteFile(const std::string& path_) const { std::string path(FileUtil::SanitizePath(path_)); - auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); if (path.empty()) { // TODO(DarkLordZach): Why do games call this and what should it do? Works as is but... return RESULT_SUCCESS; } - if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) + + auto dir = GetDirectoryRelativeWrapped(backing, FileUtil::GetParentPath(path)); + if (dir->GetFile(FileUtil::GetFilename(path)) == nullptr) { return FileSys::ERROR_PATH_NOT_FOUND; + } if (!dir->DeleteFile(FileUtil::GetFilename(path))) { // TODO(DarkLordZach): Find a better error code for this return ResultCode(-1); } + return RESULT_SUCCESS; } @@ -191,7 +197,7 @@ ResultVal<FileSys::VirtualDir> VfsDirectoryServiceWrapper::OpenDirectory(const s auto dir = GetDirectoryRelativeWrapped(backing, path); if (dir == nullptr) { // TODO(DarkLordZach): Find a better error code for this - return ResultCode(-1); + return FileSys::ERROR_PATH_NOT_FOUND; } return MakeResult(dir); } @@ -304,6 +310,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() { return sdmc_factory->Open(); } +std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() { + return std::make_shared<FileSys::RegisteredCacheUnion>( + std::vector<std::shared_ptr<FileSys::RegisteredCache>>{ + GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()}); +} + std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { LOG_TRACE(Service_FS, "Opening System NAND Contents"); diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h index 9ba0e2eab..aab65a2b8 100644 --- a/src/core/hle/service/filesystem/filesystem.h +++ b/src/core/hle/service/filesystem/filesystem.h @@ -13,6 +13,7 @@ namespace FileSys { class BISFactory; class RegisteredCache; +class RegisteredCacheUnion; class RomFSFactory; class SaveDataFactory; class SDMCFactory; @@ -45,6 +46,8 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, FileSys::SaveDataDescriptor save_struct); ResultVal<FileSys::VirtualDir> OpenSDMC(); +std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents(); + std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); @@ -61,6 +64,7 @@ void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::Virtu class VfsDirectoryServiceWrapper { public: explicit VfsDirectoryServiceWrapper(FileSys::VirtualDir backing); + ~VfsDirectoryServiceWrapper(); /** * Get a descriptive name for the archive (e.g. "RomFS", "SaveData", etc.) diff --git a/src/core/hle/service/filesystem/fsp_ldr.cpp b/src/core/hle/service/filesystem/fsp_ldr.cpp index 0ab9c2606..fb487d5bc 100644 --- a/src/core/hle/service/filesystem/fsp_ldr.cpp +++ b/src/core/hle/service/filesystem/fsp_ldr.cpp @@ -19,4 +19,6 @@ FSP_LDR::FSP_LDR() : ServiceFramework{"fsp:ldr"} { RegisterHandlers(functions); } +FSP_LDR::~FSP_LDR() = default; + } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_ldr.h b/src/core/hle/service/filesystem/fsp_ldr.h index fa8e11b4c..8210b7729 100644 --- a/src/core/hle/service/filesystem/fsp_ldr.h +++ b/src/core/hle/service/filesystem/fsp_ldr.h @@ -11,6 +11,7 @@ namespace Service::FileSystem { class FSP_LDR final : public ServiceFramework<FSP_LDR> { public: explicit FSP_LDR(); + ~FSP_LDR() override; }; } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_pr.cpp b/src/core/hle/service/filesystem/fsp_pr.cpp index 32b0ae454..378201610 100644 --- a/src/core/hle/service/filesystem/fsp_pr.cpp +++ b/src/core/hle/service/filesystem/fsp_pr.cpp @@ -20,4 +20,6 @@ FSP_PR::FSP_PR() : ServiceFramework{"fsp:pr"} { RegisterHandlers(functions); } +FSP_PR::~FSP_PR() = default; + } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_pr.h b/src/core/hle/service/filesystem/fsp_pr.h index 62edcd08a..556ae5ce9 100644 --- a/src/core/hle/service/filesystem/fsp_pr.h +++ b/src/core/hle/service/filesystem/fsp_pr.h @@ -11,6 +11,7 @@ namespace Service::FileSystem { class FSP_PR final : public ServiceFramework<FSP_PR> { public: explicit FSP_PR(); + ~FSP_PR() override; }; } // namespace Service::FileSystem diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 5759299fe..cabaf5a55 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -26,6 +26,17 @@ namespace Service::FileSystem { +enum class FileSystemType : u8 { + Invalid0 = 0, + Invalid1 = 1, + Logo = 2, + ContentControl = 3, + ContentManual = 4, + ContentMeta = 5, + ContentData = 6, + ApplicationPackage = 7, +}; + class IStorage final : public ServiceFramework<IStorage> { public: explicit IStorage(FileSys::VirtualFile backend_) @@ -420,7 +431,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { {0, nullptr, "MountContent"}, {1, &FSP_SRV::Initialize, "Initialize"}, {2, nullptr, "OpenDataFileSystemByCurrentProcess"}, - {7, nullptr, "OpenFileSystemWithPatch"}, + {7, &FSP_SRV::OpenFileSystemWithPatch, "OpenFileSystemWithPatch"}, {8, nullptr, "OpenFileSystemWithId"}, {9, nullptr, "OpenDataFileSystemByApplicationId"}, {11, nullptr, "OpenBisFileSystem"}, @@ -444,7 +455,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { {34, nullptr, "GetCacheStorageSize"}, {51, &FSP_SRV::MountSaveData, "MountSaveData"}, {52, nullptr, "OpenSaveDataFileSystemBySystemSaveDataId"}, - {53, nullptr, "OpenReadOnlySaveDataFileSystem"}, + {53, &FSP_SRV::OpenReadOnlySaveDataFileSystem, "OpenReadOnlySaveDataFileSystem"}, {57, nullptr, "ReadSaveDataFileSystemExtraDataBySaveDataSpaceId"}, {58, nullptr, "ReadSaveDataFileSystemExtraData"}, {59, nullptr, "WriteSaveDataFileSystemExtraData"}, @@ -509,6 +520,8 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") { RegisterHandlers(functions); } +FSP_SRV::~FSP_SRV() = default; + void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_FS, "(STUBBED) called"); @@ -516,6 +529,16 @@ void FSP_SRV::Initialize(Kernel::HLERequestContext& ctx) { rb.Push(RESULT_SUCCESS); } +void FSP_SRV::OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + const auto type = rp.PopRaw<FileSystemType>(); + const auto title_id = rp.PopRaw<u64>(); + + IPC::ResponseBuilder rb{ctx, 2, 0, 0}; + rb.Push(ResultCode(-1)); +} + void FSP_SRV::MountSdCard(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_FS, "called"); @@ -563,6 +586,11 @@ void FSP_SRV::MountSaveData(Kernel::HLERequestContext& ctx) { rb.PushIpcInterface<IFileSystem>(std::move(filesystem)); } +void FSP_SRV::OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_FS, "(STUBBED) called, delegating to 51 OpenSaveDataFilesystem"); + MountSaveData(ctx); +} + void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_FS, "(STUBBED) called"); diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index f073ac523..4aa0358cb 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h @@ -16,13 +16,15 @@ namespace Service::FileSystem { class FSP_SRV final : public ServiceFramework<FSP_SRV> { public: explicit FSP_SRV(); - ~FSP_SRV() = default; + ~FSP_SRV() override; private: void Initialize(Kernel::HLERequestContext& ctx); + void OpenFileSystemWithPatch(Kernel::HLERequestContext& ctx); void MountSdCard(Kernel::HLERequestContext& ctx); void CreateSaveData(Kernel::HLERequestContext& ctx); void MountSaveData(Kernel::HLERequestContext& ctx); + void OpenReadOnlySaveDataFileSystem(Kernel::HLERequestContext& ctx); void GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx); void OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx); void OpenDataStorageByDataId(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index f2b0e509a..d9225d624 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -118,6 +118,8 @@ void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) { Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::~Interface() = default; + void InstallInterfaces(SM::ServiceManager& service_manager) { auto module = std::make_shared<Module>(); std::make_shared<Friend>(module, "friend:a")->InstallAsService(service_manager); diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h index c1b36518a..e762840cb 100644 --- a/src/core/hle/service/friend/friend.h +++ b/src/core/hle/service/friend/friend.h @@ -13,6 +13,7 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, const char* name); + ~Interface() override; void CreateFriendService(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp index 27c6a09e2..5a6840af5 100644 --- a/src/core/hle/service/friend/interface.cpp +++ b/src/core/hle/service/friend/interface.cpp @@ -16,4 +16,6 @@ Friend::Friend(std::shared_ptr<Module> module, const char* name) RegisterHandlers(functions); } +Friend::~Friend() = default; + } // namespace Service::Friend diff --git a/src/core/hle/service/friend/interface.h b/src/core/hle/service/friend/interface.h index 89dae8471..1963def39 100644 --- a/src/core/hle/service/friend/interface.h +++ b/src/core/hle/service/friend/interface.h @@ -11,6 +11,7 @@ namespace Service::Friend { class Friend final : public Module::Interface { public: explicit Friend(std::shared_ptr<Module> module, const char* name); + ~Friend() override; }; } // namespace Service::Friend diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 0d31abe8b..7c6b0a4e6 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <atomic> #include "common/logging/log.h" #include "core/core.h" #include "core/core_timing.h" @@ -78,7 +77,7 @@ private: SharedMemory mem{}; std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory)); - if (is_device_reload_pending.exchange(false)) + if (Settings::values.is_device_reload_pending.exchange(false)) LoadInputDevices(); // Set up controllers as neon red+blue Joy-Con attached to console @@ -90,7 +89,7 @@ private: controller_header.left_color_body = JOYCON_BODY_NEON_BLUE; controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE; - for (size_t controller = 0; controller < mem.controllers.size(); controller++) { + for (std::size_t controller = 0; controller < mem.controllers.size(); controller++) { for (auto& layout : mem.controllers[controller].layouts) { layout.header.num_entries = HID_NUM_ENTRIES; layout.header.max_entry_index = HID_NUM_ENTRIES - 1; @@ -267,7 +266,6 @@ private: CoreTiming::EventType* pad_update_event; // Stored input state info - std::atomic<bool> is_device_reload_pending{true}; std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID> buttons; std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks; @@ -315,7 +313,7 @@ public: {64, nullptr, "DeactivateJoySixAxisSensor"}, {65, nullptr, "GetJoySixAxisSensorLifoHandle"}, {66, &Hid::StartSixAxisSensor, "StartSixAxisSensor"}, - {67, nullptr, "StopSixAxisSensor"}, + {67, &Hid::StopSixAxisSensor, "StopSixAxisSensor"}, {68, nullptr, "IsSixAxisSensorFusionEnabled"}, {69, nullptr, "EnableSixAxisSensorFusion"}, {70, nullptr, "SetSixAxisSensorFusionParameters"}, @@ -331,7 +329,7 @@ public: {80, nullptr, "GetGyroscopeZeroDriftMode"}, {81, nullptr, "ResetGyroscopeZeroDriftMode"}, {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, - {91, nullptr, "ActivateGesture"}, + {91, &Hid::ActivateGesture, "ActivateGesture"}, {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, {102, &Hid::SetSupportedNpadIdType, "SetSupportedNpadIdType"}, @@ -340,7 +338,7 @@ public: {106, &Hid::AcquireNpadStyleSetUpdateEventHandle, "AcquireNpadStyleSetUpdateEventHandle"}, {107, &Hid::DisconnectNpad, "DisconnectNpad"}, {108, &Hid::GetPlayerLedPattern, "GetPlayerLedPattern"}, - {109, nullptr, "ActivateNpadWithRevision"}, + {109, &Hid::ActivateNpadWithRevision, "ActivateNpadWithRevision"}, {120, &Hid::SetNpadJoyHoldType, "SetNpadJoyHoldType"}, {121, &Hid::GetNpadJoyHoldType, "GetNpadJoyHoldType"}, {122, &Hid::SetNpadJoyAssignmentModeSingleByDefault, "SetNpadJoyAssignmentModeSingleByDefault"}, @@ -366,8 +364,8 @@ public: {208, nullptr, "GetActualVibrationGcErmCommand"}, {209, nullptr, "BeginPermitVibrationSession"}, {210, nullptr, "EndPermitVibrationSession"}, - {300, nullptr, "ActivateConsoleSixAxisSensor"}, - {301, nullptr, "StartConsoleSixAxisSensor"}, + {300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"}, + {301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"}, {302, nullptr, "StopConsoleSixAxisSensor"}, {303, nullptr, "ActivateSevenSixAxisSensor"}, {304, nullptr, "StartSevenSixAxisSensor"}, @@ -581,6 +579,36 @@ private: rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_HID, "(STUBBED) called"); } + + void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void StopSixAxisSensor(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void ActivateGesture(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } + + void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_HID, "(STUBBED) called"); + } }; class HidDbg final : public ServiceFramework<HidDbg> { @@ -797,7 +825,9 @@ public: } }; -void ReloadInputDevices() {} +void ReloadInputDevices() { + Settings::values.is_device_reload_pending.store(true); +} void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<Hid>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp index aaf311912..e587ad0d8 100644 --- a/src/core/hle/service/hid/irs.cpp +++ b/src/core/hle/service/hid/irs.cpp @@ -33,6 +33,8 @@ IRS::IRS() : ServiceFramework{"irs"} { RegisterHandlers(functions); } +IRS::~IRS() = default; + IRS_SYS::IRS_SYS() : ServiceFramework{"irs:sys"} { // clang-format off static const FunctionInfo functions[] = { @@ -46,4 +48,6 @@ IRS_SYS::IRS_SYS() : ServiceFramework{"irs:sys"} { RegisterHandlers(functions); } +IRS_SYS::~IRS_SYS() = default; + } // namespace Service::HID diff --git a/src/core/hle/service/hid/irs.h b/src/core/hle/service/hid/irs.h index a8be701c7..6fb16b45d 100644 --- a/src/core/hle/service/hid/irs.h +++ b/src/core/hle/service/hid/irs.h @@ -11,11 +11,13 @@ namespace Service::HID { class IRS final : public ServiceFramework<IRS> { public: explicit IRS(); + ~IRS() override; }; class IRS_SYS final : public ServiceFramework<IRS_SYS> { public: explicit IRS_SYS(); + ~IRS_SYS() override; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/xcd.cpp b/src/core/hle/service/hid/xcd.cpp index 49f733f60..c8e9125f6 100644 --- a/src/core/hle/service/hid/xcd.cpp +++ b/src/core/hle/service/hid/xcd.cpp @@ -34,4 +34,6 @@ XCD_SYS::XCD_SYS() : ServiceFramework{"xcd:sys"} { RegisterHandlers(functions); } +XCD_SYS::~XCD_SYS() = default; + } // namespace Service::HID diff --git a/src/core/hle/service/hid/xcd.h b/src/core/hle/service/hid/xcd.h index 232a044df..fd506d303 100644 --- a/src/core/hle/service/hid/xcd.h +++ b/src/core/hle/service/hid/xcd.h @@ -11,6 +11,7 @@ namespace Service::HID { class XCD_SYS final : public ServiceFramework<XCD_SYS> { public: explicit XCD_SYS(); + ~XCD_SYS() override; }; } // namespace Service::HID diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 098da2a41..c89157a4d 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp @@ -99,7 +99,7 @@ private: std::string thread; while (addr < end_addr) { const Field field{static_cast<Field>(Memory::Read8(addr++))}; - const size_t length{Memory::Read8(addr++)}; + const std::size_t length{Memory::Read8(addr++)}; if (static_cast<Field>(Memory::Read8(addr)) == Field::Skip) { ++addr; diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 4f7543af5..f8d2127d9 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -14,6 +14,8 @@ namespace Service::NFP { Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::~Interface() = default; + class IUser final : public ServiceFramework<IUser> { public: IUser() : ServiceFramework("IUser") { diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 0cd7be3d5..77df343c4 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -13,6 +13,7 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, const char* name); + ~Interface() override; void CreateUserInterface(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp index b608fe693..784a87c1b 100644 --- a/src/core/hle/service/nfp/nfp_user.cpp +++ b/src/core/hle/service/nfp/nfp_user.cpp @@ -14,4 +14,6 @@ NFP_User::NFP_User(std::shared_ptr<Module> module) RegisterHandlers(functions); } +NFP_User::~NFP_User() = default; + } // namespace Service::NFP diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h index 700043114..65d9aaf48 100644 --- a/src/core/hle/service/nfp/nfp_user.h +++ b/src/core/hle/service/nfp/nfp_user.h @@ -11,6 +11,7 @@ namespace Service::NFP { class NFP_User final : public Module::Interface { public: explicit NFP_User(std::shared_ptr<Module> module); + ~NFP_User() override; }; } // namespace Service::NFP diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp index ed4f5f539..10611ed6a 100644 --- a/src/core/hle/service/nifm/nifm.cpp +++ b/src/core/hle/service/nifm/nifm.cpp @@ -31,7 +31,7 @@ public: {1, &IRequest::GetResult, "GetResult"}, {2, &IRequest::GetSystemEventReadableHandles, "GetSystemEventReadableHandles"}, {3, &IRequest::Cancel, "Cancel"}, - {4, nullptr, "Submit"}, + {4, &IRequest::Submit, "Submit"}, {5, nullptr, "SetRequirement"}, {6, nullptr, "SetRequirementPreset"}, {8, nullptr, "SetPriority"}, @@ -61,6 +61,12 @@ public: } private: + void Submit(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NIFM, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + void GetRequestState(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 3}; @@ -114,10 +120,11 @@ public: private: void GetClientId(Kernel::HLERequestContext& ctx) { + static constexpr u32 client_id = 1; LOG_WARNING(Service_NIFM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push<u64>(0); + rb.Push<u64>(client_id); // Client ID needs to be non zero otherwise it's considered invalid } void CreateScanRequest(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; @@ -141,10 +148,16 @@ private: rb.Push(RESULT_SUCCESS); } void CreateTemporaryNetworkProfile(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + ASSERT_MSG(ctx.GetReadBufferSize() == 0x17c, "NetworkProfileData is not the correct size"); + u128 uuid{}; + auto buffer = ctx.ReadBuffer(); + std::memcpy(&uuid, buffer.data() + 8, sizeof(u128)); + + IPC::ResponseBuilder rb{ctx, 6, 0, 1}; rb.Push(RESULT_SUCCESS); rb.PushIpcInterface<INetworkProfile>(); + rb.PushRaw<u128>(uuid); LOG_DEBUG(Service_NIFM, "called"); } diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index bd05b0a70..c1737defa 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp @@ -2,6 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <chrono> +#include <ctime> +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" #include "core/hle/service/nim/nim.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" @@ -100,19 +104,111 @@ public: } }; +class IEnsureNetworkClockAvailabilityService final + : public ServiceFramework<IEnsureNetworkClockAvailabilityService> { +public: + IEnsureNetworkClockAvailabilityService() + : ServiceFramework("IEnsureNetworkClockAvailabilityService") { + static const FunctionInfo functions[] = { + {0, &IEnsureNetworkClockAvailabilityService::StartTask, "StartTask"}, + {1, &IEnsureNetworkClockAvailabilityService::GetFinishNotificationEvent, + "GetFinishNotificationEvent"}, + {2, &IEnsureNetworkClockAvailabilityService::GetResult, "GetResult"}, + {3, &IEnsureNetworkClockAvailabilityService::Cancel, "Cancel"}, + {4, &IEnsureNetworkClockAvailabilityService::IsProcessing, "IsProcessing"}, + {5, &IEnsureNetworkClockAvailabilityService::GetServerTime, "GetServerTime"}, + }; + RegisterHandlers(functions); + + auto& kernel = Core::System::GetInstance().Kernel(); + finished_event = + Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, + "IEnsureNetworkClockAvailabilityService:FinishEvent"); + } + +private: + Kernel::SharedPtr<Kernel::Event> finished_event; + + void StartTask(Kernel::HLERequestContext& ctx) { + // No need to connect to the internet, just finish the task straight away. + finished_event->Signal(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_NIM, "called"); + } + + void GetFinishNotificationEvent(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(finished_event); + LOG_DEBUG(Service_NIM, "called"); + } + + void GetResult(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_NIM, "called"); + } + + void Cancel(Kernel::HLERequestContext& ctx) { + finished_event->Clear(); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_NIM, "called"); + } + + void IsProcessing(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(0); // We instantly process the request + LOG_DEBUG(Service_NIM, "called"); + } + + void GetServerTime(Kernel::HLERequestContext& ctx) { + const s64 server_time{std::chrono::duration_cast<std::chrono::seconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count()}; + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<s64>(server_time); + LOG_DEBUG(Service_NIM, "called"); + } +}; + class NTC final : public ServiceFramework<NTC> { public: explicit NTC() : ServiceFramework{"ntc"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "OpenEnsureNetworkClockAvailabilityService"}, - {100, nullptr, "SuspendAutonomicTimeCorrection"}, - {101, nullptr, "ResumeAutonomicTimeCorrection"}, + {0, &NTC::OpenEnsureNetworkClockAvailabilityService, "OpenEnsureNetworkClockAvailabilityService"}, + {100, &NTC::SuspendAutonomicTimeCorrection, "SuspendAutonomicTimeCorrection"}, + {101, &NTC::ResumeAutonomicTimeCorrection, "ResumeAutonomicTimeCorrection"}, }; // clang-format on RegisterHandlers(functions); } + +private: + void OpenEnsureNetworkClockAvailabilityService(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IEnsureNetworkClockAvailabilityService>(); + LOG_DEBUG(Service_NIM, "called"); + } + + // TODO(ogniK): Do we need these? + void SuspendAutonomicTimeCorrection(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_NIM, "(STUBBED) called"); + } + + void ResumeAutonomicTimeCorrection(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_NIM, "(STUBBED) called"); + } }; void InstallInterfaces(SM::ServiceManager& sm) { diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 51638793d..1069d103f 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -2,12 +2,30 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <cstring> +#include <vector> + +#include <FontChineseSimplified.h> +#include <FontChineseTraditional.h> +#include <FontExtendedChineseSimplified.h> +#include <FontKorean.h> +#include <FontNintendoExtended.h> +#include <FontStandard.h> + +#include "common/assert.h" #include "common/common_paths.h" +#include "common/common_types.h" #include "common/file_util.h" +#include "common/logging/log.h" +#include "common/swap.h" #include "core/core.h" -#include "core/file_sys/bis_factory.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/ns/pl_u.h" @@ -26,49 +44,41 @@ struct FontRegion { u32 size; }; -static constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{ +constexpr std::array<std::pair<FontArchives, const char*>, 7> SHARED_FONTS{ std::make_pair(FontArchives::Standard, "nintendo_udsg-r_std_003.bfttf"), std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_org_zh-cn_003.bfttf"), std::make_pair(FontArchives::ChineseSimple, "nintendo_udsg-r_ext_zh-cn_003.bfttf"), std::make_pair(FontArchives::ChineseTraditional, "nintendo_udjxh-db_zh-tw_003.bfttf"), std::make_pair(FontArchives::Korean, "nintendo_udsg-r_ko_003.bfttf"), std::make_pair(FontArchives::Extension, "nintendo_ext_003.bfttf"), - std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf")}; + std::make_pair(FontArchives::Extension, "nintendo_ext2_003.bfttf"), +}; -static constexpr std::array<const char*, 7> SHARED_FONTS_TTF{"FontStandard.ttf", - "FontChineseSimplified.ttf", - "FontExtendedChineseSimplified.ttf", - "FontChineseTraditional.ttf", - "FontKorean.ttf", - "FontNintendoExtended.ttf", - "FontNintendoExtended2.ttf"}; +constexpr std::array<const char*, 7> SHARED_FONTS_TTF{ + "FontStandard.ttf", + "FontChineseSimplified.ttf", + "FontExtendedChineseSimplified.ttf", + "FontChineseTraditional.ttf", + "FontKorean.ttf", + "FontNintendoExtended.ttf", + "FontNintendoExtended2.ttf", +}; // The below data is specific to shared font data dumped from Switch on f/w 2.2 // Virtual address and offsets/sizes likely will vary by dump -static constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; -static constexpr u32 EXPECTED_RESULT{ - 0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be -static constexpr u32 EXPECTED_MAGIC{ - 0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be -static constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; -static constexpr FontRegion EMPTY_REGION{0, 0}; -std::vector<FontRegion> - SHARED_FONT_REGIONS{}; // Automatically populated based on shared_fonts dump or system archives - -const FontRegion& GetSharedFontRegion(size_t index) { - if (index >= SHARED_FONT_REGIONS.size() || SHARED_FONT_REGIONS.empty()) { - // No font fallback - return EMPTY_REGION; - } - return SHARED_FONT_REGIONS.at(index); -} +constexpr VAddr SHARED_FONT_MEM_VADDR{0x00000009d3016000ULL}; +constexpr u32 EXPECTED_RESULT{0x7f9a0218}; // What we expect the decrypted bfttf first 4 bytes to be +constexpr u32 EXPECTED_MAGIC{0x36f81a1e}; // What we expect the encrypted bfttf first 4 bytes to be +constexpr u64 SHARED_FONT_MEM_SIZE{0x1100000}; +constexpr FontRegion EMPTY_REGION{0, 0}; enum class LoadState : u32 { Loading = 0, Done = 1, }; -void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, size_t& offset) { +static void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, + std::size_t& offset) { ASSERT_MSG(offset + (input.size() * sizeof(u32)) < SHARED_FONT_MEM_SIZE, "Shared fonts exceeds 17mb!"); ASSERT_MSG(input[0] == EXPECTED_MAGIC, "Failed to derive key, unexpected magic number"); @@ -85,7 +95,7 @@ void DecryptSharedFont(const std::vector<u32>& input, std::vector<u8>& output, s } static void EncryptSharedFont(const std::vector<u8>& input, std::vector<u8>& output, - size_t& offset) { + std::size_t& offset) { ASSERT_MSG(offset + input.size() + 8 < SHARED_FONT_MEM_SIZE, "Shared fonts exceeds 17mb!"); const u32 KEY = EXPECTED_MAGIC ^ EXPECTED_RESULT; std::memcpy(output.data() + offset, &EXPECTED_RESULT, sizeof(u32)); // Magic header @@ -95,28 +105,52 @@ static void EncryptSharedFont(const std::vector<u8>& input, std::vector<u8>& out offset += input.size() + (sizeof(u32) * 2); } +// Helper function to make BuildSharedFontsRawRegions a bit nicer static u32 GetU32Swapped(const u8* data) { u32 value; std::memcpy(&value, data, sizeof(value)); - return Common::swap32(value); // Helper function to make BuildSharedFontsRawRegions a bit nicer + return Common::swap32(value); } -void BuildSharedFontsRawRegions(const std::vector<u8>& input) { - unsigned cur_offset = 0; // As we can derive the xor key we can just populate the offsets based - // on the shared memory dump - for (size_t i = 0; i < SHARED_FONTS.size(); i++) { - // Out of shared fonts/Invalid font - if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) - break; - const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ - EXPECTED_MAGIC; // Derive key withing inverse xor - const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY; - SHARED_FONT_REGIONS.push_back(FontRegion{cur_offset + 8, SIZE}); - cur_offset += SIZE + 8; +struct PL_U::Impl { + const FontRegion& GetSharedFontRegion(std::size_t index) const { + if (index >= shared_font_regions.size() || shared_font_regions.empty()) { + // No font fallback + return EMPTY_REGION; + } + return shared_font_regions.at(index); } -} -PL_U::PL_U() : ServiceFramework("pl:u") { + void BuildSharedFontsRawRegions(const std::vector<u8>& input) { + // As we can derive the xor key we can just populate the offsets + // based on the shared memory dump + unsigned cur_offset = 0; + + for (std::size_t i = 0; i < SHARED_FONTS.size(); i++) { + // Out of shared fonts/invalid font + if (GetU32Swapped(input.data() + cur_offset) != EXPECTED_RESULT) { + break; + } + + // Derive key withing inverse xor + const u32 KEY = GetU32Swapped(input.data() + cur_offset) ^ EXPECTED_MAGIC; + const u32 SIZE = GetU32Swapped(input.data() + cur_offset + 4) ^ KEY; + shared_font_regions.push_back(FontRegion{cur_offset + 8, SIZE}); + cur_offset += SIZE + 8; + } + } + + /// Handle to shared memory region designated for a shared font + Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; + + /// Backing memory for the shared font data + std::shared_ptr<std::vector<u8>> shared_font; + + // Automatically populated based on shared_fonts dump or system archives. + std::vector<FontRegion> shared_font_regions; +}; + +PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} { static const FunctionInfo functions[] = { {0, &PL_U::RequestLoad, "RequestLoad"}, {1, &PL_U::GetLoadState, "GetLoadState"}, @@ -128,11 +162,11 @@ PL_U::PL_U() : ServiceFramework("pl:u") { RegisterHandlers(functions); // Attempt to load shared font data from disk const auto nand = FileSystem::GetSystemNANDContents(); - size_t offset = 0; + std::size_t offset = 0; // Rebuild shared fonts from data ncas if (nand->HasEntry(static_cast<u64>(FontArchives::Standard), FileSys::ContentRecordType::Data)) { - shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE); + impl->shared_font = std::make_shared<std::vector<u8>>(SHARED_FONT_MEM_SIZE); for (auto font : SHARED_FONTS) { const auto nca = nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data); @@ -168,12 +202,12 @@ PL_U::PL_U() : ServiceFramework("pl:u") { static_cast<u32>(offset + 8), static_cast<u32>((font_data_u32.size() * sizeof(u32)) - 8)}; // Font offset and size do not account for the header - DecryptSharedFont(font_data_u32, *shared_font, offset); - SHARED_FONT_REGIONS.push_back(region); + DecryptSharedFont(font_data_u32, *impl->shared_font, offset); + impl->shared_font_regions.push_back(region); } } else { - shared_font = std::make_shared<std::vector<u8>>( + impl->shared_font = std::make_shared<std::vector<u8>>( SHARED_FONT_MEM_SIZE); // Shared memory needs to always be allocated and a fixed size const std::string user_path = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir); @@ -197,8 +231,8 @@ PL_U::PL_U() : ServiceFramework("pl:u") { static_cast<u32>(offset + 8), static_cast<u32>(ttf_bytes.size())}; // Font offset and size do not account // for the header - EncryptSharedFont(ttf_bytes, *shared_font, offset); - SHARED_FONT_REGIONS.push_back(region); + EncryptSharedFont(ttf_bytes, *impl->shared_font, offset); + impl->shared_font_regions.push_back(region); } else { LOG_WARNING(Service_NS, "Unable to load font: {}", font_ttf); } @@ -213,14 +247,35 @@ PL_U::PL_U() : ServiceFramework("pl:u") { if (file.IsOpen()) { // Read shared font data ASSERT(file.GetSize() == SHARED_FONT_MEM_SIZE); - file.ReadBytes(shared_font->data(), shared_font->size()); - BuildSharedFontsRawRegions(*shared_font); + file.ReadBytes(impl->shared_font->data(), impl->shared_font->size()); + impl->BuildSharedFontsRawRegions(*impl->shared_font); } else { - LOG_WARNING(Service_NS, "Unable to load shared font: {}", filepath); + LOG_WARNING(Service_NS, + "Shared Font file missing. Loading open source replacement from memory"); + + // clang-format off + const std::vector<std::vector<u8>> open_source_shared_fonts_ttf = { + {std::begin(FontChineseSimplified), std::end(FontChineseSimplified)}, + {std::begin(FontChineseTraditional), std::end(FontChineseTraditional)}, + {std::begin(FontExtendedChineseSimplified), std::end(FontExtendedChineseSimplified)}, + {std::begin(FontKorean), std::end(FontKorean)}, + {std::begin(FontNintendoExtended), std::end(FontNintendoExtended)}, + {std::begin(FontStandard), std::end(FontStandard)}, + }; + // clang-format on + + for (const std::vector<u8>& font_ttf : open_source_shared_fonts_ttf) { + const FontRegion region{static_cast<u32>(offset + 8), + static_cast<u32>(font_ttf.size())}; + EncryptSharedFont(font_ttf, *impl->shared_font, offset); + impl->shared_font_regions.push_back(region); + } } } } +PL_U::~PL_U() = default; + void PL_U::RequestLoad(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u32 shared_font_type{rp.Pop<u32>()}; @@ -247,7 +302,7 @@ void PL_U::GetSize(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called, font_id={}", font_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(GetSharedFontRegion(font_id).size); + rb.Push<u32>(impl->GetSharedFontRegion(font_id).size); } void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { @@ -257,17 +312,18 @@ void PL_U::GetSharedMemoryAddressOffset(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called, font_id={}", font_id); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(GetSharedFontRegion(font_id).offset); + rb.Push<u32>(impl->GetSharedFontRegion(font_id).offset); } void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { // Map backing memory for the font data - Core::CurrentProcess()->vm_manager.MapMemoryBlock( - SHARED_FONT_MEM_VADDR, shared_font, 0, SHARED_FONT_MEM_SIZE, Kernel::MemoryState::Shared); + Core::CurrentProcess()->vm_manager.MapMemoryBlock(SHARED_FONT_MEM_VADDR, impl->shared_font, 0, + SHARED_FONT_MEM_SIZE, + Kernel::MemoryState::Shared); // Create shared font memory object auto& kernel = Core::System::GetInstance().Kernel(); - shared_font_mem = Kernel::SharedMemory::Create( + impl->shared_font_mem = Kernel::SharedMemory::Create( kernel, Core::CurrentProcess(), SHARED_FONT_MEM_SIZE, Kernel::MemoryPermission::ReadWrite, Kernel::MemoryPermission::Read, SHARED_FONT_MEM_VADDR, Kernel::MemoryRegion::BASE, "PL_U:shared_font_mem"); @@ -275,7 +331,7 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NS, "called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(shared_font_mem); + rb.PushCopyObjects(impl->shared_font_mem); } void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { @@ -288,9 +344,9 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { std::vector<u32> font_sizes; // TODO(ogniK): Have actual priority order - for (size_t i = 0; i < SHARED_FONT_REGIONS.size(); i++) { + for (std::size_t i = 0; i < impl->shared_font_regions.size(); i++) { font_codes.push_back(static_cast<u32>(i)); - auto region = GetSharedFontRegion(i); + auto region = impl->GetSharedFontRegion(i); font_offsets.push_back(region.offset); font_sizes.push_back(region.size); } diff --git a/src/core/hle/service/ns/pl_u.h b/src/core/hle/service/ns/pl_u.h index fcc2acab7..253f26a2a 100644 --- a/src/core/hle/service/ns/pl_u.h +++ b/src/core/hle/service/ns/pl_u.h @@ -5,7 +5,6 @@ #pragma once #include <memory> -#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/service.h" namespace Service::NS { @@ -13,7 +12,7 @@ namespace Service::NS { class PL_U final : public ServiceFramework<PL_U> { public: PL_U(); - ~PL_U() = default; + ~PL_U() override; private: void RequestLoad(Kernel::HLERequestContext& ctx); @@ -23,11 +22,8 @@ private: void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); void GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx); - /// Handle to shared memory region designated for a shared font - Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; - - /// Backing memory for the shared font data - std::shared_ptr<std::vector<u8>> shared_font; + struct Impl; + std::unique_ptr<Impl> impl; }; } // namespace Service::NS diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index 0b37098e1..92acc57b1 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -13,6 +13,9 @@ namespace Service::Nvidia::Devices { +nvdisp_disp0::nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} +nvdisp_disp0 ::~nvdisp_disp0() = default; + u32 nvdisp_disp0::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { UNIMPLEMENTED_MSG("Unimplemented ioctl"); return 0; diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 6f0697b58..a45086e45 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -17,8 +17,8 @@ class nvmap; class nvdisp_disp0 final : public nvdevice { public: - explicit nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} - ~nvdisp_disp0() = default; + explicit nvdisp_disp0(std::shared_ptr<nvmap> nvmap_dev); + ~nvdisp_disp0(); u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp index 75487c4e8..d8b8037a8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include <cstring> +#include <utility> + #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" @@ -14,6 +16,9 @@ namespace Service::Nvidia::Devices { +nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} +nvhost_as_gpu::~nvhost_as_gpu() = default; + u32 nvhost_as_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); @@ -66,7 +71,7 @@ u32 nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<u8>& } u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output) { - size_t num_entries = input.size() / sizeof(IoctlRemapEntry); + std::size_t num_entries = input.size() / sizeof(IoctlRemapEntry); LOG_WARNING(Service_NVDRV, "(STUBBED) called, num_entries=0x{:X}", num_entries); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h index 9f8999d9c..eb14b1da8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.h @@ -6,7 +6,6 @@ #include <memory> #include <unordered_map> -#include <utility> #include <vector> #include "common/common_types.h" #include "common/swap.h" @@ -18,8 +17,8 @@ class nvmap; class nvhost_as_gpu final : public nvdevice { public: - explicit nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} - ~nvhost_as_gpu() override = default; + explicit nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev); + ~nvhost_as_gpu() override; u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp index 5685eb2be..b39fb9ef9 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.cpp @@ -11,6 +11,9 @@ namespace Service::Nvidia::Devices { +nvhost_ctrl::nvhost_ctrl() = default; +nvhost_ctrl::~nvhost_ctrl() = default; + u32 nvhost_ctrl::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h index 6b496e9fe..6d0de2212 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl.h @@ -13,8 +13,8 @@ namespace Service::Nvidia::Devices { class nvhost_ctrl final : public nvdevice { public: - nvhost_ctrl() = default; - ~nvhost_ctrl() override = default; + nvhost_ctrl(); + ~nvhost_ctrl() override; u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp index ae421247d..7a88ae029 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.cpp @@ -9,6 +9,9 @@ namespace Service::Nvidia::Devices { +nvhost_ctrl_gpu::nvhost_ctrl_gpu() = default; +nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default; + u32 nvhost_ctrl_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h index f09113e67..3bbf028ad 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h @@ -13,8 +13,8 @@ namespace Service::Nvidia::Devices { class nvhost_ctrl_gpu final : public nvdevice { public: - nvhost_ctrl_gpu() = default; - ~nvhost_ctrl_gpu() override = default; + nvhost_ctrl_gpu(); + ~nvhost_ctrl_gpu() override; u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp index 4cdf7f613..874d5e1c3 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp @@ -8,11 +8,15 @@ #include "core/core.h" #include "core/hle/service/nvdrv/devices/nvhost_gpu.h" #include "core/memory.h" +#include "video_core/command_processor.h" #include "video_core/gpu.h" #include "video_core/memory_manager.h" namespace Service::Nvidia::Devices { +nvhost_gpu::nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} +nvhost_gpu::~nvhost_gpu() = default; + u32 nvhost_gpu::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); @@ -134,17 +138,16 @@ u32 nvhost_gpu::SubmitGPFIFO(const std::vector<u8>& input, std::vector<u8>& outp LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, params.num_entries, params.flags); - ASSERT_MSG(input.size() == - sizeof(IoctlSubmitGpfifo) + params.num_entries * sizeof(IoctlGpfifoEntry), + ASSERT_MSG(input.size() == sizeof(IoctlSubmitGpfifo) + + params.num_entries * sizeof(Tegra::CommandListHeader), "Incorrect input size"); - std::vector<IoctlGpfifoEntry> entries(params.num_entries); + std::vector<Tegra::CommandListHeader> entries(params.num_entries); std::memcpy(entries.data(), &input[sizeof(IoctlSubmitGpfifo)], - params.num_entries * sizeof(IoctlGpfifoEntry)); - for (auto entry : entries) { - Tegra::GPUVAddr va_addr = entry.Address(); - Core::System::GetInstance().GPU().ProcessCommandList(va_addr, entry.sz); - } + params.num_entries * sizeof(Tegra::CommandListHeader)); + + Core::System::GetInstance().GPU().ProcessCommandLists(entries); + params.fence_out.id = 0; params.fence_out.value = 0; std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo)); @@ -160,14 +163,12 @@ u32 nvhost_gpu::KickoffPB(const std::vector<u8>& input, std::vector<u8>& output) LOG_WARNING(Service_NVDRV, "(STUBBED) called, gpfifo={:X}, num_entries={:X}, flags={:X}", params.address, params.num_entries, params.flags); - std::vector<IoctlGpfifoEntry> entries(params.num_entries); + std::vector<Tegra::CommandListHeader> entries(params.num_entries); Memory::ReadBlock(params.address, entries.data(), - params.num_entries * sizeof(IoctlGpfifoEntry)); + params.num_entries * sizeof(Tegra::CommandListHeader)); + + Core::System::GetInstance().GPU().ProcessCommandLists(entries); - for (auto entry : entries) { - Tegra::GPUVAddr va_addr = entry.Address(); - Core::System::GetInstance().GPU().ProcessCommandList(va_addr, entry.sz); - } params.fence_out.id = 0; params.fence_out.value = 0; std::memcpy(output.data(), ¶ms, output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h index 03b7356d0..62beb5c0c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.h @@ -10,7 +10,6 @@ #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/nvdrv/devices/nvdevice.h" -#include "video_core/memory_manager.h" namespace Service::Nvidia::Devices { @@ -21,8 +20,8 @@ constexpr u32 NVGPU_IOCTL_CHANNEL_KICKOFF_PB(0x1b); class nvhost_gpu final : public nvdevice { public: - explicit nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {} - ~nvhost_gpu() override = default; + explicit nvhost_gpu(std::shared_ptr<nvmap> nvmap_dev); + ~nvhost_gpu() override; u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; @@ -151,22 +150,6 @@ private: }; static_assert(sizeof(IoctlAllocObjCtx) == 16, "IoctlAllocObjCtx is incorrect size"); - struct IoctlGpfifoEntry { - u32_le entry0; // gpu_va_lo - union { - u32_le entry1; // gpu_va_hi | (unk_0x02 << 0x08) | (size << 0x0A) | (unk_0x01 << 0x1F) - BitField<0, 8, u32_le> gpu_va_hi; - BitField<8, 2, u32_le> unk1; - BitField<10, 21, u32_le> sz; - BitField<31, 1, u32_le> unk2; - }; - - Tegra::GPUVAddr Address() const { - return (static_cast<Tegra::GPUVAddr>(gpu_va_hi) << 32) | entry0; - } - }; - static_assert(sizeof(IoctlGpfifoEntry) == 8, "IoctlGpfifoEntry is incorrect size"); - struct IoctlSubmitGpfifo { u64_le address; // pointer to gpfifo entry structs u32_le num_entries; // number of fence objects being submitted diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index 364619e67..46dbbc37c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp @@ -10,6 +10,9 @@ namespace Service::Nvidia::Devices { +nvhost_nvdec::nvhost_nvdec() = default; +nvhost_nvdec::~nvhost_nvdec() = default; + u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h index 6ad74421b..0e7b284f8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.h @@ -13,8 +13,8 @@ namespace Service::Nvidia::Devices { class nvhost_nvdec final : public nvdevice { public: - nvhost_nvdec() = default; - ~nvhost_nvdec() override = default; + nvhost_nvdec(); + ~nvhost_nvdec() override; u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp index 51f01077b..c67f934f6 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.cpp @@ -10,6 +10,9 @@ namespace Service::Nvidia::Devices { +nvhost_nvjpg::nvhost_nvjpg() = default; +nvhost_nvjpg::~nvhost_nvjpg() = default; + u32 nvhost_nvjpg::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h index 2b0eb43ee..89fd5e95e 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvjpg.h @@ -13,8 +13,8 @@ namespace Service::Nvidia::Devices { class nvhost_nvjpg final : public nvdevice { public: - nvhost_nvjpg() = default; - ~nvhost_nvjpg() override = default; + nvhost_nvjpg(); + ~nvhost_nvjpg() override; u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp index fcb488d50..727b9fee4 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.cpp @@ -10,6 +10,9 @@ namespace Service::Nvidia::Devices { +nvhost_vic::nvhost_vic() = default; +nvhost_vic::~nvhost_vic() = default; + u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) { LOG_DEBUG(Service_NVDRV, "called, command=0x{:08X}, input_size=0x{:X}, output_size=0x{:X}", command.raw, input.size(), output.size()); diff --git a/src/core/hle/service/nvdrv/devices/nvhost_vic.h b/src/core/hle/service/nvdrv/devices/nvhost_vic.h index c7d681e52..fc24c3f9c 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_vic.h +++ b/src/core/hle/service/nvdrv/devices/nvhost_vic.h @@ -13,8 +13,8 @@ namespace Service::Nvidia::Devices { class nvhost_vic final : public nvdevice { public: - nvhost_vic() = default; - ~nvhost_vic() override = default; + nvhost_vic(); + ~nvhost_vic() override; u32 ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override; diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp index e9305bfb3..a2287cc1b 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.cpp +++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp @@ -11,6 +11,9 @@ namespace Service::Nvidia::Devices { +nvmap::nvmap() = default; +nvmap::~nvmap() = default; + VAddr nvmap::GetObjectAddress(u32 handle) const { auto object = GetObject(handle); ASSERT(object); diff --git a/src/core/hle/service/nvdrv/devices/nvmap.h b/src/core/hle/service/nvdrv/devices/nvmap.h index f2eec6409..396230c19 100644 --- a/src/core/hle/service/nvdrv/devices/nvmap.h +++ b/src/core/hle/service/nvdrv/devices/nvmap.h @@ -16,8 +16,8 @@ namespace Service::Nvidia::Devices { class nvmap final : public nvdevice { public: - nvmap() = default; - ~nvmap() override = default; + nvmap(); + ~nvmap() override; /// Returns the allocated address of an nvmap object given its handle. VAddr GetObjectAddress(u32 handle) const; diff --git a/src/core/hle/service/nvdrv/interface.cpp b/src/core/hle/service/nvdrv/interface.cpp index 634ab9196..ac3859353 100644 --- a/src/core/hle/service/nvdrv/interface.cpp +++ b/src/core/hle/service/nvdrv/interface.cpp @@ -112,4 +112,6 @@ NVDRV::NVDRV(std::shared_ptr<Module> nvdrv, const char* name) query_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "NVDRV::query_event"); } +NVDRV::~NVDRV() = default; + } // namespace Service::Nvidia diff --git a/src/core/hle/service/nvdrv/interface.h b/src/core/hle/service/nvdrv/interface.h index 1c3529bb6..d340893c2 100644 --- a/src/core/hle/service/nvdrv/interface.h +++ b/src/core/hle/service/nvdrv/interface.h @@ -14,7 +14,7 @@ namespace Service::Nvidia { class NVDRV final : public ServiceFramework<NVDRV> { public: NVDRV(std::shared_ptr<Module> nvdrv, const char* name); - ~NVDRV() = default; + ~NVDRV(); private: void Open(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/nvdrv/nvdrv.cpp b/src/core/hle/service/nvdrv/nvdrv.cpp index 2de39822f..6e4b8f2c6 100644 --- a/src/core/hle/service/nvdrv/nvdrv.cpp +++ b/src/core/hle/service/nvdrv/nvdrv.cpp @@ -45,6 +45,8 @@ Module::Module() { devices["/dev/nvhost-vic"] = std::make_shared<Devices::nvhost_vic>(); } +Module::~Module() = default; + u32 Module::Open(const std::string& device_name) { ASSERT_MSG(devices.find(device_name) != devices.end(), "Trying to open unknown device {}", device_name); diff --git a/src/core/hle/service/nvdrv/nvdrv.h b/src/core/hle/service/nvdrv/nvdrv.h index 99eb1128a..53564f696 100644 --- a/src/core/hle/service/nvdrv/nvdrv.h +++ b/src/core/hle/service/nvdrv/nvdrv.h @@ -30,7 +30,7 @@ static_assert(sizeof(IoctlFence) == 8, "IoctlFence has wrong size"); class Module final { public: Module(); - ~Module() = default; + ~Module(); /// Returns a pointer to one of the available devices, identified by its name. template <typename T> diff --git a/src/core/hle/service/nvdrv/nvmemp.cpp b/src/core/hle/service/nvdrv/nvmemp.cpp index 0e8e21bad..b7b8b7a1b 100644 --- a/src/core/hle/service/nvdrv/nvmemp.cpp +++ b/src/core/hle/service/nvdrv/nvmemp.cpp @@ -16,6 +16,8 @@ NVMEMP::NVMEMP() : ServiceFramework("nvmemp") { RegisterHandlers(functions); } +NVMEMP::~NVMEMP() = default; + void NVMEMP::Cmd0(Kernel::HLERequestContext& ctx) { UNIMPLEMENTED(); } diff --git a/src/core/hle/service/nvdrv/nvmemp.h b/src/core/hle/service/nvdrv/nvmemp.h index dfdcabf4a..5a4dfc1f9 100644 --- a/src/core/hle/service/nvdrv/nvmemp.h +++ b/src/core/hle/service/nvdrv/nvmemp.h @@ -11,7 +11,7 @@ namespace Service::Nvidia { class NVMEMP final : public ServiceFramework<NVMEMP> { public: NVMEMP(); - ~NVMEMP() = default; + ~NVMEMP(); private: void Cmd0(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp index 8d8962276..fd98d541d 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.cpp +++ b/src/core/hle/service/nvflinger/buffer_queue.cpp @@ -9,8 +9,7 @@ #include "core/core.h" #include "core/hle/service/nvflinger/buffer_queue.h" -namespace Service { -namespace NVFlinger { +namespace Service::NVFlinger { BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { auto& kernel = Core::System::GetInstance().Kernel(); @@ -18,6 +17,8 @@ BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) { Kernel::Event::Create(kernel, Kernel::ResetType::Sticky, "BufferQueue NativeHandle"); } +BufferQueue::~BufferQueue() = default; + void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer) { Buffer buffer{}; buffer.slot = slot; @@ -102,5 +103,4 @@ u32 BufferQueue::Query(QueryType type) { return 0; } -} // namespace NVFlinger -} // namespace Service +} // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h index db2e17c0c..50b767732 100644 --- a/src/core/hle/service/nvflinger/buffer_queue.h +++ b/src/core/hle/service/nvflinger/buffer_queue.h @@ -15,8 +15,7 @@ namespace CoreTiming { struct EventType; } -namespace Service { -namespace NVFlinger { +namespace Service::NVFlinger { struct IGBPBuffer { u32_le magic; @@ -46,7 +45,7 @@ public: }; BufferQueue(u32 id, u64 layer_id); - ~BufferQueue() = default; + ~BufferQueue(); enum class BufferTransformFlags : u32 { /// No transform flags are set @@ -98,5 +97,4 @@ private: Kernel::SharedPtr<Kernel::Event> buffer_wait_event; }; -} // namespace NVFlinger -} // namespace Service +} // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 06040da6f..d47b6f659 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp @@ -23,7 +23,7 @@ namespace Service::NVFlinger { -constexpr size_t SCREEN_REFRESH_RATE = 60; +constexpr std::size_t SCREEN_REFRESH_RATE = 60; constexpr u64 frame_ticks = static_cast<u64>(CoreTiming::BASE_CLOCK_RATE / SCREEN_REFRESH_RATE); NVFlinger::NVFlinger() { @@ -160,10 +160,13 @@ void NVFlinger::Compose() { } Layer::Layer(u64 id, std::shared_ptr<BufferQueue> queue) : id(id), buffer_queue(std::move(queue)) {} +Layer::~Layer() = default; Display::Display(u64 id, std::string name) : id(id), name(std::move(name)) { auto& kernel = Core::System::GetInstance().Kernel(); vsync_event = Kernel::Event::Create(kernel, Kernel::ResetType::Pulse, "Display VSync Event"); } +Display::~Display() = default; + } // namespace Service::NVFlinger diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index f7112949f..3dc69e69b 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h @@ -26,7 +26,7 @@ class BufferQueue; struct Layer { Layer(u64 id, std::shared_ptr<BufferQueue> queue); - ~Layer() = default; + ~Layer(); u64 id; std::shared_ptr<BufferQueue> buffer_queue; @@ -34,7 +34,7 @@ struct Layer { struct Display { Display(u64 id, std::string name); - ~Display() = default; + ~Display(); u64 id; std::string name; diff --git a/src/core/hle/service/pctl/module.cpp b/src/core/hle/service/pctl/module.cpp index 6cc3b1992..4fd185f69 100644 --- a/src/core/hle/service/pctl/module.cpp +++ b/src/core/hle/service/pctl/module.cpp @@ -142,6 +142,8 @@ void Module::Interface::CreateServiceWithoutInitialize(Kernel::HLERequestContext Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::~Interface() = default; + void InstallInterfaces(SM::ServiceManager& service_manager) { auto module = std::make_shared<Module>(); std::make_shared<PCTL>(module, "pctl")->InstallAsService(service_manager); diff --git a/src/core/hle/service/pctl/module.h b/src/core/hle/service/pctl/module.h index e7d492760..3e449110d 100644 --- a/src/core/hle/service/pctl/module.h +++ b/src/core/hle/service/pctl/module.h @@ -13,6 +13,7 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, const char* name); + ~Interface() override; void CreateService(Kernel::HLERequestContext& ctx); void CreateServiceWithoutInitialize(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/pctl/pctl.cpp b/src/core/hle/service/pctl/pctl.cpp index de2741d66..af9d1433a 100644 --- a/src/core/hle/service/pctl/pctl.cpp +++ b/src/core/hle/service/pctl/pctl.cpp @@ -14,4 +14,6 @@ PCTL::PCTL(std::shared_ptr<Module> module, const char* name) }; RegisterHandlers(functions); } + +PCTL::~PCTL() = default; } // namespace Service::PCTL diff --git a/src/core/hle/service/pctl/pctl.h b/src/core/hle/service/pctl/pctl.h index 8ddf69128..c33ea80b6 100644 --- a/src/core/hle/service/pctl/pctl.h +++ b/src/core/hle/service/pctl/pctl.h @@ -11,6 +11,7 @@ namespace Service::PCTL { class PCTL final : public Module::Interface { public: explicit PCTL(std::shared_ptr<Module> module, const char* name); + ~PCTL() override; }; } // namespace Service::PCTL diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index 3c43b8d8c..6a9eccfb5 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -1,36 +1,47 @@ -#include <cinttypes> +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" -#include "core/hle/kernel/event.h" #include "core/hle/service/prepo/prepo.h" +#include "core/hle/service/service.h" namespace Service::PlayReport { -PlayReport::PlayReport(const char* name) : ServiceFramework(name) { - static const FunctionInfo functions[] = { - {10100, nullptr, "SaveReport"}, - {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, - {10200, nullptr, "RequestImmediateTransmission"}, - {10300, nullptr, "GetTransmissionStatus"}, - {20100, nullptr, "SaveSystemReport"}, - {20200, nullptr, "SetOperationMode"}, - {20101, nullptr, "SaveSystemReportWithUser"}, - {30100, nullptr, "ClearStorage"}, - {40100, nullptr, "IsUserAgreementCheckEnabled"}, - {40101, nullptr, "SetUserAgreementCheckEnabled"}, - {90100, nullptr, "GetStorageUsage"}, - {90200, nullptr, "GetStatistics"}, - {90201, nullptr, "GetThroughputHistory"}, - {90300, nullptr, "GetLastUploadError"}, - }; - RegisterHandlers(functions); -}; -void PlayReport::SaveReportWithUser(Kernel::HLERequestContext& ctx) { - // TODO(ogniK): Do we want to add play report? - LOG_WARNING(Service_PREPO, "(STUBBED) called"); +class PlayReport final : public ServiceFramework<PlayReport> { +public: + explicit PlayReport(const char* name) : ServiceFramework{name} { + // clang-format off + static const FunctionInfo functions[] = { + {10100, nullptr, "SaveReport"}, + {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, + {10200, nullptr, "RequestImmediateTransmission"}, + {10300, nullptr, "GetTransmissionStatus"}, + {20100, nullptr, "SaveSystemReport"}, + {20200, nullptr, "SetOperationMode"}, + {20101, nullptr, "SaveSystemReportWithUser"}, + {30100, nullptr, "ClearStorage"}, + {40100, nullptr, "IsUserAgreementCheckEnabled"}, + {40101, nullptr, "SetUserAgreementCheckEnabled"}, + {90100, nullptr, "GetStorageUsage"}, + {90200, nullptr, "GetStatistics"}, + {90201, nullptr, "GetThroughputHistory"}, + {90300, nullptr, "GetLastUploadError"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void SaveReportWithUser(Kernel::HLERequestContext& ctx) { + // TODO(ogniK): Do we want to add play report? + LOG_WARNING(Service_PREPO, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } }; void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/prepo/prepo.h b/src/core/hle/service/prepo/prepo.h index f5a6aba6d..0e7b01331 100644 --- a/src/core/hle/service/prepo/prepo.h +++ b/src/core/hle/service/prepo/prepo.h @@ -4,22 +4,12 @@ #pragma once -#include <memory> -#include <string> -#include "core/hle/kernel/event.h" -#include "core/hle/service/service.h" +namespace Service::SM { +class ServiceManager; +} namespace Service::PlayReport { -class PlayReport final : public ServiceFramework<PlayReport> { -public: - explicit PlayReport(const char* name); - ~PlayReport() = default; - -private: - void SaveReportWithUser(Kernel::HLERequestContext& ctx); -}; - void InstallInterfaces(SM::ServiceManager& service_manager); } // namespace Service::PlayReport diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 8fb907072..62f049660 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -12,6 +12,7 @@ #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/server_port.h" #include "core/hle/kernel/thread.h" @@ -49,6 +50,7 @@ #include "core/hle/service/nim/nim.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" +#include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/pcie/pcie.h" #include "core/hle/service/pctl/pctl.h" #include "core/hle/service/pcv/pcv.h" @@ -57,7 +59,6 @@ #include "core/hle/service/psc/psc.h" #include "core/hle/service/service.h" #include "core/hle/service/set/settings.h" -#include "core/hle/service/sm/controller.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/sockets/sockets.h" #include "core/hle/service/spl/module.h" @@ -73,8 +74,6 @@ using Kernel::SharedPtr; namespace Service { -std::unordered_map<std::string, SharedPtr<ClientPort>> g_kernel_named_ports; - /** * Creates a function string for logging, complete with the name (or header code, depending * on what's passed in) the port name, and all the cmd_buff arguments. @@ -114,7 +113,7 @@ void ServiceFrameworkBase::InstallAsNamedPort() { std::tie(server_port, client_port) = ServerPort::CreatePortPair(kernel, max_sessions, service_name); server_port->SetHleHandler(shared_from_this()); - AddNamedPort(service_name, std::move(client_port)); + kernel.AddNamedPort(service_name, std::move(client_port)); } Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { @@ -130,9 +129,9 @@ Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() { return client_port; } -void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, size_t n) { +void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) { handlers.reserve(handlers.size() + n); - for (size_t i = 0; i < n; ++i) { + for (std::size_t i = 0; i < n; ++i) { // Usually this array is sorted by id already, so hint to insert at the end handlers.emplace_hint(handlers.cend(), functions[i].expected_header, functions[i]); } @@ -197,11 +196,6 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co //////////////////////////////////////////////////////////////////////////////////////////////////// // Module interface -// TODO(yuriks): Move to kernel -void AddNamedPort(std::string name, SharedPtr<ClientPort> port) { - g_kernel_named_ports.emplace(std::move(name), std::move(port)); -} - /// Initialize ServiceManager void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) { // NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it @@ -264,7 +258,6 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys /// Shutdown ServiceManager void Shutdown() { - g_kernel_named_ports.clear(); LOG_DEBUG(Service, "shutdown OK"); } } // namespace Service diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index cd9c74f3d..2fc57a82e 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -6,7 +6,6 @@ #include <cstddef> #include <string> -#include <unordered_map> #include <boost/container/flat_map.hpp> #include "common/common_types.h" #include "core/hle/kernel/hle_ipc.h" @@ -89,7 +88,7 @@ private: ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker); ~ServiceFrameworkBase(); - void RegisterHandlersBase(const FunctionInfoBase* functions, size_t n); + void RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n); void ReportUnimplementedFunction(Kernel::HLERequestContext& ctx, const FunctionInfoBase* info); /// Identifier string used to connect to the service. @@ -153,7 +152,7 @@ protected: : ServiceFrameworkBase(service_name, max_sessions, Invoker) {} /// Registers handlers in the service. - template <size_t N> + template <std::size_t N> void RegisterHandlers(const FunctionInfo (&functions)[N]) { RegisterHandlers(functions, N); } @@ -162,7 +161,7 @@ protected: * Registers handlers in the service. Usually prefer using the other RegisterHandlers * overload in order to avoid needing to specify the array size. */ - void RegisterHandlers(const FunctionInfo* functions, size_t n) { + void RegisterHandlers(const FunctionInfo* functions, std::size_t n) { RegisterHandlersBase(functions, n); } @@ -187,10 +186,4 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, /// Shutdown ServiceManager void Shutdown(); -/// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC. -extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports; - -/// Adds a port to the named port table -void AddNamedPort(std::string name, Kernel::SharedPtr<Kernel::ClientPort> port); - } // namespace Service diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp index 92b0640e8..9e5af7839 100644 --- a/src/core/hle/service/set/set.cpp +++ b/src/core/hle/service/set/set.cpp @@ -32,21 +32,21 @@ constexpr std::array<LanguageCode, 17> available_language_codes = {{ LanguageCode::ZH_HANT, }}; -constexpr size_t pre4_0_0_max_entries = 0xF; -constexpr size_t post4_0_0_max_entries = 0x40; +constexpr std::size_t pre4_0_0_max_entries = 0xF; +constexpr std::size_t post4_0_0_max_entries = 0x40; -LanguageCode GetLanguageCodeFromIndex(size_t index) { +LanguageCode GetLanguageCodeFromIndex(std::size_t index) { return available_language_codes.at(index); } -template <size_t size> +template <std::size_t size> static std::array<LanguageCode, size> MakeLanguageCodeSubset() { std::array<LanguageCode, size> arr; std::copy_n(available_language_codes.begin(), size, arr.begin()); return arr; } -static void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, size_t max_size) { +static void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t max_size) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); if (available_language_codes.size() > max_size) @@ -112,4 +112,6 @@ SET::SET() : ServiceFramework("set") { RegisterHandlers(functions); } +SET::~SET() = default; + } // namespace Service::Set diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h index 669e740b7..266f13e46 100644 --- a/src/core/hle/service/set/set.h +++ b/src/core/hle/service/set/set.h @@ -28,12 +28,12 @@ enum class LanguageCode : u64 { ZH_HANS = 0x00736E61482D687A, ZH_HANT = 0x00746E61482D687A, }; -LanguageCode GetLanguageCodeFromIndex(size_t idx); +LanguageCode GetLanguageCodeFromIndex(std::size_t idx); class SET final : public ServiceFramework<SET> { public: explicit SET(); - ~SET() = default; + ~SET() override; private: void GetLanguageCode(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp index 7066ef725..5af356d10 100644 --- a/src/core/hle/service/set/set_cal.cpp +++ b/src/core/hle/service/set/set_cal.cpp @@ -44,4 +44,6 @@ SET_CAL::SET_CAL() : ServiceFramework("set:cal") { RegisterHandlers(functions); } +SET_CAL::~SET_CAL() = default; + } // namespace Service::Set diff --git a/src/core/hle/service/set/set_cal.h b/src/core/hle/service/set/set_cal.h index bb50336aa..583036eac 100644 --- a/src/core/hle/service/set/set_cal.h +++ b/src/core/hle/service/set/set_cal.h @@ -11,7 +11,7 @@ namespace Service::Set { class SET_CAL final : public ServiceFramework<SET_CAL> { public: explicit SET_CAL(); - ~SET_CAL() = default; + ~SET_CAL(); }; } // namespace Service::Set diff --git a/src/core/hle/service/set/set_fd.cpp b/src/core/hle/service/set/set_fd.cpp index c9f938716..cac6af86d 100644 --- a/src/core/hle/service/set/set_fd.cpp +++ b/src/core/hle/service/set/set_fd.cpp @@ -20,4 +20,6 @@ SET_FD::SET_FD() : ServiceFramework("set:fd") { RegisterHandlers(functions); } +SET_FD::~SET_FD() = default; + } // namespace Service::Set diff --git a/src/core/hle/service/set/set_fd.h b/src/core/hle/service/set/set_fd.h index dbd850bc7..216e65f1f 100644 --- a/src/core/hle/service/set/set_fd.h +++ b/src/core/hle/service/set/set_fd.h @@ -11,7 +11,7 @@ namespace Service::Set { class SET_FD final : public ServiceFramework<SET_FD> { public: explicit SET_FD(); - ~SET_FD() = default; + ~SET_FD() override; }; } // namespace Service::Set diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index 1cef73216..cdf328a26 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp @@ -57,4 +57,6 @@ Controller::Controller() : ServiceFramework("IpcController") { RegisterHandlers(functions); } +Controller::~Controller() = default; + } // namespace Service::SM diff --git a/src/core/hle/service/sm/controller.h b/src/core/hle/service/sm/controller.h index a4de52cd2..dc66c9e37 100644 --- a/src/core/hle/service/sm/controller.h +++ b/src/core/hle/service/sm/controller.h @@ -11,7 +11,7 @@ namespace Service::SM { class Controller final : public ServiceFramework<Controller> { public: Controller(); - ~Controller() = default; + ~Controller() override; private: void ConvertSessionToDomain(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index b240d7eed..464e79d01 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp @@ -15,6 +15,11 @@ namespace Service::SM { +constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::SM, 4); +constexpr ResultCode ERR_INVALID_NAME(ErrorModule::SM, 6); +constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(ErrorModule::SM, 7); + +ServiceManager::ServiceManager() = default; ServiceManager::~ServiceManager() = default; void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { @@ -23,10 +28,10 @@ void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) { static ResultCode ValidateServiceName(const std::string& name) { if (name.size() <= 0 || name.size() > 8) { - return ERR_INVALID_NAME_SIZE; + return ERR_INVALID_NAME; } if (name.find('\0') != std::string::npos) { - return ERR_NAME_CONTAINS_NUL; + return ERR_INVALID_NAME; } return RESULT_SUCCESS; } @@ -103,7 +108,7 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { auto client_port = service_manager->GetServicePort(name); if (client_port.Failed()) { - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(client_port.Code()); LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, client_port.Code().raw); if (name.length() == 0) @@ -116,8 +121,7 @@ void SM::GetService(Kernel::HLERequestContext& ctx) { ASSERT(session.Succeeded()); if (session.Succeeded()) { LOG_DEBUG(Service_SM, "called service={} -> session={}", name, (*session)->GetObjectId()); - IPC::ResponseBuilder rb = - rp.MakeBuilder(2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles); + IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; rb.Push(session.Code()); rb.PushMoveObjects(std::move(session).Unwrap()); } diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index e8ea62f08..da2c51082 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h @@ -36,16 +36,11 @@ private: std::shared_ptr<ServiceManager> service_manager; }; -constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(-1); -constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(-1); -constexpr ResultCode ERR_INVALID_NAME_SIZE(-1); -constexpr ResultCode ERR_NAME_CONTAINS_NUL(-1); -constexpr ResultCode ERR_ALREADY_REGISTERED(-1); - class ServiceManager { public: static void InstallInterfaces(std::shared_ptr<ServiceManager> self); + ServiceManager(); ~ServiceManager(); ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name, diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index 3211a8346..4342f3b2d 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -109,6 +109,8 @@ BSD::BSD(const char* name) : ServiceFramework(name) { RegisterHandlers(functions); } +BSD::~BSD() = default; + BSDCFG::BSDCFG() : ServiceFramework{"bsdcfg"} { // clang-format off static const FunctionInfo functions[] = { @@ -131,4 +133,6 @@ BSDCFG::BSDCFG() : ServiceFramework{"bsdcfg"} { RegisterHandlers(functions); } +BSDCFG::~BSDCFG() = default; + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/bsd.h b/src/core/hle/service/sockets/bsd.h index c1da59b24..0fe0e65c6 100644 --- a/src/core/hle/service/sockets/bsd.h +++ b/src/core/hle/service/sockets/bsd.h @@ -12,7 +12,7 @@ namespace Service::Sockets { class BSD final : public ServiceFramework<BSD> { public: explicit BSD(const char* name); - ~BSD() = default; + ~BSD() override; private: void RegisterClient(Kernel::HLERequestContext& ctx); @@ -29,6 +29,7 @@ private: class BSDCFG final : public ServiceFramework<BSDCFG> { public: explicit BSDCFG(); + ~BSDCFG() override; }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/ethc.cpp b/src/core/hle/service/sockets/ethc.cpp index d53c25eec..abbeb4c50 100644 --- a/src/core/hle/service/sockets/ethc.cpp +++ b/src/core/hle/service/sockets/ethc.cpp @@ -21,6 +21,8 @@ ETHC_C::ETHC_C() : ServiceFramework{"ethc:c"} { RegisterHandlers(functions); } +ETHC_C::~ETHC_C() = default; + ETHC_I::ETHC_I() : ServiceFramework{"ethc:i"} { // clang-format off static const FunctionInfo functions[] = { @@ -35,4 +37,6 @@ ETHC_I::ETHC_I() : ServiceFramework{"ethc:i"} { RegisterHandlers(functions); } +ETHC_I::~ETHC_I() = default; + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/ethc.h b/src/core/hle/service/sockets/ethc.h index 9a3c88100..da2c7f741 100644 --- a/src/core/hle/service/sockets/ethc.h +++ b/src/core/hle/service/sockets/ethc.h @@ -11,11 +11,13 @@ namespace Service::Sockets { class ETHC_C final : public ServiceFramework<ETHC_C> { public: explicit ETHC_C(); + ~ETHC_C() override; }; class ETHC_I final : public ServiceFramework<ETHC_I> { public: explicit ETHC_I(); + ~ETHC_I() override; }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/nsd.cpp b/src/core/hle/service/sockets/nsd.cpp index 8682dc2e0..e6d73065e 100644 --- a/src/core/hle/service/sockets/nsd.cpp +++ b/src/core/hle/service/sockets/nsd.cpp @@ -29,4 +29,6 @@ NSD::NSD(const char* name) : ServiceFramework(name) { RegisterHandlers(functions); } +NSD::~NSD() = default; + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/nsd.h b/src/core/hle/service/sockets/nsd.h index 3b7edfc43..d842e3232 100644 --- a/src/core/hle/service/sockets/nsd.h +++ b/src/core/hle/service/sockets/nsd.h @@ -12,7 +12,7 @@ namespace Service::Sockets { class NSD final : public ServiceFramework<NSD> { public: explicit NSD(const char* name); - ~NSD() = default; + ~NSD() override; }; } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.cpp b/src/core/hle/service/sockets/sfdnsres.cpp index d235c4cfd..13ab1d31e 100644 --- a/src/core/hle/service/sockets/sfdnsres.cpp +++ b/src/core/hle/service/sockets/sfdnsres.cpp @@ -34,4 +34,6 @@ SFDNSRES::SFDNSRES() : ServiceFramework("sfdnsres") { RegisterHandlers(functions); } +SFDNSRES::~SFDNSRES() = default; + } // namespace Service::Sockets diff --git a/src/core/hle/service/sockets/sfdnsres.h b/src/core/hle/service/sockets/sfdnsres.h index 62c7e35bf..eda432903 100644 --- a/src/core/hle/service/sockets/sfdnsres.h +++ b/src/core/hle/service/sockets/sfdnsres.h @@ -12,7 +12,7 @@ namespace Service::Sockets { class SFDNSRES final : public ServiceFramework<SFDNSRES> { public: explicit SFDNSRES(); - ~SFDNSRES() = default; + ~SFDNSRES() override; private: void GetAddrInfo(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/spl/csrng.cpp b/src/core/hle/service/spl/csrng.cpp index b9e6b799d..674928798 100644 --- a/src/core/hle/service/spl/csrng.cpp +++ b/src/core/hle/service/spl/csrng.cpp @@ -13,4 +13,6 @@ CSRNG::CSRNG(std::shared_ptr<Module> module) : Module::Interface(std::move(modul RegisterHandlers(functions); } +CSRNG::~CSRNG() = default; + } // namespace Service::SPL diff --git a/src/core/hle/service/spl/csrng.h b/src/core/hle/service/spl/csrng.h index 3f849b5a7..764d5ceb0 100644 --- a/src/core/hle/service/spl/csrng.h +++ b/src/core/hle/service/spl/csrng.h @@ -11,6 +11,7 @@ namespace Service::SPL { class CSRNG final : public Module::Interface { public: explicit CSRNG(std::shared_ptr<Module> module); + ~CSRNG() override; }; } // namespace Service::SPL diff --git a/src/core/hle/service/spl/module.cpp b/src/core/hle/service/spl/module.cpp index 3f5a342a7..44a6717d0 100644 --- a/src/core/hle/service/spl/module.cpp +++ b/src/core/hle/service/spl/module.cpp @@ -16,10 +16,12 @@ namespace Service::SPL { Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) {} +Module::Interface::~Interface() = default; + void Module::Interface::GetRandomBytes(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; - size_t size = ctx.GetWriteBufferSize(); + std::size_t size = ctx.GetWriteBufferSize(); std::vector<u8> data(size); std::generate(data.begin(), data.end(), std::rand); diff --git a/src/core/hle/service/spl/module.h b/src/core/hle/service/spl/module.h index f24d998e8..48fda6099 100644 --- a/src/core/hle/service/spl/module.h +++ b/src/core/hle/service/spl/module.h @@ -13,6 +13,7 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> module, const char* name); + ~Interface() override; void GetRandomBytes(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/spl/spl.cpp b/src/core/hle/service/spl/spl.cpp index bb1e03342..70cb41905 100644 --- a/src/core/hle/service/spl/spl.cpp +++ b/src/core/hle/service/spl/spl.cpp @@ -42,4 +42,6 @@ SPL::SPL(std::shared_ptr<Module> module) : Module::Interface(std::move(module), RegisterHandlers(functions); } +SPL::~SPL() = default; + } // namespace Service::SPL diff --git a/src/core/hle/service/spl/spl.h b/src/core/hle/service/spl/spl.h index 69c4c1747..3637d1623 100644 --- a/src/core/hle/service/spl/spl.h +++ b/src/core/hle/service/spl/spl.h @@ -11,6 +11,7 @@ namespace Service::SPL { class SPL final : public Module::Interface { public: explicit SPL(std::shared_ptr<Module> module); + ~SPL() override; }; } // namespace Service::SPL diff --git a/src/core/hle/service/ssl/ssl.cpp b/src/core/hle/service/ssl/ssl.cpp index 40aea6090..fe0a318ee 100644 --- a/src/core/hle/service/ssl/ssl.cpp +++ b/src/core/hle/service/ssl/ssl.cpp @@ -3,6 +3,9 @@ // Refer to the license.txt file included. #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/hle_ipc.h" +#include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" #include "core/hle/service/ssl/ssl.h" namespace Service::SSL { @@ -68,7 +71,7 @@ private: LOG_WARNING(Service_SSL, "(STUBBED) called"); IPC::RequestParser rp{ctx}; - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -81,36 +84,43 @@ private: } }; -void SSL::CreateContext(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_SSL, "(STUBBED) called"); +class SSL final : public ServiceFramework<SSL> { +public: + explicit SSL() : ServiceFramework{"ssl"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &SSL::CreateContext, "CreateContext"}, + {1, nullptr, "GetContextCount"}, + {2, nullptr, "GetCertificates"}, + {3, nullptr, "GetCertificateBufSize"}, + {4, nullptr, "DebugIoctl"}, + {5, &SSL::SetInterfaceVersion, "SetInterfaceVersion"}, + {6, nullptr, "FlushSessionCache"}, + }; + // clang-format on - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISslContext>(); -} + RegisterHandlers(functions); + } -SSL::SSL() : ServiceFramework("ssl") { - static const FunctionInfo functions[] = { - {0, &SSL::CreateContext, "CreateContext"}, - {1, nullptr, "GetContextCount"}, - {2, nullptr, "GetCertificates"}, - {3, nullptr, "GetCertificateBufSize"}, - {4, nullptr, "DebugIoctl"}, - {5, &SSL::SetInterfaceVersion, "SetInterfaceVersion"}, - {6, nullptr, "FlushSessionCache"}, - }; - RegisterHandlers(functions); -} +private: + void CreateContext(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_SSL, "(STUBBED) called"); -void SSL::SetInterfaceVersion(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_SSL, "(STUBBED) called"); - IPC::RequestParser rp{ctx}; - u32 unk1 = rp.Pop<u32>(); // Probably minor/major? - u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<ISslContext>(); + } - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); -} + void SetInterfaceVersion(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_SSL, "(STUBBED) called"); + IPC::RequestParser rp{ctx}; + u32 unk1 = rp.Pop<u32>(); // Probably minor/major? + u32 unk2 = rp.Pop<u32>(); // TODO(ogniK): Figure out what this does + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } +}; void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<SSL>()->InstallAsService(service_manager); diff --git a/src/core/hle/service/ssl/ssl.h b/src/core/hle/service/ssl/ssl.h index 8fef13022..5cb04c3b9 100644 --- a/src/core/hle/service/ssl/ssl.h +++ b/src/core/hle/service/ssl/ssl.h @@ -4,20 +4,12 @@ #pragma once -#include "core/hle/service/service.h" +namespace Service::SM { +class ServiceManager; +} namespace Service::SSL { -class SSL final : public ServiceFramework<SSL> { -public: - explicit SSL(); - ~SSL() = default; - -private: - void CreateContext(Kernel::HLERequestContext& ctx); - void SetInterfaceVersion(Kernel::HLERequestContext& ctx); -}; - /// Registers all SSL services with the specified service manager. void InstallInterfaces(SM::ServiceManager& service_manager); diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index 048d5b077..18a5d71d5 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -29,4 +29,6 @@ Time::Time(std::shared_ptr<Module> time, const char* name) RegisterHandlers(functions); } +Time::~Time() = default; + } // namespace Service::Time diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h index 183a53db1..cd6b44dec 100644 --- a/src/core/hle/service/time/interface.h +++ b/src/core/hle/service/time/interface.h @@ -11,6 +11,7 @@ namespace Service::Time { class Time final : public Module::Interface { public: explicit Time(std::shared_ptr<Module> time, const char* name); + ~Time() override; }; } // namespace Service::Time diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 2172c681b..28fd8debc 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -210,6 +210,8 @@ void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& c Module::Interface::Interface(std::shared_ptr<Module> time, const char* name) : ServiceFramework(name), time(std::move(time)) {} +Module::Interface::~Interface() = default; + void InstallInterfaces(SM::ServiceManager& service_manager) { auto time = std::make_shared<Module>(); std::make_shared<Time>(time, "time:a")->InstallAsService(service_manager); diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index 8dde28a94..5659ecad3 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -58,6 +58,7 @@ public: class Interface : public ServiceFramework<Interface> { public: explicit Interface(std::shared_ptr<Module> time, const char* name); + ~Interface() override; void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); void GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index 993f1e65a..2ee60f1ec 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -4,25 +4,28 @@ #include <algorithm> #include <array> +#include <cstring> #include <memory> #include <type_traits> #include <utility> #include <boost/optional.hpp> #include "common/alignment.h" +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/logging/log.h" #include "common/math_util.h" -#include "common/scope_exit.h" +#include "common/swap.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/buffer_queue.h" +#include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/vi/vi.h" #include "core/hle/service/vi/vi_m.h" #include "core/hle/service/vi/vi_s.h" #include "core/hle/service/vi/vi_u.h" #include "core/settings.h" -#include "video_core/renderer_base.h" -#include "video_core/video_core.h" namespace Service::VI { @@ -38,7 +41,7 @@ static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size"); class Parcel { public: // This default size was chosen arbitrarily. - static constexpr size_t DefaultBufferSize = 0x40; + static constexpr std::size_t DefaultBufferSize = 0x40; Parcel() : buffer(DefaultBufferSize) {} explicit Parcel(std::vector<u8> data) : buffer(std::move(data)) {} virtual ~Parcel() = default; @@ -66,7 +69,7 @@ public: return val; } - std::vector<u8> ReadBlock(size_t length) { + std::vector<u8> ReadBlock(std::size_t length) { ASSERT(read_index + length <= buffer.size()); const u8* const begin = buffer.data() + read_index; const u8* const end = begin + length; @@ -156,8 +159,8 @@ private: static_assert(sizeof(Header) == 16, "ParcelHeader has wrong size"); std::vector<u8> buffer; - size_t read_index = 0; - size_t write_index = 0; + std::size_t read_index = 0; + std::size_t write_index = 0; }; class NativeWindow : public Parcel { @@ -514,7 +517,7 @@ private: ctx.SleepClientThread( Kernel::GetCurrentThread(), "IHOSBinderDriver::DequeueBuffer", -1, [=](Kernel::SharedPtr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx, - ThreadWakeupReason reason) { + Kernel::ThreadWakeupReason reason) { // Repeat TransactParcel DequeueBuffer when a buffer is available auto buffer_queue = nv_flinger->GetBufferQueue(id); boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height); @@ -647,7 +650,7 @@ private: u64 layer_id = rp.Pop<u64>(); u64 z_value = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -655,7 +658,7 @@ private: IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); bool visibility = rp.Pop<bool>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:08X}, visibility={}", layer_id, visibility); @@ -744,7 +747,7 @@ private: IPC::RequestParser rp{ctx}; u64 display = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -758,7 +761,7 @@ private: u64 layer_id = nv_flinger->CreateLayer(display); - IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push(layer_id); } @@ -769,7 +772,7 @@ private: u32 stack = rp.Pop<u32>(); u64 layer_id = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -777,7 +780,7 @@ private: IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); bool visibility = rp.Pop<bool>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x{:X}, visibility={}", layer_id, visibility); @@ -834,7 +837,7 @@ private: ASSERT_MSG(name == "Default", "Non-default displays aren't supported yet"); - IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(nv_flinger->OpenDisplay(name)); } @@ -844,7 +847,7 @@ private: IPC::RequestParser rp{ctx}; u64 display_id = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -853,7 +856,7 @@ private: IPC::RequestParser rp{ctx}; u64 display_id = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); if (Settings::values.use_docked_mode) { @@ -871,7 +874,7 @@ private: u32 scaling_mode = rp.Pop<u32>(); u64 unknown = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -879,7 +882,7 @@ private: IPC::RequestParser rp{ctx}; DisplayInfo display_info; ctx.WriteBuffer(&display_info, sizeof(DisplayInfo)); - IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(1); LOG_WARNING(Service_VI, "(STUBBED) called"); @@ -900,7 +903,7 @@ private: u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); NativeWindow native_window{buffer_queue_id}; - IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0); + IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); } @@ -919,7 +922,7 @@ private: u32 buffer_queue_id = nv_flinger->GetBufferQueueId(display_id, layer_id); NativeWindow native_window{buffer_queue_id}; - IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0); + IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); rb.Push(layer_id); rb.Push<u64>(ctx.WriteBuffer(native_window.Serialize())); @@ -931,7 +934,7 @@ private: IPC::RequestParser rp{ctx}; u64 layer_id = rp.Pop<u64>(); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0); + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); } @@ -942,7 +945,7 @@ private: auto vsync_event = nv_flinger->GetVsyncEvent(display_id); - IPC::ResponseBuilder rb = rp.MakeBuilder(2, 1, 0); + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(vsync_event); } @@ -984,6 +987,8 @@ Module::Interface::Interface(std::shared_ptr<Module> module, const char* name, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) : ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {} +Module::Interface::~Interface() = default; + void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_VI, "(STUBBED) called"); diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h index 92f5b6059..e3963502a 100644 --- a/src/core/hle/service/vi/vi.h +++ b/src/core/hle/service/vi/vi.h @@ -4,11 +4,10 @@ #pragma once -#include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/service.h" -namespace CoreTiming { -struct EventType; +namespace Service::NVFlinger { +class NVFlinger; } namespace Service::VI { @@ -26,6 +25,7 @@ public: public: explicit Interface(std::shared_ptr<Module> module, const char* name, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + ~Interface() override; void GetDisplayService(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp index d47da565b..207c06b16 100644 --- a/src/core/hle/service/vi/vi_m.cpp +++ b/src/core/hle/service/vi/vi_m.cpp @@ -15,4 +15,6 @@ VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> RegisterHandlers(functions); } +VI_M::~VI_M() = default; + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h index 6abb9b3a3..487d58d50 100644 --- a/src/core/hle/service/vi/vi_m.h +++ b/src/core/hle/service/vi/vi_m.h @@ -11,6 +11,7 @@ namespace Service::VI { class VI_M final : public Module::Interface { public: explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + ~VI_M() override; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp index 8f82e797f..920e6a1f6 100644 --- a/src/core/hle/service/vi/vi_s.cpp +++ b/src/core/hle/service/vi/vi_s.cpp @@ -15,4 +15,6 @@ VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> RegisterHandlers(functions); } +VI_S::~VI_S() = default; + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h index 8f16f804f..bbc31148f 100644 --- a/src/core/hle/service/vi/vi_s.h +++ b/src/core/hle/service/vi/vi_s.h @@ -11,6 +11,7 @@ namespace Service::VI { class VI_S final : public Module::Interface { public: explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + ~VI_S() override; }; } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp index b84aed1d5..d81e410d6 100644 --- a/src/core/hle/service/vi/vi_u.cpp +++ b/src/core/hle/service/vi/vi_u.cpp @@ -14,4 +14,6 @@ VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> RegisterHandlers(functions); } +VI_U::~VI_U() = default; + } // namespace Service::VI diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h index e9b4f76b2..b92f28c92 100644 --- a/src/core/hle/service/vi/vi_u.h +++ b/src/core/hle/service/vi/vi_u.h @@ -11,6 +11,7 @@ namespace Service::VI { class VI_U final : public Module::Interface { public: explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger); + ~VI_U() override; }; } // namespace Service::VI diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp index 921b899e2..2b8f78136 100644 --- a/src/core/loader/deconstructed_rom_directory.cpp +++ b/src/core/loader/deconstructed_rom_directory.cpp @@ -9,6 +9,7 @@ #include "core/core.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/romfs_factory.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/kernel.h" @@ -21,10 +22,19 @@ namespace Loader { -AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_) - : AppLoader(std::move(file_)) { +AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_, + bool override_update) + : AppLoader(std::move(file_)), override_update(override_update) { const auto dir = file->GetContainingDirectory(); + // Title ID + const auto npdm = dir->GetFile("main.npdm"); + if (npdm != nullptr) { + const auto res = metadata.Load(npdm); + if (res == ResultStatus::Success) + title_id = metadata.GetTitleID(); + } + // Icon FileSys::VirtualFile icon_file = nullptr; for (const auto& language : FileSys::LANGUAGE_NAMES) { @@ -61,14 +71,14 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys if (nacp_file != nullptr) { FileSys::NACP nacp(nacp_file); - title_id = nacp.GetTitleId(); name = nacp.GetApplicationName(); } } AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( - FileSys::VirtualDir directory) - : AppLoader(directory->GetFile("main")), dir(std::move(directory)) {} + FileSys::VirtualDir directory, bool override_update) + : AppLoader(directory->GetFile("main")), dir(std::move(directory)), + override_update(override_update) {} FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) { if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) { @@ -90,7 +100,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( dir = file->GetContainingDirectory(); } - const FileSys::VirtualFile npdm = dir->GetFile("main.npdm"); + // Read meta to determine title ID + FileSys::VirtualFile npdm = dir->GetFile("main.npdm"); if (npdm == nullptr) return ResultStatus::ErrorMissingNPDM; @@ -98,6 +109,21 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( if (result != ResultStatus::Success) { return result; } + + if (override_update) { + const FileSys::PatchManager patch_manager(metadata.GetTitleID()); + dir = patch_manager.PatchExeFS(dir); + } + + // Reread in case PatchExeFS affected the main.npdm + npdm = dir->GetFile("main.npdm"); + if (npdm == nullptr) + return ResultStatus::ErrorMissingNPDM; + + ResultStatus result2 = metadata.Load(npdm); + if (result2 != ResultStatus::Success) { + return result2; + } metadata.Print(); const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()}; @@ -159,8 +185,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buff } ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) { - if (name.empty()) - return ResultStatus::ErrorNoControl; out_program_id = title_id; return ResultStatus::Success; } @@ -172,4 +196,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadTitle(std::string& title) return ResultStatus::Success; } +bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const { + return false; +} + } // namespace Loader diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h index b20804f75..8a0dc1b1e 100644 --- a/src/core/loader/deconstructed_rom_directory.h +++ b/src/core/loader/deconstructed_rom_directory.h @@ -20,10 +20,12 @@ namespace Loader { */ class AppLoader_DeconstructedRomDirectory final : public AppLoader { public: - explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file); + explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file, + bool override_update = false); // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' - explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory); + explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory, + bool override_update = false); /** * Returns the type of the file @@ -42,6 +44,7 @@ public: ResultStatus ReadIcon(std::vector<u8>& buffer) override; ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadTitle(std::string& title) override; + bool IsRomFSUpdatable() const override; private: FileSys::ProgramMetadata metadata; @@ -51,6 +54,7 @@ private: std::vector<u8> icon_data; std::string name; u64 title_id{}; + bool override_update; }; } // namespace Loader diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 120e1e133..0e2af20b4 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -300,7 +300,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(u32 vaddr) { } std::vector<u8> program_image(total_image_size); - size_t current_image_position = 0; + std::size_t current_image_position = 0; auto& kernel = Core::System::GetInstance().Kernel(); SharedPtr<CodeSet> codeset = CodeSet::Create(kernel, ""); diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index c13fb49b8..f2a183ba1 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -5,9 +5,9 @@ #include <memory> #include <ostream> #include <string> +#include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" -#include "core/file_sys/vfs_real.h" #include "core/hle/kernel/process.h" #include "core/loader/deconstructed_rom_directory.h" #include "core/loader/elf.h" @@ -15,6 +15,7 @@ #include "core/loader/nca.h" #include "core/loader/nro.h" #include "core/loader/nso.h" +#include "core/loader/nsp.h" #include "core/loader/xci.h" namespace Loader { @@ -34,6 +35,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { CHECK_TYPE(NCA) CHECK_TYPE(XCI) CHECK_TYPE(NAX) + CHECK_TYPE(NSP) #undef CHECK_TYPE @@ -59,6 +61,8 @@ FileType GuessFromFilename(const std::string& name) { return FileType::NCA; if (extension == "xci") return FileType::XCI; + if (extension == "nsp") + return FileType::NSP; return FileType::Unknown; } @@ -77,6 +81,8 @@ std::string GetFileTypeString(FileType type) { return "XCI"; case FileType::NAX: return "NAX"; + case FileType::NSP: + return "NSP"; case FileType::DeconstructedRomDirectory: return "Directory"; case FileType::Error: @@ -87,7 +93,7 @@ std::string GetFileTypeString(FileType type) { return "unknown"; } -constexpr std::array<const char*, 49> RESULT_MESSAGES{ +constexpr std::array<const char*, 58> RESULT_MESSAGES{ "The operation completed successfully.", "The loader requested to load is already loaded.", "The operation is not implemented.", @@ -137,13 +143,25 @@ constexpr std::array<const char*, 49> RESULT_MESSAGES{ "The AES Key Generation Source could not be found.", "The SD Save Key Source could not be found.", "The SD NCA Key Source could not be found.", + "The NSP file is missing a Program-type NCA.", + "The BKTR-type NCA has a bad BKTR header.", + "The BKTR Subsection entry is not located immediately after the Relocation entry.", + "The BKTR Subsection entry is not at the end of the media block.", + "The BKTR-type NCA has a bad Relocation block.", + "The BKTR-type NCA has a bad Subsection block.", + "The BKTR-type NCA has a bad Relocation bucket.", + "The BKTR-type NCA has a bad Subsection bucket.", + "The BKTR-type NCA is missing the base RomFS.", }; std::ostream& operator<<(std::ostream& os, ResultStatus status) { - os << RESULT_MESSAGES.at(static_cast<size_t>(status)); + os << RESULT_MESSAGES.at(static_cast<std::size_t>(status)); return os; } +AppLoader::AppLoader(FileSys::VirtualFile file) : file(std::move(file)) {} +AppLoader::~AppLoader() = default; + /** * Get a loader for a file with a specific type * @param file The file to load @@ -179,6 +197,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT case FileType::NAX: return std::make_unique<AppLoader_NAX>(std::move(file)); + // NX NSP (Nintendo Submission Package) file format + case FileType::NSP: + return std::make_unique<AppLoader_NSP>(std::move(file)); + // NX deconstructed ROM directory. case FileType::DeconstructedRomDirectory: return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 885fee84c..843c4bb91 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -4,7 +4,6 @@ #pragma once -#include <algorithm> #include <iosfwd> #include <memory> #include <string> @@ -12,7 +11,6 @@ #include <vector> #include <boost/optional.hpp> #include "common/common_types.h" -#include "common/file_util.h" #include "core/file_sys/vfs.h" #include "core/hle/kernel/object.h" @@ -31,6 +29,7 @@ enum class FileType { NSO, NRO, NCA, + NSP, XCI, NAX, DeconstructedRomDirectory, @@ -107,6 +106,15 @@ enum class ResultStatus : u16 { ErrorMissingAESKeyGenerationSource, ErrorMissingSDSaveKeySource, ErrorMissingSDNCAKeySource, + ErrorNSPMissingProgramNCA, + ErrorBadBKTRHeader, + ErrorBKTRSubsectionNotAfterRelocation, + ErrorBKTRSubsectionNotAtEnd, + ErrorBadRelocationBlock, + ErrorBadSubsectionBlock, + ErrorBadRelocationBuckets, + ErrorBadSubsectionBuckets, + ErrorMissingBKTRBaseRomFS, }; std::ostream& operator<<(std::ostream& os, ResultStatus status); @@ -114,8 +122,8 @@ std::ostream& operator<<(std::ostream& os, ResultStatus status); /// Interface for loading an application class AppLoader : NonCopyable { public: - explicit AppLoader(FileSys::VirtualFile file) : file(std::move(file)) {} - virtual ~AppLoader() {} + explicit AppLoader(FileSys::VirtualFile file); + virtual ~AppLoader(); /** * Returns the type of this file @@ -197,13 +205,22 @@ public: } /** - * Get the update RomFS of the application - * Since the RomFS can be huge, we return a file reference instead of copying to a buffer - * @param file The file containing the RomFS - * @return ResultStatus result of function + * Get whether or not updates can be applied to the RomFS. + * By default, this is true, however for formats where it cannot be guaranteed that the RomFS is + * the base game it should be set to false. + * @return bool whether or not updatable. */ - virtual ResultStatus ReadUpdateRomFS(FileSys::VirtualFile& file) { - return ResultStatus::ErrorNotImplemented; + virtual bool IsRomFSUpdatable() const { + return true; + } + + /** + * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS) + * data. Needed for bktr patching. + * @return IVFC offset for romfs. + */ + virtual u64 ReadRomFSIVFCOffset() const { + return 0; } /** diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp index b46d81c02..5d4380684 100644 --- a/src/core/loader/nax.cpp +++ b/src/core/loader/nax.cpp @@ -11,6 +11,20 @@ #include "core/loader/nca.h" namespace Loader { +namespace { +FileType IdentifyTypeImpl(const FileSys::NAX& nax) { + if (nax.GetStatus() != ResultStatus::Success) { + return FileType::Error; + } + + const auto nca = nax.AsNCA(); + if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) { + return FileType::Error; + } + + return FileType::NAX; +} +} // Anonymous namespace AppLoader_NAX::AppLoader_NAX(FileSys::VirtualFile file) : AppLoader(file), nax(std::make_unique<FileSys::NAX>(file)), @@ -19,14 +33,12 @@ AppLoader_NAX::AppLoader_NAX(FileSys::VirtualFile file) AppLoader_NAX::~AppLoader_NAX() = default; FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) { - FileSys::NAX nax(file); - - if (nax.GetStatus() == ResultStatus::Success && nax.AsNCA() != nullptr && - nax.AsNCA()->GetStatus() == ResultStatus::Success) { - return FileType::NAX; - } + const FileSys::NAX nax(file); + return IdentifyTypeImpl(nax); +} - return FileType::Error; +FileType AppLoader_NAX::GetFileType() { + return IdentifyTypeImpl(*nax); } ResultStatus AppLoader_NAX::Load(Kernel::SharedPtr<Kernel::Process>& process) { diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h index 4dbae2918..56605fe45 100644 --- a/src/core/loader/nax.h +++ b/src/core/loader/nax.h @@ -31,9 +31,7 @@ public: */ static FileType IdentifyType(const FileSys::VirtualFile& file); - FileType GetFileType() override { - return IdentifyType(file); - } + FileType GetFileType() override; ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index c036a8a1c..6aaffae59 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp @@ -48,7 +48,7 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) { if (exefs == nullptr) return ResultStatus::ErrorNoExeFS; - directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs); + directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true); const auto load_result = directory_loader->Load(process); if (load_result != ResultStatus::Success) @@ -71,6 +71,12 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { return ResultStatus::Success; } +u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { + if (nca == nullptr) + return 0; + return nca->GetBaseIVFCOffset(); +} + ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) return ResultStatus::ErrorNotInitialized; diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index 326f84857..10be197c4 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h @@ -37,6 +37,7 @@ public: ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + u64 ReadRomFSIVFCOffset() const override; ResultStatus ReadProgramId(u64& out_program_id) override; private: diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index 77026b850..c49ec34ab 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -191,7 +191,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->svc_access_mask.set(); process->resource_limit = kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); + process->Run(base_addr, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; @@ -232,4 +232,9 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) { title = nacp->GetApplicationName(); return ResultStatus::Success; } + +bool AppLoader_NRO::IsRomFSUpdatable() const { + return false; +} + } // namespace Loader diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h index bb01c9e25..96d2de305 100644 --- a/src/core/loader/nro.h +++ b/src/core/loader/nro.h @@ -39,6 +39,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; ResultStatus ReadTitle(std::string& title) override; + bool IsRomFSUpdatable() const override; private: bool LoadNro(FileSys::VirtualFile file, VAddr load_base); diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 082a95d40..3c6306818 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -157,7 +157,8 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) { process->svc_access_mask.set(); process->resource_limit = kernel.ResourceLimitForCategory(Kernel::ResourceLimitCategory::APPLICATION); - process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE); + process->Run(Memory::PROCESS_IMAGE_VADDR, Kernel::THREADPRIO_DEFAULT, + Memory::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp new file mode 100644 index 000000000..291a9876d --- /dev/null +++ b/src/core/loader/nsp.cpp @@ -0,0 +1,125 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <vector> + +#include "common/common_types.h" +#include "core/file_sys/card_image.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.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.h" +#include "core/file_sys/submission_package.h" +#include "core/hle/kernel/process.h" +#include "core/loader/deconstructed_rom_directory.h" +#include "core/loader/nca.h" +#include "core/loader/nsp.h" + +namespace Loader { + +AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) + : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)), + title_id(nsp->GetProgramTitleID()) { + + if (nsp->GetStatus() != ResultStatus::Success) + return; + if (nsp->IsExtractedType()) + return; + + const auto control_nca = + nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control); + if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) + return; + + std::tie(nacp_file, icon_file) = + FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca); +} + +AppLoader_NSP::~AppLoader_NSP() = default; + +FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) { + FileSys::NSP nsp(file); + + if (nsp.GetStatus() == ResultStatus::Success) { + // Extracted Type case + if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && + FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) { + return FileType::NSP; + } + + // Non-Ectracted Type case + if (!nsp.IsExtractedType() && + nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr && + AppLoader_NCA::IdentifyType(nsp.GetNCAFile( + nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA) { + return FileType::NSP; + } + } + + return FileType::Error; +} + +ResultStatus AppLoader_NSP::Load(Kernel::SharedPtr<Kernel::Process>& process) { + if (is_loaded) { + return ResultStatus::ErrorAlreadyLoaded; + } + + if (nsp->IsExtractedType()) { + secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); + } else { + if (title_id == 0) + return ResultStatus::ErrorNSPMissingProgramNCA; + + secondary_loader = std::make_unique<AppLoader_NCA>( + nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program)); + + if (nsp->GetStatus() != ResultStatus::Success) + return nsp->GetStatus(); + + if (nsp->GetProgramStatus(title_id) != ResultStatus::Success) + return nsp->GetProgramStatus(title_id); + + if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) { + if (!Core::Crypto::KeyManager::KeyFileExists(false)) + return ResultStatus::ErrorMissingProductionKeyFile; + return ResultStatus::ErrorNSPMissingProgramNCA; + } + } + + const auto result = secondary_loader->Load(process); + if (result != ResultStatus::Success) + return result; + + is_loaded = true; + + return ResultStatus::Success; +} + +ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) { + return secondary_loader->ReadRomFS(dir); +} + +ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) { + if (title_id == 0) + return ResultStatus::ErrorNotInitialized; + out_program_id = title_id; + return ResultStatus::Success; +} + +ResultStatus AppLoader_NSP::ReadIcon(std::vector<u8>& buffer) { + if (icon_file == nullptr) + return ResultStatus::ErrorNoControl; + buffer = icon_file->ReadAllBytes(); + return ResultStatus::Success; +} + +ResultStatus AppLoader_NSP::ReadTitle(std::string& title) { + if (nacp_file == nullptr) + return ResultStatus::ErrorNoControl; + title = nacp_file->GetApplicationName(); + return ResultStatus::Success; +} +} // namespace Loader diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h new file mode 100644 index 000000000..7ef810499 --- /dev/null +++ b/src/core/loader/nsp.h @@ -0,0 +1,54 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "common/common_types.h" +#include "core/file_sys/vfs.h" +#include "core/loader/loader.h" + +namespace FileSys { +class NACP; +class NSP; +} // namespace FileSys + +namespace Loader { + +class AppLoader_NCA; + +/// Loads an XCI file +class AppLoader_NSP final : public AppLoader { +public: + explicit AppLoader_NSP(FileSys::VirtualFile file); + ~AppLoader_NSP() override; + + /** + * Returns the type of the file + * @param file std::shared_ptr<VfsFile> open file + * @return FileType found, or FileType::Error if this loader doesn't know it + */ + static FileType IdentifyType(const FileSys::VirtualFile& file); + + FileType GetFileType() override { + return IdentifyType(file); + } + + ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; + + ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; + ResultStatus ReadProgramId(u64& out_program_id) override; + ResultStatus ReadIcon(std::vector<u8>& buffer) override; + ResultStatus ReadTitle(std::string& title) override; + +private: + std::unique_ptr<FileSys::NSP> nsp; + std::unique_ptr<AppLoader> secondary_loader; + + FileSys::VirtualFile icon_file; + std::shared_ptr<FileSys::NACP> nacp_file; + u64 title_id; +}; + +} // namespace Loader diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 9dc4d1f35..16509229f 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -8,7 +8,9 @@ #include "core/file_sys/card_image.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/romfs.h" +#include "core/file_sys/submission_package.h" #include "core/hle/kernel/process.h" #include "core/loader/nca.h" #include "core/loader/xci.h" @@ -17,25 +19,16 @@ namespace Loader { AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), - nca_loader(std::make_unique<AppLoader_NCA>( - xci->GetNCAFileByType(FileSys::NCAContentType::Program))) { + nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) { if (xci->GetStatus() != ResultStatus::Success) return; + const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control); if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) return; - const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS()); - if (romfs == nullptr) - return; - for (const auto& language : FileSys::LANGUAGE_NAMES) { - icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat"); - if (icon_file != nullptr) - break; - } - const auto nacp_raw = romfs->GetFile("control.nacp"); - if (nacp_raw == nullptr) - return; - nacp_file = std::make_shared<FileSys::NACP>(nacp_raw); + + std::tie(nacp_file, icon_file) = + FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca); } AppLoader_XCI::~AppLoader_XCI() = default; @@ -64,11 +57,11 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) { if (xci->GetProgramNCAStatus() != ResultStatus::Success) return xci->GetProgramNCAStatus(); - const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program); + const auto nca = xci->GetProgramNCA(); if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false)) return ResultStatus::ErrorMissingProductionKeyFile; - auto result = nca_loader->Load(process); + const auto result = nca_loader->Load(process); if (result != ResultStatus::Success) return result; diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 0e4e0157c..316b46820 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -370,16 +370,16 @@ u64 Read64(const VAddr addr) { } void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer, - const size_t size) { + const std::size_t size) { auto& page_table = process.vm_manager.page_table; - size_t remaining_size = size; - size_t page_index = src_addr >> PAGE_BITS; - size_t page_offset = src_addr & PAGE_MASK; + std::size_t remaining_size = size; + std::size_t page_index = src_addr >> PAGE_BITS; + std::size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = - std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); + const std::size_t copy_amount = + std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { @@ -414,7 +414,7 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_ } } -void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { +void ReadBlock(const VAddr src_addr, void* dest_buffer, const std::size_t size) { ReadBlock(*Core::CurrentProcess(), src_addr, dest_buffer, size); } @@ -435,15 +435,15 @@ void Write64(const VAddr addr, const u64 data) { } void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer, - const size_t size) { + const std::size_t size) { auto& page_table = process.vm_manager.page_table; - size_t remaining_size = size; - size_t page_index = dest_addr >> PAGE_BITS; - size_t page_offset = dest_addr & PAGE_MASK; + std::size_t remaining_size = size; + std::size_t page_index = dest_addr >> PAGE_BITS; + std::size_t page_offset = dest_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = - std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); + const std::size_t copy_amount = + std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { @@ -477,19 +477,19 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi } } -void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) { +void WriteBlock(const VAddr dest_addr, const void* src_buffer, const std::size_t size) { WriteBlock(*Core::CurrentProcess(), dest_addr, src_buffer, size); } -void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size_t size) { +void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std::size_t size) { auto& page_table = process.vm_manager.page_table; - size_t remaining_size = size; - size_t page_index = dest_addr >> PAGE_BITS; - size_t page_offset = dest_addr & PAGE_MASK; + std::size_t remaining_size = size; + std::size_t page_index = dest_addr >> PAGE_BITS; + std::size_t page_offset = dest_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = - std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); + const std::size_t copy_amount = + std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { @@ -522,15 +522,16 @@ void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const size } } -void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, const size_t size) { +void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, + const std::size_t size) { auto& page_table = process.vm_manager.page_table; - size_t remaining_size = size; - size_t page_index = src_addr >> PAGE_BITS; - size_t page_offset = src_addr & PAGE_MASK; + std::size_t remaining_size = size; + std::size_t page_index = src_addr >> PAGE_BITS; + std::size_t page_offset = src_addr & PAGE_MASK; while (remaining_size > 0) { - const size_t copy_amount = - std::min(static_cast<size_t>(PAGE_SIZE) - page_offset, remaining_size); + const std::size_t copy_amount = + std::min(static_cast<std::size_t>(PAGE_SIZE) - page_offset, remaining_size); const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); switch (page_table.attributes[page_index]) { @@ -565,7 +566,7 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr, } } -void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size) { +void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size) { CopyBlock(*Core::CurrentProcess(), dest_addr, src_addr, size); } diff --git a/src/core/memory.h b/src/core/memory.h index f06e04a75..2a27c0251 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -22,11 +22,11 @@ namespace Memory { * Page size used by the ARM architecture. This is the smallest granularity with which memory can * be mapped. */ -constexpr size_t PAGE_BITS = 12; +constexpr std::size_t PAGE_BITS = 12; constexpr u64 PAGE_SIZE = 1 << PAGE_BITS; constexpr u64 PAGE_MASK = PAGE_SIZE - 1; -constexpr size_t ADDRESS_SPACE_BITS = 36; -constexpr size_t PAGE_TABLE_NUM_ENTRIES = 1ULL << (ADDRESS_SPACE_BITS - PAGE_BITS); +constexpr std::size_t ADDRESS_SPACE_BITS = 36; +constexpr std::size_t PAGE_TABLE_NUM_ENTRIES = 1ULL << (ADDRESS_SPACE_BITS - PAGE_BITS); enum class PageType : u8 { /// Page is unmapped and should cause an access error. @@ -154,13 +154,13 @@ void Write16(VAddr addr, u16 data); void Write32(VAddr addr, u32 data); void Write64(VAddr addr, u64 data); -void ReadBlock(const Kernel::Process& process, VAddr src_addr, void* dest_buffer, size_t size); -void ReadBlock(VAddr src_addr, void* dest_buffer, size_t size); +void ReadBlock(const Kernel::Process& process, VAddr src_addr, void* dest_buffer, std::size_t size); +void ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size); void WriteBlock(const Kernel::Process& process, VAddr dest_addr, const void* src_buffer, - size_t size); -void WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size); -void ZeroBlock(const Kernel::Process& process, VAddr dest_addr, size_t size); -void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size); + std::size_t size); +void WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size); +void ZeroBlock(const Kernel::Process& process, VAddr dest_addr, std::size_t size); +void CopyBlock(VAddr dest_addr, VAddr src_addr, std::size_t size); u8* GetPointer(VAddr vaddr); diff --git a/src/core/memory_hook.h b/src/core/memory_hook.h index e8ea19333..0269c7ff1 100644 --- a/src/core/memory_hook.h +++ b/src/core/memory_hook.h @@ -32,14 +32,14 @@ public: virtual boost::optional<u32> Read32(VAddr addr) = 0; virtual boost::optional<u64> Read64(VAddr addr) = 0; - virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0; + virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0; virtual bool Write8(VAddr addr, u8 data) = 0; virtual bool Write16(VAddr addr, u16 data) = 0; virtual bool Write32(VAddr addr, u32 data) = 0; virtual bool Write64(VAddr addr, u64 data) = 0; - virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0; + virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) = 0; }; using MemoryHookPointer = std::shared_ptr<MemoryHook>; diff --git a/src/core/settings.h b/src/core/settings.h index ed6f42471..0318d019c 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <atomic> #include <string> #include "common/common_types.h" @@ -120,6 +121,7 @@ struct Values { std::array<std::string, NativeAnalog::NumAnalogs> analogs; std::string motion_device; std::string touch_device; + std::atomic_bool is_device_reload_pending{true}; // Core bool use_cpu_jit; @@ -127,6 +129,8 @@ struct Values { // Data Storage bool use_virtual_sd; + std::string nand_dir; + std::string sdmc_dir; // Renderer float resolution_factor; @@ -144,6 +148,7 @@ struct Values { // Audio std::string sink_id; + bool enable_audio_stretching; std::string audio_device_id; float volume; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 65571b948..b0df154ca 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -7,6 +7,8 @@ #include "common/file_util.h" #include "core/core.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/patch_manager.h" #include "core/loader/loader.h" #include "core/settings.h" #include "core/telemetry_session.h" @@ -88,12 +90,28 @@ TelemetrySession::TelemetrySession() { std::chrono::system_clock::now().time_since_epoch()) .count()}; AddField(Telemetry::FieldType::Session, "Init_Time", init_time); - std::string program_name; - const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadTitle(program_name)}; + + u64 program_id{}; + const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)}; if (res == Loader::ResultStatus::Success) { - AddField(Telemetry::FieldType::Session, "ProgramName", program_name); + AddField(Telemetry::FieldType::Session, "ProgramId", program_id); + + std::string name; + System::GetInstance().GetAppLoader().ReadTitle(name); + + if (name.empty()) { + auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata(); + if (nacp != nullptr) + name = nacp->GetApplicationName(); + } + + if (!name.empty()) + AddField(Telemetry::FieldType::Session, "ProgramName", name); } + AddField(Telemetry::FieldType::Session, "ProgramFormat", + static_cast<u8>(System::GetInstance().GetAppLoader().GetFileType())); + // Log application information Telemetry::AppendBuildInfo(field_collection); @@ -102,6 +120,9 @@ TelemetrySession::TelemetrySession() { Telemetry::AppendOSInfo(field_collection); // Log user configuration information + AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id); + AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching", + Settings::values.enable_audio_stretching); AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore", Settings::values.use_multi_core); diff --git a/src/core/tracer/recorder.cpp b/src/core/tracer/recorder.cpp index af032f0c9..73cacb47f 100644 --- a/src/core/tracer/recorder.cpp +++ b/src/core/tracer/recorder.cpp @@ -76,7 +76,7 @@ void Recorder::Finish(const std::string& filename) { try { // Open file and write header FileUtil::IOFile file(filename, "wb"); - size_t written = file.WriteObject(header); + std::size_t written = file.WriteObject(header); if (written != 1 || file.Tell() != initial.gpu_registers) throw "Failed to write header"; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index b12623d55..37f572853 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <memory> +#include <thread> #include "common/param_package.h" #include "input_common/analog_from_button.h" #include "input_common/keyboard.h" @@ -17,6 +18,10 @@ namespace InputCommon { static std::shared_ptr<Keyboard> keyboard; static std::shared_ptr<MotionEmu> motion_emu; +#ifdef HAVE_SDL2 +static std::thread poll_thread; +#endif + void Init() { keyboard = std::make_shared<Keyboard>(); Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); @@ -30,6 +35,12 @@ void Init() { #endif } +void StartJoystickEventHandler() { +#ifdef HAVE_SDL2 + poll_thread = std::thread(SDL::PollLoop); +#endif +} + void Shutdown() { Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); keyboard.reset(); @@ -39,6 +50,7 @@ void Shutdown() { #ifdef HAVE_SDL2 SDL::Shutdown(); + poll_thread.join(); #endif } diff --git a/src/input_common/main.h b/src/input_common/main.h index 77a0ce90b..9eb13106e 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -20,6 +20,8 @@ void Init(); /// Deregisters all built-in input device factories and shuts them down. void Shutdown(); +void StartJoystickEventHandler(); + class Keyboard; /// Gets the keyboard button device factory. diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp index d1b960fd7..faf3c1fa3 100644 --- a/src/input_common/sdl/sdl.cpp +++ b/src/input_common/sdl/sdl.cpp @@ -2,15 +2,24 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <atomic> #include <cmath> +#include <functional> +#include <iterator> +#include <mutex> #include <string> +#include <thread> #include <tuple> #include <unordered_map> #include <utility> +#include <vector> #include <SDL.h> +#include "common/assert.h" #include "common/logging/log.h" #include "common/math_util.h" #include "common/param_package.h" +#include "common/threadsafe_queue.h" #include "input_common/main.h" #include "input_common/sdl/sdl.h" @@ -21,33 +30,51 @@ namespace SDL { class SDLJoystick; class SDLButtonFactory; class SDLAnalogFactory; -static std::unordered_map<int, std::weak_ptr<SDLJoystick>> joystick_list; + +/// Map of GUID of a list of corresponding virtual Joysticks +static std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; +static std::mutex joystick_map_mutex; + static std::shared_ptr<SDLButtonFactory> button_factory; static std::shared_ptr<SDLAnalogFactory> analog_factory; -static bool initialized = false; +/// Used by the Pollers during config +static std::atomic<bool> polling; +static Common::SPSCQueue<SDL_Event> event_queue; + +static std::atomic<bool> initialized = false; + +static std::string GetGUID(SDL_Joystick* joystick) { + SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + char guid_str[33]; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); + return guid_str; +} class SDLJoystick { public: - explicit SDLJoystick(int joystick_index) - : joystick{SDL_JoystickOpen(joystick_index), SDL_JoystickClose} { - if (!joystick) { - LOG_ERROR(Input, "failed to open joystick {}", joystick_index); - } + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {} + + void SetButton(int button, bool value) { + std::lock_guard<std::mutex> lock(mutex); + state.buttons[button] = value; } bool GetButton(int button) const { - if (!joystick) - return {}; - SDL_JoystickUpdate(); - return SDL_JoystickGetButton(joystick.get(), button) == 1; + std::lock_guard<std::mutex> lock(mutex); + return state.buttons.at(button); + } + + void SetAxis(int axis, Sint16 value) { + std::lock_guard<std::mutex> lock(mutex); + state.axes[axis] = value; } float GetAxis(int axis) const { - if (!joystick) - return {}; - SDL_JoystickUpdate(); - return SDL_JoystickGetAxis(joystick.get(), axis) / 32767.0f; + std::lock_guard<std::mutex> lock(mutex); + return state.axes.at(axis) / 32767.0f; } std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { @@ -67,18 +94,213 @@ public: return std::make_tuple(x, y); } + void SetHat(int hat, Uint8 direction) { + std::lock_guard<std::mutex> lock(mutex); + state.hats[hat] = direction; + } + bool GetHatDirection(int hat, Uint8 direction) const { - return (SDL_JoystickGetHat(joystick.get(), hat) & direction) != 0; + std::lock_guard<std::mutex> lock(mutex); + return (state.hats.at(hat) & direction) != 0; + } + /** + * The guid of the joystick + */ + const std::string& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_Joystick* GetSDLJoystick() const { + return sdl_joystick.get(); } - SDL_JoystickID GetJoystickID() const { - return SDL_JoystickInstanceID(joystick.get()); + void SetSDLJoystick(SDL_Joystick* joystick, + decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) { + sdl_joystick = + std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter); } private: - std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> joystick; + struct State { + std::unordered_map<int, bool> buttons; + std::unordered_map<int, Sint16> axes; + std::unordered_map<int, Uint8> hats; + } state; + std::string guid; + int port; + std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; + mutable std::mutex mutex; }; +/** + * Get the nth joystick with the corresponding GUID + */ +static std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port) { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + const auto it = joystick_map.find(guid); + if (it != joystick_map.end()) { + while (it->second.size() <= port) { + auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr, + [](SDL_Joystick*) {}); + it->second.emplace_back(std::move(joystick)); + } + return it->second[port]; + } + auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {}); + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +/** + * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie + * it to a SDLJoystick with the same guid and that port + */ +static std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + const std::string guid = GetGUID(sdl_joystick); + auto map_it = joystick_map.find(guid); + if (map_it != joystick_map.end()) { + auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { + return sdl_joystick == joystick->GetSDLJoystick(); + }); + if (vec_it != map_it->second.end()) { + // This is the common case: There is already an existing SDL_Joystick maped to a + // SDLJoystick. return the SDLJoystick + return *vec_it; + } + // Search for a SDLJoystick without a mapped SDL_Joystick... + auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [](const std::shared_ptr<SDLJoystick>& joystick) { + return !joystick->GetSDLJoystick(); + }); + if (nullptr_it != map_it->second.end()) { + // ... and map it + (*nullptr_it)->SetSDLJoystick(sdl_joystick); + return *nullptr_it; + } + // There is no SDLJoystick without a mapped SDL_Joystick + // Create a new SDLJoystick + auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick); + return map_it->second.emplace_back(std::move(joystick)); + } + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +void InitJoystick(int joystick_index) { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); + if (!sdl_joystick) { + LOG_ERROR(Input, "failed to open joystick {}", joystick_index); + return; + } + std::string guid = GetGUID(sdl_joystick); + if (joystick_map.find(guid) == joystick_map.end()) { + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); + joystick_map[guid].emplace_back(std::move(joystick)); + return; + } + auto& joystick_guid_list = joystick_map[guid]; + const auto it = std::find_if( + joystick_guid_list.begin(), joystick_guid_list.end(), + [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); + if (it != joystick_guid_list.end()) { + (*it)->SetSDLJoystick(sdl_joystick); + return; + } + auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick); + joystick_guid_list.emplace_back(std::move(joystick)); +} + +void CloseJoystick(SDL_Joystick* sdl_joystick) { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + std::string guid = GetGUID(sdl_joystick); + // This call to guid is save since the joystick is guranteed to be in that map + auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {}); +} + +void HandleGameControllerEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_JOYBUTTONUP: { + auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); + if (joystick) { + joystick->SetButton(event.jbutton.button, false); + } + break; + } + case SDL_JOYBUTTONDOWN: { + auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); + if (joystick) { + joystick->SetButton(event.jbutton.button, true); + } + break; + } + case SDL_JOYHATMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jhat.which); + if (joystick) { + joystick->SetHat(event.jhat.hat, event.jhat.value); + } + break; + } + case SDL_JOYAXISMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); + if (joystick) { + joystick->SetAxis(event.jaxis.axis, event.jaxis.value); + } + break; + } + case SDL_JOYDEVICEREMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + break; + case SDL_JOYDEVICEADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + InitJoystick(event.jdevice.which); + break; + } +} + +void CloseSDLJoysticks() { + std::lock_guard<std::mutex> lock(joystick_map_mutex); + joystick_map.clear(); +} + +void PollLoop() { + if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { + LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); + return; + } + + SDL_Event event; + while (initialized) { + // Wait for 10 ms or until an event happens + if (SDL_WaitEventTimeout(&event, 10)) { + // Don't handle the event if we are configuring + if (polling) { + event_queue.Push(event); + } else { + HandleGameControllerEvent(event); + } + } + } + CloseSDLJoysticks(); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); +} + class SDLButton final : public Input::ButtonDevice { public: explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_) @@ -144,22 +366,14 @@ private: int axis_y; }; -static std::shared_ptr<SDLJoystick> GetJoystick(int joystick_index) { - std::shared_ptr<SDLJoystick> joystick = joystick_list[joystick_index].lock(); - if (!joystick) { - joystick = std::make_shared<SDLJoystick>(joystick_index); - joystick_list[joystick_index] = joystick; - } - return joystick; -} - /// A button device factory that creates button devices from SDL joystick class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { public: /** * Creates a button device from a joystick button * @param params contains parameters for creating the device: - * - "joystick": the index of the joystick to bind + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type to bind * - "button"(optional): the index of the button to bind * - "hat"(optional): the index of the hat to bind as direction buttons * - "axis"(optional): the index of the axis to bind @@ -167,12 +381,15 @@ public: * "down", "left" or "right" * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is * triggered if the axis value crosses - * - "direction"(only used for axis): "+" means the button is triggered when the axis value - * is greater than the threshold; "-" means the button is triggered when the axis value - * is smaller than the threshold + * - "direction"(only used for axis): "+" means the button is triggered when the axis + * value is greater than the threshold; "-" means the button is triggered when the axis + * value is smaller than the threshold */ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { - const int joystick_index = params.Get("joystick", 0); + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); + + auto joystick = GetSDLJoystickByGUID(guid, port); if (params.Has("hat")) { const int hat = params.Get("hat", 0); @@ -189,8 +406,9 @@ public: } else { direction = 0; } - return std::make_unique<SDLDirectionButton>(GetJoystick(joystick_index), hat, - direction); + // This is necessary so accessing GetHat with hat won't crash + joystick->SetHat(hat, SDL_HAT_CENTERED); + return std::make_unique<SDLDirectionButton>(joystick, hat, direction); } if (params.Has("axis")) { @@ -206,12 +424,15 @@ public: trigger_if_greater = true; LOG_ERROR(Input, "Unknown direction '{}'", direction_name); } - return std::make_unique<SDLAxisButton>(GetJoystick(joystick_index), axis, threshold, - trigger_if_greater); + // This is necessary so accessing GetAxis with axis won't crash + joystick->SetAxis(axis, 0); + return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); } const int button = params.Get("button", 0); - return std::make_unique<SDLButton>(GetJoystick(joystick_index), button); + // This is necessary so accessing GetButton with button won't crash + joystick->SetButton(button, false); + return std::make_unique<SDLButton>(joystick, button); } }; @@ -221,27 +442,32 @@ public: /** * Creates analog device from joystick axes * @param params contains parameters for creating the device: - * - "joystick": the index of the joystick to bind + * - "guid": the guid of the joystick to bind + * - "port": the nth joystick of the same type * - "axis_x": the index of the axis to be bind as x-axis * - "axis_y": the index of the axis to be bind as y-axis */ std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { - const int joystick_index = params.Get("joystick", 0); + const std::string guid = params.Get("guid", "0"); + const int port = params.Get("port", 0); const int axis_x = params.Get("axis_x", 0); const int axis_y = params.Get("axis_y", 1); - return std::make_unique<SDLAnalog>(GetJoystick(joystick_index), axis_x, axis_y); + + auto joystick = GetSDLJoystickByGUID(guid, port); + + // This is necessary so accessing GetAxis with axis_x and axis_y won't crash + joystick->SetAxis(axis_x, 0); + joystick->SetAxis(axis_y, 0); + return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y); } }; void Init() { - if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); - } else { - using namespace Input; - RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>()); - RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>()); - initialized = true; - } + using namespace Input; + RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>()); + RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>()); + polling = false; + initialized = true; } void Shutdown() { @@ -249,30 +475,17 @@ void Shutdown() { using namespace Input; UnregisterFactory<ButtonDevice>("sdl"); UnregisterFactory<AnalogDevice>("sdl"); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); + initialized = false; } } -/** - * This function converts a joystick ID used in SDL events to the device index. This is necessary - * because Citra opens joysticks using their indices, not their IDs. - */ -static int JoystickIDToDeviceIndex(SDL_JoystickID id) { - int num_joysticks = SDL_NumJoysticks(); - for (int i = 0; i < num_joysticks; i++) { - auto joystick = GetJoystick(i); - if (joystick->GetJoystickID() == id) { - return i; - } - } - return -1; -} - Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { Common::ParamPackage params({{"engine", "sdl"}}); switch (event.type) { - case SDL_JOYAXISMOTION: - params.Set("joystick", JoystickIDToDeviceIndex(event.jaxis.which)); + case SDL_JOYAXISMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); params.Set("axis", event.jaxis.axis); if (event.jaxis.value > 0) { params.Set("direction", "+"); @@ -282,12 +495,18 @@ Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { params.Set("threshold", "-0.5"); } break; - case SDL_JOYBUTTONUP: - params.Set("joystick", JoystickIDToDeviceIndex(event.jbutton.which)); + } + case SDL_JOYBUTTONUP: { + auto joystick = GetSDLJoystickBySDLID(event.jbutton.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); params.Set("button", event.jbutton.button); break; - case SDL_JOYHATMOTION: - params.Set("joystick", JoystickIDToDeviceIndex(event.jhat.which)); + } + case SDL_JOYHATMOTION: { + auto joystick = GetSDLJoystickBySDLID(event.jhat.which); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); params.Set("hat", event.jhat.hat); switch (event.jhat.value) { case SDL_HAT_UP: @@ -307,6 +526,7 @@ Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) { } break; } + } return params; } @@ -315,31 +535,20 @@ namespace Polling { class SDLPoller : public InputCommon::Polling::DevicePoller { public: void Start() override { - // SDL joysticks must be opened, otherwise they don't generate events - SDL_JoystickUpdate(); - int num_joysticks = SDL_NumJoysticks(); - for (int i = 0; i < num_joysticks; i++) { - joysticks_opened.emplace_back(GetJoystick(i)); - } - // Empty event queue to get rid of old events. citra-qt doesn't use the queue - SDL_Event dummy; - while (SDL_PollEvent(&dummy)) { - } + event_queue.Clear(); + polling = true; } void Stop() override { - joysticks_opened.clear(); + polling = false; } - -private: - std::vector<std::shared_ptr<SDLJoystick>> joysticks_opened; }; class SDLButtonPoller final : public SDLPoller { public: Common::ParamPackage GetNextInput() override { SDL_Event event; - while (SDL_PollEvent(&event)) { + while (event_queue.Pop(event)) { switch (event.type) { case SDL_JOYAXISMOTION: if (std::abs(event.jaxis.value / 32767.0) < 0.5) { @@ -367,7 +576,7 @@ public: Common::ParamPackage GetNextInput() override { SDL_Event event; - while (SDL_PollEvent(&event)) { + while (event_queue.Pop(event)) { if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { continue; } @@ -384,8 +593,10 @@ public: } Common::ParamPackage params; if (analog_xaxis != -1 && analog_yaxis != -1) { + auto joystick = GetSDLJoystickBySDLID(event.jaxis.which); params.Set("engine", "sdl"); - params.Set("joystick", JoystickIDToDeviceIndex(analog_axes_joystick)); + params.Set("port", joystick->GetPort()); + params.Set("guid", joystick->GetGUID()); params.Set("axis_x", analog_xaxis); params.Set("axis_y", analog_yaxis); analog_xaxis = -1; diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h index 7934099d4..0206860d3 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -28,6 +28,15 @@ void Init(); /// Unresisters SDL device factories and shut them down. void Shutdown(); +/// Needs to be called before SDL_QuitSubSystem. +void CloseSDLJoysticks(); + +/// Handle SDL_Events for joysticks from SDL_PollEvent +void HandleGameControllerEvent(const SDL_Event& event); + +/// A Loop that calls HandleGameControllerEvent until Shutdown is called +void PollLoop(); + /// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event); diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 4d74bb395..37f09ce5f 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,16 +1,15 @@ add_executable(tests common/param_package.cpp + common/ring_buffer.cpp core/arm/arm_test_common.cpp core/arm/arm_test_common.h core/core_timing.cpp - glad.cpp tests.cpp ) create_target_directory_groups(tests) target_link_libraries(tests PRIVATE common core) -target_link_libraries(tests PRIVATE glad) # To support linker work-around target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads) add_test(NAME tests COMMAND tests) diff --git a/src/tests/common/ring_buffer.cpp b/src/tests/common/ring_buffer.cpp new file mode 100644 index 000000000..c883c4d56 --- /dev/null +++ b/src/tests/common/ring_buffer.cpp @@ -0,0 +1,130 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <array> +#include <cstddef> +#include <numeric> +#include <thread> +#include <vector> +#include <catch2/catch.hpp> +#include "common/ring_buffer.h" + +namespace Common { + +TEST_CASE("RingBuffer: Basic Tests", "[common]") { + RingBuffer<char, 4, 1> buf; + + // Pushing values into a ring buffer with space should succeed. + for (std::size_t i = 0; i < 4; i++) { + const char elem = static_cast<char>(i); + const std::size_t count = buf.Push(&elem, 1); + REQUIRE(count == 1); + } + + REQUIRE(buf.Size() == 4); + + // Pushing values into a full ring buffer should fail. + { + const char elem = static_cast<char>(42); + const std::size_t count = buf.Push(&elem, 1); + REQUIRE(count == 0); + } + + REQUIRE(buf.Size() == 4); + + // Popping multiple values from a ring buffer with values should succeed. + { + const std::vector<char> popped = buf.Pop(2); + REQUIRE(popped.size() == 2); + REQUIRE(popped[0] == 0); + REQUIRE(popped[1] == 1); + } + + REQUIRE(buf.Size() == 2); + + // Popping a single value from a ring buffer with values should succeed. + { + const std::vector<char> popped = buf.Pop(1); + REQUIRE(popped.size() == 1); + REQUIRE(popped[0] == 2); + } + + REQUIRE(buf.Size() == 1); + + // Pushing more values than space available should partially suceed. + { + std::vector<char> to_push(6); + std::iota(to_push.begin(), to_push.end(), 88); + const std::size_t count = buf.Push(to_push); + REQUIRE(count == 3); + } + + REQUIRE(buf.Size() == 4); + + // Doing an unlimited pop should pop all values. + { + const std::vector<char> popped = buf.Pop(); + REQUIRE(popped.size() == 4); + REQUIRE(popped[0] == 3); + REQUIRE(popped[1] == 88); + REQUIRE(popped[2] == 89); + REQUIRE(popped[3] == 90); + } + + REQUIRE(buf.Size() == 0); +} + +TEST_CASE("RingBuffer: Threaded Test", "[common]") { + RingBuffer<char, 4, 2> buf; + const char seed = 42; + const std::size_t count = 1000000; + std::size_t full = 0; + std::size_t empty = 0; + + const auto next_value = [](std::array<char, 2>& value) { + value[0] += 1; + value[1] += 2; + }; + + std::thread producer{[&] { + std::array<char, 2> value = {seed, seed}; + std::size_t i = 0; + while (i < count) { + if (const std::size_t c = buf.Push(&value[0], 1); c > 0) { + REQUIRE(c == 1); + i++; + next_value(value); + } else { + full++; + std::this_thread::yield(); + } + } + }}; + + std::thread consumer{[&] { + std::array<char, 2> value = {seed, seed}; + std::size_t i = 0; + while (i < count) { + if (const std::vector<char> v = buf.Pop(1); v.size() > 0) { + REQUIRE(v.size() == 2); + REQUIRE(v[0] == value[0]); + REQUIRE(v[1] == value[1]); + i++; + next_value(value); + } else { + empty++; + std::this_thread::yield(); + } + } + }}; + + producer.join(); + consumer.join(); + + REQUIRE(buf.Size() == 0); + printf("RingBuffer: Threaded Test: full: %zu, empty: %zu\n", full, empty); +} + +} // namespace Common diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 038d57b3a..7c69fc26e 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -87,11 +87,11 @@ boost::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) { return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32; } -bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) { +bool TestEnvironment::TestMemory::ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) { VAddr addr = src_addr; u8* data = static_cast<u8*>(dest_buffer); - for (size_t i = 0; i < size; i++, addr++, data++) { + for (std::size_t i = 0; i < size; i++, addr++, data++) { *data = *Read8(addr); } @@ -126,11 +126,12 @@ bool TestEnvironment::TestMemory::Write64(VAddr addr, u64 data) { return true; } -bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) { +bool TestEnvironment::TestMemory::WriteBlock(VAddr dest_addr, const void* src_buffer, + std::size_t size) { VAddr addr = dest_addr; const u8* data = static_cast<const u8*>(src_buffer); - for (size_t i = 0; i < size; i++, addr++, data++) { + for (std::size_t i = 0; i < size; i++, addr++, data++) { env->write_records.emplace_back(8, addr, *data); if (env->mutable_memory) env->SetMemory8(addr, *data); diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h index e4b6df194..5de8dab4e 100644 --- a/src/tests/core/arm/arm_test_common.h +++ b/src/tests/core/arm/arm_test_common.h @@ -19,8 +19,8 @@ struct PageTable; namespace ArmTests { struct WriteRecord { - WriteRecord(size_t size, VAddr addr, u64 data) : size(size), addr(addr), data(data) {} - size_t size; + WriteRecord(std::size_t size, VAddr addr, u64 data) : size(size), addr(addr), data(data) {} + std::size_t size; VAddr addr; u64 data; bool operator==(const WriteRecord& o) const { @@ -71,14 +71,14 @@ private: boost::optional<u32> Read32(VAddr addr) override; boost::optional<u64> Read64(VAddr addr) override; - bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) override; + bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override; bool Write8(VAddr addr, u8 data) override; bool Write16(VAddr addr, u16 data) override; bool Write32(VAddr addr, u32 data) override; bool Write64(VAddr addr, u64 data) override; - bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) override; + bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) override; std::unordered_map<VAddr, u8> data; }; diff --git a/src/tests/glad.cpp b/src/tests/glad.cpp deleted file mode 100644 index 1797c0e3d..000000000 --- a/src/tests/glad.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2016 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <catch2/catch.hpp> -#include <glad/glad.h> - -// This is not an actual test, but a work-around for issue #2183. -// If tests uses functions in core but doesn't explicitly use functions in glad, the linker of macOS -// will error about undefined references from video_core to glad. So we explicitly use a glad -// function here to shut up the linker. -TEST_CASE("glad fake test", "[dummy]") { - REQUIRE(&gladLoadGL != nullptr); -} diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index aa5bc3bbe..f5ae57039 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -5,6 +5,8 @@ add_library(video_core STATIC debug_utils/debug_utils.h engines/fermi_2d.cpp engines/fermi_2d.h + engines/kepler_memory.cpp + engines/kepler_memory.h engines/maxwell_3d.cpp engines/maxwell_3d.h engines/maxwell_compute.cpp @@ -12,6 +14,7 @@ add_library(video_core STATIC engines/maxwell_dma.cpp engines/maxwell_dma.h engines/shader_bytecode.h + engines/shader_header.h gpu.cpp gpu.h macro_interpreter.cpp @@ -22,6 +25,8 @@ add_library(video_core STATIC rasterizer_interface.h renderer_base.cpp renderer_base.h + renderer_opengl/gl_buffer_cache.cpp + renderer_opengl/gl_buffer_cache.h renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer.h renderer_opengl/gl_rasterizer_cache.cpp diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index dc485e811..f1aa6091b 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -14,6 +14,7 @@ #include "core/tracer/recorder.h" #include "video_core/command_processor.h" #include "video_core/engines/fermi_2d.h" +#include "video_core/engines/kepler_memory.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_compute.h" #include "video_core/engines/maxwell_dma.h" @@ -28,98 +29,109 @@ enum class BufferMethods { CountBufferMethods = 0x40, }; -void GPU::WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params) { - LOG_TRACE(HW_GPU, - "Processing method {:08X} on subchannel {} value " - "{:08X} remaining params {}", - method, subchannel, value, remaining_params); - - if (method == static_cast<u32>(BufferMethods::BindObject)) { - // Bind the current subchannel to the desired engine id. - LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value); - bound_engines[subchannel] = static_cast<EngineID>(value); - return; - } +MICROPROFILE_DEFINE(ProcessCommandLists, "GPU", "Execute command buffer", MP_RGB(128, 128, 192)); - if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) { - // TODO(Subv): Research and implement these methods. - LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented"); - return; - } +void GPU::ProcessCommandLists(const std::vector<CommandListHeader>& commands) { + MICROPROFILE_SCOPE(ProcessCommandLists); - ASSERT(bound_engines.find(subchannel) != bound_engines.end()); - - const EngineID engine = bound_engines[subchannel]; - - switch (engine) { - case EngineID::FERMI_TWOD_A: - fermi_2d->WriteReg(method, value); - break; - case EngineID::MAXWELL_B: - maxwell_3d->WriteReg(method, value, remaining_params); - break; - case EngineID::MAXWELL_COMPUTE_B: - maxwell_compute->WriteReg(method, value); - break; - case EngineID::MAXWELL_DMA_COPY_A: - maxwell_dma->WriteReg(method, value); - break; - default: - UNIMPLEMENTED_MSG("Unimplemented engine"); - } -} + auto WriteReg = [this](u32 method, u32 subchannel, u32 value, u32 remaining_params) { + LOG_TRACE(HW_GPU, + "Processing method {:08X} on subchannel {} value " + "{:08X} remaining params {}", + method, subchannel, value, remaining_params); -void GPU::ProcessCommandList(GPUVAddr address, u32 size) { - const boost::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address); - VAddr current_addr = *head_address; - while (current_addr < *head_address + size * sizeof(CommandHeader)) { - const CommandHeader header = {Memory::Read32(current_addr)}; - current_addr += sizeof(u32); - - switch (header.mode.Value()) { - case SubmissionMode::IncreasingOld: - case SubmissionMode::Increasing: { - // Increase the method value with each argument. - for (unsigned i = 0; i < header.arg_count; ++i) { - WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr), - header.arg_count - i - 1); - current_addr += sizeof(u32); - } - break; + ASSERT(subchannel < bound_engines.size()); + + if (method == static_cast<u32>(BufferMethods::BindObject)) { + // Bind the current subchannel to the desired engine id. + LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", subchannel, value); + bound_engines[subchannel] = static_cast<EngineID>(value); + return; } - case SubmissionMode::NonIncreasingOld: - case SubmissionMode::NonIncreasing: { - // Use the same method value for all arguments. - for (unsigned i = 0; i < header.arg_count; ++i) { - WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), - header.arg_count - i - 1); - current_addr += sizeof(u32); - } + + if (method < static_cast<u32>(BufferMethods::CountBufferMethods)) { + // TODO(Subv): Research and implement these methods. + LOG_ERROR(HW_GPU, "Special buffer methods other than Bind are not implemented"); + return; + } + + const EngineID engine = bound_engines[subchannel]; + + switch (engine) { + case EngineID::FERMI_TWOD_A: + fermi_2d->WriteReg(method, value); + break; + case EngineID::MAXWELL_B: + maxwell_3d->WriteReg(method, value, remaining_params); break; + case EngineID::MAXWELL_COMPUTE_B: + maxwell_compute->WriteReg(method, value); + break; + case EngineID::MAXWELL_DMA_COPY_A: + maxwell_dma->WriteReg(method, value); + break; + case EngineID::KEPLER_INLINE_TO_MEMORY_B: + kepler_memory->WriteReg(method, value); + break; + default: + UNIMPLEMENTED_MSG("Unimplemented engine"); } - case SubmissionMode::IncreaseOnce: { - ASSERT(header.arg_count.Value() >= 1); + }; - // Use the original method for the first argument and then the next method for all other - // arguments. - WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), - header.arg_count - 1); + for (auto entry : commands) { + Tegra::GPUVAddr address = entry.Address(); + u32 size = entry.sz; + const boost::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address); + VAddr current_addr = *head_address; + while (current_addr < *head_address + size * sizeof(CommandHeader)) { + const CommandHeader header = {Memory::Read32(current_addr)}; current_addr += sizeof(u32); - for (unsigned i = 1; i < header.arg_count; ++i) { - WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr), - header.arg_count - i - 1); + switch (header.mode.Value()) { + case SubmissionMode::IncreasingOld: + case SubmissionMode::Increasing: { + // Increase the method value with each argument. + for (unsigned i = 0; i < header.arg_count; ++i) { + WriteReg(header.method + i, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); + current_addr += sizeof(u32); + } + break; + } + case SubmissionMode::NonIncreasingOld: + case SubmissionMode::NonIncreasing: { + // Use the same method value for all arguments. + for (unsigned i = 0; i < header.arg_count; ++i) { + WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); + current_addr += sizeof(u32); + } + break; + } + case SubmissionMode::IncreaseOnce: { + ASSERT(header.arg_count.Value() >= 1); + + // Use the original method for the first argument and then the next method for all + // other arguments. + WriteReg(header.method, header.subchannel, Memory::Read32(current_addr), + header.arg_count - 1); current_addr += sizeof(u32); + + for (unsigned i = 1; i < header.arg_count; ++i) { + WriteReg(header.method + 1, header.subchannel, Memory::Read32(current_addr), + header.arg_count - i - 1); + current_addr += sizeof(u32); + } + break; + } + case SubmissionMode::Inline: { + // The register value is stored in the bits 16-28 as an immediate + WriteReg(header.method, header.subchannel, header.inline_data, 0); + break; + } + default: + UNIMPLEMENTED(); } - break; - } - case SubmissionMode::Inline: { - // The register value is stored in the bits 16-28 as an immediate - WriteReg(header.method, header.subchannel, header.inline_data, 0); - break; - } - default: - UNIMPLEMENTED(); } } } diff --git a/src/video_core/command_processor.h b/src/video_core/command_processor.h index a01153e0b..bd766e77a 100644 --- a/src/video_core/command_processor.h +++ b/src/video_core/command_processor.h @@ -7,6 +7,7 @@ #include <type_traits> #include "common/bit_field.h" #include "common/common_types.h" +#include "video_core/memory_manager.h" namespace Tegra { @@ -19,6 +20,22 @@ enum class SubmissionMode : u32 { IncreaseOnce = 5 }; +struct CommandListHeader { + u32 entry0; // gpu_va_lo + union { + u32 entry1; // gpu_va_hi | (unk_0x02 << 0x08) | (size << 0x0A) | (unk_0x01 << 0x1F) + BitField<0, 8, u32> gpu_va_hi; + BitField<8, 2, u32> unk1; + BitField<10, 21, u32> sz; + BitField<31, 1, u32> unk2; + }; + + GPUVAddr Address() const { + return (static_cast<GPUVAddr>(gpu_va_hi) << 32) | entry0; + } +}; +static_assert(sizeof(CommandListHeader) == 8, "CommandListHeader is incorrect size"); + union CommandHeader { u32 hex; diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index dcf9ef8b9..021b83eaa 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -26,7 +26,7 @@ public: void WriteReg(u32 method, u32 value); struct Regs { - static constexpr size_t NUM_REGS = 0x258; + static constexpr std::size_t NUM_REGS = 0x258; struct Surface { RenderTargetFormat format; diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp new file mode 100644 index 000000000..66ae6332d --- /dev/null +++ b/src/video_core/engines/kepler_memory.cpp @@ -0,0 +1,45 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "core/memory.h" +#include "video_core/engines/kepler_memory.h" + +namespace Tegra::Engines { + +KeplerMemory::KeplerMemory(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +KeplerMemory::~KeplerMemory() = default; + +void KeplerMemory::WriteReg(u32 method, u32 value) { + ASSERT_MSG(method < Regs::NUM_REGS, + "Invalid KeplerMemory register, increase the size of the Regs structure"); + + regs.reg_array[method] = value; + + switch (method) { + case KEPLERMEMORY_REG_INDEX(exec): { + state.write_offset = 0; + break; + } + case KEPLERMEMORY_REG_INDEX(data): { + ProcessData(value); + break; + } + } +} + +void KeplerMemory::ProcessData(u32 data) { + ASSERT_MSG(regs.exec.linear, "Non-linear uploads are not supported"); + ASSERT(regs.dest.x == 0 && regs.dest.y == 0 && regs.dest.z == 0); + + GPUVAddr address = regs.dest.Address(); + VAddr dest_address = + *memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32)); + + Memory::Write32(dest_address, data); + + state.write_offset++; +} + +} // namespace Tegra::Engines diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h new file mode 100644 index 000000000..b0d0078cf --- /dev/null +++ b/src/video_core/engines/kepler_memory.h @@ -0,0 +1,90 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" +#include "video_core/memory_manager.h" + +namespace Tegra::Engines { + +#define KEPLERMEMORY_REG_INDEX(field_name) \ + (offsetof(Tegra::Engines::KeplerMemory::Regs, field_name) / sizeof(u32)) + +class KeplerMemory final { +public: + KeplerMemory(MemoryManager& memory_manager); + ~KeplerMemory(); + + /// Write the value to the register identified by method. + void WriteReg(u32 method, u32 value); + + struct Regs { + static constexpr size_t NUM_REGS = 0x7F; + + union { + struct { + INSERT_PADDING_WORDS(0x60); + + u32 line_length_in; + u32 line_count; + + struct { + u32 address_high; + u32 address_low; + u32 pitch; + u32 block_dimensions; + u32 width; + u32 height; + u32 depth; + u32 z; + u32 x; + u32 y; + + GPUVAddr Address() const { + return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | + address_low); + } + } dest; + + struct { + union { + BitField<0, 1, u32> linear; + }; + } exec; + + u32 data; + + INSERT_PADDING_WORDS(0x11); + }; + std::array<u32, NUM_REGS> reg_array; + }; + } regs{}; + + struct { + u32 write_offset = 0; + } state{}; + +private: + MemoryManager& memory_manager; + + void ProcessData(u32 data); +}; + +#define ASSERT_REG_POSITION(field_name, position) \ + static_assert(offsetof(KeplerMemory::Regs, field_name) == position * 4, \ + "Field " #field_name " has invalid position") + +ASSERT_REG_POSITION(line_length_in, 0x60); +ASSERT_REG_POSITION(line_count, 0x61); +ASSERT_REG_POSITION(dest, 0x62); +ASSERT_REG_POSITION(exec, 0x6C); +ASSERT_REG_POSITION(data, 0x6D); +#undef ASSERT_REG_POSITION + +} // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 68ff1e86b..8afd26fe9 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -5,6 +5,7 @@ #include <cinttypes> #include "common/assert.h" #include "core/core.h" +#include "core/core_timing.h" #include "core/memory.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/engines/maxwell_3d.h" @@ -134,8 +135,6 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) { break; } - rasterizer.NotifyMaxwellRegisterChanged(method); - if (debug_context) { debug_context->OnEvent(Tegra::DebugContext::Event::MaxwellCommandProcessed, nullptr); } @@ -194,8 +193,8 @@ void Maxwell3D::ProcessQueryGet() { // wait queues. LongQueryResult query_result{}; query_result.value = result; - // TODO(Subv): Generate a real GPU timestamp and write it here instead of 0 - query_result.timestamp = 0; + // TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming + query_result.timestamp = CoreTiming::GetTicks(); Memory::WriteBlock(*address, &query_result, sizeof(query_result)); } break; @@ -249,8 +248,8 @@ void Maxwell3D::DrawArrays() { void Maxwell3D::ProcessCBBind(Regs::ShaderStage stage) { // Bind the buffer currently in CB_ADDRESS to the specified index in the desired shader stage. - auto& shader = state.shader_stages[static_cast<size_t>(stage)]; - auto& bind_data = regs.cb_bind[static_cast<size_t>(stage)]; + auto& shader = state.shader_stages[static_cast<std::size_t>(stage)]; + auto& bind_data = regs.cb_bind[static_cast<std::size_t>(stage)]; auto& buffer = shader.const_buffers[bind_data.index]; @@ -292,10 +291,6 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { tic_entry.header_version == Texture::TICHeaderVersion::Pitch, "TIC versions other than BlockLinear or Pitch are unimplemented"); - ASSERT_MSG((tic_entry.texture_type == Texture::TextureType::Texture2D) || - (tic_entry.texture_type == Texture::TextureType::Texture2DNoMipmap), - "Texture types other than Texture2D are unimplemented"); - auto r_type = tic_entry.r_type.Value(); auto g_type = tic_entry.g_type.Value(); auto b_type = tic_entry.b_type.Value(); @@ -321,14 +316,14 @@ Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const { std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderStage stage) const { std::vector<Texture::FullTextureInfo> textures; - auto& fragment_shader = state.shader_stages[static_cast<size_t>(stage)]; + auto& fragment_shader = state.shader_stages[static_cast<std::size_t>(stage)]; auto& tex_info_buffer = fragment_shader.const_buffers[regs.tex_cb_index]; ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); GPUVAddr tex_info_buffer_end = tex_info_buffer.address + tex_info_buffer.size; // Offset into the texture constbuffer where the texture info begins. - static constexpr size_t TextureInfoOffset = 0x20; + static constexpr std::size_t TextureInfoOffset = 0x20; for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset; current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) { @@ -365,8 +360,9 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt return textures; } -Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage, size_t offset) const { - auto& shader = state.shader_stages[static_cast<size_t>(stage)]; +Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage, + std::size_t offset) const { + auto& shader = state.shader_stages[static_cast<std::size_t>(stage)]; auto& tex_info_buffer = shader.const_buffers[regs.tex_cb_index]; ASSERT(tex_info_buffer.enabled && tex_info_buffer.address != 0); diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 92bfda053..b81b0723d 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -34,17 +34,17 @@ public: /// Register structure of the Maxwell3D engine. /// TODO(Subv): This structure will need to be made bigger as more registers are discovered. struct Regs { - static constexpr size_t NUM_REGS = 0xE00; - - static constexpr size_t NumRenderTargets = 8; - static constexpr size_t NumViewports = 16; - static constexpr size_t NumCBData = 16; - static constexpr size_t NumVertexArrays = 32; - static constexpr size_t NumVertexAttributes = 32; - static constexpr size_t MaxShaderProgram = 6; - static constexpr size_t MaxShaderStage = 5; + static constexpr std::size_t NUM_REGS = 0xE00; + + static constexpr std::size_t NumRenderTargets = 8; + static constexpr std::size_t NumViewports = 16; + static constexpr std::size_t NumCBData = 16; + static constexpr std::size_t NumVertexArrays = 32; + static constexpr std::size_t NumVertexAttributes = 32; + static constexpr std::size_t MaxShaderProgram = 6; + static constexpr std::size_t MaxShaderStage = 5; // Maximum number of const buffers per shader stage. - static constexpr size_t MaxConstBuffers = 18; + static constexpr std::size_t MaxConstBuffers = 18; enum class QueryMode : u32 { Write = 0, @@ -127,6 +127,7 @@ public: BitField<21, 6, Size> size; BitField<27, 3, Type> type; BitField<31, 1, u32> bgra; + u32 hex; }; u32 ComponentCount() const { @@ -262,6 +263,10 @@ public: bool IsValid() const { return size != Size::Invalid; } + + bool operator<(const VertexAttribute& other) const { + return hex < other.hex; + } }; enum class PrimitiveTopology : u32 { @@ -438,9 +443,9 @@ public: } }; - bool IsShaderConfigEnabled(size_t index) const { + bool IsShaderConfigEnabled(std::size_t index) const { // The VertexB is always enabled. - if (index == static_cast<size_t>(Regs::ShaderProgram::VertexB)) { + if (index == static_cast<std::size_t>(Regs::ShaderProgram::VertexB)) { return true; } return shader_config[index].enable != 0; @@ -528,7 +533,11 @@ public: u32 stencil_back_mask; u32 stencil_back_func_mask; - INSERT_PADDING_WORDS(0x20); + INSERT_PADDING_WORDS(0x13); + + u32 rt_separate_frag_data; + + INSERT_PADDING_WORDS(0xC); struct { u32 address_high; @@ -545,14 +554,29 @@ public: INSERT_PADDING_WORDS(0x5B); - VertexAttribute vertex_attrib_format[NumVertexAttributes]; + std::array<VertexAttribute, NumVertexAttributes> vertex_attrib_format; INSERT_PADDING_WORDS(0xF); struct { union { BitField<0, 4, u32> count; + BitField<4, 3, u32> map_0; + BitField<7, 3, u32> map_1; + BitField<10, 3, u32> map_2; + BitField<13, 3, u32> map_3; + BitField<16, 3, u32> map_4; + BitField<19, 3, u32> map_5; + BitField<22, 3, u32> map_6; + BitField<25, 3, u32> map_7; }; + + u32 GetMap(std::size_t index) const { + const std::array<u32, NumRenderTargets> maps{map_0, map_1, map_2, map_3, + map_4, map_5, map_6, map_7}; + ASSERT(index < maps.size()); + return maps[index]; + } } rt_control; INSERT_PADDING_WORDS(0x2); @@ -901,7 +925,7 @@ public: std::vector<Texture::FullTextureInfo> GetStageTextures(Regs::ShaderStage stage) const; /// Returns the texture information for a specific texture in a specific shader stage. - Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, size_t offset) const; + Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const; private: VideoCore::RasterizerInterface& rasterizer; @@ -963,8 +987,9 @@ ASSERT_REG_POSITION(clear_stencil, 0x368); ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5); ASSERT_REG_POSITION(stencil_back_mask, 0x3D6); ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7); +ASSERT_REG_POSITION(rt_separate_frag_data, 0x3EB); ASSERT_REG_POSITION(zeta, 0x3F8); -ASSERT_REG_POSITION(vertex_attrib_format[0], 0x458); +ASSERT_REG_POSITION(vertex_attrib_format, 0x458); ASSERT_REG_POSITION(rt_control, 0x487); ASSERT_REG_POSITION(zeta_width, 0x48a); ASSERT_REG_POSITION(zeta_height, 0x48b); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 6e740713f..aa7481b8c 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -41,7 +41,6 @@ void MaxwellDMA::HandleCopy() { // TODO(Subv): Perform more research and implement all features of this engine. ASSERT(regs.exec.enable_swizzle == 0); - ASSERT(regs.exec.enable_2d == 1); ASSERT(regs.exec.query_mode == Regs::QueryMode::None); ASSERT(regs.exec.query_intr == Regs::QueryIntr::None); ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2); @@ -51,10 +50,19 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.dst_params.pos_y == 0); if (regs.exec.is_dst_linear == regs.exec.is_src_linear) { - Memory::CopyBlock(dest_cpu, source_cpu, regs.x_count * regs.y_count); + std::size_t copy_size = regs.x_count; + + // When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D + // buffer of length `x_count`, otherwise we copy a 2D buffer of size (x_count, y_count). + if (regs.exec.enable_2d) { + copy_size = copy_size * regs.y_count; + } + + Memory::CopyBlock(dest_cpu, source_cpu, copy_size); return; } + ASSERT(regs.exec.enable_2d == 1); u8* src_buffer = Memory::GetPointer(source_cpu); u8* dst_buffer = Memory::GetPointer(dest_cpu); diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index 7882f16e0..311ccb616 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -23,7 +23,7 @@ public: void WriteReg(u32 method, u32 value); struct Regs { - static constexpr size_t NUM_REGS = 0x1D6; + static constexpr std::size_t NUM_REGS = 0x1D6; struct Parameters { union { diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 3e4efbe0c..7e1de0fa1 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -20,10 +20,10 @@ namespace Tegra::Shader { struct Register { /// Number of registers - static constexpr size_t NumRegisters = 256; + static constexpr std::size_t NumRegisters = 256; /// Register 255 is special cased to always be 0 - static constexpr size_t ZeroIndex = 255; + static constexpr std::size_t ZeroIndex = 255; enum class Size : u64 { Byte = 0, @@ -67,6 +67,13 @@ private: u64 value{}; }; +enum class AttributeSize : u64 { + Word = 0, + DoubleWord = 1, + TripleWord = 2, + QuadWord = 3, +}; + union Attribute { Attribute() = default; @@ -76,6 +83,7 @@ union Attribute { Position = 7, Attribute_0 = 8, Attribute_31 = 39, + PointCoord = 46, // This attribute contains a tuple of (~, ~, InstanceId, VertexId) when inside a vertex // shader, and a tuple of (TessCoord.x, TessCoord.y, TessCoord.z, ~) when inside a Tess Eval // shader. @@ -86,9 +94,10 @@ union Attribute { }; union { + BitField<20, 10, u64> immediate; BitField<22, 2, u64> element; BitField<24, 6, Index> index; - BitField<47, 3, u64> size; + BitField<47, 3, AttributeSize> size; } fmt20; union { @@ -231,6 +240,41 @@ enum class FlowCondition : u64 { Fcsm_Tr = 0x1C, // TODO(bunnei): What is this used for? }; +enum class ControlCode : u64 { + F = 0, + LT = 1, + EQ = 2, + LE = 3, + GT = 4, + NE = 5, + GE = 6, + Num = 7, + Nan = 8, + LTU = 9, + EQU = 10, + LEU = 11, + GTU = 12, + NEU = 13, + GEU = 14, + // + OFF = 16, + LO = 17, + SFF = 18, + LS = 19, + HI = 20, + SFT = 21, + HS = 22, + OFT = 23, + CSM_TA = 24, + CSM_TR = 25, + CSM_MX = 26, + FCSM_TA = 27, + FCSM_TR = 28, + FCSM_MX = 29, + RLE = 30, + RGT = 31, +}; + enum class PredicateResultMode : u64 { None = 0x0, NotZero = 0x3, @@ -243,7 +287,47 @@ enum class TextureType : u64 { TextureCube = 3, }; -enum class IpaMode : u64 { Pass = 0, None = 1, Constant = 2, Sc = 3 }; +enum class TextureQueryType : u64 { + Dimension = 1, + TextureType = 2, + SamplePosition = 5, + Filter = 16, + LevelOfDetail = 18, + Wrap = 20, + BorderColor = 22, +}; + +enum class TextureProcessMode : u64 { + None = 0, + LZ = 1, // Unknown, appears to be the same as none. + LB = 2, // Load Bias. + LL = 3, // Load LOD (LevelOfDetail) + LBA = 6, // Load Bias. The A is unknown, does not appear to differ with LB + LLA = 7 // Load LOD. The A is unknown, does not appear to differ with LL +}; + +enum class TextureMiscMode : u64 { + DC, + AOFFI, // Uses Offset + NDV, + NODEP, + MZ, + PTP, +}; + +enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, Flat = 2, Sc = 3 }; +enum class IpaSampleMode : u64 { Default = 0, Centroid = 1, Offset = 2 }; + +struct IpaMode { + IpaInterpMode interpolation_mode; + IpaSampleMode sampling_mode; + inline bool operator==(const IpaMode& a) { + return (a.interpolation_mode == interpolation_mode) && (a.sampling_mode == sampling_mode); + } + inline bool operator!=(const IpaMode& a) { + return !((*this) == a); + } +}; union Instruction { Instruction& operator=(const Instruction& instr) { @@ -328,10 +412,16 @@ union Instruction { } alu; union { - BitField<54, 3, IpaMode> mode; + BitField<51, 1, u64> saturate; + BitField<52, 2, IpaSampleMode> sample_mode; + BitField<54, 2, IpaInterpMode> interp_mode; } ipa; union { + BitField<39, 2, u64> tab5cb8_2; + BitField<41, 3, u64> tab5c68_1; + BitField<44, 2, u64> tab5c68_0; + BitField<47, 1, u64> cc; BitField<48, 1, u64> negate_b; } fmul; @@ -395,12 +485,54 @@ union Instruction { } bfe; union { + BitField<48, 3, u64> pred48; + + union { + BitField<20, 20, u64> entry_a; + BitField<39, 5, u64> entry_b; + BitField<45, 1, u64> neg; + BitField<46, 1, u64> uses_cc; + } imm; + + union { + BitField<20, 14, u64> cb_index; + BitField<34, 5, u64> cb_offset; + BitField<56, 1, u64> neg; + BitField<57, 1, u64> uses_cc; + } hi; + + union { + BitField<20, 14, u64> cb_index; + BitField<34, 5, u64> cb_offset; + BitField<39, 5, u64> entry_a; + BitField<45, 1, u64> neg; + BitField<46, 1, u64> uses_cc; + } rz; + + union { + BitField<39, 5, u64> entry_a; + BitField<45, 1, u64> neg; + BitField<46, 1, u64> uses_cc; + } r1; + + union { + BitField<28, 8, u64> entry_a; + BitField<37, 1, u64> neg; + BitField<38, 1, u64> uses_cc; + } r2; + + } lea; + + union { BitField<0, 5, FlowCondition> cond; } flow; union { + BitField<47, 1, u64> cc; BitField<48, 1, u64> negate_b; BitField<49, 1, u64> negate_c; + BitField<51, 2, u64> tab5980_1; + BitField<53, 2, u64> tab5980_0; } ffma; union { @@ -446,6 +578,27 @@ union Instruction { } psetp; union { + BitField<12, 3, u64> pred12; + BitField<15, 1, u64> neg_pred12; + BitField<24, 2, PredOperation> cond; + BitField<29, 3, u64> pred29; + BitField<32, 1, u64> neg_pred29; + BitField<39, 3, u64> pred39; + BitField<42, 1, u64> neg_pred39; + BitField<44, 1, u64> bf; + BitField<45, 2, PredOperation> op; + } pset; + + union { + BitField<0, 3, u64> pred0; + BitField<3, 3, u64> pred3; + BitField<8, 5, ControlCode> cc; // flag in cc + BitField<39, 3, u64> pred39; + BitField<42, 1, u64> neg_pred39; + BitField<45, 4, PredOperation> op; // op with pred39 + } csetp; + + union { BitField<39, 3, u64> pred39; BitField<42, 1, u64> neg_pred; BitField<43, 1, u64> neg_a; @@ -490,25 +643,127 @@ union Instruction { BitField<28, 1, u64> array; BitField<29, 2, TextureType> texture_type; BitField<31, 4, u64> component_mask; + BitField<49, 1, u64> nodep_flag; + BitField<50, 1, u64> dc_flag; + BitField<54, 1, u64> aoffi_flag; + BitField<55, 3, TextureProcessMode> process_mode; - bool IsComponentEnabled(size_t component) const { + bool IsComponentEnabled(std::size_t component) const { return ((1ull << component) & component_mask) != 0; } + + TextureProcessMode GetTextureProcessMode() const { + return process_mode; + } + + bool UsesMiscMode(TextureMiscMode mode) const { + switch (mode) { + case TextureMiscMode::DC: + return dc_flag != 0; + case TextureMiscMode::NODEP: + return nodep_flag != 0; + case TextureMiscMode::AOFFI: + return aoffi_flag != 0; + default: + break; + } + return false; + } } tex; union { + BitField<22, 6, TextureQueryType> query_type; + BitField<31, 4, u64> component_mask; + BitField<49, 1, u64> nodep_flag; + + bool UsesMiscMode(TextureMiscMode mode) const { + switch (mode) { + case TextureMiscMode::NODEP: + return nodep_flag != 0; + default: + break; + } + return false; + } + } txq; + + union { BitField<28, 1, u64> array; BitField<29, 2, TextureType> texture_type; + BitField<31, 4, u64> component_mask; + BitField<35, 1, u64> ndv_flag; + BitField<49, 1, u64> nodep_flag; + + bool IsComponentEnabled(std::size_t component) const { + return ((1ull << component) & component_mask) != 0; + } + + bool UsesMiscMode(TextureMiscMode mode) const { + switch (mode) { + case TextureMiscMode::NDV: + return (ndv_flag != 0); + case TextureMiscMode::NODEP: + return (nodep_flag != 0); + default: + break; + } + return false; + } + } tmml; + + union { + BitField<28, 1, u64> array; + BitField<29, 2, TextureType> texture_type; + BitField<35, 1, u64> ndv_flag; + BitField<49, 1, u64> nodep_flag; + BitField<50, 1, u64> dc_flag; + BitField<54, 2, u64> info; BitField<56, 2, u64> component; + + bool UsesMiscMode(TextureMiscMode mode) const { + switch (mode) { + case TextureMiscMode::NDV: + return ndv_flag != 0; + case TextureMiscMode::NODEP: + return nodep_flag != 0; + case TextureMiscMode::DC: + return dc_flag != 0; + case TextureMiscMode::AOFFI: + return info == 1; + case TextureMiscMode::PTP: + return info == 2; + default: + break; + } + return false; + } } tld4; union { + BitField<49, 1, u64> nodep_flag; + BitField<50, 1, u64> dc_flag; + BitField<51, 1, u64> aoffi_flag; BitField<52, 2, u64> component; + + bool UsesMiscMode(TextureMiscMode mode) const { + switch (mode) { + case TextureMiscMode::DC: + return dc_flag != 0; + case TextureMiscMode::NODEP: + return nodep_flag != 0; + case TextureMiscMode::AOFFI: + return aoffi_flag != 0; + default: + break; + } + return false; + } } tld4s; union { BitField<0, 8, Register> gpr0; BitField<28, 8, Register> gpr28; + BitField<49, 1, u64> nodep_flag; BitField<50, 3, u64> component_mask_selector; BitField<53, 4, u64> texture_info; @@ -528,6 +783,37 @@ union Instruction { UNREACHABLE(); } + TextureProcessMode GetTextureProcessMode() const { + switch (texture_info) { + case 0: + case 2: + case 6: + case 8: + case 9: + case 11: + return TextureProcessMode::LZ; + case 3: + case 5: + case 13: + return TextureProcessMode::LL; + default: + break; + } + return TextureProcessMode::None; + } + + bool UsesMiscMode(TextureMiscMode mode) const { + switch (mode) { + case TextureMiscMode::DC: + return (texture_info >= 4 && texture_info <= 6) || texture_info == 9; + case TextureMiscMode::NODEP: + return nodep_flag != 0; + default: + break; + } + return false; + } + bool IsArrayTexture() const { // TEXS only supports Texture2D arrays. return texture_info >= 7 && texture_info <= 9; @@ -537,7 +823,7 @@ union Instruction { return gpr28.Value() != Register::ZeroIndex; } - bool IsComponentEnabled(size_t component) const { + bool IsComponentEnabled(std::size_t component) const { static constexpr std::array<std::array<u32, 8>, 4> mask_lut{{ {}, {0x1, 0x2, 0x4, 0x8, 0x3, 0x9, 0xa, 0xc}, @@ -545,7 +831,7 @@ union Instruction { {0x7, 0xb, 0xd, 0xe, 0xf}, }}; - size_t index{gpr0.Value() != Register::ZeroIndex ? 1U : 0U}; + std::size_t index{gpr0.Value() != Register::ZeroIndex ? 1U : 0U}; index |= gpr28.Value() != Register::ZeroIndex ? 2 : 0; u32 mask = mask_lut[index][component_mask_selector]; @@ -556,6 +842,7 @@ union Instruction { } texs; union { + BitField<49, 1, u64> nodep_flag; BitField<53, 4, u64> texture_info; TextureType GetTextureType() const { @@ -576,6 +863,26 @@ union Instruction { UNREACHABLE(); } + TextureProcessMode GetTextureProcessMode() const { + if (texture_info == 1 || texture_info == 5 || texture_info == 12) + return TextureProcessMode::LL; + return TextureProcessMode::LZ; + } + + bool UsesMiscMode(TextureMiscMode mode) const { + switch (mode) { + case TextureMiscMode::AOFFI: + return texture_info == 12 || texture_info == 4; + case TextureMiscMode::MZ: + return texture_info == 5; + case TextureMiscMode::NODEP: + return nodep_flag != 0; + default: + break; + } + return false; + } + bool IsArrayTexture() const { // TEXS only supports Texture2D arrays. return texture_info == 8; @@ -618,6 +925,7 @@ union Instruction { BitField<36, 5, u64> index; } cbuf36; + BitField<47, 1, u64> generates_cc; BitField<61, 1, u64> is_b_imm; BitField<60, 1, u64> is_b_gpr; BitField<59, 1, u64> is_c_gpr; @@ -647,11 +955,13 @@ public: LDG, // Load from global memory STG, // Store in global memory TEX, - TEXQ, // Texture Query - TEXS, // Texture Fetch with scalar/non-vec4 source/destinations - TLDS, // Texture Load with scalar/non-vec4 source/destinations - TLD4, // Texture Load 4 - TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations + TXQ, // Texture Query + TEXS, // Texture Fetch with scalar/non-vec4 source/destinations + TLDS, // Texture Load with scalar/non-vec4 source/destinations + TLD4, // Texture Load 4 + TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations + TMML_B, // Texture Mip Map Level + TMML, // Texture Mip Map Level EXIT, IPA, FFMA_IMM, // Fused Multiply and Add @@ -676,6 +986,11 @@ public: ISCADD_C, // Scale and Add ISCADD_R, ISCADD_IMM, + LEA_R1, + LEA_R2, + LEA_RZ, + LEA_IMM, + LEA_HI, POPC_C, POPC_R, POPC_IMM, @@ -734,6 +1049,8 @@ public: ISET_C, ISET_IMM, PSETP, + PSET, + CSETP, XMAD_IMM, XMAD_CR, XMAD_RC, @@ -757,6 +1074,7 @@ public: IntegerSet, IntegerSetPredicate, PredicateSetPredicate, + PredicateSetRegister, Conversion, Xmad, Unknown, @@ -821,7 +1139,7 @@ public: private: struct Detail { private: - static constexpr size_t opcode_bitsize = 16; + static constexpr std::size_t opcode_bitsize = 16; /** * Generates the mask and the expected value after masking from a given bitstring. @@ -830,8 +1148,8 @@ private: */ static auto GetMaskAndExpect(const char* const bitstring) { u16 mask = 0, expect = 0; - for (size_t i = 0; i < opcode_bitsize; i++) { - const size_t bit_position = opcode_bitsize - i - 1; + for (std::size_t i = 0; i < opcode_bitsize; i++) { + const std::size_t bit_position = opcode_bitsize - i - 1; switch (bitstring[i]) { case '0': mask |= 1 << bit_position; @@ -871,11 +1189,13 @@ private: INST("1110111011010---", Id::LDG, Type::Memory, "LDG"), INST("1110111011011---", Id::STG, Type::Memory, "STG"), INST("110000----111---", Id::TEX, Type::Memory, "TEX"), - INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"), + INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"), INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"), INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"), INST("110010----111---", Id::TLD4, Type::Memory, "TLD4"), INST("1101111100------", Id::TLD4S, Type::Memory, "TLD4S"), + INST("110111110110----", Id::TMML_B, Type::Memory, "TMML_B"), + INST("1101111101011---", Id::TMML, Type::Memory, "TMML"), INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), @@ -906,6 +1226,11 @@ private: INST("0100110010100---", Id::SEL_C, Type::ArithmeticInteger, "SEL_C"), INST("0101110010100---", Id::SEL_R, Type::ArithmeticInteger, "SEL_R"), INST("0011100-10100---", Id::SEL_IMM, Type::ArithmeticInteger, "SEL_IMM"), + INST("0101101111011---", Id::LEA_R2, Type::ArithmeticInteger, "LEA_R2"), + INST("0101101111010---", Id::LEA_R1, Type::ArithmeticInteger, "LEA_R1"), + INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"), + INST("010010111101----", Id::LEA_RZ, Type::ArithmeticInteger, "LEA_RZ"), + INST("00011000--------", Id::LEA_HI, Type::ArithmeticInteger, "LEA_HI"), INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"), INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"), INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"), @@ -960,7 +1285,9 @@ private: INST("010110110101----", Id::ISET_R, Type::IntegerSet, "ISET_R"), INST("010010110101----", Id::ISET_C, Type::IntegerSet, "ISET_C"), INST("0011011-0101----", Id::ISET_IMM, Type::IntegerSet, "ISET_IMM"), + INST("0101000010001---", Id::PSET, Type::PredicateSetRegister, "PSET"), INST("0101000010010---", Id::PSETP, Type::PredicateSetPredicate, "PSETP"), + INST("010100001010----", Id::CSETP, Type::PredicateSetPredicate, "CSETP"), INST("0011011-00------", Id::XMAD_IMM, Type::Xmad, "XMAD_IMM"), INST("0100111---------", Id::XMAD_CR, Type::Xmad, "XMAD_CR"), INST("010100010-------", Id::XMAD_RC, Type::Xmad, "XMAD_RC"), diff --git a/src/video_core/engines/shader_header.h b/src/video_core/engines/shader_header.h new file mode 100644 index 000000000..a885ee3cf --- /dev/null +++ b/src/video_core/engines/shader_header.h @@ -0,0 +1,103 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/bit_field.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +namespace Tegra::Shader { + +enum class OutputTopology : u32 { + PointList = 1, + LineStrip = 6, + TriangleStrip = 7, +}; + +// Documentation in: +// http://download.nvidia.com/open-gpu-doc/Shader-Program-Header/1/Shader-Program-Header.html#ImapTexture +struct Header { + union { + BitField<0, 5, u32> sph_type; + BitField<5, 5, u32> version; + BitField<10, 4, u32> shader_type; + BitField<14, 1, u32> mrt_enable; + BitField<15, 1, u32> kills_pixels; + BitField<16, 1, u32> does_global_store; + BitField<17, 4, u32> sass_version; + BitField<21, 5, u32> reserved; + BitField<26, 1, u32> does_load_or_store; + BitField<27, 1, u32> does_fp64; + BitField<28, 4, u32> stream_out_mask; + } common0; + + union { + BitField<0, 24, u32> shader_local_memory_low_size; + BitField<24, 8, u32> per_patch_attribute_count; + } common1; + + union { + BitField<0, 24, u32> shader_local_memory_high_size; + BitField<24, 8, u32> threads_per_input_primitive; + } common2; + + union { + BitField<0, 24, u32> shader_local_memory_crs_size; + BitField<24, 4, OutputTopology> output_topology; + BitField<28, 4, u32> reserved; + } common3; + + union { + BitField<0, 12, u32> max_output_vertices; + BitField<12, 8, u32> store_req_start; // NOTE: not used by geometry shaders. + BitField<24, 4, u32> reserved; + BitField<12, 8, u32> store_req_end; // NOTE: not used by geometry shaders. + } common4; + + union { + struct { + INSERT_PADDING_BYTES(3); // ImapSystemValuesA + INSERT_PADDING_BYTES(1); // ImapSystemValuesB + INSERT_PADDING_BYTES(16); // ImapGenericVector[32] + INSERT_PADDING_BYTES(2); // ImapColor + INSERT_PADDING_BYTES(2); // ImapSystemValuesC + INSERT_PADDING_BYTES(5); // ImapFixedFncTexture[10] + INSERT_PADDING_BYTES(1); // ImapReserved + INSERT_PADDING_BYTES(3); // OmapSystemValuesA + INSERT_PADDING_BYTES(1); // OmapSystemValuesB + INSERT_PADDING_BYTES(16); // OmapGenericVector[32] + INSERT_PADDING_BYTES(2); // OmapColor + INSERT_PADDING_BYTES(2); // OmapSystemValuesC + INSERT_PADDING_BYTES(5); // OmapFixedFncTexture[10] + INSERT_PADDING_BYTES(1); // OmapReserved + } vtg; + + struct { + INSERT_PADDING_BYTES(3); // ImapSystemValuesA + INSERT_PADDING_BYTES(1); // ImapSystemValuesB + INSERT_PADDING_BYTES(32); // ImapGenericVector[32] + INSERT_PADDING_BYTES(2); // ImapColor + INSERT_PADDING_BYTES(2); // ImapSystemValuesC + INSERT_PADDING_BYTES(10); // ImapFixedFncTexture[10] + INSERT_PADDING_BYTES(2); // ImapReserved + struct { + u32 target; + union { + BitField<0, 1, u32> sample_mask; + BitField<1, 1, u32> depth; + BitField<2, 30, u32> reserved; + }; + } omap; + bool IsColorComponentOutputEnabled(u32 render_target, u32 component) const { + const u32 bit = render_target * 4 + component; + return omap.target & (1 << bit); + } + } ps; + }; +}; + +static_assert(sizeof(Header) == 0x50, "Incorrect structure size"); + +} // namespace Tegra::Shader diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index e6d8e65c6..baa8b63b7 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -4,6 +4,7 @@ #include "common/assert.h" #include "video_core/engines/fermi_2d.h" +#include "video_core/engines/kepler_memory.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/engines/maxwell_compute.h" #include "video_core/engines/maxwell_dma.h" @@ -27,6 +28,7 @@ GPU::GPU(VideoCore::RasterizerInterface& rasterizer) { fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager); maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager); + kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager); } GPU::~GPU() = default; @@ -66,6 +68,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { case RenderTargetFormat::RGBA8_UINT: case RenderTargetFormat::RGB10_A2_UNORM: case RenderTargetFormat::BGRA8_UNORM: + case RenderTargetFormat::BGRA8_SRGB: case RenderTargetFormat::RG16_UNORM: case RenderTargetFormat::RG16_SNORM: case RenderTargetFormat::RG16_UINT: diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 2c3dbd97b..5cc1e19ca 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -4,8 +4,9 @@ #pragma once +#include <array> #include <memory> -#include <unordered_map> +#include <vector> #include "common/common_types.h" #include "core/hle/service/nvflinger/buffer_queue.h" #include "video_core/memory_manager.h" @@ -26,6 +27,7 @@ enum class RenderTargetFormat : u32 { RG32_FLOAT = 0xCB, RG32_UINT = 0xCD, BGRA8_UNORM = 0xCF, + BGRA8_SRGB = 0xD0, RGB10_A2_UNORM = 0xD1, RGBA8_UNORM = 0xD5, RGBA8_SRGB = 0xD6, @@ -40,6 +42,7 @@ enum class RenderTargetFormat : u32 { R32_UINT = 0xE4, R32_FLOAT = 0xE5, B5G6R5_UNORM = 0xE8, + BGR5A1_UNORM = 0xE9, RG8_UNORM = 0xEA, RG8_SNORM = 0xEB, R16_UNORM = 0xEE, @@ -67,6 +70,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format); /// Returns the number of bytes per pixel of each depth format. u32 DepthFormatBytesPerPixel(DepthFormat format); +struct CommandListHeader; class DebugContext; /** @@ -99,6 +103,7 @@ class Fermi2D; class Maxwell3D; class MaxwellCompute; class MaxwellDMA; +class KeplerMemory; } // namespace Engines enum class EngineID { @@ -115,7 +120,7 @@ public: ~GPU(); /// Processes a command list stored at the specified address in GPU memory. - void ProcessCommandList(GPUVAddr address, u32 size); + void ProcessCommandLists(const std::vector<CommandListHeader>& commands); /// Returns a reference to the Maxwell3D GPU engine. Engines::Maxwell3D& Maxwell3D(); @@ -130,13 +135,10 @@ public: const Tegra::MemoryManager& MemoryManager() const; private: - /// Writes a single register in the engine bound to the specified subchannel - void WriteReg(u32 method, u32 subchannel, u32 value, u32 remaining_params); - std::unique_ptr<Tegra::MemoryManager> memory_manager; /// Mapping of command subchannels to their bound engine ids. - std::unordered_map<u32, EngineID> bound_engines; + std::array<EngineID, 8> bound_engines = {}; /// 3D engine std::unique_ptr<Engines::Maxwell3D> maxwell_3d; @@ -146,6 +148,8 @@ private: std::unique_ptr<Engines::MaxwellCompute> maxwell_compute; /// DMA engine std::unique_ptr<Engines::MaxwellDMA> maxwell_dma; + /// Inline memory engine + std::unique_ptr<Engines::KeplerMemory> kepler_memory; }; } // namespace Tegra diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h index 7d836b816..cee0baaf3 100644 --- a/src/video_core/macro_interpreter.h +++ b/src/video_core/macro_interpreter.h @@ -152,7 +152,7 @@ private: boost::optional<u32> delayed_pc; ///< Program counter to execute at after the delay slot is executed. - static constexpr size_t NumMacroRegisters = 8; + static constexpr std::size_t NumMacroRegisters = 8; /// General purpose macro registers. std::array<u32, NumMacroRegisters> registers = {}; diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 9d78e8b6b..cd819d69f 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -20,9 +20,6 @@ public: /// Clear the current framebuffer virtual void Clear() = 0; - /// Notify rasterizer that the specified Maxwell register has been changed - virtual void NotifyMaxwellRegisterChanged(u32 method) = 0; - /// Notify rasterizer that all caches should be flushed to Switch memory virtual void FlushAll() = 0; diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index be17a2b9c..0df3725c2 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -19,6 +19,7 @@ void RendererBase::RefreshBaseSettings() { UpdateCurrentFramebufferLayout(); renderer_settings.use_framelimiter = Settings::values.use_frame_limit; + renderer_settings.set_background_color = true; } void RendererBase::UpdateCurrentFramebufferLayout() { diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index 2a357f9d0..2cd0738ff 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -19,6 +19,7 @@ namespace VideoCore { struct RendererSettings { std::atomic_bool use_framelimiter{false}; + std::atomic_bool set_background_color{false}; }; class RendererBase : NonCopyable { diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp new file mode 100644 index 000000000..578aca789 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp @@ -0,0 +1,93 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include <memory> + +#include "common/alignment.h" +#include "core/core.h" +#include "core/memory.h" +#include "video_core/renderer_opengl/gl_buffer_cache.h" + +namespace OpenGL { + +OGLBufferCache::OGLBufferCache(std::size_t size) : stream_buffer(GL_ARRAY_BUFFER, size) {} + +GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, + std::size_t alignment, bool cache) { + auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager(); + const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)}; + + // Cache management is a big overhead, so only cache entries with a given size. + // TODO: Figure out which size is the best for given games. + cache &= size >= 2048; + + if (cache) { + auto entry = TryGet(*cpu_addr); + if (entry) { + if (entry->size >= size && entry->alignment == alignment) { + return entry->offset; + } + Unregister(entry); + } + } + + AlignBuffer(alignment); + GLintptr uploaded_offset = buffer_offset; + + Memory::ReadBlock(*cpu_addr, buffer_ptr, size); + + buffer_ptr += size; + buffer_offset += size; + + if (cache) { + auto entry = std::make_shared<CachedBufferEntry>(); + entry->offset = uploaded_offset; + entry->size = size; + entry->alignment = alignment; + entry->addr = *cpu_addr; + Register(entry); + } + + return uploaded_offset; +} + +GLintptr OGLBufferCache::UploadHostMemory(const void* raw_pointer, std::size_t size, + std::size_t alignment) { + AlignBuffer(alignment); + std::memcpy(buffer_ptr, raw_pointer, size); + GLintptr uploaded_offset = buffer_offset; + + buffer_ptr += size; + buffer_offset += size; + return uploaded_offset; +} + +void OGLBufferCache::Map(std::size_t max_size) { + bool invalidate; + std::tie(buffer_ptr, buffer_offset_base, invalidate) = + stream_buffer.Map(static_cast<GLsizeiptr>(max_size), 4); + buffer_offset = buffer_offset_base; + + if (invalidate) { + InvalidateAll(); + } +} +void OGLBufferCache::Unmap() { + stream_buffer.Unmap(buffer_offset - buffer_offset_base); +} + +GLuint OGLBufferCache::GetHandle() const { + return stream_buffer.GetHandle(); +} + +void OGLBufferCache::AlignBuffer(std::size_t alignment) { + // Align the offset, not the mapped pointer + GLintptr offset_aligned = + static_cast<GLintptr>(Common::AlignUp(static_cast<std::size_t>(buffer_offset), alignment)); + buffer_ptr += offset_aligned - buffer_offset; + buffer_offset = offset_aligned; +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h new file mode 100644 index 000000000..6c18461f4 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -0,0 +1,57 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <cstddef> +#include <memory> + +#include "common/common_types.h" +#include "video_core/rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_stream_buffer.h" + +namespace OpenGL { + +struct CachedBufferEntry final { + VAddr GetAddr() const { + return addr; + } + + std::size_t GetSizeInBytes() const { + return size; + } + + VAddr addr; + std::size_t size; + GLintptr offset; + std::size_t alignment; +}; + +class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> { +public: + explicit OGLBufferCache(std::size_t size); + + GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4, + bool cache = true); + + GLintptr UploadHostMemory(const void* raw_pointer, std::size_t size, std::size_t alignment = 4); + + void Map(std::size_t max_size); + void Unmap(); + + GLuint GetHandle() const; + +protected: + void AlignBuffer(std::size_t alignment); + +private: + OGLStreamBuffer stream_buffer; + + u8* buffer_ptr = nullptr; + GLintptr buffer_offset = 0; + GLintptr buffer_offset_base = 0; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 7ce969f73..70fb54507 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <memory> #include <string> #include <string_view> @@ -33,16 +34,19 @@ using PixelFormat = SurfaceParams::PixelFormat; using SurfaceType = SurfaceParams::SurfaceType; MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_FS, "OpenGL", "Fragment Shader Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Shader, "OpenGL", "Shader Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_UBO, "OpenGL", "Const Buffer Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Index, "OpenGL", "Index Buffer Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Texture, "OpenGL", "Texture Setup", MP_RGB(128, 128, 192)); +MICROPROFILE_DEFINE(OpenGL_Framebuffer, "OpenGL", "Framebuffer Setup", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192)); -MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255)); +MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100)); RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info) - : emu_window{window}, screen_info{info}, stream_buffer(GL_ARRAY_BUFFER, STREAM_BUFFER_SIZE) { + : emu_window{window}, screen_info{info}, buffer_cache(STREAM_BUFFER_SIZE) { // Create sampler objects - for (size_t i = 0; i < texture_samplers.size(); ++i) { + for (std::size_t i = 0; i < texture_samplers.size(); ++i) { texture_samplers[i].Create(); state.texture_units[i].sampler = texture_samplers[i].sampler.handle; } @@ -55,6 +59,8 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo if (extension == "GL_ARB_direct_state_access") { has_ARB_direct_state_access = true; + } else if (extension == "GL_ARB_multi_bind") { + has_ARB_multi_bind = true; } else if (extension == "GL_ARB_separate_shader_objects") { has_ARB_separate_shader_objects = true; } else if (extension == "GL_ARB_vertex_attrib_binding") { @@ -67,28 +73,13 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0 state.clip_distance[0] = true; - // Generate VAO and UBO - sw_vao.Create(); - uniform_buffer.Create(); - - state.draw.vertex_array = sw_vao.handle; - state.draw.uniform_buffer = uniform_buffer.handle; - state.Apply(); - // Create render framebuffer framebuffer.Create(); - hw_vao.Create(); - - state.draw.vertex_buffer = stream_buffer.GetHandle(); - shader_program_manager = std::make_unique<GLShader::ProgramManager>(); state.draw.shader_program = 0; - state.draw.vertex_array = hw_vao.handle; state.Apply(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, stream_buffer.GetHandle()); - glEnable(GL_BLEND); glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment); @@ -98,14 +89,60 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo RasterizerOpenGL::~RasterizerOpenGL() {} -std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, - GLintptr buffer_offset) { +void RasterizerOpenGL::SetupVertexArrays() { MICROPROFILE_SCOPE(OpenGL_VAO); const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); const auto& regs = gpu.regs; - state.draw.vertex_array = hw_vao.handle; - state.draw.vertex_buffer = stream_buffer.GetHandle(); + auto [iter, is_cache_miss] = vertex_array_cache.try_emplace(regs.vertex_attrib_format); + auto& VAO = iter->second; + + if (is_cache_miss) { + VAO.Create(); + state.draw.vertex_array = VAO.handle; + state.Apply(); + + // The index buffer binding is stored within the VAO. Stupid OpenGL, but easy to work + // around. + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer_cache.GetHandle()); + + // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. + // Enables the first 16 vertex attributes always, as we don't know which ones are actually + // used until shader time. Note, Tegra technically supports 32, but we're capping this to 16 + // for now to avoid OpenGL errors. + // TODO(Subv): Analyze the shader to identify which attributes are actually used and don't + // assume every shader uses them all. + for (unsigned index = 0; index < 16; ++index) { + const auto& attrib = regs.vertex_attrib_format[index]; + + // Ignore invalid attributes. + if (!attrib.IsValid()) + continue; + + const auto& buffer = regs.vertex_array[attrib.buffer]; + LOG_TRACE(HW_GPU, + "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}", + index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(), + attrib.offset.Value(), attrib.IsNormalized()); + + ASSERT(buffer.IsEnabled()); + + glEnableVertexAttribArray(index); + if (attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::SignedInt || + attrib.type == + Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::UnsignedInt) { + glVertexAttribIFormat(index, attrib.ComponentCount(), + MaxwellToGL::VertexType(attrib), attrib.offset); + } else { + glVertexAttribFormat(index, attrib.ComponentCount(), + MaxwellToGL::VertexType(attrib), + attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset); + } + glVertexAttribBinding(index, attrib.buffer); + } + } + state.draw.vertex_array = VAO.handle; + state.draw.vertex_buffer = buffer_cache.GetHandle(); state.Apply(); // Upload all guest vertex arrays sequentially to our buffer @@ -117,77 +154,35 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupVertexArrays(u8* array_ptr, Tegra::GPUVAddr start = vertex_array.StartAddress(); const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress(); - if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { - start += vertex_array.stride * (gpu.state.current_instance / vertex_array.divisor); - } - ASSERT(end > start); - u64 size = end - start + 1; - - GLintptr vertex_buffer_offset; - std::tie(array_ptr, buffer_offset, vertex_buffer_offset) = - UploadMemory(array_ptr, buffer_offset, start, size); + const u64 size = end - start + 1; + const GLintptr vertex_buffer_offset = buffer_cache.UploadMemory(start, size); // Bind the vertex array to the buffer at the current offset. - glBindVertexBuffer(index, stream_buffer.GetHandle(), vertex_buffer_offset, + glBindVertexBuffer(index, buffer_cache.GetHandle(), vertex_buffer_offset, vertex_array.stride); if (regs.instanced_arrays.IsInstancingEnabled(index) && vertex_array.divisor != 0) { - // Tell OpenGL that this is an instanced vertex buffer to prevent accessing different - // indexes on each vertex. We do the instance indexing manually by incrementing the - // start address of the vertex buffer. - glVertexBindingDivisor(index, 1); + // Enable vertex buffer instancing with the specified divisor. + glVertexBindingDivisor(index, vertex_array.divisor); } else { // Disable the vertex buffer instancing. glVertexBindingDivisor(index, 0); } } - - // Use the vertex array as-is, assumes that the data is formatted correctly for OpenGL. - // Enables the first 16 vertex attributes always, as we don't know which ones are actually used - // until shader time. Note, Tegra technically supports 32, but we're capping this to 16 for now - // to avoid OpenGL errors. - // TODO(Subv): Analyze the shader to identify which attributes are actually used and don't - // assume every shader uses them all. - for (unsigned index = 0; index < 16; ++index) { - auto& attrib = regs.vertex_attrib_format[index]; - - // Ignore invalid attributes. - if (!attrib.IsValid()) - continue; - - auto& buffer = regs.vertex_array[attrib.buffer]; - LOG_TRACE(HW_GPU, "vertex attrib {}, count={}, size={}, type={}, offset={}, normalize={}", - index, attrib.ComponentCount(), attrib.SizeString(), attrib.TypeString(), - attrib.offset.Value(), attrib.IsNormalized()); - - ASSERT(buffer.IsEnabled()); - - glEnableVertexAttribArray(index); - if (attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::SignedInt || - attrib.type == Tegra::Engines::Maxwell3D::Regs::VertexAttribute::Type::UnsignedInt) { - glVertexAttribIFormat(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib), - attrib.offset); - } else { - glVertexAttribFormat(index, attrib.ComponentCount(), MaxwellToGL::VertexType(attrib), - attrib.IsNormalized() ? GL_TRUE : GL_FALSE, attrib.offset); - } - glVertexAttribBinding(index, attrib.buffer); - } - - return {array_ptr, buffer_offset}; } -std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr buffer_offset) { - auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); +void RasterizerOpenGL::SetupShaders() { + MICROPROFILE_SCOPE(OpenGL_Shader); + const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); // Next available bindpoints to use when uploading the const buffers and textures to the GLSL // shaders. The constbuffer bindpoint starts after the shader stage configuration bind points. u32 current_constbuffer_bindpoint = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage; u32 current_texture_bindpoint = 0; - for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { - auto& shader_config = gpu.regs.shader_config[index]; + for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { + const auto& shader_config = gpu.regs.shader_config[index]; const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)}; // Skip stages that are not enabled @@ -195,21 +190,15 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr continue; } - std::tie(buffer_ptr, buffer_offset) = - AlignBuffer(buffer_ptr, buffer_offset, static_cast<size_t>(uniform_buffer_alignment)); - - const size_t stage{index == 0 ? 0 : index - 1}; // Stage indices are 0 - 5 + const std::size_t stage{index == 0 ? 0 : index - 1}; // Stage indices are 0 - 5 GLShader::MaxwellUniformData ubo{}; ubo.SetFromRegs(gpu.state.shader_stages[stage]); - std::memcpy(buffer_ptr, &ubo, sizeof(ubo)); + const GLintptr offset = buffer_cache.UploadHostMemory( + &ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment)); // Bind the buffer - glBindBufferRange(GL_UNIFORM_BUFFER, stage, stream_buffer.GetHandle(), buffer_offset, - sizeof(ubo)); - - buffer_ptr += sizeof(ubo); - buffer_offset += sizeof(ubo); + glBindBufferRange(GL_UNIFORM_BUFFER, stage, buffer_cache.GetHandle(), offset, sizeof(ubo)); Shader shader{shader_cache.GetStageProgram(program)}; @@ -230,9 +219,8 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr } // Configure the const buffers for this shader stage. - std::tie(buffer_ptr, buffer_offset, current_constbuffer_bindpoint) = - SetupConstBuffers(buffer_ptr, buffer_offset, static_cast<Maxwell::ShaderStage>(stage), - shader, current_constbuffer_bindpoint); + current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), + shader, current_constbuffer_bindpoint); // Configure the textures for this shader stage. current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader, @@ -245,15 +233,15 @@ std::pair<u8*, GLintptr> RasterizerOpenGL::SetupShaders(u8* buffer_ptr, GLintptr } } - shader_program_manager->UseTrivialGeometryShader(); + state.Apply(); - return {buffer_ptr, buffer_offset}; + shader_program_manager->UseTrivialGeometryShader(); } -size_t RasterizerOpenGL::CalculateVertexArraysSize() const { +std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - size_t size = 0; + std::size_t size = 0; for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) { if (!regs.vertex_array[index].IsEnabled()) continue; @@ -309,60 +297,80 @@ void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { cached_pages.add({pages_interval, delta}); } -std::pair<Surface, Surface> RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, - bool using_depth_fb, - bool preserve_contents) { +void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb, + bool preserve_contents, + boost::optional<std::size_t> single_color_target) { + MICROPROFILE_SCOPE(OpenGL_Framebuffer); const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - if (regs.rt[0].format == Tegra::RenderTargetFormat::NONE) { - LOG_ERROR(HW_GPU, "RenderTargetFormat is not configured"); - using_color_fb = false; + Surface depth_surface; + if (using_depth_fb) { + depth_surface = res_cache.GetDepthBufferSurface(preserve_contents); } - const bool has_stencil = regs.stencil_enable; - const bool write_color_fb = - state.color_mask.red_enabled == GL_TRUE || state.color_mask.green_enabled == GL_TRUE || - state.color_mask.blue_enabled == GL_TRUE || state.color_mask.alpha_enabled == GL_TRUE; + // TODO(bunnei): Figure out how the below register works. According to envytools, this should be + // used to enable multiple render targets. However, it is left unset on all games that I have + // tested. + ASSERT_MSG(regs.rt_separate_frag_data == 0, "Unimplemented"); - const bool write_depth_fb = - (state.depth.test_enabled && state.depth.write_mask == GL_TRUE) || - (has_stencil && (state.stencil.front.write_mask || state.stencil.back.write_mask)); - - Surface color_surface; - Surface depth_surface; - MathUtil::Rectangle<u32> surfaces_rect; - std::tie(color_surface, depth_surface, surfaces_rect) = - res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, preserve_contents); + // Bind the framebuffer surfaces + state.draw.draw_framebuffer = framebuffer.handle; + state.Apply(); - const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()}; - const MathUtil::Rectangle<u32> draw_rect{ - static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left, - surfaces_rect.left, surfaces_rect.right)), // Left - static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top, - surfaces_rect.bottom, surfaces_rect.top)), // Top - static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.right, - surfaces_rect.left, surfaces_rect.right)), // Right - static_cast<u32>( - std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.bottom, - surfaces_rect.bottom, surfaces_rect.top))}; // Bottom + if (using_color_fb) { + if (single_color_target) { + // Used when just a single color attachment is enabled, e.g. for clearing a color buffer + Surface color_surface = + res_cache.GetColorBufferSurface(*single_color_target, preserve_contents); + glFramebufferTexture2D( + GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D, + color_surface != nullptr ? color_surface->Texture().handle : 0, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target)); + } else { + // Multiple color attachments are enabled + std::array<GLenum, Maxwell::NumRenderTargets> buffers; + for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { + Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents); + buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); + glFramebufferTexture2D( + GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), + GL_TEXTURE_2D, color_surface != nullptr ? color_surface->Texture().handle : 0, + 0); + } + glDrawBuffers(regs.rt_control.count, buffers.data()); + } + } else { + // No color attachments are enabled - zero out all of them + for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), GL_TEXTURE_2D, + 0, 0); + } + glDrawBuffer(GL_NONE); + } - // Bind the framebuffer surfaces - BindFramebufferSurfaces(color_surface, depth_surface, has_stencil); + if (depth_surface) { + if (regs.stencil_enable) { + // Attach both depth and stencil + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->Texture().handle, 0); + } else { + // Attach depth + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, + depth_surface->Texture().handle, 0); + // Clear stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + } + } else { + // Clear both depth and stencil attachment + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + } - SyncViewport(surfaces_rect); + SyncViewport(); - // Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect. Enable - // scissor test to prevent drawing outside of the framebuffer region - state.scissor.enabled = true; - state.scissor.x = draw_rect.left; - state.scissor.y = draw_rect.bottom; - state.scissor.width = draw_rect.GetWidth(); - state.scissor.height = draw_rect.GetHeight(); state.Apply(); - - // Only return the surface to be marked as dirty if writing to it is enabled. - return std::make_pair(write_color_fb ? color_surface : nullptr, - write_depth_fb ? depth_surface : nullptr); } void RasterizerOpenGL::Clear() { @@ -370,32 +378,24 @@ void RasterizerOpenGL::Clear() { SCOPE_EXIT({ prev_state.Apply(); }); const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - bool use_color_fb = false; - bool use_depth_fb = false; + bool use_color{}; + bool use_depth{}; + bool use_stencil{}; OpenGLState clear_state; - clear_state.draw.draw_framebuffer = state.draw.draw_framebuffer; + clear_state.draw.draw_framebuffer = framebuffer.handle; clear_state.color_mask.red_enabled = regs.clear_buffers.R ? GL_TRUE : GL_FALSE; clear_state.color_mask.green_enabled = regs.clear_buffers.G ? GL_TRUE : GL_FALSE; clear_state.color_mask.blue_enabled = regs.clear_buffers.B ? GL_TRUE : GL_FALSE; clear_state.color_mask.alpha_enabled = regs.clear_buffers.A ? GL_TRUE : GL_FALSE; - GLbitfield clear_mask{}; if (regs.clear_buffers.R || regs.clear_buffers.G || regs.clear_buffers.B || regs.clear_buffers.A) { - if (regs.clear_buffers.RT == 0) { - // We only support clearing the first color attachment for now - clear_mask |= GL_COLOR_BUFFER_BIT; - use_color_fb = true; - } else { - // TODO(subv): Add support for the other color attachments - LOG_CRITICAL(HW_GPU, "Clear unimplemented for RT {}", regs.clear_buffers.RT); - } + use_color = true; } if (regs.clear_buffers.Z) { ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear Z but buffer is not enabled!"); - use_depth_fb = true; - clear_mask |= GL_DEPTH_BUFFER_BIT; + use_depth = true; // Always enable the depth write when clearing the depth buffer. The depth write mask is // ignored when clearing the buffer in the Switch, but OpenGL obeys it so we set it to true. @@ -404,59 +404,33 @@ void RasterizerOpenGL::Clear() { } if (regs.clear_buffers.S) { ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); - use_depth_fb = true; - clear_mask |= GL_STENCIL_BUFFER_BIT; + use_stencil = true; clear_state.stencil.test_enabled = true; } - if (!use_color_fb && !use_depth_fb) { + if (!use_color && !use_depth && !use_stencil) { // No color surface nor depth/stencil surface are enabled return; } - if (clear_mask == 0) { - // No clear mask is enabled - return; - } - ScopeAcquireGLContext acquire_context{emu_window}; - auto [dirty_color_surface, dirty_depth_surface] = - ConfigureFramebuffers(use_color_fb, use_depth_fb, false); + ConfigureFramebuffers(use_color, use_depth || use_stencil, false, + regs.clear_buffers.RT.Value()); clear_state.Apply(); - glClearColor(regs.clear_color[0], regs.clear_color[1], regs.clear_color[2], - regs.clear_color[3]); - glClearDepth(regs.clear_depth); - glClearStencil(regs.clear_stencil); - - glClear(clear_mask); -} - -std::pair<u8*, GLintptr> RasterizerOpenGL::AlignBuffer(u8* buffer_ptr, GLintptr buffer_offset, - size_t alignment) { - // Align the offset, not the mapped pointer - GLintptr offset_aligned = - static_cast<GLintptr>(Common::AlignUp(static_cast<size_t>(buffer_offset), alignment)); - return {buffer_ptr + (offset_aligned - buffer_offset), offset_aligned}; -} - -std::tuple<u8*, GLintptr, GLintptr> RasterizerOpenGL::UploadMemory(u8* buffer_ptr, - GLintptr buffer_offset, - Tegra::GPUVAddr gpu_addr, - size_t size, size_t alignment) { - std::tie(buffer_ptr, buffer_offset) = AlignBuffer(buffer_ptr, buffer_offset, alignment); - GLintptr uploaded_offset = buffer_offset; - - auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager(); - const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)}; - Memory::ReadBlock(*cpu_addr, buffer_ptr, size); - - buffer_ptr += size; - buffer_offset += size; + if (use_color) { + glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color); + } - return {buffer_ptr, buffer_offset, uploaded_offset}; + if (use_depth && use_stencil) { + glClearBufferfi(GL_DEPTH_STENCIL, 0, regs.clear_depth, regs.clear_stencil); + } else if (use_depth) { + glClearBufferfv(GL_DEPTH, 0, ®s.clear_depth); + } else if (use_stencil) { + glClearBufferiv(GL_STENCIL, 0, ®s.clear_stencil); + } } void RasterizerOpenGL::DrawArrays() { @@ -464,12 +438,12 @@ void RasterizerOpenGL::DrawArrays() { return; MICROPROFILE_SCOPE(OpenGL_Drawing); - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + const auto& regs = gpu.regs; ScopeAcquireGLContext acquire_context{emu_window}; - auto [dirty_color_surface, dirty_depth_surface] = - ConfigureFramebuffers(true, regs.zeta.Address() != 0 && regs.zeta_enable != 0, true); + ConfigureFramebuffers(); SyncDepthTestState(); SyncStencilTestState(); @@ -482,43 +456,46 @@ void RasterizerOpenGL::DrawArrays() { // Draw the vertex batch const bool is_indexed = accelerate_draw == AccelDraw::Indexed; - const u64 index_buffer_size{regs.index_array.count * regs.index_array.FormatSizeInBytes()}; + const u64 index_buffer_size{static_cast<u64>(regs.index_array.count) * + static_cast<u64>(regs.index_array.FormatSizeInBytes())}; - state.draw.vertex_buffer = stream_buffer.GetHandle(); + state.draw.vertex_buffer = buffer_cache.GetHandle(); state.Apply(); - size_t buffer_size = CalculateVertexArraysSize(); + std::size_t buffer_size = CalculateVertexArraysSize(); if (is_indexed) { - buffer_size = Common::AlignUp<size_t>(buffer_size, 4) + index_buffer_size; + buffer_size = Common::AlignUp<std::size_t>(buffer_size, 4) + index_buffer_size; } // Uniform space for the 5 shader stages buffer_size = - Common::AlignUp<size_t>(buffer_size, 4) + + Common::AlignUp<std::size_t>(buffer_size, 4) + (sizeof(GLShader::MaxwellUniformData) + uniform_buffer_alignment) * Maxwell::MaxShaderStage; // Add space for at least 18 constant buffers buffer_size += Maxwell::MaxConstBuffers * (MaxConstbufferSize + uniform_buffer_alignment); - u8* buffer_ptr; - GLintptr buffer_offset; - std::tie(buffer_ptr, buffer_offset, std::ignore) = - stream_buffer.Map(static_cast<GLsizeiptr>(buffer_size), 4); - u8* buffer_ptr_base = buffer_ptr; + buffer_cache.Map(buffer_size); - std::tie(buffer_ptr, buffer_offset) = SetupVertexArrays(buffer_ptr, buffer_offset); + SetupVertexArrays(); // If indexed mode, copy the index buffer GLintptr index_buffer_offset = 0; if (is_indexed) { - std::tie(buffer_ptr, buffer_offset, index_buffer_offset) = UploadMemory( - buffer_ptr, buffer_offset, regs.index_array.StartAddress(), index_buffer_size); + MICROPROFILE_SCOPE(OpenGL_Index); + + // Adjust the index buffer offset so it points to the first desired index. + auto index_start = regs.index_array.StartAddress(); + index_start += static_cast<size_t>(regs.index_array.first) * + static_cast<size_t>(regs.index_array.FormatSizeInBytes()); + + index_buffer_offset = buffer_cache.UploadMemory(index_start, index_buffer_size); } - std::tie(buffer_ptr, buffer_offset) = SetupShaders(buffer_ptr, buffer_offset); + SetupShaders(); - stream_buffer.Unmap(buffer_ptr - buffer_ptr_base); + buffer_cache.Unmap(); shader_program_manager->ApplyTo(state); state.Apply(); @@ -527,14 +504,26 @@ void RasterizerOpenGL::DrawArrays() { if (is_indexed) { const GLint base_vertex{static_cast<GLint>(regs.vb_element_base)}; - // Adjust the index buffer offset so it points to the first desired index. - index_buffer_offset += regs.index_array.first * regs.index_array.FormatSizeInBytes(); - - glDrawElementsBaseVertex(primitive_mode, regs.index_array.count, - MaxwellToGL::IndexFormat(regs.index_array.format), - reinterpret_cast<const void*>(index_buffer_offset), base_vertex); + if (gpu.state.current_instance > 0) { + glDrawElementsInstancedBaseVertexBaseInstance( + primitive_mode, regs.index_array.count, + MaxwellToGL::IndexFormat(regs.index_array.format), + reinterpret_cast<const void*>(index_buffer_offset), 1, base_vertex, + gpu.state.current_instance); + } else { + glDrawElementsBaseVertex(primitive_mode, regs.index_array.count, + MaxwellToGL::IndexFormat(regs.index_array.format), + reinterpret_cast<const void*>(index_buffer_offset), + base_vertex); + } } else { - glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count); + if (gpu.state.current_instance > 0) { + glDrawArraysInstancedBaseInstance(primitive_mode, regs.vertex_buffer.first, + regs.vertex_buffer.count, 1, + gpu.state.current_instance); + } else { + glDrawArrays(primitive_mode, regs.vertex_buffer.first, regs.vertex_buffer.count); + } } // Disable scissor test @@ -549,24 +538,18 @@ void RasterizerOpenGL::DrawArrays() { state.Apply(); } -void RasterizerOpenGL::NotifyMaxwellRegisterChanged(u32 method) {} +void RasterizerOpenGL::FlushAll() {} -void RasterizerOpenGL::FlushAll() { - MICROPROFILE_SCOPE(OpenGL_CacheManagement); -} - -void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { - MICROPROFILE_SCOPE(OpenGL_CacheManagement); -} +void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {} void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); res_cache.InvalidateRegion(addr, size); shader_cache.InvalidateRegion(addr, size); + buffer_cache.InvalidateRegion(addr, size); } void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { - MICROPROFILE_SCOPE(OpenGL_CacheManagement); InvalidateRegion(addr, size); } @@ -614,7 +597,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config, void RasterizerOpenGL::SamplerInfo::Create() { sampler.Create(); mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear; - wrap_u = wrap_v = Tegra::Texture::WrapMode::Wrap; + wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap; // default is GL_LINEAR_MIPMAP_LINEAR glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); @@ -622,7 +605,7 @@ void RasterizerOpenGL::SamplerInfo::Create() { } void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) { - GLuint s = sampler.handle; + const GLuint s = sampler.handle; if (mag_filter != config.mag_filter) { mag_filter = config.mag_filter; @@ -641,8 +624,13 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr wrap_v = config.wrap_v; glSamplerParameteri(s, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(wrap_v)); } + if (wrap_p != config.wrap_p) { + wrap_p = config.wrap_p; + glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p)); + } - if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border) { + if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border || + wrap_p == Tegra::Texture::WrapMode::Border) { const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g, config.border_color_b, config.border_color_a}}; if (border_color != new_border_color) { @@ -652,26 +640,35 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr } } -std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(u8* buffer_ptr, - GLintptr buffer_offset, - Maxwell::ShaderStage stage, - Shader& shader, - u32 current_bindpoint) { +u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader, + u32 current_bindpoint) { + MICROPROFILE_SCOPE(OpenGL_UBO); const auto& gpu = Core::System::GetInstance().GPU(); const auto& maxwell3d = gpu.Maxwell3D(); - const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<size_t>(stage)]; + const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)]; const auto& entries = shader->GetShaderEntries().const_buffer_entries; + constexpr u64 max_binds = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers; + std::array<GLuint, max_binds> bind_buffers; + std::array<GLintptr, max_binds> bind_offsets; + std::array<GLsizeiptr, max_binds> bind_sizes; + + ASSERT_MSG(entries.size() <= max_binds, "Exceeded expected number of binding points."); + // Upload only the enabled buffers from the 16 constbuffers of each shader stage for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& used_buffer = entries[bindpoint]; const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()]; if (!buffer.enabled) { + // With disabled buffers set values as zero to unbind them + bind_buffers[bindpoint] = 0; + bind_offsets[bindpoint] = 0; + bind_sizes[bindpoint] = 0; continue; } - size_t size = 0; + std::size_t size = 0; if (used_buffer.IsIndirect()) { // Buffer is accessed indirectly, so upload the entire thing @@ -692,26 +689,28 @@ std::tuple<u8*, GLintptr, u32> RasterizerOpenGL::SetupConstBuffers(u8* buffer_pt size = Common::AlignUp(size, sizeof(GLvec4)); ASSERT_MSG(size <= MaxConstbufferSize, "Constbuffer too big"); - GLintptr const_buffer_offset; - std::tie(buffer_ptr, buffer_offset, const_buffer_offset) = - UploadMemory(buffer_ptr, buffer_offset, buffer.address, size, - static_cast<size_t>(uniform_buffer_alignment)); - - glBindBufferRange(GL_UNIFORM_BUFFER, current_bindpoint + bindpoint, - stream_buffer.GetHandle(), const_buffer_offset, size); + GLintptr const_buffer_offset = buffer_cache.UploadMemory( + buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment)); // Now configure the bindpoint of the buffer inside the shader glUniformBlockBinding(shader->GetProgramHandle(), - shader->GetProgramResourceIndex(used_buffer.GetName()), + shader->GetProgramResourceIndex(used_buffer), current_bindpoint + bindpoint); + + // Prepare values for multibind + bind_buffers[bindpoint] = buffer_cache.GetHandle(); + bind_offsets[bindpoint] = const_buffer_offset; + bind_sizes[bindpoint] = size; } - state.Apply(); + glBindBuffersRange(GL_UNIFORM_BUFFER, current_bindpoint, static_cast<GLsizei>(entries.size()), + bind_buffers.data(), bind_offsets.data(), bind_sizes.data()); - return {buffer_ptr, buffer_offset, current_bindpoint + static_cast<u32>(entries.size())}; + return current_bindpoint + static_cast<u32>(entries.size()); } u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) { + MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = Core::System::GetInstance().GPU(); const auto& maxwell3d = gpu.Maxwell3D(); const auto& entries = shader->GetShaderEntries().texture_samplers; @@ -721,24 +720,25 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& entry = entries[bindpoint]; - u32 current_bindpoint = current_unit + bindpoint; + const u32 current_bindpoint = current_unit + bindpoint; // Bind the uniform to the sampler. - glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry.GetName()), + glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry), current_bindpoint); const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset()); if (!texture.enabled) { - state.texture_units[current_bindpoint].texture_2d = 0; + state.texture_units[current_bindpoint].texture = 0; continue; } texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc); Surface surface = res_cache.GetTextureSurface(texture); if (surface != nullptr) { - state.texture_units[current_bindpoint].texture_2d = surface->Texture().handle; + state.texture_units[current_bindpoint].texture = surface->Texture().handle; + state.texture_units[current_bindpoint].target = surface->Target(); state.texture_units[current_bindpoint].swizzle.r = MaxwellToGL::SwizzleSource(texture.tic.x_source); state.texture_units[current_bindpoint].swizzle.g = @@ -749,47 +749,19 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, MaxwellToGL::SwizzleSource(texture.tic.w_source); } else { // Can occur when texture addr is null or its memory is unmapped/invalid - state.texture_units[current_bindpoint].texture_2d = 0; + state.texture_units[current_bindpoint].texture = 0; } } - state.Apply(); - return current_unit + static_cast<u32>(entries.size()); } -void RasterizerOpenGL::BindFramebufferSurfaces(const Surface& color_surface, - const Surface& depth_surface, bool has_stencil) { - state.draw.draw_framebuffer = framebuffer.handle; - state.Apply(); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - color_surface != nullptr ? color_surface->Texture().handle : 0, 0); - if (depth_surface != nullptr) { - if (has_stencil) { - // attach both depth and stencil - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - depth_surface->Texture().handle, 0); - } else { - // attach depth - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - depth_surface->Texture().handle, 0); - // clear stencil attachment - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - } - } else { - // clear both depth and stencil attachment - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - } -} - -void RasterizerOpenGL::SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect) { +void RasterizerOpenGL::SyncViewport() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[0].GetRect()}; - state.viewport.x = static_cast<GLint>(surfaces_rect.left) + viewport_rect.left; - state.viewport.y = static_cast<GLint>(surfaces_rect.bottom) + viewport_rect.bottom; + state.viewport.x = viewport_rect.left; + state.viewport.y = viewport_rect.bottom; state.viewport.width = static_cast<GLsizei>(viewport_rect.GetWidth()); state.viewport.height = static_cast<GLsizei>(viewport_rect.GetHeight()); } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 30045ebff..bf9560bdc 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -6,19 +6,23 @@ #include <array> #include <cstddef> +#include <map> #include <memory> #include <tuple> #include <utility> #include <vector> #include <boost/icl/interval_map.hpp> +#include <boost/optional.hpp> #include <boost/range/iterator_range.hpp> #include <glad/glad.h> #include "common/common_types.h" #include "video_core/engines/maxwell_3d.h" #include "video_core/memory_manager.h" +#include "video_core/rasterizer_cache.h" #include "video_core/rasterizer_interface.h" +#include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_shader_cache.h" @@ -42,7 +46,6 @@ public: void DrawArrays() override; void Clear() override; - void NotifyMaxwellRegisterChanged(u32 method) override; void FlushAll() override; void FlushRegion(VAddr addr, u64 size) override; void InvalidateRegion(VAddr addr, u64 size) override; @@ -70,7 +73,7 @@ public: }; /// Maximum supported size that a constbuffer can have in bytes. - static constexpr size_t MaxConstbufferSize = 0x10000; + static constexpr std::size_t MaxConstbufferSize = 0x10000; static_assert(MaxConstbufferSize % sizeof(GLvec4) == 0, "The maximum size of a constbuffer must be a multiple of the size of GLvec4"); @@ -90,17 +93,20 @@ private: Tegra::Texture::TextureFilter min_filter; Tegra::Texture::WrapMode wrap_u; Tegra::Texture::WrapMode wrap_v; + Tegra::Texture::WrapMode wrap_p; GLvec4 border_color; }; - /// Configures the color and depth framebuffer states and returns the dirty <Color, Depth> - /// surfaces if writing was enabled. - std::pair<Surface, Surface> ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb, - bool preserve_contents); - - /// Binds the framebuffer color and depth surface - void BindFramebufferSurfaces(const Surface& color_surface, const Surface& depth_surface, - bool has_stencil); + /** + * Configures the color and depth framebuffer states. + * @param use_color_fb If true, configure color framebuffers. + * @param using_depth_fb If true, configure the depth/stencil framebuffer. + * @param preserve_contents If true, tries to preserve data from a previously used framebuffer. + * @param single_color_target Specifies if a single color buffer target should be used. + */ + void ConfigureFramebuffers(bool use_color_fb = true, bool using_depth_fb = true, + bool preserve_contents = true, + boost::optional<std::size_t> single_color_target = {}); /* * Configures the current constbuffers to use for the draw command. @@ -109,9 +115,8 @@ private: * @param current_bindpoint The offset at which to start counting new buffer bindpoints. * @returns The next available bindpoint for use in the next shader stage. */ - std::tuple<u8*, GLintptr, u32> SetupConstBuffers( - u8* buffer_ptr, GLintptr buffer_offset, Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - Shader& shader, u32 current_bindpoint); + u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader, + u32 current_bindpoint); /* * Configures the current textures to use for the draw command. @@ -124,7 +129,7 @@ private: u32 current_unit); /// Syncs the viewport to match the guest state - void SyncViewport(const MathUtil::Rectangle<u32>& surfaces_rect); + void SyncViewport(); /// Syncs the clip enabled status to match the guest state void SyncClipEnabled(); @@ -154,6 +159,7 @@ private: void SyncLogicOpState(); bool has_ARB_direct_state_access = false; + bool has_ARB_multi_bind = false; bool has_ARB_separate_shader_objects = false; bool has_ARB_vertex_attrib_binding = false; @@ -167,28 +173,23 @@ private: ScreenInfo& screen_info; std::unique_ptr<GLShader::ProgramManager> shader_program_manager; - OGLVertexArray sw_vao; - OGLVertexArray hw_vao; + std::map<std::array<Tegra::Engines::Maxwell3D::Regs::VertexAttribute, + Tegra::Engines::Maxwell3D::Regs::NumVertexAttributes>, + OGLVertexArray> + vertex_array_cache; std::array<SamplerInfo, GLShader::NumTextureSamplers> texture_samplers; - static constexpr size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; - OGLStreamBuffer stream_buffer; - OGLBuffer uniform_buffer; + static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024; + OGLBufferCache buffer_cache; OGLFramebuffer framebuffer; GLint uniform_buffer_alignment; - size_t CalculateVertexArraysSize() const; - - std::pair<u8*, GLintptr> SetupVertexArrays(u8* array_ptr, GLintptr buffer_offset); - - std::pair<u8*, GLintptr> SetupShaders(u8* buffer_ptr, GLintptr buffer_offset); + std::size_t CalculateVertexArraysSize() const; - std::pair<u8*, GLintptr> AlignBuffer(u8* buffer_ptr, GLintptr buffer_offset, size_t alignment); + void SetupVertexArrays(); - std::tuple<u8*, GLintptr, GLintptr> UploadMemory(u8* buffer_ptr, GLintptr buffer_offset, - Tegra::GPUVAddr gpu_addr, size_t size, - size_t alignment = 4); + void SetupShaders(); enum class AccelDraw { Disabled, Arrays, Indexed }; AccelDraw accelerate_draw = AccelDraw::Disabled; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 1965ab7d5..86682d7cb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -7,6 +7,7 @@ #include "common/alignment.h" #include "common/assert.h" +#include "common/logging/log.h" #include "common/microprofile.h" #include "common/scope_exit.h" #include "core/core.h" @@ -52,14 +53,30 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); params.unaligned_height = config.tic.Height(); + params.target = SurfaceTargetFromTextureType(config.tic.texture_type); + + switch (params.target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + params.depth = 1; + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + params.depth = config.tic.Depth(); + break; + default: + LOG_CRITICAL(HW_GPU, "Unknown depth for target={}", static_cast<u32>(params.target)); + UNREACHABLE(); + params.depth = 1; + break; + } + params.size_in_bytes = params.SizeInBytes(); - params.cache_width = Common::AlignUp(params.width, 16); - params.cache_height = Common::AlignUp(params.height, 16); return params; } -/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer( - const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config) { +/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) { + const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]}; SurfaceParams params{}; params.addr = TryGetCpuAddr(config.Address()); params.is_tiled = true; @@ -70,9 +87,9 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.width = config.width; params.height = config.height; params.unaligned_height = config.height; + params.target = SurfaceTarget::Texture2D; + params.depth = 1; params.size_in_bytes = params.SizeInBytes(); - params.cache_width = Common::AlignUp(params.width, 16); - params.cache_height = Common::AlignUp(params.height, 16); return params; } @@ -86,13 +103,12 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.pixel_format = PixelFormatFromDepthFormat(format); params.component_type = ComponentTypeFromDepthFormat(format); params.type = GetFormatType(params.pixel_format); - params.size_in_bytes = params.SizeInBytes(); params.width = zeta_width; params.height = zeta_height; params.unaligned_height = zeta_height; + params.target = SurfaceTarget::Texture2D; + params.depth = 1; params.size_in_bytes = params.SizeInBytes(); - params.cache_width = Common::AlignUp(params.width, 16); - params.cache_height = Common::AlignUp(params.height, 16); return params; } @@ -100,7 +116,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI - {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U + {GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, false}, // A2B10G10R10U {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U @@ -151,6 +167,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8 // Depth formats {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F @@ -166,8 +183,28 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form ComponentType::Float, false}, // Z32FS8 }}; +static GLenum SurfaceTargetToGL(SurfaceParams::SurfaceTarget target) { + switch (target) { + case SurfaceParams::SurfaceTarget::Texture1D: + return GL_TEXTURE_1D; + case SurfaceParams::SurfaceTarget::Texture2D: + return GL_TEXTURE_2D; + case SurfaceParams::SurfaceTarget::Texture3D: + return GL_TEXTURE_3D; + case SurfaceParams::SurfaceTarget::Texture1DArray: + return GL_TEXTURE_1D_ARRAY; + case SurfaceParams::SurfaceTarget::Texture2DArray: + return GL_TEXTURE_2D_ARRAY; + case SurfaceParams::SurfaceTarget::TextureCubemap: + return GL_TEXTURE_CUBE_MAP; + } + LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target)); + UNREACHABLE(); + return {}; +} + static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) { - ASSERT(static_cast<size_t>(pixel_format) < tex_format_tuples.size()); + ASSERT(static_cast<std::size_t>(pixel_format) < tex_format_tuples.size()); auto& format = tex_format_tuples[static_cast<unsigned int>(pixel_format)]; ASSERT(component_type == format.component_type); @@ -177,6 +214,7 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType static bool IsPixelFormatASTC(PixelFormat format) { switch (format) { case PixelFormat::ASTC_2D_4X4: + case PixelFormat::ASTC_2D_8X8: return true; default: return false; @@ -187,6 +225,8 @@ static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { switch (format) { case PixelFormat::ASTC_2D_4X4: return {4, 4}; + case PixelFormat::ASTC_2D_8X8: + return {8, 8}; default: LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format)); UNREACHABLE(); @@ -220,7 +260,8 @@ static bool IsFormatBCn(PixelFormat format) { } template <bool morton_to_gl, PixelFormat format> -void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_buffer, VAddr addr) { +void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, std::size_t gl_buffer_size, + VAddr addr) { constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT; constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); @@ -230,18 +271,18 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, std::vector<u8>& gl_bu const u32 tile_size{IsFormatBCn(format) ? 4U : 1U}; const std::vector<u8> data = Tegra::Texture::UnswizzleTexture( addr, tile_size, bytes_per_pixel, stride, height, block_height); - const size_t size_to_copy{std::min(gl_buffer.size(), data.size())}; - gl_buffer.assign(data.begin(), data.begin() + size_to_copy); + const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())}; + memcpy(gl_buffer, data.data(), size_to_copy); } else { // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should // check the configuration for this and perform more generic un/swizzle LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel, - Memory::GetPointer(addr), gl_buffer.data(), morton_to_gl); + Memory::GetPointer(addr), gl_buffer, morton_to_gl); } } -static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr), +static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), SurfaceParams::MaxPixelFormat> morton_to_gl_fns = { // clang-format off @@ -290,6 +331,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr), MortonCopy<true, PixelFormat::RG8S>, MortonCopy<true, PixelFormat::RG32UI>, MortonCopy<true, PixelFormat::R32UI>, + MortonCopy<true, PixelFormat::ASTC_2D_8X8>, MortonCopy<true, PixelFormat::Z32F>, MortonCopy<true, PixelFormat::Z16>, MortonCopy<true, PixelFormat::Z24S8>, @@ -298,7 +340,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr), // clang-format on }; -static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr), +static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr), SurfaceParams::MaxPixelFormat> gl_to_morton_fns = { // clang-format off @@ -349,6 +391,7 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr), MortonCopy<false, PixelFormat::RG8S>, MortonCopy<false, PixelFormat::RG32UI>, MortonCopy<false, PixelFormat::R32UI>, + nullptr, MortonCopy<false, PixelFormat::Z32F>, MortonCopy<false, PixelFormat::Z16>, MortonCopy<false, PixelFormat::Z24S8>, @@ -357,33 +400,6 @@ static constexpr std::array<void (*)(u32, u32, u32, std::vector<u8>&, VAddr), // clang-format on }; -// Allocate an uninitialized texture of appropriate size and format for the surface -static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width, - u32 height) { - OpenGLState cur_state = OpenGLState::GetCurState(); - - // Keep track of previous texture bindings - GLuint old_tex = cur_state.texture_units[0].texture_2d; - cur_state.texture_units[0].texture_2d = texture; - cur_state.Apply(); - glActiveTexture(GL_TEXTURE0); - - if (!format_tuple.compressed) { - // Only pre-create the texture for non-compressed textures. - glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0, - format_tuple.format, format_tuple.type, nullptr); - } - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - // Restore previous texture bindings - cur_state.texture_units[0].texture_2d = old_tex; - cur_state.Apply(); -} - static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex, const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type, GLuint read_fb_handle, GLuint draw_fb_handle) { @@ -438,12 +454,53 @@ static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rec return true; } -CachedSurface::CachedSurface(const SurfaceParams& params) : params(params) { +CachedSurface::CachedSurface(const SurfaceParams& params) + : params(params), gl_target(SurfaceTargetToGL(params.target)) { texture.Create(); const auto& rect{params.GetRect()}; - AllocateSurfaceTexture(texture.handle, - GetFormatTuple(params.pixel_format, params.component_type), + + // Keep track of previous texture bindings + OpenGLState cur_state = OpenGLState::GetCurState(); + const auto& old_tex = cur_state.texture_units[0]; + SCOPE_EXIT({ + cur_state.texture_units[0] = old_tex; + cur_state.Apply(); + }); + + cur_state.texture_units[0].texture = texture.handle; + cur_state.texture_units[0].target = SurfaceTargetToGL(params.target); + cur_state.Apply(); + glActiveTexture(GL_TEXTURE0); + + const auto& format_tuple = GetFormatTuple(params.pixel_format, params.component_type); + if (!format_tuple.compressed) { + // Only pre-create the texture for non-compressed textures. + switch (params.target) { + case SurfaceParams::SurfaceTarget::Texture1D: + glTexStorage1D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format, + rect.GetWidth()); + break; + case SurfaceParams::SurfaceTarget::Texture2D: + glTexStorage2D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format, rect.GetWidth(), rect.GetHeight()); + break; + case SurfaceParams::SurfaceTarget::Texture3D: + case SurfaceParams::SurfaceTarget::Texture2DArray: + glTexStorage3D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format, + rect.GetWidth(), rect.GetHeight(), params.depth); + break; + default: + LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", + static_cast<u32>(params.target)); + UNREACHABLE(); + glTexStorage2D(GL_TEXTURE_2D, 1, format_tuple.internal_format, rect.GetWidth(), + rect.GetHeight()); + } + } + + glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { @@ -461,10 +518,10 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { S8Z24 input_pixel{}; Z24S8 output_pixel{}; - const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)}; - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; ++x) { - const size_t offset{bpp * (y * width + x)}; + constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)}; + for (std::size_t y = 0; y < height; ++y) { + for (std::size_t x = 0; x < width; ++x) { + const std::size_t offset{bpp * (y * width + x)}; std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24)); output_pixel.s8.Assign(input_pixel.s8); output_pixel.z24.Assign(input_pixel.z24); @@ -474,10 +531,10 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { } static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) { - const auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)}; - for (size_t y = 0; y < height; ++y) { - for (size_t x = 0; x < width; ++x) { - const size_t offset{bpp * (y * width + x)}; + constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)}; + for (std::size_t y = 0; y < height; ++y) { + for (std::size_t x = 0; x < width; ++x) { + const std::size_t offset{bpp * (y * width + x)}; const u8 temp{data[offset]}; data[offset] = data[offset + 1]; data[offset + 1] = temp; @@ -493,7 +550,8 @@ static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) { static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelFormat pixel_format, u32 width, u32 height) { switch (pixel_format) { - case PixelFormat::ASTC_2D_4X4: { + case PixelFormat::ASTC_2D_4X4: + case PixelFormat::ASTC_2D_8X8: { // Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC. u32 block_width{}; u32 block_height{}; @@ -514,23 +572,6 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma } } -/** - * Helper function to perform software conversion (as needed) when flushing a buffer to Switch - * memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or with - * typical desktop GPUs. - */ -static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& /*data*/, PixelFormat pixel_format, - u32 /*width*/, u32 /*height*/) { - switch (pixel_format) { - case PixelFormat::ASTC_2D_4X4: - case PixelFormat::S8Z24: - LOG_CRITICAL(Render_OpenGL, "Unimplemented pixel_format={}", - static_cast<u32>(pixel_format)); - UNREACHABLE(); - break; - } -} - MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); void CachedSurface::LoadGLBuffer() { ASSERT(params.type != SurfaceType::Fill); @@ -545,13 +586,25 @@ void CachedSurface::LoadGLBuffer() { MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); if (params.is_tiled) { - gl_buffer.resize(copy_size); + // TODO(bunnei): This only unswizzles and copies a 2D texture - we do not yet know how to do + // this for 3D textures, etc. + switch (params.target) { + case SurfaceParams::SurfaceTarget::Texture2D: + // Pass impl. to the fallback code below + break; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented tiled load for target={}", + static_cast<u32>(params.target)); + UNREACHABLE(); + } - morton_to_gl_fns[static_cast<size_t>(params.pixel_format)]( - params.width, params.block_height, params.height, gl_buffer, params.addr); + gl_buffer.resize(static_cast<std::size_t>(params.depth) * copy_size); + morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)]( + params.width, params.block_height, params.height, gl_buffer.data(), copy_size, + params.addr); } else { - const u8* const texture_src_data_end = texture_src_data + copy_size; - + const u8* const texture_src_data_end{texture_src_data + + (static_cast<std::size_t>(params.depth) * copy_size)}; gl_buffer.assign(texture_src_data, texture_src_data_end); } @@ -560,23 +613,7 @@ void CachedSurface::LoadGLBuffer() { MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); void CachedSurface::FlushGLBuffer() { - u8* const dst_buffer = Memory::GetPointer(params.addr); - - ASSERT(dst_buffer); - ASSERT(gl_buffer.size() == - params.width * params.height * GetGLBytesPerPixel(params.pixel_format)); - - MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); - - ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width, - params.height); - - if (!params.is_tiled) { - std::memcpy(dst_buffer, gl_buffer.data(), params.size_in_bytes); - } else { - gl_to_morton_fns[static_cast<size_t>(params.pixel_format)]( - params.width, params.block_height, params.height, gl_buffer, params.addr); - } + ASSERT_MSG(false, "Unimplemented"); } MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); @@ -586,22 +623,30 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle MICROPROFILE_SCOPE(OpenGL_TextureUL); - ASSERT(gl_buffer.size() == - params.width * params.height * GetGLBytesPerPixel(params.pixel_format)); + ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height * + GetGLBytesPerPixel(params.pixel_format) * params.depth); const auto& rect{params.GetRect()}; // Load data from memory to the surface - GLint x0 = static_cast<GLint>(rect.left); - GLint y0 = static_cast<GLint>(rect.bottom); - size_t buffer_offset = (y0 * params.width + x0) * GetGLBytesPerPixel(params.pixel_format); + const GLint x0 = static_cast<GLint>(rect.left); + const GLint y0 = static_cast<GLint>(rect.bottom); + const std::size_t buffer_offset = + static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width + + static_cast<std::size_t>(x0)) * + GetGLBytesPerPixel(params.pixel_format); const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); - GLuint target_tex = texture.handle; + const GLuint target_tex = texture.handle; OpenGLState cur_state = OpenGLState::GetCurState(); - GLuint old_tex = cur_state.texture_units[0].texture_2d; - cur_state.texture_units[0].texture_2d = target_tex; + const auto& old_tex = cur_state.texture_units[0]; + SCOPE_EXIT({ + cur_state.texture_units[0] = old_tex; + cur_state.Apply(); + }); + cur_state.texture_units[0].texture = target_tex; + cur_state.texture_units[0].target = SurfaceTargetToGL(params.target); cur_state.Apply(); // Ensure no bad interactions with GL_UNPACK_ALIGNMENT @@ -610,136 +655,102 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle glActiveTexture(GL_TEXTURE0); if (tuple.compressed) { - glCompressedTexImage2D( - GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width), - static_cast<GLsizei>(params.height), 0, static_cast<GLsizei>(params.size_in_bytes), - &gl_buffer[buffer_offset]); + switch (params.target) { + case SurfaceParams::SurfaceTarget::Texture2D: + glCompressedTexImage2D( + SurfaceTargetToGL(params.target), 0, tuple.internal_format, + static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0, + static_cast<GLsizei>(params.size_in_bytes), &gl_buffer[buffer_offset]); + break; + case SurfaceParams::SurfaceTarget::Texture3D: + case SurfaceParams::SurfaceTarget::Texture2DArray: + glCompressedTexImage3D( + SurfaceTargetToGL(params.target), 0, tuple.internal_format, + static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), + static_cast<GLsizei>(params.depth), 0, static_cast<GLsizei>(params.size_in_bytes), + &gl_buffer[buffer_offset]); + break; + default: + LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", + static_cast<u32>(params.target)); + UNREACHABLE(); + glCompressedTexImage2D( + GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width), + static_cast<GLsizei>(params.height), 0, static_cast<GLsizei>(params.size_in_bytes), + &gl_buffer[buffer_offset]); + } } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()), - static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, - &gl_buffer[buffer_offset]); - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - - cur_state.texture_units[0].texture_2d = old_tex; - cur_state.Apply(); -} - -MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64)); -void CachedSurface::DownloadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) { - if (params.type == SurfaceType::Fill) - return; - - MICROPROFILE_SCOPE(OpenGL_TextureDL); - - gl_buffer.resize(params.width * params.height * GetGLBytesPerPixel(params.pixel_format)); - - OpenGLState state = OpenGLState::GetCurState(); - OpenGLState prev_state = state; - SCOPE_EXIT({ prev_state.Apply(); }); - - const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); - - // Ensure no bad interactions with GL_PACK_ALIGNMENT - ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0); - glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width)); - - const auto& rect{params.GetRect()}; - size_t buffer_offset = - (rect.bottom * params.width + rect.left) * GetGLBytesPerPixel(params.pixel_format); - - state.UnbindTexture(texture.handle); - state.draw.read_framebuffer = read_fb_handle; - state.Apply(); - if (params.type == SurfaceType::ColorTexture) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - texture.handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - } else if (params.type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - texture.handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - } else { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - texture.handle, 0); + switch (params.target) { + case SurfaceParams::SurfaceTarget::Texture1D: + glTexSubImage1D(SurfaceTargetToGL(params.target), 0, x0, + static_cast<GLsizei>(rect.GetWidth()), tuple.format, tuple.type, + &gl_buffer[buffer_offset]); + break; + case SurfaceParams::SurfaceTarget::Texture2D: + glTexSubImage2D(SurfaceTargetToGL(params.target), 0, x0, y0, + static_cast<GLsizei>(rect.GetWidth()), + static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, + &gl_buffer[buffer_offset]); + break; + case SurfaceParams::SurfaceTarget::Texture3D: + case SurfaceParams::SurfaceTarget::Texture2DArray: + glTexSubImage3D(SurfaceTargetToGL(params.target), 0, x0, y0, 0, + static_cast<GLsizei>(rect.GetWidth()), + static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format, + tuple.type, &gl_buffer[buffer_offset]); + break; + default: + LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", + static_cast<u32>(params.target)); + UNREACHABLE(); + glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()), + static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, + &gl_buffer[buffer_offset]); + } } - glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom), - static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()), - tuple.format, tuple.type, &gl_buffer[buffer_offset]); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); } RasterizerCacheOpenGL::RasterizerCacheOpenGL() { read_framebuffer.Create(); draw_framebuffer.Create(); + copy_pbo.Create(); } Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config) { return GetSurface(SurfaceParams::CreateForTexture(config)); } -SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces(bool using_color_fb, - bool using_depth_fb, - bool preserve_contents) { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; +Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) { + const auto& regs{Core::System::GetInstance().GPU().Maxwell3D().regs}; + if (!regs.zeta.Address() || !regs.zeta_enable) { + return {}; + } - // TODO(bunnei): This is hard corded to use just the first render buffer - LOG_WARNING(Render_OpenGL, "hard-coded for render target 0!"); + SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer( + regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format)}; - // get color and depth surfaces - SurfaceParams color_params{}; - SurfaceParams depth_params{}; + return GetSurface(depth_params, preserve_contents); +} - if (using_color_fb) { - color_params = SurfaceParams::CreateForFramebuffer(regs.rt[0]); - } +Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool preserve_contents) { + const auto& regs{Core::System::GetInstance().GPU().Maxwell3D().regs}; - if (using_depth_fb) { - depth_params = SurfaceParams::CreateForDepthBuffer(regs.zeta_width, regs.zeta_height, - regs.zeta.Address(), regs.zeta.format); - } + ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); - MathUtil::Rectangle<u32> color_rect{}; - Surface color_surface; - if (using_color_fb) { - color_surface = GetSurface(color_params, preserve_contents); - if (color_surface) { - color_rect = color_surface->GetSurfaceParams().GetRect(); - } + if (index >= regs.rt_control.count) { + return {}; } - MathUtil::Rectangle<u32> depth_rect{}; - Surface depth_surface; - if (using_depth_fb) { - depth_surface = GetSurface(depth_params, preserve_contents); - if (depth_surface) { - depth_rect = depth_surface->GetSurfaceParams().GetRect(); - } + if (regs.rt[index].Address() == 0 || regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { + return {}; } - MathUtil::Rectangle<u32> fb_rect{}; - if (color_surface && depth_surface) { - fb_rect = color_rect; - // Color and Depth surfaces must have the same dimensions and offsets - if (color_rect.bottom != depth_rect.bottom || color_rect.top != depth_rect.top || - color_rect.left != depth_rect.left || color_rect.right != depth_rect.right) { - color_surface = GetSurface(color_params); - depth_surface = GetSurface(depth_params); - fb_rect = color_surface->GetSurfaceParams().GetRect(); - } - } else if (color_surface) { - fb_rect = color_rect; - } else if (depth_surface) { - fb_rect = depth_rect; - } + const SurfaceParams color_params{SurfaceParams::CreateForFramebuffer(index)}; - return std::make_tuple(color_surface, depth_surface, fb_rect); + return GetSurface(color_params, preserve_contents); } void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { @@ -748,7 +759,6 @@ void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { } void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) { - surface->DownloadGLTexture(read_framebuffer.handle, draw_framebuffer.handle); surface->FlushGLBuffer(); } @@ -806,27 +816,26 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, // Get a new surface with the new parameters, and blit the previous surface to it Surface new_surface{GetUncachedSurface(new_params)}; - // If format is unchanged, we can do a faster blit without reinterpreting pixel data - if (params.pixel_format == new_params.pixel_format) { + if (params.pixel_format == new_params.pixel_format || + !Settings::values.use_accurate_framebuffers) { + // If the format is the same, just do a framebuffer blit. This is significantly faster than + // using PBOs. The is also likely less accurate, as textures will be converted rather than + // reinterpreted. + BlitTextures(surface->Texture().handle, params.GetRect(), new_surface->Texture().handle, - new_surface->GetSurfaceParams().GetRect(), params.type, - read_framebuffer.handle, draw_framebuffer.handle); - return new_surface; - } + params.GetRect(), params.type, read_framebuffer.handle, + draw_framebuffer.handle); + } else { + // When use_accurate_framebuffers setting is enabled, perform a more accurate surface copy, + // where pixels are reinterpreted as a new format (without conversion). This code path uses + // OpenGL PBOs and is quite slow. - // When using accurate framebuffers, always copy old data to new surface, regardless of format - if (Settings::values.use_accurate_framebuffers) { auto source_format = GetFormatTuple(params.pixel_format, params.component_type); auto dest_format = GetFormatTuple(new_params.pixel_format, new_params.component_type); - size_t buffer_size = std::max(params.SizeInBytes(), new_params.SizeInBytes()); + std::size_t buffer_size = std::max(params.SizeInBytes(), new_params.SizeInBytes()); - // Use a Pixel Buffer Object to download the previous texture and then upload it to the new - // one using the new format. - OGLBuffer pbo; - pbo.Create(); - - glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo.handle); + glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo.handle); glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB); if (source_format.compressed) { glGetCompressedTextureImage(surface->Texture().handle, 0, @@ -845,10 +854,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, // of the data in this case. Games like Super Mario Odyssey seem to hit this case // when drawing, it re-uses the memory of a previous texture as a bigger framebuffer // but it doesn't clear it beforehand, the texture is already full of zeros. - LOG_CRITICAL(HW_GPU, "Trying to upload extra texture data from the CPU during " - "reinterpretation but the texture is tiled."); + LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during " + "reinterpretation but the texture is tiled."); } - size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes(); + std::size_t remaining_size = new_params.SizeInBytes() - params.SizeInBytes(); std::vector<u8> data(remaining_size); Memory::ReadBlock(new_params.addr + params.SizeInBytes(), data.data(), data.size()); glBufferSubData(GL_PIXEL_PACK_BUFFER, params.SizeInBytes(), remaining_size, @@ -859,21 +868,38 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& surface, const auto& dest_rect{new_params.GetRect()}; - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo.handle); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo.handle); if (dest_format.compressed) { - glCompressedTexSubImage2D( - GL_TEXTURE_2D, 0, 0, 0, static_cast<GLsizei>(dest_rect.GetWidth()), - static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, - static_cast<GLsizei>(new_params.SizeInBytes()), nullptr); + LOG_CRITICAL(HW_GPU, "Compressed copy is unimplemented!"); + UNREACHABLE(); } else { - glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0, - static_cast<GLsizei>(dest_rect.GetWidth()), - static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, - dest_format.type, nullptr); + switch (new_params.target) { + case SurfaceParams::SurfaceTarget::Texture1D: + glTextureSubImage1D(new_surface->Texture().handle, 0, 0, + static_cast<GLsizei>(dest_rect.GetWidth()), dest_format.format, + dest_format.type, nullptr); + break; + case SurfaceParams::SurfaceTarget::Texture2D: + glTextureSubImage2D(new_surface->Texture().handle, 0, 0, 0, + static_cast<GLsizei>(dest_rect.GetWidth()), + static_cast<GLsizei>(dest_rect.GetHeight()), dest_format.format, + dest_format.type, nullptr); + break; + case SurfaceParams::SurfaceTarget::Texture3D: + case SurfaceParams::SurfaceTarget::Texture2DArray: + glTextureSubImage3D(new_surface->Texture().handle, 0, 0, 0, 0, + static_cast<GLsizei>(dest_rect.GetWidth()), + static_cast<GLsizei>(dest_rect.GetHeight()), + static_cast<GLsizei>(new_params.depth), dest_format.format, + dest_format.type, nullptr); + break; + default: + LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", + static_cast<u32>(params.target)); + UNREACHABLE(); + } } glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - - pbo.Release(); } return new_surface; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index aad75f200..d7a4bc37f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -70,19 +70,20 @@ struct SurfaceParams { RG8S = 42, RG32UI = 43, R32UI = 44, + ASTC_2D_8X8 = 45, MaxColorFormat, // Depth formats - Z32F = 45, - Z16 = 46, + Z32F = 46, + Z16 = 47, MaxDepthFormat, // DepthStencil formats - Z24S8 = 47, - S8Z24 = 48, - Z32FS8 = 49, + Z24S8 = 48, + S8Z24 = 49, + Z32FS8 = 50, MaxDepthStencilFormat, @@ -90,7 +91,7 @@ struct SurfaceParams { Invalid = 255, }; - static constexpr size_t MaxPixelFormat = static_cast<size_t>(PixelFormat::Max); + static constexpr std::size_t MaxPixelFormat = static_cast<std::size_t>(PixelFormat::Max); enum class ComponentType { Invalid = 0, @@ -109,6 +110,33 @@ struct SurfaceParams { Invalid = 4, }; + enum class SurfaceTarget { + Texture1D, + Texture2D, + Texture3D, + Texture1DArray, + Texture2DArray, + TextureCubemap, + }; + + static SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type) { + switch (texture_type) { + case Tegra::Texture::TextureType::Texture1D: + return SurfaceTarget::Texture1D; + case Tegra::Texture::TextureType::Texture2D: + case Tegra::Texture::TextureType::Texture2DNoMipmap: + return SurfaceTarget::Texture2D; + case Tegra::Texture::TextureType::Texture1DArray: + return SurfaceTarget::Texture1DArray; + case Tegra::Texture::TextureType::Texture2DArray: + return SurfaceTarget::Texture2DArray; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", static_cast<u32>(texture_type)); + UNREACHABLE(); + return SurfaceTarget::Texture2D; + } + } + /** * Gets the compression factor for the specified PixelFormat. This applies to just the * "compressed width" and "compressed height", not the overall compression factor of a @@ -165,6 +193,7 @@ struct SurfaceParams { 1, // RG8S 1, // RG32UI 1, // R32UI + 4, // ASTC_2D_8X8 1, // Z32F 1, // Z16 1, // Z24S8 @@ -172,8 +201,8 @@ struct SurfaceParams { 1, // Z32FS8 }}; - ASSERT(static_cast<size_t>(format) < compression_factor_table.size()); - return compression_factor_table[static_cast<size_t>(format)]; + ASSERT(static_cast<std::size_t>(format) < compression_factor_table.size()); + return compression_factor_table[static_cast<std::size_t>(format)]; } static constexpr u32 GetFormatBpp(PixelFormat format) { @@ -226,6 +255,7 @@ struct SurfaceParams { 16, // RG8S 64, // RG32UI 32, // R32UI + 16, // ASTC_2D_8X8 32, // Z32F 16, // Z16 32, // Z24S8 @@ -233,8 +263,8 @@ struct SurfaceParams { 64, // Z32FS8 }}; - ASSERT(static_cast<size_t>(format) < bpp_table.size()); - return bpp_table[static_cast<size_t>(format)]; + ASSERT(static_cast<std::size_t>(format) < bpp_table.size()); + return bpp_table[static_cast<std::size_t>(format)]; } u32 GetFormatBpp() const { @@ -270,6 +300,7 @@ struct SurfaceParams { return PixelFormat::ABGR8S; case Tegra::RenderTargetFormat::RGBA8_UINT: return PixelFormat::ABGR8UI; + case Tegra::RenderTargetFormat::BGRA8_SRGB: case Tegra::RenderTargetFormat::BGRA8_UNORM: return PixelFormat::BGRA8; case Tegra::RenderTargetFormat::RGB10_A2_UNORM: @@ -288,6 +319,8 @@ struct SurfaceParams { return PixelFormat::R11FG11FB10F; case Tegra::RenderTargetFormat::B5G6R5_UNORM: return PixelFormat::B5G6R5U; + case Tegra::RenderTargetFormat::BGR5A1_UNORM: + return PixelFormat::A1B5G5R5U; case Tegra::RenderTargetFormat::RGBA32_UINT: return PixelFormat::RGBA32UI; case Tegra::RenderTargetFormat::R8_UNORM: @@ -494,6 +527,8 @@ struct SurfaceParams { return PixelFormat::BC6H_SF16; case Tegra::Texture::TextureFormat::ASTC_2D_4X4: return PixelFormat::ASTC_2D_4X4; + case Tegra::Texture::TextureFormat::ASTC_2D_8X8: + return PixelFormat::ASTC_2D_8X8; case Tegra::Texture::TextureFormat::R16_G16: switch (component_type) { case Tegra::Texture::ComponentType::FLOAT: @@ -542,11 +577,13 @@ struct SurfaceParams { case Tegra::RenderTargetFormat::RGBA8_UNORM: case Tegra::RenderTargetFormat::RGBA8_SRGB: case Tegra::RenderTargetFormat::BGRA8_UNORM: + case Tegra::RenderTargetFormat::BGRA8_SRGB: case Tegra::RenderTargetFormat::RGB10_A2_UNORM: case Tegra::RenderTargetFormat::R8_UNORM: case Tegra::RenderTargetFormat::RG16_UNORM: case Tegra::RenderTargetFormat::R16_UNORM: case Tegra::RenderTargetFormat::B5G6R5_UNORM: + case Tegra::RenderTargetFormat::BGR5A1_UNORM: case Tegra::RenderTargetFormat::RG8_UNORM: case Tegra::RenderTargetFormat::RGBA16_UNORM: return ComponentType::UNorm; @@ -607,16 +644,18 @@ struct SurfaceParams { } static SurfaceType GetFormatType(PixelFormat pixel_format) { - if (static_cast<size_t>(pixel_format) < static_cast<size_t>(PixelFormat::MaxColorFormat)) { + if (static_cast<std::size_t>(pixel_format) < + static_cast<std::size_t>(PixelFormat::MaxColorFormat)) { return SurfaceType::ColorTexture; } - if (static_cast<size_t>(pixel_format) < static_cast<size_t>(PixelFormat::MaxDepthFormat)) { + if (static_cast<std::size_t>(pixel_format) < + static_cast<std::size_t>(PixelFormat::MaxDepthFormat)) { return SurfaceType::Depth; } - if (static_cast<size_t>(pixel_format) < - static_cast<size_t>(PixelFormat::MaxDepthStencilFormat)) { + if (static_cast<std::size_t>(pixel_format) < + static_cast<std::size_t>(PixelFormat::MaxDepthStencilFormat)) { return SurfaceType::DepthStencil; } @@ -630,20 +669,19 @@ struct SurfaceParams { MathUtil::Rectangle<u32> GetRect() const; /// Returns the size of this surface in bytes, adjusted for compression - size_t SizeInBytes() const { + std::size_t SizeInBytes() const { const u32 compression_factor{GetCompressionFactor(pixel_format)}; ASSERT(width % compression_factor == 0); ASSERT(height % compression_factor == 0); return (width / compression_factor) * (height / compression_factor) * - GetFormatBpp(pixel_format) / CHAR_BIT; + GetFormatBpp(pixel_format) * depth / CHAR_BIT; } /// Creates SurfaceParams from a texture configuration static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config); /// Creates SurfaceParams from a framebuffer configuration - static SurfaceParams CreateForFramebuffer( - const Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig& config); + static SurfaceParams CreateForFramebuffer(std::size_t index); /// Creates SurfaceParams for a depth buffer configuration static SurfaceParams CreateForDepthBuffer(u32 zeta_width, u32 zeta_height, @@ -652,8 +690,8 @@ struct SurfaceParams { /// Checks if surfaces are compatible for caching bool IsCompatibleSurface(const SurfaceParams& other) const { - return std::tie(pixel_format, type, cache_width, cache_height) == - std::tie(other.pixel_format, other.type, other.cache_width, other.cache_height); + return std::tie(pixel_format, type, width, height) == + std::tie(other.pixel_format, other.type, other.width, other.height); } VAddr addr; @@ -664,12 +702,10 @@ struct SurfaceParams { SurfaceType type; u32 width; u32 height; + u32 depth; u32 unaligned_height; - size_t size_in_bytes; - - // Parameters used for caching only - u32 cache_width; - u32 cache_height; + std::size_t size_in_bytes; + SurfaceTarget target; }; }; // namespace OpenGL @@ -685,7 +721,7 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> { namespace std { template <> struct hash<SurfaceReserveKey> { - size_t operator()(const SurfaceReserveKey& k) const { + std::size_t operator()(const SurfaceReserveKey& k) const { return k.Hash(); } }; @@ -701,7 +737,7 @@ public: return params.addr; } - size_t GetSizeInBytes() const { + std::size_t GetSizeInBytes() const { return params.size_in_bytes; } @@ -709,6 +745,10 @@ public: return texture; } + GLenum Target() const { + return gl_target; + } + static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) { if (format == SurfaceParams::PixelFormat::Invalid) return 0; @@ -724,14 +764,14 @@ public: void LoadGLBuffer(); void FlushGLBuffer(); - // Upload/Download data in gl_buffer in/to this surface's texture + // Upload data in gl_buffer to this surface's texture void UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle); - void DownloadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle); private: OGLTexture texture; std::vector<u8> gl_buffer; SurfaceParams params; + GLenum gl_target; }; class RasterizerCacheOpenGL final : public RasterizerCache<Surface> { @@ -741,9 +781,11 @@ public: /// Get a surface based on the texture configuration Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config); - /// Get the color and depth surfaces based on the framebuffer configuration - SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, - bool preserve_contents); + /// Get the depth surface based on the framebuffer configuration + Surface GetDepthBufferSurface(bool preserve_contents); + + /// Get the color surface based on the framebuffer configuration and the specified render target + Surface GetColorBufferSurface(std::size_t index, bool preserve_contents); /// Flushes the surface to Switch memory void FlushSurface(const Surface& surface); @@ -774,6 +816,10 @@ private: OGLFramebuffer read_framebuffer; OGLFramebuffer draw_framebuffer; + + /// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one + /// using the new format. + OGLBuffer copy_pbo; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index ac9adfd83..894fe6eae 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -13,8 +13,8 @@ namespace OpenGL { /// Gets the address for the specified shader stage program static VAddr GetShaderAddress(Maxwell::ShaderProgram program) { - auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); - auto& shader_config = gpu.regs.shader_config[static_cast<size_t>(program)]; + const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D(); + const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)]; return *gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() + shader_config.offset); } @@ -28,7 +28,7 @@ static GLShader::ProgramCode GetShaderCode(VAddr addr) { /// Helper function to set shader uniform block bindings for a single shader stage static void SetShaderUniformBlockBinding(GLuint shader, const char* name, - Maxwell::ShaderStage binding, size_t expected_size) { + Maxwell::ShaderStage binding, std::size_t expected_size) { const GLuint ub_index = glGetUniformBlockIndex(shader, name); if (ub_index == GL_INVALID_INDEX) { return; @@ -36,7 +36,7 @@ static void SetShaderUniformBlockBinding(GLuint shader, const char* name, GLint ub_size = 0; glGetActiveUniformBlockiv(shader, ub_index, GL_UNIFORM_BLOCK_DATA_SIZE, &ub_size); - ASSERT_MSG(static_cast<size_t>(ub_size) == expected_size, + ASSERT_MSG(static_cast<std::size_t>(ub_size) == expected_size, "Uniform block size did not match! Got {}, expected {}", ub_size, expected_size); glUniformBlockBinding(shader, ub_index, static_cast<GLuint>(binding)); } @@ -85,23 +85,23 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type) SetShaderUniformBlockBindings(program.handle); } -GLuint CachedShader::GetProgramResourceIndex(const std::string& name) { - auto search{resource_cache.find(name)}; +GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) { + const auto search{resource_cache.find(buffer.GetHash())}; if (search == resource_cache.end()) { const GLuint index{ - glGetProgramResourceIndex(program.handle, GL_UNIFORM_BLOCK, name.c_str())}; - resource_cache[name] = index; + glGetProgramResourceIndex(program.handle, GL_UNIFORM_BLOCK, buffer.GetName().c_str())}; + resource_cache[buffer.GetHash()] = index; return index; } return search->second; } -GLint CachedShader::GetUniformLocation(const std::string& name) { - auto search{uniform_cache.find(name)}; +GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) { + const auto search{uniform_cache.find(sampler.GetHash())}; if (search == uniform_cache.end()) { - const GLint index{glGetUniformLocation(program.handle, name.c_str())}; - uniform_cache[name] = index; + const GLint index{glGetUniformLocation(program.handle, sampler.GetName().c_str())}; + uniform_cache[sampler.GetHash()] = index; return index; } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 759987604..9bafe43a9 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -4,8 +4,8 @@ #pragma once +#include <map> #include <memory> -#include <unordered_map> #include "common/common_types.h" #include "video_core/rasterizer_cache.h" @@ -28,7 +28,7 @@ public: } /// Gets the size of the shader in guest memory, required for cache management - size_t GetSizeInBytes() const { + std::size_t GetSizeInBytes() const { return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64); } @@ -43,10 +43,10 @@ public: } /// Gets the GL program resource location for the specified resource, caching as needed - GLuint GetProgramResourceIndex(const std::string& name); + GLuint GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer); /// Gets the GL uniform location for the specified resource, caching as needed - GLint GetUniformLocation(const std::string& name); + GLint GetUniformLocation(const GLShader::SamplerEntry& sampler); private: VAddr addr; @@ -55,8 +55,8 @@ private: GLShader::ShaderEntries entries; OGLProgram program; - std::unordered_map<std::string, GLuint> resource_cache; - std::unordered_map<std::string, GLint> uniform_cache; + std::map<u32, GLuint> resource_cache; + std::map<u32, GLint> uniform_cache; }; class ShaderCacheOpenGL final : public RasterizerCache<Shader> { diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 391c92d47..b3e95187e 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -12,6 +12,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "video_core/engines/shader_bytecode.h" +#include "video_core/engines/shader_header.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" @@ -26,7 +27,7 @@ using Tegra::Shader::Sampler; using Tegra::Shader::SubOp; constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; -constexpr u32 PROGRAM_HEADER_SIZE = 0x50; +constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header); class DecompileFail : public std::runtime_error { public: @@ -113,7 +114,7 @@ private: /// Scans a range of code for labels and determines the exit method. ExitMethod Scan(u32 begin, u32 end, std::set<u32>& labels) { - auto [iter, inserted] = + const auto [iter, inserted] = exit_method_map.emplace(std::make_pair(begin, end), ExitMethod::Undetermined); ExitMethod& exit_method = iter->second; if (!inserted) @@ -131,22 +132,22 @@ private: if (instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex)) { return exit_method = ExitMethod::AlwaysEnd; } else { - ExitMethod not_met = Scan(offset + 1, end, labels); + const ExitMethod not_met = Scan(offset + 1, end, labels); return exit_method = ParallelExit(ExitMethod::AlwaysEnd, not_met); } } case OpCode::Id::BRA: { - u32 target = offset + instr.bra.GetBranchTarget(); + const u32 target = offset + instr.bra.GetBranchTarget(); labels.insert(target); - ExitMethod no_jmp = Scan(offset + 1, end, labels); - ExitMethod jmp = Scan(target, end, labels); + const ExitMethod no_jmp = Scan(offset + 1, end, labels); + const ExitMethod jmp = Scan(target, end, labels); return exit_method = ParallelExit(no_jmp, jmp); } case OpCode::Id::SSY: { // The SSY instruction uses a similar encoding as the BRA instruction. ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported"); - u32 target = offset + instr.bra.GetBranchTarget(); + const u32 target = offset + instr.bra.GetBranchTarget(); labels.insert(target); // Continue scanning for an exit method. break; @@ -189,7 +190,7 @@ public: private: void AppendIndentation() { - shader_source.append(static_cast<size_t>(scope) * 4, ' '); + shader_source.append(static_cast<std::size_t>(scope) * 4, ' '); } std::string shader_source; @@ -208,7 +209,7 @@ public: UnsignedInteger, }; - GLSLRegister(size_t index, const std::string& suffix) : index{index}, suffix{suffix} {} + GLSLRegister(std::size_t index, const std::string& suffix) : index{index}, suffix{suffix} {} /// Gets the GLSL type string for a register static std::string GetTypeString() { @@ -226,15 +227,23 @@ public: } /// Returns the index of the register - size_t GetIndex() const { + std::size_t GetIndex() const { return index; } private: - const size_t index; + const std::size_t index; const std::string& suffix; }; +enum class InternalFlag : u64 { + ZeroFlag = 0, + CarryFlag = 1, + OverflowFlag = 2, + NaNFlag = 3, + Amount +}; + /** * Used to manage shader registers that are emulated with GLSL. This class keeps track of the state * of all registers (e.g. whether they are currently being used as Floats or Integers), and @@ -247,6 +256,7 @@ public: const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix) : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} { BuildRegisterList(); + BuildInputList(); } /** @@ -327,13 +337,19 @@ public: void SetRegisterToInteger(const Register& reg, bool is_signed, u64 elem, const std::string& value, u64 dest_num_components, u64 value_num_components, bool is_saturated = false, - u64 dest_elem = 0, Register::Size size = Register::Size::Word) { + u64 dest_elem = 0, Register::Size size = Register::Size::Word, + bool sets_cc = false) { ASSERT_MSG(!is_saturated, "Unimplemented"); const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"}; SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')', dest_num_components, value_num_components, dest_elem); + + if (sets_cc) { + const std::string zero_condition = "( " + ConvertIntegerSize(value, size) + " == 0 )"; + SetInternalFlag(InternalFlag::ZeroFlag, zero_condition); + } } /** @@ -343,12 +359,33 @@ public: * @param elem The element to use for the operation. * @param attribute The input attribute to use as the source value. */ - void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute) { - std::string dest = GetRegisterAsFloat(reg); - std::string src = GetInputAttribute(attribute) + GetSwizzle(elem); + void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute, + const Tegra::Shader::IpaMode& input_mode) { + const std::string dest = GetRegisterAsFloat(reg); + const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem); shader.AddLine(dest + " = " + src + ';'); } + std::string GetControlCode(const Tegra::Shader::ControlCode cc) const { + switch (cc) { + case Tegra::Shader::ControlCode::NEU: + return "!(" + GetInternalFlag(InternalFlag::ZeroFlag) + ')'; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented Control Code {}", static_cast<u32>(cc)); + UNREACHABLE(); + return "false"; + } + } + + std::string GetInternalFlag(const InternalFlag ii) const { + const u32 code = static_cast<u32>(ii); + return "internalFlag_" + std::to_string(code) + suffix; + } + + void SetInternalFlag(const InternalFlag ii, const std::string& value) const { + shader.AddLine(GetInternalFlag(ii) + " = " + value + ';'); + } + /** * Writes code that does a output attribute assignment to register operation. Output attributes * are stored as floats, so this may require conversion. @@ -357,8 +394,8 @@ public: * @param reg The register to use as the source value. */ void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) { - std::string dest = GetOutputAttribute(attribute); - std::string src = GetRegisterAsFloat(reg); + const std::string dest = GetOutputAttribute(attribute); + const std::string src = GetRegisterAsFloat(reg); if (!dest.empty()) { // Can happen with unknown/unimplemented output attributes, in which case we ignore the @@ -391,9 +428,9 @@ public: GLSLRegister::Type type) { declr_const_buffers[cbuf_index].MarkAsUsedIndirect(cbuf_index, stage); - std::string final_offset = fmt::format("({} + {})", index_str, offset / 4); - std::string value = 'c' + std::to_string(cbuf_index) + '[' + final_offset + " / 4][" + - final_offset + " % 4]"; + const std::string final_offset = fmt::format("({} + {})", index_str, offset / 4); + const std::string value = 'c' + std::to_string(cbuf_index) + '[' + final_offset + " / 4][" + + final_offset + " % 4]"; if (type == GLSLRegister::Type::Float) { return value; @@ -412,12 +449,19 @@ public: } declarations.AddNewLine(); - for (const auto& index : declr_input_attribute) { + for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) { + const InternalFlag code = static_cast<InternalFlag>(ii); + declarations.AddLine("bool " + GetInternalFlag(code) + " = false;"); + } + declarations.AddNewLine(); + + for (const auto element : declr_input_attribute) { // TODO(bunnei): Use proper number of elements for these - declarations.AddLine("layout(location = " + - std::to_string(static_cast<u32>(index) - - static_cast<u32>(Attribute::Index::Attribute_0)) + - ") in vec4 " + GetInputAttribute(index) + ';'); + u32 idx = + static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0); + declarations.AddLine("layout(location = " + std::to_string(idx) + ")" + + GetInputFlags(element.first) + "in vec4 " + + GetInputAttribute(element.first, element.second) + ';'); } declarations.AddNewLine(); @@ -440,13 +484,12 @@ public: } declarations.AddNewLine(); - // Append the sampler2D array for the used textures. - size_t num_samplers = GetSamplers().size(); - if (num_samplers > 0) { - declarations.AddLine("uniform sampler2D " + SamplerEntry::GetArrayName(stage) + '[' + - std::to_string(num_samplers) + "];"); - declarations.AddNewLine(); + const auto& samplers = GetSamplers(); + for (const auto& sampler : samplers) { + declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() + + ';'); } + declarations.AddNewLine(); } /// Returns a list of constant buffer declarations @@ -458,27 +501,29 @@ public: } /// Returns a list of samplers used in the shader - std::vector<SamplerEntry> GetSamplers() const { + const std::vector<SamplerEntry>& GetSamplers() const { return used_samplers; } /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if /// necessary. - std::string AccessSampler(const Sampler& sampler) { - size_t offset = static_cast<size_t>(sampler.index.Value()); + std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type, + bool is_array) { + const std::size_t offset = static_cast<std::size_t>(sampler.index.Value()); // If this sampler has already been used, return the existing mapping. - auto itr = + const auto itr = std::find_if(used_samplers.begin(), used_samplers.end(), [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; }); if (itr != used_samplers.end()) { + ASSERT(itr->GetType() == type && itr->IsArray() == is_array); return itr->GetName(); } // Otherwise create a new mapping for this sampler - size_t next_index = used_samplers.size(); - SamplerEntry entry{stage, offset, next_index}; + const std::size_t next_index = used_samplers.size(); + const SamplerEntry entry{stage, offset, next_index, type, is_array}; used_samplers.emplace_back(entry); return entry.GetName(); } @@ -527,16 +572,29 @@ private: void BuildRegisterList() { regs.reserve(Register::NumRegisters); - for (size_t index = 0; index < Register::NumRegisters; ++index) { + for (std::size_t index = 0; index < Register::NumRegisters; ++index) { regs.emplace_back(index, suffix); } } + void BuildInputList() { + const u32 size = static_cast<u32>(Attribute::Index::Attribute_31) - + static_cast<u32>(Attribute::Index::Attribute_0) + 1; + declr_input_attribute.reserve(size); + } + /// Generates code representing an input attribute register. - std::string GetInputAttribute(Attribute::Index attribute) { + std::string GetInputAttribute(Attribute::Index attribute, + const Tegra::Shader::IpaMode& input_mode) { switch (attribute) { case Attribute::Index::Position: - return "position"; + if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { + return "position"; + } else { + return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)"; + } + case Attribute::Index::PointCoord: + return "vec4(gl_PointCoord.x, gl_PointCoord.y, 0, 0)"; case Attribute::Index::TessCoordInstanceIDVertexID: // TODO(Subv): Find out what the values are for the first two elements when inside a // vertex shader, and what's the value of the fourth element when inside a Tess Eval @@ -552,7 +610,14 @@ private: static_cast<u32>(Attribute::Index::Attribute_0)}; if (attribute >= Attribute::Index::Attribute_0 && attribute <= Attribute::Index::Attribute_31) { - declr_input_attribute.insert(attribute); + if (declr_input_attribute.count(attribute) == 0) { + declr_input_attribute[attribute] = input_mode; + } else { + if (declr_input_attribute[attribute] != input_mode) { + LOG_CRITICAL(HW_GPU, "Same Input multiple input modes"); + UNREACHABLE(); + } + } return "input_attribute_" + std::to_string(index); } @@ -563,6 +628,49 @@ private: return "vec4(0, 0, 0, 0)"; } + std::string GetInputFlags(const Attribute::Index attribute) { + const Tegra::Shader::IpaSampleMode sample_mode = + declr_input_attribute[attribute].sampling_mode; + const Tegra::Shader::IpaInterpMode interp_mode = + declr_input_attribute[attribute].interpolation_mode; + std::string out; + switch (interp_mode) { + case Tegra::Shader::IpaInterpMode::Flat: { + out += "flat "; + break; + } + case Tegra::Shader::IpaInterpMode::Linear: { + out += "noperspective "; + break; + } + case Tegra::Shader::IpaInterpMode::Perspective: { + // Default, Smooth + break; + } + default: { + LOG_CRITICAL(HW_GPU, "Unhandled Ipa InterpMode: {}", static_cast<u32>(interp_mode)); + UNREACHABLE(); + } + } + switch (sample_mode) { + case Tegra::Shader::IpaSampleMode::Centroid: { + // Note not implemented, it can be implemented with the "centroid " keyword in glsl; + LOG_CRITICAL(HW_GPU, "Ipa Sampler Mode: centroid, not implemented"); + UNREACHABLE(); + break; + } + case Tegra::Shader::IpaSampleMode::Default: { + // Default, n/a + break; + } + default: { + LOG_CRITICAL(HW_GPU, "Unhandled Ipa SampleMode: {}", static_cast<u32>(sample_mode)); + UNREACHABLE(); + } + } + return out; + } + /// Generates code representing an output attribute register. std::string GetOutputAttribute(Attribute::Index attribute) { switch (attribute) { @@ -593,7 +701,7 @@ private: ShaderWriter& shader; ShaderWriter& declarations; std::vector<GLSLRegister> regs; - std::set<Attribute::Index> declr_input_attribute; + std::unordered_map<Attribute::Index, Tegra::Shader::IpaMode> declr_input_attribute; std::set<Attribute::Index> declr_output_attribute; std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers; std::vector<SamplerEntry> used_samplers; @@ -607,7 +715,7 @@ public: u32 main_offset, Maxwell3D::Regs::ShaderStage stage, const std::string& suffix) : subroutines(subroutines), program_code(program_code), main_offset(main_offset), stage(stage), suffix(suffix) { - + std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header)); Generate(suffix); } @@ -621,26 +729,9 @@ public: } private: - // Shader program header for a Fragment Shader. - struct FragmentHeader { - INSERT_PADDING_WORDS(5); - INSERT_PADDING_WORDS(13); - u32 enabled_color_outputs; - union { - BitField<0, 1, u32> writes_samplemask; - BitField<1, 1, u32> writes_depth; - }; - - bool IsColorComponentOutputEnabled(u32 render_target, u32 component) const { - u32 bit = render_target * 4 + component; - return enabled_color_outputs & (1 << bit); - } - }; - static_assert(sizeof(FragmentHeader) == PROGRAM_HEADER_SIZE, "FragmentHeader size is wrong"); - /// Gets the Subroutine object corresponding to the specified address. const Subroutine& GetSubroutine(u32 begin, u32 end) const { - auto iter = subroutines.find(Subroutine{begin, end, suffix}); + const auto iter = subroutines.find(Subroutine{begin, end, suffix}); ASSERT(iter != subroutines.end()); return *iter; } @@ -656,8 +747,8 @@ private: } /// Generates code representing a texture sampler. - std::string GetSampler(const Sampler& sampler) { - return regs.AccessSampler(sampler); + std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) { + return regs.AccessSampler(sampler, type, is_array); } /** @@ -685,7 +776,7 @@ private: // Can't assign to the constant predicate. ASSERT(pred != static_cast<u64>(Pred::UnusedIndex)); - std::string variable = 'p' + std::to_string(pred) + '_' + suffix; + const std::string variable = 'p' + std::to_string(pred) + '_' + suffix; shader.AddLine(variable + " = " + value + ';'); declr_predicates.insert(std::move(variable)); } @@ -795,7 +886,7 @@ private: */ bool IsSchedInstruction(u32 offset) const { // sched instructions appear once every 4 instructions. - static constexpr size_t SchedPeriod = 4; + static constexpr std::size_t SchedPeriod = 4; u32 absolute_offset = offset - main_offset; return (absolute_offset % SchedPeriod) == 0; @@ -863,7 +954,7 @@ private: std::string result; result += '('; - for (size_t i = 0; i < shift_amounts.size(); ++i) { + for (std::size_t i = 0; i < shift_amounts.size(); ++i) { if (i) result += '|'; result += "(((" + imm_lut + " >> (((" + op_c + " >> " + shift_amounts[i] + @@ -887,7 +978,7 @@ private: // TEXS has two destination registers and a swizzle. The first two elements in the swizzle // go into gpr0+0 and gpr0+1, and the rest goes into gpr28+0 and gpr28+1 - size_t written_components = 0; + std::size_t written_components = 0; for (u32 component = 0; component < 4; ++component) { if (!instr.texs.IsComponentEnabled(component)) { continue; @@ -941,10 +1032,8 @@ private: /// Writes the output values from a fragment shader to the corresponding GLSL output variables. void EmitFragmentOutputsWrite() { ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment); - FragmentHeader header; - std::memcpy(&header, program_code.data(), PROGRAM_HEADER_SIZE); - ASSERT_MSG(header.writes_samplemask == 0, "Samplemask write is unimplemented"); + ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented"); // Write the color outputs using the data in the shader registers, disabled // rendertargets/components are skipped in the register assignment. @@ -953,18 +1042,22 @@ private: ++render_target) { // TODO(Subv): Figure out how dual-source blending is configured in the Switch. for (u32 component = 0; component < 4; ++component) { - if (header.IsColorComponentOutputEnabled(render_target, component)) { - shader.AddLine(fmt::format("color[{}][{}] = {};", render_target, component, + if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { + shader.AddLine(fmt::format("FragColor{}[{}] = {};", render_target, component, regs.GetRegisterAsFloat(current_reg))); ++current_reg; } } } - if (header.writes_depth) { + if (header.ps.omap.depth) { // The depth output is always 2 registers after the last color output, and current_reg // already contains one past the last color register. - shader.AddLine("gl_FragDepth = " + regs.GetRegisterAsFloat(current_reg + 1) + ';'); + + shader.AddLine( + "gl_FragDepth = " + + regs.GetRegisterAsFloat(static_cast<Tegra::Shader::Register>(current_reg) + 1) + + ';'); } } @@ -1038,6 +1131,15 @@ private: case OpCode::Id::FMUL_R: case OpCode::Id::FMUL_IMM: { // FMUL does not have 'abs' bits and only the second operand has a 'neg' bit. + ASSERT_MSG(instr.fmul.tab5cb8_2 == 0, "FMUL tab5cb8_2({}) is not implemented", + instr.fmul.tab5cb8_2.Value()); + ASSERT_MSG(instr.fmul.tab5c68_1 == 0, "FMUL tab5cb8_1({}) is not implemented", + instr.fmul.tab5c68_1.Value()); + ASSERT_MSG(instr.fmul.tab5c68_0 == 1, "FMUL tab5cb8_0({}) is not implemented", + instr.fmul.tab5c68_0 + .Value()); // SMO typical sends 1 here which seems to be the default + ASSERT_MSG(instr.fmul.cc == 0, "FMUL cc is not implemented"); + op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b); regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1, instr.alu.saturate_d); @@ -1357,7 +1459,7 @@ private: if (instr.alu_integer.negate_b) op_b = "-(" + op_b + ')'; - std::string shift = std::to_string(instr.alu_integer.shift_amount.Value()); + const std::string shift = std::to_string(instr.alu_integer.shift_amount.Value()); regs.SetRegisterToInteger(instr.gpr0, true, 0, "((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1); @@ -1375,7 +1477,7 @@ private: case OpCode::Id::SEL_C: case OpCode::Id::SEL_R: case OpCode::Id::SEL_IMM: { - std::string condition = + const std::string condition = GetPredicateCondition(instr.sel.pred, instr.sel.neg_pred != 0); regs.SetRegisterToInteger(instr.gpr0, true, 0, '(' + condition + ") ? " + op_a + " : " + op_b, 1, 1); @@ -1397,8 +1499,9 @@ private: case OpCode::Id::LOP3_C: case OpCode::Id::LOP3_R: case OpCode::Id::LOP3_IMM: { - std::string op_c = regs.GetRegisterAsInteger(instr.gpr39); + const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39); std::string lut; + if (opcode->GetId() == OpCode::Id::LOP3_R) { lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')'; } else { @@ -1413,15 +1516,80 @@ private: case OpCode::Id::IMNMX_IMM: { ASSERT_MSG(instr.imnmx.exchange == Tegra::Shader::IMinMaxExchange::None, "Unimplemented"); - std::string condition = + const std::string condition = GetPredicateCondition(instr.imnmx.pred, instr.imnmx.negate_pred != 0); - std::string parameters = op_a + ',' + op_b; + const std::string parameters = op_a + ',' + op_b; regs.SetRegisterToInteger(instr.gpr0, instr.imnmx.is_signed, 0, '(' + condition + ") ? min(" + parameters + ") : max(" + parameters + ')', 1, 1); break; } + case OpCode::Id::LEA_R2: + case OpCode::Id::LEA_R1: + case OpCode::Id::LEA_IMM: + case OpCode::Id::LEA_RZ: + case OpCode::Id::LEA_HI: { + std::string op_c; + + switch (opcode->GetId()) { + case OpCode::Id::LEA_R2: { + op_a = regs.GetRegisterAsInteger(instr.gpr20); + op_b = regs.GetRegisterAsInteger(instr.gpr39); + op_c = std::to_string(instr.lea.r2.entry_a); + break; + } + + case OpCode::Id::LEA_R1: { + const bool neg = instr.lea.r1.neg != 0; + op_a = regs.GetRegisterAsInteger(instr.gpr8); + if (neg) + op_a = "-(" + op_a + ')'; + op_b = regs.GetRegisterAsInteger(instr.gpr20); + op_c = std::to_string(instr.lea.r1.entry_a); + break; + } + + case OpCode::Id::LEA_IMM: { + const bool neg = instr.lea.imm.neg != 0; + op_b = regs.GetRegisterAsInteger(instr.gpr8); + if (neg) + op_b = "-(" + op_b + ')'; + op_a = std::to_string(instr.lea.imm.entry_a); + op_c = std::to_string(instr.lea.imm.entry_b); + break; + } + + case OpCode::Id::LEA_RZ: { + const bool neg = instr.lea.rz.neg != 0; + op_b = regs.GetRegisterAsInteger(instr.gpr8); + if (neg) + op_b = "-(" + op_b + ')'; + op_a = regs.GetUniform(instr.lea.rz.cb_index, instr.lea.rz.cb_offset, + GLSLRegister::Type::Integer); + op_c = std::to_string(instr.lea.rz.entry_a); + + break; + } + + case OpCode::Id::LEA_HI: + default: { + op_b = regs.GetRegisterAsInteger(instr.gpr8); + op_a = std::to_string(instr.lea.imm.entry_a); + op_c = std::to_string(instr.lea.imm.entry_b); + LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}", opcode->GetName()); + UNREACHABLE(); + } + } + if (instr.lea.pred48 != static_cast<u64>(Pred::UnusedIndex)) { + LOG_ERROR(HW_GPU, "Unhandled LEA Predicate"); + UNREACHABLE(); + } + const std::string value = '(' + op_a + " + (" + op_b + "*(1 << " + op_c + ")))"; + regs.SetRegisterToInteger(instr.gpr0, true, 0, value, 1, 1); + + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}", opcode->GetName()); @@ -1432,10 +1600,16 @@ private: break; } case OpCode::Type::Ffma: { - std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); + const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); std::string op_b = instr.ffma.negate_b ? "-" : ""; std::string op_c = instr.ffma.negate_c ? "-" : ""; + ASSERT_MSG(instr.ffma.cc == 0, "FFMA cc not implemented"); + ASSERT_MSG(instr.ffma.tab5980_0 == 1, "FFMA tab5980_0({}) not implemented", + instr.ffma.tab5980_0.Value()); // Seems to be 1 by default based on SMO + ASSERT_MSG(instr.ffma.tab5980_1 == 0, "FFMA tab5980_1({}) not implemented", + instr.ffma.tab5980_1.Value()); + switch (opcode->GetId()) { case OpCode::Id::FFMA_CR: { op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, @@ -1486,7 +1660,8 @@ private: } regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1, - 1, instr.alu.saturate_d, 0, instr.conversion.dest_size); + 1, instr.alu.saturate_d, 0, instr.conversion.dest_size, + instr.generates_cc.Value() != 0); break; } case OpCode::Id::I2F_R: @@ -1616,9 +1791,34 @@ private: case OpCode::Type::Memory: { switch (opcode->GetId()) { case OpCode::Id::LD_A: { - ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested"); - regs.SetRegisterToInputAttibute(instr.gpr0, instr.attribute.fmt20.element, - instr.attribute.fmt20.index); + // Note: Shouldn't this be interp mode flat? As in no interpolation made. + ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex, + "Indirect attribute loads are not supported"); + ASSERT_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) == 0, + "Unaligned attribute loads are not supported"); + + Tegra::Shader::IpaMode input_mode{Tegra::Shader::IpaInterpMode::Perspective, + Tegra::Shader::IpaSampleMode::Default}; + + u64 next_element = instr.attribute.fmt20.element; + u64 next_index = static_cast<u64>(instr.attribute.fmt20.index.Value()); + + const auto LoadNextElement = [&](u32 reg_offset) { + regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element, + static_cast<Attribute::Index>(next_index), + input_mode); + + // Load the next attribute element into the following register. If the element + // to load goes beyond the vec4 size, load the first element of the next + // attribute. + next_element = (next_element + 1) % 4; + next_index = next_index + (next_element == 0 ? 1 : 0); + }; + + const u32 num_words = static_cast<u32>(instr.attribute.fmt20.size.Value()) + 1; + for (u32 reg_offset = 0; reg_offset < num_words; ++reg_offset) { + LoadNextElement(reg_offset); + } break; } case OpCode::Id::LD_C: { @@ -1632,7 +1832,7 @@ private: shader.AddLine("uint index = (" + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " / 4) & (MAX_CONSTBUFFER_ELEMENTS - 1);"); - std::string op_a = + const std::string op_a = regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 0, "index", GLSLRegister::Type::Float); @@ -1642,7 +1842,7 @@ private: break; case Tegra::Shader::UniformType::Double: { - std::string op_b = + const std::string op_b = regs.GetUniformIndirect(instr.cbuf36.index, instr.cbuf36.offset + 4, "index", GLSLRegister::Type::Float); regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1); @@ -1660,25 +1860,111 @@ private: break; } case OpCode::Id::ST_A: { - ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested"); - regs.SetOutputAttributeToRegister(instr.attribute.fmt20.index, - instr.attribute.fmt20.element, instr.gpr0); + ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex, + "Indirect attribute loads are not supported"); + ASSERT_MSG((instr.attribute.fmt20.immediate.Value() % sizeof(u32)) == 0, + "Unaligned attribute loads are not supported"); + + u64 next_element = instr.attribute.fmt20.element; + u64 next_index = static_cast<u64>(instr.attribute.fmt20.index.Value()); + + const auto StoreNextElement = [&](u32 reg_offset) { + regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index), + next_element, + instr.gpr0.Value() + reg_offset); + + // Load the next attribute element into the following register. If the element + // to load goes beyond the vec4 size, load the first element of the next + // attribute. + next_element = (next_element + 1) % 4; + next_index = next_index + (next_element == 0 ? 1 : 0); + }; + + const u32 num_words = static_cast<u32>(instr.attribute.fmt20.size.Value()) + 1; + for (u32 reg_offset = 0; reg_offset < num_words; ++reg_offset) { + StoreNextElement(reg_offset); + } + break; } case OpCode::Id::TEX: { - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented"); + Tegra::Shader::TextureType texture_type{instr.tex.texture_type}; + std::string coord; + + ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), + "DC is not implemented"); + + switch (texture_type) { + case Tegra::Shader::TextureType::Texture1D: { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + coord = "float coords = " + x + ';'; + break; + } + case Tegra::Shader::TextureType::Texture2D: { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(texture_type)); + UNREACHABLE(); + + // Fallback to interpreting as a 2D texture for now + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + texture_type = Tegra::Shader::TextureType::Texture2D; + } + // TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias + // or lod. + const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20); + + const std::string sampler = GetSampler(instr.sampler, texture_type, false); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. + shader.AddLine("{"); ++shader.scope; shader.AddLine(coord); - const std::string texture = "texture(" + sampler + ", coords)"; + std::string texture; - size_t dest_elem{}; - for (size_t elem = 0; elem < 4; ++elem) { + switch (instr.tex.process_mode) { + case Tegra::Shader::TextureProcessMode::None: { + texture = "texture(" + sampler + ", coords)"; + break; + } + case Tegra::Shader::TextureProcessMode::LZ: { + texture = "textureLod(" + sampler + ", coords, 0.0)"; + break; + } + case Tegra::Shader::TextureProcessMode::LB: + case Tegra::Shader::TextureProcessMode::LBA: { + // TODO: Figure if A suffix changes the equation at all. + texture = "texture(" + sampler + ", coords, " + op_c + ')'; + break; + } + case Tegra::Shader::TextureProcessMode::LL: + case Tegra::Shader::TextureProcessMode::LLA: { + // TODO: Figure if A suffix changes the equation at all. + texture = "textureLod(" + sampler + ", coords, " + op_c + ')'; + break; + } + default: { + texture = "texture(" + sampler + ", coords)"; + LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}", + static_cast<u32>(instr.tex.process_mode.Value())); + UNREACHABLE(); + } + } + std::size_t dest_elem{}; + for (std::size_t elem = 0; elem < 4; ++elem) { if (!instr.tex.IsComponentEnabled(elem)) { // Skip disabled components continue; @@ -1691,20 +1977,77 @@ private: break; } case OpCode::Id::TEXS: { - const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); - const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; + std::string coord; + Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()}; + bool is_array{instr.texs.IsArrayTexture()}; + + ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), + "DC is not implemented"); + + switch (texture_type) { + case Tegra::Shader::TextureType::Texture2D: { + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + } + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(texture_type)); + UNREACHABLE(); + // Fallback to interpreting as a 2D texture for now + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + texture_type = Tegra::Shader::TextureType::Texture2D; + is_array = false; + } + const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); const std::string texture = "texture(" + sampler + ", coords)"; WriteTexsInstruction(instr, coord, texture); break; } case OpCode::Id::TLDS: { - const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8); - const std::string op_b = regs.GetRegisterAsInteger(instr.gpr20); - const std::string sampler = GetSampler(instr.sampler); - const std::string coord = "ivec2 coords = ivec2(" + op_a + ", " + op_b + ");"; + ASSERT(instr.tlds.GetTextureType() == Tegra::Shader::TextureType::Texture2D); + ASSERT(instr.tlds.IsArrayTexture() == false); + std::string coord; + + ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), + "MZ is not implemented"); + + switch (instr.tlds.GetTextureType()) { + case Tegra::Shader::TextureType::Texture2D: { + if (instr.tlds.IsArrayTexture()) { + LOG_CRITICAL(HW_GPU, "Unhandled 2d array texture"); + UNREACHABLE(); + } else { + const std::string x = regs.GetRegisterAsInteger(instr.gpr8); + const std::string y = regs.GetRegisterAsInteger(instr.gpr20); + coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; + } + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(instr.tlds.GetTextureType())); + UNREACHABLE(); + } + const std::string sampler = GetSampler(instr.sampler, instr.tlds.GetTextureType(), + instr.tlds.IsArrayTexture()); const std::string texture = "texelFetch(" + sampler + ", coords, 0)"; WriteTexsInstruction(instr, coord, texture); break; @@ -1712,12 +2055,23 @@ private: case OpCode::Id::TLD4: { ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D); ASSERT(instr.tld4.array == 0); - std::string coord{}; + std::string coord; + + ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), + "DC is not implemented"); + ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), + "NDV is not implemented"); + ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP), + "PTP is not implemented"); switch (instr.tld4.texture_type) { case Tegra::Shader::TextureType::Texture2D: { - std::string x = regs.GetRegisterAsFloat(instr.gpr8); - std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); coord = "vec2 coords = vec2(" + x + ", " + y + ");"; break; } @@ -1727,7 +2081,8 @@ private: UNREACHABLE(); } - const std::string sampler = GetSampler(instr.sampler); + const std::string sampler = + GetSampler(instr.sampler, instr.tld4.texture_type, false); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. shader.AddLine("{"); @@ -1736,8 +2091,8 @@ private: const std::string texture = "textureGather(" + sampler + ", coords, " + std::to_string(instr.tld4.component) + ')'; - size_t dest_elem{}; - for (size_t elem = 0; elem < 4; ++elem) { + std::size_t dest_elem{}; + for (std::size_t elem = 0; elem < 4; ++elem) { if (!instr.tex.IsComponentEnabled(elem)) { // Skip disabled components continue; @@ -1750,16 +2105,100 @@ private: break; } case OpCode::Id::TLD4S: { + ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI), + "AOFFI is not implemented"); + ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC), + "DC is not implemented"); + const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20); // TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction. - const std::string sampler = GetSampler(instr.sampler); + const std::string sampler = + GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");"; const std::string texture = "textureGather(" + sampler + ", coords, " + std::to_string(instr.tld4s.component) + ')'; WriteTexsInstruction(instr, coord, texture); break; } + case OpCode::Id::TXQ: { + ASSERT_MSG(!instr.txq.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + + // TODO: the new commits on the texture refactor, change the way samplers work. + // Sadly, not all texture instructions specify the type of texture their sampler + // uses. This must be fixed at a later instance. + const std::string sampler = + GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false); + switch (instr.txq.query_type) { + case Tegra::Shader::TextureQueryType::Dimension: { + const std::string texture = "textureQueryLevels(" + sampler + ')'; + regs.SetRegisterToInteger(instr.gpr0, true, 0, texture, 1, 1); + break; + } + default: { + LOG_CRITICAL(HW_GPU, "Unhandled texture query type: {}", + static_cast<u32>(instr.txq.query_type.Value())); + UNREACHABLE(); + } + } + break; + } + case OpCode::Id::TMML: { + ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), + "NODEP is not implemented"); + ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV), + "NDV is not implemented"); + + const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); + const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const bool is_array = instr.tmml.array != 0; + auto texture_type = instr.tmml.texture_type.Value(); + const std::string sampler = GetSampler(instr.sampler, texture_type, is_array); + + // TODO: add coordinates for different samplers once other texture types are + // implemented. + std::string coord; + switch (texture_type) { + case Tegra::Shader::TextureType::Texture1D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + coord = "float coords = " + x + ';'; + break; + } + case Tegra::Shader::TextureType::Texture2D: { + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + break; + } + default: + LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", + static_cast<u32>(texture_type)); + UNREACHABLE(); + + // Fallback to interpreting as a 2D texture for now + std::string x = regs.GetRegisterAsFloat(instr.gpr8); + std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + texture_type = Tegra::Shader::TextureType::Texture2D; + } + // Add an extra scope and declare the texture coords inside to prevent + // overwriting them in case they are used as outputs of the texs instruction. + shader.AddLine('{'); + ++shader.scope; + shader.AddLine(coord); + const std::string texture = "textureQueryLod(" + sampler + ", coords)"; + const std::string tmp = "vec2 tmp = " + texture + "*vec2(256.0, 256.0);"; + shader.AddLine(tmp); + + regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(tmp.y)", 1, 1); + regs.SetRegisterToInteger(instr.gpr0.Value() + 1, false, 0, "uint(tmp.x)", 1, 1); + --shader.scope; + shader.AddLine('}'); + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName()); UNREACHABLE(); @@ -1799,12 +2238,12 @@ private: // We can't use the constant predicate as destination. ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); - std::string second_pred = + const std::string second_pred = GetPredicateCondition(instr.fsetp.pred39, instr.fsetp.neg_pred != 0); - std::string combiner = GetPredicateCombiner(instr.fsetp.op); + const std::string combiner = GetPredicateCombiner(instr.fsetp.op); - std::string predicate = GetPredicateComparison(instr.fsetp.cond, op_a, op_b); + const std::string predicate = GetPredicateComparison(instr.fsetp.cond, op_a, op_b); // Set the primary predicate to the result of Predicate OP SecondPredicate SetPredicate(instr.fsetp.pred3, '(' + predicate + ") " + combiner + " (" + second_pred + ')'); @@ -1818,7 +2257,8 @@ private: break; } case OpCode::Type::IntegerSetPredicate: { - std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed); + const std::string op_a = + regs.GetRegisterAsInteger(instr.gpr8, 0, instr.isetp.is_signed); std::string op_b; if (instr.is_b_imm) { @@ -1835,12 +2275,12 @@ private: // We can't use the constant predicate as destination. ASSERT(instr.isetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); - std::string second_pred = + const std::string second_pred = GetPredicateCondition(instr.isetp.pred39, instr.isetp.neg_pred != 0); - std::string combiner = GetPredicateCombiner(instr.isetp.op); + const std::string combiner = GetPredicateCombiner(instr.isetp.op); - std::string predicate = GetPredicateComparison(instr.isetp.cond, op_a, op_b); + const std::string predicate = GetPredicateComparison(instr.isetp.cond, op_a, op_b); // Set the primary predicate to the result of Predicate OP SecondPredicate SetPredicate(instr.isetp.pred3, '(' + predicate + ") " + combiner + " (" + second_pred + ')'); @@ -1853,32 +2293,80 @@ private: } break; } + case OpCode::Type::PredicateSetRegister: { + const std::string op_a = + GetPredicateCondition(instr.pset.pred12, instr.pset.neg_pred12 != 0); + const std::string op_b = + GetPredicateCondition(instr.pset.pred29, instr.pset.neg_pred29 != 0); + + const std::string second_pred = + GetPredicateCondition(instr.pset.pred39, instr.pset.neg_pred39 != 0); + + const std::string combiner = GetPredicateCombiner(instr.pset.op); + + const std::string predicate = + '(' + op_a + ") " + GetPredicateCombiner(instr.pset.cond) + " (" + op_b + ')'; + const std::string result = '(' + predicate + ") " + combiner + " (" + second_pred + ')'; + if (instr.pset.bf == 0) { + const std::string value = '(' + result + ") ? 0xFFFFFFFF : 0"; + regs.SetRegisterToInteger(instr.gpr0, false, 0, value, 1, 1); + } else { + const std::string value = '(' + result + ") ? 1.0 : 0.0"; + regs.SetRegisterToFloat(instr.gpr0, 0, value, 1, 1); + } + + break; + } case OpCode::Type::PredicateSetPredicate: { - std::string op_a = - GetPredicateCondition(instr.psetp.pred12, instr.psetp.neg_pred12 != 0); - std::string op_b = - GetPredicateCondition(instr.psetp.pred29, instr.psetp.neg_pred29 != 0); + switch (opcode->GetId()) { + case OpCode::Id::PSETP: { + const std::string op_a = + GetPredicateCondition(instr.psetp.pred12, instr.psetp.neg_pred12 != 0); + const std::string op_b = + GetPredicateCondition(instr.psetp.pred29, instr.psetp.neg_pred29 != 0); - // We can't use the constant predicate as destination. - ASSERT(instr.psetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); + // We can't use the constant predicate as destination. + ASSERT(instr.psetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); - std::string second_pred = - GetPredicateCondition(instr.psetp.pred39, instr.psetp.neg_pred39 != 0); + const std::string second_pred = + GetPredicateCondition(instr.psetp.pred39, instr.psetp.neg_pred39 != 0); - std::string combiner = GetPredicateCombiner(instr.psetp.op); + const std::string combiner = GetPredicateCombiner(instr.psetp.op); - std::string predicate = - '(' + op_a + ") " + GetPredicateCombiner(instr.psetp.cond) + " (" + op_b + ')'; + const std::string predicate = + '(' + op_a + ") " + GetPredicateCombiner(instr.psetp.cond) + " (" + op_b + ')'; - // Set the primary predicate to the result of Predicate OP SecondPredicate - SetPredicate(instr.psetp.pred3, - '(' + predicate + ") " + combiner + " (" + second_pred + ')'); + // Set the primary predicate to the result of Predicate OP SecondPredicate + SetPredicate(instr.psetp.pred3, + '(' + predicate + ") " + combiner + " (" + second_pred + ')'); - if (instr.psetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { - // Set the secondary predicate to the result of !Predicate OP SecondPredicate, - // if enabled - SetPredicate(instr.psetp.pred0, - "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + if (instr.psetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + // Set the secondary predicate to the result of !Predicate OP SecondPredicate, + // if enabled + SetPredicate(instr.psetp.pred0, + "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + } + break; + } + case OpCode::Id::CSETP: { + const std::string pred = + GetPredicateCondition(instr.csetp.pred39, instr.csetp.neg_pred39 != 0); + const std::string combiner = GetPredicateCombiner(instr.csetp.op); + const std::string controlCode = regs.GetControlCode(instr.csetp.cc); + if (instr.csetp.pred3 != static_cast<u64>(Pred::UnusedIndex)) { + SetPredicate(instr.csetp.pred3, + '(' + controlCode + ") " + combiner + " (" + pred + ')'); + } + if (instr.csetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + SetPredicate(instr.csetp.pred0, + "!(" + controlCode + ") " + combiner + " (" + pred + ')'); + } + break; + } + default: { + LOG_CRITICAL(HW_GPU, "Unhandled predicate instruction: {}", opcode->GetName()); + UNREACHABLE(); + } } break; } @@ -1893,7 +2381,7 @@ private: std::string op_b = instr.fset.neg_b ? "-" : ""; if (instr.is_b_imm) { - std::string imm = GetImmediate19(instr); + const std::string imm = GetImmediate19(instr); if (instr.fset.neg_imm) op_b += "(-" + imm + ')'; else @@ -1913,13 +2401,14 @@ private: // The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the // condition is true, and to 0 otherwise. - std::string second_pred = + const std::string second_pred = GetPredicateCondition(instr.fset.pred39, instr.fset.neg_pred != 0); - std::string combiner = GetPredicateCombiner(instr.fset.op); + const std::string combiner = GetPredicateCombiner(instr.fset.op); - std::string predicate = "((" + GetPredicateComparison(instr.fset.cond, op_a, op_b) + - ") " + combiner + " (" + second_pred + "))"; + const std::string predicate = "((" + + GetPredicateComparison(instr.fset.cond, op_a, op_b) + + ") " + combiner + " (" + second_pred + "))"; if (instr.fset.bf) { regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1); @@ -1930,7 +2419,7 @@ private: break; } case OpCode::Type::IntegerSet: { - std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.iset.is_signed); + const std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, instr.iset.is_signed); std::string op_b; @@ -1947,13 +2436,14 @@ private: // The iset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the // condition is true, and to 0 otherwise. - std::string second_pred = + const std::string second_pred = GetPredicateCondition(instr.iset.pred39, instr.iset.neg_pred != 0); - std::string combiner = GetPredicateCombiner(instr.iset.op); + const std::string combiner = GetPredicateCombiner(instr.iset.op); - std::string predicate = "((" + GetPredicateComparison(instr.iset.cond, op_a, op_b) + - ") " + combiner + " (" + second_pred + "))"; + const std::string predicate = "((" + + GetPredicateComparison(instr.iset.cond, op_a, op_b) + + ") " + combiner + " (" + second_pred + "))"; if (instr.iset.bf) { regs.SetRegisterToFloat(instr.gpr0, 0, predicate + " ? 1.0 : 0.0", 1, 1); @@ -2103,45 +2593,22 @@ private: case OpCode::Id::BRA: { ASSERT_MSG(instr.bra.constant_buffer == 0, "BRA with constant buffers are not implemented"); - u32 target = offset + instr.bra.GetBranchTarget(); + const u32 target = offset + instr.bra.GetBranchTarget(); shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }"); break; } case OpCode::Id::IPA: { const auto& attribute = instr.attribute.fmt28; const auto& reg = instr.gpr0; - switch (instr.ipa.mode) { - case Tegra::Shader::IpaMode::Pass: - if (stage == Maxwell3D::Regs::ShaderStage::Fragment && - attribute.index == Attribute::Index::Position) { - switch (attribute.element) { - case 0: - shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.x;"); - break; - case 1: - shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.y;"); - break; - case 2: - shader.AddLine(regs.GetRegisterAsFloat(reg) + " = gl_FragCoord.z;"); - break; - case 3: - shader.AddLine(regs.GetRegisterAsFloat(reg) + " = 1.0;"); - break; - } - } else { - regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index); - } - break; - case Tegra::Shader::IpaMode::None: - regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index); - break; - default: - LOG_CRITICAL(HW_GPU, "Unhandled IPA mode: {}", - static_cast<u32>(instr.ipa.mode.Value())); - UNREACHABLE(); - regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index); - } + Tegra::Shader::IpaMode input_mode{instr.ipa.interp_mode.Value(), + instr.ipa.sample_mode.Value()}; + regs.SetRegisterToInputAttibute(reg, attribute.element, attribute.index, + input_mode); + + if (instr.ipa.saturate) { + regs.SetRegisterToFloat(reg, 0, regs.GetRegisterAsFloat(reg), 1, 1, true); + } break; } case OpCode::Id::SSY: { @@ -2150,7 +2617,7 @@ private: // has a similar structure to the BRA opcode. ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported"); - u32 target = offset + instr.bra.GetBranchTarget(); + const u32 target = offset + instr.bra.GetBranchTarget(); EmitPushToSSYStack(target); break; } @@ -2244,10 +2711,10 @@ private: shader.AddLine("case " + std::to_string(label) + "u: {"); ++shader.scope; - auto next_it = labels.lower_bound(label + 1); - u32 next_label = next_it == labels.end() ? subroutine.end : *next_it; + const auto next_it = labels.lower_bound(label + 1); + const u32 next_label = next_it == labels.end() ? subroutine.end : *next_it; - u32 compile_end = CompileRange(label, next_label); + const u32 compile_end = CompileRange(label, next_label); if (compile_end > next_label && compile_end != PROGRAM_END) { // This happens only when there is a label inside a IF/LOOP block shader.AddLine(" jmp_to = " + std::to_string(compile_end) + "u; break; }"); @@ -2289,6 +2756,7 @@ private: private: const std::set<Subroutine>& subroutines; const ProgramCode& program_code; + Tegra::Shader::Header header; const u32 main_offset; Maxwell3D::Regs::ShaderStage stage; const std::string& suffix; @@ -2310,7 +2778,8 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, Maxwell3D::Regs::ShaderStage stage, const std::string& suffix) { try { - auto subroutines = ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines(); + const auto subroutines = + ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines(); GLSLGenerator generator(subroutines, program_code, main_offset, stage, suffix); return ProgramResult{generator.GetShaderCode(), generator.GetEntries()}; } catch (const DecompileFail& exception) { diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 6ca05945e..b0466c18f 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -42,6 +42,7 @@ layout (std140) uniform vs_config { }; void main() { + position = vec4(0.0, 0.0, 0.0, 0.0); exec_vertex(); )"; @@ -87,7 +88,14 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) { .get_value_or({}); out += R"( in vec4 position; -layout(location = 0) out vec4 color[8]; +layout(location = 0) out vec4 FragColor0; +layout(location = 1) out vec4 FragColor1; +layout(location = 2) out vec4 FragColor2; +layout(location = 3) out vec4 FragColor3; +layout(location = 4) out vec4 FragColor4; +layout(location = 5) out vec4 FragColor5; +layout(location = 6) out vec4 FragColor6; +layout(location = 7) out vec4 FragColor7; layout (std140) uniform fs_config { vec4 viewport_flip; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index c788099d4..d53b93ad5 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -9,10 +9,11 @@ #include <vector> #include "common/common_types.h" +#include "video_core/engines/shader_bytecode.h" namespace OpenGL::GLShader { -constexpr size_t MAX_PROGRAM_CODE_LENGTH{0x1000}; +constexpr std::size_t MAX_PROGRAM_CODE_LENGTH{0x1000}; using ProgramCode = std::vector<u64>; class ConstBufferEntry { @@ -50,7 +51,11 @@ public: } std::string GetName() const { - return BufferBaseNames[static_cast<size_t>(stage)] + std::to_string(index); + return BufferBaseNames[static_cast<std::size_t>(stage)] + std::to_string(index); + } + + u32 GetHash() const { + return (static_cast<u32>(stage) << 16) | index; } private: @@ -69,14 +74,15 @@ class SamplerEntry { using Maxwell = Tegra::Engines::Maxwell3D::Regs; public: - SamplerEntry(Maxwell::ShaderStage stage, size_t offset, size_t index) - : offset(offset), stage(stage), sampler_index(index) {} + SamplerEntry(Maxwell::ShaderStage stage, std::size_t offset, std::size_t index, + Tegra::Shader::TextureType type, bool is_array) + : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {} - size_t GetOffset() const { + std::size_t GetOffset() const { return offset; } - size_t GetIndex() const { + std::size_t GetIndex() const { return sampler_index; } @@ -85,23 +91,63 @@ public: } std::string GetName() const { - return std::string(TextureSamplerNames[static_cast<size_t>(stage)]) + '[' + - std::to_string(sampler_index) + ']'; + return std::string(TextureSamplerNames[static_cast<std::size_t>(stage)]) + '_' + + std::to_string(sampler_index); + } + + std::string GetTypeString() const { + using Tegra::Shader::TextureType; + std::string glsl_type; + + switch (type) { + case TextureType::Texture1D: + glsl_type = "sampler1D"; + break; + case TextureType::Texture2D: + glsl_type = "sampler2D"; + break; + case TextureType::Texture3D: + glsl_type = "sampler3D"; + break; + case TextureType::TextureCube: + glsl_type = "samplerCube"; + break; + default: + UNIMPLEMENTED(); + } + if (is_array) + glsl_type += "Array"; + return glsl_type; + } + + Tegra::Shader::TextureType GetType() const { + return type; + } + + bool IsArray() const { + return is_array; + } + + u32 GetHash() const { + return (static_cast<u32>(stage) << 16) | static_cast<u32>(sampler_index); } static std::string GetArrayName(Maxwell::ShaderStage stage) { - return TextureSamplerNames[static_cast<size_t>(stage)]; + return TextureSamplerNames[static_cast<std::size_t>(stage)]; } private: static constexpr std::array<const char*, Maxwell::MaxShaderStage> TextureSamplerNames = { "tex_vs", "tex_tessc", "tex_tesse", "tex_gs", "tex_fs", }; + /// Offset in TSC memory from which to read the sampler object, as specified by the sampling /// instruction. - size_t offset; - Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. - size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. + std::size_t offset; + Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used. + std::size_t sampler_index; ///< Value used to index into the generated GLSL sampler array. + Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc) + bool is_array; ///< Whether the texture is being sampled as an array texture or not. }; struct ShaderEntries { diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 533e42caa..b86cd96e8 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -12,7 +12,7 @@ namespace OpenGL::GLShader { /// Number of OpenGL texture samplers that can be used in the fragment shader -static constexpr size_t NumTextureSamplers = 32; +static constexpr std::size_t NumTextureSamplers = 32; using Tegra::Engines::Maxwell3D; diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index 5781d9d16..5f3fe067e 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -25,7 +25,7 @@ GLuint LoadShader(const char* source, GLenum type) { default: UNREACHABLE(); } - GLuint shader_id = glCreateShader(type); + const GLuint shader_id = glCreateShader(type); glShaderSource(shader_id, 1, &source, nullptr); LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type); glCompileShader(shader_id); diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 60a4defd1..af99132ba 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -200,9 +200,9 @@ void OpenGLState::Apply() const { const auto& texture_unit = texture_units[i]; const auto& cur_state_texture_unit = cur_state.texture_units[i]; - if (texture_unit.texture_2d != cur_state_texture_unit.texture_2d) { + if (texture_unit.texture != cur_state_texture_unit.texture) { glActiveTexture(TextureUnits::MaxwellTexture(static_cast<int>(i)).Enum()); - glBindTexture(GL_TEXTURE_2D, texture_unit.texture_2d); + glBindTexture(texture_unit.target, texture_unit.texture); } if (texture_unit.sampler != cur_state_texture_unit.sampler) { glBindSampler(static_cast<GLuint>(i), texture_unit.sampler); @@ -214,7 +214,7 @@ void OpenGLState::Apply() const { texture_unit.swizzle.a != cur_state_texture_unit.swizzle.a) { std::array<GLint, 4> mask = {texture_unit.swizzle.r, texture_unit.swizzle.g, texture_unit.swizzle.b, texture_unit.swizzle.a}; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, mask.data()); + glTexParameteriv(texture_unit.target, GL_TEXTURE_SWIZZLE_RGBA, mask.data()); } } @@ -272,7 +272,7 @@ void OpenGLState::Apply() const { } // Clip distance - for (size_t i = 0; i < clip_distance.size(); ++i) { + for (std::size_t i = 0; i < clip_distance.size(); ++i) { if (clip_distance[i] != cur_state.clip_distance[i]) { if (clip_distance[i]) { glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i)); @@ -287,7 +287,7 @@ void OpenGLState::Apply() const { OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { for (auto& unit : texture_units) { - if (unit.texture_2d == handle) { + if (unit.texture == handle) { unit.Unbind(); } } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 46e96a97d..e3e24b9e7 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -94,8 +94,9 @@ public: // 3 texture units - one for each that is used in PICA fragment shader emulation struct TextureUnit { - GLuint texture_2d; // GL_TEXTURE_BINDING_2D - GLuint sampler; // GL_SAMPLER_BINDING + GLuint texture; // GL_TEXTURE_BINDING_2D + GLuint sampler; // GL_SAMPLER_BINDING + GLenum target; struct { GLint r; // GL_TEXTURE_SWIZZLE_R GLint g; // GL_TEXTURE_SWIZZLE_G @@ -104,7 +105,7 @@ public: } swizzle; void Unbind() { - texture_2d = 0; + texture = 0; swizzle.r = GL_RED; swizzle.g = GL_GREEN; swizzle.b = GL_BLUE; @@ -114,6 +115,7 @@ public: void Reset() { Unbind(); sampler = 0; + target = GL_TEXTURE_2D; } }; std::array<TextureUnit, 32> texture_units; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp index e565afcee..664f3ca20 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -29,7 +29,7 @@ OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coh if (GLAD_GL_ARB_buffer_storage) { persistent = true; coherent = prefer_coherent; - GLbitfield flags = + const GLbitfield flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | (coherent ? GL_MAP_COHERENT_BIT : 0); glBufferStorage(gl_target, allocate_size, nullptr, flags); mapped_ptr = static_cast<u8*>(glMapBufferRange( @@ -61,7 +61,7 @@ std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr a mapped_size = size; if (alignment > 0) { - buffer_pos = Common::AlignUp<size_t>(buffer_pos, alignment); + buffer_pos = Common::AlignUp<std::size_t>(buffer_pos, alignment); } bool invalidate = false; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 411a73d50..96d916b07 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -177,7 +177,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf Memory::GetPointer(framebuffer_addr), gl_framebuffer_data.data(), true); - state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.texture_units[0].texture = screen_info.texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -194,7 +194,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - state.texture_units[0].texture_2d = 0; + state.texture_units[0].texture = 0; state.Apply(); } } @@ -205,7 +205,7 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf */ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, const TextureInfo& texture) { - state.texture_units[0].texture_2d = texture.resource.handle; + state.texture_units[0].texture = texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -214,7 +214,7 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color // Update existing texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, framebuffer_data); - state.texture_units[0].texture_2d = 0; + state.texture_units[0].texture = 0; state.Apply(); } @@ -260,7 +260,7 @@ void RendererOpenGL::InitOpenGLObjects() { // Allocation of storage is deferred until the first frame, when we // know the framebuffer size. - state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.texture_units[0].texture = screen_info.texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -272,7 +272,7 @@ void RendererOpenGL::InitOpenGLObjects() { screen_info.display_texture = screen_info.texture.resource.handle; - state.texture_units[0].texture_2d = 0; + state.texture_units[0].texture = 0; state.Apply(); // Clear screen to black @@ -305,14 +305,14 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, UNREACHABLE(); } - state.texture_units[0].texture_2d = texture.resource.handle; + state.texture_units[0].texture = texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, texture.gl_format, texture.gl_type, nullptr); - state.texture_units[0].texture_2d = 0; + state.texture_units[0].texture = 0; state.Apply(); } @@ -354,14 +354,14 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v), }}; - state.texture_units[0].texture_2d = screen_info.display_texture; + state.texture_units[0].texture = screen_info.display_texture; state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; state.Apply(); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - state.texture_units[0].texture_2d = 0; + state.texture_units[0].texture = 0; state.Apply(); } @@ -369,6 +369,12 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, * Draws the emulated screens to the emulator window. */ void RendererOpenGL::DrawScreen() { + if (renderer_settings.set_background_color) { + // Update background color before drawing + glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, + 0.0f); + } + const auto& layout = render_window.GetFramebufferLayout(); const auto& screen = layout.screen; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 272294c62..20ba6d4f6 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -46,6 +46,48 @@ void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_ } } +template <std::size_t N, std::size_t M> +struct alignas(64) SwizzleTable { + constexpr SwizzleTable() { + for (u32 y = 0; y < N; ++y) { + for (u32 x = 0; x < M; ++x) { + const u32 x2 = x * 16; + values[y][x] = static_cast<u16>(((x2 % 64) / 32) * 256 + ((y % 8) / 2) * 64 + + ((x2 % 32) / 16) * 32 + (y % 2) * 16); + } + } + } + const std::array<u16, M>& operator[](std::size_t index) const { + return values[index]; + } + std::array<std::array<u16, M>, N> values{}; +}; + +constexpr auto swizzle_table = SwizzleTable<8, 4>(); + +void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u8* swizzled_data, + u8* unswizzled_data, bool unswizzle, u32 block_height) { + std::array<u8*, 2> data_ptrs; + const std::size_t stride{width * bytes_per_pixel}; + const std::size_t image_width_in_gobs{(stride + 63) / 64}; + const std::size_t copy_size{16}; + for (std::size_t y = 0; y < height; ++y) { + const std::size_t initial_gob = + (y / (8 * block_height)) * 512 * block_height * image_width_in_gobs + + (y % (8 * block_height) / 8) * 512; + const std::size_t pixel_base{y * width * bytes_per_pixel}; + const auto& table = swizzle_table[y % 8]; + for (std::size_t xb = 0; xb < stride; xb += copy_size) { + const std::size_t gob_address{initial_gob + (xb / 64) * 512 * block_height}; + const std::size_t swizzle_offset{gob_address + table[(xb / 16) % 4]}; + const std::size_t pixel_index{xb + pixel_base}; + data_ptrs[unswizzle] = swizzled_data + swizzle_offset; + data_ptrs[!unswizzle] = unswizzled_data + pixel_index; + std::memcpy(data_ptrs[0], data_ptrs[1], copy_size); + } + } +} + u32 BytesPerPixel(TextureFormat format) { switch (format) { case TextureFormat::DXT1: @@ -63,6 +105,7 @@ u32 BytesPerPixel(TextureFormat format) { case TextureFormat::R32_G32_B32: return 12; case TextureFormat::ASTC_2D_4X4: + case TextureFormat::ASTC_2D_8X8: case TextureFormat::A8R8G8B8: case TextureFormat::A2B10G10R10: case TextureFormat::BF10GF11RF11: @@ -91,8 +134,13 @@ u32 BytesPerPixel(TextureFormat format) { std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width, u32 height, u32 block_height) { std::vector<u8> unswizzled_data(width * height * bytes_per_pixel); - CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel, - Memory::GetPointer(address), unswizzled_data.data(), true, block_height); + if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) { + FastSwizzleData(width / tile_size, height / tile_size, bytes_per_pixel, + Memory::GetPointer(address), unswizzled_data.data(), true, block_height); + } else { + CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel, + Memory::GetPointer(address), unswizzled_data.data(), true, block_height); + } return unswizzled_data; } @@ -111,6 +159,7 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat case TextureFormat::BC6H_UF16: case TextureFormat::BC6H_SF16: case TextureFormat::ASTC_2D_4X4: + case TextureFormat::ASTC_2D_8X8: case TextureFormat::A8R8G8B8: case TextureFormat::A2B10G10R10: case TextureFormat::A1B5G5R5: diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index c6bd2f4b9..c2fb824b2 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -170,8 +170,12 @@ struct TICEntry { BitField<0, 16, u32> width_minus_1; BitField<23, 4, TextureType> texture_type; }; - u16 height_minus_1; - INSERT_PADDING_BYTES(10); + union { + BitField<0, 16, u32> height_minus_1; + BitField<16, 15, u32> depth_minus_1; + }; + + INSERT_PADDING_BYTES(8); GPUVAddr Address() const { return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) | address_low); @@ -192,6 +196,10 @@ struct TICEntry { return height_minus_1 + 1; } + u32 Depth() const { + return depth_minus_1 + 1; + } + u32 BlockHeight() const { ASSERT(header_version == TICHeaderVersion::BlockLinear || header_version == TICHeaderVersion::BlockLinearColorKey); diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index ea9ea69e4..f48b69809 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -9,6 +9,8 @@ add_executable(yuzu about_dialog.h bootmanager.cpp bootmanager.h + compatibility_list.cpp + compatibility_list.h configuration/config.cpp configuration/config.h configuration/configure_audio.cpp @@ -43,6 +45,8 @@ add_executable(yuzu game_list.cpp game_list.h game_list_p.h + game_list_worker.cpp + game_list_worker.h hotkeys.cpp hotkeys.h main.cpp diff --git a/src/yuzu/about_dialog.cpp b/src/yuzu/about_dialog.cpp index a81ad2888..3efa65a38 100644 --- a/src/yuzu/about_dialog.cpp +++ b/src/yuzu/about_dialog.cpp @@ -11,7 +11,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDia ui->setupUi(this); ui->labelLogo->setPixmap(QIcon::fromTheme("yuzu").pixmap(200)); ui->labelBuildInfo->setText( - ui->labelBuildInfo->text().arg(Common::g_build_name, Common::g_scm_branch, + ui->labelBuildInfo->text().arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, QString(Common::g_build_date).left(10))); } diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 159b2c32b..4e4c108ab 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -112,6 +112,7 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) setWindowTitle(QString::fromStdString(window_title)); InputCommon::Init(); + InputCommon::StartJoystickEventHandler(); } GRenderWindow::~GRenderWindow() { @@ -256,6 +257,7 @@ void GRenderWindow::InitRenderTarget() { QGLFormat fmt; fmt.setVersion(3, 3); fmt.setProfile(QGLFormat::CoreProfile); + fmt.setSwapInterval(false); // Requests a forward-compatible context, which is required to get a 3.2+ context on OS X fmt.setOption(QGL::NoDeprecatedFunctions); diff --git a/src/yuzu/compatibility_list.cpp b/src/yuzu/compatibility_list.cpp new file mode 100644 index 000000000..2d2cfd03c --- /dev/null +++ b/src/yuzu/compatibility_list.cpp @@ -0,0 +1,18 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> + +#include <fmt/format.h> + +#include "yuzu/compatibility_list.h" + +CompatibilityList::const_iterator FindMatchingCompatibilityEntry( + const CompatibilityList& compatibility_list, u64 program_id) { + return std::find_if(compatibility_list.begin(), compatibility_list.end(), + [program_id](const auto& element) { + std::string pid = fmt::format("{:016X}", program_id); + return element.first == pid; + }); +} diff --git a/src/yuzu/compatibility_list.h b/src/yuzu/compatibility_list.h new file mode 100644 index 000000000..bc0175bd3 --- /dev/null +++ b/src/yuzu/compatibility_list.h @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <unordered_map> + +#include <QString> + +#include "common/common_types.h" + +using CompatibilityList = std::unordered_map<std::string, std::pair<QString, QString>>; + +CompatibilityList::const_iterator FindMatchingCompatibilityEntry( + const CompatibilityList& compatibility_list, u64 program_id); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 60b6d6d44..d229225b4 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -95,6 +95,8 @@ void Config::ReadValues() { qt_config->beginGroup("Audio"); Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString(); + Settings::values.enable_audio_stretching = + qt_config->value("enable_audio_stretching", true).toBool(); Settings::values.audio_device_id = qt_config->value("output_device", "auto").toString().toStdString(); Settings::values.volume = qt_config->value("volume", 1).toFloat(); @@ -102,6 +104,20 @@ void Config::ReadValues() { qt_config->beginGroup("Data Storage"); Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); + FileUtil::GetUserPath( + FileUtil::UserPath::NANDDir, + qt_config + ->value("nand_directory", + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))) + .toString() + .toStdString()); + FileUtil::GetUserPath( + FileUtil::UserPath::SDMCDir, + qt_config + ->value("sdmc_directory", + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))) + .toString() + .toStdString()); qt_config->endGroup(); qt_config->beginGroup("System"); @@ -216,12 +232,17 @@ void Config::SaveValues() { qt_config->beginGroup("Audio"); qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id)); + qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching); qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id)); qt_config->setValue("volume", Settings::values.volume); qt_config->endGroup(); qt_config->beginGroup("Data Storage"); qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); + qt_config->setValue("nand_directory", + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + qt_config->setValue("sdmc_directory", + QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); qt_config->endGroup(); qt_config->beginGroup("System"); diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp index fbb813f6c..6ea59f2a3 100644 --- a/src/yuzu/configuration/configure_audio.cpp +++ b/src/yuzu/configuration/configure_audio.cpp @@ -46,6 +46,8 @@ void ConfigureAudio::setConfiguration() { } ui->output_sink_combo_box->setCurrentIndex(new_sink_index); + ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching); + // The device list cannot be pre-populated (nor listed) until the output sink is known. updateAudioDevices(new_sink_index); @@ -67,6 +69,7 @@ void ConfigureAudio::applyConfiguration() { Settings::values.sink_id = ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex()) .toStdString(); + Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked(); Settings::values.audio_device_id = ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex()) .toStdString(); diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui index ef67890dc..a29a0e265 100644 --- a/src/yuzu/configuration/configure_audio.ui +++ b/src/yuzu/configuration/configure_audio.ui @@ -31,6 +31,16 @@ </item> </layout> </item> + <item> + <widget class="QCheckBox" name="toggle_audio_stretching"> + <property name="toolTip"> + <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string> + </property> + <property name="text"> + <string>Enable audio stretching</string> + </property> + </widget> + </item> <item> <layout class="QHBoxLayout"> <item> diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp index 1ae3423cf..8743ce982 100644 --- a/src/yuzu/configuration/configure_gamelist.cpp +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -2,47 +2,51 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "core/core.h" +#include <array> +#include <utility> + +#include "common/common_types.h" #include "core/settings.h" #include "ui_configure_gamelist.h" -#include "ui_settings.h" #include "yuzu/configuration/configure_gamelist.h" +#include "yuzu/ui_settings.h" + +namespace { +constexpr std::array<std::pair<u32, const char*>, 5> default_icon_sizes{{ + std::make_pair(0, QT_TR_NOOP("None")), + std::make_pair(32, QT_TR_NOOP("Small (32x32)")), + std::make_pair(64, QT_TR_NOOP("Standard (64x64)")), + std::make_pair(128, QT_TR_NOOP("Large (128x128)")), + std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")), +}}; + +constexpr std::array<const char*, 4> row_text_names{{ + QT_TR_NOOP("Filename"), + QT_TR_NOOP("Filetype"), + QT_TR_NOOP("Title ID"), + QT_TR_NOOP("Title Name"), +}}; +} // Anonymous namespace ConfigureGameList::ConfigureGameList(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureGameList) { ui->setupUi(this); - static const std::vector<std::pair<u32, std::string>> default_icon_sizes{ - std::make_pair(0, "None"), std::make_pair(32, "Small"), - std::make_pair(64, "Standard"), std::make_pair(128, "Large"), - std::make_pair(256, "Full Size"), - }; - - for (const auto& size : default_icon_sizes) { - ui->icon_size_combobox->addItem(QString::fromStdString(size.second + " (" + - std::to_string(size.first) + "x" + - std::to_string(size.first) + ")"), - size.first); - } - - static const std::vector<std::string> row_text_names{ - "Filename", - "Filetype", - "Title ID", - "Title Name", - }; - - for (size_t i = 0; i < row_text_names.size(); ++i) { - ui->row_1_text_combobox->addItem(QString::fromStdString(row_text_names[i]), - QVariant::fromValue(i)); - ui->row_2_text_combobox->addItem(QString::fromStdString(row_text_names[i]), - QVariant::fromValue(i)); - } + InitializeIconSizeComboBox(); + InitializeRowComboBoxes(); this->setConfiguration(); } -ConfigureGameList::~ConfigureGameList() {} +ConfigureGameList::~ConfigureGameList() = default; + +void ConfigureGameList::applyConfiguration() { + UISettings::values.show_unknown = ui->show_unknown->isChecked(); + UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); + UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); + UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); + Settings::Apply(); +} void ConfigureGameList::setConfiguration() { ui->show_unknown->setChecked(UISettings::values.show_unknown); @@ -54,10 +58,39 @@ void ConfigureGameList::setConfiguration() { ui->row_2_text_combobox->findData(UISettings::values.row_2_text_id)); } -void ConfigureGameList::applyConfiguration() { - UISettings::values.show_unknown = ui->show_unknown->isChecked(); - UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); - UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); - UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); - Settings::Apply(); +void ConfigureGameList::changeEvent(QEvent* event) { + if (event->type() == QEvent::LanguageChange) { + RetranslateUI(); + return; + } + + QWidget::changeEvent(event); +} + +void ConfigureGameList::RetranslateUI() { + ui->retranslateUi(this); + + for (int i = 0; i < ui->icon_size_combobox->count(); i++) { + ui->icon_size_combobox->setItemText(i, tr(default_icon_sizes[i].second)); + } + + for (int i = 0; i < ui->row_1_text_combobox->count(); i++) { + const QString name = tr(row_text_names[i]); + + ui->row_1_text_combobox->setItemText(i, name); + ui->row_2_text_combobox->setItemText(i, name); + } +} + +void ConfigureGameList::InitializeIconSizeComboBox() { + for (const auto& size : default_icon_sizes) { + ui->icon_size_combobox->addItem(size.second, size.first); + } +} + +void ConfigureGameList::InitializeRowComboBoxes() { + for (std::size_t i = 0; i < row_text_names.size(); ++i) { + ui->row_1_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); + ui->row_2_text_combobox->addItem(row_text_names[i], QVariant::fromValue(i)); + } } diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h index 94fba6373..ff7406c60 100644 --- a/src/yuzu/configuration/configure_gamelist.h +++ b/src/yuzu/configuration/configure_gamelist.h @@ -23,6 +23,11 @@ public: private: void setConfiguration(); -private: + void changeEvent(QEvent*) override; + void RetranslateUI(); + + void InitializeIconSizeComboBox(); + void InitializeRowComboBoxes(); + std::unique_ptr<Ui::ConfigureGameList> ui; }; diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index d8caee1e8..9292d9a42 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -20,7 +20,6 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) this->setConfiguration(); ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); - ui->use_multi_core->setEnabled(!Core::System::GetInstance().IsPoweredOn()); ui->use_docked_mode->setEnabled(!Core::System::GetInstance().IsPoweredOn()); } @@ -31,7 +30,6 @@ void ConfigureGeneral::setConfiguration() { ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); - ui->use_multi_core->setChecked(Settings::values.use_multi_core); ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); } @@ -46,6 +44,5 @@ void ConfigureGeneral::applyConfiguration() { ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); - Settings::values.use_multi_core = ui->use_multi_core->isChecked(); Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 233adbe27..1775c4d40 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -58,13 +58,6 @@ </property> </widget> </item> - <item> - <widget class="QCheckBox" name="use_multi_core"> - <property name="text"> - <string>Enable multi-core</string> - </property> - </widget> - </item> </layout> </item> </layout> diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index ee1287028..839d58f59 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <QColorDialog> #include "core/core.h" #include "core/settings.h" #include "ui_configure_graphics.h" @@ -16,6 +17,14 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent) ui->frame_limit->setEnabled(Settings::values.use_frame_limit); connect(ui->toggle_frame_limit, &QCheckBox::stateChanged, ui->frame_limit, &QSpinBox::setEnabled); + connect(ui->bg_button, &QPushButton::clicked, this, [this] { + const QColor new_bg_color = QColorDialog::getColor(bg_color); + if (!new_bg_color.isValid()) + return; + bg_color = new_bg_color; + ui->bg_button->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(bg_color.name())); + }); } ConfigureGraphics::~ConfigureGraphics() = default; @@ -65,6 +74,10 @@ void ConfigureGraphics::setConfiguration() { ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit); ui->frame_limit->setValue(Settings::values.frame_limit); ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers); + bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, + Settings::values.bg_blue); + ui->bg_button->setStyleSheet( + QString("QPushButton { background-color: %1 }").arg(bg_color.name())); } void ConfigureGraphics::applyConfiguration() { @@ -73,4 +86,7 @@ void ConfigureGraphics::applyConfiguration() { Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked(); Settings::values.frame_limit = ui->frame_limit->value(); Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked(); + Settings::values.bg_red = static_cast<float>(bg_color.redF()); + Settings::values.bg_green = static_cast<float>(bg_color.greenF()); + Settings::values.bg_blue = static_cast<float>(bg_color.blueF()); } diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index 5497a55f7..9bda26fd6 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -25,4 +25,5 @@ private: private: std::unique_ptr<Ui::ConfigureGraphics> ui; + QColor bg_color; }; diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index 3bc18c26e..8fc00af1b 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -96,6 +96,27 @@ </item> </layout> </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_6"> + <item> + <widget class="QLabel" name="bg_label"> + <property name="text"> + <string>Background Color:</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="bg_button"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + </layout> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 5e7badedf..d29abb74b 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp @@ -1,4 +1,4 @@ -// Copyright 2016 Citra Emulator Project +// Copyright 2016 Citra Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -53,19 +53,18 @@ static QString ButtonToText(const Common::ParamPackage& param) { } else if (param.Get("engine", "") == "keyboard") { return getKeyName(param.Get("code", 0)); } else if (param.Get("engine", "") == "sdl") { - QString text = QString(QObject::tr("Joystick %1")).arg(param.Get("joystick", "").c_str()); if (param.Has("hat")) { - text += QString(QObject::tr(" Hat %1 %2")) - .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); + return QString(QObject::tr("Hat %1 %2")) + .arg(param.Get("hat", "").c_str(), param.Get("direction", "").c_str()); } if (param.Has("axis")) { - text += QString(QObject::tr(" Axis %1%2")) - .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); + return QString(QObject::tr("Axis %1%2")) + .arg(param.Get("axis", "").c_str(), param.Get("direction", "").c_str()); } if (param.Has("button")) { - text += QString(QObject::tr(" Button %1")).arg(param.Get("button", "").c_str()); + return QString(QObject::tr("Button %1")).arg(param.Get("button", "").c_str()); } - return text; + return QString(); } else { return QObject::tr("[unknown]"); } @@ -81,13 +80,12 @@ static QString AnalogToText(const Common::ParamPackage& param, const std::string return QString(QObject::tr("[unused]")); } - QString text = QString(QObject::tr("Joystick %1")).arg(param.Get("joystick", "").c_str()); if (dir == "left" || dir == "right") { - text += QString(QObject::tr(" Axis %1")).arg(param.Get("axis_x", "").c_str()); + return QString(QObject::tr("Axis %1")).arg(param.Get("axis_x", "").c_str()); } else if (dir == "up" || dir == "down") { - text += QString(QObject::tr(" Axis %1")).arg(param.Get("axis_y", "").c_str()); + return QString(QObject::tr("Axis %1")).arg(param.Get("axis_y", "").c_str()); } - return text; + return QString(); } else { return QObject::tr("[unknown]"); } diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp index fe682b3b8..b5c88f944 100644 --- a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp +++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp @@ -42,7 +42,8 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const { tr("Finished primitive batch")}, }; - DEBUG_ASSERT(map.size() == static_cast<size_t>(Tegra::DebugContext::Event::NumEvents)); + DEBUG_ASSERT(map.size() == + static_cast<std::size_t>(Tegra::DebugContext::Event::NumEvents)); return (map.find(event) != map.end()) ? map.at(event) : QString(); } diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp index 7e37962d5..cbcd5dd5f 100644 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ b/src/yuzu/debugger/graphics/graphics_surface.cpp @@ -341,8 +341,8 @@ void GraphicsSurfaceWidget::OnUpdate() { // directly... const auto& registers = gpu.Maxwell3D().regs; - const auto& rt = registers.rt[static_cast<size_t>(surface_source) - - static_cast<size_t>(Source::RenderTarget0)]; + const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) - + static_cast<std::size_t>(Source::RenderTarget0)]; surface_address = rt.Address(); surface_width = rt.width; diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 6c2cd967e..a3b1fd357 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -15,6 +15,7 @@ #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" #include "core/hle/kernel/wait_object.h" +#include "core/memory.h" WaitTreeItem::WaitTreeItem() = default; WaitTreeItem::~WaitTreeItem() = default; @@ -117,7 +118,7 @@ QString WaitTreeCallstack::GetText() const { std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeCallstack::GetChildren() const { std::vector<std::unique_ptr<WaitTreeItem>> list; - constexpr size_t BaseRegister = 29; + constexpr std::size_t BaseRegister = 29; u64 base_pointer = thread.context.cpu_registers[BaseRegister]; while (base_pointer != 0) { @@ -213,35 +214,35 @@ QString WaitTreeThread::GetText() const { const auto& thread = static_cast<const Kernel::Thread&>(object); QString status; switch (thread.status) { - case ThreadStatus::Running: + case Kernel::ThreadStatus::Running: status = tr("running"); break; - case ThreadStatus::Ready: + case Kernel::ThreadStatus::Ready: status = tr("ready"); break; - case ThreadStatus::WaitHLEEvent: + case Kernel::ThreadStatus::WaitHLEEvent: status = tr("waiting for HLE return"); break; - case ThreadStatus::WaitSleep: + case Kernel::ThreadStatus::WaitSleep: status = tr("sleeping"); break; - case ThreadStatus::WaitIPC: + case Kernel::ThreadStatus::WaitIPC: status = tr("waiting for IPC reply"); break; - case ThreadStatus::WaitSynchAll: - case ThreadStatus::WaitSynchAny: + case Kernel::ThreadStatus::WaitSynchAll: + case Kernel::ThreadStatus::WaitSynchAny: status = tr("waiting for objects"); break; - case ThreadStatus::WaitMutex: + case Kernel::ThreadStatus::WaitMutex: status = tr("waiting for mutex"); break; - case ThreadStatus::WaitArb: + case Kernel::ThreadStatus::WaitArb: status = tr("waiting for address arbiter"); break; - case ThreadStatus::Dormant: + case Kernel::ThreadStatus::Dormant: status = tr("dormant"); break; - case ThreadStatus::Dead: + case Kernel::ThreadStatus::Dead: status = tr("dead"); break; } @@ -254,23 +255,23 @@ QString WaitTreeThread::GetText() const { QColor WaitTreeThread::GetColor() const { const auto& thread = static_cast<const Kernel::Thread&>(object); switch (thread.status) { - case ThreadStatus::Running: + case Kernel::ThreadStatus::Running: return QColor(Qt::GlobalColor::darkGreen); - case ThreadStatus::Ready: + case Kernel::ThreadStatus::Ready: return QColor(Qt::GlobalColor::darkBlue); - case ThreadStatus::WaitHLEEvent: - case ThreadStatus::WaitIPC: + case Kernel::ThreadStatus::WaitHLEEvent: + case Kernel::ThreadStatus::WaitIPC: return QColor(Qt::GlobalColor::darkRed); - case ThreadStatus::WaitSleep: + case Kernel::ThreadStatus::WaitSleep: return QColor(Qt::GlobalColor::darkYellow); - case ThreadStatus::WaitSynchAll: - case ThreadStatus::WaitSynchAny: - case ThreadStatus::WaitMutex: - case ThreadStatus::WaitArb: + case Kernel::ThreadStatus::WaitSynchAll: + case Kernel::ThreadStatus::WaitSynchAny: + case Kernel::ThreadStatus::WaitMutex: + case Kernel::ThreadStatus::WaitArb: return QColor(Qt::GlobalColor::red); - case ThreadStatus::Dormant: + case Kernel::ThreadStatus::Dormant: return QColor(Qt::GlobalColor::darkCyan); - case ThreadStatus::Dead: + case Kernel::ThreadStatus::Dead: return QColor(Qt::GlobalColor::gray); default: return WaitTreeItem::GetColor(); @@ -284,13 +285,13 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { QString processor; switch (thread.processor_id) { - case ThreadProcessorId::THREADPROCESSORID_DEFAULT: + case Kernel::ThreadProcessorId::THREADPROCESSORID_DEFAULT: processor = tr("default"); break; - case ThreadProcessorId::THREADPROCESSORID_0: - case ThreadProcessorId::THREADPROCESSORID_1: - case ThreadProcessorId::THREADPROCESSORID_2: - case ThreadProcessorId::THREADPROCESSORID_3: + case Kernel::ThreadProcessorId::THREADPROCESSORID_0: + case Kernel::ThreadProcessorId::THREADPROCESSORID_1: + case Kernel::ThreadProcessorId::THREADPROCESSORID_2: + case Kernel::ThreadProcessorId::THREADPROCESSORID_3: processor = tr("core %1").arg(thread.processor_id); break; default: @@ -314,8 +315,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const { else list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex"))); - if (thread.status == ThreadStatus::WaitSynchAny || - thread.status == ThreadStatus::WaitSynchAll) { + if (thread.status == Kernel::ThreadStatus::WaitSynchAny || + thread.status == Kernel::ThreadStatus::WaitSynchAll) { list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects, thread.IsSleepingOnWaitAll())); } diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index d15242d59..e8b2f720a 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -13,20 +13,16 @@ #include <QKeyEvent> #include <QMenu> #include <QThreadPool> -#include <boost/container/flat_map.hpp> #include <fmt/format.h> #include "common/common_paths.h" +#include "common/common_types.h" +#include "common/file_util.h" #include "common/logging/log.h" -#include "common/string_util.h" -#include "core/file_sys/content_archive.h" -#include "core/file_sys/control_metadata.h" -#include "core/file_sys/registered_cache.h" -#include "core/file_sys/romfs.h" -#include "core/file_sys/vfs_real.h" -#include "core/hle/service/filesystem/filesystem.h" -#include "core/loader/loader.h" +#include "core/file_sys/patch_manager.h" +#include "yuzu/compatibility_list.h" #include "yuzu/game_list.h" #include "yuzu/game_list_p.h" +#include "yuzu/game_list_worker.h" #include "yuzu/main.h" #include "yuzu/ui_settings.h" @@ -93,15 +89,7 @@ bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* e } void GameList::SearchField::setFilterResult(int visible, int total) { - QString result_of_text = tr("of"); - QString result_text; - if (total == 1) { - result_text = tr("result"); - } else { - result_text = tr("results"); - } - label_filter_result->setText( - QString("%1 %2 %3 %4").arg(visible).arg(result_of_text).arg(total).arg(result_text)); + label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); } void GameList::SearchField::clear() { @@ -231,6 +219,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent) item_model->insertColumns(0, COLUMN_COUNT); item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility"); + item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, "Add-ons"); item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); @@ -369,7 +358,7 @@ void GameList::LoadCompatibilityList() { QJsonDocument json = QJsonDocument::fromJson(string_content.toUtf8()); QJsonArray arr = json.array(); - for (const QJsonValue& value : arr) { + for (const QJsonValueRef& value : arr) { QJsonObject game = value.toObject(); if (game.contains("compatibility") && game["compatibility"].isDouble()) { @@ -377,9 +366,9 @@ void GameList::LoadCompatibilityList() { QString directory = game["directory"].toString(); QJsonArray ids = game["releases"].toArray(); - for (const QJsonValue& value : ids) { - QJsonObject object = value.toObject(); - QString id = object["id"].toString(); + for (const QJsonValueRef& id_ref : ids) { + QJsonObject id_object = id_ref.toObject(); + QString id = id_object["id"].toString(); compatibility_list.emplace( id.toUpper().toStdString(), std::make_pair(QString::number(compatibility), directory)); @@ -431,27 +420,7 @@ void GameList::LoadInterfaceLayout() { item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); } -const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; - -static bool HasSupportedFileExtension(const std::string& file_name) { - const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); - return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); -} - -static bool IsExtractedNCAMain(const std::string& file_name) { - return QFileInfo(QString::fromStdString(file_name)).fileName() == "main"; -} - -static QString FormatGameName(const std::string& physical_name) { - const QString physical_name_as_qstring = QString::fromStdString(physical_name); - const QFileInfo file_info(physical_name_as_qstring); - - if (IsExtractedNCAMain(physical_name)) { - return file_info.dir().path(); - } - - return physical_name_as_qstring; -} +const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; void GameList::RefreshGameDirectory() { if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { @@ -460,175 +429,3 @@ void GameList::RefreshGameDirectory() { PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); } } - -static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca, - std::vector<u8>& icon, std::string& name) { - const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); - if (control_dir == nullptr) - return; - - const auto nacp_file = control_dir->GetFile("control.nacp"); - if (nacp_file == nullptr) - return; - FileSys::NACP nacp(nacp_file); - name = nacp.GetApplicationName(); - - FileSys::VirtualFile icon_file = nullptr; - for (const auto& language : FileSys::LANGUAGE_NAMES) { - icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat"); - if (icon_file != nullptr) { - icon = icon_file->ReadAllBytes(); - break; - } - } -} - -GameListWorker::GameListWorker( - FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, - const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) - : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan), - compatibility_list(compatibility_list) {} - -GameListWorker::~GameListWorker() = default; - -void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) { - const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, - FileSys::ContentRecordType::Program); - - for (const auto& game : installed_games) { - const auto& file = cache->GetEntryUnparsed(game); - std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); - if (!loader) - continue; - - std::vector<u8> icon; - std::string name; - u64 program_id = 0; - loader->ReadProgramId(program_id); - - const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); - if (control != nullptr) - GetMetadataFromControlNCA(control, icon, name); - emit EntryReady({ - new GameListItemPath( - FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), - program_id), - new GameListItem( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), - new GameListItemSize(file->GetSize()), - }); - } - - const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application, - FileSys::ContentRecordType::Control); - - for (const auto& entry : control_data) { - const auto nca = cache->GetEntry(entry); - if (nca != nullptr) - nca_control_map.insert_or_assign(entry.title_id, nca); - } -} - -void GameListWorker::FillControlMap(const std::string& dir_path) { - const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { - std::string physical_name = directory + DIR_SEP + virtual_name; - - if (stop_processing) - return false; // Breaks the callback loop. - - bool is_dir = FileUtil::IsDirectory(physical_name); - QFileInfo file_info(physical_name.c_str()); - if (!is_dir && file_info.suffix().toStdString() == "nca") { - auto nca = - std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); - if (nca->GetType() == FileSys::NCAContentType::Control) - nca_control_map.insert_or_assign(nca->GetTitleId(), nca); - } - return true; - }; - - FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); -} - -void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { - const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, - const std::string& virtual_name) -> bool { - std::string physical_name = directory + DIR_SEP + virtual_name; - - if (stop_processing) - return false; // Breaks the callback loop. - - bool is_dir = FileUtil::IsDirectory(physical_name); - if (!is_dir && - (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { - std::unique_ptr<Loader::AppLoader> loader = - Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); - if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || - loader->GetFileType() == Loader::FileType::Error) && - !UISettings::values.show_unknown)) - return true; - - std::vector<u8> icon; - const auto res1 = loader->ReadIcon(icon); - - u64 program_id = 0; - const auto res2 = loader->ReadProgramId(program_id); - - std::string name = " "; - const auto res3 = loader->ReadTitle(name); - - if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && - res2 == Loader::ResultStatus::Success) { - // Use from metadata pool. - if (nca_control_map.find(program_id) != nca_control_map.end()) { - const auto nca = nca_control_map[program_id]; - GetMetadataFromControlNCA(nca, icon, name); - } - } - - auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); - - // The game list uses this as compatibility number for untested games - QString compatibility("99"); - if (it != compatibility_list.end()) - compatibility = it->second.first; - - emit EntryReady({ - new GameListItemPath( - FormatGameName(physical_name), icon, QString::fromStdString(name), - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), - program_id), - new GameListItemCompat(compatibility), - new GameListItem( - QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), - new GameListItemSize(FileUtil::GetSize(physical_name)), - }); - } else if (is_dir && recursion > 0) { - watch_list.append(QString::fromStdString(physical_name)); - AddFstEntriesToGameList(physical_name, recursion - 1); - } - - return true; - }; - - FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); -} - -void GameListWorker::run() { - stop_processing = false; - watch_list.append(dir_path); - FillControlMap(dir_path.toStdString()); - AddInstalledTitlesToGameList(Service::FileSystem::GetUserNANDContents()); - AddInstalledTitlesToGameList(Service::FileSystem::GetSystemNANDContents()); - AddInstalledTitlesToGameList(Service::FileSystem::GetSDMCContents()); - AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); - nca_control_map.clear(); - emit Finished(watch_list); -} - -void GameListWorker::Cancel() { - this->disconnect(); - stop_processing = true; -} diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 6a5c2f5f8..2713e7b54 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -4,8 +4,6 @@ #pragma once -#include <unordered_map> - #include <QFileSystemWatcher> #include <QHBoxLayout> #include <QLabel> @@ -20,6 +18,9 @@ #include <QVBoxLayout> #include <QWidget> +#include "common/common_types.h" +#include "yuzu/compatibility_list.h" + class GameListWorker; class GMainWindow; @@ -36,6 +37,7 @@ public: enum { COLUMN_NAME, COLUMN_COMPATIBILITY, + COLUMN_ADD_ONS, COLUMN_FILE_TYPE, COLUMN_SIZE, COLUMN_COUNT, // Number of columns @@ -87,9 +89,8 @@ signals: void GameChosen(QString game_path); void ShouldCancelWorker(); void OpenFolderRequested(u64 program_id, GameListOpenTarget target); - void NavigateToGamedbEntryRequested( - u64 program_id, - std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); + void NavigateToGamedbEntryRequested(u64 program_id, + const CompatibilityList& compatibility_list); private slots: void onTextChanged(const QString& newText); @@ -111,7 +112,7 @@ private: QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; QFileSystemWatcher* watcher = nullptr; - std::unordered_map<std::string, std::pair<QString, QString>> compatibility_list; + CompatibilityList compatibility_list; }; Q_DECLARE_METATYPE(GameListOpenTarget); diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h index 3624cb21a..b6272d536 100644 --- a/src/yuzu/game_list_p.h +++ b/src/yuzu/game_list_p.h @@ -4,29 +4,25 @@ #pragma once +#include <algorithm> #include <array> -#include <atomic> #include <map> -#include <memory> +#include <string> #include <unordered_map> #include <utility> + #include <QCoreApplication> #include <QImage> #include <QObject> -#include <QRunnable> #include <QStandardItem> #include <QString> + +#include "common/common_types.h" #include "common/logging/log.h" #include "common/string_util.h" #include "yuzu/ui_settings.h" #include "yuzu/util/util.h" -namespace FileSys { -class NCA; -class RegisteredCache; -class VfsFilesystem; -} // namespace FileSys - /** * Gets the default icon (for games without valid SMDH) * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) @@ -38,17 +34,6 @@ static QPixmap GetDefaultIcon(u32 size) { return icon; } -static auto FindMatchingCompatibilityEntry( - const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list, - u64 program_id) { - return std::find_if( - compatibility_list.begin(), compatibility_list.end(), - [program_id](const std::pair<std::string, std::pair<QString, QString>>& element) { - std::string pid = fmt::format("{:016X}", program_id); - return element.first == pid; - }); -} - class GameListItem : public QStandardItem { public: @@ -121,7 +106,7 @@ class GameListItemCompat : public GameListItem { public: static const int CompatNumberRole = Qt::UserRole + 1; GameListItemCompat() = default; - explicit GameListItemCompat(const QString& compatiblity) { + explicit GameListItemCompat(const QString& compatibility) { struct CompatStatus { QString color; const char* text; @@ -138,13 +123,13 @@ public: {"99", {"#000000", QT_TR_NOOP("Not Tested"), QT_TR_NOOP("The game has not yet been tested.")}}}; // clang-format on - auto iterator = status_data.find(compatiblity); + auto iterator = status_data.find(compatibility); if (iterator == status_data.end()) { - LOG_WARNING(Frontend, "Invalid compatibility number {}", compatiblity.toStdString()); + LOG_WARNING(Frontend, "Invalid compatibility number {}", compatibility.toStdString()); return; } - CompatStatus status = iterator->second; - setData(compatiblity, CompatNumberRole); + const CompatStatus& status = iterator->second; + setData(compatibility, CompatNumberRole); setText(QObject::tr(status.text)); setToolTip(QObject::tr(status.tooltip)); setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); @@ -191,50 +176,3 @@ public: return data(SizeRole).toULongLong() < other.data(SizeRole).toULongLong(); } }; - -/** - * Asynchronous worker object for populating the game list. - * Communicates with other threads through Qt's signal/slot system. - */ -class GameListWorker : public QObject, public QRunnable { - Q_OBJECT - -public: - GameListWorker( - std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan, - const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); - ~GameListWorker() override; - -public slots: - /// Starts the processing of directory tree information. - void run() override; - /// Tells the worker that it should no longer continue processing. Thread-safe. - void Cancel(); - -signals: - /** - * The `EntryReady` signal is emitted once an entry has been prepared and is ready - * to be added to the game list. - * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. - */ - void EntryReady(QList<QStandardItem*> entry_items); - - /** - * After the worker has traversed the game directory looking for entries, this signal is emmited - * with a list of folders that should be watched for changes as well. - */ - void Finished(QStringList watch_list); - -private: - std::shared_ptr<FileSys::VfsFilesystem> vfs; - std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; - QStringList watch_list; - QString dir_path; - bool deep_scan; - const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list; - std::atomic_bool stop_processing; - - void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache); - void FillControlMap(const std::string& dir_path); - void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); -}; diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp new file mode 100644 index 000000000..e228d61bd --- /dev/null +++ b/src/yuzu/game_list_worker.cpp @@ -0,0 +1,239 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include <QDir> +#include <QFileInfo> + +#include "common/common_paths.h" +#include "common/file_util.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/control_metadata.h" +#include "core/file_sys/mode.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.h" +#include "core/file_sys/registered_cache.h" +#include "core/hle/service/filesystem/filesystem.h" +#include "core/loader/loader.h" +#include "yuzu/compatibility_list.h" +#include "yuzu/game_list.h" +#include "yuzu/game_list_p.h" +#include "yuzu/game_list_worker.h" +#include "yuzu/ui_settings.h" + +namespace { +void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, + const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon, + std::string& name) { + auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca); + if (icon_file != nullptr) + icon = icon_file->ReadAllBytes(); + if (nacp != nullptr) + name = nacp->GetApplicationName(); +} + +bool HasSupportedFileExtension(const std::string& file_name) { + const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); + return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); +} + +bool IsExtractedNCAMain(const std::string& file_name) { + return QFileInfo(QString::fromStdString(file_name)).fileName() == "main"; +} + +QString FormatGameName(const std::string& physical_name) { + const QString physical_name_as_qstring = QString::fromStdString(physical_name); + const QFileInfo file_info(physical_name_as_qstring); + + if (IsExtractedNCAMain(physical_name)) { + return file_info.dir().path(); + } + + return physical_name_as_qstring; +} + +QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) { + QString out; + for (const auto& kv : patch_manager.GetPatchVersionNames()) { + if (!updatable && kv.first == FileSys::PatchType::Update) + continue; + + if (kv.second.empty()) { + out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str()); + } else { + out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second) + .c_str()); + } + } + + out.chop(1); + return out; +} +} // Anonymous namespace + +GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan, + const CompatibilityList& compatibility_list) + : vfs(std::move(vfs)), dir_path(std::move(dir_path)), deep_scan(deep_scan), + compatibility_list(compatibility_list) {} + +GameListWorker::~GameListWorker() = default; + +void GameListWorker::AddInstalledTitlesToGameList() { + const auto cache = Service::FileSystem::GetUnionContents(); + const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Program); + + for (const auto& game : installed_games) { + const auto& file = cache->GetEntryUnparsed(game); + std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file); + if (!loader) + continue; + + std::vector<u8> icon; + std::string name; + u64 program_id = 0; + loader->ReadProgramId(program_id); + + const FileSys::PatchManager patch{program_id}; + const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); + if (control != nullptr) + GetMetadataFromControlNCA(patch, control, icon, name); + + auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); + + // The game list uses this as compatibility number for untested games + QString compatibility("99"); + if (it != compatibility_list.end()) + compatibility = it->second.first; + + emit EntryReady({ + new GameListItemPath( + FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), + program_id), + new GameListItemCompat(compatibility), + new GameListItem(FormatPatchNameVersions(patch)), + new GameListItem( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), + new GameListItemSize(file->GetSize()), + }); + } + + const auto control_data = cache->ListEntriesFilter(FileSys::TitleType::Application, + FileSys::ContentRecordType::Control); + + for (const auto& entry : control_data) { + const auto nca = cache->GetEntry(entry); + if (nca != nullptr) + nca_control_map.insert_or_assign(entry.title_id, nca); + } +} + +void GameListWorker::FillControlMap(const std::string& dir_path) { + const auto nca_control_callback = [this](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (stop_processing) + return false; // Breaks the callback loop. + + bool is_dir = FileUtil::IsDirectory(physical_name); + QFileInfo file_info(physical_name.c_str()); + if (!is_dir && file_info.suffix().toStdString() == "nca") { + auto nca = + std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (nca->GetType() == FileSys::NCAContentType::Control) + nca_control_map.insert_or_assign(nca->GetTitleId(), nca); + } + return true; + }; + + FileUtil::ForeachDirectoryEntry(nullptr, dir_path, nca_control_callback); +} + +void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { + const auto callback = [this, recursion](u64* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (stop_processing) + return false; // Breaks the callback loop. + + bool is_dir = FileUtil::IsDirectory(physical_name); + if (!is_dir && + (HasSupportedFileExtension(physical_name) || IsExtractedNCAMain(physical_name))) { + std::unique_ptr<Loader::AppLoader> loader = + Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read)); + if (!loader || ((loader->GetFileType() == Loader::FileType::Unknown || + loader->GetFileType() == Loader::FileType::Error) && + !UISettings::values.show_unknown)) + return true; + + std::vector<u8> icon; + const auto res1 = loader->ReadIcon(icon); + + u64 program_id = 0; + const auto res2 = loader->ReadProgramId(program_id); + + std::string name = " "; + const auto res3 = loader->ReadTitle(name); + + const FileSys::PatchManager patch{program_id}; + + if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && + res2 == Loader::ResultStatus::Success) { + // Use from metadata pool. + if (nca_control_map.find(program_id) != nca_control_map.end()) { + const auto nca = nca_control_map[program_id]; + GetMetadataFromControlNCA(patch, nca, icon, name); + } + } + + auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); + + // The game list uses this as compatibility number for untested games + QString compatibility("99"); + if (it != compatibility_list.end()) + compatibility = it->second.first; + + emit EntryReady({ + new GameListItemPath( + FormatGameName(physical_name), icon, QString::fromStdString(name), + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), + program_id), + new GameListItemCompat(compatibility), + new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())), + new GameListItem( + QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), + new GameListItemSize(FileUtil::GetSize(physical_name)), + }); + } else if (is_dir && recursion > 0) { + watch_list.append(QString::fromStdString(physical_name)); + AddFstEntriesToGameList(physical_name, recursion - 1); + } + + return true; + }; + + FileUtil::ForeachDirectoryEntry(nullptr, dir_path, callback); +} + +void GameListWorker::run() { + stop_processing = false; + watch_list.append(dir_path); + FillControlMap(dir_path.toStdString()); + AddInstalledTitlesToGameList(); + AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); + nca_control_map.clear(); + emit Finished(watch_list); +} + +void GameListWorker::Cancel() { + this->disconnect(); + stop_processing = true; +} diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h new file mode 100644 index 000000000..09d20c42f --- /dev/null +++ b/src/yuzu/game_list_worker.h @@ -0,0 +1,72 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <map> +#include <memory> +#include <string> +#include <unordered_map> + +#include <QList> +#include <QObject> +#include <QRunnable> +#include <QString> + +#include "common/common_types.h" +#include "yuzu/compatibility_list.h" + +class QStandardItem; + +namespace FileSys { +class NCA; +class VfsFilesystem; +} // namespace FileSys + +/** + * Asynchronous worker object for populating the game list. + * Communicates with other threads through Qt's signal/slot system. + */ +class GameListWorker : public QObject, public QRunnable { + Q_OBJECT + +public: + GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, QString dir_path, bool deep_scan, + const CompatibilityList& compatibility_list); + ~GameListWorker() override; + + /// Starts the processing of directory tree information. + void run() override; + + /// Tells the worker that it should no longer continue processing. Thread-safe. + void Cancel(); + +signals: + /** + * The `EntryReady` signal is emitted once an entry has been prepared and is ready + * to be added to the game list. + * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. + */ + void EntryReady(QList<QStandardItem*> entry_items); + + /** + * After the worker has traversed the game directory looking for entries, this signal is emitted + * with a list of folders that should be watched for changes as well. + */ + void Finished(QStringList watch_list); + +private: + void AddInstalledTitlesToGameList(); + void FillControlMap(const std::string& dir_path); + void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); + + std::shared_ptr<FileSys::VfsFilesystem> vfs; + std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map; + QStringList watch_list; + QString dir_path; + bool deep_scan; + const CompatibilityList& compatibility_list; + std::atomic_bool stop_processing; +}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e11ba7854..45bb1d1d1 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -12,12 +12,14 @@ #define QT_NO_OPENGL #include <QDesktopWidget> +#include <QDialogButtonBox> #include <QFileDialog> #include <QMessageBox> #include <QtGui> #include <QtWidgets> #include <fmt/format.h> #include "common/common_paths.h" +#include "common/file_util.h" #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -29,9 +31,14 @@ #include "core/core.h" #include "core/crypto/key_manager.h" #include "core/file_sys/card_image.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/savedata_factory.h" +#include "core/file_sys/submission_package.h" #include "core/file_sys/vfs_real.h" +#include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/perf_stats.h" @@ -40,6 +47,7 @@ #include "video_core/debug_utils/debug_utils.h" #include "yuzu/about_dialog.h" #include "yuzu/bootmanager.h" +#include "yuzu/compatibility_list.h" #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_dialog.h" #include "yuzu/debugger/console.h" @@ -73,6 +81,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; */ enum class CalloutFlag : uint32_t { Telemetry = 0x1, + DRDDeprecation = 0x2, }; static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { @@ -128,11 +137,11 @@ GMainWindow::GMainWindow() ConnectMenuEvents(); ConnectWidgetEvents(); - LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch, + LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString("yuzu %1| %2-%3") - .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); + .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc)); show(); // Necessary to load titles from nand in gamelist. @@ -372,6 +381,10 @@ void GMainWindow::ConnectMenuEvents() { &GMainWindow::OnMenuInstallToNAND); connect(ui.action_Select_Game_List_Root, &QAction::triggered, this, &GMainWindow::OnMenuSelectGameListRoot); + connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, + [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); + connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, + [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); }); connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); // Emulation @@ -419,7 +432,7 @@ void GMainWindow::OnDisplayTitleBars(bool show) { } } -bool GMainWindow::SupportsRequiredGLExtensions() { +QStringList GMainWindow::GetUnsupportedGLExtensions() { QStringList unsupported_ext; if (!GLAD_GL_ARB_program_interface_query) @@ -432,6 +445,12 @@ bool GMainWindow::SupportsRequiredGLExtensions() { unsupported_ext.append("ARB_vertex_type_10f_11f_11f_rev"); if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) unsupported_ext.append("ARB_texture_mirror_clamp_to_edge"); + if (!GLAD_GL_ARB_base_instance) + unsupported_ext.append("ARB_base_instance"); + if (!GLAD_GL_ARB_texture_storage) + unsupported_ext.append("ARB_texture_storage"); + if (!GLAD_GL_ARB_multi_bind) + unsupported_ext.append("ARB_multi_bind"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) @@ -446,7 +465,7 @@ bool GMainWindow::SupportsRequiredGLExtensions() { for (const QString& ext : unsupported_ext) LOG_CRITICAL(Frontend, "Unsupported GL extension: {}", ext.toStdString()); - return unsupported_ext.empty(); + return unsupported_ext; } bool GMainWindow::LoadROM(const QString& filename) { @@ -464,11 +483,13 @@ bool GMainWindow::LoadROM(const QString& filename) { return false; } - if (!SupportsRequiredGLExtensions()) { - QMessageBox::critical( - this, tr("Error while initializing OpenGL Core!"), - tr("Your GPU may not support one or more required OpenGL extensions. Please " - "ensure you have the latest graphics driver. See the log for more details.")); + QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions(); + if (!unsupported_gl_extensions.empty()) { + QMessageBox::critical(this, tr("Error while initializing OpenGL Core!"), + tr("Your GPU may not support one or more required OpenGL" + "extensions. Please ensure you have the latest graphics " + "driver.<br><br>Unsupported extensions:<br>") + + unsupported_gl_extensions.join("<br>")); return false; } @@ -479,6 +500,23 @@ bool GMainWindow::LoadROM(const QString& filename) { const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; + const auto drd_callout = + (UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0; + + if (result == Core::System::ResultStatus::Success && + system.GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory && + drd_callout) { + UISettings::values.callout_flags |= static_cast<u32>(CalloutFlag::DRDDeprecation); + QMessageBox::warning( + this, tr("Warning Outdated Game Format"), + tr("You are using the deconstructed ROM directory format for this game, which is an " + "outdated format that has been superseded by others such as NCA, NAX, XCI, or " + "NSP. Deconstructed ROM directories lack icons, metadata, and update " + "support.<br><br>For an explanation of the various Switch formats yuzu supports, <a " + "href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our " + "wiki</a>. This message will not be shown again.")); + } + render_window->DoneCurrent(); if (result != Core::System::ResultStatus::Success) { @@ -563,11 +601,19 @@ void GMainWindow::BootGame(const QString& filename) { std::string title_name; const auto res = Core::System::GetInstance().GetGameName(title_name); - if (res != Loader::ResultStatus::Success) - title_name = FileUtil::GetFilename(filename.toStdString()); + if (res != Loader::ResultStatus::Success) { + const u64 program_id = Core::System::GetInstance().CurrentProcess()->program_id; + + const auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata(); + if (nacp != nullptr) + title_name = nacp->GetApplicationName(); + + if (title_name.empty()) + title_name = FileUtil::GetFilename(filename.toStdString()); + } setWindowTitle(QString("yuzu %1| %4 | %2-%3") - .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc, + .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, QString::fromStdString(title_name))); render_window->show(); @@ -602,7 +648,7 @@ void GMainWindow::ShutdownGame() { game_list->show(); game_list->setFilterFocus(); setWindowTitle(QString("yuzu %1| %2-%3") - .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); + .arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc)); // Disable status bar updates status_bar_update_timer.stop(); @@ -684,14 +730,11 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target QDesktopServices::openUrl(QUrl::fromLocalFile(qpath)); } -void GMainWindow::OnGameListNavigateToGamedbEntry( - u64 program_id, - std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list) { - - auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); +void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id, + const CompatibilityList& compatibility_list) { + const auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); QString directory; - if (it != compatibility_list.end()) directory = it->second.second; @@ -737,7 +780,8 @@ void GMainWindow::OnMenuLoadFolder() { void GMainWindow::OnMenuInstallToNAND() { const QString file_filter = - tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge " + tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " + "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge " "Image (*.xci)"); QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), UISettings::values.roms_path, file_filter); @@ -760,7 +804,7 @@ void GMainWindow::OnMenuInstallToNAND() { tr("Cancel"), 0, progress_maximum, this); progress.setWindowModality(Qt::WindowModal); - for (size_t i = 0; i < src->GetSize(); i += buffer.size()) { + for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { if (progress.wasCanceled()) { dest->Resize(0); return false; @@ -797,22 +841,34 @@ void GMainWindow::OnMenuInstallToNAND() { QMessageBox::Yes; }; - if (filename.endsWith("xci", Qt::CaseInsensitive)) { - const auto xci = std::make_shared<FileSys::XCI>( - vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - if (xci->GetStatus() != Loader::ResultStatus::Success) { + if (filename.endsWith("xci", Qt::CaseInsensitive) || + filename.endsWith("nsp", Qt::CaseInsensitive)) { + + std::shared_ptr<FileSys::NSP> nsp; + if (filename.endsWith("nsp", Qt::CaseInsensitive)) { + nsp = std::make_shared<FileSys::NSP>( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + if (nsp->IsExtractedType()) + failed(); + } else { + const auto xci = std::make_shared<FileSys::XCI>( + vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); + nsp = xci->GetSecurePartitionNSP(); + } + + if (nsp->GetStatus() != Loader::ResultStatus::Success) { failed(); return; } const auto res = - Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy); + Service::FileSystem::GetUserNANDContents()->InstallEntry(nsp, false, qt_raw_copy); if (res == FileSys::InstallResult::Success) { success(); } else { if (res == FileSys::InstallResult::ErrorAlreadyExists) { if (overwrite()) { const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( - xci, true, qt_raw_copy); + nsp, true, qt_raw_copy); if (res2 == FileSys::InstallResult::Success) { success(); } else { @@ -826,7 +882,11 @@ void GMainWindow::OnMenuInstallToNAND() { } else { const auto nca = std::make_shared<FileSys::NCA>( vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); - if (nca->GetStatus() != Loader::ResultStatus::Success) { + const auto id = nca->GetStatus(); + + // Game updates necessary are missing base RomFS + if (id != Loader::ResultStatus::Success && + id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { failed(); return; } @@ -885,6 +945,28 @@ void GMainWindow::OnMenuSelectGameListRoot() { } } +void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { + const auto res = QMessageBox::information( + this, tr("Changing Emulated Directory"), + tr("You are about to change the emulated %1 directory of the system. Please note " + "that this does not also move the contents of the previous directory to the " + "new one and you will have to do that yourself.") + .arg(target == EmulatedDirectoryTarget::SDMC ? tr("SD card") : tr("NAND")), + QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel}); + + if (res == QMessageBox::Cancel) + return; + + QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory")); + if (!dir_path.isEmpty()) { + FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir + : FileUtil::UserPath::NANDDir, + dir_path.toStdString()); + Service::FileSystem::CreateFactories(vfs); + game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + } +} + void GMainWindow::OnMenuRecentFile() { QAction* action = qobject_cast<QAction*>(sender()); assert(action); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 0b97e8220..552e3e61c 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -6,10 +6,14 @@ #include <memory> #include <unordered_map> + #include <QMainWindow> #include <QTimer> + +#include "common/common_types.h" #include "core/core.h" #include "ui_main.h" +#include "yuzu/compatibility_list.h" #include "yuzu/hotkeys.h" class Config; @@ -32,6 +36,11 @@ namespace Tegra { class DebugContext; } +enum class EmulatedDirectoryTarget { + NAND, + SDMC, +}; + class GMainWindow : public QMainWindow { Q_OBJECT @@ -85,7 +94,7 @@ private: void ConnectWidgetEvents(); void ConnectMenuEvents(); - bool SupportsRequiredGLExtensions(); + QStringList GetUnsupportedGLExtensions(); bool LoadROM(const QString& filename); void BootGame(const QString& filename); void ShutdownGame(); @@ -129,14 +138,15 @@ private slots: /// Called whenever a user selects a game in the game list widget. void OnGameListLoadFile(QString game_path); void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); - void OnGameListNavigateToGamedbEntry( - u64 program_id, - std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list); + void OnGameListNavigateToGamedbEntry(u64 program_id, + const CompatibilityList& compatibility_list); void OnMenuLoadFile(); void OnMenuLoadFolder(); void OnMenuInstallToNAND(); /// Called whenever a user selects the "File->Select Game List Root" menu item void OnMenuSelectGameListRoot(); + /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card + void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); void OnMenuRecentFile(); void OnConfigure(); void OnAbout(); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index faa0c626a..3879d4813 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -65,6 +65,9 @@ <addaction name="action_Select_Game_List_Root"/> <addaction name="menu_recent_files"/> <addaction name="separator"/> + <addaction name="action_Select_NAND_Directory"/> + <addaction name="action_Select_SDMC_Directory"/> + <addaction name="separator"/> <addaction name="action_Exit"/> </widget> <widget class="QMenu" name="menu_Emulation"> @@ -204,6 +207,22 @@ <string>Selects a folder to display in the game list</string> </property> </action> + <action name="action_Select_NAND_Directory"> + <property name="text"> + <string>Select NAND Directory...</string> + </property> + <property name="toolTip"> + <string>Selects a folder to use as the root of the emulated NAND</string> + </property> + </action> + <action name="action_Select_SDMC_Directory"> + <property name="text"> + <string>Select SD Card Directory...</string> + </property> + <property name="toolTip"> + <string>Selects a folder to use as the root of the emulated SD card</string> + </property> + </action> <action name="action_Fullscreen"> <property name="checkable"> <bool>true</bool> diff --git a/src/yuzu/util/util.cpp b/src/yuzu/util/util.cpp index e99042a23..62c080aff 100644 --- a/src/yuzu/util/util.cpp +++ b/src/yuzu/util/util.cpp @@ -30,8 +30,9 @@ QPixmap CreateCirclePixmapFromColor(const QColor& color) { QPixmap circle_pixmap(16, 16); circle_pixmap.fill(Qt::transparent); QPainter painter(&circle_pixmap); + painter.setRenderHint(QPainter::Antialiasing); painter.setPen(color); painter.setBrush(color); - painter.drawEllipse(0, 0, 15, 15); + painter.drawEllipse({circle_pixmap.width() / 2.0, circle_pixmap.height() / 2.0}, 7.0, 7.0); return circle_pixmap; } diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index a95580152..7ec1f5110 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -108,15 +108,27 @@ void Config::ReadValues() { // Audio Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto"); + Settings::values.enable_audio_stretching = + sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true); Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); Settings::values.volume = sdl2_config->GetReal("Audio", "volume", 1); // Data Storage Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir, + sdl2_config->Get("Data Storage", "nand_directory", + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir))); + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir, + sdl2_config->Get("Data Storage", "nand_directory", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir))); // System Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); + Settings::values.username = sdl2_config->Get("System", "username", "yuzu"); + if (Settings::values.username.empty()) { + Settings::values.username = "yuzu"; + } // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 6ed9e7962..d35c441e9 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -150,6 +150,12 @@ swap_screen = # auto (default): Auto-select, null: No audio output, cubeb: Cubeb audio engine (if available) output_engine = +# Whether or not to enable the audio-stretching post-processing effect. +# This effect adjusts audio speed to match emulation speed and helps prevent audio stutter, +# at the cost of increasing audio latency. +# 0: No, 1 (default): Yes +enable_audio_stretching = + # Which audio device to use. # auto (default): Auto-select output_device = @@ -170,7 +176,7 @@ use_docked_mode = # Sets the account username, max length is 32 characters # yuzu (default) -username = +username = yuzu # Sets the systems language index # 0: Japanese, 1: English (default), 2: French, 3: German, 4: Italian, 5: Spanish, 6: Chinese, diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 351dd9225..0733301b2 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -16,6 +16,7 @@ #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" +#include "input_common/sdl/sdl.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { @@ -91,6 +92,12 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() { unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge) unsupported_ext.push_back("ARB_texture_mirror_clamp_to_edge"); + if (!GLAD_GL_ARB_base_instance) + unsupported_ext.push_back("ARB_base_instance"); + if (!GLAD_GL_ARB_texture_storage) + unsupported_ext.push_back("ARB_texture_storage"); + if (!GLAD_GL_ARB_multi_bind) + unsupported_ext.push_back("ARB_multi_bind"); // Extensions required to support some texture formats. if (!GLAD_GL_EXT_texture_compression_s3tc) @@ -114,7 +121,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { SDL_SetMainReady(); // Initialize the window - if (SDL_Init(SDL_INIT_VIDEO) < 0) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); } @@ -128,7 +135,7 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name, + std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); render_window = SDL_CreateWindow(window_title.c_str(), @@ -166,13 +173,15 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { OnResize(); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); SDL_PumpEvents(); - LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_name, Common::g_scm_branch, + SDL_GL_SetSwapInterval(false); + LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); DoneCurrent(); } EmuWindow_SDL2::~EmuWindow_SDL2() { + InputCommon::SDL::CloseSDLJoysticks(); SDL_GL_DeleteContext(gl_context); SDL_Quit(); @@ -217,6 +226,9 @@ void EmuWindow_SDL2::PollEvents() { case SDL_QUIT: is_open = false; break; + default: + InputCommon::SDL::HandleGameControllerEvent(event); + break; } } } diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 41e7da897..b2559b717 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -10,6 +10,7 @@ #include <fmt/ostream.h> #include "common/common_paths.h" +#include "common/file_util.h" #include "common/logging/backend.h" #include "common/logging/filter.h" #include "common/logging/log.h" @@ -19,8 +20,10 @@ #include "common/string_util.h" #include "common/telemetry.h" #include "core/core.h" +#include "core/crypto/key_manager.h" #include "core/file_sys/vfs_real.h" #include "core/gdbstub/gdbstub.h" +#include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/settings.h" #include "core/telemetry_session.h" @@ -28,7 +31,6 @@ #include "yuzu_cmd/emu_window/emu_window_sdl2.h" #include <getopt.h> -#include "core/crypto/key_manager.h" #ifndef _MSC_VER #include <unistd.h> #endif @@ -81,6 +83,9 @@ int main(int argc, char** argv) { int option_index = 0; bool use_gdbstub = Settings::values.use_gdbstub; u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port); + + InitializeLogging(); + char* endarg; #ifdef _WIN32 int argc_w; @@ -143,8 +148,6 @@ int main(int argc, char** argv) { LocalFree(argv_w); #endif - InitializeLogging(); - MicroProfileOnThreadCreate("EmuThread"); SCOPE_EXIT({ MicroProfileShutdown(); }); @@ -167,6 +170,7 @@ int main(int argc, char** argv) { Core::System& system{Core::System::GetInstance()}; system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); + Service::FileSystem::CreateFactories(system.GetFilesystem()); SCOPE_EXIT({ system.Shutdown(); }); |