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