The MASM Forum Archive 2004 to 2012

General Forums => The Campus => Topic started by: falcon01 on July 28, 2011, 03:32:03 PM

Title: x86 Assembler on windows...how to start?
Post by: falcon01 on July 28, 2011, 03:32:03 PM
Hi, I already had some experience with assembly programming for motorola68k,mips and intel 8086, everything went well because I had every simulator/tool I needed + readable guides.

Now I'm trying to learn x86 but
1)I can't find any simulator (I want to execute my code step by step, view registers/memory,things like that...with a gui would be the best)
2)I can't assemble!I tried masm but...every guide I find starts in a different way! One tells me to write a c program, another starts with a segment directive, another writes directly .data and .code, another one switches directly to code...

So, out of desperation, I beg for your help!
Can you give me tools and write here a small tutorial in order to build a simple "hello world" program?
Title: Re: x86 Assembler on windows...how to start?
Post by: baltoro on July 28, 2011, 04:02:10 PM
A great place to start is: Hutch's Home Page (http://www.movsd.com/)
And, as for beginner's tutorials,...most of us started with: Iczelion's Example Code (http://www.movsd.com/icz.htm)
Have fun here,...It's really a great forum.
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 28, 2011, 04:28:29 PM
Thanks but...it's not different from what I already found on the net...iczelion just gives some code away, I'd like to have a "how to build and run your x86 assembly code" guide, I already know something about architecture, registers, op codes, ecc. it's just I can't get a program run!
For example I tried to make this code but it does not work:

.386                        ;I added this looking at iczelion but it does assemble even without it
.model small
.stack 1024

.data
MyMessage      db "Hello x86!$"

.code
start:
lea bx,MyMessage
mov dx,offset MyMessage
mov ah,9
int 21h            ;prints the string MyMessage on the console
return
end start


I did this in order to compile:
Quote1. masm hello.asm
2. link hello.obj

Then I open my command line, write "hello.exe" and...no writing at all! Why?I'm sure the system call is correct...
Title: Re: x86 Assembler on windows...how to start?
Post by: jj2007 on July 28, 2011, 04:29:51 PM
My tips & tricks page (http://www.webalice.it/jj2006/Masm32_Tips_Tricks_and_Traps.htm) has a Hello World example and step by step instructions. Iczelion's tutorial should come once you get bored of Hello World.

Welcome to the Forum :thumbu
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 28, 2011, 04:51:24 PM
Thanks to you too, for help and welcome, but I'd like to not use any macro like in your example (masm32rt.inc is a macro file right?) ... don't you have a simple print on console (with system call) example?Or maybe try my program and suggest me what's wrong with it?
I think my level of assembler will stay confined on command line, I don't plan going so far like making a gui for my programs.
Title: Re: x86 Assembler on windows...how to start?
Post by: BogdanOntanu on July 28, 2011, 05:05:22 PM
Your program uses int21h ... it is an old DOS kind of 16 bits program...

Try to forget about this kind of obsolete ASM examples and immerse your self into 32 bits windows programming (can be console and not gui if you so like).

Title: Re: x86 Assembler on windows...how to start?
Post by: jj2007 on July 28, 2011, 05:17:42 PM
Bogdan is right, forget 16-bit code. Here is a 32-bit Hello World example, for testing if your installation works:

include \masm32\include\masm32rt.inc

.code
AppName db "Masm32:", 0

start: MsgBox 0, "Hello World", addr AppName, MB_OK
exit

end start


As to macros: I understand that you want the "pure" code, but nothing is pure nowadays. Even if you replace print "hello world" with the full invoke StdOut etc stuff, you are using a third party code. In this context, \masm32\macros\macros.asm is a fascinating lecture :bg
Title: Re: x86 Assembler on windows...how to start?
Post by: FORTRANS on July 28, 2011, 05:20:23 PM
Hi,

   The masm32rt.inc is an include file that gived you access
to a run-time library (or DLL).  You can look at 16-bit programs
in DEBUG.EXE.  That will let you see the register contents.
Your program is not exiting correctly.  An *.EXE program should
use function 4C to exit to DOS.  You should use an input character
function to pause the program so you can see the message if
Windows is closing your program too quickly.

   I don't use the .MODEL directive, so this may be extra but
won't hurt.  You did not initialize the data segment.


; Put these two lines as the first lines in your program
        MOV     AX,SEG DGROUP
        MOV     DS,AX


HTH,

Steve N.

P.S.
Just saw Bogdan and JJ respond.  This forum is intended
mostly for 32-bit Windows code using the MASM32 package.

SRN
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 28, 2011, 05:23:55 PM
he obviously does not have masm32 installed
he used "link" to create an MZ EXE   :bg

step 1) install the masm32 package
the link is in the upper right corner of the forum
Title: Re: x86 Assembler on windows...how to start?
Post by: jj2007 on July 28, 2011, 05:28:04 PM
Quote from: dedndave on July 28, 2011, 05:23:55 PM
he obviously does not have masm32 installed

Again, see my signature for step by step instructions. By the way, your "emulator" is called Olly - see http://www.ollydbg.de/version2.html
Title: Re: x86 Assembler on windows...how to start?
Post by: mineiro on July 28, 2011, 05:45:01 PM
Hello falcon01, welcome. Well, I'm putting here your question, but follow other peoples sugestions, don't have fear, programming to windows is more easy than to ms-dos. If you like to talk about ms-dos, this forum have a special section to this, look in front page.
Quotegrupo group seg_cod,seg_dados
assume cs:grupo,ds:grupo

seg_cod segment public
org 100h

imp_string proc far
mov ah,9
mov dx,offset grupo:string
int 21h
int 20h
imp_string endp

seg_cod ends

seg_dados segment public
string db "Hello x86!$"
seg_dados ends

end imp_string

Quote
masm hello.asm
link hello.obj
exe2bin hello.exe hello.com
Oh, I forgot to say, you can use "emu 8086" too, search by it on net.
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 28, 2011, 08:40:32 PM
Ok I installed masm32 and it gives me this error:
Quotemov dx,offset Mystring <--error A2022: instruction operands must be the same size
maybe it's for this reason my program does not work!What's its meaning?

2 mineiro:
your code works fine, could you help me to understand it?It's pretty much the same as mine except for the fact you use segments and no directives as .model,.data e .code...what's the difference?
Moreover I like 8086emu much (I used it for 8086 assembly indeed), but I need x86 programming...too bad I'll have to use ollydb:(
2 fortrans:
even with the exit function (thanks for the hint anyway!) it still does not work!
2 everyone:
I don't really mind programming in windows or MS DOS, as long I can make command line programs...the thing is I don't want message boxer or any sign of gui...
Title: Re: x86 Assembler on windows...how to start?
Post by: baltoro on July 28, 2011, 08:42:39 PM
Offests are 32-bit values, so, use the full register (EDX) instead of the 16-bit portion.
mov edx, offset Mystring

This MASM forum thread: INVOKE and ADDR Directives  (http://www.masm32.com/board/index.php?topic=16388.0) is an interesting discussion of the actual functions of the ADDR and OFFSET directives.   

The reason the guys are showing you code that uses the MessageBox function is that it's a standard Windows API function. It's there primarily to demonstrate the INVOKE syntax, which is a very common and useful technique in MASM programming. Of course, you can accomplish essentially the same thing without using the MessageBox finction.

You can use the search function for the MASM forum, and, you will discover all kinds of great threads about subjects that you have not yet asked, but, other novice assembly programmers have. Here's what I do: I click on the Advanced Search link, and enter "dedndave" into the, "By User" text box,...then deselect all forums, and, re-select just the Campus, Workshop, and, Laboratory sub-forums. Supply keywords for the "Search For" text box (subject you are interested in), or, an asterisk (*) to select all. Dave posts all kinds of useful demo code. It's clear, it works, and, he explains everything in great detail. I'd still be a really crappy programmer, if it wasn't for Dave.

Here's a short example program that I stole from Dave:   

        INCLUDE \masm32\include\masm32rt.inc

        .DATA

Hello   db 'Hello World!'

        .DATA?

ByteCnt dd ?

        .CODE

_main   PROC

        INVOKE  GetStdHandle,STD_OUTPUT_HANDLE
        INVOKE  WriteFile,eax,offset Hello,sizeof Hello,offset ByteCnt,NULL
        INVOKE  ExitProcess,0

_main   ENDP

        END     _main
 

Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 28, 2011, 09:07:13 PM
Thanks much the error is fixed but...program still doesn't work!
And I discovered another thing: mineiro program does work as long I execute the .com file, the .exe crashes!
Ah by the way I'm assembling mineiro's program with MASM5.0, if I try assembling it with MASM32 it gives some errors...I'm still far away from the solutionT_T

I'll try to see invoke functions, though I love explicit interrupts more...
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 28, 2011, 09:13:44 PM
the EXE crashes because the DS segment register is not set
for COM programs, CS, DS, ES, SS all point to the same segment

now, if your having trouble with the 32-bit example, it may be because you are not using the correct command line switches for ML and/or LINK
ml /c /Fl /coff MyProg.asm
link /SUBSYSTEM:CONSOLE /OPT:NOREF MyProg.obj


be sure you are using the link that is in masm32\bin for 32-bit programs
the one that came with masm 5.0 is for 16-bit programs
you may have to check the PATH environment variable, too

the /Fl switch is not necessary, but it may be used to generate a listing

QuoteI'll try to see invoke functions, though I love explicit interrupts more
as far as i know, you can't make a GUI window with interrupts   :P
at least, not easily - lol
Title: Re: x86 Assembler on windows...how to start?
Post by: mineiro on July 28, 2011, 09:14:47 PM
I have commented the code based in your questions.

grupo group seg_cod,seg_dados ;here I'm telling masm to join 2 segments, the code segment(cs) and data segment(ds)
assume cs:grupo,ds:grupo ;telling to masm to treat cs and ds like a joined group segment, the name is grupo, but you can put here any other name

seg_cod segment public ;public tell to masm that this segment can be seen by other procedures
                                 ;this is the same as ".code"
org 100h                     ;I'm going to build a .com(exe2bin) file and not .exe, this is the same as .model small

imp_string proc far
mov ah,9
mov dx,offset grupo:string
int 21h
int 20h                       ;.com files end(return to O.S) with int 20, if this one is a .exe, you need end it in another way, continue reading.
imp_string endp

seg_cod ends             ;here is where my code segment ends.

seg_dados segment public    ;my data segment, the same as .data
string db "Hello x86!$"
seg_dados ends                  ;data segment ends here

end imp_string                  ;the entry point of this program.

Some questions are in the air, I know, like "why I have joined 2 segments?", follow link below:
http://www.masm32.com/board/index.php?topic=17059.0
Sr dedndave posted in the link below a hello world, in ms-dos(.exe format) or win32:
http://www.masm32.com/board/index.php?topic=17128.0
Title: Re: x86 Assembler on windows...how to start?
Post by: baltoro on July 28, 2011, 09:21:54 PM
The problem is that in Windows XP and later Operating Systems, many of the original MS DOS interrupts are NOT supported.
Troubleshooting MS-DOS-based programs in Windows XP (http://support.microsoft.com/kb/314106/EN-US)
Title: Re: x86 Assembler on windows...how to start?
Post by: mineiro on July 28, 2011, 10:50:34 PM
To not confuse you, I have rewriten that example. This one is to generate .exe files. Save it as hello.asm

code_segment segment public
imp_string proc far
mov ax,data_segment
mov ds,ax
mov ah,9
mov dx,offset string
int 21h
mov ah,4ch    ;end of our program, lets give control back to O.S.
int 21h
imp_string endp

code_segment ends

data_segment segment public
string db 'hello world!$'
data_segment ends

stack_segment segment stack
db 10 dup ('stack   ')
stack_segment ends

end imp_string

masm hello.asm
link hello.obj

Assembled with masm v5.0, link v3.60.
Title: Re: x86 Assembler on windows...how to start?
Post by: jj2007 on July 28, 2011, 10:59:15 PM
Quote from: mineiro on July 28, 2011, 10:50:34 PM
To not confuse you, ...
...
masm hello.asm
link hello.obj

Hi Mineiro,

You are confusing him. He needs 32-bit console apps, not 16-bit code; besides, in a standard Masm32 installation link launches the 32-bit linker, which will fail on your code.

Here is hello world in 32-bit console mode, no macros and no invoke. Don't forget your linker needs /Subsystem:CONSOLE

include \masm32\include\masm32rt.inc

.code
txHW db "Hello World", 0

start: push offset txHW
call StdOut
call ret_key
push 0
call ExitProcess

end start
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 29, 2011, 12:31:19 PM
Thanks everybody for helping!
Mineiro code works fine as long as I compile/link it with masm 5.0, anyway what's the problem with 16 bit code?Maybe it's because I can't use extended registers?You just say "it's obsolete" in your site but I'd like to know some reason...

jj2007 your code runs but I can't see the "hello world" string...maybe it's because it closes too fast? And what's the meaning of ret_key subroutine?
Moreover I can't see .data segment...it's ok to put everything in .code?
Anyway where can I find a list of 32 bit system call?For now I woud just need these:

1)Print a string on console (StdOut after putting string address on top of the stack right?)
2)Get a char or string or integer from console (I'm not so sure I can use 8086 interrupts now...)

so if you can simply write them here would be great

Quote from: dedndave on July 28, 2011, 09:13:44 PM
as far as i know, you can't make a GUI window with interrupts   :P
at least, not easily - lol
What part of
Quote from: falcon012 everyone:
I don't really mind programming in windows or MS DOS, as long I can make command line programs...the thing is I don't want message boxes or any sign of gui...
Is not clear to you?XD
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 29, 2011, 12:42:00 PM
for some of these programs....
try opening the console window and typing the program name at the prompt
that way, the console doesn't close immediately
if you click on the program in explorer, it may run and close before you see it
Title: Re: x86 Assembler on windows...how to start?
Post by: jj2007 on July 29, 2011, 12:44:47 PM
Quote from: falcon01 on July 29, 2011, 12:31:19 PM
jj2007 your code runs but I can't see the "hello world" string...
> maybe it's because it closes too fast?
Try from a DOS prompt. But I have a suspicion that you ignored the remark about Subsystem console above.

> And what's the meaning of ret_key subroutine?
To wait for a keypress.

> Moreover I can't see .data segment...it's ok to put everything in .code?
Read-only stuff, yes.

> Anyway where can I find a list of 32 bit system call?For now I woud just need these:
\masm32\macros\macros.asm is a very good source of inspiration.

> 1)Print a string on console (StdOut after putting string address on top of the stack right?)
Yes, exactly.

> 2)Get a char or string or integer from console (I'm not so sure I can use 8086 interrupts now...)
invoke StdIn, lpszBuffer, bufferLen
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 29, 2011, 12:45:33 PM
Ok solved, my fault! I actually forgot console linking lol!

So, an example of reading a single char from keyboard would be

push  eax ;char will be in eax
push  1    ;just a char
call StdIn

?
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 29, 2011, 12:48:14 PM
are you using the correct command line switches ?
\masm32\bin\ml /c /coff MyProg.asm
\masm32\bin\link /SUBSYSTEM:CONSOLE /OPT:NOREF MyProg.obj
Title: Re: x86 Assembler on windows...how to start?
Post by: FORTRANS on July 29, 2011, 01:21:02 PM
Hi,

   Modifying your original program with my suggested changes.
And such...

Steve


.model small
.stack 1024

.data
MyMessage      db "Hello x86!",13,10," Press a key to exit. $"

.code
start:
; Put these two lines as the first lines in your program
        MOV     AX,SEG DGROUP ; Can use @DATA as well.
        MOV     DS,AX

lea bx,MyMessage ; unused
mov dx,offset MyMessage
mov ah,9
int 21h            ;prints the string MyMessage on the console
;;return ; Not a valid opcode.

        MOV     AH,1    ; Console input character.
        INT     21H

        MOV     AH,04CH ; Exit to DOS.
        INT     21H
end start
Title: Re: x86 Assembler on windows...how to start?
Post by: jj2007 on July 29, 2011, 01:21:25 PM
Quote from: falcon01 on July 29, 2011, 12:45:33 PM
Ok solved, my fault! I actually forgot console linking lol!

So, an example of reading a single char from keyboard would be

push  eax ;char will be in eax
push  1    ;just a char
call StdIn

?

Not the best design. Eax should point to a buffer, and the user should not enter more than one char, otherwise bang...
Try this:
include \masm32\include\masm32rt.inc

.data?
zebuffer db 100 dup(?) ; create a buffer here

.code
zeprompt db "Type something and hit Return: ", 13, 10, 0
zeresultis db 13, 10, 10, "The result is: ", 0

start: invoke StdOut, offset zeprompt
invoke StdIn, offset zebuffer, 100
invoke StdOut, offset zeresultis
invoke StdOut, offset zebuffer
call ret_key
exit

end start
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 29, 2011, 01:32:55 PM
So I can't put user's digit in a register before actually storing it in memory?
That's not very effective...say I have to do a check on the character, I have to do 2 accesses in memory, one for storing and one for retrieving again...8086 "wait for keystroke" int which puts the char in AL register was way better in this case :(

Anyway, in order to have some pratice I did this small piece of code (before reading your last post), could you tell me what you think about? It does pretty much the same thing: requests an input to user and displays it.


include \masm32\include\masm32rt.inc

.data
firstStr db "Enter a number (1-3 digit):", 0
secStr   db "You entered: ",0
buff     db 0

.code

start:
            push offset firstStr
            call StdOut
            invoke StdIn,ADDR buff,3 ;if user enters more than 3 char I discovered they are ignored
            lea  ebx,buff            ;loading buff address in ebx (in x86 addresses are 32 bit long, right?)
            xor  al,al               ;putting 0 in al
            mov  [ebx+3],al          ;putting \0 at the end of char array
           
            push offset secStr
            call StdOut              ;displaying the second string
           
            push ebx
            call StdOut              ;displaying the number
           
push 0                   ;exiting
call ExitProcess

end start
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 29, 2011, 03:29:21 PM
Oh and another thing...how come if I call subsequently stdout->stdin->stdout->stdin
the latter stdin is skipped? I think it's because keyboard still contains \n char...is there a way to "flush"?
Title: Re: x86 Assembler on windows...how to start?
Post by: jj2007 on July 29, 2011, 03:29:58 PM
You are on the right track, but try this:

buff     db 0
Ouch db "That was a valuable text", 0
...
           push offset Ouch
           call StdOut              ; display your 'valuable text' ;-)
           push 0                   ;exiting
           call ExitProcess
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 29, 2011, 04:14:36 PM
the StdIn "value" is stored in memory because it is a string - not a single character
it can be any practical length so, once converted to a binary value, it could easily be too large for a register

all versions of windows include a library of functions called the "MS Visual C Runtime Library"
the file is named msvcrt.dll
in the msvcrt, there are a number of functions that handle single-character keyboard input for the console
while it can be done with windows API functions, the msvcrt is much easier to work with
i have written a few keyboard routines using the API, and they never seem to behave as nicely as the msvcrt functions
this is due, in part, because the console has bugs that are difficult to overcome

2 of the functions that you may be interested in behave like the old INT 16h stuff
crt__kbhit lets you know if there is a character in the keyboard buffer
crt__getch gets a single character from the keyboard buffer
have a look at the masm32\m32lib\wait_key.asm file
you will see how crt__kbhit and crt__getch may be used
using those 2 functions, you can write a single-character entry routine
that way, you could do something like filtering out non-numeric keys, etc
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 30, 2011, 09:09:05 AM
Thanks a bunch, these char routines are just what I wanted!Only problem is that echo is disabled..but I'll combine it with a print char function (whose name is..?XD), no problem.

And if I make a 100byte buffer I discovered I can handle strings too because the stdout->stdin->stdout->stdin sequence works...maybe it was the 1 byte allocation error's doing.
Thanks to everybody now I feel I can at least START making some assembly programs in 32 bit!:D

Mind if I ask some last questions about x86 architecture and instructions in general?
1)What's the difference betweeen lea, offset and address?They do the same thing to me...
2)Is there a way to refer to upper 16 bits of extended registers?Like AX for lower 16 bits.
3)I heard that, respect 8086, they improved the MUL operation...now it should work like this: MUL AX,BX (AX=AX*BX),true?
4)Both addresses and data are 32-bit long and x86 is basically CISC right?So there are instruction which take more space than others.
Title: Re: x86 Assembler on windows...how to start?
Post by: FORTRANS on July 30, 2011, 12:02:57 PM
Hi,

   1)  LEA is an opcode or instruction.  It is executed by the
processor.  OFFSET or address are assembler directives.
They are evaluated by the assembler, not the processor.

   2)  No, not directly.  you can move the contents of a
32-bit register like EAX to access the upper 16 bits directly.
Look at the instructions BSWAP or the rotate instructions
ROL and ROR.

   3)  That might be the IMUL instruction you are thinking
about.  IMUL got extended versions 8086 => 80386.
MUL BX does something like AX:DX = AX * BX.

   4)  Yes, insructions can range from one byte to around
15 bytes long.  The shortest perform fixed operations.
longer instructions include immediate data and instruction
prefixes such as REP, LOCK, and segment overrides for
instance.

HTH,

Steve N.
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 30, 2011, 12:42:02 PM
as to the 1-byte buffer...
you should always allocate 1 byte more than needed   :P
the StdIn routine needs a place for a terminator
if you tell it you have 1 byte, it is full when you start

besides the instructions Steve mentioned, i have had occassion to use another method to access the upper 16 bits
if i want to zero extend or sign extend the upper 16 bits of a register, without destroying the original contents.....
        push    eax
        movzx   edx,word ptr [esp+2]
        pop     eax

        push    eax
        movsx   edx,word ptr [esp+2]
        pop     eax

it may not be particularly fast, but neither is BSWAP   :bg
these code sequences do not alter any flags - also handy
if you do this....
        mov     edx,eax
        shr     edx,16

the flags are altered, and the result is always zero extended
Title: Re: x86 Assembler on windows...how to start?
Post by: falcon01 on July 30, 2011, 01:28:17 PM
Thanks much ^^
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 30, 2011, 11:55:36 PM
i have wanted something like this for a while
so, i took a break from my other project and wrote this routine

you can filter numerics only, alpha only, no spaces, and combinations
i use crt__kbhit and crt__getch
in the spirit of fun, i use WriteFile in the routine instead of the print macro   :P
i do use the macros in the test code, though.....
Console Input Test: sdfsdfSDFDF,....   234242

     Length EAX: 25
Buffer Size ECX: 255
   Buffer [EDX]: sdfsdfSDFDF,....   234242

Spaces Allowed:
      No Filtering: sdfsdfsdSDFD.....   234
      Numeric Only:    345345345  353534
        Alpha Only: sfsdfsdf   sdfsdfSDFS sdf
Alpha-Numeric Only: sdfsddSDFD34234   234234

Spaces Not Allowed:
         No Spaces: sdfsdf3242423....
      Numeric Only: 34234234
        Alpha Only: sdfdfsdSDFSD
Alpha-Numeric Only: dfsdfsd234234SDFSDF3234


this one supports backspace
but it does not allow line editing like arrow keys or insert mode/overwrite
it does allow tab, but i will also make backtab work and add the line editing stuff later
Title: Re: x86 Assembler on windows...how to start?
Post by: FORTRANS on July 31, 2011, 12:16:48 PM
Hi,

   An addendum to Dave's code in reply #32.


        MOV     EDX,EAX ; Make working copy..
        SAR     EDX,16  ; Move high word to low word,
                        ; sign extended, flags affected.


   The PUSH/access/POP stuff was new to me at least, thanks.

Cheers,

Steve N.
Title: Re: x86 Assembler on windows...how to start?
Post by: dedndave on July 31, 2011, 03:08:05 PM
ah yes - should have thought of that, Steve   :U