From a15719b21dc8bd482014c140e45c3be0d5d7b29e Mon Sep 17 00:00:00 2001 From: Zephyron Date: Sun, 12 Oct 2025 16:49:03 +1000 Subject: [PATCH] hid_core: Add NpadCondition to shared memory to fix no-controller crash Fix crash when launching games without a connected controller by adding a global NpadCondition structure at offset 0x3E200 in HID shared memory. Games read this structure on startup to verify controller state validity, and crash with a userspace PANIC if the data is uninitialized. The NpadCondition structure contains: - is_initialized flag (set to 1) - hold_type (controller orientation) - is_valid flag (set to 1) These fields are properly initialized when shared memory is created, ensuring games see valid controller state data even before any controllers are physically connected. Fixes crash in The Legend of Zelda: Echoes of Wisdom and other titles that validate controller state on startup. Based on LotP's implementation of the NpadCondition structure. Signed-off-by: Zephyron --- src/hid_core/resources/npad/npad_types.h | 11 +++++++++++ src/hid_core/resources/shared_memory_format.h | 12 ++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/hid_core/resources/npad/npad_types.h b/src/hid_core/resources/npad/npad_types.h index 92700d69a..7d38401c9 100644 --- a/src/hid_core/resources/npad/npad_types.h +++ b/src/hid_core/resources/npad/npad_types.h @@ -1,4 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Zephyron, LotP // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -159,6 +161,15 @@ struct NpadGcTriggerState { }; static_assert(sizeof(NpadGcTriggerState) == 0x10, "NpadGcTriggerState is an invalid size"); +// This is nn::hid::NpadCondition (global controller condition structure) +struct NpadCondition { + u32 _00{}; + u32 is_initialized{1}; // Must be 1 to prevent crashes + u32 hold_type{static_cast(NpadJoyHoldType::Horizontal)}; // Store enum as u32 + u32 is_valid{1}; // Must be 1 to prevent crashes +}; +static_assert(sizeof(NpadCondition) == 0x10, "NpadCondition is an invalid size"); + // This is nn::hid::NpadSystemProperties struct NPadSystemProperties { union { diff --git a/src/hid_core/resources/shared_memory_format.h b/src/hid_core/resources/shared_memory_format.h index 49755c8dc..5f13a0c0c 100644 --- a/src/hid_core/resources/shared_memory_format.h +++ b/src/hid_core/resources/shared_memory_format.h @@ -1,4 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2025 Zephyron, LotP // SPDX-License-Identifier: GPL-3.0-or-later #pragma once @@ -201,7 +203,10 @@ static_assert(sizeof(ConsoleSixAxisSensorSharedMemoryFormat) == 0x20, // This is nn::hid::detail::SharedMemoryFormat struct SharedMemoryFormat { - void Initialize() {} + void Initialize() { + // Initialize the global NpadCondition to prevent crashes when no controller is connected + npad_condition = NpadCondition{}; + } DebugPadSharedMemoryFormat debug_pad; TouchScreenSharedMemoryFormat touch_screen; @@ -218,7 +223,9 @@ struct SharedMemoryFormat { ConsoleSixAxisSensorSharedMemoryFormat console; INSERT_PADDING_BYTES(0x19E0); MouseSharedMemoryFormat debug_mouse; - INSERT_PADDING_BYTES(0x2000); + INSERT_PADDING_BYTES(0x200); + NpadCondition npad_condition; + INSERT_PADDING_BYTES(0x1DF0); }; static_assert(offsetof(SharedMemoryFormat, debug_pad) == 0x0, "debug_pad has wrong offset"); static_assert(offsetof(SharedMemoryFormat, touch_screen) == 0x400, "touch_screen has wrong offset"); @@ -236,6 +243,7 @@ static_assert(offsetof(SharedMemoryFormat, npad) == 0x9A00, "npad has wrong offs static_assert(offsetof(SharedMemoryFormat, gesture) == 0x3BA00, "gesture has wrong offset"); static_assert(offsetof(SharedMemoryFormat, console) == 0x3C200, "console has wrong offset"); static_assert(offsetof(SharedMemoryFormat, debug_mouse) == 0x3DC00, "debug_mouse has wrong offset"); +static_assert(offsetof(SharedMemoryFormat, npad_condition) == 0x3E200, "npad_condition has wrong offset"); static_assert(sizeof(SharedMemoryFormat) == 0x40000, "SharedMemoryFormat is an invalid size"); } // namespace Service::HID