diff --git a/dm/games/0100000000010000/README.md b/dm/games/0100000000010000/README.md index 469453ff7f..2af70fe48e 100644 --- a/dm/games/0100000000010000/README.md +++ b/dm/games/0100000000010000/README.md @@ -61,6 +61,8 @@ No `patches.py`. `op_mode` stays docked: Odyssey only takes its motion moves in General host and tool changes first needed here; each applies to every game: +- **Vibration device mounted query (synthetic fix; gameplay validation open):** [Vibration device mounted query](../../../docs/reference/services.md#vibration-device-mounted-query), reported in 1.4.1 when entering a Sand Kingdom pipe with rumble on ([#715](https://github.com/seedds/nxnative/issues/715)). The registered host query and its routing states are tested without game files; the pipe route and physical rumble still need device validation. + - **Chinese language aliases in both settings equality operators:** [Chinese language aliases in settings comparisons](../../../docs/reference/services.md#chinese-language-aliases-in-settings-comparisons). - **BAHS pairs omitted from vertex padding:** [Pixel shader inputs the vertex shader does not write](../../../docs/reference/shaders.md#pixel-shader-inputs-the-vertex-shader-does-not-write). - **Front end at `-O1`** (`dmsh_build.py` and `ios_metal.py`): [Front end at -O1: Apple's control-flow lowering](../../../docs/reference/shaders.md#front-end-at--o1-apples-control-flow-lowering). diff --git a/dm/host/hle_hid.c b/dm/host/hle_hid.c index 0a1b0a4b0d..9e105f16ac 100644 --- a/dm/host/hle_hid.c +++ b/dm/host/hle_hid.c @@ -805,6 +805,10 @@ HLE_FORCE("_ZN2nn3hid32GetSixAxisSensorFusionParametersEPfS1_RKNS0_19SixAxisSens HLE_FORCE("_ZN2nn3hid32SetSixAxisSensorFusionParametersERKNS0_19SixAxisSensorHandleEff", "six-axis sensor fusion parameters; ignored by the host", void, h_hid_FusionSet, const uint32_t *h, float a, float b) { (void)h; (void)a; (void)b; } HLE_FORCE("_ZN2nn3hid34ResetSixAxisSensorFusionParametersERKNS0_19SixAxisSensorHandleE", "six-axis sensor fusion parameters; ignored by the host", void, h_hid_FusionReset, const uint32_t *h) { (void)h; } HLE_FORCE("_ZN2nn3hid41IsFirmwareUpdateAvailableForSixAxisSensorERKNS0_19SixAxisSensorHandleE", "six-axis sensor firmware update: none", unsigned, h_hid_SixFw, const uint32_t *h) { (void)h; return 0; } +// The SDK's own mounted query connects to hid IPC; answer from the same live channel topology as device info and sends. +HLE_FORCE("_ZN2nn3hid24IsVibrationDeviceMountedERKNS0_21VibrationDeviceHandleE", "vibration: device mounted when the channel has an output", unsigned, h_hid_VibMounted, const uint32_t *h) { + return h && vib_targets(*h) != 0; +} // GetActual returns the sanitized guest value accepted by the host, before output-device adaptation. HLE_FORCE("_ZN2nn3hid22GetVibrationDeviceInfoEPNS0_19VibrationDeviceInfoERKNS0_21VibrationDeviceHandleE", "vibration: linear resonant actuator, left or right", void, h_hid_VibInfo, uint32_t *info, const uint32_t *h) { int on = h && vib_targets(*h); info[0] = on ? 1 : 0; info[1] = on ? 1 + (*h >> 16 & 1) : 0; diff --git a/dm/host/tests/t_hid.c b/dm/host/tests/t_hid.c index 210719ac21..ee7a127727 100644 --- a/dm/host/tests/t_hid.c +++ b/dm/host/tests/t_hid.c @@ -11,6 +11,8 @@ static int hid_usleep(useconds_t usec); #undef clock_gettime #undef usleep #include "../pad_slots.h" +#include +#include static int checks, fails, g_mode = 1, g_signals; static void *g_signalled; @@ -186,6 +188,47 @@ static void t_vibration(void) { CHECK(!h_hid_VibPermit() && !h_hid_VibHandles(h, 2, &id, STYLE_HANDHELD), "rumble disabled: no vibration devices"); } +// Removing the takeover sends this SDK query to hid IPC; resolving by its guest symbol also checks that registration. +static void t_vibration_mounted(void) { + unsigned long bytes = 0; + const hle_force_ent *entries = (const void *)getsectiondata(&_mh_execute_header, "__DATA", "swforce", &bytes); + unsigned (*mounted)(const uint32_t *) = NULL; + for (unsigned long i = 0; i < bytes / sizeof *entries; i++) + if (!strcmp(entries[i].name, "_ZN2nn3hid24IsVibrationDeviceMountedERKNS0_21VibrationDeviceHandleE")) mounted = entries[i].fn; + CHECK(mounted != NULL, "mounted query takes over the SDK's hid service client"); + if (!mounted) return; + + uint32_t h[2], id = 0; + g_mode = 1; ids((uint32_t[]){0, 1, NPAD_HANDHELD}, 3); dm_pad_players(3); + dm_haptics_init(NULL, 1); dm_haptics_route(0, 1); + CHECK(mounted(&(uint32_t){3}) == 0, "no output sink: no mounted device"); + dm_haptics_init(vibration_sink, 1); + CHECK(h_hid_VibHandles(h, 2, &id, STYLE_FULLKEY) == 2 && mounted(&h[0]) == 0, + "cached startup handles are not mounted before output routing"); + dm_haptics_route(0, 1); dm_haptics_route(1, 1); + CHECK(mounted(&h[0]) == 1 && mounted(&h[1]) == 1, "both routed motors are mounted"); + CHECK(mounted(&(uint32_t){0x103}) == 1, "player 2's output bit is normalized to true"); + const uint32_t invalid[] = {0, 4, 0x20003, 0x1000003, 0x203, 0xff03}; + for (unsigned i = 0; i < sizeof invalid / sizeof *invalid; i++) + CHECK(mounted(&invalid[i]) == 0, "invalid style, motor, reserved byte or absent npad is not mounted"); + CHECK(mounted(NULL) == 0, "a missing handle is not mounted"); + dm_haptics_suspend(DM_HAPTICS_INACTIVE, 1); + CHECK(mounted(&h[0]) == 1, "suspending playback does not unmount the output"); + dm_haptics_suspend(DM_HAPTICS_INACTIVE, 0); + dm_haptics_route(0, 0); + CHECK(mounted(&h[0]) == 0 && mounted(&(uint32_t){0x103}) == 1, "disconnect affects only that player's output"); + ids((uint32_t[]){0, NPAD_HANDHELD}, 2); + CHECK(mounted(&h[0]) == 1, "a merged controller's output mounts player 1's channel"); + g_mode = 0; id = NPAD_HANDHELD; + CHECK(h_hid_VibHandles(h, 2, &id, STYLE_HANDHELD) == 2 && mounted(&h[0]) == 1 && mounted(&h[1]) == 1, + "handheld motors use the merged output"); + CHECK(mounted(&(uint32_t){3}) == 0, "a stale docked handle is not mounted in handheld mode"); + dm_haptics_route(1, 0); + CHECK(mounted(&h[0]) == 0, "disconnecting the last merged output unmounts the channel"); + dm_haptics_init(vibration_sink, 0); dm_haptics_route(0, 1); + CHECK(mounted(&h[0]) == 0, "rumble disabled: the host exposes no mounted vibration device"); +} + static void *vibration_sender(void *arg) { (void)arg; const uint32_t handles[] = {3, 0}; @@ -485,6 +528,7 @@ int main(void) { t_script(); t_vibration(); t_rumble_log(); + t_vibration_mounted(); t_motion_threads(); t_motion_live(); t_motion_hid(); diff --git a/docs/agents/symptoms.md b/docs/agents/symptoms.md index 760b6a0172..ec6da7a1cc 100644 --- a/docs/agents/symptoms.md +++ b/docs/agents/symptoms.md @@ -167,6 +167,7 @@ One line per symptom in the [subsystem references](../reference/): what you see - a Pro Controller's buttons work on the Mac, but its gyro pointer never moves; `GCMotion` callbacks arrive with acceleration and rotation all zero. → [Switch Pro Controller advertises motion but sends zeroes](../reference/services.md#switch-pro-controller-advertises-motion-but-sends-zeroes) - SDK 20 calls that reach a service; `nfp`'s `AttachActivateEvent` / `AttachDeactivateEvent` crashed in the SDK at `sdk+0x3f2a78`. → [SDK 20 six-axis sensor and amiibo (`nfp`) calls](../reference/services.md#sdk-20-six-axis-sensor-and-amiibo-nfp-calls) - abort in `nn::sm::Initialize` under `nn::hid::VibrationNode::Update`. → [Vibration values flushed by the SDK's vibration graph](../reference/services.md#vibration-values-flushed-by-the-sdks-vibration-graph) +- entering a Sand Kingdom pipe with rumble on crashes in `hid::detail::IsVibrationDeviceMounted`, after `[RED] sf: the game connects to service hid itself, which the host does not serve`. → [Vibration device mounted query](../reference/services.md#vibration-device-mounted-query) - `DM_RUMBLE_LOG=1` shows the game's startup handle request answered with none, then every vibration value sent to handle 0 and reaching no output for the whole session, although a controller with haptics (a DualSense on the Mac) is connected. A game that rumbles would stay silent. → [Vibration handles before a controller is reported](../reference/services.md#vibration-handles-before-a-controller-is-reported) - a controller (an Xbox Wireless Controller, a GameSir Super Nova, a GameSir Cyclone) never vibrates in a game on the Mac or the iPhone, though iOS Settings' Identify buzzes it; the log repeats `[RED] haptics: controller left handle create player: Couldn’t communicate with a helper application. (NSCocoaErrorDomain 4097)`. The engine itself starts. → [Controller haptic engines without advanced players](../reference/services.md#controller-haptic-engines-without-advanced-players) - on the Mac, a DualShock 4 or a controller that presents itself as one (a GameSir Cyclone over Bluetooth: HID vendor `054C`, product `05C4`, `productCategory` "DualShock 4") does not rumble; Core Haptics failed on every locality with NSCocoaErrorDomain 4097 (issue #363, also in a minimal Swift app), while the same controller rumbles in an SDL-based emulator. → [DualShock 4 rumble through its effects report](../reference/services.md#dualshock-4-rumble-through-its-effects-report) diff --git a/docs/reference/services.md b/docs/reference/services.md index 06d64e11f8..dea7251348 100644 --- a/docs/reference/services.md +++ b/docs/reference/services.md @@ -294,6 +294,13 @@ Most service calls can be found before they crash, before the first boot or the - **Fix:** the flush calls it through the PLT, so an `HLE_FORCE` on that symbol catches it and returns success (`hle_hid.c`). - **Found on:** [Metroid Dread](../../dm/games/010093801237C000/README.md). +### Vibration device mounted query + +- **Symptom:** entering a Sand Kingdom pipe with rumble on crashes in `hid::detail::IsVibrationDeviceMounted`, after `[RED] sf: the game connects to service hid itself, which the host does not serve`. +- **Cause:** `hid::IsVibrationDeviceMounted` lacked a host takeover, so its SDK service client reached unsupported `hid` IPC and dereferenced the missing service object. The surrounding vibration APIs already use the host's channel topology. +- **Fix (resolved in synthetic tests; gameplay validation open):** `hle_hid.c` takes over the public query and returns a normalized boolean from `vib_targets`, as device information and sends do. A valid channel is mounted while its npad style has a routed haptics output, including controllers merged into player 1. Invalid or stale handles, no sink, disabled rumble and disconnected outputs report false; a playback suspension alone does not unmount a routed output. The host exposes no vibration device while rumble is disabled, consistent with its other vibration queries. `t_hid` resolves the real registered symbol and checks these states without hardware or game files. +- **Found on:** [Super Mario Odyssey](../../dm/games/0100000000010000/README.md), issue [#715](https://github.com/seedds/nxnative/issues/715). The reported crash and symbolized stack establish the missing query; the Sand Kingdom route and physical rumble remain untested locally. + ### Vibration handles before a controller is reported - **Symptom:** `DM_RUMBLE_LOG=1` shows the game's startup handle request answered with none, then every vibration value sent to handle 0 and reaching no output for the whole session, although a controller with haptics (a DualSense on the Mac) is connected. A game that rumbles would stay silent.