概览与界面

SGS Framework 由下载器在 Laya 初始化前注入。Core 统一管理生命周期、运行时事件、可逆 Hook、插件安装与配置、LocalStorage 前缀、版本检测和 Laya 底层访问。

屏幕边缘的 SGS 悬浮窗自动吸附左侧或右侧。自动隐藏开启时,只保留边缘感应区,鼠标进入当前侧边缘才显示;拖动后在边缘松开也会按该设置隐藏。点击悬浮窗可选择“选项”、切换自动隐藏,并查看不可点击的 Core 版本号。

选项窗口默认占视口宽高各 50%,可拖动和从右下角缩放。最小宽高为视口的 20%,同时保留 260×180 像素的可用下限;移动、缩放、视口变化和浏览器缩放后,窗口都会被约束在屏幕内。

窗口包含“插件管理”“插件配置”“Core”三个 Tab。Core Tab 的“Core 版本”区域同时显示版本检测与“开发者模式”开关。开关成功保存后,公共字段会立即更新;已经运行的插件可以监听对应事件,后续启动的插件可以直接读取当前值。

SGS Framework 插件配置窗口
插件声明配置,Core 负责三个 Tab 的排版、状态与交互。
全局入口window.SgsFramework
官方源/script/plugin/index.json
网络超时5000 ms / 请求

安装与入口

  1. 在油猴脚本管理器中打开 download.mjs 并安装。
  2. 下载器在 document-start 安装 Laya 初始化屏障,优先请求本站 core.mjs,失败后请求 GitHub;两个来源各自最多等待 5 秒。
  3. 远程请求都失败时,下载器先尝试页面 LocalStorage 缓存;仍不可用时显示“继续游戏 / 刷新重试”。选择继续时使用可用缓存,没有缓存则不加载 Framework。
  4. 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.SgsFrameworkwindow.__SgsFramework 指向同一个实例。插件通过 context.framework 获得该实例;页面控制台也可以直接读取全局入口。

入口暴露方法与定义
CoredeveloperMode 是由 Core UI 控制的只读布尔字段,对应能力名 core.developer-modestatus() 返回版本、开发者模式、插件、Laya、事件监视、更新和 UI 状态;addDisposer(fn,label) 注册清理;dispose(reason) 停止插件并释放全部资源。
eventson(type,handler)onceoffemitclearlistenerCounton/once 返回取消函数。
loggerscope(name) 创建作用域 Logger;infowarnerror 同时写控制台和 log 事件。
storagescope(name)get/set/removegetJson/setJsonkeys。Core 负责真实 LocalStorage 前缀与 JSON 序列化。
timerssetTimeout(fn,ms,label)setInterval(fn,ms,label),返回取消函数并参与 Core 热重载清理。
hookswrapMethod(owner,name,handler,{label})。Handler 接收 {original,args,thisArg,callOriginal};返回的取消函数恢复包装前方法。
domcreateOverlaycreatePanelcreateButtontoast。创建结果包含元素与移除函数。
browseropenUrlrequestrequestTextrequestJsonrequestHeaders。请求默认不带凭据、禁用缓存并在 5 秒后中止。
layagetLaya()getStage()status()whenReady()walk()find()snapshot()。节点引用保持为原始 Laya 对象。
runtimestart()stop()poll()status()getBattle(),封装战斗、窗口和公开日志生命周期。
pluginsinspect/addFromUrl/installenable/disable/removecheckOne/checkUpdates/updatesettings/config/invokeAction/list、插件源与孤儿配置管理方法。
updatecheck()reload()status()reload 执行当前页面 Core 热替换。
uimount()openWindow(tab)closeWindow()selectTab()refresh()confirmAction()toast()status()
install(context) {
  function applyDeveloperMode() {
    if (context.framework.developerMode) {
      // 创建或启用仅用于开发的行为。
    } else {
      // 关闭并清理仅用于开发的行为。
    }
  }

  applyDeveloperMode();
  context.addDisposer(
    context.events.on("core:developer-mode-changed", applyDeveloperMode),
    "developer-mode"
  );
}

插件安装时先读取 context.framework.developerMode,可以获得 Core 启动时恢复的值。运行期间切换开关时,字段先更新,再触发事件;只在动作执行时判断的代码也可以直接读取字段,不需要缓存副本。

插件定义

插件是可直接下载、无需构建的经典 JavaScript 文件。Core 在执行代码前解析 Header,用于安装确认和版本检测;正文必须调用 SgsFramework.plugins.define()

Header含义
@id稳定且唯一的插件 ID;更新包必须保持一致。
@name / @version显示名称与可比较版本号。
@description安装确认框和插件列表中的简介。
@permissions逗号分隔的能力声明,仅披露,不构成沙箱。
@updateModedefaultheaderapi,不区分大小写。
@versionHeaderHeader 模式读取的响应头,默认 x-sgs-plugin-version
@versionApiAPI 模式 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 提供 frameworkidmanifestsourceUrleventsloggerstorageconfigtimershooksdombrowserlayaruntimeaddDisposerinstall 返回的函数也会在禁用、更新、删除或 Core 热替换时调用。

插件配置

插件声明配置分组、默认值和可选动作,Core 负责控件排版、保存、校验、错误提示与重置。每个插件可独立收起;没有配置项的插件显示“无可配置项”,也不显示重置按钮。

type值与触发行为
toggle保存布尔值;声明 onChange 时,在保存成功后调用对应动作。
buttonaction / onClick 指向插件动作;插件未启动时不可用。
text保存字符串;支持 inputType、数值范围、长度范围和多个正则 patterns
selectoptions: [{value,label}] 显示下拉列表并保存选中值;可声明 onChange
customrender / action 接收 Core 创建的 host;插件只拥有该 host 内部内容。
defaults: { enabled: true, mode: "safe" },
settings: [{
  id: "general",
  name: "General",
  items: [
    { key: "enabled", name: "Enabled", type: "toggle", onChange: "apply" },
    { key: "mode", name: "Mode", type: "select", options: [
      { value: "safe", label: "Safe" },
      { value: "full", label: "Full" }
    ], onChange: "apply" }
  ]
}],
actions: { apply(context, value, key) {} }

context.config.get/set/reset/defaults 与配置页共享同一状态。Core 先校验并序列化新值,再触发配置动作,因此动作读取 context.config 时已经是新值。首次安装没有历史配置或用户重置时,使用插件声明的 defaults

AutoRewards 显示“领取周卡月卡、自动免费将池、自动免费祈福、自动官阶俸禄、自动签到、自动砍树”六个 Toggle。六项默认开启;免费常山战鼓没有独立开关,会在每次自动检查中处理。

AutoRewards 的“签到策略”下拉列表提供“不自动签到、自动签到-跳过试用、自动签到-跳过使用-月底领”。“自动签到”Toggle 是签到子系统总开关;下拉选择“不自动签到”也会停用整个签到子系统。“月底领”只在 +8 服务器日历的当月最后一天允许领取试用武将奖励,其他日期仍跳过。

Core Tab 的“开发者模式”是 Core 自身设置,不属于插件配置。切换它不修改任何插件的 defaults/settings;插件通过公共字段和 Core 事件决定开发专用行为。

更新检测

模式请求与版本来源
defaultGET 插件 JavaScript,读取响应体并解析 Header 的 @version
header通过 Fetch 发送 HEAD,不读取响应体;读取 @versionHeader 指定的响应头。
apiGET @versionApi JSON;必须提供 version,可提供本次下载使用的 url

Core 恢复插件后自动检测插件版本,同时检测自身版本。插件管理页提供手动检测按钮;远程版本更高时,插件行显示更新按钮。Core 页显示当前/远程版本,并在有更新时显示热重载按钮。

插件更新会保存新源码、停止旧实例、执行新定义并恢复原启用状态。Core 热重载会释放旧 Core 的插件、Hook、定时器、监听器和 DOM,再由新 Core 从缓存源码恢复已启用插件。

跨域来源必须允许当前游戏页面读取响应。Header 模式若使用自定义响应头,还要通过 Access-Control-Expose-Headers 暴露该名称。

运行时事件

事件触发证据与 Payload
framework:readyCore 服务和 UI 已发布;Payload 为 status() 快照。
core:developer-mode-changed用户成功切换开发者模式后触发;Payload 为 { enabled, previous }。事件处理器执行时,framework.developerMode 已经是 enabled
battle:start有效可见的 TableGameScene/RogueLikeGameScenemanager.seats 存在;提供 scenemanagersceneNamestartedAt
battle:endScene 离开、失去有效可见性、seats 消失或被替换;提供原 Scene/Manager、原因和结束时间。
battle:log来自公开 Laya HTML 战斗日志容器;提供 html、纯文本、节点和 Scene。
window:openStage 中有效可见的 Window/Dialog/Popup 节点出现;提供稳定对象 ID、原始节点和节点信息。
window:close此前可见窗口离开集合;提供同一对象 ID、原始节点和关闭时间。
插件事件plugin:installed/removed/started/stopped/updatedplugin:config-changedplugin:config-reset

Core 启动时从存储恢复开发者模式,并在恢复插件前公开该字段;启动恢复本身不触发变更事件。插件应在安装时读取一次字段,并在需要持续响应时注册 core:developer-mode-changed,同时把取消函数交给插件清理流程。

有效可见性会检查节点及父链的 visiblealphascaleX/scaleYdestroyed。Core 不通过 console.log 判断战斗,也不把可能残留的窗口注册表当作事实。

高层事件不足时,可从 framework.laya.getLaya()getStage() 或事件 Payload 取得原始 Laya 对象并调用底层公开接口。

存储

sgs.framework.core.developer-mode
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.*

sgs.framework.core.developer-mode 保存 Core 开发者模式,缺失时按 false 处理。Core 启动时先读取该键,再创建插件上下文;切换时先写入该键,保存成功后才更新公共字段与发送变更事件。写入失败时字段保持原值,Toggle 恢复原状态并显示错误提示。

context.config 保存插件声明的配置对象;context.storage 保存插件自己的内部数据。插件只传相对键,Core 自动添加 plugins.<pluginId> 前缀并负责字符串或 JSON 序列化。Core 的停靠位置与窗口几何信息由 sgs.framework.ui.* 独立保存。

Core 启动时把历史双分隔符键(例如 ui..dockdata..runtime-source)迁移为单点键;如果单点键已经存在,以单点键为准。

删除插件会删除注册记录和源码缓存,但保留 .config.data.*。Core 页的“检查已删除插件配置”按插件 ID 汇总这些孤儿键、数量和字符数;用户确认删除时只移除该插件的配置/数据,不影响已安装或被禁用插件。

插件源

插件管理页按来源分组,每个官方、第三方或手动来源都可收起。固定官方源 /script/plugin/index.json 发布 SGS Card Tracker、SGS Blocker、SGS DebugTools 与 SGS AutoRewards。Catalog 中 DebugTools 继续使用稳定 ID sgs.automation,AutoRewards 使用独立 ID sgs.auto-rewards

{
  "name": "Source name",
  "plugins": [
    { "id": "example.plugin", "url": "https://example/plugin.js" }
  ]
}

第三方源可添加名称与 Catalog URL,并支持禁用、重命名和删除;删除来源不删除已经安装的插件。从 URL 安装时,Core 先下载并解析 Header,显示名称、版本、简介、来源 URL 和权限列表,用户确认后才执行代码。Catalog 的 ID 与插件 Header/定义不一致时,安装校验失败。

官方插件

官方源提供 SGS Card Tracker、SGS Blocker、SGS DebugTools 与 SGS AutoRewards。它们使用普通插件相同的安装、启用、禁用、更新和删除流程;停止时释放所属 Hook、DOM、定时器、离屏对象、等待任务和可调用服务。

SGS Card Tracker

无配置项。插件加载与 Scripts/dist/sgs-runtime.js 相同的运行时构建,只在有效可见的 TableGameSceneRogueLikeGameScene 中显示 UI。离开战斗后停止描画并清理临时状态。已知牌只来自自己的手牌、公开日志或窗口、公开区和明确揭示牌的协议记录;隐藏对手 handCards 不作为已知事实。

SGS Blocker

提供“屏蔽广告 / 屏蔽特效 / 屏蔽道具弹窗”三个独立开关。每项只操作已核对的当前 Laya 路径,不按宽泛类名隐藏节点,也不全局关闭 Tween、Timer、Animation 或渲染。

开关作用范围
屏蔽广告阻止 ModeScene.showAdView、隐藏当前广告视图、过滤类型 0x27cb 的军典入口,并通过公开关闭方法处理 AdPushWindow
屏蔽特效XMLHttpRequest.open 中只替换已确认的特效资源模式;保留铁索/酒白名单和两个已确认的结束回调路径。
屏蔽道具弹窗处理 GetPropSpecialWindowGeneralOpenResultWindowSkinOpenResultWindowNewOldbackOneClickDrawAwdWinTianShuWindow type=6

禁用、更新、删除 Blocker 或热替换 Core 时,每个 Hook 都恢复包装前的方法。

SGS DebugTools

无配置项。插件显示名为 DebugTools,但为使已安装的旧插件原位更新,ID 仍是 sgs.automation。它通过 plugins.invokeAction() 暴露四个调试函数;相同函数的并发调用复用同一 Promise。

函数目标与结果
openRogueMap查找 modeId=151 并打开山河图;目标 Scene 已显示时返回 already-open
openGeneralTrial查找 modeId=147 并打开武将试炼;试炼 Scene 或挑战窗口已显示时返回 already-open
getCharacterInfo返回自己角色的 ID、昵称、等级/经验、元宝、绑定元宝、银两、在线时长、VIP、官阶、公会等白名单字段,不打开资料窗口。
claimDailyTaskRewards离屏循环领取 CanAward 的每日任务,再处理运行时提供的累计次数奖励;不假定任务数量,也不打开任务栏。
const invokeDebug = (action) => SgsFramework.plugins.invokeAction(
  "sgs.automation", action
);
const rogue = await invokeDebug("openRogueMap");
const trial = await invokeDebug("openGeneralTrial");
const character = await invokeDebug("getCharacterInfo");
const dailyTasks = await invokeDebug("claimDailyTaskRewards");

函数返回 okactionstatus 与对应的前后状态。插件未启用时返回 inactive。模式入口不选择武将、不开始挑战、不确认弹窗,也不继续战斗。

SGS AutoRewards

插件启用后每秒读取一次运行时管理器公开的 ServerTime/ServerDate。首次取得 +8 服务器时钟时执行一次启动检查;服务器日期变化时执行一次跨日检查。检查按“卡类、免费将池、免费祈福、免费常山战鼓、官阶俸禄、签到、发财树”顺序串行,正在执行时不会并发重复,期间检测到的跨日会排队补跑。

项目检查与领取规则
月卡 / 周卡先同步卡数据。启动检查只领取服务器状态标记可领的每日奖励和银两;跨日检查对仍有效的类型 3 月卡和类型 2 周卡各发送一次每日奖励及银两请求,以绕过跨日按钮状态未刷新的客户端问题。月卡随后独立领取全部满足条件的累计天数奖励;周卡没有累计路径。
免费将池免费掉落 ID、免费 UI 标记、管理器免费状态与空闲请求池同时成立时,只用 Free_MianFei_Draw 抽取一次。
免费祈福先同步全部轮次;只对 freeOpen、活动时间、抽取时间与 CanFreeBless 同时成立的轮次发送 count=1
免费擂鼓已加入公会且 CanDrum 时,重复常山战鼓的免费请求,直到 LeftFreeDrumTimes 为零或一次请求未确认。该项没有独立开关。
官阶俸禄分别判断、领取并确认每日俸禄与每周俸禄;其中一项不存在不影响另一项。
签到只处理正常免费签到状态;补签状态不点击。每日签到后或已签到时都检查累计签到奖励。试用武将按签到策略跳过;“月底领”只在 +8 服务器日历当月最后一天放行。
发财树可砍时发送一次砍树请求;免费奖励物品存在且没有必须处理的树事件时再领奖。跨日时即使客户端仍缓存“已领”状态也会尝试一次砍树;启动时相信重登后状态。

插件提供六个 Toggle 与一个签到策略下拉列表,默认开启全部 Toggle,并默认“自动签到-跳过试用”;第三项按界面显示为“自动签到-跳过使用-月底领”。AutoRewards 不暴露手动领奖 Action;配置变更影响下一次启动/跨日自动检查。禁用插件会停止服务器时钟监听并取消后续等待。

权限与边界

插件权限是安装前披露,不是能力沙箱,也不会按权限名阻止执行。插件与页面脚本处于同一环境,可以读取运行时对象、调用公开方法、发出真实游戏请求或创建 DOM。只安装可信来源,并审查声明网络、Hook、Laya、游戏请求或 DOM 能力的代码。

开发者模式只是插件可读取的布尔信号,不授予权限、不隔离 Debug 代码,也不改变生命周期。使用该信号的插件必须自己在关闭时清理开发专用 Hook、监听器、定时器和 DOM,并把最终清理交给 Disposer。

Card Tracker 不把隐藏对手手牌当作已知事实。Blocker 只使用具体、可恢复且经过运行时核对的路径。DebugTools 的模式入口会改变当前界面,但只调用活动场分页和模式入口;不选择武将、不开始挑战、不确认弹窗,也不调用购买路径。入口条件不成立时返回结构化失败。

DebugTools 的 getCharacterInfo 只读取当前 Stage 中 IsSelf=true 的角色对象,并构造白名单结果。它不返回原始对象、Session、重连 Token、二级密码、账号散列或登录 IP;对象不可用时不打开资料窗口或发出资料查询。claimDailyTaskRewards 会发出真实领奖请求,但只处理每日且 CanAward 的任务和运行时累计奖励数组,不以固定任务数量判断正确性,也不把任务 View 加入 Stage。

AutoRewards 启用后会在首次取得服务器时间和每次 +8 服务器日期变化时发出真实请求。跨日卡类流程有意忽略客户端每日/银两按钮的本地可领标记:只要卡仍有效,就对月卡和周卡各发送一次对应请求;服务器仍负责最终资格校验。启动检查会先同步状态并避免对已领取卡奖励重复请求。月卡累计奖励始终按运行时的可领/已领数组逐项判断。

AutoRewards 不调用月卡/周卡购买或续费方法。武将抽取必须同时满足免费掉落 ID、免费 UI、管理器免费状态和请求空闲,并只使用 Free_MianFei_Draw;祈福必须通过免费配置与时间检查,只发送 count=1。两者均不调用付费抽取、灯购买、道具购买或元宝补足路径。

免费擂鼓只调用常山战鼓的 SendGuildDonate(),不调用亮银槌、鎏金锤、背包道具、SendGuildDrumUse() 或元宝确认。官阶只调用资格为真的每日/每周俸禄接口,不升级官阶,也不领取其他官阶活动奖励。

签到只在普通免费状态执行,补签状态不会点击,因此不会发起补签消耗。试用武将由 IsTryGeneralSkin 判定;跳过策略不会污染将池,“月底领”会在服务器当月最后一天放行该奖励。累计签到奖励仍会独立处理。

发财树只在免费奖励物品就绪时领奖,不购买物品。存在 HasTriggerEvent 时返回 event-required,不会自动打开、跳过或伪造事件窗口;该次可能需要用户手动处理后等待下一次自动检查。跨日的强制砍树请求只用于绕过客户端缓存的昨日状态,服务器仍负责资格校验。

AutoRewards 使用串行队列避免并发重复;禁用、更新、删除插件或热替换 Core 时停止时钟监听、取消等待并销毁离屏对象。已送达服务器的请求无法撤回,但清理后不会继续下一轮请求。DebugTools 停止后 Action 返回 inactive

浏览器 Fetch 受 CORS 约束。第三方插件与 Catalog 来源需要允许游戏页面跨域访问;Header 更新模式还要显式暴露版本响应头。