File size: 3,456 Bytes
9ed8df3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""
Patch @met4citizen/talkinghead/dist/talkinghead.mjs to fix:
  "Cannot read properties of undefined (reading 'value')"

Root cause: this.mtRandomized contains morph keys not in this.mtAvatar for
the loaded GLB model. Line 2619: j = this.mtAvatar[i]; if (!j.needsUpdate) crashes.
"""
import os, sys, re

PKG_DIR = os.path.join(os.getcwd(), 'node_modules', '@met4citizen', 'talkinghead')

dist_files = ['dist/talkinghead.mjs', 'dist/talkinghead.js']
target_path = None
for name in dist_files:
    p = os.path.join(PKG_DIR, name)
    if os.path.exists(p):
        target_path = p
        break

if not target_path:
    print('ERROR: talkinghead file not found in ' + PKG_DIR)
    sys.exit(1)

print('Patching: ' + target_path)
with open(target_path, 'r') as f:
    code = f.read()

if '__VT_PATCHED__' in code:
    print('Already patched, skipping.')
    sys.exit(0)

# Fix 1: Null check for mtRandomized loop (line ~2619)
old1 = "j = this.mtAvatar[i];\n      if ( !j.needsUpdate ) {"
new1 = "j = this.mtAvatar[i];\n      if ( !j ) continue;\n      __VT_PATCHED__=1;\n      if ( !j.needsUpdate ) {"
if old1 in code:
    code = code.replace(old1, new1)
    print('OK Fix1: added null check for mtRandomized loop')
else:
    code = re.sub(
        r'(j = this\.mtAvatar\[i\];)\s*\n\s*if\s*\(\s*!j\.needsUpdate',
        r'\1\n      if ( !j ) continue;\n      __VT_PATCHED__=1;\n      if ( !j.needsUpdate',
        code
    )

# Fix 2: Guard direct .value accesses on specific keys
for key in ['bodyRotateY', 'eyeLookInLeft', 'eyeLookOutLeft', 'eyesLookDown',
            'browDownLeft', 'browDownRight', 'eyeBlinkLeft', 'eyeBlinkRight',
            'eyesLookUp', 'bodyRotateX', 'bodyRotateZ', 'headRotateX',
            'headRotateY', 'headRotateZ']:
    full_access = 'this.mtAvatar[' + repr(key) + '].value'
    safe_access = "(this.mtAvatar[" + repr(key) + "] || {}).value || 0"
    code = code.replace('this.mtAvatar[' + repr(key) + '].value', safe_access)

if '(this.mtAvatar[' + repr('bodyRotateY') + '] || {}).value' in code:
    print('OK Fix2: guarded direct .value accesses on known keys')

# Fix 3: Wrap animate method in try/catch as safety net
match = re.search(
    r'prototype\.animate\s*=\s*function\s*\(\s*t\s*\)\s*\{',
    code
)
if match:
    brace_open = code.index('{', match.end() - 1)
    depth = 1
    pos = brace_open + 1
    while depth > 0 and pos < len(code):
        ch = code[pos]
        if ch == '{': depth += 1
        elif ch == '}': depth -= 1
        pos += 1
    brace_close = pos - 1
    indent = '      '
    body = code[brace_open + 1:brace_close]
    if '__VT_CATCH__' not in body:
        new_body = (
            indent + '__VT_CATCH__=1;\n'
            + indent + 'try {\n'
            + body + '\n'
            + indent + '} catch (e) {\n'
            + indent + indent + 'if (!(e && e.message && e.message.includes("Cannot read properties of undefined"))) {\n'
            + indent + indent + indent + 'console.warn("[patch_talkinghead] animate:", e?.message || e);\n'
            + indent + indent + indent + 'throw e;\n'
            + indent + indent + '}\n'
            + indent + '}\n'
        )
        code = code[:brace_open + 1] + new_body + code[brace_close + 1:]
        print('OK Fix3: wrapped animate() in try/catch')
else:
    print('WARN Fix3: could not wrap animate()')

with open(target_path, 'w') as f:
    f.write(code)

print('Done. Wrote ' + str(len(code)) + ' bytes')