code:
/* MegaZeux
 *
 * Copyright (C) 2008 Alistair John Strachan <alistair@devzero.co.uk>
 * 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
 */

// Useful, generic utility functions

#ifndef __UTIL_H
#define __UTIL_H

#include <stdio.h>
#include "compat.h"

__M_BEGIN_DECLS

#if !defined(_MSC_VER) && !defined(__amigaos__)
#include <unistd.h>
#endif

#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))

#define CLAMP(x, low, high) \
  (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))

#ifndef DIR_SEPARATOR
#ifdef __WIN32__
#define DIR_SEPARATOR "\\"
#define DIR_SEPARATOR_CHAR '\\'
#else //!__WIN32__
#define DIR_SEPARATOR "/"
#define DIR_SEPARATOR_CHAR '/'
#endif
#endif //DIR_SEPARATOR

enum resource_id
{
  CONFIG_TXT = 0,
  MZX_DEFAULT_CHR,
  MZX_EDIT_CHR,
  SMZX_PAL,
#ifdef CONFIG_EDITOR
  MZX_ASCII_CHR,
  MZX_BLANK_CHR,
  MZX_SMZX_CHR,
#endif
#ifdef CONFIG_HELPSYS
  MZX_HELP_FIL,
#endif
#ifdef CONFIG_RENDER_GL_PROGRAM
  SHADERS_SCALER_VERT,
  SHADERS_SCALER_FRAG,
  SHADERS_TILEMAP_VERT,
  SHADERS_TILEMAP_FRAG,
  SHADERS_TILEMAP_SMZX12_FRAG,
  SHADERS_TILEMAP_SMZX3_FRAG,
  SHADERS_MOUSE_VERT,
  SHADERS_MOUSE_FRAG,
  SHADERS_CURSOR_VERT,
  SHADERS_CURSOR_FRAG,
#endif
  END_RESOURCE_ID_T // must be last
};

CORE_LIBSPEC int mzx_res_init(const char *argv0, bool editor);
CORE_LIBSPEC void mzx_res_free(void);
CORE_LIBSPEC char *mzx_res_get_by_id(enum resource_id id);

CORE_LIBSPEC long ftell_and_rewind(FILE *f);
unsigned int Random(unsigned long long range);

CORE_LIBSPEC ssize_t get_path(const char *file_name, char *dest, unsigned int buf_len);
#ifdef CONFIG_UTILS
ssize_t __get_path(const char *file_name, char *dest, unsigned int buf_len);
#endif

CORE_LIBSPEC void split_path_filename(const char *source,
 char *destpath, unsigned int path_buffer_len,
 char *destfile, unsigned int file_buffer_len);

CORE_LIBSPEC int create_path_if_not_exists(const char *filename);

CORE_LIBSPEC int change_dir_name(char *path_name, const char *dest, int buf_size);

typedef void (*fn_ptr)(void);

struct dso_syms_map
{
  const char *name;
  fn_ptr *sym_ptr;
};

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

#include <sys/dir.h>

#define PATH_BUF_LEN FILENAME_MAX
typedef DIR_ITER dir_t;

#else // !(CONFIG_NDS || CONFIG_WII)

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

#define PATH_BUF_LEN MAX_PATH
typedef DIR dir_t;

#endif // CONFIG_NDS || CONFIG_WII

struct mzx_dir {
#ifdef CONFIG_PSP
  char path[PATH_BUF_LEN];
#endif
  dir_t *d;
  long entries;
  long pos;
};

bool dir_open(struct mzx_dir *dir, const char *path);
void dir_close(struct mzx_dir *dir);
void dir_seek(struct mzx_dir *dir, long offset);
long dir_tell(struct mzx_dir *dir);
bool dir_get_next_entry(struct mzx_dir *dir, char *entry);

#if defined(__WIN32__) && defined(__STRICT_ANSI__)
CORE_LIBSPEC int strcasecmp(const char *s1, const char *s2);
CORE_LIBSPEC int strncasecmp(const char *s1, const char *s2, size_t n);
#endif // __WIN32__ && __STRICT_ANSI__

#if defined(__WIN32__) || defined(__amigaos__)
CORE_LIBSPEC char *strsep(char **stringp, const char *delim);
#endif // __WIN32__ || __amigaos__

#ifndef __WIN32__
#if defined(CONFIG_PSP) || defined(CONFIG_GP2X) \
 || defined(CONFIG_NDS) || defined(CONFIG_WII)
#include <string.h>
#else
#include <strings.h>
#endif
#endif // !__WIN32__

#if defined(CONFIG_NDS) || defined(CONFIG_WII)
// FIXME: rmdir() needs implementing on NDS/Wii
#define rmdir(x)
#endif

#if defined(__WIN32__) && !defined(_MSC_VER)
#define mkdir(file,mode) mkdir(file)
#endif

#if defined(CONFIG_AUDIO) || defined(CONFIG_EDITOR)
CORE_LIBSPEC extern const char *const mod_gdm_ext[];
#endif

#if defined(__amigaos__)
CORE_LIBSPEC extern long __stack_chk_guard[8];
CORE_LIBSPEC void __stack_chk_fail(void);
#endif

#if defined(ANDROID)

#define info(...)  LOGI(__VA_ARGS__)
#define warn(...)  LOGW(__VA_ARGS__)

#ifdef DEBUG
#define debug(...) LOGD(__VA_ARGS__)
#else
#define debug(...) do { } while(0)
#endif

#elif defined(CONFIG_NDS) /* ANDROID */

// When the graphics have initialized, print to a debug buffer rather than the screen.
void info(const char *format, ...)  __attribute__((format(printf, 1, 2)));
void warn(const char *format, ...)  __attribute__((format(printf, 1, 2)));

#ifdef DEBUG
void debug(const char *format, ...) __attribute__((format(printf, 1, 2)));
#else
#define debug(...) do { } while(0)
#endif

#else /* ANDROID, CONFIG_NDS */

#define info(...) \
 do { \
   fprintf(stdout, "INFO: " __VA_ARGS__); \
   fflush(stdout); \
 } while(0)

#define warn(...) \
 do { \
   fprintf(stderr, "WARNING: " __VA_ARGS__); \
   fflush(stderr); \
 } while(0)

#ifdef DEBUG
#define debug(...) \
 do { \
   fprintf(stderr, "DEBUG: " __VA_ARGS__); \
   fflush(stderr); \
 } while(0)
#else
#define debug(...) do { } while(0)
#endif

#endif /* ANDROID, CONFIG_NDS */

__M_END_DECLS

#endif // __UTIL_H


code:
##
# MegaZeux Utils Makefile fragment
##

include contrib/unzip/src/Makefile.in

#
# FIXME: This whole file is a giant wart on the new build system
#        and needs some love (and refactoring) to fix it
#

.PHONY: utils utils.debug utils_clean

utils_src = src/utils
utils_obj = src/utils/.build

utils_cflags := ${LIBPNG_CFLAGS}

checkres := ${utils_src}/checkres${BINEXT}
checkres_objs := ${utils_obj}/checkres.o ${core_obj}/fsafeopen.o
checkres_objs += ${core_obj}/util.o ${unzip_objs}
checkres_ldflags := ${ZLIB_LDFLAGS}

downver := ${utils_src}/downver${BINEXT}
downver_objs := ${utils_obj}/downver.o

hlp2txt := ${utils_src}/hlp2txt${BINEXT}
hlp2txt_objs := ${utils_obj}/hlp2txt.o

txt2hlp := ${utils_src}/txt2hlp${BINEXT}
txt2hlp_objs := ${utils_obj}/txt2hlp.o

png2smzx := ${utils_src}/png2smzx${BINEXT}
png2smzx_objs := ${utils_obj}/png2smzx.o ${utils_obj}/smzxconv.o
png2smzx_objs += ${core_obj}/util.o ${core_obj}/pngops.o
png2smzx_ldflags := ${LIBPNG_LDFLAGS}

${utils_obj}/%.o: ${utils_src}/%.c
	$(if ${V},,@echo "  CC      " $<)
	${CC} -MD ${CFLAGS} ${utils_cflags} -I${core_src} \
	  -I${unzip_src} ${ZLIB_CFLAGS} -c $< -o $@

-include $(checkres_objs:.o=.d)
-include $(downver_objs:.o=.d)
-include $(hlp2txt_objs:.o=.d)
-include $(txt2hlp_objs:.o=.d)
-include $(png2smzx_objs:.o=.d)

${checkres}: ${checkres_objs}
	$(if ${V},,@echo "  LINK    " ${checkres})
	${CC} ${checkres_objs} -o ${checkres} \
	  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${checkres_ldflags}

${downver}: ${downver_objs}
	$(if ${V},,@echo "  LINK    " ${downver})
	${CC} ${downver_objs} -o ${downver} \
	  ${ARCH_EXE_LDFLAGS} ${LDFLAGS}

${hlp2txt}: ${hlp2txt_objs}
	$(if ${V},,@echo "  LINK    " ${hlp2txt})
	${CC} ${hlp2txt_objs} -o ${hlp2txt} \
	  ${ARCH_EXE_LDFLAGS} ${LDFLAGS}

${txt2hlp}: ${txt2hlp_objs}
	$(if ${V},,@echo "  LINK    " ${txt2hlp})
	${CC} ${txt2hlp_objs} -o ${txt2hlp} \
	  ${ARCH_EXE_LDFLAGS} ${LDFLAGS}

${png2smzx}: ${png2smzx_objs}
	$(if ${V},,@echo "  LINK    " ${png2smzx})
	${CC} ${png2smzx_objs} -o ${png2smzx} \
	  ${ARCH_EXE_LDFLAGS} ${LDFLAGS} ${png2smzx_ldflags}

utils: $(filter-out $(wildcard ${utils_obj}), ${utils_obj})

utils: ${checkres} ${downver} ${hlp2txt} ${txt2hlp}

ifeq (${LIBPNG},1)
utils: ${png2smzx}
endif

utils.debug: ${checkres}.debug ${downver}.debug ${hlp2txt}.debug
utils.debug: ${txt2hlp}.debug ${png2smzx}.debug

utils_clean: unzip_clean
	$(if ${V},,@echo "  RM      " ${utils_obj})
	${RM} -r ${utils_obj}
	$(if ${V},,@echo "  RM      " ${checkres} ${checkres}.debug)
	${RM} ${checkres} ${checkres}.debug
	$(if ${V},,@echo "  RM      " ${downver} ${downver}.debug)
	${RM} ${downver} ${downver}.debug
	$(if ${V},,@echo "  RM      " ${hlp2txt} ${hlp2txt}.debug)
	${RM} ${hlp2txt} ${hlp2txt}.debug
	$(if ${V},,@echo "  RM      " ${txt2hlp} ${txt2hlp}.debug)
	${RM} ${txt2hlp} ${txt2hlp}.debug
	$(if ${V},,@echo "  RM      " ${png2smzx} ${png2smzx}.debug)
	${RM} ${png2smzx} ${png2smzx}.debug

repo_name: pgimeno/megazeux

license: mit


code:
/**
 * Find external resources in a MZX world. World is parsed semantically,
 * so false positives are theoretically impossible.
 *
 * Copyright (C) 2007 Alistair John Strachan <alistair@devzero.co.uk>
 * Copyright (C) 2007 Josh Matthews <josh@joshmatthews.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
 */

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

#ifdef __WIN32__
#include <strings.h>
#endif

// From MZX itself
#include "../fsafeopen.h"
#include "../const.h"
#include "../util.h"

#include "unzip.h"

#define BOARD_NAME_SIZE 25

// "You may now have up to 250 boards." -- port.txt
#define MAX_BOARDS 250

// From sfx.h
#define NUM_SFX 50

#define WORLD_PROTECTED_OFFSET      BOARD_NAME_SIZE
#define WORLD_GLOBAL_OFFSET_OFFSET  4230

#define HASH_TABLE_SIZE 100

#define EOS EOF

enum stream_type
{
  FILE_STREAM,
  BUFFER_STREAM
};

struct stream
{
  enum stream_type type;

  union
  {
    FILE *fp;
    struct
    {
      unsigned long int index;
      unsigned char *buf;
      unsigned long int len;
      unzFile f;
    } buffer;
  } stream;
};

enum status
{
  SUCCESS = 0,
  INVALID_ARGUMENTS,
  CORRUPT_WORLD,
  FOPEN_FAILED,
  GET_PATH_FAILED,
  CHDIR_FAILED,
  FREAD_FAILED,
  FSEEK_FAILED,
  MALLOC_FAILED,
  PROTECTED_WORLD,
  MAGIC_CHECK_FAILED,
  UNZ_FAILED,
  NO_WORLD,
  MISSING_FILE
};

static struct board
{
  unsigned int size;
  unsigned int offset;
}
board_list[MAX_BOARDS];

// FIXME: Fix this better
int error(const char *string, unsigned int type, unsigned int options,
 unsigned int code)
{
  return 0;
}

static char **hash_table[HASH_TABLE_SIZE];

//From http://nothings.org/stb.h, by Sean
static unsigned int stb_hash(char *str)
{
  unsigned int hash = 0;
  while(*str)
    hash = (hash << 7) + (hash >> 25) + *str++;
  return hash + (hash >> 16);
}

static enum status add_to_hash_table(char *stack_str)
{
  unsigned int slot;
  int count = 0;
  size_t len;
  char *str;

  len = strlen(stack_str);
  if(!len)
    return SUCCESS;

  str = malloc(len + 1);
  if(!str)
    return MALLOC_FAILED;

  strncpy(str, stack_str, len);
  str[len] = '\0';

  slot = stb_hash(stack_str) % HASH_TABLE_SIZE;

  if(!hash_table[slot])
  {
    hash_table[slot] = malloc(sizeof(char *) * 2);
    if(!hash_table[slot])
      return MALLOC_FAILED;
  }
  else
  {
    while(hash_table[slot][count])
    {
      if(!strcasecmp(stack_str, hash_table[slot][count]))
      {
        free(str);
        return SUCCESS;
      }
      count++;
    }

    hash_table[slot] = realloc(hash_table[slot], sizeof(char *) * (count + 2));
  }

  hash_table[slot][count] = str;
  hash_table[slot][count + 1] = NULL;

  return SUCCESS;
}

static bool is_zip_file(const char *filename)
{
  int signature = 0;
  FILE *fp;

  fp = fopen_unsafe(filename, "rb");
  if(!fp)
    return false;

  signature |= fgetc(fp) << 0;
  signature |= fgetc(fp) << 8;
  signature |= fgetc(fp) << 16;
  signature |= fgetc(fp) << 24;
  fclose(fp);

  // Zip file header obtained from:
  //   http://www.pkware.com/documents/casestudies/APPNOTE.TXT
  return (strcasecmp(filename + strlen(filename) - 3, "zip") == 0 ||
          signature == 0x04034b50);
}

static enum status s_open(const char *filename, const char *mode,
 struct stream **s)
{
  unz_file_info info;
  enum status ret;
  unzFile f;

  *s = malloc(sizeof(struct stream));

  if(!is_zip_file(filename))
  {
    char path[MAX_PATH];
    int path_len;

    /* Move into the world's directory first; this lets us look up
     * resources relative to the world.
     */
    path_len = __get_path(filename, path, MAX_PATH);

    if(path_len < 0)
    {
      ret = GET_PATH_FAILED;
      goto exit_free_stream;
    }

    if(path_len > 0)
    {
      if(chdir(path))
      {
        ret = CHDIR_FAILED;
        goto exit_free_stream;
      }

      filename += path_len + 1;
    }

    (*s)->type = FILE_STREAM;
    (*s)->stream.fp = fopen_unsafe(filename, mode);
    if(!(*s)->stream.fp)
    {
      ret = FOPEN_FAILED;
      goto exit_free_stream;
    }
    return SUCCESS;
  }

  // otherwise, it's a ZIP file, proceed with buffer logic
  (*s)->type = BUFFER_STREAM;
  f = unzOpen(filename);
  if(!f)
  {
    ret = MALLOC_FAILED;
    goto exit_free_stream;
  }

  if(unzGoToFirstFile(f) != UNZ_OK)
  {
    ret = UNZ_FAILED;
    goto exit_free_stream_close;
  }

  while(1)
  {
    char fname[FILENAME_MAX];
    int unzRet;

    // get the filename for the current ZIP file entry
    unzGetCurrentFileInfo(f, &info, fname, FILENAME_MAX, NULL, 0, NULL, 0);

    // not a MZX file; don't care
    if(!strcasecmp(fname + strlen(fname) - 3, "mzx"))
      break;

    // error occurred, or no files left
    unzRet = unzGoToNextFile(f);
    if(unzRet != UNZ_OK)
    {
      if(unzRet == UNZ_END_OF_LIST_OF_FILE)
        ret = NO_WORLD;
      else
        ret = UNZ_FAILED;

      goto exit_free_stream_close;
    }
  }

  if(unzOpenCurrentFile(f) != UNZ_OK)
  {
    ret = UNZ_FAILED;
    goto exit_free_stream_close;
  }

  (*s)->stream.buffer.len = info.uncompressed_size;
  (*s)->stream.buffer.buf = malloc((*s)->stream.buffer.len);
  if(!(*s)->stream.buffer.buf)
  {
    ret = MALLOC_FAILED;
    goto exit_free_stream_close_both;
  }

  if(unzReadCurrentFile(f, (*s)->stream.buffer.buf,
                           (*s)->stream.buffer.len) <= 0)
  {
    ret = UNZ_FAILED;
    goto exit_free_stream_close_both_free_buffer;
  }

  (*s)->stream.buffer.f = f;
  unzCloseCurrentFile(f);
  return SUCCESS;

exit_free_stream_close_both_free_buffer:
  free((*s)->stream.buffer.buf);
exit_free_stream_close_both:
  unzCloseCurrentFile(f);
exit_free_stream_close:
  unzClose(f);
exit_free_stream:
  free(*s);
  *s = NULL;
  return ret;
}

static int sclose(struct stream *s)
{
  FILE *f;

  switch(s->type)
  {
    case FILE_STREAM:
      f = s->stream.fp;
      free(s);
      return fclose(f);

    case BUFFER_STREAM:
      unzClose(s->stream.buffer.f);
      free(s->stream.buffer.buf);
      free(s);
      return 0;

    default:
      return EOS;
  }
}

static int sgetc(struct stream *s)
{
  switch(s->type)
  {
    case FILE_STREAM:
      return fgetc(s->stream.fp);

    case BUFFER_STREAM:
      if(s->stream.buffer.index == s->stream.buffer.len)
        return EOS;
      else
        return s->stream.buffer.buf[s->stream.buffer.index++];

    default:
      return EOS;
  }
}

static size_t sread(void *ptr, size_t size, size_t count, struct stream *s)
{
  switch(s->type)
  {
    case FILE_STREAM:
      return fread(ptr, size, count, s->stream.fp);

    case BUFFER_STREAM:
      if(s->stream.buffer.index + size * count >= s->stream.buffer.len)
      {
        size_t n = (s->stream.buffer.len - s->stream.buffer.index) / size;
        memcpy(ptr, s->stream.buffer.buf + s->stream.buffer.index, n * size);
        s->stream.buffer.index = s->stream.buffer.len;
        return n;
      }
      else
      {
        memcpy(ptr, s->stream.buffer.buf + s->stream.buffer.index, count * size);
        s->stream.buffer.index += count * size;
        return count;
      }

    default:
      return EOS;
  }
}

static int sseek(struct stream *s, long int offset, int origin)
{
  switch(s->type)
  {
    case FILE_STREAM:
      return fseek(s->stream.fp, offset, origin);

    case BUFFER_STREAM:
      switch(origin)
      {
        case SEEK_SET:
          s->stream.buffer.index = offset;
          break;
        case SEEK_CUR:
          s->stream.buffer.index += offset;
          break;
        case SEEK_END:
          s->stream.buffer.index = s->stream.buffer.len + offset;
          break;
      }

      if(s->stream.buffer.index >= s->stream.buffer.len)
        return EOS;
      return 0;

    default:
      return EOS;
  }
}

static long int stell(struct stream *s)
{
  switch(s->type)
  {
    case FILE_STREAM:
      return ftell(s->stream.fp);
    case BUFFER_STREAM:
      return s->stream.buffer.index;
    default:
      return EOS;
  }
}

static unsigned int sgetud(struct stream *s)
{
  return (sgetc(s) << 0)  | (sgetc(s) << 8) |
         (sgetc(s) << 16) | (sgetc(s) << 24);
}

static unsigned short sgetus(struct stream *s)
{
  return (sgetc(s) << 0) | (sgetc(s) << 8);
}

static int world_magic(const unsigned char magic_string[3])
{
  if(magic_string[0] == 'M')
  {
    if(magic_string[1] == 'Z')
    {
      switch(magic_string[2])
      {
        case '2':
          return 0x0205;
        case 'A':
          return 0x0208;
      }
    }
    else
    {
      if((magic_string[1] > 1) && (magic_string[1] < 10))
        return ((int)magic_string[1] << 8) + (int)magic_string[2];
    }
  }

  return 0;
}

static int board_magic(const unsigned char magic_string[4])
{
  if(magic_string[0] == 0xFF)
  {
    if(magic_string[1] == 'M')
    {
      if(magic_string[2] == 'B' && magic_string[3] == '2')
        return 0x0200;

      if((magic_string[2] > 1) && (magic_string[2] < 10))
        return ((int)magic_string[2] << 8) + (int)magic_string[3];
    }
  }

  return 0;
}

static const char *decode_status(enum status status)
{
  switch(status)
  {
    case CORRUPT_WORLD:
      return "Random world corruption.";
    case FOPEN_FAILED:
      return "Could not open file.";
    case GET_PATH_FAILED:
      return "Failed to obtain path from filename.";
    case CHDIR_FAILED:
      return "Failed to chdir() into parent path.";
    case FREAD_FAILED:
      return "File read failed or was short (corrupt file?).";
    case FSEEK_FAILED:
      return "Seeking in file failed (too short?).";
    case MALLOC_FAILED:
      return "Memory allocation failed or was zero.";
    case PROTECTED_WORLD:
      return "Protected worlds currently unsupported.";
    case MAGIC_CHECK_FAILED:
      return "File magic not consistent with 2.00 world or board.";
    case UNZ_FAILED:
      return "Something is wrong with the zip file.";
    case NO_WORLD:
      return "No world file present.";
    default:
      return "Unknown error.";
  }
}

static enum status parse_sfx(char *sfx_buf)
{
  char *start, *end = sfx_buf - 1, str_buf_len;
  enum status ret = SUCCESS;

  str_buf_len = strlen(sfx_buf);

  while(true)
  {
    // no starting & was found
    start = strchr(end + 1, '&');
    if(!start || start - sfx_buf + 1 > str_buf_len)
      break;

    // no ending & was found
    end = strchr(start + 1, '&');
    if(!end || end - sfx_buf + 1 > str_buf_len)
      break;

    // Wipe out the &s to get a token
    *start = 0;
    *end = 0;

    debug("SFX (class): %s\n", start + 1);
    ret = add_to_hash_table(start + 1);
    if(ret != SUCCESS)
      break;
  }

  return ret;
}

static enum status parse_robot(struct stream *s)
{
  unsigned short robot_size;
  enum status ret = SUCCESS;
  char tmp[256], tmp2[256];
  long start;
  int i;

  // robot's size
  robot_size = sgetus(s);

  // skip to robot code
  if(sseek(s, 40 - 2 + 1, SEEK_CUR) != 0)
    return FSEEK_FAILED;

  start = stell(s);

  // skip 0xff marker
  if(sgetc(s) != 0xff)
    return CORRUPT_WORLD;

  while(1)
  {
    int command, command_length, str_len;

    if(stell(s) - start >= robot_size)
      return CORRUPT_WORLD;

    command_length = sgetc(s);
    if(command_length == 0)
      break;

    command = sgetc(s);
    switch(command)
    {
      case 0xa:
        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        str_len = sgetc(s);

        if(str_len == 0)
        {
          sgetus(s);
          break;
        }

        if(sread(tmp2, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        if(!strcasecmp(tmp2, "FREAD_OPEN")
          || !strcasecmp(tmp2, "SMZX_PALETTE")
          || !strcasecmp(tmp2, "LOAD_GAME")
          || !strncasecmp(tmp2, "LOAD_BC", 7)
          || !strncasecmp(tmp2, "LOAD_ROBOT", 10))
        {
          debug("SET: %s (%s)\n", tmp, tmp2);
          ret = add_to_hash_table(tmp);
          if(ret != SUCCESS)
            return ret;
        }
        break;

      case 0x26:
        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        // ignore MOD *
        if(!strcmp(tmp, "*"))
          break;

        // FIXME: Should only match pairs?
        if(strstr(tmp, "&"))
          break;

        debug("MOD: %s\n", tmp);
        ret = add_to_hash_table(tmp);
        if(ret != SUCCESS)
          return ret;
        break;

      case 0x27:
        str_len = sgetc(s);

        if(str_len == 0)
          sgetus(s);
        else
        {
          for(i = 0; i < str_len; i++)
            sgetc(s);
        }

        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        // FIXME: Should only match pairs?
        if(strstr(tmp, "&"))
            break;

        debug("SAM: %s\n", tmp);
        ret = add_to_hash_table(tmp);
        if(ret != SUCCESS)
          return ret;
        break;

      case 0x2b:
      case 0x2d:
      case 0x31:
        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        ret = parse_sfx(tmp);
        if(ret != SUCCESS)
          return ret;
        break;

      case 0xc8:
        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        debug("MOD FADE IN: %s\n", tmp);
        ret = add_to_hash_table(tmp);
        if(ret != SUCCESS)
          return ret;
        break;

      case 0xd8:
        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        debug("LOAD CHAR SET: %s\n", tmp);

        if(tmp[0] == '+')
        {
          char *rest, tempc = tmp[3];
          tmp[3] = 0;
          strtol(tmp + 1, &rest, 16);
          tmp[3] = tempc;
          memmove(tmp, rest, str_len - (rest - tmp));
        }
        else if(tmp[0] == '@')
        {
          char *rest, tempc = tmp[4];
          tmp[4] = 0;
          strtol(tmp + 1, &rest, 10);
          tmp[4] = tempc;
          memmove(tmp, rest, str_len - (rest - tmp));
        }

        ret = add_to_hash_table(tmp);
        if(ret != SUCCESS)
          return ret;
        break;

      case 0xde:
        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        debug("LOAD PALETTE: %s\n", tmp);
        ret = add_to_hash_table(tmp);
        if(ret != SUCCESS)
          return ret;
        break;

      case 0xe2:
        str_len = sgetc(s);

        if(sread(tmp, 1, str_len, s) != (size_t)str_len)
          return FREAD_FAILED;

        debug("SWAP WORLD: %s\n", tmp);
        ret = add_to_hash_table(tmp);
        if(ret != SUCCESS)
          return ret;
        break;

      default:
        if(sseek(s, command_length - 1, SEEK_CUR) != 0)
          return FSEEK_FAILED;
        break;
    }

    if(sgetc(s) != command_length)
      return CORRUPT_WORLD;
  }

  return ret;
}

static enum status parse_board_direct(struct stream *s, int version)
{
  int i, num_robots, skip_rle_blocks = 6, skip_bytes;
  unsigned short board_mod_len;
  enum status ret = SUCCESS;
  char tmp[MAX_PATH];

  // junk the undocumented (and unused) board_mode
  sgetc(s);

  // whether the overlay is enabled or not
  if(sgetc(s))
  {
    // not enabled, so rewind this last read
    if(sseek(s, -1, SEEK_CUR) != 0)
      return FSEEK_FAILED;
  }
  else
  {
    // junk overlay_mode
    sgetc(s);
    skip_rle_blocks += 2;
  }

  // this skips either 6 blocks (with no overlay)
  // ..or 8 blocks (with overlay enabled on board)
  for(i = 0; i < skip_rle_blocks; i++)
  {
    unsigned short w = sgetus(s);
    unsigned short h = sgetus(s);
    int pos = 0;

    /* RLE "decoder"; just to skip stuff */
    while(pos < w * h)
    {
      unsigned char c = (unsigned char)sgetc(s);

      if(!(c & 0x80))
        pos++;
      else
      {
        c &= ~0x80;
        pos += c;
        sgetc(s);
      }
    }
  }

  // get length of board MOD string
  if(version < 0x0253)
    board_mod_len = 12;
  else
    board_mod_len = sgetus(s);

  // grab board's default MOD
  if(sread(tmp, 1, board_mod_len, s) != board_mod_len)
      return FREAD_FAILED;
  tmp[board_mod_len] = '\0';

  // check the board MOD exists
  if(strlen(tmp) > 0 && strcmp(tmp, "*"))
  {
    debug("BOARD MOD: %s\n", tmp);
    ret = add_to_hash_table(tmp);
    if(ret != SUCCESS)
      return ret;
  }

  if(version < 0x0253)
    skip_bytes = 208;
  else
    skip_bytes = 25;

  // skip to the robot count
  if(sseek(s, skip_bytes, SEEK_CUR) != 0)
    return FSEEK_FAILED;

  // walk the robot list, scan the robotic
  num_robots = sgetc(s);
  for(i = 0; i < num_robots; i++)
  {
    ret = parse_robot(s);
    if(ret != SUCCESS)
      break;
  }

  return ret;
}

// same as internal boards except for a 4 byte magic header

static enum status parse_board(struct stream *s)
{
  unsigned char magic[4];
  int version;

  if(sread(magic, 1, 4, s) != (size_t)4)
    return FREAD_FAILED;

  version = board_magic(magic);
  if(version <= 0)
    return MAGIC_CHECK_FAILED;

  return parse_board_direct(s, version);
}

static enum status parse_world(struct stream *s)
{
  int version, i, num_boards, global_robot_offset;
  enum status ret = SUCCESS;
  unsigned char magic[3];

  // skip to protected byte; don't care about world name
  if(sseek(s, WORLD_PROTECTED_OFFSET, SEEK_SET) != 0)
    return FSEEK_FAILED;

  // can't yet support protected worlds
  if(sgetc(s) != 0)
    return PROTECTED_WORLD;

  // read in world magic (version)
  if(sread(magic, 1, 3, s) != (size_t)3)
    return FREAD_FAILED;

  // can only support 2.00+ versioned worlds
  version = world_magic(magic);
  if(version <= 0)
    return MAGIC_CHECK_FAILED;

  // Jump to the global robot offset
  if(sseek(s, WORLD_GLOBAL_OFFSET_OFFSET, SEEK_SET) != 0)
    return FSEEK_FAILED;

  // Absolute offset (in bytes) of global robot
  global_robot_offset = sgetud(s);

  // num_boards doubles as the check for custom sfx, if zero
  num_boards = sgetc(s);

  // if we have custom sfx, check them for resources
  if(num_boards == 0)
  {
    unsigned short sfx_len_total = sgetus(s);

    for(i = 0; i < NUM_SFX; i++)
    {
      unsigned short sfx_len;
      char sfx_buf[70];

      sfx_len = sgetc(s);
      if(sfx_len > 69)
        return CORRUPT_WORLD;

      if(sfx_len > 0)
      {
        if(sread(sfx_buf, 1, sfx_len, s) != (size_t)sfx_len)
          return FREAD_FAILED;
        sfx_buf[sfx_len] = 0;

        ret = parse_sfx(sfx_buf);
        if(ret != SUCCESS)
          return ret;
      }

      // 1 for length byte + sfx string
      sfx_len_total -= 1 + sfx_len;
    }

    // better check we moved by whole amount
    if(sfx_len_total != 0)
      return CORRUPT_WORLD;

    // read the "real" num_boards
    num_boards = sgetc(s);
  }

  // skip board names; we simply don't care
  if(sseek(s, num_boards * BOARD_NAME_SIZE, SEEK_CUR) != 0)
    return FSEEK_FAILED;

  // grab the board sizes/offsets
  for(i = 0; i < num_boards; i++)
  {
    board_list[i].size = sgetud(s);
    board_list[i].offset = sgetud(s);
  }

  // walk all boards
  for(i = 0; i < num_boards; i++)
  {
    struct board *board = &board_list[i];

    // don't care about deleted boards
    if(board->size == 0)
      continue;

    // seek to board offset within world
    if(sseek(s, board->offset, SEEK_SET) != 0)
      return FSEEK_FAILED;

    // parse this board atomically
    ret = parse_board_direct(s, version);
    if(ret != SUCCESS)
      goto err_out;
  }

  // Do the global robot too..
  if(sseek(s, global_robot_offset, SEEK_SET) != 0)
    return FSEEK_FAILED;
  ret = parse_robot(s);

err_out:
  return ret;
}

static enum status file_exists(const char *file, struct stream *s)
{
  char newpath[MAX_PATH];

  switch(s->type)
  {
    case FILE_STREAM:
      if(fsafetranslate(file, newpath) == FSAFE_SUCCESS)
        return SUCCESS;
      return MISSING_FILE;

    case BUFFER_STREAM:
      if(unzLocateFile(s->stream.buffer.f, file, 2) == UNZ_OK)
        return SUCCESS;
      return MISSING_FILE;

    default:
      return MISSING_FILE;
  }
}

int main(int argc, char *argv[])
{
  const char *found_append = " - FOUND", *not_found_append = " - NOT FOUND";
  int i, len, print_all_files = 0, got_world = 0, quiet_mode = 0;
  struct stream *s;
  enum status ret;

  if(argc < 2)
  {
    fprintf(stderr, "usage: %s [-q] [-a] [mzx/mzb/zip file]\n\n", argv[0]);
    fprintf(stderr, "  -q  Do not print summary \"FOUND\"/\"NOT FOUND\".\n");
    fprintf(stderr, "  -a  Print found files as well as missing files.\n");
    fprintf(stderr, "\n");
    return INVALID_ARGUMENTS;
  }

  for(i = 1; i < argc - 1; i++)
  {
    if(!strcmp(argv[i], "-q"))
    {
      quiet_mode = 1;
      found_append = "";
      not_found_append = "";
    }
    else if(!strcmp(argv[i], "-a"))
      print_all_files = 1;
  }

  len = strlen(argv[argc - 1]);
  if(len < 4)
  {
    fprintf(stderr, "'%s' is not a valid input filename.\n", argv[argc - 1]);
    return INVALID_ARGUMENTS;
  }

  if(!strcasecmp(&argv[argc - 1][len - 4], ".MZX"))
    got_world = 1;
  else if(!strcasecmp(&argv[argc - 1][len - 4], ".MZB"))
    got_world = 0;
  else if(!strcasecmp(&argv[argc -1][len -4], ".ZIP"))
    got_world = 1;
  else
  {
    fprintf(stderr, "'%s' is not a .MZX (world), .MZB (board) "
                    "or .ZIP (archive) file.\n", argv[argc - 1]);
    return INVALID_ARGUMENTS;
  }

  ret = s_open(argv[argc - 1], "rb", &s);
  if(s)
  {
    if(got_world)
      ret = parse_world(s);
    else
      ret = parse_board(s);

    for(i = 0; i < HASH_TABLE_SIZE; i++)
    {
      char **p = hash_table[i];

      if(!p)
        continue;

      while(*p)
      {
        if(ret == SUCCESS)
        {
          if(file_exists(*p, s) == SUCCESS)
          {
            if(print_all_files)
              fprintf(stdout, "%s%s\n", *p, found_append);
          }
          else
            fprintf(stdout, "%s%s\n", *p, not_found_append);
        }

        free(*p);
        p++;
      }

      free(hash_table[i]);
    }

    sclose(s);
  }

  if(ret != SUCCESS)
    fprintf(stderr, "ERROR: %s\n", decode_status(ret));
  else
  {
    if(!quiet_mode)
      fprintf(stdout, "Finished processing '%s'.\n", argv[argc - 1]);
  }

  return ret;
}


code:
/* MegaZeux
 *
 * This is a very trivial tool to downgrade a world or board file to the
 * previous binary version. It allows a world or board created in the current
 * version of MegaZeux to be loadable in the previous version (i.e. before the
 * world format change). Of course, the downgraded world may not work, but it
 * should be loadable by the editor.
 *
 * This tool is a good insurance policy for brand new releases, to prevent
 * the case where people start work in a newer version of MegaZeux, only
 * for there to be a critical bug discovered, and for the user to be "locked
 * in" to the buggy version.
 *
 * Primarily, this tool is intended for DoZs where a new version may be used,
 * but a serious bug cannot reasonably be corrected before the end of the
 * competition.
 *
 * Since the tool will change with every release, it is hard coded to downgrade
 * only to one previous version, from one current version. This is intentional,
 * to make the tool easy to use for newbies (simply drag and drop).
 *
 * Copyright (C) 2008-2009 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 <string.h>

#ifdef __WIN32__
#include <strings.h>
#endif

#include "world.h"
#include "util.h"

#define error(...) \
  { \
    fprintf(stderr, __VA_ARGS__); \
    fflush(stderr); \
  }

#define WORLD_VERSION_HI ((WORLD_VERSION >> 8) & 0xff)
#define WORLD_VERSION_LO (WORLD_VERSION & 0xff)

#define WORLD_VERSION_PREV_HI ((WORLD_VERSION_PREV >> 8) & 0xff)
#define WORLD_VERSION_PREV_LO (WORLD_VERSION_PREV & 0xff)

#if WORLD_VERSION == 0x0254

/* Suppress a couple of annoying warnings
#define WORLD_GLOBAL_OFFSET_OFFSET 4230
#define MAX_BOARDS                 250
*/

enum status
{
  SUCCESS = 0,
  READ_ERROR,
  WRITE_ERROR,
  SEEK_ERROR,
};

/* Commenting out some useful stuff to leave around
static struct board_entry
{
  unsigned int size;
  unsigned int offset;
}
board_list[MAX_BOARDS];

static unsigned short fgetus(FILE *fp)
{
  return (fgetc(fp) << 0) | (fgetc(fp) << 8);
}

static unsigned int fgetud(FILE *fp)
{
  return (fgetc(fp) << 0)  | (fgetc(fp) << 8) |
         (fgetc(fp) << 16) | (fgetc(fp) << 24);
}

static void fputus(unsigned short src, FILE *fp)
{
  fputc(src & 0xFF, fp);
  fputc(src >> 8, fp);
}

static void fputud(unsigned int src, FILE *fp)
{
  fputc(src & 0xFF, fp);
  fputc((src >> 8) & 0xFF, fp);
  fputc((src >> 16) & 0xFF, fp);
  fputc((src >> 24) & 0xFF, fp);
}
*/

static enum status convert_284_to_283_board(FILE *fp, int *delta)
{
  //well, that was easy!!
  return SUCCESS;
}

static enum status convert_284_to_283(FILE *fp)
{
  //no changes in format
  return SUCCESS;
}

#endif // WORLD_VERSION == 0x0254

int main(int argc, char *argv[])
{
  int world, byte;
  long ext_pos;
  FILE *fp;

  if(argc <= 1)
  {
    error("Usage: %s [mzx/mzb file]\n", argv[0]);
    goto exit_out;
  }

  ext_pos = strlen(argv[1]) - 4;
  if(ext_pos < 0)
  {
    error("File '%s' of an unknown type.\n", argv[1]);
    goto exit_out;
  }

  if(!strcasecmp(argv[1] + ext_pos, ".mzb"))
  {
    world = false;
  }
  else if(!strcasecmp(argv[1] + ext_pos, ".mzx"))
  {
    world = true;
  }
  else
  {
    error("Unknown extension '%s'.\n", argv[1] + ext_pos);
    goto exit_out;
  }

  fp = fopen_unsafe(argv[1], "r+b");
  if(!fp)
  {
    error("Could not open '%s' for read/write.\n", argv[1]);
    goto exit_out;
  }

  /* World files need some crap at the beginning skipped. Also, world files
   * can theoretically be encrypted, though practically this will not happen
   * with modern worlds. Just in case, check for it and abort.
   *
   * Board files just need the 0xFF byte at the start skipping.
   */
  if(world)
  {
    char name[BOARD_NAME_SIZE];
    size_t fret;

    // Last selected board name; junked, we simply don't care
    fret = fread(name, BOARD_NAME_SIZE, 1, fp);
    if(fret != 1)
      goto err_read;

    // Check protection isn't enabled
    byte = fgetc(fp);
    if(byte < 0)
      goto err_read;
    else if(byte != 0)
    {
      error("Protected worlds are not supported.\n");
      goto exit_close;
    }
  }
  else
  {
    byte = fgetc(fp);
    if(byte < 0)
      goto err_read;
    else if(byte != 0xff)
      error("Board file is corrupt or unsupported.\n");
  }

  // Validate version is current

  byte = fgetc(fp);
  if(byte < 0)
    goto err_read;
  else if(byte != 'M')
  {
    error("World file is corrupt or unsupported.\n");
    goto exit_close;
  }

  byte = fgetc(fp);
  if(byte < 0)
    goto err_read;
  else if(byte != WORLD_VERSION_HI)
  {
    error("This tool only supports worlds or boards from %d.%d.\n",
     WORLD_VERSION_HI, WORLD_VERSION_LO);
    goto exit_close;
  }

  byte = fgetc(fp);
  if(byte < 0)
    goto err_read;
  else if(byte != WORLD_VERSION_LO)
  {
    error("This tool only supports worlds or boards from %d.%d.\n",
     WORLD_VERSION_HI, WORLD_VERSION_LO);
    goto exit_close;
  }

  // Re-write current magic with previous version's magic

  if(fseek(fp, -2, SEEK_CUR) != 0)
    goto err_seek;

  byte = fputc(WORLD_VERSION_PREV_HI, fp);
  if(byte == EOF)
    goto err_write;

  byte = fputc(WORLD_VERSION_PREV_LO, fp);
  if(byte == EOF)
    goto err_write;

#if WORLD_VERSION == 0x0254
  {
    enum status ret;

    if(world)
      ret = convert_284_to_283(fp);
    else
      ret = convert_284_to_283_board(fp, NULL);

    switch(ret)
    {
      case SEEK_ERROR:  goto err_seek;
      case READ_ERROR:  goto err_read;
      case WRITE_ERROR: goto err_write;
      case SUCCESS:     break;
    }
  }
#endif

  fprintf(stdout, "File '%s' successfully downgraded from %d.%d to %d.%d.\n",
   argv[1], WORLD_VERSION_HI, WORLD_VERSION_LO,
   WORLD_VERSION_PREV_HI, WORLD_VERSION_PREV_LO);

exit_close:
  fclose(fp);
exit_out:
  return 0;

err_seek:
  error("Seek error, aborting.\n");
  goto exit_close;

err_read:
  error("Read error, aborting.\n");
  goto exit_close;

err_write:
  error("Write error, aborting.\n");
  goto exit_close;
}


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 <stdlib.h>
#include <stdio.h>

static int fgetw(FILE *fp)
{
  int r = fgetc(fp);
  r |= fgetc(fp) << 8;
  return r;
}

static int fgetd(FILE *fp)
{
  int r = fgetc(fp);
  r |= fgetc(fp) << 8;
  r |= fgetc(fp) << 16;
  r |= fgetc(fp) << 24;
  return r;
}

#define error(...) \
  { \
    fprintf(stderr, __VA_ARGS__); \
    fflush(stderr); \
  }

struct subfile
{
  char name[13];
  int offset;
  int length;
};

int main(int argc, char *argv[])
{
  FILE *in = NULL, *out = NULL;
  int i, num_files, ret = 1;
  struct subfile *subfiles;

  if(argc != 3)
  {
    error("usage: %s [help.fil] [help.txt]\n", argv[0]);
    goto exit_out;
  }

  in = fopen(argv[1], "rb");
  if(!in)
  {
    error("Failed to open input file '%s' for reading.\n", argv[1]);
    goto exit_close;
  }

  out = fopen(argv[2], "w");
  if(!out)
  {
    error("Failed to open output file '%s' for writing.\n", argv[2]);
    goto exit_close;
  }

  /* Each help FIL is split into subfiles internally, aligned to pages,
   * and with a fixed offset and length. Walk these subfiles here.
   */
  num_files = fgetw(in);
  subfiles = calloc(num_files, sizeof(struct subfile));

  for(i = 0; i < num_files; i++)
  {
    if(fread(&subfiles[i].name, 13, 1, in) != 1)
      goto err_read_io;
    subfiles[i].offset = fgetd(in);
    subfiles[i].length = fgetd(in);
  }

  /* FIXME: Hack, remove!!
   * txt2hlp seems to inject an unnecessary 0x0A at the end of the file
   * which means we can't use hlp2txt for validation. Hack the final
   * subfile so it's two characters short.
   */
  subfiles[i - 1].length-=2;

  for(i = 0; i < num_files; i++)
  {
    int start_pos;

    if(fseek(in, subfiles[i].offset, SEEK_SET) != 0)
      goto err_read_io;

    /* Each help subfile contains a single byte indicating the index
     * of the subfile. We must ignore this for converting out.
     */
    if(fseek(in, 1, SEEK_CUR) != 0)
      goto err_read_io;
    subfiles[i].length--;

    /* We're going through line-wise but we need an absolute reference. */
    start_pos = ftell(in);
    if(start_pos < 0)
      goto err_read_io;

    if(i > 0)
    {
      /* If we're not the zero'th subfile we need to write out our name. */
      if(fprintf(out, "#%s\n", subfiles[i].name) < 0)
        goto err_write_io;
    }

    while(1)
    {
      int byte, curr_pos;
      char buffer[256];

      curr_pos = ftell(in);
      if(curr_pos < 0)
        goto err_read_io;

      if(curr_pos - start_pos >= subfiles[i].length)
        break;

      /* Peek at the next byte to check it's not 0xFF. This character
       * indicates that the line is something special, like a link.
       */
      byte = fgetc(in);
      if(byte == EOF)
        goto err_read_io;

      /* We got a control sequence of some kind. Try to decode it. */
      if(byte == 0xff)
      {
        byte = fgetc(in);
        if(byte == EOF)
          goto err_read_io;

        switch(byte)
        {
          /* Centered line */
          case '$':
            /* Write out the control code */
            if(fputc('$', out) == EOF)
              goto err_write_io;
            break;

          /* Label */
          case ':':
          /* Choice (non-file) */
          case '>':
            /* Write out the control code */
            if(fputc(byte, out) == EOF)
              goto err_write_io;

            /* Read label length in bytes */
            byte = fgetc(in);
            if(byte == EOF)
              goto err_read_io;

            /* Read this many bytes and write them out */
            if(fread(buffer, byte, 1, in) != 1)
              goto err_read_io;
            if(fwrite(buffer, byte, 1, out) != 1)
              goto err_write_io;

            /* Skip over NULL terminator, and defer */
            if(fseek(in, 1, SEEK_CUR) != 0)
              goto err_read_io;

            /* Write out separator */
            if(fputc(':', out) == EOF)
              goto err_write_io;
            break;

          /* Choice (file) */
          case '<':
            /* Write out the control code */
            if(fputc('>', out) == EOF)
              goto err_write_io;

            /* FIXME: Assume it's a file link */
            if(fputc('#', out) == EOF)
              goto err_write_io;

            /* Read label length in bytes */
            byte = fgetc(in);
            if(byte == EOF)
              goto err_read_io;

            /* Read this many bytes and write them out */
            if(fread(buffer, byte, 1, in) != 1)
              goto err_read_io;
            if(fwrite(buffer, byte, 1, out) != 1)
              goto err_write_io;

            /* Write out separator */
            if(fputc(':', out) == EOF)
              goto err_write_io;

            /* Skip over NULL terminator, and defer */
            if(fseek(in, 1, SEEK_CUR) != 0)
              goto err_read_io;
            break;

          default:
            error("Unhandled control sequence '%c', trying to skip.\n", byte);
            if(!fgets(buffer, 256, in))
              goto err_read_io;
            continue;
        }

        /* Read the tail end of the command as a regular string. */
        if(!fgets(buffer, 256, in))
          goto err_read_io;
      }
      else
      {
        /* Otherwise backtrace this peek, prepping for a normal read. */
        if(fseek(in, -1, SEEK_CUR) != 0)
          goto err_read_io;

        /* Regular string; can be read as ascii. */
        if(!fgets(buffer, 256, in))
          goto err_read_io;

        /* Special case escaping -- obviously since the first column of any
         * line can contain "control" characters, we must not emit these with
         * "normal" strings. The system has a reserved '.' character for this.
         */
        switch(buffer[0])
        {
          case '>':
          case '<':
          case ':':
          case '#':
          case '.':
            if(fputc('.', out) == EOF)
              goto err_write_io;
            break;
        }
      }

      if(fprintf(out, "%s", buffer) < 0)
        goto err_write_io;
    }
  }

  ret = 0;

exit_free:
  free(subfiles);
exit_close:
  if(out)
    fclose(out);
  if(in)
    fclose(in);
exit_out:
  return ret;

err_read_io:
  error("I/O error on read, aborting.\n");
  goto exit_free;

err_write_io:
  error("I/O error on write, aborting.\n");
  goto exit_free;
}


code:
/* Copyright (C) 2010 Alan Williams <mralert@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 <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#include "pngops.h"
#include "smzxconv.h"

// FIXME: Fix this better
int error(const char *string, unsigned int type, unsigned int options,
		  unsigned int code) {
	return 0;
}

static bool dummy_constraint(png_uint_32 w, png_uint_32 h) {
	return true;
}

static void *dummy_allocator(png_uint_32 w, png_uint_32 h,
			     png_uint_32 *stride, void **pixels) {
	void *p = malloc(w * h * 4);
	*stride = w * 4;
	*pixels = p;
	return p;
}

static rgba_color *read_png(const char *file, png_uint_32 *w, png_uint_32 *h) {
	return (rgba_color *)png_read_file(file, w, h, dummy_constraint,
					   dummy_allocator);
}

int main (int argc, char **argv) {
	FILE *fd;
	unsigned char mzmhead[16] = {
		'M', 'Z', 'M', '2', /* Magic */
		0, 0, /* Width */
		0, 0, /* Height */
		0, 0, 0, 0, 0, 1, 0, 0 /* Stuff */
	};
	png_uint_32 w, h, i, t;
	rgba_color *img;
	mzx_tile *tile;
	mzx_glyph chr[256];
	mzx_color pal[256];
	smzx_converter *c;
	if (argc != 5) {
		fprintf(stderr, "Usage: %s <in.png> <out.mzm> <out.chr> "
			"<out.pal>\n", argv[0]);
		return 1;
	}
	img = (rgba_color *)read_png(argv[1], &w, &h);
	if (!img) {
		fprintf(stderr, "Error reading image.\n");
		return 1;
	}
	if ((w % 8) || (h % 14)) {
		fprintf(stderr, "Image not divisible by 8x14; cropping...\n");
		if (w % 8) {
			t = w / 8 * 8;
			for (i = 1; i < h; i++)
				memmove(img + i * t, img + i * w,
					sizeof(rgba_color) * t);
		}
	}
	w /= 8;
	h /= 14;
	c = smzx_convert_init(w, h, 0, -1, 256, 0, 16);
	if (!c) {
		fprintf(stderr, "Error initializing converter.\n");
		free(img);
		return 1;
	}
	tile = malloc(sizeof(mzx_tile) * w * h);
	if (!tile) {
		fprintf(stderr, "Error allocating tile buffer.\n");
		smzx_convert_free(c);
		free(img);
		return 1;
	}
	smzx_convert(c, img, tile, chr, pal);
	smzx_convert_free(c);
	free(img);
	mzmhead[4] = w & 0xFF;
	mzmhead[5] = w >> 8;
	mzmhead[6] = h & 0xFF;
	mzmhead[7] = h >> 8;
	fd = fopen_unsafe(argv[2], "wb");
	if (!fd) {
		fprintf(stderr, "Error opening MZM file.\n");
		free(tile);
		return 1;
	}
	if (fwrite(mzmhead, sizeof(mzmhead), 1, fd) != 1) {
		fprintf(stderr, "Error writing MZM header.\n");
		fclose(fd);
		free(tile);
		return 1;
	}
	if (fwrite(tile, sizeof(mzx_tile) * w * h, 1, fd) != 1) {
		fprintf(stderr, "Error writing MZM data.\n");
		fclose(fd);
		free(tile);
		return 1;
	}
	free(tile);
	fclose(fd);
	fd = fopen_unsafe(argv[3], "wb");
	if (!fd) {
		fprintf(stderr, "Error opening CHR file.\n");
		return 1;
	}
	if (fwrite(chr, sizeof(chr), 1, fd) != 1) {
		fprintf(stderr, "Error writing CHR data.\n");
		fclose(fd);
		return 1;
	}
	fclose(fd);
	fd = fopen_unsafe(argv[4], "wb");
	if (!fd) {
		fprintf(stderr, "Error opening PAL file.\n");
		return 1;
	}
	if (fwrite(pal, sizeof(pal), 1, fd) != 1) {
		fprintf(stderr, "Error writing PAL data.\n");
		fclose(fd);
		return 1;
	}
	fclose(fd);
	return 0;
}


code:
/* Copyright (C) 2010 Alan Williams <mralert@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 <stdlib.h>
#include <string.h>

#include "smzxconv.h"

typedef struct {
	int r;
	int g;
	int b;
	int c;
} rgba_accum;

typedef struct {
	mzx_glyph g;
	unsigned short w;
	unsigned int d;
} glyph_dist;

struct _smzx_converter {
	int w, h;
	int iw, ih;
	int tsiz, isiz;
	int chroff, chrskip, chrlen;
	int chrprelen;
	int clroff, clrlen;
	rgba_color *src;
	unsigned char *dest;
	unsigned char *hist;
	int *lerr;
	glyph_dist *tgly;
	glyph_dist *cset;
};

smzx_converter *smzx_convert_init (int w, int h, int chroff, int chrskip,
	int chrlen, int clroff, int clrlen) {
	smzx_converter *c;
	int chrprelen = chrlen;
	int iw = w * 4;
	int ih = h * 14;
	int tsiz = w * h;
	int isiz = iw * ih;
	int setsize = tsiz;
	if ((chrskip > chroff) && (chrskip < chroff + chrlen)) chrprelen--;
	if (setsize < chroff + chrlen) setsize = chroff + chrlen;
	c = malloc(sizeof(smzx_converter));
	if (!c) return NULL;
	c->w = w;
	c->h = h;
	c->iw = iw;
	c->ih = ih;
	c->tsiz = tsiz;
	c->isiz = isiz;
	c->chroff = chroff;
	c->chrskip = chrskip;
	c->chrlen = chrlen;
	c->chrprelen = chrprelen;
	c->clroff = clroff;
	c->clrlen = clrlen;
	c->src = malloc(sizeof(rgba_color) * isiz);
	c->dest = malloc(isiz);
	c->hist = malloc(isiz);
	c->lerr = malloc(sizeof(int) * (iw + 2) * 2);
	c->tgly = malloc(sizeof(glyph_dist) * setsize);
	c->cset = malloc(sizeof(glyph_dist) * setsize);
	if (!c->src || !c->dest || !c->hist || !c->tgly || !c->cset) {
		free(c->cset);
		free(c->tgly);
		free(c->hist);
		free(c->dest);
		free(c->src);
		free(c);
		return NULL;
	}
	return c;
}

static int ccmp (const void *av, const void *bv) {
	const unsigned char *a = av;
	const unsigned char *b = bv;
	if (*a < *b) return -1;
	if (*a > *b) return 1;
	return 0;
}

static int gdist (const mzx_glyph a, const mzx_glyph b) {
	int i, x, y, res;
	static int init = 0;
	static int dist[256][256];
	const unsigned char swap[4] = {0, 2, 1, 3};
	if (!init) {
		for (y = 0; y < 4; y++)
			for (x = 0; x < 4; x++)
				dist[x][y] = (swap[x] - swap[y]) * (swap[x] - swap[y]);
		for (y = 0; y < 256; y++) {
			for (x = 0; x < 256; x++) {
				dist[x][y] = dist[x&3][y&3] + dist[(x>>2)&3][(y>>2)&3]
					+ dist[(x>>4)&3][(y>>4)&3] + dist[x>>6][y>>6];
			}
		}
		init = 1;
	}
	res = 0;
	for (i = 0; i < 14; i++)
		res += dist[a[i]][b[i]];
	return res;
}

static int grdist (const mzx_glyph a, const mzx_glyph b) {
	int i, x, y, res;
	static int init = 0;
	static int dist[256][256];
	const unsigned char swap[4] = {0, 2, 1, 3};
	if (!init) {
		for (y = 0; y < 4; y++)
			for (x = 0; x < 4; x++)
				dist[x][y] = (swap[x] - swap[y]) * (swap[x] - swap[y]);
		for (y = 0; y < 256; y++) {
			for (x = 0; x < 256; x++) {
				dist[x][y] = dist[x&3][y&3] + dist[(x>>2)&3][(y>>2)&3]
					+ dist[(x>>4)&3][(y>>4)&3] + dist[x>>6][y>>6];
			}
		}
		init = 1;
	}
	res = 0;
	for (i = 0; i < 14; i++)
		res += dist[a[i]][b[i]^0xFF];
	return res;
}

static int gpdist (const mzx_glyph a, const mzx_glyph b) {
	int res, rev;
	res = gdist(a, b);
	rev = grdist(a, b);
	return (res < rev) ? res : rev;
}

static int gcmp (const void *av, const void *bv) {
	const mzx_glyph blank = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	const glyph_dist *a = av;
	const glyph_dist *b = bv;
	int ad, bd, i;
	if (a->d > b->d) return -1;
	if (a->d < b->d) return 1;
	ad = gdist(a->g, blank);
	bd = gdist(b->g, blank);
	if (ad < bd) return -1;
	if (ad > bd) return 1;
	for (i = 0; i < 14; i++) {
		if (a->g[i] < b->g[i]) return -1;
		if (a->g[i] > b->g[i]) return 1;
	}
	return 0;
}

int smzx_convert (smzx_converter *c, const rgba_color *img, mzx_tile *tile,
	mzx_glyph *chr, mzx_color *pal) {
	const mzx_glyph blank = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	const mzx_glyph full = {255, 255, 255, 255, 255, 255, 255, 255, 255,
		255, 255, 255, 255, 255};
	const rgba_color *ipx;
	rgba_color *spx;
	unsigned char *dpx, *hpx;
	int *epx;
	unsigned char gpal[16], rpal[256];
	unsigned char lut[256];
	unsigned char bg, fg, tmp;
	rgba_accum apal[256];
	int i, j, u, v, x, y;

	if (!c) return 0;

	memset(apal, 0, sizeof(rgba_accum) * 256);

	/* Half-width copy image */
	for (i = 0, ipx = img, spx = c->src; i < c->isiz;
		i++, ipx += 2, spx++) {
		spx->r = (ipx[0].r * ipx[0].a + ipx[1].r * ipx[1].a) / 2040;
		spx->g = (ipx[0].g * ipx[0].a + ipx[1].g * ipx[1].a) / 2040;
		spx->b = (ipx[0].b * ipx[0].a + ipx[1].b * ipx[1].a) / 2040;
		spx->a = 63;
	}
	/* Grayscale image */
	for (i = 0, spx = c->src, dpx = c->dest; i < c->isiz;
		i++, spx++, dpx++)
		*dpx = (spx->r * 30 + spx->g * 59 + spx->b * 11) * spx->a
			/ 6300;
	/* Create histogram-based grayscale palette */
	memcpy(c->hist, c->dest, c->isiz);
	qsort(c->hist, c->isiz, 1, ccmp);
	for (i = 0; i < c->clrlen; i++)
		gpal[i] = (c->hist[i*(c->isiz-1)/(c->clrlen-1)] * 2
			+ (c->hist[0] * (c->clrlen - 1 - i)
			+ c->hist[c->isiz-1] * i) / (c->clrlen - 1)) / 3;
	memmove(gpal + c->clroff, gpal, c->clrlen);
	/* Find min/max values for each tile and assign tile colors accordingly */
	for (v = 0; v < c->h; v++) {
		for (u = 0; u < c->w; u++) {
			bg = 255;
			fg = 0;
			dpx = c->dest + u * 4 + v * 14 * c->iw;
			for (y = 0; y < 14; y++) {
				for (x = 0; x < 4; x++) {
					tmp = dpx[x+y*c->iw];
					if (bg > tmp) bg = tmp;
					if (fg < tmp) fg = tmp;
				}
			}
			for (i = c->clroff; i < c->clroff + c->clrlen; i++)
				if (gpal[i] <= bg) tmp = i;
			tile[u+v*c->w].clr = tmp << 4;
			for (i = c->clroff + c->clrlen - 1; i >= c->clroff;
				i--)
				if (gpal[i] >= fg) tmp = i;
			tile[u+v*c->w].clr |= tmp;
		}
	}
	/* Create SMZX interpolated palette from tile histograms */
	for (i = c->clroff; i < c->clroff + c->clrlen; i++)
		rpal[(i<<4)|i] = gpal[i];
	for (i = c->clroff; i < c->clroff + c->clrlen - 1; i++) {
		for (j = i + 1; j < c->clroff + c->clrlen; j++) {
			hpx = c->hist;
			for (x = v = 0; v < c->h; v++) for (u = 0; u < c->w; u++, x++) {
				if ((tile[x].clr == ((i << 4) | j))
					|| (tile[x].clr == ((j << 4) | i))) {
					dpx = c->dest + u * 4 + v * 14 * c->iw;
					for (y = 0; y < 14; y++, hpx += 4, dpx += c->iw)
						memcpy(hpx, dpx, 4);
				}
			}
			x = hpx - c->hist;
			if (x) {
				qsort(c->hist, x, 1, ccmp);
				rpal[(j<<4)|i] = (c->hist[(x-1)/3] * 2
					+ (c->hist[0] * 2 + c->hist[x-1]) / 3)
					/ 3;
				rpal[(i<<4)|j] = (c->hist[2*(x-1)/3] * 2
					+ (c->hist[0] + c->hist[x-1] * 2) / 3)
					/ 3;
			} else {
				rpal[(j<<4)|i] = (gpal[i] * 2 + gpal[j]) / 3;
				rpal[(i<<4)|j] = (gpal[i] + gpal[j] * 2) / 3;
			}
		}
	}
	/* Create characters for each tile (w/ Floyd-Steinberg dithering) */
	memset(c->lerr + c->iw + 1, 0, sizeof(int) * c->iw);
	for (y = 0, dpx = c->dest, hpx = c->hist; y < c->ih; y++) {
		v = y / 14;
		memcpy(c->lerr, c->lerr + c->iw + 1, sizeof(int) * c->iw);
		memset(c->lerr + c->iw + 1, 0, sizeof(int) * c->iw);
		for (x = 0, epx = c->lerr; x < c->iw; x++, dpx++, hpx++, epx++) {
			u = x / 4;
			tmp = tile[u+v*c->w].clr;
			bg = tmp >> 4;
			fg = tmp & 15;
			tmp = (rpal[(bg<<4)|bg] + rpal[(fg<<4)|fg]) / 2;
			*epx += *dpx;
			if (*epx >= tmp) {
				tmp = (rpal[(bg<<4)|fg] + rpal[(fg<<4)|fg]) / 2;
				if (*epx >= tmp) *hpx = (fg << 4) | fg;
				else *hpx = (bg << 4) | fg;
			} else {
				tmp = (rpal[(bg<<4)|bg] + rpal[(fg<<4)|bg]) / 2;
				if (*epx >= tmp) *hpx = (fg << 4) | bg;
				else *hpx = (bg << 4) | bg;
			}
			if (gpal[bg] == gpal[fg]) *hpx = (bg << 4) | bg;
			*epx -= rpal[*hpx];
			epx[1] += *epx * 7 / 16;
			epx[c->iw] += *epx * 3 / 16;
			epx[c->iw+1] += *epx * 5 / 16;
			epx[c->iw+2] += *epx - (*epx * 15 / 16);
		}
	}
	/* Convert characters to SMZX format */
	for (v = 0; v < c->h; v++) {
		for (u = 0; u < c->w; u++) {
			hpx = c->hist + u * 4 + v * 14 * c->iw;
			tmp = tile[u+v*c->w].clr;
			bg = tmp >> 4;
			fg = tmp & 15;
			lut[(bg<<4)|bg] = 0;
			lut[(fg<<4)|bg] = 2;
			lut[(bg<<4)|fg] = 1;
			lut[(fg<<4)|fg] = 3;
			for (y = 0; y < 14; y++, hpx += c->iw)
				c->tgly[u+v*c->w].g[y] = (lut[hpx[0]] << 6)
					| (lut[hpx[1]] << 4)
					| (lut[hpx[2]] << 2) | lut[hpx[3]];
			c->tgly[u+v*c->w].w = abs(gpal[fg] - gpal[bg]);
		}
	}
	/* Reduce character set to specified size */
	memcpy(c->cset, c->tgly, sizeof(glyph_dist) * c->tsiz);
	for (i = 0; i < c->tsiz; i++) {
		c->cset[i].d = 32767;
		if (gdist(c->cset[i].g, blank) > gdist(c->cset[i].g, full))
			for (j = 0; j < 14; j++)
				c->cset[i].g[j] ^= 0xFF;
	}
	qsort(c->cset, c->tsiz, sizeof(glyph_dist), gcmp);
	for (i = 0; i < c->tsiz - 1; i++) {
		for (j = i + 1; j < c->tsiz; j++) {
			x = gpdist(c->cset[i].g, c->cset[j].g);
			if ((unsigned int)x < c->cset[j].d) c->cset[j].d = x;
		}
	}
	for (i = 0; i < c->tsiz; i++)
		c->cset[i].d *= c->cset[i].w;
	qsort(c->cset, c->tsiz, sizeof(glyph_dist), gcmp);
	for (i = 0; i < c->chrprelen; i++)
		c->cset[i].d = 0;
	qsort(c->cset, c->chrprelen, sizeof(glyph_dist), gcmp);
	memmove(c->cset + c->chroff, c->cset,
		sizeof(glyph_dist) * c->chrprelen);
	if (c->chrprelen < c->chrlen) {
		memmove(c->cset + c->chrskip + 1, c->cset + c->chrskip,
			sizeof(glyph_dist) * (c->chroff + c->chrprelen
			- c->chrskip));
		memset(c->cset + c->chrskip, 0, sizeof(glyph_dist));
	}
	for (i = 0; i < c->chrlen; i++)
		memcpy(chr[i], c->cset[i+c->chroff].g, sizeof(mzx_glyph));
	/* Map tiles to characters from reduced set */
	for (i = u = 0; i < c->tsiz; i++) {
		x = gdist(c->tgly[i].g, c->cset->g) + 1;
		for (j = c->chroff; j < c->chroff + c->chrlen; j++) {
			if (j == c->chrskip) continue;
			y = gdist(c->tgly[i].g, c->cset[j].g);
			if (y < x) {
				x = y;
				tile[i].chr = j;
				u = 0;
			}
			y = grdist(c->tgly[i].g, c->cset[j].g);
			if (y < x) {
				x = y;
				tile[i].chr = j;
				u = 1;
			}
		}
		if (u) tile[i].clr = (tile[i].clr << 4) | (tile[i].clr >> 4);
	}
	/* Draw resulting grayscale image */
	for (v = 0; v < c->h; v++) {
		for (u = 0; u < c->w; u++) {
			hpx = c->hist + u * 4 + v * 14 * c->iw;
			tmp = tile[u+v*c->w].clr;
			bg = tmp >> 4;
			fg = tmp & 15;
			lut[0] = (bg << 4) | bg;
			lut[2] = (fg << 4) | bg;
			lut[1] = (bg << 4) | fg;
			lut[3] = (fg << 4) | fg;
			for (y = 0; y < 14; y++, hpx += c->iw) {
				hpx[0] = lut[c->cset[tile[u+v*c->w].chr].g[y]>>6];
				hpx[1] = lut[(c->cset[tile[u+v*c->w].chr].g[y]>>4)&3];
				hpx[2] = lut[(c->cset[tile[u+v*c->w].chr].g[y]>>2)&3];
				hpx[3] = lut[c->cset[tile[u+v*c->w].chr].g[y]&3];
			}
		}
	}
	/* Calculate average color of pixels assigned to each index */
	for (i = 0, spx = c->src, dpx = c->dest, hpx = c->hist; i < c->isiz;
		i++, spx++, dpx++, hpx++) {
			apal[*hpx].r += spx->r;
			apal[*hpx].g += spx->g;
			apal[*hpx].b += spx->b;
			apal[*hpx].c += *dpx;
	}
	for (i = 0; i < 256; i++) {
		bg = i >> 4;
		fg = i & 15;
		if (bg < c->clroff) continue;
		if (fg < c->clroff) continue;
		if (bg >= c->clroff + c->clrlen) continue;
		if (fg >= c->clroff + c->clrlen) continue;
		if (apal[i].c) {
			apal[i].r = apal[i].r * rpal[i] / apal[i].c;
			apal[i].g = apal[i].g * rpal[i] / apal[i].c;
			apal[i].b = apal[i].b * rpal[i] / apal[i].c;
			if (apal[i].r > 63) apal[i].r = 63;
			if (apal[i].g > 63) apal[i].g = 63;
			if (apal[i].b > 63) apal[i].b = 63;
		} else
			apal[i].r = apal[i].g = apal[i].b = rpal[i];
		pal[i].r = apal[i].r;
		pal[i].g = apal[i].g;
		pal[i].b = apal[i].b;
	}
	return 1;
}

void smzx_convert_free (smzx_converter *c) {
	if (!c) return;
	free(c->src);
	free(c->dest);
	free(c->hist);
	free(c->tgly);
	free(c->cset);
	free(c);
}


code:
/* Copyright (C) 2010 Alan Williams <mralert@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
 */

#ifndef __SMZXCONV_H
#define __SMZXCONV_H

typedef struct {
	unsigned char r;
	unsigned char g;
	unsigned char b;
	unsigned char a;
} rgba_color;

typedef struct {
	unsigned char chr;
	unsigned char clr;
} mzx_tile;

typedef unsigned char mzx_glyph[14];

typedef struct {
	unsigned char r;
	unsigned char g;
	unsigned char b;
} mzx_color;

typedef struct _smzx_converter smzx_converter;

smzx_converter *smzx_convert_init (int w, int h, int chroff, int chrskip,
	int chrlen, int clroff, int clrlen);
int smzx_convert (smzx_converter *c, const rgba_color *img, mzx_tile *tile,
	mzx_glyph *chr, mzx_color *pal);
void smzx_convert_free (smzx_converter *c);

#endif /* __SMZXCONV_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
 */

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

// Cleaned up/revised txt2hlp.cpp for compiling outside of DOS

static void fputw(int src, FILE *fp)
{
  fputc(src & 0xFF, fp);
  fputc(src >> 8, fp);
}

// Custom fgetc function to dump \r's and replace \xFF's and /x1's.

static int fgetc2(FILE *fp)
{
  int current_char = fgetc(fp);
  switch(current_char)
  {
    case '\r':
    {
      return fgetc2(fp);
    }

    case 255:
    {
      return 32;
    }

    case 1:
    {
      return 2;
    }

    default:
    {
      return current_char;
    }
  }
}

#define fgetc(f) fgetc2(f)

int main(int argc, char *argv[])
{
  int current_char;
  int t1, t2, t3;
  char tstr[81] = "";
  int tlong;
  int i;

  // Source/dest files open. Now scan source for number of filenames.
  // A new file is designated by a # as the first character of a line.

  int num_files = 1;

  // Count context sensitive links too. A link is designated by a : as
  // the first character of a line, followed by three numerals, followed
  // by the end of the line. (a \n)

  int num_links = 0;

  FILE *source;
  FILE *dest;

  char curr_file[13]="MAIN.HLP";
  char max_file[13]="(none)";
  long file_offs;
  long file_len=1;//For first byte
  long curr_file_storage=2;//Location to store current file's info
  int curr_lnum=1;//For error/warning info
  //Links info-
  int curr_link_ref=0;//Ref- not actual link number
  int link_numbers[1000];//Actual numbers of the links
  long link_offsets[1000];//Offsets within current file
  //Current line info-
  int line_len=0;//Used for warnings
  //Global info-
  long biggest_file=0;//For help allocation

  printf("\n\n");

  if(argc < 3)
  {
    printf("Usage: TXT2HLP source.txt output.fil\n");
    return 1;
  }

  source = fopen(argv[1], "rb");

  if(source == NULL)
  {
    printf("Error opening %s for input.\n", argv[1]);
    return -1;
  }

  dest = fopen(argv[2],"wb");

  if(dest == NULL)
  {
    fclose(source);
    printf("Error opening %s for output.\n", argv[2]);
    return 1;
  }

  do
  {
    current_char = fgetc(source);

    switch(current_char)
    {
      case '#':
      {
        num_files++;
        break;
      }

      case ':':
      {
        int link;

        // Get upcoming link
        fscanf(source, "%3d", &link);

        // Get next char
        current_char = fgetc(source);

        if(((current_char == '\n') || (current_char == ':')) &&
         link >= num_links)
        {
          num_links = link + 1;
        }
      }
    }

    // Get the rest of the line
    while((current_char != '\n') && (current_char != -1))
    {
      current_char = fgetc(source);
    }
  } while(current_char != -1);

  printf("Files: %d  Links: %d\n", num_files, num_links);

  // Number of files obtained. Write header, with room, to dest. This
  // includes number of links and blank spaces to add in filenames and
  // link info.

  fputw(num_files, dest);
  for(i = 0; i < num_files; i++)
  {
    fwrite("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 21, 1, dest);
  }

  fputw(num_links, dest);
  if(num_links > 0)
  {
    for(i = 0; i < num_links; i++)
    {
      fwrite("\0\0\0\0\0\0\0\0\0\0\0\0", 12, 1, dest);
    }
  }

  file_offs=ftell(dest);

  //Reset position on source...
  fseek(source,0,SEEK_SET);
  //...and begin our parse! New files start with a 1.
  fputc(1,dest);
  printf("Processing file %s...\n",curr_file);

  do
  {
    //Our loop. Read first char of current line
    line_len=0;
    t1=fgetc(source);
    switch(t1)
    {
      case '$'://Centered line- write prefix...
        fputc(255,dest); fputc('$',dest); file_len+=2;
        //...and read first character...
      case '.'://Normal line- read first character
        mesg://Jump point to copy until and through an \n
        t1=fgetc(source);
      default://Normal line (first char in t1)
        //Copy until a \n
        if(t1=='\n') goto blanko;
        while(!feof(source))
        {
          fputc(t1,dest); file_len++;
          line_len++;
          t1=fgetc(source);
          if(t1=='\n') break;
        }
        //Now put an \n
        blanko:
        fputc('\n',dest); file_len++;
        break;
      case '>'://Choice of some type
        fputc(255,dest); file_len++;
        //See if dest is normal or file
        t1=fgetc(source);
        if(t1=='#')
        {
          //File.
          fputc('<',dest); file_len++;
          //Grab name.
          t2=t3=0;//t2 counts str size, t3 counts colons passed
          do
          {
            t1=fgetc(source);
            if((t1==':')&&t3) break;
            if(t1=='\n') break;
            if(t1==':') t3++;
            tstr[t2++]=t1;
          } while(!feof(source));
          tstr[t2]=0;
          if(t2==10) //10 len not allowed
            printf("Error- Label length of 10 on line %d, file %s.\n",
                   curr_lnum,curr_file);
          fputc(t2,dest); file_len++;
          fwrite(tstr,++t2,1,dest); file_len+=t2;
          //Now do actual message after the label/file.
          goto mesg;
        }
        //Non-file.
        fputc('>',dest); file_len++;
        //Grab name.
        t2=0;//t2 counts str size
        do
        {
          if((t1==':')||(t1=='\n')) break;
          tstr[t2++]=t1;
          t1=fgetc(source);
        } while(!feof(source));
        tstr[t2]=0;
        if(t2==1) //1 len not allowed
          printf("Error- Label length of 1 on line %d, file %s.\n",
                 curr_lnum,curr_file);
        if(t2==10) //10 len not allowed
          printf("Error- Label length of 10 on line %d, file %s.\n",
                 curr_lnum,curr_file);
        fputc(t2,dest); file_len++;
        fwrite(tstr,++t2,1,dest); file_len+=t2;
        //Now do actual message after the label.
        goto mesg;
      case ':'://Label, possibly with a message attached
        fputc(255,dest); fputc(':',dest); file_len+=2;
        //Grab name.
        t2=0;//Counts str length
        do
        {
          t1=fgetc(source);
          if((t1==':')||(t1=='\n')) break;
          tstr[t2++]=t1;
        } while(!feof(source));
        tstr[t2]=0;
        if(t2==1) //1 len not allowed
          printf("Error- Label length of 1 on line %d, file %s.\n",
                 curr_lnum,curr_file);
        if(t2==10) //10 len not allowed
          printf("Error- Label length of 10 on line %d, file %s.\n",
                 curr_lnum,curr_file);
        fputc(t2,dest); file_len++;
        fwrite(tstr,++t2,1,dest); file_len+=t2;
        //Is this a context-link?
        if((t2==4)&&(isdigit(tstr[0]))&&
           (isdigit(tstr[1]))&&(isdigit(tstr[2])))
        {
          //Yep.
          t3=atoi(tstr);
          link_numbers[curr_link_ref]=t3;
          link_offsets[curr_link_ref++]=file_len-7;
        }
        //Is there a message next?
        if(t1==':') goto mesg;//Yep.
        //Nope- Just output an EOL.
        fputc('\n',dest); file_len++;
        break;
      case '#'://New file
        //First, end current
        fputc(0,dest); file_len++;
        if(file_len>biggest_file)
        {
          biggest_file=file_len;
          strncpy(max_file,curr_file,12);
          max_file[12] = '\0';
        }
        //Put it's info at the start
        tlong=ftell(dest);
        fseek(dest,curr_file_storage,SEEK_SET);
        fwrite(curr_file,1,13,dest);
        fwrite(&file_offs,1,4,dest);
        fwrite(&file_len,1,4,dest);
        if(file_len>65535) printf("Warning- File %s over 64k bytes in length.\n",curr_file);
        //Write in links info
        for(t1=0;t1<curr_link_ref;t1++)
        {
          fseek(dest,4+num_files*21+link_numbers[t1]*12,SEEK_SET);
          fwrite(&file_offs,1,4,dest);
          fwrite(&file_len,1,4,dest);
          fwrite(&link_offsets[t1],1,4,dest);
        }
        //Start next file
        fseek(dest,tlong,SEEK_SET);
        file_offs=tlong;
        file_len=curr_lnum=1;
        curr_file_storage+=21;
        curr_link_ref=0;
        fputc(1,dest);
        //Get new filename
        t2=0;//Counts str length
        do
        {
          t1=fgetc(source);
          if(t1=='\n') break;
          tstr[t2++]=t1;
        } while(!feof(source));
        tstr[t2]=0;
        if(t2>12) tstr[12]=0;
        //Copy over new filename
        strncpy(curr_file,tstr,12);
        curr_file[12] = '\0';
        //Next file ready to roar!
        printf("Processing file %s...\n",curr_file);
        break;
      case '@'://End of help.doc file
        goto alldone;
    }
    if(line_len>64)
      printf("Warning: Line %d over 64 chars in file %s.\n",curr_lnum,curr_file);
    curr_lnum++;
    //Done with this line.
  } while(!feof(source));
  alldone:
  //End last file
  fputc(0,dest); file_len++;
  if(file_len>biggest_file)
  {
    biggest_file=file_len;
    strncpy(max_file,curr_file,12);
    max_file[12] = '\0';
  }
  //Put it's info at the start
  fseek(dest,curr_file_storage,SEEK_SET);
  fwrite(curr_file,1,13,dest);
  fwrite(&file_offs,1,4,dest);
  fwrite(&file_len,1,4,dest);
  if(file_len>65535) printf("Warning- File %s over 64k bytes in length.\n",curr_file);
  //Write in links info
  for(t1=0;t1<curr_link_ref;t1++)
  {
    fseek(dest,4+num_files*21+link_numbers[t1]*12,SEEK_SET);
    fwrite(&file_offs,1,4,dest);
    fwrite(&file_len,1,4,dest);
    fwrite(&link_offsets[t1],1,4,dest);
  }
  //All done! Close files.
  fclose(dest);
  fclose(source);
  printf("Done! Biggest file- %ld bytes. (%s)\n\n",biggest_file,max_file);
  return 0;
}


code:
/* MegaZeux
 *
 * 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
 */

/*********************************
 * LEGACY FILE FORMAT VALIDATION *
 *********************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/stat.h>

#include "validation.h"
#include "world.h"
#include "error.h"
#include "window.h"
#include "const.h"
#include "util.h"
#include "legacy_rasm.h"

/****************************
 * LEGACY WORLD FORMAT INFO *
 ****************************/

/* world:
 * 25 name
 * 1  protected?
 * 3  magic
 *
 * 4129 block 1
 * 3584 char set
 *  455 charid table
 *   90 status counters
 *
 *   72 block 2
 *   24 global info settings
 *   48 palette
 *
 *@4230:
 * ???? block 3 -- board and robot data, and custom sfx
 *    4 global robot offset
 *+   1 number of boards?  if that byte was actually 0:
 *    {
 *      2 sfx size
 *+    50 sfx strings up to 68 chars long each
 *      1 number of boards
 *    }
 *+  25 times the number of boards -- board names
 *    8 times the number of boards -- size, offset
 */

/* save (2.84):
 *    5 save magic
 *    2 world magic
 *    1 current board
 *
 * (block 1)
 *
 *+  73 save block 1
 *   71 keys, potion timers, save player position data, misc
 *+   2 real_mod_playing, up to 514
 *
 * (block 2)
 *
 *+7508 save block 2
 *   24 intensity, faded, coords, player under
 *+   4 counters, up to 2^31 * counter size
 *+   4 strings, up to 2^31 * max string size
 * 4612 sprites
 *(4096   data
 *    4   info
 *  512   collision list)
 *   12 misc settings
 *+   2 fread filename, up to 514
 *    4 fread pos
 *+   2 fwrite filename, up to 514
 *    4 fwrite pos
 *    2 screen mode
 *  768 smzx palette
 *    4 commands
 *+   8 vlayer size, w, h; then data: vlayer size (up to 2^31) * 2
 *
 * (block 3)
 */

#ifndef WORLD_GLOBAL_OFFSET_OFFSET
#define WORLD_GLOBAL_OFFSET_OFFSET 4230
#define WORLD_BLOCK_1_SIZE 4129
#define WORLD_BLOCK_2_SIZE 72
#endif

int suppress_errors = -1;

/* error messages */
void val_error(enum val_error error_id, int value)
{
  char error_mesg[80];
  int hi = (value & 0xFF00) >> 8, lo = (value & 0xFF);
  int severity = 1;
  int code = 0;

  switch (error_id)
  {
    case FILE_DOES_NOT_EXIST:
    {
      sprintf(error_mesg, "File doesn't exist!");
      code = 0x0D01;
      break;
    }
    case SAVE_FILE_INVALID:
    {
      sprintf(error_mesg, "File is not a valid .SAV file or is corrupt");
      code = 0x2101;
      break;
    }
    case SAVE_VERSION_OLD:
    {
      snprintf(error_mesg, 80,
       ".SAV files from older versions of MZX (%d.%d) are not supported", hi, lo);
      code = 0x2101;
      break;
    }
    case SAVE_VERSION_TOO_RECENT:
    {
      snprintf(error_mesg, 80,
       ".SAV files from newer versions of MZX (%d.%d) are not supported", hi, lo);
      code = 0x2101;
      break;
    }
    case WORLD_FILE_INVALID:
    {
      sprintf(error_mesg, "File is not a valid world file or is corrupt");
      code = 0x0D02;
      break;
    }
    case WORLD_FILE_VERSION_OLD:
    {
      snprintf(error_mesg, 80,
       "World is from old version (%d.%d); use converter", hi, lo);
      code = 0x0D02;
      break;
    }
    case WORLD_FILE_VERSION_TOO_RECENT:
    {
      snprintf(error_mesg, 80,
       "World is from a more recent version (%d.%d)", hi, lo);
      code = 0x0D02;
      break;
    }
    case WORLD_PASSWORD_PROTECTED:
    {
      sprintf(error_mesg, "This world may be password protected.");
      code = 0x0D02;
      break;
    }
    case WORLD_LOCKED:
    {
      sprintf(error_mesg, "Cannot load password protected world.");
      code = 0x0D02;
      break;
    }
    case WORLD_BOARD_MISSING:
    {
      snprintf(error_mesg, 80,
       "Board @ %Xh could not be found", value);
      code = 0x0D03;
      break;
    }
    case WORLD_BOARD_CORRUPT:
    {
      snprintf(error_mesg, 80,
       "Board @ %Xh is irrecoverably truncated or corrupt", value);
      code = 0x0D03;
      break;
    }
    case WORLD_BOARD_TRUNCATED_SAFE:
    {
      snprintf(error_mesg, 80,
       "Board @ %Xh is truncated, but could be partially recovered", value);
      code = 0x0D03;
      break;
    }
    case WORLD_ROBOT_MISSING:
    {
      snprintf(error_mesg, 80,
       "Robot @ %Xh could not be found", value);
      code = 0x0D03;
      break;
    }
    case BOARD_FILE_INVALID:
    {
      sprintf(error_mesg, "File is not a board file or is corrupt");
      code = 0x4040;
      break;
    }
    case BOARD_ROBOT_CORRUPT:
    case BOARD_SCROLL_CORRUPT:
    case BOARD_SENSOR_CORRUPT:
    {
      snprintf(error_mesg, 80,
       "Robot @ %Xh is truncated or corrupt", value);
      code = 0x0D03;
      break;
    }
    case MZM_FILE_INVALID:
    {
      sprintf(error_mesg, "File is not an MZM or is corrupt");
      code = 0x6660;
      break;
    }
    case MZM_FILE_FROM_SAVEGAME:
    {
      sprintf(error_mesg, "Runtime robots incompatible with editor -- robots will be dummied out");
      code = 0x6661;
      break;
    }
    case MZM_FILE_VERSION_TOO_RECENT:
    {
      snprintf(error_mesg, 80,
       "MZM file of a more recent version (%d.%d) -- robots will be dummied out", hi, lo);
      code = 0x6661;
      break;
    }
  }

  if(severity > suppress_errors)
    error(error_mesg, 1, 8, code);
}

FILE * val_fopen(const char *filename)
{
  struct stat stat_result;
  int stat_op_result = stat(filename, &stat_result);
  FILE *f;

  if(stat_op_result)
    return NULL;

  if(!S_ISREG(stat_result.st_mode))
    return NULL;

  if(!(f = fopen_unsafe(filename, "rb")))
    return NULL;

  return f;
}

void set_validation_suppression(int level)
{
  suppress_errors = level;
}

/* This is a lot like try_load_world but much more thorough, and doesn't
 * pass data through or leave a file open.  This needs to be done before
 * any data is ever loaded, so that Megazeux can cleanly abort if there
 * is an issue.
 */
enum val_result validate_world_file(const char *filename,
 int savegame, int *end_of_global_offset, int decrypt_attempted)
{
  enum val_result result = VAL_SUCCESS;

  FILE *f;
  char magic[15];
  int num_boards;
  int board_name_offset;
  int v, i;

  /* TEST 1:  Make sure it's a readable file */
  if(!(f = val_fopen(filename)))
  {
    val_error(FILE_DOES_NOT_EXIST, 0);
    result = VAL_MISSING;
    goto err_out;
  }

  /* TEST 2:  Is it a save file? */
  if(savegame)
  {
    int screen_mode, num_counters, num_strings, len;

    if(fread(magic, 5, 1, f) != 1)
      goto err_invalid;

    v = save_magic(magic);
    if(!v)
      goto err_invalid;

    else if (v > WORLD_VERSION)
    {
      val_error(SAVE_VERSION_TOO_RECENT, v);
      result = VAL_VERSION;
      goto err_close;
    }
    else if (v < WORLD_VERSION)
    {
      val_error(SAVE_VERSION_OLD, v);
      result = VAL_VERSION;
      goto err_close;
    }

    /* TEST 3:  Check for truncation, savegame style, hope this
     * doesn't explode :erm:
     */
    if(
     fseek(f, 8, SEEK_SET) ||
     fseek(f, WORLD_BLOCK_1_SIZE, SEEK_CUR) ||
     fseek(f, 71, SEEK_CUR) ||
     fseek(f, (len = fgetw(f)), SEEK_CUR) ||
     (len < 0) ||
     fseek(f, WORLD_BLOCK_2_SIZE, SEEK_CUR) ||
     fseek(f, 24, SEEK_CUR))
    {
      debug("pre-counters\n");
      goto err_invalid;
    }

    //do counters - vvvvnnnn(name)
    num_counters = fgetd(f);
    if(num_counters < 0)
    {
      debug("counter num\n");
      goto err_invalid;
    }

    for(i = 0; i < num_counters; i++)
    {
      if(
       fseek(f, 4, SEEK_CUR) || //value
       fseek(f, (len = fgetd(f)), SEEK_CUR) ||
       (len < 0))
      {
        debug("counters\n");
        goto err_invalid;
      }
    }

    //do strings-   nnnnllll(name)(value)
    num_strings = fgetd(f);
    if(num_strings < 0)
    {
      debug("string num\n");
      goto err_invalid;
    }

    for(i = 0; i < num_strings; i++)
    {
      int name_length = fgetd(f);
      int value_length = fgetd(f);
      if(
       (name_length < 0) ||
       (value_length < 0) ||
       fseek(f, name_length, SEEK_CUR) ||
       fseek(f, value_length, SEEK_CUR))
      {
        debug("strings\n");
        goto err_invalid;
      }
    }

    if(
     fseek(f, 4612, SEEK_CUR) || //sprites
     fseek(f, 12, SEEK_CUR) || //misc
     fseek(f, fgetw(f), SEEK_CUR) || //fread_open
     fseek(f, 4, SEEK_CUR) || //fread_pos
     fseek(f, fgetw(f), SEEK_CUR) || //fwrite_open
     fseek(f, 4, SEEK_CUR)) //fwrite_pos
    {
      debug("post strings\n");
      goto err_invalid;
    }

    screen_mode = fgetw(f);
    if((screen_mode > 3) || (screen_mode > 1 &&
     fseek(f, 768, SEEK_CUR))) //smzx palette
    {
      debug("smzx palette\n");
      goto err_invalid;
    }

    if(
     fseek(f, 4, SEEK_CUR) || //commands
     ((len = fgetd(f)) < 0) || //vlayer size
     fseek(f, 4, SEEK_CUR) || // width & height
     fseek(f, len, SEEK_CUR) || //chars
     fseek(f, len, SEEK_CUR)) //colors
    {
      debug("vlayer\n");
      goto err_invalid;
    }

    /* now we should be at the global robot pointer! */
  }

  else /* !savegame */
  {
    int protection_method;

    /* TEST 3:  Check for truncation */
    if(fseek(f, WORLD_GLOBAL_OFFSET_OFFSET, SEEK_SET))
      goto err_invalid;

    fseek(f, BOARD_NAME_SIZE, SEEK_SET);

    /* TEST 4:  If we think it's locked, try to decrypt it. */
    protection_method = fgetc(f);
    if(protection_method > 0)
    {
      if(protection_method > 3)
        goto err_invalid;

      if(decrypt_attempted) // In the unlikely event that this will happen again
        goto err_invalid;

      val_error(WORLD_PASSWORD_PROTECTED, 0);

      if(!confirm(NULL, "Would you like to decrypt it?"))
      {
        result = VAL_NEED_UNLOCK;
        goto err_close;
      }
      else
      {
        val_error(WORLD_LOCKED, 0);
        result = VAL_ABORTED;
        goto err_close;
      }
    }

    /* TEST 5:  Make sure the magic is awwrightttt~ */
    fread(magic, 1, 3, f);

    v = world_magic(magic);
    if(v == 0)
      goto err_invalid;

    else if (v < 0x0205)
    {
      val_error(WORLD_FILE_VERSION_OLD, v);
      result = VAL_VERSION;
      goto err_close;
    }
    else if (v > WORLD_VERSION)
    {
      val_error(WORLD_FILE_VERSION_TOO_RECENT, v);
      result = VAL_VERSION;
      goto err_close;
    }

    /* TEST 6:  Attempt to eliminate invalid files by
     * testing the palette for impossible values.
     */
    fseek(f, WORLD_GLOBAL_OFFSET_OFFSET - 48, SEEK_SET);
    for(i = 0; i<48; i++)
    {
      int val = fgetc(f);
      if((val < 0) || (val > 63))
        goto err_invalid;
    }

    /* now we should be at the global robot pointer! */
  }

  /* TEST 7:  Either branch should be at the global robot pointer now.
   * Test for valid SFX structure, if applicable, and board information.
   */
  fseek(f, 4, SEEK_CUR);

  // Do the sfx
  num_boards = fgetc(f);
  if(num_boards == 0)
  {
    int sfx_size = fgetw(f);
    int sfx_off = ftell(f);

    for (i = 0; i < NUM_SFX; i++)
    {
      if(fseek(f, fgetc(f), SEEK_CUR))
        break;
    }

    if((i != NUM_SFX) || ((ftell(f) - sfx_off) != sfx_size))
      goto err_invalid;

    num_boards = fgetc(f);
  }
  if(num_boards == 0)
    goto err_invalid;

  board_name_offset = ftell(f);

  //Make sure board name and pointer data exists
  if(
   fseek(f, num_boards * BOARD_NAME_SIZE, SEEK_CUR) ||
   fseek(f, num_boards * 8, SEEK_CUR) ||
   ((ftell(f) - board_name_offset) != num_boards * (BOARD_NAME_SIZE + 8)))
    goto err_invalid;

  /* If any of the pointers are less than this pos we probably
   * aren't dealing with a valid world, but it's not our job
   * to figure that out right now, so we'll pass it back.
   */
  if(end_of_global_offset)
    *end_of_global_offset = ftell(f);

  //todo: maybe have a complete fail when N number of pointers fail?

  goto err_close;

err_invalid:
  result = VAL_INVALID;
  if(savegame)
    val_error(SAVE_FILE_INVALID, 0);
  else
    val_error(WORLD_FILE_INVALID, 0);
err_close:
  fclose(f);
err_out:
  return result;
}

// This is part of robot validation, don't give any messages.
/*enum val_result validate_legacy_bytecode(char *bc, int program_length)
{
  int i = 1;
  int cur_command_start, cur_command_length, cur_param_length;

  if(!bc)
    goto err_invalid;

  // First -- fix the odd robots that appear in old MZX games,
  // such as Catacombs of Zeux.
  if((program_length == 2) || (bc[0] != 0xFF))
  {
    bc[0] = 0xFF;
    bc[1] = 0x0;
  }

  if(bc[0] != 0xFF)
    goto err_invalid;

  // One iteration should be a single command.
  while(1)
  {
    cur_command_length = bc[i];
    i++;
    if(cur_command_length == 0)
      break;

    cur_command_start = i;

    if((i + cur_command_length) > program_length)
      goto err_invalid;

    if(bc[i + cur_command_length] != cur_command_length)
      goto err_invalid;

    i++;

    while(i < cur_command_start + cur_command_length)
    {
      cur_param_length = bc[i];
      if(cur_param_length == 0)
        cur_param_length = 2;

      i += cur_param_length + 1;
    }
    i++;

    if(i != cur_command_start + cur_command_length + 1)
      goto err_invalid;

    if(i > program_length)
      goto err_invalid;
  }

  if(i < program_length)
  {
    debug("Robot checked for %i but program length is %i; extra wiped\n",
     program_length, i);
    memset(bc + i, '\0', program_length-i);
  }

  if(i > program_length)
    goto err_invalid;

  return VAL_SUCCESS;

err_invalid:
  {
    int n;
    char hex_seg[4];
    char *err_mesg = cmalloc(sizeof(char) * ((cur_command_length + 2) * 3 + 2));
    err_mesg[0] = 0;

    for(n = cur_command_start - 1;
     n < (cur_command_start + cur_command_length + 1) &&
     n < program_length;
     n++)
    {
      snprintf(hex_seg, 4, "%X ", bc[n]);
      strcat(err_mesg, hex_seg);
    }

    debug("Prog len: %i    i: %i   bc[0]: %i   bc[1]: %i\n",
     program_length, i, bc[0], bc[1]);

    debug("Bytecode: %s\n\n", err_mesg);

    free(err_mesg);
  }

  return VAL_INVALID;
}*/


code:
/* MegaZeux
 *
 * 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
 */

/* VALIDATION.H- Declarations for VALIDATION.C */

#ifndef __VALIDATION_H
#define __VALIDATION_H

#include "compat.h"

__M_BEGIN_DECLS

#include "world.h"
#include "const.h"

#include <stdlib.h>

enum val_result
{
  VAL_SUCCESS,
  VAL_VERSION,    // Version issue
  VAL_INVALID,    // Failed validation
  VAL_TRUNCATED,  // Passed validation until it hit EOF
  VAL_MISSING,    // file or ptr location in file does not exist
  VAL_ABORTED,    // Load aborted by user
  VAL_NEED_UNLOCK // Unlock world file.
};

enum val_error
{
  FILE_DOES_NOT_EXIST,
  SAVE_FILE_INVALID,
  SAVE_VERSION_OLD,
  SAVE_VERSION_TOO_RECENT,
  WORLD_FILE_INVALID,
  WORLD_FILE_VERSION_OLD,
  WORLD_FILE_VERSION_TOO_RECENT,
  WORLD_PASSWORD_PROTECTED,
  WORLD_LOCKED,
  WORLD_BOARD_MISSING,
  WORLD_BOARD_CORRUPT,
  WORLD_BOARD_TRUNCATED_SAFE,
  WORLD_ROBOT_MISSING,
  BOARD_FILE_INVALID,
  BOARD_ROBOT_CORRUPT,
  BOARD_SCROLL_CORRUPT,
  BOARD_SENSOR_CORRUPT,
  MZM_FILE_INVALID,
  MZM_FILE_FROM_SAVEGAME,
  MZM_FILE_VERSION_TOO_RECENT
};

CORE_LIBSPEC void val_error(enum val_error error_id, int value);
CORE_LIBSPEC FILE * val_fopen(const char *filename);

void set_validation_suppression(int level);

CORE_LIBSPEC enum val_result validate_world_file(const char *filename, int savegame,
 int *end_of_global_offset, int decrypt_attempted);
//CORE_LIBSPEC enum val_result validate_legacy_bytecode(char *bc, int program_length);

__M_END_DECLS

#endif // __VALIDATION_H