diff options
Diffstat (limited to 'src/core')
24 files changed, 699 insertions, 92 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 3f3fdbd55..30eb9d82e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -271,6 +271,7 @@ add_library(core STATIC hle/service/filesystem/fsp_srv.h hle/service/fgm/fgm.cpp hle/service/fgm/fgm.h + hle/service/friend/errors.h hle/service/friend/friend.cpp hle/service/friend/friend.h hle/service/friend/interface.cpp @@ -430,6 +431,8 @@ add_library(core STATIC hle/service/time/interface.h hle/service/time/time.cpp hle/service/time/time.h + hle/service/time/time_sharedmemory.cpp + hle/service/time/time_sharedmemory.h hle/service/usb/usb.cpp hle/service/usb/usb.h hle/service/vi/display/vi_display.cpp @@ -477,6 +480,8 @@ add_library(core STATIC settings.h telemetry_session.cpp telemetry_session.h + tools/freezer.cpp + tools/freezer.h ) create_target_directory_groups(core) diff --git a/src/core/core.cpp b/src/core/core.cpp index df26eb109..262411db8 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -33,6 +33,7 @@ #include "core/reporter.h" #include "core/settings.h" #include "core/telemetry_session.h" +#include "core/tools/freezer.h" #include "file_sys/cheat_engine.h" #include "file_sys/patch_manager.h" #include "video_core/debug_utils/debug_utils.h" @@ -300,6 +301,7 @@ struct System::Impl { bool is_powered_on = false; std::unique_ptr<FileSys::CheatEngine> cheat_engine; + std::unique_ptr<Tools::Freezer> memory_freezer; /// Frontend applets Service::AM::Applets::AppletManager applet_manager; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 41adb2302..a58f7b131 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -56,12 +56,12 @@ void CoreTiming::Initialize() { } void CoreTiming::Shutdown() { - MoveEvents(); ClearPendingEvents(); UnregisterAllEvents(); } EventType* CoreTiming::RegisterEvent(const std::string& name, TimedCallback callback) { + std::lock_guard guard{inner_mutex}; // check for existing type with same name. // we want event type names to remain unique so that we can use them for serialization. ASSERT_MSG(event_types.find(name) == event_types.end(), @@ -82,6 +82,7 @@ void CoreTiming::UnregisterAllEvents() { void CoreTiming::ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) { ASSERT(event_type != nullptr); + std::lock_guard guard{inner_mutex}; const s64 timeout = GetTicks() + cycles_into_future; // If this event needs to be scheduled before the next advance(), force one early @@ -93,12 +94,8 @@ void CoreTiming::ScheduleEvent(s64 cycles_into_future, const EventType* event_ty std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); } -void CoreTiming::ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, - u64 userdata) { - ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type}); -} - void CoreTiming::UnscheduleEvent(const EventType* event_type, u64 userdata) { + std::lock_guard guard{inner_mutex}; const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { return e.type == event_type && e.userdata == userdata; }); @@ -110,10 +107,6 @@ void CoreTiming::UnscheduleEvent(const EventType* event_type, u64 userdata) { } } -void CoreTiming::UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) { - unschedule_queue.Push(std::make_pair(event_type, userdata)); -} - u64 CoreTiming::GetTicks() const { u64 ticks = static_cast<u64>(global_timer); if (!is_global_timer_sane) { @@ -135,6 +128,7 @@ void CoreTiming::ClearPendingEvents() { } void CoreTiming::RemoveEvent(const EventType* event_type) { + std::lock_guard guard{inner_mutex}; const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) { return e.type == event_type; }); @@ -145,11 +139,6 @@ void CoreTiming::RemoveEvent(const EventType* event_type) { } } -void CoreTiming::RemoveNormalAndThreadsafeEvent(const EventType* event_type) { - MoveEvents(); - RemoveEvent(event_type); -} - void CoreTiming::ForceExceptionCheck(s64 cycles) { cycles = std::max<s64>(0, cycles); if (downcount <= cycles) { @@ -162,19 +151,8 @@ void CoreTiming::ForceExceptionCheck(s64 cycles) { downcount = static_cast<int>(cycles); } -void CoreTiming::MoveEvents() { - for (Event ev; ts_queue.Pop(ev);) { - ev.fifo_order = event_fifo_id++; - event_queue.emplace_back(std::move(ev)); - std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); - } -} - void CoreTiming::Advance() { - MoveEvents(); - for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) { - UnscheduleEvent(ev.first, ev.second); - } + std::unique_lock<std::mutex> guard(inner_mutex); const int cycles_executed = slice_length - downcount; global_timer += cycles_executed; @@ -186,7 +164,9 @@ void CoreTiming::Advance() { Event evt = std::move(event_queue.front()); std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>()); event_queue.pop_back(); + inner_mutex.unlock(); evt.type->callback(evt.userdata, global_timer - evt.time); + inner_mutex.lock(); } is_global_timer_sane = false; diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 9d2efde37..161c7007d 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -6,6 +6,7 @@ #include <chrono> #include <functional> +#include <mutex> #include <string> #include <unordered_map> #include <vector> @@ -67,7 +68,7 @@ public: /// EventType* RegisterEvent(const std::string& name, TimedCallback callback); - /// Unregisters all registered events thus far. + /// Unregisters all registered events thus far. Note: not thread unsafe void UnregisterAllEvents(); /// After the first Advance, the slice lengths and the downcount will be reduced whenever an @@ -76,20 +77,10 @@ public: /// Scheduling from a callback will not update the downcount until the Advance() completes. void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata = 0); - /// This is to be called when outside of hle threads, such as the graphics thread, wants to - /// schedule things to be executed on the main thread. - /// - /// @note This doesn't change slice_length and thus events scheduled by this might be - /// called with a delay of up to MAX_SLICE_LENGTH - void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, - u64 userdata = 0); - void UnscheduleEvent(const EventType* event_type, u64 userdata); - void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata); /// We only permit one event of each type in the queue at a time. void RemoveEvent(const EventType* event_type); - void RemoveNormalAndThreadsafeEvent(const EventType* event_type); void ForceExceptionCheck(s64 cycles); @@ -120,7 +111,6 @@ private: /// Clear all pending events. This should ONLY be done on exit. void ClearPendingEvents(); - void MoveEvents(); s64 global_timer = 0; s64 idled_cycles = 0; @@ -143,14 +133,9 @@ private: // remain stable regardless of rehashes/resizing. std::unordered_map<std::string, EventType> event_types; - // The queue for storing the events from other threads threadsafe until they will be added - // to the event_queue by the emu thread - Common::MPSCQueue<Event> ts_queue; - - // The queue for unscheduling the events from other threads threadsafe - Common::MPSCQueue<std::pair<const EventType*, u64>> unschedule_queue; - EventType* ev_lost = nullptr; + + std::mutex inner_mutex; }; } // namespace Core::Timing diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index 84d5cd1e0..1f82fff0a 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h @@ -35,9 +35,9 @@ enum class ContentRecordType : u8 { Program = 1, Data = 2, Control = 3, - Manual = 4, - Legal = 5, - Patch = 6, + HtmlDocument = 4, + LegalInformation = 5, + DeltaFragment = 6, }; struct ContentRecord { diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 4608490e0..3725b10f7 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -99,7 +99,7 @@ ContentRecordType GetCRTypeFromNCAType(NCAContentType type) { return ContentRecordType::Data; case NCAContentType::Manual: // TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal. - return ContentRecordType::Manual; + return ContentRecordType::HtmlDocument; default: UNREACHABLE_MSG("Invalid NCAContentType={:02X}", static_cast<u8>(type)); } @@ -397,8 +397,8 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex }); if (meta_iter == ncas.end()) { - LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and " - "is therefore malformed. Double check your encryption keys."); + LOG_ERROR(Loader, "The file you are attempting to install does not have a metadata NCA and " + "is therefore malformed. Check your encryption keys."); return InstallResult::ErrorMetaFailed; } @@ -415,6 +415,9 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex const auto cnmt_file = section0->GetFiles()[0]; const CNMT cnmt(cnmt_file); for (const auto& record : cnmt.GetContentRecords()) { + // Ignore DeltaFragments, they are not useful to us + if (record.type == ContentRecordType::DeltaFragment) + continue; const auto nca = GetNCAFromNSPForID(nsp, record.nca_id); if (nca == nullptr) return InstallResult::ErrorCopyFailed; diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp index d0428a457..8b3b14e25 100644 --- a/src/core/file_sys/submission_package.cpp +++ b/src/core/file_sys/submission_package.cpp @@ -248,10 +248,13 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) { auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); if (next_file == nullptr) { - LOG_WARNING(Service_FS, - "NCA with ID {}.nca is listed in content metadata, but cannot " - "be found in PFS. NSP appears to be corrupted.", - id_string); + if (rec.type != ContentRecordType::DeltaFragment) { + LOG_WARNING(Service_FS, + "NCA with ID {}.nca is listed in content metadata, but cannot " + "be found in PFS. NSP appears to be corrupted.", + id_string); + } + continue; } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index c73a40977..a055a5002 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -76,13 +76,13 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { // This function might be called from any thread so we have to be cautious and use the // thread-safe version of ScheduleEvent. const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds}); - Core::System::GetInstance().CoreTiming().ScheduleEventThreadsafe( + Core::System::GetInstance().CoreTiming().ScheduleEvent( cycles, kernel.ThreadWakeupCallbackEventType(), callback_handle); } void Thread::CancelWakeupTimer() { - Core::System::GetInstance().CoreTiming().UnscheduleEventThreadsafe( - kernel.ThreadWakeupCallbackEventType(), callback_handle); + Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), + callback_handle); } static std::optional<s32> GetNextProcessorId(u64 mask) { diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp index 2762e0653..f3c9fef0e 100644 --- a/src/core/hle/service/am/applets/web_browser.cpp +++ b/src/core/hle/service/am/applets/web_browser.cpp @@ -459,10 +459,10 @@ void WebBrowser::InitializeOffline() { case OfflineWebSource::OfflineHtmlPage: // While there is an AppID TLV field, in official SW this is always ignored. title_id = 0; - type = FileSys::ContentRecordType::Manual; + type = FileSys::ContentRecordType::HtmlDocument; break; case OfflineWebSource::ApplicationLegalInformation: - type = FileSys::ContentRecordType::Legal; + type = FileSys::ContentRecordType::LegalInformation; break; case OfflineWebSource::SystemDataPage: type = FileSys::ContentRecordType::Data; diff --git a/src/core/hle/service/friend/errors.h b/src/core/hle/service/friend/errors.h new file mode 100644 index 000000000..b3996e275 --- /dev/null +++ b/src/core/hle/service/friend/errors.h @@ -0,0 +1,12 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/result.h" + +namespace Service::Friend { + +constexpr ResultCode ERR_NO_NOTIFICATIONS{ErrorModule::Account, 15}; +} diff --git a/src/core/hle/service/friend/friend.cpp b/src/core/hle/service/friend/friend.cpp index 5100e376c..dec541f2e 100644 --- a/src/core/hle/service/friend/friend.cpp +++ b/src/core/hle/service/friend/friend.cpp @@ -2,8 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <queue> #include "common/logging/log.h" +#include "common/uuid.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/readable_event.h" +#include "core/hle/kernel/writable_event.h" +#include "core/hle/service/friend/errors.h" #include "core/hle/service/friend/friend.h" #include "core/hle/service/friend/interface.h" @@ -109,6 +114,105 @@ private: } }; +class INotificationService final : public ServiceFramework<INotificationService> { +public: + INotificationService(Common::UUID uuid) : ServiceFramework("INotificationService"), uuid(uuid) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &INotificationService::GetEvent, "GetEvent"}, + {1, &INotificationService::Clear, "Clear"}, + {2, &INotificationService::Pop, "Pop"} + }; + // clang-format on + + RegisterHandlers(functions); + } + +private: + void GetEvent(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + + if (!is_event_created) { + auto& kernel = Core::System::GetInstance().Kernel(); + notification_event = Kernel::WritableEvent::CreateEventPair( + kernel, Kernel::ResetType::Manual, "INotificationService:NotifyEvent"); + is_event_created = true; + } + rb.PushCopyObjects(notification_event.readable); + } + + void Clear(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + while (!notifications.empty()) { + notifications.pop(); + } + std::memset(&states, 0, sizeof(States)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Pop(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_ACC, "called"); + + if (notifications.empty()) { + LOG_ERROR(Service_ACC, "No notifications in queue!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_NO_NOTIFICATIONS); + return; + } + + const auto notification = notifications.front(); + notifications.pop(); + + switch (notification.notification_type) { + case NotificationTypes::HasUpdatedFriendsList: + states.has_updated_friends = false; + break; + case NotificationTypes::HasReceivedFriendRequest: + states.has_received_friend_request = false; + break; + default: + // HOS seems not have an error case for an unknown notification + LOG_WARNING(Service_ACC, "Unknown notification {:08X}", + static_cast<u32>(notification.notification_type)); + break; + } + + IPC::ResponseBuilder rb{ctx, 6}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<SizedNotificationInfo>(notification); + } + + enum class NotificationTypes : u32 { + HasUpdatedFriendsList = 0x65, + HasReceivedFriendRequest = 0x1 + }; + + struct SizedNotificationInfo { + NotificationTypes notification_type; + INSERT_PADDING_WORDS( + 1); // TODO(ogniK): This doesn't seem to be used within any IPC returns as of now + u64_le account_id; + }; + static_assert(sizeof(SizedNotificationInfo) == 0x10, + "SizedNotificationInfo is an incorrect size"); + + struct States { + bool has_updated_friends; + bool has_received_friend_request; + }; + + Common::UUID uuid; + bool is_event_created = false; + Kernel::EventPair notification_event; + std::queue<SizedNotificationInfo> notifications; + States states{}; +}; + void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); @@ -116,6 +220,17 @@ void Module::Interface::CreateFriendService(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_ACC, "called"); } +void Module::Interface::CreateNotificationService(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + auto uuid = rp.PopRaw<Common::UUID>(); + + LOG_DEBUG(Service_ACC, "called, uuid={}", uuid.Format()); + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<INotificationService>(uuid); +} + Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) : ServiceFramework(name), module(std::move(module)) {} diff --git a/src/core/hle/service/friend/friend.h b/src/core/hle/service/friend/friend.h index e762840cb..38d05fa8e 100644 --- a/src/core/hle/service/friend/friend.h +++ b/src/core/hle/service/friend/friend.h @@ -16,6 +16,7 @@ public: ~Interface() override; void CreateFriendService(Kernel::HLERequestContext& ctx); + void CreateNotificationService(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> module; diff --git a/src/core/hle/service/friend/interface.cpp b/src/core/hle/service/friend/interface.cpp index 5a6840af5..5b384f733 100644 --- a/src/core/hle/service/friend/interface.cpp +++ b/src/core/hle/service/friend/interface.cpp @@ -10,7 +10,7 @@ Friend::Friend(std::shared_ptr<Module> module, const char* name) : Interface(std::move(module), name) { static const FunctionInfo functions[] = { {0, &Friend::CreateFriendService, "CreateFriendService"}, - {1, nullptr, "CreateNotificationService"}, + {1, &Friend::CreateNotificationService, "CreateNotificationService"}, {2, nullptr, "CreateDaemonSuspendSessionService"}, }; RegisterHandlers(functions); diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index ec9d755b7..5fc7d3cab 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -249,7 +249,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system, Sockets::InstallInterfaces(*sm); SPL::InstallInterfaces(*sm); SSL::InstallInterfaces(*sm); - Time::InstallInterfaces(*sm); + Time::InstallInterfaces(system); USB::InstallInterfaces(*sm); VI::InstallInterfaces(*sm, nv_flinger); WLAN::InstallInterfaces(*sm); diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index 8d122ae33..1030185e0 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -6,8 +6,9 @@ namespace Service::Time { -Time::Time(std::shared_ptr<Module> time, const char* name) - : Module::Interface(std::move(time), name) { +Time::Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory, + const char* name) + : Module::Interface(std::move(time), std::move(shared_memory), name) { // clang-format off static const FunctionInfo functions[] = { {0, &Time::GetStandardUserSystemClock, "GetStandardUserSystemClock"}, @@ -16,12 +17,12 @@ Time::Time(std::shared_ptr<Module> time, const char* name) {3, &Time::GetTimeZoneService, "GetTimeZoneService"}, {4, &Time::GetStandardLocalSystemClock, "GetStandardLocalSystemClock"}, {5, nullptr, "GetEphemeralNetworkSystemClock"}, - {20, nullptr, "GetSharedMemoryNativeHandle"}, + {20, &Time::GetSharedMemoryNativeHandle, "GetSharedMemoryNativeHandle"}, {30, nullptr, "GetStandardNetworkClockOperationEventReadableHandle"}, {31, nullptr, "GetEphemeralNetworkClockOperationEventReadableHandle"}, {50, nullptr, "SetStandardSteadyClockInternalOffset"}, - {100, nullptr, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, - {101, nullptr, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, + {100, &Time::IsStandardUserSystemClockAutomaticCorrectionEnabled, "IsStandardUserSystemClockAutomaticCorrectionEnabled"}, + {101, &Time::SetStandardUserSystemClockAutomaticCorrectionEnabled, "SetStandardUserSystemClockAutomaticCorrectionEnabled"}, {102, nullptr, "GetStandardUserSystemClockInitialYear"}, {200, nullptr, "IsStandardNetworkSystemClockAccuracySufficient"}, {201, nullptr, "GetStandardUserSystemClockAutomaticCorrectionUpdatedTime"}, diff --git a/src/core/hle/service/time/interface.h b/src/core/hle/service/time/interface.h index cd6b44dec..bdf0883e2 100644 --- a/src/core/hle/service/time/interface.h +++ b/src/core/hle/service/time/interface.h @@ -8,9 +8,12 @@ namespace Service::Time { +class SharedMemory; + class Time final : public Module::Interface { public: - explicit Time(std::shared_ptr<Module> time, const char* name); + explicit Time(std::shared_ptr<Module> time, std::shared_ptr<SharedMemory> shared_memory, + const char* name); ~Time() override; }; diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 346bad80d..ae6446204 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -13,6 +13,7 @@ #include "core/hle/kernel/client_session.h" #include "core/hle/service/time/interface.h" #include "core/hle/service/time/time.h" +#include "core/hle/service/time/time_sharedmemory.h" #include "core/settings.h" namespace Service::Time { @@ -61,9 +62,18 @@ static u64 CalendarToPosix(const CalendarTime& calendar_time, return static_cast<u64>(epoch_time); } +enum class ClockContextType { + StandardSteady, + StandardUserSystem, + StandardNetworkSystem, + StandardLocalSystem, +}; + class ISystemClock final : public ServiceFramework<ISystemClock> { public: - ISystemClock() : ServiceFramework("ISystemClock") { + ISystemClock(std::shared_ptr<Service::Time::SharedMemory> shared_memory, + ClockContextType clock_type) + : ServiceFramework("ISystemClock"), shared_memory(shared_memory), clock_type(clock_type) { static const FunctionInfo functions[] = { {0, &ISystemClock::GetCurrentTime, "GetCurrentTime"}, {1, nullptr, "SetCurrentTime"}, @@ -72,6 +82,8 @@ public: }; RegisterHandlers(functions); + + UpdateSharedMemoryContext(system_clock_context); } private: @@ -87,34 +99,63 @@ private: void GetSystemClockContext(Kernel::HLERequestContext& ctx) { LOG_WARNING(Service_Time, "(STUBBED) called"); - SystemClockContext system_clock_ontext{}; + // TODO(ogniK): This should be updated periodically however since we have it stubbed we'll + // only update when we get a new context + UpdateSharedMemoryContext(system_clock_context); + IPC::ResponseBuilder rb{ctx, (sizeof(SystemClockContext) / 4) + 2}; rb.Push(RESULT_SUCCESS); - rb.PushRaw(system_clock_ontext); + rb.PushRaw(system_clock_context); } + + void UpdateSharedMemoryContext(const SystemClockContext& clock_context) { + switch (clock_type) { + case ClockContextType::StandardLocalSystem: + shared_memory->SetStandardLocalSystemClockContext(clock_context); + break; + case ClockContextType::StandardNetworkSystem: + shared_memory->SetStandardNetworkSystemClockContext(clock_context); + break; + } + } + + SystemClockContext system_clock_context{}; + std::shared_ptr<Service::Time::SharedMemory> shared_memory; + ClockContextType clock_type; }; class ISteadyClock final : public ServiceFramework<ISteadyClock> { public: - ISteadyClock() : ServiceFramework("ISteadyClock") { + ISteadyClock(std::shared_ptr<SharedMemory> shared_memory) + : ServiceFramework("ISteadyClock"), shared_memory(shared_memory) { static const FunctionInfo functions[] = { {0, &ISteadyClock::GetCurrentTimePoint, "GetCurrentTimePoint"}, }; RegisterHandlers(functions); + + shared_memory->SetStandardSteadyClockTimepoint(GetCurrentTimePoint()); } private: void GetCurrentTimePoint(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_Time, "called"); - const auto& core_timing = Core::System::GetInstance().CoreTiming(); - const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); - const SteadyClockTimePoint steady_clock_time_point{static_cast<u64_le>(ms.count() / 1000), - {}}; + const auto time_point = GetCurrentTimePoint(); + // TODO(ogniK): This should be updated periodically + shared_memory->SetStandardSteadyClockTimepoint(time_point); + IPC::ResponseBuilder rb{ctx, (sizeof(SteadyClockTimePoint) / 4) + 2}; rb.Push(RESULT_SUCCESS); - rb.PushRaw(steady_clock_time_point); + rb.PushRaw(time_point); } + + SteadyClockTimePoint GetCurrentTimePoint() const { + const auto& core_timing = Core::System::GetInstance().CoreTiming(); + const auto ms = Core::Timing::CyclesToMs(core_timing.GetTicks()); + return {static_cast<u64_le>(ms.count() / 1000), {}}; + } + + std::shared_ptr<SharedMemory> shared_memory; }; class ITimeZoneService final : public ServiceFramework<ITimeZoneService> { @@ -233,7 +274,7 @@ void Module::Interface::GetStandardUserSystemClock(Kernel::HLERequestContext& ct IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(); + rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardUserSystem); } void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& ctx) { @@ -241,7 +282,7 @@ void Module::Interface::GetStandardNetworkSystemClock(Kernel::HLERequestContext& IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(); + rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardNetworkSystem); } void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { @@ -249,7 +290,7 @@ void Module::Interface::GetStandardSteadyClock(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISteadyClock>(); + rb.PushIpcInterface<ISteadyClock>(shared_memory); } void Module::Interface::GetTimeZoneService(Kernel::HLERequestContext& ctx) { @@ -265,7 +306,7 @@ void Module::Interface::GetStandardLocalSystemClock(Kernel::HLERequestContext& c IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<ISystemClock>(); + rb.PushIpcInterface<ISystemClock>(shared_memory, ClockContextType::StandardLocalSystem); } void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { @@ -333,16 +374,52 @@ void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser( rb.PushRaw<u64>(difference); } -Module::Interface::Interface(std::shared_ptr<Module> time, const char* name) - : ServiceFramework(name), time(std::move(time)) {} +void Module::Interface::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(shared_memory->GetSharedMemoryHolder()); +} + +void Module::Interface::IsStandardUserSystemClockAutomaticCorrectionEnabled( + Kernel::HLERequestContext& ctx) { + // ogniK(TODO): When clock contexts are implemented, the value should be read from the context + // instead of our shared memory holder + LOG_DEBUG(Service_Time, "called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u8>(shared_memory->GetStandardUserSystemClockAutomaticCorrectionEnabled()); +} + +void Module::Interface::SetStandardUserSystemClockAutomaticCorrectionEnabled( + Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto enabled = rp.Pop<u8>(); + + LOG_WARNING(Service_Time, "(PARTIAL IMPLEMENTATION) called"); + + // TODO(ogniK): Update clock contexts and correct timespans + + shared_memory->SetStandardUserSystemClockAutomaticCorrectionEnabled(enabled > 0); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + +Module::Interface::Interface(std::shared_ptr<Module> time, + std::shared_ptr<SharedMemory> shared_memory, const char* name) + : ServiceFramework(name), time(std::move(time)), shared_memory(std::move(shared_memory)) {} Module::Interface::~Interface() = default; -void InstallInterfaces(SM::ServiceManager& service_manager) { +void InstallInterfaces(Core::System& system) { auto time = std::make_shared<Module>(); - std::make_shared<Time>(time, "time:a")->InstallAsService(service_manager); - std::make_shared<Time>(time, "time:s")->InstallAsService(service_manager); - std::make_shared<Time>(time, "time:u")->InstallAsService(service_manager); + auto shared_mem = std::make_shared<SharedMemory>(system); + + std::make_shared<Time>(time, shared_mem, "time:a")->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(time, shared_mem, "time:s")->InstallAsService(system.ServiceManager()); + std::make_shared<Time>(std::move(time), shared_mem, "time:u") + ->InstallAsService(system.ServiceManager()); } } // namespace Service::Time diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index f11affe95..e0708f856 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -10,6 +10,8 @@ namespace Service::Time { +class SharedMemory; + struct LocationName { std::array<u8, 0x24> name; }; @@ -77,7 +79,8 @@ class Module final { public: class Interface : public ServiceFramework<Interface> { public: - explicit Interface(std::shared_ptr<Module> time, const char* name); + explicit Interface(std::shared_ptr<Module> time, + std::shared_ptr<SharedMemory> shared_memory, const char* name); ~Interface() override; void GetStandardUserSystemClock(Kernel::HLERequestContext& ctx); @@ -87,13 +90,17 @@ public: void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx); void GetClockSnapshot(Kernel::HLERequestContext& ctx); void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx); + void GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx); + void IsStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx); + void SetStandardUserSystemClockAutomaticCorrectionEnabled(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> time; + std::shared_ptr<SharedMemory> shared_memory; }; }; /// Registers all Time services with the specified service manager. -void InstallInterfaces(SM::ServiceManager& service_manager); +void InstallInterfaces(Core::System& system); } // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp new file mode 100644 index 000000000..bfc81b83c --- /dev/null +++ b/src/core/hle/service/time/time_sharedmemory.cpp @@ -0,0 +1,68 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/core.h" +#include "core/hle/service/time/time_sharedmemory.h" + +namespace Service::Time { +const std::size_t SHARED_MEMORY_SIZE = 0x1000; + +SharedMemory::SharedMemory(Core::System& system) : system(system) { + shared_memory_holder = Kernel::SharedMemory::Create( + system.Kernel(), nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite, + Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "Time:SharedMemory"); + + // Seems static from 1.0.0 -> 8.1.0. Specific games seem to check this value and crash + // if it's set to anything else + shared_memory_format.format_version = 14; + std::memcpy(shared_memory_holder->GetPointer(), &shared_memory_format, sizeof(Format)); +} + +SharedMemory::~SharedMemory() = default; + +Kernel::SharedPtr<Kernel::SharedMemory> SharedMemory::GetSharedMemoryHolder() const { + return shared_memory_holder; +} + +void SharedMemory::SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint) { + shared_memory_format.standard_steady_clock_timepoint.StoreData( + shared_memory_holder->GetPointer(), timepoint); +} + +void SharedMemory::SetStandardLocalSystemClockContext(const SystemClockContext& context) { + shared_memory_format.standard_local_system_clock_context.StoreData( + shared_memory_holder->GetPointer(), context); +} + +void SharedMemory::SetStandardNetworkSystemClockContext(const SystemClockContext& context) { + shared_memory_format.standard_network_system_clock_context.StoreData( + shared_memory_holder->GetPointer(), context); +} + +void SharedMemory::SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled) { + shared_memory_format.standard_user_system_clock_automatic_correction.StoreData( + shared_memory_holder->GetPointer(), enabled); +} + +SteadyClockTimePoint SharedMemory::GetStandardSteadyClockTimepoint() { + return shared_memory_format.standard_steady_clock_timepoint.ReadData( + shared_memory_holder->GetPointer()); +} + +SystemClockContext SharedMemory::GetStandardLocalSystemClockContext() { + return shared_memory_format.standard_local_system_clock_context.ReadData( + shared_memory_holder->GetPointer()); +} + +SystemClockContext SharedMemory::GetStandardNetworkSystemClockContext() { + return shared_memory_format.standard_network_system_clock_context.ReadData( + shared_memory_holder->GetPointer()); +} + +bool SharedMemory::GetStandardUserSystemClockAutomaticCorrectionEnabled() { + return shared_memory_format.standard_user_system_clock_automatic_correction.ReadData( + shared_memory_holder->GetPointer()); +} + +} // namespace Service::Time diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h new file mode 100644 index 000000000..cb8253541 --- /dev/null +++ b/src/core/hle/service/time/time_sharedmemory.h @@ -0,0 +1,74 @@ +// Copyright 2019 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/hle/kernel/shared_memory.h" +#include "core/hle/service/time/time.h" + +namespace Service::Time { +class SharedMemory { +public: + explicit SharedMemory(Core::System& system); + ~SharedMemory(); + + // Return the shared memory handle + Kernel::SharedPtr<Kernel::SharedMemory> GetSharedMemoryHolder() const; + + // Set memory barriers in shared memory and update them + void SetStandardSteadyClockTimepoint(const SteadyClockTimePoint& timepoint); + void SetStandardLocalSystemClockContext(const SystemClockContext& context); + void SetStandardNetworkSystemClockContext(const SystemClockContext& context); + void SetStandardUserSystemClockAutomaticCorrectionEnabled(bool enabled); + + // Pull from memory barriers in the shared memory + SteadyClockTimePoint GetStandardSteadyClockTimepoint(); + SystemClockContext GetStandardLocalSystemClockContext(); + SystemClockContext GetStandardNetworkSystemClockContext(); + bool GetStandardUserSystemClockAutomaticCorrectionEnabled(); + + // TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this? + template <typename T, std::size_t Offset> + struct MemoryBarrier { + static_assert(std::is_trivially_constructible_v<T>, "T must be trivially constructable"); + u32_le read_attempt{}; + std::array<T, 2> data{}; + + // These are not actually memory barriers at the moment as we don't have multicore and all + // HLE is mutexed. This will need to properly be implemented when we start updating the time + // points on threads. As of right now, we'll be updated both values synchronously and just + // incrementing the read_attempt to indicate that we waited. + void StoreData(u8* shared_memory, T data_to_store) { + std::memcpy(this, shared_memory + Offset, sizeof(*this)); + read_attempt++; + data[read_attempt & 1] = data_to_store; + std::memcpy(shared_memory + Offset, this, sizeof(*this)); + } + + // For reading we're just going to read the last stored value. If there was no value stored + // it will just end up reading an empty value as intended. + T ReadData(u8* shared_memory) { + std::memcpy(this, shared_memory + Offset, sizeof(*this)); + return data[(read_attempt - 1) & 1]; + } + }; + + // Shared memory format + struct Format { + MemoryBarrier<SteadyClockTimePoint, 0x0> standard_steady_clock_timepoint; + MemoryBarrier<SystemClockContext, 0x38> standard_local_system_clock_context; + MemoryBarrier<SystemClockContext, 0x80> standard_network_system_clock_context; + MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction; + u32_le format_version; + }; + static_assert(sizeof(Format) == 0xd8, "Format is an invalid size"); + +private: + Kernel::SharedPtr<Kernel::SharedMemory> shared_memory_holder{}; + Core::System& system; + Format shared_memory_format{}; +}; + +} // namespace Service::Time diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 3a22ec2c6..b1171ce65 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -168,7 +168,8 @@ ResultStatus AppLoader_NSP::ReadControlData(FileSys::NACP& nacp) { } ResultStatus AppLoader_NSP::ReadManualRomFS(FileSys::VirtualFile& file) { - const auto nca = nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Manual); + const auto nca = + nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::HtmlDocument); if (nsp->GetStatus() != ResultStatus::Success || nca == nullptr) return ResultStatus::ErrorNoRomFS; file = nca->GetRomFS(); diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index a5c4d3688..5e8553db9 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -134,7 +134,7 @@ ResultStatus AppLoader_XCI::ReadControlData(FileSys::NACP& control) { ResultStatus AppLoader_XCI::ReadManualRomFS(FileSys::VirtualFile& file) { const auto nca = xci->GetSecurePartitionNSP()->GetNCA(xci->GetProgramTitleID(), - FileSys::ContentRecordType::Manual); + FileSys::ContentRecordType::HtmlDocument); if (xci->GetStatus() != ResultStatus::Success || nca == nullptr) return ResultStatus::ErrorXCIMissingPartition; file = nca->GetRomFS(); diff --git a/src/core/tools/freezer.cpp b/src/core/tools/freezer.cpp new file mode 100644 index 000000000..17f050068 --- /dev/null +++ b/src/core/tools/freezer.cpp @@ -0,0 +1,188 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/memory.h" +#include "core/tools/freezer.h" + +namespace Tools { + +namespace { + +constexpr s64 MEMORY_FREEZER_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); + +u64 MemoryReadWidth(u32 width, VAddr addr) { + switch (width) { + case 1: + return Memory::Read8(addr); + case 2: + return Memory::Read16(addr); + case 4: + return Memory::Read32(addr); + case 8: + return Memory::Read64(addr); + default: + UNREACHABLE(); + return 0; + } +} + +void MemoryWriteWidth(u32 width, VAddr addr, u64 value) { + switch (width) { + case 1: + Memory::Write8(addr, static_cast<u8>(value)); + break; + case 2: + Memory::Write16(addr, static_cast<u16>(value)); + break; + case 4: + Memory::Write32(addr, static_cast<u32>(value)); + break; + case 8: + Memory::Write64(addr, value); + break; + default: + UNREACHABLE(); + } +} + +} // Anonymous namespace + +Freezer::Freezer(Core::Timing::CoreTiming& core_timing) : core_timing(core_timing) { + event = core_timing.RegisterEvent( + "MemoryFreezer::FrameCallback", + [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); +} + +Freezer::~Freezer() { + core_timing.UnscheduleEvent(event, 0); +} + +void Freezer::SetActive(bool active) { + if (!this->active.exchange(active)) { + FillEntryReads(); + core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS, event); + LOG_DEBUG(Common_Memory, "Memory freezer activated!"); + } else { + LOG_DEBUG(Common_Memory, "Memory freezer deactivated!"); + } +} + +bool Freezer::IsActive() const { + return active.load(std::memory_order_relaxed); +} + +void Freezer::Clear() { + std::lock_guard lock{entries_mutex}; + + LOG_DEBUG(Common_Memory, "Clearing all frozen memory values."); + + entries.clear(); +} + +u64 Freezer::Freeze(VAddr address, u32 width) { + std::lock_guard lock{entries_mutex}; + + const auto current_value = MemoryReadWidth(width, address); + entries.push_back({address, width, current_value}); + + LOG_DEBUG(Common_Memory, + "Freezing memory for address={:016X}, width={:02X}, current_value={:016X}", address, + width, current_value); + + return current_value; +} + +void Freezer::Unfreeze(VAddr address) { + std::lock_guard lock{entries_mutex}; + + LOG_DEBUG(Common_Memory, "Unfreezing memory for address={:016X}", address); + + entries.erase( + std::remove_if(entries.begin(), entries.end(), + [&address](const Entry& entry) { return entry.address == address; }), + entries.end()); +} + +bool Freezer::IsFrozen(VAddr address) const { + std::lock_guard lock{entries_mutex}; + + return std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { + return entry.address == address; + }) != entries.end(); +} + +void Freezer::SetFrozenValue(VAddr address, u64 value) { + std::lock_guard lock{entries_mutex}; + + const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { + return entry.address == address; + }); + + if (iter == entries.end()) { + LOG_ERROR(Common_Memory, + "Tried to set freeze value for address={:016X} that is not frozen!", address); + return; + } + + LOG_DEBUG(Common_Memory, + "Manually overridden freeze value for address={:016X}, width={:02X} to value={:016X}", + iter->address, iter->width, value); + iter->value = value; +} + +std::optional<Freezer::Entry> Freezer::GetEntry(VAddr address) const { + std::lock_guard lock{entries_mutex}; + + const auto iter = std::find_if(entries.begin(), entries.end(), [&address](const Entry& entry) { + return entry.address == address; + }); + + if (iter == entries.end()) { + return std::nullopt; + } + + return *iter; +} + +std::vector<Freezer::Entry> Freezer::GetEntries() const { + std::lock_guard lock{entries_mutex}; + + return entries; +} + +void Freezer::FrameCallback(u64 userdata, s64 cycles_late) { + if (!IsActive()) { + LOG_DEBUG(Common_Memory, "Memory freezer has been deactivated, ending callback events."); + return; + } + + std::lock_guard lock{entries_mutex}; + + for (const auto& entry : entries) { + LOG_DEBUG(Common_Memory, + "Enforcing memory freeze at address={:016X}, value={:016X}, width={:02X}", + entry.address, entry.value, entry.width); + MemoryWriteWidth(entry.width, entry.address, entry.value); + } + + core_timing.ScheduleEvent(MEMORY_FREEZER_TICKS - cycles_late, event); +} + +void Freezer::FillEntryReads() { + std::lock_guard lock{entries_mutex}; + + LOG_DEBUG(Common_Memory, "Updating memory freeze entries to current values."); + + for (auto& entry : entries) { + entry.value = MemoryReadWidth(entry.width, entry.address); + } +} + +} // namespace Tools diff --git a/src/core/tools/freezer.h b/src/core/tools/freezer.h new file mode 100644 index 000000000..b58de5472 --- /dev/null +++ b/src/core/tools/freezer.h @@ -0,0 +1,82 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <atomic> +#include <mutex> +#include <optional> +#include <vector> +#include "common/common_types.h" + +namespace Core::Timing { +class CoreTiming; +struct EventType; +} // namespace Core::Timing + +namespace Tools { + +/** + * This class allows the user to prevent an application from writing new values to certain memory + * locations. This has a variety of uses when attempting to reverse a game. + * + * One example could be a cheat to prevent Mario from taking damage in SMO. One could freeze the + * memory address that the game uses to store Mario's health so when he takes damage (and the game + * tries to write the new health value to memory), the value won't change. + */ +class Freezer { +public: + struct Entry { + VAddr address; + u32 width; + u64 value; + }; + + explicit Freezer(Core::Timing::CoreTiming& core_timing); + ~Freezer(); + + // Enables or disables the entire memory freezer. + void SetActive(bool active); + + // Returns whether or not the freezer is active. + bool IsActive() const; + + // Removes all entries from the freezer. + void Clear(); + + // Freezes a value to its current memory address. The value the memory is kept at will be the + // value that is read during this function. Width can be 1, 2, 4, or 8 (in bytes). + u64 Freeze(VAddr address, u32 width); + + // Unfreezes the memory value at address. If the address isn't frozen, this is a no-op. + void Unfreeze(VAddr address); + + // Returns whether or not the address is frozen. + bool IsFrozen(VAddr address) const; + + // Sets the value that address should be frozen to. This doesn't change the width set by using + // Freeze(). If the value isn't frozen, this will not freeze it and is thus a no-op. + void SetFrozenValue(VAddr address, u64 value); + + // Returns the entry corresponding to the address if the address is frozen, otherwise + // std::nullopt. + std::optional<Entry> GetEntry(VAddr address) const; + + // Returns all the entries in the freezer, an empty vector means nothing is frozen. + std::vector<Entry> GetEntries() const; + +private: + void FrameCallback(u64 userdata, s64 cycles_late); + void FillEntryReads(); + + std::atomic_bool active{false}; + + mutable std::mutex entries_mutex; + std::vector<Entry> entries; + + Core::Timing::EventType* event; + Core::Timing::CoreTiming& core_timing; +}; + +} // namespace Tools |