Hola,
Quería saber sobre el funcionamiento de este Snippet publicado por Elektro para esperar a que una aplicación termine de cargar..
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
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
Ese código es muy antiguo y es una guarrada (todo hay que decirlo), yo no lo escribí: http://stackoverflow.com/a/15906638/1248295 (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:
(http://i.imgur.com/aH0EmiQ.png)
Cita de: https://github.com/ElektroStudios/ElektroKit/blob/master/Solution/Elektro.Processes/Tools/ProcessUtil.vbPublic 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):
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:
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:
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
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
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/ (http://www.nektra.com/products/deviare-api-hook-windows/).
Un ejemplo de implementación que escribí:
- http://foro.elhacker.net/net/api_para_impedir_la_ejecucion_de_ejecutables-t451335.0.html;msg2067510#msg2067510
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:
(http://i.imgur.com/2vH8dpZ.png)
- https://github.com/ElektroStudios/ElektroKit/blob/master/Solution/Elektro.Processes/Types/ProcessWatcher.vb
Pero como ya he explicado WMI es lento, así que sería mejor usar un Timer (de verdad, da muchos mejores resultados que WMI):
(http://i.imgur.com/JQcgCLS.png)
- https://github.com/ElektroStudios/ElektroKit/blob/master/Solution/Elektro.Processes/Types/ProcessMonitor.vb
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:
- https://msdn.microsoft.com/en-us/library/windows/desktop/ms645536%28v=vs.85%29.aspx
( 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
una forma sencillita sería esta
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.