CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

Magnetic card reader

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Ed Arnold
Guest







Magnetic card reader
PostPosted: Sun Oct 27, 2002 1:39 am     Reply with quote

<font face="Courier New" size=-1>Has anyone written any code to interface a magnetic card reader. I am trying to code using a Magtek reader to read track 2. This reader has a ttl interface with all active low outputs. A _CD(card detect) line, a _STB(strobe) line, and _DATA line. Data is valid on the neg edge of the strobe line. The following code is what I have done, but so far no joy. Does anyone have any input?

#include <16f84.h>
#fuses HS, NOWDT, NOPROTECT
#use delay(clock=8000000)
#use fast_io(a)
#use fast_io(b)
#zero_ram

#include <lcd.c>

#define _CD PIN_A2 //Card Detect line (active low)
#define _CLK PIN_A3 //Card Clock Line (active low)
#define _CDATA PIN_A4 //Card Data Line (active low)
#define MAX 5
#define MID 245
#define MIN 250

#define BUFF_SIZE 16
char CR_buff[BUFF_SIZE];
int in=0;
int out=0;
int edge=0;
short CDtst=0;
int err=0;
int bit=0;

///.............................................................
#INT_RTCC
void card_reader_poll()
{
//int bit=0;
//edge=0;
set_rtcc(5); //default 1msec
if(!input(_CD))
{
switch(input_a() & 0x0C)
{
case 0x08 : //CD low, CLK high
CDtst=1;
edge++; //CLK edge detect
set_rtcc(MIN); //MIN time = 250 (20usec)
return;
case 0x00 : //CD low, CLK low
if(edge>0)
{
CR_buff[in]=(CR_buff[in]>>1 | input(_CDATA)); //get next incoming bit
bit++;
edge=0;
}
if(bit==4)
{
CR_buff[in]>>=3;
in=++in\%16;
bit=0;
}
set_rtcc(MID); //MID time = 245 (40usec)
return;
default :
err++;
return;
}
}
else if(input(_CD))
{
in=0;
out=0;
CDtst=0;
return;
}
}
///.............................................................

main()
{
setup_timer_0(RTCC_INTERNAL | RTCC_DIV_8); //4usec x 255 1.04msec
enable_interrupts(INT_RTCC);
enable_interrupts(GLOBAL);
set_tris_a(0xFF);
lcd_init();
printf(lcd_putc,"Card Reader ver 1.0\n");
delay_ms(500);
while(1)
{
lcd_goto_xy(1,2);
printf(lcd_putc,"CD\%1u CLK\%3u BIT\%1u ERR 3u",CDtst,edge,bit,err);
while(!input(_CD) && in==out);
lcd_goto_xy(out+1,1);
CR_buff[out]=(CR_buff[out]&0x3F);
printf(lcd_putc,"\%x",CR_buff[out]); //output is always 0
out=++out\%16;
}
}

output is always the same hex value 00
Thanks in advance
</font>
___________________________
This message was ported from CCS's old forum
Original Post ID: 8213
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

Re: Magnetic card reader
PostPosted: Mon Oct 28, 2002 12:16 pm     Reply with quote

:=<font face="Courier New" size=-1>Has anyone written any code to interface a magnetic card reader. I am trying to code using a Magtek reader to read track 2. This reader has a ttl interface with all active low outputs. A _CD(card detect) line, a _STB(strobe) line, and _DATA line. Data is valid on the neg edge of the strobe line. The following code is what I have done, but so far no joy. Does anyone have any input?
:=</font>
-----------------------------------------------------
I have code for a Panasonic ZU-1870MA4T4 (Digikey p/n PCR-109).
This code is for reading Track 1. The reader uses the same
type of interface that you describe above. Do you want me to
post the code ?
___________________________
This message was ported from CCS's old forum
Original Post ID: 8240
Sean Petty
Guest







Re: Magnetic card reader
PostPosted: Mon Oct 28, 2002 3:20 pm     Reply with quote

I would very much like to see that code! Please!

:=:=<font face="Courier New" size=-1>Has anyone written any code to interface a magnetic card reader. I am trying to code using a Magtek reader to read track 2. This reader has a ttl interface with all active low outputs. A _CD(card detect) line, a _STB(strobe) line, and _DATA line. Data is valid on the neg edge of the strobe line. The following code is what I have done, but so far no joy. Does anyone have any input?
:=:=</font>
:=-----------------------------------------------------
:=I have code for a Panasonic ZU-1870MA4T4 (Digikey p/n PCR-109).
:=This code is for reading Track 1. The reader uses the same
:=type of interface that you describe above. Do you want me to
:=post the code ?
___________________________
This message was ported from CCS's old forum
Original Post ID: 8248
Ed Arnold
Guest







Re: Magnetic card reader
PostPosted: Mon Oct 28, 2002 5:50 pm     Reply with quote

:=This code is for reading Track 1. The reader uses the same
:=type of interface that you describe above. Do you want me to
:=post the code ?

Yes, please any examples would be helpful!

Thanks

Ed Arnold
___________________________
This message was ported from CCS's old forum
Original Post ID: 8254
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

card.c
PostPosted: Mon Oct 28, 2002 11:13 pm     Reply with quote

:=Yes, please any examples would be helpful!
:=
--------------------------------------------------

I wrote the following routines a few years ago, so they
may not be as perfect as I would write them today.
For example, there's one place where I say that I
should have a timeout, but I don't, because the timing
is very tight. Well, today I probably wouldn't accept
that excuse from myself. I'd find a way to do it.
Anyway, with that said, the routines are very reliable.
-----------------------------------------------

Code:
//  card.c  -- Card reader routines
//
//
//  get_card_data() --
//
//  This routine must be called immediately after the card
//  is removed from the slot.  This routine will then read
//  the serial data from the card reader, and assemble it
//  into bytes, and put it into a global array.  This
//  routine will also put the character count in a global
//  variable.  The routine will return TRUE if no error
//  occurred, and FALSE if we got a parity or LRCC error.
//
//
/*
The signals look like this:
(not to scale)
       ___                /CLD = 0 means card removed
/CLD      |_______________________________________________

                             300 us
       _________   300 us  __________   300 us   _________
Data            |_________|          |__________|

            ________   _______   ________   _______
Clock  ____|        |_|       |_|        |_|       |______
                    8.6 us


Clock periods, based on the speed of pulling out the card:

Ultra-fast:      160 us
very fast:       300 us
moderate:        500 us
slow:            1 ms
Ultra-slow:      3 ms

If I pull it out any slower than the "Ultra-slow" rate,
I get parity errors.

Clock pulse width is always 8.6 us.
Data to clock setup is always 4.3 us.

/CLD low to the first clock is about 20 ms.
/CLD low to the first data is about 50 ms

The inactive state for Data is a logic 1, and for Clock a logic 0.


POSSIBLE ERROR CONDITIONS:

1.  The card is inserted fully, and then pulled out slightly.
    The /CLD line goes low and stays there.  There are no clocks.

    Solution:
    After /CLD goes low (card is removed), wait for /CLK to go high.
    This normally takes from 10 to 110 ms (during a good card read).
    If it does not go high within 200 ms, then timeout and return.


2.  The card is inserted fully, and then pulled out slightly and
    pushed back in.  The /CLD line goes low for a 330 ms pulse
    (or so), and then goes high and stays high. There are no clocks.
    The clock line stays low.

    Solution:  Same as #1.


3.  The card is inserted fully, and then pulled out slightly and
    pushed back in.  The /CLD line goes low for a 330 ms pulse
    (or so). There are a few clocks near the end of the /CLD pulse.
    (This is the same as #2 above, except there are a few clocks at
    the end.)  The data line remains high constantly.  Because the
    data is inverted, that means it's stuck at a constant zero level.
    The get_card_data() routine locks up in the loop that counts zero
    bits, because it never sees a logic one bit to break it out of
    the loop.  The number of clock pulses that occur can vary.
    In fact, the code is more likely locking up in the get_data_bit()
    routine, where it's waiting for the clock pulse to go high.
    That's because after /CLK does 10 to 30 negative pulses or so,
    it goes low and stays there.

    Solution:
    Put a counter in the while(1) loop that counts zero bits, while
    it looks for the first non-zero bit.   If it counts up too many
    zero bits (say 250), then have it break out with an error.

    Also put a counter in the 2nd loop in the get_data_bit() routine.
    This will allow a timeout in the loop that waits for clock to go
    high.

*/
//========================================
// This routine expects the card to be in motion, and just starting
// to be pulled out of the card reader.  Returns True if good data
// was read or False if any error occurred.


char get_card_data(void)
{
char i;
char read_bit;
char read_byte;
char computed_lrcc_byte;
char read_lrcc_byte;
char zero_bit_count;
char *card_data_buffer_ptr;
char card_data_count;
char card_data_buffer[CARD_DATA_BUFFER_LENGTH];


#define CLOCK_HIGH_TIMEOUT_COUNT   250   // in ms
#define MIN_LEADING_ZERO_BIT_COUNT   9
#define MAX_LEADING_ZERO_BIT_COUNT 250

// Clear the card reader buffer, before this all starts.
memset(card_data_buffer, 0, CARD_DATA_BUFFER_LENGTH);


// Wait for the clock line to go high.
// The number of counts in the following loop depends upon the speed
// at which the card is pulled out of the reader.  By pulling the
// card out very slowly, I got a count of 160.  This is rare.
// Normally the count varies from 11 to 60 or so.

for(i = 0; i < CLOCK_HIGH_TIMEOUT_COUNT; i++)
   {
    if(input(CARD_READER_RCL1_PIN))
       break;

    delay_ms(1);
   }

if(i == CLOCK_HIGH_TIMEOUT_COUNT)
   return(FALSE);

// Now begin looking for the Start Sentinel code.
// This is done in a special way, where we look for
// the code, but with 9 leading zeros in front of it.
// This is done to avoid the possibility of falsely
// "reading" a Start Sentinel on card with a blank track.
// (I did not see this happen with the new cards and the new card
// readers.  I think it many only happen with "lo-co" cards on the
// old readers.)

zero_bit_count = 0;

// Wait for the first non-zero bit.   Count up all the consecutive
// zero bits that occur while waiting for the non-zero bit.
// I did some testing, and the number of zero bits seems to vary
// between 27 and 45, depending on the speed of the card.

read_bit = 0;          // Make LCLINT happy

while(1)
  {
   if(get_data_bit(&read_bit) == FALSE)    // Was clock stuck high ?
      return(FALSE);                       // If so, return an error

   if(read_bit == 0)
      zero_bit_count++;
   else
      break;

  if(zero_bit_count > MAX_LEADING_ZERO_BIT_COUNT)
     return(FALSE);
  }


// Did we get the required minimum number of leading zero bits ?
// Note:  There are at least 160 usec between bits, so there is
// plenty of time to do the following test before getting the
// Start Sentinel bits.
if(zero_bit_count < MIN_LEADING_ZERO_BIT_COUNT)
   return(FALSE);         // Return error if not

read_byte = 0;           // Make LCLINT happy

// If so, the next 7 bits should be the Start Sentinel.
// If there is a Timeout or parity error, then return an error.
if(get_data_byte(&read_byte, TRUE) == FALSE)
   return(FALSE);

// If we did not get the Start Sentinel code, then return an error.
// This will also return False if the parity error bit is set.
if(read_byte != START_SENTINEL_BYTE)
   return(FALSE);

// If we got a good Start Sentinel, then read a stream of bytes until
// we get an End Sentinel, or until we read the max number allowed.
// Store the bytes in a global array, and store the count too.  Note
// that the code below does not include the Start and End Sentinel
// bytes in the card_data_count.

card_data_buffer_ptr = card_data_buffer;
card_data_count = 0;
computed_lrcc_byte = read_byte;  // LRCC includes the Start Sentinel

// Only get up to the maximum number of bytes allowed.
for(i = 0; i < MAX_CARD_DATA_LENGTH; i++)
   {
    // Read the next byte from the card.
    if(get_data_byte(&read_byte, FALSE) == FALSE)
       return(FALSE);     // Return an error if one occurred

    // The LRCC also includes the End Sentinel.
    computed_lrcc_byte ^= read_byte;

    // Don't save the End Sentinel in the buffer.
    if(read_byte == END_SENTINEL_BYTE)
       break;

    // Store the data as ASCII bytes.  Add bias of 0x20 to do this.
    *card_data_buffer_ptr++ = read_byte + 0x20;
    card_data_count++;
   }

*card_data_buffer_ptr = 0;   // Make the card data into a string.

read_lrcc_byte = 0;          // Make LCLINT happy
// Get the LRCC byte.  It occurs right after the End Sentinel byte.
// If an error occurred while doing that, return an error.
if(get_data_byte(&read_lrcc_byte, FALSE) == FALSE)
   return(FALSE);

// Check if the LRCC byte matches the computed value.
if(computed_lrcc_byte != read_lrcc_byte)
   return(FALSE);                  // If bad LRCC, return error

// Get pointers to the card's data
if(parse_card_data(card_data_buffer) == FALSE)
   return(FALSE);

return(TRUE);     // Otherwise, return True

}

//------------------------------------------------------------------
// Wait for the next data byte from the card reader, read it, do a
// parity check on it, convert it to ASCII, and pass it back in
// data_byte.  If a parity error occurred, or if an error occurred
// in the get_data_bit() function, then return False.  Otherwise,
// return True.
// The card reader sends bits in this order:
// D0, D1, D2, D3, D4, D5, Parity.

char get_data_byte(char *data_byte, char get_sentinel)
{
char data_bit;
char value_read;
char parity_sum;
char num_bits;
char i;

// Do a different loop setup, depending upon whether we're reading
// a regular byte, or a Sentinel byte.  In the case of the Sentinel
// byte, we already got the first bit, so we adjust the loop
// parameters.
if(get_sentinel == FALSE)
  {
   value_read = 0;
   parity_sum = 0;
   num_bits = CHAR_LENGTH_IN_BITS;
  }
else   // Get the Sentinel byte
  {
   value_read = 0x40;  // We already got the 1st bit, and it's = 1
   // The parity sum also starts = 1  (since we have a 1 bit).
   parity_sum = 1;
   num_bits = CHAR_LENGTH_IN_BITS -1;
  }

data_bit = 0;          // Make LCLINT happy

for(i = 0; i < num_bits; i++)
   {
    if(get_data_bit(&data_bit) == FALSE)  // Read a bit
       return(FALSE);    // Return error if get_data_bit() timed out.
    if(data_bit == 1)    // If the bit = 1, then OR it in
       value_read |= 0x80;
    value_read >>= 1;   // Shift the byte 1 bit position to the right
    parity_sum += data_bit;
   }

// Clear the parity bit and pass the value back.
*data_byte = value_read & 0x3f;

if(parity_sum & 1)  // Is the parity sum odd ?
   return(TRUE);    // If so, return success
else
   return(FALSE);   // If not, return an error

}

//-------------------------------------------------------------------
// Wait until the card reader puts out the next data bit, and pass
// back its value in data_bit.   If a timeout error occurred, this
// function returns False.  If no error occurred, it returns True.

char get_data_bit(char *data_bit)
{
char value_read;

#define DATA_CLOCK_HIGH_TIMEOUT_COUNT  5   // 5 * 23 usec = 105 usec

// Wait until the clock line goes low.  The clock line will never be
// stuck high, if the card reader is working properly.  So we don't
// need a timeout in this loop.  That's good, because:
// This loop is the only one in this whole module that is speed
// critical. It must execute in less time than the clock pulse width,
// which is 8.6 us.  That's about 17 instruction times.  This loop
// normally takes about 9 instructions, so it's short enough.
// We won't ever miss a clock pulse.

while(1)
  {
   if(input(CARD_READER_RCL1_PIN) == 0)
      break;
  }

// Then read the data line.  This takes 8-10 instructions (4-5 usec).
value_read = input(CARD_READER_RDT1_PIN);

// Then wait until the clock line goes high.  This loop normally
// takes 23 instructions which is 11.5 usec (with an 8 MHz crystal).
// Since the clock is normally low for only 8.6 usec, it should be
// take only a few more usec before it goes high.  So we don't really
// need a timeout loop here.  Just a short delay and a test for clock
// high should do it.
// Note:  This may not be true for all readers.  Even the Panasonic
// data sheet says their clock pulse width is 1/3 of the total clock
// period.  The data sheet is wrong, but some other reader could do
// it that way.

// Wait for a full clock pulse width, to be sure it has finished.
delay_us(10);

// Then check if clock is still zero.  If so, return an error.
if(input(CARD_READER_RCL1_PIN) == 0)
   return(FALSE);

// Invert and mask down the data bit, so that we return
// non-inverted data.
*data_bit = (~value_read) & 0x01;

return(TRUE);
}

___________________________
This message was ported from CCS's old forum
Original Post ID: 8262
Edited on July 13, 2005 to add code formatting and to
remove double-spacing that occurred when this post to the
new forum. I had to do it a few times to get it to look nice.


Last edited by PCM programmer on Wed Jul 13, 2005 6:31 pm; edited 4 times in total
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

card.h
PostPosted: Mon Oct 28, 2002 11:18 pm     Reply with quote

Code:
// card.h

// This string is at the start of the card, and identifies it as
// a Product card (as opposed to an ATM card, VISA card, etc.)
#define CARD_IDENTIFIER_STRING "PRO1"
#define CARD_IDENTIFIER_STRING_LEN  4

#define CARD_ID_STRING_CHAR_0 'P'
#define CARD_ID_STRING_CHAR_1 'R'
#define CARD_ID_STRING_CHAR_2 'O'
#define CARD_ID_STRING_CHAR_3 '1'

// The spec for Track 1 data (ISO1 spec), is 79 characters, max.
// I believe this includes the text fields, and the field separator
// chars, but not the preamble, start sentinel, end sentinel, and
// checksum chars.  But we don't use the full 79 chars, and I can
// save ram space by not allocating a buffer for them all.
// We define our format as:
//
// PRO1^                 5 chars for ID string  (including ^ char)
// Axxxxxxxxxxxxxxxx^   18 chars max for LCD line 1 field 
// Bxxxxxxxxxxxxxxxx^   18 chars max for LCD line 2 field 
// Cnnnn^                6 chars for product code 
//              Total = 47

#define MAX_CARD_DATA_LENGTH  60
#define CARD_DATA_BUFFER_LENGTH  (MAX_CARD_DATA_LENGTH +1)

#define MAX_CARD_LINE1_DATA_LENGTH 16  // Not incl. the A and ^ chars
#define CARD_LINE1_BUFFER_LENGTH  (MAX_CARD_LINE1_DATA_LENGTH +1)

#define MAX_CARD_LINE2_DATA_LENGTH 16  // Not incl. the B and ^ chars
#define CARD_LINE2_BUFFER_LENGTH  (MAX_CARD_LINE2_DATA_LENGTH +1)

#define CARD_PRODUCT_CODE_LENGTH 4  // Not incl. the C and ^ chars
#define CARD_PRODUCT_CODE_BUFFER_LENGTH  (CARD_PRODUCT_CODE_LENGTH +1)

// Define the global arrays to the parsed fields.
char ga_card_line1_buffer[CARD_LINE1_BUFFER_LENGTH];
char ga_card_line2_buffer[CARD_LINE2_BUFFER_LENGTH];
char ga_card_product_code_buffer[CARD_PRODUCT_CODE_BUFFER_LENGTH];

//------------------------------------------------------------
// Define the number of bits in a char, including parity.
#define CHAR_LENGTH_IN_BITS  7 
#define START_SENTINEL_BYTE 0x05
#define END_SENTINEL_BYTE  0x1F

#define FIELD_SEPARATOR_STRING "^"  // This is for ISO1 track 1 data
#define FIELD_SEPARATOR_STRING_LEN  1   // Just 1 char in the string

// Define the state of various signals on the Card Reader interface.
#define CARD_INSERTED  1
#define CARD_REMOVED   0

// Define various results of get_card_data() routine.
// Note:  I don't need this much detail, as far as the end user
// is concerned.  I just need to tell them if the card didn't read
// properly.
/*
#define CARD_RESULT_GOOD  0
#define CARD_RESULT_BAD_ZERO_COUNT  1
#define CARD_RESULT_BAD_START_SENTINEL  2
#define CARD_RESULT_PARITY_ERROR  3
#define CARD_RESULT_LRCC_ERROR    4
#define CARD_RESULT_DATA_TOO_LONG 5
#define CARD_RESULT_TIMEOUT  6
*/

//--------------------------------------------------------------
// Function declarations

char get_card_data(void);
char get_data_bit(char *data_bit);
char get_data_byte(char *data_byte, char get_sentinel);
//--------------------

___________________________
This message was ported from CCS's old forum
Original Post ID: 8263
Edited on July 13, 2005 to remove double-spacing
and to put it in a Code format block.


Last edited by PCM programmer on Wed Jul 13, 2005 6:44 pm; edited 1 time in total
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

How to use the card functions
PostPosted: Mon Oct 28, 2002 11:37 pm     Reply with quote

The following code shows how to use card reader routines.
I poll the CLD line in the main loop. If I find that a
card is inserted, then I call the routine to see if I
got a valid response (ie., read good data from the card).
My actual main loop is a lot bigger than this, because
I am doing other things in it, such as checking a keypad
and writing to an LCD display.

I'm not sure why I have the test for the CLD pin in the
main loop, instead of in one of the card routines.
I think it might have been done for reasons of speed
or responsiveness. It's been a while. I don't remember.
Like I said, I'd probably tune these routines up a bit
if I was writing them today. But they work.

[ Edited to add: The "line1" and "line2" in the variable names
below refer to my data format, which is put on lines 1 and 2
of a 16x2 LCD. The data for this comes from Track 1 of the
card. I just want to make this clear, so you don't think
I'm referring to Track 1 and Track 2 of the card. The code
only reads data from Track 1. -- PCM programmer ]

Code:
while(1)
  {
   if(input(CARD_READER_CLD_PIN) == TRUE) // Card inserted ?
     {
      if(check_card_reader() == TRUE)   // If so, read the card
        {
         if(validate_product_code() == TRUE) 
           {
              // Do something if product code is valid.
           }
        }
     }

   // Put code here to do other things in the main loop.

  }

--------------------------

char check_card_reader(void)
{
char retval;

#define INNER_TIMEOUT_COUNT 200   // 200 ms for inner loop
// 150 * 200 ms = 30 seconds for outer loop
#define OUTER_TIMEOUT_COUNT 150   

disable_interrupts(INT_RB);   // Don't allow keypad interrupts

// Mark the card data buffers as empty.
ga_card_line1_buffer[0] = 0;
ga_card_line2_buffer[0] = 0;
ga_card_product_code_buffer[0] = 0;

// Debounce the card reader's insertion switch, before checking
// for card removal.  The switch bounced for up to 250 usec, on
// the logic analyzer, so I will debounce it for 1 ms.
delay_ms(1);

// Ask user to remove card.
lcd_putc("\fPlease remove\ncard");

// Wait for user to remove card.  Don't do a timeout.  If the user
// forgets and leaves a card in the reader, then it will display the
// "Please remove card" message until they do so.
while(1)
  {
   if(input(CARD_READER_CLD_PIN) == CARD_REMOVED)
      break;
  }

delay_ms(1);  // Wait for switch to debounce after card is removed

retval = get_card_data();   // Read the card's data

// If we got an error, then exit.
if(retval == FALSE)
  {
   ga_product_code[0] = 0;   // Mark the product code as invalid
   return(FALSE);
  }

// If we read the card OK, then copy the product code from
// the card reader buffer to the product code array.  The code
// will be validated later.
strncpy(ga_product_code, ga_card_product_code_buffer,
            PRODUCT_CODE_LENGTH);

// Re-enable keypad interrupts
enable_interrupts(INT_RB);

return(TRUE);   // Return success.
}

___________________________
This message was ported from CCS's old forum
Original Post ID: 8264

Edited on July 13, 2005 to remove double-spacing
and to put it in a Code format block.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group