#include "main.h"

#define TYPE_KEY     0
#define TYPE_BOOL    1  /* int */
#define TYPE_INT     2
#define TYPE_FLOAT   3
#define TYPE_VECTOR  4
#define TYPE_COORD   5
#define TYPE_STRING  6
#define TYPE_WEAPON  7
#define TYPE_PATCH   8

#define LTRIM(str) while(*(str) == '\t' || *(str) == ' ') (str)++

struct key_alias
{
   int   key;
   char  *name;
};

struct ini_entry_data
{
   int            count;      /* number of times it has been set */
   void           *data;      /* teh value */
   const char     *def;       /* default */
};

struct ini_entry
{
   const char     *name;
   int            type;
   int            size;          /* number of data items (1 for non-arrays) */
   struct ini_entry_data *data;  /* size # of ini_entry_data */
};


struct settings set;

static struct key_alias key_alias[] =
{
   { VK_LBUTTON,    "lbutton" },
   { VK_RBUTTON,    "rbutton" },
   { VK_MBUTTON,    "mbutton" },
   { VK_BACK,       "backspace" },
   { VK_TAB,        "tab" },
   { VK_RETURN,     "return" },
   { VK_LSHIFT,     "lshift" },
   { VK_RSHIFT,     "rshift" },
   { VK_LCONTROL,   "lctrl" },
   { VK_RCONTROL,   "rctrl" },
   { VK_LMENU,      "lalt" },
   { VK_RMENU,      "ralt" },
   { VK_SPACE,      "space" },
   { VK_PRIOR,      "pageup" },
   { VK_NEXT,       "pagedn" },
   { VK_END,        "end" },
   { VK_HOME,       "home" },
   { VK_LEFT,       "left" },
   { VK_UP,         "up" },
   { VK_RIGHT,      "right" },
   { VK_DOWN,       "down" },
   { VK_INSERT,     "insert" },
   { VK_DELETE,     "delete" },
   { VK_PAUSE,      "pause" },
   { VK_NUMPAD0,    "np0" },
   { VK_NUMPAD1,    "np1" },
   { VK_NUMPAD2,    "np2" },
   { VK_NUMPAD3,    "np3" },
   { VK_NUMPAD4,    "np4" },
   { VK_NUMPAD5,    "np5" },
   { VK_NUMPAD6,    "np6" },
   { VK_NUMPAD7,    "np7" },
   { VK_NUMPAD8,    "np8" },
   { VK_NUMPAD9,    "np9" },
   { VK_MULTIPLY,   "multiply" },
   { VK_ADD,        "add" },
   { VK_SEPARATOR,  "separator" },
   { VK_SUBTRACT,   "subtract" },
   { VK_DECIMAL,    "decimal" },
   { VK_DIVIDE,     "divide" },
   { VK_F1,         "f1" },
   { VK_F2,         "f2" },
   { VK_F3,         "f3" },
   { VK_F4,         "f4" },
   { VK_F5,         "f5" },
   { VK_F6,         "f6" },
   { VK_F7,         "f7" },
   { VK_F8,         "f8" },
   { VK_F9,         "f9" },
   { VK_F10,        "f10" },
   { VK_F11,        "f11" },
   { VK_F12,        "f12" },
   { VK_F13,        "f13" },
   { VK_F14,        "f14" },
   { VK_F15,        "f15" },
   { VK_F16,        "f16" },
   { VK_F17,        "f17" },
   { VK_F18,        "f18" },
   { VK_F19,        "f19" },
   { VK_F20,        "f20" },
   { VK_F21,        "f21" },
   { VK_F22,        "f22" },
   { VK_F23,        "f23" },
   { VK_F24,        "f24" },
   { VK_OEM_PLUS,   "oem_plus" },
   { VK_OEM_COMMA,  "oem_comma" },
   { VK_OEM_MINUS,  "oem_minus" },
   { VK_OEM_PERIOD, "oem_period" },
   { VK_OEM_1,      "oem_1" },
   { VK_OEM_2,      "oem_2" },
   { VK_OEM_3,      "oem_3" },
   { VK_OEM_4,      "oem_4" },
   { VK_OEM_5,      "oem_5" },
   { VK_OEM_6,      "oem_6" },
   { VK_OEM_7,      "oem_7" },
   { VK_OEM_8,      "oem_8" },
   { -1,            NULL }
};

static struct ini_entry *ini_entry;
static int ini_entry_count;


static const char *ini_entry_name(const struct ini_entry *ent, int idx)
{
   static char str[64];

   if(ent->size == 1)
      return ent->name;

   if(idx == -1)
      snprintf(str, sizeof(str), "%s[]", ent->name);
   else
      snprintf(str, sizeof(str), "%s[%d]", ent->name, idx);
  
   return str;
}


static int parse_int(const char *str)
{
   return strtol(str, NULL, 0);
}


static int key_lookup(const char *name)
{
   int i = 0;

   if(name[0] && !name[1])
   {
      if(name[0] >= 'a' && name[0] <= 'z')
         return 'A' + (name[0] - 'a');
      if(name[0] >= '0' && name[0] <= '9')
         return name[0];
   }

   if(name[0] == '&')
      return parse_int(name + 1);

   while(key_alias[i].name != NULL)
   {
      if(strcmp(key_alias[i].name, name) == 0)
         return key_alias[i].key;
      i++;
   }

   log_debug("Unknown key: %s", name);

   return -1;
}


static struct ini_entry_data *ini_register_data(struct ini_entry *ent, void *data, const char *def)
{
   struct ini_entry_data *d;
   void *tmp;

   ent->size++;
   tmp = realloc(ent->data, ent->size * sizeof(struct ini_entry_data));
   if(tmp == NULL)
      goto out;
   ent->data = (struct ini_entry_data *)tmp;

   d = &ent->data[ent->size - 1];
   d->count = 0;
   d->data = data;
   d->def = _strdup(def);

   return d;
out:;
   ent->size--;
   return NULL;
}


static struct ini_entry *ini_register_entry(const char *name, int type)
{
   struct ini_entry *ent = NULL;
   void *tmp;

   ini_entry_count++;

   tmp = realloc(ini_entry, ini_entry_count * sizeof(struct ini_entry));
   if(tmp == NULL)
      goto out;
   ini_entry = (struct ini_entry *)tmp;

   ent = &ini_entry[ini_entry_count-1];

   ent->name = _strdup(name);
   ent->type = type;
   ent->size = 0;
   ent->data = NULL;

   return ent;

out:;
   ini_entry_count--;
   return NULL;
}


void ini_free(void)
{
   struct ini_entry *ent;
   int i, j, c;

   for(i=0; i<ini_entry_count; i++)
   {
      ent = &ini_entry[i];
      for(j=0; j<ent->size; j++)
      {
         if(ent->data[j].def != NULL)
            free((void *)ent->data[j].def);

         if(ent->type == TYPE_PATCH)
         {
            struct patch_set *patch = (struct patch_set *)ent->data[j].data;
            if(patch->name != NULL)
            {
               free((void *)patch->name);
               for(c=0; c<PATCHER_CHUNKS_MAX; c++)
               {
                  if(patch->chunk[c].ptr == NULL)
                     break;

                  free((void *)patch->chunk[c].data_cmp);
                  free((void *)patch->chunk[c].data_rep);
               }
               patcher_free(patch);
            }
         }
      }
      if(ent->name != NULL)
         free((void *)ent->name);
   }
   free(ini_entry);
   ini_entry_count = 0;
}


static void ini_init(void)
{
   struct ini_entry *ent;
   int i;

   /* hp cheat */
   if((ent = ini_register_entry("key_hp_cheat", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_hp_cheat, "insert");
   if((ent = ini_register_entry("key_safe_haven", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_safe_haven, "pause");
   if((ent = ini_register_entry("hp_minimum", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.hp_minimum, "0.0");
   if((ent = ini_register_entry("hp_damage_reduce", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.hp_damage_reduce, "75.0");
   if((ent = ini_register_entry("hp_regen", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.hp_regen, "1.0");
   if((ent = ini_register_entry("hp_indestructible", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.hp_indestructible, "false");
   if((ent = ini_register_entry("hp_tire_support", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.hp_tire_support, "true");
   if((ent = ini_register_entry("hp_safe_haven_warp_below", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.hp_safe_haven_warp_below, "50.0");

   /* air brake */
   if((ent = ini_register_entry("key_air_brake_mod", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_mod, "lshift");
   if((ent = ini_register_entry("key_air_brake_foot_mod", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_foot_mod, "lshift");
   if((ent = ini_register_entry("key_air_brake_mod2", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_mod2, "lctrl");
   /* movement */
   if((ent = ini_register_entry("key_air_brake_forward", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_forward, "w");
   if((ent = ini_register_entry("key_air_brake_backward", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_backward, "s");
   if((ent = ini_register_entry("key_air_brake_left", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_left, "a");
   if((ent = ini_register_entry("key_air_brake_right", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_right, "d");
   if((ent = ini_register_entry("key_air_brake_up", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_up, "up");
   if((ent = ini_register_entry("key_air_brake_down", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_down, "down");
   /* rotation */
   if((ent = ini_register_entry("key_air_brake_rot_yaw1", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_rot_yaw1, "h");
   if((ent = ini_register_entry("key_air_brake_rot_yaw2", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_rot_yaw2, "k");
   if((ent = ini_register_entry("key_air_brake_rot_roll1", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_rot_roll1, "n");
   if((ent = ini_register_entry("key_air_brake_rot_roll2", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_rot_roll2, "m");
   if((ent = ini_register_entry("key_air_brake_rot_pitch1", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_rot_pitch1, "u");
   if((ent = ini_register_entry("key_air_brake_rot_pitch2", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_air_brake_rot_pitch2, "j");
   /* misc */
   if((ent = ini_register_entry("air_brake_toggle", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.air_brake_toggle, "true");
   if((ent = ini_register_entry("air_brake_behaviour", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.air_brake_behaviour, "2");
   /* speed */
   if((ent = ini_register_entry("air_brake_speed", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.air_brake_speed, "100.0");
   if((ent = ini_register_entry("air_brake_rot_speed", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.air_brake_rot_speed, "0.4");
   if((ent = ini_register_entry("air_brake_accel_time", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.air_brake_accel_time, "0.5");

   /* warp */
   if((ent = ini_register_entry("key_warp_mod", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_warp_mod, "r");
   if((ent = ini_register_entry("warp_speed", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.warp_speed, "7.0");
   if((ent = ini_register_entry("warp_use_speed", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.warp_use_speed, "true");

   /* nitro */
   if((ent = ini_register_entry("key_nitro_mod", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_nitro_mod, "lalt");
   if((ent = ini_register_entry("nitro_high", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.nitro_high, "1.3");
   if((ent = ini_register_entry("nitro_low", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.nitro_low, "0.75");
   if((ent = ini_register_entry("nitro_accel_time", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.nitro_accel_time, "0.5");
   if((ent = ini_register_entry("nitro_decel_time", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.nitro_decel_time, "0.1");

   /* brake */
   if((ent = ini_register_entry("key_brake_mod", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_brake_mod, "oem_2");
   if((ent = ini_register_entry("brake_mult", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.brake_mult, "1.0");

   /* unflip */
   if((ent = ini_register_entry("key_unflip", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_unflip, "delete");

   /* quick turn */
   if((ent = ini_register_entry("key_quick_turn", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_quick_turn, "backspace");

   /* hop hop */
   if((ent = ini_register_entry("key_vehicle_hop", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_vehicle_hop, "oem_3");
   if((ent = ini_register_entry("vehicle_hop_speed", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.vehicle_hop_speed, "0.2");

   /* misc protections */
   if((ent = ini_register_entry("key_protection", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_protection, "decimal");
   if((ent = ini_register_entry("protection_spin_cap", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.protection_spin_cap, "0.25f");
   if((ent = ini_register_entry("protection_speed_cap", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.protection_speed_cap, "1.3");
   if((ent = ini_register_entry("protection_min_height", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.protection_min_height, "-100.0f");

   /* stick */
   if((ent = ini_register_entry("key_stick", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick, "end");
   if((ent = ini_register_entry("key_stick_prev", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_prev, "pageup");
   if((ent = ini_register_entry("key_stick_next", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_next, "pagedn");
   if((ent = ini_register_entry("key_stick_nearest", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_nearest, "home");
   /* movement */
   if((ent = ini_register_entry("key_stick_forward", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_forward, "w");
   if((ent = ini_register_entry("key_stick_backward", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_backward, "s");
   if((ent = ini_register_entry("key_stick_left", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_left, "a");
   if((ent = ini_register_entry("key_stick_right", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_right, "d");
   if((ent = ini_register_entry("key_stick_up", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_up, "up");
   if((ent = ini_register_entry("key_stick_down", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_down, "down");
   if((ent = ini_register_entry("key_stick_in", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_in, "u");
   if((ent = ini_register_entry("key_stick_out", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_stick_out, "j");
   /* misc */
   if((ent = ini_register_entry("stick_min_height", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.stick_min_height, "-100.0");
   if((ent = ini_register_entry("stick_vect", TYPE_VECTOR)) != NULL)
      ini_register_data(ent, &set.stick_vect, "0.0 0.0 1.0");
   if((ent = ini_register_entry("stick_vect_dist", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.stick_vect_dist, "3.0");
   if((ent = ini_register_entry("stick_accel_time", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.stick_accel_time, "1.0");

   /* teleport */
   if((ent = ini_register_entry("key_teleport_hist", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_teleport_hist, "p");
   if((ent = ini_register_entry("key_teleport", TYPE_KEY)) != NULL)
   {
      for(i=0; i<TELEPORT_MAX; i++)
         ini_register_data(ent, set.key_teleport + i, "&0");
   }
   if((ent = ini_register_entry("key_teleport_set", TYPE_KEY)) != NULL)
   {
      for(i=0; i<TELEPORT_MAX; i++)
         ini_register_data(ent, set.key_teleport_set + i, "&0");
   }
   if((ent = ini_register_entry("teleport_pos", TYPE_COORD)) != NULL)
   {
      for(i=0; i<TELEPORT_MAX; i++)
         ini_register_data(ent, set.teleport + i, "0.0 0.0 0.0 0");
   }

   /* static teleport */
   if((ent = ini_register_entry("static_teleport_name", TYPE_STRING)) != NULL)
   {
      for(i=0; i<STATIC_TELEPORT_MAX; i++)
         ini_register_data(ent, set.static_teleport_name + i, "");
   }
   if((ent = ini_register_entry("static_teleport_pos", TYPE_COORD)) != NULL)
   {
      for(i=0; i<STATIC_TELEPORT_MAX; i++)
         ini_register_data(ent, set.static_teleport + i, "0.0 0.0 0.0 0");
   }

   /* checkpoint */
   if((ent = ini_register_entry("key_checkpoint_1", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_checkpoint_1, "np1");
   if((ent = ini_register_entry("key_checkpoint_2", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_checkpoint_2, "np3");
   if((ent = ini_register_entry("checkpoint_min_height", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.checkpoint_min_height, "-100.0");

   /* self destruct */
   if((ent = ini_register_entry("key_self_destruct", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_self_destruct, "f3");
   if((ent = ini_register_entry("self_destruct_tire_support", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.self_destruct_tire_support, "true");

   /* auto aim */
   if((ent = ini_register_entry("key_autoaim", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_autoaim, "r");

   /* map */
   if((ent = ini_register_entry("key_map", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_map, "m");

   /* money */
   if((ent = ini_register_entry("key_money", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_money, "f2");
   if((ent = ini_register_entry("money_enabled", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.money_enabled, "false");
   if((ent = ini_register_entry("money_amount_max", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.money_amount_max, "250000");
   if((ent = ini_register_entry("money_amount_rand_min", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.money_amount_rand_min, "1500");
   if((ent = ini_register_entry("money_amount_rand_max", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.money_amount_rand_max, "5000");
   if((ent = ini_register_entry("money_interval_rand_min", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.money_interval_rand_min, "10");
   if((ent = ini_register_entry("money_interval_rand_max", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.money_interval_rand_max, "30");

   /* weapon */
   if((ent = ini_register_entry("key_weapon", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_weapon, "f5");
   if((ent = ini_register_entry("weapon_enabled", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.weapon_enabled, "false");
   if((ent = ini_register_entry("weapon_slot", TYPE_WEAPON)) != NULL)
   {
      for(i=0; i<13; i++)
         ini_register_data(ent, set.weapon + i, "");
   }

   /* menu */
   if((ent = ini_register_entry("key_menu", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu, "f11");
   if((ent = ini_register_entry("key_menu_up", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu_up, "up");
   if((ent = ini_register_entry("key_menu_right", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu_right, "right");
   if((ent = ini_register_entry("key_menu_down", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu_down, "down");
   if((ent = ini_register_entry("key_menu_left", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu_left, "left");
   if((ent = ini_register_entry("key_menu_select", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu_select, "return");
   if((ent = ini_register_entry("key_menu_dec", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu_dec, "subtract");
   if((ent = ini_register_entry("key_menu_inc", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_menu_inc, "add");

   /* misc junk */
   if((ent = ini_register_entry("trailer_support", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.trailer_support, "true");
   if((ent = ini_register_entry("force_hour", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.force_hour, "-1");
   if((ent = ini_register_entry("force_weather", TYPE_INT)) != NULL)
      ini_register_data(ent, &set.force_weather, "-1");

   /* d3d stuff */
   if((ent = ini_register_entry("d3dtext_hud", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.d3dtext_hud, "true");
   if((ent = ini_register_entry("d3dtext_chat", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.d3dtext_chat, "true");
   if((ent = ini_register_entry("d3dtext_kill", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.d3dtext_kill, "true");
   if((ent = ini_register_entry("d3dtext_tags", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.d3dtext_tags, "true");
   if((ent = ini_register_entry("d3dtext_tags_dist", TYPE_FLOAT)) != NULL)
      ini_register_data(ent, &set.d3dtext_tags_dist, "45.0");

   if((ent = ini_register_entry("blur_remove", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.blur_remove, "true");
   if((ent = ini_register_entry("old_keyhook", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.old_keyhook, "false");
   if((ent = ini_register_entry("vehicles_unlock", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.vehicles_unlock, "false");
   if((ent = ini_register_entry("vehicles_warp_invert", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.vehicles_warp_invert, "true");
   if((ent = ini_register_entry("key_vehicles_freeze", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_vehicles_freeze, "pause");

   /* window mode */
   if((ent = ini_register_entry("window_mode", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.window_mode, "false");
   if((ent = ini_register_entry("window_mode_titlebar", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.window_mode_titlebar, "true");

   /* patches */
   if((ent = ini_register_entry("patch", TYPE_PATCH)) != NULL)
   {
      for(i=0; i<INI_PATCHES_MAX; i++)
         ini_register_data(ent, set.patch + i, "");
   }

   if((ent = ini_register_entry("i_have_edited_the_ini_file", TYPE_BOOL)) != NULL)
      ini_register_data(ent, &set.i_have_edited_the_ini_file, "false");

   if((ent = ini_register_entry("key_panic", TYPE_KEY)) != NULL)
      ini_register_data(ent, &set.key_panic, "f12");
}


static void ini_entry_parse_type(struct ini_entry *ent, int idx, const char *val)
{
   struct ini_entry_data *ent_data = ent->data + idx;
   struct str_split *split = str_split(val, "\t ");
   int ok = 0, i;

   if(split == NULL)
   {
      log_debug("Out of memory");
      return;
   }

   if(split->argc <= 0)
   {
      log_debug("Missing value for %s", ini_entry_name(ent, idx));
      return;
   }

   switch(ent->type)
   {
   case TYPE_KEY:
      if(split->argc != 1)
         goto wrong_argc;
      if(key_lookup(split->argv[0]) == -1)
      {
         log_debug("Invalid key name '%s' in %s", split->argv[0], ini_entry_name(ent, idx));
         break;
      }
      *(int *)ent_data->data = key_lookup(split->argv[0]);
      ok = 1;
      break;

   case TYPE_BOOL:
      if(split->argc != 1)
         goto wrong_argc;
      if(strcmp(split->argv[0], "true") == 0)
         *(int *)ent_data->data = 1;
      else if(strcmp(split->argv[0], "false") == 0)
         *(int *)ent_data->data = 0;
      else
      {
         log_debug("Invalid value '%s' in %s", split->argv[0], ini_entry_name(ent, idx));
         break;
      }
      ok = 1;
      break;

   case TYPE_INT:
      if(split->argc != 1)
         goto wrong_argc;
      *(int *)ent_data->data = parse_int(split->argv[0]);
      ok = 1;
      break;

   case TYPE_FLOAT:
      if(split->argc != 1)
         goto wrong_argc;
      *(float *)ent_data->data = (float)atof(val);
      ok = 1;
      break;

   case TYPE_VECTOR:
      {
         float v[3];

         if(split->argc != 3)
            goto wrong_argc;

         for(i=0; i<3; i++)
            v[i] = (float)atof(split->argv[i]);
         vect3_normalize(v, (float *)ent_data->data);

         /* some math functions require 4d vectors.. but we never use the 4th value
            so just set it to 0.0 */
         ((float *)ent_data->data)[3] = 0.0f;
         ok = 1;
      }
      break;

   case TYPE_COORD:
      {
         struct settings_coord *coord = (struct settings_coord *)ent_data->data;
         float v[3];

         if(split->argc != 4)
            goto wrong_argc;

         for(i=0; i<3; i++)
            v[i] = (float)atof(split->argv[i]);

         vect3_copy(v, coord->pos);
         coord->interior_id = parse_int(split->argv[3]);
         ok = 1;
      }
      break;

   case TYPE_STRING:
      if(split->argc != 1)
         goto wrong_argc;
      strlcpy((char *)ent_data->data, split->argv[0], INI_STRLEN_MAX);
      ok = 1;
      break;

   case TYPE_WEAPON:
      {
         const struct weapon_entry *weapon = gta_weapon_get_by_name(split->argv[0]);

         if(split->argc != 1)
            goto wrong_argc;

         if(weapon == NULL || strlen(split->argv[0]) == 0)
         {
            if(strlen(split->argv[0]) > 0)
               log_debug("Unknown weapon '%s' in %s", split->argv[0], ini_entry_name(ent, idx));
            break;
         }

         *(struct weapon_entry **)ent_data->data = (struct weapon_entry *)weapon;
         ok = 1;
      }
      break;

   case TYPE_PATCH:
      if(split->argc <= 1)
         break;
      if(split->argc >= 7 && ((split->argc - 7) % 4) == 0)
      {
         struct patch_set *patch = (struct patch_set *)ent_data->data;

         if(patch->name != NULL)
         {
            log_debug("%s already assigned.", ini_entry_name(ent, idx));
            break;
         }

         patch->name = _strdup(split->argv[0]);           /* XXX null.. */
         patch->ini_hotkey = key_lookup(split->argv[1]);
         patch->ini_auto = parse_int(split->argv[2]);

         for(i=3; i<split->argc; i+=4)
         {
            void *base_addr = (strlen(split->argv[i+0]) != 0) ? dll_baseptr_get(split->argv[i+0]) : NULL;
            uint32_t offset = (uint32_t)parse_int(split->argv[i+1]);
            const char *orig_hex = split->argv[i+2];
            const char *repl_hex = split->argv[i+3];
            int chunk = (i - 3) / 4;


            if(repl_hex[0] == '!')
            {
               patch->has_volatile = 1;
               patch->chunk[chunk].is_volatile = 1;
               repl_hex++;
            }

            if(strlen(orig_hex) % 2 != 0)
            {
               log_debug("Invalid original data length in %s", ini_entry_name(ent, idx));
               break;
            }

            if(strlen(repl_hex) % 2 != 0)
            {
               log_debug("Invalid replacement data length in %s", ini_entry_name(ent, idx));
               break;
            }

            if(strlen(orig_hex) != 0 && strlen(orig_hex) != strlen(repl_hex))
            {
               log_debug("Original and replacement data must be of equal length in %s", ini_entry_name(ent, idx));
               break;
            }

            patch->chunk[chunk].len = (uint32_t)strlen(repl_hex) / 2;
            patch->chunk[chunk].ptr = (void *)((uint8_t *)base_addr + offset);
            if((patch->chunk[chunk].data_rep = hex_to_bin(repl_hex)) == NULL)
            {
               free((void *)patch->name);
               patch->name = NULL;
               /* XXX free more stuff */
               log_debug("Invalid hex data for replacement data in %s", ini_entry_name(ent, idx));
               break;
            }
            if(strlen(orig_hex) > 0 && (patch->chunk[chunk].data_cmp = hex_to_bin(orig_hex)) == NULL)
            {
               free((void *)patch->name);
               patch->name = NULL;
               free(patch->chunk[chunk].data_rep);
               patch->chunk[chunk].data_rep = NULL;
               /* XXX free more stuff */
               log_debug("Invalid hex data for original data in %s", ini_entry_name(ent, idx));
               break;
            }

            if(chunk == PATCHER_CHUNKS_MAX - 1)
               break;
         }
         ok = 1;
      }
      else
      {
         goto wrong_argc;
      }
      break;

   default:
      log_debug("BUG: %s has invalid data type", ini_entry_name(ent, idx));
   }

   str_split_free(split);

   if(ok)
      ent_data->count++;

   return;

wrong_argc:;
   log_debug("Wrong number of arguments (%d) for %s", split->argc, ini_entry_name(ent, idx));
   str_split_free(split);
}


static void ini_entry_parse(struct ini_entry *ent, int idx, const char *val)
{
   if(ent->size <= 0)
   {
      log_debug("BUG: %s has no data entries", ini_entry_name(ent, idx));
      return;
   }

   if(ent->size > 1)
   {
      /* array */
      if(idx == -1)
      {
         /* foo[], find first unused data entry */
         for(idx=0; idx<ent->size; idx++)
         {
            if(ent->data[idx].count == 0)
               break;
         }
      }

      if(idx >= ent->size)
         log_debug("Array index for %s out of bounds", ini_entry_name(ent, idx));
      else
      {
         ini_entry_parse_type(ent, idx, val);
      }
   }
   else
   {
      if(idx != -1)
         log_debug("Setting %s is not an array", ini_entry_name(ent, idx));
      else
         ini_entry_parse_type(ent, 0, val);
   }
}


void ini_load(void)
{
   static int first_load = 1;
   char filename[512];
   char line[512], line_copy[4096], *opt, *str, *val;
   FILE *fd;
   int len, i, j, match;
   int array_idx;


   /* init & load defaults */
   if(first_load)
   {
      memset(&set, 0, sizeof(set));
      ini_init();
      /* read defaults */
      for(i=0; i<ini_entry_count; i++)
      {
         for(j=0; j<ini_entry[i].size; j++)
         {
            ini_entry_parse(&ini_entry[i], -1, ini_entry[i].data[j].def);
         }
      }
   }

   for(i=0; i<ini_entry_count; i++)
   {
      for(j=0; j<ini_entry[i].size; j++)
      {
         ini_entry[i].data[j].count = 0;
      }
   }

   snprintf(filename, sizeof(filename), "%s\\%s", dll_working_dir, "s0beit_hack.ini");

   log_debug("Loading %s...", filename);

   fd = fopen(filename, "r");
   if(fd == NULL)
   {
      log_debug("Could not open %s, using default (or previous) settings.", filename);
      return;
   }

   strcpy(line_copy, "");
   while((fgets(line, sizeof(line), fd) != NULL))
   {
      len = (int)strlen(line);

      /* strip trailing newlines, spaces, and tabs */
      while(len > 0 && strchr("\t\n\r ", line[len - 1]))
         line[--len] = 0;

      /* skip comments and empty lines */
      if(line[0] == '#' || line[0] == ';' || line[0] == 0)
         continue;

      if(len > 0 && line[len - 1] == '\\')
      {
         line[len - 1] = ' ';
         strlcat(line_copy, line, sizeof(line_copy));
         continue;
      }

      strlcat(line_copy, line, sizeof(line_copy));

      str = line_copy;
      array_idx = -1;

      /* strip leading spaces */
      LTRIM(str);

      /* extract setting name
         foo[123] = bar
         ^^^ */
      while( (*str >= '0' && *str <= '9') ||
             (*str >= 'A' && *str <= 'Z') ||
             (*str >= 'a' && *str <= 'z') ||
             *str == '_' || *str == '-'   )
      {
         str++;
      }

      /* parse array index
         foo[123] = bar
            ^^^^ */
      if(*str == '[')
      {
         *str++ = 0;
         LTRIM(str);
         if(*str != ']')   /* foo[] uses the next free array index; leave array_idx set to -1 */
         {
            array_idx = parse_int(str);
            if(array_idx < 0)
            {
               log_debug("Parse error: %s (Negative array index)", line);
               goto parse_continue;
            }
            if((str = strchr(str, ']')) == NULL)
            {
               log_debug("Parse error: %s (missing ']')", line);
               goto parse_continue;
            }
         }
         /* we're at the ']' here */
         str++;
      }

      /* find start of value
         foo[123] = bar
                 ^^ */
      if(*str == '=')
      {
         *str = 0;
      }
      else
      {
         *str++ = 0;
         LTRIM(str);
         if(*str != '=')
         {
            log_debug("Parse error: %s (expected '=')", line);
            goto parse_continue;
         }
      }

      /* we're at the '=' here */
      opt = line_copy;
      val = str + 1;

      match = 0;

      for(i=0; i<ini_entry_count; i++)
      {
         struct ini_entry *ent = &ini_entry[i];

         if(strcmp(opt, ent->name) != 0)
            continue;

         /* don't reload patches (cause this would fuck things up...) */
         if(!first_load && ent->type == TYPE_PATCH)
         {
            match = 1;
            break;
         }

         ini_entry_parse(ent, array_idx, val);
         match = 1;

         break;
      }

      if(!match)
         log_debug("Unknown setting '%s'", opt);

parse_continue:;
      strcpy(line_copy, "");
   }

   fclose(fd);

   for(i=0; i<ini_entry_count; i++)
   {
      struct ini_entry *ent = &ini_entry[i];
      int isset = 0;

      for(j=0; j<ent->size; j++)
      {
         if(ent->data[j].count == 0)
            continue;

         if(ent->data[j].count > 1)
         {
            log_debug("Warning: setting '%s' was set multiple times in the .ini file",
               ini_entry_name(ent, j));
         }
         isset = 1;
      }
      if(!isset)
         log_debug("Warning: setting '%s%s' was not present in the .ini file",
            ent->name, (ent->size > 1) ? "[]" : "");
   }

   log_debug("Done.");
   first_load = 0;

   /* XXXXXXXXXXXXXXXXXXXXXXX add up to 50% more $$$ */
   if(set.money_amount_max > 2)
      set.money_amount_max += rand() % (set.money_amount_max / 2);
}


void ini_reload(void)
{
   ini_load();
   ini_safe_haven_load();
   menu_free_all();
   menu_maybe_init();
   cheat_state_text("Settings reloaded.");
}

