diff options
Diffstat (limited to 'src')
36 files changed, 1016 insertions, 323 deletions
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h index ee698c8a7..8b58d701d 100644 --- a/src/core/hle/kernel/errors.h +++ b/src/core/hle/kernel/errors.h @@ -8,58 +8,28 @@ namespace Kernel { -namespace ErrCodes { -enum { - // Confirmed Switch OS error codes - MaxConnectionsReached = 7, - InvalidSize = 101, - InvalidAddress = 102, - HandleTableFull = 105, - InvalidMemoryState = 106, - InvalidMemoryPermissions = 108, - InvalidMemoryRange = 110, - InvalidThreadPriority = 112, - InvalidProcessorId = 113, - InvalidHandle = 114, - InvalidPointer = 115, - InvalidCombination = 116, - Timeout = 117, - SynchronizationCanceled = 118, - TooLarge = 119, - InvalidEnumValue = 120, - NoSuchEntry = 121, - AlreadyRegistered = 122, - SessionClosed = 123, - InvalidState = 125, - ResourceLimitExceeded = 132, -}; -} +// Confirmed Switch kernel error codes -// WARNING: The kernel is quite inconsistent in it's usage of errors code. Make sure to always -// double check that the code matches before re-using the constant. - -constexpr ResultCode ERR_HANDLE_TABLE_FULL(ErrorModule::Kernel, ErrCodes::HandleTableFull); -constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(ErrorModule::Kernel, ErrCodes::SessionClosed); -constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrorModule::Kernel, ErrCodes::TooLarge); -constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(ErrorModule::Kernel, - ErrCodes::MaxConnectionsReached); -constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue); -constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(ErrorModule::Kernel, - ErrCodes::InvalidCombination); -constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress); -constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState); -constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel, - ErrCodes::InvalidMemoryPermissions); -constexpr ResultCode ERR_INVALID_MEMORY_RANGE(ErrorModule::Kernel, ErrCodes::InvalidMemoryRange); -constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle); -constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId); -constexpr ResultCode ERR_INVALID_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize); -constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::Kernel, ErrCodes::AlreadyRegistered); -constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState); -constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel, - ErrCodes::InvalidThreadPriority); -constexpr ResultCode ERR_INVALID_POINTER(ErrorModule::Kernel, ErrCodes::InvalidPointer); -constexpr ResultCode ERR_NOT_FOUND(ErrorModule::Kernel, ErrCodes::NoSuchEntry); -constexpr ResultCode RESULT_TIMEOUT(ErrorModule::Kernel, ErrCodes::Timeout); +constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7}; +constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101}; +constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102}; +constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105}; +constexpr ResultCode ERR_INVALID_ADDRESS_STATE{ErrorModule::Kernel, 106}; +constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS{ErrorModule::Kernel, 108}; +constexpr ResultCode ERR_INVALID_MEMORY_RANGE{ErrorModule::Kernel, 110}; +constexpr ResultCode ERR_INVALID_PROCESSOR_ID{ErrorModule::Kernel, 113}; +constexpr ResultCode ERR_INVALID_THREAD_PRIORITY{ErrorModule::Kernel, 112}; +constexpr ResultCode ERR_INVALID_HANDLE{ErrorModule::Kernel, 114}; +constexpr ResultCode ERR_INVALID_POINTER{ErrorModule::Kernel, 115}; +constexpr ResultCode ERR_INVALID_COMBINATION{ErrorModule::Kernel, 116}; +constexpr ResultCode RESULT_TIMEOUT{ErrorModule::Kernel, 117}; +constexpr ResultCode ERR_SYNCHRONIZATION_CANCELED{ErrorModule::Kernel, 118}; +constexpr ResultCode ERR_OUT_OF_RANGE{ErrorModule::Kernel, 119}; +constexpr ResultCode ERR_INVALID_ENUM_VALUE{ErrorModule::Kernel, 120}; +constexpr ResultCode ERR_NOT_FOUND{ErrorModule::Kernel, 121}; +constexpr ResultCode ERR_ALREADY_REGISTERED{ErrorModule::Kernel, 122}; +constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE{ErrorModule::Kernel, 123}; +constexpr ResultCode ERR_INVALID_STATE{ErrorModule::Kernel, 125}; +constexpr ResultCode ERR_RESOURCE_LIMIT_EXCEEDED{ErrorModule::Kernel, 132}; } // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index f06b6bb55..a257c3726 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -252,8 +252,8 @@ ResultCode Process::HeapFree(VAddr target, u32 size) { return vm_manager.HeapFree(target, size); } -ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { - return vm_manager.MirrorMemory(dst_addr, src_addr, size); +ResultCode Process::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) { + return vm_manager.MirrorMemory(dst_addr, src_addr, size, state); } ResultCode Process::UnmapMemory(VAddr dst_addr, VAddr /*src_addr*/, u64 size) { diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index cf48787ce..230e395ff 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -259,7 +259,8 @@ public: ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); ResultCode HeapFree(VAddr target, u32 size); - ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size); + ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, + MemoryState state = MemoryState::Mapped); ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size); diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 5f4521122..75dbfc31d 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -214,7 +214,7 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address // Read 1 char beyond the max allowed port name to detect names that are too long. std::string port_name = Memory::ReadCString(port_name_address, PortNameMaxLength + 1); if (port_name.size() > PortNameMaxLength) { - return ERR_PORT_NAME_TOO_LONG; + return ERR_OUT_OF_RANGE; } LOG_TRACE(Kernel_SVC, "called port_name={}", port_name); @@ -310,8 +310,9 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 static constexpr u64 MaxHandles = 0x40; - if (handle_count > MaxHandles) - return ResultCode(ErrorModule::Kernel, ErrCodes::TooLarge); + if (handle_count > MaxHandles) { + return ERR_OUT_OF_RANGE; + } auto* const thread = GetCurrentThread(); @@ -376,8 +377,7 @@ static ResultCode CancelSynchronization(Handle thread_handle) { } ASSERT(thread->GetStatus() == ThreadStatus::WaitSynchAny); - thread->SetWaitSynchronizationResult( - ResultCode(ErrorModule::Kernel, ErrCodes::SynchronizationCanceled)); + thread->SetWaitSynchronizationResult(ERR_SYNCHRONIZATION_CANCELED); thread->ResumeFromWait(); return RESULT_SUCCESS; } @@ -606,7 +606,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) } if (info_sub_id >= Process::RANDOM_ENTROPY_SIZE) { - return ERR_INVALID_COMBINATION_KERNEL; + return ERR_INVALID_COMBINATION; } *result = current_process->GetRandomEntropy(info_sub_id); @@ -643,7 +643,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id) case GetInfoType::ThreadTickCount: { constexpr u64 num_cpus = 4; if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { - return ERR_INVALID_COMBINATION_KERNEL; + return ERR_INVALID_COMBINATION; } const auto thread = @@ -1181,7 +1181,7 @@ static ResultCode CloseHandle(Handle handle) { /// Reset an event static ResultCode ResetSignal(Handle handle) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle); + LOG_DEBUG(Kernel_SVC, "called handle 0x{:08X}", handle); const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); auto event = handle_table.Get<Event>(handle); @@ -1236,7 +1236,7 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) { } if (mask == 0) { - return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination); + return ERR_INVALID_COMBINATION; } /// This value is used to only change the affinity mask without changing the current ideal core. @@ -1245,12 +1245,12 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) { if (core == OnlyChangeMask) { core = thread->GetIdealCore(); } else if (core >= Core::NUM_CPU_CORES && core != static_cast<u32>(-1)) { - return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidProcessorId); + return ERR_INVALID_PROCESSOR_ID; } // Error out if the input core isn't enabled in the input mask. if (core < Core::NUM_CPU_CORES && (mask & (1ull << core)) == 0) { - return ResultCode(ErrorModule::Kernel, ErrCodes::InvalidCombination); + return ERR_INVALID_COMBINATION; } thread->ChangeCore(core, mask); diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index ec7fd6150..100f8f6bf 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -298,7 +298,7 @@ ResultCode VMManager::HeapFree(VAddr target, u64 size) { return RESULT_SUCCESS; } -ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { +ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state) { const auto vma = FindVMA(src_addr); ASSERT_MSG(vma != vma_map.end(), "Invalid memory address"); @@ -312,8 +312,8 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size) { const std::shared_ptr<std::vector<u8>>& backing_block = vma->second.backing_block; const std::size_t backing_block_offset = vma->second.offset + vma_offset; - CASCADE_RESULT(auto new_vma, MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, - MemoryState::Mapped)); + CASCADE_RESULT(auto new_vma, + MapMemoryBlock(dst_addr, backing_block, backing_block_offset, size, state)); // Protect mirror with permissions from old region Reprotect(new_vma, vma->second.permissions); // Remove permissions from old region diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 248cc46dc..d522404fe 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -189,7 +189,8 @@ public: ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms); ResultCode HeapFree(VAddr target, u64 size); - ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size); + ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, + MemoryState state = MemoryState::Mapped); /** * Scans all VMAs and updates the page table range of any that use the given vector as backing diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 0477ce66e..3758ecae1 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -203,8 +203,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger ISelfController::~ISelfController() = default; void ISelfController::SetFocusHandlingMode(Kernel::HLERequestContext& ctx) { - // Takes 3 input u8s with each field located immediately after the previous u8, these are - // bool flags. No output. + // Takes 3 input u8s with each field located immediately after the previous + // u8, these are bool flags. No output. IPC::RequestParser rp{ctx}; @@ -258,8 +258,8 @@ void ISelfController::SetOperationModeChangedNotification(Kernel::HLERequestCont } void ISelfController::SetOutOfFocusSuspendingEnabled(Kernel::HLERequestContext& ctx) { - // Takes 3 input u8s with each field located immediately after the previous u8, these are - // bool flags. No output. + // Takes 3 input u8s with each field located immediately after the previous + // u8, these are bool flags. No output. IPC::RequestParser rp{ctx}; bool enabled = rp.Pop<bool>(); @@ -302,8 +302,8 @@ void ISelfController::SetScreenShotImageOrientation(Kernel::HLERequestContext& c } void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) { - // TODO(Subv): Find out how AM determines the display to use, for now just create the layer - // in the Default display. + // TODO(Subv): Find out how AM determines the display to use, for now just + // create the layer in the Default display. u64 display_id = nvflinger->OpenDisplay("Default"); u64 layer_id = nvflinger->CreateLayer(display_id); @@ -733,7 +733,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF {70, nullptr, "RequestToShutdown"}, {71, nullptr, "RequestToReboot"}, {80, nullptr, "ExitAndRequestToShowThanksMessage"}, - {90, nullptr, "EnableApplicationCrashReport"}, + {90, &IApplicationFunctions::EnableApplicationCrashReport, "EnableApplicationCrashReport"}, {100, nullptr, "InitializeApplicationCopyrightFrameBuffer"}, {101, nullptr, "SetApplicationCopyrightImage"}, {102, nullptr, "SetApplicationCopyrightVisibility"}, @@ -752,6 +752,12 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF IApplicationFunctions::~IApplicationFunctions() = default; +void IApplicationFunctions::EnableApplicationCrashReport(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + LOG_WARNING(Service_AM, "(STUBBED) called"); +} + void IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed( Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 2}; @@ -821,7 +827,8 @@ void IApplicationFunctions::EnsureSaveData(Kernel::HLERequestContext& ctx) { void IApplicationFunctions::SetTerminateResult(Kernel::HLERequestContext& ctx) { // Takes an input u32 Result, no output. - // For example, in some cases official apps use this with error 0x2A2 then uses svcBreak. + // For example, in some cases official apps use this with error 0x2A2 then + // uses svcBreak. IPC::RequestParser rp{ctx}; u32 result = rp.Pop<u32>(); @@ -884,8 +891,8 @@ void IApplicationFunctions::GetPseudoDeviceId(Kernel::HLERequestContext& ctx) { void InstallInterfaces(SM::ServiceManager& service_manager, std::shared_ptr<NVFlinger::NVFlinger> nvflinger) { auto message_queue = std::make_shared<AppletMessageQueue>(); - message_queue->PushMessage( - AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on game boot + message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on + // game boot std::make_shared<AppletAE>(nvflinger, message_queue)->InstallAsService(service_manager); std::make_shared<AppletOE>(nvflinger, message_queue)->InstallAsService(service_manager); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 2f1c20bce..5a3fcba8f 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -185,6 +185,7 @@ private: void EndBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx); void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); + void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); }; class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp index 783c39503..763e619a4 100644 --- a/src/core/hle/service/audio/hwopus.cpp +++ b/src/core/hle/service/audio/hwopus.cpp @@ -77,8 +77,8 @@ private: IPC::ResponseBuilder rb{ctx, 6}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(consumed); - rb.Push<u64>(performance); rb.Push<u32>(sample_count); + rb.Push<u64>(performance); ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16)); } diff --git a/src/core/hle/service/btdrv/btdrv.cpp b/src/core/hle/service/btdrv/btdrv.cpp index d0a15cc4c..f3bde6d0d 100644 --- a/src/core/hle/service/btdrv/btdrv.cpp +++ b/src/core/hle/service/btdrv/btdrv.cpp @@ -2,12 +2,49 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/btdrv/btdrv.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" namespace Service::BtDrv { +class Bt final : public ServiceFramework<Bt> { +public: + explicit Bt() : ServiceFramework{"bt"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "Unknown0"}, + {1, nullptr, "Unknown1"}, + {2, nullptr, "Unknown2"}, + {3, nullptr, "Unknown3"}, + {4, nullptr, "Unknown4"}, + {5, nullptr, "Unknown5"}, + {6, nullptr, "Unknown6"}, + {7, nullptr, "Unknown7"}, + {8, nullptr, "Unknown8"}, + {9, &Bt::RegisterEvent, "RegisterEvent"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + void RegisterEvent(Kernel::HLERequestContext& ctx) { + auto& kernel = Core::System::GetInstance().Kernel(); + register_event = + Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "BT:RegisterEvent"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(register_event); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + } + Kernel::SharedPtr<Kernel::Event> register_event; +}; + class BtDrv final : public ServiceFramework<BtDrv> { public: explicit BtDrv() : ServiceFramework{"btdrv"} { @@ -67,6 +104,7 @@ public: void InstallInterfaces(SM::ServiceManager& sm) { std::make_shared<BtDrv>()->InstallAsService(sm); + std::make_shared<Bt>()->InstallAsService(sm); } } // namespace Service::BtDrv diff --git a/src/core/hle/service/btm/btm.cpp b/src/core/hle/service/btm/btm.cpp index b949bfabd..a02f6b53a 100644 --- a/src/core/hle/service/btm/btm.cpp +++ b/src/core/hle/service/btm/btm.cpp @@ -6,13 +6,118 @@ #include "common/logging/log.h" #include "core/hle/ipc_helpers.h" +#include "core/hle/kernel/event.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/service/btm/btm.h" #include "core/hle/service/service.h" -#include "core/hle/service/sm/sm.h" namespace Service::BTM { +class IBtmUserCore final : public ServiceFramework<IBtmUserCore> { +public: + explicit IBtmUserCore() : ServiceFramework{"IBtmUserCore"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IBtmUserCore::GetScanEvent, "GetScanEvent"}, + {1, nullptr, "Unknown1"}, + {2, nullptr, "Unknown2"}, + {3, nullptr, "Unknown3"}, + {4, nullptr, "Unknown4"}, + {5, nullptr, "Unknown5"}, + {6, nullptr, "Unknown6"}, + {7, nullptr, "Unknown7"}, + {8, nullptr, "Unknown8"}, + {9, nullptr, "Unknown9"}, + {10, nullptr, "Unknown10"}, + {17, &IBtmUserCore::GetConnectionEvent, "GetConnectionEvent"}, + {18, nullptr, "Unknown18"}, + {19, nullptr, "Unknown19"}, + {20, nullptr, "Unknown20"}, + {21, nullptr, "Unknown21"}, + {22, nullptr, "Unknown22"}, + {23, nullptr, "Unknown23"}, + {24, nullptr, "Unknown24"}, + {25, nullptr, "Unknown25"}, + {26, &IBtmUserCore::GetDiscoveryEvent, "AcquireBleServiceDiscoveryEventImpl"}, + {27, nullptr, "Unknown27"}, + {28, nullptr, "Unknown28"}, + {29, nullptr, "Unknown29"}, + {30, nullptr, "Unknown30"}, + {31, nullptr, "Unknown31"}, + {32, nullptr, "Unknown32"}, + {33, &IBtmUserCore::GetConfigEvent, "GetConfigEvent"}, + {34, nullptr, "Unknown34"}, + {35, nullptr, "Unknown35"}, + {36, nullptr, "Unknown36"}, + {37, nullptr, "Unknown37"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + void GetScanEvent(Kernel::HLERequestContext& ctx) { + auto& kernel = Core::System::GetInstance().Kernel(); + scan_event = + Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IBtmUserCore:ScanEvent"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(scan_event); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + } + void GetConnectionEvent(Kernel::HLERequestContext& ctx) { + auto& kernel = Core::System::GetInstance().Kernel(); + connection_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, + "IBtmUserCore:ConnectionEvent"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(connection_event); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + } + void GetDiscoveryEvent(Kernel::HLERequestContext& ctx) { + auto& kernel = Core::System::GetInstance().Kernel(); + service_discovery = + Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IBtmUserCore:Discovery"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(service_discovery); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + } + void GetConfigEvent(Kernel::HLERequestContext& ctx) { + auto& kernel = Core::System::GetInstance().Kernel(); + config_event = + Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IBtmUserCore:ConfigEvent"); + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(config_event); + LOG_WARNING(Service_BTM, "(STUBBED) called"); + } + Kernel::SharedPtr<Kernel::Event> scan_event; + Kernel::SharedPtr<Kernel::Event> connection_event; + Kernel::SharedPtr<Kernel::Event> service_discovery; + Kernel::SharedPtr<Kernel::Event> config_event; +}; + +class BTM_USR final : public ServiceFramework<BTM_USR> { +public: + explicit BTM_USR() : ServiceFramework{"btm:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &BTM_USR::GetCoreImpl, "GetCoreImpl"}, + }; + // clang-format on + RegisterHandlers(functions); + } + +private: + void GetCoreImpl(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IBtmUserCore>(); + LOG_DEBUG(Service_BTM, "called"); + } +}; + class BTM final : public ServiceFramework<BTM> { public: explicit BTM() : ServiceFramework{"btm"} { @@ -116,6 +221,7 @@ void InstallInterfaces(SM::ServiceManager& sm) { std::make_shared<BTM>()->InstallAsService(sm); std::make_shared<BTM_DBG>()->InstallAsService(sm); std::make_shared<BTM_SYS>()->InstallAsService(sm); + std::make_shared<BTM_USR>()->InstallAsService(sm); } } // namespace Service::BTM diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp index d607d985e..b43f1f054 100644 --- a/src/core/hle/service/ldr/ldr.cpp +++ b/src/core/hle/service/ldr/ldr.cpp @@ -4,7 +4,10 @@ #include <memory> #include <fmt/format.h> +#include <mbedtls/sha256.h> +#include "common/alignment.h" +#include "common/hex_util.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/process.h" #include "core/hle/service/ldr/ldr.h" @@ -13,6 +16,38 @@ namespace Service::LDR { +namespace ErrCodes { +enum { + InvalidMemoryState = 51, + InvalidNRO = 52, + InvalidNRR = 53, + MissingNRRHash = 54, + MaximumNRO = 55, + MaximumNRR = 56, + AlreadyLoaded = 57, + InvalidAlignment = 81, + InvalidSize = 82, + InvalidNROAddress = 84, + InvalidNRRAddress = 85, + NotInitialized = 87, +}; +} + +constexpr ResultCode ERROR_INVALID_MEMORY_STATE(ErrorModule::Loader, ErrCodes::InvalidMemoryState); +constexpr ResultCode ERROR_INVALID_NRO(ErrorModule::Loader, ErrCodes::InvalidNRO); +constexpr ResultCode ERROR_INVALID_NRR(ErrorModule::Loader, ErrCodes::InvalidNRR); +constexpr ResultCode ERROR_MISSING_NRR_HASH(ErrorModule::Loader, ErrCodes::MissingNRRHash); +constexpr ResultCode ERROR_MAXIMUM_NRO(ErrorModule::Loader, ErrCodes::MaximumNRO); +constexpr ResultCode ERROR_MAXIMUM_NRR(ErrorModule::Loader, ErrCodes::MaximumNRR); +constexpr ResultCode ERROR_ALREADY_LOADED(ErrorModule::Loader, ErrCodes::AlreadyLoaded); +constexpr ResultCode ERROR_INVALID_ALIGNMENT(ErrorModule::Loader, ErrCodes::InvalidAlignment); +constexpr ResultCode ERROR_INVALID_SIZE(ErrorModule::Loader, ErrCodes::InvalidSize); +constexpr ResultCode ERROR_INVALID_NRO_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNROAddress); +constexpr ResultCode ERROR_INVALID_NRR_ADDRESS(ErrorModule::Loader, ErrCodes::InvalidNRRAddress); +constexpr ResultCode ERROR_NOT_INITIALIZED(ErrorModule::Loader, ErrCodes::NotInitialized); + +constexpr u64 MAXIMUM_LOADED_RO = 0x40; + class DebugMonitor final : public ServiceFramework<DebugMonitor> { public: explicit DebugMonitor() : ServiceFramework{"ldr:dmnt"} { @@ -64,9 +99,9 @@ public: // clang-format off static const FunctionInfo functions[] = { {0, &RelocatableObject::LoadNro, "LoadNro"}, - {1, nullptr, "UnloadNro"}, + {1, &RelocatableObject::UnloadNro, "UnloadNro"}, {2, &RelocatableObject::LoadNrr, "LoadNrr"}, - {3, nullptr, "UnloadNrr"}, + {3, &RelocatableObject::UnloadNrr, "UnloadNrr"}, {4, &RelocatableObject::Initialize, "Initialize"}, }; // clang-format on @@ -75,9 +110,123 @@ public: } void LoadNrr(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + rp.Skip(2, false); + const VAddr nrr_addr{rp.Pop<VAddr>()}; + const u64 nrr_size{rp.Pop<u64>()}; + + if (!initialized) { + LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NOT_INITIALIZED); + return; + } + + if (nrr.size() >= MAXIMUM_LOADED_RO) { + LOG_ERROR(Service_LDR, "Loading new NRR would exceed the maximum number of loaded NRRs " + "(0x40)! Failing..."); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_MAXIMUM_NRR); + return; + } + + // NRR Address does not fall on 0x1000 byte boundary + if (!Common::Is4KBAligned(nrr_addr)) { + LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ALIGNMENT); + return; + } + + // NRR Size is zero or causes overflow + if (nrr_addr + nrr_size <= nrr_addr || nrr_size == 0 || !Common::Is4KBAligned(nrr_size)) { + LOG_ERROR(Service_LDR, "NRR Size is invalid! (nrr_address={:016X}, nrr_size={:016X})", + nrr_addr, nrr_size); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_SIZE); + return; + } + // Read NRR data from memory + std::vector<u8> nrr_data(nrr_size); + Memory::ReadBlock(nrr_addr, nrr_data.data(), nrr_size); + NRRHeader header; + std::memcpy(&header, nrr_data.data(), sizeof(NRRHeader)); + + if (header.magic != Common::MakeMagic('N', 'R', 'R', '0')) { + LOG_ERROR(Service_LDR, "NRR did not have magic 'NRR0' (actual {:08X})!", header.magic); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_NRR); + return; + } + + if (header.size != nrr_size) { + LOG_ERROR(Service_LDR, + "NRR header reported size did not match LoadNrr parameter size! " + "(header_size={:016X}, loadnrr_size={:016X})", + header.size, nrr_size); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_SIZE); + return; + } + + if (Core::CurrentProcess()->GetTitleID() != header.title_id) { + LOG_ERROR(Service_LDR, + "Attempting to load NRR with title ID other than current process. (actual " + "{:016X})!", + header.title_id); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_NRR); + return; + } + + std::vector<SHA256Hash> hashes; + + // Copy all hashes in the NRR (specified by hash count/hash offset) into vector. + for (std::size_t i = header.hash_offset; + i < (header.hash_offset + (header.hash_count * sizeof(SHA256Hash))); i += 8) { + SHA256Hash hash; + std::memcpy(hash.data(), nrr_data.data() + i, sizeof(SHA256Hash)); + hashes.emplace_back(hash); + } + + nrr.insert_or_assign(nrr_addr, std::move(hashes)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void UnloadNrr(Kernel::HLERequestContext& ctx) { + if (!initialized) { + LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NOT_INITIALIZED); + return; + } + + IPC::RequestParser rp{ctx}; + rp.Skip(2, false); + const auto nrr_addr{rp.Pop<VAddr>()}; + + if (!Common::Is4KBAligned(nrr_addr)) { + LOG_ERROR(Service_LDR, "NRR Address has invalid alignment (actual {:016X})!", nrr_addr); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ALIGNMENT); + return; + } + + const auto iter = nrr.find(nrr_addr); + if (iter == nrr.end()) { + LOG_ERROR(Service_LDR, + "Attempting to unload NRR which has not been loaded! (addr={:016X})", + nrr_addr); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_NRR_ADDRESS); + return; + } + + nrr.erase(iter); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_WARNING(Service_LDR, "(STUBBED) called"); } void LoadNro(Kernel::HLERequestContext& ctx) { @@ -88,33 +237,253 @@ public: const VAddr bss_addr{rp.Pop<VAddr>()}; const u64 bss_size{rp.Pop<u64>()}; + if (!initialized) { + LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NOT_INITIALIZED); + return; + } + + if (nro.size() >= MAXIMUM_LOADED_RO) { + LOG_ERROR(Service_LDR, "Loading new NRO would exceed the maximum number of loaded NROs " + "(0x40)! Failing..."); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_MAXIMUM_NRO); + return; + } + + // NRO Address does not fall on 0x1000 byte boundary + if (!Common::Is4KBAligned(nro_addr)) { + LOG_ERROR(Service_LDR, "NRO Address has invalid alignment (actual {:016X})!", nro_addr); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ALIGNMENT); + return; + } + + // NRO Size or BSS Size is zero or causes overflow + const auto nro_size_valid = + nro_size != 0 && nro_addr + nro_size > nro_addr && Common::Is4KBAligned(nro_size); + const auto bss_size_valid = + nro_size + bss_size >= nro_size && (bss_size == 0 || bss_addr + bss_size > bss_addr); + + if (!nro_size_valid || !bss_size_valid) { + LOG_ERROR(Service_LDR, + "NRO Size or BSS Size is invalid! (nro_address={:016X}, nro_size={:016X}, " + "bss_address={:016X}, bss_size={:016X})", + nro_addr, nro_size, bss_addr, bss_size); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_SIZE); + return; + } + // Read NRO data from memory std::vector<u8> nro_data(nro_size); Memory::ReadBlock(nro_addr, nro_data.data(), nro_size); + SHA256Hash hash{}; + mbedtls_sha256(nro_data.data(), nro_data.size(), hash.data(), 0); + + // NRO Hash is already loaded + if (std::any_of(nro.begin(), nro.end(), [&hash](const std::pair<VAddr, NROInfo>& info) { + return info.second.hash == hash; + })) { + LOG_ERROR(Service_LDR, "NRO is already loaded!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_ALREADY_LOADED); + return; + } + + // NRO Hash is not in any loaded NRR + if (!IsValidNROHash(hash)) { + LOG_ERROR(Service_LDR, + "NRO hash is not present in any currently loaded NRRs (hash={})!", + Common::HexArrayToString(hash)); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_MISSING_NRR_HASH); + return; + } + + NROHeader header; + std::memcpy(&header, nro_data.data(), sizeof(NROHeader)); + + if (!IsValidNRO(header, nro_size, bss_size)) { + LOG_ERROR(Service_LDR, "NRO was invalid!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_NRO); + return; + } + // Load NRO as new executable module - const VAddr addr{*Core::CurrentProcess()->VMManager().FindFreeRegion(nro_size + bss_size)}; - Loader::AppLoader_NRO::LoadNro(nro_data, fmt::format("nro-{:08x}", addr), addr); + auto* process = Core::CurrentProcess(); + auto& vm_manager = process->VMManager(); + auto map_address = vm_manager.FindFreeRegion(nro_size + bss_size); + + if (!map_address.Succeeded() || + *map_address + nro_size + bss_size > vm_manager.GetAddressSpaceEndAddress()) { + + LOG_ERROR(Service_LDR, + "General error while allocation memory or no available memory to allocate!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_MEMORY_STATE); + return; + } + + ASSERT(process->MirrorMemory(*map_address, nro_addr, nro_size, + Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS); + ASSERT(process->UnmapMemory(nro_addr, 0, nro_size) == RESULT_SUCCESS); + + if (bss_size > 0) { + ASSERT(process->MirrorMemory(*map_address + nro_size, bss_addr, bss_size, + Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS); + ASSERT(process->UnmapMemory(bss_addr, 0, bss_size) == RESULT_SUCCESS); + } + + vm_manager.ReprotectRange(*map_address, header.text_size, + Kernel::VMAPermission::ReadExecute); + vm_manager.ReprotectRange(*map_address + header.ro_offset, header.ro_size, + Kernel::VMAPermission::Read); + vm_manager.ReprotectRange(*map_address + header.rw_offset, header.rw_size, + Kernel::VMAPermission::ReadWrite); - // TODO(bunnei): This is an incomplete implementation. It was tested with Super Mario Party. - // It is currently missing: - // - Signature checks with LoadNRR - // - Checking if a module has already been loaded - // - Using/validating BSS, etc. params (these are used from NRO header instead) - // - Error checking - // - ...Probably other things + Core::System::GetInstance().ArmInterface(0).ClearInstructionCache(); + Core::System::GetInstance().ArmInterface(1).ClearInstructionCache(); + Core::System::GetInstance().ArmInterface(2).ClearInstructionCache(); + Core::System::GetInstance().ArmInterface(3).ClearInstructionCache(); + + nro.insert_or_assign(*map_address, NROInfo{hash, nro_size + bss_size}); IPC::ResponseBuilder rb{ctx, 4}; rb.Push(RESULT_SUCCESS); - rb.Push(addr); - LOG_WARNING(Service_LDR, "(STUBBED) called"); + rb.Push(*map_address); + } + + void UnloadNro(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + rp.Skip(2, false); + const VAddr mapped_addr{rp.PopRaw<VAddr>()}; + const VAddr heap_addr{rp.PopRaw<VAddr>()}; + + if (!initialized) { + LOG_ERROR(Service_LDR, "LDR:RO not initialized before use!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_NOT_INITIALIZED); + return; + } + + if (!Common::Is4KBAligned(mapped_addr) || !Common::Is4KBAligned(heap_addr)) { + LOG_ERROR(Service_LDR, + "NRO/BSS Address has invalid alignment (actual nro_addr={:016X}, " + "bss_addr={:016X})!", + mapped_addr, heap_addr); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_ALIGNMENT); + return; + } + + const auto iter = nro.find(mapped_addr); + if (iter == nro.end()) { + LOG_ERROR(Service_LDR, + "The NRO attempting to unmap was not mapped or has an invalid address " + "(actual {:016X})!", + mapped_addr); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERROR_INVALID_NRO_ADDRESS); + return; + } + + auto* process = Core::CurrentProcess(); + auto& vm_manager = process->VMManager(); + const auto& nro_size = iter->second.size; + + ASSERT(process->MirrorMemory(heap_addr, mapped_addr, nro_size, + Kernel::MemoryState::ModuleCodeStatic) == RESULT_SUCCESS); + ASSERT(process->UnmapMemory(mapped_addr, 0, nro_size) == RESULT_SUCCESS); + + Core::System::GetInstance().ArmInterface(0).ClearInstructionCache(); + Core::System::GetInstance().ArmInterface(1).ClearInstructionCache(); + Core::System::GetInstance().ArmInterface(2).ClearInstructionCache(); + Core::System::GetInstance().ArmInterface(3).ClearInstructionCache(); + + nro.erase(iter); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); } void Initialize(Kernel::HLERequestContext& ctx) { + initialized = true; + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_LDR, "(STUBBED) called"); } + +private: + using SHA256Hash = std::array<u8, 0x20>; + + struct NROHeader { + u32_le entrypoint_insn; + u32_le mod_offset; + INSERT_PADDING_WORDS(2); + u32_le magic; + INSERT_PADDING_WORDS(1); + u32_le nro_size; + INSERT_PADDING_WORDS(1); + u32_le text_offset; + u32_le text_size; + u32_le ro_offset; + u32_le ro_size; + u32_le rw_offset; + u32_le rw_size; + u32_le bss_size; + INSERT_PADDING_WORDS(1); + std::array<u8, 0x20> build_id; + INSERT_PADDING_BYTES(0x20); + }; + static_assert(sizeof(NROHeader) == 0x80, "NROHeader has invalid size."); + + struct NRRHeader { + u32_le magic; + INSERT_PADDING_BYTES(0x1C); + u64_le title_id_mask; + u64_le title_id_pattern; + std::array<u8, 0x100> modulus; + std::array<u8, 0x100> signature_1; + std::array<u8, 0x100> signature_2; + u64_le title_id; + u32_le size; + INSERT_PADDING_BYTES(4); + u32_le hash_offset; + u32_le hash_count; + INSERT_PADDING_BYTES(8); + }; + static_assert(sizeof(NRRHeader) == 0x350, "NRRHeader has incorrect size."); + + struct NROInfo { + SHA256Hash hash; + u64 size; + }; + + bool initialized = false; + + std::map<VAddr, NROInfo> nro; + std::map<VAddr, std::vector<SHA256Hash>> nrr; + + bool IsValidNROHash(const SHA256Hash& hash) { + return std::any_of( + nrr.begin(), nrr.end(), [&hash](const std::pair<VAddr, std::vector<SHA256Hash>>& p) { + return std::find(p.second.begin(), p.second.end(), hash) != p.second.end(); + }); + } + + static bool IsValidNRO(const NROHeader& header, u64 nro_size, u64 bss_size) { + return header.magic == Common::MakeMagic('N', 'R', 'O', '0') && + header.nro_size == nro_size && header.bss_size == bss_size && + header.ro_offset == header.text_offset + header.text_size && + header.rw_offset == header.ro_offset + header.ro_size && + nro_size == header.rw_offset + header.rw_size && + Common::Is4KBAligned(header.text_size) && Common::Is4KBAligned(header.ro_size) && + Common::Is4KBAligned(header.rw_size); + } }; void InstallInterfaces(SM::ServiceManager& sm) { diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 44accecb7..1066bf505 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp @@ -351,6 +351,14 @@ void PL_U::GetSharedFontInOrderOfPriority(Kernel::HLERequestContext& ctx) { font_sizes.push_back(region.size); } + // Resize buffers if game requests smaller size output. + font_codes.resize( + std::min<std::size_t>(font_codes.size(), ctx.GetWriteBufferSize(0) / sizeof(u32))); + font_offsets.resize( + std::min<std::size_t>(font_offsets.size(), ctx.GetWriteBufferSize(1) / sizeof(u32))); + font_sizes.resize( + std::min<std::size_t>(font_sizes.size(), ctx.GetWriteBufferSize(2) / sizeof(u32))); + ctx.WriteBuffer(font_codes, 0); ctx.WriteBuffer(font_offsets, 1); ctx.WriteBuffer(font_sizes, 2); diff --git a/src/core/hle/service/time/interface.cpp b/src/core/hle/service/time/interface.cpp index e3cbd7004..b3a196f65 100644 --- a/src/core/hle/service/time/interface.cpp +++ b/src/core/hle/service/time/interface.cpp @@ -23,7 +23,8 @@ Time::Time(std::shared_ptr<Module> time, const char* name) {300, nullptr, "CalculateMonotonicSystemClockBaseTimePoint"}, {400, &Time::GetClockSnapshot, "GetClockSnapshot"}, {401, nullptr, "GetClockSnapshotFromSystemClockContext"}, - {500, nullptr, "CalculateStandardUserSystemClockDifferenceByUser"}, + {500, &Time::CalculateStandardUserSystemClockDifferenceByUser, + "CalculateStandardUserSystemClockDifferenceByUser"}, {501, nullptr, "CalculateSpanBetween"}, }; RegisterHandlers(functions); diff --git a/src/core/hle/service/time/time.cpp b/src/core/hle/service/time/time.cpp index 85e7b1195..e561a0c52 100644 --- a/src/core/hle/service/time/time.cpp +++ b/src/core/hle/service/time/time.cpp @@ -299,6 +299,21 @@ void Module::Interface::GetClockSnapshot(Kernel::HLERequestContext& ctx) { ctx.WriteBuffer(&clock_snapshot, sizeof(ClockSnapshot)); } +void Module::Interface::CalculateStandardUserSystemClockDifferenceByUser( + Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_Time, "called"); + + IPC::RequestParser rp{ctx}; + const auto snapshot_a = rp.PopRaw<ClockSnapshot>(); + const auto snapshot_b = rp.PopRaw<ClockSnapshot>(); + const u64 difference = + snapshot_b.user_clock_context.offset - snapshot_a.user_clock_context.offset; + + IPC::ResponseBuilder rb{ctx, 4}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u64>(difference); +} + Module::Interface::Interface(std::shared_ptr<Module> time, const char* name) : ServiceFramework(name), time(std::move(time)) {} diff --git a/src/core/hle/service/time/time.h b/src/core/hle/service/time/time.h index 77871ae07..ea43fbea7 100644 --- a/src/core/hle/service/time/time.h +++ b/src/core/hle/service/time/time.h @@ -84,6 +84,7 @@ public: void GetTimeZoneService(Kernel::HLERequestContext& ctx); void GetStandardLocalSystemClock(Kernel::HLERequestContext& ctx); void GetClockSnapshot(Kernel::HLERequestContext& ctx); + void CalculateStandardUserSystemClockDifferenceByUser(Kernel::HLERequestContext& ctx); protected: std::shared_ptr<Module> time; diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp index d764b2406..d25fdb1fe 100644 --- a/src/core/hle/service/vi/vi.cpp +++ b/src/core/hle/service/vi/vi.cpp @@ -237,6 +237,22 @@ private: Data data{}; }; +/// Represents a parcel containing one int '0' as its data +/// Used by DetachBuffer and Disconnect +class IGBPEmptyResponseParcel : public Parcel { +protected: + void SerializeData() override { + Write(data); + } + +private: + struct Data { + u32_le unk_0; + }; + + Data data{}; +}; + class IGBPSetPreallocatedBufferRequestParcel : public Parcel { public: explicit IGBPSetPreallocatedBufferRequestParcel(std::vector<u8> buffer) @@ -554,6 +570,12 @@ private: ctx.WriteBuffer(response.Serialize()); } else if (transaction == TransactionId::CancelBuffer) { LOG_CRITICAL(Service_VI, "(STUBBED) called, transaction=CancelBuffer"); + } else if (transaction == TransactionId::Disconnect || + transaction == TransactionId::DetachBuffer) { + const auto buffer = ctx.ReadBuffer(); + + IGBPEmptyResponseParcel response{}; + ctx.WriteBuffer(response.Serialize()); } else { ASSERT_MSG(false, "Unimplemented"); } diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp index c8e491fec..fbbd6b0de 100644 --- a/src/core/loader/nro.cpp +++ b/src/core/loader/nro.cpp @@ -170,17 +170,20 @@ static constexpr u32 PageAlignSize(u32 size) { arg_data.size()); } - // Read MOD header - ModHeader mod_header{}; // Default .bss to NRO header bss size if MOD0 section doesn't exist u32 bss_size{PageAlignSize(nro_header.bss_size)}; + + // Read MOD header + ModHeader mod_header{}; std::memcpy(&mod_header, program_image.data() + nro_header.module_header_offset, sizeof(ModHeader)); + const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')}; if (has_mod_header) { // Resize program image to include .bss section and page align each section bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset); } + codeset.DataSegment().size += bss_size; program_image.resize(static_cast<u32>(program_image.size()) + bss_size); diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 6de07ea56..a04e00ecb 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -34,8 +34,8 @@ void Maxwell3D::InitializeRegisterDefaults() { // Depth range near/far is not always set, but is expected to be the default 0.0f, 1.0f. This is // needed for ARMS. for (std::size_t viewport{}; viewport < Regs::NumViewports; ++viewport) { - regs.viewport[viewport].depth_range_near = 0.0f; - regs.viewport[viewport].depth_range_far = 1.0f; + regs.viewports[viewport].depth_range_near = 0.0f; + regs.viewports[viewport].depth_range_far = 1.0f; } // Doom and Bomberman seems to use the uninitialized registers and just enable blend // so initialize blend registers with sane values @@ -66,6 +66,9 @@ void Maxwell3D::InitializeRegisterDefaults() { regs.stencil_back_func_func = Regs::ComparisonOp::Always; regs.stencil_back_func_mask = 0xFFFFFFFF; regs.stencil_back_mask = 0xFFFFFFFF; + // TODO(Rodrigo): Most games do not set a point size. I think this is a case of a + // register carrying a default value. Assume it's OpenGL's default (1). + regs.point_size = 1.0f; } void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 91ca57883..9e480dc39 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -480,6 +480,67 @@ public: }; }; + struct ViewportTransform { + f32 scale_x; + f32 scale_y; + f32 scale_z; + f32 translate_x; + f32 translate_y; + f32 translate_z; + INSERT_PADDING_WORDS(2); + + MathUtil::Rectangle<s32> GetRect() const { + return { + GetX(), // left + GetY() + GetHeight(), // top + GetX() + GetWidth(), // right + GetY() // bottom + }; + }; + + s32 GetX() const { + return static_cast<s32>(std::max(0.0f, translate_x - std::fabs(scale_x))); + } + + s32 GetY() const { + return static_cast<s32>(std::max(0.0f, translate_y - std::fabs(scale_y))); + } + + s32 GetWidth() const { + return static_cast<s32>(translate_x + std::fabs(scale_x)) - GetX(); + } + + s32 GetHeight() const { + return static_cast<s32>(translate_y + std::fabs(scale_y)) - GetY(); + } + }; + + struct ScissorTest { + u32 enable; + union { + BitField<0, 16, u32> min_x; + BitField<16, 16, u32> max_x; + }; + union { + BitField<0, 16, u32> min_y; + BitField<16, 16, u32> max_y; + }; + u32 fill; + }; + + struct ViewPort { + union { + BitField<0, 16, u32> x; + BitField<16, 16, u32> width; + }; + union { + BitField<0, 16, u32> y; + BitField<16, 16, u32> height; + }; + float depth_range_near; + float depth_range_far; + }; + bool IsShaderConfigEnabled(std::size_t index) const { // The VertexB is always enabled. if (index == static_cast<std::size_t>(Regs::ShaderProgram::VertexB)) { @@ -505,55 +566,11 @@ public: INSERT_PADDING_WORDS(0x2E); - RenderTargetConfig rt[NumRenderTargets]; - - struct { - f32 scale_x; - f32 scale_y; - f32 scale_z; - f32 translate_x; - f32 translate_y; - f32 translate_z; - INSERT_PADDING_WORDS(2); - - MathUtil::Rectangle<s32> GetRect() const { - return { - GetX(), // left - GetY() + GetHeight(), // top - GetX() + GetWidth(), // right - GetY() // bottom - }; - }; - - s32 GetX() const { - return static_cast<s32>(std::max(0.0f, translate_x - std::fabs(scale_x))); - } + std::array<RenderTargetConfig, NumRenderTargets> rt; - s32 GetY() const { - return static_cast<s32>(std::max(0.0f, translate_y - std::fabs(scale_y))); - } + std::array<ViewportTransform, NumViewports> viewport_transform; - s32 GetWidth() const { - return static_cast<s32>(translate_x + std::fabs(scale_x)) - GetX(); - } - - s32 GetHeight() const { - return static_cast<s32>(translate_y + std::fabs(scale_y)) - GetY(); - } - } viewport_transform[NumViewports]; - - struct { - union { - BitField<0, 16, u32> x; - BitField<16, 16, u32> width; - }; - union { - BitField<0, 16, u32> y; - BitField<16, 16, u32> height; - }; - float depth_range_near; - float depth_range_far; - } viewport[NumViewports]; + std::array<ViewPort, NumViewports> viewports; INSERT_PADDING_WORDS(0x1D); @@ -571,19 +588,9 @@ public: INSERT_PADDING_WORDS(0x17); - struct { - u32 enable; - union { - BitField<0, 16, u32> min_x; - BitField<16, 16, u32> max_x; - }; - union { - BitField<0, 16, u32> min_y; - BitField<16, 16, u32> max_y; - }; - } scissor_test; + std::array<ScissorTest, NumViewports> scissor_test; - INSERT_PADDING_WORDS(0x52); + INSERT_PADDING_WORDS(0x15); s32 stencil_back_func_ref; u32 stencil_back_mask; @@ -700,7 +707,9 @@ public: u32 stencil_front_func_mask; u32 stencil_front_mask; - INSERT_PADDING_WORDS(0x3); + INSERT_PADDING_WORDS(0x2); + + u32 frag_color_clamp; union { BitField<4, 1, u32> triangle_rast_flip; @@ -718,7 +727,12 @@ public: u32 zeta_enable; - INSERT_PADDING_WORDS(0x8); + union { + BitField<0, 1, u32> alpha_to_coverage; + BitField<4, 1, u32> alpha_to_one; + } multisample_control; + + INSERT_PADDING_WORDS(0x7); struct { u32 tsc_address_high; @@ -1100,8 +1114,8 @@ private: ASSERT_REG_POSITION(macros, 0x45); ASSERT_REG_POSITION(tfb_enabled, 0x1D1); ASSERT_REG_POSITION(rt, 0x200); -ASSERT_REG_POSITION(viewport_transform[0], 0x280); -ASSERT_REG_POSITION(viewport, 0x300); +ASSERT_REG_POSITION(viewport_transform, 0x280); +ASSERT_REG_POSITION(viewports, 0x300); ASSERT_REG_POSITION(vertex_buffer, 0x35D); ASSERT_REG_POSITION(clear_color[0], 0x360); ASSERT_REG_POSITION(clear_depth, 0x364); @@ -1136,10 +1150,12 @@ ASSERT_REG_POSITION(stencil_front_func_func, 0x4E4); ASSERT_REG_POSITION(stencil_front_func_ref, 0x4E5); ASSERT_REG_POSITION(stencil_front_func_mask, 0x4E6); ASSERT_REG_POSITION(stencil_front_mask, 0x4E7); +ASSERT_REG_POSITION(frag_color_clamp, 0x4EA); ASSERT_REG_POSITION(screen_y_control, 0x4EB); ASSERT_REG_POSITION(vb_element_base, 0x50D); ASSERT_REG_POSITION(point_size, 0x546); ASSERT_REG_POSITION(zeta_enable, 0x54E); +ASSERT_REG_POSITION(multisample_control, 0x54F); ASSERT_REG_POSITION(tsc, 0x557); ASSERT_REG_POSITION(tic, 0x55D); ASSERT_REG_POSITION(stencil_two_side_enable, 0x565); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 54cc47a9b..ae6aaee4c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -107,8 +107,6 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo ASSERT_MSG(has_ARB_separate_shader_objects, "has_ARB_separate_shader_objects is unsupported"); OpenGLState::ApplyDefaultState(); - // Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0 - state.clip_distance[0] = true; // Create render framebuffer framebuffer.Create(); @@ -582,6 +580,8 @@ void RasterizerOpenGL::DrawArrays() { ConfigureFramebuffers(state); SyncColorMask(); + SyncFragmentColorClampState(); + SyncMultiSampleState(); SyncDepthTestState(); SyncStencilTestState(); SyncBlendState(); @@ -642,7 +642,7 @@ void RasterizerOpenGL::DrawArrays() { params.DispatchDraw(); // Disable scissor test - state.scissor.enabled = false; + state.viewports[0].scissor.enabled = false; accelerate_draw = AccelDraw::Disabled; @@ -733,9 +733,8 @@ void RasterizerOpenGL::SamplerInfo::Create() { glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER); } -void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::FullTextureInfo& info) { +void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) { const GLuint s = sampler.handle; - const Tegra::Texture::TSCEntry& config = info.tsc; if (mag_filter != config.mag_filter) { mag_filter = config.mag_filter; glSamplerParameteri( @@ -777,30 +776,50 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::FullTex MaxwellToGL::DepthCompareFunc(depth_compare_func)); } - if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border || - wrap_p == Tegra::Texture::WrapMode::Border) { - const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g, - config.border_color_b, config.border_color_a}}; - if (border_color != new_border_color) { - border_color = new_border_color; - glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, border_color.data()); - } + GLvec4 new_border_color; + if (config.srgb_conversion) { + new_border_color[0] = config.srgb_border_color_r / 255.0f; + new_border_color[1] = config.srgb_border_color_g / 255.0f; + new_border_color[2] = config.srgb_border_color_g / 255.0f; + } else { + new_border_color[0] = config.border_color_r; + new_border_color[1] = config.border_color_g; + new_border_color[2] = config.border_color_b; } - if (info.tic.use_header_opt_control == 0) { + new_border_color[3] = config.border_color_a; + + if (border_color != new_border_color) { + border_color = new_border_color; + glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, border_color.data()); + } + + const float anisotropic_max = static_cast<float>(1 << config.max_anisotropy.Value()); + if (anisotropic_max != max_anisotropic) { + max_anisotropic = anisotropic_max; if (GLAD_GL_ARB_texture_filter_anisotropic) { - glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY, - static_cast<float>(1 << info.tic.max_anisotropy.Value())); + glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY, max_anisotropic); } else if (GLAD_GL_EXT_texture_filter_anisotropic) { - glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, - static_cast<float>(1 << info.tic.max_anisotropy.Value())); + glSamplerParameterf(s, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropic); } - glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, - static_cast<float>(info.tic.res_min_mip_level.Value())); - glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, - static_cast<float>(info.tic.res_max_mip_level.Value() == 0 - ? 16 - : info.tic.res_max_mip_level.Value())); - glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, info.tic.mip_lod_bias.Value() / 256.f); + } + const float lod_min = static_cast<float>(config.min_lod_clamp.Value()) / 256.0f; + if (lod_min != min_lod) { + min_lod = lod_min; + glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, min_lod); + } + + const float lod_max = static_cast<float>(config.max_lod_clamp.Value()) / 256.0f; + if (lod_max != max_lod) { + max_lod = lod_max; + glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, max_lod); + } + const u32 bias = config.mip_lod_bias.Value(); + // Sign extend the 13-bit value. + const u32 mask = 1U << (13 - 1); + const float bias_lod = static_cast<s32>((bias ^ mask) - mask) / 256.f; + if (lod_bias != bias_lod) { + lod_bias = bias_lod; + glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, lod_bias); } } @@ -899,7 +918,7 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, continue; } - texture_samplers[current_bindpoint].SyncWithConfig(texture); + texture_samplers[current_bindpoint].SyncWithConfig(texture.tsc); Surface surface = res_cache.GetTextureSurface(texture, entry); if (surface != nullptr) { state.texture_units[current_bindpoint].texture = surface->Texture().handle; @@ -923,15 +942,15 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { + for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumViewports; i++) { const MathUtil::Rectangle<s32> viewport_rect{regs.viewport_transform[i].GetRect()}; auto& viewport = current_state.viewports[i]; viewport.x = viewport_rect.left; viewport.y = viewport_rect.bottom; viewport.width = static_cast<GLfloat>(viewport_rect.GetWidth()); viewport.height = static_cast<GLfloat>(viewport_rect.GetHeight()); - viewport.depth_range_far = regs.viewport[i].depth_range_far; - viewport.depth_range_near = regs.viewport[i].depth_range_near; + viewport.depth_range_far = regs.viewports[i].depth_range_far; + viewport.depth_range_near = regs.viewports[i].depth_range_near; } } @@ -1022,7 +1041,9 @@ void RasterizerOpenGL::SyncStencilTestState() { void RasterizerOpenGL::SyncColorMask() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { + const std::size_t count = + regs.independent_blend_enable ? Tegra::Engines::Maxwell3D::Regs::NumRenderTargets : 1; + for (std::size_t i = 0; i < count; i++) { const auto& source = regs.color_mask[regs.color_mask_common ? 0 : i]; auto& dest = state.color_mask[i]; dest.red_enabled = (source.R == 0) ? GL_FALSE : GL_TRUE; @@ -1032,6 +1053,17 @@ void RasterizerOpenGL::SyncColorMask() { } } +void RasterizerOpenGL::SyncMultiSampleState() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + state.multisample_control.alpha_to_coverage = regs.multisample_control.alpha_to_coverage != 0; + state.multisample_control.alpha_to_one = regs.multisample_control.alpha_to_one != 0; +} + +void RasterizerOpenGL::SyncFragmentColorClampState() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + state.fragment_color_clamp.enabled = regs.frag_color_clamp != 0; +} + void RasterizerOpenGL::SyncBlendState() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; @@ -1043,43 +1075,40 @@ void RasterizerOpenGL::SyncBlendState() { state.independant_blend.enabled = regs.independent_blend_enable; if (!state.independant_blend.enabled) { auto& blend = state.blend[0]; - blend.enabled = regs.blend.enable[0] != 0; - blend.separate_alpha = regs.blend.separate_alpha; - blend.rgb_equation = MaxwellToGL::BlendEquation(regs.blend.equation_rgb); - blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.blend.factor_source_rgb); - blend.dst_rgb_func = MaxwellToGL::BlendFunc(regs.blend.factor_dest_rgb); - if (blend.separate_alpha) { - blend.a_equation = MaxwellToGL::BlendEquation(regs.blend.equation_a); - blend.src_a_func = MaxwellToGL::BlendFunc(regs.blend.factor_source_a); - blend.dst_a_func = MaxwellToGL::BlendFunc(regs.blend.factor_dest_a); + const auto& src = regs.blend; + blend.enabled = src.enable[0] != 0; + if (blend.enabled) { + blend.rgb_equation = MaxwellToGL::BlendEquation(src.equation_rgb); + blend.src_rgb_func = MaxwellToGL::BlendFunc(src.factor_source_rgb); + blend.dst_rgb_func = MaxwellToGL::BlendFunc(src.factor_dest_rgb); + blend.a_equation = MaxwellToGL::BlendEquation(src.equation_a); + blend.src_a_func = MaxwellToGL::BlendFunc(src.factor_source_a); + blend.dst_a_func = MaxwellToGL::BlendFunc(src.factor_dest_a); } - for (size_t i = 1; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { + for (std::size_t i = 1; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { state.blend[i].enabled = false; } return; } - for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { + for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { auto& blend = state.blend[i]; + const auto& src = regs.independent_blend[i]; blend.enabled = regs.blend.enable[i] != 0; if (!blend.enabled) continue; - blend.separate_alpha = regs.independent_blend[i].separate_alpha; - blend.rgb_equation = MaxwellToGL::BlendEquation(regs.independent_blend[i].equation_rgb); - blend.src_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_source_rgb); - blend.dst_rgb_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_dest_rgb); - if (blend.separate_alpha) { - blend.a_equation = MaxwellToGL::BlendEquation(regs.independent_blend[i].equation_a); - blend.src_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_source_a); - blend.dst_a_func = MaxwellToGL::BlendFunc(regs.independent_blend[i].factor_dest_a); - } + blend.rgb_equation = MaxwellToGL::BlendEquation(src.equation_rgb); + blend.src_rgb_func = MaxwellToGL::BlendFunc(src.factor_source_rgb); + blend.dst_rgb_func = MaxwellToGL::BlendFunc(src.factor_dest_rgb); + blend.a_equation = MaxwellToGL::BlendEquation(src.equation_a); + blend.src_a_func = MaxwellToGL::BlendFunc(src.factor_source_a); + blend.dst_a_func = MaxwellToGL::BlendFunc(src.factor_dest_a); } } void RasterizerOpenGL::SyncLogicOpState() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - // TODO(Subv): Support more than just render target 0. state.logic_op.enabled = regs.logic_op.enable != 0; if (!state.logic_op.enabled) @@ -1092,19 +1121,21 @@ void RasterizerOpenGL::SyncLogicOpState() { } void RasterizerOpenGL::SyncScissorTest() { - // TODO: what is the correct behavior here, a single scissor for all targets - // or scissor disabled for the rest of the targets? const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - state.scissor.enabled = (regs.scissor_test.enable != 0); - if (regs.scissor_test.enable == 0) { - return; + for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumViewports; i++) { + const auto& src = regs.scissor_test[i]; + auto& dst = state.viewports[i].scissor; + dst.enabled = (src.enable != 0); + if (dst.enabled == 0) { + return; + } + const u32 width = src.max_x - src.min_x; + const u32 height = src.max_y - src.min_y; + dst.x = src.min_x; + dst.y = src.min_y; + dst.width = width; + dst.height = height; } - const u32 width = regs.scissor_test.max_x - regs.scissor_test.min_x; - const u32 height = regs.scissor_test.max_y - regs.scissor_test.min_y; - state.scissor.x = regs.scissor_test.min_x; - state.scissor.y = regs.scissor_test.min_y; - state.scissor.width = width; - state.scissor.height = height; } void RasterizerOpenGL::SyncTransformFeedback() { @@ -1118,11 +1149,7 @@ void RasterizerOpenGL::SyncTransformFeedback() { void RasterizerOpenGL::SyncPointState() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - - // TODO(Rodrigo): Most games do not set a point size. I think this is a case of a - // register carrying a default value. For now, if the point size is zero, assume it's - // OpenGL's default (1). - state.point.size = regs.point_size == 0 ? 1 : regs.point_size; + state.point.size = regs.point_size; } void RasterizerOpenGL::CheckAlphaTests() { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 8ef0f6c12..6e78ab4cd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -88,7 +88,7 @@ private: /// SamplerInfo struct. void Create(); /// Syncs the sampler object with the config, updating any necessary state. - void SyncWithConfig(const Tegra::Texture::FullTextureInfo& info); + void SyncWithConfig(const Tegra::Texture::TSCEntry& info); private: Tegra::Texture::TextureFilter mag_filter; @@ -100,6 +100,10 @@ private: bool uses_depth_compare; Tegra::Texture::DepthCompareFunc depth_compare_func; GLvec4 border_color; + float min_lod; + float max_lod; + float lod_bias; + float max_anisotropic; }; /** @@ -160,6 +164,12 @@ private: /// Syncs the LogicOp state to match the guest state void SyncLogicOpState(); + /// Syncs the the color clamp state + void SyncFragmentColorClampState(); + + /// Syncs the alpha coverage and alpha to one + void SyncMultiSampleState(); + /// Syncs the scissor test state to match the guest state void SyncScissorTest(); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index b44ecfa1c..9ca82c06c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -381,11 +381,8 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 d const u32 tile_size_y{GetDefaultBlockHeight(format)}; if (morton_to_gl) { - const std::vector<u8> data = - Tegra::Texture::UnswizzleTexture(addr, tile_size_x, tile_size_y, bytes_per_pixel, - stride, height, depth, block_height, block_depth); - const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())}; - memcpy(gl_buffer, data.data(), size_to_copy); + Tegra::Texture::UnswizzleTexture(gl_buffer, addr, tile_size_x, tile_size_y, bytes_per_pixel, + stride, height, depth, block_height, block_depth); } else { Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y, depth, diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index 2a069cdd8..9a5d7e289 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -67,6 +67,7 @@ public: glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs); state.draw.shader_program = 0; state.draw.program_pipeline = pipeline.handle; + state.geometry_shaders.enabled = (gs != 0); } private: diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 98622a058..d9910c6e8 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -14,7 +14,10 @@ OpenGLState OpenGLState::cur_state; bool OpenGLState::s_rgb_used; OpenGLState::OpenGLState() { // These all match default OpenGL values + geometry_shaders.enabled = false; framebuffer_srgb.enabled = false; + multisample_control.alpha_to_coverage = false; + multisample_control.alpha_to_one = false; cull.enabled = false; cull.mode = GL_BACK; cull.front_face = GL_CCW; @@ -50,12 +53,12 @@ OpenGLState::OpenGLState() { item.height = 0; item.depth_range_near = 0.0f; item.depth_range_far = 1.0f; + item.scissor.enabled = false; + item.scissor.x = 0; + item.scissor.y = 0; + item.scissor.width = 0; + item.scissor.height = 0; } - scissor.enabled = false; - scissor.x = 0; - scissor.y = 0; - scissor.width = 0; - scissor.height = 0; for (auto& item : blend) { item.enabled = true; item.rgb_equation = GL_FUNC_ADD; @@ -88,6 +91,7 @@ OpenGLState::OpenGLState() { clip_distance = {}; point.size = 1; + fragment_color_clamp.enabled = false; } void OpenGLState::ApplyDefaultState() { @@ -136,7 +140,7 @@ void OpenGLState::ApplyCulling() const { } void OpenGLState::ApplyColorMask() const { - if (GLAD_GL_ARB_viewport_array) { + if (GLAD_GL_ARB_viewport_array && independant_blend.enabled) { for (size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { const auto& updated = color_mask[i]; const auto& current = cur_state.color_mask[i]; @@ -230,26 +234,10 @@ void OpenGLState::ApplyStencilTest() const { } } -void OpenGLState::ApplyScissor() const { - const bool scissor_changed = scissor.enabled != cur_state.scissor.enabled; - if (scissor_changed) { - if (scissor.enabled) { - glEnable(GL_SCISSOR_TEST); - } else { - glDisable(GL_SCISSOR_TEST); - } - } - if (scissor.enabled && - (scissor_changed || scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y || - scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height)) { - glScissor(scissor.x, scissor.y, scissor.width, scissor.height); - } -} - void OpenGLState::ApplyViewport() const { - if (GLAD_GL_ARB_viewport_array) { - for (GLuint i = 0; - i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); i++) { + if (GLAD_GL_ARB_viewport_array && geometry_shaders.enabled) { + for (GLuint i = 0; i < static_cast<GLuint>(Tegra::Engines::Maxwell3D::Regs::NumViewports); + i++) { const auto& current = cur_state.viewports[i]; const auto& updated = viewports[i]; if (updated.x != current.x || updated.y != current.y || @@ -260,6 +248,22 @@ void OpenGLState::ApplyViewport() const { updated.depth_range_far != current.depth_range_far) { glDepthRangeIndexed(i, updated.depth_range_near, updated.depth_range_far); } + const bool scissor_changed = updated.scissor.enabled != current.scissor.enabled; + if (scissor_changed) { + if (updated.scissor.enabled) { + glEnablei(GL_SCISSOR_TEST, i); + } else { + glDisablei(GL_SCISSOR_TEST, i); + } + } + if (updated.scissor.enabled && + (scissor_changed || updated.scissor.x != current.scissor.x || + updated.scissor.y != current.scissor.y || + updated.scissor.width != current.scissor.width || + updated.scissor.height != current.scissor.height)) { + glScissorIndexed(i, updated.scissor.x, updated.scissor.y, updated.scissor.width, + updated.scissor.height); + } } } else { const auto& current = cur_state.viewports[0]; @@ -273,6 +277,21 @@ void OpenGLState::ApplyViewport() const { updated.depth_range_far != current.depth_range_far) { glDepthRange(updated.depth_range_near, updated.depth_range_far); } + const bool scissor_changed = updated.scissor.enabled != current.scissor.enabled; + if (scissor_changed) { + if (updated.scissor.enabled) { + glEnable(GL_SCISSOR_TEST); + } else { + glDisable(GL_SCISSOR_TEST); + } + } + if (updated.scissor.enabled && (scissor_changed || updated.scissor.x != current.scissor.x || + updated.scissor.y != current.scissor.y || + updated.scissor.width != current.scissor.width || + updated.scissor.height != current.scissor.height)) { + glScissor(updated.scissor.x, updated.scissor.y, updated.scissor.width, + updated.scissor.height); + } } } @@ -290,27 +309,16 @@ void OpenGLState::ApplyGlobalBlending() const { if (!updated.enabled) { return; } - if (updated.separate_alpha) { - if (blend_changed || updated.src_rgb_func != current.src_rgb_func || - updated.dst_rgb_func != current.dst_rgb_func || - updated.src_a_func != current.src_a_func || updated.dst_a_func != current.dst_a_func) { - glBlendFuncSeparate(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func, - updated.dst_a_func); - } - - if (blend_changed || updated.rgb_equation != current.rgb_equation || - updated.a_equation != current.a_equation) { - glBlendEquationSeparate(updated.rgb_equation, updated.a_equation); - } - } else { - if (blend_changed || updated.src_rgb_func != current.src_rgb_func || - updated.dst_rgb_func != current.dst_rgb_func) { - glBlendFunc(updated.src_rgb_func, updated.dst_rgb_func); - } + if (blend_changed || updated.src_rgb_func != current.src_rgb_func || + updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func || + updated.dst_a_func != current.dst_a_func) { + glBlendFuncSeparate(updated.src_rgb_func, updated.dst_rgb_func, updated.src_a_func, + updated.dst_a_func); + } - if (blend_changed || updated.rgb_equation != current.rgb_equation) { - glBlendEquation(updated.rgb_equation); - } + if (blend_changed || updated.rgb_equation != current.rgb_equation || + updated.a_equation != current.a_equation) { + glBlendEquationSeparate(updated.rgb_equation, updated.a_equation); } } @@ -328,29 +336,17 @@ void OpenGLState::ApplyTargetBlending(std::size_t target, bool force) const { if (!updated.enabled) { return; } - if (updated.separate_alpha) { - if (blend_changed || updated.src_rgb_func != current.src_rgb_func || - updated.dst_rgb_func != current.dst_rgb_func || - updated.src_a_func != current.src_a_func || updated.dst_a_func != current.dst_a_func) { - glBlendFuncSeparateiARB(static_cast<GLuint>(target), updated.src_rgb_func, - updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func); - } - - if (blend_changed || updated.rgb_equation != current.rgb_equation || - updated.a_equation != current.a_equation) { - glBlendEquationSeparateiARB(static_cast<GLuint>(target), updated.rgb_equation, - updated.a_equation); - } - } else { - if (blend_changed || updated.src_rgb_func != current.src_rgb_func || - updated.dst_rgb_func != current.dst_rgb_func) { - glBlendFunciARB(static_cast<GLuint>(target), updated.src_rgb_func, - updated.dst_rgb_func); - } + if (blend_changed || updated.src_rgb_func != current.src_rgb_func || + updated.dst_rgb_func != current.dst_rgb_func || updated.src_a_func != current.src_a_func || + updated.dst_a_func != current.dst_a_func) { + glBlendFuncSeparateiARB(static_cast<GLuint>(target), updated.src_rgb_func, + updated.dst_rgb_func, updated.src_a_func, updated.dst_a_func); + } - if (blend_changed || updated.rgb_equation != current.rgb_equation) { - glBlendEquationiARB(static_cast<GLuint>(target), updated.rgb_equation); - } + if (blend_changed || updated.rgb_equation != current.rgb_equation || + updated.a_equation != current.a_equation) { + glBlendEquationSeparateiARB(static_cast<GLuint>(target), updated.rgb_equation, + updated.a_equation); } } @@ -481,9 +477,29 @@ void OpenGLState::Apply() const { if (point.size != cur_state.point.size) { glPointSize(point.size); } + if (GLAD_GL_ARB_color_buffer_float) { + if (fragment_color_clamp.enabled != cur_state.fragment_color_clamp.enabled) { + glClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB, + fragment_color_clamp.enabled ? GL_TRUE : GL_FALSE); + } + } + if (multisample_control.alpha_to_coverage != cur_state.multisample_control.alpha_to_coverage) { + if (multisample_control.alpha_to_coverage) { + glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE); + } else { + glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE); + } + } + if (multisample_control.alpha_to_one != cur_state.multisample_control.alpha_to_one) { + if (multisample_control.alpha_to_one) { + glEnable(GL_SAMPLE_ALPHA_TO_ONE); + } else { + glDisable(GL_SAMPLE_ALPHA_TO_ONE); + } + } + ApplyColorMask(); ApplyViewport(); - ApplyScissor(); ApplyStencilTest(); ApplySRgb(); ApplyCulling(); diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index e5d1baae6..bdc743b0f 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -40,6 +40,19 @@ public: } framebuffer_srgb; struct { + bool alpha_to_coverage; // GL_ALPHA_TO_COVERAGE + bool alpha_to_one; // GL_ALPHA_TO_ONE + } multisample_control; + + struct { + bool enabled; // GL_CLAMP_FRAGMENT_COLOR_ARB + } fragment_color_clamp; + + struct { + bool enabled; // viewports arrays are only supported when geometry shaders are enabled. + } geometry_shaders; + + struct { bool enabled; // GL_CULL_FACE GLenum mode; // GL_CULL_FACE_MODE GLenum front_face; // GL_FRONT_FACE @@ -79,7 +92,6 @@ public: struct Blend { bool enabled; // GL_BLEND - bool separate_alpha; // Independent blend enabled GLenum rgb_equation; // GL_BLEND_EQUATION_RGB GLenum a_equation; // GL_BLEND_EQUATION_ALPHA GLenum src_rgb_func; // GL_BLEND_SRC_RGB @@ -150,16 +162,15 @@ public: GLfloat height; GLfloat depth_range_near; // GL_DEPTH_RANGE GLfloat depth_range_far; // GL_DEPTH_RANGE + struct { + bool enabled; // GL_SCISSOR_TEST + GLint x; + GLint y; + GLsizei width; + GLsizei height; + } scissor; }; - std::array<viewport, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> viewports; - - struct { - bool enabled; // GL_SCISSOR_TEST - GLint x; - GLint y; - GLsizei width; - GLsizei height; - } scissor; + std::array<viewport, Tegra::Engines::Maxwell3D::Regs::NumViewports> viewports; struct { float size; // GL_POINT_SIZE @@ -214,7 +225,6 @@ private: void ApplyLogicOp() const; void ApplyTextures() const; void ApplySamplers() const; - void ApplyScissor() const; }; } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 3ce2cc6d2..065b3929c 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -180,6 +180,12 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) { return GL_CLAMP_TO_BORDER; case Tegra::Texture::WrapMode::MirrorOnceClampToEdge: return GL_MIRROR_CLAMP_TO_EDGE; + case Tegra::Texture::WrapMode::MirrorOnceBorder: + if (GL_EXT_texture_mirror_clamp) { + return GL_MIRROR_CLAMP_TO_BORDER_EXT; + } else { + return GL_MIRROR_CLAMP_TO_EDGE; + } } LOG_ERROR(Render_OpenGL, "Unimplemented texture wrap mode={}", static_cast<u32>(wrap_mode)); return GL_REPEAT; diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 7040d9bf5..7eabd34f1 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -226,14 +226,21 @@ u32 BytesPerPixel(TextureFormat format) { } } +void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y, + u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, + u32 block_depth) { + CopySwizzledData((width + tile_size_x - 1) / tile_size_x, + (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel, + bytes_per_pixel, Memory::GetPointer(address), unswizzled_data, true, + block_height, block_depth); +} + std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth) { std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel); - CopySwizzledData((width + tile_size_x - 1) / tile_size_x, - (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel, - bytes_per_pixel, Memory::GetPointer(address), unswizzled_data.data(), true, - block_height, block_depth); + UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel, + width, height, depth, block_height, block_depth); return unswizzled_data; } diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index ba065510b..f4ef7c73e 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -19,6 +19,13 @@ inline std::size_t GetGOBSize() { /** * Unswizzles a swizzled texture without changing its format. */ +void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y, + u32 bytes_per_pixel, u32 width, u32 height, u32 depth, + u32 block_height = TICEntry::DefaultBlockHeight, + u32 block_depth = TICEntry::DefaultBlockHeight); +/** + * Unswizzles a swizzled texture without changing its format. + */ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height = TICEntry::DefaultBlockHeight, diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index e199d019a..ffa08f5c1 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -190,6 +190,7 @@ struct TICEntry { union { BitField<0, 4, u32> res_min_mip_level; BitField<4, 4, u32> res_max_mip_level; + BitField<12, 12, u32> min_lod_clamp; }; GPUVAddr Address() const { @@ -284,13 +285,25 @@ struct TSCEntry { BitField<6, 3, WrapMode> wrap_p; BitField<9, 1, u32> depth_compare_enabled; BitField<10, 3, DepthCompareFunc> depth_compare_func; + BitField<13, 1, u32> srgb_conversion; + BitField<20, 3, u32> max_anisotropy; }; union { BitField<0, 2, TextureFilter> mag_filter; BitField<4, 2, TextureFilter> min_filter; BitField<6, 2, TextureMipmapFilter> mip_filter; + BitField<9, 1, u32> cubemap_interface_filtering; + BitField<12, 13, u32> mip_lod_bias; + }; + union { + BitField<0, 12, u32> min_lod_clamp; + BitField<12, 12, u32> max_lod_clamp; + BitField<24, 8, u32> srgb_border_color_r; + }; + union { + BitField<12, 8, u32> srgb_border_color_g; + BitField<20, 8, u32> srgb_border_color_b; }; - INSERT_PADDING_BYTES(8); float border_color_r; float border_color_g; float border_color_b; diff --git a/src/yuzu/configuration/configure_gamelist.cpp b/src/yuzu/configuration/configure_gamelist.cpp index 639d5df0f..ae8cac243 100644 --- a/src/yuzu/configuration/configure_gamelist.cpp +++ b/src/yuzu/configuration/configure_gamelist.cpp @@ -36,6 +36,16 @@ ConfigureGameList::ConfigureGameList(QWidget* parent) InitializeRowComboBoxes(); this->setConfiguration(); + + // Force game list reload if any of the relevant settings are changed. + connect(ui->show_unknown, &QCheckBox::stateChanged, this, + &ConfigureGameList::RequestGameListUpdate); + connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureGameList::RequestGameListUpdate); + connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureGameList::RequestGameListUpdate); + connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + &ConfigureGameList::RequestGameListUpdate); } ConfigureGameList::~ConfigureGameList() = default; @@ -49,6 +59,10 @@ void ConfigureGameList::applyConfiguration() { Settings::Apply(); } +void ConfigureGameList::RequestGameListUpdate() { + UISettings::values.is_game_list_reload_pending.exchange(true); +} + void ConfigureGameList::setConfiguration() { ui->show_unknown->setChecked(UISettings::values.show_unknown); ui->show_add_ons->setChecked(UISettings::values.show_add_ons); diff --git a/src/yuzu/configuration/configure_gamelist.h b/src/yuzu/configuration/configure_gamelist.h index ff7406c60..bbf7e25f1 100644 --- a/src/yuzu/configuration/configure_gamelist.h +++ b/src/yuzu/configuration/configure_gamelist.h @@ -21,6 +21,8 @@ public: void applyConfiguration(); private: + void RequestGameListUpdate(); + void setConfiguration(); void changeEvent(QEvent*) override; diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 314f51203..c22742007 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -23,6 +23,9 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) this->setConfiguration(); + connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this, + [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); + ui->use_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 131ad19de..4b969119c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -12,7 +12,7 @@ #include "core/file_sys/vfs_real.h" #include "core/hle/service/acc/profile_manager.h" -// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows +// These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( const FileSys::VirtualFilesystem& vfs, const std::string& path, FileSys::Mode mode) { @@ -308,6 +308,8 @@ void GMainWindow::InitializeHotkeys() { Qt::ApplicationShortcut); hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"), Qt::ApplicationShortcut); + hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2), + Qt::ApplicationShortcut); hotkey_registry.LoadHotkeys(); connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, @@ -361,6 +363,12 @@ void GMainWindow::InitializeHotkeys() { UpdateStatusBar(); } }); + connect(hotkey_registry.GetHotkey("Main Window", "Load Amiibo", this), &QShortcut::activated, + this, [&] { + if (ui.action_Load_Amiibo->isEnabled()) { + OnLoadAmiibo(); + } + }); } void GMainWindow::SetDefaultUIGeometry() { @@ -1333,7 +1341,13 @@ void GMainWindow::OnConfigure() { UpdateUITheme(); if (UISettings::values.enable_discord_presence != old_discord_presence) SetDiscordEnabled(UISettings::values.enable_discord_presence); - game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + + const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); + if (reload) { + game_list->PopulateAsync(UISettings::values.gamedir, + UISettings::values.gamedir_deepscan); + } + config->Save(); } } diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h index 32a0d813c..e80aebc0a 100644 --- a/src/yuzu/ui_settings.h +++ b/src/yuzu/ui_settings.h @@ -5,6 +5,7 @@ #pragma once #include <array> +#include <atomic> #include <vector> #include <QByteArray> #include <QString> @@ -63,6 +64,7 @@ struct Values { uint32_t icon_size; uint8_t row_1_text_id; uint8_t row_2_text_id; + std::atomic_bool is_game_list_reload_pending{false}; }; extern Values values; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 9cc409fd5..96f1ef636 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -139,6 +139,8 @@ void Config::ReadValues() { Settings::values.rng_seed = std::nullopt; } + Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); + // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); |