Skip to content

Development Guide

DNT_OF edited this page Oct 4, 2026 · 9 revisions

开发者指南

面向在 SLDataAPI 仓库贡献功能的开发者。

main 当前版本:2.6.1 RIDGE(预发布,tag v2.6.1_RIDGE,正式发布后可用);稳定版 2.6.0 PEAK(tag v2.6.0_PEAK)。

DevKit:Release 附件 SLDataAPI-DevKit-v2.6.1_RIDGE.zip(含内测端点清单;2.6.1 Release 发布后可下载)或 SLDataAPI-DevKit-v2.6.0_PEAK.zip(sl-dataapi-dev skill + 冒烟脚本)。


项目结构

目录 命名空间 职责
Plugin.cs SLDataAPI Enable/Disable、事件订阅、服务启动顺序
Config.cs SLDataAPI config.yml 属性(snake_case)
Data/ SLDataAPI.Data HTTP/WS JSON DTO
Control/ SLDataAPI.Control 路由 ControlController、WS WsControlService、ControlAuth
Auth/ SLDataAPI.Auth ApiKeyService、EndpointAcl、RemoteCommandGuard
Commands/ SLDataAPI.Commands sldataapi / apikey(本地 only)
Services/ SLDataAPI.Services HttpServer、DataCollector、MainThreadExecutor、ReportService、ControlLogService、WebDavUploader、UpdateChecker…
Voice/ SLDataAPI.Voice SPY 转发 + 录音
Map/ SLDataAPI.Map seed/layout/export
Integrations/ SLDataAPI.Integrations EXILED 反射、第三方插件探测、PluginEndpointRegistry(适配插件注册表)
Capture/ SLDataAPI.Capture 控制台输出 Harmony 补丁
examples/AdaptedPluginSample/ — 适配插件注册示例

架构:Architecture。编译发布:Building。


强制约定

1. 事件链保护

LabAPI 事件 handler 必须整体 try/catch,异常 Log.Error 后吞掉,不得抛回游戏。参考 Plugin.OnRoundStarted。

2. 主线程派发

var result = MainThreadExecutor.RunOnMainThread(() =>
{
    // Unity / Mirror API
    return (200, Json(true, "ok", data));
}, out Exception err);
if (err != null)
    return (400, Json(false, err.Message));

纯文件、鉴权校验、序列化可不派发。超时后设置取消标志,迟到的 action 不得再执行。

3. 错误对外表述

ControlController.Handle 顶层 catch → 500 "内部错误"。不向客户端返回堆栈、绝对路径。

4. 配置 snake_case

C# ReportMaxRecords → YAML report_max_records。单字段错误可能导致整文件回退默认。

5. 版本注释

功能块用注释标版本,例如:

// ===== 举报(v2.5.4 推出)=====

Plugin.Version 为单一真相(2.6.1 · RIDGE);SLDataAPI.csproj 的 Version / InformationalVersion、README / Wiki / Release tag 对齐。新功能注释写成「v2.6.1 推出,代号 RIDGE」。新文件顶部加 SPDX 头:

// SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 DNT_OF (https://github.com/DNTOF/SLDataAPI)

添加控制端点

  1. Data/ControlModels.cs:新增请求类(JSON 属性 snake_case)。
  2. ControlController.Handle:switch 注册 path(RA 对齐路径)。
  3. 实现 XxxAction(string body):Parse<T> → 校验 → MainThreadExecutor → (status, Json(...))。
  4. 鉴权:ApiKeyService.TryAuthenticate(Bearer / X-SLDataAPI-Key)+ EndpointAcl。
  5. 审计:侵入性写操作经 ControlLogService(若启用)。
  6. 文档:更新 HTTP-API;历史对照仍见 Old-HTTP-API。
  7. WS:无需 duplicate——call.path 与 HTTP 相同即自动兼容。

参考:ReportsAction、MapFacilityAction、broadcast / staffchat。

501 占位

未实现:Stub501("name")。当前仍为 501:/control/player/inventory、/control/dummies。
已实现:/control/broadcast、/control/staffchat。

远程命令护栏

RemoteCommandGuard 拦截经控制通道执行的 sldataapi/slda。新增本地管理命令应注册在 Commands/ 并默认拒绝远程。


适配插件集成

第三方 LabAPI 插件引用 SLDataAPI.dll(编译期引用即可,部署时与 SLDataAPI 同放 LabAPI/plugins/global/),在 Enable 里注册、Disable 里注销。完整示例见仓库 examples/AdaptedPluginSample。

只读注册(2.5.5 起)

bool ok = PluginEndpointRegistry.TryRegister(
    id: "dntof.sample_adapted",                 // ^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$,建议反向域名
    name: "Adapted Plugin Sample",
    version: Version.ToString(),
    capabilities: new[] { "sample.hello" },     // ≤32
    statusCallback: () => JsonConvert.SerializeObject(new { ok = true }),  // ≤4KB
    routes: new List<AdaptedRoute>               // ≤16;GET /plugins/<id>/<route>
    {
        new AdaptedRoute { Path = "hello", Handler = _ => "{\"ok\":true}" },
    },
    out string error);

写 / 动作路由(2.6.1 内测)

先 TryRegister,再调用 TryRegisterActions 追加动作(每次调用替换该插件此前的全部动作;每插件 ≤16)。动作经控制面 POST /control/adapted/<id>/<action> 调用,只有服务器打开 beta_adapted_plugin_actions 且调用方 Key 显式获得 /control/adapted/ 授权时才可达(见 Beta-Features)。

为兼容旧版 SLDataAPI(没有 TryRegisterActions),把调用隔离在单独的方法里并标 NoInlining,捕获 MissingMethodException / TypeLoadException 后退回只读:

using System.Runtime.CompilerServices;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SLDataAPI.Integrations;

private static int _bumps;

public override void Enable()
{
    // ……先 TryRegister(见上)……
    try { RegisterActions(); }
    catch (MissingMethodException) { Logger.Info("SLDataAPI 版本过旧,仅只读"); }
    catch (TypeLoadException)      { Logger.Info("SLDataAPI 版本过旧,仅只读"); }
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static void RegisterActions()
{
    bool ok = PluginEndpointRegistry.TryRegisterActions("dntof.sample_adapted", new List<AdaptedAction>
    {
        new AdaptedAction
        {
            Path = "echo",                       // ^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$
            Description = "原样返回请求体与调用方信息",
            Handler = req => JsonConvert.SerializeObject(new
            {
                echo = JToken.Parse(req.BodyJson),   // 空请求体时为 "{}"
                actor = req.Actor,                   // 调用方 API Key 的 id
                transport = req.Transport,           // "http" 或 "ws"
            }),
        },
        new AdaptedAction
        {
            Path = "bump",
            Description = "计数器 +by(1..100)",
            Handler = req =>
            {
                int by = 1;
                try
                {
                    var body = JObject.Parse(req.BodyJson);
                    if (body["by"] != null) by = (int)body["by"]!;
                }
                catch (Exception) { throw new AdaptedActionException("body must be {\"by\": <int>}"); }
                if (by < 1 || by > 100)
                    throw new AdaptedActionException("by must be 1..100");   // → 400;可传第二个参数指定 4xx
                return JsonConvert.SerializeObject(new { bumps = Interlocked.Add(ref _bumps, by) });
            },
        },
    }, out string error);

    if (!ok) Logger.Warn($"action register failed: {error}");
}

public override void Disable() => PluginEndpointRegistry.Unregister("dntof.sample_adapted");

要点:

  • 处理器在 Unity 主线程执行,超时 3s;不要做阻塞 IO 或长计算。
  • 请求体(BodyJson)与返回值各 ≤64KB;返回 JSON 字符串,返回 null 表示无返回体;非 JSON 文本会被当作字符串。
  • 要以特定状态码拒绝时抛 AdaptedActionException(message, statusCode)(400–499,默认 400);其他异常一律 500 action handler error,堆栈不会回给客户端。
  • 每次调用都会写入 control_log;插件可以用 req.Actor 做自己的二次授权或记录。
  • 内置包装 id(dntof.sl_player、dntof.omega_warhead)不能追加动作。
  • 适配插件可能先于 SLDataAPI 启用;动作是否对外由 SLDataAPI 运行时判断开关,注册时机不受影响。

SSS 游戏内 UI

模式见 Services/ReportService.cs(UserSettings.ServerSpecific):

控件 用途
SSGroupHeader 分组标题
SSDropdownSetting 下拉
SSPlaintextSetting 文本
SSButton + holdTimeSeconds 长按提交

流程:DefinedSettings → SendToAll();ServerOnSettingValueReceived 在主线程按 SettingId 处理。
注意:DefinedSettings 全局单例,与其他插件可能冲突。


本地测试

先本地执行 sldataapi apikey create <id> admin,再:

curl -s -X POST "http://127.0.0.1:8081/control/broadcast" \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{"message":"ping","duration_seconds":5}'

curl -s "http://127.0.0.1:8081/get_sl_data" \
  -H "Authorization: Bearer <verify_token>"

DevKit 冒烟:

.\scripts\Test-ControlEndpoints.ps1 -BaseUrl http://127.0.0.1:8081 `
  -VerifyToken "<verify_token>" -ApiKey "<admin_key>"

仅对自有服务器;勿把真实密钥写进脚本。

dotnet test tests/SLDataAPI.Auth.Tests
dotnet test tests/SLDataAPI.Update.Tests
dotnet test tests/SLDataAPI.WebDav.Tests

WS:握手带 Bearer。见 WS-Control-Protocol。


文档分工

内容 页面
现行 HTTP / 控制 HTTP-API
内测功能(2.6.1) Beta-Features
安全 / 配置 / 构建 Security-Model · Configuration · Building
历史 2.5 对照 Old-HTTP-API

发布

见 Building。正式 Release 请用带 key.snk 的本机构建并核对公钥令牌后再上传附件。DevKit zip 与 DLL 一并挂到对应 tag。

Clone this wiki locally