Assault Cube FPS Overlay

Veröffentliche deine funktionierenden Skripte und Funktionen

Moderator: jNizM

User avatar
pizzapizze
Posts: 44
Joined: 08 May 2019, 15:38

Assault Cube FPS Overlay

Post by pizzapizze » 15 Jan 2021, 21:14

Ich habe (aus Spaß) ein eine FPS Anzeige für Assault Cube erstellt.
Ich weiß, dass es unnötig ist weil:
  • Assault Cube eh schon eine built-in FPS Anzeige hat
  • Es bei diesem Spiel nicht von Relevanz ist, die FPS zu messen
dennoch dachte ich es wäre eine kleine Herausforderung und es ist Lustig mit AHK eine FPS Anzeige zu machen.

Falls jemand das Script aus Spaß ausprobiert: Erst Assault Cube starten, dann egal wann das Script ausführen.

(Interessant, was man nicht alles mit AHK machen kann..)
Code:

Code: Select all

;Pizzapizze's FPS Overplay für Assault Cube
;Version: 2 (17.01.2020)
#SingleInstance, force
OnExit, GuiClose
DetectHiddenWindows, On

global proc, ERR, bytes
bytes := []
proc 	:= {name: "ac_client.exe", pid: 0, handle: 0, dsrBaseName: "OPENGL32.dll", dsrBaseAdr: 0, allocBase: 0, renderFuncBaseOffset: 0x32EA0}
ERR := {ERR_PROC_CONNECT: "Fehler beim Aufbau mit dem Zielprozess."}

Gui, 2:New, +HwndLoadingHWND, AC FPS Overlay
Gui, Font, s13
Gui, Add, Text, , Suche nach Spiel
Gui, Font, s20
Gui, Add, Text, vloadingDots w150
Gui, Show

Gui, 1:New, +toolwindow -caption +HwndOverlayHWND +alwaysontop
Gui, Color, 0x000000
Gui, Font, bold s16 cGreen
Gui, Add, Text, vfpsText w270, FPS here
;Gui, Show, w280 h150

WinSet, TransColor, 0x000000, ahk_id %OverlayHWND%
WinSet, ExStyle, +0x20, ahk_id %OverlayHWND%

SetTimer, UpdateDots, 50

while (!manageProcessStuff())
	Sleep, 100

SetTimer, UpdateDots, off
GuiControl,2:, loadingDots, Gefunden!
WinActivate, % "ahk_exe " proc.name
WinGetPos, gx, gy,,, % "ahk_exe " proc.name
WinActivate, % "ahk_Id " LoadingHWND
WinGetPos, wx, wy,,, % "ahk_Id " LoadingHWND
stepX := (gx - wx) / 20, stepY := (gy - wy) / 20
Loop, 20
{
	wx += stepX, wy += stepY
	Gui, 2:Show, % "x" wx + stepX " y" wy + stepY
	sleep, 1
}
Gui, 2:Destroy
Gui, 1:Show, w280 h150
WinActivate, % "ahk_exe " proc.name

proc.allocBase := allocMem()
;msgbox % hex(proc.allocBase)

setupFramesCounter()

framesBuffer := 0x00000000
clrbad := 0xd00000, clrmed := 0xd0d000, clrwell := 0x00d000, clrultra := 0x00d0d0
Loop
{
	lastTimeStamp := newTimeStamp
	lastFramesCount := crntFramesCount

	DllCall("ReadProcessMemory", "UInt", proc.handle, "UPtr", proc.allocBase, "UPtr", &framesBuffer, "UInt", 4, "UPtr", 0, "int")	;get frames
	newTimeStamp := ToTimeUnit(, A_MSec, A_Sec, A_Min, A_Hour)
	crntFramesCount := NumGet(framesBuffer,, "UInt")

	frameTime := ((newTimeStamp - lastTimeStamp)) / (crntFramesCount - lastFramesCount)
	fps := (crntFramesCount - lastFramesCount) / ((newTimeStamp - lastTimeStamp) / 1000) ;get frames per second
	;tooltip %lastTimeStamp% %newTimeStamp% %lastFramesCount% %crntFramesCount%

	fpsString := "FPS:  " Round(fps, 0) "   (" Round(frameTime, 0) "ms)"
	GuiControl,1:, fpsText, %fpsString%
	crntFont := fps < 24 ? clrbad : fps < 46 ? clrmed : fps < 85 ? clrwell : clrultra
	Gui, Font, c%crntFont%
	GuiControl, Font, fpsText, 

	WinGetPos, wx, wy,,, % "ahk_exe " proc.name
	WinMove, ahk_id %OverlayHWND%,, wx, % wy + 30
	
	if (crntFramesCount > 0xFFFFFF)
	{
		DllCall("WriteProcessMemory", "UInt", proc.handle, "UPtr", proc.allocBase, "UInt*", 0, "UInt", 4, "UPtr", 0, "int")
		newTimeStamp := 0
	}
	
	Sleep 300
}
return

UpdateDots:
dots .= "."
if dots = ....................
	dots = .
GuiControl,2:, loadingDots, %dots%
return

manageProcessStuff() {
	WinGet, pid, PID, % "ahk_exe " proc.name
	if (!pid)
		return false

	proc.pid := pid
	proc.handle := getHandle(proc.pid, 0x438)

	proc.dsrBaseAdr := getModBase()
	;msgbox % hex(proc.allocBase)
	if (!proc.dsrBaseAdr)
	{
		displayError(ERR.ERR_PROC_CONNECT) 
		Return False
	}
	BASE_ADDRESS := proc.dsrBaseAdr + 0x1B39980
	Return True
}

getModBase() {
	hModules := 0 ;Modul Handle Pseudo Array.
	szHModules := VarSetCapacity(hModules, 2048) ;Sicher stellen, dass alle handles reinpassen.
	cbNeeded := 0 ;Hier wird später drinstehen, wie viel Platz ich tatsächlich gebraucht hätte.
	strBaseName := ""
	szStrBaseName := VarSetCapacity(strBaseName, 128)
	
	funcWorked := DllCall("Psapi\EnumProcessModulesEx", "UInt", proc.handle, "UPtr", &hModules, "UInt", szHModules, "UPtr", &cbNeeded, "UInt", 0x1, "Char") ;Alle 32 oder 64bit handles (Base Addressen) des Zielprozesses erhalten und in einem "Array" speichern. 0x1=32bit

	loop % NumGet(cbNeeded) / 8
	{
	;msgbox % "Base: " format("{:X}", NumGet(hModules, A_Index*8-8, "UPtr"))
		funcWorked := DllCall("Psapi\GetModuleBaseNameA", "UInt", proc.handle, "UInt", NumGet(hModules, A_Index*8-8, "UPtr"), "UPtr", &strBaseName, "UInt", szStrBaseName, "int") ;Alle handles durchgehen und den Basename abfragen, und vergleichen.
		if (StrGet(&strBaseName, szStrBaseName, "UTF-8") = proc.dsrBaseName)
		{
			Return NumGet(hModules, A_Index*8-8, "UPtr")
		}
	}
	Return False
}

displayError(errText) {
	MsgBox, 16, Win [%A_PtrSize% byte] - Fehler, %errText%
	Return
}

allocMem() {
	allocBase := DllCall("VirtualAllocEx", "UInt", proc.handle, "UInt", 0, "UInt", 1000, "UInt", 0x3000, "UInt", 0x40, "UInt")
	return allocBase
}

setupFramesCounter() {
	abc := 0
	NumPut(proc.allocBase, abc, 0)

	
	bytes[0] := 11
	bytes[1] := 0xA1
	bytes[2] := NumGet(abc, 0, "UChar")
	bytes[3] := NumGet(abc, 1, "UChar")
	bytes[4] := NumGet(abc, 2, "UChar")
	bytes[5] := NumGet(abc, 3, "UChar")
	bytes[6] := 0x40
	bytes[7] := 0xA3
	bytes[8] := NumGet(abc, 0, "UChar")
	bytes[9] := NumGet(abc, 1, "UChar")
	bytes[10] := NumGet(abc, 2, "UChar")
	bytes[11] := NumGet(abc, 3, "UChar")

	byteBuffer := 0x00

	Loop, 5		;Vorherige Bytes mitspeichern
	{
		DllCall("ReadProcessMemory", "UInt", proc.handle, "UPtr", proc.dsrBaseAdr + proc.renderFuncBaseOffset + A_Index - 1, "UPtr", &byteBuffer, "UInt", 1, "UPtr", 0, "int")
		bytes[bytes[0] + A_Index] := NumGet(byteBuffer, 0, "UChar")
	}
	bytes[0] += 5

	jmpNum := proc.dsrBaseAdr + proc.renderFuncBaseOffset - (proc.allocBase + bytes[0] + 6) ;referenzweet für JMP ausrechnen
	NumPut(jmpNum, abc, 0)
	bytes[bytes[0] + 1] := 0xE9		;Jmp Near
	bytes[bytes[0] + 2] := NumGet(abc, 0, "UChar")
	bytes[bytes[0] + 3] := NumGet(abc, 1, "UChar")
	bytes[bytes[0] + 4] := NumGet(abc, 2, "UChar")
	bytes[bytes[0] + 5] := NumGet(abc, 3, "UChar")
	
	bytes[0] += 5

	Loop, % bytes[0]	;alle bytes in die allocated memory schreiben
	{
		DllCall("WriteProcessMemory", "UInt", proc.handle, "UPtr", proc.allocBase + 5 + (A_Index), "UChar*", bytes[A_Index], "UInt", 1, "UPtr", 0, "int")
	}

	NumPut(proc.allocBase - (proc.dsrBaseAdr + proc.renderFuncBaseOffset) + 1, abc, 0, "Int64")	;jmp ref
	deBytes := []
	deBytes[1] := 0xE9
	deBytes[2] := NumGet(abc, 0, "UChar")
	deBytes[3] := NumGet(abc, 1, "UChar")
	deBytes[4] := NumGet(abc, 2, "UChar")
	deBytes[5] := NumGet(abc, 3, "UChar")
	
	Loop, 5		;Detour einleiten; bytes überschreiben
	{
		DllCall("WriteProcessMemory", "UInt", proc.handle, "UPtr", proc.dsrBaseAdr + proc.renderFuncBaseOffset + A_Index - 1, "UChar*", deBytes[A_Index], "UInt", 1, "UPtr", 0, "int")
	}

	soundplay *0
}

cleanUp() {
	if (bytes[12] !> 0)
		return
	Loop, 5		;vorherige bytes wieder hineinschreiben
	{
		DllCall("WriteProcessMemory", "UInt", proc.handle, "UPtr", proc.dsrBaseAdr + proc.renderFuncBaseOffset + A_Index - 1, "UChar*", Bytes[11 + A_Index], "UInt", 1, "UPtr", 0, "int")
	}

	DllCall("VirtualFreeEx", "UInt", proc.handle, "UPtr", proc.allocBase, "UInt", 0, "UInt", 0x00008000, "Char")
}

hex(a) {
	return format("0x{:X}", a)
}

ToTimeUnit(desiredTimeUnit := 1, timeMsec := 0, timeSec := 0, timeMin := 0, timeHr := 0) {
	totalTimeMsec := timeMsec + (timeSec*1000) + (timeMin*60*1000) + (timeHr*60*60*1000)

	totalTimeDesiredUnit := totalTimeMsec / desiredTimeUnit
	Return totalTimeMsec
}


GetHandle(PID, ACCES_RIGHT) {
    if (ACCES_RIGHT == "ReadWrite")
        ACCES_RIGHT := 0x38
    Return DllCall("OpenProcess", "UInt", ACCES_RIGHT, "Int", false, "UInt", PID, "UInt")
}

GuiClose:
cleanUp()
DllCAll("CloseHandle", "UInt", proc.handle)
ExitApp 0 
Attachments
AC FPS Overlay 2.ahk
(7.37 KiB) Downloaded 55 times
assault cube fps anuzeige (2).png
assault cube fps anuzeige (2).png (755.81 KiB) Viewed 2407 times
Last edited by pizzapizze on 17 Jan 2021, 09:28, edited 1 time in total.

just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Assault Cube FPS Overlay

Post by just me » 16 Jan 2021, 04:52

Moin,
pizzapizze wrote:... Und man sollte man beachten, dass das Spiel garantiert(!) abstürtzt, wenn man in einer Assault Cube Sitzung das Script 2x ausführt. ...
Das liegt wohl daran, dass das Skript die Änderungen im Speicher des Zielprogramms nicht zurückdreht. Bei zweiten Aufruf führen die geänderten Adressen dann ins Chaos. Nach Wiederherstellung des ursprünglichen Codes wäre vielleicht auch ein VirtualFreeEx() möglich.

Die ursprünglich verwendete Sprache war wohl nicht AHK, oder?

User avatar
pizzapizze
Posts: 44
Joined: 08 May 2019, 15:38

Re: Assault Cube FPS Overlay

Post by pizzapizze » 16 Jan 2021, 07:20

@just me
Danke für den Tip.
(Ich hab übrigens nicht so viel erfahrung in FPS Zählern, Grafik API's etc..)

Achso, und das Script habe ich nur mit AHK gemacht (Ja, es wäre bei einer low level Sprache wahrscheinlich viel effizienter gewesen xD)

just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Assault Cube FPS Overlay

Post by just me » 16 Jan 2021, 10:29

Nichts für ungut, wegen der "Byte-Arrays" dachte ich, die ursprüngliche Sprache sei nicht AHK. ;)

User avatar
pizzapizze
Posts: 44
Joined: 08 May 2019, 15:38

Re: Assault Cube FPS Overlay

Post by pizzapizze » 17 Jan 2021, 09:28

So, Ich hab jetzt das besagte Problem beseitigt, und noch ein bisschen extra Schnickschnack hinzugefügt.

just me
Posts: 9424
Joined: 02 Oct 2013, 08:51
Location: Germany

Re: Assault Cube FPS Overlay

Post by just me » 18 Jan 2021, 09:27

Gut! ;)

Post Reply

Return to “Skripte und Funktionen”