微调代码;处理init/index.js、client.js、gameEventPromise.js、player.js、util/index.js、otherMenu.js、extensionMenu.js、game/index.js

This commit is contained in:
IceCola 2024-05-25 13:21:00 +08:00
parent 3dbd145f79
commit 6ffd290489
9 changed files with 143 additions and 96 deletions

View File

@ -22,6 +22,8 @@ import { DynamicStyle } from "./dynamic-style/index.js";
import { GamePromises } from "./promises.js"; import { GamePromises } from "./promises.js";
import { Check } from "./check.js"; import { Check } from "./check.js";
import security from "../util/security.js";
export class Game { export class Game {
online = false; online = false;
onlineID = null; onlineID = null;
@ -1312,6 +1314,7 @@ export class Game {
if (callback) callback(false); if (callback) callback(false);
return; return;
} }
game.sandbox = security.createSandbox();
game.ws.onopen = lib.element.ws.onopen; game.ws.onopen = lib.element.ws.onopen;
game.ws.onmessage = lib.element.ws.onmessage; game.ws.onmessage = lib.element.ws.onmessage;
game.ws.onerror = lib.element.ws.onerror; game.ws.onerror = lib.element.ws.onerror;
@ -2135,8 +2138,8 @@ export class Game {
_status.importingExtension = true; _status.importingExtension = true;
try { try {
// 导入普通扩展 // 导入普通扩展
eval(str); security.eval(str);
// esm扩展可以不写game.impoprt或许会导致_status.extensionLoading不存在 // esm扩展可以不写game.import或许会导致_status.extensionLoading不存在
if (Array.isArray(_status.extensionLoading)) { if (Array.isArray(_status.extensionLoading)) {
await Promise.allSettled(_status.extensionLoading); await Promise.allSettled(_status.extensionLoading);
delete _status.extensionLoading; delete _status.extensionLoading;
@ -5453,6 +5456,8 @@ export class Game {
newvid.name1 = newvid.name2.slice(10, newvid.name1.lastIndexOf("_")); newvid.name1 = newvid.name2.slice(10, newvid.name1.lastIndexOf("_"));
} }
lib.videos.unshift(newvid); lib.videos.unshift(newvid);
// 清洗代理对象
newvid.video = structuredClone(newvid.video);
store.put(newvid); store.put(newvid);
ui.create.videoNode(newvid, true); ui.create.videoNode(newvid, true);
} }
@ -7695,7 +7700,8 @@ export class Game {
); );
lib.status.reload++; lib.status.reload++;
return new Promise((resolve, reject) => { 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 => { record.onerror = event => {
if (typeof onError == "function") { if (typeof onError == "function") {
onError(event); onError(event);

View File

@ -1490,9 +1490,9 @@ export class Get {
return Array.from(infos || []).map(get.infoPlayerOL); return Array.from(infos || []).map(get.infoPlayerOL);
} }
/** 箭头函数头 */ /** 箭头函数头 */
static #arrowPattern = /^(?:async\b\s*)?(?:([\w$]+)|\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\))\s*=>/; #arrowPattern = /^(?:async\b\s*)?(?:([\w$]+)|\((\s*[\w$]+(?:\s*=\s*.+?)?(?:\s*,\s*[\w$]+(?:\s*=\s*.+?)?)*\s*)?\))\s*=>/;
/** 标准函数头 */ /** 标准函数头 */
static #fullPattern = /^([\w\s*]+)\((\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 * ```plain
* 测试一段代码是否为函数体 * 测试一段代码是否为函数体
@ -1501,7 +1501,7 @@ export class Get {
* @param {string} code * @param {string} code
* @returns {boolean} * @returns {boolean}
*/ */
static isFunctionBody(code) { isFunctionBody(code) {
try { try {
new Function(code); new Function(code);
} catch (e) { } catch (e) {
@ -1517,24 +1517,24 @@ export class Get {
* @param {string} str * @param {string} str
* @returns * @returns
*/ */
static pureFunctionStr(str) { pureFunctionStr(str) {
str = str.trim(); str = str.trim();
const arrowMatch = Get.#arrowPattern.exec(str); const arrowMatch = get.#arrowPattern.exec(str);
if (arrowMatch) { if (arrowMatch) {
const body = `return ${str.slice(arrowMatch[0].length)}`; const body = `return ${str.slice(arrowMatch[0].length)}`;
if (!Get.isFunctionBody(body)) { if (!get.isFunctionBody(body)) {
console.error("发现疑似恶意的远程代码:", str); console.error("发现疑似恶意的远程代码:", str);
return `()=>console.error("尝试执行疑似恶意的远程代码")`; return `()=>console.error("尝试执行疑似恶意的远程代码")`;
} }
return `${arrowMatch[0]}{${body}}`; return `${arrowMatch[0]}{${body}}`;
} }
if (!str.endsWith("}")) return '()=>console.warn("无法识别的远程代码")'; if (!str.endsWith("}")) return '()=>console.warn("无法识别的远程代码")';
const fullMatch = Get.#fullPattern.exec(str); const fullMatch = get.#fullPattern.exec(str);
if (!fullMatch) return '()=>console.warn("无法识别的远程代码")'; if (!fullMatch) return '()=>console.warn("无法识别的远程代码")';
const head = fullMatch[1]; const head = fullMatch[1];
const args = fullMatch[2] || ''; const args = fullMatch[2] || '';
const body = str.slice(fullMatch[0].length).slice(0, -1); const body = str.slice(fullMatch[0].length).slice(0, -1);
if (!Get.isFunctionBody(body)) { if (!get.isFunctionBody(body)) {
console.error("发现疑似恶意的远程代码:", str); console.error("发现疑似恶意的远程代码:", str);
return `()=>console.error("尝试执行疑似恶意的远程代码")`; return `()=>console.error("尝试执行疑似恶意的远程代码")`;
} }
@ -1552,13 +1552,13 @@ export class Get {
const str = func.toString(); const str = func.toString();
// js内置的函数 // js内置的函数
if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}"; if (/\{\s*\[native code\]\s*\}/.test(str)) return "_noname_func:function () {}";
return "_noname_func:" + Get.pureFunctionStr(str); return "_noname_func:" + get.pureFunctionStr(str);
} }
return ""; return "";
} }
infoFuncOL(info) { infoFuncOL(info) {
let func; let func;
const str = Get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入 const str = get.pureFunctionStr(info.slice(13)); // 清洗函数并阻止注入
try { try {
// js内置的函数 // js内置的函数
if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { }; if (/\{\s*\[native code\]\s*\}/.test(str)) return function () { };

View File

@ -10,6 +10,7 @@ import * as config from "../util/config.js";
import { promiseErrorHandlerMap } from "../util/browser.js"; import { promiseErrorHandlerMap } from "../util/browser.js";
import { importCardPack, importCharacterPack, importExtension, importMode } from "./import.js"; import { importCardPack, importCharacterPack, importExtension, importMode } from "./import.js";
import { onload } from "./onload.js"; import { onload } from "./onload.js";
import security from "../util/security.js";
// 判断是否从file协议切换到http/s协议 // 判断是否从file协议切换到http/s协议
export function canUseHttpProtocol() { export function canUseHttpProtocol() {
@ -124,6 +125,11 @@ export async function boot() {
// 加载polyfill内容 // 加载polyfill内容
await import("./polyfill.js"); await import("./polyfill.js");
// 初始化security
security.initSecurity({
lib, game, ui, get, ai, _status, gnc,
});
// 设定游戏加载时间,超过时间未加载就提醒 // 设定游戏加载时间,超过时间未加载就提醒
const configLoadTime = localStorage.getItem(lib.configprefix + "loadtime"); const configLoadTime = localStorage.getItem(lib.configprefix + "loadtime");
// 现在不暴露到全局变量里了直接传给onload // 现在不暴露到全局变量里了直接传给onload
@ -478,7 +484,8 @@ export async function boot() {
//var backup_onload=lib.init.onload; //var backup_onload=lib.init.onload;
_status.evaluatingExtension = true; _status.evaluatingExtension = true;
try { try {
eval(extcontent); debugger; // NEED TO VIEW DATA
security.eval(extcontent);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
} }

View File

@ -3,6 +3,7 @@ import { game } from "../../game/index.js";
import { lib } from "../index.js"; import { lib } from "../index.js";
import { _status } from "../../status/index.js"; import { _status } from "../../status/index.js";
import { ui } from "../../ui/index.js"; import { ui } from "../../ui/index.js";
import security from "../../util/security.js";
export class Client { export class Client {
/** /**
@ -17,6 +18,7 @@ export class Client {
// @ts-ignore // @ts-ignore
this.id = ws.wsid || get.id(); this.id = ws.wsid || get.id();
this.closed = false; this.closed = false;
this.sandbox = security.createSandbox();
} }
send() { send() {
if (this.closed) return this; if (this.closed) return this;

View File

@ -3,6 +3,7 @@ import { game } from "../../game/index.js";
import { lib } from "../index.js"; import { lib } from "../index.js";
import { _status } from "../../status/index.js"; import { _status } from "../../status/index.js";
import { AsyncFunction } from "../../util/index.js"; import { AsyncFunction } from "../../util/index.js";
import security from '../../util/security.js';
/** /**
* 将事件Promise化以使用async异步函数来执行事件 * 将事件Promise化以使用async异步函数来执行事件
@ -220,6 +221,7 @@ export class GameEventPromise extends Promise {
* ``` * ```
*/ */
async debugger() { async debugger() {
if (security.isSandboxRequired()) throw new Error("当前模式下禁止调试");
return new Promise((resolve) => { return new Promise((resolve) => {
const runCode = function (event, code) { const runCode = function (event, code) {
try { try {

View File

@ -20,6 +20,7 @@ import { _status } from "../../status/index.js";
import { ui } from "../../ui/index.js"; import { ui } from "../../ui/index.js";
import { CacheContext } from "../cache/cacheContext.js"; import { CacheContext } from "../cache/cacheContext.js";
import { ChildNodesWatcher } from "../cache/childNodesWatcher.js"; import { ChildNodesWatcher } from "../cache/childNodesWatcher.js";
import security from "../../util/security.js";
export class Player extends HTMLDivElement { export class Player extends HTMLDivElement {
/** /**
@ -454,7 +455,7 @@ export class Player extends HTMLDivElement {
const vars = {}; const vars = {};
/** /**
* 作用域 * 作用域
* @type { (code: string) => any } * @type { ((code: string) => any)? }
*/ */
let scope; let scope;
/** @type { Skill } */ /** @type { Skill } */
@ -500,14 +501,13 @@ export class Player extends HTMLDivElement {
varstr += `var ${key}=lib.skill['${skillName}'].vars['${key}'];\n`; varstr += `var ${key}=lib.skill['${skillName}'].vars['${key}'];\n`;
} }
let str = ` let str = `
function content(){ ${varstr}if(event.triggername=='${skillName}After'){
${varstr}if(event.triggername=='${skillName}After'){ player.removeSkill('${skillName}');
player.removeSkill('${skillName}'); delete lib.skill['${skillName}'];
delete lib.skill['${skillName}']; delete lib.translate['${skillName}'];
delete lib.translate['${skillName}']; return event.finish();
return event.finish(); }
} `;
`;
for (let i = 0; i < skill.contentFuns.length; i++) { for (let i = 0; i < skill.contentFuns.length; i++) {
const fun2 = skill.contentFuns[i]; const fun2 = skill.contentFuns[i];
const a = fun2.toString(); 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(); const str2 = a.slice(begin, a.lastIndexOf("}") != -1 ? a.lastIndexOf("}") : undefined).trim();
str += `'step ${i}'\n\t${str2}\n\t`; 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 // @ts-ignore
skill.content._parsed = true; skill.content._parsed = true;
}; };
@ -632,6 +640,7 @@ export class Player extends HTMLDivElement {
*/ */
apply(_scope) { apply(_scope) {
if (lib.skill[skillName] != skill) throw `This skill has been destroyed`; if (lib.skill[skillName] != skill) throw `This skill has been destroyed`;
if (security.isSandboxRequired()) console.warn("`player.when().apply()` 在沙盒模式下不推荐使用");
// @ts-ignore // @ts-ignore
scope = _scope; scope = _scope;
if (skill.contentFuns.length > 0) createContent(); if (skill.contentFuns.length > 0) createContent();
@ -3654,9 +3663,9 @@ export class Player extends HTMLDivElement {
return Array.from(this.iterableGetCards(arg1, arg2)); return Array.from(this.iterableGetCards(arg1, arg2));
} }
/** /**
* @param { Player } player * @param { Player } player
* @param { string } [arg1] * @param { string } [arg1]
* @param { string } [arg2] * @param { string } [arg2]
* @returns { Generator<Card, void, unknown> } * @returns { Generator<Card, void, unknown> }
*/ */
*iterableGetDiscardableCards(player, arg1, arg2) { *iterableGetDiscardableCards(player, arg1, arg2) {
@ -5861,7 +5870,7 @@ export class Player extends HTMLDivElement {
return get.is.sameNature(natures, naturesx); return get.is.sameNature(natures, naturesx);
}; };
if (next.hasNature("poison")) delete next._triggered; 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.setContent("damage");
next.filterStop = function () { next.filterStop = function () {
if (this.source && this.source.isDead()) delete this.source; if (this.source && this.source.isDead()) delete this.source;

View File

@ -18,6 +18,7 @@ import {
} from "../index.js"; } from "../index.js";
import { ui, game, get, ai, lib, _status } from "../../../../../noname.js"; import { ui, game, get, ai, lib, _status } from "../../../../../noname.js";
import { nonameInitialized } from "../../../../util/index.js"; import { nonameInitialized } from "../../../../util/index.js";
import security from "../../../../util/security.js";
export const extensionMenu = function (connectMenu) { export const extensionMenu = function (connectMenu) {
if (connectMenu) return; if (connectMenu) return;
@ -334,18 +335,18 @@ export const extensionMenu = function (connectMenu) {
var ext = {}; var ext = {};
var config = null, var config = null,
help = null; help = null;
debugger; // NEED TO VIEW DATA
for (var i in dash4.content) { for (var i in dash4.content) {
try { try {
if (i == "content" || i == "precontent") { if (i == "content" || i == "precontent") {
eval("ext[i]=" + dash4.content[i]); ({ config, help, return: ext[i] } = security.exec2(`return (${dash4.content[i]});`));
if (typeof ext[i] != "function") { if (typeof ext[i] != "function") {
throw "err"; throw "err";
} else { } else {
ext[i] = ext[i].toString(); ext[i] = ext[i].toString();
} }
} else { } else {
eval(dash4.content[i]); ({ config, help, return: ext[i] } = security.exec2(`${dash4.content[i]}; return (${i});`));
eval("ext[i]=" + i);
if (ext[i] == null || typeof ext[i] != "object") { if (ext[i] == null || typeof ext[i] != "object") {
throw "err"; throw "err";
} else { } else {

View File

@ -20,6 +20,8 @@ import {
getLatestVersionFromGitHub, getLatestVersionFromGitHub,
getTreesFromGithub, getTreesFromGithub,
} from "../../../../library/update.js"; } from "../../../../library/update.js";
import security from "../../../../util/security.js";
import { AccessAction, Marshal, Monitor } from "../../../../util/sandbox.js";
export const otherMenu = function (/** @type { boolean | undefined } */ connectMenu) { export const otherMenu = function (/** @type { boolean | undefined } */ connectMenu) {
if (connectMenu) return; if (connectMenu) return;
@ -1212,73 +1214,84 @@ export const otherMenu = function (/** @type { boolean | undefined } */ connectM
ai: ai, ai: ai,
cheat: lib.cheat, cheat: lib.cheat,
}); });
Object.defineProperties(proxyWindow, { if (security.isSandboxRequired()) {
_status: { new Monitor()
configurable: false, .action(AccessAction.DEFINE)
enumerable: true, .action(AccessAction.WRITE)
writable: false, .action(AccessAction.DELETE)
}, .require("target", proxyWindow)
lib: { .require("property", "_status", "lib", "game", "ui", "get", "ai", "cheat")
configurable: false, .then((access, nameds, control) => {
enumerable: true, if (access.action == AccessAction.DEFINE) {
writable: false, control.preventDefault();
}, control.stopPropagation();
game: { control.setReturnValue(false);
configurable: false, return;
enumerable: true, }
writable: false,
}, control.overrideParameter("target", window);
ui: { })
configurable: false, .start();
enumerable: true, } else {
writable: false, const keys = ["_status", "lib", "game", "ui", "get", "ai", "cheat"];
},
get: { for (const key of keys) {
configurable: false, const descriptor = Reflect.getOwnPropertyDescriptor(proxyWindow, key);
enumerable: true, if (!descriptor) continue;
writable: false, descriptor.writable = false;
}, descriptor.enumerable = true;
ai: { descriptor.configurable = false;
configurable: false, Reflect.defineProperty(proxyWindow, key, descriptor);
enumerable: true, }
writable: false,
}, proxyWindow = new Proxy(proxyWindow, {
cheat: { set(target, propertyKey, value, receiver) {
configurable: false, if (typeof propertyKey == "string" && keys.includes(propertyKey)) {
enumerable: true, return Reflect.set(target, propertyKey, value, receiver);
writable: false, }
},
}); return Reflect.set(window, propertyKey, value);
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);
},
});
//使用new Function隔绝作用域避免在控制台可以直接访问到runCommand等变量 //使用new Function隔绝作用域避免在控制台可以直接访问到runCommand等变量
/** /**
* @type { (value:string)=>any } * @type { (value:string)=>any }
*/ */
const fun = new Function( let fun
"window", if (security.isSandboxRequired()) {
` fun = security.eval(`
const _status=window._status; const _status=window._status;
const lib=window.lib; const lib=window.lib;
const game=window.game; const game=window.game;
const ui=window.ui; const ui=window.ui;
const get=window.get; const get=window.get;
const ai=window.ai; const ai=window.ai;
const cheat=window.lib.cheat; const cheat=window.lib.cheat;
//使用正则匹配绝大多数的普通obj对象避免解析成代码块。 //使用正则匹配绝大多数的普通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$_]*\(\))))*\}$/}; 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){ return function(value){
"use strict"; "use strict";
return eval(reg.test(value)?('('+value+')'):value); return eval(reg.test(value)?('('+value+')'):value);
} };
` `);
)(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 = () => { const runCommand = () => {
if (text2.value && !["up", "down"].includes(text2.value)) { if (text2.value && !["up", "down"].includes(text2.value)) {
logindex = -1; logindex = -1;

View File

@ -7,8 +7,15 @@ export const assetURL =
nonameInitialized == "nodejs" nonameInitialized == "nodejs"
? "" ? ""
: nonameInitialized; : nonameInitialized;
export const GeneratorFunction = function* () {}.constructor; /** @type {typeof Function} */
export const AsyncFunction = async function () {}.constructor; // @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 const userAgent = navigator.userAgent.toLowerCase();
export { Mutex } from "./mutex.js"; export { Mutex } from "./mutex.js";
export const characterDefaultPicturePath = "image/character/default_silhouette_"; export const characterDefaultPicturePath = "image/character/default_silhouette_";