- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我是 PC 游戏的速通玩家(喜欢以尽可能快的方式完成游戏的人),我想在玩游戏时记录我的输入,以便稍后自动重播。所以我创建了一个小 C# 程序来做到这一点:基本上,它会启动一个计时器,每次我按下/松开一个键时,它都会保存操作(keyup/keydown)、键以及我这样做的毫秒数。然后,当我想再次播放它时,它会启动一个计时器,当它达到发生击键的毫秒时,它会重现它。
它有效!嗯...事实上,它几乎可以工作:键复制得很好,但有时,它们有点不同,导致我之前成功的意外死亡。
这是显示问题的视频:
https://www.youtube.com/watch?v=4RPkcx68hpw&feature=youtu.be
上面的视频是重制键,下面的视频是原版播放。一切似乎都相似,直到第 3 个房间,原始戏剧击中“蜘蛛”并使其返回,而复制的键不接触它,因此它干扰了其余的进程。当然,这部分游戏是 100% 确定的,因此相同的输入会导致相同的结果。通过在我的视频编辑器中逐帧推进视频,当角色爬上第一个 crate 时,我清楚地看到了 2 帧的差距,并且这个差距继续扩大。
这是我的(大量评论)代码:
KeysSaver.cs,保存我输入的类
class KeysSaver
{
public static IntPtr KEYUP = (IntPtr)0x0101; // Code of the "key up" signal
public static IntPtr KEYDOWN = (IntPtr)0x0100; // Code of the "key down" signal
private Stopwatch watch; // Timer used to trace at which millisecond each key have been pressed
private Dictionary<long, Dictionary<Keys, IntPtr>> savedKeys; // Recorded keys activity, indexed by the millisecond the have been pressed. The activity is indexed by the concerned key ("Keys" type) and is associated with the activity code (0x0101 for "key up", 0x0100 for "key down").
private IntPtr hookId; // Hook used to listen to the keyboard
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam); // Imported type : LowLevelKeyboardProc. Now we can use this type.
/*
* Constructor
*/
public KeysSaver()
{
this.savedKeys = new Dictionary<long, Dictionary<Keys, IntPtr>>();
this.watch = new Stopwatch();
}
/*
* method Start()
* Description : starts to save the keyboard inputs.
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms644990%28v=vs.85%29.aspx
*/
public void Start()
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule) // Get the actual thread
{
// Installs a hook to the keyboard (the "13" params means "keyboard", see the link above for the codes), by saying "Hey, I want the function 'onActivity' being called at each activity. You can find this function in the actual thread (GetModuleHandle(curModule.ModuleName)), and you listen to the keyboard activity of ALL the treads (code : 0)
this.hookId = SetWindowsHookEx(13, onActivity, GetModuleHandle(curModule.ModuleName), 0);
}
this.watch.Start(); // Starts the timer
}
/*
* method Stop()
* Description : stops to save the keyboard inputs.
* Returns : the recorded keys activity since Start().
*/
public Dictionary<long, Dictionary<Keys, IntPtr>> Stop()
{
this.watch.Stop(); // Stops the timer
UnhookWindowsHookEx(this.hookId); //Uninstalls the hook of the keyboard (the one we installed in Start())
return this.savedKeys;
}
/*
* method onActivity()
* Description : function called each time there is a keyboard activity (key up of key down). Saves the detected activity and the time at the moment it have been done.
* @nCode : Validity code. If >= 0, we can use the information, otherwise we have to let it.
* @wParam : Activity that have been detected (keyup or keydown). Must be compared to KeysSaver.KEYUP and KeysSaver.KEYDOWN to see what activity it is.
* @lParam : (once read and casted) Key of the keyboard that have been triggered.
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms644985%28v=vs.85%29.aspx (for this function documentation)
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms644974%28v=vs.85%29.aspx (for CallNextHookEx documentation)
*/
private IntPtr onActivity(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0) //We check the validity of the informations. If >= 0, we can use them.
{
long time = this.watch.ElapsedMilliseconds; //Number of milliseconds elapsed since we called the Start() method
int vkCode = Marshal.ReadInt32(lParam); //We read the value associated with the pointer (?)
Keys key = (Keys)vkCode; //We convert the int to the Keys type
if (!this.savedKeys.ContainsKey(time))
{
// If no key activity have been detected for this millisecond yet, we create the entry in the savedKeys Dictionnary
this.savedKeys.Add(time, new Dictionary<Keys, IntPtr>());
}
this.savedKeys[time].Add(key, wParam); //Saves the key and the activity
}
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam); //Bubbles the informations for others applications using similar hooks
}
// Importation of native libraries
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
class KeysPlayer
{
private Dictionary<long, Dictionary<Keys, IntPtr>> keysToPlay; // Keys to play, with the timing. See KeysSaver.savedKeys for more informations.
private Dictionary<long, INPUT[]> playedKeys; // The inputs that will be played. This is a "translation" of keysToPlay, transforming Keys into Inputs.
private Stopwatch watch; // Timer used to respect the strokes timing.
private long currentFrame; // While playing, keeps the last keysToPlay frame that have been played.
/*
* Constructor
*/
public KeysPlayer(Dictionary<long, Dictionary<Keys, IntPtr>> keysToPlay)
{
this.keysToPlay = keysToPlay;
this.playedKeys = new Dictionary<long, INPUT[]>();
this.watch = new Stopwatch();
this.currentFrame = 0;
this.loadPlayedKeys(); //Load the keys that will be played.
}
/*
* method Start()
* Description : starts to play the keyboard inputs.
*/
public void Start()
{
this.currentFrame = 0; //currentFrame is 0 at the beginning.
this.watch.Reset(); //Resets the timer
this.watch.Start(); //Starts the timer (yeah, pretty obvious)
IEnumerator<long> enumerator = this.playedKeys.Keys.GetEnumerator(); //The playedKeys enumerator. Used to jump from one frame to another.
long t; //Will receive the elapsed milliseconds, to track desync.
while (enumerator.MoveNext()) //Moves the pointer of the playedKeys dictionnary to the next entry (so, to the next frame).
{
Thread.Sleep((int)(enumerator.Current - this.currentFrame - 1)); //The thread sleeps until the millisecond before the next frame. For exemple, if there is an input at the 42th millisecond, the thread will sleep to the 41st millisecond. Seems optionnal, since we have a "while" that waits, but it allows to consume less ressources. Also, in a too long "while", the processor tends to "forget" the thread for a long time, resulting in desyncs.
while (this.watch.ElapsedMilliseconds < enumerator.Current) { } //We wait until the very precise millisecond that we want
t = this.watch.ElapsedMilliseconds; //We save the actual millisecond
uint err = SendInput((UInt32)this.playedKeys[enumerator.Current].Length, this.playedKeys[enumerator.Current], Marshal.SizeOf(typeof(INPUT))); //Simulate the inputs of the actual frame
if (t != enumerator.Current) // We compare the saved time with the supposed millisecond. If they are different, we have a desync, so we log some infos to track the bug.
{
Console.WriteLine("DESYNC : " + t + "/" + enumerator.Current + " - Inputs : " + err);
}
this.currentFrame = enumerator.Current; //Updates the currentFrame to the frame we just played.
}
}
/*
* method Stop()
* Description : stops to play the keyboard inputs.
*/
public void Stop()
{
this.watch.Stop(); //Stops the timer.
}
/*
* method loadPlayedKeys()
* Description : Transforms the keysToPlay dictionnary into a sequence of inputs. Also, pre-load the inputs we need (loading takes a bit of time that could lead to desyncs).
*/
private void loadPlayedKeys()
{
foreach (KeyValuePair<long, Dictionary<Keys, IntPtr>> kvp in this.keysToPlay)
{
List<INPUT> inputs = new List<INPUT>(); //For each recorded frame, creates a list of inputs
foreach (KeyValuePair<Keys, IntPtr> kvp2 in kvp.Value)
{
inputs.Add(this.loadKey(kvp2.Key, this.intPtrToFlags(kvp2.Value))); //Load the key that will be played and adds it to the list.
}
this.playedKeys.Add(kvp.Key, inputs.ToArray());//Transforms the list into an array and adds it to the playedKeys "partition".
}
}
/*
* method intPtrToFlags()
* Description : Translate the IntPtr which references the activity (keydown/keyup) into input flags.
*/
private UInt32 intPtrToFlags(IntPtr activity)
{
if (activity == KeysSaver.KEYDOWN) //Todo : extended keys
{
return 0;
}
if (activity == KeysSaver.KEYUP)
{
return 0x0002;
}
return 0;
}
/*
* method loadKey()
* Description : Transforms the Key into a sendable input (using the above structures).
*/
private INPUT loadKey(Keys key, UInt32 flags)
{
return new INPUT
{
Type = 1, //1 = "this is a keyboad event"
Data =
{
Keyboard = new KEYBDINPUT
{
KeyCode = (UInt16)key,
Scan = 0,
Flags = flags,
Time = 0,
ExtraInfo = IntPtr.Zero
}
}
};
}
// Importation of native libraries
[DllImport("user32.dll", SetLastError = true)]
public static extern UInt32 SendInput(UInt32 numberOfInputs, INPUT[] inputs, Int32 sizeOfInputStructure);
[DllImport("kernel32.dll")]
static extern uint GetLastError();
}
}
/*
* Struct MOUSEINPUT
* Mouse internal input struct
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms646273(v=vs.85).aspx
*/
internal struct MOUSEINPUT
{
public Int32 X;
public Int32 Y;
public UInt32 MouseData;
public UInt32 Flags;
public UInt32 Time;
public IntPtr ExtraInfo;
}
/*
* Struct HARDWAREINPUT
* Hardware internal input struct
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms646269(v=vs.85).aspx
*/
internal struct HARDWAREINPUT
{
public UInt32 Msg;
public UInt16 ParamL;
public UInt16 ParamH;
}
/*
* Struct KEYBDINPUT
* Keyboard internal input struct (Yes, actually only this one is used, but we need the 2 others to properly send inputs)
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms646271(v=vs.85).aspx
*/
internal struct KEYBDINPUT
{
public UInt16 KeyCode; //The keycode of the triggered key. See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
public UInt16 Scan; //Unicode character in some keys (when flags are saying "hey, this is unicode"). Ununsed in our case.
public UInt32 Flags; //Type of action (keyup or keydown). Specifies too if the key is a "special" key.
public UInt32 Time; //Timestamp of the event. Ununsed in our case.
public IntPtr ExtraInfo; //Extra information (yeah, it wasn't that hard to guess). Ununsed in our case.
}
/*
* Struct MOUSEKEYBDHARDWAREINPUT
* Union struct for key sending
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms646270%28v=vs.85%29.aspx
*/
[StructLayout(LayoutKind.Explicit)]
internal struct MOUSEKEYBDHARDWAREINPUT
{
[FieldOffset(0)]
public MOUSEINPUT Mouse;
[FieldOffset(0)]
public KEYBDINPUT Keyboard;
[FieldOffset(0)]
public HARDWAREINPUT Hardware;
}
/*
* Struct INPUT
* Input internal struct for key sending
* See : https://msdn.microsoft.com/en-us/library/windows/desktop/ms646270%28v=vs.85%29.aspx
*/
internal struct INPUT
{
public UInt32 Type; //Type of the input (0 = Mouse, 1 = Keyboard, 2 = Hardware)
public MOUSEKEYBDHARDWAREINPUT Data; //The union of "Mouse/Keyboard/Hardware". Only one is read, depending of the type.
}
public partial class Taslagrad : Form
{
private KeysSaver k;
private KeysPlayer p;
//Initialisation
public Taslagrad()
{
InitializeComponent();
this.k = new KeysSaver();
}
/*
* method launchRecording()
* Description : Starts to record the keys. Called when the "record" button is triggered.
*/
private void launchRecording(object sender, EventArgs e)
{
this.k.Start(); //Starts to save the keys
startButton.Text = "Stop"; //Updates the button
startButton.Click -= launchRecording;
startButton.Click += stopRecording;
}
/*
* method stopRecording()
* Description : Stops to record the keys and logs the recorded keys in the console. Called when the "record" button is triggered.
*/
private void stopRecording(object sender, EventArgs e)
{
startButton.Text = "Record";//Updates the button
startButton.Click += launchRecording;
startButton.Click -= stopRecording;
Dictionary<long, Dictionary<Keys, IntPtr>> keys = this.k.Stop(); //Gets the recorded keys
foreach (KeyValuePair<long, Dictionary<Keys, IntPtr>> kvp in keys)
{
foreach (KeyValuePair<Keys, IntPtr> kvp2 in kvp.Value)
{
//Displays the recorded keys in the console
if (kvp2.Value == KeysSaver.KEYDOWN)
{
Console.WriteLine(kvp.Key + " : (down)" + kvp2.Key);
}
if (kvp2.Value == KeysSaver.KEYUP)
{
Console.WriteLine(kvp.Key + " : (up)" + kvp2.Key);
}
}
}
this.p = new KeysPlayer(keys); //Creates a new player and gives it the recorded keys.
}
/*
* method launchPlaying()
* Description : Starts to play the keys. Called when the "play" button is triggered.
*/
private void launchPlaying(object sender, EventArgs e)
{
this.p.Start(); //Starts to play the keys.
}
}
最佳答案
碰巧欣赏MetalFoxDoS的工作。乍一看,它的边缘有点粗糙,但工作的要点就在那里。
诚实的答案是肯定的,如果用户提供输入出现延迟,他的精度有限并且运行的 CPU % 比 NO-OP 所需的要多一些。在采用异步方法的同时解决这个问题给了我微秒精度 - 远远超出人类所能提供的准确性。
我继续加强这项工作,试图进一步完善它,因为我意识到需要根据按键事件之间的延迟创建“框架”。我还纠正了过去几年差距所需的一些错误/更改。
https://github.com/houseofcat/WinKeyRecorder
关于c# - 在 C# : synchronisation issues 中记住和重放击键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31333240/
我在 php 方面遇到了一个小问题,我发现很难用语言来解释。我有一个包含键值的关联数组。我想制作一个函数(或者如果已经有一个函数),它将一个数组作为输入并删除重复项,但两种方式都是如此。 例如: 在我
我有一个在系统托盘中运行的应用程序,是否可以允许用户通过 C# 中的 Windows 键 + 键 恢复该应用程序? 谢谢 最佳答案 是的,使用 Windows API。我认为 Windows 键与 C
我正在使用 Waterline通过 Sails 查询 MySQL 数据库。我找到了 2 种方法。 不知道哪个更好? 顺便问一下,如何处理这两种情况的错误? 1. Model.findOne().whe
我正在尝试测试是否按下了 Alt 键。 我的支票类似于: private void ProcessCmdKey(Keys keyData) { if (keyData == Keys.Alt) {
我正在使用 Selenium WebDriver 和 Ruby 进行自动化测试。我需要点击一个按钮。我无法通过 id 或 css 或 xpath 获取按钮元素,因为按钮是透明的。我想使用 Tab 和
我是 IntelliJ 的新手,我看到一个启动提示说,“任何工具窗口中的 ⎋ 键都会将焦点移动到编辑器。”但是,我不知道⎋键是什么。我一直在编程很长时间。我的键盘上可能有一个我多年来一直错过的键吗?
我使用 OMDB API 创建了一个电影搜索页面。我遇到的问题是,如果我搜索一部包含多个单词的电影,此 API 会出错,因为 API 的 URL 必须在 URL 中的每个单词之间有 + 键。所以我想知
我已经用 Elasticsearch 玩了大约一天了,所以我非常陌生。我正在尝试 POST/import 一个简单的文件: { "compression" : "none", "com
enter image description here 在此示例中,要记录带有“title”和“director”键的属性值,使用 obj[key]。因为我们已经处于对象的执行上下文中:在本例中是电
我是新开类。 我使用新的电子邮件 ID 和密码在 openshift 上创建了一个项目。让我们称之为 firstApp 。我做了 rhc 设置和我的 ssh key 与我的项目相关联。 我的 frie
当我使用 Jackson 反序列化 json 字符串时,我通常不想创建所有 bean 类的属性,而且我只需要一些 json 字符串的字段,其他字段我不需要。所以我经常只在我需要的 java 类 bea
我想编写一个带有 keys/keys* 的规范,但能够内联值规范,但不支持 by design ,我明白了其背后的原因。然而,有时,本地图存在特定上下文时,您确实希望(或者只是通过遗留或第三方)键和值
my %fruit_colors = ("apple", "red", "banana", "yellow"); my @fruits = keys %fruit_colors; my @colors
我正在使用 vb.net 2008 和 DataGridView。我正在寻找允许我将 enter 键移动到右侧的下一列而不是在保持在同一列时向下移动一行的代码。 最佳答案 如果您正在确认编辑,只需移动
我刚刚开始学习编码,我遇到了这个我无法理解的问题。 “我们将添加的第二个函数称为搜索,它将以名字作为参数。它将尝试将收到的名字与我们 friend 联系人列表中的任何名字相匹配。如果它找到匹配项,就会
我已经在 Python 中运行了下面的代码,以从文本文件中生成单词列表及其计数。我该如何从“Frequency_list”变量中过滤掉计数为 1 的单词? 另外,如何将底部的打印语句循环导出到CSV
我正在尝试 XSLT 中的查找表示例,但无法使其正常工作
是否可以在 Javascript/Typescript 中编写一个将参数名称/键作为字符串返回的函数? function foo(arg) {...} let user = new User(); f
我正在尝试创建一个带有键/值的对象,但是当我看到该对象时,键没有正确填充.. 我希望键是 - 0,1,2,3 但它显示“索引”作为键。 > categories = ["09/07/2016 00:0
将 Android Studio 从 1.5 升级到 2.0 后,模拟器(现在版本为 25.1.1,我在其上配置了模拟硬件键盘)不再将 [Esc] 键识别为等同于 [Back] 按钮。 如何恢复这个有
我是一名优秀的程序员,十分优秀!