Autor Beitrag
Limster
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 72


Delphi 5 & 7 Enterprise
BeitragVerfasst: Mo 05.01.09 21:43 
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

ausblenden volle Höhe 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;
    { Private declarations }
  end;

var
  Form1: TForm1;

implementation

uses
  Math;

const
  RecordKeys : Array [0..4of Integer = (VK_LEFT,
                                          VK_RIGHT,
                                          VK_UP,
                                          VK_DOWN,
                                          VK_LCONTROL);

{$R *.dfm}
/// SENDEN DER AUFGENOMMENEN KEYS STARTET IN 5 Sekunden
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;

// WIRD ALLE 1 MS AUSGEFÜHRT
procedure TForm1.tiSendTimer(Sender: TObject);
var
  lObj   : TKeyStateInfo;
  lInput : TInput;
  lDxKey : Integer;
begin
  if Assigned(FKeyLogTmp) then begin
    while (FKeyLogTmp.Count > 0do 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 < 0then 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;  // TODO: Obj freigeben
  FKeyLogTmp.Free;
end;

//STARTET / STOPPT DAS AUFNEHMEN DER KEYS
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;

//WIRD ALLE 1 MS AUSGEFÜHRT
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 < 0then 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
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
EE-Regisseur
Beiträge: 5248
Erhaltene Danke: 2

WIN XP, IE 7, FF 2.0
Delphi 7, Lazarus
BeitragVerfasst: 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.

_________________
"Wer Ego-Shooter Killerspiele nennt, muss konsequenterweise jeden Horrorstreifen als Killerfilm bezeichnen." (Zeit.de)
toms
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1099
Erhaltene Danke: 2



BeitragVerfasst: Di 06.01.09 06:53 
Hallo

Hast du es mit einem WH_ JOURNALRECORD Hook probiert?
Limster Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 72


Delphi 5 & 7 Enterprise
BeitragVerfasst: 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


Zuletzt bearbeitet von Limster am Di 06.01.09 15:52, insgesamt 1-mal bearbeitet
Boldar
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Beiträge: 1555
Erhaltene Danke: 70

Win7 Enterprise 64bit, Win XP SP2
Turbo Delphi
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 72


Delphi 5 & 7 Enterprise
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19341
Erhaltene Danke: 1752

W11 x64 (Chrome, Edge)
Delphi 12 Pro, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 06.01.09 16:30 
Wie hast du es denn jetzt gemacht?

Kannst du da nicht auch die Keycodes entsprechend ändern?
Limster Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 72


Delphi 5 & 7 Enterprise
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 72


Delphi 5 & 7 Enterprise
BeitragVerfasst: 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

ausblenden 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;