Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dm/games/0100000000010000/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 4 additions & 0 deletions dm/host/hle_hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
44 changes: 44 additions & 0 deletions dm/host/tests/t_hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ static int hid_usleep(useconds_t usec);
#undef clock_gettime
#undef usleep
#include "../pad_slots.h"
#include <mach-o/getsect.h>
#include <mach-o/ldsyms.h>

static int checks, fails, g_mode = 1, g_signals;
static void *g_signalled;
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions docs/agents/symptoms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions docs/reference/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down