Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

[module] RemoteBuffer 2.0


  • Please log in to reply
25 replies to this topic
majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Remote Buffer
Read and write process memory

Download                 Documentation
Posted Image

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
This is more complicated example.

The goal is to read so called root item from the Explorers TreeView on the left (folders). This item is "Desktop" on all systems but you can not get the text of it using regular API functions. Example is a little complicated then shimanov's work for reading columns of ListView control in Explorer because it requires 2 remote buffers - one to receive the text, and one as an input to SendMessage function witch represents data of the item to be obtained (TVITEM structure in MSDN). If neither of those 2 buffers is local, the Windows will return nothing.

Run explorer, make sure you have folders open on the left and execute the script. You should get message containing word "Desktop" (or whatever localisation you have)

;Run Explorer
;WinWait ahk_class ExploreWClass

	GetHandles()	;set XHandle & TreeHandle
	GetRoot()		;display the text of root item 
return


;------------------------------------------------------------------------------------------------
; Get the text of the root item (Desktop)
GetRoot()
{
	global
	local bufID, txt

	[color=red];open remote buffer[/color]
	bufID  := RemoteBuf_Open(hwEx, 128)
	
	if bufID < 0
		MsgBox Can not open remote buffer (code %bufID%)
    bufAdr := RemoteBuf_GetAdr(bufID)


	;[color=red]get root handle[/color]   TVM_GETNEXTITEM = 0x110A  TVGN_ROOT = 0
	SendMessage 0x110A,	0, 0, ,ahk_id %hwTV%
	root = %ErrorLevel%

	[color=red];set TVITEM struct[/color]
	VarSetCapacity(sTV, 40,  1) 	 ;10x4 = 40
	InsertInteger(0x011,   sTV, 0)	 ;set mask to TVIF_TEXT | TVIF_HANDLE  = 0x001 | 0x0010  
	InsertInteger(root,    sTV, 4)	 ;set itemid to root
	InsertInteger(bufAdr,  sTV, 16)  ;set txt pointer
	InsertInteger(127,     sTV, 20)  ;set txt size
	
	[color=red];copy the TVITEM structure to explorer address space       [/color]
	r_tvi := RemoteBuf_Open(hwEx, 40)
	r_adr := RemoteBuf_GetAdr(r_tvi)
	RemoteBuf_Write(r_tvi, sTV, 40)

	[color=red];send tv_getitem message[/color]
	SendMessage 0x110C, 0, r_adr ,, ahk_id %hwTV%

	[color=red];read the text from the remote buffer[/color]
[color=blue]	VarSetCapacity(txt, 128, 1)[/color]
	RemoteBuf_Read(bufID, txt, 128 )

	[color=red];close the buffers[/color]
	RemoteBuf_Close( bufID )
	RemoteBuf_Close( r_tvi )

	MsgBox %txt%
}

;------------------------------------------------------------------------------------------------
; Get Explorer and its TreeView handle
GetHandles()
{
	global
	;get tree view handle
	hwEx	:= WinExist("ahk_class ExploreWClass")

	hwTV	:= FindWindowExId(hwEx, "BaseBar", 0)
	hwTV	:= FindWindowExID(hwTV, "ReBarWindow32", 0)
	hwTV	:= FindWindowExID(hwTV, "SysTreeView32", 100)
}

;------------------------------------------------------------------------------------------------
; Iterate through controls with the same class, find the one with ctrlID and return its handle
; Used for finding a specific control
;
FindWindowExID(dlg, className, ctrlId)
{
	local ctrl, id

	ctrl = 0
	Loop
	{
		ctrl := DllCall("FindWindowEx", "uint", dlg, "uint", ctrl, "str", className, "uint", 0 )
		if (ctrlId = "0")
		{
			return ctrl
		}

		if (ctrl != "0")
		{
			id := DllCall( "GetDlgCtrlID", "uint", ctrl )
			if (id = ctrlId) 
				return ctrl				
		}
		else 
			return 0
	}

}


InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
; The caller must ensure that pDest has sufficient capacity.  To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.
{
	Loop %pSize%  ; Copy each byte in the integer into the structure as raw binary data.
		DllCall("RtlFillMemory", "UInt", &pDest + pOffset + A_Index-1, "UInt", 1, "UChar", pInteger >> 8*(A_Index-1) & 0xFF)
}

#include RemoteBuf.ahk

One question remains here about the blue line: why I must initialize array of bytes to non-zero value. Script doesn't work if you set 0 instead of any positive value.
Posted Image

PhiLho
  • Moderators
  • 6850 posts
  • Last active: Jan 02 2012 10:09 PM
  • Joined: 27 Dec 2005
Well, I just tested with these values at 0 on WinXP Pro SP2, and it worked fine, showing "Bureau"...
Posted Image vPhiLho := RegExReplace("Philippe Lhoste", "^(\w{3})\w*\s+\b(\w{3})\w*$", "$1$2")

Chris
  • Administrators
  • 10727 posts
  • Last active:
  • Joined: 02 Mar 2004
Nice demonstration. Also, your RemoteBuf.ahk will make life easier and be a good foundation for future scripts. I can't explain why the non-zero value in VarSetCapacity(txt, 128, 1) is needed, but I didn't study it very much.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Thx

To get currently selected item change one 0 to 9 in:

   ;get root handle   TVM_GETNEXTITEM = 0x110A  TVGN_ROOT = 0 
   SendMessage 0x110A,   [color=blue]9[/color], 0, ,ahk_id %hwTV% 
   root = %ErrorLevel%


2 Philho
I don't know. I encounter this in several API functions so far... on XP SP1 + up to date hotfixes
Posted Image

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Some small bug fixed. Documentation created.
Posted Image

ashamed
  • Guests
  • Last active:
  • Joined: --
if I take the example of majkinetor 01.09.2006, http://www.autohotke...5346.html#75346, I get the error

Error: Call to nonexistent function.

Specifically: RemoteBuf_GetAdr(bufID)

Line#
005: GetHandles()
006: GetRoot()
007: Return
013: {
018: bufID := RemoteBuf_Open(hwEx, 128)
020: if bufID < 0
021: MsgBox,Can not open remote buffer (code %bufID%)
---> 022: bufAdr := RemoteBuf_GetAdr(bufID)
026: SendMessage,0x110A,0,0,,ahk_id %hwTV%
027: root = %ErrorLevel%
030: VarSetCapacity(sTV, 40, 1)
031: InsertInteger(0x011, sTV, 0)
032: InsertInteger(root, sTV, 4)
033: InsertInteger(bufAdr, sTV, 16)
034: InsertInteger(127, sTV, 20)

The program will exit.



majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
So why did you take it ? I removed it.

There is no such function any more. Instead GetSize and GetAdr are united into Get:

;----------------------------------------------------
;Function:	Get
;			Get address or size of the remote buffer
;
;Parameters: 
;			p_hRemote	- Remote buffer handle
;			p_info		- Set to "adr" to get address (default) or to "size" to get size.
;
;Returns:
;			Address or size of the remote buffer
;

Posted Image

nonov
  • Guests
  • Last active:
  • Joined: --
is it supposed to read/write from/to external process's memory area? like when you're trying to cheat games and such. i think it's a little confusedness. could you demonstrate a simple and basic example? Thanks for sharing anyway. i like your libraries :)

jballi
  • Members
  • 1029 posts
  • Last active:
  • Joined: 01 Oct 2005
majkinetor,

Thanks for directing me to this post. Although somewhat obscure, I'm sure this library will be useful to a lot of users.

I noticed one bug when using this library to solve the EM_GETSELTEXT message problem. OK, maybe not a bug but an inefficiency.

The library uses a global variable to store the Remote Buffer index and 3 global variables for each index to hold the following information: process handle, buffer size, and buffer address. By the way the library is written, these global variables are a probably a necessary evil. Although the RemoteBuf_Close function clears the contents of these variables when the buffer is closed, the memory allocated to each variable is not actually freed because each variable contain less than 4K.

The problem I have is not with amount of memory wasted (trivial amount) but the number of global variables created. After doing just a few dozen tests with a function that uses the Remote Buffer library, I checked the list of variables and found more than a hundred RemoteBuf_* global variables.

There are a few workarounds....

One option is to open a remote buffer with an extra large buffer an reuse the address/space over and over again until you're ready to close the program. I hate this option but it will probably work for some commands/messages/etc.

A better solution might be for the developer to reset the Remote Buffer index after closing the last opened handle. Something like this:
RemoteBuf_Close(bufID)
RemoteBuf_idx=0
If the Remote Buffer library is called from within a function, the RemoteBuf_idx variable must be defined as a global variable.

Resetting the RemoteBuf_idx will not remove the global variables but it will insure that a minimum number of global variables are created. If only one remote buffer is opened at time, a total of 4 global variables will be created.

An even better solution is to add a "reset" parameter (default FALSE) to the RemoteBuf_Close function that if set to TRUE will reset the RemoteBuf_idx variable. Something like this:
RemoteBuf_Close(p_hRemote,ResetIndex=false)
    {
    Global
    ...
    ...
    if ResetIndex
        RemoteBuf_idx=0

    return
    }
Them be my thoughts. Thank you for your consideration.

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Hey jballi.

This library is written long time ago.

RemoteBuffer and for instance MMenu use large number of global variables in the form of array. At that time, I expected that arrays will be introduced soon enough (so I was bulding everything with pseudo-arrays) but I was obviously wrong.

Now this lib can be done to contain data within one global or even static.
Blank variables aren't that consuming by the way, u can safely have hundreeds of them from my experience.

It can also be design problem from your side. Instead creating new remote buffer each time you can probably create only 1 and assing it to running instance of remote process. You use static to keep handle, create it first time only, and afterwards just check if process is restarted to alocate new buffer for that process. I say this as you were probably opening and closing buffer on each function call.

I hope you solved your problem btw.
Posted Image

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
Btw, easy way to fix massive variable creation is to use another technique.

As I already said, I used pesudo-associative array creation. I expected that I will today have some way to create normal arrays or at least free empty variables for good. As that is not the case you can do it like this:

The problem is this peace of code in Open function:

RemoteBuf_idx += 1
RemoteBuf_%RemoteBuf_idx%_handle  := proc_hwnd
RemoteBuf_%RemoteBuf_idx%_size	  := p_size
RemoteBuf_%RemoteBuf_idx%_adr	  := bufAdr

You can see that idx is incremented everytime. To prevent this, Close functions must mark that index is deleted, we can use 0 as handle to mark closed buffer:
RemoteBuf_%RemoteBuf_idx%_handle := 0

Now, above peace of code in Open fun can be changed as:
;find first available index (use idx of closed buffer if exists)
loop
    if !RemoteBuf_%A_Index%_handle
          new_idx := A_Index

RemoteBuf_%new_idx%_handle  := proc_hwnd
RemoteBuf_%new_idx%_size	  := p_size
RemoteBuf_%new_idx%_adr	  := bufAdr

The construct if !RemoteBuf_%A_Index%_handle will pass if handle is both 0 (closed buffer) or "" (we need new index).

This will keep the number of variables to number of globaly opened buffers x 3. In your case, you will have only 3 vars all the time. You can also put all tree variables in 1 var, ultimatively, u can have number of variables equal to maximum created number of buffers in session. This is the fastest fix for this issue.
Posted Image

jballi
  • Members
  • 1029 posts
  • Last active:
  • Joined: 01 Oct 2005
This will work. I've made these change (with of couple of modifications) in my copy of the RemoteBuf library.

Thanks. :)

majkinetor
  • Moderators
  • 4512 posts
  • Last active: May 20 2019 07:41 AM
  • Joined: 24 May 2006
The module is updated by infogulch to not use globals.
Error reports changed.
The documentation updated
Posted Image

TheGood
  • Members
  • 589 posts
  • Last active: Mar 22 2014 03:22 PM
  • Joined: 30 Jul 2007
Very nice wrapper majkinetor!