commit
173e5d94f4
|
@ -53,9 +53,11 @@ game.import("mode", function (lib, game, ui, get, ai, _status) {
|
|||
event.textnode.textContent = "正在连接...";
|
||||
clearTimeout(event.timeout);
|
||||
if (e) e.preventDefault();
|
||||
game.saveConfig("last_ip", node.textContent);
|
||||
game.connect(node.textContent, function (success) {
|
||||
const ip = node.textContent;
|
||||
game.saveConfig("last_ip", ip);
|
||||
game.connect(ip, function (success) {
|
||||
if (success) {
|
||||
game.requireSandboxOn(ip);
|
||||
var info = lib.config.reconnect_info;
|
||||
if (info && info[0] == _status.ip) {
|
||||
game.onlineID = info[1];
|
||||
|
|
|
@ -22,6 +22,8 @@ import { DynamicStyle } from "./dynamic-style/index.js";
|
|||
import { GamePromises } from "./promises.js";
|
||||
import { Check } from "./check.js";
|
||||
|
||||
import security from "../util/security.js";
|
||||
|
||||
export class Game {
|
||||
online = false;
|
||||
onlineID = null;
|
||||
|
@ -1281,6 +1283,16 @@ export class Game {
|
|||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ```plain
|
||||
* 进入沙盒运行模式
|
||||
* ```
|
||||
*
|
||||
* @param { string } ip
|
||||
*/
|
||||
requireSandboxOn(ip = "") {
|
||||
security.requireSandboxOn(ip);
|
||||
}
|
||||
/**
|
||||
* @param { string } ip
|
||||
* @param { (result: boolean) => any } callback
|
||||
|
@ -1312,6 +1324,7 @@ export class Game {
|
|||
if (callback) callback(false);
|
||||
return;
|
||||
}
|
||||
game.sandbox = security.createSandbox();
|
||||
game.ws.onopen = lib.element.ws.onopen;
|
||||
game.ws.onmessage = lib.element.ws.onmessage;
|
||||
game.ws.onerror = lib.element.ws.onerror;
|
||||
|
@ -1333,7 +1346,7 @@ export class Game {
|
|||
* @param {*} message
|
||||
*/
|
||||
sendTo(id, message) {
|
||||
return new lib.element.Client(new lib.element.NodeWS(id)).send(message);
|
||||
return new lib.element.Client(new lib.element.NodeWS(id), true).send(message);
|
||||
}
|
||||
createServer() {
|
||||
lib.node.clients = [];
|
||||
|
@ -1455,7 +1468,7 @@ export class Game {
|
|||
* @param { boolean } [options.addVideo = true]
|
||||
* @returns
|
||||
*/
|
||||
tryAudio({ audioList, autoplay = true, random = true, addVideo=true}) {
|
||||
tryAudio({ audioList, autoplay = true, random = true, addVideo = true }) {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
|
@ -2038,8 +2051,8 @@ export class Game {
|
|||
);
|
||||
}
|
||||
const blob = zip.generate({
|
||||
type: "blob",
|
||||
}),
|
||||
type: "blob",
|
||||
}),
|
||||
fileNameToSaveAs = `${exportExtension.replace(/\\|\/|:|\?|"|\*|<|>|\|/g, "-")}.zip`;
|
||||
|
||||
if (lib.device) {
|
||||
|
@ -2135,8 +2148,8 @@ export class Game {
|
|||
_status.importingExtension = true;
|
||||
try {
|
||||
// 导入普通扩展
|
||||
eval(str);
|
||||
// esm扩展可以不写game.impoprt或许会导致_status.extensionLoading不存在
|
||||
security.eval(str);
|
||||
// esm扩展可以不写game.import或许会导致_status.extensionLoading不存在
|
||||
if (Array.isArray(_status.extensionLoading)) {
|
||||
await Promise.allSettled(_status.extensionLoading);
|
||||
delete _status.extensionLoading;
|
||||
|
@ -3964,7 +3977,7 @@ export class Game {
|
|||
}
|
||||
}
|
||||
if (!callback) {
|
||||
callback = function () {};
|
||||
callback = function () { };
|
||||
}
|
||||
//try{
|
||||
// if(noinput){
|
||||
|
@ -5453,6 +5466,8 @@ export class Game {
|
|||
newvid.name1 = newvid.name2.slice(10, newvid.name1.lastIndexOf("_"));
|
||||
}
|
||||
lib.videos.unshift(newvid);
|
||||
// 清洗代理对象
|
||||
newvid.video = structuredClone(newvid.video);
|
||||
store.put(newvid);
|
||||
ui.create.videoNode(newvid, true);
|
||||
}
|
||||
|
@ -7695,7 +7710,8 @@ export class Game {
|
|||
);
|
||||
lib.status.reload++;
|
||||
return new Promise((resolve, reject) => {
|
||||
const record = lib.db.transaction([storeName], "readwrite").objectStore(storeName).put(value, idbValidKey);
|
||||
const record = lib.db.transaction([storeName], "readwrite")
|
||||
.objectStore(storeName).put(structuredClone(value), idbValidKey);
|
||||
record.onerror = event => {
|
||||
if (typeof onError == "function") {
|
||||
onError(event);
|
||||
|
@ -7753,59 +7769,59 @@ export class Game {
|
|||
return new Promise(
|
||||
query
|
||||
? (resolve, reject) => {
|
||||
lib.status.reload++;
|
||||
const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).get(query);
|
||||
idbRequest.onerror = event => {
|
||||
if (typeof onError == "function") {
|
||||
onError(event);
|
||||
game.reload2();
|
||||
resolve();
|
||||
} else {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
}
|
||||
};
|
||||
idbRequest.onsuccess = event => {
|
||||
const result = event.target.result;
|
||||
if (typeof onSuccess == "function") {
|
||||
_status.dburgent = true;
|
||||
onSuccess(result);
|
||||
delete _status.dburgent;
|
||||
}
|
||||
lib.status.reload++;
|
||||
const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).get(query);
|
||||
idbRequest.onerror = event => {
|
||||
if (typeof onError == "function") {
|
||||
onError(event);
|
||||
game.reload2();
|
||||
resolve(result);
|
||||
};
|
||||
}
|
||||
resolve();
|
||||
} else {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
}
|
||||
};
|
||||
idbRequest.onsuccess = event => {
|
||||
const result = event.target.result;
|
||||
if (typeof onSuccess == "function") {
|
||||
_status.dburgent = true;
|
||||
onSuccess(result);
|
||||
delete _status.dburgent;
|
||||
}
|
||||
game.reload2();
|
||||
resolve(result);
|
||||
};
|
||||
}
|
||||
: (resolve, reject) => {
|
||||
lib.status.reload++;
|
||||
const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).openCursor(),
|
||||
object = {};
|
||||
idbRequest.onerror = event => {
|
||||
if (typeof onError == "function") {
|
||||
onError(event);
|
||||
game.reload2();
|
||||
resolve();
|
||||
} else {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
}
|
||||
};
|
||||
idbRequest.onsuccess = event => {
|
||||
const result = event.target.result;
|
||||
if (result) {
|
||||
object[result.key] = result.value;
|
||||
result.continue();
|
||||
return;
|
||||
}
|
||||
if (typeof onSuccess == "function") {
|
||||
_status.dburgent = true;
|
||||
onSuccess(object);
|
||||
delete _status.dburgent;
|
||||
}
|
||||
lib.status.reload++;
|
||||
const idbRequest = lib.db.transaction([storeName], "readwrite").objectStore(storeName).openCursor(),
|
||||
object = {};
|
||||
idbRequest.onerror = event => {
|
||||
if (typeof onError == "function") {
|
||||
onError(event);
|
||||
game.reload2();
|
||||
resolve(object);
|
||||
};
|
||||
}
|
||||
resolve();
|
||||
} else {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
}
|
||||
};
|
||||
idbRequest.onsuccess = event => {
|
||||
const result = event.target.result;
|
||||
if (result) {
|
||||
object[result.key] = result.value;
|
||||
result.continue();
|
||||
return;
|
||||
}
|
||||
if (typeof onSuccess == "function") {
|
||||
_status.dburgent = true;
|
||||
onSuccess(object);
|
||||
delete _status.dburgent;
|
||||
}
|
||||
game.reload2();
|
||||
resolve(object);
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
|
@ -7842,45 +7858,45 @@ export class Game {
|
|||
);
|
||||
return query
|
||||
? new Promise((resolve, reject) => {
|
||||
lib.status.reload++;
|
||||
const record = lib.db.transaction([storeName], "readwrite").objectStore(storeName).delete(query);
|
||||
record.onerror = event => {
|
||||
if (typeof onError == "function") {
|
||||
onError(event);
|
||||
game.reload2();
|
||||
resolve();
|
||||
} else {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
}
|
||||
};
|
||||
record.onsuccess = event => {
|
||||
if (typeof onSuccess == "function") onSuccess(event);
|
||||
lib.status.reload++;
|
||||
const record = lib.db.transaction([storeName], "readwrite").objectStore(storeName).delete(query);
|
||||
record.onerror = event => {
|
||||
if (typeof onError == "function") {
|
||||
onError(event);
|
||||
game.reload2();
|
||||
resolve(event);
|
||||
};
|
||||
})
|
||||
resolve();
|
||||
} else {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
}
|
||||
};
|
||||
record.onsuccess = event => {
|
||||
if (typeof onSuccess == "function") onSuccess(event);
|
||||
game.reload2();
|
||||
resolve(event);
|
||||
};
|
||||
})
|
||||
: game.getDB(storeName).then(object => {
|
||||
const keys = Object.keys(object);
|
||||
lib.status.reload += keys.length;
|
||||
const store = lib.db.transaction([storeName], "readwrite").objectStore(storeName);
|
||||
return Promise.allSettled(
|
||||
keys.map(
|
||||
key =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = store.delete(key);
|
||||
request.onerror = event => {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
};
|
||||
request.onsuccess = event => {
|
||||
game.reload2();
|
||||
resolve(event);
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
const keys = Object.keys(object);
|
||||
lib.status.reload += keys.length;
|
||||
const store = lib.db.transaction([storeName], "readwrite").objectStore(storeName);
|
||||
return Promise.allSettled(
|
||||
keys.map(
|
||||
key =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = store.delete(key);
|
||||
request.onerror = event => {
|
||||
game.reload2();
|
||||
reject(event);
|
||||
};
|
||||
request.onsuccess = event => {
|
||||
game.reload2();
|
||||
resolve(event);
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @param { string } key
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Promises } from "./promises.js";
|
|||
import { rootURL } from "../../noname.js";
|
||||
import * as pinyinPro from "./pinyins/index.js";
|
||||
import { Audio } from "./audio.js";
|
||||
import security from "../util/security.js";
|
||||
|
||||
export class Get {
|
||||
is = new Is();
|
||||
|
@ -927,10 +928,10 @@ export class Get {
|
|||
const target = constructor
|
||||
? Array.isArray(obj) || obj instanceof Map || obj instanceof Set || constructor === Object
|
||||
? // @ts-ignore
|
||||
new constructor()
|
||||
new constructor()
|
||||
: constructor.name in window && /\[native code\]/.test(constructor.toString())
|
||||
? // @ts-ignore
|
||||
new constructor(obj)
|
||||
new constructor(obj)
|
||||
: obj
|
||||
: Object.create(null);
|
||||
if (target === obj) return target;
|
||||
|
@ -1488,37 +1489,98 @@ export class Get {
|
|||
infoPlayersOL(infos) {
|
||||
return Array.from(infos || []).map(get.infoPlayerOL);
|
||||
}
|
||||
/** 箭头函数头 */
|
||||
#arrowPattern = /^(?:async\b\s*)?(?:([\w$]+)|\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\))\s*=>/;
|
||||
/** 标准函数头 */
|
||||
#fullPattern = /^([\w\s*]+)\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\)\s*\{/;
|
||||
/**
|
||||
* ```plain
|
||||
* 测试一段代码是否为函数体
|
||||
* ```
|
||||
*
|
||||
* @param {string} code
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFunctionBody(code) {
|
||||
try {
|
||||
new Function(code);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* ```plain
|
||||
* 清洗函数体代码
|
||||
* ```
|
||||
*
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
pureFunctionStr(str) {
|
||||
str = str.trim();
|
||||
const arrowMatch = get.#arrowPattern.exec(str);
|
||||
if (arrowMatch) {
|
||||
let body = str.slice(arrowMatch[0].length).trim();
|
||||
if (body.startsWith("{") && body.endsWith("}")) body = body.slice(1, -1);
|
||||
else body = `return ${body}`;
|
||||
if (!get.isFunctionBody(body)) {
|
||||
console.error("发现疑似恶意的远程代码:", str);
|
||||
return `()=>console.error("尝试执行疑似恶意的远程代码")`;
|
||||
}
|
||||
return `${arrowMatch[0]}{${body}}`;
|
||||
}
|
||||
if (!str.endsWith("}")) return '()=>console.warn("无法识别的远程代码")';
|
||||
const fullMatch = get.#fullPattern.exec(str);
|
||||
if (!fullMatch) return '()=>console.warn("无法识别的远程代码")';
|
||||
const head = fullMatch[1];
|
||||
const args = fullMatch[2] || '';
|
||||
const body = str.slice(fullMatch[0].length).slice(0, -1);
|
||||
if (!get.isFunctionBody(body)) {
|
||||
console.error("发现疑似恶意的远程代码:", str);
|
||||
return `()=>console.error("尝试执行疑似恶意的远程代码")`;
|
||||
}
|
||||
str = `(${args}){${body}}`;
|
||||
if (head.includes("*")) str = "*" + str;
|
||||
str = "function" + str;
|
||||
if (/\basync\b/.test(head)) str = "async " + str;
|
||||
return str;
|
||||
}
|
||||
funcInfoOL(func) {
|
||||
if (typeof func == "function") {
|
||||
if (func._filter_args) {
|
||||
return "_noname_func:" + JSON.stringify(get.stringifiedResult(func._filter_args, 3));
|
||||
}
|
||||
const str = func.toString();
|
||||
const { Marshal, Sandbox } = security.importSandbox();
|
||||
const domain = Marshal.getMarshalledDomain(func);
|
||||
if (domain) {
|
||||
const sandbox = Sandbox.from(domain);
|
||||
if (sandbox && "client" in sandbox) throw new Error("不应该二次扩散远程代码");
|
||||
}
|
||||
const str = Marshal.decompileFunction(func);
|
||||
// js内置的函数
|
||||
if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}";
|
||||
return "_noname_func:" + str;
|
||||
return "_noname_func:" + get.pureFunctionStr(str);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
infoFuncOL(info) {
|
||||
let func;
|
||||
const str = info.slice(13).trim();
|
||||
if ("sandbox" in window) console.log("[infoFuncOL] info:", info);
|
||||
const str = get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入
|
||||
if ("sandbox" in window) console.log("[infoFuncOL] pured:", str);
|
||||
try {
|
||||
// js内置的函数
|
||||
if (/\{\s*\[native code\]\s*\}/.test(str)) return function () {};
|
||||
// 一般fun和数组形式
|
||||
if (str.startsWith("function") || str.startsWith("(")) eval(`func=(${str});`);
|
||||
// 其他奇形怪状的fun
|
||||
else {
|
||||
try {
|
||||
eval(`func = ${str}`);
|
||||
} catch {
|
||||
eval(`let obj = {${str}}; func = obj[Object.keys(obj)[0]]`);
|
||||
}
|
||||
}
|
||||
if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { };
|
||||
if (security.isSandboxRequired()) {
|
||||
const loadStr = `return (${str});`;
|
||||
const box = security.currentSandbox();
|
||||
if (!box) throw new ReferenceError("没有找到当前沙盒");
|
||||
func = box.exec(loadStr);
|
||||
} else func = security.exec(`return (${str});`);
|
||||
} catch (e) {
|
||||
console.error(`${e} in \n${str}`);
|
||||
return function () {};
|
||||
return function () { };
|
||||
}
|
||||
if (Array.isArray(func)) {
|
||||
func = get.filter.apply(this, get.parsedResult(func));
|
||||
|
@ -1528,14 +1590,14 @@ export class Get {
|
|||
eventInfoOL(item, level, noMore) {
|
||||
return get.itemtype(item) == "event"
|
||||
? `_noname_event:${JSON.stringify(
|
||||
Object.entries(item).reduce((stringifying, entry) => {
|
||||
const key = entry[0];
|
||||
if (key == "_trigger") {
|
||||
if (noMore !== false) stringifying[key] = get.eventInfoOL(entry[1], null, false);
|
||||
} else if (!lib.element.GameEvent.prototype[key] && key != "content" && get.itemtype(entry[1]) != "event") stringifying[key] = get.stringifiedResult(entry[1], null, false);
|
||||
return stringifying;
|
||||
}, {})
|
||||
)}`
|
||||
Object.entries(item).reduce((stringifying, entry) => {
|
||||
const key = entry[0];
|
||||
if (key == "_trigger") {
|
||||
if (noMore !== false) stringifying[key] = get.eventInfoOL(entry[1], null, false);
|
||||
} else if (!lib.element.GameEvent.prototype[key] && key != "content" && get.itemtype(entry[1]) != "event") stringifying[key] = get.stringifiedResult(entry[1], null, false);
|
||||
return stringifying;
|
||||
}, {})
|
||||
)}`
|
||||
: "";
|
||||
}
|
||||
/**
|
||||
|
@ -2074,8 +2136,8 @@ export class Get {
|
|||
n = game.checkMod(from, to, n, "globalFrom", from);
|
||||
n = game.checkMod(from, to, n, "globalTo", to);
|
||||
const equips1 = from.getCards("e", function (card) {
|
||||
return !ui.selected.cards || !ui.selected.cards.includes(card);
|
||||
}),
|
||||
return !ui.selected.cards || !ui.selected.cards.includes(card);
|
||||
}),
|
||||
equips2 = to.getCards("e", function (card) {
|
||||
return !ui.selected.cards || !ui.selected.cards.includes(card);
|
||||
});
|
||||
|
@ -4896,6 +4958,19 @@ export class Get {
|
|||
}
|
||||
}
|
||||
|
||||
function freezeSlot(obj, key) {
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(obj, key);
|
||||
if (!descriptor) return;
|
||||
descriptor.writable = false;
|
||||
descriptor.configurable = false;
|
||||
Reflect.defineProperty(obj, key, descriptor);
|
||||
}
|
||||
|
||||
freezeSlot(Get.prototype, "isFunctionBody");
|
||||
freezeSlot(Get.prototype, "pureFunctionStr");
|
||||
freezeSlot(Get.prototype, "funcInfoOL");
|
||||
freezeSlot(Get.prototype, "infoFuncOL");
|
||||
|
||||
export let get = new Get();
|
||||
/**
|
||||
* @param { InstanceType<typeof Get> } [instance]
|
||||
|
|
|
@ -10,6 +10,7 @@ import * as config from "../util/config.js";
|
|||
import { promiseErrorHandlerMap } from "../util/browser.js";
|
||||
import { importCardPack, importCharacterPack, importExtension, importMode } from "./import.js";
|
||||
import { onload } from "./onload.js";
|
||||
import { initializeSandboxRealms } from "../util/initRealms.js";
|
||||
|
||||
// 判断是否从file协议切换到http/s协议
|
||||
export function canUseHttpProtocol() {
|
||||
|
@ -124,6 +125,9 @@ export async function boot() {
|
|||
// 加载polyfill内容
|
||||
await import("./polyfill.js");
|
||||
|
||||
// 初始化沙盒的Realms
|
||||
await initializeSandboxRealms();
|
||||
|
||||
// 设定游戏加载时间,超过时间未加载就提醒
|
||||
const configLoadTime = localStorage.getItem(lib.configprefix + "loadtime");
|
||||
// 现在不暴露到全局变量里了,直接传给onload
|
||||
|
@ -232,6 +236,7 @@ export async function boot() {
|
|||
await window.initReadWriteFunction(g).catch((e) => {
|
||||
console.error("文件读写函数初始化失败:", e);
|
||||
});
|
||||
delete window.initReadWriteFunction; // 后续用不到了喵
|
||||
}
|
||||
window.onbeforeunload = function () {
|
||||
if (config.get("confirm_exit") && !_status.reloading) {
|
||||
|
@ -243,6 +248,13 @@ export async function boot() {
|
|||
}
|
||||
}
|
||||
|
||||
// 初始化security
|
||||
const securityModule = await import("../util/security.js");
|
||||
const security = securityModule.default;
|
||||
security.initSecurity({
|
||||
lib, game, ui, get, ai, _status, gnc,
|
||||
});
|
||||
|
||||
const loadCssPromise = loadCss();
|
||||
const loadConfigPromise = loadConfig();
|
||||
await loadCssPromise;
|
||||
|
@ -478,7 +490,7 @@ export async function boot() {
|
|||
//var backup_onload=lib.init.onload;
|
||||
_status.evaluatingExtension = true;
|
||||
try {
|
||||
eval(extcontent);
|
||||
security.eval(extcontent); // 喵?
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
|
|
@ -3,12 +3,14 @@ import { game } from "../../game/index.js";
|
|||
import { lib } from "../index.js";
|
||||
import { _status } from "../../status/index.js";
|
||||
import { ui } from "../../ui/index.js";
|
||||
import security from "../../util/security.js";
|
||||
|
||||
export class Client {
|
||||
/**
|
||||
* @param {import('../index.js').NodeWS | InstanceType<typeof import('ws').WebSocket> | Client} ws
|
||||
* @param {boolean} temp
|
||||
*/
|
||||
constructor(ws) {
|
||||
constructor(ws, temp = false) {
|
||||
if (ws instanceof Client) throw new Error("Client cannot copy.");
|
||||
this.ws = ws;
|
||||
/**
|
||||
|
@ -17,6 +19,22 @@ export class Client {
|
|||
// @ts-ignore
|
||||
this.id = ws.wsid || get.id();
|
||||
this.closed = false;
|
||||
|
||||
if (!temp) {
|
||||
this.sandbox = security.createSandbox();
|
||||
if (this.sandbox) {
|
||||
Reflect.defineProperty(this, "sandbox", {
|
||||
value: this.sandbox,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
});
|
||||
Reflect.defineProperty(this.sandbox, "client", {
|
||||
value: this,
|
||||
writable: false,
|
||||
configurable: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
send() {
|
||||
if (this.closed) return this;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { game } from "../../game/index.js";
|
|||
import { lib } from "../index.js";
|
||||
import { _status } from "../../status/index.js";
|
||||
import { AsyncFunction } from "../../util/index.js";
|
||||
import security from '../../util/security.js';
|
||||
|
||||
/**
|
||||
* 将事件Promise化以使用async异步函数来执行事件。
|
||||
|
@ -220,12 +221,19 @@ export class GameEventPromise extends Promise {
|
|||
* ```
|
||||
*/
|
||||
async debugger() {
|
||||
if (security.isSandboxRequired()) throw new Error("当前模式下禁止调试");
|
||||
return new Promise((resolve) => {
|
||||
const runCode = function (event, code) {
|
||||
try {
|
||||
// 为了使玩家调试时使用var player=xxx时不报错,故使用var
|
||||
var { player, _trigger: trigger, _result: result } = event;
|
||||
return eval(code);
|
||||
// var { player, _trigger: trigger, _result: result } = event;
|
||||
var context = {
|
||||
event,
|
||||
player: event.player,
|
||||
_trigger: event.trigger,
|
||||
_result: event.result,
|
||||
};
|
||||
return security.exec(`return ${code}`, context);
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import { _status } from "../../status/index.js";
|
|||
import { ui } from "../../ui/index.js";
|
||||
import { CacheContext } from "../cache/cacheContext.js";
|
||||
import { ChildNodesWatcher } from "../cache/childNodesWatcher.js";
|
||||
import security from "../../util/security.js";
|
||||
|
||||
export class Player extends HTMLDivElement {
|
||||
/**
|
||||
|
@ -454,7 +455,7 @@ export class Player extends HTMLDivElement {
|
|||
const vars = {};
|
||||
/**
|
||||
* 作用域
|
||||
* @type { (code: string) => any }
|
||||
* @type { ((code: string) => any)? }
|
||||
*/
|
||||
let scope;
|
||||
/** @type { Skill } */
|
||||
|
@ -500,14 +501,13 @@ export class Player extends HTMLDivElement {
|
|||
varstr += `var ${key}=lib.skill['${skillName}'].vars['${key}'];\n`;
|
||||
}
|
||||
let str = `
|
||||
function content(){
|
||||
${varstr}if(event.triggername=='${skillName}After'){
|
||||
player.removeSkill('${skillName}');
|
||||
delete lib.skill['${skillName}'];
|
||||
delete lib.translate['${skillName}'];
|
||||
return event.finish();
|
||||
}
|
||||
`;
|
||||
${varstr}if(event.triggername=='${skillName}After'){
|
||||
player.removeSkill('${skillName}');
|
||||
delete lib.skill['${skillName}'];
|
||||
delete lib.translate['${skillName}'];
|
||||
return event.finish();
|
||||
}
|
||||
`;
|
||||
for (let i = 0; i < skill.contentFuns.length; i++) {
|
||||
const fun2 = skill.contentFuns[i];
|
||||
const a = fun2.toString();
|
||||
|
@ -516,7 +516,15 @@ export class Player extends HTMLDivElement {
|
|||
const str2 = a.slice(begin, a.lastIndexOf("}") != -1 ? a.lastIndexOf("}") : undefined).trim();
|
||||
str += `'step ${i}'\n\t${str2}\n\t`;
|
||||
}
|
||||
skill.content = lib.init.parsex((scope || eval)(str + `\n};content;`), scope);
|
||||
// 防止注入喵
|
||||
if (!get.isFunctionBody(str)) throw new Error("无效的content函数代码");
|
||||
let recompiledScope;
|
||||
if (security.isSandboxRequired()) {
|
||||
recompiledScope = scope ? security.eval(`return (${scope.toString()})`) : code => security.eval(`return (${code.toString()})`);
|
||||
} else {
|
||||
recompiledScope = scope || eval;
|
||||
}
|
||||
skill.content = lib.init.parsex(recompiledScope(`(function(){\n${str}\n})`), scope && recompiledScope);
|
||||
// @ts-ignore
|
||||
skill.content._parsed = true;
|
||||
};
|
||||
|
@ -632,6 +640,7 @@ export class Player extends HTMLDivElement {
|
|||
*/
|
||||
apply(_scope) {
|
||||
if (lib.skill[skillName] != skill) throw `This skill has been destroyed`;
|
||||
if (security.isSandboxRequired()) console.warn("`player.when().apply()` 在沙盒模式下不推荐使用");
|
||||
// @ts-ignore
|
||||
scope = _scope;
|
||||
if (skill.contentFuns.length > 0) createContent();
|
||||
|
@ -3654,9 +3663,9 @@ export class Player extends HTMLDivElement {
|
|||
return Array.from(this.iterableGetCards(arg1, arg2));
|
||||
}
|
||||
/**
|
||||
* @param { Player } player
|
||||
* @param { string } [arg1]
|
||||
* @param { string } [arg2]
|
||||
* @param { Player } player
|
||||
* @param { string } [arg1]
|
||||
* @param { string } [arg2]
|
||||
* @returns { Generator<Card, void, unknown> }
|
||||
*/
|
||||
*iterableGetDiscardableCards(player, arg1, arg2) {
|
||||
|
@ -5862,7 +5871,7 @@ export class Player extends HTMLDivElement {
|
|||
return get.is.sameNature(natures, naturesx);
|
||||
};
|
||||
if (next.hasNature("poison")) delete next._triggered;
|
||||
else if(next.unreal) next._triggered = 2;
|
||||
else if (next.unreal) next._triggered = 2;
|
||||
next.setContent("damage");
|
||||
next.filterStop = function () {
|
||||
if (this.source && this.source.isDead()) delete this.source;
|
||||
|
|
|
@ -24,6 +24,7 @@ import * as Element from "./element/index.js";
|
|||
import { updateURLs } from "./update-urls.js";
|
||||
import { defaultHooks } from "./hooks/index.js";
|
||||
import { freezeButExtensible } from "../util/index.js";
|
||||
import security from "../util/security.js";
|
||||
|
||||
export class Library {
|
||||
configprefix = "noname_0.9_";
|
||||
|
@ -163,8 +164,9 @@ export class Library {
|
|||
* } }
|
||||
*/
|
||||
node;
|
||||
// 谁写的值类型是string,这也太离谱了喵
|
||||
/**
|
||||
* @type { { [key: string]: string } }
|
||||
* @type { { [key: string]: Player } }
|
||||
*/
|
||||
playerOL;
|
||||
/**
|
||||
|
@ -305,14 +307,14 @@ export class Library {
|
|||
typeof yingbianZhuzhanAI == "function"
|
||||
? yingbianZhuzhanAI(player, card, source, targets)
|
||||
: cardx => {
|
||||
var info = get.info(card);
|
||||
if (info && info.ai && info.ai.yingbian) {
|
||||
var ai = info.ai.yingbian(card, source, targets, player);
|
||||
if (!ai) return 0;
|
||||
return ai - get.value(cardx);
|
||||
} else if (get.attitude(player, source) <= 0) return 0;
|
||||
return 5 - get.value(cardx);
|
||||
},
|
||||
var info = get.info(card);
|
||||
if (info && info.ai && info.ai.yingbian) {
|
||||
var ai = info.ai.yingbian(card, source, targets, player);
|
||||
if (!ai) return 0;
|
||||
return ai - get.value(cardx);
|
||||
} else if (get.attitude(player, source) <= 0) return 0;
|
||||
return 5 - get.value(cardx);
|
||||
},
|
||||
});
|
||||
if (!game.online) return;
|
||||
_status.event._resultid = id;
|
||||
|
@ -6333,8 +6335,7 @@ export class Library {
|
|||
code = container.textarea.value;
|
||||
}
|
||||
try {
|
||||
var character = null;
|
||||
eval(code);
|
||||
var { character } = security.exec2(code);
|
||||
if (!Array.isArray(character)) {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -6421,8 +6422,7 @@ export class Library {
|
|||
code = container.textarea.value;
|
||||
}
|
||||
try {
|
||||
var character = null;
|
||||
eval(code);
|
||||
var { character } = security.exec2(code);
|
||||
if (!Array.isArray(character)) {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -6850,8 +6850,7 @@ export class Library {
|
|||
code = container.textarea.value;
|
||||
}
|
||||
try {
|
||||
var character = null;
|
||||
eval(code);
|
||||
var { character } = security.exec2(code);
|
||||
if (!get.is.object(character)) {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -7752,7 +7751,10 @@ export class Library {
|
|||
if (Array.isArray(context)) {
|
||||
try {
|
||||
const code = context.length == 1 ? context[0].string : context.reduceRight((pre, cur) => (pre.string || pre) + "." + cur.string);
|
||||
obj = eval(code);
|
||||
obj = security.exec(`return ${code};`, {
|
||||
event, trigger, player, card, cards,
|
||||
result, source, target, targets,
|
||||
});
|
||||
if (![null, undefined].includes(obj)) {
|
||||
const keys = Object.getOwnPropertyNames(obj)
|
||||
.concat(Object.getOwnPropertyNames(Object.getPrototypeOf(obj)))
|
||||
|
@ -8040,10 +8042,10 @@ export class Library {
|
|||
genAwait(item) {
|
||||
return gnc.is.generator(item)
|
||||
? gnc.of(function* () {
|
||||
for (const content of item) {
|
||||
yield content;
|
||||
}
|
||||
})()
|
||||
for (const content of item) {
|
||||
yield content;
|
||||
}
|
||||
})()
|
||||
: Promise.resolve(item);
|
||||
}
|
||||
gnc = {
|
||||
|
@ -9560,8 +9562,13 @@ export class Library {
|
|||
if (!Array.isArray(message) || typeof lib.message.client[message[0]] !== "function") {
|
||||
throw "err";
|
||||
}
|
||||
for (var i = 1; i < message.length; i++) {
|
||||
message[i] = get.parsedResult(message[i]);
|
||||
if (game.sandbox) security.enterSandbox(game.sandbox);
|
||||
try {
|
||||
for (var i = 1; i < message.length; i++) {
|
||||
message[i] = get.parsedResult(message[i]);
|
||||
}
|
||||
} finally {
|
||||
if (game.sandbox) security.exitSandbox();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
@ -9597,6 +9604,7 @@ export class Library {
|
|||
}
|
||||
game.online = false;
|
||||
game.ws = null;
|
||||
game.sandbox = null;
|
||||
},
|
||||
},
|
||||
/**
|
||||
|
@ -10613,16 +10621,16 @@ export class Library {
|
|||
const cardName = get.name(cards[0], player);
|
||||
return cardName
|
||||
? new lib.element.VCard({
|
||||
name: cardName,
|
||||
nature: get.nature(cards[0], player),
|
||||
suit: get.suit(cards[0], player),
|
||||
number: get.number(cards[0], player),
|
||||
isCard: true,
|
||||
cards: [cards[0]],
|
||||
storage: {
|
||||
stratagem_buffed: 1,
|
||||
},
|
||||
})
|
||||
name: cardName,
|
||||
nature: get.nature(cards[0], player),
|
||||
suit: get.suit(cards[0], player),
|
||||
number: get.number(cards[0], player),
|
||||
isCard: true,
|
||||
cards: [cards[0]],
|
||||
storage: {
|
||||
stratagem_buffed: 1,
|
||||
},
|
||||
})
|
||||
: new lib.element.VCard();
|
||||
}
|
||||
return null;
|
||||
|
@ -12003,7 +12011,9 @@ export class Library {
|
|||
cardPile = {};
|
||||
message = {
|
||||
server: {
|
||||
/** @this { any } */
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
init(version, config, banned_info) {
|
||||
if (lib.node.banned.includes(banned_info)) {
|
||||
this.send("denied", "banned");
|
||||
|
@ -12022,9 +12032,16 @@ export class Library {
|
|||
lib.node.clients.remove(this);
|
||||
this.closed = true;
|
||||
} else if (!_status.waitingForPlayer) {
|
||||
if (game.phaseNumber && lib.configOL.observe) {
|
||||
if (!config.nickname) {
|
||||
this.send("denied", "banned");
|
||||
lib.node.clients.remove(this);
|
||||
this.closed = true;
|
||||
} else if (game.phaseNumber && lib.configOL.observe) {
|
||||
lib.node.observing.push(this);
|
||||
this.send("reinit", lib.configOL, get.arenaState(), game.getState ? game.getState() : {}, game.ip, game.players[0].playerid, null, _status.cardtag);
|
||||
// 没有系统提示的接口喵?
|
||||
game.log("玩家 ", `#y${config.nickname}`, " 进入房间观战");
|
||||
game.me.chat(`玩家 <span style="font-weight: bold; color: rgb(126, 180, 255)">${config.nickname}</span> 进入房间观战`);
|
||||
if (!ui.removeObserve) {
|
||||
ui.removeObserve = ui.create.system(
|
||||
"移除旁观",
|
||||
|
@ -12069,37 +12086,53 @@ export class Library {
|
|||
this.send("init", this.id, lib.configOL, game.ip, window.isNonameServer, game.roomId);
|
||||
}
|
||||
},
|
||||
/** @this { any } */
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
inited() {
|
||||
this.inited = true;
|
||||
if (_status.waitingForPlayer) {
|
||||
game.updateWaiting();
|
||||
}
|
||||
},
|
||||
/** @this { any } */
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
reinited() {
|
||||
this.inited = true;
|
||||
},
|
||||
result: function (result) {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
result(result) {
|
||||
if (lib.node.observing.includes(this)) return;
|
||||
var player = lib.playerOL[this.id];
|
||||
if (player) {
|
||||
player.unwait(result);
|
||||
}
|
||||
},
|
||||
tempResult: function (result) {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
tempResult(result) {
|
||||
if (lib.node.observing.includes(this)) return;
|
||||
var player = lib.playerOL[this.id];
|
||||
if (player) {
|
||||
player.tempUnwait(result);
|
||||
}
|
||||
},
|
||||
startGame: function () {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
startGame() {
|
||||
if (this.id == game.onlinezhu) {
|
||||
game.resume();
|
||||
}
|
||||
},
|
||||
changeRoomConfig: function (config) {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
changeRoomConfig(config) {
|
||||
if (this.id == game.onlinezhu) {
|
||||
game.broadcastAll(function (config) {
|
||||
for (var i in config) {
|
||||
|
@ -12126,7 +12159,10 @@ export class Library {
|
|||
}
|
||||
}
|
||||
},
|
||||
changeNumConfig: function (num, index, bool) {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
changeNumConfig(num, index, bool) {
|
||||
if (this.id == game.onlinezhu) {
|
||||
lib.configOL.number = num;
|
||||
game.send("server", "config", lib.configOL);
|
||||
|
@ -12140,14 +12176,20 @@ export class Library {
|
|||
}
|
||||
}
|
||||
},
|
||||
throwEmotion: function (target, emotion, rotate) {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
throwEmotion(target, emotion, rotate) {
|
||||
if (lib.node.observing.includes(this)) return;
|
||||
var player = lib.playerOL[this.id];
|
||||
if (player) {
|
||||
player.throwEmotion(target, emotion, rotate);
|
||||
}
|
||||
},
|
||||
emotion: function (id, pack, emotion) {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
emotion(id, pack, emotion) {
|
||||
if (lib.node.observing.includes(this)) return;
|
||||
var that = this;
|
||||
if (
|
||||
|
@ -12177,7 +12219,10 @@ export class Library {
|
|||
}
|
||||
if (player) player.emotion(pack, emotion);
|
||||
},
|
||||
chat: function (id, str) {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
chat(id, str) {
|
||||
if (lib.node.observing.includes(this)) return;
|
||||
var that = this;
|
||||
if (
|
||||
|
@ -12207,8 +12252,18 @@ export class Library {
|
|||
}
|
||||
if (player) player.chat(str);
|
||||
},
|
||||
giveup: function (player) {
|
||||
/**
|
||||
* ```plain
|
||||
* 当客机向主机发送投降请求时的回调
|
||||
* ```
|
||||
*
|
||||
* @this {import("./element/client.js").Client}
|
||||
* @param {Player} player
|
||||
*/
|
||||
giveup(player) {
|
||||
if (lib.node.observing.includes(this) || !player || !player._giveUp) return;
|
||||
var self = lib.playerOL[this.id];
|
||||
if (self !== player) return; // 禁止让别人投降
|
||||
_status.event.next.length = 0;
|
||||
game
|
||||
.createEvent("giveup", false)
|
||||
|
@ -12219,7 +12274,10 @@ export class Library {
|
|||
player.die("nosource").includeOut = true;
|
||||
}).player = player;
|
||||
},
|
||||
auto: function () {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
auto() {
|
||||
if (lib.node.observing.includes(this)) return;
|
||||
var player = lib.playerOL[this.id];
|
||||
if (player) {
|
||||
|
@ -12230,7 +12288,10 @@ export class Library {
|
|||
}, player);
|
||||
}
|
||||
},
|
||||
unauto: function () {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
unauto() {
|
||||
if (lib.node.observing.includes(this)) return;
|
||||
var player = lib.playerOL[this.id];
|
||||
if (player) {
|
||||
|
@ -12241,18 +12302,21 @@ export class Library {
|
|||
}, player);
|
||||
}
|
||||
},
|
||||
exec: function (func) {
|
||||
exec(func) {
|
||||
// if(typeof func=='function'){
|
||||
// var args=Array.from(arguments);
|
||||
// args.shift();
|
||||
// func.apply(this,args);
|
||||
// }
|
||||
},
|
||||
log: function () {
|
||||
/**
|
||||
* @this {import("./element/client.js").Client}
|
||||
*/
|
||||
log() {
|
||||
var items = [];
|
||||
try {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
eval("items.push(" + arguments[i] + ")");
|
||||
items.push(this.sandbox.exec(`return ${arguments[i]}`));
|
||||
}
|
||||
} catch (e) {
|
||||
this.send("log", ["err"]);
|
||||
|
@ -12521,7 +12585,7 @@ export class Library {
|
|||
navigator.clipboard
|
||||
.readText()
|
||||
.then(read)
|
||||
.catch(_ => {});
|
||||
.catch(_ => { });
|
||||
} else {
|
||||
var input = ui.create.node("textarea", ui.window, { opacity: "0" });
|
||||
input.select();
|
||||
|
|
|
@ -11,6 +11,8 @@ import { GameEvent } from "../element/gameEvent.js";
|
|||
import { GameEventPromise } from "../element/gameEventPromise.js";
|
||||
import { rootURL } from "../../../noname.js";
|
||||
|
||||
import security from "../../util/security.js";
|
||||
|
||||
export class LibInit {
|
||||
/**
|
||||
* 部分函数的Promise版本
|
||||
|
@ -140,8 +142,15 @@ export class LibInit {
|
|||
if (!Array.isArray(message) || typeof lib.message.server[message[0]] !== "function") {
|
||||
throw "err";
|
||||
}
|
||||
for (var i = 1; i < message.length; i++) {
|
||||
message[i] = get.parsedResult(message[i]);
|
||||
// @ts-ignore
|
||||
if (client.sandbox) security.enterSandbox(client.sandbox);
|
||||
try {
|
||||
for (var i = 1; i < message.length; i++) {
|
||||
message[i] = get.parsedResult(message[i]);
|
||||
}
|
||||
} finally {
|
||||
// @ts-ignore
|
||||
if (client.sandbox) security.exitSandbox();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
@ -283,7 +292,7 @@ export class LibInit {
|
|||
if (data.includes("sojson") || data.includes("jsjiami") || data.includes("var _0x")) alert(`检测到您安装了使用免费版sojson进行加密的扩展。请谨慎使用这些扩展,避免游戏数据遭到破坏。\n扩展文件:${pathToRead}`);
|
||||
}
|
||||
try {
|
||||
window.eval(data);
|
||||
security.eval(data);
|
||||
if (typeof onLoad == "function") onLoad();
|
||||
} catch (error) {
|
||||
if (typeof onError == "function") onError(error);
|
||||
|
@ -584,15 +593,25 @@ export class LibInit {
|
|||
* @returns
|
||||
*/
|
||||
parsex(item, scope) {
|
||||
// 虽然现在 parsex 被控制到了沙盒,
|
||||
// 但是因为默认沙盒还是可以额外操作东西,
|
||||
// 故而对不同的运行域做了区分
|
||||
let [
|
||||
ModFunction,
|
||||
ModGeneratorFunction,
|
||||
// ModAsyncFunction,
|
||||
// ModAsyncGeneratorFunction,
|
||||
] = security.getIsolatedsFrom(item);
|
||||
|
||||
//by 诗笺、Tipx-L
|
||||
/**
|
||||
* @param {Function} func
|
||||
*/
|
||||
function Legacy(func) {
|
||||
const { Marshal } = security.importSandbox();
|
||||
//Remove all comments
|
||||
//移除所有注释
|
||||
let str = func
|
||||
.toString()
|
||||
let str = Marshal.decompileFunction(func)
|
||||
.replace(/((?:(?:^[ \t]*)?(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/(?:[ \t]*\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/)))?|\/\/(?:[^\\]|\\(?:\r?\n)?)*?(?:\r?\n(?=[ \t]*(?:\r?\n|\/\*|\/\/))|(?=\r?\n))))+)|("(?:\\[\s\S]|[^"\\])*"|'(?:\\[\s\S]|[^'\\])*'|(?:\r?\n|[\s\S])[^/"'\\\s]*)/gm, "$2")
|
||||
.trim();
|
||||
//获取第一个 { 后的所有字符
|
||||
|
@ -608,7 +627,7 @@ export class LibInit {
|
|||
debuggerCopy = debuggerCopy.slice(0, debuggerSkip + debuggerResult.index) + insertDebugger + debuggerCopy.slice(debuggerSkip + debuggerResult.index + debuggerResult[0].length, -1);
|
||||
//测试是否有错误
|
||||
try {
|
||||
new GeneratorFunction(debuggerCopy);
|
||||
new ModGeneratorFunction(debuggerCopy);
|
||||
str = debuggerCopy + "}";
|
||||
debuggerSkip += debuggerResult.index + insertDebugger.length;
|
||||
hasDebugger = true;
|
||||
|
@ -635,7 +654,7 @@ export class LibInit {
|
|||
copy = copy.slice(0, skip + result.index) + insertStr + copy.slice(skip + result.index + result[0].length);
|
||||
//测试是否有错误
|
||||
try {
|
||||
new (hasDebugger ? GeneratorFunction : Function)(copy);
|
||||
new (hasDebugger ? ModGeneratorFunction : ModFunction)(copy);
|
||||
str = copy;
|
||||
skip += result.index + insertStr.length;
|
||||
} catch (error) {
|
||||
|
@ -647,11 +666,12 @@ export class LibInit {
|
|||
str = `if(event.step==${k}){event.finish();return;}` + str;
|
||||
}
|
||||
if (!scope) {
|
||||
return new (hasDebugger ? GeneratorFunction : Function)("event", "step", "source", "player", "target", "targets", "card", "cards", "skill", "forced", "num", "trigger", "result", "_status", "lib", "game", "ui", "get", "ai", str);
|
||||
return new (hasDebugger ? ModGeneratorFunction : ModFunction)("event", "step", "source", "player", "target", "targets", "card", "cards", "skill", "forced", "num", "trigger", "result", "_status", "lib", "game", "ui", "get", "ai", str);
|
||||
} else {
|
||||
return scope(`function${hasDebugger ? "*" : ""} anonymous(event,step,source,player,target,targets,
|
||||
new (hasDebugger ? ModGeneratorFunction : ModFunction)(str); // 防止注入喵
|
||||
return scope(`(function${hasDebugger ? "*" : ""}(event,step,source,player,target,targets,
|
||||
card,cards,skill,forced,num,trigger,result,
|
||||
_status,lib,game,ui,get,ai){${str}}; anonymous;`);
|
||||
_status,lib,game,ui,get,ai){${str}})`);
|
||||
}
|
||||
}
|
||||
switch (typeof item) {
|
||||
|
@ -687,7 +707,8 @@ export class LibInit {
|
|||
};
|
||||
} else {
|
||||
if (Symbol.iterator in item) return lib.init.parsex(Array.from(item));
|
||||
if (item.toString !== Object.prototype.toString) return lib.init.parsex(item.toString());
|
||||
// 根据狂神喵的建议,禁用parsex接受字符串喵
|
||||
// if (item.toString !== Object.prototype.toString) return lib.init.parsex(item.toString());
|
||||
if ("render" in item) {
|
||||
// TODO: Object Render Parse
|
||||
throw new Error("NYI: Object Render Parse");
|
||||
|
@ -744,22 +765,22 @@ export class LibInit {
|
|||
content._gen = true;
|
||||
return content;
|
||||
} else if (item._parsed) return item;
|
||||
// falls through
|
||||
default:
|
||||
return Legacy(item);
|
||||
default:
|
||||
throw new TypeError("为确保安全禁止用parsex解析字符串");
|
||||
}
|
||||
}
|
||||
|
||||
eval(func) {
|
||||
if (typeof func == "function") {
|
||||
return eval("(" + func.toString() + ")");
|
||||
return security.eval(`return (${func.toString()});`);
|
||||
} else if (typeof func == "object") {
|
||||
for (var i in func) {
|
||||
if (Object.prototype.hasOwnProperty.call(func, i)) {
|
||||
if (typeof func[i] == "function") {
|
||||
let checkObject = {};
|
||||
checkObject[i] = func[i];
|
||||
return eval(`(function(){return ${get.stringify(checkObject)};})()`)[i];
|
||||
return security.eval(`return ${get.stringify(checkObject)};`)[i];
|
||||
} else {
|
||||
func[i] = lib.init.eval(func[i]);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from "../index.js";
|
||||
import { ui, game, get, ai, lib, _status } from "../../../../../noname.js";
|
||||
import { nonameInitialized } from "../../../../util/index.js";
|
||||
import security from "../../../../util/security.js";
|
||||
|
||||
export const extensionMenu = function (connectMenu) {
|
||||
if (connectMenu) return;
|
||||
|
@ -332,20 +333,17 @@ export const extensionMenu = function (connectMenu) {
|
|||
inputExtName.disabled = true;
|
||||
setTimeout(function () {
|
||||
var ext = {};
|
||||
var config = null,
|
||||
help = null;
|
||||
for (var i in dash4.content) {
|
||||
try {
|
||||
if (i == "content" || i == "precontent") {
|
||||
eval("ext[i]=" + dash4.content[i]);
|
||||
ext[i] = security.exec2(`return (${dash4.content[i]});`).return;
|
||||
if (typeof ext[i] != "function") {
|
||||
throw "err";
|
||||
} else {
|
||||
ext[i] = ext[i].toString();
|
||||
}
|
||||
} else {
|
||||
eval(dash4.content[i]);
|
||||
eval("ext[i]=" + i);
|
||||
ext[i] = security.exec2(dash4.content[i])[i];
|
||||
if (ext[i] == null || typeof ext[i] != "object") {
|
||||
throw "err";
|
||||
} else {
|
||||
|
@ -382,16 +380,16 @@ export const extensionMenu = function (connectMenu) {
|
|||
if (typeof game.readFile == "function") {
|
||||
info[4].push(
|
||||
"die:ext:" +
|
||||
page.currentExtension +
|
||||
"/audio/die/" +
|
||||
tag.slice(tag.lastIndexOf("/") + 1)
|
||||
page.currentExtension +
|
||||
"/audio/die/" +
|
||||
tag.slice(tag.lastIndexOf("/") + 1)
|
||||
);
|
||||
} else {
|
||||
info[4].push(
|
||||
"die:db:extension-" +
|
||||
page.currentExtension +
|
||||
":audio/die/" +
|
||||
tag.slice(tag.lastIndexOf("/") + 1)
|
||||
page.currentExtension +
|
||||
":audio/die/" +
|
||||
tag.slice(tag.lastIndexOf("/") + 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1681,8 +1679,7 @@ export const extensionMenu = function (connectMenu) {
|
|||
code = container.textarea.value;
|
||||
}
|
||||
try {
|
||||
var card = null;
|
||||
eval(code);
|
||||
var { card } = security.exec2(code);
|
||||
if (card == null || typeof card != "object") {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -1771,8 +1768,7 @@ export const extensionMenu = function (connectMenu) {
|
|||
page.content.pack.translate[name] = translate;
|
||||
page.content.pack.translate[name + "_info"] = info;
|
||||
try {
|
||||
var card = null;
|
||||
eval(container.code);
|
||||
var { card } = security.exec2(container.code);
|
||||
if (card == null || typeof card != "object") {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -2140,8 +2136,7 @@ export const extensionMenu = function (connectMenu) {
|
|||
code = container.textarea.value;
|
||||
}
|
||||
try {
|
||||
var skill = null;
|
||||
eval(code);
|
||||
var { skill } = security.exec2(code);
|
||||
if (skill == null || typeof skill != "object") {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -2323,8 +2318,7 @@ export const extensionMenu = function (connectMenu) {
|
|||
page.content.pack.translate[name] = translate;
|
||||
page.content.pack.translate[name + "_info"] = info;
|
||||
try {
|
||||
var skill = null;
|
||||
eval(container.code);
|
||||
var { skill } = security.exec2(container.code);
|
||||
if (skill == null || typeof skill != "object") {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -2454,20 +2448,17 @@ export const extensionMenu = function (connectMenu) {
|
|||
}
|
||||
try {
|
||||
if (link == "content" || link == "precontent") {
|
||||
var func = null;
|
||||
eval("func=" + code);
|
||||
var { func } = security.exec2(`func = ${code}`);
|
||||
if (typeof func != "function") {
|
||||
throw "err";
|
||||
}
|
||||
} else if (link == "config") {
|
||||
var config = null;
|
||||
eval(code);
|
||||
var { config } = security.exec2(code);
|
||||
if (config == null || typeof config != "object") {
|
||||
throw "err";
|
||||
}
|
||||
} else if (link == "help") {
|
||||
var help = null;
|
||||
eval(code);
|
||||
var { help } = security.exec2(code);
|
||||
if (help == null || typeof help != "object") {
|
||||
throw "err";
|
||||
}
|
||||
|
@ -2913,7 +2904,7 @@ export const extensionMenu = function (connectMenu) {
|
|||
referrerPolicy: "no-referrer",
|
||||
})
|
||||
.then((response) => response.text())
|
||||
.then(eval)
|
||||
.then(security.eval) // 返回的是HTML?
|
||||
.then(loaded)
|
||||
.catch((reason) => {
|
||||
console.log(reason);
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
getLatestVersionFromGitHub,
|
||||
getTreesFromGithub,
|
||||
} from "../../../../library/update.js";
|
||||
import security from "../../../../util/security.js";
|
||||
|
||||
export const otherMenu = function (/** @type { boolean | undefined } */ connectMenu) {
|
||||
if (connectMenu) return;
|
||||
|
@ -1212,73 +1213,92 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM
|
|||
ai: ai,
|
||||
cheat: lib.cheat,
|
||||
});
|
||||
Object.defineProperties(proxyWindow, {
|
||||
_status: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
lib: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
game: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
ui: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
get: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
ai: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
cheat: {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
},
|
||||
});
|
||||
proxyWindow = new Proxy(proxyWindow, {
|
||||
set(target, prop, newValue) {
|
||||
if (!["_status", "lib", "game", "ui", "get", "ai", "cheat"].includes(prop)) {
|
||||
Reflect.set(window, prop, newValue);
|
||||
}
|
||||
return Reflect.set(target, prop, newValue);
|
||||
},
|
||||
});
|
||||
if (security.isSandboxRequired()) {
|
||||
const { Monitor, AccessAction } = security.importSandbox();
|
||||
new Monitor()
|
||||
.action(AccessAction.DEFINE)
|
||||
.action(AccessAction.WRITE)
|
||||
.action(AccessAction.DELETE)
|
||||
.require("target", proxyWindow)
|
||||
.require("property", "_status", "lib", "game", "ui", "get", "ai", "cheat")
|
||||
.then((access, nameds, control) => {
|
||||
if (access.action == AccessAction.DEFINE) {
|
||||
control.preventDefault();
|
||||
control.stopPropagation();
|
||||
control.setReturnValue(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
control.overrideParameter("target", window);
|
||||
})
|
||||
.start();
|
||||
} else {
|
||||
const keys = ["_status", "lib", "game", "ui", "get", "ai", "cheat"];
|
||||
|
||||
for (const key of keys) {
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(proxyWindow, key);
|
||||
if (!descriptor) continue;
|
||||
descriptor.writable = false;
|
||||
descriptor.enumerable = true;
|
||||
descriptor.configurable = false;
|
||||
Reflect.defineProperty(proxyWindow, key, descriptor);
|
||||
}
|
||||
|
||||
proxyWindow = new Proxy(proxyWindow, {
|
||||
set(target, propertyKey, value, receiver) {
|
||||
if (typeof propertyKey == "string" && keys.includes(propertyKey)) {
|
||||
return Reflect.set(target, propertyKey, value, receiver);
|
||||
}
|
||||
|
||||
return Reflect.set(window, propertyKey, value);
|
||||
},
|
||||
});
|
||||
}
|
||||
//使用new Function隔绝作用域,避免在控制台可以直接访问到runCommand等变量
|
||||
/**
|
||||
* @type { (value:string)=>any }
|
||||
*/
|
||||
const fun = new Function(
|
||||
"window",
|
||||
`
|
||||
const _status=window._status;
|
||||
const lib=window.lib;
|
||||
const game=window.game;
|
||||
const ui=window.ui;
|
||||
const get=window.get;
|
||||
const ai=window.ai;
|
||||
const cheat=window.lib.cheat;
|
||||
//使用正则匹配绝大多数的普通obj对象,避免解析成代码块。
|
||||
const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/};
|
||||
return function(value){
|
||||
"use strict";
|
||||
return eval(reg.test(value)?('('+value+')'):value);
|
||||
}
|
||||
`
|
||||
)(proxyWindow);
|
||||
let fun
|
||||
if (security.isSandboxRequired()) {
|
||||
const reg = /^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/;
|
||||
fun = function (value) {
|
||||
const exp = reg.test(value) ? `(${value})` : value;
|
||||
const expName = "_" + Math.random().toString().slice(2);
|
||||
return security.exec(`return eval(${expName})`, { window: proxyWindow, [expName]: exp });
|
||||
};
|
||||
// security.exec(`
|
||||
// const _status=window._status;
|
||||
// const lib=window.lib;
|
||||
// const game=window.game;
|
||||
// const ui=window.ui;
|
||||
// const get=window.get;
|
||||
// const ai=window.ai;
|
||||
// // const cheat=window.lib.cheat; // 不再允许使用 cheat,因为它是不允许访问的变量
|
||||
// //使用正则匹配绝大多数的普通obj对象,避免解析成代码块。
|
||||
// const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/};
|
||||
// return function(value){
|
||||
// "use strict";
|
||||
// return eval(reg.test(value)?('('+value+')'):value);
|
||||
// };
|
||||
// `, { window: proxyWindow });
|
||||
} else {
|
||||
fun = (new Function('window', `
|
||||
const _status=window._status;
|
||||
const lib=window.lib;
|
||||
const game=window.game;
|
||||
const ui=window.ui;
|
||||
const get=window.get;
|
||||
const ai=window.ai;
|
||||
const cheat=window.lib.cheat;
|
||||
//使用正则匹配绝大多数的普通obj对象,避免解析成代码块。
|
||||
const reg=${/^\{([^{}]+:\s*([^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\)))(?:,\s*([^{}]+:\s*(?:[^\s,]*|'[^']*'|"[^"]*"|\{[^}]*\}|\[[^\]]*\]|null|undefined|([a-zA-Z$_][a-zA-Z0-9$_]*\s*:\s*)?[a-zA-Z$_][a-zA-Z0-9$_]*\(\))))*\}$/};
|
||||
return function(value){
|
||||
"use strict";
|
||||
return eval(reg.test(value)?('('+value+')'):value);
|
||||
}
|
||||
`))(proxyWindow);
|
||||
}
|
||||
const runCommand = () => {
|
||||
if (text2.value && !["up", "down"].includes(text2.value)) {
|
||||
logindex = -1;
|
||||
|
|
|
@ -125,6 +125,7 @@ export const startMenu = function (connectMenu) {
|
|||
true
|
||||
);
|
||||
game.switchMode(active.mode);
|
||||
game.requireSandboxOn();
|
||||
}
|
||||
clickContainer.call(cacheMenuContainer, connectMenu);
|
||||
} else {
|
||||
|
|
|
@ -7,8 +7,15 @@ export const assetURL =
|
|||
nonameInitialized == "nodejs"
|
||||
? ""
|
||||
: nonameInitialized;
|
||||
export const GeneratorFunction = function* () {}.constructor;
|
||||
export const AsyncFunction = async function () {}.constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
export const GeneratorFunction = (function* () {}).constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
export const AsyncFunction = (async function () {}).constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
export const AsyncGeneratorFunction = (async function* () {}).constructor;
|
||||
export const userAgent = navigator.userAgent.toLowerCase();
|
||||
export { Mutex } from "./mutex.js";
|
||||
export const characterDefaultPicturePath = "image/character/default_silhouette_";
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
// 为了兼容无法使用顶级await的版本
|
||||
const iframe = document.createElement("iframe");
|
||||
|
||||
// 执行上下文传递函数,请勿动喵
|
||||
// 用于传递顶级execute context
|
||||
|
||||
/** @type {(target: Function, thiz: Object, args: Array) => any} */
|
||||
// @ts-ignore
|
||||
const ContextInvoker1 = (function (apply, target, thiz, args) {
|
||||
return apply(target, thiz, args);
|
||||
}).bind(null, Reflect.apply);
|
||||
|
||||
/** @type {(target: Function, args: Array, newTarget: Function) => any} */
|
||||
// @ts-ignore
|
||||
const ContextInvoker2 = (function (construct, target, args, newTarget) {
|
||||
return construct(target, args, newTarget);
|
||||
}).bind(null, Reflect.construct);
|
||||
|
||||
/** @type {(closure: Object, target: Function) => ((...args: any[]) => any)} */
|
||||
// @ts-ignore
|
||||
const ContextInvokerCreator = (function (apply, closure, target) {
|
||||
return function (...args) {
|
||||
return apply(target, closure,
|
||||
// @ts-ignore
|
||||
[this === window ? null : this, args, new.target]);
|
||||
};
|
||||
}).bind(null, Reflect.apply);
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {string} name
|
||||
*/
|
||||
function replaceName(path, name) {
|
||||
const index = path.lastIndexOf("/");
|
||||
return path.slice(0, index + 1) + name;
|
||||
}
|
||||
|
||||
const TARGET_URL = replaceName(import.meta.url, "sandbox.js");
|
||||
const SANDBOX_EXPORT = {};
|
||||
|
||||
async function initializeSandboxRealms() {
|
||||
const document = window.document;
|
||||
const createElement = document.createElement.bind(document);
|
||||
const appendChild = document.body.appendChild.bind(document.body);
|
||||
|
||||
// 通过构造 iframe 来创建新的变量域
|
||||
// 我们需要确保顶级运行域的原型链不暴露
|
||||
// 为此我们从新的变量域重新载入当前脚本
|
||||
// 然后就可以直接冻结当前变量域的原型链
|
||||
const iframe = createElement("iframe");
|
||||
iframe.style.display = "none";
|
||||
appendChild(iframe);
|
||||
|
||||
if (!iframe.contentWindow)
|
||||
throw new ReferenceError("无法载入运行域");
|
||||
|
||||
// 定义 createRealms 函数
|
||||
Reflect.defineProperty(iframe.contentWindow, "createRealms", {
|
||||
value() {
|
||||
// 通过构造 iframe 来创建新的变量域
|
||||
const iframe = createElement("iframe");
|
||||
iframe.style.display = "none";
|
||||
appendChild(iframe);
|
||||
|
||||
const window = iframe.contentWindow;
|
||||
if (!window)
|
||||
throw new ReferenceError("顶级域已经被卸载");
|
||||
|
||||
iframe.remove();
|
||||
return window;
|
||||
},
|
||||
});
|
||||
|
||||
// 传递顶级变量域、上下文执行器
|
||||
// @ts-ignore
|
||||
iframe.contentWindow.replacedGlobal = window;
|
||||
// @ts-ignore
|
||||
iframe.contentWindow.replacedCI1 = ContextInvoker1;
|
||||
// @ts-ignore
|
||||
iframe.contentWindow.replacedCI2 = ContextInvoker2;
|
||||
// @ts-ignore
|
||||
iframe.contentWindow.replacedCIC = ContextInvokerCreator;
|
||||
|
||||
// 重新以新的变量域载入当前脚本
|
||||
const script = iframe.contentWindow.document.createElement("script");
|
||||
script.src = TARGET_URL;
|
||||
script.type = "module";
|
||||
|
||||
// 无法同步载入
|
||||
// script.async = false;
|
||||
// script.defer = false;
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
});
|
||||
iframe.contentWindow.document.head.appendChild(script);
|
||||
await promise; // Top Await Required Chrome 89
|
||||
|
||||
// @ts-ignore
|
||||
delete iframe.contentWindow.replacedGlobal;
|
||||
// @ts-ignore
|
||||
delete iframe.contentWindow.replacedCI1;
|
||||
// @ts-ignore
|
||||
delete iframe.contentWindow.replacedCI2;
|
||||
// @ts-ignore
|
||||
delete iframe.contentWindow.replacedCIC;
|
||||
|
||||
// @ts-ignore
|
||||
Object.assign(SANDBOX_EXPORT, iframe.contentWindow.SANDBOX_EXPORT);
|
||||
iframe.remove();
|
||||
}
|
||||
|
||||
export {
|
||||
initializeSandboxRealms,
|
||||
SANDBOX_EXPORT,
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,940 @@
|
|||
// 声明:沙盒维护的是服务器秩序,让服务器秩序不会因为非房主的玩家以及旁观者的影响,并在此基础上维护玩家设备不受危险代码攻击
|
||||
// 但沙盒不会也没有办法维护恶意服务器/房主对于游戏规则的破坏,请玩家尽量选择官方或其他安全的服务器,同时选择一个受信任的玩家作为房主
|
||||
|
||||
// 是否强制所有模式下使用沙盒
|
||||
const SANDBOX_FORCED = true;
|
||||
// 是否启用自动测试
|
||||
const SANDBOX_AUTOTEST = false;
|
||||
// 是否禁用自动测试延迟
|
||||
// 这将放弃渲染,在游戏结束前无响应
|
||||
const SANDBOX_AUTOTEST_NODELAY = false;
|
||||
// 沙盒开发模式
|
||||
const SANDBOX_DEV = false;
|
||||
|
||||
const WSURL_FOR_IP = /ws:\/\/(\d+.\d+.\d+.\d+):\d+\//;
|
||||
const TRUSTED_IPS = Object.freeze([
|
||||
"47.99.105.222",
|
||||
]);
|
||||
|
||||
// 声明导入类
|
||||
/** @type {boolean} */
|
||||
let SANDBOX_ENABLED = true;
|
||||
/** @type {typeof import("./sandbox.js").AccessAction} */
|
||||
let AccessAction;
|
||||
/** @type {typeof import("./sandbox.js").Domain} */
|
||||
let Domain;
|
||||
/** @type {typeof import("./sandbox.js").Marshal} */
|
||||
let Marshal;
|
||||
/** @type {typeof import("./sandbox.js").Monitor} */
|
||||
let Monitor;
|
||||
/** @type {typeof import("./sandbox.js").Rule} */
|
||||
let Rule;
|
||||
/** @type {typeof import("./sandbox.js").Sandbox} */
|
||||
let Sandbox;
|
||||
|
||||
/** @typedef {import("./sandbox.js").AccessAction} AccessAction */
|
||||
/** @typedef {import("./sandbox.js").Domain} Domain */
|
||||
/** @typedef {import("./sandbox.js").Marshal} Marshal */
|
||||
/** @typedef {import("./sandbox.js").Monitor} Monitor */
|
||||
/** @typedef {import("./sandbox.js").Rule} Rule */
|
||||
/** @typedef {import("./sandbox.js").Sandbox} Sandbox */
|
||||
|
||||
/** @type {boolean} */
|
||||
let initialized = false;
|
||||
/** @type {Sandbox} */
|
||||
let defaultSandbox;
|
||||
|
||||
/** @type {Array<Sandbox>} */
|
||||
const sandboxStack = [];
|
||||
|
||||
// 沙盒Function类型缓存
|
||||
/** @type {WeakMap<Sandbox, Array<typeof Function>>} */
|
||||
const isolatedsMap = new WeakMap();
|
||||
|
||||
// noname 顶级变量
|
||||
/** @type {Object<string|symbol, any>} */
|
||||
const topVariables = {
|
||||
lib: null,
|
||||
game: null,
|
||||
ui: null,
|
||||
get: null,
|
||||
ai: null,
|
||||
_status: null,
|
||||
gnc: null,
|
||||
};
|
||||
|
||||
// eval保存
|
||||
const defaultEval = window.eval;
|
||||
|
||||
// 对于 `lib.init.start` 的首次编译我们放宽
|
||||
let initStartParsed = false;
|
||||
// 是否软启用沙盒
|
||||
let sandBoxRequired = SANDBOX_FORCED;
|
||||
|
||||
// 可能的垫片函数
|
||||
const pfPrototypes = ["Object", "Array", "String", "Map"]; // 传递的实例垫片
|
||||
const pfNamespaces = ["Object", "Array", "Reflect", "Math", "Promise"]; // 传递的静态垫片
|
||||
// 可能还要补充喵?
|
||||
const nativePattern = /^function \w*\(\) \{ \[native code\] \}$/;
|
||||
|
||||
// 垫片备份
|
||||
const polyfills = {
|
||||
prototypes: {},
|
||||
namespaces: {},
|
||||
};
|
||||
|
||||
// 被封装的Function类型
|
||||
/** @type {typeof Function} */
|
||||
let ModFunction;
|
||||
/** @type {typeof Function} */
|
||||
let ModGeneratorFunction;
|
||||
/** @type {typeof Function} */
|
||||
let ModAsyncFunction;
|
||||
/** @type {typeof Function} */
|
||||
let ModAsyncGeneratorFunction;
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 将一个沙盒作为当前联网传输的运行沙盒
|
||||
* ```
|
||||
*
|
||||
* @param {Sandbox} box
|
||||
*/
|
||||
function enterSandbox(box) {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return;
|
||||
if (!(box instanceof Sandbox))
|
||||
throw new TypeError("无效的沙盒对象");
|
||||
|
||||
if (!Domain.isBelievable(Domain.topDomain))
|
||||
throw "无法在沙盒里面访问";
|
||||
|
||||
sandboxStack.push(box);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 退出当前联网传输的运行沙盒
|
||||
* ```
|
||||
*/
|
||||
function exitSandbox() {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return;
|
||||
|
||||
if (!Domain.isBelievable(Domain.topDomain))
|
||||
throw "无法在沙盒里面访问";
|
||||
if (!sandboxStack.length)
|
||||
throw new ReferenceError("无法弹出更多的沙盒");
|
||||
|
||||
sandboxStack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 判断对象是否是安全对象
|
||||
* ```
|
||||
*
|
||||
* @param {Object?} obj 要检查的对象
|
||||
* @param {string?} prop 指定要检查的属性描述符
|
||||
*/
|
||||
function isUnsafeObject(obj, prop = null) {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return true;
|
||||
|
||||
if (prop != null) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
||||
|
||||
if (descriptor) {
|
||||
if (descriptor.get
|
||||
&& isUnsafeObject(descriptor.get))
|
||||
return true;
|
||||
if (descriptor.set
|
||||
&& isUnsafeObject(descriptor.set))
|
||||
return true;
|
||||
if (isUnsafeObject(descriptor.value))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrimitive(obj))
|
||||
return false;
|
||||
|
||||
return !Domain.topDomain.isFrom(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 确保对象是安全对象
|
||||
* ```
|
||||
*
|
||||
* @param {Object?} obj 要检查的对象
|
||||
* @param {string?} prop 指定要检查的属性描述符
|
||||
*/
|
||||
function assertSafeObject(obj, prop = null) {
|
||||
if (isUnsafeObject(obj, prop))
|
||||
throw "unsafe object denied";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object?} obj
|
||||
*/
|
||||
function isPrimitive(obj) {
|
||||
return Object(obj) !== obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 获取当前指定的联网传输运行沙盒
|
||||
* ```
|
||||
*
|
||||
* @returns {Sandbox?}
|
||||
*/
|
||||
function currentSandbox() {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return null;
|
||||
|
||||
return sandboxStack[sandboxStack.length - 1] || defaultSandbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 进入沙盒运行模式
|
||||
* ```
|
||||
*/
|
||||
function requireSandbox() {
|
||||
sandBoxRequired = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 进入沙盒运行模式
|
||||
* ```
|
||||
*
|
||||
* @param {string} ip
|
||||
*/
|
||||
function requireSandboxOn(ip) {
|
||||
if (!TRUSTED_IPS.includes(ip)) {
|
||||
sandBoxRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (SANDBOX_FORCED
|
||||
&& topVariables.game
|
||||
&& topVariables.game.ws) {
|
||||
const match = WSURL_FOR_IP.exec(topVariables.game.ws.url);
|
||||
|
||||
if (match && match[1] === ip)
|
||||
sandBoxRequired = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 判断是否是沙盒运行模式
|
||||
* ```
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isSandboxRequired() {
|
||||
return SANDBOX_ENABLED && sandBoxRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 是否可以跳过沙盒进行编译
|
||||
* ```
|
||||
*
|
||||
* @param {any} item
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function canSkipSandbox(item) {
|
||||
if (!topVariables.lib)
|
||||
return false;
|
||||
|
||||
if (item === topVariables.lib.init.start) {
|
||||
if (!initStartParsed) {
|
||||
initStartParsed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 简单的、不带上下文的模拟eval函数
|
||||
*
|
||||
* 自动根据沙盒的启用状态使用不同的实现
|
||||
* ```
|
||||
*
|
||||
* @param {any} x
|
||||
* @returns {any}
|
||||
*/
|
||||
function _eval(x) {
|
||||
if (!SANDBOX_ENABLED || !sandBoxRequired) {
|
||||
new Function(x);
|
||||
const topVars = Object.assign({}, topVariables);
|
||||
const vars = "_" + Math.random().toString(36).slice(2);
|
||||
return new Function(vars, `with(${vars}){${x}}`)(topVars);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return defaultSandbox.exec(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 携带简单上下文的eval函数
|
||||
*
|
||||
* 自动根据沙盒的启用状态使用不同的实现
|
||||
* ```
|
||||
*
|
||||
* @param {any} x
|
||||
* @param {Object} scope
|
||||
* @returns {any}
|
||||
*/
|
||||
function _exec(x, scope = {}) {
|
||||
if (isPrimitive(scope))
|
||||
scope = {};
|
||||
|
||||
if (!SANDBOX_ENABLED || !sandBoxRequired) {
|
||||
// 如果没有沙盒,则进行简单模拟
|
||||
new Function(x);
|
||||
const topVars = Object.assign({}, topVariables);
|
||||
const vars = "__vars_" + Math.random().toString(36).slice(2);
|
||||
const name = "__scope_" + Math.random().toString(36).slice(2);
|
||||
return new Function(vars, name, `with(${vars}){with(${name}){${x}}}`)(topVars, scope);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return defaultSandbox.exec(x, scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 携带简单上下文的eval函数,并返回scope
|
||||
* eval代码的返回值将覆盖 `scope.return` 这个属性
|
||||
* 另外任意因对未定义变量赋值导致全局变量赋值的行为将被转移到scope里面
|
||||
* (替代eval的对策函数,具体看下面的例子)
|
||||
*
|
||||
* 自动根据沙盒的启用状态使用不同的实现
|
||||
*
|
||||
* 下面是 `security.exec2` 的使用示例:
|
||||
* ```
|
||||
* @example
|
||||
* ```javascript
|
||||
* // 执行一段代码并获取赋值的多个变量
|
||||
* let { return: skill, filter, content } = security.exec2(`
|
||||
* filter = (e, p) => e.source && e.source == p;
|
||||
* content = async (e, t, p) => t.cancel();
|
||||
* return { filter, content };
|
||||
* `, { content: () => {}, lib, game, ui, get, ai, _status, }); // 提供默认的content,提供六个变量
|
||||
* ```
|
||||
*
|
||||
* @param {any} x
|
||||
* @param {Object|"window"} scope 传入一个对象作为上下文,或者传入 "window" 来生成一个包含指向自身的 `window` 属性的对象
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _exec2(x, scope = {}) {
|
||||
if (scope == "window") {
|
||||
scope = {};
|
||||
scope.window = scope;
|
||||
} else if (isPrimitive(scope))
|
||||
scope = {};
|
||||
|
||||
if (!SANDBOX_ENABLED || !sandBoxRequired) {
|
||||
// 如果没有沙盒,则进行简单模拟
|
||||
// 进行语法检查
|
||||
new Function(x);
|
||||
|
||||
// 构造拦截器
|
||||
const intercepter = new Proxy(scope, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === Symbol.unscopables)
|
||||
return undefined;
|
||||
|
||||
if (!Reflect.has(target, prop)
|
||||
&& !Reflect.has(window, prop))
|
||||
throw new ReferenceError(`"${String(prop)}" is not defined`);
|
||||
|
||||
return Reflect.get(target, prop, receiver)
|
||||
|| topVariables[prop] || window[prop];
|
||||
},
|
||||
has(target, prop) {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
const result = new Function("_", `with(_){return(()=>{"use strict";\n${x}})()}`)(intercepter);
|
||||
scope.return = result;
|
||||
return scope;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const [result] = defaultSandbox.exec2(x, scope);
|
||||
scope.return = result;
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 初始化模块
|
||||
* ```
|
||||
*/
|
||||
async function initSecurity({
|
||||
lib,
|
||||
game,
|
||||
ui,
|
||||
get,
|
||||
ai,
|
||||
_status,
|
||||
gnc,
|
||||
}) {
|
||||
if (initialized)
|
||||
throw "security 已经被初始化过了";
|
||||
|
||||
const sandbox = await import("./sandbox.js");
|
||||
SANDBOX_ENABLED = sandbox.SANDBOX_ENABLED;
|
||||
AccessAction = sandbox.AccessAction;
|
||||
Domain = sandbox.Domain;
|
||||
Marshal = sandbox.Marshal;
|
||||
Monitor = sandbox.Monitor;
|
||||
Rule = sandbox.Rule;
|
||||
Sandbox = sandbox.Sandbox;
|
||||
|
||||
topVariables.lib = lib;
|
||||
topVariables.game = game;
|
||||
topVariables.ui = ui;
|
||||
topVariables.get = get;
|
||||
topVariables.ai = ai;
|
||||
topVariables._status = _status;
|
||||
topVariables.gnc = gnc;
|
||||
|
||||
if (!SANDBOX_ENABLED)
|
||||
return;
|
||||
|
||||
loadPolyfills();
|
||||
initIsolatedEnvironment();
|
||||
|
||||
// 不允许被远程代码访问的game函数
|
||||
const ioFuncs = [
|
||||
"download",
|
||||
"readFile",
|
||||
"readFileAsText",
|
||||
"writeFile",
|
||||
"removeFile",
|
||||
"getFileList",
|
||||
"ensureDirectory",
|
||||
"createDir",
|
||||
"removeDir",
|
||||
"checkForUpdate",
|
||||
"checkForAssetUpdate",
|
||||
"importExtension",
|
||||
"export",
|
||||
"multiDownload2",
|
||||
"multiDownload",
|
||||
"fetch",
|
||||
];
|
||||
|
||||
const accessDenieds = [
|
||||
...ioFuncs.map(n => game[n]).filter(Boolean),
|
||||
...Object.values(game.promises),
|
||||
defaultEval,
|
||||
localStorage.setItem,
|
||||
window.require,
|
||||
// @ts-ignore
|
||||
window.define,
|
||||
];
|
||||
|
||||
// 构造禁止函数调用的规则
|
||||
const callRule = new Rule();
|
||||
callRule.canMarshal = false; // 禁止获取函数
|
||||
callRule.setGranted(AccessAction.CALL, false); // 禁止函数调用
|
||||
callRule.setGranted(AccessAction.NEW, false); // 禁止函数new调用
|
||||
|
||||
// 为禁止的函数设置规则
|
||||
accessDenieds.filter(Boolean).forEach(o => {
|
||||
Marshal.setRule(o, callRule);
|
||||
});
|
||||
|
||||
// 构造禁止访问的规则
|
||||
const bannedRule = new Rule();
|
||||
bannedRule.canMarshal = false; // 禁止获取
|
||||
bannedRule.setGranted(AccessAction.READ, false); // 禁止读取属性
|
||||
bannedRule.setGranted(AccessAction.WRITE, false); // 禁止读取属性
|
||||
bannedRule.setGranted(AccessAction.DEFINE, false); // 禁止定义属性
|
||||
bannedRule.setGranted(AccessAction.DESCRIBE, false); // 禁止描述属性
|
||||
bannedRule.setGranted(AccessAction.TRACE, false); // 禁止获取原型
|
||||
bannedRule.setGranted(AccessAction.META, false); // 禁止设置原型
|
||||
|
||||
// 禁止访问关键对象
|
||||
[
|
||||
lib.cheat,
|
||||
lib.node,
|
||||
lib.message,
|
||||
window.process,
|
||||
window.module,
|
||||
window.exports,
|
||||
window.cordova,
|
||||
// @ts-ignore
|
||||
window.NonameAndroidBridge,
|
||||
// @ts-ignore
|
||||
window.noname_shijianInterfaces,
|
||||
window,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.forEach(o => Marshal.setRule(o, bannedRule));
|
||||
|
||||
// 构造禁止修改的规则
|
||||
const writeRule = new Rule();
|
||||
writeRule.setGranted(AccessAction.WRITE, false); // 禁止写入属性
|
||||
writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性
|
||||
// 禁止修改 game.promises 的函数
|
||||
Marshal.setRule(game.promises, writeRule);
|
||||
// 禁止修改 localStorage
|
||||
Marshal.setRule(localStorage, writeRule);
|
||||
|
||||
// 对于 game 当中访问特定函数我们通过 Monitor 进行拦截
|
||||
new Monitor()
|
||||
// 如果是写入或重定义属性
|
||||
.action(AccessAction.WRITE)
|
||||
.action(AccessAction.DEFINE)
|
||||
// 如果目标是 game 的 ioFuncs 包含的所有函数
|
||||
.require("target", game)
|
||||
.require("property", ...ioFuncs)
|
||||
.require("property", "ws", "sandbox")
|
||||
// 抛出异常
|
||||
.then((access, nameds, control) => {
|
||||
throw `有不信任的代码修改 \`game.${String(nameds.property)}\` 属性`;
|
||||
})
|
||||
// 让 Monitor 开始工作
|
||||
.start(); // 差点忘记启动了喵
|
||||
|
||||
// 监听原型、toStringTag的更改
|
||||
const toStringTag = Symbol.toStringTag;
|
||||
new Monitor()
|
||||
.action(AccessAction.WRITE)
|
||||
.action(AccessAction.DEFINE)
|
||||
.action(AccessAction.META)
|
||||
.require("property", toStringTag)
|
||||
.then((access, nameds, control) => {
|
||||
// 阻止原型、toStringTag的更改
|
||||
control.preventDefault();
|
||||
control.stopPropagation();
|
||||
control.setReturnValue(false);
|
||||
})
|
||||
.start();
|
||||
|
||||
if (SANDBOX_AUTOTEST) {
|
||||
// 一个测试循环喵
|
||||
Reflect.defineProperty(lib.element.GameEvent.prototype, "animate", {
|
||||
get: () => undefined,
|
||||
set() { },
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
});
|
||||
|
||||
if (!lib.videos)
|
||||
lib.videos = [];
|
||||
|
||||
game.over = function (...args) {
|
||||
if (_status.over) return;
|
||||
_status.over = true;
|
||||
setTimeout(() => {
|
||||
if (!_status.auto)
|
||||
return;
|
||||
|
||||
const count = parseInt(localStorage.getItem("__sandboxTestCount") || "0");
|
||||
localStorage.setItem("__sandboxTestCount", String(count + 1));
|
||||
|
||||
localStorage.setItem(
|
||||
lib.configprefix + "directstart", "true");
|
||||
game.reload();
|
||||
}, SANDBOX_AUTOTEST_NODELAY ? 5000 : 1000);
|
||||
};
|
||||
|
||||
lib.arenaReady.push(() => setTimeout(() => {
|
||||
if (SANDBOX_AUTOTEST_NODELAY) {
|
||||
game.resume = () => { };
|
||||
game.pause = () => { };
|
||||
}
|
||||
game.delay = game.delayx = () => { };
|
||||
game.asyncDelay = game.asyncDelayx = async () => { };
|
||||
|
||||
ui.auto.click();
|
||||
}, 1000));
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 创建一个新的沙盒
|
||||
* ```
|
||||
*
|
||||
* @returns {Sandbox?}
|
||||
*/
|
||||
function createSandbox() {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return null;
|
||||
|
||||
const box = new Sandbox();
|
||||
box.freeAccess = true;
|
||||
box.initBuiltins();
|
||||
|
||||
// 向沙盒提供顶级运行域的文档对象
|
||||
// TODO: 仅提供必要的document函数(?)
|
||||
box.document = document;
|
||||
|
||||
// 传递七个变量
|
||||
Object.assign(box.scope, topVariables);
|
||||
// 复制垫片函数
|
||||
setupPolyfills(box);
|
||||
|
||||
box.pushScope();
|
||||
return box;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 导出当前沙盒的Function类型
|
||||
* ```
|
||||
*
|
||||
* @param {Sandbox} sandbox
|
||||
* @returns {Array<typeof Function>}
|
||||
*/
|
||||
function getIsolateds(sandbox) {
|
||||
let isolateds = isolatedsMap.get(sandbox);
|
||||
|
||||
if (isolateds)
|
||||
return isolateds.slice();
|
||||
|
||||
// 获取当前沙盒的Function类型
|
||||
isolateds = Array.from(sandbox.exec(`
|
||||
return [
|
||||
(function(){}).constructor,
|
||||
(function*(){}).constructor,
|
||||
(async function(){}).constructor,
|
||||
(async function*(){}).constructor,
|
||||
];
|
||||
`));
|
||||
|
||||
isolatedsMap.set(sandbox, isolateds);
|
||||
return isolateds.slice();
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 根据传入对象的运行域获取对应的Function类型
|
||||
* ```
|
||||
*
|
||||
* @param {Object} item
|
||||
* @returns {Array<typeof Function>}
|
||||
*/
|
||||
function getIsolatedsFrom(item) {
|
||||
if (canSkipSandbox(item) || !SANDBOX_ENABLED) {
|
||||
return [
|
||||
defaultFunction,
|
||||
defaultGeneratorFunction,
|
||||
defaultAsyncFunction,
|
||||
defaultAsyncGeneratorFunction,
|
||||
];
|
||||
}
|
||||
|
||||
const domain = Marshal.getMarshalledDomain(item) || Domain.caller;
|
||||
|
||||
// 非顶级域调用情况下我们替换掉Function类型
|
||||
if (domain && domain !== Domain.topDomain) {
|
||||
const box = Sandbox.from(domain);
|
||||
|
||||
if (!box)
|
||||
throw "意外的运行域: 运行域没有绑定沙盒";
|
||||
|
||||
return getIsolateds(box);
|
||||
}
|
||||
|
||||
return [
|
||||
ModFunction,
|
||||
ModGeneratorFunction,
|
||||
ModAsyncFunction,
|
||||
ModAsyncGeneratorFunction,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 导入 `sandbox.js` 的相关类
|
||||
* ```
|
||||
*
|
||||
* @returns {{
|
||||
* AccessAction: typeof import("./sandbox.js").AccessAction,
|
||||
* Domain: typeof import("./sandbox.js").Domain,
|
||||
* Marshal: typeof import("./sandbox.js").Marshal,
|
||||
* Monitor: typeof import("./sandbox.js").Monitor,
|
||||
* Rule: typeof import("./sandbox.js").Rule,
|
||||
* Sandbox: typeof import("./sandbox.js").Sandbox,
|
||||
* }}
|
||||
*/
|
||||
function importSandbox() {
|
||||
if (!AccessAction)
|
||||
throw new ReferenceError("sandbox.js 还没有被载入");
|
||||
|
||||
return {
|
||||
AccessAction,
|
||||
Domain,
|
||||
Marshal,
|
||||
Monitor,
|
||||
Rule,
|
||||
Sandbox,
|
||||
};
|
||||
}
|
||||
|
||||
// 原本的Function类型记录
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultFunction = function () { }.constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultGeneratorFunction = function* () { }.constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultAsyncFunction = async function () { }.constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultAsyncGeneratorFunction = async function* () { }.constructor;
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 初始化顶级域的Funcion类型封装
|
||||
* ```
|
||||
*/
|
||||
function initIsolatedEnvironment() {
|
||||
// @ts-ignore
|
||||
defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵
|
||||
|
||||
// @ts-ignore
|
||||
// 对于 defaultSandbox 我们要补充一些东西喵
|
||||
defaultSandbox.scope.localStorage = localStorage;
|
||||
|
||||
// 对Function类型进行包裹
|
||||
/** @type {Array<typeof Function>} */
|
||||
const [
|
||||
IsolatedFunction,
|
||||
IsolatedGeneratorFunction,
|
||||
IsolatedAsyncFunction,
|
||||
IsolatedAsyncGeneratorFunction,
|
||||
]
|
||||
// @ts-ignore
|
||||
= getIsolateds(defaultSandbox);
|
||||
|
||||
// 封装Function类型
|
||||
|
||||
ModFunction = new Proxy(defaultFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {typeof Function} */
|
||||
ModGeneratorFunction = new Proxy(defaultGeneratorFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedGeneratorFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedGeneratorFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {typeof Function} */
|
||||
ModAsyncFunction = new Proxy(defaultAsyncFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {typeof Function} */
|
||||
ModAsyncGeneratorFunction = new Proxy(defaultAsyncGeneratorFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncGeneratorFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncGeneratorFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
function rewriteCtor(prototype, newCtor) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor')
|
||||
|| { configurable: true, writable: true, enumerable: false };
|
||||
if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数");
|
||||
descriptor.value = newCtor;
|
||||
Reflect.defineProperty(prototype, 'constructor', descriptor);
|
||||
}
|
||||
|
||||
// 覆盖所有的Function类型构造函数
|
||||
window.Function = ModFunction;
|
||||
rewriteCtor(defaultFunction.prototype, ModFunction);
|
||||
rewriteCtor(defaultGeneratorFunction.prototype, ModGeneratorFunction);
|
||||
rewriteCtor(defaultAsyncFunction.prototype, ModAsyncFunction);
|
||||
rewriteCtor(defaultAsyncGeneratorFunction.prototype, ModAsyncGeneratorFunction);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 加载当前的垫片函数
|
||||
* ```
|
||||
*/
|
||||
function loadPolyfills() {
|
||||
function isNativeDescriptor(descriptor) {
|
||||
if (typeof descriptor.value == "function"
|
||||
&& !nativePattern.test(descriptor.value.toString()))
|
||||
return false;
|
||||
if (typeof descriptor.get == "function"
|
||||
&& !nativePattern.test(descriptor.get.toString()))
|
||||
return false;
|
||||
if (typeof descriptor.set == "function"
|
||||
&& !nativePattern.test(descriptor.set.toString()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function copyDescriptors(top, box) {
|
||||
for (const key of Reflect.ownKeys(top)) {
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(top, key);
|
||||
|
||||
if (!descriptor
|
||||
|| (typeof descriptor.value !== "function"
|
||||
&& !descriptor.get && !descriptor.set))
|
||||
continue;
|
||||
|
||||
if (isNativeDescriptor(descriptor))
|
||||
continue;
|
||||
|
||||
box[key] = descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
// 将垫片函数的描述器复制出来
|
||||
|
||||
for (const key of pfPrototypes) {
|
||||
const top = window[key];
|
||||
|
||||
if (!top || !top.prototype)
|
||||
continue;
|
||||
|
||||
copyDescriptors(top.prototype, polyfills.prototypes[key] = {});
|
||||
}
|
||||
|
||||
for (const key of pfNamespaces) {
|
||||
const top = window[key];
|
||||
|
||||
if (!top)
|
||||
continue;
|
||||
|
||||
copyDescriptors(top, polyfills.namespaces[key] = {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 初始化沙盒的垫片
|
||||
* ```
|
||||
*
|
||||
* @param {Sandbox} sandbox
|
||||
*/
|
||||
function setupPolyfills(sandbox) {
|
||||
const context = {
|
||||
pfPrototypes,
|
||||
pfNamespaces,
|
||||
prototypes: polyfills.prototypes,
|
||||
namespaces: polyfills.namespaces,
|
||||
};
|
||||
|
||||
// 根据之前复制的垫片函数描述器定义垫片函数
|
||||
sandbox.exec(`
|
||||
function definePolyfills(top, box) {
|
||||
for (const key in top)
|
||||
Reflect.defineProperty(box, key, top[key]);
|
||||
}
|
||||
|
||||
for (const key of pfPrototypes) {
|
||||
if (key in prototypes)
|
||||
definePolyfills(
|
||||
prototypes[key],
|
||||
window[key].prototype
|
||||
);
|
||||
}
|
||||
|
||||
for (const key of pfNamespaces) {
|
||||
if (key in namespaces)
|
||||
definePolyfills(
|
||||
namespaces[key],
|
||||
window[key]
|
||||
);
|
||||
}
|
||||
`, context);
|
||||
}
|
||||
|
||||
// 测试暴露喵
|
||||
if (SANDBOX_DEV) {
|
||||
Reflect.defineProperty(window, "sandbox", {
|
||||
get: () => defaultSandbox,
|
||||
set: () => { },
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
|
||||
const exports = {
|
||||
enterSandbox,
|
||||
exitSandbox,
|
||||
currentSandbox,
|
||||
createSandbox,
|
||||
isUnsafeObject,
|
||||
assertSafeObject,
|
||||
getIsolateds,
|
||||
getIsolatedsFrom,
|
||||
importSandbox,
|
||||
requireSandbox,
|
||||
requireSandboxOn,
|
||||
isSandboxRequired,
|
||||
initSecurity,
|
||||
eval: _eval,
|
||||
exec: _exec,
|
||||
exec2: _exec2,
|
||||
SANDBOX_ENABLED,
|
||||
};
|
||||
|
||||
Object.freeze(exports);
|
||||
export default exports;
|
Loading…
Reference in New Issue