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