Repository Wiki
BeyondDimension/SteamTools

迅游 SDK 第三方加速集成

迅游(XunYou)加速器 SDK 集成是 Watt Toolkit(SteamTools)加速器插件的第三方加速通道之一。它在仓库中以 src/XunYouSDK 下的静态分部类 XunYouSDK 作为原生 SDK 的托管封装,并由加速器插件(BD.WTTS.Client.Plugins.Accelerator)在启动与各加速操作入口处接入。当前该集成处于占位/未激活状态:合作凭据常量为空,原生库加载逻辑被注释,IsSupported 恒为 false,所有迅游分支被安全旁路。

目的与范围

本页覆盖迅游 SDK 第三方加速集成的完整实现边界:

  • SDK 封装层:src/XunYouSDK/XunYouSDK.cs、XunYouSDK.AppId.cs、XunYouSDK.Constants.cs 三个分部文件组成的 static partial class XunYouSDK
  • 平台与架构探测:静态构造函数中对 OS 架构/进程架构的判定,以及 libraryPath 的计算
  • 合作凭据与签名存根:appId、userType、channel_no、webapi_host 等由迅游方分配的常量,以及 CalcWebApiSign 签名函数
  • 集成点:加速器插件的 Plugin.cs 初始化调用,以及 BackendAcceleratorServiceImpl.XunYou.cs 中各加速操作前的 IsSupported 守卫

以下内容有意留给兄弟页面,不在本页展开:

  • 加速器插件整体的服务注册、UI 与加速流程编排——见"加速器插件"相关页面
  • BackendAcceleratorServiceImpl 的主流程与其它第三方加速通道(其它 partial 实现文件)——见对应加速通道的页面
  • 原生库 P/Invoke 的通用基础设施——本页仅涉及迅游专属的 DllImportResolver 设计意图(且当前为注释状态)

概述

迅游是国内的商业游戏加速服务商。Watt Toolkit 的加速器插件采用"多后端加速通道"设计,允许在不同运行环境下选择不同的第三方加速实现。迅游通道的结构是:

  1. 封装层(src/XunYouSDK):一个 static partial class XunYouSDK,以三个物理文件拆分职责——主文件负责平台检测与初始化,AppId.cs 负责合作凭据与 WebApi 签名,Constants.cs 负责原生库文件名常量。
  2. 原生层(设计目标,未激活):迅游提供的本机库 xunyoucall.dll(x86)与 xunyoucall64.dll(x64),随程序集部署在其输出目录旁;以及迅游 WebApi(签名请求)。
  3. 接入层:加速器插件在 #if WINDOWS 下调用 XunYouSDK.Initialize();BackendAcceleratorServiceImpl 的迅游分部实现(BackendAcceleratorServiceImpl.XunYou.cs)在每个迅游专属操作入口处先检查 XunYouSDK.IsSupported,不支持时直接返回。

关键设计意图:整个迅游通道由一个单点开关 appId 控制——只有当迅游方分配了真实的合作 id(appId != 0)后,静态构造函数才会把 IsSupported 置为 true。这种"凭据即开关"的设计使得未签约的社区构建可以完整编译运行,而无需删除任何代码路径,同时保证所有迅游分支在未激活时零开销旁路。

架构

Loading diagram...

上图反映了集成的分层与单向依赖:

  • 插件层 → SDK 层:插件只依赖 XunYouSDK 的两个公共面——Initialize() 与 IsSupported。迅游分部实现不直接触碰原生库或凭据常量,而是完全经由 SDK 判定结果决定是否执行。
  • SDK 层 → 原生层(虚线):当前全部是"设计目标"关系——libraryPath 只是被计算出来而从未加载(加载逻辑被注释),CalcWebApiSign 返回空串,webapi_host 为空。虚线表示这些链路尚未真正建立。
  • 编译期裁剪:主文件的平台检测与初始化逻辑包裹在 #if WINDOWS 中,非 Windows 平台编译产物中只剩 IsSupported(恒为默认值 false),保证跨平台构建无原生依赖。

分部类职责划分

Loading diagram...

三个物理文件在编译期合并为同一个 XunYouSDK 类型。这种拆分让"合作凭据"(AppId.cs)可以独立于"平台检测逻辑"(XunYouSDK.cs)维护——签约落地时只需替换凭据文件,无需触碰检测与初始化代码。

实现详解

平台检测与静态构造

XunYouSDK 的可用性判定发生在类型初始化(静态构造函数)阶段,而不是每次调用时动态判断。核心逻辑位于 XunYouSDK.cs 的 #if WINDOWS 块中:

csharp
1#if WINDOWS 2 static XunYouSDK() 3 { 4 if (appId != 0) 5 { 6 switch (RuntimeInformation.OSArchitecture) 7 { 8 case Architecture.X86: 9 case Architecture.X64: 10 IsSupported = true; 11 switch (RuntimeInformation.ProcessArchitecture) 12 { 13 case Architecture.X86: 14 libraryPath = Path.GetFullPath(Path.Combine(typeof(XunYouSDK).Assembly.Location, "..", libraryFileNameX86)); 15 break; 16 case Architecture.X64: 17 libraryPath = Path.GetFullPath(Path.Combine(typeof(XunYouSDK).Assembly.Location, "..", libraryFileNameX64)); 18 break; 19 } 20 break; 21 } 22 } 23 } 24#endif

Source: XunYouSDK.cs

逐层拆解这段代码的判定链:

  1. 第一层:凭据门(appId != 0)——这是整个迅游通道的总开关。appId 是迅游方分配的合作 id,在 XunYouSDK.AppId.cs 中声明为 const int appId = 0。当前仓库中它为 0,因此整个静态构造函数体不会执行,IsSupported 保持字段默认值 false。
  2. 第二层:OS 架构( RuntimeInformation.OSArchitecture )——只有操作系统本身是 x86 或 x64 才继续。迅游原生库不提供 ARM64 等其它架构,Windows on ARM(x64 模拟除外)会被排除。
  3. 第三层:进程架构( RuntimeInformation.ProcessArchitecture )——注意这与第二层是两个不同维度:第二层决定"这个操作系统上能不能跑迅游库",第三层决定"当前进程应该加载哪一份 DLL"。例如在 x64 Windows 上运行 x86 进程时,libraryPath 会指向 xunyoucall.dll 而非 xunyoucall64.dll。libraryPath 使用 Path.Combine(Assembly.Location, "..", ...) 解析为绝对路径,即原生库与 SDK 程序集同目录部署。

设计意图:把可用性判定全部前置到静态构造函数,使得 IsSupported 成为廉价的、线程安全的(static 字段初始化由 CLR 保证单次执行)、且在进程生命周期内不变的事实。调用方(BackendAcceleratorServiceImpl.XunYou.cs)因此可以在每个操作入口做无锁、无 try/catch 的守卫检查。

凭据常量与 WebApi 签名存根

合作凭据集中在 XunYouSDK.AppId.cs 中,全部为 const(编译期常量):

csharp
1static partial class XunYouSDK 2{ 3 /// <summary> 4 /// 合作 id,由迅游给出明确值 5 /// </summary> 6 const int appId = 0; 7 8 /// <summary> 9 /// 账号类型,由迅游给出明确值 10 /// </summary> 11 const int userType = 0; 12 13 /// <summary> 14 /// 渠道版本,由迅游给出明确值 15 /// </summary> 16 const string channel_no = ""; 17 18 const string webapi_host = ""; 19 20 const string webapi_vip_endtime = ""; 21 22 static string CalcWebApiSign(XunYouBaseRequest body) 23 { 24 return ""; 25 } 26}

Source: XunYouSDK.AppId.cs

各常量含义:

常量类型当前值说明
appIdint0迅游分配的合作 id,兼作整个迅游通道的启用开关
userTypeint0迅游分配的账号类型
string channel_nostring""渠道版本号
webapi_hoststring""迅游 WebApi 服务主机地址
webapi_vip_endtimestring""WebApi 会员到期时间相关参数

CalcWebApiSign(XunYouBaseRequest body) 是 WebApi 请求签名函数,当前实现直接返回空字符串。它的参数类型 XunYouBaseRequest 在本仓库中未找到定义(仅在此方法签名中被引用),说明该类型属于迅游 SDK 的原始分发内容或外部依赖,仓库内只保留了调用形态。

原生库常量与文件名约定

csharp
1static partial class XunYouSDK 2{ 3 const string libraryName = libraryFileNameX64; 4 5 //const string libraryName = "xunyoucall"; 6 public const string libraryFileNameX86 = "xunyoucall.dll"; 7 public const string libraryFileNameX64 = "xunyoucall64.dll"; 8}

Source: XunYouSDK.Constants.cs

  • libraryFileNameX86 / libraryFileNameX64 是仅有的两个 public 常量,对应迅游 SDK 的两份原生库文件名。构建时它们需与 XunYouSDK 程序集输出到同一目录,静态构造函数中的 Path.Combine(..., "..", ...) 依赖这一部署约定。
  • libraryName 当前被定义为 libraryFileNameX64,其旁有一行注释掉的 //const string libraryName = "xunyoucall";,表明该常量曾经用于按名称引用原生库的 P/Invoke 场景,后改为直接引用 DLL 文件名常量。libraryName 在当前代码中未被实际使用(仅出现在被注释掉的 DllImportResolver 代码中)。

初始化入口:Initialize() 与被注释的加载方案

Initialize() 是插件启动时调用的公共入口,但其当前实现为空方法(仅保留注释掉的历史实现):

csharp
1/// <summary> 2/// 初始化 <see cref="XunYouSDK"/> 3/// </summary> 4public static void Initialize() 5{ 6 //if (isInitialize) 7 // return; 8 //isInitialize = true; 9 ... 10}

Source: XunYouSDK.cs

被注释的代码揭示了一条清晰的历史演进轨迹,即"如何把原生库接进 .NET"的完整设计思路:

  1. 防重入:isInitialize 标志保证初始化只执行一次。
  2. 显式加载:NativeLibrary.Load(libraryPath) 把原生库显式加载进进程,失败时静默捕获(catch 空块)。
  3. 解析器重定向:NativeLibrary.SetDllImportResolver(typeof(XunYouSDK).Assembly, DllImportResolver) 为整个程序集注册自定义 DLL 解析器。其配套的 DllImportResolver(同样被注释)在收到 libraryName 的解析请求时,若已持有 libraryIntPtr 则直接返回该句柄。

为什么要这样设计(设计意图):.NET 默认的 P/Invoke 解析按默认搜索顺序找 DLL,而迅游库需要按进程架构精确选择 x86/x64 两个文件,且必须从程序集旁目录加载。SetDllImportResolver + 预加载句柄的方案把"选哪份 DLL、从哪加载"这个决策完全收拢到 SDK 内部,对上层 P/Invoke 声明透明。这条链路当前整段被注释停用,libraryIntPtr 字段(//static nint libraryIntPtr;)也一并注释,与 appId = 0 的总开关状态一致——未签约前不需要真实加载原生库。

插件接入点

加速器插件的入口在 Plugin.cs 中,于 Windows 平台编译时执行一次初始化:

csharp
#if WINDOWS XunYouSDK.Initialize(); #endif

Source: Plugin.cs

迅游分部实现 BackendAcceleratorServiceImpl.XunYou.cs 则在每个迅游专属操作入口处重复同一守卫模式(该文件中至少 10 处):

csharp
1if (!XunYouSDK.IsSupported) 2{ 3 ... 4}

Source: BackendAcceleratorServiceImpl.XunYou.cs

这一模式出现在 L83、L96、L117、L148、L166、L179、L212、L225、L238、L251 等多处,覆盖迅游通道的全部操作入口。所有守卫在 IsSupported == false 时提前返回,确保未激活的迅游通道不会产生任何原生调用或网络请求。

核心流程

下图展示从插件启动到一次迅游加速操作判定的完整链路(以当前未激活状态为准):

Loading diagram...

流程要点:

  1. 启动期:插件加载时调用一次 Initialize()。由于静态构造函数先于任何成员访问执行,即使 Initialize() 为空,IsSupported 也已在首次触碰类型时完成判定。
  2. 运行期:每个迅游操作入口的守卫模式保证——在 appId == 0 的社区构建中,迅游分支是纯"死分支":不加载原生库、不发网络请求、无异常抛出。
  3. 激活后的预期路径(设计目标,未实现):appId 被填入真实值后,静态构造会把 IsSupported 置 true 并计算 libraryPath;届时需恢复 Initialize() 中被注释的 NativeLibrary.Load + SetDllImportResolver 链路,P/Invoke 声明即可正常解析到正确架构的原生库。

使用示例

示例 1:插件启动时初始化(实际接入方式)

csharp
#if WINDOWS XunYouSDK.Initialize(); #endif

Source: Plugin.cs

加速器插件在 Windows 平台编译时于启动路径调用一次 Initialize()。#if WINDOWS 保证非 Windows 构建中此调用被完全裁剪,不会因缺少 Initialize 的 Windows 专属实现而编译失败(该方法本身定义在 XunYouSDK.cs 的 #if WINDOWS 块内)。

示例 2:操作入口的可用性守卫

csharp
1if (!XunYouSDK.IsSupported) 2{ 3 ... 4}

Source: BackendAcceleratorServiceImpl.XunYou.cs

这是迅游分部实现中重复出现的标准守卫。它读取的是静态构造函数已判定好的 IsSupported 属性,无锁、无 I/O、无异常路径,因此可以在高频入口处廉价地反复检查。

API 参考

XunYouSDK.IsSupported

csharp
public static bool IsSupported { get; private set; }

Source: XunYouSDK.cs

含义:当前运行环境是否支持使用迅游加速器。

取值逻辑:true 需同时满足三个条件——(1) 编译目标为 Windows(#if WINDOWS);(2) appId != 0;(3) RuntimeInformation.OSArchitecture 为 X86 或 X64。任一不满足则保持默认值 false。

可见性注意:属性的 setter 为 private,外部代码无法篡改判定结果;但请注意 static 字段语义在多 AppDomain/测试隔离场景下不会重置,IsSupported 在单个进程内是初始化后即固定的。

XunYouSDK.Initialize(): void

参数:无。

返回:void,无返回值。

异常:当前实现为空方法,不抛出任何异常。被注释的历史实现中,NativeLibrary.Load 失败会被空 catch 静默吞掉,IsSupported 会被置 false——即初始化失败被设计为"降级为不支持",而非向调用方抛错。

线程安全:静态构造函数保证 IsSupported 的初始化由 CLR 保证恰好执行一次且对所有线程可见。Initialize() 被注释的历史实现额外用 isInitialize 标志做了防重入,当前空实现天然幂等。

XunYouSDK.CalcWebApiSign(body: XunYouBaseRequest): string(私有)

参数:body(类型 XunYouBaseRequest,该类型在仓库内无定义)。

返回:签名串;当前实现返回 ""。

可见性:私有静态方法,仅为 WebApi 请求签名而预留。激活后需按迅游文档实现签名算法并填入 webapi_host。

常量参考

常量类型可见性当前值说明
appIdintprivate const0合作 id,迅游通道总开关
userTypeintprivate const0账号类型
channel_nostringconst""渠道版本
webapi_hoststringconst""WebApi 主机地址
webapi_vip_endtimestringconst""会员到期时间参数
libraryNamestringconst= libraryFileNameX64原生库逻辑名(未使用)
libraryFileNameX86stringpublic const"xunyoucall.dll"x86 原生库文件名
libraryFileNameX64stringpublic const"xunyoucall64.dll"x64 原生库文件名

失败模式、边界情况与并发

场景行为源码依据
appId == 0(当前状态)IsSupported == false,所有迅游操作守卫提前返回,零原生调用XunYouSDK.cs L71-L92
非 Windows 平台平台检测代码整体被 #if WINDOWS 裁剪,IsSupported 恒为 falseXunYouSDK.cs L8, L70
OS 为 ARM64switch (RuntimeInformation.OSArchitecture) 无匹配分支,IsSupported 保持 falseXunYouSDK.cs L75-L90
x64 Windows 上的 x86 进程IsSupported 为 true,libraryPath 指向 xunyoucall.dll(x86 版本)XunYouSDK.cs L80-L88
原生库加载失败(激活后)历史实现为空 catch 静默 + IsSupported = false,降级而非抛错XunYouSDK.cs L52-L61(注释代码)
原生库未随程序集部署libraryPath 仅是计算出的路径,不校验文件存在;真实加载在激活后才会暴露缺失XunYouSDK.cs L83-L87
并发访问 IsSupportedCLR 静态初始化保证单次执行与可见性;属性只读无竞态XunYouSDK.cs L68
CalcWebApiSign 被调用返回空串;若激活后未实现签名,WebApi 请求会因签名缺失而失败XunYouSDK.AppId.cs L32-L35

并发要点:迅游 SDK 的可用性判定完全集中在类型初始化阶段,进程内只执行一次。IsSupported 为只读属性,所有调用方读到的都是初始化后的稳定值,不需要任何同步原语。被注释的 libraryIntPtr(原生库句柄)若被激活,将成为跨线程共享资源,需注意 NativeLibrary.Load 句柄的释放语义与 DllImportResolver 返回句柄的生命周期管理——这也是当时选择"加载一次、缓存句柄、解析器复用"设计的动机之一。

边界情况:XunYouBaseRequest 类型在仓库中不存在定义,CalcWebApiSign 的签名在当前形态下无法被有意义的调用;这说明该文件保留的是迅游 SDK 原始分发的接口形态,激活时需连同请求模型一起补齐。

性能与运维要点

  • 零开销旁路:当前 appId == 0 状态下,迅游通道不加载原生库、不发起任何网络请求,守卫检查只是一次静态属性读取。对社区构建的运行时性能与启动时间无可观测影响。
  • 原生库部署约定:libraryFileNameX86/libraryFileNameX64 必须与 XunYouSDK 程序集输出同目录(静态构造用 Assembly.Location + ".." 解析)。运维打包时若改变目录结构会破坏该约定,激活后表现为加载失败 → 静默降级。
  • 构建产物注意:由于 libraryPath 逻辑在 #if WINDOWS 内,非 Windows 构建不产生对 DLL 文件名的运行时依赖;但两个文件名常量是 public const,任何引用它们的外部程序集都会在编译期内联其值。
  • 激活成本:激活迅游通道需要(1) 替换 AppId.cs 中的凭据常量;(2) 实现 CalcWebApiSign 与请求模型;(3) 恢复 Initialize() 中被注释的 NativeLibrary.Load + SetDllImportResolver 链路;(4) 在构建中引入两份原生库文件。四步彼此独立,可分阶段推进。

扩展点

  1. 凭据文件独立替换:AppId.cs 是纯粹的凭据/签名分部文件,与平台检测逻辑完全解耦。签约落地时只需替换此文件,无需触碰 XunYouSDK.cs 的检测与初始化代码——这是分部类拆分的直接收益。
  2. DllImportResolver 自定义解析钩子:SetDllImportResolver(typeof(XunYouSDK).Assembly, DllImportResolver) 以程序集为粒度注册解析器,激活后可在不修改任何 P/Invoke 声明的前提下,统一控制该程序集内所有原生导入的加载来源(按进程架构选 DLL、从指定目录加载)。
  3. 新增第三方加速通道:迅游通道展示了"SDK 封装(src/XunYouSDK)+ 插件守卫(IsSupported)+ 分部服务实现(BackendAcceleratorServiceImpl.XunYou.cs)"的三段式接入模式,可作为接入其它加速服务商的模板。
  4. public const 文件名常量:libraryFileNameX86/libraryFileNameX64 暴露为公共常量,允许构建脚本或打包工具引用它们以校验原生库是否随产物部署。

相关链接