Compare commits

..

13 Commits

Author SHA1 Message Date
lizzie 73bf451034 2026-09-21 02:08:29
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-21 02:08:29 +00:00
lizzie a95eea365c 2026-09-21 02:07:30
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-21 02:07:30 +00:00
lizzie ca99dad19a Fix license headers 2026-09-21 01:26:16 +00:00
lizzie bc53e281e7 Fix license headers 2026-09-21 00:39:23 +00:00
lizzie 3bab4fdc2e 2026-09-21 00:39:13
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-21 00:39:13 +00:00
lizzie 9fac15716d Fix license headers 2026-09-20 14:43:02 +00:00
lizzie 565f9a3005 2026-09-20 13:20:40
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 13:20:40 +00:00
lizzie 01f149ddfd 2026-09-20 13:02:52
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 13:02:52 +00:00
lizzie 4bcc2a2319 2026-09-20 12:52:02
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 12:52:02 +00:00
lizzie 8b03a1ba01 Fix license headers 2026-09-20 07:46:07 +00:00
lizzie 652ab5c25c 2026-09-20 07:03:36
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-20 07:46:07 +00:00
xbzk a277b62fe4 [fs] revert #4319 savedata_factory changes (#4462)
- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

This one reverts part of the PR 4319 which was aimed to allow child titles to inherit bundle settings.
The fix seems good so far, but by applying it to save folders caused a trap:
Now child's save folders move to bundle folder, hence saves will vanish, and the android/qt save tools are still pointing to old folders.
Since the save part was only a compliance fix, instead of fiddle in master i'd rather revert this part now, then only touch this again if any bug show up.
No worries. I'll be right here waiting.

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4462
Reviewed-by: lizzie <lizzie@eden-emu.dev>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-09-20 04:40:14 +02:00
lizzie 908b1e9a37 [dynarmic] fix bad arm codegen on relocations (#4458)
In an earlier version I had these as constants which were referred to via `db`, I haphazardly left the LDR and didn't notice.
Even still relocations apply to only 1 ins at a time.

Fixes regressions on #4158

Signed-off-by: lizzie <lizzie@eden-emu.dev>

- [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions).
- [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md)
- [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability.

-------------------

Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4458
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
2026-09-20 00:29:28 +02:00
43 changed files with 771 additions and 421 deletions
+5 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -19,6 +19,10 @@ ADSP::ADSP(Core::System& system, Sink::Sink& sink) {
}
}
void ADSP::NotifyShutdown() {
opus_decoder->NotifyShutdown();
}
AudioRenderer::AudioRenderer& ADSP::AudioRenderer() {
return *audio_renderer;
}
+2 -1
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -45,6 +45,7 @@ public:
explicit ADSP(Core::System& system, Sink::Sink& sink);
~ADSP() = default;
void NotifyShutdown();
AudioRenderer::AudioRenderer& AudioRenderer();
OpusDecoder::OpusDecoder& OpusDecoder();
@@ -82,6 +82,9 @@ void OpusDecoder::Main(std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
auto msg = Receive(Direction::DSP, stop_token);
if (stop_token.stop_requested())
break;
switch (msg) {
case Shutdown:
Send(Direction::Host, Message::ShutdownOK);
@@ -266,4 +269,10 @@ void OpusDecoder::Main(std::stop_token stop_token) {
}
}
void OpusDecoder::NotifyShutdown() {
init_thread.request_stop();
main_thread.request_stop();
Send(Direction::DSP, Message::Shutdown);
}
} // namespace AudioCore::ADSP::OpusDecoder
@@ -67,6 +67,8 @@ public:
shared_memory = &shared_memory_;
}
void NotifyShutdown();
private:
/**
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
+5
View File
@@ -31,6 +31,11 @@ void AudioCore::CreateSinks() {
input_sink = Sink::CreateSinkFromID(sink_id.GetValue(), audio_input_device_id.GetValue());
}
void AudioCore::NotifyShutdown() {
audio_manager->NotifyShutdown();
adsp->NotifyShutdown();
}
void AudioCore::Shutdown() {
audio_manager->Shutdown();
}
+2
View File
@@ -24,6 +24,8 @@ public:
explicit AudioCore(Core::System& system);
~AudioCore();
void NotifyShutdown();
/**
* Shutdown the audio core.
*/
+6 -1
View File
@@ -17,7 +17,7 @@ AudioManager::AudioManager() {
std::unique_lock l{events.GetAudioEventLock()};
events.ClearEvents();
while (!stop_token.stop_requested()) {
const auto timed_out{events.Wait(l, std::chrono::seconds(2))};
const auto timed_out = events.Wait(l, std::chrono::seconds{2});
if (events.CheckAudioEventSet(Event::Type::Max)) {
break;
}
@@ -34,6 +34,11 @@ AudioManager::AudioManager() {
});
}
void AudioManager::NotifyShutdown() {
events.SetAudioEvent(Event::Type::Max, true);
thread.request_stop();
}
void AudioManager::Shutdown() {
events.SetAudioEvent(Event::Type::Max, true);
if (thread.joinable()) {
+4 -3
View File
@@ -39,9 +39,10 @@ class AudioManager {
public:
explicit AudioManager();
/**
* Shutdown the audio manager.
*/
/// @brief Notify of impending shutdown
void NotifyShutdown();
/// @brief Shutdown the audio manager.
void Shutdown();
/**
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -35,7 +38,7 @@ void CircularBufferSinkInfo::Update(BehaviorInfo::ErrorInfo& error_info, OutStat
auto current_params{reinterpret_cast<CircularBufferInParameter*>(parameter.data())};
auto current_state{reinterpret_cast<CircularBufferState*>(state.data())};
if (in_use == buffer_params->in_use && !buffer_unmapped) {
if (in_use == bool(buffer_params->in_use) && !buffer_unmapped) {
error_info.error_code = ResultSuccess;
error_info.address = CpuAddr(0);
out_status.writeOffset = current_state->last_pos2;
+19 -20
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
@@ -31,10 +31,10 @@ public:
};
struct DeviceInParameter {
/* 0x000 */ char name[0x100];
/* 0x000 */ u8 name[0x100];
/* 0x100 */ u32 input_count;
/* 0x104 */ std::array<s8, MaxChannels> inputs;
/* 0x10A */ char unk10A[0x1];
/* 0x10A */ u8 unk10A[0x1];
/* 0x10B */ bool downmix_enabled;
/* 0x10C */ std::array<f32, 4> downmix_coeff;
};
@@ -43,7 +43,7 @@ public:
struct DeviceState {
/* 0x00 */ UpsamplerInfo* upsampler_info;
/* 0x08 */ std::array<Common::FixedPoint<16, 16>, 4> downmix_coeff;
/* 0x18 */ char unk18[0x18];
/* 0x18 */ u8 unk18[0x18];
};
static_assert(sizeof(DeviceState) == 0x30, "DeviceState has the wrong size!");
@@ -55,8 +55,8 @@ public:
/* 0x14 */ u32 previous_pos;
/* 0x18 */ SampleFormat format;
/* 0x1C */ std::array<s8, MaxChannels> inputs;
/* 0x22 */ bool in_use;
/* 0x23 */ char unk23[0x5];
/* 0x22 */ u8 in_use;
/* 0x23 */ u8 unk23[0x5];
};
static_assert(sizeof(CircularBufferInParameter) == 0x28,
"CircularBufferInParameter has the wrong size!");
@@ -65,16 +65,16 @@ public:
/* 0x00 */ u32 last_pos2;
/* 0x04 */ s32 current_pos;
/* 0x08 */ u32 last_pos;
/* 0x0C */ char unk0C[0x4];
/* 0x0C */ u8 unk0C[0x4];
/* 0x10 */ AddressInfo address_info;
};
static_assert(sizeof(CircularBufferState) == 0x30, "CircularBufferState has the wrong size!");
struct InParameter {
/* 0x000 */ Type type;
/* 0x001 */ bool in_use;
/* 0x001 */ u8 in_use;
/* 0x004 */ u32 node_id;
/* 0x008 */ char unk08[0x18];
/* 0x008 */ u8 unk08[0x18];
union {
/* 0x020 */ DeviceInParameter device;
/* 0x020 */ CircularBufferInParameter circular_buffer;
@@ -84,7 +84,7 @@ public:
struct OutStatus {
/* 0x00 */ u32 writeOffset;
/* 0x04 */ char unk04[0x1C];
/* 0x04 */ u8 unk04[0x1C];
}; // size == 0x20
static_assert(sizeof(OutStatus) == 0x20, "SinkInfoBase::OutStatus has the wrong size!");
@@ -162,19 +162,18 @@ public:
u8* GetParameter();
protected:
/// Type of this sink
Type type{Type::Invalid};
/// Is this sink in use?
bool in_use{};
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped{};
/// Node id for this sink
u32 node_id{};
/// State buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceState), sizeof(CircularBufferState))> state{};
/// Parameter buffer for this sink
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))>
parameter{};
std::array<u8, (std::max)(sizeof(DeviceInParameter), sizeof(CircularBufferInParameter))> parameter{};
/// Type of this sink
Type type{Type::Invalid};
/// Node id for this sink
u32 node_id{};
/// Is this sink in use?
bool in_use : 1 = false;
/// Is this sink's buffer unmapped? Circular only
bool buffer_unmapped : 1 = false;
};
} // namespace AudioCore::Renderer
+46 -21
View File
@@ -110,8 +110,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, hid_core{kernel}, cpu_manager{system},
reporter{system}, applet_manager{system}, frontend_applets{system}, profile_manager{} {}
: kernel{system}
, fs_controller{system}
, hid_core{kernel}
, cpu_manager{system}
, reporter{system}
, profile_manager{}
{}
u64 program_id;
@@ -124,6 +129,12 @@ struct System::Impl {
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
applet_manager.emplace(system);
frontend_applets.emplace(system);
apm_controller.emplace(core_timing);
arp_manager.emplace();
profile_manager.emplace();
// Create a default fs if one doesn't already exist.
if (virtual_filesystem == nullptr) {
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
@@ -133,7 +144,7 @@ struct System::Impl {
}
// Create default implementations of applets if one is not provided.
frontend_applets.SetDefaultAppletsIfMissing();
frontend_applets->SetDefaultAppletsIfMissing();
auto const is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
@@ -376,7 +387,7 @@ struct System::Impl {
// Register with applet manager
// All threads are started, begin main process execution, now that we're in the clear
applet_manager.CreateAndInsertByFrontendAppletParameters(std::move(process), params);
applet_manager->CreateAndInsertByFrontendAppletParameters(std::move(process), params);
if (Settings::values.gamecard_inserted) {
if (Settings::values.gamecard_current_game) {
@@ -428,13 +439,27 @@ struct System::Impl {
core_timing.SyncPause(false);
Network::CancelPendingSocketOperations();
kernel.SuspendEmulation(true);
kernel.CloseServices();
kernel.ShutdownCores();
// Notify services helpers of shutdown
audio_core->NotifyShutdown();
// Wait for threads/services to join
kernel.CloseServices();
// service shutdown
services.reset();
service_manager.reset();
frontend_applets.reset();
applet_manager.reset();
apm_controller.reset();
arp_manager.reset();
profile_manager.reset();
fs_controller.Reset();
cheat_engine.reset();
core_timing.ClearPendingEvents();
core_timing.Reset();
app_loader.reset();
audio_core.reset();
gpu_core.reset();
@@ -452,7 +477,7 @@ struct System::Impl {
}
// Reset all glue registrations
arp_manager.ResetAll();
arp_manager->ResetAll();
LOG_DEBUG(Core, "Shutdown OK");
}
@@ -482,13 +507,13 @@ struct System::Impl {
CpuManager cpu_manager;
Reporter reporter;
/// Applets
Service::AM::AppletManager applet_manager;
Service::AM::Frontend::FrontendAppletHolder frontend_applets;
std::optional<Service::AM::AppletManager> applet_manager;
std::optional<Service::AM::Frontend::FrontendAppletHolder> frontend_applets;
/// APM (Performance) services
Service::APM::Controller apm_controller{core_timing};
std::optional<Service::APM::Controller> apm_controller;
/// Service State
Service::Glue::ARPManager arp_manager;
Service::Account::ProfileManager profile_manager;
std::optional<Service::Glue::ARPManager> arp_manager;
std::optional<Service::Account::ProfileManager> profile_manager;
/// Network instance
Network::NetworkInstance network_instance;
Core::SpeedLimiter speed_limiter;
@@ -815,19 +840,19 @@ void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
}
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
impl->frontend_applets.SetFrontendAppletSet(std::move(set));
impl->frontend_applets->SetFrontendAppletSet(std::move(set));
}
Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() {
return impl->frontend_applets;
return *impl->frontend_applets;
}
const Service::AM::Frontend::FrontendAppletHolder& System::GetFrontendAppletHolder() const {
return impl->frontend_applets;
return *impl->frontend_applets;
}
Service::AM::AppletManager& System::GetAppletManager() {
return impl->applet_manager;
return *impl->applet_manager;
}
void System::SetContentProvider(std::unique_ptr<FileSys::ContentProviderUnion> provider) {
@@ -868,27 +893,27 @@ const Reporter& System::GetReporter() const {
}
Service::Glue::ARPManager& System::GetARPManager() {
return impl->arp_manager;
return *impl->arp_manager;
}
const Service::Glue::ARPManager& System::GetARPManager() const {
return impl->arp_manager;
return *impl->arp_manager;
}
Service::APM::Controller& System::GetAPMController() {
return impl->apm_controller;
return *impl->apm_controller;
}
const Service::APM::Controller& System::GetAPMController() const {
return impl->apm_controller;
return *impl->apm_controller;
}
Service::Account::ProfileManager& System::GetProfileManager() {
return impl->profile_manager;
return *impl->profile_manager;
}
const Service::Account::ProfileManager& System::GetProfileManager() const {
return impl->profile_manager;
return *impl->profile_manager;
}
void System::SetExitLocked(bool locked) {
+2 -2
View File
@@ -304,10 +304,10 @@ std::optional<s64> CoreTiming::Advance() {
void CoreTiming::Reset() {
paused = true;
pause_event.Set();
event.Set();
if (timer_thread.joinable()) {
timer_thread.request_stop();
pause_event.Set();
event.Set();
timer_thread.join();
}
has_started = false;
+8 -14
View File
@@ -9,7 +9,6 @@
#include "common/logging.h"
#include "common/uuid.h"
#include "core/core.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs/vfs.h"
@@ -62,24 +61,17 @@ SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
SaveDataFactory::~SaveDataFactory() = default;
std::string SaveDataFactory::GetSaveDataPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, u64 save_id) const {
if (type == SaveDataType::Account || type == SaveDataType::Device) {
const auto requested_id = title_id != 0 ? title_id : program_id;
const auto parent_id = system.GetContentProvider().GetParentApplicationId(requested_id);
title_id = parent_id.value_or(requested_id);
}
return GetFullPath(program_id, dir, space, type, title_id, user_id, save_id);
}
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory = GetSaveDataPath(space, meta.type, meta.program_id, meta.user_id, meta.system_save_data_id);
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
meta.user_id, meta.system_save_data_id);
return dir->CreateDirectoryRelative(save_directory);
}
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
const auto save_directory = GetSaveDataPath(space, meta.type, meta.program_id, meta.user_id, meta.system_save_data_id);
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
meta.user_id, meta.system_save_data_id);
auto out = dir->GetDirectoryRelative(save_directory);
@@ -162,7 +154,8 @@ std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future)
SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
u128 user_id) const {
const auto path = GetSaveDataPath(SaveDataSpaceId::User, type, title_id, user_id, 0);
const auto path =
GetFullPath(program_id, dir, SaveDataSpaceId::User, type, title_id, user_id, 0);
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
const auto size_file = relative_dir->GetFile(GetSaveDataSizeFileName());
@@ -180,7 +173,8 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id,
void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id,
SaveDataSize new_value) const {
const auto path = GetSaveDataPath(SaveDataSpaceId::User, type, title_id, user_id, 0);
const auto path =
GetFullPath(program_id, dir, SaveDataSpaceId::User, type, title_id, user_id, 0);
const auto relative_dir = GetOrCreateDirectoryRelative(dir, path);
const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName());
-1
View File
@@ -50,7 +50,6 @@ public:
void SetAutoCreate(bool state);
private:
std::string GetSaveDataPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, u64 save_id) const;
Core::System& system;
ProgramId program_id;
VirtualDir dir;
@@ -72,11 +72,9 @@ void GlobalSchedulerContext::UnregisterDummyThreadForWakeup(KThread* thread) noe
void GlobalSchedulerContext::WakeupWaitingDummyThreads(KernelCore& kernel) noexcept {
ASSERT(this->IsLocked());
if (m_woken_dummy_threads.size() > 0) {
for (auto* thread : m_woken_dummy_threads)
thread->DummyThreadEndWait(kernel);
m_woken_dummy_threads.clear();
}
for (auto* thread : m_woken_dummy_threads)
thread->DummyThreadEndWait(kernel);
m_woken_dummy_threads.clear();
}
} // namespace Kernel
-1
View File
@@ -59,7 +59,6 @@ Result TerminateChildren(KernelCore& kernel, KProcess* process, const KThread* t
KThread* cur_child = nullptr;
{
KScopedLightLock proc_lk(process->GetListLock());
auto& thread_list = process->GetThreadList();
for (auto it = thread_list.begin(); it != thread_list.end(); ++it) {
if (KThread* thread = std::addressof(*it); thread != thread_to_not_terminate) {
+4 -10
View File
@@ -1236,18 +1236,13 @@ namespace Kernel {
KScopedSchedulerLock sl{kernel};
// Determine if this is the first termination request.
const bool first_request = [&]() -> bool {
// Perform an atomic compare-and-swap from false to true.
bool expected = false;
return m_termination_requested.compare_exchange_strong(expected, true);
}();
// Perform an atomic compare-and-swap from false to true.
bool expected = false;
// If this is the first request, start termination procedure.
if (first_request) {
if (m_termination_requested.compare_exchange_strong(expected, true)) {
// If the thread is in initialized state, just change state to terminated.
if (this->GetState() == ThreadState::Initialized) {
m_thread_state = ThreadState::Terminated;
return ThreadState::Terminated;
return m_thread_state = ThreadState::Terminated;
}
// Register the terminating dpc.
@@ -1281,7 +1276,6 @@ namespace Kernel {
m_wait_queue->CancelWait(kernel, this, ResultTerminationRequested, true);
}
}
return this->GetState();
}
+35 -5
View File
@@ -4,6 +4,7 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <thread>
#include "common/assert.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_thread.h"
@@ -26,7 +27,15 @@ void KWorkerTask::DoWorkerTask(KernelCore& kernel) {
}
}
KWorkerTaskManager::KWorkerTaskManager() : m_waiting_thread(1, "KWorkerTaskManager") {}
KWorkerTaskManager::KWorkerTaskManager() {}
KWorkerTaskManager::~KWorkerTaskManager() {
if (m_waiting_thread.joinable()) {
m_waiting_thread.request_stop();
m_task_cv.notify_one();
m_waiting_thread.join();
}
}
void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task) {
ASSERT(type <= WorkerType::Count);
@@ -35,10 +44,31 @@ void KWorkerTaskManager::AddTask(KernelCore& kernel, WorkerType type, KWorkerTas
void KWorkerTaskManager::AddTask(KernelCore& kernel, KWorkerTask* task) {
KScopedSchedulerLock sl(kernel);
m_waiting_thread.QueueWork([&kernel, task]() {
// Do the task.
task->DoWorkerTask(kernel);
});
// spawn thread on demand
if (!m_waiting_thread.joinable()) {
LOG_INFO(Kernel, "spawning KWorkerTaskManager thread");
m_waiting_thread = std::jthread([&kernel, this](std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
KWorkerTask* t;
{
std::unique_lock lk{m_task_mutex};
m_task_cv.wait(lk);
if (stop_token.stop_requested())
break;
t = m_task_queue.back();
m_task_queue.pop_back();
}
t->DoWorkerTask(kernel);
}
});
}
{
std::scoped_lock lk{m_task_mutex};
m_task_queue.emplace_back(task);
}
m_task_cv.notify_one();
}
} // namespace Kernel
+10 -3
View File
@@ -1,8 +1,13 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <thread>
#include "common/common_types.h"
#include "common/thread_worker.h"
@@ -19,14 +24,16 @@ public:
};
KWorkerTaskManager();
~KWorkerTaskManager();
static void AddTask(KernelCore& kernel, WorkerType type, KWorkerTask* task);
private:
void AddTask(KernelCore& kernel, KWorkerTask* task);
std::jthread m_waiting_thread;
private:
Common::ThreadWorker m_waiting_thread;
std::mutex m_task_mutex;
std::condition_variable m_task_cv;
std::vector<KWorkerTask*> m_task_queue;
};
} // namespace Kernel
+16 -12
View File
@@ -80,7 +80,9 @@ struct KernelCore::Impl {
// so it will be statically given a TLS slot anyways.
static inline thread_local ThreadLocalData tls_data = {};
explicit Impl(Core::System& system_, KernelCore& kernel_) : system{system_} {
explicit Impl(Core::System& system_, KernelCore& kernel_)
: system{system_}
{
tls_data.lock = true;
}
@@ -967,12 +969,16 @@ Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
return *impl->hardware_timer;
}
KAutoObjectWithListContainer& KernelCore::ObjectListContainer() {
return *impl->global_object_list_container;
KAutoObjectWithListContainer* KernelCore::ObjectListContainer() {
if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
}
const KAutoObjectWithListContainer& KernelCore::ObjectListContainer() const {
return *impl->global_object_list_container;
const KAutoObjectWithListContainer* KernelCore::ObjectListContainer() const {
if (!impl->global_object_list_container)
return nullptr;
return std::addressof(*impl->global_object_list_container);
}
void KernelCore::PrepareReschedule(std::size_t id) {
@@ -1001,16 +1007,10 @@ void KernelCore::UnregisterInUseObject(KAutoObject* object) {
void KernelCore::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
auto* manager = server_manager.get();
{
if (!impl->is_shutting_down) {
std::scoped_lock lk{impl->server_lock};
if (impl->is_shutting_down) {
return;
}
impl->server_managers.emplace_back(std::move(server_manager));
}
manager->LoopProcess();
}
@@ -1255,6 +1255,10 @@ void KernelCore::SuspendEmulation(bool suspended) {
}
void KernelCore::ShutdownCores() {
// Notify shutdown (pre-emptively)
for (auto& sm : impl->server_managers) {
sm->NotifyShutdown();
}
impl->TerminateAllProcesses();
KScopedSchedulerLock lk{*this};
for (auto* thread : impl->shutdown_threads)
+2 -3
View File
@@ -175,9 +175,8 @@ public:
/// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id);
KAutoObjectWithListContainer& ObjectListContainer();
const KAutoObjectWithListContainer& ObjectListContainer() const;
KAutoObjectWithListContainer* ObjectListContainer();
const KAutoObjectWithListContainer* ObjectListContainer() const;
/// Registers all kernel objects with the global emulation state, this is purely for tracking
/// leaks after emulation has been shutdown.
+4 -3
View File
@@ -151,7 +151,8 @@ public:
const bool is_initialized = this->IsInitialized();
uintptr_t arg = 0;
if (is_initialized) {
kernel.ObjectListContainer().Unregister(this);
if (auto const olc = kernel.ObjectListContainer(); olc)
olc->Unregister(this);
arg = this->GetPostDestroyArgument();
this->Finalize(kernel);
}
@@ -175,7 +176,7 @@ public:
public:
static void InitializeSlabHeap(KernelCore& kernel, void* memory, size_t memory_size) {
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
kernel.ObjectListContainer().Initialize();
kernel.ObjectListContainer()->Initialize();
}
static Derived* Create(KernelCore& kernel) {
@@ -186,7 +187,7 @@ public:
}
static void Register(KernelCore& kernel, Derived* obj) {
return kernel.ObjectListContainer().Register(obj);
return kernel.ObjectListContainer()->Register(obj);
}
static size_t GetObjectSize(KernelCore& kernel) {
+9 -8
View File
@@ -26,24 +26,25 @@ EventObserver::EventObserver(Core::System& system, WindowSystem& window_system)
m_window_system.SetEventObserver(this);
m_wakeup_holder.SetUserData(static_cast<uintptr_t>(UserDataTag::WakeupEvent));
m_wakeup_holder.LinkToMultiWait(std::addressof(m_multi_wait));
m_thread = std::jthread([this](std::stop_token stop_token) {
Common::SetCurrentThreadName("am:EventObserver");
system.Kernel().RunOnGuestCoreProcess("am:EventObserver", [this]() {
auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) {
auto* signaled_holder = this->WaitSignaled(stop_token);
if (!signaled_holder)
if (stop_token.stop_requested() || !signaled_holder)
break;
this->Process(signaled_holder);
}
m_process_cv.notify_one();
});
}
EventObserver::~EventObserver() {
// Signal thread and wait for processing to finish.
if (m_thread.joinable()) {
// Signal thread and wait for processing to finish.
m_thread.request_stop();
m_wakeup_event.Signal(m_system.Kernel());
m_thread.join();
m_stop_source.request_stop();
m_wakeup_event.Signal(m_system.Kernel());
{
std::unique_lock lk{m_process_mutex};
m_process_cv.wait(lk);
}
// Free remaining owned sessions.
+5 -1
View File
@@ -6,6 +6,8 @@
#pragma once
#include <condition_variable>
#include <stop_token>
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/hle/service/kernel_helpers.h"
@@ -62,7 +64,9 @@ private:
MultiWait m_deferred_wait_list;
// Processing thread.
std::jthread m_thread{};
std::stop_source m_stop_source{};
std::mutex m_process_mutex;
std::condition_variable m_process_cv{};
};
} // namespace Service::AM
+4 -5
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
@@ -90,10 +90,9 @@ static Service::PSC::Time::LocationName GetTimeZoneString(
}
TimeManager::TimeManager(Core::System& system)
: m_steady_clock_resource{system}, m_time_zone_binary{system}, m_worker{
system,
m_steady_clock_resource,
m_file_timestamp_worker} {
: m_steady_clock_resource{system}, m_time_zone_binary{system}
, m_worker{system, m_steady_clock_resource, m_file_timestamp_worker}
{
m_time_m =
system.ServiceManager().GetService<Service::PSC::Time::ServiceManager>("time:m", true);
+151 -151
View File
@@ -20,15 +20,17 @@
namespace Service::Glue::Time {
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource,
FileTimestampWorker& file_timestamp_worker)
: m_system{system}, m_ctx{m_system, "Glue:TimeWorker"}, m_event{m_ctx.CreateEvent(
"Glue:TimeWorker:Event")},
m_steady_clock_resource{steady_clock_resource},
m_file_timestamp_worker{file_timestamp_worker}, m_timer_steady_clock{m_ctx.CreateEvent(
"Glue:TimeWorker:SteadyClockTimerEvent")},
m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")},
m_alarm_worker{m_system, m_steady_clock_resource}, m_pm_state_change_handler{m_alarm_worker} {
TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady_clock_resource, FileTimestampWorker& file_timestamp_worker)
: m_system{system}
, m_ctx{m_system, "Glue:TimeWorker"}
, m_event{m_ctx.CreateEvent("Glue:TimeWorker:Event")}
, m_steady_clock_resource{steady_clock_resource}
, m_file_timestamp_worker{file_timestamp_worker}
, m_timer_steady_clock{m_ctx.CreateEvent("Glue:TimeWorker:SteadyClockTimerEvent")}
, m_timer_file_system{m_ctx.CreateEvent("Glue:TimeWorker:FileTimeTimerEvent")}
, m_alarm_worker{m_system, m_steady_clock_resource}
, m_pm_state_change_handler{m_alarm_worker}
{
m_timer_steady_clock_timing_event = Core::Timing::CreateEvent(
"Time::SteadyClockEvent",
[this](s64 time,
@@ -47,14 +49,16 @@ TimeWorker::TimeWorker(Core::System& system, StandardSteadyClockResource& steady
}
TimeWorker::~TimeWorker() {
// Wait for processing to stop
m_stop_source.request_stop();
m_local_clock_event->Signal(m_system.Kernel());
m_network_clock_event->Signal(m_system.Kernel());
m_ephemeral_clock_event->Signal(m_system.Kernel());
std::this_thread::sleep_for(std::chrono::milliseconds(16));
m_thread.request_stop();
m_event->Signal(m_system.Kernel());
m_thread.join();
{
std::unique_lock lk{m_process_mutex};
m_process_cv.wait(lk);
}
m_ctx.CloseEvent(m_event);
m_system.CoreTiming().UnscheduleEvent(m_timer_steady_clock_timing_event);
@@ -122,166 +126,162 @@ void TimeWorker::Initialize(std::shared_ptr<Service::PSC::Time::StaticService> t
}
void TimeWorker::StartThread() {
m_thread = std::jthread(std::bind_front(&TimeWorker::ThreadFunc, this));
}
void TimeWorker::ThreadFunc(std::stop_token stop_token) {
Common::SetCurrentThreadName("TimeWorker");
Common::SetCurrentThreadPriority(Common::ThreadPriority::Low);
while (!stop_token.stop_requested()) {
enum class EventType : s32 {
Exit = 0,
PowerStateChange = 1,
SignalAlarms = 2,
UpdateLocalSystemClock = 3,
UpdateNetworkSystemClock = 4,
UpdateEphemeralSystemClock = 5,
UpdateSteadyClock = 6,
UpdateFileTimestamp = 7,
AutoCorrect = 8,
};
s32 index{};
if (m_pm_state_change_handler.m_priority != 0) {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent() // 1
);
} else {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent(), // 1
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
m_local_clock_event, // 3
m_network_clock_event, // 4
m_ephemeral_clock_event, // 5
&m_timer_steady_clock->GetReadableEvent(), // 6
&m_timer_file_system->GetReadableEvent(), // 7
m_standard_user_auto_correct_clock_event // 8
);
}
switch (static_cast<EventType>(index)) {
case EventType::Exit:
return;
case EventType::PowerStateChange:
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
if (m_pm_state_change_handler.m_priority <= 1) {
m_alarm_worker.OnPowerStateChanged();
m_system.Kernel().RunOnGuestCoreProcess("TimeWorker", [this]() {
auto const stop_token = m_stop_source.get_token();
while (!stop_token.stop_requested()) {
enum class EventType : s32 {
Exit = 0,
PowerStateChange = 1,
SignalAlarms = 2,
UpdateLocalSystemClock = 3,
UpdateNetworkSystemClock = 4,
UpdateEphemeralSystemClock = 5,
UpdateSteadyClock = 6,
UpdateFileTimestamp = 7,
AutoCorrect = 8,
};
s32 index{};
if (m_pm_state_change_handler.m_priority != 0) {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(
m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent() // 1
);
} else {
// TODO: gIPmModuleService::GetEvent() 1
index = WaitAny(
m_system.Kernel(),
&m_event->GetReadableEvent(), // 0
&m_alarm_worker.GetEvent(), // 1
&m_alarm_worker.GetTimerEvent().GetReadableEvent(), // 2
m_local_clock_event, // 3
m_network_clock_event, // 4
m_ephemeral_clock_event, // 5
&m_timer_steady_clock->GetReadableEvent(), // 6
&m_timer_file_system->GetReadableEvent(), // 7
m_standard_user_auto_correct_clock_event // 8
);
}
break;
case EventType::SignalAlarms:
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
m_time_m->CheckAndSignalAlarms();
break;
if (stop_token.stop_requested())
break;
case EventType::UpdateLocalSystemClock: {
m_local_clock_event->Clear(m_system.Kernel());
switch (EventType(index)) {
case EventType::Exit:
return;
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
case EventType::PowerStateChange:
m_alarm_worker.GetEvent().Clear(m_system.Kernel());
if (m_pm_state_change_handler.m_priority <= 1) {
m_alarm_worker.OnPowerStateChanged();
}
break;
m_set_sys->SetUserSystemClockContext(context);
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
case EventType::SignalAlarms:
m_alarm_worker.GetTimerEvent().Clear(m_system.Kernel());
m_time_m->CheckAndSignalAlarms();
break;
case EventType::UpdateNetworkSystemClock: {
m_network_clock_event->Clear(m_system.Kernel());
case EventType::UpdateLocalSystemClock: {
m_local_clock_event->Clear(m_system.Kernel());
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_local_clock->GetSystemClockContext(&context));
m_set_sys->SetNetworkSystemClockContext(context);
s64 time{};
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
m_set_sys->SetUserSystemClockContext(context);
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
[[maybe_unused]] auto offset_before{
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
// TODO system report "standard_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_network_clock_context = context;
if (!m_ig_report_network_clock_context_set) {
m_ig_report_network_clock_context_set = true;
}
case EventType::UpdateNetworkSystemClock: {
m_network_clock_event->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
Service::PSC::Time::SystemClockContext context{};
R_ASSERT(m_network_clock->GetSystemClockContext(&context));
case EventType::UpdateEphemeralSystemClock: {
m_ephemeral_clock_event->Clear(m_system.Kernel());
m_set_sys->SetNetworkSystemClockContext(context);
Service::PSC::Time::SystemClockContext context{};
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
if (res != ResultSuccess) {
s64 time{};
if (m_network_clock->GetCurrentTime(&time) != ResultSuccess) {
break;
}
[[maybe_unused]] auto offset_before{
m_ig_report_network_clock_context_set ? m_report_network_clock_context.offset : 0};
// TODO system report "standard_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_network_clock_context = context;
if (!m_ig_report_network_clock_context_set) {
m_ig_report_network_clock_context_set = true;
}
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
}
s64 time{};
res = m_ephemeral_clock->GetCurrentTime(&time);
if (res != ResultSuccess) {
case EventType::UpdateEphemeralSystemClock: {
m_ephemeral_clock_event->Clear(m_system.Kernel());
Service::PSC::Time::SystemClockContext context{};
auto res = m_ephemeral_clock->GetSystemClockContext(&context);
if (res != ResultSuccess) {
break;
}
s64 time{};
res = m_ephemeral_clock->GetCurrentTime(&time);
if (res != ResultSuccess) {
break;
}
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
? m_report_ephemeral_clock_context.offset
: 0};
// TODO system report "ephemeral_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_ephemeral_clock_context = context;
if (!m_ig_report_ephemeral_clock_context_set) {
m_ig_report_ephemeral_clock_context_set = true;
}
break;
}
[[maybe_unused]] auto offset_before{m_ig_report_ephemeral_clock_context_set
? m_report_ephemeral_clock_context.offset
: 0};
// TODO system report "ephemeral_netclock_operation"
// "clock_time" = time
// "context_offset_before" = offset_before
// "context_offset_after" = context.offset
m_report_ephemeral_clock_context = context;
if (!m_ig_report_ephemeral_clock_context_set) {
m_ig_report_ephemeral_clock_context_set = true;
case EventType::UpdateSteadyClock:
m_timer_steady_clock->Clear(m_system.Kernel());
m_steady_clock_resource.UpdateTime();
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
break;
case EventType::UpdateFileTimestamp:
m_timer_file_system->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
case EventType::AutoCorrect: {
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
bool automatic_correction{};
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(&automatic_correction));
Service::PSC::Time::SteadyClockTimePoint time_point{};
R_ASSERT(m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
break;
}
default:
UNREACHABLE();
}
break;
}
case EventType::UpdateSteadyClock:
m_timer_steady_clock->Clear(m_system.Kernel());
m_steady_clock_resource.UpdateTime();
m_time_m->SetStandardSteadyClockBaseTime(m_steady_clock_resource.GetTime());
break;
case EventType::UpdateFileTimestamp:
m_timer_file_system->Clear(m_system.Kernel());
m_file_timestamp_worker.SetFilesystemPosixTime();
break;
case EventType::AutoCorrect: {
m_standard_user_auto_correct_clock_event->Clear(m_system.Kernel());
bool automatic_correction{};
R_ASSERT(m_time_sm->IsStandardUserSystemClockAutomaticCorrectionEnabled(
&automatic_correction));
Service::PSC::Time::SteadyClockTimePoint time_point{};
R_ASSERT(
m_time_sm->GetStandardUserSystemClockAutomaticCorrectionUpdatedTime(&time_point));
m_set_sys->SetUserSystemClockAutomaticCorrectionEnabled(automatic_correction);
m_set_sys->SetUserSystemClockAutomaticCorrectionUpdatedTime(time_point);
break;
}
default:
UNREACHABLE();
}
}
m_process_cv.notify_one();
});
}
} // namespace Service::Glue::Time
+8 -4
View File
@@ -1,8 +1,12 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include "common/common_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/glue/time/alarm_worker.h"
@@ -33,17 +37,17 @@ public:
void StartThread();
private:
template <typename T>
T GetSettingsItemValue(const std::string& category, const std::string& name);
void ThreadFunc(std::stop_token stop_token);
Core::System& m_system;
KernelHelpers::ServiceContext m_ctx;
std::shared_ptr<Service::Set::ISystemSettingsServer> m_set_sys;
std::jthread m_thread;
std::mutex m_process_mutex;
std::condition_variable m_process_cv;
std::stop_source m_stop_source;
Kernel::KEvent* m_event{};
std::shared_ptr<Service::PSC::Time::ServiceManager> m_time_m;
std::shared_ptr<Service::PSC::Time::StaticService> m_time_sm;
+8 -2
View File
@@ -253,6 +253,13 @@ void ServerManager::StartAdditionalHostThreads(const char* name, size_t num_thre
}
}
/// @brief Notifies that the system is shutting down (pre-emptively terminate threads)
void ServerManager::NotifyShutdown() {
m_stop_source.request_stop();
// Wake them up regardless
m_wakeup_event->Signal(m_system.Kernel());
}
Result ServerManager::LoopProcess() {
SCOPE_EXIT {
m_stopped.Set();
@@ -285,9 +292,8 @@ MultiWaitHolder* ServerManager::WaitSignaled() {
this->LinkDeferred();
// If we're done, return before we start waiting.
if (m_stop_source.stop_requested()) {
if (m_stop_source.stop_requested())
return nullptr;
}
auto* selected = m_multi_wait.WaitAny(m_system.Kernel());
if (selected == std::addressof(*m_wakeup_holder)) {
+4
View File
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -49,6 +52,7 @@ public:
Result LoopProcess();
void StartAdditionalHostThreads(const char* name, size_t num_threads);
void NotifyShutdown();
static void RunServer(std::unique_ptr<ServerManager>&& server);
+1 -1
View File
@@ -117,7 +117,6 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"friends", &Friend::LoopProcess},
{"settings", &Set::LoopProcess},
{"psc", &PSC::LoopProcess},
{"glue", &Glue::LoopProcess},
{"grc", &GRC::LoopProcess},
{"hid", &HID::LoopProcess},
{"jit", &JIT::LoopProcess},
@@ -153,6 +152,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"usb", &USB::LoopProcess},
{"i2c", &I2C::LoopProcess},
{"gpio", &GPIO::LoopProcess},
{"glue", &Glue::LoopProcess},
})
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
}
+23 -33
View File
@@ -11,6 +11,7 @@
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/service/vi/conductor.h"
#include "core/hle/service/vi/container.h"
#include "core/hle/service/vi/display_list.h"
@@ -29,24 +30,29 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
});
if (system.IsMulticore()) {
m_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_signal.Set();
return std::chrono::nanoseconds(this->GetNextTicks());
});
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_signal.Set();
return std::chrono::nanoseconds(this->GetNextTicks());
});
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
m_thread = std::jthread([this](std::stop_token token) { this->VsyncThread(token); });
} else {
m_event = Core::Timing::CreateEvent(
"ScreenComposition",
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
m_thread = system.Kernel().RunOnHostCoreThread("VSyncThread", [this]() {
auto const stop_token = m_thread.get_stop_token();
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
while (!stop_token.stop_requested()) {
m_signal.Wait();
if (stop_token.stop_requested() || m_system.IsShuttingDown()) {
break;
}
this->ProcessVsync();
return std::chrono::nanoseconds(this->GetNextTicks());
});
}
});
} else {
m_event = Core::Timing::CreateEvent("ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
this->ProcessVsync();
return std::chrono::nanoseconds(this->GetNextTicks());
});
system.CoreTiming().ScheduleLoopingEvent(FrameNs, FrameNs, m_event);
}
@@ -54,10 +60,10 @@ Conductor::Conductor(Core::System& system, Container& container, DisplayList& di
Conductor::~Conductor() {
m_system.CoreTiming().UnscheduleEvent(m_event);
if (m_system.IsMulticore()) {
if (m_thread.joinable()) {
m_thread.request_stop();
m_signal.Set();
m_thread.join();
}
}
@@ -83,22 +89,6 @@ void Conductor::ProcessVsync() {
}
}
void Conductor::VsyncThread(std::stop_token token) {
Common::SetCurrentThreadName("VSyncThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
while (!token.stop_requested()) {
m_signal.Wait();
if (m_system.IsShuttingDown()) {
return;
}
this->ProcessVsync();
}
}
s64 Conductor::GetNextTicks() const {
const auto& settings = Settings::values;
auto speed_scale = 1.f;
+14
View File
@@ -1,6 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// gcc14 bug
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include "common/assert.h"
#include "common/scope_exit.h"
#include "core/memory/dmnt_cheat_types.h"
@@ -1266,3 +1275,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
}
} // namespace Core::Memory
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
+11 -9
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
@@ -254,14 +254,16 @@ struct UnrecognizedInstruction {
struct CheatVmOpcode {
bool begin_conditional_block{};
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
opcode{};
std::variant<
std::monostate,
StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction
> opcode{};
};
class DmntCheatVm {
@@ -24,29 +24,65 @@
namespace Dynarmic::Backend::Arm64 {
template<auto mfp, typename T>
static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
static void* EmitCallReadTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
oaknut::Label l_addr, l_this;
void* target = code.xptr<void*>();
// params = { this, vaddr, bitsize }
code.LDR(X0, l_this);
// X1 = vaddr
code.MOV(X2, bitsize);
code.LDR(Xscratch0, l_addr);
code.BR(Xscratch0);
code.align(8);
code.l(l_this);
code.dx(info.this_ptr);
code.l(l_addr);
code.dx(info.fn_ptr);
return target;
}
template<auto mfp, typename T>
static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
static void* EmitCallWriteTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
oaknut::Label l_addr, l_this;
void* target = code.xptr<void*>();
// params = { this, vaddr, value, bitsize }
code.LDR(X0, l_this);
// X1 = vaddr
// X2 = value
code.MOV(X3, bitsize);
code.LDR(Xscratch0, l_addr);
code.BR(Xscratch0);
code.align(8);
code.l(l_this);
code.dx(info.this_ptr);
code.l(l_addr);
code.dx(info.fn_ptr);
return target;
}
template<auto mfp, typename T>
static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
oaknut::Label l_addr, l_this;
void* target = code.xptr<void*>();
code.LDR(X0, l_this);
code.LDR(Xscratch0, l_addr);
code.BR(Xscratch0);
code.align(8);
code.l(l_this);
code.dx(info.this_ptr);
code.l(l_addr);
code.dx(info.fn_ptr);
return target;
}
template<auto mfp, typename T>
static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
@@ -59,6 +95,7 @@ static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_
ABI_PushRegisters(code, save_regs, 0);
code.LDR(X0, l_this);
code.MOV(X1, Xscratch0);
code.MOV(X2, bitsize);
code.LDR(Xscratch0, l_addr);
code.BLR(Xscratch0);
code.MOV(Xscratch0, X0);
@@ -101,7 +138,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const
}
template<auto mfp, typename T>
static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
@@ -115,6 +152,7 @@ static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this
code.LDR(X0, l_this);
code.MOV(X1, Xscratch0);
code.MOV(X2, Xscratch1);
code.MOV(X3, bitsize);
code.LDR(Xscratch0, l_addr);
code.BLR(Xscratch0);
ABI_PopRegisters(code, save_regs, 0);
@@ -176,14 +214,27 @@ void A32AddressSpace::EmitPrelude() {
UnprotectCodeMemory();
prelude_info.read_memory = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks);
prelude_info.wrapped_read_memory = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks);
prelude_info.read_memory_8 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8));
prelude_info.read_memory_16 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16));
prelude_info.read_memory_32 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32));
prelude_info.read_memory_64 = EmitCallReadTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64));
prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8));
prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16));
prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32));
prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64));
prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u8>(code, conf);
prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u16>(code, conf);
prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u32>(code, conf);
prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u64>(code, conf);
prelude_info.write_memory = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks);
prelude_info.wrapped_write_memory = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks);
prelude_info.write_memory_8 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8));
prelude_info.write_memory_16 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16));
prelude_info.write_memory_32 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32));
prelude_info.write_memory_64 = EmitCallWriteTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64));
prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8));
prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16));
prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32));
prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64));
prelude_info.exclusive_write_memory_8 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive8, u8>(code, conf);
prelude_info.exclusive_write_memory_16 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive16, u16>(code, conf);
prelude_info.exclusive_write_memory_32 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive32, u32>(code, conf);
@@ -23,29 +23,65 @@
namespace Dynarmic::Backend::Arm64 {
template<auto mfp, typename T>
static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
static void* EmitCallReadTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
oaknut::Label l_addr, l_this;
void* target = code.xptr<void*>();
// params = { this, vaddr, bitsize }
code.LDR(X0, l_this);
// X1 = vaddr
code.MOV(X2, bitsize);
code.LDR(Xscratch0, l_addr);
code.BR(Xscratch0);
code.align(8);
code.l(l_this);
code.dx(info.this_ptr);
code.l(l_addr);
code.dx(info.fn_ptr);
return target;
}
template<auto mfp, typename T>
static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
static void* EmitCallWriteTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
oaknut::Label l_addr, l_this;
void* target = code.xptr<void*>();
// params = { this, vaddr, value, bitsize }
code.LDR(X0, l_this);
// X1 = vaddr
// X2 = value
code.MOV(X3, bitsize);
code.LDR(Xscratch0, l_addr);
code.BR(Xscratch0);
code.align(8);
code.l(l_this);
code.dx(info.this_ptr);
code.l(l_addr);
code.dx(info.fn_ptr);
return target;
}
template<auto mfp, typename T>
static void* EmitCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
oaknut::Label l_addr, l_this;
void* target = code.xptr<void*>();
code.LDR(X0, l_this);
code.LDR(Xscratch0, l_addr);
code.BR(Xscratch0);
code.align(8);
code.l(l_this);
code.dx(info.this_ptr);
code.l(l_addr);
code.dx(info.fn_ptr);
return target;
}
template<auto mfp, typename T>
static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
@@ -58,6 +94,7 @@ static void* EmitWrappedReadCallTrampoline(oaknut::CodeGenerator& code, T* this_
ABI_PushRegisters(code, save_regs, 0);
code.LDR(X0, l_this);
code.MOV(X1, Xscratch0);
code.MOV(X2, bitsize);
code.LDR(Xscratch0, l_addr);
code.BLR(Xscratch0);
code.MOV(Xscratch0, X0);
@@ -100,7 +137,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const
}
template<auto mfp, typename T>
static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_) {
static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this_, size_t bitsize) {
using namespace oaknut::util;
const auto info = Devirtualize<mfp>(this_);
@@ -114,6 +151,7 @@ static void* EmitWrappedWriteCallTrampoline(oaknut::CodeGenerator& code, T* this
code.LDR(X0, l_this);
code.MOV(X1, Xscratch0);
code.MOV(X2, Xscratch1);
code.MOV(X3, bitsize);
code.LDR(Xscratch0, l_addr);
code.BLR(Xscratch0);
ABI_PopRegisters(code, save_regs, 0);
@@ -342,18 +380,30 @@ void A64AddressSpace::EmitPrelude() {
UnprotectCodeMemory();
prelude_info.read_memory = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks);
prelude_info.read_memory_8 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8));
prelude_info.read_memory_16 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16));
prelude_info.read_memory_32 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32));
prelude_info.read_memory_64 = EmitCallReadTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64));
prelude_info.read_memory_128 = EmitRead128CallTrampoline(code, conf.callbacks);
prelude_info.wrapped_read_memory = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks);
prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u8));
prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u16));
prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u32));
prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks, sizeof(u64));
prelude_info.wrapped_read_memory_128 = EmitWrappedRead128CallTrampoline(code, conf.callbacks);
prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u8>(code, conf);
prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u16>(code, conf);
prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u32>(code, conf);
prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u64>(code, conf);
prelude_info.exclusive_read_memory_128 = EmitExclusiveRead128CallTrampoline(code, conf);
prelude_info.write_memory = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks);
prelude_info.write_memory_8 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8));
prelude_info.write_memory_16 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16));
prelude_info.write_memory_32 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32));
prelude_info.write_memory_64 = EmitCallWriteTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64));
prelude_info.write_memory_128 = EmitWrite128CallTrampoline(code, conf.callbacks);
prelude_info.wrapped_write_memory = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks);
prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u8));
prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u16));
prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u32));
prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks, sizeof(u64));
prelude_info.wrapped_write_memory_128 = EmitWrappedWrite128CallTrampoline(code, conf.callbacks);
prelude_info.exclusive_write_memory_8 = EmitExclusiveWriteCallTrampoline<&A64::UserCallbacks::MemoryWriteExclusive8, u8>(code, conf);
prelude_info.exclusive_write_memory_16 = EmitExclusiveWriteCallTrampoline<&A64::UserCallbacks::MemoryWriteExclusive16, u16>(code, conf);
@@ -147,44 +147,37 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) {
break;
// { this, vaddr, size }
case LinkTarget::ReadMemory8:
c.LDR(X2, 1);
c.BL(prelude_info.read_memory);
c.BL(prelude_info.read_memory_8);
break;
case LinkTarget::ReadMemory16:
c.LDR(X2, 2);
c.BL(prelude_info.read_memory);
c.BL(prelude_info.read_memory_16);
break;
case LinkTarget::ReadMemory32:
c.LDR(X2, 4);
c.BL(prelude_info.read_memory);
c.BL(prelude_info.read_memory_32);
break;
case LinkTarget::ReadMemory64:
c.LDR(X2, 8);
c.BL(prelude_info.read_memory);
c.BL(prelude_info.read_memory_64);
break;
case LinkTarget::ReadMemory128:
c.BL(prelude_info.read_memory_128);
break;
// { this, vaddr, size }
case LinkTarget::WrappedReadMemory8:
c.LDR(X2, 1);
c.BL(prelude_info.wrapped_read_memory);
c.BL(prelude_info.wrapped_read_memory_8);
break;
case LinkTarget::WrappedReadMemory16:
c.LDR(X2, 2);
c.BL(prelude_info.wrapped_read_memory);
c.BL(prelude_info.wrapped_read_memory_16);
break;
case LinkTarget::WrappedReadMemory32:
c.LDR(X2, 4);
c.BL(prelude_info.wrapped_read_memory);
c.BL(prelude_info.wrapped_read_memory_32);
break;
case LinkTarget::WrappedReadMemory64:
c.LDR(X2, 8);
c.BL(prelude_info.wrapped_read_memory);
c.BL(prelude_info.wrapped_read_memory_64);
break;
case LinkTarget::WrappedReadMemory128:
c.BL(prelude_info.wrapped_read_memory_128);
break;
//
case LinkTarget::ExclusiveReadMemory8:
c.BL(prelude_info.exclusive_read_memory_8);
break;
@@ -202,43 +195,37 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) {
break;
// { this, vaddr, value, size }
case LinkTarget::WriteMemory8:
c.LDR(X3, 1);
c.BL(prelude_info.write_memory);
c.BL(prelude_info.write_memory_8);
break;
case LinkTarget::WriteMemory16:
c.LDR(X3, 2);
c.BL(prelude_info.write_memory);
c.BL(prelude_info.write_memory_16);
break;
case LinkTarget::WriteMemory32:
c.LDR(X3, 4);
c.BL(prelude_info.write_memory);
c.BL(prelude_info.write_memory_32);
break;
case LinkTarget::WriteMemory64:
c.LDR(X3, 8);
c.BL(prelude_info.write_memory);
c.BL(prelude_info.write_memory_64);
break;
case LinkTarget::WriteMemory128:
c.BL(prelude_info.write_memory_128);
break;
//
case LinkTarget::WrappedWriteMemory8:
c.LDR(X3, 1);
c.BL(prelude_info.wrapped_write_memory);
c.BL(prelude_info.wrapped_write_memory_8);
break;
case LinkTarget::WrappedWriteMemory16:
c.LDR(X3, 2);
c.BL(prelude_info.wrapped_write_memory);
c.BL(prelude_info.wrapped_write_memory_16);
break;
case LinkTarget::WrappedWriteMemory32:
c.LDR(X3, 4);
c.BL(prelude_info.wrapped_write_memory);
c.BL(prelude_info.wrapped_write_memory_32);
break;
case LinkTarget::WrappedWriteMemory64:
c.LDR(X3, 8);
c.BL(prelude_info.wrapped_write_memory);
c.BL(prelude_info.wrapped_write_memory_64);
break;
case LinkTarget::WrappedWriteMemory128:
c.BL(prelude_info.wrapped_write_memory_128);
break;
//
case LinkTarget::ExclusiveWriteMemory8:
c.BL(prelude_info.exclusive_write_memory_8);
break;
@@ -93,18 +93,30 @@ protected:
void* return_to_dispatcher;
void* return_from_run_code;
void* read_memory;
void* read_memory_8;
void* read_memory_16;
void* read_memory_32;
void* read_memory_64;
void* read_memory_128;
void* wrapped_read_memory;
void* wrapped_read_memory_8;
void* wrapped_read_memory_16;
void* wrapped_read_memory_32;
void* wrapped_read_memory_64;
void* wrapped_read_memory_128;
void* exclusive_read_memory_8;
void* exclusive_read_memory_16;
void* exclusive_read_memory_32;
void* exclusive_read_memory_64;
void* exclusive_read_memory_128;
void* write_memory;
void* write_memory_8;
void* write_memory_16;
void* write_memory_32;
void* write_memory_64;
void* write_memory_128;
void* wrapped_write_memory;
void* wrapped_write_memory_8;
void* wrapped_write_memory_16;
void* wrapped_write_memory_32;
void* wrapped_write_memory_64;
void* wrapped_write_memory_128;
void* exclusive_write_memory_8;
void* exclusive_write_memory_16;
@@ -170,13 +170,19 @@ template<>
// check for marked bit, use as unmapped if marked
if (ctx.conf.page_table_marked_bit) {
code.bt(page, *ctx.conf.page_table_marked_bit);
code.jc(abort, code.T_NEAR);
auto const marked_bit = *ctx.conf.page_table_marked_bit;
if (s64(s32(1 << marked_bit)) == s64(1 << marked_bit)) {
code.test(page, s32(1 << marked_bit));
code.jnz(abort, code.T_NEAR);
} else {
code.bt(page, marked_bit);
code.jc(abort, code.T_NEAR);
}
}
// mask away attributes
if (ctx.conf.page_table_pointer_mask == 0) {
code.test(page, page);
} else if (std::in_range<s32>(ctx.conf.page_table_pointer_mask)) {
} else if (s64(s32(ctx.conf.page_table_pointer_mask)) == s64(ctx.conf.page_table_pointer_mask)) {
code.and_(page, ctx.conf.page_table_pointer_mask);
} else {
code.mov(tmp, ctx.conf.page_table_pointer_mask);
+18 -4
View File
@@ -761,6 +761,9 @@ void EmulatedController::StartMotionCalibration() {
}
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) {
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
if (index >= controller.button_values.size()) {
return;
}
@@ -913,10 +916,21 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
break;
}
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
const auto& player = Settings::values.players.GetValue()[player_index];
if (player.connected) {
Connect();
if (!is_connected) {
if (npad_type == NpadStyleIndex::Handheld) {
if (npad_id_type == NpadIdType::Handheld) {
Connect();
controller_connected[player_index] = true;
}
} else if (npad_type != NpadStyleIndex::Handheld) {
if (npad_id_type == NpadIdType::Player1) {
Connect();
controller_connected[player_index] = true;
} else if (player.connected && !controller_connected[player_index]) {
Connect();
controller_connected[player_index] = true;
}
}
}
TriggerOnChange(ControllerTriggerType::Button, true);
@@ -22,6 +22,7 @@
#include "common/settings.h"
#include "common/vector_math.h"
#include "hid_core/frontend/motion_input.h"
#include "hid_core/hid_core.h"
#include "hid_core/hid_types.h"
#include "hid_core/irsensor/irs_types.h"
@@ -584,6 +585,7 @@ private:
std::array<VibrationValue, 2> last_vibration_value{DEFAULT_VIBRATION_VALUE,
DEFAULT_VIBRATION_VALUE};
std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{};
std::array<bool, HIDCore::available_controllers> controller_connected{};
// Atomically synched values
std::atomic<HID::NpadStyleIndex> npad_type{HID::NpadStyleIndex::None};
@@ -79,8 +79,10 @@ void NpadAbstractButtonHandler::UpdateAllButtonLifo() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
if (auto const shfmt = data->shared_memory_format; shfmt) {
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
}
}
@@ -88,19 +90,19 @@ void NpadAbstractButtonHandler::UpdateCoreBatteryState() {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
for (std::size_t i = 0; i < AruidIndexMax; i++) {
auto* data = applet_resource_holder->applet_resource->GetAruidDataByIndex(i);
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
if (auto const shfmt = data->shared_memory_format; shfmt) {
auto& npad_entry = shfmt->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, data->aruid);
}
}
}
void NpadAbstractButtonHandler::UpdateButtonState(u64 aruid) {
Core::HID::NpadIdType npad_id = properties_handler->GetNpadId();
auto* data = applet_resource_holder->applet_resource->GetAruidData(aruid);
if (data == nullptr) {
return;
if (auto data = applet_resource_holder->applet_resource->GetAruidData(aruid); data) {
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, aruid);
}
auto& npad_entry = data->shared_memory_format->npad.npad_entry[NpadIdTypeToIndex(npad_id)];
UpdateButtonLifo(npad_entry, aruid);
}
Result NpadAbstractButtonHandler::SetHomeProtection(bool is_enabled, u64 aruid) {
@@ -496,31 +496,30 @@ void SetupCapabilities(const Profile& profile, const Info& info, EmitContext& ct
}
void PatchPhiNodes(IR::Program& program, EmitContext& ctx) {
// Flatten all leading PHIs from each block into a vector
std::vector<IR::Inst*> phi_instructions;
for (IR::Block* block : program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (it->GetOpcode() != IR::Opcode::Phi)
break;
phi_instructions.push_back(&*it);
}
}
if (phi_instructions.empty()) {
return; // nothing to patch
}
// Start "before" first PHI; advance on phi_arg == 0
size_t phi_index = static_cast<size_t>(-1);
ctx.PatchDeferredPhi([&](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
if (phi_arg == 0) {
++phi_index;
}
IR::Inst* phi = phi_instructions[phi_index];
return { ctx.Def(phi->Arg(phi_arg)), parent };
});
// Flatten all leading PHIs from each block into a vector
std::vector<IR::Inst*> phi_instructions;
for (IR::Block* block : program.blocks) {
for (auto it = block->begin(); it != block->end(); ++it) {
if (it->GetOpcode() != IR::Opcode::Phi)
break;
phi_instructions.push_back(&*it);
}
}
if (phi_instructions.empty()) {
return; // nothing to patch
}
// Start "before" first PHI; advance on phi_arg == 0
size_t phi_index = size_t(-1);
ctx.PatchDeferredPhi([&ctx, &phi_index, phi_insts = std::move(phi_instructions)](size_t phi_arg, Id parent) -> std::pair<Id, Id> {
if (phi_arg == 0) {
++phi_index;
}
IR::Inst* phi = phi_insts[phi_index];
return { ctx.Def(phi->Arg(phi_arg)), parent };
});
}
} // Anonymous namespace
std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_info, IR::Program& program, Bindings& bindings) {
+122
View File
@@ -0,0 +1,122 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <chrono>
#include <iostream>
#include <memory>
#include <mutex>
#include <regex>
#include <string>
#include "common/logging.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/cpu_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/file_sys/vfs/vfs_real.h"
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "frontend_common/config.h"
#include "input_common/main.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_null.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3_vk.h"
class EmuWindow_Headless : public Core::Frontend::EmuWindow {
public:
explicit EmuWindow_Headless() = default;
~EmuWindow_Headless() = default;
bool IsShown() const override {
return true;
}
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override {
}
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
return std::make_unique<DummyContext>();
}
};
int main(int argc, char *argv[]) {
struct {
Core::System system{};
EmuWindow_Headless emu_window{};
} state = {};
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();
if (argc < 2) {
LOG_CRITICAL(Frontend, "Usage: {} [ms-to-run] [file]", argv[0]);
return EXIT_FAILURE;
}
std::chrono::milliseconds time_quanta = std::chrono::milliseconds{atoi(argv[1])};
auto const time_end = std::chrono::steady_clock::now() + time_quanta;
std::string filepath = argv[2];
// apply the log_filter setting
// the logger was initialized before and doesn't pick up the filter on its own
Common::Log::Filter filter;
filter.ParseFilterString("*:Info");
Common::Log::SetGlobalFilter(filter);
if (filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
return EXIT_FAILURE;
}
state.system.Initialize();
InputCommon::InputSubsystem input_subsystem{};
// Apply the command line arguments
state.system.ApplySettings();
Settings::values.renderer_backend.SetValue(Settings::RendererBackend::Null);
state.system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
state.system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
state.system.GetFileSystemController().CreateFactories(*state.system.GetFilesystem());
state.system.GetUserChannel().clear();
Service::AM::FrontendAppletParameters load_parameters{
.applet_id = Service::AM::AppletId::Application,
};
if (auto const load_result = state.system.Load(state.emu_window, filepath, load_parameters); load_result != Core::SystemResultStatus::Success) {
LOG_CRITICAL(Frontend, "load result = {}", u32(load_result));
// shutdown
void(state.system.Pause());
state.system.DetachDebugger();
state.system.ShutdownMainProcess();
return EXIT_FAILURE;
}
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
state.system.GPU().Start();
state.system.GetCpuManager().OnGpuReady();
// don't do anything, SDL3 already exists for us :D
state.system.RegisterExitCallback([] {});
void(state.system.Run());
if (state.system.DebuggerEnabled())
state.system.InitializeDebugger();
while (state.system.IsPoweredOn() && !state.system.GetExitRequested()) {
auto const time_now = std::chrono::steady_clock::now();
if (time_now > time_end)
break;
}
// shutdown
void(state.system.Pause());
state.system.DetachDebugger();
state.system.ShutdownMainProcess();
return EXIT_SUCCESS;
}
#define VMA_IMPLEMENTATION
#include "video_core/vulkan_common/vma.h"