none
Tasten an ein Spiel senden RRS feed

  • Frage

  • Ich möchte eine Anwendung programmiere, die es ermöglicht, Rennspiele mit der Maus zu steuern. Dazu will ich je nach abstand der Maus zum Mittelpunkt des Bildschrims unterschiedlich kurz nach einander die Tasten zum Steuern drücken (Pfeiltasten). Ich bekomme es aber nicht hin, dem Spiel die Tasten zu senden. Testprogramme waren Need for Speed World und Trackmana Nations Forever.

    Ich habe mir schon folgende Dokumentationen angesehen, hat aber alles nichts genutzt:

    Hier mein Code:

    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Dim xAbs As Integer
        Dim yAbs As Integer
    
        Dim xRel As Decimal
        Dim yRel As Decimal
    
        Dim sleepTime As Integer = 200
    
        Private Declare Sub keybd_event Lib "user32.dll" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Int32, ByVal dwExtraInfo As Int32)
        Private Const VK_1 = &H31  ' Taste 1
        Private Const KEYEVENTF_KEYUP = &H2
    
        Dim moniSize As System.Drawing.Size = System.Windows.Forms.SystemInformation.PrimaryMonitorSize
    
    
        'Timer zur Ermittlung der Mausposition
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles TimerErmittler.Tick
            moniSize = System.Windows.Forms.SystemInformation.PrimaryMonitorSize
            monSize.Text = "Bildschirmgröße: " & moniSize.ToString
            'Absolute Mausposition ermitteln
            xAbs = MousePosition.X
            yAbs = MousePosition.Y
    
            'Absolute Mausposition eintragen
            xAbsolute.Text = "X: " & xAbs
            yAbsolute.Text = "Y: " & yAbs
    
            'Relative Mausposition ermitteln
            xRel = 1 - xAbs / moniSize.Width * 2
            yRel = 1 - yAbs / moniSize.Height * 2
    
            'Relative Mausposition eintragen
            xRelative.Text = "X in %: " & Math.Round(xRel * 100, 2)
            yRelative.Text = "Y in %: " & Math.Round(yRel * 100, 2)
    
            'Timer einstellen
            If xRel < 0 Then
                xRel = 0 - xRel
            End If
            If yRel < 0 Then
                yRel = 0 - yRel
            End If
            ''Vertikal-Timer
            Try
                TimerVert.Interval = 1 / yRel
                Label1.Text = TimerVert.Interval
            Catch ex As Exception
            End Try
    
            ''Horizontal-Timer
            Try
                TimerHori.Interval = 1 / xRel
                Label2.Text = TimerHori.Interval
            Catch ex As Exception
            End Try
    
        End Sub
    
        'Änderung der Checkbox
        Private Sub CheckBoxOnOff_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBoxOnOff.CheckedChanged
            If CheckBoxOnOff.Checked = True Then
                CheckBoxOnOff.Text = "Maussteuerung ist an"
                TimerVert.Enabled = True
                TimerHori.Enabled = True
                TimerVert.Start()
                TimerHori.Start()
            Else
                CheckBoxOnOff.Text = "Maussteuerung ist aus"
                TimerVert.Enabled = False
                TimerHori.Enabled = False
            End If
        End Sub
    
        'Timer für vertikale Navigation
        Private Sub TimerVert_Tick(sender As Object, e As EventArgs) Handles TimerVert.Tick
            If xRel > 0 Then
                Call keybd_event(Keys.Left, 0&, 0&, 0)
                System.Threading.Thread.Sleep(sleepTime)
                Call keybd_event(Keys.Left, 0&, KEYEVENTF_KEYUP, 0&)
            Else
                Call keybd_event(Keys.Right, 0&, 0&, 0)
                System.Threading.Thread.Sleep(sleepTime)
                Call keybd_event(Keys.Right, 0&, KEYEVENTF_KEYUP, 0&)
            End If
        End Sub
    
        'Timer für horizontale Navigation
        Private Sub TimerHori_Tick(sender As Object, e As EventArgs) Handles TimerHori.Tick
            If yRel > 0 Then
                Call keybd_event(Keys.Up, 0&, 0&, 0)
                System.Threading.Thread.Sleep(sleepTime)
                Call keybd_event(Keys.Up, 0&, KEYEVENTF_KEYUP, 0&)
            Else
                Call keybd_event(Keys.Down, 0&, 0&, 0)
                System.Threading.Thread.Sleep(sleepTime)
                Call keybd_event(Keys.Down, 0&, KEYEVENTF_KEYUP, 0&)
            End If
        End Sub
    End Class
    

    Weiß jemand wie ich das machen kann?

    Freitag, 11. April 2014 16:49

Alle Antworten

  • Hi,

    das kann so nicht funktionieren ;-)

    Wie soll ich das erklären?! Was dein Programm macht, ist ein Tastaturevent an den eigenen Thread zu senden. Nicht aber an das Spiel.

    Um Tastaturereignisse an ein anderes Programm (Spiel) zu senden, bedarf es einer "offenen Schnittstelle" (z.B. per WCF etc.).

    Einfach nur im eigenen Programm zu sagen "x" wird gedrückt, lässt bei anderen Programmen nicht das Ereignis werfen "x" wird gedrückt ;-)

    Anders ausgedrückt:
    Du und dein Kumpel stellst dich auf die Straße. Du hast die Maus, dein Kumpel die Tastatur und "simuliert den  Drücker an der Taste". Eine Freundin von euch, steht in der Wohnung 2 Straßen weiter. Wenn du jetzt deine Maus bewegst, sagst du zu deinem Kumpel "1x nach links". Und dein Kumpel ruft zu eurer Freundin "nach links nach links nach links". Aber eure Freundin kann euch nicht hören. Warum kann sie euch nicht hören? Sie ist nicht mit euch "verbunden". Sie sitzt in ihrer Wohnung mit geschlossenen Fenstern, zwei Straßen weiter.

    EURE STRASSE (also euer eigene Programmthread) kann euch hören, und macht auch die Tastaturevents (u.U., möglicherweise, nicht sicher).

    Es sei denn, ein Spiel (wie Need for Speed) bietet eine entsprechende Schnittstelle an. Der Microsoft Flight Simulator bot solche eine Schnittstelle an. Da konnte ich von einem anderen Rechner, mittels VB, mit einem Forcefeedback Lenkrad das Flugzeug (das auf einem anderen Rechner lief) nach links und rechts lenken. Aber auch nur, weil mir die Schnittstelle offiziell "angeboten" (zugänglich) gemacht wurde. Da sendet man auch entsprechende Befehle(!) hin (z.B. per XML).

    Aber einfach ein Tastaturevent auswerfen?! Stell dir mal was anderes vor: Ich drücke in MEINER Anwendung die Taste X und in 5 x-beliebigen anderen Word/Excel/Notpad-Anwendungen wird er Text einfach verändert. Wie wäre denn das?!

    Dein Code-Schinppsel arbeitet in einem EIGENEN Thread! Und da sollte er auch bleiben ;-) Ein "Angeblich-hat-ein-User-Taste-Links-Gedrückt" reicht nicht aus, um in einer x-beliebigen anderen Anwendung vorzugaukeln "Taste-Links-wurde-gedrückt-erniedrige-dich-und-gehorche".

    Wäre ja noch schöner ;-)

    Gruß
    Andy

    PS: Falls ich deine Anfrage richtig verstanden habe ;-)

    Freitag, 11. April 2014 19:54
  • Hallo zusammen,

    @Andy
    Der vom TE gezeigte Code funktioniert grundsätzlich! SendKeys und keybd_event sind durchaus in der Lage einfach einen Tastendruck zu simulieren - das hat nichts mit dem Thread oder Prozess zu tun. Teste es einfach mal mit Notepad, dort funktioniert es ohne Probleme. Gleiches gilt übrigens für die Simulation von Mausevents und wahrscheinlich auch für den Touchscreen.

    @QR-3
    Wie ich in dem anderen Thread schon schrieb, machen DirectX/OpenGL ihre eigene Sache. Diese sprechen nur sehr bedingt auf die Windows API an, da Windows oft umgangen wird um Leistung zu sparen. Ich habe dort damals einige Seiten mit Lösungen verlinkt. Hast du die alle schon durchprobiert? Verschiedene Dinge wie SendInput und auch keybd_event schienen mir damals eine erfolgreiche Aussicht zu haben. Hast du dein Programm auch als Administrator gestartet? (Wenn du VS als Admin startest, wird beim Debuggen das Programm auch mit Admin-Rechten ausgeführt.)

    Du müsstest auch schon den Hinweis auf die DirectX SDK gelesen haben. Davon weiß ich allerdings nicht wirklich viel und ob es funktioniert kann ich ebenfalls nicht sagen. Zu den WinAPI Lösungen kann ich dir eventuell noch weiter helfen.


    Tom Lambert - C# MVP
    Bitte bewertet- und markiert Beiträge als Antwort. Danke.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Freitag, 11. April 2014 20:31
    Moderator
  • @Tom:
    Ich kann mit meiner EIGENEN VB.NET-Anwendung OHNE einer speziellen Anfrage und Rechten, an irgendeine x-beliebige anderen Anwendung IRGENDEIN-Key senden? Unter Windows Vista? 7? 8? 8.1? 8.1 Update1?

    Gruß
    Andy

    Freitag, 11. April 2014 21:42
  • @Andy
    Ja. Ich habe es eben nochmal unter Windows 8.1 (mit allen Updates) getestet. Einmal mit eingeschalteter und mit ausgeschalteter Benutzerkontensteuerung (UAC) unter einem Adminkonto aber ohne Adminrechten. Da die UAC unter Windows Vista und 7 leicht anders reagiert, kann es sich dort auch anders verhalten. Aber Grundsätzlich kann man davon ausgehen, dass es funktioniert.

    Man kann es sicherlich als Sicherheitslücke betrachten, aber da habe ich auch noch nicht wirklich drüber nach gedacht. Bisher scheint es damit keine wirklichen Probleme zu geben und auch ich habe einige Hilfsprogramme die so arbeiten. Es ist nicht der beste Weg, aber er funktioniert.
    Selbst wenn es irgendwann mal blockiert wird, aufgrund der Abwärtskompatibilität sollte es wenigstens in einem erreichbaren Modus (Admin o.ä.) funktionsfähig bleiben. So verhielt es sich zumindest mit anderen Features.


    Tom Lambert - C# MVP
    Bitte bewertet- und markiert Beiträge als Antwort. Danke.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Freitag, 11. April 2014 22:00
    Moderator
  • @Tom:

    Also jetzt bin ich echt überrascht. Ich darf aus meiner EIGENEN(!!!) Anwendung (die im Adminmodus läuft) aus dem Explorer NICHTS empfangen, aber an jeder anderen Anwendung x-beliebiges senden? Auch PIN und TANs?

    Ich hatte vor ein paar Wochen folgendes Problem: Meine VB.NET-Anwendung lief im Adminmodus. Und NUR, weil sie im Adminmodus lief, durfte vom Explorer NICHTS (per Drag&Drop) empfangen werden. Aber UMGEKEHRT soll jede meiner VB.NET-Anwendung jedes x-beliebige Programm Keyboard-Eingaben steuern/empfangen dürfen?

    Jetzt bin ich total verwirrt :-(

    Gruß
    Andy

    Freitag, 11. April 2014 22:16
  • @Andy
    Für Windows sind das 2 vollkommen verschiedene Dinge. SendKeys etc. funktionieren wie die Bildschirmtastatur. D.h. das Windows dazwischen hängt.
    Drag and Drop Vorgänge dagegen laufen direkt zwischen 2 Programmen ab, da kann Windows nicht viel machen.

    Ich vermute mal, das die Benutzerkontensteuerung dahinter steckt. In den meisten Fällen muss der Benutzer einem Programm expliziet Administratorrechte geben. D.h. das er auch einen Grund dafür hat und sich dem erhöhten Risiko bewusst ist, wenn er dem Programm nicht vertraut. Wenn nun dein Admin-Programm mit dem normalen Windows Explorer kommunizieren will, dann merkt Windows das und blockiert es. Es könnte schließlich sein das ein Programm versucht durch Sicherheitslücken Adminrechte zu erhalten ohne dass es der Benutzer weiß. Das es dabei die originale explorer.exe ist, spielt da scheinbar keine Rolle.

    Beim senden von Tastatur (oder allgemeiner "Eingabe Events") verhält es sich so wie wenn man eine Hardware virtualisiert. Die Programme können über diese nicht kommunizieren, folglich gibt es kein Sicherheitsproblem.

    Da PINs und TANs auch nur Zahlen (ggf. Buchstaben) sind, stellt das auch kein Problem dar. Das sind zwar grundsätzlich vertrauliche Informationen, aber kein Programm kann diese direkt auslesen. Über die Tastatur können diese höchstens an ein anderes Programm weitergeleitet werden und das auch nur sehr unsicher ob es wirklich funktioniert.

    Zusammenfassung: Wenn 2 Programme kommunizieren, dann stellt das ein Sicherheitsrisiko dar. Wenn 2 Programme nur über eine gemeinsame Schnittstelle (Tastatur, ...) verbunden sind, stellt das i.d.R. kein Problem dar.


    Tom Lambert - C# MVP
    Bitte bewertet- und markiert Beiträge als Antwort. Danke.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Freitag, 11. April 2014 22:38
    Moderator
  • @Koopakilla:

    Dein Link zu SendInput führt zu einer Beschreibung für C++. Das kann ich nicht. Werde mir aber mal was googeln (oder gibts das nur für C++?). Vielleicht hast du ja eine schöne Beschreibung zur Hand?

    keybd_event habe ich oben im Beispiel verwendet, funzt nicht.

    Habe das Proggi als Admin gestartet, hilft aber nix.



    • Bearbeitet QR-3 Samstag, 12. April 2014 19:56
    Samstag, 12. April 2014 19:52
  • Hallo,
    grundsätzlich kann man davon ausgehen, dass die WinAPI nur in C++ geschrieben wurde. Ich habe darum mit absicht auf den originalen C++ Artikel verlinkt, da man diese Methoden alle importieren kann. Das hast du auch schon bei keybd_event gemacht.

    pInvoke.net bietet sehr viele solche Importdeklarationen an. Siehe SendInput (user32). Für VB.NET findest du aber eher selten die zusätzlich benötigten Klassen etc. Den bereitgestellten C#-Code kannst du aber mit dem in der Signatur stehenden Converter übersetzen.

    Mehr Ideen, als in dem anderen Thread geschrieben, habe ich aber auch jetzt nicht.


    Tom Lambert - C# MVP
    Bitte bewertet- und markiert Beiträge als Antwort. Danke.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Samstag, 12. April 2014 20:03
    Moderator
  • Aus dem Code in deinem Link werde ich nicht schlau. Was davon brauche ich, was nicht?

    Hier der Übersetzte Code:

    Dim pInputs = New () {New StructLib.INPUT() With { _
    	Key .type = EnumLib.INPUT_TYPE.INPUT_KEYBOARD, _
    	Key .ki = New StructLib.KEYBDINPUT() With { _
    		Key .wScan = EnumLib.ScanCodeShort.KEY_S, _
    		Key .wVk = EnumLib.VirtualKeyShort.KEY_S _
    	} _
    }, New StructLib.INPUT() With { _
    	Key .type = EnumLib.INPUT_TYPE.INPUT_KEYBOARD, _
    	Key .ki = New StructLib.KEYBDINPUT() With { _
    		Key .wScan = EnumLib.ScanCodeShort.KEY_S, _
    		Key .wVk = EnumLib.VirtualKeyShort.KEY_S, _
    		Key .dwFlags = EnumLib.KEYEVENTF.KEYUP _
    	} _
    }, New StructLib.INPUT() With { _
    	Key .type = EnumLib.INPUT_TYPE.INPUT_KEYBOARD, _
    	Key .ki = New StructLib.KEYBDINPUT() With { _
    		Key .wScan = EnumLib.ScanCodeShort.KEY_S, _
    		Key .wVk = EnumLib.VirtualKeyShort.KEY_S _
    	} _
    }, New StructLib.INPUT() With { _
    	Key .type = EnumLib.INPUT_TYPE.INPUT_KEYBOARD, _
    	Key .ki = New StructLib.KEYBDINPUT() With { _
    		Key .wScan = EnumLib.ScanCodeShort.KEY_S, _
    		Key .wVk = EnumLib.VirtualKeyShort.KEY_S, _
    		Key .dwFlags = EnumLib.KEYEVENTF.KEYUP _
    	} _
    }, New StructLib.INPUT() With { _
    	Key .type = EnumLib.INPUT_TYPE.INPUT_KEYBOARD, _
    	Key .ki = New StructLib.KEYBDINPUT() With { _
    		Key .wScan = EnumLib.ScanCodeShort.KEY_S, _
    		Key .wVk = EnumLib.VirtualKeyShort.KEY_S _
    	} _
    }, New StructLib.INPUT() With { _
    	Key .type = EnumLib.INPUT_TYPE.INPUT_KEYBOARD, _
    	Key .ki = New StructLib.KEYBDINPUT() With { _
    		Key .wScan = EnumLib.ScanCodeShort.KEY_S, _
    		Key .wVk = EnumLib.VirtualKeyShort.KEY_S, _
    		Key .dwFlags = EnumLib.KEYEVENTF.KEYUP _
    	} _
    }}
    Dim hWindow = Api.user32.FindWindow("notepad", Nothing)
    Api.user32.SetForegroundWindow(hWindow)
    Thread.Sleep(2500)
    Api.user32.SendInput(CUInt(pInputs.Length), pInputs, StructLib.INPUT.Size)

    Sonntag, 13. April 2014 06:32