code:
/* MegaZeux
 *
 * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "run_stubs.h"

#ifdef CONFIG_EDITOR
bool is_editor(void) { return false; }
void editor_init(void) { }
void init_macros(struct world *mzx_world) { }
void free_extended_macros(struct world *mzx_world) { }
void load_editor_config(struct world *mzx_world, int *argc, char *argv[]) { }
#endif

#ifdef CONFIG_UPDATER
bool updater_init(char *argv[]) { return true; }
#endif

repo_name: pgimeno/megazeux

license: mit


code:
/* MegaZeux
 *
 * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __RUN_STUBS_H
#define __RUN_STUBS_H

#include "compat.h"

__M_BEGIN_DECLS

#include "world_struct.h"

#ifdef CONFIG_EDITOR
bool is_editor(void);
void editor_init(void);
void init_macros(struct world *mzx_world);
void free_extended_macros(struct world *mzx_world);
void load_editor_config(struct world *mzx_world, int *argc, char *argv[]);
#else
static inline bool is_editor(void) { return false; }
static inline void editor_init(void) {}
static inline void init_macros(struct world *mzx_world) {}
static inline void free_extended_macros(struct world *mzx_world) {}
static inline void load_editor_config(struct world *mzx_world,
 int *argc, char *argv[]) {}
#endif

#ifdef CONFIG_UPDATER
bool updater_init(char *argv[]);
#else
static inline bool updater_init(char *argv[])
{ return true; }
#endif

__M_END_DECLS

#endif // __RUN_STUBS_H


code:
/* MegaZeux
 *
 * Copyright (C) 1996 Greg Janson
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/* Scroll display code */

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

#include "event.h"
#include "data.h"
#include "scrdisp.h"
#include "window.h"
#include "graphics.h"
#include "error.h"
#include "intake.h"
#include "world.h"
#include "robot.h"

static const char scr_nm_strs[5][12] =
 { "  Scroll   ", "   Sign    ", "Edit Scroll", "   Help    ", "" };

static void scroll_frame(struct world *mzx_world, struct scroll *scroll,
 int pos, bool mask)
{
  // Displays one frame of a scroll. The scroll edging, arrows, and title
  // must already be shown. Simply prints each line. POS is the position
  // of the scroll's line in the text. This is of the center line.
  int t1;
  unsigned int old_pos = pos;
  char *where;
  int scroll_base_color = mzx_world->scroll_base_color;

  where = scroll->mesg;

  // Display center line
  fill_line(64, 8, 12, 32, scroll_base_color);
  if(mask)
    write_line_mask(where + pos, 8, 12, scroll_base_color, 1);
  else
    write_line_ext(where + pos, 8, 12, scroll_base_color, 1, 0, 16);

  // Display lines above center line
  for(t1 = 11; t1 >= 6; t1--)
  {
    fill_line(64, 8, t1, 32, scroll_base_color);
    // Go backward to previous line
    if(where[pos] != 1)
    {
      pos--;
      if(where[pos] == '\n')
      {
        do
        {
          pos--;
        } while((where[pos] != '\n') && (where[pos] != 1));
        // At start of prev. line -1. Display.
        pos++;
        if(mask)
          write_line_mask(where + pos, 8, t1, scroll_base_color, 1);
        else
          write_line_ext(where + pos, 8, t1, scroll_base_color, 1, 0, 16);
      }
    }
    // Next line...
  }
  // Display lines below center line
  pos = old_pos;
  for(t1 = 13; t1 <= 18; t1++)
  {
    fill_line(64, 8, t1, 32, scroll_base_color);
    if(where[pos] == 0) continue;
    // Go forward to next line
    while(where[pos] != '\n') pos++;
    // At end of current. If next is a 0, don't show nuthin'.
    pos++;
    if(where[pos])
    {
      if(mask)
        write_line_mask(where + pos, 8, t1, scroll_base_color, 1);
      else
        write_line_ext(where + pos, 8, t1, scroll_base_color, 1, 0, 16);
    }
    // Next line...
  }
}

// Also used to display scrolls. Use a type of 0 for Show Scroll, 1 for Show
// Sign, 2 for Edit Scroll.

void scroll_edit(struct world *mzx_world, struct scroll *scroll, int type)
{
  // Important status vars (insert kept in intake.cpp)
  unsigned int pos = 1, old_pos; // Where IN scroll?
  int currx = 0; // X position in line
  int key; // Key returned by intake()
  int t1, t2 = -1, t3;
  char *where; // Where scroll is
  char line[80]; // For editing
  int scroll_base_color = mzx_world->scroll_base_color;
  bool editing = (type == 2);

  // Draw screen
  save_screen();

  dialog_fadein();

  if(editing)
    scroll_edging_ext(mzx_world, type, 256, 16);
  else
    scroll_edging_ext(mzx_world, type, 0, 16);

  // Loop
  where = scroll->mesg;

  do
  {
    // If the user wants to mask, and we're in the editor..
    bool mask = mzx_world->conf.mask_midchars && editing;

    // Display scroll
    scroll_frame(mzx_world, scroll, pos, mask);
    update_screen();

    if(editing)
    {
      // Figure length
      for(t1 = 0; t1 < 80; t1++)
      {
        if(where[pos + t1] == '\n')
          break;
      }
      // t1 == length
      // Edit line
      where[pos + t1] = 0;
      strcpy(line, where + pos);
      where[pos + t1] = '\n';
      key = intake(mzx_world, line, 64, 8, 12, scroll_base_color,
       2, 0, &currx, 0, NULL);
      // Modify scroll to hold new line (give errors here)
      t2 = (int)strlen(line); // Get length of NEW line
      // Resize and move
      t3 = scroll->mesg_size;

      if(t2 - t1 > 0)
      {
        reallocate_scroll(scroll, t3 + t2 - t1);
        where = scroll->mesg;

        memmove(where + pos + t2, where + pos + t1, t3 - pos - t1);
      }
      else if(t2 - t1 < 0)
      {
        memmove(where + pos + t2, where + pos + t1, t3 - pos - t1);

        reallocate_scroll(scroll, t3 + t2 - t1);
        where = scroll->mesg;
      }

      // Copy in new line
      strcpy(where + pos, line);
      where[pos + t2] = '\n';
    }
    else
    {
      update_event_status_delay();
      key = get_key(keycode_internal);
    }

    old_pos = pos;

    if(get_mouse_press() || (key == -1))
    {
      int mouse_x, mouse_y;
      get_mouse_position(&mouse_x, &mouse_y);

      if((mouse_y >= 6) && (mouse_y <= 18) && (mouse_x >= 8) &&
       (mouse_x <= 71))
      {
        t1 = mouse_y - 12;
        if(t1)
        {
          if(t1 < 0)
            goto pgdn;
          else
            goto pgup;
        }
      }
      key = IKEY_ESCAPE;
    }

    switch(key)
    {
      case IKEY_UP:
      {
        // Go back a line (if possible)
        if(where[pos - 1] == 1) break; // Can't.
        pos--;
        // Go to start of this line.
        do
        {
          pos--;
        } while((where[pos] != '\n') && (where[pos] != 1));
        pos++;
        // Done.
        break;
      }

      case IKEY_DOWN:
      {
        // Go forward a line (if possible)
        while(where[pos] != '\n')
          pos++;
        // At end of current. Is there a next line?
        pos++;
        if(where[pos] == 0)
        {
          // Nope.
          pos = old_pos;
          break;
        }
        // Yep. Done.
        break;
      }

      case IKEY_RETURN:
      {
        if(type < 2)
        {
          key = IKEY_ESCAPE;
        }
        else
        {
          // Add in new line below. Need only add one byte.
          t3 = scroll->mesg_size;
          reallocate_scroll(scroll, t3 + 1);
          where = scroll->mesg;
          // Move all at pos + currx up a space
          memmove(where + pos + currx + 1, where + pos + currx,
           t3 - pos - currx);
          // Insert a \n
          where[pos + currx] = '\n';
          // Change pos and currx
          pos = pos + currx + 1;
          currx = 0;
          scroll->num_lines++;
        }
        break;
      }

      case IKEY_BACKSPACE:
      {
        if(type < 2)
          break;

        // We are at the start of the current line and we are trying to
        // append it to the end of the previous line. First, remember
        // the size of the current line is in t2. Now we go back a line,
        // if there is one.
        if(where[pos - 1] == 1)
          break; // Nope.

        pos--;
        // Go to start of this line, COUNTING CHARACTERS.
        t1 = 0;
        do
        {
          pos--;
          t1++;
        } while((where[pos] != '\n') && (where[pos] != 1));

        // pos is at one before the start of this line. WHO CARES!? :)
        // Now we have to see if the size, t2, is over 64 characters.
        // t2 + t1 is currently one too many so check for >65 for error.

        if((t2 + t1) > 65)
        {
          pos = old_pos;
          break; // Too long. Reset position.
        }

        // OKAY! Just copy backwards over the \n in the middle to
        // append...
        t3 = scroll->mesg_size;
        memmove(where + (old_pos - 1), where + old_pos, t3 - (old_pos + 1));
        // ...and reallocate to one space less!
        reallocate_scroll(scroll, t3 - 1);
        where = scroll->mesg;

        // pos is one before this start. Fix to the start of this new
        // line. Set currx to the length of the old line this was.
        pos++;
        currx = (int)t1 - 1;
        scroll->num_lines--;

        // FIXME - total hack!
        scroll->mesg[scroll->mesg_size - 1] = 0;

        // Done.
        break;
      }

      case IKEY_PAGEDOWN:
      {
        for(t1 = 6; t1 > 0; t1--)
        {
          pgdn:
          // Go forward a line (if possible)
          old_pos = pos;
          while(where[pos] != '\n') pos++;
          // At end of current. Is there a next line?
          pos++;
          if(where[pos] == 0)
          {
            // Nope.
            pos = old_pos;
            break;
          }
          // Yep. Done.
        }
        break;
      }

      case IKEY_PAGEUP:
      {
        for(t1 = -6; t1 < 0; t1++)
        {
          pgup:
          // Go back a line (if possible)
          if(where[pos - 1] == 1) break; // Can't.
          pos--;
          // Go to start of this line.
          do
          {
            pos--;
          } while((where[pos] != '\n') && (where[pos] != 1));
          pos++;
          // Done.
        }
        break;
      }

      case IKEY_HOME:
      {
        // FIXME - This is so dirty. Please replace it.
        t1 = -30000;
        goto pgup;
      }

      case IKEY_END:
      {
        // FIXME - See above.
        t1 = 30000;
        goto pgdn;
      }

      default:
      case IKEY_ESCAPE:
      case 0:
        break;
    }
    // Continue?
  } while(key != IKEY_ESCAPE);
  // Restore screen and exit
  restore_screen();

  dialog_fadeout();
}

void scroll_edging_ext(struct world *mzx_world, int type, int offset,
 int c_offset)
{
  int scroll_base_color = mzx_world->scroll_base_color;
  int scroll_corner_color = mzx_world->scroll_corner_color;
  int scroll_pointer_color = mzx_world->scroll_pointer_color;
  int scroll_title_color = mzx_world->scroll_title_color;

  // Displays the edging box of scrolls. Type=0 for Scroll, 1 for Sign, 2
  // for Edit Scroll, 3 for Help, 4 for Robot (w/o title)
  // Doesn't save the screen.
  // Box for the title
  draw_window_box_ext(5, 3, 74, 5, scroll_base_color & 0xF0, scroll_base_color,
   scroll_corner_color, 1, 1, offset, c_offset);
  // Main box
  draw_window_box_ext(5, 5, 74, 19, scroll_base_color, scroll_base_color & 0xF0,
   scroll_corner_color, 1, 1, offset, c_offset); /* Text on 6 - 18 */
  // Shows keys in a box at the bottom
  if(type == 3)
  {
    draw_window_box_ext(5, 19, 74, 22, scroll_base_color & 0xF0,
     scroll_base_color, scroll_corner_color, 1, 1, offset, c_offset);
  }
  else
  {
    draw_window_box_ext(5, 19, 74, 21, scroll_base_color & 0xF0,
     scroll_base_color, scroll_corner_color, 1, 1, offset, c_offset);
  }

  // Fix chars on edging
  draw_char_ext(217, scroll_base_color, 74, 5, offset, c_offset);
  draw_char_ext(217, scroll_base_color & 0xF0, 74, 19, offset, c_offset);
  // Add arrows
  draw_char_ext(16, scroll_pointer_color, 6, 12, offset, c_offset);
  draw_char_ext(17, scroll_pointer_color, 73, 12, offset, c_offset);
  // Write title
  write_string_ext(scr_nm_strs[type], 34, 4, scroll_title_color, 0,
   offset, c_offset);
  // Write key reminders
  if(type == 2)
  {
    write_string_ext("\x12\x1d: Move cursor   Alt+C: Character "
     "  Esc: Done editing", 13, 20, scroll_corner_color, 0,
     offset, c_offset);
  }
  else

  if(type < 2)
  {
    write_string_ext("\x12: Scroll text   Esc/Enter: End reading",
     21, 20, scroll_corner_color, 0, offset, c_offset);
  }
  else

  if(type == 3)
  {
    write_string_ext("\x12:Scroll text  Esc:Exit help"
     "  Enter:Select  Alt+P:Print", 13, 20,
     scroll_corner_color, 0, offset, c_offset);
    write_string_ext("F1:Help on Help "
     " Alt+F1:Table of Contents", 20, 21,
     scroll_corner_color, 0, offset, c_offset);
  }
  else
  {
    write_string_ext("\x12:Scroll text  Esc:Exit "
     "  Enter:Select", 21, 20, scroll_corner_color, 0, offset, c_offset);
  }
  update_screen();
}

#ifdef CONFIG_HELPSYS

static void help_frame(struct world *mzx_world, char *help, int pos)
{
  // Displays one frame of the help. Simply prints each line. POS is the
  // position of the center line.
  int t1, t2;
  int first = 12;
  int scroll_base_color = mzx_world->scroll_base_color;
  int scroll_arrow_color = mzx_world->scroll_arrow_color;
  unsigned int next_pos;

  // Work backwards to line
  do
  {
    if(help[pos - 1] == 1) break; // Can't.
    pos--;
    // Go to start of this line.
    do
    {
      pos--;
    } while((help[pos] != '\n') && (help[pos] != 1));
    pos++;
    //Back a line!
    first--;
  } while(first > 6);
  //First holds first line pos (6-12) to draw
  if(first > 6)
  {
    for(t1 = 6; t1 < first; t1++)
      fill_line(64, 8, t1, 32, scroll_base_color);
  }
  // Display from First to either 18 or end of help
  for(t1 = first; t1 < 19; t1++)
  {
    // Fill...
    fill_line(64, 8, t1, 32, scroll_base_color);
    // Find NEXT line NOW - Actually get end of this one.
    next_pos = pos;
    while(help[next_pos] != '\n')
      next_pos++;

    // Temp. make a 0
    help[next_pos] = 0;
    // Write- What TYPE is it?
    if(help[pos] != 255) //Normal
      color_string(help + pos, 8, t1, scroll_base_color);
    else
    {
      pos++;
      switch(help[pos])
      {
        case '$':
          // Centered. :)
          pos++;
          t2 = strlencolor(help + pos);
          color_string(help + pos, 40 - (t2 >> 1), t1, scroll_base_color);
          break;
        case '>':
        case '<':
          // Option- Jump to AFTER dest. label/fill
          pos += help[pos + 1] + 3;
          // Now show, two spaces over
          color_string(help + pos, 10, t1, scroll_base_color);
          // Add arrow
          draw_char('\x10', scroll_arrow_color, 8, t1);
          break;
        case ':':
          // Label- Jump to mesg and show
          pos += help[pos + 1] + 3;
          color_string(help + pos, 8, t1, scroll_base_color);
          break;
      }
    }
    // Now fix EOS to be a \n
    help[next_pos] = '\n';
    // Next line...
    next_pos++;
    pos = next_pos;
    if(help[pos] == 0)  break;
  }
  if(t1 < 19)
  {
    for(t1 += 1; t1 < 19; t1++)
      fill_line(64, 8, t1, 32, scroll_base_color);
  }

  update_screen();
}

void help_display(struct world *mzx_world, char *help, int offs, char *file,
 char *label)
{
  // Display a help file
  int pos = offs, old_pos; // Where
  int key = 0;
  int t1;
  char mclick;
  // allow_help = 0;
  // Draw screen
  save_screen();

  dialog_fadein();

  scroll_edging_ext(mzx_world, 3, 256, 16);

  // Loop
  file[0] = label[0] = 0;
  do
  {
    // Display scroll
    help_frame(mzx_world, help, pos);
    mclick = 0;

    update_screen();

    update_event_status_delay();

    if(get_mouse_press())
    {
      int mouse_x, mouse_y;
      get_mouse_position(&mouse_x, &mouse_y);

      // Move to line clicked on if mouse is in scroll, else exit
      if((mouse_y >= 6) && (mouse_y <= 18) &&
       (mouse_x >= 8) && (mouse_x <= 71))
      {
        mclick = 1;
        t1 = mouse_y - 12;
        if(t1 == 0)
          goto option;

        //t1<0 = PGUP t1 lines
        //t1>0 = PGDN t1 lines
        if(t1 < 0)
          goto pgup;

        goto pgdn;
      }
      key = IKEY_ESCAPE;
    }

    key = get_key(keycode_internal);
    old_pos = pos;
    switch(key)
    {
      case IKEY_F1:
      {
        if(get_alt_status(keycode_internal))
        {
          // Jump to label 072 in MAIN.HLP
          strcpy(file,"MAIN.HLP");
          strcpy(label,"072");
        }
        else
        {
          // Jump to label 000 in HELPONHE.HLP
          strcpy(file, "HELPONHE.HLP");
          strcpy(label, "000");
        }
        goto ex;
      }

      case IKEY_UP:
      {
        // Go back a line (if possible)
        if(help[pos - 1] == 1) break; // Can't.
        pos--;
        // Go to start of this line.
        do
        {
          pos--;
        } while((help[pos] != '\n') && (help[pos] != 1));
        pos++;
        // Done.
        break;
      }

      case IKEY_DOWN:
      {
        // Go forward a line (if possible)
        while(help[pos] != '\n') pos++;
        // At end of current. Is there a next line?
        pos++;
        if(help[pos] == 0)
        {
          // Nope.
          pos = old_pos;
          break;
        }
        // Yep. Done.
        break;
      }

      case IKEY_RETURN:
      {
        option:
        // Option?
        if((help[pos] == 255) && ((help[pos + 1] == '>') ||
         (help[pos + 1] == '<')))
        {
          // Yep!
          pos++;
          if(help[pos] == '<')
          {
            // Get file and label and exit
            t1 = 0;
            pos++;
            do
            {
              pos++;
              file[t1] = help[pos];
              t1++;
            } while(help[pos] != ':');
            file[t1 - 1] = 0;
            strcpy(label, help + pos + 1);
            goto ex;
          }
          // Get label and jump
          strcpy(label, help + pos + 2);

          // Search backwards for a 1
          do
          {
            pos--;
          } while(help[pos] != 1);
          // Search for label OR a \n followed by a \0
          do
          {
            pos++;
            if(help[pos] == 255)
              if(help[pos + 1] == ':')
                if(!strcmp(help + pos + 3, label))
                  // pos is correct!
                  goto labdone;
            if(help[pos] == '\n')
              if(help[pos + 1] == 0) break;
          } while(1);
        }
        // If there WAS an option, any existing label was found.
        labdone:
        break;
      }

      case IKEY_PAGEDOWN:
      {
        for(t1 = 6; t1 > 0; t1--)
        {
          pgdn:
          // Go forward a line (if possible)
          old_pos = pos;
          while(help[pos] != '\n') pos++;
          // At end of current. Is there a next line?
          pos++;
          if(help[pos] == 0)
          {
            // Nope.
            pos = old_pos;
            break;
          }
          // Yep. Done.
        }
        if(mclick)
          goto option;

        break;
      }

      case IKEY_PAGEUP:
      {
        for(t1 = -6; t1 < 0; t1++)
        {
          pgup:
          // Go back a line (if possible)
          if(help[pos - 1] == 1) break; // Can't.
          pos--;
          // Go to start of this line.
          do
          {
            pos--;
          } while((help[pos] != '\n') && (help[pos] != 1));
          pos++;
          // Done.
        }
        if(mclick)
          goto option;
        break;
      }

      case IKEY_HOME:
      {
        // FIXME - :(
        t1 = -30000;
        goto pgup;
      }

      case IKEY_END:
      {
        t1 = 30000;
        goto pgdn;
      }

      default:
      {
        break;
      }
    }
  } while(key != IKEY_ESCAPE);

  // Restore screen and exit
ex:
  dialog_fadeout();

  restore_screen();
}

#endif // CONFIG_HELPSYS

int strlencolor(char *str)
{
  int len = 0;
  char cur_char = *str;\

  while(cur_char != 0)
  {
    switch(cur_char)
    {
      // Color character
      case '@':
      case '~':
      {
        str++;
        cur_char = *str;

        // If 0, stop right there
        if(!cur_char)
          return len;

        // If the next isn't hex, count as one
        if(!isxdigit((int)cur_char))
        {
          len++;
        }
        break;
      }

      default:
      {
        len++;
      }
    }

    str++;
    cur_char = *str;
  }

  return len;
}


code:
/* MegaZeux
 *
 * Copyright (C) 1996 Greg Janson
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __SCRDISP_H
#define __SCRDISP_H

#include "compat.h"

__M_BEGIN_DECLS

#include "world_struct.h"
#include "robot_struct.h"

// Type 0/1 to DISPLAY a scroll/sign
CORE_LIBSPEC void scroll_edit(struct world *mzx_world, struct scroll *scroll,
 int type);

// type == 0 scroll, 1 sign, 2 scroll edit
void scroll_edging_ext(struct world *mzx_world, int type, int offset,
 int c_offset);
void help_display(struct world *mzx_world, char *help, int offs,
 char *file, char *label);
char print(char *str);
int strlencolor(char *str);

__M_END_DECLS

#endif // __SCRDISP_H


code:
/* MegaZeux
 *
 * Copyright (C) 1996  Greg Janson
 * Copyright (C) 2004  Gilead Kutnick <exophase@adelphia.net>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/* Sound effects system- PLAY, SFX, and bkground code */

#include <string.h>
#include <stdlib.h>

#include "sfx.h"
#include "data.h"
#include "audio.h"

int topindex = 0;  // Marks the top of the queue
int backindex = 0; // Marks bottom of queue

#if defined(CONFIG_AUDIO) || defined(CONFIG_EDITOR)

__editor_maybe_static char sfx_strs[NUM_SFX][69] =
{
  "5c-gec-gec", // Gem
  "5c-gec-gec", // Magic Gem
  "cge-zcge", // Health
  "-c+c+c-g-g-g", // Ammo
  "6a#a#zx", // Coin
  "-cegeg+c-g+cecegeg+c", // Life
  "-cc#dd#e", // Lo Bomb
  "cc#dd#e", // Hi Bomb
  "4gec-g+ec-ge+c-gec", // Key
  "-gc#-a#a", // Full Keys
  "ceg+c-eg+ce-g+ceg", // Unlock
  "s1a#z+a#-a#x", // Can't Unlock
  "-a-a+e-e+c-c", // Invis. Wall
  "zax", // Forest
  "0a#+a#-a#b+b-b", // Gate Locked
  "0a+a-a+a-a", // Opening Gate
  "-g+g-g+g+g-g+g-g-g+g-g-tg+g", // Invinco Start
  "sc-cqxsg-g+a#-a#xx+g-g+a#-a#", // Invinco Beat
  "sc-cqxsg-g+f-f+d#-d#+c-ca#-a#2c-c", // Invinco End
  "0g+g-gd#+d#-d#", // 19-Door locked
  "0g+gd#+d#", // Door opening
  "c-zgd#c-gd#c", // Hurt
  "a#e-a#e-a#e-a#e", // AUGH!
  "+c-gd#cd#c-gd#gd#c-g+c-gd#cd#c-gd#gd#c", // Death
  "ic1c+ca#+c1c+cx+d#1c+ca#+c1c+cx"
  "+c1c+ca#+c1c+cx+d#1c+c+fc1c+cx", // Game over
  "0c+c-c+c-c", //25 - Gate closing
  "1d#x", //26-Push
  "2c+c+c2d#+d#+d#2g+g+g3cd#g", // 27-Transport
  "z+c-c",// 28-shoot
  "1a+a+a", // 29-break
  "-af#-f#a", // 30-out of ammo
  "+f#", // 31-ricochet
  "s-d-d-d", // 32-out of bombs
  "c-aec-a", // 33-place bomb (lo)
  "+c-aec-a", // 34-place bomb (hi)
  "+g-ege-ege", // 35-switch bomb type
  "1c+c0d#+d#-g+g-c#", // 36-explosion
  "2cg+e+c2c#g#+f+c#2da+f#+d2d#a#+g+d#", // 37-Entrance
  "cge+c-g+ecge+c-g+ec", // 38-Pouch
  "zcd#gd#cd#gd#cd#gd#cd#gd#"
  "cfg#fcfg#fcfg#fcfg#f"
  "cga#gcga#gcga#gcga#g"
  "s+c",//39-ring/potion
  "z-a-a-aa-aa", // 40-Empty chest
  "1c+c-c#+c#-d+d-d#+d#-e+e-ec", // 41-Chest
  "c-gd#c-zd#gd#c", // 42-Out of time
  "zc-d#g-cd#", // 43-Fire ouch
  "cd#g+cd#g", // 44-Stolen gem
  "z1d#+d#+d#", // 45-Enemy HP down
  "z0ca#f+d#cf#", // 46-Dragon fire
  "cg+c-eda+d-f#eb+e-g#", // 47-Scroll/sign
  "1c+c+c+c+c", // 48-Goop
  "" // 49-Unused
};

#endif // CONFIG_AUDIO || CONFIG_EDITOR

#ifdef CONFIG_AUDIO

// Special freqs
#define F_REST          1

struct noise
{
  int duration;//This is the struc of the sound queue
  int freq;
};

// Frequencies of 6C thru 6B
static const int note_freq[12] =
 { 2032, 2152, 2280, 2416, 2560, 2712, 2880, 3048, 3232, 3424, 3624, 3840 };

// Sample frequencies of 0C thru 0B
static const int sam_freq[12] =
 { 3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1812 };

static struct noise background[NOISEMAX]; // The sound queue itself
static int sound_in_queue = 0;     // Tells if sound in queue

#ifdef CONFIG_DEBYTECODE
static const char open_char  = '(';
static const char close_char = ')';
#else
static const char open_char  = '&';
static const char close_char = '&';
#endif

static void submit_sound(int freq, int delay)
{
  if((backindex == 0) && (topindex == NOISEMAX))
    return;
  if(topindex != (backindex - 1))
  {
    // Queue full?
    background[topindex].freq = freq; // No, put it in queue
    background[topindex++].duration = delay;
    if(topindex == NOISEMAX)
      topindex = 0; // Wraparound
    sound_in_queue = 1; // Note sound in queue
  }
}

static void play_note(int note, int octave, int delay)
{
  // Note is # 1-12
  // Octave #0-6
  submit_sound(note_freq[note - 1] >> (6 - octave), delay);
}

void play_sfx(struct world *mzx_world, int sfxn)
{
  if(sfxn < NUM_SFX)
  {
    if(mzx_world->custom_sfx_on)
    {
      play_str(mzx_world->custom_sfx + (sfxn * 69), 1);
    }
    else
    {
      play_str(sfx_strs[sfxn], 1);
    }
  }
}

// sfx_play = 1 to NOT play non-digi unless queue empty

void play_str(char *str, int sfx_play)
{
  int t1, oct = 3, note = 1, dur = 18, t2, last_note = -1,
   digi_st = -1, digi_end = -1, digi_played = 0;
  char chr;
  // Note trans. table from 1-7 (a-z) to 1-12
  char nn[7] = { 10, 12, 1, 3, 5, 6, 8 };

  if(!get_sfx_on_state())
  {
    sfx_play = 1; // SFX off
  }
  else
  {
    if(!sound_in_queue)
      sfx_play = 0;
  }

  // Now, if sfx_play, only play digi
  for(t1 = 0; (unsigned int)t1 < strlen((char *)str); t1++)
  {
    chr = str[t1];
    if((chr >= 'a') && (chr <= 'z'))
      chr -= 32;

    if(chr == '-')
    {
      // Octave down
      if(oct > 0) oct--;
      continue;
    }
    if(chr == '+')
    {
      // Octave up
      if(oct < 6) oct++;
      continue;
    }
    if(chr == 'X')
    {
      // Rest
      if(!sfx_play && !(digi_st > 0))
        submit_sound(F_REST,dur);

      continue;
    }
    if((chr > 47) && (chr < 55))
      oct = chr - 48; // Set octave

    if(chr == '.')
      dur += (dur >> 1); // Dot (duration 150%)

    if(chr == '!')
      dur /= 3; // Triplet (cut duration to 33%)

    if((chr > 64) && (chr < 72))
    {
      // Note
      note = nn[chr - 65]; // Convert to 1-12
      t2 = oct; // Save old octave in case # or $ changes it
      if(str[t1 + 1] == '#')
      {
        // Increase if sharp
        t1++;
        if(++note == 13)
        {
          // "B#" := "+C-"
          note = 1;
          if(++oct == 7) oct = 6;
        }
      }
      else

      if(str[t1 + 1] == '$')
      {
        // Decrease if flat
        t1++;
        if(--note == 0)
        {
          // "C$" := "-B+"
          note = 12;
          if(--oct == -1) oct = 0;
        }
      }
      // Digi
      if(digi_st > 0)
      {
        if(get_music_on_state())
        {
          str[digi_end] = 0;

          play_sample(sam_freq[note - 1] >> oct, str + digi_st, true);

          str[digi_end] = close_char;
          digi_played = 1;
          continue;
        }
      }
      else

      if(sfx_play)
      {
        continue;
      }
      else

      if(last_note == note)
      {
        if(dur <= 9)
        {
          submit_sound(F_REST, 1);
          play_note(note, oct, dur - 1);
        }
        else
        {
          submit_sound(F_REST, 2);
          play_note(note, oct, dur - 2);
        }
      }
      else
      {
        play_note(note, oct, dur);
        last_note = note;
      }
      oct = t2; // Restore old octave
    }

    switch(chr)
    {
      case 'Z':
        dur = 9;
        break;

      case 'T':
        dur = 18;
        break;

      case 'S':
        dur = 36;
        break;

      case 'I':
        dur = 72;
        break;

      case 'Q':
        dur = 144;
        break;

      case 'H':
        dur = 288;
        break;

      case 'W':
        dur = 576;
        break;
    }

    if(chr == open_char)
    {
      // Digitized
      t1++;
      digi_st = t1;

      if(str[t1] == 0)
        break;

      do
      {
        t1++;
        if(str[t1] == 0)
          break;

        if(str[t1] == close_char)
          break;

      } while(1);

      digi_end = t1;
      digi_played = 0;
    }

    if(chr == '_')
    {
      if(get_music_on_state())
        break;

      digi_st = -1;
    }
  }

  // Pending digital music?
  if((digi_st > 0) && (digi_played == 0))
  {
    str[digi_end] = 0;

    play_sample(0, str + digi_st, true);

    str[digi_end] = close_char;
  }
}

void clear_sfx_queue(void)
{
  backindex = topindex = sound_in_queue = 0; // queue pointers
  nosound(1);
}

void sound_system(void)
{
  int sfx_on = get_sfx_on_state();

  if(sound_in_queue)
  {
    // Current sound finished, more?
    if(background[backindex].freq == F_REST)
    {
      if(sfx_on)
        nosound(background[backindex].duration + 1); // freq == 1 for rest
    }
    else

    if(sfx_on)
    {
      // Start sound
      sound(background[backindex].freq, background[backindex].duration + 1);
    }

    backindex++;
    if(backindex == NOISEMAX)
      backindex = 0; // Wraparound
    if(backindex == topindex) // If last sound reset queue pointers
      backindex = topindex = sound_in_queue = 0;
  }
  else
  {
    if(sfx_on)
      nosound(1);
  }
}

char is_playing(void)
{
  return sound_in_queue;
}

#endif // CONFIG_AUDIO


code:
/* MegaZeux
 *
 * Copyright (C) 1996 Greg Janson
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

/* Prototypes for SFX.CPP */

#ifndef __SFX_H
#define __SFX_H

#include "compat.h"

__M_BEGIN_DECLS

// Number of unique sound effects
#define NUM_SFX         50

#include "world_struct.h"

// Size of sound queue
#define NOISEMAX        4096

#ifdef CONFIG_EDITOR
CORE_LIBSPEC extern char sfx_strs[NUM_SFX][69];
#endif // CONFIG_EDITOR

extern int topindex, backindex;

#ifdef CONFIG_AUDIO

void play_sfx(struct world *mzx_world, int sfx);
void clear_sfx_queue(void);
void sound_system(void);
char is_playing(void);
void play_str(char *str, int sfx_play);

#else // !CONFIG_AUDIO

static inline void clear_sfx_queue(void) {}
static inline void play_sfx(struct world *mzx_world, int sfxn) {}
static inline void play_str(char *str, int sfx_play) {}
static inline char is_playing(void) { return 0; }

#endif // CONFIG_AUDIO

__M_END_DECLS

#endif // __SFX_H


code:
/* MegaZeux
 *
 * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

// sprite.cpp, by Exophase

// Global declarations:

#include <stdlib.h>

#include "data.h"
#include "sprite.h"
#include "graphics.h"
#include "idput.h"
#include "error.h"
#include "game.h"
#include "world.h"

// For the qsort.
static int compare_spr(const void *dest, const void *src)
{
  struct sprite *spr_dest = *((struct sprite **)dest);
  struct sprite *spr_src = *((struct sprite **)src);

  return ((spr_dest->y + spr_dest->col_y) -
   (spr_src->y + spr_src->col_y));
}

static int is_blank(char c)
{
  int cp[4];
  int blank = 0;

  cp[3] = 0;
  ec_read_char(c, (char *)cp);

  blank |= cp[0];
  blank |= cp[1];
  blank |= cp[2];
  blank |= cp[3];

  return !blank;
}

void plot_sprite(struct world *mzx_world, struct sprite *cur_sprite, int color,
 int x, int y)
{
  if(((cur_sprite->width) && (cur_sprite->height)))
  {
    cur_sprite->x = x;
    cur_sprite->y = y;

    if(color == 288)
    {
      cur_sprite->flags |= SPRITE_SRC_COLORS;
    }
    else
    {
      cur_sprite->flags &= ~SPRITE_SRC_COLORS;
      cur_sprite->color = color;
    }

    if(!(cur_sprite->flags & SPRITE_INITIALIZED))
    {
      cur_sprite->flags |= SPRITE_INITIALIZED;
      mzx_world->active_sprites++;
    }
  }
}

void draw_sprites(struct world *mzx_world)
{
  struct board *src_board = mzx_world->current_board;
  int i, i2, i3, i4, i5, i6, start_x, start_y, offset_x, offset_y;
  int skip, skip2, skip3;
  int draw_width, draw_height, ref_x, ref_y, screen_x, screen_y;
  int bwidth, bheight;
  bool use_vlayer;
  struct sprite **sprite_list = mzx_world->sprite_list;
  struct sprite *draw_order[MAX_SPRITES];
  struct sprite *cur_sprite;
  char ch, color, dcolor;
  int viewport_x = src_board->viewport_x;
  int viewport_y = src_board->viewport_y;
  int viewport_width = src_board->viewport_width;
  int viewport_height = src_board->viewport_height;
  int board_width = src_board->board_width;
  int overlay_mode = src_board->overlay_mode;
  char *overlay = src_board->overlay;
  char *src_chars;
  char *src_colors;

  // see if y sort should be done
  if(mzx_world->sprite_y_order)
  {
    // Switched to stdlib qsort. Hooray!
    // Fill it with the active sprites in the beginning
    // and the inactive sprites in the end.
    for(i = 0, i2 = 0, i3 = MAX_SPRITES - 1; i < MAX_SPRITES; i++)
    {
      if((sprite_list[i])->flags & SPRITE_INITIALIZED)
      {
        draw_order[i2] = sprite_list[i];
        i2++;
      }
      else
      {
        draw_order[i3] = sprite_list[i];
        i3--;
      }
    }

    // Now sort it, using qsort.

    qsort(draw_order, i2, sizeof(struct sprite *), compare_spr);
  }

  // draw this on top of the SCREEN window.
  for(i = 0; i < MAX_SPRITES; i++)
  {
    if(mzx_world->sprite_y_order)
      cur_sprite = draw_order[i];
    else
      cur_sprite = sprite_list[i];

    if(cur_sprite->flags & SPRITE_INITIALIZED)
    {
      calculate_xytop(mzx_world, &screen_x, &screen_y);
      ref_x = cur_sprite->ref_x;
      ref_y = cur_sprite->ref_y;
      offset_x = 0;
      offset_y = 0;
      start_x = (cur_sprite->x + viewport_x);
      start_y = (cur_sprite->y + viewport_y);

      if(!(cur_sprite->flags & SPRITE_STATIC))
      {
        start_x -= screen_x;
        start_y -= screen_y;
      }

      draw_width = cur_sprite->width;
      draw_height = cur_sprite->height;

      // clip draw position against viewport

      if(start_x < viewport_x)
      {
        offset_x = viewport_x - start_x;
        draw_width -= offset_x;
        if(draw_width < 1)
          continue;

        start_x = viewport_x;
      }

      if(start_y < viewport_y)
      {
        offset_y = viewport_y - start_y;
        draw_height -= offset_y;
        if(draw_height < 1)
          continue;

        start_y = viewport_y;
      }

      if((start_x > (viewport_x + viewport_width)) ||
       (start_y > (viewport_y + viewport_height)))
      {
        continue;
      }

      if((start_x + draw_width) > (viewport_x + viewport_width))
        draw_width = viewport_x + viewport_width - start_x;

      if((start_y + draw_height) > (viewport_y + viewport_height))
        draw_height = viewport_y + viewport_height - start_y;

      if(cur_sprite->flags & SPRITE_VLAYER)
      {
        use_vlayer = true;
        bwidth = mzx_world->vlayer_width;
        bheight = mzx_world->vlayer_height;
        src_chars = mzx_world->vlayer_chars;
        src_colors = mzx_world->vlayer_colors;
      }
      else
      {
        use_vlayer = false;
        bwidth = board_width;
        bheight = src_board->board_height;
        src_chars = src_board->level_param;
        src_colors = src_board->level_color;
      }

      if(ref_x < 0)
      {
        draw_width += ref_x;
        ref_x = 0;
      }

      if(ref_y < 0)
      {
        draw_height += ref_y;
        ref_y = 0;
      }

      if((ref_x + draw_width) > bwidth)
        draw_width = bwidth - ref_x;

      if((ref_y + draw_height) > bheight)
        draw_height = bheight - ref_y;

      i4 = ((ref_y + offset_y) * bwidth) + ref_x + offset_x;
      i5 = (start_y * 80) + start_x;

      if(overlay_mode == 2)
      {
        i6 = ((start_y - viewport_y) * board_width) + start_x - viewport_x;
      }
      else
      {
        i6 = ((cur_sprite->y + offset_y) * board_width) +
         cur_sprite->x + offset_x;
      }

      skip = bwidth - draw_width;
      skip2 = 80 - draw_width;
      skip3 = board_width - draw_width;

      switch((cur_sprite->flags & 0x0C) >> 2)
      {
        // natural colors, over overlay
        case 3: // 11
        {
          for(i2 = 0; i2 < draw_height; i2++)
          {
            for(i3 = 0; i3 < draw_width; i3++)
            {
              if(use_vlayer)
              {
                color = src_colors[i4];
                ch = src_chars[i4];
              }
              else
              {
                color = get_id_color(src_board, i4);
                ch = get_id_char(src_board, i4);
              }

              if(!(color & 0xF0))
                color = (color & 0x0F) | (get_color_linear(i5) & 0xF0);

              if(ch != 32)
              {
                if(!(cur_sprite->flags & SPRITE_CHAR_CHECK2) || !is_blank(ch))
                  draw_char_linear_ext(color, ch, i5, 0, 0);
              }
              i4++;
              i5++;
            }
            i4 += skip;
            i5 += skip2;
          }
          break;
        }

        // natural colors, under overlay
        case 2: // 10
        {
          for(i2 = 0; i2 < draw_height; i2++)
          {
            for(i3 = 0; i3 < draw_width; i3++)
            {
              if(use_vlayer)
              {
                color = src_colors[i4];
                ch = src_chars[i4];
              }
              else
              {
                color = get_id_color(src_board, i4);
                ch = get_id_char(src_board, i4);
              }

              if(!(color & 0xF0))
                color = (color & 0x0F) | (get_color_linear(i5) & 0xF0);

              if((!overlay_mode || overlay_mode == 3 ||
               (overlay[i6] == 32)) && ch != 32)
              {
                if(!(cur_sprite->flags & SPRITE_CHAR_CHECK2) || !is_blank(ch))
                  draw_char_linear_ext(color, ch, i5, 0, 0);
              }
              i4++;
              i5++;
              i6++;
            }
            i4 += skip;
            i5 += skip2;
            i6 += skip3;
          }
          break;
        }

        // given colors, over overlay
        case 1: // 01
        {
          color = cur_sprite->color;

          for(i2 = 0; i2 < draw_height; i2++)
          {
            for(i3 = 0; i3 < draw_width; i3++)
            {
              if(use_vlayer)
                ch = src_chars[i4];
              else
                ch = get_id_char(src_board, i4);

              dcolor = color;
              if(!(color & 0xF0))
                dcolor = (color & 0x0F) | (get_color_linear(i5) & 0xF0);

              if(ch != 32)
              {
                if(!(cur_sprite->flags & SPRITE_CHAR_CHECK2) || !is_blank(ch))
                  draw_char_linear_ext(dcolor, ch, i5, 0, 0);
              }
              i4++;
              i5++;
            }
            i4 += skip;
            i5 += skip2;
          }
          break;
        }

        // given colors, under overlay
        case 0: // 00
        {
          color = cur_sprite->color;

          for(i2 = 0; i2 < draw_height; i2++)
          {
            for(i3 = 0; i3 < draw_width; i3++)
            {
              if(use_vlayer)
                ch = src_chars[i4];
              else
                ch = get_id_char(src_board, i4);

              dcolor = color;
              if(!(color & 0xF0))
                dcolor = (color & 0x0F) | (get_color_linear(i5) & 0xF0);

              if((!overlay_mode || overlay_mode == 3 ||
               (overlay[i6] == 32)) && ch != 32)
              {
                if(!(cur_sprite->flags & SPRITE_CHAR_CHECK2) || !is_blank(ch))
                  draw_char_linear_ext(dcolor, ch, i5, 0, 0);
              }
              i4++;
              i5++;
              i6++;
            }
            i4 += skip;
            i5 += skip2;
            i6 += skip3;
          }
          break;
        }
      }
    }
  }
}

int sprite_at_xy(struct sprite *cur_sprite, int x, int y)
{
  if((x >= cur_sprite->x) && (x < cur_sprite->x + cur_sprite->width) &&
   (y >= cur_sprite->y) && (y < cur_sprite->y + cur_sprite->height)
   && (cur_sprite->flags & SPRITE_INITIALIZED))
  {
    return 1;
  }
  else
  {
    return 0;
  }
}

int sprite_colliding_xy(struct world *mzx_world, struct sprite *check_sprite,
 int x, int y)
{
  struct board *src_board = mzx_world->current_board;
  int board_width = src_board->board_width;
  int board_height = src_board->board_height;
  int ref_x = check_sprite->ref_x + check_sprite->col_x;
  int ref_y = check_sprite->ref_y + check_sprite->col_y;
  int col_width = check_sprite->col_width;
  int col_height = check_sprite->col_height;
  int check_x = x + check_sprite->col_x;
  int check_y = y + check_sprite->col_y;
  struct sprite **sprite_list = mzx_world->sprite_list;
  struct sprite *cur_sprite;
  int colliding = 0;
  int skip, skip2, i, i2, i3, i4, i5;
  int bwidth, bheight;
  int x1, x2, y1, y2;
  int mw, mh;
  bool use_vlayer, use_vlayer2;
  unsigned int x_lmask, x_gmask, y_lmask, y_gmask;
  unsigned int xl, xg, yl, yg, wl, hl, wg, hg;
  char *vlayer_chars = mzx_world->vlayer_chars;
  char *level_id = src_board->level_id;
  int *collision_list = mzx_world->collision_list;
  int vlayer_width = mzx_world->vlayer_width;
  int board_collide = 0;

  if(!(check_sprite->flags & SPRITE_INITIALIZED))
    return -1;

  // Check against the background, will only collide against
  // customblock for now (id 5)

  if(check_sprite->flags & SPRITE_VLAYER)
  {
    use_vlayer = true;
    bwidth = vlayer_width;
    bheight = mzx_world->vlayer_height;
  }
  else
  {
    use_vlayer = false;
    bwidth = board_width;
    bheight = src_board->board_height;
  }

  if(ref_x < 0)
  {
    col_width += ref_x;
    ref_x = 0;
  }

  if(ref_y < 0)
  {
    col_height += ref_y;
    ref_y = 0;
  }

  if(check_x < 0)
  {
    col_width += check_x;
    check_x = 0;
  }

  if(check_y < 0)
  {
    col_height += check_y;
    check_y = 0;
  }

  if((ref_x + col_width) >= bwidth)
    col_width = bwidth - ref_x;

  if((ref_y + col_width) >= bheight)
    col_height = bheight - ref_y;

  if((check_x + col_width) >= board_width)
    col_width = board_width - check_x;

  if((check_y + col_height) >= board_height)
    col_height = board_height - check_y;

  // Check for <= 0 width or height (prevent crashing)
  if((col_width <= 0) || (col_height <= 0))
  {
    mzx_world->collision_count = 0;
    return 0;
  }

  // Scan board area

  skip = board_width - col_width;
  skip2 = bwidth - col_width;

  i3 = (check_y * board_width) + check_x;
  i4 = (ref_y * bwidth) + ref_x;

  for(i = 0; i < col_height; i++)
  {
    for(i2 = 0; i2 < col_width; i2++)
    {
      // First, if ccheck is on, it won't care if the source is 32
      int c;

      if(use_vlayer)
      {
        c = vlayer_chars[i4];
      }
      else
      {
        c = get_id_char(src_board, i4);
      }

      if(!(check_sprite->flags & SPRITE_CHAR_CHECK) ||
       (c != 32))
      {
        // if ccheck2 is on and the char is blank, don't trigger.
        if(!(check_sprite->flags & SPRITE_CHAR_CHECK2) ||
         (!is_blank(c)))
        {
          if(level_id[i3] == 5)
          {
            // Colliding against background
            if(board_collide) break;
            collision_list[colliding] = -1;
            colliding++;
            board_collide = 1;
          }
        }
      }

      i3++;
      i4++;
    }
    i3 += skip;
    i4 += skip2;
  }

  for(i = 0; i < MAX_SPRITES; i++)
  {
    cur_sprite = sprite_list[i];
    if((cur_sprite == check_sprite) ||
     !(cur_sprite->flags & SPRITE_INITIALIZED))
      continue;

    if(!(cur_sprite->col_width) || !(cur_sprite->col_height))
     continue;

    x1 = cur_sprite->x + cur_sprite->col_x;
    x2 = x + check_sprite->col_x;
    y1 = cur_sprite->y + cur_sprite->col_y;
    y2 = y + check_sprite->col_y;
    x_lmask = (signed int)(x1 - x2) >> 31;
    x_gmask = ~x_lmask;
    y_lmask = (signed int)(y1 - y2) >> 31;
    y_gmask = ~y_lmask;
    xl = (x1 & x_lmask) | (x2 & x_gmask);
    xg = (x1 & x_gmask) | (x2 & x_lmask);
    yl = (y1 & y_lmask) | (y2 & y_gmask);
    yg = (y1 & y_gmask) | (y2 & y_lmask);
    wl = (cur_sprite->col_width & x_lmask) |
     (check_sprite->col_width & x_gmask);
    hl = (cur_sprite->col_height & y_lmask) |
     (check_sprite->col_height & y_gmask);
    if((((xg - xl) - wl) & ((yg - yl) - hl)) >> 31)
    {
      // Does it require char/char verification?
      if(check_sprite->flags & (SPRITE_CHAR_CHECK | SPRITE_CHAR_CHECK2))
      {
        // The sub rectangle is going to be somewhere within both sprites;
        // It's going to start at the beginning of the component further
        // along (xg/yg) which is an absolute board position; find the
        // i5 both sprites are from this... and you will iterate in a
        // rectangle there that is (xl + wl) - xg... the difference between
        // where the first one ends and the second one begins
        mw = (xl + wl) - xg;
        mh = (yl + hl) - yg;
        // Reuse these.. i5s. For both you must look at PHYSICAL data, that
        // is, where the chars of the sprite itself is.
        x1 = cur_sprite->ref_x + (xg - x1) + cur_sprite->col_x;
        y1 = cur_sprite->ref_y + (yg - y1) + cur_sprite->col_y;
        x2 = check_sprite->ref_x + (xg - x2) + check_sprite->col_x;
        y2 = check_sprite->ref_y + (yg - y2) + check_sprite->col_y;
        // Check to make sure draw area doesn't go over.
        wg = (cur_sprite->col_width & x_gmask) |
         (check_sprite->col_width & x_lmask);
        hg = (cur_sprite->col_height & y_gmask) |
         (check_sprite->col_height & y_lmask);

        if((unsigned int)mw > wg)
        {
          mw = wg;
        }
        if((unsigned int)mh > hg)
        {
          mh = hg;
        }

        // Now iterate through the rect; if both are NOT 32 then a
        // collision is flagged.

        if(cur_sprite->flags & SPRITE_VLAYER)
        {
          use_vlayer2 = true;
          i4 = (y1 * vlayer_width) + x1;
          skip = vlayer_width - mw;
        }
        else
        {
          use_vlayer2 = false;
          i4 = (y1 * board_width) + x1;
          skip = board_width - mw;
        }

        i5 = (y2 * bwidth) + x2;
        skip2 = bwidth - mw;

        // If ccheck mode 2 is on it should do a further strength check.
        if(check_sprite->flags & SPRITE_CHAR_CHECK2)
        {
          for(i2 = 0; i2 < mh; i2++)
          {
            for(i3 = 0; i3 < mw; i3++)
            {
              char c1, c2;
              if(!use_vlayer2)
              {
                c1 = get_id_char(src_board, i4);
              }
              else
              {
                c1 = vlayer_chars[i4];
              }
              if(!use_vlayer)
              {
                c2 = get_id_char(src_board, i5);
              }
              else
              {
                c2 = vlayer_chars[i5];
              }
              if((c1 != 32) && (c2 != 32))
              {
                // Collision.. maybe.
                // Neither char may be blank
                if(!(is_blank(c1) || is_blank(c2)))
                {
                  collision_list[colliding] = i;
                  colliding++;

                  goto next;
                }
              }
              i4++;
              i5++;
            }
            i4 += skip;
            i5 += skip2;
          }
        }
        else
        {
          for(i2 = 0; i2 < mh; i2++)
          {
            for(i3 = 0; i3 < mw; i3++)
            {
              char c1, c2;
              if(!use_vlayer2)
              {
                c1 = get_id_char(src_board, i4);
              }
              else
              {
                c1 = vlayer_chars[i4];
              }
              if(!use_vlayer)
              {
                c2 = get_id_char(src_board, i5);
              }
              else
              {
                c2 = vlayer_chars[i5];
              }
              if((c1 != 32) && (c2 != 32))
              {
                // Collision!
                collision_list[colliding] = i;
                colliding++;

                goto next;
              }
              i4++;
              i5++;
            }
            i4 += skip;
            i5 += skip;
          }
        }
      }
      else
      {
        collision_list[colliding] = i;
        colliding++;
      }

      next:
      continue;
    }
  }

  mzx_world->collision_count = colliding;
  return colliding;
}


code:
/* MegaZeux
 *
 * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __SPRITE_H
#define __SPRITE_H

#include "compat.h"

__M_BEGIN_DECLS

#include "sprite_struct.h"
#include "world_struct.h"

// sprite.h, by Exophase

// structures

  // flags - abcdefgh
  // a - nothing
  // b - nothing
  // c - nothing
  // d - static
  // e - use "natural" colors
  // f - draw over overlay
  // g - char based collision
  // h - initialized

enum
{
  SPRITE_INITIALIZED  = (1 << 0),
  SPRITE_CHAR_CHECK   = (1 << 1),
  SPRITE_OVER_OVERLAY = (1 << 2),
  SPRITE_SRC_COLORS   = (1 << 3),
  SPRITE_STATIC       = (1 << 4),
  SPRITE_CHAR_CHECK2  = (1 << 5),
  SPRITE_VLAYER       = (1 << 6),
};

void plot_sprite(struct world *mzx_world, struct sprite *cur_sprite, int color,
 int x, int y);
void draw_sprites(struct world *mzx_world);
int sprite_at_xy(struct sprite *cur_sprite, int x, int y);
int sprite_colliding_xy(struct world *mzx_world, struct sprite *check_sprite,
 int x, int y);

__M_END_DECLS

#endif // __SPRITE_H


code:
/* MegaZeux
 *
 * Copyright (C) 2002 Gilead Kutnick <exophase@adelphia.net>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __SPRITE_STRUCT_H
#define __SPRITE_STRUCT_H

#include "compat.h"

__M_BEGIN_DECLS

#define MAX_SPRITES         256

struct sprite
{
  int x;
  int y;
  int ref_x;
  int ref_y;
  char color;
  char flags;
  char width;
  char height;
  signed char col_x;
  signed char col_y;
  char col_width;
  char col_height;
};

struct collision_list
{
  int num;
  int collisions[MAX_SPRITES];
};

__M_END_DECLS

#endif // __SPRITE_STRUCT_H


code:
/* MegaZeux
 *
 * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
#include "updater.h"
#include "const.h"
#include "event.h"
#include "util.h"
#include "game.h"
#include "graphics.h"
#include "window.h"
#include "error.h"

#include "editor/window.h"

#include "network/manifest.h"

#include <sys/types.h>
#include <sys/stat.h>

#include <assert.h>
#include <string.h>
#include <errno.h>

#ifndef _MSC_VER
#include <unistd.h>
#endif

#ifndef PLATFORM
#error Must define a valid "friendly" platform name!
#endif

#define MAX_RETRIES   3

#define OUTBOUND_PORT 80
#define LINE_BUF_LEN  256
#define BLOCK_SIZE    4096

#define UPDATES_TXT   "updates.txt"
#define DELETE_TXT    "delete.txt"

#define WIDGET_BUF_LEN 80

static struct manifest_entry *delete_list, *delete_p;

static char widget_buf[WIDGET_BUF_LEN];

static char previous_dir[MAX_PATH];

static long final_size = -1;
static bool cancel_update;

static char **process_argv;

static bool check_prune_basedir(const char *file)
{
  static char path[MAX_PATH];
  ssize_t ret;

  ret = get_path(file, path, MAX_PATH);
  if(ret < 0)
  {
    error("Failed to prune directories (path too long)", 1, 8, 0);
    return false;
  }

  // This file has no base directory
  if(ret == 0)
    return true;

  // At the head of the recursion we remove the directory
  rmdir(path);

  // Recursion; remove any parent directory
  return check_prune_basedir(path);
}

/* FIXME: The allocation of MAX_PATH on the stack in a recursive
 *        function WILL cause problems, eventually!
 */
static bool check_create_basedir(const char *file)
{
  static struct stat s;
  char path[MAX_PATH];
  ssize_t ret;

  ret = get_path(file, path, MAX_PATH);
  if(ret < 0)
  {
    error("Failed to create directories (path too long)", 1, 8, 0);
    return false;
  }

  // This file has no base directory
  if(ret == 0)
    return true;

  if(stat(path, &s) < 0)
  {
    // Every other kind of error is fatal
    if(errno != ENOENT)
    {
      error("Unknown stat() error occurred", 1, 8, 0);
      return false;
    }

    // Recursion; create any parent directory
    if(!check_create_basedir(path))
      return false;

    // At the tail of the recursion we create the directory
    mkdir(path, 0777);
    return true;
  }

  if(!S_ISDIR(s.st_mode))
  {
    snprintf(widget_buf, WIDGET_BUF_LEN,
     "File \"%s\" prevents creation of directory by same name", path);
    widget_buf[WIDGET_BUF_LEN - 1] = 0;
    error(widget_buf, 1, 8, 0);
    return false;
  }

  return true;
}

static void check_cancel_update(void)
{
  update_event_status();
  if(get_key(keycode_internal) == IKEY_ESCAPE)
    cancel_update = true;
}

static void recv_cb(long offset)
{
  check_cancel_update();

  if(final_size > 0 && offset > final_size)
  {
    error("Transferred more than expected uncompressed size.", 1, 8, 0);
    cancel_update = true;
    return;
  }

  meter_interior(offset, final_size);
  update_screen();
}

static bool cancel_cb(void)
{
  return cancel_update;
}

static void delete_hook(const char *file)
{
  struct manifest_entry *new_entry;
  struct SHA256_ctx ctx;
  bool ret;
  FILE *f;

  new_entry = ccalloc(1, sizeof(struct manifest_entry));
  if(!new_entry)
    goto err_out;

  if(delete_p)
  {
    delete_p->next = new_entry;
    delete_p = delete_p->next;
  }
  else
    delete_list = delete_p = new_entry;

  delete_p->name = cmalloc(strlen(file) + 1);
  if(!delete_p->name)
    goto err_delete_p;
  strcpy(delete_p->name, file);

  f = fopen_unsafe(file, "rb");
  if(!f)
    goto err_delete_p_name;
  delete_p->size = (unsigned long)ftell_and_rewind(f);

  ret = manifest_compute_sha256(&ctx, f, delete_p->size);
  fclose(f);
  if(!ret)
    goto err_delete_p_name;

  memcpy(delete_p->sha256, ctx.H, sizeof(Uint32) * 8);
  return;

err_delete_p_name:
  free(delete_p->name);
err_delete_p:
  free(delete_p);
  delete_p = NULL;
err_out:
  return;
}

static bool swivel_current_dir(bool have_video)
{
  bool ret = false;
  char *base_path;
  int g_ret;

  // Store the user's current directory, so we can get back to it
  getcwd(previous_dir, MAX_PATH);

  base_path = cmalloc(MAX_PATH);

  // Find and change into the base path for this MZX binary
  g_ret = get_path(process_argv[0], base_path, MAX_PATH);
  if(g_ret <= 0)
    goto err_free_base_path;

  if(chdir(base_path))
  {
    if(have_video)
      error("Failed to change into install directory.", 1, 8, 0);
    else
      warn("Failed to change into install directory.\n");
    goto err_free_base_path;
  }

  ret = true;
err_free_base_path:
  free(base_path);
  return ret;
}

static bool swivel_current_dir_back(bool have_video)
{
  if(chdir(previous_dir))
  {
    if(have_video)
      error("Failed to change back to user directory.", 1, 8, 0);
    else
      warn("Failed to change back to user directory.\n");
    return false;
  }

  return true;
}

static bool backup_original_manifest(void)
{
  unsigned int len, pos = 0;
  char block[BLOCK_SIZE];
  FILE *input, *output;
  bool ret = false;
  struct stat s;

  // No existing manifest; this is non-fatal
  if(stat(MANIFEST_TXT, &s))
  {
    ret = true;
    goto err_out;
  }

  input = fopen_unsafe(MANIFEST_TXT, "rb");
  if(!input)
    goto err_out;

  output = fopen_unsafe(MANIFEST_TXT "~", "wb");
  if(!output)
    goto err_close_input;

  len = (unsigned int)ftell_and_rewind(input);

  while(pos < len)
  {
    unsigned int block_size = MIN(BLOCK_SIZE, len - pos);

    if(fread(block, block_size, 1, input) != 1)
      goto err_close_output;
    if(fwrite(block, block_size, 1, output) != 1)
      goto err_close_output;

    pos += block_size;
  }

  ret = true;
err_close_output:
  fclose(output);
err_close_input:
  fclose(input);
err_out:
  return ret;
}

static bool restore_original_manifest(bool ret)
{
  struct stat s;

  if(ret)
  {
    /* The update was successful, so we try to remove the backup manifest.
     * Ignore any errors that might occur as a result of this file not
     * existing (it won't if we never backed a manifest up).
     */
    unlink(MANIFEST_TXT "~");
    return true;
  }

  // Try to remove original manifest before restoration
  if(unlink(MANIFEST_TXT))
  {
    error("Failed to remove " MANIFEST_TXT ". Check permissions.", 1, 8, 0);
    return false;
  }

  // No manifest to restore; consider this successful
  if(stat(MANIFEST_TXT "~", &s))
    return true;

  // Try to restore backup manifest
  if(rename(MANIFEST_TXT "~", MANIFEST_TXT))
  {
    error("Failed to roll back manifest. Check permissions.", 1, 8, 0);
    return false;
  }

  return true;
}

static bool reissue_connection(struct config_info *conf, struct host **h, char *host_name)
{
  bool ret = false;
  int buf_len;

  assert(h != NULL);

  /* We might be passed an existing socket. If we have been, close
   * and destroy the associated host, and create a new one.
   */
  if(*h)
    host_destroy(*h);

  *h = host_create(HOST_TYPE_TCP, HOST_FAMILY_IPV4);
  if(!*h)
  {
    error("Failed to create TCP client socket.", 1, 8, 0);
    goto err_out;
  }

  m_hide();

  buf_len = snprintf(widget_buf, WIDGET_BUF_LEN,
   "Connecting to \"%s\". Please wait..", host_name);
  widget_buf[WIDGET_BUF_LEN - 1] = 0;

  draw_window_box(3, 11, 76, 13, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);
  write_string(widget_buf, (WIDGET_BUF_LEN - buf_len) >> 1, 12, DI_TEXT, 0);
  update_screen();

  if(!host_connect(*h, host_name, OUTBOUND_PORT))
  {
    buf_len = snprintf(widget_buf, WIDGET_BUF_LEN,
     "Connection to \"%s\" failed.", host_name);
    widget_buf[WIDGET_BUF_LEN - 1] = 0;
    error(widget_buf, 1, 8, 0);
  }
  else
    ret = true;

  clear_screen(32, 7);
  m_show();
  update_screen();

err_out:
  return ret;
}

static void __check_for_updates(struct config_info *conf)
{
  char **list_entries, buffer[LINE_BUF_LEN], *url_base, *value;
  struct manifest_entry *removed, *replaced, *added, *e;
  int i = 0, entries = 0, buf_len, result;
  char update_branch[LINE_BUF_LEN];
  const char *version = VERSION;
  int list_entry_width = 0;
  enum host_status status;
  struct host *h = NULL;
  unsigned int retries;
  bool ret = false;
  FILE *f;

  int cur_host;
  char *update_host;
  bool try_next_host = true;

  if(conf->update_host_count < 1)
  {
    error("No updater hosts defined! Aborting.", 1, 8, 0);
    goto err_out;
  }

  if(!swivel_current_dir(true))
    goto err_out;

  // Acid test: Can we write to this directory?
  f = fopen_unsafe(UPDATES_TXT, "w+b");
  if(!f)
  {
    error("Failed to create \"" UPDATES_TXT "\". Check permissions.", 1, 8, 0);
    goto err_chdir;
  }

  for(cur_host = 0; (cur_host < conf->update_host_count) && try_next_host; cur_host++)
  {
    update_host = conf->update_hosts[cur_host];

    if(!reissue_connection(conf, &h, update_host))
      goto err_host_destroy;

    for(retries = 0; retries < MAX_RETRIES; retries++)
    {
      // Grab the file containing the names of the current Stable and Unstable
      status = host_recv_file(h, "/" UPDATES_TXT, f, "text/plain");
      rewind(f);

      if(status == HOST_SUCCESS)
        break;

      if(!reissue_connection(conf, &h, update_host))
        goto err_host_destroy;
    }

    if(retries == MAX_RETRIES)
    {
      snprintf(widget_buf, WIDGET_BUF_LEN, "Failed to download \""
       UPDATES_TXT "\" (err=%d).\n", status);
      widget_buf[WIDGET_BUF_LEN - 1] = 0;
      error(widget_buf, 1, 8, 0);
      goto err_host_destroy;
    }

    snprintf(update_branch, LINE_BUF_LEN, "Current-%s",
     conf->update_branch_pin);

    // Walk this list (of two, hopefully)
    while(true)
    {
      char *m = buffer, *key;
      value = NULL;

      // Grab a single line from the manifest
      if(!fgets(buffer, LINE_BUF_LEN, f))
        break;

      key = strsep(&m, ":\n");
      if(!key)
        break;

      value = strsep(&m, ":\n");
      if(!value)
        break;

      if(strcmp(key, update_branch) == 0)
        break;
    }

    fclose(f);
    unlink(UPDATES_TXT);

    /* There was no "Current-XXX: Version" found; we cannot proceed with the
     * update because we cannot compute an update URL below.
     */
    if(!value)
    {
      error("Failed to identify applicable update version.", 1, 8, 0);
      goto err_host_destroy;
    }

    /* There's likely to be a space prepended to the version number.
     * Skip it here.
     */
    if(value[0] == ' ')
      value++;

    /* We found the latest update version, but we should check to see if that
     * matches the version we're already using. The user may choose to receive
     * "stability" updates for their current major version, or upgrade to the
     * newest one.
     */
    if(strcmp(value, version) != 0)
    {
      struct element *elements[6];
      struct dialog di;

      buf_len = snprintf(widget_buf, WIDGET_BUF_LEN,
       "A new major version is available (%s)", value);
      widget_buf[WIDGET_BUF_LEN - 1] = 0;

      elements[0] = construct_label((55 - buf_len) >> 1, 2, widget_buf);

      elements[1] = construct_label(2, 4,
       "You can continue to receive updates for the version\n"
       "installed (if available), or you can upgrade to the\n"
       "newest major version (recommended).");

      elements[2] = construct_label(2, 8,
       "If you do not upgrade, this question will be asked\n"
       "again the next time you run the updater.\n");

      elements[3] = construct_button(9, 11, "Upgrade", 0);
      elements[4] = construct_button(21, 11, "Update Old", 1);
      elements[5] = construct_button(36, 11, "Cancel", 2);

      construct_dialog(&di, "New Major Version", 11, 6, 55, 14, elements, 6, 3);
      result = run_dialog(NULL, &di);
      destruct_dialog(&di);

      // User pressed Escape, abort all updates
      if(result < 0 || result == 2)
      {
        try_next_host = false;
        goto err_host_destroy;
      }

      // User pressed Upgrade, use new major
      if(result == 0)
        version = value;
    }

    /* We can now compute a unique URL base for the updater. This will
     * be composed of a user-selected version and a static platform-archicture
     * name.
     */
    url_base = cmalloc(LINE_BUF_LEN);
    snprintf(url_base, LINE_BUF_LEN, "/%s/" PLATFORM, version);
    debug("Update base URL: %s\n", url_base);

    /* The call to manifest_get_updates() destroys any existing manifest
     * file in this directory. Since we still allow user to abort after
     * this call, and downloading the updates may fail, we copy the
     * old manifest to a backup location and optionally restore it later.
     */
    if(!backup_original_manifest())
    {
      error("Failed to back up manifest. Check permissions.", 1, 8, 0);
      try_next_host = false;
      goto err_free_url_base;
    }

    for(retries = 0; retries < MAX_RETRIES; retries++)
    {
      bool m_ret;

      m_hide();

      draw_window_box(3, 11, 76, 13, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);
      write_string("Computing manifest deltas (added, replaced, deleted)..",
       13, 12, DI_TEXT, 0);
      update_screen();

      m_ret = manifest_get_updates(h, url_base, &removed, &replaced, &added);

      clear_screen(32, 7);
      m_show();
      update_screen();

      if(m_ret)
        break;

      if(!reissue_connection(conf, &h, update_host))
        goto err_roll_back_manifest;
    }

    if(retries == MAX_RETRIES)
    {
      error("Failed to compute update manifests", 1, 8, 0);
      goto err_roll_back_manifest;
    }

    if(!removed && !replaced && !added)
    {
      struct element *elements[3];
      struct dialog di;

      elements[0] = construct_label(2, 2, "This client is already current.");
      elements[1] = construct_button(7, 4, "OK", 0);
      elements[2] = construct_button(13, 4, "Try next host", 1);

      construct_dialog(&di, "No Updates", 22, 9, 35, 6, elements, 3, 1);
      result = run_dialog(NULL, &di);
      destruct_dialog(&di);

      if((result != 1) || (cur_host + 1 >= conf->update_host_count))
        try_next_host = false;

      goto err_free_update_manifests;
    }

    for(e = removed; e; e = e->next, entries++)
      list_entry_width = MAX(list_entry_width, 2 + (int)strlen(e->name)+1+1);
    for(e = replaced; e; e = e->next, entries++)
      list_entry_width = MAX(list_entry_width, 2 + (int)strlen(e->name)+1+1);
    for(e = added; e; e = e->next, entries++)
      list_entry_width = MAX(list_entry_width, 2 + (int)strlen(e->name)+1+1);

    // We don't want the listbox to be too wide
    list_entry_width = MIN(list_entry_width, 60);

    list_entries = cmalloc(entries * sizeof(char *));

    for(e = removed; e; e = e->next, i++)
    {
      list_entries[i] = cmalloc(list_entry_width);
      snprintf(list_entries[i], list_entry_width, "- %s", e->name);
      list_entries[i][list_entry_width - 1] = 0;
    }

    for(e = replaced; e; e = e->next, i++)
    {
      list_entries[i] = cmalloc(list_entry_width);
      snprintf(list_entries[i], list_entry_width, "* %s", e->name);
      list_entries[i][list_entry_width - 1] = 0;
    }

    for(e = added; e; e = e->next, i++)
    {
      list_entries[i] = cmalloc(list_entry_width);
      snprintf(list_entries[i], list_entry_width, "+ %s", e->name);
      list_entries[i][list_entry_width - 1] = 0;
    }

    draw_window_box(19, 1, 59, 4, DI_MAIN, DI_DARK, DI_CORNER, 1, 1);
    write_string(" Task Summary ", 33, 1, DI_TITLE, 0);
    write_string("ESC   - Cancel   [+] Add   [-] Delete", 21, 2, DI_TEXT, 0);
    write_string("ENTER - Proceed  [*] Replace  ", 21, 3, DI_TEXT, 0);

    result = list_menu((const char **)list_entries, list_entry_width,
     NULL, 0, entries, ((80 - (list_entry_width + 9)) >> 1) + 1, 4);

    for(i = 0; i < entries; i++)
      free(list_entries[i]);
    free(list_entries);

    clear_screen(32, 7);
    update_screen();

    if(result < 0)
      goto err_free_update_manifests;

    /* Defer deletions until we restart; any of these files may still be
     * in use by this (old) process. Reduce the number of entries by the
     * number of removed items for the progress meter below.
     */
    for(e = removed; e; e = e->next, entries--)
      delete_hook(e->name);

    /* Since the operations for adding and replacing a file are identical,
     * we modify the replaced list and tack on the added list to the end.
     *
     * Either list may be NULL; in the case that `replaced' is NULL, simply
     * re-assign the `added' pointer. `added' being NULL has no effect.
     *
     * Later, we need only free the replaced list (see below).
     */
    if(replaced)
    {
      for(e = replaced; e->next; e = e->next)
        ;
      e->next = added;
    }
    else
      replaced = added;

    cancel_update = false;
    host_set_callbacks(h, NULL, recv_cb, cancel_cb);

    i = 1;
    for(e = replaced; e; e = e->next, i++)
    {
      for(retries = 0; retries < MAX_RETRIES; retries++)
      {
        char name[72];
        bool m_ret;

        if(!check_create_basedir(e->name))
          goto err_free_delete_list;

        final_size = (long)e->size;

        m_hide();
        snprintf(name, 72, "%s (%ldb) [%u/%u]", e->name, final_size, i, entries);
        meter(name, 0, final_size);
        update_screen();

        m_ret = manifest_entry_download_replace(h, url_base, e, delete_hook);

        clear_screen(32, 7);
        m_show();
        update_screen();

        if(m_ret)
          break;

        if(cancel_update)
        {
          error("Download was cancelled; update aborted.", 1, 8, 0);
          try_next_host = false;
          goto err_free_delete_list;
        }

        if(!reissue_connection(conf, &h, update_host))
          goto err_free_delete_list;
        host_set_callbacks(h, NULL, recv_cb, cancel_cb);
      }

      if(retries == MAX_RETRIES)
      {
        snprintf(widget_buf, WIDGET_BUF_LEN,
         "Failed to download \"%s\" (after %d attempts).", e->name, retries);
        widget_buf[WIDGET_BUF_LEN - 1] = 0;
        error(widget_buf, 1, 8, 0);
        goto err_free_delete_list;
      }
    }

    if(delete_list)
    {
      f = fopen_unsafe(DELETE_TXT, "wb");
      if(!f)
      {
        error("Failed to create \"" DELETE_TXT "\". Check permissions.", 1, 8, 0);
        try_next_host = false;
        goto err_free_delete_list;
      }

      for(e = delete_list; e; e = e->next)
      {
        fprintf(f, "%08x%08x%08x%08x%08x%08x%08x%08x %lu %s\n",
         e->sha256[0], e->sha256[1], e->sha256[2], e->sha256[3],
         e->sha256[4], e->sha256[5], e->sha256[6], e->sha256[7],
         e->size, e->name);
      }

      fclose(f);
    }

    try_next_host = false;
    ret = true;
err_free_delete_list:
    manifest_list_free(&delete_list);
    delete_list = delete_p = NULL;
err_free_update_manifests:
    manifest_list_free(&removed);
    manifest_list_free(&replaced);
err_roll_back_manifest:
    restore_original_manifest(ret);
err_free_url_base:
    free(url_base);
err_host_destroy:
    host_destroy(h);
    h = NULL;
  } //end host for loop

err_chdir:
  swivel_current_dir_back(true);
err_out:

  /* At this point we found updates and we successfully updated
   * to them. Reload the program with the original argv.
   */
  if(ret)
  {
    const void *argv = process_argv;
    struct element *elements[2];
    struct dialog di;

    elements[0] = construct_label(2, 2,
     "This client will now attempt to restart itself.");
    elements[1] = construct_button(23, 4, "OK", 0);

    construct_dialog(&di, "Update Successful", 14, 9, 51, 6, elements, 2, 1);
    run_dialog(NULL, &di);
    destruct_dialog(&di);

    execv(process_argv[0], argv);
    perror("execv");

    error("Attempt to invoke self failed!", 1, 8, 0);
    return;
  }
}

bool updater_init(char *argv[])
{
  struct manifest_entry *e;
  bool ret;
  FILE *f;

  process_argv = argv;

  if(!swivel_current_dir(false))
    return false;

  check_for_updates = __check_for_updates;

  f = fopen_unsafe(DELETE_TXT, "rb");
  if(!f)
    goto err_swivel_back;

  delete_list = manifest_list_create(f);
  fclose(f);

  for(e = delete_list; e; e = e->next)
  {
    f = fopen_unsafe(e->name, "rb");
    if(!f)
      continue;

    ret = manifest_entry_check_validity(e, f);
    fclose(f);

    if(!ret)
      continue;

    if(unlink(e->name))
      continue;

    /* Obtain the path for this file. If the file isn't at the top
     * level, and the directory is empty (rmdir ensures this)
     * the directory will be pruned.
     */
    check_prune_basedir(e->name);
  }

  manifest_list_free(&delete_list);
  unlink(DELETE_TXT);

err_swivel_back:
  swivel_current_dir_back(false);
  return true;
}


code:
/* MegaZeux
 *
 * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef __UPDATER_H
#define __UPDATER_H

#include "compat.h"

__M_BEGIN_DECLS

bool updater_init(char *argv[]);

__M_END_DECLS

#endif // __UPDATER_H


code:
/* MegaZeux
 *
 * Copyright (C) 2004 Gilead Kutnick <exophase@adelphia.net>
 * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>
 * Copyright (C) 2012 Alice Lauren Rowan <petrifiedrowan@gmail.com>
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */
#include <sys/stat.h>
#include <ctype.h>
#include <time.h>

// Must include after <time.h> (MSVC bug)
#include "util.h"

#ifndef _MSC_VER
#include <unistd.h>
#endif

#include <stdlib.h>
#include <string.h>

#include "const.h" // for MAX_PATH
#include "error.h"

struct mzx_resource
{
  const char *const base_name;
  char *path;

  /* So mzxrun requires fewer files even in CONFIG_EDITOR=1 build */
  bool optional;
};

/* Using C99 initializers would be nicer here, but MSVC doesn't support
 * them and we try to allow compilation with that compiler.
 *
 * As a result, these must be in exactly the same order as the
 * enum resource_id enumeration defines them.
 */
static struct mzx_resource mzx_res[] = {
#define ASSETS "assets/"
  { CONFFILE,                      NULL, false },
  { ASSETS "default.chr",          NULL, false },
  { ASSETS "edit.chr",             NULL, false },
  { ASSETS "smzx.pal",             NULL, false },
#ifdef CONFIG_EDITOR
  { ASSETS "ascii.chr",            NULL, true },
  { ASSETS "blank.chr",            NULL, true },
  { ASSETS "smzx.chr",             NULL, true },
#endif
#ifdef CONFIG_HELPSYS
  { ASSETS "help.fil",             NULL, true },
#endif
#ifdef CONFIG_RENDER_GL_PROGRAM
#define SHADERS ASSETS "shaders/"
  { SHADERS "scaler.vert",         NULL, false },
  { SHADERS "scaler.frag",         NULL, false },
  { SHADERS "tilemap.vert",        NULL, false },
  { SHADERS "tilemap.frag",        NULL, false },
  { SHADERS "tilemap.smzx12.frag", NULL, false },
  { SHADERS "tilemap.smzx3.frag",  NULL, false },
  { SHADERS "mouse.vert",          NULL, false },
  { SHADERS "mouse.frag",          NULL, false },
  { SHADERS "cursor.vert",         NULL, false },
  { SHADERS "cursor.frag",         NULL, false },
#endif
};

#ifdef CONFIG_CHECK_ALLOC

static void out_of_memory_check(void *p, const char *file, int line)
{
  char msgbuf[128];
  if(!p)
  {
    snprintf(msgbuf, sizeof(msgbuf), "Out of memory in %s:%d", file, line);
    msgbuf[sizeof(msgbuf)-1] = '\0';
    error(msgbuf, 2, 4, 0);
  }
}

void *check_calloc(size_t nmemb, size_t size, const char *file, int line)
{
  void *result = calloc(nmemb, size);
  out_of_memory_check(result, file, line);
  return result;
}

void *check_malloc(size_t size, const char *file, int line)
{
  void *result = malloc(size);
  out_of_memory_check(result, file, line);
  return result;
}

void *check_realloc(void *ptr, size_t size, const char *file, int line)
{
  void *result = realloc(ptr, size);
  out_of_memory_check(result, file, line);
  return result;
}

#endif /* CONFIG_CHECK_ALLOC */

int mzx_res_init(const char *argv0, bool editor)
{
  size_t i, bin_path_len = 0;
  struct stat file_info;
  char *bin_path;
  ssize_t g_ret;
  char *p_dir;
  int ret = 0;

  bin_path = cmalloc(MAX_PATH);
  p_dir = cmalloc(MAX_PATH);

  g_ret = get_path(argv0, bin_path, MAX_PATH);
  if(g_ret > 0)
  {
    // move and convert to absolute path style
    chdir(bin_path);
    getcwd(bin_path, MAX_PATH);
    bin_path_len = strlen(bin_path);

    // append the trailing '/'
    bin_path[bin_path_len++] = '/';
    bin_path[bin_path_len] = 0;
  }
  else
  {
    free(bin_path);
    bin_path = NULL;
  }

  /* Always try to use SHAREDIR/CONFDIR first, if possible. On some
   * platforms, such as Linux, this will be something other than
   * the current working directory (i.e. '.'). If this fails, try
   * to look up the resources relative to the binary's install
   * location, which should allow the MegaZeux binary to be more
   * "portable".
   */
  for(i = 0; i < END_RESOURCE_ID_T; i++)
  {
    size_t p_dir_len, base_name_len = strlen(mzx_res[i].base_name);
    char *full_path;

    if(i == CONFIG_TXT)
      chdir(CONFDIR);
    else
      chdir(SHAREDIR);

    getcwd(p_dir, MAX_PATH);
    p_dir_len = strlen(p_dir);

    // since we can't add the path delimeter we should really fail hard
    if(p_dir_len >= MAX_PATH)
      continue;

    // append the trailing '/'
    p_dir[p_dir_len++] = '/';
    p_dir[p_dir_len] = 0;

    full_path = cmalloc(p_dir_len + base_name_len + 1);
    memcpy(full_path, p_dir, p_dir_len);
    memcpy(full_path + p_dir_len, mzx_res[i].base_name, base_name_len);
    full_path[p_dir_len + base_name_len] = 0;

    // Attempt to load it from this new path
    if(!stat(full_path, &file_info))
    {
      mzx_res[i].path = full_path;
      continue;
    }

    free(full_path);

    // Try to locate the resource relative to the bin_path
    if(bin_path)
    {
      chdir(bin_path);
      if(!stat(mzx_res[i].base_name, &file_info))
      {
        mzx_res[i].path = cmalloc(bin_path_len + base_name_len + 1);
        memcpy(mzx_res[i].path, bin_path, bin_path_len);
        memcpy(mzx_res[i].path + bin_path_len,
         mzx_res[i].base_name, base_name_len);
        mzx_res[i].path[bin_path_len + base_name_len] = 0;
      }
    }
  }

  for(i = 0; i < END_RESOURCE_ID_T; i++)
  {
    /* Skip non-essential resources */
    if(!editor && mzx_res[i].optional)
      continue;

    if(!mzx_res[i].path)
    {
      warn("Failed to locate critical resource '%s'.\n",
       mzx_res[i].base_name);
      ret = 1;
    }
  }

  free(p_dir);
  free(bin_path);

  return ret;
}

void mzx_res_free(void)
{
  int i;
  for(i = 0; i < END_RESOURCE_ID_T; i++)
    if(mzx_res[i].path)
      free(mzx_res[i].path);
}

char *mzx_res_get_by_id(enum resource_id id)
{
  return mzx_res[id].path;
}

// Determine file size of an open FILE and rewind it

long ftell_and_rewind(FILE *f)
{
  long size;

  fseek(f, 0, SEEK_END);
  size = ftell(f);
  rewind(f);

  return size;
}

// Random function, returns an integer [0-range)

unsigned int Random(unsigned long long range)
{
  static unsigned long long seed = 0;
  unsigned long long value;

  // If the seed is 0, initialise it with time and clock
  if(seed == 0)
    seed = time(NULL) + clock();

  seed = seed * 1664525 + 1013904223;

  value = (seed & 0xFFFFFFFF) * range / 0xFFFFFFFF;
  return (unsigned int)value;
}

__utils_maybe_static ssize_t __get_path(const char *file_name, char *dest,
 unsigned int buf_len)
{
  ssize_t c = (ssize_t)strlen(file_name) - 1;

  // no path, or it's too long to store
  if(c == -1 || c > (int)buf_len)
  {
    if(buf_len > 0)
      dest[0] = 0;
    return -1;
  }

  while((file_name[c] != '/') && (file_name[c] != '\\') && c)
    c--;

  if(c > 0)
    memcpy(dest, file_name, c);
  dest[c] = 0;

  return c;
}

ssize_t get_path(const char *file_name, char *dest, unsigned int buf_len)
{
  return __get_path(file_name, dest, buf_len);
}

void split_path_filename(const char *source,
 char *destpath, unsigned int dest_buffer_len,
 char *destfile, unsigned int file_buffer_len)
{
  char temppath[MAX_PATH];
  struct stat path_info;
  int stat_res = stat(source, &path_info);

  // If the entirety of source is a directory
  if((stat_res >= 0) && S_ISDIR(path_info.st_mode))
  {
    if(dest_buffer_len)
      strncpy(destpath, source, dest_buffer_len);

    if(file_buffer_len)
      strcpy(destfile, "");
  }
  else
  // If source has a directory and a file
  if((source[0] != '\0') && get_path(source, temppath, MAX_PATH))
  {
    // get_path leaves off trailing /, add 1 to offset.
    if(dest_buffer_len)
      strncpy(destpath, temppath, dest_buffer_len);

    if(file_buffer_len)
      strncpy(destfile, &(source[strlen(temppath) + 1]), file_buffer_len);
  }
  // Source is just a file or blank.
  else
  {
    if(dest_buffer_len)
      strcpy(destpath, "");

    if(file_buffer_len)
      strncpy(destfile, source, file_buffer_len);
  }
}

int create_path_if_not_exists(const char *filename)
{
  struct stat stat_info;
  char parent_directory[MAX_PATH];

  if(!get_path(filename, parent_directory, MAX_PATH))
    return 1;

  if(!stat(parent_directory, &stat_info))
    return 2;

  create_path_if_not_exists(parent_directory);

  if(mkdir(parent_directory, 0755))
    return 3;

  return 0;
}

static void clean_path_slashes(const char *source, char *dest, int buf_size)
{
  unsigned int i;
  int p;

  for(i = 0, p = 0;
   (i < strlen(source)) && (source[i] != 0) && (p < buf_size-1);
   i++, p++)
  {
    dest[p] = source[i];
    while((dest[p] == DIR_SEPARATOR_CHAR) &&
     (source[i + 1] == DIR_SEPARATOR_CHAR) &&
     (source[i + 1] != 0))
      i++;
  }
  dest[p] = '\0';

  if((p >= 2) && (dest[p-1] == DIR_SEPARATOR_CHAR) && (dest[p-2] != ':'))
    dest[p-1] = '\0';

}

// Navigate a path name.
int change_dir_name(char *path_name, const char *dest, int buf_size)
{
  char path[buf_size];
  char *colon_loc = strchr(dest, ':');
  struct stat stat_info;
  int size;

  if(!dest || !dest[0])
    return -2;

  if(!path_name)
    return -1;

  // Stop!  recursion time
  if(strchr(dest, DIR_SEPARATOR_CHAR) || colon_loc)
  {
    char dir_element[buf_size];
    unsigned int i, dir_start = 0;

    strncpy(path, dest, buf_size-1);
    path[buf_size-1] = 0;

    if(path[strlen(path) - 1] == ':')
      strcat(path, DIR_SEPARATOR);

    if(stat(path, &stat_info) < 0)
      return -20;

    for(i = 0; i <= strlen(path); i++)
    {
      if((path[i] == DIR_SEPARATOR_CHAR) || (i == strlen(path)))
      {
        // Root directory.  Remember colon_loc is relative to dest, not path
        if((i == 0) || (colon_loc && (size_t)(colon_loc - dest) < i))
        {
          strncpy(path_name, path, i+1);
          path_name[i + 1] = '\0';
        }
        else if (i != dir_start)
        {
          int res;
          strncpy(dir_element, path + dir_start, i-dir_start);
          dir_element[i - dir_start] = '\0';
          res = change_dir_name(path_name, dir_element, buf_size);
          if(res)
            return res;
        }

        dir_start = i + 1;
      }
    }

    return 0;
  }

  // Check for the special . and ..
  if(dest[0] == '.')
  {
    if((dest[1] == '.') && (dest[2] == '\0'))
    {
      size = get_path(path_name, path, buf_size);

      // Fix ..ing to root paths
      if(path[strlen(path) - 1] == ':')
        strcat(path, DIR_SEPARATOR);

      if(size > 0)
      {
        strcpy(path_name, path);
        return 0;
      }
      else

      //One step above root on unix-based systems
      if(path_name[0] == '/')
      {
        strcpy(path_name, "/");
        return 0;
      }

    }
    else if (dest[1] == '\0')
      return 0;
  }

  snprintf(path, buf_size, "%s%s%s",
   path_name, DIR_SEPARATOR, dest);
  path[buf_size - 1] = 0;

  if(stat(path, &stat_info) >= 0)
  {
    clean_path_slashes(path, path_name, buf_size - 1);
    path_name[buf_size - 1] = 0;
    return 0;
  }

  return -3;
}

#if defined(__WIN32__) && defined(__STRICT_ANSI__)

/* On WIN32 with C99 defining __STRICT_ANSI__ these POSIX.1-2001 functions
 * are not available. The stricmp/strnicmp functions are available, but not
 * to C99 programs.
 *
 * Redefine them here; these are copied from glibc, and should be optimal.
 */

int strcasecmp(const char *s1, const char *s2)
{
  while(*s1 != '\0' && tolower(*s1) == tolower(*s2))
  {
    s1++;
    s2++;
  }

  return tolower(*(unsigned char *)s1) - tolower(*(unsigned char *)s2);
}

int strncasecmp(const char *s1, const char *s2, size_t n)
{
  if(n == 0)
    return 0;

  while(n-- != 0 && tolower(*s1) == tolower(*s2))
  {
    if(n == 0 || *s1 == '\0' || *s2 == '\0')
      break;
    s1++;
    s2++;
  }

  return tolower(*(unsigned char *)s1) - tolower(*(unsigned char *)s2);
}

#endif // __WIN32__ && __STRICT_ANSI__

#if defined(__WIN32__) || defined(__amigaos__)

// strsep() stolen from glibc (GPL)

char *strsep(char **stringp, const char *delim)
{
  char *begin, *end;

  begin = *stringp;
  if(!begin)
    return NULL;

  if(delim[0] == '\0' || delim[1] == '\0')
  {
    char ch = delim[0];
    if(ch == '\0')
      end = NULL;
    else
    {
      if(*begin == ch)
        end = begin;
      else if(*begin == '\0')
        end = NULL;
      else
        end = strchr(begin + 1, ch);
    }
  }
  else
    end = strpbrk(begin, delim);

  if(end)
  {
    *end++ = '\0';
    *stringp = end;
  }
  else
    *stringp = NULL;

  return begin;
}

#endif // __WIN32__ || __amigaos__

long dir_tell(struct mzx_dir *dir)
{
  return dir->pos;
}

#if defined(CONFIG_NDS) || defined(CONFIG_WII)

// NDS/Wii versions of these functions backend directly to libfat

bool dir_open(struct mzx_dir *dir, const char *path)
{
  char entry[PATH_BUF_LEN];

  dir->d = diropen(path);
  if(!dir->d)
    return false;

  dir->entries = 0;
  while(dirnext(dir->d, entry, NULL) == 0)
    dir->entries++;

  dirreset(dir->d);
  dir->pos = 0;
  return true;
}

void dir_close(struct mzx_dir *dir)
{
  if(dir->d)
  {
    dirclose(dir->d);
    dir->d = NULL;
    dir->entries = 0;
    dir->pos = 0;
  }
}

void dir_seek(struct mzx_dir *dir, long offset)
{
  char entry[PATH_BUF_LEN];
  long i;

  if(!dir->d)
    return;

  dir->pos = CLAMP(offset, 0L, dir->entries);

  dirreset(dir->d);
  for(i = 0; i < dir->pos; i++)
    dirnext(dir->d, entry, NULL);
}

bool dir_get_next_entry(struct mzx_dir *dir, char *entry)
{
  if(!dir->d)
    return false;
  dir->pos = MIN(dir->pos + 1, dir->entries);
  return dirnext(dir->d, entry, NULL) == 0;
}

#else // !(CONFIG_NDS || CONFIG_WII)

bool dir_open(struct mzx_dir *dir, const char *path)
{
  dir->d = opendir(path);
  if(!dir->d)
    return false;

  dir->entries = 0;
  while(readdir(dir->d) != NULL)
    dir->entries++;

#ifdef CONFIG_PSP
  strncpy(dir->path, path, PATH_BUF_LEN);
  dir->path[PATH_BUF_LEN - 1] = 0;
  closedir(dir->d);
  dir->d = opendir(path);
#else
  rewinddir(dir->d);
#endif

  dir->pos = 0;
  return true;
}

void dir_close(struct mzx_dir *dir)
{
  if(dir->d)
  {
    closedir(dir->d);
    dir->d = NULL;
    dir->entries = 0;
    dir->pos = 0;
  }
}

void dir_seek(struct mzx_dir *dir, long offset)
{
  long i;

  if(!dir->d)
    return;

  dir->pos = CLAMP(offset, 0L, dir->entries);

#ifdef CONFIG_PSP
  closedir(dir->d);
  dir->d = opendir(dir->path);
  if(!dir->d)
    return;
#else
  rewinddir(dir->d);
#endif

  for(i = 0; i < dir->pos; i++)
    readdir(dir->d);
}

bool dir_get_next_entry(struct mzx_dir *dir, char *entry)
{
  struct dirent *inode;

  if(!dir->d)
    return false;

  dir->pos = MIN(dir->pos + 1, dir->entries);

  inode = readdir(dir->d);
  if(!inode)
  {
    entry[0] = 0;
    return false;
  }

  strncpy(entry, inode->d_name, PATH_BUF_LEN - 1);
  entry[PATH_BUF_LEN - 1] = 0;
  return true;
}

#endif // CONFIG_NDS || CONFIG_WII

#if defined(CONFIG_AUDIO) || defined(CONFIG_EDITOR)

/* It would be nice if this could be static, but we can't put it in either
 * edit.c or audio.c, because one (but NOT the other) might be disabled.
 *
 * In this case, the code wouldn't be compiled (noticed by the PSP port).
 */
const char *const mod_gdm_ext[] =
{
  ".xm", ".s3m", ".mod", ".med", ".mtm", ".stm", ".it", ".669", ".ult",
  ".wav", ".dsm", ".far", ".okt", ".amf", ".ogg", ".gdm", NULL
};

#endif // CONFIG_AUDIO || CONFIG_EDITOR

#if defined(__amigaos__)

long __stack_chk_guard[8];

void __stack_chk_fail(void)
{
  warn("Stack overflow detected; terminated");
  exit(0);
}

#endif // __amigaos__