physmem http://www.sysinternals.com - programmable in PB ?

Everything else that doesn't fall into one of the other PB categories.
User avatar
bingo
Enthusiast
Enthusiast
Posts: 210
Joined: Fri Apr 02, 2004 12:21 pm
Location: germany/thueringen
Contact:

physmem http://www.sysinternals.com - programmable in PB ?

Post by bingo »

is it possible with PB ? :?:

i dont now if pb can handle "InitializeObjectAttributes" ...

Code: Select all

//========================================================
//
// Physmem
//
// Mark Russinovich
// Systems Internals
// http://www.sysinternals.com
//
// This program demonstrates how you can open and
// map physical memory. This is essentially the NT 
// equivalent of the \dev\kmem device in UNIX.
//
//========================================================
#include <windows.h>
#include <stdio.h>
#include "native.h"

//
// Number of bytes to print per line
//
#define BYTESPERLINE	16

//
// Lines to print before pause
//
#define LINESPERSCREEN	25


//
// Functions in NTDLL that we dynamically locate
//

NTSTATUS (__stdcall *NtUnmapViewOfSection)(
		IN HANDLE  ProcessHandle,
		IN PVOID  BaseAddress
		);

NTSTATUS (__stdcall *NtOpenSection)(
		OUT PHANDLE  SectionHandle,
		IN ACCESS_MASK  DesiredAccess,
		IN POBJECT_ATTRIBUTES  ObjectAttributes
		);

NTSTATUS (__stdcall *NtMapViewOfSection)(
		IN HANDLE  SectionHandle,
		IN HANDLE  ProcessHandle,
		IN OUT PVOID  *BaseAddress,
		IN ULONG  ZeroBits,
		IN ULONG  CommitSize,
		IN OUT PLARGE_INTEGER  SectionOffset,	/* optional */
		IN OUT PULONG  ViewSize,
		IN SECTION_INHERIT  InheritDisposition,
		IN ULONG  AllocationType,
		IN ULONG  Protect
		);

VOID (__stdcall *RtlInitUnicodeString)(
		IN OUT PUNICODE_STRING  DestinationString,
		IN PCWSTR  SourceString
		);

ULONG (__stdcall *RtlNtStatusToDosError) (
		IN NTSTATUS Status
		);

//----------------------------------------------------------------------
//
// PrintError
//
// Formats an error message for the last error
//
//----------------------------------------------------------------------
void PrintError( char *message, NTSTATUS status )
{
	char *errMsg;

	FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
			NULL, RtlNtStatusToDosError( status ), 
			MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 
			(LPTSTR) &errMsg, 0, NULL );
	printf("%s: %s\n", message, errMsg );
	LocalFree( errMsg );
}


//--------------------------------------------------------
//
// UnmapPhysicalMemory
//
// Maps a view of a section.
//
//--------------------------------------------------------
VOID UnmapPhysicalMemory( DWORD Address )
{
	NTSTATUS		status;

	status = NtUnmapViewOfSection( (HANDLE) -1, (PVOID) Address );
	if( !NT_SUCCESS(status)) {

		PrintError("Unable to unmap view", status );
	}
}


//--------------------------------------------------------
//
// MapPhysicalMemory
//
// Maps a view of a section.
//
//--------------------------------------------------------
BOOLEAN MapPhysicalMemory( HANDLE PhysicalMemory,
							PDWORD Address, PDWORD Length,
							PDWORD VirtualAddress )
{
	NTSTATUS			ntStatus;
	PHYSICAL_ADDRESS	viewBase;
	char				error[256];

	*VirtualAddress = 0;
	viewBase.QuadPart = (ULONGLONG) (*Address);
	ntStatus = NtMapViewOfSection (PhysicalMemory,
                               (HANDLE) -1,
                               (PVOID) VirtualAddress,
                               0L,
                               *Length,
                               &viewBase,
                               Length,
                               ViewShare,
                               0,
                               PAGE_READONLY );

	if( !NT_SUCCESS( ntStatus )) {

		sprintf( error, "Could not map view of %X length %X",
				*Address, *Length );
		PrintError( error, ntStatus );
		return FALSE;					
	}

	*Address = viewBase.LowPart;
	return TRUE;
}


//--------------------------------------------------------
//
// OpensPhysicalMemory
//
// This function opens the physical memory device. It
// uses the native API since 
//
//--------------------------------------------------------
HANDLE OpenPhysicalMemory()
{
	NTSTATUS		status;
	HANDLE			physmem;
	UNICODE_STRING	physmemString;
	OBJECT_ATTRIBUTES attributes;
	WCHAR			physmemName[] = L"\\device\\physicalmemory";

	RtlInitUnicodeString( &physmemString, physmemName );	

	InitializeObjectAttributes( &attributes, &physmemString,
								OBJ_CASE_INSENSITIVE, NULL, NULL );			
	status = NtOpenSection( &physmem, SECTION_MAP_READ, &attributes );

	if( !NT_SUCCESS( status )) {

		PrintError( "Could not open \\device\\physicalmemory", status );
		return NULL;
	}

	return physmem;
}



//--------------------------------------------------------
//
// LocateNtdllEntryPoints
//
// Finds the entry points for all the functions we 
// need within NTDLL.DLL.
//
//--------------------------------------------------------
BOOLEAN LocateNtdllEntryPoints()
{
	if( !(RtlInitUnicodeString = (void *) GetProcAddress( GetModuleHandle("ntdll.dll"),
			"RtlInitUnicodeString" )) ) {

		return FALSE;
	}
	if( !(NtUnmapViewOfSection = (void *) GetProcAddress( GetModuleHandle("ntdll.dll"),
			"NtUnmapViewOfSection" )) ) {

		return FALSE;
	}
	if( !(NtOpenSection = (void *) GetProcAddress( GetModuleHandle("ntdll.dll"),
			"NtOpenSection" )) ) {

		return FALSE;
	}
	if( !(NtMapViewOfSection = (void *) GetProcAddress( GetModuleHandle("ntdll.dll"),
			"NtMapViewOfSection" )) ) {

		return FALSE;
	}
	if( !(RtlNtStatusToDosError = (void *) GetProcAddress( GetModuleHandle("ntdll.dll"),
			"RtlNtStatusToDosError" )) ) {

		return FALSE;
	}
	return TRUE;
}


//--------------------------------------------------------
//
// Main
// 
// This program drives the command loop
//
//--------------------------------------------------------
int main( int argc, char *argv[] )
{
	HANDLE		physmem;
	DWORD		vaddress, paddress, length;
	char		input[256];
	DWORD		lines;
	char		ch;
	DWORD		i, j;

	printf("\nPhysmem v1.0: physical memory viewer\n"
		   "By Mark Russinovich\n"
		   "Systems Internals - http://www.sysinternals.com\n\n");

	//
	// Load NTDLL entry points
	//
	if( !LocateNtdllEntryPoints() ) {

		printf("Unable to locate NTDLL entry points.\n\n");
		return -1;
	}

	//
	// Open physical memory
	//
	if( !(physmem = OpenPhysicalMemory())) {

		return -1;
	}

	//
	// Enter the command loop
	//
	printf("Enter values in hexadecimal. Enter 'q' to quit.\n");
	while( 1 ) {

		printf("\nAddress: " ); fflush( stdout );
		gets( input );
		if( input[0] == 'q' || input[0] == 'Q' ) break;
		sscanf( input, "%x", &paddress );

		printf("Bytes: "); fflush( stdout );
		gets( input );
		if( input[0] == 'q' || input[0] == 'Q' ) break;
		sscanf( input, "%x", &length );

		//
		// Map it
		//
		if( !MapPhysicalMemory( physmem, &paddress, &length,
								&vaddress )) 
			continue;

		//
		// Dump it
		//
		lines = 0;
		for( i = 0; i < length; i += BYTESPERLINE ) {

			printf("%08X: ", paddress + i );

			for( j = 0; j < BYTESPERLINE; j++ ) {

				if( i+j == length ) break;
				if( j == BYTESPERLINE/2 ) printf("-" );
				printf("%02X ", *(PUCHAR) (vaddress + i +j ));
			}

			for( j = 0; j < BYTESPERLINE; j++ ) {

				if( i+j == length ) break;
				ch = *(PUCHAR) (vaddress + i +j );

				if( __iscsym( ch ) || 
					isalnum( ch ) ||
					ch == ' ') {

					printf("%c", ch);

				} else {

					printf("." );
				}
			}

			printf("\n");

			if( lines++ == LINESPERSCREEN ) {

				printf("-- more -- ('q' to abort)" ); fflush(stdout);
				ch = getchar();
				if( ch == 'q' || ch == 'Q' ) {
					fflush( stdin );
					break;
				}
				lines = 0;
			}
		}

		//
		// Unmap the view
		//
		UnmapPhysicalMemory( vaddress );
	}

	//
	// Close physical memory section
	//
	CloseHandle( physmem );

	return 0;
}

Code: Select all

//========================================================
//
// Native.h
//
// Mark Russinovich
// Systems Internals
// http://www.sysinternals.com
//
// This file contains tyepdefs and defines from NTDDK.H.
// They are included here so that we don't have to
// include NTDDK.H and get all the other stuff that
// we don't really need or want.
//
//========================================================

#define PAGE_NOACCESS          0x01     // winnt
#define PAGE_READONLY          0x02     // winnt
#define PAGE_READWRITE         0x04     // winnt
#define PAGE_WRITECOPY         0x08     // winnt
#define PAGE_EXECUTE           0x10     // winnt
#define PAGE_EXECUTE_READ      0x20     // winnt
#define PAGE_EXECUTE_READWRITE 0x40     // winnt
#define PAGE_EXECUTE_WRITECOPY 0x80     // winnt
#define PAGE_GUARD            0x100     // winnt
#define PAGE_NOCACHE          0x200     // winnt

typedef LARGE_INTEGER PHYSICAL_ADDRESS, *PPHYSICAL_ADDRESS; // windbgkd


typedef LONG NTSTATUS;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
#ifdef MIDL_PASS
    [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
#else // MIDL_PASS
    PWSTR  Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;
typedef UNICODE_STRING *PUNICODE_STRING;

typedef enum _SECTION_INHERIT {
    ViewShare = 1,
    ViewUnmap = 2
} SECTION_INHERIT;

#define OBJ_INHERIT             0x00000002L
#define OBJ_PERMANENT           0x00000010L
#define OBJ_EXCLUSIVE           0x00000020L
#define OBJ_CASE_INSENSITIVE    0x00000040L
#define OBJ_OPENIF              0x00000080L
#define OBJ_OPENLINK            0x00000100L
#define OBJ_VALID_ATTRIBUTES    0x000001F2L


typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PUNICODE_STRING ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;        // Points to type SECURITY_DESCRIPTOR
    PVOID SecurityQualityOfService;  // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;


#define InitializeObjectAttributes( p, n, a, r, s ) { \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \
    (p)->RootDirectory = r;                             \
    (p)->Attributes = a;                                \
    (p)->ObjectName = n;                                \
    (p)->SecurityDescriptor = s;                        \
    (p)->SecurityQualityOfService = NULL;               \
    }
["1:0>1"]
El_Choni
TailBite Expert
TailBite Expert
Posts: 1007
Joined: Fri Apr 25, 2003 6:09 pm
Location: Spain

Post by El_Choni »

InitializeObjectAttributes is a macro, not a function. In fact, it's defined at the end of native.h, in the code you posted, and very easy to translate to PB.
El_Choni
User avatar
bingo
Enthusiast
Enthusiast
Posts: 210
Joined: Fri Apr 02, 2004 12:21 pm
Location: germany/thueringen
Contact:

Post by bingo »

PhysMem uses the native API to open and map \Device\PhysicalMemory because that name is inaccessible via the Win32 API.

native API ... should be possible in pb .

but to translate any macros ... "horror" :evil:

#define InitializeObjectAttributes( p, n, a, r, s ) { \
(p)->Length = sizeof( OBJECT_ATTRIBUTES ); \
(p)->RootDirectory = r; \
(p)->Attributes = a; \
(p)->ObjectName = n; \
(p)->SecurityDescriptor = s; \
(p)->SecurityQualityOfService = NULL; \
}
["1:0>1"]
User avatar
Rings
Moderator
Moderator
Posts: 1435
Joined: Sat Apr 26, 2003 1:11 am

Post by Rings »

maybe this can help (source is older)
retrieve the hardware adresses of your ports.

Code: Select all

;Physically Memory
Procedure msg(Instring.s)
 MessageRequester("Info",Instring,0)
EndProcedure
#OBJ_INHERIT = $2
#OBJ_PERMANENT = $10
#OBJ_EXCLUSIVE = $20
#OBJ_CASE_INSENSITIVE = $40
#OBJ_OPENIF = $80
#OBJ_OPENLINK = $100
#OBJ_KERNEL_HANDLE = $200
#OBJ_VALID_ATTRIBUTES = $3F2

#SECTION_QUERY = $1
#SECTION_MAP_WRITE = $2
#SECTION_MAP_READ = $4
#SECTION_MAP_EXECUTE = $8

#PAGE_READONLY = 2
#PAGE_READWRITE=4
#VIEW_SHARE = 1

Structure UNICODE_STRING
  usLength.w 
  usMaximumLength.w 
  usBuffer.s 
EndStructure
Structure UNICODE_lSTRING
  usLength.w 
  usMaximumLength.w 
  usBuffer.l
EndStructure
Structure OBJECT_ATTRIBUTES
    Length.l 
    RootDirectory.l 
    ObjectName.l 
    Attributes.l 
    SecurityDescriptor.l 
    SecurityQualityOfService.l 
EndStructure
Structure PHYSICAL_ADDRESS
    lowpart.l 
    highpart.l 
EndStructure

 ;Private Declare Function NtOpenSection Lib "NTDLL.DLL" (hdlSection.l , ByVal desAccess.l , objAtt.OBJECT_;S).l 
 ;Private Declare Function NtMapViewOfSection  Lib "NTDLL.DLL" _     __(ByVal hdlSection.l , _      ByVal hdlProcess.l , _      BaseAddress.l , _      ByVal ZeroBits.l , _      ByVal CommitSize.l , _      SectionOffset.PHYSICAL_ADDRESS, _      ViewSize.l , _      ByVal InheritDisposition.l , _ Structure ByVal AllocationType.l , _      ByVal Protect.l ).l 
 ;Private Declare Function NtUnmapViewOfSection _     Lib "NTDLL.DLL" _     ___(ByVal hdlProcess.l , _      ByVal BaseAddress.l ).l 
 ;Private Declare Function CloseHandle _     Lib "kernel32" _     ____(ByVal hObject.l ).l 
 ;Private Declare Sub CopyMemory _     Lib "kernel32" Alias "RtlMoveMemory" _     _____(Destination.l, _     Source.l, _     ByVal Length.l )

   status.l 
   ia.OBJECT_ATTRIBUTES
   hdlPhysMem.l 
   
;    usDevName.UNICODE_STRING
;    usDevName\usBuffer = "\device\physicalmemory" 
;    usDevName\usMaximumLength = Len(usDevName\usBuffer) * 2
;    usDevName\usLength = usDevName\usMaximumLength - 2
; 

mydevice.s="\device\physicalmemory" + Chr(0)  

Buffer1 = AllocateMemory( Len(mydevice)*2 + 8) 
;Dim Bytefeld.b(255)
;Buffer1.l=@Bytefeld(0)
Result=MultiByteToWideChar_(#CP_ACP ,0,@mydevice.s,-1,Buffer1,Len(mydevice.s)*2)
;msg(PeekS(Buffer1) )
;PeekS(Buffer1+2)
;Debug Hex(Buffer1)


usDevName.UNICODE_lSTRING
usDevName\usBuffer = Buffer1
usDevName\usMaximumLength = (Len(mydevice.s) * 2) +2
usDevName\usLength = Len(mydevice.s) * 2 

   ia\Length = 24;SizeOf(OBJECT_ATTRIBUTES)
   ia\ObjectName = @usDevName
   ia\Attributes  = #OBJ_CASE_INSENSITIVE
   ia\SecurityDescriptor = 0
   ia\RootDirectory = 0
   ia\SecurityQualityOfService = 0
  
   
   status = NtOpenSection_(@hdlPhysMem, #SECTION_MAP_READ, @ia)

   If status<>0
    msg("NtOpenSection: "+ Hex(status))
    sBuffer.s=Space(256)
    Result=GetLastError_()
    FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM,0,Result,0,@sBuffer,255,0)
    MessageRequester("Info NtOpenSection!",Hex(Result)+Chr(13)+sBuffer,0)
    End
   EndIf
   
   memVirtualAddress.l 
   memLen.l 
   memVirtualAddress.l = 0

;Goto weiter
   viewBase.PHYSICAL_ADDRESS
   viewBase\highpart = 0
   viewBase\lowpart = $400
   memLen = $10

   status = NtMapViewOfSection_(hdlPhysMem, -1, @memVirtualAddress,0, memLen, @viewBase, @memLen, #VIEW_SHARE, 0, #PAGE_READONLY)
   ;msg("NtMapViewOfSection: "+ Hex(status))
   
   If status<>0
    sBuffer.s=Space(256)
    Result=GetLastError_()
    FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM,0,Result,0,@sBuffer,255,0)
    MessageRequester("Info NtMapViewOfSection!",Hex(Result)+Chr(13)+ sBuffer,0)
    End
   EndIf
   
   i=0
   MyInfo.s=MyInfo.s + "COM1="+Hex(PeekW(memVirtualAddress - viewBase\lowpart + $400 + i)) + Chr(13)
   i=2
   MyInfo.s=MyInfo.s + "COM2="+Hex(PeekW(memVirtualAddress - viewBase\lowpart + $400 + i)) + Chr(13)
   i=4
   MyInfo.s=MyInfo.s + "COM3="+Hex(PeekW(memVirtualAddress - viewBase\lowpart + $400 + i)) + Chr(13)
   i=8
   MyInfo.s=MyInfo.s + "LPT1="+Hex(PeekW(memVirtualAddress - viewBase\lowpart + $400 + i)) + Chr(13)
   adrLPT1.l=PeekW(memVirtualAddress - viewBase\lowpart + $400 + i)
   i=10
   MyInfo.s=MyInfo.s +"LPT2="+ Hex(PeekW(memVirtualAddress - viewBase\lowpart + $400 + i)) + Chr(13)
   i=12
   MyInfo.s=MyInfo.s +"LPT3="+ Hex(PeekW(memVirtualAddress - viewBase\lowpart + $400 + i)) + Chr(13)

   MessageRequester("Info",MyInfo.s,0)

   status = NtUnmapViewOfSection_(-1, memVirtualAddress)

weiter:  

   Offset=$378
   viewBase.PHYSICAL_ADDRESS
   viewBase\highpart = 0
   viewBase\lowpart = Offset
   memLen = $10 ;16 Bytes
   
   memVirtualAddress=1
   
   status = NtMapViewOfSection_(hdlPhysMem, -1, @memVirtualAddress,0, memLen, @viewBase, @memLen, #VIEW_SHARE, 0, #PAGE_READONLY)
   
   If status<>0
    sBuffer.s=Space(256)
    Result=GetLastError_()
    FormatMessage_(#FORMAT_MESSAGE_FROM_SYSTEM,0,Result,0,@sBuffer,255,0)
    MessageRequester("Info NtMapViewOfSection!",Hex(Result)+Chr(13)+ sBuffer,0)
    End
   
   EndIf
;    i=0
;    Repeat
;     Event = WindowEvent()
;     Delay(50)
;     MyInfo=""
;     For i=0 To 9;memLen -1
;      MyInfo=Myinfo +Bin(PeekB(memVirtualAddress - viewBase\lowpart + Offset  + i)) +";"; Chr(13)
;      ;MyInfo=Myinfo +Right("00"+Hex(PeekB(memVirtualAddress - viewBase\lowpart + Offset  + i)),2) +";"; Chr(13)
;     Next i 
;     If MyInfo.s<>Oldinfo.s 
;      SetGadgetText(#Gadget_1,MyInfo.s)
;      OldInfo=MyInfo
;     EndIf 
;    Until Event = #PB_EventCloseWindow
;   MessageRequester("Info",Hex(viewBase\lowpart)+ MyInfo.s,0)

  
   status = NtUnmapViewOfSection_(-1, memVirtualAddress) 
   status = CloseHandle_(hdlPhysMem)
  
End
SPAMINATOR NR.1
User avatar
bingo
Enthusiast
Enthusiast
Posts: 210
Joined: Fri Apr 02, 2004 12:21 pm
Location: germany/thueringen
Contact:

Post by bingo »

thanks :D ...

now a sample . biosdump ... and no device driver (winio...) needed to map any memory !!!

Code: Select all

;biosdump $F0000-$FFFFF To c:\biosimage.tmp
;tested on xp/sp2

Procedure.l Ansi2Uni(ansi.s) 
SHStrDup_(@ansi,@memziel)
ProcedureReturn memziel
EndProcedure

#OBJ_CASE_INSENSITIVE = $40 

#SECTION_MAP_READ = $4 

#PAGE_READONLY = 2 

#VIEW_SHARE = 1 

Structure UNICODE_lSTRING 
  usLength.w 
  usMaximumLength.w 
  usBuffer.l 
EndStructure 

Structure OBJECT_ATTRIBUTES 
    Length.l 
    RootDirectory.l 
    ObjectName.l 
    Attributes.l 
    SecurityDescriptor.l 
    SecurityQualityOfService.l 
EndStructure 

Structure PHYSICAL_ADDRESS 
    lowpart.l 
    highpart.l 
EndStructure 

status.l 
ia.OBJECT_ATTRIBUTES 
hdlPhysMem.l 
    
mydevice.s="\device\physicalmemory"

usDevName.UNICODE_lSTRING 
usDevName\usBuffer = Ansi2Uni(mydevice)  
usDevName\usMaximumLength = (Len(mydevice) * 2) + 2 
usDevName\usLength = Len(mydevice) * 2 

ia\Length = SizeOf(OBJECT_ATTRIBUTES) 
ia\ObjectName = @usDevName 
ia\Attributes  = #OBJ_CASE_INSENSITIVE 
ia\SecurityDescriptor = 0 
ia\RootDirectory = 0 
ia\SecurityQualityOfService = 0 
  
status = NtOpenSection_(@hdlPhysMem, #SECTION_MAP_READ, @ia) 
   
If status<>0 
Debug "error"
End
EndIf 
    
memVirtualAddress.l 
memLen.l 
   
viewBase.PHYSICAL_ADDRESS 
viewBase\highpart = 0
viewBase\lowpart = $F0000
memLen = $FFFF
   
status = NtMapViewOfSection_(hdlPhysMem, -1, @memVirtualAddress,0, memLen, @viewbase, @memLen, #VIEW_SHARE, 0, #PAGE_READONLY) 
   
If status<>0 
Debug "error"
End
EndIf 
    
OpenFile(1,"c:\biosimage.tmp")
For i=0 To memLen-1
WriteByte(PeekB(memVirtualAddress + i))
Next
CloseFile(1)

status = NtUnmapViewOfSection_(-1, memVirtualAddress) 
status = CloseHandle_(hdlPhysMem) 
End
i like purebasic ... :arrow:
["1:0>1"]
okasvi
Enthusiast
Enthusiast
Posts: 150
Joined: Wed Apr 27, 2005 9:41 pm
Location: Finland

Post by okasvi »

what this could be used?
DevilDog
Enthusiast
Enthusiast
Posts: 210
Joined: Thu Aug 04, 2005 9:32 pm
Location: Houston, Tx.

What does this code do?

Post by DevilDog »

I know this is an old post but I'm looking for PB code that can give me information about my PC, such as BIOS, HD info, Memory, etc.

I need to write a program that can get the kind of info you can get using Belarc Advisor http://www.belarc.com/free_download.html for my client.

I think much of the software that is installed I can get pretty easily, but the low level stuff I'm having more trouble with.

Anyway, I came across this code and I'm not clear what it does and whether it's something I can use to get the info I need.

Can someone explain what it does?

Thanks
When all is said and done, more is said than done.
Post Reply