概览与界面
SGS Framework 由下载器在 Laya 初始化前注入。Core 统一管理生命周期、运行时事件、可逆 Hook、插件安装与配置、LocalStorage 前缀、版本检测和 Laya 底层访问。
屏幕边缘的 SGS 悬浮窗自动吸附左侧或右侧。自动隐藏开启时,只保留边缘感应区,鼠标进入当前侧边缘才显示;拖动后在边缘松开也会按该设置隐藏。点击悬浮窗可选择“选项”、切换自动隐藏,并查看不可点击的 Core 版本号。
选项窗口默认占视口宽高各 50%,可拖动和从右下角缩放。最小宽高为视口的 20%,同时保留 260×180 像素的可用下限;移动、缩放、视口变化和浏览器缩放后,窗口都会被约束在屏幕内。
window.SgsFramework/script/plugin/index.json5000 ms / 请求安装与入口
- 在油猴脚本管理器中打开 download.mjs 并安装。
- 下载器在
document-start安装 Laya 初始化屏障,优先请求本站core.mjs,失败后请求 GitHub;两个来源各自最多等待 5 秒。 - 远程请求都失败时,下载器先尝试页面 LocalStorage 缓存;仍不可用时显示“继续游戏 / 刷新重试”。选择继续时使用可用缓存,没有缓存则不加载 Framework。
- Core 注入完成后释放 Laya 屏障,恢复已启用插件并执行静默版本检测。
用户手册:https://sgs.senrax.com/
安装脚本:https://sgs.senrax.com/script/download.mjs
公开目录:https://sgs.senrax.com/script/
在 https://web.sanguosha.com/login/index.html 登录页,Core 会显示只读版本标记,便于确认注入是否成功。
Core API
window.SgsFramework 与 window.__SgsFramework 指向同一个实例。插件优先使用传入的 context;只有框架调试或插件接口不足时才直接使用顶层服务。
| 入口 | 暴露方法与定义 |
|---|---|
| Core | status() 返回版本、插件、Laya、事件监视、更新和 UI 状态;addDisposer(fn,label) 注册清理;dispose(reason) 停止插件并释放全部资源。 |
events | on(type,handler)、once、off、emit、clear、listenerCount。on/once 返回取消函数。 |
logger | scope(name) 创建作用域 Logger;info、warn、error 同时写控制台和 log 事件。 |
storage | scope(name)、get/set/remove、getJson/setJson、keys。Core 负责真实 LocalStorage 前缀与 JSON 序列化。 |
timers | setTimeout(fn,ms,label)、setInterval(fn,ms,label),返回取消函数并参与 Core 热重载清理。 |
hooks | wrapMethod(owner,name,handler,{label})。Handler 接收 {original,args,thisArg,callOriginal};返回的取消函数恢复包装前方法。 |
dom | createOverlay、createPanel、createButton、toast。创建结果包含元素与移除函数。 |
browser | openUrl、request、requestText、requestJson、requestHeaders。请求默认不带凭据、禁用缓存并在 5 秒后中止。 |
laya | getLaya()、getStage()、status()、whenReady()、walk()、find()、snapshot()。节点引用保持为原始 Laya 对象。 |
runtime | start()、stop()、poll()、status()、getBattle(),封装战斗、窗口和公开日志生命周期。 |
plugins | inspect/addFromUrl/install、enable/disable/remove、checkOne/checkUpdates/update、settings/config/invokeAction/list、插件源与孤儿配置管理方法。 |
update | check()、reload()、status();reload 执行当前页面 Core 热替换。 |
ui | mount()、openWindow(tab)、closeWindow()、selectTab()、refresh()、confirmAction()、toast()、status()。 |
const framework = window.SgsFramework;
const remove = framework.events.on("battle:start", ({ scene, manager }) => {
framework.logger.info("battle started", scene, manager);
});
// 插件卸载时调用 remove,或交给 context.addDisposer(remove)。
插件定义
插件是可直接下载、无需构建的经典 JavaScript 文件。Core 在执行代码前解析 Header,用于安装确认和版本检测;正文必须调用 SgsFramework.plugins.define()。
| Header | 含义 |
|---|---|
@id | 稳定且唯一的插件 ID;更新包必须保持一致。 |
@name / @version | 显示名称与可比较版本号。 |
@description | 安装确认框和插件列表中的简介。 |
@permissions | 逗号分隔的能力声明,仅披露,不构成沙箱。 |
@updateMode | default、header 或 api,不区分大小写。 |
@versionHeader | Header 模式读取的响应头,默认 x-sgs-plugin-version。 |
@versionApi | API 模式 JSON 地址;相对地址按插件 URL 解析。 |
// ==SgsPlugin==
// @id example.plugin
// @name Example Plugin
// @version 1.0.0
// @description Example description.
// @permissions core.events,browser.dom-overlay
// @updateMode default
// ==/SgsPlugin==
(() => {
SgsFramework.plugins.define({
id: "example.plugin",
manifest: {
name: "Example Plugin",
version: "1.0.0",
description: "Example description.",
permissions: ["core.events", "browser.dom-overlay"]
},
defaults: {},
settings: [],
install(context) {
const remove = context.events.on("battle:end", () => {});
context.addDisposer(remove, "battle-end");
return () => {};
}
});
})();
context 提供 framework、id、manifest、sourceUrl、events、logger、storage、config、timers、hooks、dom、browser、laya、runtime 和 addDisposer。install 返回的函数也会在禁用、更新、删除或 Core 热替换时调用。
插件配置
插件只声明配置、默认值和动作,Core 负责 Group、行列、控件、错误提示与重置按钮。每个插件独立显示并可收起;没有配置项的插件仍显示“无可配置项”,且不显示重置按钮。
type | 声明与触发 |
|---|---|
toggle | 布尔开关;onChange 在成功保存后调用插件动作。 |
button | action / onClick 指向插件函数;插件未启动时禁用。 |
text | TextBox;支持 inputType、min/max、minLength/maxLength 和一个或多个 patterns。 |
select | options: [{value,label}] 下拉框;可声明 onChange。 |
custom | render / action 接收 Core 创建的 host;插件仅负责 host 内部复杂 UI。 |
defaults: { enabled: true, name: "demo", mode: "safe" },
settings: [{
id: "general",
name: "General",
items: [
{ key: "enabled", name: "Enabled", type: "toggle", onChange: "apply" },
{ key: "name", name: "Name", type: "text",
minLength: 1, maxLength: 20,
patterns: ["^[A-Za-z]", "^[A-Za-z0-9_-]+$"] },
{ key: "mode", name: "Mode", type: "select", options: [
{ value: "safe", label: "Safe" },
{ value: "full", label: "Full" }
], onChange: "apply" },
{ name: "Open list", type: "button", action: "openList" },
{ name: "Complex", type: "custom", render: "renderComplex" }
]
}],
actions: {
apply(context, value, key) {},
openList(context) {},
renderComplex(context, host) {},
onReset(context, values) {}
}
context.config.get/set/reset/defaults 与配置页共享同一状态。值先通过全部约束,再序列化到 LocalStorage,随后触发动作;动作读取 context.config 时得到的就是本次新值。首次安装且没有配置、或用户按下重置时,使用插件的 defaults。
更新检测
| 模式 | 请求与版本来源 |
|---|---|
default | GET 插件 JavaScript,读取响应体并解析 Header 的 @version。 |
header | 通过 Fetch 发送 HEAD,不读取响应体;读取 @versionHeader 指定的响应头。 |
api | GET @versionApi JSON;必须提供 version,可提供本次下载使用的 url。 |
Core 恢复插件后自动检测插件版本,同时检测自身版本。插件管理页提供手动检测按钮;远程版本更高时,插件行显示更新按钮。Core 页显示当前/远程版本,并在有更新时显示热重载按钮。
插件更新会保存新源码、停止旧实例、执行新定义并恢复原启用状态。Core 热重载会释放旧 Core 的插件、Hook、定时器、监听器和 DOM,再由新 Core 从缓存源码恢复已启用插件。
跨域来源必须允许当前游戏页面读取响应。Header 模式若使用自定义响应头,还要通过 Access-Control-Expose-Headers 暴露该名称。
运行时事件
| 事件 | 触发证据与 Payload |
|---|---|
framework:ready | Core 服务和 UI 已发布;Payload 为 status() 快照。 |
battle:start | 有效可见的 TableGameScene/RogueLikeGameScene 且 manager.seats 存在;提供 scene、manager、sceneName、startedAt。 |
battle:end | Scene 离开、失去有效可见性、seats 消失或被替换;提供原 Scene/Manager、原因和结束时间。 |
battle:log | 来自公开 Laya HTML 战斗日志容器;提供 html、纯文本、节点和 Scene。 |
window:open | Stage 中有效可见的 Window/Dialog/Popup 节点出现;提供稳定对象 ID、原始节点和节点信息。 |
window:close | 此前可见窗口离开集合;提供同一对象 ID、原始节点和关闭时间。 |
| 插件事件 | plugin:installed/removed/started/stopped/updated、plugin:config-changed、plugin:config-reset。 |
有效可见性会检查节点及父链的 visible、alpha、scaleX/scaleY 和 destroyed。Core 不通过 console.log 判断战斗,也不把可能残留的窗口注册表当作事实。
高层事件不足时,可从 framework.laya.getLaya()、getStage() 或事件 Payload 取得原始 Laya 对象并调用底层公开接口。
存储
sgs.framework.ui.dock
sgs.framework.ui.window
sgs.framework.plugins.registry
sgs.framework.plugins.sources
sgs.framework.plugins.catalog-cache
sgs.framework.plugins.<pluginId>.code
sgs.framework.plugins.<pluginId>.config
sgs.framework.plugins.<pluginId>.data.*
context.config 保存 Core 排版配置;context.storage 保存插件内部配置或数据。插件只传相对键,Core 自动添加 plugins.<pluginId> 前缀并负责序列化。
Core 启动时把历史双分隔符键(例如 ui..dock、data..runtime-source)迁移为单点键;如果单点键已经存在,以单点键为准。
删除插件会删除注册记录和源码缓存,但保留 .config 与 .data.*。Core 页的“检查已删除插件配置”按插件 ID 汇总这些孤儿键、数量和字符数;用户确认删除时只移除该插件的配置/数据,不影响已安装或被禁用插件。
插件源
插件管理页按来源分组,每个官方/第三方/手动来源都可收起。官方源固定为 /script/plugin/index.json。第三方源可添加名称与 Catalog URL,并支持禁用、重命名和删除;删除来源不会删除已经安装的插件。
{
"name": "Source name",
"plugins": [
{ "id": "example.plugin", "url": "https://example/plugin.js" }
]
}
从 URL 安装插件时,Core 先下载并解析 Header,再显示名称、版本、简介、来源 URL 和权限列表。用户确认后才执行插件代码。
官方插件
官方源提供记牌器、可逆屏蔽、模式入口自动化、当前角色信息读取、每日任务奖励领取和月卡奖励领取。插件仍按普通插件安装、启用、禁用、更新和删除;禁用或卸载后不得继续保留 Hook、DOM、定时器、离屏对象或可调用服务。
SGS Card Tracker
无配置项。插件加载与 Scripts/dist/sgs-runtime.js 相同的运行时构建,只在有效可见的 TableGameScene 或 RogueLikeGameScene 中显示 UI,离开战斗立即停止描画并清理临时状态。已知牌只来自自己的手牌、公开日志或窗口、公开区和明确揭示牌的协议记录;隐藏对手 handCards 不属于可用事实。
SGS Blocker
提供“屏蔽广告 / 屏蔽特效 / 屏蔽道具弹窗”三个独立开关。插件使用当前 Laya 类和已经核对的具体路径,不以宽泛英文类名正则隐藏节点,也不全局关闭 Tween、Timer、Animation 或渲染。
| 开关 | 作用范围 |
|---|---|
| 屏蔽广告 | 阻止 ModeScene.showAdView、隐藏当前广告视图、过滤类型 0x27cb 的军典入口,并通过公开关闭方法处理 AdPushWindow。 |
| 屏蔽特效 | 在 XMLHttpRequest.open 中仅替换已确认的特效资源模式;保留铁索/酒白名单,并保留 Plot_tiesuolianhuan 与 FX_SHT_SLCX 的结束回调语义。 |
| 屏蔽道具弹窗 | 处理 GetPropSpecialWindow、GeneralOpenResultWindow、SkinOpenResultWindowNew、OldbackOneClickDrawAwdWin 和 TianShuWindow type=6。 |
禁用、更新、删除 Blocker 或热替换 Core 时,每个 Hook 都恢复包装前的方法。
SGS Automation
无配置项。插件通过既存 plugins.invokeAction() 暴露五个供 Agent 调用的函数。模式入口函数只使用当前有效可见的 Laya 节点:必要时从 ModeScene 打开活动场,在 ActivityModeView 中按真实页码查找模式入口,再调用对应 NCt.onEnterMode()。角色信息函数只读当前 Stage 中标记为自己的角色数据。每日任务和月卡奖励函数都使用调用时的服务器状态,不假定任务或累计档位数量固定,也不要求目标界面已经显示。
| 函数 | 目标与结果 |
|---|---|
openRogueMap | 查找 modeId=151 并打开山河图;已经位于大地图、小地图或山河图战斗 Scene 时返回 already-open。 |
openGeneralTrial | 查找 modeId=147 并打开武将试炼;已经位于试炼 Scene 或挑战窗口时返回 already-open。 |
getCharacterInfo | 返回当前角色的 ID、昵称、等级与经验、元宝/绑定元宝/总元宝、银两、今日在线时长、VIP 与官阶、工会、信誉/人气、势力和主公等级等白名单字段;不会打开个人资料窗口。 |
claimDailyTaskRewards | 先循环点击状态为 CanAward 的每日任务并等待单项领奖状态更新,再循环点击运行时给出的 3/6/9 次银两档位;档位自身会判断是否达标或已领取。函数不会打开任务栏或把任务 View 加入 Stage。 |
claimMonthCardRewards | 先局部同步直购月卡数据,解决跨日后客户端仍缓存“已领取”的问题;月卡有效且当日奖励可领时调用原生领取函数,随后按运行时数组循环领取全部 AddAwardsCanAwardList() 为真的累计奖励。即使当日奖励已领取,也会继续检查累计档位。 |
const rogue = await SgsFramework.plugins.invokeAction(
"sgs.automation", "openRogueMap"
);
const trial = await SgsFramework.plugins.invokeAction(
"sgs.automation", "openGeneralTrial"
);
const character = await SgsFramework.plugins.invokeAction(
"sgs.automation", "getCharacterInfo"
);
const dailyRewards = await SgsFramework.plugins.invokeAction(
"sgs.automation", "claimDailyTaskRewards"
);
const monthCardRewards = await SgsFramework.plugins.invokeAction(
"sgs.automation", "claimMonthCardRewards"
);
模式入口返回值包含 ok、action、status、modeId 和目标摘要。角色信息成功时返回 status=ok、数据来源摘要和 character 白名单;当前 Stage 没有自己的角色数据时返回 character-unavailable,不会为了读取而跳转界面。每日任务奖励返回本次实际每日任务数、每个任务的领取状态、累计完成数、每档银两奖励状态,以及执行前后 Stage 中的任务界面摘要。月卡奖励返回同步前后的卡片天数、当日奖励状态、每个累计档位的天数/物品/数量/领取状态,以及执行前后 Stage 中的月卡界面摘要。没有可领取奖励时返回 nothing-claimable;月卡未激活时返回 card-inactive;部分请求超时或失败时返回 partial。插件未启用时返回 inactive;Laya、任务数据或所需长名称接口不可用时返回结构化失败。函数不会代替用户返回主页、选择武将、开始挑战、确认弹窗、购买、续费或继续战斗。
权限与边界
插件权限用于安装前披露,不是能力沙箱,也不会按权限名拒绝执行。插件与普通页面脚本处于同一页面环境,可以读取对象、调用公开方法、发起网络请求或创建 DOM;只安装可信来源,并审查声明了网络、Hook、Laya 或 DOM 能力的代码。
Framework 不替插件绕过付费、购买确认或服务器权限。Card Tracker 不得把隐藏对手手牌当作已知事实。Blocker 只能使用具体、可恢复且经过当前运行时核对的路径。Automation 的模式入口函数会改变当前游戏界面,但只调用活动场分页和模式入口方法,不选择武将、不开始挑战、不确认弹窗,也不调用购买路径;不满足入口条件时直接返回失败。
getCharacterInfo 只读取当前 Stage 中 IsSelf=true 的角色对象,并按固定白名单构造新对象。它不返回原始运行时引用,也不返回 Session、重连 Token、二级密码、账号散列或登录 IP 等内部字段。当前 Scene 没有携带自己的角色对象时直接返回 character-unavailable,不打开个人资料窗口,也不发出资料查询请求。
claimDailyTaskRewards 会向游戏服务器发出真实的任务领奖请求。它循环处理调用时运行时标记为每日的任务,只点击状态为 CanAward 的项目;完成单项循环后,再循环点击全部累计银两档位,由游戏自身方法过滤未达标和已领取项目。任务数量不参与正确性判断,跨日增减只是本次循环数组不同。实现创建的任务对象始终离屏并在结束时销毁,不加入 Laya.stage;返回值中的 taskUi.opened 用于确认本次调用没有新增任务界面。
claimMonthCardRewards 会向游戏服务器发出真实的特权卡数据同步和领奖请求。它先调用只读的 SendClientPrivilegeDataReq() 取得当前服务器月卡状态;仅当类型 3 月卡处于激活状态且 bReward=true 时调用 sendAwardReq()。随后循环运行时提供的累计档位,只对 AddAwardsCanAwardList() 为真的索引调用 addItemClick(index),并通过 AddAwardsHasAwardList() 回读确认。实现不会调用同时包含购买分支的 getBtnClick(),不会购买或续费;未激活月卡返回 card-inactive。离屏月卡对象不加入 Laya.stage,结束时解除监听并销毁;monthCardUi.opened 用于确认本次调用没有新增福利界面。
禁用、更新、删除插件或热替换 Core 时,插件必须释放 Hook、监听器、定时器、离屏对象、DOM 和内部可调用服务。Automation 的 action 只在插件启用期间工作;停用后的直接调用返回 inactive,等待中的领取流程会在当前短轮询结束后停止继续请求。
浏览器 Fetch 受 CORS 约束。第三方插件和 Catalog 来源需要允许游戏页面跨域访问;Header 更新模式还需要显式暴露版本响应头。
没有匹配的章节。