1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
|
import { Patch, callOriginal, findModuleExport, replacePatch } from '@decky/ui';
import DeckyErrorBoundary from './components/DeckyErrorBoundary';
import Logger from './logger';
import { getLikelyErrorSourceFromValveError } from './utils/errors';
declare global {
interface Window {
__ERRORBOUNDARY_HOOK_INSTANCE: any;
}
}
class ErrorBoundaryHook extends Logger {
private errorBoundaryPatch?: Patch;
private errorCheckPatch?: Patch;
public doNotReportErrors: boolean = false;
constructor() {
super('ErrorBoundaryHook');
this.log('Initialized');
window.__ERRORBOUNDARY_HOOK_INSTANCE?.deinit?.();
window.__ERRORBOUNDARY_HOOK_INSTANCE = this;
}
init() {
// valve writes only the sanest of code
const exp = /^\(\)=>\(.\|\|.\(new .\),.\)$/;
const initErrorReportingStore = findModuleExport(
(e) => typeof e == 'function' && e?.toString && exp.test(e.toString()),
);
if (!initErrorReportingStore) {
this.error('could not find initErrorReportingStore! error boundary hook disabled!');
return;
}
// will replace the existing one for us seemingly? doesnt matter anyway lol
const errorReportingStore = initErrorReportingStore();
// NUH UH.
// Object.defineProperty(Object.getPrototypeOf(errorReportingStore), 'reporting_enabled', {
// get: () => false,
// });
// errorReportingStore.m_bEnabled = false;
// @ts-ignore
// window.errorStore = errorReportingStore;
const react15069WorkaroundRegex = / at .+\.componentDidCatch\..+\.callback /;
this.errorCheckPatch = replacePatch(Object.getPrototypeOf(errorReportingStore), 'BIsBlacklisted', (args: any[]) => {
const [errorSource, wasPlugin, shouldReport] = getLikelyErrorSourceFromValveError(args[0]);
this.debug('Caught an error', args, {
errorSource,
wasPlugin,
shouldReport,
skipAllReporting: this.doNotReportErrors,
});
// react#15069 workaround. this took 2 hours to figure out.
if (
args[0]?.message?.[3]?.[0] &&
args[0]?.message?.[1]?.[0] == ' at console.error ' &&
react15069WorkaroundRegex.test(args[0].message[3][0])
) {
this.debug('ignoring early report caused by react#15069');
return true;
}
if (this.doNotReportErrors) return true;
return shouldReport ? callOriginal : true;
});
const ValveErrorBoundary = findModuleExport(
(e) => e.InstallErrorReportingStore && e?.prototype?.Reset && e?.prototype?.componentDidCatch,
);
if (!ValveErrorBoundary) {
this.error('could not find ValveErrorBoundary');
return;
}
this.errorBoundaryPatch = replacePatch(ValveErrorBoundary.prototype, 'render', function (this: any) {
if (this.state.error) {
const store = Object.getPrototypeOf(this)?.constructor?.sm_ErrorReportingStore || errorReportingStore;
return (
<DeckyErrorBoundary
error={this.state.error}
errorKey={this.props.errorKey}
identifier={`${store.product}_${store.version}_${this.state.identifierHash}`}
reset={() => this.Reset()}
/>
);
}
return callOriginal;
});
}
deinit() {
this.errorCheckPatch?.unpatch();
this.errorBoundaryPatch?.unpatch();
}
}
export default ErrorBoundaryHook;
|