#!/usr/bin/perl -w
### uCode extractor
### Copyright 2005, Joseph Jezak
### Alex Beregszaszi: be verbose regarding offset and size
use strict;

### uCode and PCM data names
my @size_names = ("d11pcm.sz",
		"d11ucode.sz");

### Initial Value names
my @no_size_names = ("initvals");

if($#ARGV < 0) {
  print "BCM43xx microcode extractor\n";
  print "Usage:\n\tuCode.pl [uCode Source]\n\n";
  exit;
}

### Open the file
open UCODE, "<".$ARGV[0] or die "Can't open ".$ARGV[0]."!\n";
my $int = 0;
### Apple! (TODO: Add Dell driver support)
if(1){
  ### Symbol #
  my $sym;

  ### Skip header to find the segment command
  seek UCODE, 28, 0;
  read UCODE, $int, 4;
  while (unpack("N", $int) != 1) {
  	read UCODE, $int, 4;
	seek UCODE, unpack("N", $int), 1;
	read UCODE, $int, 4;
  }
  ### Skip to the sections immediately after the segment
  seek UCODE, 52, 1; 

  ### Get the __DATA,__data address
  my $data_off;
  my $data_addr;
  read UCODE, $data_addr, 16; 
  $data_addr = unpack("Z16",$data_addr);
  while($data_addr ne "__data"){
  	seek UCODE, 52, 1;	
	read UCODE, $data_addr, 16; 
	$data_addr = unpack("Z16",$data_addr);
  }
  seek UCODE, 16, 1;
  read UCODE, $data_addr, 4; 
  $data_addr = unpack("N", $data_addr);
  seek UCODE, 4, 1;
  read UCODE, $data_off, 4; 
  $data_off = unpack("N", $data_off);
  
  ### Seek back to the Load Command Data Structures to look for the Symbol Table
  seek UCODE, 28, 0; 
  while(read UCODE, $int, 4) {
    ### Skip to the next command if it's not the Symbol table command
    if(unpack("N", $int) != 2) {
      read UCODE, $int, 4;
      seek UCODE, unpack("N", $int) - 8, 1;
    }
    ### Found it!
    else {
      last;
    }
  }
  
  ### Get the Symbol Table Command
  seek UCODE, 4, 1;
  read UCODE, $int, 16;
  my ($sym_offset, $sym_num_entries, $sym_string_offset, $sym_string_size) = unpack("N4", $int);
  
  ### Jump to the symbol table
  seek UCODE, $sym_offset, 0;
  ### Read in symbol table
  my @symbols;
  for($sym=0; $sym < $sym_num_entries; $sym++){
    read UCODE, $int, 12;
    my ($strx, $type, $sect, $desc, $value) = unpack("NC2nN", $int);
    $symbols[$sym] = [$strx, $type, $sect, $desc, $value]; 
  }
  
  my $check_sym;
  my $sym_name;
  my $sym_off;
  my $tname;
  ### Now, get the symbols and offsets we need and dump them to files
  for ($sym=0; $sym < $sym_num_entries; $sym++) {
    $check_sym = $symbols[$sym]; 
    seek UCODE, $sym_string_offset + $$check_sym[0], 0;
    read UCODE, $int, 1;
    $int = chr(unpack("C", $int));
    $sym_name = "";
    while(ord($int) != 0){
      $sym_name = $sym_name.$int;
      read UCODE, $int, 1;
    }
    ### Check for uCode and PCM data (has size)
    my $usize;
    foreach $tname (@size_names) {
      if ($sym_name =~ /$tname/){
      	### Get size data
	seek UCODE, $data_off + $$check_sym[4] - $data_addr, 0;
    	read UCODE, $int, 4;
	$usize = unpack("N", $int);
	$check_sym = $symbols[$sym-1];
	### Get data
	seek UCODE, $data_off + $$check_sym[4] - $data_addr, 0;
	my $ucode;
	$sym_name = substr $sym_name, 0, -2;
        read UCODE, $ucode, $usize; 
	open UOUT, ">".$sym_name or die "Can't open $sym_name for writing!\n";
	print UOUT $ucode;
	close UOUT;
	
	$sym_off = $data_off + $$check_sym[4] - $data_addr;
	print "$sym_name found at $sym_off size $usize\n";
      }
    }
    ### Check for Initial Values (no size)
    foreach $tname (@no_size_names) {
      if ($sym_name =~ /$tname/){
        my $size = 0;
	seek UCODE, $data_off + $$check_sym[4] - $data_addr, 0;
	my $addr1 = 0;
	my $addr2 = 0;
	open UOUT, ">".$sym_name or die "Can't open $sym_name for writing!\n";
	$sym_off = $data_off + $$check_sym[4] - $data_addr;
	### Loop until we get the magic #
	while(!($addr1 == 0xFFFF0000 && $addr2 == 0x00000000)) {
	  read UCODE, $addr1, 4;
	  print UOUT $addr1;
	  read UCODE, $addr2, 4;
	  print UOUT $addr2;
	  $addr1 = unpack("N", $addr1);
	  $addr2 = unpack("N", $addr2);
	  $size += 8;
	}
	print "$sym_name found at $sym_off size $size\n";
	close UOUT;
      } 
    }
  }
}
