5.8 KiB
5.8 KiB
游戏插件兼容开发指南
TurboSu 通过插件系统支持任意赛车游戏的遥测数据接入。每个插件是 games/builtin/ 或 games/user/ 下的独立文件夹。
文件夹结构
games/user/my_game/
├── manifest.json # 元信息
└── parser.py # 解析逻辑
manifest.json 格式
{
"id": "my_game",
"name": "我的游戏",
"parser_type": "forza",
"description": "自定义游戏遥测",
"author": "你的名字",
"version": "1.0.0",
"default_port": 20777
}
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id |
string | 是 | 唯一标识,= 文件夹名 |
name |
string | 是 | 显示名称 |
parser_type |
string | 是 | 解析器类型标签 |
description |
string | 否 | 描述文字 |
author |
string | 否 | 作者 |
version |
string | 否 | 版本号 |
default_port |
int | 否 | 默认 UDP 端口 (默认 20777) |
parser.py 规范
必须导出 get_parser() 函数,返回的对象必须实现以下接口:
class MyParser:
def game_id(self) -> str:
"""返回与 manifest.json 一致的 game id"""
return "my_game"
def parse(self, data: bytes, addr: tuple) -> TelemetryData:
"""
解析 UDP 数据包
data: 原始字节数据
addr: (host, port) 发送方地址
返回: server.telemetry.data.TelemetryData
"""
完整示例
import struct
import time
from server.telemetry.data import TelemetryData
def get_parser():
return MyGameParser()
class MyGameParser:
def game_id(self) -> str:
return "my_game"
def parse(self, data: bytes, addr: tuple) -> TelemetryData:
td = TelemetryData(
game_id="my_game",
timestamp=time.time(),
)
td.raw = {
"raw_hex": data.hex(),
"length": len(data),
"addr": f"{addr[0]}:{addr[1]}",
}
# 解析你的游戏数据协议
# 示例: 简单的二进制格式
try:
td.speed_kmh = struct.unpack_from("<f", data, 0)[0]
td.rpm = struct.unpack_from("<f", data, 4)[0]
td.gear = struct.unpack_from("<B", data, 8)[0]
td.throttle = struct.unpack_from("<f", data, 12)[0]
td.brake = struct.unpack_from("<f", data, 16)[0]
td.steering = struct.unpack_from("<f", data, 20)[0]
except struct.error:
pass
return td
TelemetryData 完整字段
@dataclass
class TelemetryData:
game_id: str = "" # 游戏标识
timestamp: float = 0.0 # 时间戳
# 速度和转速
speed_kmh: float = 0.0 # 速度 (km/h)
speed_mph: float = 0.0 # 速度 (mph)
rpm: float = 0.0 # 发动机转速
max_rpm: float = 8000.0 # 最大转速
# 传动
gear: int = 0 # 档位 (0=N, -1=R, 1~8+)
# 踏板 (0~1)
throttle: float = 0.0 # 油门
brake: float = 0.0 # 刹车
clutch: float = 0.0 # 离合
handbrake: float = 0.0 # 手刹
# 转向 (-1 ~ 1)
steering: float = 0.0
# 计时
lap_time: float = 0.0 # 当前圈速 (秒)
best_lap: float = 0.0 # 最佳圈速 (秒)
last_lap: float = 0.0 # 上圈时间 (秒)
lap_number: int = 0 # 圈数
# 位置/加速度
position_x: float = 0.0
position_y: float = 0.0
position_z: float = 0.0
acceleration_x: float = 0.0
acceleration_y: float = 0.0
acceleration_z: float = 0.0
# 车辆状态
engine_temp: float = 0.0 # 发动机温度
oil_temp: float = 0.0 # 油温
fuel: float = 0.0 # 燃油量
# 性能
boost: float = 0.0 # 涡轮增压
horsepower: float = 0.0 # 马力
torque: float = 0.0 # 扭矩
# 车辆信息
car_name: str = "" # 车辆名称
car_class: str = "" # 车辆等级
# 原始数据 (自动转发给前端调试)
raw: dict = {}
数据协议解析技巧
二进制格式 (Forza / ACC / F1 / iRacing)
使用 struct 模块解析固定长度的二进制包:
import struct
# 小端浮点
speed = struct.unpack_from("<f", data, offset)[0]
# 小端无符号字节
gear = struct.unpack_from("<B", data, offset)[0]
# 小端无符号短整型
rpm = struct.unpack_from("<H", data, offset)[0]
文本格式 (Assetto Corsa)
使用字符串分割解析:
text = data.decode("utf-8", errors="replace").rstrip("\r\n")
parts = text.split("\t")
speed = float(parts[0])
rpm = float(parts[1])
gear = int(float(parts[2]))
未知协议
如果游戏使用未知/自定义协议:
- 启用 TurboSu 的数据测试页面查看原始 hex 数据
- 分析数据模式 (浮点数/整数/字符串)
- 对照游戏文档或社区逆向结果
测试方法
- 将插件文件夹放入
games/user/ - 启动 TurboSu → 侧边栏选择你的游戏
- 打开数据测试页面查看实时数据
- 调整解析逻辑直到字段正确
打包分发
将整个文件夹压缩为 zip,改后缀为 .tsp,即可在 TurboSu 设置页面导入。
# 进入插件目录
cd games/user/my_game
# 打包 (不含父路径)
zip -r my_game.tsp manifest.json parser.py
常见游戏数据格式参考
| 游戏 | 格式 | 数据包大小 | 参考来源 |
|---|---|---|---|
| Forza Horizon 4/5 | 二进制 LE | ~324 bytes | Forza Data Out 文档 |
| Forza Motorsport | 二进制 LE | ~324 bytes | Forza Data Out 文档 |
| Assetto Corsa | 文本 (TSV) | 变长 | AC UDP 插件协议 |
| ACC | 二进制 LE | ~200 bytes | ACC Broadcasting SDK |
| F1 系列 | 二进制 LE | ~1289+ bytes | Codemasters F1 UDP Spec |
| iRacing | 二进制 LE | 变长 | iRacing IRSDK |