文档信息#

  • 项目名称: SF6 Gemini Input Control System
  • 版本: 2.0
  • 最后更新: 2026-02-22
  • 作者: 研究团队
  • 目的: 记录通过REFramework控制SF6角色输入的完整技术方案

目录#

  1. 概述
  2. 核心发现
  3. 系统架构
  4. 关键数据结构
  5. Hook技术
  6. 输入注入方法
  7. 已知问题与解决方案
  8. 最佳实践
  9. 调试技巧
  10. 未来改进方向

概述#

本文档记录了通过REFramework Lua脚本控制Street Fighter 6角色输入的完整技术方案。该系统允许:

  • 通过Python GUI或Lua脚本控制P1/P2角色
  • 注入虚拟输入(方向+按钮)
  • 可选的硬件输入合并功能
  • 支持输入序列和连招编辑

技术栈#

  • 游戏引擎: RE Engine
  • Hook框架: REFramework
  • 脚本语言: Lua 5.4
  • GUI: Python + PyQt5
  • 通信方式: 文件IPC

核心发现#

1. cPlayer对象结构#

cPlayer是游戏中管理玩家输入和状态的核心对象。

访问路径:

local training_mgr = sdk.get_managed_singleton("app.training.TrainingManager")
local datas = training_mgr:call("GetPLDatas")
local data0 = datas:call("get_Item", 0)  -- P1
local cPlayer = data0:get_field("cPlayer")

关键字段:

字段名 类型 说明
pl_input_new int 新输入(当前帧)
pl_input_now int 当前输入
pl_input_old int 上一帧输入
pl_input_old1 int 上上帧输入
pl_sw_new int 新按钮状态
pl_sw_now int 当前按钮状态
pl_sw_old int 上一帧按钮状态
pl_cmd_now int 当前命令
pl_cmd_buff int 命令缓冲
input_data array 输入数据数组(历史缓冲)
act_st int 动作状态
act_st_old int 上一帧动作状态
act_dir int 动作方向
move_dir int 移动方向
free_to_move bool 是否可以移动
auto_pilot bool 是否自动驾驶
dummy_work bool 是否为假人

2. 输入位掩码#

输入使用位标志(bit flags)编码:

方向位:

INPUT = {
    NONE  = 0,
    UP    = 1,    -- 0b00000001
    DOWN  = 2,    -- 0b00000010
    LEFT  = 4,    -- 0b00000100
    RIGHT = 8,    -- 0b00001000
}

按钮位:

BUTTON = {
    NONE = 0,
    LP   = 16,    -- 0b00010000 (轻拳)
    MP   = 32,    -- 0b00100000 (中拳)
    HP   = 64,    -- 0b01000000 (重拳)
    LK   = 128,   -- 0b10000000 (轻腿)
    MK   = 256,   -- 0b100000000 (中腿)
    HK   = 512,   -- 0b1000000000 (重腿)
}

组合输入:

-- 向下+轻拳 = 2 + 16 = 18
-- 向右+中拳 = 8 + 32 = 40
-- 向下+向右+重拳 = 2 + 8 + 64 = 74

3. 可Hook的方法#

通过研究发现,cPlayer对象有多个可以hook的方法:

方法名 调用频率 适用性 说明
pl_input_sub 每帧 ✅ 推荐 输入处理子函数,最稳定
pl_input_main 每帧 ⚠️ 可用 主输入处理函数
pl_cmd_check 每帧 ⚠️ 可用 命令检查函数
update 每帧 ❌ 不推荐 太通用,可能影响其他逻辑

推荐: 使用pl_input_sub,它在输入处理流程中位置合适,不会干扰其他系统。


系统架构#

整体架构图#

┌─────────────────────────────────────────────────────────────┐
│                     Python GUI (ComboTrialGui)              │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │ 输入控制器   │  │ 动作编辑器   │  │ 试炼编辑器   │     │
│  └──────┬───────┘  └──────────────┘  └──────────────┘     │
│         │                                                    │
│         │ IPC (文件通信)                                    │
└─────────┼────────────────────────────────────────────────────┘
          │
          ▼
┌─────────────────────────────────────────────────────────────┐
│              REFramework Lua Scripts                        │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  GeminiInputBridge.lua (IPC桥接)                     │  │
│  │  - 读取command.txt                                    │  │
│  │  - 解析命令                                           │  │
│  │  - 调用Core模块                                       │  │
│  │  - 写入status.txt                                     │  │
│  └────────────────┬─────────────────────────────────────┘  │
│                   │                                          │
│                   ▼                                          │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  GeminiInputCore.lua (核心逻辑)                      │  │
│  │  - Hook管理                                           │  │
│  │  - 输入队列                                           │  │
│  │  - 字段写入                                           │  │
│  │  - 硬件输入合并                                       │  │
│  └────────────────┬─────────────────────────────────────┘  │
│                   │                                          │
│                   ▼                                          │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  GeminiInputUI.lua (可选,REFramework内置UI)        │  │
│  │  - 提供游戏内控制面板                                │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
          │
          │ SDK Hook
          ▼
┌─────────────────────────────────────────────────────────────┐
│                  Street Fighter 6 Game                      │
│  ┌──────────────────────────────────────────────────────┐  │
│  │  cPlayer Object                                       │  │
│  │  - pl_input_sub() ← Hook Point                       │  │
│  │  - pl_input_new/now/old                              │  │
│  │  - pl_sw_new/now/old                                 │  │
│  │  - input_data[]                                       │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

模块职责#

GeminiInputBridge.lua#

  • 职责: IPC通信桥接
  • 功能:
    • 监听ipc/command.txt文件
    • 解析命令字符串
    • 调用Core模块的API
    • 更新ipc/status.txt状态文件
  • 命令格式: command:param1:param2:...

GeminiInputCore.lua#

  • 职责: 核心输入注入逻辑
  • 功能:
    • 管理Hook的安装和卸载
    • 维护输入队列
    • 处理持续按住(hold)
    • 写入cPlayer字段
    • 可选的硬件输入合并
  • 关键变量:
    • hook_installed: Hook是否已安装
    • active: 输入注入是否激活
    • queue: 输入队列
    • hold_dir/hold_btn: 持续按住的方向/按钮
    • cur_dir/cur_btn: 当前输入

GeminiInputUI.lua#

  • 职责: REFramework内置UI
  • 功能:
    • 提供游戏内控制面板
    • 可视化状态显示
    • 手动测试接口

关键数据结构#

输入队列#

queue = {
    {dir = 8, btn = 16, frames = 3},  -- 向右+轻拳,持续3帧
    {dir = 0, btn = 0, frames = 2},   -- 释放,持续2帧
    {dir = 2, btn = 64, frames = 5},  -- 向下+重拳,持续5帧
}

状态对象#

state = {
    active = true,           -- 是否激活
    hook = true,            -- Hook是否安装
    hold_dir = 1,           -- 持续按住的方向
    hold_btn = 16,          -- 持续按住的按钮
    cur_dir = 8,            -- 当前方向
    cur_btn = 32,           -- 当前按钮
    qlen = 3,               -- 队列长度
    target = 0,             -- 目标玩家索引(0=P1, 1=P2)
    method = "pl_input_sub" -- Hook的方法名
}

Hook技术#

Hook时机#

REFramework提供两个Hook时机:

  1. PreHook: 在原函数执行之前

    • 用途: 读取原始硬件输入
    • 时机: 游戏还未处理输入
  2. PostHook: 在原函数执行之后

    • 用途: 写入虚拟输入
    • 时机: 游戏已处理完输入

Hook实现#

local function do_hook(method)
    sdk.hook(
        method,
        function(args)  -- PreHook
            if use_pre then
                local this = sdk.to_managed_object(args[2])
                inject_now(this, true)  -- 读取硬件输入
            end
            return sdk.PreHookResult.CALL_ORIGINAL
        end,
        function(retval)  -- PostHook
            if use_post then
                inject_now(nil, false)  -- 写入虚拟输入
            end
            return retval
        end
    )
end

Hook安装流程#

function install_hook()
    -- 1. 初始化目标对象
    if not init_target(target_index) then
        return false
    end

    -- 2. 获取类型定义
    local td = p_target:get_type_definition()
    if not td then return false end

    -- 3. 获取方法
    local method = td:get_method("pl_input_sub")
    if not method then return false end

    -- 4. 安装Hook
    local ok = do_hook(method)
    if ok then
        hook_installed = true
        return true
    end

    return false
end

输入注入方法#

方法1: Fields+Buffer (write_strategy=0)#

最稳定的方法,写入所有相关字段。

local combined = dir_bits + btn_bits

-- 写入输入字段
o:set_field("pl_input_new", combined)
o:set_field("pl_input_now", combined)
o:set_field("pl_input_old", dir_bits)
o:set_field("pl_input_old1", dir_bits)

-- 写入按钮字段
o:set_field("pl_sw_new", combined)
o:set_field("pl_sw_now", combined)
o:set_field("pl_sw_old", btn_bits)

-- 写入input_data数组
local arr = o:get_field("input_data")
if arr then
    for i = 0, span - 1 do
        arr:call("set_Item", i, combined)
    end
end

优点:

  • 最稳定,兼容性最好
  • 适用于所有场景

缺点:

  • 完全覆盖硬件输入
  • 不支持硬件输入合并

方法2: Buffer Only (write_strategy=1)#

仅写入缓冲区,适合合并模式。

local arr = o:get_field("input_data")
if arr then
    for i = 0, span - 1 do
        arr:call("set_Item", i, combined)
    end
end

优点:

  • 对其他字段影响小
  • 适合硬件输入合并

缺点:

  • 可能不够稳定
  • 某些情况下输入可能被忽略

方法3: Now Fields Only (write_strategy=2)#

仅写入当前字段,实验性方法。

o:set_field("pl_input_now", combined)
o:set_field("pl_sw_now", combined)

优点:

  • 最小化干扰

缺点:

  • 不稳定
  • 不推荐使用

硬件输入合并#

当merge_enabled=true时,系统会合并硬件输入和虚拟输入。

合并策略:

  • 按轴独立合并: 水平轴和垂直轴分别处理
  • 虚拟输入优先: 如果虚拟输入占用了某个轴,则忽略该轴的硬件输入
  • 按钮直接合并: 虚拟按钮 OR 硬件按钮
-- 提取硬件输入的各个轴
local hw_horizontal = bit.band(hw_dir, INPUT.LEFT + INPUT.RIGHT)
local hw_vertical = bit.band(hw_dir, INPUT.UP + INPUT.DOWN)

-- 检查虚拟输入是否占用了某个轴
local virt_has_horizontal = bit.band(virt_dir, INPUT.LEFT + INPUT.RIGHT) ~= 0
local virt_has_vertical = bit.band(virt_dir, INPUT.UP + INPUT.DOWN) ~= 0

-- 只合并虚拟输入未占用的轴
if not virt_has_horizontal then
    final_dir = bit.bor(final_dir, hw_horizontal)
end
if not virt_has_vertical then
    final_dir = bit.bor(final_dir, hw_vertical)
end

-- 按钮直接合并
final_btn = bit.bor(virt_btn, hw_buttons)

示例:

  • 虚拟输入: 向上 (1)
  • 硬件输入: 向右+轻拳 (8+16=24)
  • 合并结果: 向上+向右+轻拳 (1+8+16=25)

已知问题与解决方案#

问题1: 视角Bug#

现象: 使用某些write_strategy时,游戏视角会出现异常。

原因: 过度写入字段,干扰了游戏的其他系统。

解决方案:

  • 使用write_strategy=0 (Fields+Buffer)
  • 禁用merge模式
  • 确保Hook时机正确(PreHook + PostHook)

问题2: 输入延迟#

现象: 虚拟输入有明显延迟。

原因:

  • Hook时机不对
  • 队列处理不及时
  • 帧数设置过大

解决方案:

  • 使用PostHook写入
  • 减少tap的frames参数(推荐1-3帧)
  • 优化队列处理逻辑

问题3: 输入被忽略#

现象: 某些输入无效。

原因:

  • Hook未安装
  • active=false
  • 字段写入不完整

解决方案:

  • 确保调用install_hook()
  • 确保set_active(true)
  • 使用write_strategy=0

问题4: 与硬件输入冲突#

现象: 虚拟输入和硬件输入互相干扰。

原因:

  • 完全覆盖模式会屏蔽硬件输入
  • 合并模式的时序问题

解决方案:

  • 使用合并模式(merge_enabled=true)
  • 使用PreHook读取硬件输入
  • 使用PostHook写入合并后的输入

问题5: 重复Hook#

现象: 多个脚本同时hook导致冲突。

原因:

  • autorun目录有多个测试脚本
  • 没有使用全局单例模式

解决方案:

  • 使用全局单例(_G.__GeminiInputCore)
  • 清理autorun目录的测试脚本
  • 只保留必要的脚本

最佳实践#

1. 配置推荐#

稳定配置 (推荐用于生产):

write_strategy = 0        -- Fields+Buffer
merge_enabled = false     -- 禁用合并
use_pre = false          -- 禁用PreHook
use_post = true          -- 启用PostHook
input_data_span = 2      -- 缓冲区跨度

合并配置 (实验性):

write_strategy = 1        -- Buffer Only
merge_enabled = true      -- 启用合并
use_pre = true           -- 启用PreHook (读取硬件输入)
use_post = true          -- 启用PostHook (写入合并输入)
input_data_span = 2      -- 缓冲区跨度

2. 输入序列设计#

原则:

  • 每个动作后添加释放帧(0,0,2)
  • 使用合理的帧数(1-5帧)
  • 避免过长的队列

示例 - 波动拳:

-- 下 (2帧)
tap(INPUT.DOWN, 0, 2)
tap(0, 0, 1)  -- 释放

-- 下前 (2帧)
tap(INPUT.DOWN + INPUT.RIGHT, 0, 2)
tap(0, 0, 1)  -- 释放

-- 前+拳 (3帧)
tap(INPUT.RIGHT, BUTTON.LP, 3)
tap(0, 0, 2)  -- 释放

3. 错误处理#

总是使用pcall:

local ok, result = pcall(function()
    return obj:get_field("pl_input_now")
end)
if ok then
    -- 处理result
else
    -- 处理错误
end

4. 调试输出#

分级日志:

-- 关键事件
print("[INFO] Hook installed successfully")

-- 调试信息
if debug_mode then
    print(string.format("[DEBUG] cur_dir=%d cur_btn=%d", cur_dir, cur_btn))
end

-- 错误信息
print("[ERROR] Failed to initialize target")

5. 性能优化#

避免每帧操作:

-- 不好
re.on_frame(function()
    print("Frame")  -- 每帧都输出
end)

-- 好
local frame_count = 0
re.on_frame(function()
    frame_count = frame_count + 1
    if frame_count % 60 == 0 then
        print("60 frames passed")
    end
end)


调试技巧#

1. 查看cPlayer字段#

local function dump_fields(obj)
    local td = obj:get_type_definition()
    local fields = td:get_fields()
    for _, field in ipairs(fields) do
        local name = field:get_name()
        local value = obj:get_field(name)
        print(string.format("%s = %s", name, tostring(value)))
    end
end

2. 监控输入变化#

local last_input = 0
re.on_frame(function()
    local input = cPlayer:get_field("pl_input_now")
    if input ~= last_input then
        print(string.format("Input changed: %d -> %d", last_input, input))
        last_input = input
    end
end)

3. 验证Hook状态#

print(string.format("Hook installed: %s", tostring(hook_installed)))
print(string.format("Active: %s", tostring(active)))
print(string.format("Queue length: %d", #queue))
print(string.format("Current input: dir=%d btn=%d", cur_dir, cur_btn))

4. 使用InputComparison脚本#

这个脚本可以对比真实输入和虚拟输入的差异:

-- 监控所有输入相关字段
local watch_fields = {
    "pl_input_new", "pl_input_old", "pl_input_now",
    "pl_sw_new", "pl_sw_old", "pl_sw_now",
    "pl_cmd_now", "act_st", "act_dir"
}

for _, field in ipairs(watch_fields) do
    local value = cPlayer:get_field(field)
    print(string.format("%s = %d", field, value))
end

未来改进方向#

1. 更精确的时序控制#

目标: 实现帧级精确的输入控制

方案:

  • 研究游戏的帧同步机制
  • 实现基于帧计数的输入调度
  • 支持负边输入(negative edge)

2. 录制与回放#

目标: 录制玩家输入并回放

方案:

  • 监控硬件输入
  • 保存输入序列到文件
  • 实现回放功能
  • 支持循环播放

3. AI集成#

目标: 集成AI模型进行自动操作

方案:

  • 读取游戏状态(角色位置、血量等)
  • AI决策输入
  • 实时注入输入

4. 网络对战支持#

目标: 支持在线对战中的输入控制

挑战:

  • 网络延迟
  • 反外挂检测
  • 同步问题

注意: 在线对战中使用输入控制可能违反游戏条款,仅用于研究目的。

5. 跨平台支持#

目标: 支持其他RE Engine游戏

方案:

  • 抽象化cPlayer接口
  • 适配不同游戏的字段名
  • 统一的配置系统

附录#

A. 完整的输入位掩码表#

输入 十进制 十六进制 二进制
NONE 0 0x0 0b0000000000
UP 1 0x1 0b0000000001
DOWN 2 0x2 0b0000000010
LEFT 4 0x4 0b0000000100
RIGHT 8 0x8 0b0000001000
LP 16 0x10 0b0000010000
MP 32 0x20 0b0000100000
HP 64 0x40 0b0001000000
LK 128 0x80 0b0010000000
MK 256 0x100 0b0100000000
HK 512 0x200 0b1000000000

B. 常用组合输入#

组合 计算 值
下+轻拳 2+16 18
下+中拳 2+32 34
下+重拳 2+64 66
右+轻拳 8+16 24
右+中拳 8+32 40
下右+轻拳 2+8+16 26
下右+重拳 2+8+64 74

C. IPC命令参考#

命令 格式 示例 说明
tap tap:dir:btn:frames tap:8:16:3 点按输入
hold_dir hold_dir:bits hold_dir:1 持续按住方向
hold_btn hold_btn:bits hold_btn:16 持续按住按钮
clear_hold clear_hold clear_hold 清除持续输入
set_active set_active:bool set_active:true 设置激活状态
install_hook install_hook install_hook 安装Hook
set_target set_target:index set_target:0 设置目标玩家
set_write_strategy set_write_strategy:n set_write_strategy:0 设置写入策略
set_merge set_merge:bool set_merge:true 设置合并模式
set_hook_timing set_hook_timing:pre:post set_hook_timing:true:true 设置Hook时机

D. 参考资料#


版本历史#

v2.0 (2026-02-22)#

  • 添加硬件输入合并功能
  • 优化Hook时机(PreHook + PostHook)
  • 添加write_strategy配置
  • 改进调试输出
  • 修复视角bug

v1.0 (2026-01-22)#

  • 初始版本
  • 基本的输入注入功能
  • IPC通信
  • Python GUI

致谢#

感谢所有参与研究和开发的成员,以及REFramework社区的支持。


文档结束