diff --git a/src/core/debug/patches/patchAnimatedValue.ts b/src/core/debug/patches/patchAnimatedValue.ts deleted file mode 100644 index d5f37e8..0000000 --- a/src/core/debug/patches/patchAnimatedValue.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { instead } from "@lib/api/patcher"; -import { logger } from "@lib/utils/logger"; -import { _lazyContextSymbol } from "@metro/lazy"; -import { requireModule } from "@metro/internals/modules"; -import { LazyModuleContext } from "@metro/types"; -import { findByNameLazy } from "@metro/wrappers"; - -function log(...messages: any[]) { - try { - logger.warn("[ShiggyCord] patchAnimatedValue:", ...messages); - } catch {} -} - -/** - * Discord's bundled React Native throws `AnimatedValue: Attempting to set value - * to undefined` when a `undefined` value reaches an `Animated.Value` — either - * through `setValue()` (which calls `_updateValue`) or through the constructor. - * Some of Discord's own flows (and third-party plugins) hit this during - * animation setup, which bubbles up to the React ErrorBoundary and makes - * ShiggyCord render its custom crash screen. - * - * Fix the root cause instead of masking the symptom: - * - `_updateValue` is a prototype method, so one patch covers every instance no - * matter which module captured the class reference. - * - The constructor is guarded by swapping the public `Animated.Value` - * reference (and the module's `default` export) for a patched proxy so that - * `new Animated.Value(undefined)` can no longer throw. - */ -export default function patchAnimatedValue() { - const unpatchers: Array<() => boolean> = []; - - const install = (Class: any, source: string) => { - try { - if ( - !Class?.prototype || - typeof Class.prototype._updateValue !== "function" - ) { - log(`${source}: resolved value is not AnimatedValue, skipping`); - return; - } - - // setValue(undefined) → _updateValue(undefined). Keep the last value. - unpatchers.push( - instead( - "_updateValue", - Class.prototype, - function (this: any, args: any[], origFunc: Function) { - if (args[0] === undefined) args[0] = this?._value ?? 0; - return origFunc(...args); - }, - ), - ); - - // Constructor guard: coerce non-number to 0 so `new Animated.Value(undefined)` can't throw. - const guard = (args: any[], origFunc: Function) => { - if (typeof args[0] !== "number") args[0] = 0; - return origFunc(...args); - }; - - // 1. Module exports (`require(...).default`). - try { - const ctxt: LazyModuleContext | undefined = - findByNameLazy("AnimatedValue")[_lazyContextSymbol]; - if (ctxt?.moduleId != null) { - const moduleExports = requireModule(ctxt.moduleId); - if (moduleExports?.default === Class) { - unpatchers.push(instead("default", moduleExports, guard)); - } - } - } catch {} - - // 2. The public `Animated.Value` namespace reference. - try { - const ReactNative: any = require("react-native"); - const Animated = ReactNative?.Animated; - if (Animated && Animated.Value === Class) { - unpatchers.push(instead("Value", Animated, guard)); - } - } catch {} - - log(`${source}: patched _updateValue + constructor guard`); - } catch (e) { - log(`${source}: patch failed`, e); - } - }; - - // Primary: Discord's own RN Animated namespace (most reliable, no full scan). - try { - const ReactNative: any = require("react-native"); - install(ReactNative?.Animated?.Value, "react-native.Animated.Value"); - if (unpatchers.length > 0) { - return () => unpatchers.forEach((unpatch) => unpatch()); - } - } catch (e) { - log("react-native.Animated path failed", e); - } - - // Fallback: byName lookup. - try { - const ctxt: LazyModuleContext = - findByNameLazy("AnimatedValue")[_lazyContextSymbol]; - if (ctxt) { - ctxt.getExports((Class: any) => install(Class, "findByNameLazy")); - } else { - log("no lazy context found"); - } - } catch (e) { - log("findByNameLazy path failed", e); - } - - return () => unpatchers.forEach((unpatch) => unpatch()); -} diff --git a/src/core/debug/patches/patchSafeCard.ts b/src/core/debug/patches/patchSafeCard.ts deleted file mode 100644 index 1a61e6d..0000000 --- a/src/core/debug/patches/patchSafeCard.ts +++ /dev/null @@ -1,141 +0,0 @@ -import React from "react"; -import { View } from "react-native"; -import { byProps } from "@metro/filters"; -import { findExports } from "@metro/finders"; -import { findByStoreNameLazy } from "@metro/wrappers"; -import { instead } from "@lib/api/patcher"; -import { logger } from "@lib/utils/logger"; -import { isSemanticColor, resolveSemanticColor, semanticColors } from "@ui/color"; - -const ThemeStore = findByStoreNameLazy("ThemeStore"); - -function log(...messages: any[]) { - try { - logger.warn("[ShiggyCord] patchSafeCard:", ...messages); - } catch {} -} - -let cardHealthy = false; - -const FALLBACK_STYLE = { - borderRadius: 8, - padding: 16, -}; - -function resolveCardBackground(): string | undefined { - try { - const color = semanticColors?.CARD_BACKGROUND_DEFAULT; - if (isSemanticColor(color)) return resolveSemanticColor(color); - if (typeof color === "string") return color; - } catch {} - - return undefined; -} - -export default function patchSafeCard() { - const unpatchers: Array<() => boolean> = []; - - try { - const cardModule = findExports(byProps("Card")); - const origCard = cardModule?.Card; - if (typeof origCard !== "function") { - log("no Card function on the resolved module, skipping"); - return () => unpatchers.forEach((unpatch) => unpatch()); - } - - log("resolved design Card from first-match module"); - - let cardBackground = resolveCardBackground(); - - const unsubscribeTheme = ThemeStore?.addChangeListener?.(() => { - const resolved = resolveCardBackground(); - if (resolved) cardBackground = resolved; - }); - if (typeof unsubscribeTheme === "function") { - unpatchers.push(() => { - unsubscribeTheme(); - return true; - }); - } - - class CardProbeBoundary extends React.Component< - { - fallback: React.ReactNode; - children?: React.ReactNode; - }, - { hasError: boolean } - > { - state = { hasError: false }; - - static getDerivedStateFromError() { - return { hasError: true }; - } - - componentDidCatch(error: unknown) { - try { - log("Card render threw, using View fallback", error); - } catch {} - } - - render() { - if (this.state.hasError) return this.props.fallback; - return this.props.children; - } - } - - class HealthyProbe extends React.Component<{ - onHealthy: () => void; - children?: React.ReactNode; - }> { - componentDidMount() { - this.props.onHealthy(); - } - - render() { - return this.props.children; - } - } - - const SafeCard = function SafeCard(props: any) { - if (cardHealthy) return React.createElement(origCard, props); - - return React.createElement( - CardProbeBoundary, - { - fallback: React.createElement( - View, - { - style: [ - FALLBACK_STYLE, - ...(cardBackground - ? [{ backgroundColor: cardBackground }] - : []), - props?.style, - ], - }, - props?.children, - ), - }, - React.createElement( - HealthyProbe, - { - onHealthy: () => { - cardHealthy = true; - }, - }, - React.createElement(origCard, props), - ), - ); - }; - - unpatchers.push( - instead("Card", cardModule, (args: any[]) => SafeCard(args[0] ?? {})), - ); - - log("patched design Card (adaptive probe on first mount)"); - } catch (e) { - log("patch failed", e); - } - - return () => unpatchers.forEach((unpatch) => unpatch()); -} diff --git a/src/index.ts b/src/index.ts index 3ebf2d7..afd7bb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,4 @@ -import patchAnimatedValue from "@core/debug/patches/patchAnimatedValue"; import patchErrorBoundary from "@core/debug/patches/patchErrorBoundary"; -import patchSafeCard from "@core/debug/patches/patchSafeCard"; import initFixes from "@core/fixes"; import { initFetchI18nStrings } from "@core/i18n"; import initSettings from "@core/ui/settings"; @@ -32,8 +30,6 @@ export default async () => { ["patchCommands", () => patchCommands()], ["patchJsx", () => patchJsx()], ["patchErrorBoundary", () => patchErrorBoundary()], - ["patchAnimatedValue", () => patchAnimatedValue()], - ["patchSafeCard", () => patchSafeCard()], ["initVendettaObject", () => initVendettaObject()], ["initFetchI18nStrings", () => initFetchI18nStrings()], ["initSettings", () => initSettings()], diff --git a/src/metro/polyfills/redesign.ts b/src/metro/polyfills/redesign.ts index 9e5c1a8..f29aabd 100644 --- a/src/metro/polyfills/redesign.ts +++ b/src/metro/polyfills/redesign.ts @@ -117,17 +117,13 @@ for (const [id, moduleExports] of cacher.getModules()) { } const exportsKeysLength = Reflect.ownKeys(actualExports).length; - if (_source[prop] && exportsKeysLength >= _source[prop]) { + if (_source[prop] && exportsKeysLength <= _source[prop]) { continue; } _module[prop] = actualExports[prop]; _source[prop] = Reflect.ownKeys(actualExports).length; cacher.cacheId(id); - - if (exportsKeysLength === 1) { - redesignProps.delete(prop); - } } }