Skip to content

About

This project aims to add more features and fix bugs for OpenInputLagPatch. And a new frame interpolation function that keeps the original 60 Hz logic untouched and inserts interpolated render frames for smoother motion is work-in-progress.

Resources

Stars

5 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

简体中文 | English | 日本語

OpenInputLagPatch

一个用于替代Vpatch 的补丁,目前仍处于高度开发中,用于修复东方系列作品的限帧与输入延迟问题。 注意:该fork的新增功能大量依赖于Vibe Coding,因此可能有更多代码史山和BUG,欢迎在issue提出改进意见和反馈BUG

联系我们 Discord 点击加入zmcx的Discord群组

QQ群群号:1128317778

使用

  1. 以 x86/Release 编译
  2. 把 openinputlagpatch.dll 和 oilp_loader.exe 复制到游戏目录
  3. (th09.5 及以下) 安装 dgVoodoo2(推荐,flip 模型呈现延迟最低)或者 d3d8to9——(ENB converter 无法生效!)
  4. 运行 oilp_loader.exe,或使用最新版 thprac(在对应作品的启动页面勾选 "使用OpenInputLagPatch(如果可用)")

兼容性

并非所有作品都受支持,支持范围仍在持续扩充中。

作品 支持 Replay 速度控制
东方红魔乡 ✅ ✅
东方妖妖梦 ✅ ✅
东方永夜抄 ✅ ✅
东方花映塚 ✅ ✅
东方文花帖 ✅ ✅
东方风神录 ✅ ✅
黄昏酒场 ❌ ❔
东方地灵殿 ✅ ✅
东方星莲船 ✅ ✅
东方文花帖DS ✅ ✅
妖精大战争 ✅ ✅
东方神灵庙 ✅ ✅
东方辉针城 ✅ ✅
弹幕天邪鬼 ✅ ✅
东方绀珠传 ✅ ✅
东方天空璋 ✅ ✅
秘封噩梦日记 ✅ ✅
东方鬼形兽 ✅ ✅
东方虹龙洞 ✅ ✅
弹幕狂们的黑市 ✅ N/A
东方兽王园 ✅ N/A
东方锦上京 ✅ ✅
弹幕风 v0.12m ❌ ❔
弹幕风 ph3 ❌ ❔

配置

OpenInputLagPatch 通过游戏可执行文件同目录下的 openinputlagpatch.ini 进行配置。没有该文件游戏也能正常运行,随时可以手动创建一份。下面是一份示例配置(内容与随包提供的那份一致,注释为中英双语):

; OpenInputLagPatch 参考配置([Option] 与 [Window] 两节),注释为中英双语,键名与取值保持不变。
;
; OpenInputLagPatch reference config ([Option] and [Window]), with bilingual comments. The keys
; and their values are unchanged.

[Option]

; 目标帧率。
; 超过 60 不会让画面更顺滑,只会让游戏跑得更快。
;
; Target framerate of the game.
; Going above 60 doesn't make the game any smoother, it just makes it faster.
;
; 默认 / Default: 60
GameFPS = 60

; 补帧(动态平滑):在游戏自己的帧之间多画几帧,游戏保持自己的帧率,但呈现得更频繁。
;
; 多出来的那些帧是「外推」出来的:把每个要动的物体按「位置 += 速度 × 这一帧的小数部分」前移,
; 画出来之后再立刻还原,所以游戏自己的逻辑和随机数完全不受影响。这里没有保存上一帧、也没有做
; 反向插值——是单向的向前外推(extrapolation),不是常见的双向插值。
;
; 目前对东方红魔乡(th06)、东方妖妖梦(th07)、东方永夜抄(th08)、东方风神录(th10)、东方地灵殿(th11)、东方星莲船(th12)、东方神灵庙(th13)、东方辉针城(th14)、东方绀珠传(th15)、
; 东方天空璋(th16)、东方鬼形兽(th17)、东方虹龙洞(th18)、东方兽王园(th19)与东方锦上京(th20)生效:
; 子弹、自机本体(含子机/option 与自机弹)、直线激光会被外推;敌人本体不做,曲线激光不做。
; ShowOverlay 勾了「帧率」这一项时,会显示实际呈现率(每秒呈现多少次)。
;
; 取值:0 关闭;1 补到显示屏刷新率(144hz 显示器就呈现 144fps,60fps 逻辑则是每帧 2/3 次交替);-1 取显示屏刷新率里游戏帧率的最大整数倍(间隔最均匀);
;       N 每游戏帧呈现 N 次;*R 按固定呈现率(例如 *144,小数部分会在帧与帧之间累计)。
;
; Frame interpolation (motion smoothing): draws extra frames between the game's own ones, so the
; game stays at its own frame rate but is presented more often.
;
; The extra frames are made by *extrapolating*: every object that is moved forward is put where it
; will be a fraction of a frame from now (its position plus its velocity times that fraction),
; drawn, and put back again, so the game's own logic and its random numbers never see it. No
; previous frame is kept and nothing is interpolated backwards - this is forward extrapolation.
;
; Touhou 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 and 20 for now: their bullets, the
; player itself (with its options or orbs and its shots) and the straight lasers are extrapolated;
; the enemies aren't interpolated and the curve lasers are left alone. When ShowOverlay asks for the
; rate readout, the rate the frames are actually presented at is shown as well.
;
; 0: off, 1: present at the display's own refresh rate (a 144hz display shows 144fps, which for
; 60fps logic is 2 and 3 presentations a frame alternating), -1: pick the largest whole multiple of
; the game's frame rate the display can show (evenly spaced presentations),
; N: present N times per game frame, *R: present at R frames per second (e.g. *144)
;
; 检测到 Ju.N.Owen(th19 的非官方联机工具,游戏目录下有 modules\th19_junowen.dll)时,补帧会被强制关闭。
; Ju.N.Owen(th19 の非公式対戦ツール。ゲームのフォルダに modules\th19_junowen.dll がある場合)を検出すると、補間は強制的に無効になります。
; When Ju.N.Owen (th19's unofficial netplay tool, modules\th19_junowen.dll in the game's directory) is
; installed, the interpolation is always turned off.
;
; 默认 / Default: 1
Interpolation = 1

; 是否允许控制 replay 回放速度(会覆盖游戏自带的回放速度控制,如果有的话)。
;
; Allow controlling the speed of replay playback.
; This will override the in-game replay speed control if it exists.
;
; 默认 / Default: 1
ReplaySpeedControl = 1

; 回放快进时的目标帧率(回放中按住 LCTRL 或射击键)。
;
; Target framerate for replay skipping (hold LCTRL or attack key during replay playback).
;
; 默认 / Default: 240
ReplaySkipFPS = 240

; 回放慢放时的目标帧率(回放中按住 LSHIFT)。
;
; Target framerate for replay slowdown (hold LSHIFT during replay playback).
;
; 默认 / Default: 30
ReplaySlowFPS = 30

; 距屏幕刷新多少毫秒时开始跑游戏逻辑。越小越好,但太小会让游戏来不及跑完,反而增加输入延迟。
;
; Amount of time in milliseconds before screen refresh to try running the game logic.
; Lower is better, but it comes with a higher risk of the game not being able to run in time and
; causing more input lag.
;
; 默认 / Default: 2
BltPrepareTime = 2

; 帧间等待方式:0 纯自旋等待(非常准,但很吃 CPU);1 vpatch 的做法(视机器可能有跳帧风险,但 CPU 占用低得多)。
;
; Chooses the method used for waiting between frames.
; 0: Basic spinwait (very accurate, but uses lots of CPU), 1: vpatch (slightly higher risk of
; skipping a frame depending on PC, but uses way less CPU).
;
; 默认 / Default: 1
Sleep = 1

; 用 Direct3D9Ex 取代 Direct3D9,可以再减少 0~2 帧输入延迟。
; th09.5 及以下如果没有用 d3d8to9 则无效。
; (dgVoodoo2 也不走 D3D9,但它用 flip 模型交换链呈现,对输入延迟至少同样好。)
;
; Use Direct3D9Ex instead of Direct3D9 to allow reducing input lag by 0 to 2 frames.
; Has no effect on Touhou 9.5 and below if d3d8to9 isn't being used.
; (dgVoodoo2 doesn't go through D3D9 either, but it presents with a flip model swapchain, which is
; at least as good for input lag.)
;
; 默认 / Default: 1
D3D9Ex = 1

; 游戏使用的刷新率:
; 0 最高刷新率;1 固定 60hz;2 60 的最大整数倍(60/120/180/240hz 等)。
;
; Chooses the refresh rate used by the game.
; 0: Maximum refresh rate, 1: 60hz, 2: Largest multiple of 60 (60hz, 120hz, 180hz, 240hz, etc.).
;
; 默认 / Default: 2
FullscreenRefreshRate = 2

; 画补丁自己的小 overlay,两行读数可以分别开关(它们的和就是这个值):
;   0  都不显示(等于完全没有 overlay)
;   1  「帧率」:实际呈现率(每秒呈现多少次)。只有补帧开着时才有意义,所以补帧关闭时这一项不显示
;      (单独显示时它就贴在左下角)
;   2  「延迟」:左下角那行渲染耗时(`0.65/2.00ms` = 游戏自己这一帧花了多久 / BltPrepareTime 的设定值,调 BltPrepareTime 就看它)
;   3  两个都显示
;
; Draws the patch's own little overlay; its two readouts are switched on separately (this value is
; their sum):
;   0  neither (no overlay at all)
;   1  the rate readout: how many frames a second are actually presented. It only means something
;      while the interpolation is on, so it isn't shown with the interpolation off (on its own it
;      sits at the left edge)
;   2  the render time line: "0.65/2.00ms", how long the game's own part of the frame took and what
;      BltPrepareTime is set to (the line that setting is tuned with)
;   3  both
;
; 默认 / Default: 1
ShowOverlay = 1

; 打开控制台窗口,方便调试。
;
; Opens a console for debugging purposes.
;
; 默认 / Default: 0
DebugConsole = 0

; 启动时弹一个消息框,方便挂调试器。
;
; Opens a message box on boot to allow attaching a debugger.
;
; 默认 / Default: 0
DebugWait = 0

; 修掉红魔乡/妖妖梦里「窗口不在前台时 DirectInput 拒绝给状态,游戏却只把『设备丢失』当失败,
; 于是继续读一份从没被填过的缓冲区」造成的乱按键、按键卡住(相当于 vpatch 的 FixInputGlitching;
; vpatch 是在它的 AlwaysBlt 里一起做的)。
;
; 永夜抄与妖妖梦那种「自机读的是上一帧输入副本」的 1 帧输入延迟**不在这个选项里**:那两处是
; vpatch 无条件生效的代码,oilp 现在也一样无条件生效,与本选项无关。
;
; Fixes the keyboard state EoSD and PCB read while their window is not in the foreground: the
; device refuses to hand the state over, but the game only treats "device lost" as a failure and
; goes on reading a buffer that was never filled in - keys show up as pressed out of nowhere, and
; they can stay stuck (vpatch does this as part of its AlwaysBlt).
;
; The input-timing fix for PCB and IN - the player reading a copy of the input that the game only
; advances later - is NOT this option: vpatch applies it unconditionally, and so does oilp now.
;
; 默认 / Default: 1
FixInputGlitching = 1

; 是否接管游戏自己测量的帧率(相当于 vpatch 的 CalcFPS,默认同为 1)。
;
; 红魔乡 / 妖妖梦 / 永夜抄 每一帧给一个计数器 +1,每半秒用这段时间除一次,得到自己的帧率;
; 这个读数决定它把那一秒里多少算作"丢掉的帧率"(结果画面显示的处理落ち率,以及录像里记的
; 那个值,都是从这里算出来的)。只要有一个半秒窗口落在读盘 / 切换场景的卡顿上,这一局的
; 处理落ち率就会被那一次拖住(所以短关卡、过场待得久都会看到几个百分点)。
;
; 打开后本补丁不再让游戏自己算:改用限帧器实测的帧率交给它(和 vpatch 的 CalcFPS=1 同一个
; 思路),处理落ち率因此保持 0.00%、帧率文字也不再忽高忽低。关掉则完全按原版行为。
;
; Whether the patch should replace the frame rate the game measures for itself (vpatch's CalcFPS,
; which also defaults to 1).
;
; EoSD / PCB / IN add one to a counter every frame, divide it by the elapsed half second, and use
; that reading to decide how much of the half second they lost: it is where the processing rate on
; the results screen and the value a replay records come from. One window landing on a load or a
; scene change is enough to drag a whole run's reading with it, which is why short stages and time
; spent between stages show up as a few percent.
;
; On, the patch measures the frame rate itself and hands the game that instead (the same idea as
; vpatch's CalcFPS=1), so the processing rate stays 0.00% and the frame rate text stops wandering.
; Off leaves the game to measure for itself.
;
; 默认 / Default: 1
CalcFPS = 1

; 游戏窗口失去焦点时继续渲染并呈现,切出去画面不会冻住(相当于 vpatch 的 AlwaysBlt)。
; 只对 th06 与 th07 实现。
;
; Keeps rendering while the game window isn't active, so the picture doesn't freeze after switching
; away from it (same idea as vpatch's AlwaysBlt). Only implemented for th06 and th07.
;
; 红魔乡/妖妖梦里失焦时整个 Render 会直接返回,所以只有这一项打开、FixInputGlitching 关掉,
; 才会真的跑进上面那个「读未初始化缓冲区」的坑里。
;
; In EoSD and PCB the whole render bails out while unfocused, so it takes this option with
; FixInputGlitching off to actually run into the uninitialised-buffer bug described above.
;
; 默认 / Default: 1
AlwaysBlt = 1

; 覆盖游戏检测结果,强制按指定作品处理。
; 取值见 games.h。
; 改这个大概率会直接弄坏一切,只有在 100% 确定该作品偏移完全一样时才用!
;
; Overrides game detection to a specific game.
; See games.h for possible values.
; CHANGING THIS WILL PROBABLY BREAK EVERYTHING. Only use this if you are 100% sure that the game's
; offsets are exactly the same!
;
; 默认 / Default: -1
GameOverride = -1

[Window]

; 启动时询问这次是否用全屏运行,这样不必去改游戏自己的全屏设置。
; 答「否」用窗口模式,答「是」用全屏。
; 只有东方 6~9.5 会被询问,之后的几作自带这个选项。
;
; Asks whether the game should run in fullscreen mode on boot, so the game's own fullscreen
; setting doesn't have to be changed. Answering no makes the game run in window mode, answering yes
; makes it run in fullscreen. Only Touhou 6 to 9.5 are asked, because the later games have their
; own option for this.
;
; 默认 / Default: 0
AskWindowMode = 0

; 是否套用下面这些窗口设置。它们只在游戏处于窗口模式时生效。
;
; Whether the settings below are applied to the game's window.
; They only take effect while the game runs in window mode.
;
; 默认 / Default: 0
enabled = 0

; 窗口左上角坐标(像素)。2147483648(0x80000000)表示让 Windows 自己决定位置。
;
; Position of the top left corner of the window, in pixels.
; 2147483648 (0x80000000) lets Windows pick the position.
;
; 默认 / Default: 2147483648
X = 2147483648
Y = 2147483648

; 窗口大小(像素)。0 表示保持游戏原本的大小。
;
; Size of the window, in pixels. 0 keeps the size the game would normally use.
;
; 默认 / Default: 640
Width = 640

; 默认 / Default: 480
Height = 480

; 窗口是否保留标题栏。
;
; Whether the window keeps its title bar.
;
; 默认 / Default: 1
TitleBar = 1

; 窗口是否始终保持在其他窗口之上(开启时会盖住任务栏)。
;
; Whether the window is always on top of other windows.
; This can cover the taskbar while enabled.
;
; 默认 / Default: 0
AlwaysOnTop = 0

; 是否在游戏窗口处于前台时让任务栏「让开」,而不像 AlwaysOnTop 那样一直压在其它窗口之上。
; 0 关闭;1 告诉 shell 这个窗口是全屏窗口,让任务栏退开(窗口本身仍是普通窗口);2 只在处于前台时把窗口放进置顶层。
; 两种做法在窗口不是前台时都不会有任何效果(非置顶窗口本来就位于任务栏之下);切到别的窗口后,游戏会正常让位。
;
; Whether the taskbar is kept out of the way while the game's window is the active window, without
; holding the window above every other one the way AlwaysOnTop does.
; 0: off, 1: tell the shell the window is fullscreen so it steps the taskbar aside,
; 2: hold the window on top only while it is the active window.
; The window is a normal one either way: switch to something else and it covers the game again.
;
; 默认 / Default: 0
CoverTaskbar = 0

技术细节

(仅红魔乡与妖妖梦) 游戏主循环被改为在游戏更新逻辑之后再执行绘制逻辑(与原本的顺序相反),可减少约 1 帧输入延迟。

Direct3DCreate9 被钩住并改用 Direct3DCreate9Ex,从而可以使用 IDirect3DDevice9Ex::SetMaximumFrameLatency,再减少 0~2 帧输入延迟。这个优化只对老的 blt 模型呈现有效(原生 D3D9,或者用 d3d8to9 的场合):装了 dgVoodoo2 之后游戏是通过 DXGI 的 flip 模型交换链呈现的,队列本身已经压到最短,这个优化无事可做——dgVoodoo2 的 flip 模型也正是它在实测中延迟最低的原因。

补帧(Interpolation)把每一游戏帧呈现多次:游戏仍然按自己的帧率跑逻辑,补丁在游戏自己那次呈现之后、下一帧逻辑开始之前,把同一份状态再画几遍并呈现(60fps 的游戏在 180hz 上就是 0、半帧、一帧各呈现一次)。zun 的引擎把逻辑与绘制分开,所以重画同一份状态本身是安全的;要让额外帧里的画面确实在动,补丁在每次额外呈现之前把要动的物体按「速度 × 这一帧的几分之几」前移(和游戏自己的 位置 += 速度 同一套算法),画完立刻还原,游戏自己的逻辑永远看不到这些改动。额外呈现走的是设备自己的 Present,不经过 thprac 等工具替换掉的那个(它们按"一游戏帧一次呈现"来测输入延迟,多吃到额外呈现会让读数变成整帧的几分之一);工具在这一帧已经建好的覆盖层会被重画进每次额外呈现,但不会重跑它的 UI 与输入代码(按住方向键不会在它菜单里连跳)。目前红魔乡(th06)、妖妖梦(th07)、永夜抄(th08)与风神录(th10)、地灵殿(th11)、星莲船(th12)、神灵庙(th13)、辉针城(th14)、绀珠传(th15)、天空璋(th16)、鬼形兽(th17)、虹龙洞(th18)、兽王园(th19)与锦上京(th20)实现了补帧:子弹、自机本体(连同子机与自机弹,鬼形兽里还包括咆哮/hyper 的特效与特殊自机弹;虹龙洞的自机弹系统与 th17 不同,另外还包括能力卡在自机身上画的特效精灵,以及商店买卡界面的文字修复;兽王园是对战作品,两侧各有一套管理器,所以上面每一项都是两侧一起做的,它的自机弹也不在子弹管理器的那几条链上,和直线激光一样是按每帧量出来的位移外推的;锦上京跑的是同一套引擎但它是单人作,自机弹是自机自己池子里的 256 格(位置在格内 +0x50、精灵 id 在 +0x18),而且它的绘制位是更新期烘焙进精灵的,所以格内位置与精灵 +0x5BC 两处都写;它的文字管理器不在全局里,是在构造处抓下来的;永夜抄是更早那一代的引擎,自机/自机弹/子机的绘制位都是绘制期才从逻辑位写进精灵的,所以自机在"画它的那一句"上做、自机弹与子机改的是各自的源位置;子机的四个槽用的是运行时才挂上去的绘制回调,五队共用同一个绘制函数;自机弹、子机与直线激光的运动由脚本/跟随决定、没有速度字段可读,都是按每帧量出来的位移外推的;红魔乡与它同代,有三处这个引擎特有的地方:它的自机弹画的是精灵自己的位置(逻辑位另有一份,额外帧只动精灵那份、逻辑位一个字节都不碰),它的自机带两颗 orb(火力 8 以上才出现,就是这一作的"子机"——由聚焦状态机摆位,自机弹也从它出发),而额外帧必须把这一趟开画时的深度缓冲一起还原(游戏的计算链每帧清一次 Z;不还原的话这一趟先画的东西会被上一趟后画的 HUD/本体用深度挡住,表现为魔法阵与背景局部闪烁),摄像机也要还两份(全局的 viewMatrix/projectionMatrix 是舞台裁剪与投影用的,设备上另有一份);它的 AnmManager 还把贴图检查嵌在"精灵变了"那个分支里,所以额外帧开画前要作废整套 current* 缓存(只作废贴图会画出没有贴图的白色方块))、直线激光都会在额外帧里前移,菜单/暂停这类界面的文字也不会被额外帧多画一遍(永夜抄的字符串表本来就是"画完即清",天然不会重复)。敌人本体不做——它们的画面位置由动画脚本决定,没有能读出来的速度,与其按猜的做不如明确排除;曲线激光同样不做。

最后,游戏自带的限帧器被禁用,改用大幅简化的 vpatch 限帧器。

已知问题

  • 转场时背景淡入淡出动画可能视觉上变快,但不影响游戏实际速度。

TODO

  • 支持更多作品
  • 修 bug、加新功能
  • 把补帧扩展到其它作品;敌人本体与曲线激光目前按需求不做
  • 看看 vpatch 的 AutoBltPrepareTime 算法(不确定)
  • 可能还有我忘了的

About

This project aims to add more features and fix bugs for OpenInputLagPatch. And a new frame interpolation function that keeps the original 60 Hz logic untouched and inserts interpolated render frames for smoother motion is work-in-progress.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages