summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/audio_core/algorithm/interpolate.cpp5
-rw-r--r--src/audio_core/audio_out.cpp3
-rw-r--r--src/audio_core/audio_renderer.cpp7
-rw-r--r--src/audio_core/cubeb_sink.cpp2
-rw-r--r--src/audio_core/cubeb_sink.h2
-rw-r--r--src/audio_core/null_sink.h2
-rw-r--r--src/audio_core/sink_details.cpp53
-rw-r--r--src/audio_core/sink_details.h25
-rw-r--r--src/common/logging/backend.cpp4
-rw-r--r--src/common/thread_queue_list.h16
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp1
-rw-r--r--src/core/core.cpp8
-rw-r--r--src/core/core.h4
-rw-r--r--src/core/crypto/key_manager.cpp1
-rw-r--r--src/core/file_sys/directory.h4
-rw-r--r--src/core/file_sys/patch_manager.cpp63
-rw-r--r--src/core/file_sys/patch_manager.h5
-rw-r--r--src/core/file_sys/registered_cache.cpp53
-rw-r--r--src/core/file_sys/savedata_factory.cpp9
-rw-r--r--src/core/file_sys/savedata_factory.h4
-rw-r--r--src/core/file_sys/system_archive/ng_word.cpp81
-rw-r--r--src/core/file_sys/system_archive/ng_word.h14
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp90
-rw-r--r--src/core/file_sys/system_archive/system_archive.h14
-rw-r--r--src/core/file_sys/vfs_vector.cpp1
-rw-r--r--src/core/file_sys/vfs_vector.h53
-rw-r--r--src/core/gdbstub/gdbstub.cpp4
-rw-r--r--src/core/hle/kernel/handle_table.cpp5
-rw-r--r--src/core/hle/kernel/handle_table.h1
-rw-r--r--src/core/hle/kernel/kernel.cpp12
-rw-r--r--src/core/hle/kernel/kernel.h4
-rw-r--r--src/core/hle/kernel/object.cpp6
-rw-r--r--src/core/hle/kernel/object.h3
-rw-r--r--src/core/hle/kernel/process.cpp49
-rw-r--r--src/core/hle/kernel/process.h60
-rw-r--r--src/core/hle/kernel/readable_event.cpp12
-rw-r--r--src/core/hle/kernel/readable_event.h13
-rw-r--r--src/core/hle/kernel/scheduler.cpp66
-rw-r--r--src/core/hle/kernel/scheduler.h69
-rw-r--r--src/core/hle/kernel/shared_memory.cpp14
-rw-r--r--src/core/hle/kernel/shared_memory.h6
-rw-r--r--src/core/hle/kernel/svc.cpp452
-rw-r--r--src/core/hle/kernel/svc.h16
-rw-r--r--src/core/hle/kernel/svc_wrap.h47
-rw-r--r--src/core/hle/kernel/thread.cpp3
-rw-r--r--src/core/hle/kernel/thread.h5
-rw-r--r--src/core/hle/kernel/vm_manager.cpp130
-rw-r--r--src/core/hle/kernel/vm_manager.h317
-rw-r--r--src/core/hle/kernel/writable_event.h2
-rw-r--r--src/core/hle/service/am/am.cpp8
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp3
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp12
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp38
-rw-r--r--src/core/hle/service/ldr/ldr.cpp19
-rw-r--r--src/core/hle/service/nfp/nfp.cpp4
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp4
-rw-r--r--src/core/hle/service/service.cpp31
-rw-r--r--src/core/hle/service/service.h8
-rw-r--r--src/core/hle/service/sm/sm.cpp17
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp3
-rw-r--r--src/core/loader/deconstructed_rom_directory.h2
-rw-r--r--src/core/loader/elf.h2
-rw-r--r--src/core/loader/loader.h12
-rw-r--r--src/core/loader/nax.cpp2
-rw-r--r--src/core/loader/nax.h2
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/nro.cpp15
-rw-r--r--src/core/loader/nro.h10
-rw-r--r--src/core/loader/nso.cpp8
-rw-r--r--src/core/loader/nso.h10
-rw-r--r--src/core/loader/nsp.cpp7
-rw-r--r--src/core/loader/nsp.h3
-rw-r--r--src/core/loader/xci.cpp7
-rw-r--r--src/core/loader/xci.h3
-rw-r--r--src/core/memory.cpp20
-rw-r--r--src/core/settings.h5
-rw-r--r--src/core/telemetry_session.cpp12
-rw-r--r--src/video_core/command_processor.cpp142
-rw-r--r--src/video_core/engines/maxwell_3d.h2
-rw-r--r--src/video_core/engines/shader_bytecode.h9
-rw-r--r--src/video_core/gpu.cpp8
-rw-r--r--src/video_core/macro_interpreter.cpp2
-rw-r--r--src/video_core/morton.cpp1
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp116
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp12
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp45
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp754
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h3
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp2
-rw-r--r--src/video_core/surface.cpp7
-rw-r--r--src/video_core/surface.h552
-rw-r--r--src/video_core/textures/decoders.cpp2
-rw-r--r--src/yuzu/CMakeLists.txt6
-rw-r--r--src/yuzu/configuration/config.cpp123
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure.ui52
-rw-r--r--src/yuzu/configuration/configure_audio.cpp7
-rw-r--r--src/yuzu/configuration/configure_input.cpp60
-rw-r--r--src/yuzu/configuration/configure_input.h8
-rw-r--r--src/yuzu/configuration/configure_input.ui48
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp1
-rw-r--r--src/yuzu/configuration/configure_input_simple.cpp142
-rw-r--r--src/yuzu/configuration/configure_input_simple.h40
-rw-r--r--src/yuzu/configuration/configure_input_simple.ui97
-rw-r--r--src/yuzu/configuration/configure_per_general.cpp170
-rw-r--r--src/yuzu/configuration/configure_per_general.h50
-rw-r--r--src/yuzu/configuration/configure_per_general.ui276
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp1
-rw-r--r--src/yuzu/debugger/wait_tree.cpp4
-rw-r--r--src/yuzu/debugger/wait_tree.h2
-rw-r--r--src/yuzu/game_list.cpp3
-rw-r--r--src/yuzu/game_list.h1
-rw-r--r--src/yuzu/game_list_worker.cpp123
-rw-r--r--src/yuzu/main.cpp29
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/ui_settings.h3
-rw-r--r--src/yuzu_cmd/config.cpp18
-rw-r--r--src/yuzu_cmd/default_ini.h7
122 files changed, 3589 insertions, 1450 deletions
diff --git a/src/audio_core/algorithm/interpolate.cpp b/src/audio_core/algorithm/interpolate.cpp
index 3aea9b0f2..5005ba519 100644
--- a/src/audio_core/algorithm/interpolate.cpp
+++ b/src/audio_core/algorithm/interpolate.cpp
@@ -54,8 +54,9 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
double l = 0.0;
double r = 0.0;
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];
+ const double lanczos_calc = Lanczos(taps, pos + j - taps + 1);
+ l += lanczos_calc * h[j][0];
+ r += lanczos_calc * h[j][1];
}
output.emplace_back(static_cast<s16>(std::clamp(l, -32768.0, 32767.0)));
output.emplace_back(static_cast<s16>(std::clamp(r, -32768.0, 32767.0)));
diff --git a/src/audio_core/audio_out.cpp b/src/audio_core/audio_out.cpp
index 0c8f5b18e..cbba17632 100644
--- a/src/audio_core/audio_out.cpp
+++ b/src/audio_core/audio_out.cpp
@@ -30,8 +30,7 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
Stream::ReleaseCallback&& release_callback) {
if (!sink) {
- const SinkDetails& sink_details = GetSinkDetails(Settings::values.sink_id);
- sink = sink_details.factory(Settings::values.audio_device_id);
+ sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
}
return std::make_shared<Stream>(
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index 2e59894ab..2683f3a5f 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -285,8 +285,11 @@ void AudioRenderer::VoiceState::RefreshBuffer() {
break;
}
- samples =
- Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE);
+ // Only interpolate when necessary, expensive.
+ if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
+ samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
+ STREAM_SAMPLE_RATE);
+ }
is_refresh_pending = false;
}
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index d31a1c844..097328901 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -107,7 +107,7 @@ private:
static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state);
};
-CubebSink::CubebSink(std::string target_device_name) {
+CubebSink::CubebSink(std::string_view target_device_name) {
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
return;
diff --git a/src/audio_core/cubeb_sink.h b/src/audio_core/cubeb_sink.h
index 59cbf05e9..efb9d1634 100644
--- a/src/audio_core/cubeb_sink.h
+++ b/src/audio_core/cubeb_sink.h
@@ -15,7 +15,7 @@ namespace AudioCore {
class CubebSink final : public Sink {
public:
- explicit CubebSink(std::string device_id);
+ explicit CubebSink(std::string_view device_id);
~CubebSink() override;
SinkStream& AcquireSinkStream(u32 sample_rate, u32 num_channels,
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
index a78d78893..61a28d542 100644
--- a/src/audio_core/null_sink.h
+++ b/src/audio_core/null_sink.h
@@ -10,7 +10,7 @@ namespace AudioCore {
class NullSink final : public Sink {
public:
- explicit NullSink(std::string){};
+ explicit NullSink(std::string_view) {}
~NullSink() override = default;
SinkStream& AcquireSinkStream(u32 /*sample_rate*/, u32 /*num_channels*/,
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index 67cf1f3b2..a848eb1c9 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -14,31 +14,68 @@
#include "common/logging/log.h"
namespace AudioCore {
+namespace {
+struct SinkDetails {
+ using FactoryFn = std::unique_ptr<Sink> (*)(std::string_view);
+ using ListDevicesFn = std::vector<std::string> (*)();
-// g_sink_details is ordered in terms of desirability, with the best choice at the top.
-const std::vector<SinkDetails> g_sink_details = {
+ /// Name for this sink.
+ const char* id;
+ /// A method to call to construct an instance of this type of sink.
+ FactoryFn factory;
+ /// A method to call to list available devices.
+ ListDevicesFn list_devices;
+};
+
+// sink_details is ordered in terms of desirability, with the best choice at the top.
+constexpr SinkDetails sink_details[] = {
#ifdef HAVE_CUBEB
- SinkDetails{"cubeb", &std::make_unique<CubebSink, std::string>, &ListCubebSinkDevices},
+ SinkDetails{"cubeb",
+ [](std::string_view device_id) -> std::unique_ptr<Sink> {
+ return std::make_unique<CubebSink>(device_id);
+ },
+ &ListCubebSinkDevices},
#endif
- SinkDetails{"null", &std::make_unique<NullSink, std::string>,
+ SinkDetails{"null",
+ [](std::string_view device_id) -> std::unique_ptr<Sink> {
+ return std::make_unique<NullSink>(device_id);
+ },
[] { return std::vector<std::string>{"null"}; }},
};
const SinkDetails& GetSinkDetails(std::string_view sink_id) {
auto iter =
- std::find_if(g_sink_details.begin(), g_sink_details.end(),
+ std::find_if(std::begin(sink_details), std::end(sink_details),
[sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
- if (sink_id == "auto" || iter == g_sink_details.end()) {
+ if (sink_id == "auto" || iter == std::end(sink_details)) {
if (sink_id != "auto") {
LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id {}", sink_id);
}
// Auto-select.
- // g_sink_details is ordered in terms of desirability, with the best choice at the front.
- iter = g_sink_details.begin();
+ // sink_details is ordered in terms of desirability, with the best choice at the front.
+ iter = std::begin(sink_details);
}
return *iter;
}
+} // Anonymous namespace
+
+std::vector<const char*> GetSinkIDs() {
+ std::vector<const char*> sink_ids(std::size(sink_details));
+
+ std::transform(std::begin(sink_details), std::end(sink_details), std::begin(sink_ids),
+ [](const auto& sink) { return sink.id; });
+
+ return sink_ids;
+}
+
+std::vector<std::string> GetDeviceListForSink(std::string_view sink_id) {
+ return GetSinkDetails(sink_id).list_devices();
+}
+
+std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id) {
+ return GetSinkDetails(sink_id).factory(device_id);
+}
} // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
index 03534b187..bc8786270 100644
--- a/src/audio_core/sink_details.h
+++ b/src/audio_core/sink_details.h
@@ -4,34 +4,21 @@
#pragma once
-#include <functional>
-#include <memory>
#include <string>
#include <string_view>
-#include <utility>
#include <vector>
namespace AudioCore {
class Sink;
-struct SinkDetails {
- using FactoryFn = std::function<std::unique_ptr<Sink>(std::string)>;
- using ListDevicesFn = std::function<std::vector<std::string>()>;
+/// Retrieves the IDs for all available audio sinks.
+std::vector<const char*> GetSinkIDs();
- SinkDetails(const char* id_, FactoryFn factory_, ListDevicesFn list_devices_)
- : id(id_), factory(std::move(factory_)), list_devices(std::move(list_devices_)) {}
+/// Gets the list of devices for a particular sink identified by the given ID.
+std::vector<std::string> GetDeviceListForSink(std::string_view sink_id);
- /// Name for this sink.
- const char* id;
- /// A method to call to construct an instance of this type of sink.
- FactoryFn factory;
- /// A method to call to list available devices.
- ListDevicesFn list_devices;
-};
-
-extern const std::vector<SinkDetails> g_sink_details;
-
-const SinkDetails& GetSinkDetails(std::string_view sink_id);
+/// Creates an audio sink identified by the given device ID.
+std::unique_ptr<Sink> CreateSinkFromID(std::string_view sink_id, std::string_view device_id);
} // namespace AudioCore
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 5753b871a..12f6d0114 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -13,7 +13,7 @@
#include <vector>
#ifdef _WIN32
#include <share.h> // For _SH_DENYWR
-#include <windows.h> // For OutputDebugStringA
+#include <windows.h> // For OutputDebugStringW
#else
#define _SH_DENYWR 0
#endif
@@ -148,7 +148,7 @@ void FileBackend::Write(const Entry& entry) {
void DebuggerBackend::Write(const Entry& entry) {
#ifdef _WIN32
- ::OutputDebugStringA(FormatLogMessage(entry).append(1, '\n').c_str());
+ ::OutputDebugStringW(Common::UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
#endif
}
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index 133122c5f..e7594db68 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -49,6 +49,22 @@ struct ThreadQueueList {
return T();
}
+ template <typename UnaryPredicate>
+ T get_first_filter(UnaryPredicate filter) const {
+ const Queue* cur = first;
+ while (cur != nullptr) {
+ if (!cur->data.empty()) {
+ for (const auto& item : cur->data) {
+ if (filter(item))
+ return item;
+ }
+ }
+ cur = cur->next_nonempty;
+ }
+
+ return T();
+ }
+
T pop_first() {
Queue* cur = first;
while (cur != nullptr) {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4f0ea5d67..93f5ba3fe 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -63,6 +63,10 @@ add_library(core STATIC
file_sys/sdmc_factory.h
file_sys/submission_package.cpp
file_sys/submission_package.h
+ file_sys/system_archive/ng_word.cpp
+ file_sys/system_archive/ng_word.h
+ file_sys/system_archive/system_archive.cpp
+ file_sys/system_archive/system_archive.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 4d2491870..afbda8d8b 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -151,6 +151,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
config.tpidr_el0 = &cb->tpidr_el0;
config.dczid_el0 = 4;
config.ctr_el0 = 0x8444c004;
+ config.cntfrq_el0 = 19200000; // Value from fusee.
// Unpredictable instructions
config.define_unpredictable_behaviour = true;
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 19b4b97c5..fd10199ec 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -8,6 +8,7 @@
#include <thread>
#include <utility>
+#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
@@ -40,7 +41,6 @@ namespace Core {
/*static*/ System System::s_instance;
-namespace {
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
const std::string& path) {
// To account for split 00+01+etc files.
@@ -69,11 +69,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
}
+ if (FileUtil::IsDirectory(path))
+ return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
+
return vfs->OpenFile(path, FileSys::Mode::Read);
}
-} // Anonymous namespace
-
struct System::Impl {
+
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
}
diff --git a/src/core/core.h b/src/core/core.h
index 21dea051b..869921493 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -9,6 +9,7 @@
#include <string>
#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
#include "core/hle/kernel/object.h"
#include "frontend/applets/profile_select.h"
@@ -56,6 +57,9 @@ class TelemetrySession;
struct PerfStatsResults;
+FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
+ const std::string& path);
+
class System {
public:
System(const System&) = delete;
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index ed231927b..ca12fb4ab 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -246,7 +246,6 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
}
std::vector<TicketRaw> out;
- u32 magic{};
for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
buffer[offset + 3] == 0x0) {
diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h
index 12bb90ec8..6690aa575 100644
--- a/src/core/file_sys/directory.h
+++ b/src/core/file_sys/directory.h
@@ -29,8 +29,8 @@ struct Entry {
filename[copy_size] = '\0';
}
- char filename[0x300];
- INSERT_PADDING_BYTES(4);
+ char filename[0x301];
+ INSERT_PADDING_BYTES(3);
EntryType type;
INSERT_PADDING_BYTES(3);
u64 file_size;
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 6b14e08be..61706966e 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -56,6 +56,10 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
PatchManager::~PatchManager() = default;
+u64 PatchManager::GetTitleID() const {
+ return title_id;
+}
+
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
@@ -73,11 +77,15 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+ const auto update_disabled =
+ std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
+
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
- if (update != nullptr && update->GetExeFS() != nullptr &&
+ if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
@@ -95,6 +103,9 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
std::vector<VirtualDir> layers;
layers.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
+ if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
+ continue;
+
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr)
layers.push_back(std::move(exefs_dir));
@@ -111,11 +122,16 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
return exefs;
}
-static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
- const std::string& build_id) {
+std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) const {
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+
std::vector<VirtualFile> out;
out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
+ if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
+ continue;
+
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
@@ -228,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
+ const auto& disabled = Settings::values.disabled_addons[title_id];
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
@@ -237,6 +254,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
layers.reserve(patch_dirs.size() + 1);
layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
+ if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
+ continue;
+
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
@@ -266,13 +286,12 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
VirtualFile update_raw) const {
const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
- title_id, static_cast<u8>(type))
- .c_str();
+ title_id, static_cast<u8>(type));
if (type == ContentRecordType::Program || type == ContentRecordType::Data)
- LOG_INFO(Loader, log_string);
+ LOG_INFO(Loader, "{}", log_string);
else
- LOG_DEBUG(Loader, log_string);
+ LOG_DEBUG(Loader, "{}", log_string);
if (romfs == nullptr)
return romfs;
@@ -282,7 +301,12 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed.GetEntryRaw(update_tid, type);
- if (update != nullptr) {
+
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+ const auto update_disabled =
+ std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
+
+ if (!update_disabled && 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) {
@@ -290,7 +314,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
romfs = new_nca->GetRomFS();
}
- } else if (update_raw != nullptr) {
+ } else if (!update_disabled && update_raw != nullptr) {
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
@@ -320,25 +344,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
+ const auto& disabled = Settings::values.disabled_addons[title_id];
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
PatchManager update{update_tid};
auto [nacp, discard_icon_file] = update.GetControlMetadata();
+ const auto update_disabled =
+ std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
+ const auto update_label = update_disabled ? "[D] Update" : "Update";
+
if (nacp != nullptr) {
- out.insert_or_assign("Update", nacp->GetVersionString());
+ out.insert_or_assign(update_label, nacp->GetVersionString());
} else {
if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = installed.GetEntryVersion(update_tid);
if (meta_ver.value_or(0) == 0) {
- out.insert_or_assign("Update", "");
+ out.insert_or_assign(update_label, "");
} else {
out.insert_or_assign(
- "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
+ update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
}
} else if (update_raw != nullptr) {
- out.insert_or_assign("Update", "PACKED");
+ out.insert_or_assign(update_label, "PACKED");
}
}
@@ -378,7 +407,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (types.empty())
continue;
- out.insert_or_assign(mod->GetName(), types);
+ const auto mod_disabled =
+ std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
+ out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
}
}
@@ -401,7 +432,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
- out.insert_or_assign("DLC", std::move(list));
+ const auto dlc_disabled =
+ std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
+ out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
}
return out;
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 7d168837f..b8a1652fd 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -30,6 +30,8 @@ public:
explicit PatchManager(u64 title_id);
~PatchManager();
+ u64 GetTitleID() const;
+
// Currently tracked ExeFS patches:
// - Game Updates
VirtualDir PatchExeFS(VirtualDir exefs) const;
@@ -63,6 +65,9 @@ public:
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
private:
+ std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) const;
+
u64 title_id;
};
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 07c3af64a..128199063 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -107,42 +107,41 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
VirtualFile RegisteredCache::OpenFileOrDirectoryConcat(const VirtualDir& dir,
std::string_view path) const {
const auto file = dir->GetFileRelative(path);
- if (file != nullptr)
+ if (file != nullptr) {
return file;
+ }
const auto nca_dir = dir->GetDirectoryRelative(path);
- if (nca_dir != nullptr) {
- const auto nca_dir = dir->GetDirectoryRelative(path);
- VirtualFile file = nullptr;
+ if (nca_dir == nullptr) {
+ return nullptr;
+ }
- const auto files = nca_dir->GetFiles();
- if (files.size() == 1 && files[0]->GetName() == "00") {
- file = files[0];
+ const auto files = nca_dir->GetFiles();
+ if (files.size() == 1 && files[0]->GetName() == "00") {
+ return files[0];
+ }
+
+ std::vector<VirtualFile> concat;
+ // Since the files are a two-digit hex number, max is FF.
+ 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));
} else {
- std::vector<VirtualFile> concat;
- // Since the files are a two-digit hex number, max is FF.
- 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));
- } else {
- next = nca_dir->GetFile(fmt::format("{:02x}", i));
- if (next != nullptr)
- concat.push_back(std::move(next));
- else
- break;
- }
+ next = nca_dir->GetFile(fmt::format("{:02x}", i));
+ if (next != nullptr) {
+ concat.push_back(std::move(next));
+ } else {
+ break;
}
-
- if (concat.empty())
- return nullptr;
-
- file = ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
}
+ }
- return file;
+ if (concat.empty()) {
+ return nullptr;
}
- return nullptr;
+
+ return ConcatenatedVfsFile::MakeConcatenatedFile(concat, concat.front()->GetName());
}
VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 5434f2149..d63b7f19b 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -18,7 +18,11 @@ std::string SaveDataDescriptor::DebugInfo() const {
static_cast<u8>(type), title_id, user_id[1], user_id[0], save_id);
}
-SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {}
+SaveDataFactory::SaveDataFactory(VirtualDir save_directory) : dir(std::move(save_directory)) {
+ // Delete all temporary storages
+ // On hardware, it is expected that temporary storage be empty at first use.
+ dir->DeleteSubdirectoryRecursive("temp");
+}
SaveDataFactory::~SaveDataFactory() = default;
@@ -120,8 +124,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::TemporaryStorage:
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
+ case SaveDataType::CacheStorage:
+ return fmt::format("{}save/cache/{:016X}", out, title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
+ return fmt::format("{}save/unknown_{:X}/{:016X}", out, static_cast<u8>(type), title_id);
}
}
diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h
index 2a0088040..bd4919610 100644
--- a/src/core/file_sys/savedata_factory.h
+++ b/src/core/file_sys/savedata_factory.h
@@ -17,8 +17,10 @@ namespace FileSys {
enum class SaveDataSpaceId : u8 {
NandSystem = 0,
NandUser = 1,
- SdCard = 2,
+ SdCardSystem = 2,
TemporaryStorage = 3,
+ SdCardUser = 4,
+ ProperSystem = 100,
};
enum class SaveDataType : u8 {
diff --git a/src/core/file_sys/system_archive/ng_word.cpp b/src/core/file_sys/system_archive/ng_word.cpp
new file mode 100644
index 000000000..f4443784d
--- /dev/null
+++ b/src/core/file_sys/system_archive/ng_word.cpp
@@ -0,0 +1,81 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/format.h>
+#include "common/common_types.h"
+#include "core/file_sys/system_archive/ng_word.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys::SystemArchive {
+
+namespace NgWord1Data {
+
+constexpr std::size_t NUMBER_WORD_TXT_FILES = 0x10;
+
+// Should this archive replacement mysteriously not work on a future game, consider updating.
+constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x19}; // 5.1.0 System Version
+
+constexpr std::array<u8, 30> WORD_TXT{
+ 0xFE, 0xFF, 0x00, 0x5E, 0x00, 0x76, 0x00, 0x65, 0x00, 0x72, 0x00, 0x79, 0x00, 0x62, 0x00,
+ 0x61, 0x00, 0x64, 0x00, 0x77, 0x00, 0x6F, 0x00, 0x72, 0x00, 0x64, 0x00, 0x24, 0x00, 0x0A,
+}; // "^verybadword$" in UTF-16
+
+} // namespace NgWord1Data
+
+VirtualDir NgWord1() {
+ std::vector<VirtualFile> files(NgWord1Data::NUMBER_WORD_TXT_FILES);
+
+ for (std::size_t i = 0; i < files.size(); ++i) {
+ files[i] = std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
+ NgWord1Data::WORD_TXT, fmt::format("{}.txt", i));
+ }
+
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::WORD_TXT.size()>>(
+ NgWord1Data::WORD_TXT, "common.txt"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord1Data::VERSION_DAT.size()>>(
+ NgWord1Data::VERSION_DAT, "version.dat"));
+
+ return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+}
+
+namespace NgWord2Data {
+
+constexpr std::size_t NUMBER_AC_NX_FILES = 0x10;
+
+// Should this archive replacement mysteriously not work on a future game, consider updating.
+constexpr std::array<u8, 4> VERSION_DAT{0x0, 0x0, 0x0, 0x15}; // 5.1.0 System Version
+
+constexpr std::array<u8, 0x2C> AC_NX_DATA{
+ 0x1F, 0x8B, 0x08, 0x08, 0xD5, 0x2C, 0x09, 0x5C, 0x04, 0x00, 0x61, 0x63, 0x72, 0x61, 0x77,
+ 0x00, 0xED, 0xC1, 0x01, 0x0D, 0x00, 0x00, 0x00, 0xC2, 0x20, 0xFB, 0xA7, 0xB6, 0xC7, 0x07,
+ 0x0C, 0x00, 0x00, 0x00, 0xC8, 0x3B, 0x11, 0x00, 0x1C, 0xC7, 0x00, 0x10, 0x00, 0x00,
+}; // Deserializes to no bad words
+
+} // namespace NgWord2Data
+
+VirtualDir NgWord2() {
+ std::vector<VirtualFile> files(NgWord2Data::NUMBER_AC_NX_FILES * 3);
+
+ for (std::size_t i = 0; i < NgWord2Data::NUMBER_AC_NX_FILES; ++i) {
+ files[3 * i] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b1_nx", i));
+ files[3 * i + 1] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_b2_nx", i));
+ files[3 * i + 2] = std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, fmt::format("ac_{}_not_b_nx", i));
+ }
+
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, "ac_common_b1_nx"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, "ac_common_b2_nx"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::AC_NX_DATA.size()>>(
+ NgWord2Data::AC_NX_DATA, "ac_common_not_b_nx"));
+ files.push_back(std::make_shared<ArrayVfsFile<NgWord2Data::VERSION_DAT.size()>>(
+ NgWord2Data::VERSION_DAT, "version.dat"));
+
+ return std::make_shared<VectorVfsDirectory>(files, std::vector<VirtualDir>{}, "data");
+}
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/ng_word.h b/src/core/file_sys/system_archive/ng_word.h
new file mode 100644
index 000000000..cd81e0abb
--- /dev/null
+++ b/src/core/file_sys/system_archive/ng_word.h
@@ -0,0 +1,14 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys::SystemArchive {
+
+VirtualDir NgWord1();
+VirtualDir NgWord2();
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
new file mode 100644
index 000000000..e3e79f40a
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -0,0 +1,90 @@
+// 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/file_sys/romfs.h"
+#include "core/file_sys/system_archive/ng_word.h"
+#include "core/file_sys/system_archive/system_archive.h"
+
+namespace FileSys::SystemArchive {
+
+constexpr u64 SYSTEM_ARCHIVE_BASE_TITLE_ID = 0x0100000000000800;
+constexpr std::size_t SYSTEM_ARCHIVE_COUNT = 0x28;
+
+using SystemArchiveSupplier = VirtualDir (*)();
+
+struct SystemArchiveDescriptor {
+ u64 title_id;
+ const char* name;
+ SystemArchiveSupplier supplier;
+};
+
+constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
+ {0x0100000000000800, "CertStore", nullptr},
+ {0x0100000000000801, "ErrorMessage", nullptr},
+ {0x0100000000000802, "MiiModel", nullptr},
+ {0x0100000000000803, "BrowserDll", nullptr},
+ {0x0100000000000804, "Help", nullptr},
+ {0x0100000000000805, "SharedFont", nullptr},
+ {0x0100000000000806, "NgWord", &NgWord1},
+ {0x0100000000000807, "SsidList", nullptr},
+ {0x0100000000000808, "Dictionary", nullptr},
+ {0x0100000000000809, "SystemVersion", nullptr},
+ {0x010000000000080A, "AvatarImage", nullptr},
+ {0x010000000000080B, "LocalNews", nullptr},
+ {0x010000000000080C, "Eula", nullptr},
+ {0x010000000000080D, "UrlBlackList", nullptr},
+ {0x010000000000080E, "TimeZoneBinary", nullptr},
+ {0x010000000000080F, "CertStoreCruiser", nullptr},
+ {0x0100000000000810, "FontNintendoExtension", nullptr},
+ {0x0100000000000811, "FontStandard", nullptr},
+ {0x0100000000000812, "FontKorean", nullptr},
+ {0x0100000000000813, "FontChineseTraditional", nullptr},
+ {0x0100000000000814, "FontChineseSimple", nullptr},
+ {0x0100000000000815, "FontBfcpx", nullptr},
+ {0x0100000000000816, "SystemUpdate", nullptr},
+ {0x0100000000000817, "0100000000000817", nullptr},
+ {0x0100000000000818, "FirmwareDebugSettings", nullptr},
+ {0x0100000000000819, "BootImagePackage", nullptr},
+ {0x010000000000081A, "BootImagePackageSafe", nullptr},
+ {0x010000000000081B, "BootImagePackageExFat", nullptr},
+ {0x010000000000081C, "BootImagePackageExFatSafe", nullptr},
+ {0x010000000000081D, "FatalMessage", nullptr},
+ {0x010000000000081E, "ControllerIcon", nullptr},
+ {0x010000000000081F, "PlatformConfigIcosa", nullptr},
+ {0x0100000000000820, "PlatformConfigCopper", nullptr},
+ {0x0100000000000821, "PlatformConfigHoag", nullptr},
+ {0x0100000000000822, "ControllerFirmware", nullptr},
+ {0x0100000000000823, "NgWord2", &NgWord2},
+ {0x0100000000000824, "PlatformConfigIcosaMariko", nullptr},
+ {0x0100000000000825, "ApplicationBlackList", nullptr},
+ {0x0100000000000826, "RebootlessSystemUpdateVersion", nullptr},
+ {0x0100000000000827, "ContentActionTable", nullptr},
+}};
+
+VirtualFile SynthesizeSystemArchive(const u64 title_id) {
+ if (title_id < SYSTEM_ARCHIVES.front().title_id || title_id > SYSTEM_ARCHIVES.back().title_id)
+ return nullptr;
+
+ const auto& desc = SYSTEM_ARCHIVES[title_id - SYSTEM_ARCHIVE_BASE_TITLE_ID];
+
+ LOG_INFO(Service_FS, "Synthesizing system archive '{}' (0x{:016X}).", desc.name, desc.title_id);
+
+ if (desc.supplier == nullptr)
+ return nullptr;
+
+ const auto dir = desc.supplier();
+
+ if (dir == nullptr)
+ return nullptr;
+
+ const auto romfs = CreateRomFS(dir);
+
+ if (romfs == nullptr)
+ return nullptr;
+
+ LOG_INFO(Service_FS, " - System archive generation successful!");
+ return romfs;
+}
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_archive.h b/src/core/file_sys/system_archive/system_archive.h
new file mode 100644
index 000000000..724a8eb17
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_archive.h
@@ -0,0 +1,14 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys::SystemArchive {
+
+VirtualFile SynthesizeSystemArchive(u64 title_id);
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index 808f31e81..515626658 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <algorithm>
-#include <cstring>
#include <utility>
#include "core/file_sys/vfs_vector.h"
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index 3e3f790c3..ac36cb2ee 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -4,10 +4,63 @@
#pragma once
+#include <cstring>
#include "core/file_sys/vfs.h"
namespace FileSys {
+// An implementation of VfsFile that is backed by a statically-sized array
+template <std::size_t size>
+class ArrayVfsFile : public VfsFile {
+public:
+ ArrayVfsFile(std::array<u8, size> data, std::string name = "", VirtualDir parent = nullptr)
+ : data(data), name(std::move(name)), parent(std::move(parent)) {}
+
+ std::string GetName() const override {
+ return name;
+ }
+
+ std::size_t GetSize() const override {
+ return size;
+ }
+
+ bool Resize(std::size_t new_size) override {
+ return false;
+ }
+
+ std::shared_ptr<VfsDirectory> GetContainingDirectory() const override {
+ return parent;
+ }
+
+ bool IsWritable() const override {
+ return false;
+ }
+
+ bool IsReadable() const override {
+ return true;
+ }
+
+ std::size_t Read(u8* data_, std::size_t length, std::size_t offset) const override {
+ const auto read = std::min(length, size - offset);
+ std::memcpy(data_, data.data() + offset, read);
+ return read;
+ }
+
+ std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override {
+ return 0;
+ }
+
+ bool Rename(std::string_view name) override {
+ this->name = name;
+ return true;
+ }
+
+private:
+ std::array<u8, size> data;
+ std::string name;
+ VirtualDir parent;
+};
+
// An implementation of VfsFile that is backed by a vector optionally supplied upon construction
class VectorVfsFile : public VfsFile {
public:
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index e6b5171ee..a1cad4fcb 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -201,11 +201,11 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
modules.push_back(std::move(module));
}
-static Kernel::Thread* FindThreadById(int id) {
+static Kernel::Thread* FindThreadById(s64 id) {
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (auto& thread : threads) {
- if (thread->GetThreadID() == static_cast<u32>(id)) {
+ if (thread->GetThreadID() == static_cast<u64>(id)) {
current_core = core;
return thread.get();
}
diff --git a/src/core/hle/kernel/handle_table.cpp b/src/core/hle/kernel/handle_table.cpp
index 1bf79b692..c8acde5b1 100644
--- a/src/core/hle/kernel/handle_table.cpp
+++ b/src/core/hle/kernel/handle_table.cpp
@@ -42,9 +42,10 @@ ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
u16 generation = next_generation++;
// Overflow count so it fits in the 15 bits dedicated to the generation in the handle.
- // CTR-OS doesn't use generation 0, so skip straight to 1.
- if (next_generation >= (1 << 15))
+ // Horizon OS uses zero to represent an invalid handle, so skip to 1.
+ if (next_generation >= (1 << 15)) {
next_generation = 1;
+ }
generations[slot] = generation;
objects[slot] = std::move(obj);
diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h
index e3f3e3fb8..6b7927fd8 100644
--- a/src/core/hle/kernel/handle_table.h
+++ b/src/core/hle/kernel/handle_table.h
@@ -13,6 +13,7 @@
namespace Kernel {
enum KernelHandle : Handle {
+ InvalidHandle = 0,
CurrentThread = 0xFFFF8000,
CurrentProcess = 0xFFFF8001,
};
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index e441c5bc6..1c2290651 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -112,7 +112,7 @@ struct KernelCore::Impl {
void Shutdown() {
next_object_id = 0;
- next_process_id = 10;
+ next_process_id = Process::ProcessIDMin;
next_thread_id = 1;
process_list.clear();
@@ -153,10 +153,8 @@ struct KernelCore::Impl {
}
std::atomic<u32> next_object_id{0};
- // TODO(Subv): Start the process ids from 10 for now, as lower PIDs are
- // reserved for low-level services
- std::atomic<u32> next_process_id{10};
- std::atomic<u32> next_thread_id{1};
+ std::atomic<u64> next_process_id{Process::ProcessIDMin};
+ std::atomic<u64> next_thread_id{1};
// Lists all processes that exist in the current session.
std::vector<SharedPtr<Process>> process_list;
@@ -242,11 +240,11 @@ u32 KernelCore::CreateNewObjectID() {
return impl->next_object_id++;
}
-u32 KernelCore::CreateNewThreadID() {
+u64 KernelCore::CreateNewThreadID() {
return impl->next_thread_id++;
}
-u32 KernelCore::CreateNewProcessID() {
+u64 KernelCore::CreateNewProcessID() {
return impl->next_process_id++;
}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index ea00c89f5..58c9d108b 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -88,10 +88,10 @@ private:
u32 CreateNewObjectID();
/// Creates a new process ID, incrementing the internal process ID counter;
- u32 CreateNewProcessID();
+ u64 CreateNewProcessID();
/// Creates a new thread ID, incrementing the internal thread ID counter.
- u32 CreateNewThreadID();
+ u64 CreateNewThreadID();
/// Creates a timer callback handle for the given timer.
ResultVal<Handle> CreateTimerCallbackHandle(const SharedPtr<Timer>& timer);
diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp
index d87a62bb9..806078638 100644
--- a/src/core/hle/kernel/object.cpp
+++ b/src/core/hle/kernel/object.cpp
@@ -13,16 +13,17 @@ Object::~Object() = default;
bool Object::IsWaitable() const {
switch (GetHandleType()) {
- case HandleType::Event:
+ case HandleType::ReadableEvent:
case HandleType::Thread:
+ case HandleType::Process:
case HandleType::Timer:
case HandleType::ServerPort:
case HandleType::ServerSession:
return true;
case HandleType::Unknown:
+ case HandleType::WritableEvent:
case HandleType::SharedMemory:
- case HandleType::Process:
case HandleType::AddressArbiter:
case HandleType::ResourceLimit:
case HandleType::ClientPort:
@@ -31,6 +32,7 @@ bool Object::IsWaitable() const {
}
UNREACHABLE();
+ return false;
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index 69082ce3e..f1606a204 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -19,7 +19,8 @@ using Handle = u32;
enum class HandleType : u32 {
Unknown,
- Event,
+ WritableEvent,
+ ReadableEvent,
SharedMemory,
Thread,
Process,
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 7ca538401..5356a4a3f 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -9,6 +9,7 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/program_metadata.h"
+#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
@@ -44,8 +45,28 @@ SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
return process;
}
+SharedPtr<ResourceLimit> Process::GetResourceLimit() const {
+ return resource_limit;
+}
+
+ResultCode Process::ClearSignalState() {
+ if (status == ProcessStatus::Exited) {
+ LOG_ERROR(Kernel, "called on a terminated process instance.");
+ return ERR_INVALID_STATE;
+ }
+
+ if (!is_signaled) {
+ LOG_ERROR(Kernel, "called on a process instance that isn't signaled.");
+ return ERR_INVALID_STATE;
+ }
+
+ is_signaled = false;
+ return RESULT_SUCCESS;
+}
+
void Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
program_id = metadata.GetTitleID();
+ ideal_processor = metadata.GetMainThreadCore();
is_64bit_process = metadata.Is64BitProgram();
vm_manager.Reset(metadata.GetAddressSpaceType());
}
@@ -129,17 +150,17 @@ void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
vm_manager
.MapMemoryBlock(vm_manager.GetTLSIORegionEndAddress() - stack_size,
std::make_shared<std::vector<u8>>(stack_size, 0), 0, stack_size,
- MemoryState::Mapped)
+ MemoryState::Stack)
.Unwrap();
vm_manager.LogLayout();
- status = ProcessStatus::Running;
+ ChangeStatus(ProcessStatus::Running);
Kernel::SetupMainThread(kernel, entry_point, main_thread_priority, *this);
}
void Process::PrepareForTermination() {
- status = ProcessStatus::Exited;
+ ChangeStatus(ProcessStatus::Exiting);
const auto stop_threads = [this](const std::vector<SharedPtr<Thread>>& thread_list) {
for (auto& thread : thread_list) {
@@ -163,6 +184,8 @@ void Process::PrepareForTermination() {
stop_threads(system.Scheduler(1).GetThreadList());
stop_threads(system.Scheduler(2).GetThreadList());
stop_threads(system.Scheduler(3).GetThreadList());
+
+ ChangeStatus(ProcessStatus::Exited);
}
/**
@@ -261,7 +284,25 @@ ResultCode Process::UnmapMemory(VAddr dst_addr, VAddr /*src_addr*/, u64 size) {
return vm_manager.UnmapRange(dst_addr, size);
}
-Kernel::Process::Process(KernelCore& kernel) : Object{kernel} {}
+Kernel::Process::Process(KernelCore& kernel) : WaitObject{kernel} {}
Kernel::Process::~Process() {}
+void Process::Acquire(Thread* thread) {
+ ASSERT_MSG(!ShouldWait(thread), "Object unavailable!");
+}
+
+bool Process::ShouldWait(Thread* thread) const {
+ return !is_signaled;
+}
+
+void Process::ChangeStatus(ProcessStatus new_status) {
+ if (status == new_status) {
+ return;
+ }
+
+ status = new_status;
+ is_signaled = true;
+ WakeupAllWaitingThreads();
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index ada845c7f..7da367251 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -14,9 +14,10 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/object.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/vm_manager.h"
+#include "core/hle/kernel/wait_object.h"
+#include "core/hle/result.h"
namespace FileSys {
class ProgramMetadata;
@@ -117,8 +118,20 @@ struct CodeSet final {
VAddr entrypoint = 0;
};
-class Process final : public Object {
+class Process final : public WaitObject {
public:
+ enum : u64 {
+ /// Lowest allowed process ID for a kernel initial process.
+ InitialKIPIDMin = 1,
+ /// Highest allowed process ID for a kernel initial process.
+ InitialKIPIDMax = 80,
+
+ /// Lowest allowed process ID for a userland process.
+ ProcessIDMin = 81,
+ /// Highest allowed process ID for a userland process.
+ ProcessIDMax = 0xFFFFFFFFFFFFFFFF,
+ };
+
static constexpr std::size_t RANDOM_ENTROPY_SIZE = 4;
static SharedPtr<Process> Create(KernelCore& kernel, std::string&& name);
@@ -161,7 +174,7 @@ public:
}
/// Gets the unique ID that identifies this particular process.
- u32 GetProcessID() const {
+ u64 GetProcessID() const {
return process_id;
}
@@ -171,14 +184,7 @@ public:
}
/// Gets the resource limit descriptor for this process
- ResourceLimit& GetResourceLimit() {
- return *resource_limit;
- }
-
- /// Gets the resource limit descriptor for this process
- const ResourceLimit& GetResourceLimit() const {
- return *resource_limit;
- }
+ SharedPtr<ResourceLimit> GetResourceLimit() const;
/// Gets the default CPU ID for this process
u8 GetDefaultProcessorID() const {
@@ -219,6 +225,16 @@ public:
return random_entropy.at(index);
}
+ /// Clears the signaled state of the process if and only if it's signaled.
+ ///
+ /// @pre The process must not be already terminated. If this is called on a
+ /// terminated process, then ERR_INVALID_STATE will be returned.
+ ///
+ /// @pre The process must be in a signaled state. If this is called on a
+ /// process instance that is not signaled, ERR_INVALID_STATE will be
+ /// returned.
+ ResultCode ClearSignalState();
+
/**
* Loads process-specifics configuration info with metadata provided
* by an executable.
@@ -258,8 +274,7 @@ public:
ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
ResultCode HeapFree(VAddr target, u32 size);
- ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size,
- MemoryState state = MemoryState::Mapped);
+ ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size);
@@ -267,6 +282,17 @@ private:
explicit Process(KernelCore& kernel);
~Process() override;
+ /// Checks if the specified thread should wait until this process is available.
+ bool ShouldWait(Thread* thread) const override;
+
+ /// Acquires/locks this process for the specified thread if it's available.
+ void Acquire(Thread* thread) override;
+
+ /// Changes the process status. If the status is different
+ /// from the current process status, then this will trigger
+ /// a process signal.
+ void ChangeStatus(ProcessStatus new_status);
+
/// Memory manager for this process.
Kernel::VMManager vm_manager;
@@ -274,10 +300,10 @@ private:
ProcessStatus status;
/// The ID of this process
- u32 process_id = 0;
+ u64 process_id = 0;
/// Title ID corresponding to the process
- u64 program_id;
+ u64 program_id = 0;
/// Resource limit descriptor for this process
SharedPtr<ResourceLimit> resource_limit;
@@ -312,6 +338,10 @@ private:
/// specified by metadata provided to the process during loading.
bool is_64bit_process = true;
+ /// Whether or not this process is signaled. This occurs
+ /// upon the process changing to a different state.
+ bool is_signaled = false;
+
/// Total running time for the process in ticks.
u64 total_process_running_time_ticks = 0;
diff --git a/src/core/hle/kernel/readable_event.cpp b/src/core/hle/kernel/readable_event.cpp
index 92e16b4e6..ba01f495c 100644
--- a/src/core/hle/kernel/readable_event.cpp
+++ b/src/core/hle/kernel/readable_event.cpp
@@ -4,10 +4,10 @@
#include <algorithm>
#include "common/assert.h"
+#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/readable_event.h"
#include "core/hle/kernel/thread.h"
-#include "core/hle/kernel/writable_event.h"
namespace Kernel {
@@ -34,6 +34,16 @@ void ReadableEvent::Clear() {
signaled = false;
}
+ResultCode ReadableEvent::Reset() {
+ if (!signaled) {
+ return ERR_INVALID_STATE;
+ }
+
+ Clear();
+
+ return RESULT_SUCCESS;
+}
+
void ReadableEvent::WakeupAllWaitingThreads() {
WaitObject::WakeupAllWaitingThreads();
diff --git a/src/core/hle/kernel/readable_event.h b/src/core/hle/kernel/readable_event.h
index b1f1f4871..80b3b0aba 100644
--- a/src/core/hle/kernel/readable_event.h
+++ b/src/core/hle/kernel/readable_event.h
@@ -7,6 +7,8 @@
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/wait_object.h"
+union ResultCode;
+
namespace Kernel {
class KernelCore;
@@ -29,7 +31,7 @@ public:
return reset_type;
}
- static const HandleType HANDLE_TYPE = HandleType::Event;
+ static const HandleType HANDLE_TYPE = HandleType::ReadableEvent;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
@@ -39,8 +41,17 @@ public:
void WakeupAllWaitingThreads() override;
+ /// Unconditionally clears the readable event's state.
void Clear();
+ /// Clears the readable event's state if and only if it
+ /// has already been signaled.
+ ///
+ /// @pre The event must be in a signaled state. If this event
+ /// is in an unsignaled state and this function is called,
+ /// then ERR_INVALID_STATE will be returned.
+ ResultCode Reset();
+
private:
explicit ReadableEvent(KernelCore& kernel);
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 5a5f4cef1..df4d6cf0a 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -9,6 +9,7 @@
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
+#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
@@ -179,4 +180,69 @@ void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
ready_queue.prepare(priority);
}
+Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
+ std::lock_guard<std::mutex> lock(scheduler_mutex);
+
+ const u32 mask = 1U << core;
+ return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) {
+ return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority;
+ });
+}
+
+void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
+ ASSERT(thread != nullptr);
+ // Avoid yielding if the thread isn't even running.
+ ASSERT(thread->GetStatus() == ThreadStatus::Running);
+
+ // Sanity check that the priority is valid
+ ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
+
+ // Yield this thread -- sleep for zero time and force reschedule to different thread
+ WaitCurrentThread_Sleep();
+ GetCurrentThread()->WakeAfterDelay(0);
+}
+
+void Scheduler::YieldWithLoadBalancing(Thread* thread) {
+ ASSERT(thread != nullptr);
+ const auto priority = thread->GetPriority();
+ const auto core = static_cast<u32>(thread->GetProcessorID());
+
+ // Avoid yielding if the thread isn't even running.
+ ASSERT(thread->GetStatus() == ThreadStatus::Running);
+
+ // Sanity check that the priority is valid
+ ASSERT(priority < THREADPRIO_COUNT);
+
+ // Sleep for zero time to be able to force reschedule to different thread
+ WaitCurrentThread_Sleep();
+ GetCurrentThread()->WakeAfterDelay(0);
+
+ Thread* suggested_thread = nullptr;
+
+ // Search through all of the cpu cores (except this one) for a suggested thread.
+ // Take the first non-nullptr one
+ for (unsigned cur_core = 0; cur_core < Core::NUM_CPU_CORES; ++cur_core) {
+ const auto res =
+ Core::System::GetInstance().CpuCore(cur_core).Scheduler().GetNextSuggestedThread(
+ core, priority);
+
+ // If scheduler provides a suggested thread
+ if (res != nullptr) {
+ // And its better than the current suggested thread (or is the first valid one)
+ if (suggested_thread == nullptr ||
+ suggested_thread->GetPriority() > res->GetPriority()) {
+ suggested_thread = res;
+ }
+ }
+ }
+
+ // If a suggested thread was found, queue that for this core
+ if (suggested_thread != nullptr)
+ suggested_thread->ChangeCore(core, suggested_thread->GetAffinityMask());
+}
+
+void Scheduler::YieldAndWaitForLoadBalancing(Thread* thread) {
+ UNIMPLEMENTED_MSG("Wait for load balancing thread yield type is not implemented!");
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index c63032b7d..97ced4dfc 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -51,6 +51,75 @@ public:
/// Sets the priority of a thread in the scheduler
void SetThreadPriority(Thread* thread, u32 priority);
+ /// Gets the next suggested thread for load balancing
+ Thread* GetNextSuggestedThread(u32 core, u32 minimum_priority) const;
+
+ /**
+ * YieldWithoutLoadBalancing -- analogous to normal yield on a system
+ * Moves the thread to the end of the ready queue for its priority, and then reschedules the
+ * system to the new head of the queue.
+ *
+ * Example (Single Core -- but can be extrapolated to multi):
+ * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC (->exec order->)
+ * Currently Running: ThreadR
+ *
+ * ThreadR calls YieldWithoutLoadBalancing
+ *
+ * ThreadR is moved to the end of ready_queue[prio=0]:
+ * ready_queue[prio=0]: ThreadA, ThreadB, ThreadC, ThreadR (->exec order->)
+ * Currently Running: Nothing
+ *
+ * System is rescheduled (ThreadA is popped off of queue):
+ * ready_queue[prio=0]: ThreadB, ThreadC, ThreadR (->exec order->)
+ * Currently Running: ThreadA
+ *
+ * If the queue is empty at time of call, no yielding occurs. This does not cross between cores
+ * or priorities at all.
+ */
+ void YieldWithoutLoadBalancing(Thread* thread);
+
+ /**
+ * YieldWithLoadBalancing -- yield but with better selection of the new running thread
+ * Moves the current thread to the end of the ready queue for its priority, then selects a
+ * 'suggested thread' (a thread on a different core that could run on this core) from the
+ * scheduler, changes its core, and reschedules the current core to that thread.
+ *
+ * Example (Dual Core -- can be extrapolated to Quad Core, this is just normal yield if it were
+ * single core):
+ * ready_queue[core=0][prio=0]: ThreadA, ThreadB (affinities not pictured as irrelevant
+ * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
+ * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
+ *
+ * ThreadQ calls YieldWithLoadBalancing
+ *
+ * ThreadQ is moved to the end of ready_queue[core=0][prio=0]:
+ * ready_queue[core=0][prio=0]: ThreadA, ThreadB
+ * ready_queue[core=1][prio=0]: ThreadC[affinity=both], ThreadD[affinity=core1only]
+ * Currently Running: ThreadQ on Core 0 || ThreadP on Core 1
+ *
+ * A list of suggested threads for each core is compiled
+ * Suggested Threads: {ThreadC on Core 1}
+ * If this were quad core (as the switch is), there could be between 0 and 3 threads in this
+ * list. If there are more than one, the thread is selected by highest prio.
+ *
+ * ThreadC is core changed to Core 0:
+ * ready_queue[core=0][prio=0]: ThreadC, ThreadA, ThreadB, ThreadQ
+ * ready_queue[core=1][prio=0]: ThreadD
+ * Currently Running: None on Core 0 || ThreadP on Core 1
+ *
+ * System is rescheduled (ThreadC is popped off of queue):
+ * ready_queue[core=0][prio=0]: ThreadA, ThreadB, ThreadQ
+ * ready_queue[core=1][prio=0]: ThreadD
+ * Currently Running: ThreadC on Core 0 || ThreadP on Core 1
+ *
+ * If no suggested threads can be found this will behave just as normal yield. If there are
+ * multiple candidates for the suggested thread on a core, the highest prio is taken.
+ */
+ void YieldWithLoadBalancing(Thread* thread);
+
+ /// Currently unknown -- asserts as unimplemented on call
+ void YieldAndWaitForLoadBalancing(Thread* thread);
+
/// Returns a list of all threads managed by the scheduler
const std::vector<SharedPtr<Thread>>& GetThreadList() const {
return thread_list;
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 0494581f5..22d0c1dd5 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -17,13 +17,13 @@ namespace Kernel {
SharedMemory::SharedMemory(KernelCore& kernel) : Object{kernel} {}
SharedMemory::~SharedMemory() = default;
-SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Process> owner_process,
- u64 size, MemoryPermission permissions,
+SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, Process* owner_process, u64 size,
+ MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address,
MemoryRegion region, std::string name) {
SharedPtr<SharedMemory> shared_memory(new SharedMemory(kernel));
- shared_memory->owner_process = std::move(owner_process);
+ shared_memory->owner_process = owner_process;
shared_memory->name = std::move(name);
shared_memory->size = size;
shared_memory->permissions = permissions;
@@ -39,15 +39,15 @@ SharedPtr<SharedMemory> SharedMemory::Create(KernelCore& kernel, SharedPtr<Proce
shared_memory->backing_block.get());
}
} else {
- auto& vm_manager = shared_memory->owner_process->VMManager();
+ const auto& vm_manager = shared_memory->owner_process->VMManager();
// The memory is already available and mapped in the owner process.
- auto vma = vm_manager.FindVMA(address);
- ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address");
+ const auto vma = vm_manager.FindVMA(address);
+ ASSERT_MSG(vm_manager.IsValidHandle(vma), "Invalid memory address");
ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address");
// The returned VMA might be a bigger one encompassing the desired address.
- auto vma_offset = address - vma->first;
+ const auto vma_offset = address - vma->first;
ASSERT_MSG(vma_offset + size <= vma->second.size,
"Shared memory exceeds bounds of mapped block");
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index 0b48db699..dab2a6bea 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -45,8 +45,8 @@ public:
* linear heap.
* @param name Optional object name, used for debugging purposes.
*/
- static SharedPtr<SharedMemory> Create(KernelCore& kernel, SharedPtr<Process> owner_process,
- u64 size, MemoryPermission permissions,
+ static SharedPtr<SharedMemory> Create(KernelCore& kernel, Process* owner_process, u64 size,
+ MemoryPermission permissions,
MemoryPermission other_permissions, VAddr address = 0,
MemoryRegion region = MemoryRegion::BASE,
std::string name = "Unknown");
@@ -139,7 +139,7 @@ private:
/// Permission restrictions applied to other processes mapping the block.
MemoryPermission other_permissions{};
/// Process that created this shared memory block.
- SharedPtr<Process> owner_process;
+ Process* owner_process;
/// Address of shared memory block in the owner process if specified.
VAddr base_address = 0;
/// Name of shared memory object.
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 051b09d00..28268e112 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -35,7 +35,7 @@
#include "core/hle/lock.h"
#include "core/hle/result.h"
#include "core/hle/service/service.h"
-#include "core/settings.h"
+#include "core/memory.h"
namespace Kernel {
namespace {
@@ -240,7 +240,7 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
}
const VMManager::VMAHandle iter = vm_manager.FindVMA(addr);
- if (iter == vm_manager.vma_map.end()) {
+ if (!vm_manager.IsValidHandle(iter)) {
LOG_ERROR(Kernel_SVC, "Unable to find VMA for address=0x{:016X}", addr);
return ERR_INVALID_ADDRESS_STATE;
}
@@ -254,11 +254,52 @@ static ResultCode SetMemoryPermission(VAddr addr, u64 size, u32 prot) {
return vm_manager.ReprotectRange(addr, size, converted_permissions);
}
-static ResultCode SetMemoryAttribute(VAddr addr, u64 size, u32 state0, u32 state1) {
- LOG_WARNING(Kernel_SVC,
- "(STUBBED) called, addr=0x{:X}, size=0x{:X}, state0=0x{:X}, state1=0x{:X}", addr,
- size, state0, state1);
- return RESULT_SUCCESS;
+static ResultCode SetMemoryAttribute(VAddr address, u64 size, u32 mask, u32 attribute) {
+ LOG_DEBUG(Kernel_SVC,
+ "called, address=0x{:016X}, size=0x{:X}, mask=0x{:08X}, attribute=0x{:08X}", address,
+ size, mask, attribute);
+
+ if (!Common::Is4KBAligned(address)) {
+ LOG_ERROR(Kernel_SVC, "Address not page aligned (0x{:016X})", address);
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (size == 0 || !Common::Is4KBAligned(size)) {
+ LOG_ERROR(Kernel_SVC, "Invalid size (0x{:X}). Size must be non-zero and page aligned.",
+ size);
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (!IsValidAddressRange(address, size)) {
+ LOG_ERROR(Kernel_SVC, "Address range overflowed (Address: 0x{:016X}, Size: 0x{:016X})",
+ address, size);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ const auto mem_attribute = static_cast<MemoryAttribute>(attribute);
+ const auto mem_mask = static_cast<MemoryAttribute>(mask);
+ const auto attribute_with_mask = mem_attribute | mem_mask;
+
+ if (attribute_with_mask != mem_mask) {
+ LOG_ERROR(Kernel_SVC,
+ "Memory attribute doesn't match the given mask (Attribute: 0x{:X}, Mask: {:X}",
+ attribute, mask);
+ return ERR_INVALID_COMBINATION;
+ }
+
+ if ((attribute_with_mask | MemoryAttribute::Uncached) != MemoryAttribute::Uncached) {
+ LOG_ERROR(Kernel_SVC, "Specified attribute isn't equal to MemoryAttributeUncached (8).");
+ return ERR_INVALID_COMBINATION;
+ }
+
+ auto& vm_manager = Core::CurrentProcess()->VMManager();
+ if (!IsInsideAddressSpace(vm_manager, address, size)) {
+ LOG_ERROR(Kernel_SVC,
+ "Given address (0x{:016X}) is outside the bounds of the address space.", address);
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ return vm_manager.SetMemoryAttribute(address, size, mem_mask, mem_attribute);
}
/// Maps a memory range into a different range.
@@ -274,7 +315,7 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
return result;
}
- return current_process->MirrorMemory(dst_addr, src_addr, size);
+ return current_process->MirrorMemory(dst_addr, src_addr, size, MemoryState::Stack);
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -350,7 +391,7 @@ static ResultCode SendSyncRequest(Handle handle) {
}
/// Get the ID for the specified thread.
-static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
+static ResultCode GetThreadId(u64* thread_id, Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
@@ -364,20 +405,33 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
return RESULT_SUCCESS;
}
-/// Get the ID of the specified process
-static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
- LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
+/// Gets the ID of the specified process or a specified thread's owning process.
+static ResultCode GetProcessId(u64* process_id, Handle handle) {
+ LOG_DEBUG(Kernel_SVC, "called handle=0x{:08X}", handle);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
- const SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
- if (!process) {
- LOG_ERROR(Kernel_SVC, "Process handle does not exist, process_handle=0x{:08X}",
- process_handle);
- return ERR_INVALID_HANDLE;
+ const SharedPtr<Process> process = handle_table.Get<Process>(handle);
+ if (process) {
+ *process_id = process->GetProcessID();
+ return RESULT_SUCCESS;
}
- *process_id = process->GetProcessID();
- return RESULT_SUCCESS;
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle);
+ if (thread) {
+ const Process* const owner_process = thread->GetOwnerProcess();
+ if (!owner_process) {
+ LOG_ERROR(Kernel_SVC, "Non-existent owning process encountered.");
+ return ERR_INVALID_HANDLE;
+ }
+
+ *process_id = owner_process->GetProcessID();
+ return RESULT_SUCCESS;
+ }
+
+ // NOTE: This should also handle debug objects before returning.
+
+ LOG_ERROR(Kernel_SVC, "Handle does not exist, handle=0x{:08X}", handle);
+ return ERR_INVALID_HANDLE;
}
/// Default thread wakeup callback for WaitSynchronization
@@ -663,7 +717,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
TotalMemoryUsage = 6,
TotalHeapUsage = 7,
IsCurrentProcessBeingDebugged = 8,
- ResourceHandleLimit = 9,
+ RegisterResourceLimit = 9,
IdleTickCount = 10,
RandomEntropy = 11,
PerformanceCounter = 0xF0000002,
@@ -683,37 +737,137 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
ThreadTickCount = 0xF0000002,
};
- const auto* current_process = Core::CurrentProcess();
- const auto& vm_manager = current_process->VMManager();
+ const auto info_id_type = static_cast<GetInfoType>(info_id);
- switch (static_cast<GetInfoType>(info_id)) {
+ switch (info_id_type) {
case GetInfoType::AllowedCpuIdBitmask:
- *result = current_process->GetAllowedProcessorMask();
- break;
case GetInfoType::AllowedThreadPrioBitmask:
- *result = current_process->GetAllowedThreadPriorityMask();
- break;
case GetInfoType::MapRegionBaseAddr:
- *result = vm_manager.GetMapRegionBaseAddress();
- break;
case GetInfoType::MapRegionSize:
- *result = vm_manager.GetMapRegionSize();
- break;
case GetInfoType::HeapRegionBaseAddr:
- *result = vm_manager.GetHeapRegionBaseAddress();
- break;
case GetInfoType::HeapRegionSize:
- *result = vm_manager.GetHeapRegionSize();
- break;
+ case GetInfoType::ASLRRegionBaseAddr:
+ case GetInfoType::ASLRRegionSize:
+ case GetInfoType::NewMapRegionBaseAddr:
+ case GetInfoType::NewMapRegionSize:
case GetInfoType::TotalMemoryUsage:
- *result = vm_manager.GetTotalMemoryUsage();
- break;
case GetInfoType::TotalHeapUsage:
- *result = vm_manager.GetTotalHeapUsage();
- break;
+ case GetInfoType::IsVirtualAddressMemoryEnabled:
+ case GetInfoType::PersonalMmHeapUsage:
+ case GetInfoType::TitleId:
+ case GetInfoType::UserExceptionContextAddr: {
+ if (info_sub_id != 0) {
+ return ERR_INVALID_ENUM_VALUE;
+ }
+
+ const auto& current_process_handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto process = current_process_handle_table.Get<Process>(static_cast<Handle>(handle));
+ if (!process) {
+ return ERR_INVALID_HANDLE;
+ }
+
+ switch (info_id_type) {
+ case GetInfoType::AllowedCpuIdBitmask:
+ *result = process->GetAllowedProcessorMask();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::AllowedThreadPrioBitmask:
+ *result = process->GetAllowedThreadPriorityMask();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::MapRegionBaseAddr:
+ *result = process->VMManager().GetMapRegionBaseAddress();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::MapRegionSize:
+ *result = process->VMManager().GetMapRegionSize();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::HeapRegionBaseAddr:
+ *result = process->VMManager().GetHeapRegionBaseAddress();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::HeapRegionSize:
+ *result = process->VMManager().GetHeapRegionSize();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::ASLRRegionBaseAddr:
+ *result = process->VMManager().GetASLRRegionBaseAddress();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::ASLRRegionSize:
+ *result = process->VMManager().GetASLRRegionSize();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::NewMapRegionBaseAddr:
+ *result = process->VMManager().GetNewMapRegionBaseAddress();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::NewMapRegionSize:
+ *result = process->VMManager().GetNewMapRegionSize();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::TotalMemoryUsage:
+ *result = process->VMManager().GetTotalMemoryUsage();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::TotalHeapUsage:
+ *result = process->VMManager().GetTotalHeapUsage();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::IsVirtualAddressMemoryEnabled:
+ *result = process->IsVirtualMemoryEnabled();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::TitleId:
+ *result = process->GetTitleID();
+ return RESULT_SUCCESS;
+
+ case GetInfoType::UserExceptionContextAddr:
+ LOG_WARNING(Kernel_SVC,
+ "(STUBBED) Attempted to query user exception context address, returned 0");
+ *result = 0;
+ return RESULT_SUCCESS;
+
+ default:
+ break;
+ }
+
+ LOG_WARNING(Kernel_SVC, "(STUBBED) Unimplemented svcGetInfo id=0x{:016X}", info_id);
+ return ERR_INVALID_ENUM_VALUE;
+ }
+
case GetInfoType::IsCurrentProcessBeingDebugged:
*result = 0;
- break;
+ return RESULT_SUCCESS;
+
+ case GetInfoType::RegisterResourceLimit: {
+ if (handle != 0) {
+ return ERR_INVALID_HANDLE;
+ }
+
+ if (info_sub_id != 0) {
+ return ERR_INVALID_COMBINATION;
+ }
+
+ Process* const current_process = Core::CurrentProcess();
+ HandleTable& handle_table = current_process->GetHandleTable();
+ const auto resource_limit = current_process->GetResourceLimit();
+ if (!resource_limit) {
+ *result = KernelHandle::InvalidHandle;
+ // Yes, the kernel considers this a successful operation.
+ return RESULT_SUCCESS;
+ }
+
+ const auto table_result = handle_table.Create(resource_limit);
+ if (table_result.Failed()) {
+ return table_result.Code();
+ }
+
+ *result = *table_result;
+ return RESULT_SUCCESS;
+ }
+
case GetInfoType::RandomEntropy:
if (handle != 0) {
LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}",
@@ -727,37 +881,15 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
return ERR_INVALID_COMBINATION;
}
- *result = current_process->GetRandomEntropy(info_sub_id);
+ *result = Core::CurrentProcess()->GetRandomEntropy(info_sub_id);
return RESULT_SUCCESS;
- break;
- case GetInfoType::ASLRRegionBaseAddr:
- *result = vm_manager.GetASLRRegionBaseAddress();
- break;
- case GetInfoType::ASLRRegionSize:
- *result = vm_manager.GetASLRRegionSize();
- break;
- case GetInfoType::NewMapRegionBaseAddr:
- *result = vm_manager.GetNewMapRegionBaseAddress();
- break;
- case GetInfoType::NewMapRegionSize:
- *result = vm_manager.GetNewMapRegionSize();
- break;
- case GetInfoType::IsVirtualAddressMemoryEnabled:
- *result = current_process->IsVirtualMemoryEnabled();
- break;
- case GetInfoType::TitleId:
- *result = current_process->GetTitleID();
- break;
+
case GetInfoType::PrivilegedProcessId:
LOG_WARNING(Kernel_SVC,
"(STUBBED) Attempted to query privileged process id bounds, returned 0");
*result = 0;
- break;
- case GetInfoType::UserExceptionContextAddr:
- LOG_WARNING(Kernel_SVC,
- "(STUBBED) Attempted to query user exception context address, returned 0");
- *result = 0;
- break;
+ return RESULT_SUCCESS;
+
case GetInfoType::ThreadTickCount: {
constexpr u64 num_cpus = 4;
if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
@@ -767,7 +899,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
}
const auto thread =
- current_process->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
+ Core::CurrentProcess()->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
if (!thread) {
LOG_ERROR(Kernel_SVC, "Thread handle does not exist, handle=0x{:08X}",
static_cast<Handle>(handle));
@@ -790,14 +922,13 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
}
*result = out_ticks;
- break;
+ return RESULT_SUCCESS;
}
+
default:
LOG_WARNING(Kernel_SVC, "(STUBBED) Unimplemented svcGetInfo id=0x{:016X}", info_id);
return ERR_INVALID_ENUM_VALUE;
}
-
- return RESULT_SUCCESS;
}
/// Sets the thread activity
@@ -990,10 +1121,9 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64
return shared_memory->Unmap(*current_process, addr);
}
-/// Query process memory
-static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/,
- Handle process_handle, u64 addr) {
- LOG_TRACE(Kernel_SVC, "called process=0x{:08X} addr={:X}", process_handle, addr);
+static ResultCode QueryProcessMemory(VAddr memory_info_address, VAddr page_info_address,
+ Handle process_handle, VAddr address) {
+ LOG_TRACE(Kernel_SVC, "called process=0x{:08X} address={:X}", process_handle, address);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
if (!process) {
@@ -1001,26 +1131,34 @@ static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_i
process_handle);
return ERR_INVALID_HANDLE;
}
- auto vma = process->VMManager().FindVMA(addr);
- memory_info->attributes = 0;
- if (vma == process->VMManager().vma_map.end()) {
- memory_info->base_address = 0;
- memory_info->permission = static_cast<u32>(VMAPermission::None);
- memory_info->size = 0;
- memory_info->type = static_cast<u32>(MemoryState::Unmapped);
- } else {
- memory_info->base_address = vma->second.base;
- memory_info->permission = static_cast<u32>(vma->second.permissions);
- memory_info->size = vma->second.size;
- memory_info->type = static_cast<u32>(vma->second.meminfo_state);
- }
+
+ const auto& vm_manager = process->VMManager();
+ const MemoryInfo memory_info = vm_manager.QueryMemory(address);
+
+ Memory::Write64(memory_info_address, memory_info.base_address);
+ Memory::Write64(memory_info_address + 8, memory_info.size);
+ Memory::Write32(memory_info_address + 16, memory_info.state);
+ Memory::Write32(memory_info_address + 20, memory_info.attributes);
+ Memory::Write32(memory_info_address + 24, memory_info.permission);
+ Memory::Write32(memory_info_address + 32, memory_info.ipc_ref_count);
+ Memory::Write32(memory_info_address + 28, memory_info.device_ref_count);
+ Memory::Write32(memory_info_address + 36, 0);
+
+ // Page info appears to be currently unused by the kernel and is always set to zero.
+ Memory::Write32(page_info_address, 0);
+
return RESULT_SUCCESS;
}
-/// Query memory
-static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAddr addr) {
- LOG_TRACE(Kernel_SVC, "called, addr={:X}", addr);
- return QueryProcessMemory(memory_info, page_info, CurrentProcess, addr);
+static ResultCode QueryMemory(VAddr memory_info_address, VAddr page_info_address,
+ VAddr query_address) {
+ LOG_TRACE(Kernel_SVC,
+ "called, memory_info_address=0x{:016X}, page_info_address=0x{:016X}, "
+ "query_address=0x{:016X}",
+ memory_info_address, page_info_address, query_address);
+
+ return QueryProcessMemory(memory_info_address, page_info_address, CurrentProcess,
+ query_address);
}
/// Exits the current process
@@ -1043,7 +1181,7 @@ static void ExitProcess() {
static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top,
u32 priority, s32 processor_id) {
LOG_TRACE(Kernel_SVC,
- "called entrypoint=0x{:08X} ({}), arg=0x{:08X}, stacktop=0x{:08X}, "
+ "called entrypoint=0x{:08X}, arg=0x{:08X}, stacktop=0x{:08X}, "
"threadpriority=0x{:08X}, processorid=0x{:08X} : created handle=0x{:08X}",
entry_point, arg, stack_top, priority, processor_id, *out_handle);
@@ -1124,18 +1262,38 @@ static void ExitThread() {
static void SleepThread(s64 nanoseconds) {
LOG_TRACE(Kernel_SVC, "called nanoseconds={}", nanoseconds);
- // Don't attempt to yield execution if there are no available threads to run,
- // this way we avoid a useless reschedule to the idle thread.
- if (nanoseconds == 0 && !Core::System::GetInstance().CurrentScheduler().HaveReadyThreads())
- return;
+ enum class SleepType : s64 {
+ YieldWithoutLoadBalancing = 0,
+ YieldWithLoadBalancing = -1,
+ YieldAndWaitForLoadBalancing = -2,
+ };
- // Sleep current thread and check for next thread to schedule
- WaitCurrentThread_Sleep();
+ if (nanoseconds <= 0) {
+ auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
+ switch (static_cast<SleepType>(nanoseconds)) {
+ case SleepType::YieldWithoutLoadBalancing:
+ scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
+ break;
+ case SleepType::YieldWithLoadBalancing:
+ scheduler.YieldWithLoadBalancing(GetCurrentThread());
+ break;
+ case SleepType::YieldAndWaitForLoadBalancing:
+ scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
+ break;
+ default:
+ UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
+ }
+ } else {
+ // Sleep current thread and check for next thread to schedule
+ WaitCurrentThread_Sleep();
- // Create an event to wake the thread up after the specified nanosecond delay has passed
- GetCurrentThread()->WakeAfterDelay(nanoseconds);
+ // Create an event to wake the thread up after the specified nanosecond delay has passed
+ GetCurrentThread()->WakeAfterDelay(nanoseconds);
+ }
- Core::System::GetInstance().PrepareReschedule();
+ // Reschedule all CPU cores
+ for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
+ Core::System::GetInstance().CpuCore(i).PrepareReschedule();
}
/// Wait process wide key atomic
@@ -1357,17 +1515,24 @@ static ResultCode CloseHandle(Handle handle) {
return handle_table.Close(handle);
}
-/// Reset an event
+/// Clears the signaled state of an event or process.
static ResultCode ResetSignal(Handle handle) {
LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+
auto event = handle_table.Get<ReadableEvent>(handle);
+ if (event) {
+ return event->Reset();
+ }
- ASSERT(event != nullptr);
+ auto process = handle_table.Get<Process>(handle);
+ if (process) {
+ return process->ClearSignalState();
+ }
- event->Clear();
- return RESULT_SUCCESS;
+ LOG_ERROR(Kernel_SVC, "Invalid handle (0x{:08X})", handle);
+ return ERR_INVALID_HANDLE;
}
/// Creates a TransferMemory object
@@ -1400,9 +1565,9 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32
}
auto& kernel = Core::System::GetInstance().Kernel();
- auto& handle_table = Core::CurrentProcess()->GetHandleTable();
- const auto shared_mem_handle = SharedMemory::Create(
- kernel, handle_table.Get<Process>(CurrentProcess), size, perms, perms, addr);
+ auto process = kernel.CurrentProcess();
+ auto& handle_table = process->GetHandleTable();
+ const auto shared_mem_handle = SharedMemory::Create(kernel, process, size, perms, perms, addr);
CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
return RESULT_SUCCESS;
@@ -1512,26 +1677,75 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
}
auto& kernel = Core::System::GetInstance().Kernel();
- auto& handle_table = Core::CurrentProcess()->GetHandleTable();
- auto shared_mem_handle =
- SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
- local_perms, remote_perms);
+ auto process = kernel.CurrentProcess();
+ auto& handle_table = process->GetHandleTable();
+ auto shared_mem_handle = SharedMemory::Create(kernel, process, size, local_perms, remote_perms);
CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle));
return RESULT_SUCCESS;
}
+static ResultCode CreateEvent(Handle* write_handle, Handle* read_handle) {
+ LOG_DEBUG(Kernel_SVC, "called");
+
+ auto& kernel = Core::System::GetInstance().Kernel();
+ const auto [readable_event, writable_event] =
+ WritableEvent::CreateEventPair(kernel, ResetType::Sticky, "CreateEvent");
+
+ HandleTable& handle_table = kernel.CurrentProcess()->GetHandleTable();
+
+ const auto write_create_result = handle_table.Create(writable_event);
+ if (write_create_result.Failed()) {
+ return write_create_result.Code();
+ }
+ *write_handle = *write_create_result;
+
+ const auto read_create_result = handle_table.Create(readable_event);
+ if (read_create_result.Failed()) {
+ handle_table.Close(*write_create_result);
+ return read_create_result.Code();
+ }
+ *read_handle = *read_create_result;
+
+ LOG_DEBUG(Kernel_SVC,
+ "successful. Writable event handle=0x{:08X}, Readable event handle=0x{:08X}",
+ *write_create_result, *read_create_result);
+ return RESULT_SUCCESS;
+}
+
static ResultCode ClearEvent(Handle handle) {
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
- SharedPtr<ReadableEvent> evt = handle_table.Get<ReadableEvent>(handle);
- if (evt == nullptr) {
- LOG_ERROR(Kernel_SVC, "Event handle does not exist, handle=0x{:08X}", handle);
+
+ auto writable_event = handle_table.Get<WritableEvent>(handle);
+ if (writable_event) {
+ writable_event->Clear();
+ return RESULT_SUCCESS;
+ }
+
+ auto readable_event = handle_table.Get<ReadableEvent>(handle);
+ if (readable_event) {
+ readable_event->Clear();
+ return RESULT_SUCCESS;
+ }
+
+ LOG_ERROR(Kernel_SVC, "Event handle does not exist, handle=0x{:08X}", handle);
+ return ERR_INVALID_HANDLE;
+}
+
+static ResultCode SignalEvent(Handle handle) {
+ LOG_DEBUG(Kernel_SVC, "called. Handle=0x{:08X}", handle);
+
+ HandleTable& handle_table = Core::CurrentProcess()->GetHandleTable();
+ auto writable_event = handle_table.Get<WritableEvent>(handle);
+
+ if (!writable_event) {
+ LOG_ERROR(Kernel_SVC, "Non-existent writable event handle used (0x{:08X})", handle);
return ERR_INVALID_HANDLE;
}
- evt->Clear();
+ writable_event->Signal();
return RESULT_SUCCESS;
}
@@ -1670,7 +1884,7 @@ static const FunctionDef SVC_Table[] = {
{0x0E, SvcWrap<GetThreadCoreMask>, "GetThreadCoreMask"},
{0x0F, SvcWrap<SetThreadCoreMask>, "SetThreadCoreMask"},
{0x10, SvcWrap<GetCurrentProcessorNumber>, "GetCurrentProcessorNumber"},
- {0x11, nullptr, "SignalEvent"},
+ {0x11, SvcWrap<SignalEvent>, "SignalEvent"},
{0x12, SvcWrap<ClearEvent>, "ClearEvent"},
{0x13, SvcWrap<MapSharedMemory>, "MapSharedMemory"},
{0x14, SvcWrap<UnmapSharedMemory>, "UnmapSharedMemory"},
@@ -1722,7 +1936,7 @@ static const FunctionDef SVC_Table[] = {
{0x42, nullptr, "ReplyAndReceiveLight"},
{0x43, nullptr, "ReplyAndReceive"},
{0x44, nullptr, "ReplyAndReceiveWithUserBuffer"},
- {0x45, nullptr, "CreateEvent"},
+ {0x45, SvcWrap<CreateEvent>, "CreateEvent"},
{0x46, nullptr, "Unknown"},
{0x47, nullptr, "Unknown"},
{0x48, nullptr, "MapPhysicalMemoryUnsafe"},
@@ -1771,7 +1985,7 @@ static const FunctionDef SVC_Table[] = {
{0x73, nullptr, "SetProcessMemoryPermission"},
{0x74, nullptr, "MapProcessMemory"},
{0x75, nullptr, "UnmapProcessMemory"},
- {0x76, nullptr, "QueryProcessMemory"},
+ {0x76, SvcWrap<QueryProcessMemory>, "QueryProcessMemory"},
{0x77, nullptr, "MapProcessCodeMemory"},
{0x78, nullptr, "UnmapProcessCodeMemory"},
{0x79, nullptr, "CreateProcess"},
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index b06aac4ec..c37ae0f98 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -8,22 +8,6 @@
namespace Kernel {
-struct MemoryInfo {
- u64 base_address;
- u64 size;
- u32 type;
- u32 attributes;
- u32 permission;
- u32 device_refcount;
- u32 ipc_refcount;
- INSERT_PADDING_WORDS(1);
-};
-static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
-
-struct PageInfo {
- u64 flags;
-};
-
void CallSVC(u32 immediate);
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index fa1116624..2a2c2c5ea 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -7,9 +7,7 @@
#include "common/common_types.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
-#include "core/hle/kernel/svc.h"
#include "core/hle/result.h"
-#include "core/memory.h"
namespace Kernel {
@@ -59,10 +57,31 @@ void SvcWrap() {
FuncReturn(retval);
}
+template <ResultCode func(u32*, u32*)>
+void SvcWrap() {
+ u32 param_1 = 0;
+ u32 param_2 = 0;
+ const u32 retval = func(&param_1, &param_2).raw;
+
+ auto& arm_interface = Core::CurrentArmInterface();
+ arm_interface.SetReg(1, param_1);
+ arm_interface.SetReg(2, param_2);
+
+ FuncReturn(retval);
+}
+
template <ResultCode func(u32*, u64)>
void SvcWrap() {
u32 param_1 = 0;
- u32 retval = func(&param_1, Param(1)).raw;
+ const u32 retval = func(&param_1, Param(1)).raw;
+ Core::CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(retval);
+}
+
+template <ResultCode func(u64*, u32)>
+void SvcWrap() {
+ u64 param_1 = 0;
+ const u32 retval = func(&param_1, static_cast<u32>(Param(1))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -116,7 +135,12 @@ void SvcWrap() {
template <ResultCode func(u64, u64, u32, u32)>
void SvcWrap() {
FuncReturn(
- func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw);
+ func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw);
+}
+
+template <ResultCode func(u64, u64, u32, u64)>
+void SvcWrap() {
+ FuncReturn(func(Param(0), Param(1), static_cast<u32>(Param(2)), Param(3)).raw);
}
template <ResultCode func(u32, u64, u32)>
@@ -178,21 +202,6 @@ void SvcWrap() {
FuncReturn(retval);
}
-template <ResultCode func(MemoryInfo*, PageInfo*, u64)>
-void SvcWrap() {
- MemoryInfo memory_info = {};
- PageInfo page_info = {};
- 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);
-
- FuncReturn(retval);
-}
-
template <ResultCode func(u32*, u64, u64, u32)>
void SvcWrap() {
u32 param_1 = 0;
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 4ffb76818..63f8923fd 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -158,6 +158,9 @@ static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAdd
context.cpu_registers[0] = arg;
context.pc = entry_point;
context.sp = stack_top;
+ // TODO(merry): Perform a hardware test to determine the below value.
+ // AHP = 0, DN = 1, FTZ = 1, RMode = Round towards zero
+ context.fpcr = 0x03C00000;
}
ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point,
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index d384d50db..d6e7981d3 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -26,6 +26,7 @@ enum ThreadPriority : u32 {
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
+ THREADPRIO_COUNT = 64, ///< Total number of possible thread priorities.
};
enum ThreadProcessorId : s32 {
@@ -150,7 +151,7 @@ public:
* Gets the thread's thread ID
* @return The thread's ID
*/
- u32 GetThreadID() const {
+ u64 GetThreadID() const {
return thread_id;
}
@@ -378,7 +379,7 @@ private:
Core::ARM_Interface::ThreadContext context{};
- u32 thread_id = 0;
+ u64 thread_id = 0;
ThreadStatus status = ThreadStatus::Dormant;
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 100f8f6bf..f39e096ca 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -25,19 +25,19 @@ static const char* GetMemoryStateName(MemoryState state) {
"CodeMutable", "Heap",
"Shared", "Unknown1",
"ModuleCodeStatic", "ModuleCodeMutable",
- "IpcBuffer0", "Mapped",
+ "IpcBuffer0", "Stack",
"ThreadLocal", "TransferMemoryIsolated",
"TransferMemory", "ProcessMemory",
- "Unknown2", "IpcBuffer1",
+ "Inaccessible", "IpcBuffer1",
"IpcBuffer3", "KernelStack",
};
- return names[static_cast<int>(state)];
+ return names[ToSvcMemoryState(state)];
}
bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
ASSERT(base + size == next.base);
- if (permissions != next.permissions || meminfo_state != next.meminfo_state ||
+ if (permissions != next.permissions || state != next.state || attribute != next.attribute ||
type != next.type) {
return false;
}
@@ -87,6 +87,10 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {
}
}
+bool VMManager::IsValidHandle(VMAHandle handle) const {
+ return handle != vma_map.cend();
+}
+
ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
std::shared_ptr<std::vector<u8>> block,
std::size_t offset, u64 size,
@@ -111,7 +115,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
final_vma.type = VMAType::AllocatedMemoryBlock;
final_vma.permissions = VMAPermission::ReadWrite;
- final_vma.meminfo_state = state;
+ final_vma.state = state;
final_vma.backing_block = std::move(block);
final_vma.offset = offset;
UpdatePageTableForVMA(final_vma);
@@ -136,7 +140,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
final_vma.type = VMAType::BackingMemory;
final_vma.permissions = VMAPermission::ReadWrite;
- final_vma.meminfo_state = state;
+ final_vma.state = state;
final_vma.backing_memory = memory;
UpdatePageTableForVMA(final_vma);
@@ -173,7 +177,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u6
final_vma.type = VMAType::MMIO;
final_vma.permissions = VMAPermission::ReadWrite;
- final_vma.meminfo_state = state;
+ final_vma.state = state;
final_vma.paddr = paddr;
final_vma.mmio_handler = std::move(mmio_handler);
UpdatePageTableForVMA(final_vma);
@@ -185,7 +189,7 @@ VMManager::VMAIter VMManager::Unmap(VMAIter vma_handle) {
VirtualMemoryArea& vma = vma_handle->second;
vma.type = VMAType::Free;
vma.permissions = VMAPermission::None;
- vma.meminfo_state = MemoryState::Unmapped;
+ vma.state = MemoryState::Unmapped;
vma.backing_block = nullptr;
vma.offset = 0;
@@ -298,6 +302,54 @@ ResultCode VMManager::HeapFree(VAddr target, u64 size) {
return RESULT_SUCCESS;
}
+MemoryInfo VMManager::QueryMemory(VAddr address) const {
+ const auto vma = FindVMA(address);
+ MemoryInfo memory_info{};
+
+ if (IsValidHandle(vma)) {
+ memory_info.base_address = vma->second.base;
+ memory_info.attributes = ToSvcMemoryAttribute(vma->second.attribute);
+ memory_info.permission = static_cast<u32>(vma->second.permissions);
+ memory_info.size = vma->second.size;
+ memory_info.state = ToSvcMemoryState(vma->second.state);
+ } else {
+ memory_info.base_address = address_space_end;
+ memory_info.permission = static_cast<u32>(VMAPermission::None);
+ memory_info.size = 0 - address_space_end;
+ memory_info.state = static_cast<u32>(MemoryState::Inaccessible);
+ }
+
+ return memory_info;
+}
+
+ResultCode VMManager::SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask,
+ MemoryAttribute attribute) {
+ constexpr auto ignore_mask = MemoryAttribute::Uncached | MemoryAttribute::DeviceMapped;
+ constexpr auto attribute_mask = ~ignore_mask;
+
+ const auto result = CheckRangeState(
+ address, size, MemoryState::FlagUncached, MemoryState::FlagUncached, VMAPermission::None,
+ VMAPermission::None, attribute_mask, MemoryAttribute::None, ignore_mask);
+
+ if (result.Failed()) {
+ return result.Code();
+ }
+
+ const auto [prev_state, prev_permissions, prev_attributes] = *result;
+ const auto new_attribute = (prev_attributes & ~mask) | (mask & attribute);
+
+ const auto carve_result = CarveVMARange(address, size);
+ if (carve_result.Failed()) {
+ return carve_result.Code();
+ }
+
+ auto vma_iter = *carve_result;
+ vma_iter->second.attribute = new_attribute;
+
+ MergeAdjacent(vma_iter);
+ return RESULT_SUCCESS;
+}
+
ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) {
const auto vma = FindVMA(src_addr);
@@ -341,7 +393,7 @@ void VMManager::LogLayout() const {
(u8)vma.permissions & (u8)VMAPermission::Read ? 'R' : '-',
(u8)vma.permissions & (u8)VMAPermission::Write ? 'W' : '-',
(u8)vma.permissions & (u8)VMAPermission::Execute ? 'X' : '-',
- GetMemoryStateName(vma.meminfo_state));
+ GetMemoryStateName(vma.state));
}
}
@@ -568,6 +620,66 @@ void VMManager::ClearPageTable() {
Memory::PageType::Unmapped);
}
+VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
+ MemoryState state, VMAPermission permission_mask,
+ VMAPermission permissions,
+ MemoryAttribute attribute_mask,
+ MemoryAttribute attribute,
+ MemoryAttribute ignore_mask) const {
+ auto iter = FindVMA(address);
+
+ // If we don't have a valid VMA handle at this point, then it means this is
+ // being called with an address outside of the address space, which is definitely
+ // indicative of a bug, as this function only operates on mapped memory regions.
+ DEBUG_ASSERT(IsValidHandle(iter));
+
+ const VAddr end_address = address + size - 1;
+ const MemoryAttribute initial_attributes = iter->second.attribute;
+ const VMAPermission initial_permissions = iter->second.permissions;
+ const MemoryState initial_state = iter->second.state;
+
+ while (true) {
+ // The iterator should be valid throughout the traversal. Hitting the end of
+ // the mapped VMA regions is unquestionably indicative of a bug.
+ DEBUG_ASSERT(IsValidHandle(iter));
+
+ const auto& vma = iter->second;
+
+ if (vma.state != initial_state) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if ((vma.state & state_mask) != state) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (vma.permissions != initial_permissions) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if ((vma.permissions & permission_mask) != permissions) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if ((vma.attribute | ignore_mask) != (initial_attributes | ignore_mask)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if ((vma.attribute & attribute_mask) != attribute) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (end_address <= vma.EndAddress()) {
+ break;
+ }
+
+ ++iter;
+ }
+
+ return MakeResult(
+ std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask));
+}
+
u64 VMManager::GetTotalMemoryUsage() const {
LOG_WARNING(Kernel, "(STUBBED) called");
return 0xF8000000;
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index d522404fe..6091533bc 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -6,6 +6,7 @@
#include <map>
#include <memory>
+#include <tuple>
#include <vector>
#include "common/common_types.h"
#include "core/hle/result.h"
@@ -43,26 +44,211 @@ enum class VMAPermission : u8 {
ReadWriteExecute = Read | Write | Execute,
};
-/// Set of values returned in MemoryInfo.state by svcQueryMemory.
+constexpr VMAPermission operator|(VMAPermission lhs, VMAPermission rhs) {
+ return static_cast<VMAPermission>(u32(lhs) | u32(rhs));
+}
+
+constexpr VMAPermission operator&(VMAPermission lhs, VMAPermission rhs) {
+ return static_cast<VMAPermission>(u32(lhs) & u32(rhs));
+}
+
+constexpr VMAPermission operator^(VMAPermission lhs, VMAPermission rhs) {
+ return static_cast<VMAPermission>(u32(lhs) ^ u32(rhs));
+}
+
+constexpr VMAPermission operator~(VMAPermission permission) {
+ return static_cast<VMAPermission>(~u32(permission));
+}
+
+constexpr VMAPermission& operator|=(VMAPermission& lhs, VMAPermission rhs) {
+ lhs = lhs | rhs;
+ return lhs;
+}
+
+constexpr VMAPermission& operator&=(VMAPermission& lhs, VMAPermission rhs) {
+ lhs = lhs & rhs;
+ return lhs;
+}
+
+constexpr VMAPermission& operator^=(VMAPermission& lhs, VMAPermission rhs) {
+ lhs = lhs ^ rhs;
+ return lhs;
+}
+
+/// Attribute flags that can be applied to a VMA
+enum class MemoryAttribute : u32 {
+ Mask = 0xFF,
+
+ /// No particular qualities
+ None = 0,
+ /// Memory locked/borrowed for use. e.g. This would be used by transfer memory.
+ Locked = 1,
+ /// Memory locked for use by IPC-related internals.
+ LockedForIPC = 2,
+ /// Mapped as part of the device address space.
+ DeviceMapped = 4,
+ /// Uncached memory
+ Uncached = 8,
+};
+
+constexpr MemoryAttribute operator|(MemoryAttribute lhs, MemoryAttribute rhs) {
+ return static_cast<MemoryAttribute>(u32(lhs) | u32(rhs));
+}
+
+constexpr MemoryAttribute operator&(MemoryAttribute lhs, MemoryAttribute rhs) {
+ return static_cast<MemoryAttribute>(u32(lhs) & u32(rhs));
+}
+
+constexpr MemoryAttribute operator^(MemoryAttribute lhs, MemoryAttribute rhs) {
+ return static_cast<MemoryAttribute>(u32(lhs) ^ u32(rhs));
+}
+
+constexpr MemoryAttribute operator~(MemoryAttribute attribute) {
+ return static_cast<MemoryAttribute>(~u32(attribute));
+}
+
+constexpr MemoryAttribute& operator|=(MemoryAttribute& lhs, MemoryAttribute rhs) {
+ lhs = lhs | rhs;
+ return lhs;
+}
+
+constexpr MemoryAttribute& operator&=(MemoryAttribute& lhs, MemoryAttribute rhs) {
+ lhs = lhs & rhs;
+ return lhs;
+}
+
+constexpr MemoryAttribute& operator^=(MemoryAttribute& lhs, MemoryAttribute rhs) {
+ lhs = lhs ^ rhs;
+ return lhs;
+}
+
+constexpr u32 ToSvcMemoryAttribute(MemoryAttribute attribute) {
+ return static_cast<u32>(attribute & MemoryAttribute::Mask);
+}
+
+// clang-format off
+/// Represents memory states and any relevant flags, as used by the kernel.
+/// svcQueryMemory interprets these by masking away all but the first eight
+/// bits when storing memory state into a MemoryInfo instance.
enum class MemoryState : u32 {
- Unmapped = 0x0,
- Io = 0x1,
- Normal = 0x2,
- CodeStatic = 0x3,
- CodeMutable = 0x4,
- Heap = 0x5,
- Shared = 0x6,
- ModuleCodeStatic = 0x8,
- ModuleCodeMutable = 0x9,
- IpcBuffer0 = 0xA,
- Mapped = 0xB,
- ThreadLocal = 0xC,
- TransferMemoryIsolated = 0xD,
- TransferMemory = 0xE,
- ProcessMemory = 0xF,
- IpcBuffer1 = 0x11,
- IpcBuffer3 = 0x12,
- KernelStack = 0x13,
+ Mask = 0xFF,
+ FlagProtect = 1U << 8,
+ FlagDebug = 1U << 9,
+ FlagIPC0 = 1U << 10,
+ FlagIPC3 = 1U << 11,
+ FlagIPC1 = 1U << 12,
+ FlagMapped = 1U << 13,
+ FlagCode = 1U << 14,
+ FlagAlias = 1U << 15,
+ FlagModule = 1U << 16,
+ FlagTransfer = 1U << 17,
+ FlagQueryPhysicalAddressAllowed = 1U << 18,
+ FlagSharedDevice = 1U << 19,
+ FlagSharedDeviceAligned = 1U << 20,
+ FlagIPCBuffer = 1U << 21,
+ FlagMemoryPoolAllocated = 1U << 22,
+ FlagMapProcess = 1U << 23,
+ FlagUncached = 1U << 24,
+ FlagCodeMemory = 1U << 25,
+
+ // Convenience flag sets to reduce repetition
+ IPCFlags = FlagIPC0 | FlagIPC3 | FlagIPC1,
+
+ CodeFlags = FlagDebug | IPCFlags | FlagMapped | FlagCode | FlagQueryPhysicalAddressAllowed |
+ FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
+
+ DataFlags = FlagProtect | IPCFlags | FlagMapped | FlagAlias | FlagTransfer |
+ FlagQueryPhysicalAddressAllowed | FlagSharedDevice | FlagSharedDeviceAligned |
+ FlagMemoryPoolAllocated | FlagIPCBuffer | FlagUncached,
+
+ Unmapped = 0x00,
+ Io = 0x01 | FlagMapped,
+ Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
+ CodeStatic = 0x03 | CodeFlags | FlagMapProcess,
+ CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory,
+ Heap = 0x05 | DataFlags | FlagCodeMemory,
+ Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
+ ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
+ ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
+
+ IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
+ IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
+
+ Stack = 0x0B | FlagMapped | IPCFlags | FlagQueryPhysicalAddressAllowed |
+ FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
+
+ ThreadLocal = 0x0C | FlagMapped | FlagMemoryPoolAllocated,
+
+ TransferMemoryIsolated = 0x0D | IPCFlags | FlagMapped | FlagQueryPhysicalAddressAllowed |
+ FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated |
+ FlagUncached,
+
+ TransferMemory = 0x0E | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
+ FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
+
+ ProcessMemory = 0x0F | FlagIPC3 | FlagIPC1 | FlagMapped | FlagMemoryPoolAllocated,
+
+ // Used to signify an inaccessible or invalid memory region with memory queries
+ Inaccessible = 0x10,
+
+ IpcBuffer1 = 0x11 | FlagIPC3 | FlagIPC1 | FlagMapped | FlagQueryPhysicalAddressAllowed |
+ FlagSharedDevice | FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
+
+ IpcBuffer3 = 0x12 | FlagIPC3 | FlagMapped | FlagQueryPhysicalAddressAllowed |
+ FlagSharedDeviceAligned | FlagMemoryPoolAllocated,
+
+ KernelStack = 0x13 | FlagMapped,
+};
+// clang-format on
+
+constexpr MemoryState operator|(MemoryState lhs, MemoryState rhs) {
+ return static_cast<MemoryState>(u32(lhs) | u32(rhs));
+}
+
+constexpr MemoryState operator&(MemoryState lhs, MemoryState rhs) {
+ return static_cast<MemoryState>(u32(lhs) & u32(rhs));
+}
+
+constexpr MemoryState operator^(MemoryState lhs, MemoryState rhs) {
+ return static_cast<MemoryState>(u32(lhs) ^ u32(rhs));
+}
+
+constexpr MemoryState operator~(MemoryState lhs) {
+ return static_cast<MemoryState>(~u32(lhs));
+}
+
+constexpr MemoryState& operator|=(MemoryState& lhs, MemoryState rhs) {
+ lhs = lhs | rhs;
+ return lhs;
+}
+
+constexpr MemoryState& operator&=(MemoryState& lhs, MemoryState rhs) {
+ lhs = lhs & rhs;
+ return lhs;
+}
+
+constexpr MemoryState& operator^=(MemoryState& lhs, MemoryState rhs) {
+ lhs = lhs ^ rhs;
+ return lhs;
+}
+
+constexpr u32 ToSvcMemoryState(MemoryState state) {
+ return static_cast<u32>(state & MemoryState::Mask);
+}
+
+struct MemoryInfo {
+ u64 base_address;
+ u64 size;
+ u32 state;
+ u32 attributes;
+ u32 permission;
+ u32 ipc_ref_count;
+ u32 device_ref_count;
+};
+static_assert(sizeof(MemoryInfo) == 0x28, "MemoryInfo has incorrect size.");
+
+struct PageInfo {
+ u32 flags;
};
/**
@@ -71,6 +257,16 @@ enum class MemoryState : u32 {
* also backed by a single host memory allocation.
*/
struct VirtualMemoryArea {
+ /// Gets the starting (base) address of this VMA.
+ VAddr StartAddress() const {
+ return base;
+ }
+
+ /// Gets the ending address of this VMA.
+ VAddr EndAddress() const {
+ return base + size - 1;
+ }
+
/// Virtual base address of the region.
VAddr base = 0;
/// Size of the region.
@@ -78,8 +274,8 @@ struct VirtualMemoryArea {
VMAType type = VMAType::Free;
VMAPermission permissions = VMAPermission::None;
- /// Tag returned by svcQueryMemory. Not otherwise used.
- MemoryState meminfo_state = MemoryState::Unmapped;
+ MemoryState state = MemoryState::Unmapped;
+ MemoryAttribute attribute = MemoryAttribute::None;
// Settings for type = AllocatedMemoryBlock
/// Memory block backing this VMA.
@@ -113,16 +309,10 @@ struct VirtualMemoryArea {
* - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/
*/
class VMManager final {
+ using VMAMap = std::map<VAddr, VirtualMemoryArea>;
+
public:
- /**
- * A map covering the entirety of the managed address space, keyed by the `base` field of each
- * VMA. It must always be modified by splitting or merging VMAs, so that the invariant
- * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
- * merged when possible so that no two similar and adjacent regions exist that have not been
- * merged.
- */
- std::map<VAddr, VirtualMemoryArea> vma_map;
- using VMAHandle = decltype(vma_map)::const_iterator;
+ using VMAHandle = VMAMap::const_iterator;
VMManager();
~VMManager();
@@ -133,6 +323,9 @@ public:
/// Finds the VMA in which the given address is included in, or `vma_map.end()`.
VMAHandle FindVMA(VAddr target) const;
+ /// Indicates whether or not the given handle is within the VMA map.
+ bool IsValidHandle(VMAHandle handle) const;
+
// TODO(yuriks): Should these functions actually return the handle?
/**
@@ -189,8 +382,28 @@ public:
ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
ResultCode HeapFree(VAddr target, u64 size);
- ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size,
- MemoryState state = MemoryState::Mapped);
+ ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
+
+ /// Queries the memory manager for information about the given address.
+ ///
+ /// @param address The address to query the memory manager about for information.
+ ///
+ /// @return A MemoryInfo instance containing information about the given address.
+ ///
+ MemoryInfo QueryMemory(VAddr address) const;
+
+ /// Sets an attribute across the given address range.
+ ///
+ /// @param address The starting address
+ /// @param size The size of the range to set the attribute on.
+ /// @param mask The attribute mask
+ /// @param attribute The attribute to set across the given address range
+ ///
+ /// @returns RESULT_SUCCESS if successful
+ /// @returns ERR_INVALID_ADDRESS_STATE if the attribute could not be set.
+ ///
+ ResultCode SetMemoryAttribute(VAddr address, u64 size, MemoryAttribute mask,
+ MemoryAttribute attribute);
/**
* Scans all VMAs and updates the page table range of any that use the given vector as backing
@@ -281,7 +494,7 @@ public:
Memory::PageTable page_table;
private:
- using VMAIter = decltype(vma_map)::iterator;
+ using VMAIter = VMAMap::iterator;
/// Converts a VMAHandle to a mutable VMAIter.
VMAIter StripIterConstness(const VMAHandle& iter);
@@ -328,6 +541,44 @@ private:
/// Clears out the page table
void ClearPageTable();
+ using CheckResults = ResultVal<std::tuple<MemoryState, VMAPermission, MemoryAttribute>>;
+
+ /// Checks if an address range adheres to the specified states provided.
+ ///
+ /// @param address The starting address of the address range.
+ /// @param size The size of the address range.
+ /// @param state_mask The memory state mask.
+ /// @param state The state to compare the individual VMA states against,
+ /// which is done in the form of: (vma.state & state_mask) != state.
+ /// @param permission_mask The memory permissions mask.
+ /// @param permissions The permission to compare the individual VMA permissions against,
+ /// which is done in the form of:
+ /// (vma.permission & permission_mask) != permission.
+ /// @param attribute_mask The memory attribute mask.
+ /// @param attribute The memory attributes to compare the individual VMA attributes
+ /// against, which is done in the form of:
+ /// (vma.attributes & attribute_mask) != attribute.
+ /// @param ignore_mask The memory attributes to ignore during the check.
+ ///
+ /// @returns If successful, returns a tuple containing the memory attributes
+ /// (with ignored bits specified by ignore_mask unset), memory permissions, and
+ /// memory state across the memory range.
+ /// @returns If not successful, returns ERR_INVALID_ADDRESS_STATE.
+ ///
+ CheckResults CheckRangeState(VAddr address, u64 size, MemoryState state_mask, MemoryState state,
+ VMAPermission permission_mask, VMAPermission permissions,
+ MemoryAttribute attribute_mask, MemoryAttribute attribute,
+ MemoryAttribute ignore_mask) const;
+
+ /**
+ * A map covering the entirety of the managed address space, keyed by the `base` field of each
+ * VMA. It must always be modified by splitting or merging VMAs, so that the invariant
+ * `elem.base + elem.size == next.base` is preserved, and mergeable regions must always be
+ * merged when possible so that no two similar and adjacent regions exist that have not been
+ * merged.
+ */
+ VMAMap vma_map;
+
u32 address_space_width = 0;
VAddr address_space_base = 0;
VAddr address_space_end = 0;
diff --git a/src/core/hle/kernel/writable_event.h b/src/core/hle/kernel/writable_event.h
index fc57d18d7..8fa8d68ee 100644
--- a/src/core/hle/kernel/writable_event.h
+++ b/src/core/hle/kernel/writable_event.h
@@ -39,7 +39,7 @@ public:
return name;
}
- static const HandleType HANDLE_TYPE = HandleType::Event;
+ static const HandleType HANDLE_TYPE = HandleType::WritableEvent;
HandleType GetHandleType() const override {
return HANDLE_TYPE;
}
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 1a15d85cb..5fc02a521 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -73,10 +73,13 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") {
IWindowController::~IWindowController() = default;
void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID();
+
+ LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id);
+
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(0);
+ rb.Push<u64>(process_id);
}
void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx) {
@@ -567,7 +570,6 @@ private:
void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_AM, "called");
- applet->GetBroker().SignalStateChanged();
const auto event = applet->GetBroker().GetStateChangedEvent();
IPC::ResponseBuilder rb{ctx, 2, 1};
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index 981bdec51..f255f74b5 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -146,11 +146,10 @@ void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) {
if (complete) {
broker.PushNormalDataFromApplet(IStorage{output_main});
+ broker.SignalStateChanged();
} else {
broker.PushInteractiveDataFromApplet(IStorage{output_sub});
}
-
- broker.SignalStateChanged();
} else {
output_main[0] = 1;
complete = true;
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 0417fdb92..b506bc3dd 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -20,6 +20,7 @@
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
+#include "core/settings.h"
namespace Service::AOC {
@@ -76,6 +77,13 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+
+ const auto& disabled = Settings::values.disabled_addons[current];
+ if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
+ rb.Push<u32>(0);
+ return;
+ }
+
rb.Push<u32>(static_cast<u32>(
std::count_if(add_on_content.begin(), add_on_content.end(),
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
@@ -96,6 +104,10 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
}
+ const auto& disabled = Settings::values.disabled_addons[current];
+ if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end())
+ out = {};
+
if (out.size() < offset) {
IPC::ResponseBuilder rb{ctx, 2};
// TODO(DarkLordZach): Find the correct error code.
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 694ec40ec..74c4e583b 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -20,6 +20,7 @@
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/savedata_factory.h"
+#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/process.h"
@@ -44,8 +45,12 @@ public:
explicit IStorage(FileSys::VirtualFile backend_)
: ServiceFramework("IStorage"), backend(std::move(backend_)) {
static const FunctionInfo functions[] = {
- {0, &IStorage::Read, "Read"}, {1, nullptr, "Write"}, {2, nullptr, "Flush"},
- {3, nullptr, "SetSize"}, {4, nullptr, "GetSize"}, {5, nullptr, "OperateRange"},
+ {0, &IStorage::Read, "Read"},
+ {1, nullptr, "Write"},
+ {2, nullptr, "Flush"},
+ {3, nullptr, "SetSize"},
+ {4, &IStorage::GetSize, "GetSize"},
+ {5, nullptr, "OperateRange"},
};
RegisterHandlers(functions);
}
@@ -82,6 +87,15 @@ private:
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
+
+ void GetSize(Kernel::HLERequestContext& ctx) {
+ const u64 size = backend->GetSize();
+ LOG_DEBUG(Service_FS, "called, size={}", size);
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u64>(size);
+ }
};
class IFile final : public ServiceFramework<IFile> {
@@ -795,9 +809,18 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(Kernel::HLERequestContext&
void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called");
+ enum class LogMode : u32 {
+ Off,
+ Log,
+ RedirectToSdCard,
+ LogToSdCard = Log | RedirectToSdCard,
+ };
+
+ // Given we always want to receive logging information,
+ // we always specify logging as enabled.
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(5);
+ rb.PushEnum(LogMode::Log);
}
void FSP_SRV::OpenDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx) {
@@ -831,6 +854,15 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
if (data.Failed()) {
+ const auto archive = FileSys::SystemArchive::SynthesizeSystemArchive(title_id);
+
+ if (archive != nullptr) {
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushIpcInterface(std::make_shared<IStorage>(archive));
+ return;
+ }
+
// TODO(DarkLordZach): Find the right error code to use here
LOG_ERROR(Service_FS,
"could not open data storage with title_id={:016X}, storage_id={:02X}", title_id,
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index ca119dd3a..13bcefe07 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -335,10 +335,7 @@ public:
vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size,
Kernel::VMAPermission::ReadWrite);
- Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
+ Core::System::GetInstance().InvalidateCpuInstructionCaches();
nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size});
@@ -391,10 +388,7 @@ public:
Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS);
ASSERT(process->UnmapMemory(mapped_addr, 0, nro_size) == RESULT_SUCCESS);
- Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
+ Core::System::GetInstance().InvalidateCpuInstructionCaches();
nro.erase(iter);
IPC::ResponseBuilder rb{ctx, 2};
@@ -414,13 +408,13 @@ private:
using SHA256Hash = std::array<u8, 0x20>;
struct NROHeader {
- u32_le entrypoint_insn;
+ INSERT_PADDING_WORDS(1);
u32_le mod_offset;
INSERT_PADDING_WORDS(2);
u32_le magic;
- INSERT_PADDING_WORDS(1);
+ u32_le version;
u32_le nro_size;
- INSERT_PADDING_WORDS(1);
+ u32_le flags;
u32_le text_offset;
u32_le text_size;
u32_le ro_offset;
@@ -436,9 +430,10 @@ private:
struct NRRHeader {
u32_le magic;
- INSERT_PADDING_BYTES(0x1C);
+ INSERT_PADDING_BYTES(12);
u64_le title_id_mask;
u64_le title_id_pattern;
+ INSERT_PADDING_BYTES(16);
std::array<u8, 0x100> modulus;
std::array<u8, 0x100> signature_1;
std::array<u8, 0x100> signature_2;
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index d5df112a0..a7bed0040 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -317,8 +317,8 @@ private:
}
bool has_attached_handle{};
- const u64 device_handle{Common::MakeMagic('Y', 'U', 'Z', 'U')};
- const u32 npad_id{0}; // Player 1 controller
+ const u64 device_handle{0}; // Npad device 1
+ const u32 npad_id{0}; // Player 1 controller
State state{State::NonInitialized};
DeviceState device_state{DeviceState::Initialized};
Kernel::EventPair deactivate_event;
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
index 3bfce0110..0a650f36c 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_gpu.cpp
@@ -137,6 +137,10 @@ u32 nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::vector<
}
static void PushGPUEntries(Tegra::CommandList&& entries) {
+ if (entries.empty()) {
+ return;
+ }
+
auto& dma_pusher{Core::System::GetInstance().GPU().DmaPusher()};
dma_pusher.Push(std::move(entries));
dma_pusher.DispatchCalls();
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 1ec340466..d25b80ab0 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -70,10 +70,6 @@
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/wlan/wlan.h"
-using Kernel::ClientPort;
-using Kernel::ServerPort;
-using Kernel::SharedPtr;
-
namespace Service {
/**
@@ -101,33 +97,33 @@ ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_ses
ServiceFrameworkBase::~ServiceFrameworkBase() = default;
void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
- ASSERT(port == nullptr);
- port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
+ ASSERT(!port_installed);
+
+ auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
port->SetHleHandler(shared_from_this());
+ port_installed = true;
}
void ServiceFrameworkBase::InstallAsNamedPort() {
- ASSERT(port == nullptr);
+ ASSERT(!port_installed);
auto& kernel = Core::System::GetInstance().Kernel();
- SharedPtr<ServerPort> server_port;
- SharedPtr<ClientPort> client_port;
- std::tie(server_port, client_port) =
- ServerPort::CreatePortPair(kernel, max_sessions, service_name);
+ auto [server_port, client_port] =
+ Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
server_port->SetHleHandler(shared_from_this());
kernel.AddNamedPort(service_name, std::move(client_port));
+ port_installed = true;
}
Kernel::SharedPtr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort() {
- ASSERT(port == nullptr);
+ ASSERT(!port_installed);
auto& kernel = Core::System::GetInstance().Kernel();
- Kernel::SharedPtr<Kernel::ServerPort> server_port;
- Kernel::SharedPtr<Kernel::ClientPort> client_port;
- std::tie(server_port, client_port) =
+ auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
- port = MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port)).Unwrap();
+ auto port = MakeResult(std::move(server_port)).Unwrap();
port->SetHleHandler(shared_from_this());
+ port_installed = true;
return client_port;
}
@@ -152,8 +148,7 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(Kernel::HLERequestContext
}
buf.push_back('}');
- LOG_ERROR(Service, "unknown / unimplemented {}", fmt::to_string(buf));
- UNIMPLEMENTED();
+ UNIMPLEMENTED_MSG("Unknown / unimplemented {}", fmt::to_string(buf));
}
void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 98483ecf1..029533628 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -96,11 +96,9 @@ private:
/// Maximum number of concurrent sessions that this service can handle.
u32 max_sessions;
- /**
- * Port where incoming connections will be received. Only created when InstallAsService() or
- * InstallAsNamedPort() are called.
- */
- Kernel::SharedPtr<Kernel::ServerPort> port;
+ /// Flag to store if a port was already create/installed to detect multiple install attempts,
+ /// which is not supported.
+ bool port_installed = false;
/// Function used to safely up-cast pointers to the derived class before invoking a handler.
InvokerFn* handler_invoker;
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 0d0f63a78..142929124 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -54,13 +54,11 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService
return ERR_ALREADY_REGISTERED;
auto& kernel = Core::System::GetInstance().Kernel();
- Kernel::SharedPtr<Kernel::ServerPort> server_port;
- Kernel::SharedPtr<Kernel::ClientPort> client_port;
- std::tie(server_port, client_port) =
+ auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, name);
registered_services.emplace(std::move(name), std::move(client_port));
- return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port));
+ return MakeResult(std::move(server_port));
}
ResultCode ServiceManager::UnregisterService(const std::string& name) {
@@ -83,7 +81,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> ServiceManager::GetServicePort(
return ERR_SERVICE_NOT_REGISTERED;
}
- return MakeResult<Kernel::SharedPtr<Kernel::ClientPort>>(it->second);
+ return MakeResult(it->second);
}
ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToService(
@@ -147,12 +145,13 @@ void SM::RegisterService(Kernel::HLERequestContext& ctx) {
const std::string name(name_buf.begin(), end);
- const auto unk_bool = static_cast<bool>(rp.PopRaw<u32>());
- const auto session_count = rp.PopRaw<u32>();
+ const auto is_light = static_cast<bool>(rp.PopRaw<u32>());
+ const auto max_session_count = rp.PopRaw<u32>();
- LOG_DEBUG(Service_SM, "called with unk_bool={}", unk_bool);
+ LOG_DEBUG(Service_SM, "called with name={}, max_session_count={}, is_light={}", name,
+ max_session_count, is_light);
- auto handle = service_manager->RegisterService(name, session_count);
+ auto handle = service_manager->RegisterService(name, max_session_count);
if (handle.Failed()) {
LOG_ERROR(Service_SM, "failed to register service with error_code={:08X}",
handle.Code().raw);
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 8518dddcb..ac04d72d7 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -7,7 +7,6 @@
#include "common/common_funcs.h"
#include "common/file_util.h"
#include "common/logging/log.h"
-#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"
@@ -146,7 +145,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
const VAddr load_addr = next_load_addr;
const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
const auto tentative_next_load_addr =
- AppLoader_NSO::LoadModule(*module_file, load_addr, should_pass_arguments, pm);
+ AppLoader_NSO::LoadModule(process, *module_file, load_addr, should_pass_arguments, pm);
if (!tentative_next_load_addr) {
return ResultStatus::ErrorLoadingNSO;
}
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index d109ed2b5..1615cb5a8 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -33,7 +33,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override {
+ FileType GetFileType() const override {
return IdentifyType(file);
}
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h
index 6af76441c..a2d33021c 100644
--- a/src/core/loader/elf.h
+++ b/src/core/loader/elf.h
@@ -22,7 +22,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override {
+ FileType GetFileType() const override {
return IdentifyType(file);
}
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 7686634bf..0838e303b 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -12,6 +12,7 @@
#include <vector>
#include "common/common_types.h"
+#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs.h"
namespace Kernel {
@@ -131,7 +132,7 @@ public:
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
- virtual FileType GetFileType() = 0;
+ virtual FileType GetFileType() const = 0;
/**
* Load the application and return the created Process instance
@@ -243,6 +244,15 @@ public:
return ResultStatus::ErrorNotImplemented;
}
+ /**
+ * Get the developer of the application
+ * @param developer Reference to store the application developer into
+ * @return ResultStatus result of function
+ */
+ virtual ResultStatus ReadDeveloper(std::string& developer) {
+ return ResultStatus::ErrorNotImplemented;
+ }
+
protected:
FileSys::VirtualFile file;
bool is_loaded = false;
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index 42f4a777b..a093e3d36 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -37,7 +37,7 @@ FileType AppLoader_NAX::IdentifyType(const FileSys::VirtualFile& file) {
return IdentifyTypeImpl(nax);
}
-FileType AppLoader_NAX::GetFileType() {
+FileType AppLoader_NAX::GetFileType() const {
return IdentifyTypeImpl(*nax);
}
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index b4d93bd01..0a97511b8 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override;
+ FileType GetFileType() const override;
ResultStatus Load(Kernel::Process& process) override;
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index 95d9b73a1..cbbe701d2 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -29,7 +29,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override {
+ FileType GetFileType() const override {
return IdentifyType(file);
}
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index fbbd6b0de..4fad0c0dd 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -10,7 +10,6 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
-#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs_offset.h"
@@ -129,9 +128,8 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
-/*static*/ bool AppLoader_NRO::LoadNro(const std::vector<u8>& data, const std::string& name,
- VAddr load_base) {
-
+static bool LoadNroImpl(Kernel::Process& process, const std::vector<u8>& data,
+ const std::string& name, VAddr load_base) {
if (data.size() < sizeof(NroHeader)) {
return {};
}
@@ -189,7 +187,7 @@ static constexpr u32 PageAlignSize(u32 size) {
// Load codeset for current process
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
+ process.LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
GDBStub::RegisterModule(name, load_base, load_base);
@@ -197,8 +195,9 @@ static constexpr u32 PageAlignSize(u32 size) {
return true;
}
-bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
- return AppLoader_NRO::LoadNro(file.ReadAllBytes(), file.GetName(), load_base);
+bool AppLoader_NRO::LoadNro(Kernel::Process& process, const FileSys::VfsFile& file,
+ VAddr load_base) {
+ return LoadNroImpl(process, file.ReadAllBytes(), file.GetName(), load_base);
}
ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
@@ -209,7 +208,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
// Load NRO
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
- if (!LoadNro(*file, base_address)) {
+ if (!LoadNro(process, *file, base_address)) {
return ResultStatus::ErrorLoadingNRO;
}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 3e6959302..013d629c0 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -14,6 +14,10 @@ namespace FileSys {
class NACP;
}
+namespace Kernel {
+class Process;
+}
+
namespace Loader {
/// Loads an NRO file
@@ -29,7 +33,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override {
+ FileType GetFileType() const override {
return IdentifyType(file);
}
@@ -41,10 +45,8 @@ public:
ResultStatus ReadTitle(std::string& title) override;
bool IsRomFSUpdatable() const override;
- static bool LoadNro(const std::vector<u8>& data, const std::string& name, VAddr load_base);
-
private:
- bool LoadNro(const FileSys::VfsFile& file, VAddr load_base);
+ bool LoadNro(Kernel::Process& process, const FileSys::VfsFile& file, VAddr load_base);
std::vector<u8> icon_data;
std::unique_ptr<FileSys::NACP> nacp;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index aaf006309..6ded0b707 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -9,7 +9,6 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
-#include "core/core.h"
#include "core/file_sys/patch_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
@@ -93,7 +92,8 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
-std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAddr load_base,
+std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
+ const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm) {
if (file.GetSize() < sizeof(NsoHeader))
@@ -166,7 +166,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAd
// Load codeset for current process
codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
+ process.LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
GDBStub::RegisterModule(file.GetName(), load_base, load_base);
@@ -181,7 +181,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
// Load module
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
- if (!LoadModule(*file, base_address, true)) {
+ if (!LoadModule(process, *file, base_address, true)) {
return ResultStatus::ErrorLoadingNSO;
}
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 433306139..135b6ea5a 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -10,6 +10,10 @@
#include "core/loader/linker.h"
#include "core/loader/loader.h"
+namespace Kernel {
+class Process;
+}
+
namespace Loader {
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
@@ -33,12 +37,12 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override {
+ FileType GetFileType() const override {
return IdentifyType(file);
}
- static std::optional<VAddr> LoadModule(const FileSys::VfsFile& file, VAddr load_base,
- bool should_pass_arguments,
+ static std::optional<VAddr> LoadModule(Kernel::Process& process, const FileSys::VfsFile& file,
+ VAddr load_base, bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm = {});
ResultStatus Load(Kernel::Process& process) override;
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 080d89904..b4ab88ae8 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -151,4 +151,11 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {
title = nacp_file->GetApplicationName();
return ResultStatus::Success;
}
+
+ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) {
+ if (nacp_file == nullptr)
+ return ResultStatus::ErrorNoControl;
+ developer = nacp_file->GetDeveloperName();
+ return ResultStatus::Success;
+}
} // namespace Loader
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index db91cd01e..2b1e0719b 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override {
+ FileType GetFileType() const override {
return IdentifyType(file);
}
@@ -43,6 +43,7 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
+ ResultStatus ReadDeveloper(std::string& developer) override;
private:
std::unique_ptr<FileSys::NSP> nsp;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 461607c95..bd5a83b49 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -120,4 +120,11 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) {
title = nacp_file->GetApplicationName();
return ResultStatus::Success;
}
+
+ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) {
+ if (nacp_file == nullptr)
+ return ResultStatus::ErrorNoControl;
+ developer = nacp_file->GetDeveloperName();
+ return ResultStatus::Success;
+}
} // namespace Loader
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 46f8dfc9e..15d1b1a23 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -31,7 +31,7 @@ public:
*/
static FileType IdentifyType(const FileSys::VirtualFile& file);
- FileType GetFileType() override {
+ FileType GetFileType() const override {
return IdentifyType(file);
}
@@ -43,6 +43,7 @@ public:
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
+ ResultStatus ReadDeveloper(std::string& developer) override;
private:
std::unique_ptr<FileSys::XCI> xci;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 70abd856a..e9166dbd9 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -53,6 +53,14 @@ void PageTable::Resize(std::size_t address_space_width_in_bits) {
pointers.resize(num_page_table_entries);
attributes.resize(num_page_table_entries);
+
+ // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
+ // vector size is subsequently decreased (via resize), the vector might not automatically
+ // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
+ // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
+
+ pointers.shrink_to_fit();
+ attributes.shrink_to_fit();
}
static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
@@ -117,14 +125,13 @@ void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPoin
* using a VMA from the current process
*/
static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) {
- u8* direct_pointer = nullptr;
+ const auto& vm_manager = process.VMManager();
- auto& vm_manager = process.VMManager();
+ const auto it = vm_manager.FindVMA(vaddr);
+ DEBUG_ASSERT(vm_manager.IsValidHandle(it));
- auto it = vm_manager.FindVMA(vaddr);
- ASSERT(it != vm_manager.vma_map.end());
-
- auto& vma = it->second;
+ u8* direct_pointer = nullptr;
+ const auto& vma = it->second;
switch (vma.type) {
case Kernel::VMAType::AllocatedMemoryBlock:
direct_pointer = vma.backing_block->data() + vma.offset;
@@ -180,6 +187,7 @@ T Read(const VAddr vaddr) {
default:
UNREACHABLE();
}
+ return {};
}
template <typename T>
diff --git a/src/core/settings.h b/src/core/settings.h
index a0c5fd447..de01b05c0 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -6,8 +6,10 @@
#include <array>
#include <atomic>
+#include <map>
#include <optional>
#include <string>
+#include <vector>
#include "common/common_types.h"
namespace Settings {
@@ -411,6 +413,9 @@ struct Values {
std::string web_api_url;
std::string yuzu_username;
std::string yuzu_token;
+
+ // Add-Ons
+ std::map<u64, std::vector<std::string>> disabled_addons;
} extern values;
void Apply();
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index a3b08c740..09ed74d78 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -103,13 +103,8 @@ bool VerifyLogin(const std::string& username, const std::string& token) {
TelemetrySession::TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
- if (Settings::values.enable_telemetry) {
- backend = std::make_unique<WebService::TelemetryJson>(Settings::values.web_api_url,
- Settings::values.yuzu_username,
- Settings::values.yuzu_token);
- } else {
- backend = std::make_unique<Telemetry::NullVisitor>();
- }
+ backend = std::make_unique<WebService::TelemetryJson>(
+ Settings::values.web_api_url, Settings::values.yuzu_username, Settings::values.yuzu_token);
#else
backend = std::make_unique<Telemetry::NullVisitor>();
#endif
@@ -180,7 +175,8 @@ TelemetrySession::~TelemetrySession() {
// This is just a placeholder to wrap up the session once the core completes and this is
// destroyed. This will be moved elsewhere once we are actually doing real I/O with the service.
field_collection.Accept(*backend);
- backend->Complete();
+ if (Settings::values.enable_telemetry)
+ backend->Complete();
backend = nullptr;
}
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
deleted file mode 100644
index 8b9c548cc..000000000
--- a/src/video_core/command_processor.cpp
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include <cstddef>
-#include <memory>
-#include <utility>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/microprofile.h"
-#include "common/vector_math.h"
-#include "core/memory.h"
-#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"
-#include "video_core/gpu.h"
-#include "video_core/renderer_base.h"
-#include "video_core/video_core.h"
-
-namespace Tegra {
-
-enum class BufferMethods {
- BindObject = 0,
- CountBufferMethods = 0x40,
-};
-
-MICROPROFILE_DEFINE(ProcessCommandLists, "GPU", "Execute command buffer", MP_RGB(128, 128, 192));
-
-void GPU::ProcessCommandLists(const std::vector<CommandListHeader>& commands) {
- MICROPROFILE_SCOPE(ProcessCommandLists);
-
- // On entering GPU code, assume all memory may be touched by the ARM core.
- maxwell_3d->dirty_flags.OnMemoryWrite();
-
- 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);
-
- 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;
- }
-
- 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");
- }
- };
-
- for (auto entry : commands) {
- Tegra::GPUVAddr address = entry.Address();
- u32 size = entry.sz;
- const std::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;
- }
- 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();
- }
- }
- }
-}
-
-} // namespace Tegra
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 25bb7604a..0faff6fdf 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -164,6 +164,7 @@ public:
return 3;
default:
UNREACHABLE();
+ return 1;
}
}
@@ -871,6 +872,7 @@ public:
return 4;
}
UNREACHABLE();
+ return 1;
}
GPUVAddr StartAddress() const {
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index b9faaf8e0..eb703bb5a 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -575,7 +575,7 @@ union Instruction {
union {
BitField<39, 2, u64> tab5cb8_2;
- BitField<41, 3, u64> tab5c68_1;
+ BitField<41, 3, u64> postfactor;
BitField<44, 2, u64> tab5c68_0;
BitField<48, 1, u64> negate_b;
} fmul;
@@ -609,7 +609,7 @@ union Instruction {
BitField<31, 1, u64> negate_b;
BitField<30, 1, u64> abs_b;
- BitField<47, 2, HalfType> type_b;
+ BitField<28, 2, HalfType> type_b;
BitField<35, 2, HalfType> type_c;
} alu_half;
@@ -1049,6 +1049,7 @@ union Instruction {
BitField<49, 1, u64> nodep_flag;
BitField<50, 3, u64> component_mask_selector;
BitField<53, 4, u64> texture_info;
+ BitField<60, 1, u64> fp32_flag;
TextureType GetTextureType() const {
// The TEXS instruction has a weird encoding for the texture type.
@@ -1064,6 +1065,7 @@ union Instruction {
LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
static_cast<u32>(texture_info.Value()));
UNREACHABLE();
+ return TextureType::Texture1D;
}
TextureProcessMode GetTextureProcessMode() const {
@@ -1144,6 +1146,7 @@ union Instruction {
LOG_CRITICAL(HW_GPU, "Unhandled texture_info: {}",
static_cast<u32>(texture_info.Value()));
UNREACHABLE();
+ return TextureType::Texture1D;
}
TextureProcessMode GetTextureProcessMode() const {
@@ -1549,7 +1552,7 @@ private:
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
INST("1101111101001---", Id::TXQ, Type::Memory, "TXQ"),
- INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
+ INST("1101-00---------", 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"),
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index fd1242333..08cf6268f 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -102,6 +102,7 @@ u32 RenderTargetBytesPerPixel(RenderTargetFormat format) {
return 1;
default:
UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format));
+ return 1;
}
}
@@ -119,6 +120,7 @@ u32 DepthFormatBytesPerPixel(DepthFormat format) {
return 2;
default:
UNIMPLEMENTED_MSG("Unimplemented Depth format {}", static_cast<u32>(format));
+ return 1;
}
}
@@ -141,6 +143,12 @@ void GPU::CallMethod(const MethodCall& method_call) {
return;
}
+ if (method_call.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[method_call.subchannel];
switch (engine) {
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index 9c55e9f1e..64f75db43 100644
--- a/src/video_core/macro_interpreter.cpp
+++ b/src/video_core/macro_interpreter.cpp
@@ -171,6 +171,7 @@ u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b)
default:
UNIMPLEMENTED_MSG("Unimplemented ALU operation {}", static_cast<u32>(operation));
+ return 0;
}
}
@@ -268,6 +269,7 @@ bool MacroInterpreter::EvaluateBranchCondition(BranchCondition cond, u32 value)
return value != 0;
}
UNREACHABLE();
+ return true;
}
} // namespace Tegra
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp
index a310491a8..47e76d8fe 100644
--- a/src/video_core/morton.cpp
+++ b/src/video_core/morton.cpp
@@ -192,6 +192,7 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor
return linear_to_morton_fns[static_cast<std::size_t>(format)];
}
UNREACHABLE();
+ return morton_to_linear_fns[static_cast<std::size_t>(format)];
}
/// 8x8 Z-Order coordinate from 2D coordinates
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 9e93bd609..2b29fc45f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -79,6 +79,26 @@ struct DrawParameters {
}
};
+struct FramebufferCacheKey {
+ bool is_single_buffer = false;
+ bool stencil_enable = false;
+
+ std::array<GLenum, Maxwell::NumRenderTargets> color_attachments{};
+ std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors{};
+ u32 colors_count = 0;
+
+ GLuint zeta = 0;
+
+ auto Tie() const {
+ return std::tie(is_single_buffer, stencil_enable, color_attachments, colors, colors_count,
+ zeta);
+ }
+
+ bool operator<(const FramebufferCacheKey& rhs) const {
+ return Tie() < rhs.Tie();
+ }
+};
+
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo& info)
: res_cache{*this}, shader_cache{*this}, emu_window{window}, screen_info{info},
buffer_cache(*this, STREAM_BUFFER_SIZE) {
@@ -90,9 +110,6 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
OpenGLState::ApplyDefaultState();
- // Create render framebuffer
- framebuffer.Create();
-
shader_program_manager = std::make_unique<GLShader::ProgramManager>();
state.draw.shader_program = 0;
state.Apply();
@@ -361,6 +378,44 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
SyncClipEnabled(clip_distances);
}
+void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
+ OpenGLState& current_state) {
+ const auto [entry, is_cache_miss] = framebuffer_cache.try_emplace(fbkey);
+ auto& framebuffer = entry->second;
+
+ if (is_cache_miss)
+ framebuffer.Create();
+
+ current_state.draw.draw_framebuffer = framebuffer.handle;
+ current_state.ApplyFramebufferState();
+
+ if (!is_cache_miss)
+ return;
+
+ if (fbkey.is_single_buffer) {
+ if (fbkey.color_attachments[0] != GL_NONE) {
+ glFramebufferTexture(GL_DRAW_FRAMEBUFFER, fbkey.color_attachments[0], fbkey.colors[0],
+ 0);
+ }
+ glDrawBuffer(fbkey.color_attachments[0]);
+ } else {
+ for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
+ if (fbkey.colors[index]) {
+ glFramebufferTexture(GL_DRAW_FRAMEBUFFER,
+ GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
+ fbkey.colors[index], 0);
+ }
+ }
+ glDrawBuffers(fbkey.colors_count, fbkey.color_attachments.data());
+ }
+
+ if (fbkey.zeta) {
+ GLenum zeta_attachment =
+ fbkey.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
+ glFramebufferTexture(GL_DRAW_FRAMEBUFFER, zeta_attachment, fbkey.zeta, 0);
+ }
+}
+
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
@@ -444,10 +499,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
UNIMPLEMENTED_IF(regs.rt_separate_frag_data != 0);
// Bind the framebuffer surfaces
- current_state.draw.draw_framebuffer = framebuffer.handle;
- current_state.ApplyFramebufferState();
current_state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0;
+ FramebufferCacheKey fbkey;
+
if (using_color_fb) {
if (single_color_target) {
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
@@ -463,14 +518,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion;
}
- 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));
+ fbkey.is_single_buffer = true;
+ fbkey.color_attachments[0] =
+ GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target);
+ fbkey.colors[0] = color_surface != nullptr ? color_surface->Texture().handle : 0;
} 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);
@@ -485,22 +538,17 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
color_surface->GetSurfaceParams().srgb_conversion;
}
- 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);
+ fbkey.color_attachments[index] =
+ GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
+ fbkey.colors[index] =
+ color_surface != nullptr ? color_surface->Texture().handle : 0;
}
- glDrawBuffers(regs.rt_control.count, buffers.data());
+ fbkey.is_single_buffer = false;
+ fbkey.colors_count = regs.rt_control.count;
}
} 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);
+ // No color attachments are enabled - leave them as zero
+ fbkey.is_single_buffer = true;
}
if (depth_surface) {
@@ -508,22 +556,12 @@ void RasterizerOpenGL::ConfigureFramebuffers(OpenGLState& current_state, bool us
// the shader doesn't actually write to it.
depth_surface->MarkAsModified(true, res_cache);
- 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);
+ fbkey.zeta = depth_surface->Texture().handle;
+ fbkey.stencil_enable = regs.stencil_enable;
}
+
+ SetupCachedFramebuffer(fbkey, current_state);
+
SyncViewport(current_state);
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 988fa3e27..8a891ffc7 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -40,6 +40,7 @@ namespace OpenGL {
struct ScreenInfo;
struct DrawParameters;
+struct FramebufferCacheKey;
class RasterizerOpenGL : public VideoCore::RasterizerInterface {
public:
@@ -195,11 +196,12 @@ private:
OGLVertexArray>
vertex_array_cache;
+ std::map<FramebufferCacheKey, OGLFramebuffer> framebuffer_cache;
+
std::array<SamplerInfo, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_samplers;
static constexpr std::size_t STREAM_BUFFER_SIZE = 128 * 1024 * 1024;
OGLBufferCache buffer_cache;
- OGLFramebuffer framebuffer;
PrimitiveAssembler primitive_assembler{buffer_cache};
GLint uniform_buffer_alignment;
@@ -214,6 +216,8 @@ private:
void SetupShaders(GLenum primitive_mode);
+ void SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, OpenGLState& current_state);
+
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 5f4cdd119..7ea07631a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -101,8 +101,18 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
params.srgb_conversion = config.tic.IsSrgbConversionEnabled();
params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
params.srgb_conversion);
+
+ if (params.pixel_format == PixelFormat::R16U && config.tsc.depth_compare_enabled) {
+ // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled,
+ // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also
+ // causes GetFormatType to properly return 'Depth' below).
+ params.pixel_format = PixelFormat::Z16;
+ }
+
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
params.type = GetFormatType(params.pixel_format);
+ UNIMPLEMENTED_IF(params.type == SurfaceType::ColorTexture && config.tsc.depth_compare_enabled);
+
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();
@@ -257,7 +267,7 @@ static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex
{GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U
- {GL_RGBA16UI, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI
+ {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI
{GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float,
false}, // R11FG11FB10F
{GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 038b25c75..aea6bf1af 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -2,7 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <boost/functional/hash.hpp>
#include "common/assert.h"
+#include "common/hash.h"
#include "core/core.h"
#include "core/memory.h"
#include "video_core/engines/maxwell_3d.h"
@@ -66,14 +68,17 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
// stage here.
setup.SetProgramB(GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
case Maxwell::ShaderProgram::VertexB:
+ CalculateProperties();
program_result = GLShader::GenerateVertexShader(setup);
gl_type = GL_VERTEX_SHADER;
break;
case Maxwell::ShaderProgram::Geometry:
+ CalculateProperties();
program_result = GLShader::GenerateGeometryShader(setup);
gl_type = GL_GEOMETRY_SHADER;
break;
case Maxwell::ShaderProgram::Fragment:
+ CalculateProperties();
program_result = GLShader::GenerateFragmentShader(setup);
gl_type = GL_FRAGMENT_SHADER;
break;
@@ -140,6 +145,46 @@ GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,
return target_program.handle;
};
+static bool IsSchedInstruction(std::size_t offset, std::size_t main_offset) {
+ // sched instructions appear once every 4 instructions.
+ static constexpr std::size_t SchedPeriod = 4;
+ const std::size_t absolute_offset = offset - main_offset;
+ return (absolute_offset % SchedPeriod) == 0;
+}
+
+static std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) {
+ constexpr std::size_t start_offset = 10;
+ std::size_t offset = start_offset;
+ std::size_t size = start_offset * sizeof(u64);
+ while (offset < program.size()) {
+ const u64 inst = program[offset];
+ if (!IsSchedInstruction(offset, start_offset)) {
+ if (inst == 0 || (inst >> 52) == 0x50b) {
+ break;
+ }
+ }
+ size += sizeof(inst);
+ offset++;
+ }
+ return size;
+}
+
+void CachedShader::CalculateProperties() {
+ setup.program.real_size = CalculateProgramSize(setup.program.code);
+ setup.program.real_size_b = 0;
+ setup.program.unique_identifier = Common::CityHash64(
+ reinterpret_cast<const char*>(setup.program.code.data()), setup.program.real_size);
+ if (program_type == Maxwell::ShaderProgram::VertexA) {
+ std::size_t seed = 0;
+ boost::hash_combine(seed, setup.program.unique_identifier);
+ setup.program.real_size_b = CalculateProgramSize(setup.program.code_b);
+ const u64 identifier_b = Common::CityHash64(
+ reinterpret_cast<const char*>(setup.program.code_b.data()), setup.program.real_size_b);
+ boost::hash_combine(seed, identifier_b);
+ setup.program.unique_identifier = static_cast<u64>(seed);
+ }
+}
+
ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer) : RasterizerCache{rasterizer} {}
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 08f470de3..de3671acf 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -67,6 +67,7 @@ public:
6, "ShaderTrianglesAdjacency");
default:
UNREACHABLE_MSG("Unknown primitive mode.");
+ return LazyGeometryProgram(geometry_programs.points, "points", 1, "ShaderPoints");
}
}
@@ -81,6 +82,8 @@ private:
GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,
u32 max_vertices, const std::string& debug_name);
+ void CalculateProperties();
+
VAddr addr;
std::size_t shader_length;
Maxwell::ShaderProgram program_type;
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index d235bfcd4..4e685fa2c 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -50,6 +50,14 @@ public:
using std::runtime_error::runtime_error;
};
+/// Generates code to use for a swizzle operation.
+static std::string GetSwizzle(u64 elem) {
+ ASSERT(elem <= 3);
+ std::string swizzle = ".";
+ swizzle += "xyzw"[elem];
+ return swizzle;
+}
+
/// Translate topology
static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
switch (topology) {
@@ -356,6 +364,7 @@ public:
return value;
default:
UNREACHABLE_MSG("Unimplemented conversion size: {}", static_cast<u32>(size));
+ return value;
}
}
@@ -618,6 +627,7 @@ public:
return "floatBitsToInt(" + value + ')';
} else {
UNREACHABLE();
+ return value;
}
}
@@ -920,7 +930,7 @@ private:
case Attribute::Index::FrontFacing:
// TODO(Subv): Find out what the values are for the other elements.
ASSERT(stage == Maxwell3D::Regs::ShaderStage::Fragment);
- return "vec4(0, 0, 0, uintBitsToFloat(gl_FrontFacing ? 1 : 0))";
+ return "vec4(0, 0, 0, intBitsToFloat(gl_FrontFacing ? -1 : 0))";
default:
const u32 index{static_cast<u32>(attribute) -
static_cast<u32>(Attribute::Index::Attribute_0)};
@@ -1004,14 +1014,6 @@ private:
}
}
- /// Generates code to use for a swizzle operation.
- static std::string GetSwizzle(u64 elem) {
- ASSERT(elem <= 3);
- std::string swizzle = ".";
- swizzle += "xyzw"[elem];
- return swizzle;
- }
-
ShaderWriter& shader;
ShaderWriter& declarations;
std::vector<GLSLRegister> regs;
@@ -1343,7 +1345,7 @@ private:
regs.SetRegisterToInteger(dest, true, 0, result, 1, 1);
}
- void WriteTexsInstruction(const Instruction& instr, const std::string& texture) {
+ void WriteTexsInstructionFloat(const Instruction& instr, const std::string& texture) {
// 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
@@ -1368,6 +1370,38 @@ private:
}
}
+ void WriteTexsInstructionHalfFloat(const Instruction& instr, const std::string& texture) {
+ // TEXS.F16 destionation registers are packed in two registers in pairs (just like any half
+ // float instruction).
+
+ std::array<std::string, 4> components;
+ u32 written_components = 0;
+
+ for (u32 component = 0; component < 4; ++component) {
+ if (!instr.texs.IsComponentEnabled(component))
+ continue;
+ components[written_components++] = texture + GetSwizzle(component);
+ }
+ if (written_components == 0)
+ return;
+
+ const auto BuildComponent = [&](std::string low, std::string high, bool high_enabled) {
+ return "vec2(" + low + ", " + (high_enabled ? high : "0") + ')';
+ };
+
+ regs.SetRegisterToHalfFloat(
+ instr.gpr0, 0, BuildComponent(components[0], components[1], written_components > 1),
+ Tegra::Shader::HalfMerge::H0_H1, 1, 1);
+
+ if (written_components > 2) {
+ ASSERT(instr.texs.HasTwoDestinations());
+ regs.SetRegisterToHalfFloat(
+ instr.gpr28, 0,
+ BuildComponent(components[2], components[3], written_components > 3),
+ Tegra::Shader::HalfMerge::H0_H1, 1, 1);
+ }
+ }
+
static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) {
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D:
@@ -1515,6 +1549,252 @@ private:
}
}
+ std::pair<size_t, std::string> ValidateAndGetCoordinateElement(
+ const Tegra::Shader::TextureType texture_type, const bool depth_compare,
+ const bool is_array, const bool lod_bias_enabled, size_t max_coords, size_t max_inputs) {
+ const size_t coord_count = TextureCoordinates(texture_type);
+
+ size_t total_coord_count = coord_count + (is_array ? 1 : 0) + (depth_compare ? 1 : 0);
+ const size_t total_reg_count = total_coord_count + (lod_bias_enabled ? 1 : 0);
+ if (total_coord_count > max_coords || total_reg_count > max_inputs) {
+ UNIMPLEMENTED_MSG("Unsupported Texture operation");
+ total_coord_count = std::min(total_coord_count, max_coords);
+ }
+ // 1D.DC opengl is using a vec3 but 2nd component is ignored later.
+ total_coord_count +=
+ (depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D)
+ ? 1
+ : 0;
+
+ constexpr std::array<const char*, 5> coord_container{
+ {"", "float coord = (", "vec2 coord = vec2(", "vec3 coord = vec3(",
+ "vec4 coord = vec4("}};
+
+ return std::pair<size_t, std::string>(coord_count, coord_container[total_coord_count]);
+ }
+
+ std::string GetTextureCode(const Tegra::Shader::Instruction& instr,
+ const Tegra::Shader::TextureType texture_type,
+ const Tegra::Shader::TextureProcessMode process_mode,
+ const bool depth_compare, const bool is_array,
+ const size_t bias_offset) {
+
+ if ((texture_type == Tegra::Shader::TextureType::Texture3D &&
+ (is_array || depth_compare)) ||
+ (texture_type == Tegra::Shader::TextureType::TextureCube && is_array &&
+ depth_compare)) {
+ UNIMPLEMENTED_MSG("This method is not supported.");
+ }
+
+ const std::string sampler =
+ GetSampler(instr.sampler, texture_type, is_array, depth_compare);
+
+ const bool lod_needed = process_mode == Tegra::Shader::TextureProcessMode::LZ ||
+ process_mode == Tegra::Shader::TextureProcessMode::LL ||
+ process_mode == Tegra::Shader::TextureProcessMode::LLA;
+
+ // LOD selection (either via bias or explicit textureLod) not supported in GL for
+ // sampler2DArrayShadow and samplerCubeArrayShadow.
+ const bool gl_lod_supported = !(
+ (texture_type == Tegra::Shader::TextureType::Texture2D && is_array && depth_compare) ||
+ (texture_type == Tegra::Shader::TextureType::TextureCube && is_array && depth_compare));
+
+ const std::string read_method = lod_needed && gl_lod_supported ? "textureLod(" : "texture(";
+ std::string texture = read_method + sampler + ", coord";
+
+ UNIMPLEMENTED_IF(process_mode != Tegra::Shader::TextureProcessMode::None &&
+ !gl_lod_supported);
+
+ if (process_mode != Tegra::Shader::TextureProcessMode::None && gl_lod_supported) {
+ if (process_mode == Tegra::Shader::TextureProcessMode::LZ) {
+ texture += ", 0.0";
+ } else {
+ // If present, lod or bias are always stored in the register indexed by the
+ // gpr20
+ // field with an offset depending on the usage of the other registers
+ texture += ',' + regs.GetRegisterAsFloat(instr.gpr20.Value() + bias_offset);
+ }
+ }
+ texture += ")";
+ return texture;
+ }
+
+ std::pair<std::string, std::string> GetTEXCode(
+ const Instruction& instr, const Tegra::Shader::TextureType texture_type,
+ const Tegra::Shader::TextureProcessMode process_mode, const bool depth_compare,
+ const bool is_array) {
+ const bool lod_bias_enabled = (process_mode != Tegra::Shader::TextureProcessMode::None &&
+ process_mode != Tegra::Shader::TextureProcessMode::LZ);
+
+ const auto [coord_count, coord_dcl] = ValidateAndGetCoordinateElement(
+ texture_type, depth_compare, is_array, lod_bias_enabled, 4, 5);
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+ // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
+ const u64 coord_register = array_register + (is_array ? 1 : 0);
+
+ std::string coord = coord_dcl;
+ for (size_t i = 0; i < coord_count;) {
+ coord += regs.GetRegisterAsFloat(coord_register + i);
+ ++i;
+ if (i != coord_count) {
+ coord += ',';
+ }
+ }
+ // 1D.DC in opengl the 2nd component is ignored.
+ if (depth_compare && !is_array && texture_type == Tegra::Shader::TextureType::Texture1D) {
+ coord += ",0.0";
+ }
+ if (is_array) {
+ coord += ',' + regs.GetRegisterAsInteger(array_register);
+ }
+ if (depth_compare) {
+ // Depth is always stored in the register signaled by gpr20
+ // or in the next register if lod or bias are used
+ const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
+ coord += ',' + regs.GetRegisterAsFloat(depth_register);
+ }
+ coord += ");";
+ return std::make_pair(
+ coord, GetTextureCode(instr, texture_type, process_mode, depth_compare, is_array, 0));
+ }
+
+ std::pair<std::string, std::string> GetTEXSCode(
+ const Instruction& instr, const Tegra::Shader::TextureType texture_type,
+ const Tegra::Shader::TextureProcessMode process_mode, const bool depth_compare,
+ const bool is_array) {
+ const bool lod_bias_enabled = (process_mode != Tegra::Shader::TextureProcessMode::None &&
+ process_mode != Tegra::Shader::TextureProcessMode::LZ);
+
+ const auto [coord_count, coord_dcl] = ValidateAndGetCoordinateElement(
+ texture_type, depth_compare, is_array, lod_bias_enabled, 4, 4);
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+ // First coordinate index is stored in gpr8 field or (gpr8 + 1) when arrays are used
+ const u64 coord_register = array_register + (is_array ? 1 : 0);
+ const u64 last_coord_register =
+ (is_array || !(lod_bias_enabled || depth_compare) || (coord_count > 2))
+ ? static_cast<u64>(instr.gpr20.Value())
+ : coord_register + 1;
+
+ std::string coord = coord_dcl;
+ for (size_t i = 0; i < coord_count; ++i) {
+ const bool last = (i == (coord_count - 1)) && (coord_count > 1);
+ coord += regs.GetRegisterAsFloat(last ? last_coord_register : coord_register + i);
+ if (i < coord_count - 1) {
+ coord += ',';
+ }
+ }
+
+ if (is_array) {
+ coord += ',' + regs.GetRegisterAsInteger(array_register);
+ }
+ if (depth_compare) {
+ // Depth is always stored in the register signaled by gpr20
+ // or in the next register if lod or bias are used
+ const u64 depth_register = instr.gpr20.Value() + (lod_bias_enabled ? 1 : 0);
+ coord += ',' + regs.GetRegisterAsFloat(depth_register);
+ }
+ coord += ");";
+
+ return std::make_pair(coord,
+ GetTextureCode(instr, texture_type, process_mode, depth_compare,
+ is_array, (coord_count > 2 ? 1 : 0)));
+ }
+
+ std::pair<std::string, std::string> GetTLD4Code(const Instruction& instr,
+ const Tegra::Shader::TextureType texture_type,
+ const bool depth_compare, const bool is_array) {
+
+ const size_t coord_count = TextureCoordinates(texture_type);
+ const size_t total_coord_count = coord_count + (is_array ? 1 : 0);
+ const size_t total_reg_count = total_coord_count + (depth_compare ? 1 : 0);
+
+ constexpr std::array<const char*, 5> coord_container{
+ {"", "", "vec2 coord = vec2(", "vec3 coord = vec3(", "vec4 coord = vec4("}};
+
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+ // First coordinate index is the gpr8 or gpr8 + 1 when arrays are used
+ const u64 coord_register = array_register + (is_array ? 1 : 0);
+
+ std::string coord = coord_container[total_coord_count];
+ for (size_t i = 0; i < coord_count;) {
+ coord += regs.GetRegisterAsFloat(coord_register + i);
+ ++i;
+ if (i != coord_count) {
+ coord += ',';
+ }
+ }
+
+ if (is_array) {
+ coord += ',' + regs.GetRegisterAsInteger(array_register);
+ }
+ coord += ");";
+
+ const std::string sampler =
+ GetSampler(instr.sampler, texture_type, is_array, depth_compare);
+
+ std::string texture = "textureGather(" + sampler + ", coord, ";
+ if (depth_compare) {
+ // Depth is always stored in the register signaled by gpr20
+ texture += regs.GetRegisterAsFloat(instr.gpr20.Value()) + ')';
+ } else {
+ texture += std::to_string(instr.tld4.component) + ')';
+ }
+ return std::make_pair(coord, texture);
+ }
+
+ std::pair<std::string, std::string> GetTLDSCode(const Instruction& instr,
+ const Tegra::Shader::TextureType texture_type,
+ const bool is_array) {
+
+ const size_t coord_count = TextureCoordinates(texture_type);
+ const size_t total_coord_count = coord_count + (is_array ? 1 : 0);
+ const bool lod_enabled =
+ instr.tlds.GetTextureProcessMode() == Tegra::Shader::TextureProcessMode::LL;
+
+ constexpr std::array<const char*, 4> coord_container{
+ {"", "int coord = (", "ivec2 coord = ivec2(", "ivec3 coord = ivec3("}};
+
+ std::string coord = coord_container[total_coord_count];
+
+ // If enabled arrays index is always stored in the gpr8 field
+ const u64 array_register = instr.gpr8.Value();
+
+ // if is array gpr20 is used
+ const u64 coord_register = is_array ? instr.gpr20.Value() : instr.gpr8.Value();
+
+ const u64 last_coord_register =
+ ((coord_count > 2) || (coord_count == 2 && !lod_enabled)) && !is_array
+ ? static_cast<u64>(instr.gpr20.Value())
+ : coord_register + 1;
+
+ for (size_t i = 0; i < coord_count; ++i) {
+ const bool last = (i == (coord_count - 1)) && (coord_count > 1);
+ coord += regs.GetRegisterAsInteger(last ? last_coord_register : coord_register + i);
+ if (i < coord_count - 1) {
+ coord += ',';
+ }
+ }
+ if (is_array) {
+ coord += ',' + regs.GetRegisterAsInteger(array_register);
+ }
+ coord += ");";
+
+ const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false);
+
+ std::string texture = "texelFetch(" + sampler + ", coords";
+
+ if (lod_enabled) {
+ // When lod is used always is in grp20
+ texture += ", " + regs.GetRegisterAsInteger(instr.gpr20) + ')';
+ } else {
+ texture += ", 0)";
+ }
+ return std::make_pair(coord, texture);
+ }
+
/**
* Compiles a single instruction from Tegra to GLSL.
* @param offset the offset of the Tegra shader instruction.
@@ -1587,9 +1867,6 @@ private:
UNIMPLEMENTED_IF_MSG(instr.fmul.tab5cb8_2 != 0,
"FMUL tab5cb8_2({}) is not implemented",
instr.fmul.tab5cb8_2.Value());
- UNIMPLEMENTED_IF_MSG(instr.fmul.tab5c68_1 != 0,
- "FMUL tab5cb8_1({}) is not implemented",
- instr.fmul.tab5c68_1.Value());
UNIMPLEMENTED_IF_MSG(
instr.fmul.tab5c68_0 != 1, "FMUL tab5cb8_0({}) is not implemented",
instr.fmul.tab5c68_0
@@ -1599,7 +1876,26 @@ private:
op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b);
- regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
+ std::string postfactor_op;
+ if (instr.fmul.postfactor != 0) {
+ s8 postfactor = static_cast<s8>(instr.fmul.postfactor);
+
+ // postfactor encoded as 3-bit 1's complement in instruction,
+ // interpreted with below logic.
+ if (postfactor >= 4) {
+ postfactor = 7 - postfactor;
+ } else {
+ postfactor = 0 - postfactor;
+ }
+
+ if (postfactor > 0) {
+ postfactor_op = " * " + std::to_string(1 << postfactor);
+ } else {
+ postfactor_op = " / " + std::to_string(1 << -postfactor);
+ }
+ }
+
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + postfactor_op, 1, 1,
instr.alu.saturate_d, 0, true);
break;
}
@@ -1768,6 +2064,8 @@ private:
std::to_string(instr.alu.GetSignedImm20_20())};
default:
UNREACHABLE();
+ return {regs.GetRegisterAsInteger(instr.gpr39, 0, false),
+ std::to_string(instr.alu.GetSignedImm20_20())};
}
}();
const std::string offset = '(' + packed_shift + " & 0xff)";
@@ -2574,168 +2872,32 @@ private:
case OpCode::Id::TEX: {
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
const bool is_array = instr.tex.array != 0;
-
+ const bool depth_compare =
+ instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
+ const auto process_mode = instr.tex.GetTextureProcessMode();
UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
"AOFFI is not implemented");
- const bool depth_compare =
- instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
- u32 num_coordinates = TextureCoordinates(texture_type);
- u32 start_index = 0;
- std::string array_elem;
- if (is_array) {
- array_elem = regs.GetRegisterAsInteger(instr.gpr8);
- start_index = 1;
- }
- const auto process_mode = instr.tex.GetTextureProcessMode();
- u32 start_index_b = 0;
- std::string lod_value;
- if (process_mode != Tegra::Shader::TextureProcessMode::LZ &&
- process_mode != Tegra::Shader::TextureProcessMode::None) {
- start_index_b = 1;
- lod_value = regs.GetRegisterAsFloat(instr.gpr20);
- }
-
- std::string depth_value;
- if (depth_compare) {
- depth_value = regs.GetRegisterAsFloat(instr.gpr20.Value() + start_index_b);
- }
-
- bool depth_compare_extra = false;
+ const auto [coord, texture] =
+ GetTEXCode(instr, texture_type, process_mode, depth_compare, is_array);
const auto scope = shader.Scope();
-
- switch (num_coordinates) {
- case 1: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index);
- if (is_array) {
- if (depth_compare) {
- shader.AddLine("vec3 coords = vec3(" + x + ", " + depth_value + ", " +
- array_elem + ");");
- } else {
- shader.AddLine("vec2 coords = vec2(" + x + ", " + array_elem + ");");
- }
- } else {
- if (depth_compare) {
- shader.AddLine("vec2 coords = vec2(" + x + ", " + depth_value + ");");
- } else {
- shader.AddLine("float coords = " + x + ';');
- }
- }
- break;
- }
- case 2: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index);
- const std::string y =
- regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index + 1);
- if (is_array) {
- if (depth_compare) {
- shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " +
- depth_value + ", " + array_elem + ");");
- } else {
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " +
- array_elem + ");");
- }
- } else {
- if (depth_compare) {
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " +
- depth_value + ");");
- } else {
- shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
- }
- }
- break;
- }
- case 3: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index);
- const std::string y =
- regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index + 1);
- const std::string z =
- regs.GetRegisterAsFloat(instr.gpr8.Value() + start_index + 2);
- if (is_array) {
- depth_compare_extra = depth_compare;
- shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " +
- array_elem + ");");
- } else {
- if (depth_compare) {
- shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " +
- depth_value + ");");
- } else {
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");");
- }
- }
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unhandled coordinates number {}",
- static_cast<u32>(num_coordinates));
-
- // 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);
- shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
- texture_type = Tegra::Shader::TextureType::Texture2D;
- }
-
- const std::string sampler =
- GetSampler(instr.sampler, texture_type, is_array, depth_compare);
- // 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.
-
- const std::string texture = [&]() {
- switch (instr.tex.GetTextureProcessMode()) {
- case Tegra::Shader::TextureProcessMode::None:
- if (depth_compare_extra) {
- return "texture(" + sampler + ", coords, " + depth_value + ')';
- }
- return "texture(" + sampler + ", coords)";
- case Tegra::Shader::TextureProcessMode::LZ:
- if (depth_compare_extra) {
- return "texture(" + sampler + ", coords, " + depth_value + ')';
- }
- return "textureLod(" + sampler + ", coords, 0.0)";
- case Tegra::Shader::TextureProcessMode::LB:
- case Tegra::Shader::TextureProcessMode::LBA:
- // TODO: Figure if A suffix changes the equation at all.
- if (depth_compare_extra) {
- LOG_WARNING(
- HW_GPU,
- "OpenGL Limitation: can't set bias value along depth compare");
- return "texture(" + sampler + ", coords, " + depth_value + ')';
- }
- return "texture(" + sampler + ", coords, " + lod_value + ')';
- case Tegra::Shader::TextureProcessMode::LL:
- case Tegra::Shader::TextureProcessMode::LLA:
- // TODO: Figure if A suffix changes the equation at all.
- if (depth_compare_extra) {
- LOG_WARNING(
- HW_GPU,
- "OpenGL Limitation: can't set lod value along depth compare");
- return "texture(" + sampler + ", coords, " + depth_value + ')';
- }
- return "textureLod(" + sampler + ", coords, " + lod_value + ')';
- default:
- UNIMPLEMENTED_MSG("Unhandled texture process mode {}",
- static_cast<u32>(instr.tex.GetTextureProcessMode()));
- if (depth_compare_extra) {
- return "texture(" + sampler + ", coords, " + depth_value + ')';
- }
- return "texture(" + sampler + ", coords)";
- }
- }();
+ shader.AddLine(coord);
if (depth_compare) {
regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
} else {
+ shader.AddLine("vec4 texture_tmp = " + texture + ';');
std::size_t dest_elem{};
for (std::size_t elem = 0; elem < 4; ++elem) {
if (!instr.tex.IsComponentEnabled(elem)) {
// Skip disabled components
continue;
}
- regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
+ regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false,
+ dest_elem);
++dest_elem;
}
}
@@ -2743,138 +2905,37 @@ private:
}
case OpCode::Id::TEXS: {
Tegra::Shader::TextureType texture_type{instr.texs.GetTextureType()};
- bool is_array{instr.texs.IsArrayTexture()};
+ const bool is_array{instr.texs.IsArrayTexture()};
+ const bool depth_compare =
+ instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
+ const auto process_mode = instr.texs.GetTextureProcessMode();
UNIMPLEMENTED_IF_MSG(instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
const auto scope = shader.Scope();
- const bool depth_compare =
- instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
- u32 num_coordinates = TextureCoordinates(texture_type);
- const auto process_mode = instr.texs.GetTextureProcessMode();
- u32 lod_offset = 0;
- if (process_mode == Tegra::Shader::TextureProcessMode::LL) {
- if (num_coordinates > 2) {
- shader.AddLine("float lod_value = " +
- regs.GetRegisterAsFloat(instr.gpr20.Value() + 1) + ';');
- lod_offset = 2;
- } else {
- shader.AddLine("float lod_value = " + regs.GetRegisterAsFloat(instr.gpr20) +
- ';');
- lod_offset = 1;
- }
- }
+ auto [coord, texture] =
+ GetTEXSCode(instr, texture_type, process_mode, depth_compare, is_array);
- switch (num_coordinates) {
- case 1: {
- shader.AddLine("float coords = " + regs.GetRegisterAsFloat(instr.gpr8) + ';');
- break;
- }
- case 2: {
- if (is_array) {
- if (depth_compare) {
- 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);
- const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
- shader.AddLine("vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " +
- index + ");");
- } else {
- 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);
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + index +
- ");");
- }
- } else {
- if (lod_offset != 0) {
- if (depth_compare) {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y =
- regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- const std::string z =
- regs.GetRegisterAsFloat(instr.gpr20.Value() + lod_offset);
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z +
- ");");
- } else {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y =
- regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
- }
- } else {
- if (depth_compare) {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y =
- regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z +
- ");");
- } else {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
- shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
- }
- }
- }
- break;
- }
- case 3: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");");
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unhandled coordinates number {}",
- static_cast<u32>(num_coordinates));
+ shader.AddLine(coord);
- // 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);
- shader.AddLine("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, depth_compare);
-
- std::string texture = [&]() {
- switch (process_mode) {
- case Tegra::Shader::TextureProcessMode::None:
- return "texture(" + sampler + ", coords)";
- case Tegra::Shader::TextureProcessMode::LZ:
- if (depth_compare && is_array) {
- return "texture(" + sampler + ", coords)";
- } else {
- return "textureLod(" + sampler + ", coords, 0.0)";
- }
- break;
- case Tegra::Shader::TextureProcessMode::LL:
- return "textureLod(" + sampler + ", coords, lod_value)";
- default:
- UNIMPLEMENTED_MSG("Unhandled texture process mode {}",
- static_cast<u32>(instr.texs.GetTextureProcessMode()));
- return "texture(" + sampler + ", coords)";
- }
- }();
if (depth_compare) {
texture = "vec4(" + texture + ')';
}
+ shader.AddLine("vec4 texture_tmp = " + texture + ';');
- WriteTexsInstruction(instr, texture);
+ if (instr.texs.fp32_flag) {
+ WriteTexsInstructionFloat(instr, "texture_tmp");
+ } else {
+ WriteTexsInstructionHalfFloat(instr, "texture_tmp");
+ }
break;
}
case OpCode::Id::TLDS: {
const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()};
const bool is_array{instr.tlds.IsArrayTexture()};
- ASSERT(texture_type == Tegra::Shader::TextureType::Texture2D);
- ASSERT(is_array == false);
-
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
@@ -2882,54 +2943,16 @@ private:
UNIMPLEMENTED_IF_MSG(instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ),
"MZ is not implemented");
- u32 extra_op_offset = 0;
-
- ShaderScopedScope scope = shader.Scope();
-
- switch (texture_type) {
- case Tegra::Shader::TextureType::Texture1D: {
- const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
- shader.AddLine("float coords = " + x + ';');
- break;
- }
- case Tegra::Shader::TextureType::Texture2D: {
- UNIMPLEMENTED_IF_MSG(is_array, "Unhandled 2d array texture");
-
- const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
- const std::string y = regs.GetRegisterAsInteger(instr.gpr20);
- // shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");");
- shader.AddLine("ivec2 coords = ivec2(" + x + ", " + y + ");");
- extra_op_offset = 1;
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unhandled texture type {}", static_cast<u32>(texture_type));
- }
- const std::string sampler =
- GetSampler(instr.sampler, texture_type, is_array, false);
+ const auto [coord, texture] = GetTLDSCode(instr, texture_type, is_array);
- const std::string texture = [&]() {
- switch (instr.tlds.GetTextureProcessMode()) {
- case Tegra::Shader::TextureProcessMode::LZ:
- return "texelFetch(" + sampler + ", coords, 0)";
- case Tegra::Shader::TextureProcessMode::LL:
- shader.AddLine(
- "float lod = " +
- regs.GetRegisterAsInteger(instr.gpr20.Value() + extra_op_offset) + ';');
- return "texelFetch(" + sampler + ", coords, lod)";
- default:
- UNIMPLEMENTED_MSG("Unhandled texture process mode {}",
- static_cast<u32>(instr.tlds.GetTextureProcessMode()));
- return "texelFetch(" + sampler + ", coords, 0)";
- }
- }();
+ const auto scope = shader.Scope();
- WriteTexsInstruction(instr, texture);
+ shader.AddLine(coord);
+ shader.AddLine("vec4 texture_tmp = " + texture + ';');
+ WriteTexsInstructionFloat(instr, "texture_tmp");
break;
}
case OpCode::Id::TLD4: {
- ASSERT(instr.tld4.texture_type == Tegra::Shader::TextureType::Texture2D);
- ASSERT(instr.tld4.array == 0);
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
@@ -2939,56 +2962,29 @@ private:
"NDV is not implemented");
UNIMPLEMENTED_IF_MSG(instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP),
"PTP is not implemented");
+
+ auto texture_type = instr.tld4.texture_type.Value();
const bool depth_compare =
instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
- auto texture_type = instr.tld4.texture_type.Value();
- u32 num_coordinates = TextureCoordinates(texture_type);
- if (depth_compare)
- num_coordinates += 1;
+ const bool is_array = instr.tld4.array != 0;
- const auto scope = shader.Scope();
-
- switch (num_coordinates) {
- case 2: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
- break;
- }
- case 3: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
- shader.AddLine("vec3 coords = vec3(" + x + ", " + y + ", " + z + ");");
- break;
- }
- default:
- UNIMPLEMENTED_MSG("Unhandled coordinates number {}",
- static_cast<u32>(num_coordinates));
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- shader.AddLine("vec2 coords = vec2(" + x + ", " + y + ");");
- texture_type = Tegra::Shader::TextureType::Texture2D;
- }
+ const auto [coord, texture] =
+ GetTLD4Code(instr, texture_type, depth_compare, is_array);
- const std::string sampler =
- GetSampler(instr.sampler, texture_type, false, depth_compare);
+ const auto scope = shader.Scope();
- const std::string texture = "textureGather(" + sampler + ", coords, " +
- std::to_string(instr.tld4.component) + ')';
+ shader.AddLine(coord);
+ std::size_t dest_elem{};
- if (depth_compare) {
- regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
- } else {
- std::size_t dest_elem{};
- for (std::size_t elem = 0; elem < 4; ++elem) {
- if (!instr.tex.IsComponentEnabled(elem)) {
- // Skip disabled components
- continue;
- }
- regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
- ++dest_elem;
+ shader.AddLine("vec4 texture_tmp = " + texture + ';');
+ for (std::size_t elem = 0; elem < 4; ++elem) {
+ if (!instr.tex.IsComponentEnabled(elem)) {
+ // Skip disabled components
+ continue;
}
+ regs.SetRegisterToFloat(instr.gpr0, elem, "texture_tmp", 1, 4, false,
+ dest_elem);
+ ++dest_elem;
}
break;
}
@@ -3002,27 +2998,31 @@ private:
const auto scope = shader.Scope();
+ std::string coords;
+
const bool depth_compare =
instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
- 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, Tegra::Shader::TextureType::Texture2D, false, depth_compare);
- if (depth_compare) {
- // Note: TLD4S coordinate encoding works just like TEXS's
- const std::string op_y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- shader.AddLine("vec3 coords = vec3(" + op_a + ", " + op_y + ", " + op_b + ");");
- } else {
- shader.AddLine("vec2 coords = vec2(" + op_a + ", " + op_b + ");");
- }
- std::string texture = "textureGather(" + sampler + ", coords, " +
- std::to_string(instr.tld4s.component) + ')';
- if (depth_compare) {
- texture = "vec4(" + texture + ')';
+ const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
+ coords = "vec2 coords = vec2(" + op_a + ", ";
+ std::string texture = "textureGather(" + sampler + ", coords, ";
+
+ if (!depth_compare) {
+ const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
+ coords += op_b + ");";
+ texture += std::to_string(instr.tld4s.component) + ')';
+ } else {
+ const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20);
+ coords += op_b + ");";
+ texture += op_c + ')';
}
- WriteTexsInstruction(instr, texture);
+ shader.AddLine(coords);
+ shader.AddLine("vec4 texture_tmp = " + texture + ';');
+ WriteTexsInstructionFloat(instr, "texture_tmp");
break;
}
case OpCode::Id::TXQ: {
@@ -3316,6 +3316,7 @@ private:
return std::to_string(instr.r2p.immediate_mask);
default:
UNREACHABLE();
+ return std::to_string(instr.r2p.immediate_mask);
}
}();
const std::string mask = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) +
@@ -3779,7 +3780,10 @@ private:
}
break;
}
- default: { UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName()); }
+ default: {
+ UNIMPLEMENTED_MSG("Unhandled instruction: {}", opcode->get().GetName());
+ break;
+ }
}
break;
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 23ed91e27..5d0819dc5 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <fmt/format.h>
#include "common/assert.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
@@ -16,6 +17,8 @@ static constexpr u32 PROGRAM_OFFSET{10};
ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
+ const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
+ out += "// Shader Unique Id: VS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += R"(
@@ -84,6 +87,8 @@ void main() {
ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
// Version is intentionally skipped in shader generation, it's added by the lazy compilation.
std::string out = "#extension GL_ARB_separate_shader_objects : enable\n\n";
+ const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
+ out += "// Shader Unique Id: GS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_geometry();\n";
@@ -117,6 +122,8 @@ void main() {
ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
+ const std::string id = fmt::format("{:016x}", setup.program.unique_identifier);
+ out += "// Shader Unique Id: FS" + id + "\n\n";
out += Decompiler::GetCommonDeclarations();
out += "bool exec_fragment();\n";
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index 4fa6d7612..fcc20d3b4 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -177,6 +177,9 @@ struct ShaderSetup {
struct {
ProgramCode code;
ProgramCode code_b; // Used for dual vertex shaders
+ u64 unique_identifier;
+ std::size_t real_size;
+ std::size_t real_size_b;
} program;
/// Used in scenarios where we have a dual vertex shaders
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 4fd0d66c5..f02415139 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -427,6 +427,7 @@ static const char* GetSource(GLenum source) {
RET(OTHER);
default:
UNREACHABLE();
+ return "Unknown source";
}
#undef RET
}
@@ -445,6 +446,7 @@ static const char* GetType(GLenum type) {
RET(MARKER);
default:
UNREACHABLE();
+ return "Unknown type";
}
#undef RET
}
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 9582dd2ca..a97b1562b 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -65,6 +65,7 @@ PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
+ return PixelFormat::S8Z24;
}
}
@@ -141,6 +142,7 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format)
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
+ return PixelFormat::RGBA8_SRGB;
}
}
@@ -327,6 +329,7 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format),
static_cast<u32>(component_type));
UNREACHABLE();
+ return PixelFormat::ABGR8U;
}
}
@@ -346,6 +349,7 @@ ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type));
UNREACHABLE();
+ return ComponentType::UNorm;
}
}
@@ -393,6 +397,7 @@ ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
+ return ComponentType::UNorm;
}
}
@@ -403,6 +408,7 @@ PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
+ return PixelFormat::ABGR8U;
}
}
@@ -418,6 +424,7 @@ ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format) {
default:
LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
UNREACHABLE();
+ return ComponentType::UNorm;
}
}
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 0dd3eb2e4..e23cfecbc 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -125,6 +125,75 @@ enum class SurfaceTarget {
TextureCubeArray,
};
+constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
+ 1, // ABGR8U
+ 1, // ABGR8S
+ 1, // ABGR8UI
+ 1, // B5G6R5U
+ 1, // A2B10G10R10U
+ 1, // A1B5G5R5U
+ 1, // R8U
+ 1, // R8UI
+ 1, // RGBA16F
+ 1, // RGBA16U
+ 1, // RGBA16UI
+ 1, // R11FG11FB10F
+ 1, // RGBA32UI
+ 4, // DXT1
+ 4, // DXT23
+ 4, // DXT45
+ 4, // DXN1
+ 4, // DXN2UNORM
+ 4, // DXN2SNORM
+ 4, // BC7U
+ 4, // BC6H_UF16
+ 4, // BC6H_SF16
+ 4, // ASTC_2D_4X4
+ 1, // G8R8U
+ 1, // G8R8S
+ 1, // BGRA8
+ 1, // RGBA32F
+ 1, // RG32F
+ 1, // R32F
+ 1, // R16F
+ 1, // R16U
+ 1, // R16S
+ 1, // R16UI
+ 1, // R16I
+ 1, // RG16
+ 1, // RG16F
+ 1, // RG16UI
+ 1, // RG16I
+ 1, // RG16S
+ 1, // RGB32F
+ 1, // RGBA8_SRGB
+ 1, // RG8U
+ 1, // RG8S
+ 1, // RG32UI
+ 1, // R32UI
+ 4, // ASTC_2D_8X8
+ 4, // ASTC_2D_8X5
+ 4, // ASTC_2D_5X4
+ 1, // BGRA8_SRGB
+ 4, // DXT1_SRGB
+ 4, // DXT23_SRGB
+ 4, // DXT45_SRGB
+ 4, // BC7U_SRGB
+ 4, // ASTC_2D_4X4_SRGB
+ 4, // ASTC_2D_8X8_SRGB
+ 4, // ASTC_2D_8X5_SRGB
+ 4, // ASTC_2D_5X4_SRGB
+ 4, // ASTC_2D_5X5
+ 4, // ASTC_2D_5X5_SRGB
+ 4, // ASTC_2D_10X8
+ 4, // ASTC_2D_10X8_SRGB
+ 1, // Z32F
+ 1, // Z16
+ 1, // Z24S8
+ 1, // S8Z24
+ 1, // Z32FS8
+}};
+
/**
* 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
@@ -135,304 +204,237 @@ static constexpr u32 GetCompressionFactor(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
- constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
- 1, // ABGR8U
- 1, // ABGR8S
- 1, // ABGR8UI
- 1, // B5G6R5U
- 1, // A2B10G10R10U
- 1, // A1B5G5R5U
- 1, // R8U
- 1, // R8UI
- 1, // RGBA16F
- 1, // RGBA16U
- 1, // RGBA16UI
- 1, // R11FG11FB10F
- 1, // RGBA32UI
- 4, // DXT1
- 4, // DXT23
- 4, // DXT45
- 4, // DXN1
- 4, // DXN2UNORM
- 4, // DXN2SNORM
- 4, // BC7U
- 4, // BC6H_UF16
- 4, // BC6H_SF16
- 4, // ASTC_2D_4X4
- 1, // G8R8U
- 1, // G8R8S
- 1, // BGRA8
- 1, // RGBA32F
- 1, // RG32F
- 1, // R32F
- 1, // R16F
- 1, // R16U
- 1, // R16S
- 1, // R16UI
- 1, // R16I
- 1, // RG16
- 1, // RG16F
- 1, // RG16UI
- 1, // RG16I
- 1, // RG16S
- 1, // RGB32F
- 1, // RGBA8_SRGB
- 1, // RG8U
- 1, // RG8S
- 1, // RG32UI
- 1, // R32UI
- 4, // ASTC_2D_8X8
- 4, // ASTC_2D_8X5
- 4, // ASTC_2D_5X4
- 1, // BGRA8_SRGB
- 4, // DXT1_SRGB
- 4, // DXT23_SRGB
- 4, // DXT45_SRGB
- 4, // BC7U_SRGB
- 4, // ASTC_2D_4X4_SRGB
- 4, // ASTC_2D_8X8_SRGB
- 4, // ASTC_2D_8X5_SRGB
- 4, // ASTC_2D_5X4_SRGB
- 4, // ASTC_2D_5X5
- 4, // ASTC_2D_5X5_SRGB
- 4, // ASTC_2D_10X8
- 4, // ASTC_2D_10X8_SRGB
- 1, // Z32F
- 1, // Z16
- 1, // Z24S8
- 1, // S8Z24
- 1, // Z32FS8
- }};
-
ASSERT(static_cast<std::size_t>(format) < compression_factor_table.size());
return compression_factor_table[static_cast<std::size_t>(format)];
}
+constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
+ 1, // ABGR8U
+ 1, // ABGR8S
+ 1, // ABGR8UI
+ 1, // B5G6R5U
+ 1, // A2B10G10R10U
+ 1, // A1B5G5R5U
+ 1, // R8U
+ 1, // R8UI
+ 1, // RGBA16F
+ 1, // RGBA16U
+ 1, // RGBA16UI
+ 1, // R11FG11FB10F
+ 1, // RGBA32UI
+ 4, // DXT1
+ 4, // DXT23
+ 4, // DXT45
+ 4, // DXN1
+ 4, // DXN2UNORM
+ 4, // DXN2SNORM
+ 4, // BC7U
+ 4, // BC6H_UF16
+ 4, // BC6H_SF16
+ 4, // ASTC_2D_4X4
+ 1, // G8R8U
+ 1, // G8R8S
+ 1, // BGRA8
+ 1, // RGBA32F
+ 1, // RG32F
+ 1, // R32F
+ 1, // R16F
+ 1, // R16U
+ 1, // R16S
+ 1, // R16UI
+ 1, // R16I
+ 1, // RG16
+ 1, // RG16F
+ 1, // RG16UI
+ 1, // RG16I
+ 1, // RG16S
+ 1, // RGB32F
+ 1, // RGBA8_SRGB
+ 1, // RG8U
+ 1, // RG8S
+ 1, // RG32UI
+ 1, // R32UI
+ 8, // ASTC_2D_8X8
+ 8, // ASTC_2D_8X5
+ 5, // ASTC_2D_5X4
+ 1, // BGRA8_SRGB
+ 4, // DXT1_SRGB
+ 4, // DXT23_SRGB
+ 4, // DXT45_SRGB
+ 4, // BC7U_SRGB
+ 4, // ASTC_2D_4X4_SRGB
+ 8, // ASTC_2D_8X8_SRGB
+ 8, // ASTC_2D_8X5_SRGB
+ 5, // ASTC_2D_5X4_SRGB
+ 5, // ASTC_2D_5X5
+ 5, // ASTC_2D_5X5_SRGB
+ 10, // ASTC_2D_10X8
+ 10, // ASTC_2D_10X8_SRGB
+ 1, // Z32F
+ 1, // Z16
+ 1, // Z24S8
+ 1, // S8Z24
+ 1, // Z32FS8
+}};
+
static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
- constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
- 1, // ABGR8U
- 1, // ABGR8S
- 1, // ABGR8UI
- 1, // B5G6R5U
- 1, // A2B10G10R10U
- 1, // A1B5G5R5U
- 1, // R8U
- 1, // R8UI
- 1, // RGBA16F
- 1, // RGBA16U
- 1, // RGBA16UI
- 1, // R11FG11FB10F
- 1, // RGBA32UI
- 4, // DXT1
- 4, // DXT23
- 4, // DXT45
- 4, // DXN1
- 4, // DXN2UNORM
- 4, // DXN2SNORM
- 4, // BC7U
- 4, // BC6H_UF16
- 4, // BC6H_SF16
- 4, // ASTC_2D_4X4
- 1, // G8R8U
- 1, // G8R8S
- 1, // BGRA8
- 1, // RGBA32F
- 1, // RG32F
- 1, // R32F
- 1, // R16F
- 1, // R16U
- 1, // R16S
- 1, // R16UI
- 1, // R16I
- 1, // RG16
- 1, // RG16F
- 1, // RG16UI
- 1, // RG16I
- 1, // RG16S
- 1, // RGB32F
- 1, // RGBA8_SRGB
- 1, // RG8U
- 1, // RG8S
- 1, // RG32UI
- 1, // R32UI
- 8, // ASTC_2D_8X8
- 8, // ASTC_2D_8X5
- 5, // ASTC_2D_5X4
- 1, // BGRA8_SRGB
- 4, // DXT1_SRGB
- 4, // DXT23_SRGB
- 4, // DXT45_SRGB
- 4, // BC7U_SRGB
- 4, // ASTC_2D_4X4_SRGB
- 8, // ASTC_2D_8X8_SRGB
- 8, // ASTC_2D_8X5_SRGB
- 5, // ASTC_2D_5X4_SRGB
- 5, // ASTC_2D_5X5
- 5, // ASTC_2D_5X5_SRGB
- 10, // ASTC_2D_10X8
- 10, // ASTC_2D_10X8_SRGB
- 1, // Z32F
- 1, // Z16
- 1, // Z24S8
- 1, // S8Z24
- 1, // Z32FS8
- }};
+
ASSERT(static_cast<std::size_t>(format) < block_width_table.size());
return block_width_table[static_cast<std::size_t>(format)];
}
+constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
+ 1, // ABGR8U
+ 1, // ABGR8S
+ 1, // ABGR8UI
+ 1, // B5G6R5U
+ 1, // A2B10G10R10U
+ 1, // A1B5G5R5U
+ 1, // R8U
+ 1, // R8UI
+ 1, // RGBA16F
+ 1, // RGBA16U
+ 1, // RGBA16UI
+ 1, // R11FG11FB10F
+ 1, // RGBA32UI
+ 4, // DXT1
+ 4, // DXT23
+ 4, // DXT45
+ 4, // DXN1
+ 4, // DXN2UNORM
+ 4, // DXN2SNORM
+ 4, // BC7U
+ 4, // BC6H_UF16
+ 4, // BC6H_SF16
+ 4, // ASTC_2D_4X4
+ 1, // G8R8U
+ 1, // G8R8S
+ 1, // BGRA8
+ 1, // RGBA32F
+ 1, // RG32F
+ 1, // R32F
+ 1, // R16F
+ 1, // R16U
+ 1, // R16S
+ 1, // R16UI
+ 1, // R16I
+ 1, // RG16
+ 1, // RG16F
+ 1, // RG16UI
+ 1, // RG16I
+ 1, // RG16S
+ 1, // RGB32F
+ 1, // RGBA8_SRGB
+ 1, // RG8U
+ 1, // RG8S
+ 1, // RG32UI
+ 1, // R32UI
+ 8, // ASTC_2D_8X8
+ 5, // ASTC_2D_8X5
+ 4, // ASTC_2D_5X4
+ 1, // BGRA8_SRGB
+ 4, // DXT1_SRGB
+ 4, // DXT23_SRGB
+ 4, // DXT45_SRGB
+ 4, // BC7U_SRGB
+ 4, // ASTC_2D_4X4_SRGB
+ 8, // ASTC_2D_8X8_SRGB
+ 5, // ASTC_2D_8X5_SRGB
+ 4, // ASTC_2D_5X4_SRGB
+ 5, // ASTC_2D_5X5
+ 5, // ASTC_2D_5X5_SRGB
+ 8, // ASTC_2D_10X8
+ 8, // ASTC_2D_10X8_SRGB
+ 1, // Z32F
+ 1, // Z16
+ 1, // Z24S8
+ 1, // S8Z24
+ 1, // Z32FS8
+}};
+
static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
- constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
- 1, // ABGR8U
- 1, // ABGR8S
- 1, // ABGR8UI
- 1, // B5G6R5U
- 1, // A2B10G10R10U
- 1, // A1B5G5R5U
- 1, // R8U
- 1, // R8UI
- 1, // RGBA16F
- 1, // RGBA16U
- 1, // RGBA16UI
- 1, // R11FG11FB10F
- 1, // RGBA32UI
- 4, // DXT1
- 4, // DXT23
- 4, // DXT45
- 4, // DXN1
- 4, // DXN2UNORM
- 4, // DXN2SNORM
- 4, // BC7U
- 4, // BC6H_UF16
- 4, // BC6H_SF16
- 4, // ASTC_2D_4X4
- 1, // G8R8U
- 1, // G8R8S
- 1, // BGRA8
- 1, // RGBA32F
- 1, // RG32F
- 1, // R32F
- 1, // R16F
- 1, // R16U
- 1, // R16S
- 1, // R16UI
- 1, // R16I
- 1, // RG16
- 1, // RG16F
- 1, // RG16UI
- 1, // RG16I
- 1, // RG16S
- 1, // RGB32F
- 1, // RGBA8_SRGB
- 1, // RG8U
- 1, // RG8S
- 1, // RG32UI
- 1, // R32UI
- 8, // ASTC_2D_8X8
- 5, // ASTC_2D_8X5
- 4, // ASTC_2D_5X4
- 1, // BGRA8_SRGB
- 4, // DXT1_SRGB
- 4, // DXT23_SRGB
- 4, // DXT45_SRGB
- 4, // BC7U_SRGB
- 4, // ASTC_2D_4X4_SRGB
- 8, // ASTC_2D_8X8_SRGB
- 5, // ASTC_2D_8X5_SRGB
- 4, // ASTC_2D_5X4_SRGB
- 5, // ASTC_2D_5X5
- 5, // ASTC_2D_5X5_SRGB
- 8, // ASTC_2D_10X8
- 8, // ASTC_2D_10X8_SRGB
- 1, // Z32F
- 1, // Z16
- 1, // Z24S8
- 1, // S8Z24
- 1, // Z32FS8
- }};
-
ASSERT(static_cast<std::size_t>(format) < block_height_table.size());
return block_height_table[static_cast<std::size_t>(format)];
}
+constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
+ 32, // ABGR8U
+ 32, // ABGR8S
+ 32, // ABGR8UI
+ 16, // B5G6R5U
+ 32, // A2B10G10R10U
+ 16, // A1B5G5R5U
+ 8, // R8U
+ 8, // R8UI
+ 64, // RGBA16F
+ 64, // RGBA16U
+ 64, // RGBA16UI
+ 32, // R11FG11FB10F
+ 128, // RGBA32UI
+ 64, // DXT1
+ 128, // DXT23
+ 128, // DXT45
+ 64, // DXN1
+ 128, // DXN2UNORM
+ 128, // DXN2SNORM
+ 128, // BC7U
+ 128, // BC6H_UF16
+ 128, // BC6H_SF16
+ 128, // ASTC_2D_4X4
+ 16, // G8R8U
+ 16, // G8R8S
+ 32, // BGRA8
+ 128, // RGBA32F
+ 64, // RG32F
+ 32, // R32F
+ 16, // R16F
+ 16, // R16U
+ 16, // R16S
+ 16, // R16UI
+ 16, // R16I
+ 32, // RG16
+ 32, // RG16F
+ 32, // RG16UI
+ 32, // RG16I
+ 32, // RG16S
+ 96, // RGB32F
+ 32, // RGBA8_SRGB
+ 16, // RG8U
+ 16, // RG8S
+ 64, // RG32UI
+ 32, // R32UI
+ 128, // ASTC_2D_8X8
+ 128, // ASTC_2D_8X5
+ 128, // ASTC_2D_5X4
+ 32, // BGRA8_SRGB
+ 64, // DXT1_SRGB
+ 128, // DXT23_SRGB
+ 128, // DXT45_SRGB
+ 128, // BC7U
+ 128, // ASTC_2D_4X4_SRGB
+ 128, // ASTC_2D_8X8_SRGB
+ 128, // ASTC_2D_8X5_SRGB
+ 128, // ASTC_2D_5X4_SRGB
+ 128, // ASTC_2D_5X5
+ 128, // ASTC_2D_5X5_SRGB
+ 128, // ASTC_2D_10X8
+ 128, // ASTC_2D_10X8_SRGB
+ 32, // Z32F
+ 16, // Z16
+ 32, // Z24S8
+ 32, // S8Z24
+ 64, // Z32FS8
+}};
+
static constexpr u32 GetFormatBpp(PixelFormat format) {
if (format == PixelFormat::Invalid)
return 0;
- constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
- 32, // ABGR8U
- 32, // ABGR8S
- 32, // ABGR8UI
- 16, // B5G6R5U
- 32, // A2B10G10R10U
- 16, // A1B5G5R5U
- 8, // R8U
- 8, // R8UI
- 64, // RGBA16F
- 64, // RGBA16U
- 64, // RGBA16UI
- 32, // R11FG11FB10F
- 128, // RGBA32UI
- 64, // DXT1
- 128, // DXT23
- 128, // DXT45
- 64, // DXN1
- 128, // DXN2UNORM
- 128, // DXN2SNORM
- 128, // BC7U
- 128, // BC6H_UF16
- 128, // BC6H_SF16
- 128, // ASTC_2D_4X4
- 16, // G8R8U
- 16, // G8R8S
- 32, // BGRA8
- 128, // RGBA32F
- 64, // RG32F
- 32, // R32F
- 16, // R16F
- 16, // R16U
- 16, // R16S
- 16, // R16UI
- 16, // R16I
- 32, // RG16
- 32, // RG16F
- 32, // RG16UI
- 32, // RG16I
- 32, // RG16S
- 96, // RGB32F
- 32, // RGBA8_SRGB
- 16, // RG8U
- 16, // RG8S
- 64, // RG32UI
- 32, // R32UI
- 128, // ASTC_2D_8X8
- 128, // ASTC_2D_8X5
- 128, // ASTC_2D_5X4
- 32, // BGRA8_SRGB
- 64, // DXT1_SRGB
- 128, // DXT23_SRGB
- 128, // DXT45_SRGB
- 128, // BC7U
- 128, // ASTC_2D_4X4_SRGB
- 128, // ASTC_2D_8X8_SRGB
- 128, // ASTC_2D_8X5_SRGB
- 128, // ASTC_2D_5X4_SRGB
- 128, // ASTC_2D_5X5
- 128, // ASTC_2D_5X5_SRGB
- 128, // ASTC_2D_10X8
- 128, // ASTC_2D_10X8_SRGB
- 32, // Z32F
- 16, // Z16
- 32, // Z24S8
- 32, // S8Z24
- 64, // Z32FS8
- }};
-
ASSERT(static_cast<std::size_t>(format) < bpp_table.size());
return bpp_table[static_cast<std::size_t>(format)];
}
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index bbae9285f..5db75de22 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -226,7 +226,7 @@ u32 BytesPerPixel(TextureFormat format) {
return 8;
default:
UNIMPLEMENTED_MSG("Format not implemented");
- break;
+ return 1;
}
}
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 61562b9b2..17ecaafde 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -33,10 +33,14 @@ add_executable(yuzu
configuration/configure_input.h
configuration/configure_input_player.cpp
configuration/configure_input_player.h
+ configuration/configure_input_simple.cpp
+ configuration/configure_input_simple.h
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_system.cpp
configuration/configure_system.h
+ configuration/configure_per_general.cpp
+ configuration/configure_per_general.h
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_web.cpp
@@ -87,7 +91,9 @@ set(UIS
configuration/configure_graphics.ui
configuration/configure_input.ui
configuration/configure_input_player.ui
+ configuration/configure_input_simple.ui
configuration/configure_mouse_advanced.ui
+ configuration/configure_per_general.ui
configuration/configure_system.ui
configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 83ebbd1fe..759362ce3 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -4,6 +4,7 @@
#include <QSettings>
#include "common/file_util.h"
+#include "configure_input_simple.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
@@ -206,60 +207,57 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
- Settings::values.players[p].connected =
- qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
+ auto& player = Settings::values.players[p];
- Settings::values.players[p].type = static_cast<Settings::ControllerType>(
+ player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
+
+ player.type = static_cast<Settings::ControllerType>(
qt_config
->value(QString("player_%1_type").arg(p),
static_cast<u8>(Settings::ControllerType::DualJoycon))
.toUInt());
- Settings::values.players[p].body_color_left =
- qt_config
- ->value(QString("player_%1_body_color_left").arg(p),
- Settings::JOYCON_BODY_NEON_BLUE)
- .toUInt();
- Settings::values.players[p].body_color_right =
- qt_config
- ->value(QString("player_%1_body_color_right").arg(p),
- Settings::JOYCON_BODY_NEON_RED)
- .toUInt();
- Settings::values.players[p].button_color_left =
- qt_config
- ->value(QString("player_%1_button_color_left").arg(p),
- Settings::JOYCON_BUTTONS_NEON_BLUE)
- .toUInt();
- Settings::values.players[p].button_color_right =
- qt_config
- ->value(QString("player_%1_button_color_right").arg(p),
- Settings::JOYCON_BUTTONS_NEON_RED)
- .toUInt();
+ player.body_color_left = qt_config
+ ->value(QString("player_%1_body_color_left").arg(p),
+ Settings::JOYCON_BODY_NEON_BLUE)
+ .toUInt();
+ player.body_color_right = qt_config
+ ->value(QString("player_%1_body_color_right").arg(p),
+ Settings::JOYCON_BODY_NEON_RED)
+ .toUInt();
+ player.button_color_left = qt_config
+ ->value(QString("player_%1_button_color_left").arg(p),
+ Settings::JOYCON_BUTTONS_NEON_BLUE)
+ .toUInt();
+ player.button_color_right = qt_config
+ ->value(QString("player_%1_button_color_right").arg(p),
+ Settings::JOYCON_BUTTONS_NEON_RED)
+ .toUInt();
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
- Settings::values.players[p].buttons[i] =
+ player.buttons[i] =
qt_config
->value(QString("player_%1_").arg(p) + Settings::NativeButton::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
- if (Settings::values.players[p].buttons[i].empty())
- Settings::values.players[p].buttons[i] = default_param;
+ if (player.buttons[i].empty())
+ player.buttons[i] = default_param;
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
default_analogs[i][3], default_analogs[i][4], 0.5f);
- Settings::values.players[p].analogs[i] =
+ player.analogs[i] =
qt_config
->value(QString("player_%1_").arg(p) + Settings::NativeAnalog::mapping[i],
QString::fromStdString(default_param))
.toString()
.toStdString();
- if (Settings::values.players[p].analogs[i].empty())
- Settings::values.players[p].analogs[i] = default_param;
+ if (player.analogs[i].empty())
+ player.analogs[i] = default_param;
}
}
@@ -342,6 +340,13 @@ void Config::ReadTouchscreenValues() {
qt_config->endGroup();
}
+void Config::ApplyDefaultProfileIfInputInvalid() {
+ if (!std::any_of(Settings::values.players.begin(), Settings::values.players.end(),
+ [](const Settings::PlayerInput& in) { return in.connected; })) {
+ ApplyInputProfileConfiguration(UISettings::values.profile_index);
+ }
+}
+
void Config::ReadValues() {
qt_config->beginGroup("Controls");
@@ -444,6 +449,21 @@ void Config::ReadValues() {
Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
qt_config->endGroup();
+ const auto size = qt_config->beginReadArray("DisabledAddOns");
+ for (int i = 0; i < size; ++i) {
+ qt_config->setArrayIndex(i);
+ const auto title_id = qt_config->value("title_id", 0).toULongLong();
+ std::vector<std::string> out;
+ const auto d_size = qt_config->beginReadArray("disabled");
+ for (int j = 0; j < d_size; ++j) {
+ qt_config->setArrayIndex(j);
+ out.push_back(qt_config->value("d", "").toString().toStdString());
+ }
+ qt_config->endArray();
+ Settings::values.disabled_addons.insert_or_assign(title_id, out);
+ }
+ qt_config->endArray();
+
qt_config->beginGroup("UI");
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
UISettings::values.enable_discord_presence =
@@ -506,35 +526,36 @@ void Config::ReadValues() {
UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
UISettings::values.show_console = qt_config->value("showConsole", false).toBool();
+ UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt();
+
+ ApplyDefaultProfileIfInputInvalid();
qt_config->endGroup();
}
void Config::SavePlayerValues() {
- for (int p = 0; p < Settings::values.players.size(); ++p) {
- qt_config->setValue(QString("player_%1_connected").arg(p),
- Settings::values.players[p].connected);
- qt_config->setValue(QString("player_%1_type").arg(p),
- static_cast<u8>(Settings::values.players[p].type));
-
- qt_config->setValue(QString("player_%1_body_color_left").arg(p),
- Settings::values.players[p].body_color_left);
- qt_config->setValue(QString("player_%1_body_color_right").arg(p),
- Settings::values.players[p].body_color_right);
+ for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
+ const auto& player = Settings::values.players[p];
+
+ qt_config->setValue(QString("player_%1_connected").arg(p), player.connected);
+ qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type));
+
+ qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left);
+ qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right);
qt_config->setValue(QString("player_%1_button_color_left").arg(p),
- Settings::values.players[p].button_color_left);
+ player.button_color_left);
qt_config->setValue(QString("player_%1_button_color_right").arg(p),
- Settings::values.players[p].button_color_right);
+ player.button_color_right);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
qt_config->setValue(QString("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeButton::mapping[i]),
- QString::fromStdString(Settings::values.players[p].buttons[i]));
+ QString::fromStdString(player.buttons[i]));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
qt_config->setValue(QString("player_%1_").arg(p) +
QString::fromStdString(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(Settings::values.players[p].analogs[i]));
+ QString::fromStdString(player.analogs[i]));
}
}
}
@@ -650,6 +671,21 @@ void Config::SaveValues() {
qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
qt_config->endGroup();
+ qt_config->beginWriteArray("DisabledAddOns");
+ int i = 0;
+ for (const auto& elem : Settings::values.disabled_addons) {
+ qt_config->setArrayIndex(i);
+ qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
+ qt_config->beginWriteArray("disabled");
+ for (std::size_t j = 0; j < elem.second.size(); ++j) {
+ qt_config->setArrayIndex(j);
+ qt_config->setValue("d", QString::fromStdString(elem.second[j]));
+ }
+ qt_config->endArray();
+ ++i;
+ }
+ qt_config->endArray();
+
qt_config->beginGroup("UI");
qt_config->setValue("theme", UISettings::values.theme);
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
@@ -695,6 +731,7 @@ void Config::SaveValues() {
qt_config->setValue("firstStart", UISettings::values.first_start);
qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
qt_config->setValue("showConsole", UISettings::values.show_console);
+ qt_config->setValue("profileIndex", UISettings::values.profile_index);
qt_config->endGroup();
}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index a1c27bbf9..e73ad19bb 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -34,6 +34,7 @@ private:
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
+ void ApplyDefaultProfileIfInputInvalid();
void SaveValues();
void SavePlayerValues();
diff --git a/src/yuzu/configuration/configure.ui b/src/yuzu/configuration/configure.ui
index 9b297df28..8706b80d2 100644
--- a/src/yuzu/configuration/configure.ui
+++ b/src/yuzu/configuration/configure.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>461</width>
- <height>500</height>
+ <height>659</height>
</rect>
</property>
<property name="windowTitle">
@@ -24,17 +24,17 @@
<string>General</string>
</attribute>
</widget>
- <widget class="ConfigureGameList" name="gameListTab">
- <attribute name="title">
- <string>Game List</string>
- </attribute>
- </widget>
+ <widget class="ConfigureGameList" name="gameListTab">
+ <attribute name="title">
+ <string>Game List</string>
+ </attribute>
+ </widget>
<widget class="ConfigureSystem" name="systemTab">
<attribute name="title">
<string>System</string>
</attribute>
</widget>
- <widget class="ConfigureInput" name="inputTab">
+ <widget class="ConfigureInputSimple" name="inputTab">
<attribute name="title">
<string>Input</string>
</attribute>
@@ -54,11 +54,11 @@
<string>Debug</string>
</attribute>
</widget>
- <widget class="ConfigureWeb" name="webTab">
- <attribute name="title">
- <string>Web</string>
- </attribute>
- </widget>
+ <widget class="ConfigureWeb" name="webTab">
+ <attribute name="title">
+ <string>Web</string>
+ </attribute>
+ </widget>
</widget>
</item>
<item>
@@ -77,12 +77,12 @@
<header>configuration/configure_general.h</header>
<container>1</container>
</customwidget>
- <customwidget>
- <class>ConfigureGameList</class>
- <extends>QWidget</extends>
- <header>configuration/configure_gamelist.h</header>
- <container>1</container>
- </customwidget>
+ <customwidget>
+ <class>ConfigureGameList</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_gamelist.h</header>
+ <container>1</container>
+ </customwidget>
<customwidget>
<class>ConfigureSystem</class>
<extends>QWidget</extends>
@@ -102,9 +102,9 @@
<container>1</container>
</customwidget>
<customwidget>
- <class>ConfigureInput</class>
+ <class>ConfigureInputSimple</class>
<extends>QWidget</extends>
- <header>configuration/configure_input.h</header>
+ <header>configuration/configure_input_simple.h</header>
<container>1</container>
</customwidget>
<customwidget>
@@ -113,12 +113,12 @@
<header>configuration/configure_graphics.h</header>
<container>1</container>
</customwidget>
- <customwidget>
- <class>ConfigureWeb</class>
- <extends>QWidget</extends>
- <header>configuration/configure_web.h</header>
- <container>1</container>
- </customwidget>
+ <customwidget>
+ <class>ConfigureWeb</class>
+ <extends>QWidget</extends>
+ <header>configuration/configure_web.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections>
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index eb1da0f9e..5d9ccc6e8 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -17,8 +17,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
ui->output_sink_combo_box->clear();
ui->output_sink_combo_box->addItem("auto");
- for (const auto& sink_detail : AudioCore::g_sink_details) {
- ui->output_sink_combo_box->addItem(sink_detail.id);
+ for (const char* id : AudioCore::GetSinkIDs()) {
+ ui->output_sink_combo_box->addItem(id);
}
connect(ui->volume_slider, &QSlider::valueChanged, this,
@@ -97,8 +97,7 @@ void ConfigureAudio::updateAudioDevices(int sink_index) {
ui->audio_device_combo_box->addItem(AudioCore::auto_device_name);
const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
- const std::vector<std::string> device_list = AudioCore::GetSinkDetails(sink_id).list_devices();
- for (const auto& device : device_list) {
+ for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
ui->audio_device_combo_box->addItem(QString::fromStdString(device));
}
}
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 830d26115..f39d57998 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -20,6 +20,33 @@
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
+void OnDockedModeChanged(bool last_state, bool new_state) {
+ if (last_state == new_state) {
+ return;
+ }
+
+ Core::System& system{Core::System::GetInstance()};
+ if (!system.IsPoweredOn()) {
+ return;
+ }
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+
+ // Message queue is shared between these services, we just need to signal an operation
+ // change to one and it will handle both automatically
+ auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
+ auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
+ bool has_signalled = false;
+
+ if (applet_oe != nullptr) {
+ applet_oe->GetMessageQueue()->OperationModeChanged();
+ has_signalled = true;
+ }
+
+ if (applet_ae != nullptr && !has_signalled) {
+ applet_ae->GetMessageQueue()->OperationModeChanged();
+ }
+}
+
namespace {
template <typename Dialog, typename... Args>
void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
@@ -34,7 +61,7 @@ void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
} // Anonymous namespace
ConfigureInput::ConfigureInput(QWidget* parent)
- : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigureInput>()) {
ui->setupUi(this);
players_controller = {
@@ -90,37 +117,6 @@ ConfigureInput::ConfigureInput(QWidget* parent)
ConfigureInput::~ConfigureInput() = default;
-void ConfigureInput::OnDockedModeChanged(bool last_state, bool new_state) {
- if (ui->use_docked_mode->isChecked() && ui->handheld_connected->isChecked()) {
- ui->handheld_connected->setChecked(false);
- }
-
- if (last_state == new_state) {
- return;
- }
-
- Core::System& system{Core::System::GetInstance()};
- if (!system.IsPoweredOn()) {
- return;
- }
- Service::SM::ServiceManager& sm = system.ServiceManager();
-
- // Message queue is shared between these services, we just need to signal an operation
- // change to one and it will handle both automatically
- auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
- auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
- bool has_signalled = false;
-
- if (applet_oe != nullptr) {
- applet_oe->GetMessageQueue()->OperationModeChanged();
- has_signalled = true;
- }
-
- if (applet_ae != nullptr && !has_signalled) {
- applet_ae->GetMessageQueue()->OperationModeChanged();
- }
-}
-
void ConfigureInput::applyConfiguration() {
for (std::size_t i = 0; i < players_controller.size(); ++i) {
const auto controller_type_index = players_controller[i]->currentIndex();
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index 1649e4c0b..b8e62cc2b 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -7,8 +7,8 @@
#include <array>
#include <memory>
+#include <QDialog>
#include <QKeyEvent>
-#include <QWidget>
#include "ui_configure_input.h"
@@ -20,7 +20,9 @@ namespace Ui {
class ConfigureInput;
}
-class ConfigureInput : public QWidget {
+void OnDockedModeChanged(bool last_state, bool new_state);
+
+class ConfigureInput : public QDialog {
Q_OBJECT
public:
@@ -33,8 +35,6 @@ public:
private:
void updateUIEnabled();
- void OnDockedModeChanged(bool last_state, bool new_state);
-
/// Load configuration settings.
void loadConfiguration();
/// Restore all buttons to their default values.
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index dae8277bc..0a2d9f024 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInput</class>
- <widget class="QWidget" name="ConfigureInput">
+ <widget class="QDialog" name="ConfigureInput">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>473</width>
- <height>685</height>
+ <width>384</width>
+ <height>576</height>
</rect>
</property>
<property name="windowTitle">
@@ -478,6 +478,13 @@
</property>
</spacer>
</item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
@@ -485,5 +492,38 @@
</layout>
</widget>
<resources/>
- <connections/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigureInput</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>294</x>
+ <y>553</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>191</x>
+ <y>287</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigureInput</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>294</x>
+ <y>553</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>191</x>
+ <y>287</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
</ui>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 7dadd83c1..ba2b32c4f 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -6,6 +6,7 @@
#include <memory>
#include <utility>
#include <QColorDialog>
+#include <QGridLayout>
#include <QMenu>
#include <QMessageBox>
#include <QTimer>
diff --git a/src/yuzu/configuration/configure_input_simple.cpp b/src/yuzu/configuration/configure_input_simple.cpp
new file mode 100644
index 000000000..b4f3724bd
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_simple.cpp
@@ -0,0 +1,142 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstring>
+#include <functional>
+#include <tuple>
+
+#include <QDialog>
+
+#include "ui_configure_input_simple.h"
+#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_input_player.h"
+#include "yuzu/configuration/configure_input_simple.h"
+#include "yuzu/ui_settings.h"
+
+namespace {
+
+template <typename Dialog, typename... Args>
+void CallConfigureDialog(ConfigureInputSimple* caller, Args&&... args) {
+ caller->applyConfiguration();
+ Dialog dialog(caller, std::forward<Args>(args)...);
+
+ const auto res = dialog.exec();
+ if (res == QDialog::Accepted) {
+ dialog.applyConfiguration();
+ }
+}
+
+// OnProfileSelect functions should (when applicable):
+// - Set controller types
+// - Set controller enabled
+// - Set docked mode
+// - Set advanced controller config/enabled (i.e. debug, kbd, mouse, touch)
+//
+// OnProfileSelect function should NOT however:
+// - Reset any button mappings
+// - Open any dialogs
+// - Block in any way
+
+constexpr std::size_t HANDHELD_INDEX = 8;
+
+void HandheldOnProfileSelect() {
+ Settings::values.players[HANDHELD_INDEX].connected = true;
+ Settings::values.players[HANDHELD_INDEX].type = Settings::ControllerType::DualJoycon;
+
+ for (std::size_t player = 0; player < HANDHELD_INDEX; ++player) {
+ Settings::values.players[player].connected = false;
+ }
+
+ Settings::values.use_docked_mode = false;
+ Settings::values.keyboard_enabled = false;
+ Settings::values.mouse_enabled = false;
+ Settings::values.debug_pad_enabled = false;
+ Settings::values.touchscreen.enabled = true;
+}
+
+void DualJoyconsDockedOnProfileSelect() {
+ Settings::values.players[0].connected = true;
+ Settings::values.players[0].type = Settings::ControllerType::DualJoycon;
+
+ for (std::size_t player = 1; player <= HANDHELD_INDEX; ++player) {
+ Settings::values.players[player].connected = false;
+ }
+
+ Settings::values.use_docked_mode = true;
+ Settings::values.keyboard_enabled = false;
+ Settings::values.mouse_enabled = false;
+ Settings::values.debug_pad_enabled = false;
+ Settings::values.touchscreen.enabled = false;
+}
+
+// Name, OnProfileSelect (called when selected in drop down), OnConfigure (called when configure
+// is clicked)
+using InputProfile =
+ std::tuple<QString, std::function<void()>, std::function<void(ConfigureInputSimple*)>>;
+
+const std::array<InputProfile, 3> INPUT_PROFILES{{
+ {ConfigureInputSimple::tr("Single Player - Handheld - Undocked"), HandheldOnProfileSelect,
+ [](ConfigureInputSimple* caller) {
+ CallConfigureDialog<ConfigureInputPlayer>(caller, HANDHELD_INDEX, false);
+ }},
+ {ConfigureInputSimple::tr("Single Player - Dual Joycons - Docked"),
+ DualJoyconsDockedOnProfileSelect,
+ [](ConfigureInputSimple* caller) {
+ CallConfigureDialog<ConfigureInputPlayer>(caller, 1, false);
+ }},
+ {ConfigureInputSimple::tr("Custom"), [] {}, CallConfigureDialog<ConfigureInput>},
+}};
+
+} // namespace
+
+void ApplyInputProfileConfiguration(int profile_index) {
+ std::get<1>(
+ INPUT_PROFILES.at(std::min(profile_index, static_cast<int>(INPUT_PROFILES.size() - 1))))();
+}
+
+ConfigureInputSimple::ConfigureInputSimple(QWidget* parent)
+ : QWidget(parent), ui(std::make_unique<Ui::ConfigureInputSimple>()) {
+ ui->setupUi(this);
+
+ for (const auto& profile : INPUT_PROFILES) {
+ ui->profile_combobox->addItem(std::get<0>(profile), std::get<0>(profile));
+ }
+
+ connect(ui->profile_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
+ &ConfigureInputSimple::OnSelectProfile);
+ connect(ui->profile_configure, &QPushButton::pressed, this, &ConfigureInputSimple::OnConfigure);
+
+ this->loadConfiguration();
+}
+
+ConfigureInputSimple::~ConfigureInputSimple() = default;
+
+void ConfigureInputSimple::applyConfiguration() {
+ auto index = ui->profile_combobox->currentIndex();
+ // Make the stored index for "Custom" very large so that if new profiles are added it
+ // doesn't change.
+ if (index >= static_cast<int>(INPUT_PROFILES.size() - 1))
+ index = std::numeric_limits<int>::max();
+
+ UISettings::values.profile_index = index;
+}
+
+void ConfigureInputSimple::loadConfiguration() {
+ const auto index = UISettings::values.profile_index;
+ if (index >= static_cast<int>(INPUT_PROFILES.size()) || index < 0)
+ ui->profile_combobox->setCurrentIndex(static_cast<int>(INPUT_PROFILES.size() - 1));
+ else
+ ui->profile_combobox->setCurrentIndex(index);
+}
+
+void ConfigureInputSimple::OnSelectProfile(int index) {
+ const auto old_docked = Settings::values.use_docked_mode;
+ ApplyInputProfileConfiguration(index);
+ OnDockedModeChanged(old_docked, Settings::values.use_docked_mode);
+}
+
+void ConfigureInputSimple::OnConfigure() {
+ std::get<2>(INPUT_PROFILES.at(ui->profile_combobox->currentIndex()))(this);
+}
diff --git a/src/yuzu/configuration/configure_input_simple.h b/src/yuzu/configuration/configure_input_simple.h
new file mode 100644
index 000000000..5b6b69994
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_simple.h
@@ -0,0 +1,40 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+#include <QWidget>
+
+class QPushButton;
+class QString;
+class QTimer;
+
+namespace Ui {
+class ConfigureInputSimple;
+}
+
+// Used by configuration loader to apply a profile if the input is invalid.
+void ApplyInputProfileConfiguration(int profile_index);
+
+class ConfigureInputSimple : public QWidget {
+ Q_OBJECT
+
+public:
+ explicit ConfigureInputSimple(QWidget* parent = nullptr);
+ ~ConfigureInputSimple() override;
+
+ /// Save all button configurations to settings file
+ void applyConfiguration();
+
+private:
+ /// Load configuration settings.
+ void loadConfiguration();
+
+ void OnSelectProfile(int index);
+ void OnConfigure();
+
+ std::unique_ptr<Ui::ConfigureInputSimple> ui;
+};
diff --git a/src/yuzu/configuration/configure_input_simple.ui b/src/yuzu/configuration/configure_input_simple.ui
new file mode 100644
index 000000000..c4889caa9
--- /dev/null
+++ b/src/yuzu/configuration/configure_input_simple.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureInputSimple</class>
+ <widget class="QWidget" name="ConfigureInputSimple">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>473</width>
+ <height>685</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>ConfigureInputSimple</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QGroupBox" name="gridGroupBox">
+ <property name="title">
+ <string>Profile</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="1" column="2">
+ <widget class="QPushButton" name="profile_configure">
+ <property name="text">
+ <string>Configure</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="3">
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="profile_combobox">
+ <property name="minimumSize">
+ <size>
+ <width>250</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Choose a controller configuration:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp
new file mode 100644
index 000000000..80109b434
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_general.cpp
@@ -0,0 +1,170 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include <QHeaderView>
+#include <QMenu>
+#include <QMessageBox>
+#include <QStandardItemModel>
+#include <QString>
+#include <QTimer>
+#include <QTreeView>
+
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/xts_archive.h"
+#include "core/loader/loader.h"
+#include "ui_configure_per_general.h"
+#include "yuzu/configuration/config.h"
+#include "yuzu/configuration/configure_input.h"
+#include "yuzu/configuration/configure_per_general.h"
+#include "yuzu/ui_settings.h"
+#include "yuzu/util/util.h"
+
+ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
+ : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) {
+
+ ui->setupUi(this);
+ setFocusPolicy(Qt::ClickFocus);
+ setWindowTitle(tr("Properties"));
+
+ layout = new QVBoxLayout;
+ tree_view = new QTreeView;
+ item_model = new QStandardItemModel(tree_view);
+ tree_view->setModel(item_model);
+ tree_view->setAlternatingRowColors(true);
+ tree_view->setSelectionMode(QHeaderView::SingleSelection);
+ tree_view->setSelectionBehavior(QHeaderView::SelectRows);
+ tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setSortingEnabled(true);
+ tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
+ tree_view->setUniformRowHeights(true);
+ tree_view->setContextMenuPolicy(Qt::NoContextMenu);
+
+ item_model->insertColumns(0, 2);
+ item_model->setHeaderData(0, Qt::Horizontal, "Patch Name");
+ item_model->setHeaderData(1, Qt::Horizontal, "Version");
+
+ // We must register all custom types with the Qt Automoc system so that we are able to use it
+ // with signals/slots. In this case, QList falls under the umbrells of custom types.
+ qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
+
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+ layout->addWidget(tree_view);
+
+ ui->scrollArea->setLayout(layout);
+
+ scene = new QGraphicsScene;
+ ui->icon_view->setScene(scene);
+
+ connect(item_model, &QStandardItemModel::itemChanged,
+ [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
+
+ this->loadConfiguration();
+}
+
+ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
+
+void ConfigurePerGameGeneral::applyConfiguration() {
+ std::vector<std::string> disabled_addons;
+
+ for (const auto& item : list_items) {
+ const auto disabled = item.front()->checkState() == Qt::Unchecked;
+ if (disabled)
+ disabled_addons.push_back(item.front()->text().toStdString());
+ }
+
+ Settings::values.disabled_addons[title_id] = disabled_addons;
+}
+
+void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
+ this->file = std::move(file);
+ this->loadConfiguration();
+}
+
+void ConfigurePerGameGeneral::loadConfiguration() {
+ if (file == nullptr)
+ return;
+
+ const auto loader = Loader::GetLoader(file);
+
+ ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str());
+
+ FileSys::PatchManager pm{title_id};
+ const auto control = pm.GetControlMetadata();
+
+ if (control.first != nullptr) {
+ ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
+ ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
+ ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
+ } else {
+ std::string title;
+ if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
+ ui->display_name->setText(QString::fromStdString(title));
+
+ std::string developer;
+ if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success)
+ ui->display_developer->setText(QString::fromStdString(developer));
+
+ ui->display_version->setText(QStringLiteral("1.0.0"));
+ }
+
+ if (control.second != nullptr) {
+ scene->clear();
+
+ QPixmap map;
+ const auto bytes = control.second->ReadAllBytes();
+ map.loadFromData(bytes.data(), bytes.size());
+
+ scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
+ Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ } else {
+ std::vector<u8> bytes;
+ if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
+ scene->clear();
+
+ QPixmap map;
+ map.loadFromData(bytes.data(), bytes.size());
+
+ scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
+ Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ }
+ }
+
+ FileSys::VirtualFile update_raw;
+ loader->ReadUpdateRaw(update_raw);
+
+ const auto& disabled = Settings::values.disabled_addons[title_id];
+
+ for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
+ QStandardItem* first_item = new QStandardItem;
+ const auto name = QString::fromStdString(patch.first).replace("[D] ", "");
+ first_item->setText(name);
+ first_item->setCheckable(true);
+
+ const auto patch_disabled =
+ std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
+
+ first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
+
+ list_items.push_back(QList<QStandardItem*>{
+ first_item, new QStandardItem{QString::fromStdString(patch.second)}});
+ item_model->appendRow(list_items.back());
+ }
+
+ tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
+
+ ui->display_filename->setText(QString::fromStdString(file->GetName()));
+
+ ui->display_format->setText(
+ QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
+
+ const auto valueText = ReadableByteSize(file->GetSize());
+ ui->display_size->setText(valueText);
+}
diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h
new file mode 100644
index 000000000..a4494446c
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_general.h
@@ -0,0 +1,50 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include <QKeyEvent>
+#include <QList>
+#include <QWidget>
+
+#include "core/file_sys/vfs_types.h"
+
+class QTreeView;
+class QGraphicsScene;
+class QStandardItem;
+class QStandardItemModel;
+
+namespace Ui {
+class ConfigurePerGameGeneral;
+}
+
+class ConfigurePerGameGeneral : public QDialog {
+ Q_OBJECT
+
+public:
+ explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id);
+ ~ConfigurePerGameGeneral() override;
+
+ /// Save all button configurations to settings file
+ void applyConfiguration();
+
+ void loadFromFile(FileSys::VirtualFile file);
+
+private:
+ std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
+ FileSys::VirtualFile file;
+ u64 title_id;
+
+ QVBoxLayout* layout;
+ QTreeView* tree_view;
+ QStandardItemModel* item_model;
+ QGraphicsScene* scene;
+
+ std::vector<QList<QStandardItem*>> list_items;
+
+ void loadConfiguration();
+};
diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui
new file mode 100644
index 000000000..8fdd96fa4
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_general.ui
@@ -0,0 +1,276 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigurePerGameGeneral</class>
+ <widget class="QDialog" name="ConfigurePerGameGeneral">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>520</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>ConfigurePerGameGeneral</string>
+ </property>
+ <layout class="QHBoxLayout" name="HorizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="VerticalLayout">
+ <item>
+ <widget class="QGroupBox" name="GeneralGroupBox">
+ <property name="title">
+ <string>Info</string>
+ </property>
+ <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="6" column="1" colspan="2">
+ <widget class="QLineEdit" name="display_filename">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="display_name">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Developer</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1" colspan="2">
+ <widget class="QLineEdit" name="display_size">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Name</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Filename</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Version</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Format</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="display_version">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLineEdit" name="display_format">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Size</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="display_developer">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Title ID</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLineEdit" name="display_title_id">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2" rowspan="5">
+ <widget class="QGraphicsView" name="icon_view">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>128</width>
+ <height>128</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>128</width>
+ <height>128</height>
+ </size>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
+ </property>
+ <property name="interactive">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="PerformanceGroupBox">
+ <property name="title">
+ <string>Add-Ons</string>
+ </property>
+ <layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>350</width>
+ <height>169</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>ConfigurePerGameGeneral</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>269</x>
+ <y>567</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>269</x>
+ <y>294</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>ConfigurePerGameGeneral</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>269</x>
+ <y>567</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>269</x>
+ <y>294</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
index 707747422..209798521 100644
--- a/src/yuzu/debugger/graphics/graphics_surface.cpp
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -30,6 +30,7 @@ static Tegra::Texture::TextureFormat ConvertToTextureFormat(
return Tegra::Texture::TextureFormat::A2B10G10R10;
default:
UNIMPLEMENTED_MSG("Unimplemented RT format");
+ return Tegra::Texture::TextureFormat::A8R8G8B8;
}
}
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 96c57fe97..6b3a757e0 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -75,7 +75,7 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
return item_list;
}
-WaitTreeText::WaitTreeText(const QString& t) : text(t) {}
+WaitTreeText::WaitTreeText(QString t) : text(std::move(t)) {}
WaitTreeText::~WaitTreeText() = default;
QString WaitTreeText::GetText() const {
@@ -153,7 +153,7 @@ QString WaitTreeWaitObject::GetText() const {
std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitObject& object) {
switch (object.GetHandleType()) {
- case Kernel::HandleType::Event:
+ case Kernel::HandleType::ReadableEvent:
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::ReadableEvent&>(object));
case Kernel::HandleType::Timer:
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index 492fb6ac9..e639ef412 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -52,7 +52,7 @@ private:
class WaitTreeText : public WaitTreeItem {
Q_OBJECT
public:
- explicit WaitTreeText(const QString& text);
+ explicit WaitTreeText(QString text);
~WaitTreeText() override;
QString GetText() const override;
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index b52a50915..8e9524fd6 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -333,6 +333,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
+ context_menu.addSeparator();
+ QAction* properties = context_menu.addAction(tr("Properties"));
open_save_location->setEnabled(program_id != 0);
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -346,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
connect(navigate_to_gamedb_entry, &QAction::triggered,
[&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
+ connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 05e115e19..b317eb2fc 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -70,6 +70,7 @@ signals:
void CopyTIDRequested(u64 program_id);
void NavigateToGamedbEntryRequested(u64 program_id,
const CompatibilityList& compatibility_list);
+ void OpenPerGameGeneralRequested(const std::string& file);
private slots:
void onTextChanged(const QString& newText);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 1edc60df7..b37710f59 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
FileSys::VirtualFile update_raw;
loader.ReadUpdateRaw(update_raw);
for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
- const bool is_update = kv.first == "Update";
+ const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
if (!updatable && is_update) {
continue;
}
@@ -86,6 +86,37 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
out.chop(1);
return out;
}
+
+QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::string& name,
+ const std::vector<u8>& icon, Loader::AppLoader& loader,
+ u64 program_id, const CompatibilityList& compatibility_list,
+ const FileSys::PatchManager& patch) {
+ const 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;
+ }
+
+ const auto file_type = loader.GetFileType();
+ const auto file_type_string = QString::fromStdString(Loader::GetFileTypeString(file_type));
+
+ QList<QStandardItem*> list{
+ new GameListItemPath(FormatGameName(path), icon, QString::fromStdString(name),
+ file_type_string, program_id),
+ new GameListItemCompat(compatibility),
+ new GameListItem(file_type_string),
+ new GameListItemSize(FileUtil::GetSize(path)),
+ };
+
+ if (UISettings::values.show_add_ons) {
+ list.insert(
+ 2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable())));
+ }
+
+ return list;
+}
} // Anonymous namespace
GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, QString dir_path, bool deep_scan,
@@ -116,29 +147,8 @@ void GameListWorker::AddInstalledTitlesToGameList() {
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;
-
- QList<QStandardItem*> list{
- new GameListItemPath(
- FormatGameName(file->GetFullPath()), 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(file->GetSize()),
- };
-
- if (UISettings::values.show_add_ons) {
- list.insert(2, new GameListItem(FormatPatchNameVersions(patch, *loader)));
- }
-
- emit EntryReady(list);
+ emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
+ compatibility_list, patch));
}
const auto control_data = cache.ListEntriesFilter(FileSys::TitleType::Application,
@@ -155,14 +165,14 @@ void GameListWorker::AddInstalledTitlesToGameList() {
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.
+ if (stop_processing) {
+ // Breaks the callback loop
+ return false;
+ }
- bool is_dir = FileUtil::IsDirectory(physical_name);
- QFileInfo file_info(physical_name.c_str());
- if (!is_dir && file_info.suffix().toStdString() == "nca") {
+ const std::string physical_name = directory + DIR_SEP + virtual_name;
+ const QFileInfo file_info(QString::fromStdString(physical_name));
+ if (!file_info.isDir() && file_info.suffix() == QStringLiteral("nca")) {
auto nca =
std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
if (nca->GetType() == FileSys::NCAContentType::Control) {
@@ -179,20 +189,25 @@ void GameListWorker::FillControlMap(const std::string& dir_path) {
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.
+ if (stop_processing) {
+ // Breaks the callback loop.
+ return false;
+ }
- bool is_dir = FileUtil::IsDirectory(physical_name);
+ const std::string physical_name = directory + DIR_SEP + virtual_name;
+ const 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))
+ auto loader = Loader::GetLoader(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+ if (!loader) {
return true;
+ }
+
+ const auto file_type = loader->GetFileType();
+ if ((file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) &&
+ !UISettings::values.show_unknown) {
+ return true;
+ }
std::vector<u8> icon;
const auto res1 = loader->ReadIcon(icon);
@@ -214,30 +229,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
}
}
- 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;
-
- QList<QStandardItem*> list{
- 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)),
- };
-
- if (UISettings::values.show_add_ons) {
- list.insert(2, new GameListItem(FormatPatchNameVersions(
- patch, *loader, loader->IsRomFSUpdatable())));
- }
-
- emit EntryReady(std::move(list));
+ emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
+ compatibility_list, patch));
} else if (is_dir && recursion > 0) {
watch_list.append(QString::fromStdString(physical_name));
AddFstEntriesToGameList(physical_name, recursion - 1);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 085cab74b..feb5572c6 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,6 +10,7 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "applets/profile_select.h"
#include "applets/software_keyboard.h"
+#include "configuration/configure_per_general.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/hle/service/acc/profile_manager.h"
@@ -464,6 +465,8 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
+ connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
+ &GMainWindow::OnGameListOpenPerGameProperties);
connect(this, &GMainWindow::EmulationStarting, render_window,
&GRenderWindow::OnEmulationStarting);
@@ -1012,6 +1015,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
}
+void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
+ u64 title_id{};
+ const auto v_file = Core::GetGameFileFromPath(vfs, file);
+ const auto loader = Loader::GetLoader(v_file);
+ if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
+ QMessageBox::information(this, tr("Properties"),
+ tr("The game properties could not be loaded."));
+ return;
+ }
+
+ ConfigurePerGameGeneral dialog(this, title_id);
+ dialog.loadFromFile(v_file);
+ auto result = dialog.exec();
+ if (result == QDialog::Accepted) {
+ dialog.applyConfiguration();
+
+ const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
+ if (reload) {
+ game_list->PopulateAsync(UISettings::values.gamedir,
+ UISettings::values.gamedir_deepscan);
+ }
+
+ config->Save();
+ }
+}
+
void GMainWindow::OnMenuLoadFile() {
const QString extensions =
QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 7c5eaf16b..4a982bb02 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -170,6 +170,7 @@ private slots:
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
+ void OnGameListOpenPerGameProperties(const std::string& file);
void OnMenuLoadFile();
void OnMenuLoadFolder();
void OnMenuInstallToNAND();
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index e80aebc0a..2f434eab0 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -58,6 +58,9 @@ struct Values {
// logging
bool show_console;
+ // Controllers
+ int profile_index;
+
// Game List
bool show_unknown;
bool show_add_ons;
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 097c1fbe3..fe0d1eebf 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <memory>
+#include <sstream>
#include <SDL.h>
#include <inih/cpp/INIReader.h>
#include "common/file_util.h"
@@ -369,6 +370,23 @@ void Config::ReadValues() {
Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
+ const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
+ std::stringstream ss(title_list);
+ std::string line;
+ while (std::getline(ss, line, '|')) {
+ const auto title_id = std::stoul(line, nullptr, 16);
+ const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
+
+ std::stringstream inner_ss(disabled_list);
+ std::string inner_line;
+ std::vector<std::string> out;
+ while (std::getline(inner_ss, inner_line, '|')) {
+ out.push_back(inner_line);
+ }
+
+ Settings::values.disabled_addons.insert_or_assign(title_id, out);
+ }
+
// Web Service
Settings::values.enable_telemetry =
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index d73669f36..0f3f8da50 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -221,5 +221,12 @@ web_api_url = https://api.yuzu-emu.org
# See https://profile.yuzu-emu.org/ for more info
yuzu_username =
yuzu_token =
+
+[AddOns]
+# Used to disable add-ons
+# List of title IDs of games that will have add-ons disabled (separated by '|'):
+title_ids =
+# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
+# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
)";
}