Konsole von Sacred 2 hooken (Computerspiel)

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Konsole von Sacred 2 hooken (Computerspiel)

Beitrag von Thorium »

Was sehr spezielles, aber vieleicht kanns ja jemand gebrauchen.

Hier möchte ich jetzt mal ein paar Infos zu Sacred 2 raushauen, die aus einem meiner Projekte stammen. Um was für ein Projekt es sich handelt kann ich leider nicht öffentlich sagen. Es ist auch schon längst vorbei und wird nicht veröffentlicht.

Eine interessante Frage ist immer, wenn man ein Tool zu einem Spiel baut: "Wie kann ich Rückmeldungen meines Programms an den User im Spiel geben?"

Sacred 2 bietet uns durch seine Konsole (öffnen via ^ ) eine einfache und effektive Möglichkeit dazu und lässt uns sogar Eingaben vom User entgegennehmen. Damit das klappt müssen wir die Konsole hooken. Das heist wir haken uns mit unserem Programm ein und alles was über die Konsole läuft wird vortan zuerst über unser Programm laufen.

Die folgenden Codes setzten vorraus das sie in einer in S2 injizierten DLL liegen.

Als erstes der einfache Teil: Text in der Konsole ausgeben.
Sacred 2 hat dafür eine Prozedur, welche sich in der sacred2.exe befindet.
Die Prozedur erwartet 2 Parameter: Einen Pointer, dessen Bedeutung mir nicht klar ist und den Pointer zu einem nullterminierten ANSI-String, der in der Konsole dargestellt werden soll.

Da sich sowohl die Adresse der Prozedur als auch des unbekannten Pointers mit jeder Version ändern kann, sollten diese dynamisch ermittelt werden. Dazu später mehr.
Hier erstmal meine Prozedur zur Konsolenausgabe in Sacred 2:

Code: Alles auswählen

Procedure S2ConsoleOutput(OutputText.s)

  !push dword [p.v_OutputText]
  !mov eax,[v_S2ConsolePointer]
  !push dword [eax]
  !mov eax,[v_S2ConsoleOutputProcAddr]
  !call eax
  !pop ecx
  !pop ecx

EndProcedure
Nun zum Konsolenhook um Eingaben auswerten zu können:

Hier die Hookprozedur, also die Prozedur, die von Sacred 2 aufgerufen wird, wenn der User Text in die Konsole eingibt und eine Wrapper-Prozedur um das ganze gefahrlos in PureBasic verwenden zu können. Beim Aufruf der Prozedur S2ConsoleHook liegt der Pointer zum eingegebenen Text auf dem Stack und zwar an esp+12. Soll Sacred 2 nicht auf den Text reagieren der eingeben wurde, da euer Programm ihn schon verarbeitet, dann nullt den String einfach. Aber nullt nicht den Pointer sonst kracht es!!!

Code: Alles auswählen

Procedure S2ConsoleWrapper()

  Define.s ConsoleInput
  
  !mov eax,[esp+12]
  !mov [p.v_ConsoleInput],eax
  
  If ConsoleInterpreter(ConsoleInput) = 0
    ConsoleInput = ""
  EndIf

EndProcedure

Procedure S2ConsoleHook() 
  
  ;Die beiden folgenden Asm-Anweisungen müssen hier
  ;ausgeführt werden, da sie in S2 überschrieben wurden,
  ;beim einpatchen des Hooks.
  !add eax,[v_S2ConsoleConstant]
  !push eax
  
  S2ConsoleWrapper()

  !jmp [v_S2ConsoleAddyEnd]

EndProcedure
Und hier noch die Prozeduren zum installieren und deinstallieren des Konsolenhooks:

Code: Alles auswählen

Procedure InstallS2ConsoleHook()

  Define.l OldProtect,Dummy

  GetS2ConsoleHookAddr()

  VirtualProtect_(S2ConsoleAddy,6,#PAGE_EXECUTE_READWRITE,@OldProtect)
  
  PokeB(S2ConsoleAddy,$68)
  PokeL(S2ConsoleAddy+1,@S2ConsoleHook())
  PokeB(S2ConsoleAddy+5,$C3)

  VirtualProtect_(S2ConsoleAddy,6,OldProtect,@Dummy)

EndProcedure

Procedure UninstallS2ConsoleHook()

  Define.l OldProtect,Dummy

  VirtualProtect_(S2ConsoleAddy,6,#PAGE_EXECUTE_READWRITE,@OldProtect)

  PokeB(S2ConsoleAddy,$05)
  PokeL(S2ConsoleAddy+1,$19590)
  PokeB(S2ConsoleAddy+5,$50)

  VirtualProtect_(S2ConsoleAddy,6,OldProtect,@Dummy)

EndProcedure
So nun werded ihr euch fragen wo den die Adressen herkommen, weil ich überall ja nur Variablen verwende. Das ist eine ganz feine Sache: Ein Suchalgo sucht sich die Adressen raus. So funktioniert das ganze mit _jeder_ Version von S2. Vollkommen egal ob Release, Demo und sogar mit kommenden Versionen wird das einwandfrei funktionieren! 8)

Code: Alles auswählen

Procedure.l GetExecutableMemRegions(RegionList.Region(1))

  Define.l Addr,RetVal,RegionCnt
  Define.MEMORY_BASIC_INFORMATION MemoryInfo

  RegionCnt = -1

  Repeat
  
    Addr = Addr + MemoryInfo\RegionSize
    RetVal = VirtualQuery_(Addr,MemoryInfo,SizeOf(MEMORY_BASIC_INFORMATION))
    
    If RetVal > 0 
      If MemoryInfo\State = #MEM_COMMIT
      
        If MemoryInfo\Protect = #PAGE_EXECUTE Or MemoryInfo\Protect = #PAGE_EXECUTE_READ Or MemoryInfo\Protect = #PAGE_EXECUTE_READWRITE Or MemoryInfo\Protect = #PAGE_EXECUTE_WRITECOPY
        
          RegionCnt = RegionCnt + 1
          ReDim RegionList.Region(RegionCnt)
          RegionList(RegionCnt)\BaseAddr = MemoryInfo\BaseAddress
          RegionList(RegionCnt)\Size = MemoryInfo\RegionSize
          
        EndIf
      
      EndIf  
    EndIf
  
  Until RetVal < 1

  ProcedureReturn RegionCnt

EndProcedure

Procedure GetS2ConsoleHookAddr()

  Define.l i,RegionCnt,RegionStart,RegionSize,FoundAddr
  Dim RegionList.Region(0)
  
  RegionCnt = GetExecutableMemRegions(RegionList())
  
  For i = 0 To RegionCnt

    RegionStart = RegionList(i)\BaseAddr
    RegionSize = RegionList(i)\Size - 64
    FoundAddr = 0
    
    !mov edi,[p.v_RegionStart]
    !mov ecx,[p.v_RegionSize]
    !mov esi,edi
    !add esi,ecx
    
    !GCH_LoopStart:
    
    !mov al,$7C
    !repne scasb
    
    !mov eax,[edi+1]
    !cmp eax,$FF2C858B
    !jne GCH_NotFound
    
    !mov eax,[edi+5]
    !cmp eax,$A083FFFF
    !jne GCH_NotFound

    !mov [p.v_FoundAddr],edi
    !jmp GCH_Found
    
    !GCH_NotFound:
    !cmp edi,esi
    !jl GCH_LoopStart
    
    !GCH_Found:
    
    If FoundAddr <> 0
      
      S2ConsoleAddy = FoundAddr + 20
      S2ConsoleAddyEnd = S2ConsoleAddy + 6
      S2ConsoleConstant = PeekL(FoundAddr+21)
      
      Break
    
    EndIf
  
  Next
  
  If S2ConsoleAddy = 0
    MessageRequester(#AppTitle,"Can't find console code.",16)
  EndIf

EndProcedure

Procedure GetS2ConsoleProc()
  
  Define.l i,RegionCnt,RegionStart,RegionSize,FoundAddr
  Dim RegionList.Region(0)
  
  RegionCnt = GetExecutableMemRegions(RegionList())
  
  For i = 0 To RegionCnt

    RegionStart = RegionList(i)\BaseAddr
    RegionSize = RegionList(i)\Size - 20
    FoundAddr = 0
    
    !mov edi,[p.v_RegionStart]
    !mov ecx,[p.v_RegionSize]
    !mov esi,edi
    !add esi,ecx
    
    !GCP_LoopStart:
    
    !mov al,$68
    !repne scasb
    
    !mov ax,[edi+4]
    !cmp ax,$35FF
    !jne GCP_NotFound
    
    !mov al,[edi+10]
    !cmp al,$E8
    !jne GCP_NotFound
    
    !mov ax,[edi+15]
    !cmp ax,$5959
    !jne GCP_NotFound
        
    !mov [p.v_FoundAddr],edi
    !jmp GCP_Found
    
    !GCP_NotFound:
    !cmp edi,esi
    !jl GCP_LoopStart
    
    !GCP_Found:
    
    If FoundAddr <> 0
      
      S2ConsolePointer = PeekL(FoundAddr+6)
      S2ConsoleOutputProcAddr = PeekL(FoundAddr+11) + FoundAddr + 15

      Break
    
    EndIf
  
  Next
  
  If S2ConsolePointer = 0
    MessageRequester(#AppTitle,"Can't find console code.",16)
  EndIf

EndProcedure
Hoffe ihr blickt durch, trotz eher flüchtiger Erklärungen. Ansonsten halt einfach Fragen.
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild