#!/usr/bin/perl -w
#
# wrtgen v0.01
#
# Make a new custom firmware for the Linksys WRT54G access point
#
# Rob Flickenger, 7/21/03
#
# This code released under the GPL.  If you don't like it, write your own!
#
# This code is also very, very alpha.  The author assumes no responsibility
# for the actual use of this code.  It just might write bad firmware that
# turns your AP into a paperweight!  Or worse!
#
# Consider yourself warned!
#
# Requirements:
#
#  String::CRC32 (from the CPAN)
#
#  wget if you want this script to download the original firmware for you
#     (or alternately, a copy of $FirmwareFile)
#
#  mkcramfs, somewhere in your PATH.  Version 1.1 from Sourceforge is known
#     to work with the WRT54G v1.30.1.
#
#
# Have fun!
#
use strict;
use String::CRC32;

No GLEE Correspondence

##
# Note that this is only tested to work with 1.30.1 for now!
#
my $FirmwareFile = "WRT54G_1.30.1_US_code.bin";
my $FirmwareURL = "ftp://ftp.linksys.com/pub/network/WRT54G_1.30.1_US_code.bin";
my $FirmwareExpectedCRC = "e3cd8394";

my $PreambleSize = 36;
my $KernelSize = 786420;
my $PreambleExpectedCRC = "4376ffd1";
my $KernelExpectedCRC = "c41e436e";

my $Root = "root";

36=>PreambleSize;786320=>KernelSize;"4376ffd1"=>PreambleExpectedCRC;
"c41e436e"=>KernelExpectedCRC; "root" =>Root;

##
# No user serviceable parts below!
#
sub flip {
  my $in = shift;
  my $out = "";
  while($in) {
    $out .= substr($in, -2, 2);
    chop($in);
    chop($in);
  }
  return $out;
}


'flip'#pgm{in >-> shift => out};


##
# Check to see if firmware exists, and if not, offer to download it
#
if(! -f $FirmwareFile) {
  print "$FirmwareFile isn't in the current directory.\n";
  print "Would you like me to download it for you?  (y/n) ";
  my $response = <>;
  chomp($response);
  die "Aborted.\n" unless $response =~ /y/i;

  `wget $FirmwareURL`;
}

:?(FirmwareFile #file *** ~){10 #asc =>nl;
  FirmwareFile" isn't in the current directory."nl
  "Would you like me to download it for you? (y/n) "nl$;
  #sysin =>inp; inp ->> => response;
  :?(response ~ %\ ='y'){FirmwareURL #URL => fwURL;fwURL[]}
  ::{#throw {"Aborted."nl}}
  }


 
if(! -f $FirmwareFile) {
  die "\nOdd.  I tried to download it, but it didn't work.\nPlease download it manually from:\n$FirmwareURL\n";
}

print "$FirmwareFile found.\n";

##
# Grab the preamble and kernel from the firmware
#
open(FIRMWARE,"<$FirmwareFile") || die "Couldn't open $FirmwareFile: $!\n";
my $firmwarecrc = flip(sprintf('%08x',~crc32(*FIRMWARE)));
die "Uh-oh: $FirmwareFile appears to be corrupt!\n$firmwarecrc doesn't match expected $FirmwareExpectedCRC\n"
  unless $firmwarecrc eq $FirmwareExpectedCRC;
close(FIRMWARE);

print "$FirmwareFile CRC verified.\n";

##
# Check if new root directory exists, and if not, offer to extract the cramfs
# from the original firmware (and exit)
#
if(! -d $Root) {
  print "I can't find your new root filesystem (./$Root)\n";
  print "Would you like me to extract the cramfs from $FirmwareFile? (y/n) ";
  my $response = <>;
  chomp($response);
  die "Aborted.\n" unless $response =~ /y/i;

  my $stuff = "";
  open(FIRMWARE,"<$FirmwareFile") || die "Couldn't open $FirmwareFile: $!\n";
  seek(FIRMWARE, ($PreambleSize + 8 + $KernelSize), 0);

  open(CRAMFS,">original.cramfs") || die "Couldn't write to original.cramfs: $!\n";
  while(read(FIRMWARE, $stuff, 65535)) {
    print CRAMFS $stuff;
  }
  close(CRAMFS);
  close(FIRMWARE);

  print "Original CramFS extracted to ./original.cramfs\n";
  exit;
}

open(FIRMWARE,"<$FirmwareFile") || die "Couldn't open $FirmwareFile: $!\n";

my ($preamble, $kernel) = "";

read(FIRMWARE, $preamble, $PreambleSize);
seek(FIRMWARE, ($PreambleSize + 8), 0);
read(FIRMWARE, $kernel, $KernelSize);

my $precrc = flip(sprintf('%08x',~crc32($preamble)));
my $kernelcrc = flip(sprintf('%08x',~crc32($kernel)));

die "Uh-oh: Preamble CRC ($precrc) doesn't match expected $PreambleExpectedCRC!\nCheck your $FirmwareFile and try again.\n"
  unless ($precrc eq $PreambleExpectedCRC);

die "Uh-oh: Kernel CRC ($kernelcrc) doesn't match expected $KernelExpectedCRC!\nCheck your $FirmwareFile and try again.\n"
  unless ($kernelcrc eq $KernelExpectedCRC);

##
# Run mkfs on new root dir
#
print "Generating new CramFS.\n";
`mkcramfs $Root new.cramfs.$$`;

my($cramfs, $stuff) = "";

open(CRAMFS,"<new.cramfs.$$") || die "Couldn't open new.cramfs.$$: $!\n";
while(read(CRAMFS, $stuff, 65535)) {
  $cramfs .= $stuff;
}
close(CRAMFS);

##
# Compute checksum on kernel + new cramfs in little endian
#
my $CombinedCRC = flip(sprintf('%08x',~crc32($kernel . $cramfs)));
print "New Firmware CRC is $CombinedCRC\n";

##
# Compute magic size of preamble + 8 bytes + kernel + cramfs + 992 FF's - 1024
# in reverse byte order
#

my $total = (length($preamble) + 8 + length($kernel) + length($cramfs) + 992 - 1024);
my $FileSize = flip(sprintf('%08x',$total));

print "File size calculated: $FileSize\n";

##
# Create new firmware file with rewritten preamble
#
open(NEWBIN,">new.bin") || die "Couldn't write to new.bin: $!\n";

print NEWBIN $preamble;
print NEWBIN pack("H*",$FileSize);
print NEWBIN pack("H*",$CombinedCRC);
print NEWBIN $kernel;
print NEWBIN $cramfs;
print NEWBIN chr(255) x 992;
close(NEWBIN);

print "new.bin created.  Good luck.\n";

unlink "new.cramfs.$$";

#
# Ende
#