Wait For Application To Load

Iniciado por TrashAmbishion, 5 Septiembre 2016, 06:04 AM

0 Miembros y 1 Visitante están viendo este tema.

TrashAmbishion

Hola,

Quería saber sobre el funcionamiento de este Snippet publicado por Elektro para esperar a que una aplicación termine de cargar..

Código (vbnet) [Seleccionar]


Timer_CheckCPU.Tag = "photoshop"          'Nombre del proceso a chequear
Timer_CheckCPU.Enabled = True
While Not Timer_CheckCPU.Tag = ""
         Application.DoEvents()
End While


#Region " Wait For Application To Load "

    Private Declare Function ReadProcessMemory Lib "kernel32" (ByVal hProcess As Integer, ByVal lpBaseAddress As Integer, ByVal lpBuffer As Integer, ByVal nSize As Integer, ByRef lpNumberOfBytesWritten As Integer) As Integer
    Private WithEvents Timer_CheckCPU As New Timer

    Dim Memory_Value_Changed As Boolean
    Dim CPU_Changed As Boolean
    Dim CPU_Time As Boolean
    Dim Running_Time As Boolean
    Private _desiredTime_ms As Integer = 1500

    Private Sub Timer_CheckCPU_Tick(sender As Object, ev As EventArgs) Handles Timer_CheckCPU.Tick
        Timer_CheckCPU.Enabled = False
        Dim pProcess() As Process = Process.GetProcessesByName(Timer_CheckCPU.Tag)
        Dim hprocess As Process = pProcess(0)
        If hprocess Is Nothing Then
            Running = False
            Timer_CheckCPU.Enabled = True
            Return
        End If
        Running = True
        Memory = hprocess.PrivateMemorySize64
        CPUTotal = hprocess.TotalProcessorTime.TotalMilliseconds

        If AllConditionsGood() Then
            If Not (_countdown.IsRunning) Then
                _countdown.Reset()
                _countdown.Start()
            End If
            Dim _elapsed As Long = _countdown.ElapsedMilliseconds
            If _elapsed >= _desiredTime_ms Then
                Timer_CheckCPU.Tag = ""
                Return
            End If
        Else
            _countdown.Reset()
        End If
        Timer_CheckCPU.Enabled = True
    End Sub

    Private Function AllConditionsGood() As Boolean
        If CPU_Time Then Return False
        If Memory_Value_Changed Then Return False
        If Running_Time Then Return False
        Return True
    End Function

    Private _countdown As New Stopwatch

    Private _Running As Boolean = False
    Public WriteOnly Property Running() As Boolean
        Set(ByVal value As Boolean)
            _Running = value
            If value Then
                Running_Time = False
            Else
                Running_Time = True
            End If
        End Set
    End Property

    Private _CPUTotal As Double
    Public WriteOnly Property CPUTotal() As Double
        Set(ByVal value As Double)
            CPU = value - _CPUTotal 'used cputime since last check
            _CPUTotal = value
        End Set
    End Property

    Private _CPU As Double
    Public WriteOnly Property CPU() As Double
        Set(ByVal value As Double)
            If value = 0 Then
                CPU_Time = False
            Else
                CPU_Time = True
            End If
            _CPU = value
        End Set
    End Property

    Private _Memory As Long
    Public WriteOnly Property Memory() As Long
        Set(ByVal value As Long)
            MemoryDiff = Math.Abs(value - _Memory)
            _Memory = value
        End Set
    End Property

    Private _MemoryDiff As Long
    Public WriteOnly Property MemoryDiff() As Long
        Set(ByVal value As Long)
            If value = _MemoryDiff Then
                Memory_Value_Changed = False
            Else
                Memory_Value_Changed = True
            End If
            _MemoryDiff = value
        End Set
    End Property

#End Region


Necesito verificar los modulos de ese proceso una vez que termine de cargar.. pero no me funciona me devuelve menos modulos de los esperados..

Gracias de antemano

TrashAmbishion

Resolví una posible variante del Snippet

http://stackoverflow.com/questions/15906479/execute-a-application-and-wait-for-it-to-be-loaded

Después reviso a mas profundidad pero hice una prueba rápida y funcionó, lo que me parece es que el programa mucho antes de que caer en un estado de tranquilidad ya hizo la carga todos los módulos que en mi caso con 100...

Salu2

Eleкtro

#2
Ese código es muy antiguo y es una guarrada (todo hay que decirlo), yo no lo escribí: http://stackoverflow.com/a/15906638/1248295

El algoritmo de ese código se basa en la comparación de ciertos parámetros de consumo del proceso para determinar si el proceso está "inactivo". Cada 1500 milisegundos, mediante un Timer, comprueba si ha habido un incremento en el tamaño de alojamiento de memoria privada del proceso, y el tiempo de ejecución en modo kernel.

Cuando inicias un proceso se disparan muchos parámetros, entre ellos los que ya he mencionado, y cuando la interfáz de usuario del proceso se ha cargado completamente, los valores de esos parámetros dejan de incrementar (o al menos deberían, los bugs y/o fugas de memoria serían una excepción, jeje), y en ese preciso momento podemos concluir (aunque no en todos los casos) que la interfaz de usuario se ha cargado por completo, por que no está gastando más recursos.

No existe una ciencia ni botón mágico con el que poder determinar cuando la interfaz de usuario de un proceso externo se ha cargado completamente, de hecho hay interfaces de usuario llenas de animaciones que siempre están generando recursos (como por ejemplo el cliente de Battle.net de Blizzard) y eso es un problema muy grande para hacer nuestros cálculos.

En .NET podemos hacer uso del método Process.WaitforInputIdle() pero esto es una metología engañosa, puesto que solo sirve para esperar a que la cola de mensajes de la aplicación entre en un estado Idle, es decir, cuando esté lista para recibir mensajes de entrada, el problema es que muchas aplicaciones (como or ejemplo Steam) entran en ese estado mientras está cargando la UI ...mucho antes, durante la etapa de startup.




Bueno, despues de todo este rollo que he soltado para explicar varios aspectos, vamos a lo que te interesa, el código:

En el código fuente de mi API gratuita ElektroKit puedes encontrar una refactorización que le hice a esa guarrada de código que publiqué en su día, estoy seguro que te será mucho más sencilla de entender:



Cita de: https://github.com/ElektroStudios/ElektroKit/blob/master/Solution/Elektro.Processes/Tools/ProcessUtil.vb
Código (vbnet) [Seleccionar]
Public Shared Sub WaitUntilLoaded(ByVal p As Process,
                                 Optional ByVal timeOut As Integer = 1500)

   Dim cpuChanged As Boolean = True
   Dim memChanged As Boolean = True

   Dim oldCpu As Double, newCpu As Double
   Dim oldMem As Long, newMem As Long

   While ((cpuChanged OrElse memChanged) = True)

       Do Until (p.TotalProcessorTime.TotalMilliseconds <> 0.0R)
           Thread.Sleep(10)
       Loop

       If (p Is Nothing) OrElse (p.HasExited) Then
           Exit While

       Else
           newMem = p.PrivateMemorySize64
           memChanged = (newMem <> oldMem)
           oldMem = newMem

           newCpu = p.TotalProcessorTime.TotalMilliseconds
           cpuChanged = (newCpu <> oldCpu)
           oldCpu = newCpu

           Thread.Sleep(timeOut)

       End If

   End While

End Sub

De hecho, cualquier sippet mio que encuentres en el foro probablemente con el paso del tiempo lo haya mejorado e integrado en ElektroKit, solo tienes que buscar por el nombre de la función, o preguntarme.




Hoy me he tomado un rato para volver a refactorizar el código, no me convencia demasiado la manera de usarlo, así que he decidido convertirlo en extensiones de método, y he implementado una lógica basada en WMI, ¿por qué?, por que aparte de servirnos para analizar mayor cantidad de parámetros como por ejemplo la cantidad de operaciones de transferencia de lectura/escritura del proceso, además nos sirve para evitar conflictos al intentar leer los parámetros de un exe de 64-bits desde un exe de 32 Bits ...en caso de que lo necesitemos.

En fin, aquí tienes el código (le he eliminado la documentación Xml no-esencial para que no ocupe demasiado espacio):

Código (vbnet) [Seleccionar]
Public Module ProcessExtensions

   ''' ----------------------------------------------------------------------------------------------------
   ''' <summary>
   ''' Causes the <see cref="Process"/> component to wait indefinitely
   ''' until the user interface has been fully loaded then entered in IDLE state.
   ''' <para></para>
   ''' This applies only to processes with a user interface and, therefore, a message loop.
   ''' </summary>
   ''' ----------------------------------------------------------------------------------------------------
   <DebuggerStepThrough>
   <Extension>
   Public Sub WaitForIdle(ByVal sender As Process)

       ProcessExtensions.WaitForIdle(sender, checkInterval:=1500)

   End Sub

   ''' ----------------------------------------------------------------------------------------------------
   ''' <summary>
   ''' Causes the <see cref="Process"/> component to wait for the specified amount of time, in milliseconds,
   ''' until the user interface has been fully loaded then entered in IDLE state.
   ''' <para></para>
   ''' This applies only to processes with a user interface and, therefore, a message loop.
   ''' </summary>
   ''' ----------------------------------------------------------------------------------------------------
   ''' <param name="checkInterval">
   ''' The interval, in milliseconds, to check the parameters that determines
   ''' whether the user-interface has been loaded and the preocess entered in IDLE state.
   ''' <para></para>
   ''' It is recommended to experiment with a value between <c>1000</c> and <c>2000</c> ms,
   ''' smaller values could give unexpected results.
   ''' </param>
   ''' ----------------------------------------------------------------------------------------------------
   <DebuggerStepThrough>
   <Extension>
   Public Sub WaitForIdle(ByVal sender As Process, ByVal checkInterval As Integer)

       If (checkInterval < 0) Then
           Throw New ArgumentOutOfRangeException(paramName:="checkInterval")
           Exit Sub
       End If

       ' Total time of the process running in kernel mode, in milliseconds.
       ' This is the same as Process.TotalProcessorTime
       Dim oldCpuTime As ULong, newCpuTime As ULong
       Dim cpuTimeChanged As Boolean = True

       ' Current number of pages allocated that are only accessible to the process.
       ' This is the same as Process.PrivateMemorySize64
       Dim oldMemSize As ULong, newMemSize As ULong
       Dim memSizeChanged As Boolean = True

       ' Total amount of active threads in the process.
       ' This is the same as Process.ThreadCount
       Dim oldThreadCount As UInteger, newThreadCount As UInteger
       Dim threadCountChanged As Boolean = True

       ' Total amount of open handles owned by the process.
       ' This is the same as Process.HandleCount
       Dim oldHandleCount As UInteger, newHandleCount As UInteger
       Dim handleCountChanged As Boolean = True

       ' Total amount of data transferred by the process in I/O read operations.
       Dim oldReadDataCount As ULong, newReadDataCount As ULong
       Dim readDataChanged As Boolean = True

       ' Total amount of data transferred by the process in I/O write operations.
       Dim oldWriteRateCount As ULong, newWriteDataCount As ULong
       Dim writeDataChanged As Boolean = True

       ' WMI settings for process query.
       Dim scope As New ManagementScope("root\CIMV2")
       Dim query As New SelectQuery(String.Format("SELECT * FROM Win32_Process Where ProcessId = '{0}'", sender.Id))
       Dim options As New EnumerationOptions With {.ReturnImmediately = True, .DirectRead = False}

       Do
           If (sender Is Nothing) OrElse (sender.HasExited) Then
               Throw New InvalidOperationException(message:="The process is not running.")
               Exit Sub

           Else
               sender.Refresh()
               sender.WaitForInputIdle(Timeout.Infinite)

               Using wmi As New ManagementObjectSearcher(scope, query, options)

                   Using obj As ManagementObject = wmi.Get().Cast(Of ManagementObject).SingleOrDefault()

                       If (obj IsNot Nothing) Then

                           newCpuTime = CULng(obj.Properties("KernelModeTime").Value)
                           cpuTimeChanged = (newCpuTime <> oldCpuTime)
                           oldCpuTime = newCpuTime

                           newMemSize = CULng(obj.Properties("PrivatePageCount").Value)
                           memSizeChanged = (newMemSize <> oldMemSize)
                           oldMemSize = newMemSize

                           newThreadCount = CUInt(obj.Properties("ThreadCount").Value)
                           threadCountChanged = (newThreadCount <> oldThreadCount)
                           oldThreadCount = newThreadCount

                           newHandleCount = CUInt(obj.Properties("HandleCount").Value)
                           handleCountChanged = (newHandleCount <> oldHandleCount)
                           oldHandleCount = newHandleCount

                           newReadDataCount = CULng(obj.Properties("ReadTransferCount").Value)
                           readDataChanged = (newReadDataCount <> oldReadDataCount)
                           oldReadDataCount = newReadDataCount

                           newWriteDataCount = CULng(obj.Properties("WriteTransferCount").Value)
                           writeDataChanged = (newWriteDataCount <> oldWriteRateCount)
                           oldWriteRateCount = newWriteDataCount

#If DEBUG Then
                           Debug.WriteLine("CPU.Time........:{0}(ms)
                                            Memomy.Size.....:{1}(bytes)
                                            Thread.Count....:{2}
                                            Handle.Count....:{3}
                                            Read..Data.Count:{4}
                                            Write.Data.Count:{5}
                                           ".Replace(" "c, ""),
                                           newCpuTime, newMemSize,
                                           newThreadCount, newHandleCount,
                                           newReadDataCount, newWriteDataCount)
#End If

                       Else
                           Continue Do

                       End If

                   End Using

               End Using

               If (cpuTimeChanged Or memSizeChanged Or
                   threadCountChanged Or handleCountChanged Or
                   readDataChanged Or writeDataChanged) = True Then

                   Thread.Sleep(TimeSpan.FromMilliseconds(checkInterval))
                   Continue Do

               End If

           End If

       Loop Until (cpuTimeChanged Or memSizeChanged Or
                   threadCountChanged Or handleCountChanged Or
                   readDataChanged Or writeDataChanged) = False

   End Sub

   ''' ----------------------------------------------------------------------------------------------------
   ''' <summary>
   ''' Aynchronouslly causes the <see cref="Process"/> component to wait indefinitely  
   ''' until the user interface has been fully loaded then entered in IDLE state.
   ''' <para></para>
   ''' This applies only to processes with a user interface and, therefore, a message loop.
   ''' </summary>
   ''' ----------------------------------------------------------------------------------------------------
   ''' <param name="callback">
   ''' A <see cref="Action"/> delegate which will be invoked inmmediately
   ''' after the process is fully loaded and entered in IDLE state.
   ''' <para></para>
   ''' When <see cref="WaitForIdleAsync"/> method is called from a UI thread,
   ''' <paramref name="callback"/> is invoked on the same UI thread.
   ''' </param>
   ''' ----------------------------------------------------------------------------------------------------
   <DebuggerStepThrough>
   <Extension>
   Public Sub WaitForIdleAsync(ByVal sender As Process,
                               ByVal callback As Action)

       ProcessExtensions.WaitForIdleAsync(sender, callback, checkInterval:=1500)

   End Sub

   ''' ----------------------------------------------------------------------------------------------------
   ''' <summary>
   ''' Aynchronouslly causes the <see cref="Process"/> component to wait for the specified amount of time, in milliseconds,
   ''' until the user interface has been fully loaded then entered in IDLE state.
   ''' <para></para>
   ''' This applies only to processes with a user interface and, therefore, a message loop.
   ''' </summary>
   ''' ----------------------------------------------------------------------------------------------------
   ''' <param name="callback">
   ''' A <see cref="Action"/> delegate that will be invoked inmmediately
   ''' after the process is fully loaded and entered in IDLE state.
   ''' <para></para>
   ''' When <see cref="WaitForIdleAsync"/> method is called from a UI thread,
   ''' <paramref name="callback"/> is invoked on the same UI thread.
   ''' </param>
   '''
   ''' <param name="checkInterval">
   ''' The interval, in milliseconds, to check the parameters that determines
   ''' whether the user-interface has been loaded and the preocess entered in IDLE state.
   ''' <para></para>
   ''' It is recommended to experiment with a value between <c>1000</c> and <c>2000</c> ms,
   ''' smaller values could give unexpected results.
   ''' </param>
   ''' ----------------------------------------------------------------------------------------------------
   <DebuggerStepThrough>
   <Extension>
   Public Sub WaitForIdleAsync(ByVal sender As Process,
                               ByVal callback As Action,
                               ByVal checkInterval As Integer)

       Dim tScheduler As TaskScheduler

       ' If the current thread is a UI thread then run the callback synchronouslly on the UI thread...
       If (Application.MessageLoop) Then
           tScheduler = TaskScheduler.FromCurrentSynchronizationContext()

       Else
           tScheduler = TaskScheduler.Default

       End If

       Task.Factory.StartNew(Sub() ProcessExtensions.WaitForIdle(sender, checkInterval)).
                    ContinueWith(Sub(t) callback.Invoke(), CancellationToken.None, TaskContinuationOptions.None, tScheduler)

   End Sub

End Module


Ejemplos de uso sincrónico:
Código (vbnet) [Seleccionar]
Using p As New Process
    p.StartInfo.FileName = "C:\Program Files\Photoshop\Photoshop.exe"
    p.Start()
    p.WaitForIdle(checkInterval:=1500)
End Using

MsgBox("Process's UI has been fully loaded.")


Ejemplos de uso asincrónico:
Código (vbnet) [Seleccionar]
Using p As New Process
    p.StartInfo.FileName = "C:\Program Files\Photoshop\Photoshop.exe"
    p.Start()
    p.WaitForIdleAsync(p, AddressOf WaitForIdleAsync_CallBack, checkInterval:=1500)
End Using

Private Sub WaitForIdleAsync_CallBack()
    MsgBox("Process's UI has been fully loaded.")
End Sub





Cita de: TrashAmbishion en  5 Septiembre 2016, 20:28 PMlo que me parece es que el programa mucho antes de que caer en un estado de tranquilidad ya hizo la carga todos los módulos que en mi caso con 100...

Como ya te dije no existe una ciencia con la que predecir la carga completa de la UI, yo te recomiendo usar un valor de entre 1000 a 2000 ms para el parámetro "checkInterval" de los códigos de arriba (en el código que tú pusiste de Stackoverflow también), si usas un valor como 100 ms no es nada bueno, creeme, si el PC está haciendo muchas tareas e intentas iniciar un proceso entonces la información del proceso (me refiero al objeto Process, y la información de WMI), tardará mucho más que 100 ms en actualizarse, y esto quiere decir que los valores que obtendrás en la siguiente iteración serán idénticos así que la comprobación de valores te podría dar un falso positivo, y te lo daría. En otras palabras, cuanto mayor sea el intervalo de ms que especifiques, más seguro será la comprobación.

Un saludo








TrashAmbishion

Muchas gracias lo revisaré...

Salu2

Pd: Ya estoy a punto de poner a prueba el proyecto y quería saber (para cubrir dudas) que como obligatoriamente para poder jugar el juego tiene que ser a traves de mi programa tendría cierta ventaja como por ejemplo bloquear el proceso del juego para que nada mas que mi programa acceda a el, estuve mirando "mutex" pero creo que no tiene nada que ver, y lo otro es iniciarlo de forma que no se pudiera listar en los procesos como un rootkit, pero que la GUI si se mantuviera o cualquier otro invento

No obstante

El injector que estoy mirando, trae una función para oculta la DLL que injecta.. pero esa DLL requiere otra DLL que si se carga y ahi pita mi programa, pero pasa que hay programas que sirven para hacer capturas del juego o grabar incluso hasta el IDM a veces mete una DLL en las del juego..

Estaba pensado hacer una lista de las DLL que carga en un sistema virgen

y entonces de ahi ir partiendo revisar las que esten de mas, hacerle unload si hay algo sospechoso..

que crees ?

Salu2

Eleкtro

#4
Cita de: TrashAmbishion en  6 Septiembre 2016, 21:08 PMquería saber (para cubrir dudas) que como obligatoriamente para poder jugar el juego tiene que ser a traves de mi programa tendría cierta ventaja como por ejemplo bloquear el proceso del juego para que nada mas que mi programa acceda a el, estuve mirando "mutex" pero creo que no tiene nada que ver, y lo otro es iniciarlo de forma que no se pudiera listar en los procesos como un rootkit, pero que la GUI si se mantuviera o cualquier otro invento

Bueno, en mi opinión no está del todo claro lo que dices, si tu programa debería ser el único que pudiera INICIAR el executable, o por otro lado el único que pudiera ACCEDER a él, o ambas cosas.




Si te refieres a imposibilitar que el usuario pueda iniciar el exe del juego:
Puedes hacer un API hooking a la función CreateProcessW/CreateProcessA para interceptar las lalmadas a dicha función y anular la llamada en caso de que el parámetro lpApplicationName corresponda con el exe del juego. En .NET puedes implementar técnicas de API Hooking de forma relativamente sencilla con la librería Deviare: http://www.nektra.com/products/deviare-api-hook-windows/.

Un ejemplo de implementación que escribí:

Otra opción para llevar a cabo lo mismo sería monitorizar la ejecución de nuevos procesos mediante WMI, lo que practicamente no costaría nada de esfuerzo implementarlo, sin embargo, WMI necesita un mínimo de tiempo para actualizarse y detectar nuevos procesos en la lista de procesos interna del sistema, dicho de otra forma: es una metodología mucho menos sensible que el API hooking.

En mi API gratuita ElektroKit tienes un ejemplo de implementación:


Pero como ya he explicado WMI es lento, así que sería mejor usar un Timer (de verdad, da muchos mejores resultados que WMI):


Notas adicionales:
- Si quieres imposibilitar más de una instancia del executable entonces puedes usar un MUTEX o semáforo, no te va a servir para otra cosa en este propósito.
- Si, puedes ocultar el nombre del exe del juego en el administrador de tareas, puedes hacerlo mediante la metodología tradicional y casi obsoleta de la Win32 API (FindWindow + EnumChildWindows + etc) o también mediante el framework de Windows UI Automation de la librería de classes de .NET Framework, pero no te aconsejo ocultar el nombre del proceso de ninguna forma, puesto que se requiere mucho trabajo para cada versión de Windows necesitarás hacer cambios en los nombres de las classes y etc, te llevaría bastante tiempo solo en investigar los nombres haciéndole un Spy al taskmanager, vaya. No se si existe otra forma para ocultarlo.




Si te refieres a impedir que el usuario pueda utilizar la ventana del proceso:

Mediante la API RAWINPUT puedes registrar un dispositivo de teclado y/o mouse virtual y solo lo usuarias tú, pero requiere mucho trabajo, mucho p/invoking:
( En mi API ElektroKit en el namespace Elektro.Interop.Win32 tienes muchas definiciones de RAWINPUT si las llegases a necesitar... )

Cabe mencionar que puedes utilizar la función Win32 BlockInput para imposibilitar el uso del teclado y el mouse, pero lamentablemente esto afectaría de forma global al usuario, no solo a la ventana del juego.

Se me ocurre que podrías registrar un mensaje de ventana, e intentar bloquear la cola de mensajes de la ventana del juego para que solo acepte tu mensaje, supongo que esto es lo que harán los bots más sofisticados (como los Buddy bots de los juegos de Blizzard) pero solo es una hipótesis o una conjetura, realmente no estoy nada seguro de como se implementaría esta técnica.




Cita de: TrashAmbishion en  6 Septiembre 2016, 21:08 PMque crees ?
La inyección de código en video juegos y en general estas cosas para juegos no es lo mio, te podrían orientar mejor en el foro de ingenieria inversa, supongo.

Saludos








FJDA

#5
una forma sencillita sería esta


Código (vbnet) [Seleccionar]

Public Class Form1

   Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
       'Process.Start("C:\Program Files\Adobe\Adobe Photoshop CS5 (64 Bit)\Photoshop.exe")
       Process.Start("C:\Program Files (x86)\Adobe\Adobe Photoshop CS5\Photoshop.exe")

       'Subprocesoo
       Task.Factory.StartNew(Sub()
                                 Do
                                     'Espera a obtener el handle de la ventana principal de Photoshop
                                     Dim pcs As Process() = Process.GetProcessesByName("Photoshop")
                                     For Each proceso As Process In pcs
                                         If proceso.MainWindowHandle <> IntPtr.Zero Then
                                             Exit Do
                                         End If
                                     Next
                                 Loop
                             End Sub).ContinueWith(Sub()
                                                       System.Threading.Thread.Sleep(1500) 'Espera un segundo y medio
                                                       AppActivate(Process.GetCurrentProcess.Id) 'Activa esta ventana
                                                       MessageBox.Show("Photoshop se ha iniciado") 'Muestra un mensaje
                                                   End Sub)

   End Sub
 
End Class


__________________________________________________________

y se puede usar HasExited de forma análoga para saber si la aplicación se ha cerrado. Devuelve True.