<?php
error_reporting
E_ALL );

// wad file lesen und boss-sprites anzeigen
// by Dennis Luehring and the "Unoffical Doom Specs"

/*
  The starting point is the concept of "WAD". It is not an acronym, it
just means a collection of data. Throughout this document, "WAD" or "wad"
will mean a file with a .WAD extension that contains data for the doom
engine to use.
  A WAD file has three parts:

  (1) a twelve-byte header
  (2) one or more "lumps"
  (3) a directory or "info table" that contains the names, offsets, and
    sizes of all the lumps in the WAD
*/

$fp fopen('DOOM1.WAD',  'r');

/*
  The header consists of three four-byte parts:

    (a) an ASCII string which must be either "IWAD" or "PWAD"
    (b) a 4-byte (long) integer which is the number of lumps in the wad
    (c) a long integer which is the file offset to the start of
      the directory
*/
$wad_header_serialized fread$fp12 );
$wad_header = array(
  
'wad_type' => substr($wad_header_serialized04),
  
'lump_count' => bin2long substr($wad_header_serialized44) ),
  
'dir_offset' => bin2long substr($wad_header_serialized84) )
);

/*
  The directory has one 16-byte entry for every lump. Each entry consists
  of three parts:

    (a) a long integer, the file offset to the start of the lump
    (b) a long integer, the size of the lump in bytes
    (c) an 8-byte ASCII string, the name of the lump, padded with zeros.
      For example, the "DEMO1" entry in hexadecimal would be
      (44 45 4D 4F 31 00 00 00)
*/
fseek$fp$wad_header['dir_offset'] );

$sprites false;

for( 
$lump_nr=0$lump_nr<$wad_header['lump_count']; $lump_nr++ )
{
  
$lump_info_serialized fread$fp16 );

  
$lump_name trim(substr$lump_info_serialized88));

  
// nur die "sprite" lumps anzeigen
  
if( $lump_name == 'S_START')
  {
    
$sprites true;
    continue;
  }

  if( 
$lump_name == 'S_END')
  {
    
$sprites false;
  }

  if( 
$lump_name != 'PLAYPAL' )
  {
    if ( !
$sprites ) continue;
  }

  
$lump_info[$lump_name]=array(
    
'lump_offset' => bin2long substr$lump_info_serialized04) ),
    
'lump_size' => bin2long substr$lump_info_serialized44) ),
    
'lump_name' => $lump_name
  
);

  if ( !isset(
$_GET['sprite_name']) )
  {
    if( 
$lump_name != 'PLAYPAL' )
    {
      echo(
' <a href="?sprite_name='.urlencode($lump_name).'">'.htmlspecialchars($lump_name).'</a><br>');
    }
  }
 }


// -------------------------------------------------------------------------
// wenn sprite gewaehlt dann anzeigen
// -------------------------------------------------------------------------
if (isset($_GET['sprite_name']))
{
  
$name $_GET['sprite_name'];

  
// playpal laden
  
fseek$fp$lump_info['PLAYPAL']['lump_offset'] );
  
$playpal_serialized fread$fp14*768 );

  for(
$palette_nr=0$palette_nr<14$palette_nr++ )
  {
    for(
$color_nr=0$color_nr<256$color_nr++)
    {
      
$palette[$palette_nr][$color_nr]=array(
        
'red' => ord(substr($playpal_serialized,($palette_nr*768)+($color_nr*3),1)),
        
'green' => ord(substr($playpal_serialized,($palette_nr*768)+($color_nr*3)+1,1)),
        
'blue' => ord(substr($playpal_serialized,($palette_nr*768)+($color_nr*3)+2,1))
      );
    }
  }

  
fseek$fp$lump_info[$name]['lump_offset'] );
  
$sprite_buffer_serialized fread$fp$lump_info[$name]['lump_size'] );

  
// -------------------------------------------------------------------------
  /*
  Each picture has three sections. First, an 8-byte header composed of
  four short-integers. Then a number of long-integer pointers. Then the
  picture's pixel/color data. See [A-1] for concise BNF style definitions,
  here is a meatier explanation of the format:

  (A) The header's four fields are:

    (1) Width. The number of columns of picture data.
    (2) Height. The number of rows.
    (3) Left offset. The number of pixels to the left of the center;
      where the first column gets drawn.
    (4) Top offset. The number of pixels above the origin;
      where the top row is.

  The width and height define a rectangular space or limits for drawing
  a picture within. To be "centered", (3) is usually about half of the
  total width. If the picture had 30 columns, and (3) was 10, then it
  would be off-center to the right, especially when the player is standing
  right in front of it, looking at it. If a picture has 30 rows, and (4)
  is 60, it will appear to "float" like a blue soul-sphere. If (4) equals
  the number of rows, it will appear to rest on the ground. If (4) is less
  than that for an object, the bottom part of the picture looks awkward.
    With walls patches, (3) is always (columns/2)-1, and (4) is always
  (rows)-5. This is because the walls are drawn consistently within their
  own space (There are two integers in each SIDEDEF which can offset the
  starting position for drawing a wall's texture within the wall space).

  Finally, if (3) and (4) are NEGATIVE integers, then they are the
  absolute coordinates from the top-left corner of the screen, to begin
  drawing the picture, assuming the VIEW is full-screen (i.e., the full
  320x200). This is only done with the picture of the player's current
  weapon - fist, chainsaw, bfg9000, etc. The game engine scales the
  picture down appropriatelyif the view is less than full-screen.
  */
  
$sprite_header_serialized substr$sprite_buffer_serialized0);
  
$sprite_header=array(
    
'width' => bin2word substr $sprite_header_serialized02) ),
    
'height' => bin2word substr $sprite_header_serialized22) ),
    
'left_offset' => bin2word substr $sprite_header_serialized42) ),
    
'top_offset' => bin2word substr $sprite_header_serialized62) ),
  );

  
// -------------------------------------------------------------------------
  /*
  (B) After the header, there are N = field (1) = <width> = (# of columns)
  4-byte <long> integers. These are pointers to the data for each COLUMN.
  The value of the pointer represents the offset in bytes from the first
  byte of the picture lump.
  */
  
$sprite_columnpointers_serialized substr$sprite_buffer_serialized8, ($sprite_header['width']*4) );
  for(
$sprite_column 0;$sprite_column<$sprite_header['width']; $sprite_column++)
  {
    
$sprite_columnpointers[$sprite_column]=bin2long(substr$sprite_columnpointers_serialized$sprite_column*44) );
  }

  
// -------------------------------------------------------------------------
  /*
  (C) Each column is composed of some number of BYTES (NOT integers),
  arranged in "posts":
  The first byte is the row to begin drawing this post at. 0 means
  whatever height the header (4) upwards-offset describes, larger numbers
  move correspondingly down.
  The second byte is how many colored pixels (non-transparent) to draw,
  going downwards.
  Then follow (# of pixels) + 2 bytes, which define what color each
  pixel is, using the game palette. The first and last bytes AREN'T drawn,
  and I don't know why they are there. Probably just leftovers from the
  creation process on the NeXT machines. Only the middle (# of pixels in
  this post) are drawn, starting at the row specified in the first byte
  of the post.
  After the last byte of a post, either the column ends, or there is
  another post, which will start as stated above.
  255 (0xFF) ends the column, so a column that starts this way is a null
  column, all "transparent". Draw the next column.
  */

  
$image=imagecreate($sprite_header['width'],$sprite_header['height']);
  
$background_color imagecolorallocate ($image000);
  
$dest_image=imagecreate($sprite_header['width']*4,$sprite_header['height']*4);

  
// playpal muss noch angepasst werden
  // isch brauch doom-colors im web...
  
for($palette_nr=0$palette_nr<14$palette_nr++ )
  {
    for(
$color_nr=0$color_nr<255$color_nr++)
    {
      
$color=&$palette[$palette_nr][$color_nr];
      
$color['pixel_color'] = imagecolorallocate ($image,
        
$color['red'],
        
$color['green'],
        
$color['blue']
      );
    }
  }




  
$pixel_x=0;
  foreach(
$sprite_columnpointers as $sprite_column_start_post)
  {
    
$post $sprite_column_start_post;

    while( 
ord($sprite_buffer_serialized[$post]) != 255 )
    {
    
$pixel_direction ord($sprite_buffer_serialized[$post]);
    
$pixel_count ord($sprite_buffer_serialized[$post+1]);


    if ( 
$pixel_direction == )
      
$pixel_y $pixel_direction;
    else if ( 
$pixel_direction 0  )
      
$pixel_y $pixel_direction;

    for(
$pixel_nr =1$pixel_nr<$pixel_count$pixel_nr++ )
    {
      
$pixel_palette_color ord($sprite_buffer_serialized[$post+2+$pixel_nr]);

      
$p=0;
      
$pixel_color $palette[$p][$pixel_palette_color]['pixel_color'];

      
imagesetpixel ($image,$pixel_x,$pixel_y++,$pixel_color);
    }

    
$post+=2+$pixel_count+2;
    }
    
$pixel_x++;
  }

  
ImageCopyResized($dest_image,$image,0,0,0,0,$sprite_header['width']*4,$sprite_header['height']*4,ImageSX($image),ImageSY($image));

  
header("content-type: image/jpeg");
  
imagejpeg($dest_image);
  
imagedestroy($image);
  
imagedestroy($dest_image);

}

fclose$fp );

/********************************************/

// converts an 2-char binary string into an short-integer/word (intel endian)
function bin2word$bin )
{
  return 
ord($bin[0]) | (ord($bin[1])<<8);
}

// converts an 4-char binary string into an long (intel endian)
function bin2long$bin )
{
  return 
ord($bin[0]) | (ord($bin[1])<<8) | (ord($bin[2])<<16) | (ord($bin[3])<<24);
}

// print pre-print_r-pre
function pepp$mixed )
{
  echo(
"<pre>");
  
print_r$mixed );
  echo(
"\n</pre>");
}



?>