Entwickler-Ecke
Windows API - Keyboard-Tastendrücke aufnehmen / simulieren - Zeitkritisch
Limster - Mo 05.01.09 21:43
Titel: Keyboard-Tastendrücke aufnehmen / simulieren - Zeitkritisch
Hallo!
Ich habe ein Spiel (nicht meines) , wobei ein Level praktischerweise durch drücken einer Taste beginnt,
daher sollte es sich erst recht für mein Vorhaben eignen.
Ich möchte die ersten paar Minuten nicht immer selbst bis zu einer bestimmten Stelle spielen,
daher möchte ich die gedrückten Tasten aufnehmen und im nachhinein simulieren.
Jedoch kriege ich es nicht hin, dass es zeitlich mit meinen eigenen Eingaben exakt übereinstimmt.
Nach ein paar Sekunden schon passt was nicht und er lenkt zu früh ein.
Das Spiel beginnt aber 100% immer exakt an der gleichen Stelle und wird mit der "VK_UP" - Taste gestartet
Habe schon verschiedenes probiert, mit einem Tastatur-Hook oder alle 1ms die Keystate abfragen,
simulieren mittels SendInput oder keybd_event. Nix hilft, obwohl ich natürlich Timestamps verwende.
Hier mein code, nicht schön, da in kurzer Zeit zusammengeschustert (nur zum testen) :
- es ist die die Version OHNE Hook .. dieses zeitliche Problem habe ich mit beiden Versionen
- das Spiel benutzt wohl directInput, darum wandle ich VK_LEFT usw nochmal um
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175:
| unit FMain;
interface
uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, LTypes;
type TKeyStateInfo = class TimeStamp : TDateTime; VKey : Integer; State : Integer; end;
TForm1 = class(TForm) tiSend: TTimer; Button1: TButton; meInput: TMemo; nbListen: TButton; nbClear: TButton; tiStartSend: TTimer; tiReadKeyStates: TTimer; procedure Button1Click(Sender: TObject); procedure tiSendTimer(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure nbListenClick(Sender: TObject); procedure nbClearClick(Sender: TObject); procedure tiStartSendTimer(Sender: TObject); procedure tiReadKeyStatesTimer(Sender: TObject); private FCatchBeginTime: TDatetime; FSendBegintime : TDateTime; FKeyStates : TStringList; fCount: Integer; fListen : Boolean; FKeyLog : TList; FKeyLogTmp: TList; end;
var Form1: TForm1;
implementation
uses Math;
const RecordKeys : Array [0..4] of Integer = (VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_LCONTROL);
{$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin FKeyLogTmp.Assign(FKeyLog); tiStartSend.Enabled := not tiSend.Enabled; if tiStartSend.Enabled then begin Self.Caption := 'EIN'; end else begin tiSend.Enabled := FALSE; Self.Caption := 'AUS'; end; end;
procedure TForm1.tiSendTimer(Sender: TObject); var lObj : TKeyStateInfo; lInput : TInput; lDxKey : Integer; begin if Assigned(FKeyLogTmp) then begin while (FKeyLogTmp.Count > 0) do begin lObj := TKeyStateInfo(FKeyLogTmp.Items[0]); if (lObj.TimeStamp <= (now - FSendBegintime)) then begin lDxKey := lObj.VKey; case lObj.VKey of VK_LEFT : lDxKey := 203; VK_RIGHT : lDxKey := 205; VK_UP : lDxKey := 200; VK_DOWN : lDxKey := 208; VK_LCONTROL : lDxKey := 29; end;
lInput.Itype := INPUT_KEYBOARD; lInput.ki.wVk := 0; lInput.ki.wScan := lDxKey; lInput.ki.time := 0; lInput.ki.dwExtraInfo := 0; if (lObj.State < 0) then begin lInput.ki.dwFlags := 0; end else begin lInput.ki.dwFlags := KEYEVENTF_KEYUP; end; SendInput(1, lInput, SizeOf(lInput));
FKeyLogTmp.Delete(0); end else begin break; end; end; end; end;
procedure TForm1.FormCreate(Sender: TObject); begin FKeyLog := TList.Create; FKeyLogTmp := TList.Create; FKeyStates := TStringList.Create; end;
procedure TForm1.FormDestroy(Sender: TObject); begin FKeyStates.Free; FKeyLog.Free; FKeyLogTmp.Free; end;
procedure TForm1.nbListenClick(Sender: TObject); begin tiReadKeyStates.Enabled := not tiReadKeyStates.Enabled; if tiReadKeyStates.Enabled then begin FCatchBeginTime := now; nbListen.Caption := 'Listen - ON'; end else begin nbListen.Caption := 'Listen - OFF'; end; end;
procedure TForm1.nbClearClick(Sender: TObject); begin meInput.Lines.Clear; FKeyStates.Clear; FKeyLog.Clear; end;
procedure TForm1.tiStartSendTimer(Sender: TObject); begin FSendBegintime := now; tiSend.Enabled := TRUE; tiStartSend.Enabled := FALSE; end;
procedure TForm1.tiReadKeyStatesTimer(Sender: TObject); var i : Integer; lObj : TKeyStateInfo; lState : Integer; begin for i := Low(RecordKeys) to High(RecordKeys) do begin lState := GetAsyncKeyState(RecordKeys[i]); if (lState < 0) then lState := -1 else lState := 1; if (FKeyStates.Values[IntToStr(RecordKeys[i])] <> InttoStr(lState)) then begin FKeyStates.Values[IntToStr(RecordKeys[i])] := IntToStr(lState); lObj := TKeyStateInfo.Create; lObj.TimeStamp := now - FCatchBeginTime; lObj.VKey := RecordKeys[i]; lObj.State := lState; meInput.Lines.Add('Key: ' + IntToStr(RecordKeys[i])); FKeyLog.Add(lObj); end; end; end;
end. |
Ist mein Anliegen überhaupt lösbar ? Oder ist das ganze Tastatursimulieren niemals so genau ?
mfg,
Limster
GTA-Place - Di 06.01.09 01:59
Das mit dem Timer wird nicht funktionieren. Es ist unmöglich, dass der Timer jede Millisekunde aufgerufen wird (minimum (gerade getestet) 15ms). Außerdem ist der Timer sehr sehr ungenau. Wenns richtig schnell gehen soll, dann ist OnIdle das richtige (< 1/100 ms). Aber normalerweise muss es ja gar nicht so genau sein.
toms - Di 06.01.09 06:53
Hallo
Hast du es mit einem WH_ JOURNALRECORD Hook probiert?
Limster - Di 06.01.09 15:48
Titel: DirectX
Danke für den Tipp..
Hab das jetzt mal ausprobiert. funktioniert so im allgemeinen supa.
Menüführung im Spiel automatisieren funktioniert wunderbar,
jedoch wenns dann zum Spielen wird, reagiert mein Spieler nicht..
Wird ein DirectX / DirectInput Problem sein ?
Warum muss das so schwierig sein ;)
mfg
Boldar - Di 06.01.09 15:51
mmh villeicht soll genau das im Spiel verhindert werden...
du könntest dir alternativ auch ein G-15 Keyboard kaufen, da is das von Haus aus mit dabei...
Limster - Di 06.01.09 16:08
nein das denke ich nicht.
Mit meinen anderen Versuchen (normalem Keyboard hook, bzw. Timer) habe ich zb. zwar
VK_LEFT aufgenommen, gesendet (SendInput oder Keyb_Event) habe ich jedoch 203 (habe ich mir gegoogelt -> 203 ist für DirectInput ein Links) als Scancode und nicht
VK_LEFT.. dann hat sich auch der Spieler bewegt .. aber hier war halt das "zeitliche" Problem..
mfg
jaenicke - Di 06.01.09 16:30
Wie hast du es denn jetzt gemacht?
Kannst du da nicht auch die Keycodes entsprechend ändern?
Limster - Di 06.01.09 17:28
das krieg ich grad nicht gebacken:
The paramL member of the EVENTMSG structure specifies the virtual key code of the key that was pressed.
The paramH member of the EVENTMSG structure specifies the scan code.
es scheint aber so, als ob das nicht so daherkommt wie beschrieben..
habs getestet wenn ich die "Rechts"- Taste drücke:
paramL ist nicht VK_RIGHT (=38) .. sondern 19751
paramH dürfte auch kein Scancode sein (=32845) .. zumindest liefert MapVirtualKey immer 0
--> MapVirtualKey: für die MapType-Übergabe find ich keine Konstante, habs aber mit 0, 1 bzw 2 ausprobert um vom
ScanCode auf den VK zu kommen
hm..
mfg
Limster - Di 06.01.09 18:01
Hab das EventMsg doch gebacken bekommen.. mehr oder weniger, der Spieler rührt sich aber so
nicht :
bei meinen anderen Versionen, bei denen sich wenigstens der Spieler bewegt hat,
hatte ich für das Keysimulieren als VKCode = 0 und als ScanCode zb. 203 geschickt.
Hier habe ich es so gelöst: (funktioniert aber nicht)
Low-Order Byte von LParam is der VKKeyCode, High-Order Byte von LParam wäre der Scancode
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16:
| if (lEventStrut.message = WM_KEYDOWN) or (lEventStrut.message = WM_KEYUP) then begin case Lo(lEventStrut.paramL) of VK_LEFT : begin lEventStrut.paramL := (203 shl 8); end; VK_UP: begin lEventStrut.paramL := (200 shl 8); end; VK_RIGHT: begin lEventStrut.paramL := (205 shl 8); end; VK_DOWN: begin lEventStrut.paramL := (208 shl 8); end; end; end; |
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!