Blob Blame History Raw
/*  Utility to extract the embedded sounds from Commander Keen
    Episode 2 (keen2.exe) and Episode 3 (keen3.exe)

    Copyright (C) 2007 Hans de Goede  <j.w.r.degoede@hhs.nl>
    
    Many thanks to Mitugu(Kou) Kurizono for the decompression algorithm.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or   
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/   

#include <stdio.h>
#include <string.h>

int bit_count;
FILE *fin;

int get_bit(void)
{
  static unsigned short bits;
  int bit;
  
  bit = bits & 1;
  bit_count--;

  if (bit_count <= 0)
  {
    bits = getc(fin) | getc(fin) << 8;

    if (bit_count == -1) /* special case for first bit word */
    {
      bit = bits & 1;
      bits >>= 1;
    }

    bit_count += 16;
  }
  else
    bits >>= 1;
  
  return bit;
}

int main(int argc, char *argv[])
{
  unsigned char buf[131072];
  short offset;
  const char *output_filename;
  int pos, repeat, sounds_start, sounds_end, ret = 0;
  FILE *fout;

  pos = 0;
  bit_count = 0;
  
  if (argc != 2)
  {
    fprintf(stderr, "%s: Usage: %s keen?.exe\n", argv[0], argv[0]);
    return 1;
  }
  
  if (!strcmp(argv[1], "keen2.exe"))
  {
    output_filename = "sounds.ck2";
    sounds_start  = 0x12730;
    sounds_end    = 0x14BDA;
  }
  else if (!strcmp(argv[1], "keen3.exe"))
  {
    output_filename = "sounds.ck3";
    sounds_start  = 0x13A70;
    sounds_end    = 0x164D4;
  }
  else
  {
    fprintf(stderr, "%s: Error: Unknown keen executable name: %s\n",
      argv[0], argv[1]);
    return 1;
  }
  
  fin = fopen(argv[1], "r");
  if (!fin)
  {
    fprintf(stderr, "%s: Error opening input file %s: ", argv[0], argv[1]);
    perror(NULL);
  }

  fout = fopen(output_filename, "w");
  if (!fout)
  {
    fprintf(stderr, "%s: Error opening output file %s: ", argv[0],
      output_filename);
    perror(NULL);
    fclose(fin);
    return 1;
  }

  /* skip header */
  fseek(fin, 32, SEEK_SET);

  while (1)
  {
    if (ferror(fin))
    {
      fprintf(stderr, "%s: Error reading from input file %s: ", argv[0],
        argv[1]);
      perror(NULL);
      fclose(fin);
      fclose(fout);
      return 1;
    }
    
    if (get_bit())
    {
      buf[pos++] = getc(fin);
    }
    else
    {
      if (get_bit())
      {
        unsigned char tmp[2];
        fread(tmp, 1, 2, fin);
        repeat = (tmp[1] & 0x07);
        offset = ((tmp[1] & ~0x07) << 5) | tmp[0] | 0xE000;

        if (repeat == 0)
        {
          repeat = getc (fin);

          if (repeat == 0)
            break;
          else if (repeat == 1)
            continue;
          else
            repeat++;
        }
        else
          repeat += 2;
      }
      else
      {
        repeat = ((get_bit() << 1) | get_bit()) + 2;
        offset = getc(fin) | 0xFF00;
      }

      while (repeat > 0)
      {
        buf[pos] = buf[pos + offset];
        pos++;
        repeat--;
      }
    }
  }
  
  printf("%s: Decompression (unlzexe) of %s done\n", argv[0], argv[1]);
  
  if (strcmp(&buf[sounds_start], "SND"))
  {
    fprintf(stderr, "%s: Error: Beginning of sound data not found at expected offset\n",
      argv[0]);
    ret = 1;
  }
  else if (fwrite(&buf[sounds_start], 1, sounds_end - sounds_start, fout) !=
      (sounds_end - sounds_start))
  {
    fprintf(stderr, "%s: error writing to output file %s: ", argv[0],
      output_filename);
    perror(NULL);
    ret = 1;
  }
  printf("%s: %s has been successfully written\n", argv[0], output_filename);
  
  fclose(fin);
  fclose(fout);

  return ret;
}