fix(lua): require() no-op + улучшенный Proxy для Instance

1. require(): в Roblox загружает ModuleScript. У нас модулей нет —
   возвращаем mod как есть (если объект) или undefined.

2. Proxy улучшения:
   - Object.hasOwnProperty + 'in' checks: методы (WaitForChild,
     FindFirstChild и т.д.) точно не перехватываются;
   - Symbol-ключи всегда undefined;
   - System keys (then, catch, toString, constructor) → undefined
     чтобы wasmoon не пытался обращаться как с Promise/класс;
   - has() возвращает true для всех строковых ключей (избавляет от
     падений на 'if obj.SomeField then ...').
This commit is contained in:
min 2026-06-08 13:08:28 +03:00
parent 59d0d86811
commit dc7420a61d

View File

@ -250,6 +250,21 @@ function makeStubSignal() {
return sig;
}
// Callable proxy: сам вызывается как function (ничего не делает), также имеет
// поля Connect/Disconnect и Fire/fire — то есть выглядит и как метод, и как
// сигнал, и как объект. Используется для unknown method-like свойств.
function makeStubCallable() {
const fn = function () { return undefined; };
fn.__stub = true;
fn.Connect = function () { return { Disconnect: () => {}, disconnect: () => {}, Connected: false }; };
fn.connect = fn.Connect;
fn.Fire = function () {};
fn.fire = fn.Fire;
fn.Wait = function () { return null; };
fn.wait = fn.Wait;
return fn;
}
// Эвристика: какие имена свойств вероятно сигналы?
// В Roblox сигналы заканчиваются на: Changed, Added, Removed, Began, Ended,
// Clicked, Activated, Touched, Selected, Deselected, Equipped, Unequipped, и т.д.
@ -290,30 +305,39 @@ function newInstance(className, name) {
// Proxy: для unknown свойств возвращаем stub чтобы скрипты не падали.
return new Proxy(target, {
get(t, prop) {
if (prop in t) return t[prop];
// Существующее свойство всегда возвращаем как есть (включая методы)
if (Object.prototype.hasOwnProperty.call(t, prop) || prop in t) {
return t[prop];
}
// Не-строки и Symbol.* — undefined чтобы wasmoon не путался
if (typeof prop !== 'string') return undefined;
// Системные/wasmoon-внутренние ключи — undefined чтобы wasmoon не путался
if (prop === 'then' || prop === 'catch' || prop === 'toJSON' ||
prop === Symbol.toPrimitive || prop.startsWith('__')) {
// wasmoon JS-internal ключи — undefined
if (prop === 'then' || prop === 'catch' || prop === 'finally' ||
prop === 'toJSON' || prop === 'toString' || prop === 'valueOf' ||
prop === 'constructor' || prop === 'prototype' ||
prop.startsWith('__') || prop.startsWith('Symbol')) {
return undefined;
}
// Эвристика: имена сигналов → stub-сигнал. Иначе — stub-Folder.
// Stub-Folder сам по себе callable (на случай если скрипт его вызовет
// как функцию: `foo()` вместо `foo:Connect()` — оба не падают).
let stub;
if (isProbablySignalName(prop)) {
const stub = makeStubSignal();
t[prop] = stub;
return stub;
stub = makeStubSignal();
} else {
stub = newInstance('Folder', prop);
}
// Иначе — child stub (Folder) который тоже выживет на чтении свойств
// и может иметь свои дочерние stub'ы. Cache в target чтобы тот же ссылочно.
const childStub = newInstance('Folder', prop);
t[prop] = childStub;
return childStub;
t[prop] = stub;
return stub;
},
set(t, prop, value) {
t[prop] = value;
return true;
},
has(t, prop) {
return prop in t || (typeof prop === 'string' && !prop.startsWith('__'));
// Для in-проверок отвечаем true почти всегда (чтобы Lua не падал на
// условиях вроде if obj.SomeField then ...)
return true;
},
});
}
@ -485,6 +509,16 @@ export function registerRobloxShim(lua, opts) {
send('log', { level: 'warn', text: args.map(stringify).join('\t') });
});
// require(ModuleScript) — в Roblox загружает модуль. У нас модулей нет —
// возвращаем undefined (Lua nil) чтобы скрипты типа local mod = require(...)
// не падали. require строкой (стандартный Lua) перехватывать не будем.
global.set('require', (mod) => {
// Если передали Instance-stub — возвращаем сам stub (чтобы хоть
// что-то можно было сделать с возвращённым значением).
if (mod && typeof mod === 'object') return mod;
return undefined;
});
// === task.* + wait ===
// task.wait/wait — реальный yield через coroutines. Юзер пишет:
// while true do part.Position = ... ; task.wait(0.1) end