1. Dashboard
  2. Mitglieder
    1. Letzte Aktivitäten
    2. Benutzer online
    3. Team
    4. Mitgliedersuche
  3. Forenregeln
  4. Forum
    1. Unerledigte Themen
  • Anmelden
  • Registrieren
  • Suche
Alles
  • Alles
  • Artikel
  • Seiten
  • Forum
  • Erweiterte Suche
  1. AutoIt.de - Das deutschsprachige Forum.
  2. Mitglieder
  3. TheDeath24

Beiträge von TheDeath24

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 9. März 2018 um 14:02
    Zitat von alpines

    Das ist auch der Grund warum die Memory-UDFs immer als 1. Index im PointerOffset-Array eine 0 haben wollen.

    Es wird eine einfache For-Schleife verwendet und es wird an baseaddress + offset[0] (dieser ist ja 0) gelesen und dann wird das Ergebnis genommen

    und im nächsten Schleifendurchlauf wird an der stelle ergebnis + offset[1] gelesen und immer so weiter.

    So in etwa?

    AutoIt
    #AutoIt3Wrapper_UseX64=y
    #RequireAdmin
    
    #include <ProcessConstants.au3>
    #include <WinAPI.au3>
    
    Dim $aiOffsets[6]
    $aiOffsets[0] = 0x0
    $aiOffsets[1] = 0x18
    $aiOffsets[2] = 0x20
    $aiOffsets[3] = 0x28
    $aiOffsets[4] = 0x10
    $aiOffsets[5] = 0x20
    
    Local $iPid = ProcessExists("LCore.exe")
    Local $hProcess = _WinAPI_OpenProcess($PROCESS_ALL_ACCESS, True, $iPid)
    
    Local $iBase = 0x007FF73117D9C8
    
    
    Local $tNewStruct = DllStructCreate("INT_PTR")
    Local $pNewStruct = DllStructGetPtr($tNewStruct)
    Local $iRead = 0
    
    
    For $i=0 to 5
       _WinAPI_ReadProcessMemory($hProcess, $iBase, $pNewStruct, DllStructGetSize($tNewStruct), $iRead)
       $ergebnis=DllStructGetData($tNewStruct, 1)
       MsgBox(0, "Ausgelesene Adresse", hex($ergebnis))
       $iBase='0x' & Hex($ergebnis+$aiOffsets[$i])
       MsgBox(0, "Neue Adresse", $iBase)
    Next
    Alles anzeigen

    Konnte ich noch nicht testen bin gerade unterwegs.

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 9. März 2018 um 13:04

    Ich habe gerade nochmal mein Headset angemacht. Vielleicht verdeutlicht es das besser.

    Wie im Screenshot zu sehen ist mein Akkustand bei 100%.

    Basepointer: 7FF73117D9C8

    Mit der hilfe von dir (alpines) kann ich die nächste Adresse auslesen -> 59F7D1E590.

    Nun müsste ich das offset von 18 drauf rechnen und zu nächsten Adresse kommen.

    Die lautet 59F85EFCA0.

    Vielleicht verständlicher so?

    Dateien

    LCore CE.png 13,13 kB – 0 Downloads
  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 9. März 2018 um 12:58
    Zitat von alpines

    Du kannst die Zahlen auch hexadezimal draufaddieren, AutoIt macht keinen Unterschied zwischen 0x10 + 0x10 und 16 + 16.

    Die Offsets in CE sind unter Garantie in hexadezimal, also verwende 0x.

    So habe ich das auch gemacht. Komme aber nicht drauf.

    AutoIt
    ConsoleWrite('0x' & Hex(0x59F7D1E590 + 0x18) & @CRLF)

    So sollte es dann richtig sein komm aber auf das gleiche Ergebnis wie Bitnugger.

    Ausgabe: 0x00000059F7D1E5A8

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 9. März 2018 um 12:55
    Zitat von Bitnugger
    AutoIt
    ConsoleWrite('0x' & Hex(0x59F7D1E590 + 18) & @CRLF)

    Ausgabe: 0x00000059F7D1E5A2

    Vielleicht habe ich mich oben falsch ausgedrückt.

    Aber rauskommen müsste das 59F85EFCA.

    Als erste Adresse bekomme ich 59F7D1E590 und der Pointer offset ist 18.
    Laut CE ist die nächste Adresse 59F85EFCA. Doch da weis ich nicht wie man rechnerisch hin kommt.

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 9. März 2018 um 12:44

    Sorry das ich nochmal störe. Ich hab eigentlich gedacht das das auf addieren einfach wäre und wollte die zahlen als dezimal addieren und in hex zurück wandeln.

    Geht aber nicht. Bekomme nie die nächste richtige Adresse raus.

    59F7D1E590+18 = 59F85EFCA

    wie kommt man darauf?

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 9. März 2018 um 12:03
    Zitat von alpines

    Kannst du das Script mal compilen und so ausführen? Ich weiß nicht ob der Präprozessor reicht um SciTE zu sagen, dass AutoIT die 64-Bit Variante nehmen soll.

    Ansonsten probier mal statt int als Parameter PTR, INT_PTR oder LONG_PTR (respektive UINT_PTR, ULONG_PTR).

    Int ist in AutoIt nämlich 32-Bit groß und du liest ja in einer 64-Bit Anwendung.

    Aber wieso zeigt dir CE nicht mehr den Prozentwert am Ende an? Sicher, dass du den Basepointer erwischt hast? War er in der Liste denn grün?

    Tut mir leid stimmt. Ja der Wert ist ja 64bit. Das war die Lösung. DllStructCreate("INT_PTR")
    Es wird nur keine Wert angezeigt weil das Headset nicht an war. Dann ist kein Wert verfügbar.
    Aber ja der Basepointer war grün. Und jetzt geht´s auch. Super vielen Dank!
    jetzt werde ich die einzelnen Offset rauf rechnen können und am Ende Wert auslesen.

    :party:

    Hier nochmal ein Screenshot wo es geht.

    Bilder

    • Ausgelesen.jpg
      • 15,28 kB
      • 420 × 172
  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 9. März 2018 um 11:44
    Zitat von alpines

    Ich hab dir mal ein kleines Beispiel zusammengeschrieben.

    Starte die tgt.exe und dann trag die Adresse in dem anderen Script ein, das beschreibt die Variable und liest sie anschließend aus.

    Viele Dank, das ist ja super!

    Im groben Funktioniert alles ,aber ich bekomme nicht die ganze Adresse raus.

    AutoIt
    #AutoIt3Wrapper_UseX64=y
    #RequireAdmin
    
    #include <ProcessConstants.au3>
    #include <WinAPI.au3>
    
    Local $tStruct = DllStructCreate("int")
    Local $pStruct = DllStructGetPtr($tStruct)
    DllStructSetData($tStruct, 1, 15)
    
    Local $iPid = ProcessExists("LCore.exe")
    Local $hProcess = _WinAPI_OpenProcess($PROCESS_ALL_ACCESS, True, $iPid)
    
    ;~ Local $iBase = 0x007FF73117D9C8
    Local $iBase = 0x007FF73117D9C8
    ;~ Local $iWritten = 05
    
    ;~ _WinAPI_WriteProcessMemory($hProcess, $iBase, $pStruct, DllStructGetSize($tStruct), $iWritten)
    
    ;~ MsgBox(64, "Write", "Es wurden " & $iWritten & " Bytes geschrieben.")
    
    Local $tNewStruct = DllStructCreate("int")
    Local $pNewStruct = DllStructGetPtr($tNewStruct)
    Local $iRead = 0
    
    _WinAPI_ReadProcessMemory($hProcess, $iBase, $pNewStruct, DllStructGetSize($tNewStruct), $iRead)
    
    MsgBox(64, "Read", "Es wurden " & $iRead & " Bytes gelesen und der Integer hält den Wert: " & Hex(DllStructGetData($tNewStruct, 1)))
    ;~ MsgBox(64, "Read", "Es wurden " & $iRead & " Bytes gelesen und der Integer hält den Wert: " & DllStructGetData($tNewStruct, 1))
    
    _WinAPI_CloseHandle($hProcess)
    Alles anzeigen

    Im Anhang habe ich mal 2 Screenshots gemacht.

    Der erste ist von dem Wert der Ausgelesen wird über dein Script.
    Allerdings fehlt dort irgendwie die 59 vorne. Habe ich was übersehen ?

    Bilder

    • Ausgelesen.jpg
      • 14,43 kB
      • 469 × 172

    Dateien

    CE Zeigt auf andere Adresse.jpg 44,82 kB – 0 Downloads
  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 8. März 2018 um 23:59

    Hey alpines,

    ja genau da bin ich auch gerade :). Leider funktioniert es damit auch nicht.
    Ich mach erstmal Schluss für heute. Gute Nacht.

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 8. März 2018 um 23:33
    Zitat von alpines

    Dein Buffer ist auch nicht richtig. Da musst du den Pointer zu einem DllStruct übergeben.

    Der Versuch mit Nomad scheint soweit richtig zu sein, aber die UDF ist leider nicht 64-bit kompatibel.

    Es gibt bestimmt andere Memory-UDFs die 64-Bit kompatibel sind.

    DllStruct.... da bin ich erstmal raus. noch nichts mit gemacht.
    Danke für deine Hilfe.
    Ich schau mal ob ich andere UDF´s finde die mit 64bit arbeiten.

    Falls jemand eine Memory UDF kennt die 64bit kompatibel ist dann wäre ich ihm sehr verbunden wenn er dies schreib.

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 8. März 2018 um 22:32

    Ich habe jetzt mal eben geschaut und hätte gedacht das ich so zumindest den ersten wert bekommen würde und dann so wie du geschrieben hast den offset drauf rechnen aber ich bekomme keinen Wert zurück.

    AutoIt
    #RequireAdmin
    #Include <WinAPI.au3>
    Local $pBuffer
    
    $processId = ProcessExists("LCore.exe")
    $processHandle = _WinAPI_OpenProcess(0x1000, 0, $processId)
    _WinAPI_ReadProcessMemory($processHandle, 0xF73117D9C8, $pBuffer, 4, 4)
    MsgBox(0,"",$pBuffer)
  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 8. März 2018 um 22:24

    Hi,

    ich habe nun nicht ganz verstanden welche Adresse ich als Baseadresse nehmen soll?

    AutoIt
    #RequireAdmin
    #include <NomadMemory.au3>
    
    $szProcessName = "LCore.exe"
    $dwBaseAddress = 0xF73117D9C8
    Dim $a_dwOffset[6] = [0x0, 0x18, 0x20, 0x28, 0x10, 0x20]
    
    While Sleep(10)
        $dwProcessId = ProcessExists($szProcessName)
        If $dwProcessId > 0 Then
            $hProcess = _MemoryOpen($dwProcessId)
            If Not @error Then
                $dwValue = _MemoryPointerRead($dwBaseAddress, $hProcess, $a_dwOffset)
                If IsArray($dwValue) Then MsgBox(0, "", "Pointer: " & $dwValue[0] & ", Value: " & $dwValue[1])
                _MemoryClose($hProcess)
            EndIf
        EndIf
     WEnd
    Alles anzeigen

    So bekomme ich leider auch keinen Wert.

  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 8. März 2018 um 21:59

    Hallo alpines,

    nein keine Schandtaten! Ich möchte wirklich nur eine Akkuanzeige auf meinem Desktop haben, weil es mich wirklich nervt und Logitech es nicht schafft ein Widget dafür an zu bieten.

    Im Anhang befindet sich der gewünschte Screenshot.

    Ich muss noch dazu sagen ich habe mich in den letzten 2 Tagen das erste mal richtig mit Pointern auseinander gesetzt. Ich finde das sehr interessant und werde mich wohl mehr damit auseinandersetzen aber wollte erstmal was nützliches mit meinen ersten Ergebnissen anfangen. Vielleicht hilft der der Screenshot.

    Vielen Dank schon mal für deine Hilfe.

    Das mit der WinAPI probiere ich auch mal aus. Danke

    Dateien

    LCore.jpg 46,84 kB – 0 Downloads
  • Basepointer auslesen mit NomadMemory.au3 oder WinAPI.au3

    • TheDeath24
    • 8. März 2018 um 21:35

    Hallo zusammen,

    ich wollte mir gerne eine Akkuanzeige für mein Wireless Headset (G933 Logitech) basteln. Ich habe den Basepointer ausgelesen und möchte nun mit AutoIT den Wert auslesen.

    Dabei bin ich auf die NomadMemory.au3 UDF gestoßen. Leider bekomme ich da den Wert nicht ausgelesen. Vielleicht kann mir da jemand weiterhelfen?

    Ich habe das Script von jemand anderen aus einem anderen Forum übernommen und nur meine Sachen eingefügt.


    AutoIt
    #AutoIt3Wrapper_UseX64=n
    #RequireAdmin
    #include <NomadMemory.au3>
    
    $szProcessName = "LCore.exe"
    $dwBaseAddress = 0x7FF73117D9C8
    Dim $a_dwOffset[5] = [18, 20, 28, 10, 20]
    
    While Sleep(10)
        $dwProcessId = ProcessExists($szProcessName)
        If $dwProcessId > 0 Then
            $hProcess = _MemoryOpen($dwProcessId)
            If Not @error Then
                $dwValue = _MemoryPointerRead($dwBaseAddress, $hProcess, $a_dwOffset)
                If IsArray($dwValue) Then MsgBox(0, "", "Pointer: " & $dwValue[0] & ", Value: " & $dwValue[1])
                _MemoryClose($hProcess)
            EndIf
        EndIf
    WEnd
    Alles anzeigen
  • PixelChecksum lässt sich nicht vergleich?

    • TheDeath24
    • 1. März 2018 um 18:13
    Zitat von Zeitriss

    Hi,

    Das Problem liegt bei deiner If-Bedingung.

    Siehe mal hier:

    mfg

    Zeitriss

    Sorry ja, das habe ich schon mal durch ein andere gebracht.

    Zitat von Tuxedo

    Bevor du dich auf die Suche nach diesem Fehler machst, würde ich erstmal den grundlegenden

    Aufbau des Scripts korrigieren, denn dein Script wird zwangsweise früher oder später in einem

    Speicher-Überlauf enden.

    Eine Endlosschleife in einer Funktion die immer wieder aufgerufen wird ist niemals eine gute Idee.

    Es ist ein beispiel Script. Das man das so nicht macht ist mir klar war aber zum testen eben mal so gemacht.

  • PixelChecksum lässt sich nicht vergleich?

    • TheDeath24
    • 1. März 2018 um 15:29

    Hallo zusammen,

    ich würde gerne ein kleines Bild analysieren und schauen ob sich was verändert hat.

    Simple wollte ich dafür PixelChecksum benutzen. Doch leider wenn ich versuche das in einer If-Abfrage zumachen wird kein Unterschied erkannt obwohl in der Msgbox unterschiedliche Werte angezeigt werden.

    AutoIt
    #include <MsgBoxConstants.au3>
    
    AutoItSetOption( "PixelCoordMode" , 2)
    HotKeySet("{TAB}", main)
    
    Global $iCheckSum
    
    While 1
       sleep(10)
    WEnd
    
    Func main()
    
    $iCheckSum = PixelChecksum(0, 0, 50, 50)
    mainschleife()
    
    EndFunc
    
    Func mainschleife()
    
    While 1
        checkchange()
        Sleep(100)
     WEnd
    
    EndFunc
    
    Func checkchange()
    Local $now =PixelChecksum(0, 0, 50, 50)
    MsgBox(0, "Test", $iCheckSum &"="&$now)
    
    if Not $now = $iCheckSum Then
       $iCheckSum = PixelChecksum(0, 0, 50, 50)
       MsgBox(0, "Test", "Geht!")    
    Endif
    
    EndFunc
    Alles anzeigen
  • Logitech LED - Dll Call

    • TheDeath24
    • 28. Februar 2018 um 00:23
    Zitat von BugFix

    Das gabs doch schon mal, vielleicht hilft dir das:

    https://autoit.de/index.php?thre…0838#post360838

    Super danke dir. Das letzte mal als ich nach dem Thema gesucht hatte war der Link nicht zu erreichen. Ist nun aber auch schon ein bisschen her.
    Jetzt geht der Link!

  • Logitech LED - Dll Call

    • TheDeath24
    • 27. Februar 2018 um 11:24

    Hallo nochmal,

    ich habe nun ein bisschen rum probiert. Soweit funktionierten die Einzelnen Funktionen. Leider bricht er aber immer nach dem ersten setzen der Farbe oder Effekts das ganze Script ab oder Überspringt den Rest.
    Was genau passiert weis ich leider nicht. Kann mir da jemand weiterhelfen?

    So ist es im Fehler Fall:

    _Logiledinit("C:\Program Files\Logitech Gaming Software\SDK\LED\x86\LogitechLed.dll")

    _LogiLedSaveCurrentLighting()

    _LogiLedSetLighting()

    ### Bis hier hin gehts aber danach leider nicht mehr ###

    _LogiLedSetLightingForKeyWithKeyName()

    _LogiLedRestoreLighting()

    _LogiLedShutDown()

    Liegt das an Autoit oder einfach an der DLL?

    AutoIt
    _Logiledinit("C:\Program Files\Logitech Gaming Software\SDK\LED\x86\LogitechLed.dll")
    _LogiLedSaveCurrentLighting()
    _LogiLedSetLighting()
    _LogiLedSetLightingForKeyWithKeyName()
    _LogiLedRestoreLighting()
    _LogiLedShutDown()
    
    
    Func _Logiledinit($path="")
    if FileExists ($path) Then
       Global Const $hDLL = DllOpen($path)
       If @error Then
          MsgBox(16, "", "DLL kann nicht geladen werden! Programm wird beendet!")
          Exit
       Else
          $aReturn = DllCall($hDLL, "bool", "LogiLedInit")
          if not $aReturn[0] Then
             MsgBox(0, "Error", "LogiLedinitalisierung konnte nicht durchgeführt werden!")
          EndIf
       EndIf
    
    Else
       MsgBox(0, "Error", "DLL konnte nicht gefunden werden!")
    EndIf
    EndFunc
    
    Func _LogiLedSaveCurrentLighting()
    $aReturn = DllCall($hDLL, "bool", "LogiLedSaveCurrentLighting")
    if @error Then
       MsgBox(0, "", "LogiLedSaveCurrentLighting konnte nicht aufgerufen werden!")
    ;~ Else
    ;~    MsgBox(0, "", $aReturn[0])
    EndIf
    EndFunc
    
    Func _LogiLedRestoreLighting()
    $aReturn = DllCall($hDLL, "bool", "LogiLedRestoreLighting")
    if @error Then
       MsgBox(0, "", "LogiLedRestoreLighting konnte nicht aufgerufen werden!")
    ;~ Else
    ;~    MsgBox(0, "", $aReturn[0])
    EndIf
    EndFunc
    
    Func _LogiLedSetLighting()
      $aReturn = DllCall($hDLL, "bool", "LogiLedSetLighting", "int", 100, "int", 0, "int", 0)
      If @error Then
          MsgBox(0, "", "Methode konnte nicht aufgerufen werden!")
    ;~    Else
    ;~       MsgBox(0, "", $aReturn[0])
       EndIf
    EndFunc
    
    
    Func _LogiLedSetLightingForKeyWithKeyName()
      $aReturn = DllCall($hDLL, "bool", "LogiLedSetLightingForKeyWithKeyName", "int", 0x58 ,"int", 100, "int", 100, "int", 0)
      If @error Then
          MsgBox(0, "", "LogiLedSetLightingForKeyWithKeyName konnte nicht aufgerufen werden!")
    ;~    Else
    ;~       MsgBox(0, "", $aReturn[0])
       EndIf
    EndFunc
    
    Func _LogiLedShutDown()
    $aReturn = DllCall($hDLL, "bool", "LogiLedShutDown")
    If @error Then
       MsgBox(0, "", "LogiLedShutDown konnte nicht aufgerufen werden!")
    ;~ Else
    ;~    MsgBox(0, "", $aReturn[0])
    EndIf
    DllClose($hDLL)
    EndFunc
    Alles anzeigen
  • Logitech LED - Dll Call

    • TheDeath24
    • 25. Februar 2018 um 19:24

    Hey Bitnugger vielen Dank für den Hinweis. Werde ich nachher mal testen.

    Zitat von Bitnugger

    Wenn du mit 64-Bit Programmen oder Dlls hantieren möchtest, dann musst du AutoIt das auch sagen...

    AutoIt
    #AutoIt3Wrapper_UseX64=y
    
    Local $hDLL = DllOpen(@ScriptDir & "\LogitechLed.dll")
    If @error Then Exit MsgBox(16, @ScriptName, "LogitechLed.dll kann nicht geladen werden - das Script wird beendet!")
    Local $aReturn = DllCall($hDLL, "bool", "LogiLedInit")
    If @error Then MsgBox(0, "", "Methode konnte nicht aufgerufen werden!")
    DllClose($hDLL)
    Exit

    Hey das Tool ist ja super, Danke. Gleich in meine Tool Library

    aufgenommen. :thumbup:

    Zitat von NO1 :-)

    Kein Problem :)

    Ja bei mir hat der DLLCall auch nicht funktioniert, deswegen wollt ich den Punkt 4 wissen...

    Und für DLLs gibts ein hilfreiches Tool names DLL Export Viewer.zip, mit dem du alle in der DLL vorhandenen Funktionen anschauen kannst ;)... ich hängs mal an

  • Logitech LED - Dll Call

    • TheDeath24
    • 25. Februar 2018 um 18:51

    Hey N01,

    ich habe den Fehler gefunden. Es lag an der 64bit dll mit der 32bit funktioniert es.

    Super vielen Dank!

  • Logitech LED - Dll Call

    • TheDeath24
    • 25. Februar 2018 um 18:42
    Zitat von NO1 :-)

    Hi :)

    1. In deiner Dokumentation steht bool LogiLedInit(), deswegen darfst du in deinem Funktionsaufruf auch keinen Integer ("int") verwenden, sondern musst "BOOL" benutzen.

    2. Der Aufruf von DLLCall gibt ein Array zurück -> Das heißt du, wenn du in der MessageBox den Rückgabewert haben willst, musst du DLLCall()[0] verwenden, also so zum Beispiel:

    AutoIt
    $aCall = DllCall($DLLOpen,"bool","LogiLedInit")
    MsgBox(0, "", $aCall[0])

    3. Da "bool" auch ein Integer ist und somit false als 0 und true als 1 gehandhabt wird, wirst du niemals ein true oder false zurückbekommen.

    4. Teste mal, ob der DLLCall überhaupt erfolgreich ist.

    AutoIt
    $hDLL = DllOpen(@ScriptDir & "\LogitechLed.dll")
    If @error Then
        MsgBox(16, "", "DLL kann nicht geladen werden! Programm wird beendet!")
        Exit
    EndIf
    $aReturn = DllCall($hDLL, "bool", "LogiLedInit")
    If @error Then MsgBox(0, "", "Methode konnte nicht aufgerufen werden!")
    DllClose($hDLL)
    Exit

    Lg NO1 :)

    Alles anzeigen

    Hey NO1,

    vielen dank für deine Hilfestellung. Das mit Bool da hätte ich echt mal selber drauf kommen können. :(
    Leider funktioniert der DLLCall nicht. Habe ihn eins zu eins übernommen.

    Kann man da jetzt überhaupt noch weiter nach einer Ursache gucken?
    Ich mein die Dokumentation sagt ja es soll so funktionieren.

Spenden

Jeder Euro hilft uns, Euch zu helfen.

Download

AutoIt Tutorial
AutoIt Buch
Onlinehilfe
AutoIt Entwickler
  1. Datenschutzerklärung
  2. Impressum
  3. Shoutbox-Archiv
Community-Software: WoltLab Suite™