Controller customization feature

This commit is contained in:
redphx
2024-12-22 17:17:03 +07:00
parent 8ef5a95c88
commit 7b60ba3a3e
89 changed files with 3286 additions and 1188 deletions

View File

@@ -1,22 +1,30 @@
import type { PatchArray, PatchName, PatchPage } from "./patcher";
export class PatcherUtils {
static indexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
static indexOf(txt: string, searchString: string, startIndex: number, maxRange=0, after=false): number {
if (startIndex < 0) {
return -1;
}
const index = txt.indexOf(searchString, startIndex);
if (index < 0 || (maxRange && index - startIndex > maxRange)) {
return -1;
}
return index;
return after ? index + searchString.length : index;
}
static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange: number): number {
static lastIndexOf(txt: string, searchString: string, startIndex: number, maxRange=0, after=false): number {
if (startIndex < 0) {
return -1;
}
const index = txt.lastIndexOf(searchString, startIndex);
if (index < 0 || (maxRange && startIndex - index > maxRange)) {
return -1;
}
return index;
return after ? index + searchString.length : index;
}
static insertAt(txt: string, index: number, insertString: string): string {

View File

@@ -4,10 +4,10 @@ import { BxLogger } from "@utils/bx-logger";
import { blockSomeNotifications, hashCode, renderString } from "@utils/utils";
import { BxEvent } from "@/utils/bx-event";
import codeControllerShortcuts from "./patches/controller-shortcuts.js" with { type: "text" };
import codeControllerCustomization from "./patches/controller-customization.js" with { type: "text" };
import codePollGamepad from "./patches/poll-gamepad.js" with { type: "text" };
import codeExposeStreamSession from "./patches/expose-stream-session.js" with { type: "text" };
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type: "text" };
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
import { PrefKey, StorageKey } from "@/enums/pref-keys.js";
@@ -88,7 +88,7 @@ const PATCHES = {
return false;
}
const layout = getPref<UiLayout>(PrefKey.UI_LAYOUT) === UiLayout.TV ? UiLayout.TV : UiLayout.DEFAULT;
const layout = getPref(PrefKey.UI_LAYOUT) === UiLayout.TV ? UiLayout.TV : UiLayout.DEFAULT;
return str.replace(text, `?"${layout}":"${layout}"`);
},
@@ -120,7 +120,9 @@ const PATCHES = {
return false;
}
return str.replace(text, codeRemotePlayEnable);
const newCode = `connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',`;
return str.replace(text, newCode);
},
// Remote Play: Disable achievement toast
@@ -191,17 +193,34 @@ const PATCHES = {
codeBlock = codeBlock.replace('this.inputPollingDurationStats.addValue', '');
}
// Map the Share button on Xbox Series controller with the capturing screenshot feature
const match = codeBlock.match(/this\.gamepadTimestamps\.set\((\w+)\.index/);
if (match) {
const gamepadVar = match[1];
const newCode = renderString(codeControllerShortcuts, {
gamepadVar,
});
codeBlock = codeBlock.replace('this.gamepadTimestamps.set', newCode + 'this.gamepadTimestamps.set');
// Controller shortcuts
let match = codeBlock.match(/this\.gamepadTimestamps\.set\(([A-Za-z0-9_$]+)\.index/);
if (!match) {
return false;
}
let newCode = renderString(codePollGamepad, {
gamepadVar: match[1],
});
codeBlock = codeBlock.replace('this.gamepadTimestamps.set', newCode + 'this.gamepadTimestamps.set');
// Controller customization
match = codeBlock.match(/let ([A-Za-z0-9_$]+)=this\.gamepadMappings\.find/);
if (!match) {
return false;
}
const xCloudGamepadVar = match[1];
const inputFeedbackManager = PatcherUtils.indexOf(codeBlock, 'this.inputFeedbackManager.onGamepadConnected(', 0, 10000);
const backetIndex = PatcherUtils.indexOf(codeBlock, '}', inputFeedbackManager, 100);
if (backetIndex < 0) {
return false;
}
let customizationCode = ';'; // End previous code line
customizationCode += renderString(codeControllerCustomization, { xCloudGamepadVar });
codeBlock = PatcherUtils.insertAt(codeBlock, backetIndex, customizationCode);
str = str.substring(0, index) + codeBlock + str.substring(setTimeoutIndex);
return str;
},
@@ -357,7 +376,7 @@ if (window.BX_EXPOSED.stopTakRendering) {
}
let autoOffCode = '';
if (getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
autoOffCode = 'return;';
} else if (getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) {
autoOffCode = `
@@ -414,7 +433,7 @@ e.guideUI = null;
`;
// Remove the TAK Edit button when the touch controller is disabled
if (getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF) {
newCode += 'e.canShowTakHUD = false;';
}
@@ -534,7 +553,7 @@ BxLogger.info('patchRemotePlayMkb', ${configsVar});
return false;
}
const opacity = (getPref<TouchControllerDefaultOpacity>(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) / 100).toFixed(1);
const opacity = (getPref(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) / 100).toFixed(1);
const newCode = `opacityMultiplier: ${opacity}`;
str = str.replace(text, newCode);
return str;
@@ -771,7 +790,7 @@ true` + text;
return false;
}
const PREF_HIDE_SECTIONS = getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS);
const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
const siglIds: GamePassCloudGallery[] = [];
const sections: PartialRecord<UiSection, GamePassCloudGallery> = {
@@ -962,7 +981,7 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
// Find index after {
index = str.indexOf('{', index) + 1;
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
const blockFeatures = getPref(PrefKey.BLOCK_FEATURES);
const filters = [];
if (blockFeatures.includes(BlockFeature.NOTIFICATIONS_INVITES)) {
filters.push('GameInvite', 'PartyInvite');
@@ -987,7 +1006,7 @@ ${subsVar} = subs;
};
let PATCH_ORDERS = PatcherUtils.filterPatches([
...(AppInterface && getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
'enableNativeMkb',
'exposeInputSink',
'disableAbsoluteMouse',
@@ -1019,7 +1038,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
'overrideStorageGetSettings',
getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentFocus',
getPref<UiLayout>(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
getPref(PrefKey.UI_LAYOUT) !== UiLayout.DEFAULT && 'websiteLayout',
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
...(STATES.userAgent.capabilities.touch ? [
@@ -1055,7 +1074,7 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
] : []),
]);
const hideSections = getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS);
const hideSections = getPref(PrefKey.UI_HIDE_SECTIONS);
let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
hideSections.includes(UiSection.NEWS) && 'ignoreNewsSection',
hideSections.includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
@@ -1086,11 +1105,11 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
getPref(PrefKey.UI_DISABLE_FEEDBACK_DIALOG) && 'skipFeedbackDialog',
...(STATES.userAgent.capabilities.touch ? [
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'patchShowSensorControls',
getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'exposeTouchLayoutManager',
(getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
getPref<TouchControllerDefaultOpacity>(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
(getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getPref(PrefKey.MKB_ENABLED) || getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass',
getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'patchShowSensorControls',
getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL && 'exposeTouchLayoutManager',
(getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.OFF || getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
getPref(PrefKey.TOUCH_CONTROLLER_DEFAULT_OPACITY) !== 100 && 'patchTouchControlDefaultOpacity',
(getPref(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF && (getPref(PrefKey.MKB_ENABLED) || getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) && 'patchBabylonRendererClass',
] : []),
BX_FLAGS.EnableXcloudLogging && 'enableConsoleLogging',
@@ -1105,7 +1124,7 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
] : []),
// Native MKB
...(AppInterface && getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
'patchMouseAndKeyboardEnabled',
'disableNativeRequestPointerLock',
] : []),

View File

@@ -1,99 +0,0 @@
if (window.BX_EXPOSED.disableGamepadPolling) {
this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(50) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, 50);
return;
}
const currentGamepad = ${gamepadVar};
// Share button on XS controller
if (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {
window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));
}
const btnHome = currentGamepad.buttons[16];
if (btnHome) {
if (!this.bxHomeStates) {
this.bxHomeStates = {};
}
let intervalMs = 0;
let hijack = false;
if (btnHome.pressed) {
hijack = true;
intervalMs = 16;
this.gamepadIsIdle.set(currentGamepad.index, false);
if (this.bxHomeStates[currentGamepad.index]) {
const lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;
if (currentGamepad.timestamp !== lastTimestamp) {
this.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;
const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);
if (handled) {
this.bxHomeStates[currentGamepad.index].shortcutPressed += 1;
}
}
} else {
// First time pressing > save current timestamp
window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);
this.bxHomeStates[currentGamepad.index] = {
shortcutPressed: 0,
timestamp: currentGamepad.timestamp,
};
}
} else if (this.bxHomeStates[currentGamepad.index]) {
hijack = true;
const info = structuredClone(this.bxHomeStates[currentGamepad.index]);
// Home button released
this.bxHomeStates[currentGamepad.index] = null;
if (info.shortcutPressed === 0) {
const fakeGamepadMappings = [{
GamepadIndex: currentGamepad.index,
A: 0,
B: 0,
X: 0,
Y: 0,
LeftShoulder: 0,
RightShoulder: 0,
LeftTrigger: 0,
RightTrigger: 0,
View: 0,
Menu: 0,
LeftThumb: 0,
RightThumb: 0,
DPadUp: 0,
DPadDown: 0,
DPadLeft: 0,
DPadRight: 0,
Nexus: 1,
LeftThumbXAxis: 0,
LeftThumbYAxis: 0,
RightThumbXAxis: 0,
RightThumbYAxis: 0,
PhysicalPhysicality: 0,
VirtualPhysicality: 0,
Dirty: true,
Virtual: false,
}];
const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;
intervalMs = isLongPress ? 500 : 100;
this.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);
} else {
intervalMs = window.BX_STREAM_SETTINGS.controllerPollingRate;
}
}
if (hijack && intervalMs) {
// Listen to next button press
this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);
// Hijack this button
return;
}
}

View File

@@ -1,2 +0,0 @@
connectMode: window.BX_REMOTE_PLAY_CONFIG ? "xhome-connect" : "cloud-connect",
remotePlayServerId: (window.BX_REMOTE_PLAY_CONFIG && window.BX_REMOTE_PLAY_CONFIG.serverId) || '',

View File

@@ -1,7 +0,0 @@
const msg = JSON.parse(e);
if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {
try {
this.sendKeepAlive();
return;
} catch (ex) { console.log(ex); }
}

View File

@@ -0,0 +1,167 @@
import { BxEvent as BxEventNamespace } from "@/utils/bx-event";
// "currentGamepad" variable in poll-gamepad.js
declare const currentGamepad: Gamepad;
declare const $xCloudGamepadVar$: XcloudGamepad;
declare const BxEvent: typeof BxEventNamespace;
// Share button on XS controller
const shareButtonPressed = currentGamepad.buttons[17]?.pressed;
let shareButtonHandled = false;
const xCloudGamepad: XcloudGamepad = $xCloudGamepadVar$;
if (currentGamepad.id in window.BX_STREAM_SETTINGS.controllers) {
const controller = window.BX_STREAM_SETTINGS.controllers[currentGamepad.id];
if (controller?.customization) {
const MIN_RANGE = 0.1;
const { mapping, ranges } = controller.customization;
const pressedButtons: Partial<Record<keyof XcloudGamepad, number>> = {};
const releasedButtons: Partial<Record<keyof XcloudGamepad, number>> = {};
let isModified = false;
// Limit left trigger range
if (ranges.LeftTrigger) {
const [from, to] = ranges.LeftTrigger;
xCloudGamepad.LeftTrigger = xCloudGamepad.LeftTrigger > to ? 1 : xCloudGamepad.LeftTrigger;
xCloudGamepad.LeftTrigger = xCloudGamepad.LeftTrigger < from ? 0 : xCloudGamepad.LeftTrigger;
}
// Limit right trigger range
if (ranges.RightTrigger) {
const [from, to] = ranges.RightTrigger;
xCloudGamepad.RightTrigger = xCloudGamepad.RightTrigger > to ? 1 : xCloudGamepad.RightTrigger;
xCloudGamepad.RightTrigger = xCloudGamepad.RightTrigger < from ? 0 : xCloudGamepad.RightTrigger;
}
// Limit left stick deadzone
if (ranges.LeftThumb) {
const [from, to] = ranges.LeftThumb;
const xAxis = xCloudGamepad.LeftThumbXAxis;
const yAxis = xCloudGamepad.LeftThumbYAxis;
const range = Math.abs(Math.sqrt(xAxis * xAxis + yAxis * yAxis));
let newRange = range > to ? 1 : range;
newRange = newRange < from ? 0 : newRange;
if (newRange !== range) {
xCloudGamepad.LeftThumbXAxis = xAxis * (newRange / range);
xCloudGamepad.LeftThumbYAxis = yAxis * (newRange / range);
}
}
// Limit right stick deadzone
if (ranges.RightThumb) {
const [from, to] = ranges.RightThumb;
const xAxis = xCloudGamepad.RightThumbXAxis;
const yAxis = xCloudGamepad.RightThumbYAxis;
const range = Math.abs(Math.sqrt(xAxis * xAxis + yAxis * yAxis));
let newRange = range > to ? 1 : range;
newRange = newRange < from ? 0 : newRange;
if (newRange !== range) {
xCloudGamepad.RightThumbXAxis = xAxis * (newRange / range);
xCloudGamepad.RightThumbYAxis = yAxis * (newRange / range);
}
}
// Handle the Share button
if (shareButtonPressed && 'Share' in mapping) {
const targetButton = mapping['Share'];
if (typeof targetButton === 'string') {
pressedButtons[targetButton] = 1;
}
// Don't send capturing request
shareButtonHandled = true;
delete mapping['Share'];
}
// Handle other buttons
let key: keyof typeof mapping;
for (key in mapping) {
const mappedKey = mapping[key];
if (key === 'LeftStickAxes' || key === 'RightStickAxes') {
let sourceX: keyof XcloudGamepad;
let sourceY: keyof XcloudGamepad;
let targetX: keyof XcloudGamepad;
let targetY: keyof XcloudGamepad;
if (key === 'LeftStickAxes') {
sourceX = 'LeftThumbXAxis';
sourceY = 'LeftThumbYAxis';
targetX = 'RightThumbXAxis';
targetY = 'RightThumbYAxis';
} else {
sourceX = 'RightThumbXAxis';
sourceY = 'RightThumbYAxis';
targetX = 'LeftThumbXAxis';
targetY = 'LeftThumbYAxis';
}
if (typeof mappedKey === 'string') {
// Calculate moved range
const rangeX = xCloudGamepad[sourceX];
const rangeY = xCloudGamepad[sourceY];
const movedRange = Math.abs(Math.sqrt(rangeX * rangeX + rangeY * rangeY));
const moved = movedRange >= MIN_RANGE;
// Swap sticks
if (moved) {
pressedButtons[targetX] = rangeX;
pressedButtons[targetY] = rangeY;
}
}
// Unbind original stick
releasedButtons[sourceX] = 0;
releasedButtons[sourceY] = 0;
isModified = true;
} else if (typeof mappedKey === 'string') {
let pressed = false;
let value = 0;
if (key === 'LeftTrigger' || key === 'RightTrigger') {
// Only set pressed state when pressing pass max range
const currentRange = xCloudGamepad[key];
if (mappedKey === 'LeftTrigger' || mappedKey === 'RightTrigger') {
pressed = currentRange >= MIN_RANGE;
value = currentRange;
} else {
pressed = true;
value = currentRange >= 0.9 ? 1 : 0;
}
} else if (xCloudGamepad[key]) {
pressed = true;
value = xCloudGamepad[key] as number;
}
if (pressed) {
// Only copy button value when it's being pressed
pressedButtons[mappedKey] = value;
// Unbind original button
releasedButtons[key] = 0;
isModified = true;
}
} else if (mappedKey === false) {
// Disable key
pressedButtons[key] = 0;
isModified = true;
}
}
isModified && Object.assign(xCloudGamepad, releasedButtons, pressedButtons);
}
}
// Capture screenshot when the Share button is pressed
if (shareButtonPressed && !shareButtonHandled) {
window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));
}

View File

@@ -1,7 +1,15 @@
window.BX_EXPOSED.streamSession = this;
import type { MicrophoneState } from "@/modules/shortcuts/microphone-shortcut";
import { BxEvent as BxEventNamespace } from "@/utils/bx-event";
const orgSetMicrophoneState = this.setMicrophoneState.bind(this);
this.setMicrophoneState = state => {
declare const $this$: any;
declare const BxEvent: typeof BxEventNamespace;
const self = $this$;
window.BX_EXPOSED.streamSession = self;
// Patch setMicrophoneState()
const orgSetMicrophoneState = self.setMicrophoneState.bind(self);
self.setMicrophoneState = (state: MicrophoneState) => {
orgSetMicrophoneState(state);
window.BxEventBus.Stream.emit('microphone.state.changed', { state });
};
@@ -9,7 +17,7 @@ this.setMicrophoneState = state => {
window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));
// Patch updateDimensions() to make native touch work correctly with WebGL2
let updateDimensionsStr = this.updateDimensions.toString();
let updateDimensionsStr = self.updateDimensions.toString();
if (updateDimensionsStr.startsWith('function ')) {
updateDimensionsStr = updateDimensionsStr.substring(9);
@@ -19,7 +27,6 @@ if (updateDimensionsStr.startsWith('function ')) {
const renderTargetVar = updateDimensionsStr.match(/if\((\w+)\){/)[1];
updateDimensionsStr = updateDimensionsStr.replaceAll(renderTargetVar + '.scroll', 'scroll');
updateDimensionsStr = updateDimensionsStr.replace(`if(${renderTargetVar}){`, `
if (${renderTargetVar}) {
const scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;

View File

@@ -1,9 +1,14 @@
import { BxLogger as OrgBxLogger } from "@/utils/bx-logger";
declare const BxLogger: typeof OrgBxLogger;
declare const $this$: any;
// Save the original onGamepadChanged() and onGamepadInput()
this.orgOnGamepadChanged = this.onGamepadChanged;
this.orgOnGamepadInput = this.onGamepadInput;
$this$.orgOnGamepadChanged = $this$.onGamepadChanged;
$this$.orgOnGamepadInput = $this$.onGamepadInput;
let match;
let onGamepadChangedStr = this.onGamepadChanged.toString();
let onGamepadChangedStr = $this$.onGamepadChanged.toString();
// Fix problem with Safari
if (onGamepadChangedStr.startsWith('function ')) {
@@ -11,9 +16,9 @@ if (onGamepadChangedStr.startsWith('function ')) {
}
onGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');
eval(`this.patchedOnGamepadChanged = function ${onGamepadChangedStr}`);
eval(`$this$.patchedOnGamepadChanged = function ${onGamepadChangedStr}`);
let onGamepadInputStr = this.onGamepadInput.toString();
let onGamepadInputStr = $this$.onGamepadInput.toString();
// Fix problem with Safari
if (onGamepadInputStr.startsWith('function ')) {
onGamepadInputStr = onGamepadInputStr.substring(9);
@@ -22,19 +27,19 @@ if (onGamepadInputStr.startsWith('function ')) {
match = onGamepadInputStr.match(/(\w+\.GamepadIndex)/);
if (match) {
const gamepadIndexVar = match[0];
onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`);
eval(`this.patchedOnGamepadInput = function ${onGamepadInputStr}`);
onGamepadInputStr = onGamepadInputStr.replace('$this$.gamepadStates.get(', `$this$.gamepadStates.get(${gamepadIndexVar},`);
eval(`$this$.patchedOnGamepadInput = function ${onGamepadInputStr}`);
BxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');
} else {
BxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');
}
// Add method to switch between patched and original methods
this.toggleLocalCoOp = enable => {
$this$.toggleLocalCoOp = (enable: boolean) => {
BxLogger.info('toggleLocalCoOp', enable ? 'Enabled' : 'Disabled');
this.onGamepadChanged = enable ? this.patchedOnGamepadChanged : this.orgOnGamepadChanged;
this.onGamepadInput = enable ? this.patchedOnGamepadInput : this.orgOnGamepadInput;
$this$.onGamepadChanged = enable ? $this$.patchedOnGamepadChanged : $this$.orgOnGamepadChanged;
$this$.onGamepadInput = enable ? $this$.patchedOnGamepadInput : $this$.orgOnGamepadInput;
// Reconnect all gamepads
const gamepads = window.navigator.getGamepads();
@@ -54,4 +59,4 @@ this.toggleLocalCoOp = enable => {
};
// Expose this method
window.BX_EXPOSED.toggleLocalCoOp = this.toggleLocalCoOp.bind(this);
window.BX_EXPOSED.toggleLocalCoOp = $this$.toggleLocalCoOp.bind(this);

View File

@@ -0,0 +1,98 @@
type GamepadManager = {
pollGamepadssetTimeoutTimerID: number;
intervalWorker: any;
pollGamepads(pollGamepads: any, arg1: number): any;
gamepadIsIdle: any;
inputSink: any;
inputConfiguration: any;
bxHomeStates: any;
};
declare const $gamepadVar$: Gamepad;
declare const $this$: GamepadManager;
const self = $this$;
if (window.BX_EXPOSED.disableGamepadPolling) {
self.inputConfiguration.useIntervalWorkerThreadForInput && self.intervalWorker ? self.intervalWorker.scheduleTimer(50) : self.pollGamepadssetTimeoutTimerID = window.setTimeout(self.pollGamepads, 50);
// @ts-ignore
return;
}
const currentGamepad = $gamepadVar$;
const btnHome = currentGamepad.buttons[16];
// Controller shortcuts
if (btnHome) {
if (!self.bxHomeStates) {
self.bxHomeStates = {};
}
let intervalMs = 0;
let hijack = false;
if (btnHome.pressed) {
hijack = true;
intervalMs = 16;
self.gamepadIsIdle.set(currentGamepad.index, false);
if (self.bxHomeStates[currentGamepad.index]) {
const lastTimestamp = self.bxHomeStates[currentGamepad.index].timestamp;
if (currentGamepad.timestamp !== lastTimestamp) {
self.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;
const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);
if (handled) {
self.bxHomeStates[currentGamepad.index].shortcutPressed += 1;
}
}
} else {
// First time pressing > save current timestamp
window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);
self.bxHomeStates[currentGamepad.index] = {
shortcutPressed: 0,
timestamp: currentGamepad.timestamp,
};
}
} else if (self.bxHomeStates[currentGamepad.index]) {
hijack = true;
const info = structuredClone(self.bxHomeStates[currentGamepad.index]);
// Home button released
self.bxHomeStates[currentGamepad.index] = null;
if (info.shortcutPressed === 0) {
const fakeGamepadMappings: XcloudGamepad[] = [{
GamepadIndex: currentGamepad.index,
A: 0, B: 0, X: 0, Y: 0,
LeftShoulder: 0, RightShoulder: 0,
LeftTrigger: 0, RightTrigger: 0,
View: 0, Menu: 0,
LeftThumb: 0, RightThumb: 0,
DPadUp: 0, DPadDown: 0, DPadLeft: 0, DPadRight: 0,
Nexus: 1,
LeftThumbXAxis: 0, LeftThumbYAxis: 0,
RightThumbXAxis: 0, RightThumbYAxis: 0,
PhysicalPhysicality: 0, VirtualPhysicality: 0,
Dirty: true, Virtual: false,
}];
const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;
intervalMs = isLongPress ? 500 : 100;
self.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);
} else {
intervalMs = window.BX_STREAM_SETTINGS.controllerPollingRate;
}
}
if (hijack && intervalMs) {
// Listen to next button press
self.inputConfiguration.useIntervalWorkerThreadForInput && self.intervalWorker ? self.intervalWorker.scheduleTimer(intervalMs) : self.pollGamepadssetTimeoutTimerID = setTimeout(self.pollGamepads, intervalMs);
// Hijack this button
// @ts-ignore
return;
}
}

View File

@@ -0,0 +1,11 @@
declare const $this$: any;
declare const e: string;
try {
const msg = JSON.parse(e);
if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {
$this$.sendKeepAlive();
// @ts-ignore
return;
}
} catch (ex) { console.log(ex); }

View File

@@ -0,0 +1,26 @@
declare const e: {
gamepad: Gamepad;
repeat: number;
leftMotorPercent: number;
rightMotorPercent: number;
leftTriggerMotorPercent: number;
rightTriggerMotorPercent: number;
};
if (e?.gamepad?.connected) {
const gamepadSettings = window.BX_STREAM_SETTINGS.controllers[e.gamepad.id];
if (gamepadSettings?.customization) {
const intensity = gamepadSettings.customization.vibrationIntensity;
if (intensity <= 0) {
e.repeat = 0;
// @ts-ignore
return;
} else if (intensity < 1) {
e.leftMotorPercent *= intensity;
e.rightMotorPercent *= intensity;
e.leftTriggerMotorPercent *= intensity;
e.rightTriggerMotorPercent *= intensity;
}
}
}

View File

@@ -1,16 +0,0 @@
const gamepad = e.gamepad;
if (gamepad?.connected) {
const gamepadSettings = window.BX_STREAM_SETTINGS.controllers[gamepad.id];
if (gamepadSettings) {
const intensity = gamepadSettings.vibrationIntensity;
if (intensity === 0) {
return void(e.repeat = 0);
} else if (intensity < 1) {
e.leftMotorPercent *= intensity;
e.rightMotorPercent *= intensity;
e.leftTriggerMotorPercent *= intensity;
e.rightTriggerMotorPercent *= intensity;
}
}
}