Advertisement
Firex

AutoIt AutThread UDF

Jul 14th, 2014
205
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
AutoIt 16.98 KB | None | 0 0
  1. #include-once
  2. #include <Memory.au3>
  3.  
  4. Global $AUTOITTHREAD = False
  5. ; *
  6. #OnAutoItStartRegister "__AT_Init"
  7.  
  8. ; #INDEX# =======================================================================================================================
  9. ; Title .........: AutThread - Concept!
  10. ; AutoIt Version : >=3.3.8.1
  11. ; Language ......: Russian
  12. ; Description ...: Псевдо-потоки в AutoIt!
  13. ; ===============================================================================================================================
  14.  
  15. ; #CURRENT# =====================================================================================================================
  16. ;_AutThread_Create
  17. ;_AutThread_Kill
  18. ;_AutThread_IsAlive
  19. ;_AutThread_ParentThreadPID
  20. ;_AutThread_SelfIsThread
  21. ;_AutThread_ErrorCallbackRegister
  22. ; ===============================================================================================================================
  23.  
  24. ; #INTERNAL_USE_ONLY#============================================================================================================
  25. ;__AT_Init
  26. ;__AT_Main
  27. ;__AT_VarType
  28. ;__AT_ReinterpretCast
  29. ;__AT_TagSize
  30. ;__AT_OpenProcess
  31. ;__AT_ReadProcessMemory
  32. ;__AT_WriteProcessMemory
  33. ;__AT_CloseHandle
  34. ;__AT_OnExit
  35. ; ===============================================================================================================================
  36.  
  37.  
  38.  
  39.  
  40. ; #FUNCTION# =================================================================================================
  41. ; Name...........: _AutThread_Create
  42. ; Description ...: Запускает функцию параллельно работе основного скрипта
  43. ; Syntax.........: _AutThread_Create( $sFunc [, $iRetSize [, $sVarRet [, $aArgs = 0 [, $sCmdLine = "" ]]]] )
  44. ; Parameters ....: $sFunc - Имя функции
  45. ;                  $iRetSize - Сколько выделить памяти под ответ (в байтах) [ Optional ]
  46. ;                  $sVarRet - Имя переменной в которую поместится Return (Global only) [ Optional ]
  47. ;                  $aArgs - Массив аргументов которые передадутся функции [ Optional ]
  48. ;                  $sCmdLine - CMDLINE [ Optional ]
  49. ; Return values .: ( ThreadIndex ) Or ( False and set @Error )
  50. ; Author ........: Firex
  51. ; Remarks .......:
  52. ; ============================================================================================================
  53. Func _AutThread_Create( $sFunc, $iRetSize = 0, $sVarRet = "", $aArgs = 0, $sCmdLine = "" )
  54.     Local $iAutThread, $__aArgs, $Idx, $Jix, $hMemory, $tagAutThread, $tAutThread, $iTmp, $iFunc, $iErr = 0, $iArgs = 0
  55.     ; ---
  56.     $iFunc = StringLen( $sFunc )
  57.     If Not $iFunc Then _
  58.         Return SetError( 1, 0, 0 )
  59.  
  60.     $tagAutThread = $__tagAutThreadInfo & "char[" & $iFunc & "]; "
  61.     ; ---
  62.     If IsArray( $aArgs ) And UBound( $aArgs ) = $aArgs[0] + 1 Then
  63.         Dim $__aArgs[ $aArgs[0] + 1 ][3]
  64.         For $Idx = 1 To $aArgs[0] Step 1
  65.             $tagAutThread &= $__tagAutThreadBody
  66.             $__aArgs[$Idx][0] = __AT_VarType( VarGetType( $aArgs[$Idx] ) )
  67.             If $aArgs[$Idx] == "" Or IsArray( $aArgs[$Idx] ) Then
  68.                 $__aArgs[$Idx][2] = Binary( '0x00' )
  69.                 $__aArgs[$Idx][1] = 1
  70.             Else
  71.                 $__aArgs[$Idx][2] = StringToBinary( String( $aArgs[$Idx] ) )
  72.                 $__aArgs[$Idx][1] = BinaryLen( $__aArgs[$Idx][2] )
  73.                 If $__aArgs[$Idx][1] > 16581375 Then _
  74.                     $__aArgs[$Idx][1] = 16581375 ;~byte iArg[3]; in $__tagAutThreadBody
  75.  
  76.                 $tagAutThread &= "byte[" & $__aArgs[$Idx][1] & "]; "
  77.             EndIf
  78.         Next
  79.         $iArgs = $aArgs[0]
  80.     EndIf
  81.     ; ---
  82.     $iTmp = __AT_TagSize( $tagAutThread )
  83.     If $iTmp - $__tagATI_Size < $iRetSize Then _
  84.         $iTmp = $iRetSize
  85.  
  86.     $hMemory = _MemGlobalAlloc( $iTmp, 0x0040 ) ;$GPTR
  87.     If $hMemory <> 0 Then
  88.         $tAutThread = DllStructCreate( $tagAutThread, $hMemory )
  89.         If Not @Error Then
  90.             DllStructSetData( $tAutThread, "iRet", False )
  91.             DllStructSetData( $tAutThread, "iArgs", $iArgs )
  92.             DllStructSetData( $tAutThread, "iFunc", $iFunc )
  93.             DllStructSetData( $tAutThread, "iBuf", $iTmp )
  94.             DllStructSetData( $tAutThread, 5, $sFunc )
  95.             For $Idx = 1 To $iArgs Step 1
  96.                 $iTmp = 6 + ( ( $Idx - 1 ) * 3 )
  97.                 For $Jix = 0 To 2 Step 1
  98.                     DllStructSetData( $tAutThread, $iTmp + $Jix, $__aArgs[$Idx][$Jix] )
  99.                 Next
  100.             Next
  101.         Else
  102.             $iErr = 2
  103.         EndIf
  104.     Else
  105.         $iErr = 1
  106.     EndIf
  107.     ; ---
  108.     If Not $iErr Then
  109.         $iAutThread = Run( @ScriptFullPath & ' /AutThreadInfo="' & @AutoItPID & ',' & Int( $hMemory ) & '" ' & $sCmdLine, @ScriptDir )
  110.         If Not $iAutThread Then _
  111.             $iErr = 3
  112.     EndIf
  113.     If Not $iErr Then
  114.         $Idx = $__aAutThreads[0][0] + 1
  115.  
  116.         ReDim $__aAutThreads[ $Idx + 1 ][4]
  117.         $__aAutThreads[0][0] = $Idx
  118.         $__aAutThreads[$Idx][0] = $hMemory
  119.         $__aAutThreads[$Idx][1] = $tAutThread
  120.         $__aAutThreads[$Idx][2] = $iAutThread
  121.         $__aAutThreads[$Idx][3] = $sVarRet
  122.  
  123.         $iAutThread = $Idx
  124.     Else
  125.         _MemGlobalFree( $hMemory )
  126.         $iAutThread = 0
  127.     EndIf
  128.     ; ---
  129.     Return SetError( $iErr + 1, 0, $iAutThread )
  130. EndFunc
  131.  
  132. ; #FUNCTION# =================================================================================================
  133. ; Name...........: _AutThread_Kill
  134. ; Description ...: Прекращает выполнение параллельной скрипту функции
  135. ; Syntax.........: _AutThread_Kill( $iAutThread )
  136. ; Parameters ....: $iAutThread - ThreadIndex
  137. ; Return values .: ( True ) Or ( False And set @Error )
  138. ; Author ........: Firex
  139. ; Remarks .......:
  140. ; ============================================================================================================
  141. Func _AutThread_Kill( $iAutThread )
  142.     If $__aAutThreads[0][0] < $iAutThread Then _
  143.         Return SetError( 1, 0, False )
  144.     ; ---
  145.     If $__aAutThreads[$iAutThread][0] Then
  146.         ProcessClose( $__aAutThreads[$iAutThread][2] )
  147.         _MemGlobalFree( $__aAutThreads[$iAutThread][0] )
  148.         $__aAutThreads[$iAutThread][0] = 0
  149.         $__aAutThreads[$iAutThread][1] = 0
  150.         $__aAutThreads[$iAutThread][2] = 0
  151.         $__aAutThreads[$iAutThread][3] = 0
  152.     Else
  153.         Return SetError( 2, 0, False )
  154.     EndIf
  155.     ; ---
  156.     Return True
  157. EndFunc
  158.  
  159. ; #FUNCTION# =================================================================================================
  160. ; Name...........: _AutThread_IsAlive
  161. ; Description ...: Проверяет существует ли указанный поток
  162. ; Syntax.........: _AutThread_IsAlive( $iAutThread )
  163. ; Parameters ....: $iAutThread - ThreadIndex
  164. ; Return values .: ( True ) Or ( False Or set @Error )
  165. ; Author ........: Firex
  166. ; Remarks .......:
  167. ; ============================================================================================================
  168. Func _AutThread_IsAlive( $iAutThread )
  169.     If $__aAutThreads[0][0] < $iAutThread Then _
  170.         Return SetError( 1, 0, False )
  171.  
  172.     Return $__aAutThreads[$iAutThread][0] <> 0
  173. EndFunc
  174.  
  175. ; #FUNCTION# =================================================================================================
  176. ; Name...........: _AutThread_ParentThreadPID
  177. ; Description ...: Возвращает PID родительского процесса ( если таковой имеется ).
  178. ; Syntax.........: _AutThread_ParentThreadPID()
  179. ; Parameters ....:
  180. ; Return values .: ( ParentPID ) Or ( False And set @Error )
  181. ; Author ........: Firex
  182. ; Remarks .......:
  183. ; ============================================================================================================
  184. Func _AutThread_ParentThreadPID()
  185.     Local $iPid = Eval( "__autThread_ParentScriptPID" )
  186.     If Not $iPid Then _
  187.         $iPid = SetError( 1, 0, 0 )
  188.  
  189.     Return $iPid
  190. EndFunc
  191.  
  192. ; #FUNCTION# =================================================================================================
  193. ; Name...........: _AutThread_SelfIsThread
  194. ; Description ...: Проверяет является ли текущее выполнение потоковым
  195. ; Syntax.........: _AutThread_SelfIsThread()
  196. ; Parameters ....:
  197. ; Return values .: ( True ) Or ( False )
  198. ; Author ........: Firex
  199. ; Remarks .......:
  200. ; ============================================================================================================
  201. Func _AutThread_SelfIsThread()
  202.     Return Not IsDeclared( "AUTOITTHREAD" )
  203. EndFunc
  204.  
  205. ; #FUNCTION# =================================================================================================
  206. ; Name...........: _AutThread_ErrorCallbackRegister
  207. ; Description ...: Регистрирует функцию ( будет вызвана при падении потока ).
  208. ; Syntax.........: _AutThread_ErrorCallbackRegister( [ $sFunc ] )
  209. ; Parameters ....: $sFunc - Имя функции
  210. ; Return values .:
  211. ; Author ........: Firex
  212. ; Remarks .......: Функция должна содержать один аргумент, он получит ThreadIndex.
  213. ; ============================================================================================================
  214. Func _AutThread_ErrorCallbackRegister( $sFunc = "" )
  215.     $__sAutThread_OnErrCallback = $sFunc
  216. EndFunc
  217.  
  218.  
  219. ; # INTERNAL USE ONLY
  220. ; =======================================================
  221. Func __AT_Init()
  222.     Local $Idx, $hProcess, $_aAutThreadInfo, $tBufThread, $pBufThread, $tAutThread
  223.     Local $iArgs, $iLen, $sType, $sFunc, $aCallArg, $tBuf, $pBuf, $iBuf
  224.     Local $vCallResult, $vCallReport[3]
  225.  
  226.     Global $__aAutThreads[1][4] = [ [ 0 ] ], $__sAutThread_OnErrCallback = ""
  227.     Global Const $__tagAutThreadBody = "byte vType; byte iArg[3]; ", $__tagATB_Size = __AT_TagSize( $__tagAutThreadBody )
  228.     Global Const $__tagAutThreadInfo = "byte iRet; byte iArgs; ushort iFunc; dword iBuf; ", $__tagATI_Size = __AT_TagSize( $__tagAutThreadInfo )
  229.     OnAutoItExitRegister( "__AT_OnExit" )
  230.     AdlibRegister( "__AT_Main", 450 )
  231.     ; ---
  232.     $_aAutThreadInfo = StringRegExp( $CmdLineRaw, '^.*/(?i)AutThreadInfo="(\d+),(\d+)".*$', 3 )
  233.     If UBound( $_aAutThreadInfo ) <> 2 Then _
  234.         Return
  235.  
  236.     $_aAutThreadInfo[0] = Int( $_aAutThreadInfo[0] )
  237.     $_aAutThreadInfo[1] = Ptr( $_aAutThreadInfo[1] )
  238.     ; ---
  239.     Global Const $__autThread_ParentScriptPID = $_aAutThreadInfo[0]
  240.     ; ---
  241.     $hProcess = __AT_OpenProcess( $_aAutThreadInfo[0] )
  242.     If $hProcess Then
  243.         $tAutThread = DllStructCreate( $__tagAutThreadInfo )
  244.         __AT_ReadProcessMemory( $hProcess, $_aAutThreadInfo[1], $tAutThread )
  245.         If Not @Error Then
  246.             $iArgs = DllStructGetData( $tAutThread, "iArgs" )
  247.             $iLen = DllStructGetData( $tAutThread, "iFunc" )
  248.             $iBuf = DllStructGetData( $tAutThread, "iBuf" )
  249.             If $iBuf - $iLen > 0 And $iArgs Then _
  250.                 Dim $aCallArg[ $iArgs + 1 ] = [ "CallArgArray" ]
  251.  
  252.             $tBufThread = DllStructCreate( "char[" & $iLen & "]; byte[" & $iBuf - $iLen & "]; " )
  253.             $pBufThread = $_aAutThreadInfo[1] + DllStructGetSize( $tAutThread )
  254.             __AT_ReadProcessMemory( $hProcess, $pBufThread, $tBufThread )
  255.             If Not @Error Then
  256.                 $sFunc = DllStructGetData( $tBufThread, 1 )
  257.                 $pBuf = DllStructGetPtr( $tBufThread ) + $iLen
  258.                 ; ~~~
  259.                 For $Idx = 1 To $iArgs Step 1
  260.                     $tBuf = DllStructCreate( $__tagAutThreadBody, $pBuf )
  261.                     $sType = DllStructGetData( $tBuf, "vType" )
  262.                     $iLen = Int( DllStructGetData( $tBuf, "iArg" ) )
  263.                     ; *
  264.                     $pBuf += $__tagATB_Size
  265.                     $tBuf = DllStructCreate( "byte[" & $iLen & "]", $pBuf )
  266.                     $aCallArg[$Idx] = BinaryToString( DllStructGetData( $tBuf, 1 ) )
  267.                     __AT_ReinterpretCast( $sType, $aCallArg[$Idx] )
  268.                     $pBuf += $iLen
  269.                 Next
  270.                 Dim $tBufThread = "", $tBuf = "" ;Free memory
  271.                 ; ---
  272.                 $vCallResult = Call( $sFunc, $aCallArg )
  273.                 $vCallReport[0] = @Error
  274.                 $vCallReport[1] = @Extended
  275.                 $vCallReport[2] = __AT_VarType( VarGetType( $vCallResult ), 1 )
  276.                 DllStructSetData( $tAutThread, 1, $vCallReport[2] )
  277.                 DllStructSetData( $tAutThread, 2, $vCallReport[0] )
  278.                 DllStructSetData( $tAutThread, 3, $vCallReport[1] )
  279.                 ; ---
  280.                 $vCallResult = StringToBinary( $vCallResult )
  281.                 $iLen = BinaryLen( $vCallResult )
  282.                 If $iLen > $iBuf Then _
  283.                     $iLen = $iBuf
  284.                 DllStructSetData( $tAutThread, 4, $iLen )
  285.  
  286.                 $tBuf = DllStructCreate( "byte[" & $iLen & "]; " )
  287.                 DllStructSetData( $tBuf, 1, $vCallResult )
  288.  
  289.                 __AT_WriteProcessMemory( $hProcess, $pBufThread, $tBuf )
  290.                 __AT_WriteProcessMemory( $hProcess, $_aAutThreadInfo[1], $tAutThread )
  291.                 __AT_CloseHandle( $hProcess )
  292.                 ; *
  293.                 Exit 148 ;Thread success exit!
  294.             EndIf
  295.         EndIf
  296.     EndIf
  297.     ; ---
  298.     Exit ( 148 - @Error ) ;Error
  299. EndFunc
  300.  
  301. Func __AT_Main()
  302.     Local $Idx, $Jix, $iRetType, $iPid
  303.     ; ---
  304.     For $Idx = 1 To $__aAutThreads[0][0] Step 1
  305.         If $__aAutThreads[$Idx][0] Then
  306.             $iRetType = DllStructGetData( $__aAutThreads[$Idx][1], 1 )
  307.             If $iRetType Then
  308.                 Local $tRet, $iErr, $iExt, $iBuf, $vRet
  309.                 ; ~
  310.                 $iErr = DllStructGetData( $__aAutThreads[$Idx][1], 2 )
  311.                 $iExt = DllStructGetData( $__aAutThreads[$Idx][1], 3 )
  312.                 $iBuf = DllStructGetData( $__aAutThreads[$Idx][1], 4 )
  313.                 If $iBuf Then
  314.                     $tRet = DllStructCreate( "byte[" & $iBuf & "]; ", $__aAutThreads[$Idx][0] + $__tagATI_Size )
  315.                     $vRet = BinaryToString( DllStructGetData( $tRet, 1 ) )
  316.                     __AT_ReinterpretCast( $iRetType, $vRet )
  317.                 Else
  318.                     $vRet = ""
  319.                 EndIf
  320.                 ; ---
  321.                 If $__aAutThreads[$Idx][3] Then
  322.                     Assign( $__aAutThreads[$Idx][3], $vRet, 2 )
  323.                     Assign( $__aAutThreads[$Idx][3] & "_err", $iErr, 2 )
  324.                     Assign( $__aAutThreads[$Idx][3] & "_ext", $iExt, 2 )
  325.                 EndIf
  326.             ElseIf Not ProcessExists( $__aAutThreads[$Idx][2] ) Then
  327.                 Call( $__sAutThread_OnErrCallback, $Idx )
  328.             Else
  329.                 ContinueLoop
  330.             EndIf
  331.             ; ---
  332.             _AutThread_Kill( $Idx )
  333.         EndIf
  334.     Next
  335.     ; ---
  336.     $iPid = _AutThread_ParentThreadPID()
  337.     If $iPid And Not ProcessExists( $iPid ) Then _
  338.         Exit 149
  339. EndFunc
  340.  
  341. Func __AT_VarType( $vType, $iRet = 1 )
  342.     Local $aTypes[10][2] = [ [ 9, 1 ], [ 10, "Array" ], [ 11, "Int32" ], [ 12, "Int64" ], [ 13, "Binary" ], _
  343.         [ 14, "Bool" ], [ 15, "Ptr" ], [ 16, "Double" ], [ 17, "String" ], [ 18, "Keyword" ] ], $Idx, $Jix, $vRet = 0
  344.     ; ---
  345.     For $Idx = 1 To $aTypes[0][0] Step 1
  346.         For $Jix = 0 To $aTypes[0][1] Step 1
  347.             If $vType = $aTypes[$Idx][$Jix] Then
  348.                 $vRet = $aTypes[$Idx][($Jix-1)*-1]
  349.                 If $Jix = $iRet - 1 Then _
  350.                     $vRet = $vType
  351.  
  352.                 ExitLoop 2
  353.             EndIf
  354.         Next
  355.     Next
  356.     ; ---
  357.     Return $vRet
  358. EndFunc
  359.  
  360. Func __AT_ReinterpretCast( $sType, ByRef $vVar )
  361.     Switch __AT_VarType( $sType, 2 )
  362.         Case "Array"
  363.             $vVar = ""
  364.         Case "Int32", "Int64"
  365.             $vVar = Int( $vVar )
  366.         Case "Binary"
  367.             If StringLeft( $vVar, 2 ) <> "0x" Then _
  368.                 $vVar = "0x" & String( $vVar )
  369.             $vVar = Binary( $vVar )
  370.         Case "Bool"
  371.             Switch $vVar
  372.                 Case "True"
  373.                     $vVar = True
  374.                 Case "False"
  375.                     $vVar = False
  376.                 Case Else
  377.                     $vVar = ( Number( $vVar ) >= 1 )
  378.             EndSwitch
  379.         Case "Ptr"
  380.             $vVar = Ptr( Int( $vVar ) )
  381.         Case "Double"
  382.             $vVar = Round( $vVar, 16 )
  383.         Case "String"
  384.             $vVar = String( $vVar )
  385.         Case "Keyword"
  386.             $vVar = Default
  387.         Case Else
  388.             Return SetError( 1 )
  389.     EndSwitch
  390. EndFunc
  391.  
  392. Func __AT_TagSize( $sTagStruct )
  393.     ;Local $tStruct = DllStructCreate( $sTagStruct )
  394.     ; ---
  395.     ;If @Error Then _
  396.     ;   Return SetError( @Error, 0, 0 )
  397.     ; ---
  398.     ;Return DllStructGetSize( $tStruct )
  399.     Local $aStruct, $iSize, $iTypeSize, $Idx
  400.     ; ---
  401.     $aStruct = StringRegExp( $sTagStruct, '\040*([\w\*]+)[\s\w]*\[?((?<=\[)\d*|)', 3 )
  402.     For $Idx = 0 To UBound( $aStruct ) - 1 Step 2
  403.         Switch StringLower( $aStruct[$Idx] )
  404.             Case "byte", "char", "boolean"
  405.                 $iTypeSize = 1
  406.             Case "wchar", "short", "ushort", "word"
  407.                 $iTypeSize = 2
  408.             Case "int", "long", "bool", "uint", "ulong", "dword", "hwnd", "handle", "float"
  409.                 $iTypeSize = 4
  410.             Case "int64", "uint64", "double"
  411.                 $iTypeSize = 8
  412.             Case "ptr", "int*", "long*", "uint*", "ulong*", "dword*"
  413.                 If @AutoItX64 Then
  414.                     $iTypeSize = 8
  415.                 Else
  416.                     $iTypeSize = 4
  417.                 EndIf
  418.             Case Else
  419.                 $iTypeSize = 0
  420.         EndSwitch
  421.         If $aStruct[$Idx+1] = "" Then _
  422.             $aStruct[$Idx+1] = 1
  423.  
  424.         $iSize += $iTypeSize * $aStruct[$Idx+1]
  425.     Next
  426.     ; ---
  427.     Return $iSize
  428. EndFunc
  429.  
  430. Func __AT_OpenProcess( $iProcessID )
  431.     Local $aResult = DllCall( "kernel32.dll", "handle", "OpenProcess", "dword", 0x1F0FFF, "bool", 0, "dword", $iProcessID )
  432.     If @error Or Not $aResult[0] Then Return SetError(@error+1, @extended, 0)
  433.  
  434.     Return $aResult[0]
  435. EndFunc   ;==>_WinAPI_OpenProcess
  436.  
  437. Func __AT_ReadProcessMemory( $hProcess, $pBaseAddress, $tBuffer )
  438.     Local $aResult = DllCall("kernel32.dll", "bool", "ReadProcessMemory", "handle", $hProcess, _
  439.         "ptr", $pBaseAddress, "ptr", DllStructGetPtr( $tBuffer ), "ulong_ptr", DllStructGetSize( $tBuffer ), "ulong_ptr*", 0)
  440.     If @error Then Return SetError(@error, @extended, False)
  441.     Return $aResult[0]
  442. EndFunc   ;==>_WinAPI_ReadProcessMemory
  443.  
  444. Func __AT_WriteProcessMemory( $hProcess, $pBaseAddress, $tBuffer )
  445.     Local $aResult = DllCall("kernel32.dll", "bool", "WriteProcessMemory", "handle", $hProcess, "ptr", $pBaseAddress, "ptr", _
  446.             DllStructGetPtr( $tBuffer ), "ulong_ptr", DllStructGetSize( $tBuffer ), "ulong_ptr*", 0 )
  447.     If @error Then Return SetError(@error, @extended, False)
  448.     Return $aResult[0]
  449. EndFunc   ;==>_WinAPI_WriteProcessMemory
  450.  
  451. Func __AT_CloseHandle( $hObject )
  452.     Local $aResult = DllCall("kernel32.dll", "bool", "CloseHandle", "handle", $hObject)
  453.     If @error Then Return SetError(@error, @extended, False)
  454.     Return $aResult[0]
  455. EndFunc   ;==>_WinAPI_CloseHandle
  456.  
  457. Func __AT_OnExit()
  458.     For $Idx = 1 To $__aAutThreads[0][0] Step 1
  459.         _AutThread_Kill( $Idx )
  460.     Next
  461. EndFunc
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement