-----BEGIN decode.pl-----
#!/usr/bin/perl
#
# Display MetroCard Raw And Parsed Data
# Version 0.01
# Copyright (c) 2004-2005 Joseph Battaglia <redbird@2600.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
#
# Notes on input:
# A line that begins with a '#' is not processed and not printed
# A line that begins with a '%' is printed as a comment
#
$card_type = 0; #FIXME: this should be set by track 3, but since I only have
# a single-track reader...
$typesfile = 'types.txt';
$lidfile = 'lids.txt';
# track 1-2 field names
@T12_FIELDS = (
'Time', 'Sub-Type', 'Time', 'Date',
'Times Used', 'Expiration', 'Transfer', 'Last ID',
'Value', 'Purch ID', 'Unknown'
);
# track 3 field names
@T3_FIELDS = (
'Type', 'Unknown', 'Expiration', 'Unkonwn',
'Unknown', 'Unknown', 'Serial', 'Unused',
'Unkonwn', 'Constant'
);
# generate regexps for track 1-2
@t12_lens = (2, 6, 6, 10, 6, 10, 1, 15, 16, 16); # lengths of track 1-2 fields
$t12_re1 = '11010111'; # start sentinel
$t12_re1 .= join('', map("([01]{$_})", @t12_lens)); # track 1-2 fields
$t12_re2 .= $t12_re1 . '(.*)' . $t12_re1; # regexp for dual records
# generate regexp for track 3
@t3_lens = (4, 4, 12, 4, 8, 8, 80, 16, 16); # lengths of track 3 fields
$t3_re = '000011000111'; # start sentinel
$t3_re .= join('', map("([01]{$_})", @t3_lens)); # track 3 fields
$t3_re .= '0010010100110010011010010110010101001100101001'; # end sentinel
$t3_re .= '0100110011010101001101001010100110100101011010';
# lookup card type/subtype in typesfile and return its name
sub lookup_type($$)
{
($in_type, $in_subtype) = @_; # read arguments
open(FH, $typesfile) or die "Can't open $typesfile: $!"; # open typesfile
while (<FH>) { # loop through each record
($type, $subtype, $name) = split(/:/); # split fields
if (($type eq $in_type) and ($subtype eq $in_subtype)) { # look for match
chomp($name); # remove newline
return $name; # return name
}
}
return 'UNKNOWN'; # could not find type/subtype; return 'UNKNOWN'
}
# lookup last id in lidfile and return its name
sub lookup_lid($)
{
($in_value) = @_; # read arguments
open(FH, $lidfile) or die "Can't open $lidfile: $!"; # open lidfile
while (<FH>) { # loop through each record
($value, $name) = split(/:/); # split fields
if (($in_value eq $value)) { # look for match
chomp($name); # remove newline
return $name; # return name
}
}
return 'UNKNOWN'; # could not find lid; return 'UNKNOWN'
}
# print header
sub print_header($)
{
($title) = @_; # read arguments
print("$title\n"); # print title
print("Field Hex Decimal Parsed\n"); # print header
print("--------------- ---------- ---------- ------\n");
}
# print field as hex and decimal and return decimal value
sub print_field($$$)
{
($track, $field, $value) = @_; # read arguments
$tvar = 'T' . $track . '_FIELDS'; # create variable name
$value = oct('0b' . $value); # convert to decimal
printf('%2d: %-11s ', $field, $$tvar[$field - 1]);
printf('%10X %10d ', $value, $value); # print base 10/16 values
return $value; # return decimal value
}
# parse field
sub parse_field($$$)
{
($track, $field, $value) = @_; # read arguments
$decvalue = oct('0b' . $value);
if ($track == 12) { # track 1-2 fields
if ($field == 1 or $field == 3) { # time
if ($card_type == 0) {
$time = $decvalue * 6;
$hr = int($time / 60) - 1;
$min = $time % 60;
printf("%.2d:%.2d", $hr, $min);
}
}
if ($field == 2) { # subtype
print(lookup_type($card_type, $decvalue));
}
if ($field == 5) { # times used
print($decvalue);
}
if ($field == 7) { # transfer
print(($decvalue == 1) ? "YES" : "NO");
}
if ($field == 8) { # last id
print(lookup_lid($decvalue));
}
if ($field == 9) { # value
$dollars = int($decvalue / 100);
$cents = $decvalue % 100;
printf("\$%d.%.2d", $dollars, $cents);
}
}
if ($track == 3) { # track 3 fields
if ($field == 1) { # type
print(lookup_type('M', $decvalue));
$card_type = $decvalue;
}
if ($card_type == 0 and $field == 3) { # expiration
$day = 30;
if (!$decvalue % 2) {
$decvalue++;
}
$decvalue /= 2;
my $year = int ($decvalue / 12) + 1992;
my $month = $decvalue % 12;
if ($month > 1) {
$month--;
}
else {
$year--;
$month = 12;
}
if ($month < 8) {
if ($month % 2) {
$day = "31";
}
}
else {
if (!($month % 2)) {
$day = "31";
}
}
if ($month == 2) {
if (!($year % 4)) {
$day = 29;
}
else {
$day = 28;
}
}
print("$month/$day/$year");
}
if ($field == 7) { # serial
printf("%.10d", $decvalue);
}
}
print("\n");
}
# main loop
while (<STDIN>) {
if (/^%(.*)/) { # process printed comments
print("### $1\n"); # print
next; # process next input line
}
if (/^#/) { # process ignored comments
next; # process next input line
}
# track 3
if (/$t3_re/ or reverse =~ /$t3_re/) {
print_header 'Track 3 Record';
for ($i = 1; $i <= ($#t3_lens + 2); $i++) {
$field = $i; # set field
print_field(3, $field, $$i); # print each field
parse_field(3, $field, $$i); # parse each field
}
print "\n";
}
# track 1-2 dual records
if (/$t12_re2/ or reverse =~ /$t12_re2/) { # regexp
print_header 'Track 1-2 Record 1'; # print header
for ($i = 1; $i <= ($#t12_lens + 2); $i++) {
$field = $i; # set field
print_field(12, $field, $$i); # print/parse each field
if ($field == 1 or $field == 3) { # time field is split in two
parse_field(12, $field, $1 . $3); # combine time fields
}
else { # everything else is normal
parse_field(12, $field, $$i); # parse each field
}
}
print "\n";
print_header 'Track 1-2 Record 2'; # print header
for ($i = ($#t12_lens + 3); $i <= (($#t12_lens * 2) + 4); $i++) {
$field = $i - ($#t12_lens + 2); # set field
print_field(12, $field, $$i); # print/parse each field
if ($field == 1 or $field == 3) { # time field is split in two
$timevar1 = ($#t12_lens + 3);
$timevar2 = ($#t12_lens + 5);
parse_field(12, $field, $$timevar1 . $$timevar2); # combine time fields
}
else { # everything else is normal
parse_field(12, $field, $$i); # parse each field
}
}
print "\n";
next; # process next input line
}
# track 1-2 single record
if (/$t12_re1/ or reverse =~ /$t12_re1/) { # regexp
print_header 'Track 1-2 (Read Error)'; # print header
for ($i = 1; $i <= ($#t12_lens + 2); $i++) {
$field = $i; # set field
print_field(12, $field, $$i); # print each field
if ($field == 1 or $field == 3) { # time field is split in two
parse_field(12, $field, $1 . $3); # combine time fields
}
else { # everything else is normal
parse_field(12, $field, $$i); # parse each field
}
}
print "\n";
next; # process next input line
}
}
-----END decode.pl-----
-----BEGIN lids.txt-----
1513:14th St/Union Sq
1519:8th St/Broadway (A39)
1880:Lexington Ave (N601)
1942:ASTOR PLACE (R219)
2157:34th St/6th Ave (N506)
2204:42nd St/Grand Central
2278:9th Street PATH
-----END lids.txt-----
-----BEGIN mxms.txt-----
14TH ST. - UNION SQUARE :MVM:0530:A033 0400
14TH ST. - UNION SQUARE :MVM:0400:A033 0700
14TH ST. - UNION SQUARE :MVM:0481:A033 0701
14TH ST. - UNION SQUARE :MVM:1122:A034 0400
14TH ST. - UNION SQUARE :MVM:0216:A034 0700
14TH ST. - UNION SQUARE :MVM:0215:A034 0701
14TH ST. - UNION SQUARE :MVM:1370:A035 0700
14TH ST. - UNION SQUARE :MVM:0541:A037 0700
14TH ST. - UNION SQUARE :MVM:0265:A037 0701
8TH STREET & BROADWAY :MEM:5462:A039 0400
8TH STREET & BROADWAY :MEM:5662:A038 0401
95TH ST & FT. HAMILTON :MVM:0982:C028 0700
14TH STREET & 8TH AVE :MEM:5314:H001 0702
1ST AVE & 14TH STREET :MVM:1358:H007 0700
1ST AVE & 14TH STREET :MVM:1145:H007 0701
175 ST/FT. WASHINGTON AV:MVM:1632:N010 0400
175 ST/FT. WASHINGTON AV:MVM:1611:N010 0700
175 ST/FT. WASHINGTON AV:MEM:5274:N010 0701
W 4TH ST - WASHINGTON SQ:MVM:0321:N080 0700
W 4TH ST - WASHINGTON SQ:MVM:0109:N080 0701
FORDHAM ROAD :MVM:0550:N218 0700
LEXINGTON AVE - 3RD AVE :MVM:0740:N305 0401
NASSAU AV & MANHATTAN AV:MVM:1738:N408A 0500
34TH STREET/SIXTH AVENUE:MVM:1428:N506 0702
34TH STREET/SIXTH AVENUE:MVM:0540:N507 0701
14TH STREET & 6TH AVENUE:MEM:5383:N513 0400
CHRISTOPHER STREET :MVM:0637:R125 0700
CHRISTOPHER STREET :MEM:5359:R125
CHRISTOPHER STREET :MVM:0063:R125 0701
14TH STREET - 7TH AVENUE:MVM:0294:R127 0400
14TH STREET - 7TH AVENUE:MVM:1643:R127 0401
14TH STREET - 7TH AVENUE:MVM:0357:R127 0700
14TH STREET - 7TH AVENUE:MVM:0376:R127 0701
34TH STREET-PENN STATION:MVM:0553:R138 0701
WALL STREET & BROADWAY :MVM:1123:R203 0400
WALL STREET & BROADWAY :MVM:1038:R203 0700
ASTOR PLACE :MVM:0654:R219 0400
ASTOR PLACE :MVM:0586:R219 0700
ASTOR PLACE :MVM:0545:R219 0701
ASTOR PLACE :MVM:0744:R220 0700
ASTOR PLACE :MVM:0318:R220 0701
ASTOR PLACE :MEM:5389:R220
14TH ST. - UNION SQUARE :MVM:0576:R221 0400
14TH ST. - UNION SQUARE :MVM:0514:R221 0401
14TH ST. - UNION SQUARE :MVM:0475:R221 0700
14TH ST. - UNION SQUARE :MVM:0564:R221 0701
23RD STREET - PARK AVE :MVM:0489:R227 0701
28TH STREET - PARK AVE :MVM:1228:R229 0700
-----END mxms.txt-----