Jump to content

Threads


UEZ
 Share

Recommended Posts

I'm searching for a solution to get the threads of a process.

E.g. if I'm using the build-in ZIP functionality in my program it will use SHLWAPI.DLL as a thread.

Is it possible to get such kind of information from a process? :)

With Process Explorer you can see such kind of information (TID).

UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

I'm searching for a solution to get the threads of a process.

E.g. if I'm using the build-in ZIP functionality in my program it will use SHLWAPI.DLL as a thread.

Is it possible to get such kind of information from a process? :)

With Process Explorer you can see such kind of information (TID).

UEZ

Try:

_WinAPI_GetCurrentThread() or _WinAPI_GetCurrentThreadId().

EDIT:

If you want for a window you can try this function:

Func GetWindowThreadProcessId($hWindow)
    $RESULT = DllCall('user32.dll','int','GetWindowThreadProcessId','int',$hWindow,'dword*',0)
    Return $RESULT[0]
EndFunc
Edited by Andreik
Link to comment
Share on other sites

Here an example:

$zip_file = "c:\Test.zip"
$source_folder = "c:\Windows\Inf\"
Add_Folder_2_ZIP($source_folder, $zip_file)
Func Add_Folder_2_ZIP($folder, $zip_filename, $flag = 4)
    Local $hZIP, $ZIP_fileheader, $obj_ZIP, $obj_Folder, $obj_Copy, $obj_List
    Local $obj_FolderCount, $obj_ZIPCount
    $hZIP = FileOpen($zip_filename, 26)
    $ZIP_fileheader = Chr(80) & Chr(75) & Chr(5) & Chr(6) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0)
    FileWrite($hZIP, $ZIP_fileheader)
    FileClose($hZIP)
    $obj_ZIP = ObjCreate("Shell.Application")
    $obj_Folder = $obj_ZIP.NameSpace($folder)
    $obj_ZIP_Folder = $obj_ZIP.NameSpace($zip_filename)
    $obj_Copy = $obj_ZIP.NameSpace($zip_filename).CopyHere($obj_Folder.Items, $flag) ;add files to ZIP archive
    While 1
        Sleep(1000)
    WEnd
EndFunc   ;==>Add_Folder_2_ZIP

Note: you need to close the script from the icon in the taskbar!

If you run this script by F5, open e.g. Process Explorer and double click on AutoIt3.exe then select the Threads tab, you will see that SHLWAPI.DLL will be used for compression.

When the compression has ended then also the appropriate threads will be ended.

How can I read these threads out off the process (AutoIt3.exe)?

UEZ

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

monoceres

Hi! Thank you for UDF :)

I curiously enough for the NtQuerySystemInformation and the ZwQuerySystemInformation function. I'm trying, but unsuccessfully:

NtQuerySystemInformation

Global Const $STATUS_SUCCESS              = 0x00000000
Global Const $STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
Global $SystemProcessInformation = 5

$tSYSTEM_PROCESS_INFORMATION = DllStructCreate("long NextEntryOffset;byte Reserved1[52];ptr Reserved2[3];hwnd UniqueProcessId;" & _
                                               "ptr Reserved3;long HandleCount;byte Reserved4[4];ptr Reserved5[11];" & _
                                               "int PeakPagefileUsage;int PrivatePageCount;int64 Reserved6[6]")
If @error Then Exit

$aRet = DllCall("Ntdll.dll", "int", "NtQuerySystemInformation", _
                                    "int", $SystemProcessInformation, _
                                    "ptr", 0, _
                                    "int", 0, _
                                    "int*", 0)
If $aRet[0] <> $STATUS_INFO_LENGTH_MISMATCH Then Exit

ConsoleWrite("Buffer length: " & $aRet[4] & @LF)
$iSize = $aRet[4] + 1

$aRet = DllCall("Ntdll.dll", "int", "NtQuerySystemInformation", _
                                    "int", $SystemProcessInformation, _
                                    "ptr", DllStructGetPtr($tSYSTEM_PROCESS_INFORMATION), _
                                    "int", $iSize, _
                                    "int*", 0)

ConsoleWrite(Hex($aRet[0]) & @LF) ;Unknown erroroÝ÷ Ù¶pBç«É,¬µéú+«b¢Ûjëh×6Global Const $STATUS_SUCCESS              = 0x00000000
Global Const $STATUS_INFO_LENGTH_MISMATCH = 0xC0000004

Global Const $SystemProcessInformation = 5

Dim $hDLL = DllOpen("Ntdll.dll")

$tSYSTEM_PROCESS_INFORMATION = DllStructCreate("long NextEntryOffset;long NumberOfThreads;byte Reserved1[48];ptr Reserved2[3];" & _
                                               "hwnd UniqueProcessId;ptr Reserved3;long HandleCount;byte Reserved4[4];" & _
                                               "ptr Reserved5[11];int PeakPagefileUsage;int PrivatePageCount;int64 Reserved6[6]")
If @error Then Exit

$aRet = DllCall($hDLL, "int", "ZwQuerySystemInformation", _
                              "int", $SystemProcessInformation, _
                              "ptr", 0, _
                              "int", 0, _
                              "int*", 0)
If $aRet[0] <> $STATUS_INFO_LENGTH_MISMATCH Then Exit

ConsoleWrite("Buffer length: " & $aRet[4] & @LF)
$iSize = $aRet[4] + 1

$aRet = DllCall($hDLL, "int", "ZwQuerySystemInformation", _
                              "int", $SystemProcessInformation, _
                              "ptr", DllStructGetPtr($tSYSTEM_PROCESS_INFORMATION), _
                              "int", $iSize, _
                              "int*", 0)

ConsoleWrite(Hex($aRet[0]) & @LF) ;Unknown error

DllClose($hDLL)

Any suggestion? :)

Link to comment
Share on other sites

  • Moderators

You're not running those functions on a 64 bit OS are you?

Common sense plays a role in the basics of understanding AutoIt... If you're lacking in that, do us all a favor, and step away from the computer.

Link to comment
Share on other sites

OK, I found what the mean the 0xC0000005, STATUS_ACCESS_VIOLATION = 0xC0000005. And try again, but now script is crashes:

#include <WinAPI.au3>

Global Const $TOKEN_ADJUST_PRIVILEGES = 0x20

Global Const $STATUS_SUCCESS              = 0x00000000
Global Const $STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
Global Const $STATUS_ACCESS_VIOLATION     = 0xC0000005
Global $SystemProcessInformation = 5

Dim $count = 1

$tHandle = DllStructCreate("hwnd")

$aRet = DllCall("Advapi32.dll", "int", "OpenProcessToken", _
                                        "hwnd", _WinAPI_GetCurrentProcess(), _
                                        "int", $TOKEN_ADJUST_PRIVILEGES, _
                                        "ptr", DllStructGetPtr($tHandle))
$hToken = DllStructGetData($tHandle, 1)

$pLUID = DllStructCreate("int64")

$aRet = DllCall("Advapi32.dll", "int", "LookupPrivilegeValue", _
                                        "str", "", _
                                        "str", $SE_DEBUG_NAME, _
                                        "ptr", DllStructGetPtr($pLUID))

$tTOKEN_PRIVILEGES = DllStructCreate("int PrivilegeCount;byte Privileges[12]")
$tLUID_AND_ATTRIBUTES = DllStructCreate("int64 Luid;int Attributes", DllStructGetPtr($tTOKEN_PRIVILEGES, "Privileges"))

DllStructSetData($tTOKEN_PRIVILEGES, "PrivilegeCount", 1)
DllStructSetData($tLUID_AND_ATTRIBUTES, "Luid", DllStructGetData($pLUID, 1))
DllStructSetData($tLUID_AND_ATTRIBUTES, "Attributes", $SE_PRIVILEGE_ENABLED)

$aRet = DllCall("Advapi32.dll", "int", "AdjustTokenPrivileges", _
                                        "hwnd", $hToken, _
                                        "int", 0, _
                                        "ptr", DllStructGetPtr($tTOKEN_PRIVILEGES), _
                                        "int", DllStructGetSize($tTOKEN_PRIVILEGES), _
                                        "ptr", 0, _
                                        "ptr", 0)
MsgBox(0, "GetLastError", _WinAPI_GetLastError()) ;SHould be 0
ConsoleWrite("AdjustTokenPrivileges: " & $aRet[0] & @LF)                                        

_WinAPI_CloseHandle($hToken)

$tSYSTEM_PROCESS_INFORMATION = DllStructCreate("long NextEntryOffset;byte Reserved1[52];ptr Reserved2[3];hwnd UniqueProcessId;" & _
                                               "ptr Reserved3;long HandleCount;byte Reserved4[4];ptr Reserved5[11];" & _
                                               "int PeakPagefileUsage;int PrivatePageCount;int64 Reserved6[6]")
If @error Then Exit

$aRet = DllCall("Ntdll.dll", "int", "NtQuerySystemInformation", _
                                    "int", $SystemProcessInformation, _
                                    "ptr", 0, _
                                    "int", 0, _
                                    "int*", 0)
If $aRet[0] <> $STATUS_INFO_LENGTH_MISMATCH Then Exit

ConsoleWrite("Buffer length: " & $aRet[4] & @LF)
$iSize = $aRet[4]

$aRet = DllCall("Ntdll.dll", "int", "NtQuerySystemInformation", _
                                    "int", $SystemProcessInformation, _
                                    "ptr", DllStructGetPtr($tSYSTEM_PROCESS_INFORMATION), _
                                    "int", $iSize, _
                                    "int*", 0)

ConsoleWrite($aRet[0] & @LF)
Link to comment
Share on other sites

monoceres

Hi! Thank you for UDF :)

I curiously enough for the NtQuerySystemInformation and the ZwQuerySystemInformation function. I'm trying, but unsuccessfully:

Any suggestion? :)

Read the msdn documentation carefully: http://msdn.microsoft.com/en-us/library/ms724509.aspx

It says:

When the SystemInformationClass parameter is SystemProcessInformation, the buffer pointed to by the SystemInformation parameter should be large enough to hold an array that contains as many SYSTEM_PROCESS_INFORMATION structures as there are processes running in the system.

So you need a array. Like this:

Global Const $STATUS_SUCCESS              = 0x00000000
Global Const $STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
Global $SystemProcessInformation = 5

$SYSTEM_PROCESS_INFORMATION = "long NextEntryOffset;byte Reserved1[52];ptr Reserved2[3];hwnd UniqueProcessId;" & _
                                               "ptr Reserved3;long HandleCount;byte Reserved4[4];ptr Reserved5[11];" & _
                                               "int PeakPagefileUsage;int PrivatePageCount;int64 Reserved6[6]"
                                               
$SYSTEM_PROCESS_INFORMATION_WITHOUTNAME="long;byte[52];ptr[3];ptr;ptr;ulong;byte[4];ptr[11];int;int;int64[6];"



$structstring=""
For $i=0 To 1000; We assume there are less than 1000 processes on the system
    $structstring&=$SYSTEM_PROCESS_INFORMATION_WITHOUTNAME
Next

$spi_array=DllStructCreate($structstring)



$aRet = DllCall("Ntdll.dll", "int", "NtQuerySystemInformation", _
                                    "int", $SystemProcessInformation, _
                                    "ptr", DllStructGetPtr($spi_array), _
                                    "int", DllStructGetSize($spi_array), _
                                    "int*", 0)
                                    
$spi_element=DllStructCreate($SYSTEM_PROCESS_INFORMATION,DllStructGetPtr($spi_array))
While DllStructGetData($spi_element,"NextEntryOffset")<>0
    ConsoleWrite("UniqueProcessId: "&DllStructGetData($spi_element,"UniqueProcessId")&@CRLF)
    $spi_element=DllStructCreate($SYSTEM_PROCESS_INFORMATION,DllStructGetPtr($spi_element)+DllStructGetData($spi_element,"NextEntryOffset"))
WEnd

o:)

Edited by monoceres

Broken link? PM me and I'll send you the file!

Link to comment
Share on other sites

monoceres

Read the msdn documentation carefully

:) I have missed this :)

Thank you friend! Now I understand the concept. Now my solution:

Global Const $STATUS_SUCCESS              = 0x00000000
Global Const $STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
Global Const $STATUS_ACCESS_VIOLATION     = 0xC0000005

Global Const $SystemProcessInformation = 5

$tagSYSTEM_PROCESS_INFORMATION = "long NextEntryOffset;byte Reserved1[52];ptr Reserved2[3];hwnd UniqueProcessId;" & _
                                 "ptr Reserved3;long HandleCount;byte Reserved4[4];ptr Reserved5[11];" & _
                                 "int PeakPagefileUsage;int PrivatePageCount;int64 Reserved6[6]"

$aRet = DllCall("Ntdll.dll", "int", "NtQuerySystemInformation", _
                                    "int", $SystemProcessInformation, _
                                    "ptr", 0, _
                                    "int", 0, _
                                    "int*", 0)
If $aRet[0] <> $STATUS_INFO_LENGTH_MISMATCH Then Exit

$iSize = $aRet[4] ;Get the buffer size

$pBUFFER = DllStructCreate("byte[" & $iSize & "]")

$aRet = DllCall("Ntdll.dll", "int", "NtQuerySystemInformation", _
                                    "int", $SystemProcessInformation, _
                                    "ptr", DllStructGetPtr($pBUFFER), _
                                    "int", $iSize, _
                                    "int*", 0)
If $aRet[0] Then Exit MsgBox(16, "NtQuerySystemInformation error", Hex($aRet[0]))

$tSYSTEM_PROCESS_INFORMATION = DllStructCreate($tagSYSTEM_PROCESS_INFORMATION, DllStructGetPtr($pBUFFER))

While DllStructGetData($tSYSTEM_PROCESS_INFORMATION, "NextEntryOffset") <> 0
    ConsoleWrite(DllStructGetData($tSYSTEM_PROCESS_INFORMATION, "UniqueProcessId") & @LF)
    $tSYSTEM_PROCESS_INFORMATION = DllStructCreate($tagSYSTEM_PROCESS_INFORMATION, DllStructGetPtr($tSYSTEM_PROCESS_INFORMATION) + _
                                                   DllStructGetData($tSYSTEM_PROCESS_INFORMATION, "NextEntryOffset"))
WEnd

o:)

Link to comment
Share on other sites

@all: thanks a lot but is it possible also to get the start address name of a thread? See here-> click me

My idea is to control an external thread like in the zip example. I want to know when the external Zip process is finished.

I know that there is ZIP.UDF but maybe this is also possible...

Or any other idea? :)

UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Check if the .zip file is still in use...

#614355

Maybe add a TimerInit() TimerDiff() check to the while loop to have a timeout.

Best Regards

Edit: Wie du schon in deiner Signature so schön sagst :)

Edited by KaFu
Link to comment
Share on other sites

Check if the .zip file is still in use...

#614355

Maybe add a TimerInit() TimerDiff() check to the while loop to have a timeout.

Best Regards

Edit: Wie du schon in deiner Signature so schön sagst :)

Already tested, but it is not accurate on Windows Server 2008! Or count lines same situation. Trying to get modified time didn't also worked properly! Further access to the object is only working as long as zip isn't finished!

"Manchmal ist weniger mehr, aber hier anscheinend nicht" :)

UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Okay, hard to test without a Windows Server 2008 :) ...

How about a loop of FileGetSize() and if this didnt change for the last x seconds exit?

File size: it looks like this (another folder):

4671308

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

4884216

0

0

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

78077086

0

0

0

0

0

0

0

0

0

0

0

0

0

0

78077350 <- here the ext. zip ends

78077350

78077350

78077350

78077350

78077350

78077350

78077350

78077350

I think this is also not accurate enough! What if you have large files? Nice try :)

I thing the best way it to check when the external zip process has ended...

UEZ

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

This method seems to be working:

#include <WinAPi.au3>
#include <Array.au3>
Opt('MustDeclareVars', 1)

HotKeySet("{ESC}", "_Exit")
Global Const $TH32CS_SNAPTHREAD = 0x00000004
Global Const $THREADENTRY32 = "dword dwSize;dword cntUsage;dword th32ThreadId;dword th32OwnerProcessID;long tpBasePri;long tpDeltaPri;dword dwFlags;"

Global $zip_file = "c:\Test.zip"
Global $source_folder = @WindowsDir & "\Inf\"
Add_Folder_2_ZIP($source_folder, $zip_file)

Func Add_Folder_2_ZIP($folder, $zip_filename, $flag = 4)
    Local $hZIP, $ZIP_fileheader, $obj_ZIP, $obj_Folder, $obj_ZIP_Folder, $obj_Copy, $arr, $arr1, $arr2
    $hZIP = FileOpen($zip_filename, 26)
    $ZIP_fileheader = Chr(80) & Chr(75) & Chr(5) & Chr(6) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0) & Chr(0)
    FileWrite($hZIP, $ZIP_fileheader)
    FileClose($hZIP)
    
    $arr1 = _GetAllProcessThreads(@AutoItPID)
    $obj_ZIP = ObjCreate("Shell.Application")
    $obj_Folder = $obj_ZIP.NameSpace($folder)
    $obj_ZIP_Folder = $obj_ZIP.NameSpace($zip_filename)
    $obj_Copy = $obj_ZIP.NameSpace($zip_filename).CopyHere($obj_Folder.Items, $flag) ;add files to ZIP archive
    While 1
        Sleep(1000)
        $arr2 = _GetAllProcessThreads(@AutoItPID)
        $arr = _ArrayDiff($arr2, $arr1)
        If $arr[0] = 0 Then ExitLoop
    WEnd
EndFunc ;==>Add_Folder_2_ZIP

; -------------------------------------------------
; Function: _ArrayDiff
; Purpose: Returns an array of elements from $avArray1 that do not occur in $avArray2
; Syntax: _ArrayDiff(ByRef $avArray1, ByRef $avArray2 [, $f_CaseSense = 0])
; Where: $avArray1 = ByRef source array
;   $avArray2 = ByRef array to search for $avArray1 elements
;   $f_CaseSense (optional) = Case sensitivity as passed to StringInStr()
;   0 = not case sensitive, using the user's locale (default)
;   1 = case sensitive
;   2 = not case sensitive, using a basic/faster comparison
; Returns: On success returns an array with [0] = count containing any elements
;   from $avArray1 that do not occur in $avArray2
;   If all elements in $avArray1 occur in $avArray2 then [0] = 0
;   On Failure returns 1-element array [0] = 0 and sets @error.
; Author: PsaltyDS on www.autoitscript.com/forum
; Notes:    Similar to PHP array_diff function
; --------------------------------------------------
Func _ArrayDiff(ByRef $avArray1, ByRef $avArray2, $f_CaseSense = 0)
    Local $avRET[1] = [0], $sRET = ""

    If (IsArray($avArray1) = 0) Or (UBound($avArray1, 0) <> 1) Then Return SetError(1, 1, $avRET)
    If (IsArray($avArray2) = 0) Or (UBound($avArray2, 0) <> 1) Then Return SetError(1, 2, $avRET)

    Local $sArray2 = Chr(1) & _ArrayToString($avArray2, Chr(1)) & Chr(1)

    For $n = 0 To UBound($avArray1) - 1
    If Not StringInStr($sArray2, Chr(1) & $avArray1[$n] & Chr(1)) Then $sRET &= $avArray1[$n] & Chr(1)
    Next

    If StringLen($sRET) Then
    $sRET = StringTrimRight($sRET, 1)
    $avRET = StringSplit($sRET, Chr(1))
    EndIf

    Return $avRET
EndFunc ;==>_ArrayDiff


Func _GetAllProcessThreads($iPid) ; thanks to monoceres
    Local $call = DllCall("Kernel32.dll", "ptr", "CreateToolhelp32Snapshot", "dword", $TH32CS_SNAPTHREAD, "dword", 0)
    Local $handle = $call[0]
    Local $RetArr[1]
    Local $te32 = DllStructCreate($THREADENTRY32)
    DllStructSetData($te32, "dwSize", DllStructGetSize($te32))
    $call = DllCall("Kernel32.dll", "int", "Thread32First", "ptr", $handle, "ptr", DllStructGetPtr($te32))
    If DllStructGetData($te32, "th32OwnerProcessID") = $iPid Then _GetAllThreads_ArrHelper($RetArr, $te32)
    Do
    $call = DllCall("Kernel32.dll", "int", "Thread32Next", "ptr", $handle, "ptr", DllStructGetPtr($te32))
    If Not $call[0] Then ExitLoop
    If DllStructGetData($te32, "th32OwnerProcessID") = $iPid Then _GetAllThreads_ArrHelper($RetArr, $te32)
    Until True And False
    _ArrayDelete($RetArr, 0)
    _WinAPI_CloseHandle($handle)
    Return $RetArr
EndFunc ;==>_GetAllProcessThreads

Func _GetAllThreads_ArrHelper(ByRef $arr, $TE32_Struct) ; thanks to monoceres
    Local $ub = UBound($arr)
    ReDim $arr[$ub + 1]
    $arr[$ub] = DllStructGetData($TE32_Struct, "th32ThreadId")
EndFunc ;==>_GetAllThreads_ArrHelper

Func _Exit()
    Exit
EndFunc

Need more tests on different windows operating systems.... Maybe you can help :)

UEZ

Edited by UEZ

Please don't send me any personal message and ask for support! I will not reply!

Selection of finest graphical examples at Codepen.io

The own fart smells best!
Her 'sikim hıyar' diyene bir avuç tuz alıp koşma!
¯\_(ツ)_/¯  ٩(●̮̮̃•̃)۶ ٩(-̮̮̃-̃)۶ૐ

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...