
/*
 *
 * main.c
 * Created on the 23rd of August 2005
 *
 * Part of the OpenJazz project
 *
 *
 * Copyright (c) 2005 Alister Thomson
 * MacOSX code by Mark Spear (Ice M A N)
 * Dreamcast code by Troy(GPF)
 * GP2X code by Israel López Fernández (Puck2099)
 * GP32 code by A600
 *
 * OpenJazz is distributed under the terms of
 * the GNU General Public License, version 2.0
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

/*
 * Contains the main function and core utility functions.
 *
 */


// Uncomment the following line to compile on the GP2X
//#define __GP2X__

#define Extern
#include "OpenJazz.h"
#include <string.h>
#include <psppower.h>

#ifdef _arch_dreamcast
  #include <SDL_dreamcast.h>
  #include <kos.h>
  #include <arch/gdb.h> 
#endif

#ifdef __GP2X__
  #include "minimal.h"
#endif

#ifdef GP32
  #include <x_gp32.h>
#endif 

// Functions in font.c
extern font * loadFont   (char * fn);
extern void   freeFont   (font * f);

// Functions in sound.c
extern int  loadSounds (char * fn);
extern void freeSounds (void);


int oldTicks;
int fakePalette;


FILE * fopenFromPath (char * fileName) {

  FILE *f;
  char *filePath;

  // Allocate space for the file path
  filePath = malloc(strlen(path) + strlen(fileName) + 1);

  // Create the file path
  strcpy(filePath, path);
  strcat(filePath, fileName);

  printf("Opening %s\n", filePath);

  // Open the file
  f = fopen(filePath, "rb");

  // Free the file path
  free(filePath);

  return f;

}


int fileExists (char * fileName) {

  FILE *f;

  printf("Check: ");

  f = fopenFromPath(fileName);

  if (f == NULL) return 0;

  fclose(f);

  return 1;

}


unsigned char * loadRLE (FILE *f, int length) {

  char *buffer;
  int rle, pos, byte, count, next;

  // Determine the byte that follows the block
  next = fgetc(f);
  next += fgetc(f) << 8;
  next += ftell(f);

  // Allocate memory for the decompressed data
  buffer = malloc(length);

  pos = 0;

  while (pos < length) {

    rle = fgetc(f);

    if (rle > 127) {

      byte = fgetc(f);

      for (count = 0; count < (rle & 127); count++) {

        buffer[pos++] = byte;
        if (pos >= length) break;

      }

    } else if (rle > 0) {

      for (count = 0; count < rle; count++) {

        buffer[pos++] = fgetc(f);
        if (pos >= length) break;

      }

    } else break;

  }

  fseek(f, next, SEEK_SET);

  return buffer;

}


void skipRLE (FILE *f) {

  int next;

  next = fgetc(f);
  next += fgetc(f) << 8;

  fseek(f, next, SEEK_CUR);

  return;

}


char * loadString (FILE *f) {

  char *string;
  int length, count;

  length = fgetc(f);

  if (length) {

    string = malloc(length + 1);
    for (count = 0; count < length; count++) string[count] = fgetc(f);
    string[length] = 0;

  } else {

    // If the length is not given, assume it is an 8.3 (or 7.3, etc) file name
    string = malloc(13);
    for (count = 0; (count < 8) && (!length); count++) {

      string[count] = fgetc(f);
      if (string[count] == '.') length = ~0;

    }
    string[count++] = fgetc(f);
    string[count++] = fgetc(f);
    string[count++] = fgetc(f);
    string[count] = 0;

  }

  return string;

}


SDL_Surface * createSurface (unsigned char * pixels, int width, int height) {

  SDL_Surface *ret;
  int y;

  // Create the surface
  ret = SDL_CreateRGBSurface(SDL_HWSURFACE, width, height, 8, 0, 0, 0, 0);

  // Set the surface's palette
  SDL_SetPalette(ret, SDL_LOGPAL, logicalPalette, 0, 256);

  // Upload pixel data to the surface
  if (SDL_MUSTLOCK(ret)) SDL_LockSurface(ret);
  for (y = 0; y < height; y++)
    memcpy(ret->pixels + (ret->pitch * y), pixels + (width * y), width);
  if (SDL_MUSTLOCK(ret)) SDL_UnlockSurface(ret);

  // Free redundant pixel data
  free(pixels);

  return ret;

}


SDL_Surface * createBlankSurface (void) {

  SDL_Surface *ret;
  unsigned char *pixels;

  pixels = malloc(1);
  *pixels = 0;
  ret = createSurface(pixels, 1, 1);
  SDL_SetColorKey(ret, SDL_SRCCOLORKEY, 0);

  return ret;

}


void loadPalette (SDL_Color *palette, FILE *f) {

  unsigned char *buffer;
  int count;

  buffer = loadRLE(f, 768);

//  printf("JASC-PAL\n0100\n256\n");

  for (count = 0; count < 256; count++) {

    // Palette entries are 6-bit
    // Shift them upwards to 8-bit, and fill in the lower 2 bits
    palette[count].r = (buffer[count * 3] << 2) + (buffer[count * 3] >> 4);
    palette[count].g = (buffer[(count * 3) + 1] << 2) +
                       (buffer[(count * 3) + 1] >> 4);
    palette[count].b = (buffer[(count * 3) + 2] << 2) +
                       (buffer[(count * 3) + 2] >> 4);

//    printf("%i %i %i\n", palette[count].r, palette[count].g,
//           palette[count].b);

  }

  free(buffer);

  return;

}


int loadMain (void) {

  FILE *f;
  unsigned char *pixels, *sorted;
  int rle, pos, index, count, x, y;


  // Generate the logical palette
  for (count = 0; count < 256; count++) {

    logicalPalette[count].r = count;
    logicalPalette[count].g = count;
    logicalPalette[count].b = count;

  }

  SDL_SetPalette(screen, SDL_LOGPAL, logicalPalette, 0, 256);


  // Default level & difficulty settings

  currentLevel = malloc(11);
  strcpy(currentLevel, "level0.000");

  nextLevel = malloc(11);
  strcpy(nextLevel, "level0.000");

  difficulty = 1;


  // Load the panel

  f = fopenFromPath("panel.000");

  if (f == NULL) {

    free(currentLevel);
    free(nextLevel);

    return FAILURE;

  }

  // Load the panel background
  panel = loadSurface(f, 320, 32);


  // Load the large panel font
  // Starts at 4691 and goes 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-:.

  pixels = malloc(320 * 8);
  
  pixels[0] = 31;

  pos = 1;

  fseek(f, 4691, SEEK_SET);

  // RLE decompression and horizontal to vertical character rearrangement
  while (pos < 320 * 8) {

    rle = fgetc(f);

    if (rle >= 128) {

      index = fgetc(f);

      for (count = 0; count < (rle & 127); count++) {

        pixels[(pos & 7) + ((pos / 320) * 8) + (((pos % 320)>>3) * 8 * 8)] =
          index;
        pos++;

      }

    } else if (rle > 0) {

      for (count = 0; count < rle; count++) {

        pixels[(pos & 7) + ((pos / 320) * 8) + (((pos % 320)>>3) * 8 * 8)] =
          fgetc(f);
        pos++;

      }

    } else break;

  }

  // Create ASCII->font map
  for (count = 0; count < 45; count++) panelBigFont.map[count] = 39;
  panelBigFont.map[45] = 36;
  panelBigFont.map[46] = 38;
  for (count = 47; count < 48; count++) panelBigFont.map[count] = 39;
  for (; count < 58; count++) panelBigFont.map[count] = count - 48;
  panelBigFont.map[58] = 37;
  for (count = 59; count < 65; count++) panelBigFont.map[count] = 39;
  for (; count < 91; count++) panelBigFont.map[count] = count - 55;
  for (; count < 97; count++) panelBigFont.map[count] = 39;
  for (; count < 123; count++) panelBigFont.map[count] = count - 87;
  for (; count < 128; count++) panelBigFont.map[count] = 39;

  // Set font dimensions
  panelBigFont.w = malloc(40);
  for (count = 0; count < 40; count++) panelBigFont.w[count] = 8;
  panelBigFont.h = 8;

  panelBigFont.pixels = createSurface(pixels, 8, 320);


  // Load the small panel font
  // Starts at 6975 and goes 0123456789oo (where oo = infinity)

  pixels = malloc(320 * 7);
  
  pos = 0;

  fseek(f, 6975, SEEK_SET);

  // RLE decompression and horizontal to vertical character rearrangement
  while (pos < 320 * 7) {

    rle = fgetc(f);

    if (rle >= 128) {

      index = fgetc(f);

      for (count = 0; count < (rle & 127); count++) {

        pixels[(pos & 7) + ((pos / 320) * 8) + (((pos % 320)>>3) * 8 * 7)] =
          index;
        pos++;

      }

    } else if (rle > 0) {

      for (count = 0; count < rle; count++) {

        pixels[(pos & 7) + ((pos / 320) * 8) + (((pos % 320)>>3) * 8 * 7)] =
          fgetc(f);
        pos++;

      }

    } else break;

  }

  // Create ASCII->font map
  for (count = 0; count < 48; count++) panelSmallFont.map[count] = 12;
  // Use :; to represent oo
  for (; count < 60; count++) panelSmallFont.map[count] = count - 48;
  for (; count < 128; count++) panelSmallFont.map[count] = 12;

  // Set font dimensions
  panelSmallFont.w = malloc(13);
  for (count = 0; count < 13; count++) panelSmallFont.w[count] = 8;
  panelSmallFont.h = 7;

  panelSmallFont.pixels = createSurface(pixels, 8, 280);

  // Load the panel's ammo graphics

  fseek(f, 7537, SEEK_SET);
  sorted = malloc(64 * 27);
  pixels = loadRLE(f, 64 * 27);

  for (y = 0; y < 27; y++) {

    for (x = 0; x < 64; x++)
      sorted[(y * 64) + x] = pixels[(y * 64) + (x >> 2) + ((x & 3) << 4)];

  }

  panelAmmo[0] = createSurface(sorted, 64, 27);
  sorted = pixels; // Re-use the allocated memory

  fseek(f, 8264, SEEK_SET);
  pixels = loadRLE(f, 64 * 27);

  for (y = 0; y < 27; y++) {

    for (x = 0; x < 64; x++)
      sorted[(y * 64) + x] = pixels[(y * 64) + (x >> 2) + ((x & 3) << 4)];

  }

  panelAmmo[1] = createSurface(sorted, 64, 27);
  sorted = pixels; // Re-use the allocated memory

  fseek(f, 9550, SEEK_SET);
  pixels = loadRLE(f, 64 * 27);

  for (y = 0; y < 27; y++) {

    for (x = 0; x < 64; x++)
      sorted[(y * 64) + x] = pixels[(y * 64) + (x >> 2) + ((x & 3) << 4)];

  }

  panelAmmo[2] = createSurface(sorted, 64, 27);
  sorted = pixels; // Re-use the allocated memory

  fseek(f, 11060, SEEK_SET);
  pixels = loadRLE(f, 64 * 27);

  for (y = 0; y < 27; y++) {

    for (x = 0; x < 64; x++)
      sorted[(y * 64) + x] = pixels[(y * 64) + (x >> 2) + ((x & 3) << 4)];

  }

  panelAmmo[3] = createSurface(sorted, 64, 27);
  sorted = pixels; // Re-use the allocated memory

  fseek(f, 12258, SEEK_SET);
  pixels = loadRLE(f, 64 * 27);

  for (y = 0; y < 27; y++) {

    for (x = 0; x < 64; x++)
      sorted[(y * 64) + x] = pixels[(y * 64) + (x >> 2) + ((x & 3) << 4)];

  }

  panelAmmo[4] = createSurface(sorted, 64, 27);
  free(pixels);

  fclose(f);

  // Load real fonts
  font2 = loadFont("font2.0fn");
  fontbig = loadFont("fontbig.0fn");
  fontiny = loadFont("fontiny.0fn");
  fontmn1 = loadFont("fontmn1.0fn");
  fontmn2 = loadFont("fontmn2.0fn");

  firstPE = NULL;

  return SUCCESS;

}


void usePalette (SDL_Color *palette) {

  // Make palette changes invisible until the next draw. Hopefully.
  SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
  SDL_Flip(screen);

  SDL_SetPalette(screen, SDL_PHYSPAL, palette, 0, 256);
  currentPalette = palette;

  return;

}


void scalePalette (SDL_Surface *surface, fixed scale, signed int offset) {

  SDL_Color palette[256];
  int count;

  for (count = 0; count < 256; count++) {

    palette[count].r = ((count * scale) >> 10) + offset;
    palette[count].g = ((count * scale) >> 10) + offset;
    palette[count].b = ((count * scale) >> 10) + offset;

  }

  SDL_SetPalette(surface, SDL_LOGPAL, palette, 0, 256);

  return;

}


void restorePalette (SDL_Surface *surface) {

  SDL_SetPalette(surface, SDL_LOGPAL, logicalPalette, 0, 256);

  return;

}


void releaseControl (int control) {

  controls[control].time = oldTicks + 500;
  controls[control].state = SDL_RELEASED;

  return;

}


int loop (void) {

  SDL_Event event;
  paletteEffect *currentPE;
  SDL_Color shownPalette[256];
  int count, x, y;

#ifdef __GP2X__
  unsigned long pad;
#endif

  // Show everything that has been drawn so far
  SDL_Flip(screen);

  // Calculate frame rate and key timing
  count = SDL_GetTicks();
  fps = 1000.0f / ((float)(count - oldTicks));
  if (count - oldTicks > 100) oldTicks = count - 100;
  mspf = count - oldTicks;
  oldTicks = count;

#ifdef __GP2X__
  pad = gp2x_joystick_read();
#endif


  // Process system events
  while (SDL_PollEvent(&event)) {

    switch (event.type) {

      case SDL_KEYDOWN:

#ifndef FULLSCREEN_ONLY
        // If Alt + Enter has been pressed, go to full screen
        if ((event.key.keysym.sym == SDLK_RETURN) &&
            (event.key.keysym.mod & KMOD_ALT)        ) {

          fullscreen = !fullscreen;

          if (fullscreen) {

            SDL_ShowCursor(SDL_DISABLE);
            screen = SDL_SetVideoMode(screenW, screenH, 8,
                                      SDL_FULLSCREEN | SDL_DOUBLEBUF |
                                      SDL_HWSURFACE | SDL_HWPALETTE);
            SDL_SetPalette(screen, SDL_LOGPAL, logicalPalette, 0, 256);
            SDL_SetPalette(screen, SDL_PHYSPAL, currentPalette, 0, 256);

            // A real 8-bit display is quite likely if the user has the right
            // video card, the right video drivers, the right version of
            // DirectX/whatever, and the right version of SDL. In other words,
            // it's not likely enough.
            // If a real palette is assumed when
            // a) There really is a real palette, there will be an extremely
            // small speed gain
            // b) The palette is emulated, there will be a HUGE speed loss
            // Therefore, assume the palette is emulated
            // To do: Find a better way!
            fakePalette = 1;

          } else {

            screen = SDL_SetVideoMode(screenW, screenH, 8,
                                      SDL_RESIZABLE | SDL_DOUBLEBUF |
                                      SDL_HWSURFACE | SDL_HWPALETTE  );
            SDL_SetPalette(screen, SDL_LOGPAL, logicalPalette, 0, 256);
            SDL_SetPalette(screen, SDL_PHYSPAL, currentPalette, 0, 256);
            SDL_ShowCursor(SDL_ENABLE);

            // Assume that in windowed mode the palette is being emulated
            // This is extremely likely
            // To do: Find a better way!
            fakePalette = 1;

          }

        }

        // The absence of a break statement is intentional
#endif

      case SDL_KEYUP:

        for (count = 0; count < CONTROLS; count++)
          if (event.key.keysym.sym == keys[count].key)
            keys[count].state = event.key.state;

        break;

#ifdef __GP2X__
      case SDL_JOYBUTTONDOWN:

        if (pad & GP2X_DOWN) keys[K_DOWN].state = SDL_PRESSED;
        if (pad & GP2X_UP) keys[K_UP].state = SDL_PRESSED;
        if (pad & GP2X_LEFT) keys[K_LEFT].state = SDL_PRESSED;
        if (pad & GP2X_RIGHT) keys[K_RIGHT].state = SDL_PRESSED;
        if (pad & GP2X_A) keys[K_JUMP].state = SDL_PRESSED;
        if (pad & GP2X_B) keys[K_FIRE].state = SDL_PRESSED;
        if (pad & GP2X_Y) keys[K_CHANGE].state = SDL_PRESSED;
        if (pad & GP2X_START) keys[K_ESCAPE].state = SDL_PRESSED;

        break;

      case SDL_JOYBUTTONUP:

        if (!(pad & GP2X_DOWN)) keys[K_DOWN].state = SDL_RELEASED;
        if (!(pad & GP2X_UP)) keys[K_UP].state = SDL_RELEASED;
        if (!(pad & GP2X_LEFT)) keys[K_LEFT].state = SDL_RELEASED;
        if (!(pad & GP2X_RIGHT)) keys[K_RIGHT].state = SDL_RELEASED;
        if (!(pad & GP2X_A)) keys[K_JUMP].state = SDL_RELEASED;
        if (!(pad & GP2X_B)) keys[K_FIRE].state = SDL_RELEASED;
        if (!(pad & GP2X_Y)) keys[K_CHANGE].state = SDL_RELEASED;

        break;
#endif

#if !defined(__GP2X__) && !defined(_arch_dreamcast)
      case SDL_JOYBUTTONDOWN:
      case SDL_JOYBUTTONUP:

        for (count = 0; count < CONTROLS; count++)
          if (event.jbutton.button == buttons[count].button)
            buttons[count].state = event.jbutton.state;

        break;

      case SDL_JOYAXISMOTION:

        for (count = 0; count < CONTROLS; count++)
          if (event.jaxis.axis == axes[count].axis) {

            if (!axes[count].direction && (event.jaxis.value < -16384))
              axes[count].state = SDL_PRESSED;

            else if (axes[count].direction && (event.jaxis.value > 16384))
              axes[count].state = SDL_PRESSED;

            else axes[count].state = SDL_RELEASED;

          }

      break;
#endif

#ifndef FULLSCREEN_ONLY
      case SDL_VIDEORESIZE:

        screenW = event.resize.w;
        screenH = event.resize.h;
        if (screenW > 320) bgScale = ((screenH - 1) / 100) + 1;
        else bgScale = ((screenH - 34) / 100) + 1;
        screen = SDL_SetVideoMode(screenW, screenH, 8,
                                  SDL_RESIZABLE | SDL_DOUBLEBUF |
                                  SDL_HWSURFACE | SDL_HWPALETTE  );

        // The absence of a break statement is intentional

      case SDL_VIDEOEXPOSE:

        SDL_SetPalette(screen, SDL_LOGPAL, logicalPalette, 0, 256);
        SDL_SetPalette(screen, SDL_PHYSPAL, currentPalette, 0, 256);

        break;
#endif

      case SDL_QUIT:

        return QUIT;

    }

  }

  // Apply controls to universal control tracking
  for (count = 0; count < CONTROLS; count++) {

    if ((keys[count].state == SDL_PRESSED) ||
        (buttons[count].state == SDL_PRESSED) || 
        (axes[count].state == SDL_PRESSED)      ) {

      if (controls[count].time < oldTicks) controls[count].state = SDL_PRESSED;

    } else {

      controls[count].time = 0;
      controls[count].state = SDL_RELEASED;

    }

  }


  // Apply palette effects

  currentPE = firstPE;

  // If the palette is being emulated, compile all palette changes and apply
  // them all at once.
  // If the palette is being used directly, apply all palette effects directly
  if (fakePalette) {

    memcpy(shownPalette, currentPalette, sizeof(SDL_Color) << 8);

    while (currentPE) {

      switch (currentPE->type) {

        case PE_FADE:

          if (currentPE->position) {

            for (count = currentPE->first;
                 count < currentPE->first + currentPE->amount; count++) {

              shownPalette[count].r = (currentPalette[count].r *
                                       currentPE->position) >> 10;
              shownPalette[count].g = (currentPalette[count].g *
                                       currentPE->position) >> 10;
              shownPalette[count].b = (currentPalette[count].b *
                                       currentPE->position) >> 10;

            }

            currentPE->position -= (mspf << 10) / currentPE->speed;

          } else {

            memset(shownPalette + currentPE->first, 0,
                   sizeof(SDL_Color) * currentPE->amount);

          }

          break;

        case PE_1D:

          currentPE->position = currentPE->amount - 1 -
                                (((currentPE->position * currentPE->speed) >>
                                  10)
                                 % currentPE->amount                          );

          // The lack of a break statement is intentional

        case PE_ROTATE:

          for (count = 0; count < currentPE->amount; count++) {

            memcpy(shownPalette + currentPE->first + count,
                   currentPalette + currentPE->first +
                   ((count + (currentPE->position >> 10)) % currentPE->amount),
                   sizeof(SDL_Color));

          }

          currentPE->position -= (mspf * currentPE->speed) >> 10;
          while (currentPE->position < 0)
            currentPE->position += currentPE->amount << 10;

          break;

        case PE_SKY:

          count = (((currentPE->position * currentPE->speed) / bgScale) >> 20) %
                  255;

          if (count > 255 - currentPE->amount) {

            memcpy(shownPalette + currentPE->first, skyPalette + count,
                   sizeof(SDL_Color) * (255 - count));
            memcpy(shownPalette + currentPE->first + (255 - count), skyPalette,
                   sizeof(SDL_Color) * (count + currentPE->amount - 255));

          } else {

            memcpy(shownPalette + currentPE->first, skyPalette + count,
                   sizeof(SDL_Color) * currentPE->amount);

          }

          break;

        case PE_2D:

          x = (((256 * 32) - *((short int *)&(currentPE->position))) *
               currentPE->speed) >> 10;
          y = (((64 * 32) - *((short int *)&(currentPE->position) + 1)) *
               currentPE->speed) >> 10;

          for (count = 0; count < currentPE->amount >> 3; count++) {

            int j;

            for (j = 0; j < 8; j++) {

              memcpy(shownPalette + currentPE->first + (count << 3) + j,
                     currentPalette + currentPE->first +
                     (((count + y) % 8) << 3) + ((j + x) % 8),
                     sizeof(SDL_Color));

            }

          }

          break;

        case PE_WATER:

          // To do: Underwater darkness palette effect
          break;

      }

      currentPE = currentPE->next;

    }

    if (firstPE) SDL_SetPalette(screen, SDL_PHYSPAL, shownPalette, 0, 256);

  } else {

    while (currentPE) {

      switch (currentPE->type) {

        case PE_FADE:

          if (currentPE->position) {

            for (count = currentPE->first;
                 count < currentPE->first + currentPE->amount; count++) {

              shownPalette[count].r = (currentPalette[count].r *
                                       currentPE->position) >> 10;
              shownPalette[count].g = (currentPalette[count].g *
                                       currentPE->position) >> 10;
              shownPalette[count].b = (currentPalette[count].b *
                                       currentPE->position) >> 10;

            }

            currentPE->position -= (mspf << 10) / currentPE->speed;

          } else {

            memset(shownPalette + currentPE->first, 0,
                   sizeof(SDL_Color) * currentPE->amount);

          }

          SDL_SetPalette(screen, SDL_PHYSPAL, shownPalette + currentPE->first,
                         currentPE->first, currentPE->amount);

          break;

        case PE_1D:

          currentPE->position = currentPE->amount - 1 -
                                (((currentPE->position * currentPE->speed) >>
                                  10)
                                 % currentPE->amount                          );

          // The lack of a break statement is intentional

        case PE_ROTATE:

          for (count = 0; count < currentPE->amount; count++) {

            memcpy(shownPalette + currentPE->first + count,
                   currentPalette + currentPE->first +
                   ((count + (currentPE->position >> 10)) % currentPE->amount),
                   sizeof(SDL_Color));

          }

          currentPE->position -= (mspf * currentPE->speed) >> 10;
          while (currentPE->position < 0)
            currentPE->position += currentPE->amount << 10;

          SDL_SetPalette(screen, SDL_PHYSPAL, shownPalette + currentPE->first,
                         currentPE->first, currentPE->amount);

          break;

        case PE_SKY:

          count = (((currentPE->position * currentPE->speed) / bgScale) >> 20) %
                  255;

          if (count > 255 - currentPE->amount) {

            SDL_SetPalette(screen, SDL_PHYSPAL, skyPalette + count,
                           currentPE->first, 255 - count);
            SDL_SetPalette(screen, SDL_PHYSPAL, skyPalette,
                           currentPE->first + (255 - count),
                           count + currentPE->amount - 255);

          } else {

            SDL_SetPalette(screen, SDL_PHYSPAL, skyPalette + count,
                           currentPE->first, currentPE->amount);

          }

          break;

        case PE_2D:

          x = (((256 * 32) - *((short int *)&(currentPE->position))) *
               currentPE->speed) >> 10;
          y = (((64 * 32) - *((short int *)&(currentPE->position) + 1)) *
               currentPE->speed) >> 10;

          for (count = 0; count < currentPE->amount >> 3; count++) {

            int j;

            for (j = 0; j < 8; j++) {

              memcpy(shownPalette + currentPE->first + (count << 3) + j,
                     currentPalette + currentPE->first +
                     (((count + y) % 8) << 3) + ((j + x) % 8),
                     sizeof(SDL_Color));

            }

          }

          SDL_SetPalette(screen, SDL_PHYSPAL, shownPalette + currentPE->first,
                         currentPE->first, currentPE->amount);

          break;

        case PE_WATER:

          // To do: Underwater darkness palette effect
          break;

      }

      currentPE = currentPE->next;

    }

  }


  return SUCCESS;

}


void freeMain (void) {

  SDL_FreeSurface(panel);

  SDL_FreeSurface(panelBigFont.pixels);
  free(panelBigFont.w);

  SDL_FreeSurface(panelSmallFont.pixels);
  free(panelSmallFont.w);

  freeFont(font2);
  freeFont(fontbig);
  freeFont(fontiny);
  freeFont(fontmn1);
  freeFont(fontmn2);

  SDL_FreeSurface(panelAmmo[0]);
  SDL_FreeSurface(panelAmmo[1]);
  SDL_FreeSurface(panelAmmo[2]);
  SDL_FreeSurface(panelAmmo[3]);
  SDL_FreeSurface(panelAmmo[4]);

  free(currentLevel);
  free(nextLevel);
  free(path);

  return;

}


int main(int argc, char *argv[]) {

  int count;
scePowerSetClockFrequency (333,333,166);
#ifdef GP32
  x_gp32_SetCPUSpeed_133();
#endif

  // Find path

#if !defined(_arch_dreamcast) && !defined(GP32)
  if (argc < 2) {

    int count;

    count = strlen(argv[0]) - 1;

  #ifdef WIN32
    while ((argv[0][count] != '\\') && (count >= 0)) count--;
  #else
    #ifdef __MACH__
    count = (unsigned int)(strstr(argv[0], "app/Contents/MacOS") - argv[0]);
    #endif

    while ((argv[0][count] != '/') && (count >= 0)) count--;
  #endif

    path = malloc(count + 2);
    if (count >= 0) {

      memcpy(path, argv[0], count + 1);
      path[count + 1] = 0;

    } else path[0] = 0;

  } else {

  #ifdef WIN32
    if (argv[1][strlen(argv[1]) - 1] != '\\') {
  #else
    if (argv[1][strlen(argv[1]) - 1] != '/') {
  #endif

      path = malloc(strlen(argv[1]) + 2);
      strcpy(path, argv[1]);

  #ifdef WIN32
      strcat(path, "\\");
  #else
      strcat(path, "/");
  #endif

    } else {

      path = malloc(strlen(argv[1]) + 1);
      strcpy(path, argv[1]);

    }

  }
#endif
#ifdef _arch_dreamcast
  path = malloc(5);
  strcpy(path, "/cd/");
#endif
#ifdef GP32
  path = malloc(12);
  strcpy(path, DATA_PREFIX);
#endif


  // Initialise global variables

  keys[C_ENTER].key  = SDLK_RETURN;
  keys[C_ESCAPE].key = SDLK_ESCAPE;

  // To do: Load controls from config file

#ifdef GP32
  keys[C_UP].key     = SDLK_UP;
  keys[C_DOWN].key   = SDLK_DOWN;
  keys[C_LEFT].key   = SDLK_LEFT;
  keys[C_RIGHT].key  = SDLK_RIGHT;
  keys[C_JUMP].key   = SDLK_LCTRL;
  keys[C_FIRE].key   = SDLK_LALT;
  keys[C_CHANGE].key = SDLK_SPACE;
#else
  keys[C_UP].key     = SDLK_UP;
  keys[C_DOWN].key   = SDLK_DOWN;
  keys[C_LEFT].key   = SDLK_LEFT;
  keys[C_RIGHT].key  = SDLK_RIGHT;
  #ifdef WIN32
  keys[C_JUMP].key   = SDLK_RALT;
  keys[C_FIRE].key   = SDLK_SPACE;
  #else
  keys[C_JUMP].key   = SDLK_SPACE;
  keys[C_FIRE].key   = SDLK_LALT;
  #endif
  keys[C_CHANGE].key = SDLK_RCTRL;
#endif

  buttons[C_UP].button = 8;
  buttons[C_DOWN].button = 6;
  buttons[C_LEFT].button = 7;
  buttons[C_RIGHT].button = 9;
  buttons[C_JUMP].button = 5;
  buttons[C_FIRE].button = 2;
  buttons[C_CHANGE].button = 1;
  buttons[C_ENTER].button = 0;
  buttons[C_ESCAPE].button = 10;

  axes[C_UP].axis = 1;
  axes[C_UP].direction = 0;
  axes[C_DOWN].axis = 1;
  axes[C_DOWN].direction = 1;
  axes[C_LEFT].axis = 0;
  axes[C_LEFT].direction = 0;
  axes[C_RIGHT].axis = 0;
  axes[C_RIGHT].direction = 1;
  axes[C_JUMP].axis = -1;
  axes[C_FIRE].axis = -1;
  axes[C_CHANGE].axis = -1;
  axes[C_ENTER].axis = -1;
  axes[C_ESCAPE].axis = -1;

  for (count = 0; count < CONTROLS; count++) {

    controls[count].time = 0;
    controls[count].state= SDL_RELEASED;

  }

  // To do: Load resolution from config file
  screenW = 480;
  screenH = 272;
#ifndef FULLSCREEN_ONLY
  fullscreen = 0;
#endif
  bgScale = 2;

  mspf = 20;
  fps = 50.0f; // Arbitrarily chosen starting speed

#ifdef __GP2X__
  gp2x_init(1000, 16, 11025, 16, 1, 60, 1);
#endif

  // Initialise SDL

  if(SDL_Init(SDL_INIT_VIDEO |  SDL_INIT_JOYSTICK |
              SDL_INIT_TIMER                                       ) < 0) {

    fprintf(stderr, "Could not start SDL: %s\n", SDL_GetError());

    return -1;

  }


  // Create the game's window

#ifdef FULLSCREEN_ONLY
  #ifdef __GP2X__
  if ((screen = SDL_SetVideoMode(screenW, screenH, 8, SDL_SWSURFACE)) == NULL) {
  #else
  if ((screen = SDL_SetVideoMode(screenW, screenH, 8,
                                 SDL_FULLSCREEN | SDL_DOUBLEBUF |
                                 SDL_HWSURFACE | SDL_HWPALETTE   )) == NULL) {
  #endif
#else
  if ((screen = SDL_SetVideoMode(screenW, screenH, 8,
                                 SDL_RESIZABLE | SDL_DOUBLEBUF |
                                 SDL_HWSURFACE | SDL_HWPALETTE  )) == NULL) {
#endif
    fprintf(stderr, "Could not set video mode: %s\n", SDL_GetError());

    return -1;

  }

  if (SDL_NumJoysticks() > 0) SDL_JoystickOpen(0);

#ifdef _arch_dreamcast	
  // Map dreamcast controls to the keyboard
  SDL_DC_SetVideoDriver(SDL_DC_DIRECT_VIDEO);
  SDL_DC_VerticalWait(SDL_FALSE);
  SDL_DC_EmulateKeyboard(SDL_TRUE);
  SDL_DC_MapKey(0, SDL_DC_LEFT, SDLK_LEFT);
  SDL_DC_MapKey(0, SDL_DC_RIGHT, SDLK_RIGHT);
  SDL_DC_MapKey(0, SDL_DC_UP, SDLK_UP);
  SDL_DC_MapKey(0, SDL_DC_DOWN, SDLK_DOWN);
  SDL_DC_MapKey(0, SDL_DC_START, SDLK_RETURN);
  SDL_DC_MapKey(0, SDL_DC_A, SDLK_SPACE);
  SDL_DC_MapKey(0, SDL_DC_X, SDLK_LALT);
  SDL_DC_MapKey(0, SDL_DC_Y, SDLK_LALT);
  SDL_DC_MapKey(0, SDL_DC_B, SDLK_SPACE);
#endif

#ifdef __GP2X__
  SDL_ShowCursor(0);
#endif


  // Assume that in windowed mode the palette is being emulated
  // This is extremely likely
  // To do: Find a better way!
  fakePalette = 1;

  //SDL_WM_SetCaption("OpenJazz", NULL);


  // Load universal game data

  if (loadMain()) {

    SDL_Quit();

    return -1;

  }

  // Establish timing
  oldTicks = SDL_GetTicks() - 20;

  // Show the startup cutscene
//  if (runScene("startup.0sc") != QUIT) {

  initSound();
    // Run the menu
    if (runMenu() != QUIT) {

      // Show the ending cutscene
//      runScene("end.0sc");

    }

//  }

  freeMain();

  SDL_Quit();

#ifdef __GP2X__
  chdir("/usr/gp2x");
  execl("/usr/gp2x/gp2xmenu", "/usr/gp2x/gp2xmenu", NULL);
#endif

  return 0;

}


