修复联机bug;去除部分debugger

This commit is contained in:
IceCola 2024-05-27 14:16:18 +08:00
parent 271b8039a4
commit 7c0fa0b02e
11 changed files with 163 additions and 102 deletions

View File

@ -57,7 +57,7 @@ game.import("mode", function (lib, game, ui, get, ai, _status) {
game.saveConfig("last_ip", ip);
game.connect(ip, function (success) {
if (success) {
game.requireSandboxOn(ip); // 启用沙盒喵
game.requireSandboxOn(ip);
var info = lib.config.reconnect_info;
if (info && info[0] == _status.ip) {
game.onlineID = info[1];

View File

@ -1346,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 = [];

View File

@ -1521,7 +1521,9 @@ export class Get {
str = str.trim();
const arrowMatch = get.#arrowPattern.exec(str);
if (arrowMatch) {
const body = `return ${str.slice(arrowMatch[0].length)}`;
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("尝试执行疑似恶意的远程代码")`;
@ -1558,7 +1560,9 @@ export class Get {
}
infoFuncOL(info) {
let func;
console.log("[infoFuncOL] info:", info);
const str = get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入
console.log("[infoFuncOL] pured:", str);
try {
// js内置的函数
if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { };

View File

@ -128,13 +128,6 @@ export async function boot() {
// 初始化沙盒的Realms
await initializeSandboxRealms();
// 初始化security
const securityModule = await import("../util/security.js");
const security = securityModule.default;
security.initSecurity({
lib, game, ui, get, ai, _status, gnc,
});
// 设定游戏加载时间,超过时间未加载就提醒
const configLoadTime = localStorage.getItem(lib.configprefix + "loadtime");
// 现在不暴露到全局变量里了直接传给onload
@ -243,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) {
@ -254,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;
@ -489,8 +490,7 @@ export async function boot() {
//var backup_onload=lib.init.onload;
_status.evaluatingExtension = true;
try {
debugger; // NEED TO VIEW DATA
security.eval(extcontent);
security.eval(extcontent); // 喵?
} catch (e) {
console.log(e);
}

View File

@ -9,7 +9,7 @@ export class Client {
/**
* @param {import('../index.js').NodeWS | InstanceType<typeof import('ws').WebSocket> | Client} ws
*/
constructor(ws) {
constructor(ws, temp) {
if (ws instanceof Client) throw new Error("Client cannot copy.");
this.ws = ws;
/**
@ -18,7 +18,9 @@ export class Client {
// @ts-ignore
this.id = ws.wsid || get.id();
this.closed = false;
this.sandbox = security.createSandbox();
if (!temp)
this.sandbox = security.createSandbox();
}
send() {
if (this.closed) return this;

View File

@ -306,14 +306,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;
@ -6334,7 +6334,6 @@ export class Library {
code = container.textarea.value;
}
try {
debugger; // NEED TO VIEW DATA
var { character } = security.exec2(code);
if (!Array.isArray(character)) {
throw "err";
@ -6422,7 +6421,6 @@ export class Library {
code = container.textarea.value;
}
try {
debugger; // NEED TO VIEW DATA
var { character } = security.exec2(code);
if (!Array.isArray(character)) {
throw "err";
@ -6851,7 +6849,6 @@ export class Library {
code = container.textarea.value;
}
try {
debugger; // NEED TO VIEW DATA
var { character } = security.exec2(code);
if (!get.is.object(character)) {
throw "err";
@ -7753,8 +7750,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);
debugger; // NEED TO VIEW DATA
obj = security.eval(`return ${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)))
@ -8042,10 +8041,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 = {
@ -10622,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;
@ -12260,7 +12259,6 @@ export class Library {
log: function () {
var items = [];
try {
debugger; // NEED TO VIEW DATA
for (var i = 0; i < arguments.length; i++) {
items.push(security.eval(`return ${arguments[i]}`));
}
@ -12531,7 +12529,7 @@ export class Library {
navigator.clipboard
.readText()
.then(read)
.catch(_ => {});
.catch(_ => { });
} else {
var input = ui.create.node("textarea", ui.window, { opacity: "0" });
input.select();

View File

@ -293,7 +293,6 @@ export class LibInit {
if (data.includes("sojson") || data.includes("jsjiami") || data.includes("var _0x")) alert(`检测到您安装了使用免费版sojson进行加密的扩展。请谨慎使用这些扩展避免游戏数据遭到破坏。\n扩展文件:${pathToRead}`);
}
try {
debugger; // NEED TO VIEW DATA
security.eval(data);
if (typeof onLoad == "function") onLoad();
} catch (error) {
@ -775,7 +774,6 @@ export class LibInit {
eval(func) {
if (typeof func == "function") {
debugger; // NEED TO VIEW DATA
return security.eval(`return (${func.toString()});`);
} else if (typeof func == "object") {
for (var i in func) {
@ -783,7 +781,6 @@ export class LibInit {
if (typeof func[i] == "function") {
let checkObject = {};
checkObject[i] = func[i];
debugger; // NEED TO VIEW DATA
return security.eval(`return ${get.stringify(checkObject)};`)[i];
} else {
func[i] = lib.init.eval(func[i]);

View File

@ -1679,7 +1679,6 @@ export const extensionMenu = function (connectMenu) {
code = container.textarea.value;
}
try {
debugger; // NEED TO VIEW DATA
var { card } = security.exec2(code);
if (card == null || typeof card != "object") {
throw "err";
@ -1769,7 +1768,6 @@ export const extensionMenu = function (connectMenu) {
page.content.pack.translate[name] = translate;
page.content.pack.translate[name + "_info"] = info;
try {
debugger; // NEED TO VIEW DATA
var { card } = security.exec2(container.code);
if (card == null || typeof card != "object") {
throw "err";
@ -2138,7 +2136,6 @@ export const extensionMenu = function (connectMenu) {
code = container.textarea.value;
}
try {
debugger; // NEED TO VIEW DATA
var { skill } = security.exec2(code);
if (skill == null || typeof skill != "object") {
throw "err";
@ -2321,7 +2318,6 @@ export const extensionMenu = function (connectMenu) {
page.content.pack.translate[name] = translate;
page.content.pack.translate[name + "_info"] = info;
try {
debugger; // NEED TO VIEW DATA
var { skill } = security.exec2(container.code);
if (skill == null || typeof skill != "object") {
throw "err";
@ -2452,19 +2448,16 @@ export const extensionMenu = function (connectMenu) {
}
try {
if (link == "content" || link == "precontent") {
debugger; // NEED TO VIEW DATA
var { func } = security.exec2(`func = ${code}`);
if (typeof func != "function") {
throw "err";
}
} else if (link == "config") {
debugger; // NEED TO VIEW DATA
var { config } = security.exec2(code);
if (config == null || typeof config != "object") {
throw "err";
}
} else if (link == "help") {
debugger; // NEED TO VIEW DATA
var { help } = security.exec2(code);
if (help == null || typeof help != "object") {
throw "err";
@ -2906,13 +2899,12 @@ export const extensionMenu = function (connectMenu) {
}
}
};
debugger; // NEED TO VIEW DATA
window.extension = {};
fetch(`${extensionURL}catalog.js`, {
referrerPolicy: "no-referrer",
})
.then((response) => response.text())
.then(security.eval)
.then(security.eval) // 返回的是HTML?
.then(loaded)
.catch((reason) => {
console.log(reason);

View File

@ -97,10 +97,6 @@ async function initializeSandboxRealms() {
iframe.contentWindow.document.head.appendChild(script);
await promise; // Top Await Required Chrome 89
// @ts-ignore
if (!iframe.contentWindow.SANDBOX_EXPORT)
throw new ReferenceError("无法同步载入运行域");
// @ts-ignore
delete iframe.contentWindow.replacedGlobal;
// @ts-ignore

View File

@ -316,7 +316,7 @@ class Rule {
* 返回值则控制本次访问是否允许
* ```
*
* @param {(...args: any[]) => boolean} accessControl
* @param {(action: number, ...args: any[]) => boolean} accessControl
*/
setAccessControl(accessControl) {
Rule.#assertOperator(this);
@ -442,6 +442,7 @@ const MARSHALLED_LIST = Object.freeze([
"/requestIdleCallback",
"/cancelIdleCallback",
"/queueMicrotask",
"/MutationObserver",
]);
/**
@ -2146,7 +2147,7 @@ class Marshal {
if (rule && !rule.canAccess(AccessAction.CALL,
target, marshalledThis, marshalledArgs))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, marshalledThis, marshalledArgs];
const dispatched = DomainMonitors.dispatch(
@ -2177,7 +2178,7 @@ class Marshal {
if (rule && !rule.canAccess(AccessAction.NEW,
target, argArray, newTarget))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, marshalledArgs, marshalledNewTarget];
const dispatched = DomainMonitors.dispatch(
@ -2227,7 +2228,7 @@ class Marshal {
if (rule && !rule.canAccess(AccessAction.DEFINE,
target, property, attributes))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, property, attributes];
const dispatched = DomainMonitors.dispatch(
@ -2251,7 +2252,7 @@ class Marshal {
const rule = ruleRef.rule;
if (rule && !rule.canAccess(AccessAction.DELETE, target, p))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, p];
const dispatched = DomainMonitors.dispatch(
@ -2286,7 +2287,7 @@ class Marshal {
if (rule && !rule.canAccess(AccessAction.READ,
target, p, marshalledReceiver))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
// 通知 Monitor
const args = [target, p, marshalledReceiver];
@ -2310,7 +2311,7 @@ class Marshal {
const rule = ruleRef.rule;
if (rule && !rule.canAccess(AccessAction.DESCRIBE, target, p))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, p];
const dispatched = DomainMonitors.dispatch(
@ -2336,7 +2337,7 @@ class Marshal {
const rule = ruleRef.rule;
if (rule && !rule.canAccess(AccessAction.TRACE, target))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target];
const dispatched = DomainMonitors.dispatch(
@ -2361,7 +2362,7 @@ class Marshal {
const rule = ruleRef.rule;
if (rule && !rule.canAccess(AccessAction.EXISTS, target, p))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, p];
const dispatched = DomainMonitors.dispatch(
@ -2387,7 +2388,7 @@ class Marshal {
const rule = ruleRef.rule;
if (rule && !rule.canAccess(AccessAction.LIST, target))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target];
const dispatched = DomainMonitors.dispatch(
@ -2414,7 +2415,7 @@ class Marshal {
const rule = ruleRef.rule;
if (rule && !rule.canAccess(AccessAction.SEAL, target))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target];
const dispatched = DomainMonitors.dispatch(
@ -2436,7 +2437,7 @@ class Marshal {
if (rule && !rule.canAccess(AccessAction.WRITE,
target, p, marshalledNewValue, marshalledReceiver))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, p, marshalledNewValue, marshalledReceiver];
const dispatched = DomainMonitors.dispatch(
@ -2459,7 +2460,7 @@ class Marshal {
const rule = ruleRef.rule;
if (rule && !rule.canAccess(AccessAction.META, target, marshalledV))
throw new ReferenceError("Access denied");
throw new ReferenceError("封送对象的源运行域禁止了此项操作");
const args = [target, marshalledV];
const dispatched = DomainMonitors.dispatch(
@ -3190,6 +3191,7 @@ class Sandbox {
parseFloat: this.#domainWindow.parseFloat,
isFinite: this.#domainWindow.isFinite,
isNaN: this.#domainWindow.isNaN,
Domain: Domain,
};
const hardBuiltins = {

View File

@ -1,15 +1,17 @@
// 是否强制所有模式下使用沙盒
const SANDBOX_FORCED = true;
// 是否启用自动测试
const SANDBOX_AUTOTEST = true;
const SANDBOX_AUTOTEST = false;
// 是否禁用自动测试延迟
// 这将放弃渲染,在游戏结束前无响应
const SANDBOX_AUTOTEST_NODELAY = 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} */
@ -45,6 +47,7 @@ const sandboxStack = [];
const isolatedsMap = new WeakMap();
// noname 顶级变量
/** @type {Object<string|symbol, any>} */
const topVariables = {
lib: null,
game: null,
@ -54,8 +57,13 @@ const topVariables = {
_status: null,
gnc: null,
};
// eval保存
const defaultEval = window.eval;
// 对于 `lib.init.start` 的首次编译我们放宽
let initStartParsed = false;
// 是否软启用沙盒
let sandBoxRequired = SANDBOX_FORCED;
// 可能的垫片函数
@ -198,8 +206,19 @@ function requireSandbox() {
* @param {string} ip
*/
function requireSandboxOn(ip) {
if (!TRUSTED_IPS.includes(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;
}
}
/**
@ -213,6 +232,28 @@ 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函数
@ -382,15 +423,8 @@ async function initSecurity({
...Object.values(game.promises),
defaultEval,
window.require,
window.process,
window.module,
window.exports,
window.cordova,
// @ts-ignore
window.NonameAndroidBridge,
// @ts-ignore
window.noname_shijianInterfaces,
window,
window.define,
];
// 构造禁止函数调用的规则
@ -409,12 +443,25 @@ async function initSecurity({
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));
@ -434,9 +481,10 @@ async function initSecurity({
// 如果目标是 game 的 ioFuncs 包含的所有函数
.require("target", game)
.require("property", ...ioFuncs)
.require("property", "ws")
// 抛出异常
.then(() => {
throw "禁止修改关键函数";
.then((access, nameds, control) => {
throw `禁止沙盒修改 \`game.${nameds.prototype}\` 属性`;
})
// 让 Monitor 开始工作
.start(); // 差点忘记启动了喵
@ -517,6 +565,18 @@ function createSandbox() {
// TODO: 仅提供必要的document函数(?)
box.document = document;
if (topVariables.game
&& topVariables.game.ws) {
const match = WSURL_FOR_IP.exec(topVariables.game.ws.url);
if (match && TRUSTED_IPS.includes(match[1])) {
box.scope.ArrayBuffer = ArrayBuffer;
box.scope.localStorage = localStorage;
box.scope.exports = undefined;
box.scope.define = undefined;
}
}
// 传递七个变量
Object.assign(box.scope, topVariables);
// 复制垫片函数
@ -563,6 +623,15 @@ function getIsolateds(sandbox) {
* @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类型
@ -611,25 +680,26 @@ function importSandbox() {
};
}
// 原本的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() {
/** @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;
// @ts-ignore
defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵
@ -827,11 +897,11 @@ function setupPolyfills(sandbox) {
}
// 测试暴露喵
// Reflect.defineProperty(window, "sandbox", {
// get: () => defaultSandbox,
// set: () => { },
// configurable: true,
// });
Reflect.defineProperty(window, "sandbox", {
get: () => defaultSandbox,
set: () => { },
configurable: true,
});
const exports = {
enterSandbox,