diff --git a/apps/loadout-overlay/src/bun/native/deck-hidraw-watcher.ts b/apps/loadout-overlay/src/bun/native/deck-hidraw-watcher.ts index 2033bd0a..00ce43dc 100644 --- a/apps/loadout-overlay/src/bun/native/deck-hidraw-watcher.ts +++ b/apps/loadout-overlay/src/bun/native/deck-hidraw-watcher.ts @@ -131,7 +131,9 @@ export async function startDeckHidrawWatcher( // button map per frame is unnecessary on the hot path — chase the one // bit and bail. (Less GC pressure than building a Map every report.) if (bound) { - const cur = (report[bound.byte] & (1 << bound.bit)) !== 0; + // splitReports only yields full REPORT_LEN (64-byte) frames and + // bound.byte is always < 64, so this index is provably in-bounds. + const cur = (report[bound.byte]! & (1 << bound.bit)) !== 0; if (cur && !lastBitValue && !suppressNextEdge) { opts.onWake("QamToggle"); } diff --git a/apps/loadout-overlay/src/bun/native/devices.ts b/apps/loadout-overlay/src/bun/native/devices.ts index b85ddd57..057b383d 100644 --- a/apps/loadout-overlay/src/bun/native/devices.ts +++ b/apps/loadout-overlay/src/bun/native/devices.ts @@ -164,7 +164,8 @@ export function hasCapability(caps: Uint8Array, code: number): boolean { const byteIdx = code >> 3; const bitIdx = code & 0x07; if (byteIdx >= caps.length) return false; - return (caps[byteIdx] & (1 << bitIdx)) !== 0; + // Guarded above: byteIdx < caps.length, so this index is in-bounds. + return (caps[byteIdx]! & (1 << bitIdx)) !== 0; } /** diff --git a/apps/loadout-overlay/src/bun/native/gamescope-atoms.ts b/apps/loadout-overlay/src/bun/native/gamescope-atoms.ts index 527ba3f4..e9100669 100644 --- a/apps/loadout-overlay/src/bun/native/gamescope-atoms.ts +++ b/apps/loadout-overlay/src/bun/native/gamescope-atoms.ts @@ -286,7 +286,7 @@ export class GamescopeAtoms { for (const line of lines) { if (!line.startsWith(prefix + "(")) continue; const m = line.match(/=\s*"((?:[^"\\]|\\.)*)"/); - if (m) return m[1]; + if (m) return m[1] ?? null; } } return null; @@ -311,6 +311,9 @@ export class GamescopeAtoms { managed.push(id); } } + // The sole caller only invokes this with a non-empty candidate set + // (findSteamWindow returns early when allIds is empty), so pool is + // guaranteed non-empty and pool[0] below is always defined. const pool = managed.length > 0 ? managed : candidates; // Prefer a pool window currently claiming overlay/focus. @@ -326,7 +329,7 @@ export class GamescopeAtoms { return id; } } - return pool[0]; + return pool[0]!; } /** True if `atom` is set on `windowId`. Uses xprop output to @@ -851,7 +854,9 @@ export class GamescopeAtoms { const m = line.match(/^(\w+)\([^)]+\)\s*=\s*(-?\d+)/); if (!m) continue; const n = Number(m[2]); - if (Number.isFinite(n)) out.set(m[1], n); + // Both capture groups are mandatory in the regex, so on a match + // m[1] is always present. + if (Number.isFinite(n)) out.set(m[1]!, n); } } catch (err) { console.warn( diff --git a/apps/loadout-overlay/src/bun/native/input-intercept.ts b/apps/loadout-overlay/src/bun/native/input-intercept.ts index b4ca3fb1..14e93c62 100644 --- a/apps/loadout-overlay/src/bun/native/input-intercept.ts +++ b/apps/loadout-overlay/src/bun/native/input-intercept.ts @@ -211,7 +211,9 @@ function buildBitmask(codes: readonly number[]): Uint8Array { if (byteLen < 8) byteLen = 8; const bits = new Uint8Array(byteLen); for (const code of codes) { - bits[code >> 3] |= 1 << (code & 7); + // byteLen covers (maxCode >> 3) + 1 bytes and code <= maxCode, so + // code >> 3 is always in-bounds. + bits[code >> 3]! |= 1 << (code & 7); } return bits; } @@ -411,7 +413,9 @@ function readModifierState(fd: number): { guide: boolean; select: boolean } { const buf = new Uint8Array(96); const rc = libc.symbols.ioctl(fd, eviocgkey(buf.length), ptr(buf)); if (rc < 0) return { guide: false, select: false }; - const bit = (code: number) => (buf[code >> 3] & (1 << (code & 7))) !== 0; + // The 96-byte buffer covers codes 0..767, past every code we query, so + // code >> 3 is always in-bounds. + const bit = (code: number) => (buf[code >> 3]! & (1 << (code & 7))) !== 0; return { guide: bit(BTN_MODE), select: bit(BTN_SELECT) }; } @@ -979,6 +983,7 @@ export async function startInputIntercept( const idx = tracked.findIndex((t) => t.path === eventPath); if (idx < 0) return; const [t] = tracked.splice(idx, 1); + if (!t) return; // Drop any deferred-grab entry before closing the fd: a stale entry would // make pollOnce ioctl a closed (possibly OS-reused) fd next tick. pendingGrabs.delete(t); diff --git a/apps/loadout-overlay/src/bun/native/ip-intercept.ts b/apps/loadout-overlay/src/bun/native/ip-intercept.ts index 25ac3354..a0e7519b 100644 --- a/apps/loadout-overlay/src/bun/native/ip-intercept.ts +++ b/apps/loadout-overlay/src/bun/native/ip-intercept.ts @@ -203,9 +203,11 @@ export function parseInputEventLine( ): { cap: string; value: number } | null { const m = line.match(/\.InputEvent\s+\('([^']+)',\s*([-0-9.eE]+)\)/); if (!m) return null; - const value = Number.parseFloat(m[2]); + // Both capture groups are mandatory in the regex, so on a match m[1] + // and m[2] are always present. + const value = Number.parseFloat(m[2]!); if (Number.isNaN(value)) return null; - return { cap: m[1], value }; + return { cap: m[1]!, value }; } // ---- Public API ------------------------------------------------------------ diff --git a/apps/loadout-overlay/src/overlay/components/Settings.tsx b/apps/loadout-overlay/src/overlay/components/Settings.tsx index bf01a2be..e5ad2555 100644 --- a/apps/loadout-overlay/src/overlay/components/Settings.tsx +++ b/apps/loadout-overlay/src/overlay/components/Settings.tsx @@ -777,7 +777,7 @@ function SettingsInner({ : "bg-base-300/70 text-base-content/50" }`} > - {(plugin.icon ?? plugin.name)[0].toUpperCase()} + {(plugin.icon ?? plugin.name).charAt(0).toUpperCase()}