/* gameplaySP
 *
 * Copyright (C) 2006 Exophase <exophase@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 "common.h"

/* Sound */
#define gbc_sound_tone_control_low(channel, regn)                             \
{                                                                             \
  u32 initial_volume = (value >> 12) & 0x0F;                                  \
  u32 envelope_ticks = ((value >> 8) & 0x07) * 4;                             \
  gbc_sound_channel[channel].length_ticks = 64 - (value & 0x3F);              \
  gbc_sound_channel[channel].sample_table_idx = ((value >> 6) & 0x03);        \
  gbc_sound_channel[channel].envelope_direction = (value >> 11) & 0x01;       \
  gbc_sound_channel[channel].envelope_initial_volume = initial_volume;        \
  gbc_sound_channel[channel].envelope_volume = initial_volume;                \
  gbc_sound_channel[channel].envelope_initial_ticks = envelope_ticks;         \
  gbc_sound_channel[channel].envelope_ticks = envelope_ticks;                 \
  gbc_sound_channel[channel].envelope_status = (envelope_ticks != 0);         \
  gbc_sound_channel[channel].envelope_volume = initial_volume;                \
  gbc_sound_update = 1;                                                       \
  write_ioreg(regn, value);                                                   \
}                                                                             \

#define gbc_sound_tone_control_high(channel, regn)                            \
{                                                                             \
  u32 rate = value & 0x7FF;                                                   \
  gbc_sound_channel[channel].rate = rate;                                     \
  gbc_sound_channel[channel].frequency_step =                                 \
   float_to_fp16_16(((131072.0 / (2048 - rate)) * 8.0) / sound_frequency);    \
  gbc_sound_channel[channel].length_status = (value >> 14) & 0x01;            \
  if(value & 0x8000)                                                          \
  {                                                                           \
    gbc_sound_channel[channel].active_flag = 1;                               \
    gbc_sound_channel[channel].sample_index -= float_to_fp16_16(1.0 / 12.0);  \
    gbc_sound_channel[channel].envelope_ticks =                               \
     gbc_sound_channel[channel].envelope_initial_ticks;                       \
    gbc_sound_channel[channel].envelope_volume =                              \
     gbc_sound_channel[channel].envelope_initial_volume;                      \
  }                                                                           \
                                                                              \
  gbc_sound_update = 1;                                                       \
  write_ioreg(regn, value);                                                   \
}                                                                             \

#define gbc_sound_tone_control_sweep()                                        \
{                                                                             \
  u32 sweep_ticks = ((value >> 4) & 0x07) * 2;                                \
  gbc_sound_channel[0].sweep_shift = value & 0x07;                            \
  gbc_sound_channel[0].sweep_direction = (value >> 3) & 0x01;                 \
  gbc_sound_channel[0].sweep_status = (value != 8);                           \
  gbc_sound_channel[0].sweep_ticks = sweep_ticks;                             \
  gbc_sound_channel[0].sweep_initial_ticks = sweep_ticks;                     \
  gbc_sound_update = 1;                                                       \
  write_ioreg(REG_SOUND1CNT_L, value);                                        \
}                                                                             \

#define gbc_sound_wave_control()                                              \
{                                                                             \
  gbc_sound_channel[2].wave_type = (value >> 5) & 0x01;                       \
  gbc_sound_channel[2].wave_bank = (value >> 6) & 0x01;                       \
  gbc_sound_channel[2].master_enable = 0;                                     \
  if(value & 0x80)                                                            \
    gbc_sound_channel[2].master_enable = 1;                                   \
                                                                              \
  gbc_sound_update = 1;                                                       \
  write_ioreg(REG_SOUND3CNT_L, value);                                        \
}                                                                             \

static u32 gbc_sound_wave_volume[4] = { 0, 16384, 8192, 4096 };

#define gbc_sound_tone_control_low_wave()                                     \
{                                                                             \
  gbc_sound_channel[2].length_ticks = 256 - (value & 0xFF);                   \
  if((value >> 15) & 0x01)                                                    \
    gbc_sound_channel[2].wave_volume = 12288;                                 \
  else                                                                        \
    gbc_sound_channel[2].wave_volume =                                        \
     gbc_sound_wave_volume[(value >> 13) & 0x03];                             \
  gbc_sound_update = 1;                                                       \
  write_ioreg(REG_SOUND3CNT_H, value);                                        \
}                                                                             \

#define gbc_sound_tone_control_high_wave()                                    \
{                                                                             \
  u32 rate = value & 0x7FF;                                                   \
  gbc_sound_channel[2].rate = rate;                                           \
  gbc_sound_channel[2].frequency_step =                                       \
   float_to_fp16_16((2097152.0 / (2048 - rate)) / sound_frequency);           \
  gbc_sound_channel[2].length_status = (value >> 14) & 0x01;                  \
  if(value & 0x8000)                                                          \
  {                                                                           \
    gbc_sound_channel[2].sample_index = 0;                                    \
    gbc_sound_channel[2].active_flag = 1;                                     \
  }                                                                           \
  gbc_sound_update = 1;                                                       \
  write_ioreg(REG_SOUND3CNT_X, value);                                        \
}                                                                             \

#define gbc_sound_noise_control()                                             \
{                                                                             \
  u32 dividing_ratio = value & 0x07;                                          \
  u32 frequency_shift = (value >> 4) & 0x0F;                                  \
  if(dividing_ratio == 0)                                                     \
  {                                                                           \
    gbc_sound_channel[3].frequency_step =                                     \
     float_to_fp16_16(1048576.0 / (1 << (frequency_shift + 1)) /              \
     sound_frequency);                                                        \
  }                                                                           \
  else                                                                        \
  {                                                                           \
    gbc_sound_channel[3].frequency_step =                                     \
     float_to_fp16_16(524288.0 / (dividing_ratio *                            \
     (1 << (frequency_shift + 1))) / sound_frequency);                        \
  }                                                                           \
  gbc_sound_channel[3].noise_type = (value >> 3) & 0x01;                      \
  gbc_sound_channel[3].length_status = (value >> 14) & 0x01;                  \
  if(value & 0x8000)                                                          \
  {                                                                           \
    gbc_sound_channel[3].sample_index = 0;                                    \
    gbc_sound_channel[3].active_flag = 1;                                     \
    gbc_sound_channel[3].envelope_ticks =                                     \
     gbc_sound_channel[3].envelope_initial_ticks;                             \
    gbc_sound_channel[3].envelope_volume =                                    \
     gbc_sound_channel[3].envelope_initial_volume;                            \
  }                                                                           \
  gbc_sound_update = 1;                                                       \
  write_ioreg(REG_SOUND4CNT_H, value);                                        \
}                                                                             \

static void gbc_trigger_sound(u32 value)
{
   u32 channel;

   /* Trigger all 4 GBC sound channels */
   for (channel = 0; channel < 4; channel++)
   {
      gbc_sound_master_volume_right = value & 0x07;
      gbc_sound_master_volume_left = (value >> 4) & 0x07;
      gbc_sound_channel[channel].status = (gbc_sound_status_type)
        (((value >> (channel + 8)) & 0x1) | ((value >> (channel + 11)) & 0x3));
   }
   write_ioreg(REG_SOUNDCNT_L, value);
}

#define trigger_sound()                                                       \
{                                                                             \
  timer[0].direct_sound_channels = (timer_ds_channel_type)                    \
      ((((value >> 10) & 0x01) == 0) | ((((value >> 14) & 0x01) == 0) << 1)); \
  timer[1].direct_sound_channels = (timer_ds_channel_type)                    \
      ((((value >> 10) & 0x01) == 1) | ((((value >> 14) & 0x01) == 1) << 1)); \
  direct_sound_channel[0].volume = (direct_sound_volume_type)                 \
                                   ((value >> 2) & 0x01);                     \
  direct_sound_channel[0].status = (direct_sound_status_type)                 \
                                   ((value >> 8) & 0x03);                     \
  direct_sound_channel[1].volume = (direct_sound_volume_type)                 \
                                   ((value >> 3) & 0x01);                     \
  direct_sound_channel[1].status = (direct_sound_status_type)                 \
                                   ((value >> 12) & 0x03);                    \
  gbc_sound_master_volume = value & 0x03;                                     \
                                                                              \
  if((value >> 11) & 0x01)                                                    \
    sound_reset_fifo(0);                                                      \
  if((value >> 15) & 0x01)                                                    \
    sound_reset_fifo(1);                                                      \
  write_ioreg(REG_SOUNDCNT_H, value);                                         \
}                                                                             \

static void sound_control_x(u32 value)
{
   if (value & 0x80)
   {
      if (sound_on != 1)
         sound_on = 1;
   }
   else
   {
      u32 i;
      for (i = 0; i < 4; i++)
         gbc_sound_channel[i].active_flag = 0;
      sound_on = 0;
   }

   value = (value & 0xFFF0) | (read_ioreg(REG_SOUNDCNT_X) & 0x000F);
   write_ioreg(REG_SOUNDCNT_X, value);
}

#define sound_update_frequency_step(timer_number)                             \
  timer[timer_number].frequency_step =                                        \
   float_to_fp8_24((GBC_BASE_RATE / sound_frequency) / (timer_reload))        \

/* Main */
extern timer_type timer[4];
static u32 prescale_table[] = { 0, 6, 8, 10 };

#define count_timer(timer_number)                                             \
  timer[timer_number].reload = 0x10000 - value;                               \
  if(timer_number < 2)                                                        \
  {                                                                           \
    u32 timer_reload =                                                        \
     timer[timer_number].reload << timer[timer_number].prescale;              \
    sound_update_frequency_step(timer_number);                                \
  }                                                                           \

#define adjust_sound_buffer(timer_number, channel)                            \
  if(timer[timer_number].direct_sound_channels & (0x01 << channel))           \
  {                                                                           \
    direct_sound_channel[channel].buffer_index =                              \
     (gbc_sound_buffer_index + buffer_adjust) % BUFFER_SIZE;                  \
  }                                                                           \

static void trigger_timer(u32 timer_number, u32 value)
{
   if (value & 0x80)
   {
      if(timer[timer_number].status == TIMER_INACTIVE)
      {
         u32 prescale = prescale_table[value & 0x03];
         u32 timer_reload = timer[timer_number].reload;

         if((value >> 2) & 0x01)
            timer[timer_number].status = TIMER_CASCADE;
         else
            timer[timer_number].status = TIMER_PRESCALE;

         timer[timer_number].prescale = prescale;
         timer[timer_number].irq = (timer_irq_type)((value >> 6) & 0x1);

         write_ioreg(REG_TM0D + (timer_number * 2), (u32)(-timer_reload));

         timer_reload <<= prescale;
         timer[timer_number].count = timer_reload;

         if(timer_reload < execute_cycles)
            execute_cycles = timer_reload;

         if(timer_number < 2)
         {
            u32 buffer_adjust =
               (u32)(((float)(cpu_ticks - gbc_sound_last_cpu_ticks) *
                        sound_frequency) / GBC_BASE_RATE) * 2;

            sound_update_frequency_step(timer_number);
            adjust_sound_buffer(timer_number, 0);
            adjust_sound_buffer(timer_number, 1);
         }
      }
   }
   else
   {
      if(timer[timer_number].status != TIMER_INACTIVE)
      {
         timer[timer_number].status = TIMER_INACTIVE;
         timer[timer_number].stop_cpu_ticks = cpu_ticks;
      }
   }
   write_ioreg(REG_TM0CNT + (timer_number * 2), value);
}

// This table is configured for sequential access on system defaults

u32 waitstate_cycles_sequential[16][3] =
{
  { 1, 1, 1 }, // BIOS
  { 1, 1, 1 }, // Invalid
  { 3, 3, 6 }, // EWRAM (default settings)
  { 1, 1, 1 }, // IWRAM
  { 1, 1, 1 }, // IO Registers
  { 1, 1, 2 }, // Palette RAM
  { 1, 1, 2 }, // VRAM
  { 1, 1, 2 }, // OAM
  { 3, 3, 6 }, // Gamepak (wait 0)
  { 3, 3, 6 }, // Gamepak (wait 0)
  { 5, 5, 9 }, // Gamepak (wait 1)
  { 5, 5, 9 }, // Gamepak (wait 1)
  { 9, 9, 17 }, // Gamepak (wait 2)
  { 9, 9, 17 }, // Gamepak (wait 2)
};

// Different settings for gamepak ws0-2 sequential (2nd) access

u32 gamepak_waitstate_sequential[2][3][3] =
{
  {
    { 3, 3, 6 },
    { 5, 5, 9 },
    { 9, 9, 17 }
  },
  {
    { 2, 2, 3 },
    { 2, 2, 3 },
    { 2, 2, 3 }
  }
};

u8 bios_rom[1024 * 16];
u32 bios_read_protect;

// Up to 128kb, store SRAM, flash ROM, or EEPROM here.
u8 gamepak_backup[1024 * 128];

dma_transfer_type dma[4];

// ROM memory is allocated in blocks of 1MB to better map the native block
// mapping system. We will try to allocate 32 of them to allow loading
// ROMs up to 32MB, but we might fail on memory constrained systems.

u8 *gamepak_buffers[32];    /* Pointers to malloc'ed blocks */
u32 gamepak_buffer_count;   /* Value between 1 and 32 */
u32 gamepak_size;           /* Size of the ROM in bytes */

// LRU queue with the loaded blocks and what they map to
struct {
  u16 next_lru;             /* Index in the struct to the next LRU entry */
  s16 phy_rom;              /* ROM page number (-1 means not mapped) */
} gamepak_blk_queue[1024];

u16 gamepak_lru_head;
u16 gamepak_lru_tail;

// Stick page bit: prevents page eviction for a frame. This is used to prevent
// unmapping code pages while being used (ie. in the interpreter).
u32 gamepak_sticky_bit[1024/32];

#define gamepak_sb_test(idx) \
 (gamepak_sticky_bit[((unsigned)(idx)) >> 5] & (1 << (((unsigned)(idx)) & 31)))

// This is global so that it can be kept open for large ROMs to swap
// pages from, so there's no slowdown with opening and closing the file
// a lot.
FILE *gamepak_file_large = NULL;

// Writes to these respective locations should trigger an update
// so the related subsystem may react to it.

// If GBC audio is written to:
u32 gbc_sound_update = 0;

// If the GBC audio waveform is modified:
u32 gbc_sound_wave_update = 0;

// Keep it 32KB until the upper 64KB is accessed, then make it 64KB.

backup_type_type backup_type = BACKUP_NONE;
sram_size_type sram_size = SRAM_SIZE_32KB;

flash_mode_type flash_mode = FLASH_BASE_MODE;
u32 flash_command_position = 0;
u8 *flash_bank_ptr = gamepak_backup;

flash_device_id_type flash_device_id = FLASH_DEVICE_MACRONIX_64KB;
flash_manufacturer_id_type flash_manufacturer_id =
 FLASH_MANUFACTURER_MACRONIX;
flash_size_type flash_size = FLASH_SIZE_64KB;

u8 read_backup(u32 address)
{
  u8 value = 0;

  if(backup_type == BACKUP_NONE)
    backup_type = BACKUP_SRAM;

  if(backup_type == BACKUP_SRAM)
    value = gamepak_backup[address];
  else if(flash_mode == FLASH_ID_MODE)
  {
    if (flash_size == FLASH_SIZE_128KB)
    {
      /* ID manufacturer type */
      if(address == 0x0000)
        value = FLASH_MANUFACTURER_MACRONIX;
      /* ID device type */
      else if(address == 0x0001)
        value = FLASH_DEVICE_MACRONIX_128KB;
    }
    else
    {
      /* ID manufacturer type */
      if(address == 0x0000)
        value = FLASH_MANUFACTURER_PANASONIC;
      /* ID device type */
      else if(address == 0x0001)
        value = FLASH_DEVICE_PANASONIC_64KB;
    }
  }
  else
    value = flash_bank_ptr[address];

  return value;
}

#define read_backup8()                                                        \
  value = read_backup(address & 0xFFFF)                                       \

#define read_backup16()                                                       \
  value = 0                                                                   \

#define read_backup32()                                                       \
  value = 0                                                                   \


// EEPROM is 512 bytes by default; it is autodetecte as 8KB if
// 14bit address DMAs are made (this is done in the DMA handler).

eeprom_size_type eeprom_size = EEPROM_512_BYTE;
eeprom_mode_type eeprom_mode = EEPROM_BASE_MODE;
u32 eeprom_address_length;
u32 eeprom_address = 0;
u32 eeprom_counter = 0;
u8 eeprom_buffer[8];

void function_cc write_eeprom(u32 unused_address, u32 value)
{
  switch(eeprom_mode)
  {
    case EEPROM_BASE_MODE:
      backup_type = BACKUP_EEPROM;
      eeprom_buffer[0] |= (value & 0x01) << (1 - eeprom_counter);
      eeprom_counter++;
      if(eeprom_counter == 2)
      {
        if(eeprom_size == EEPROM_512_BYTE)
          eeprom_address_length = 6;
        else
          eeprom_address_length = 14;

        eeprom_counter = 0;

        switch(eeprom_buffer[0] & 0x03)
        {
          case 0x02:
            eeprom_mode = EEPROM_WRITE_ADDRESS_MODE;
            break;

          case 0x03:
            eeprom_mode = EEPROM_ADDRESS_MODE;
            break;
        }
        address16(eeprom_buffer, 0) = 0;
      }
      break;

    case EEPROM_ADDRESS_MODE:
    case EEPROM_WRITE_ADDRESS_MODE:
      eeprom_buffer[eeprom_counter / 8]
       |= (value & 0x01) << (7 - (eeprom_counter % 8));
      eeprom_counter++;
      if(eeprom_counter == eeprom_address_length)
      {
        if(eeprom_size == EEPROM_512_BYTE)
        {
          eeprom_address =
           (readaddress16(eeprom_buffer, 0) >> 2) * 8;
        }
        else
        {
          eeprom_address = (((u32)eeprom_buffer[1] >> 2) |
           ((u32)eeprom_buffer[0] << 6)) * 8;
        }

        address16(eeprom_buffer, 0) = 0;
        eeprom_counter = 0;

        if(eeprom_mode == EEPROM_ADDRESS_MODE)
          eeprom_mode = EEPROM_ADDRESS_FOOTER_MODE;
        else
        {
          eeprom_mode = EEPROM_WRITE_MODE;
          memset(gamepak_backup + eeprom_address, 0, 8);
        }
      }
      break;

    case EEPROM_WRITE_MODE:
      gamepak_backup[eeprom_address + (eeprom_counter / 8)] |=
       (value & 0x01) << (7 - (eeprom_counter % 8));
      eeprom_counter++;
      if(eeprom_counter == 64)
      {
        eeprom_counter = 0;
        eeprom_mode = EEPROM_WRITE_FOOTER_MODE;
      }
      break;

    case EEPROM_ADDRESS_FOOTER_MODE:
    case EEPROM_WRITE_FOOTER_MODE:
      eeprom_counter = 0;
      if(eeprom_mode == EEPROM_ADDRESS_FOOTER_MODE)
        eeprom_mode = EEPROM_READ_HEADER_MODE;
      else
        eeprom_mode = EEPROM_BASE_MODE;
      break;

    default:
      break;
  }
}

#define read_memory_gamepak(type)                                             \
  u32 gamepak_index = address >> 15;                                          \
  u8 *map = memory_map_read[gamepak_index];                                   \
                                                                              \
  if(!map)                                                                    \
    map = load_gamepak_page(gamepak_index & 0x3FF);                           \
                                                                              \
  value = readaddress##type(map, address & 0x7FFF)                            \

#define read_open8()                                                          \
  if(!(reg[REG_CPSR] & 0x20))                                                 \
    value = read_memory8(reg[REG_PC] + 4 + (address & 0x03));                 \
  else                                                                        \
    value = read_memory8(reg[REG_PC] + 2 + (address & 0x01))                  \

#define read_open16()                                                         \
  if(!(reg[REG_CPSR] & 0x20))                                                 \
    value = read_memory16(reg[REG_PC] + 4 + (address & 0x02));                \
  else                                                                        \
    value = read_memory16(reg[REG_PC] + 2)                                    \

#define read_open32()                                                         \
  if(!(reg[REG_CPSR] & 0x20))                                                 \
    value = read_memory32(reg[REG_PC] + 4);                                   \
  else                                                                        \
  {                                                                           \
    u32 current_instruction = read_memory16(reg[REG_PC] + 2);                 \
    value = current_instruction | (current_instruction << 16);                \
  }                                                                           \

u32 function_cc read_eeprom(void)
{
  u32 value;

  switch(eeprom_mode)
  {
    case EEPROM_BASE_MODE:
      value = 1;
      break;

    case EEPROM_READ_MODE:
      value = (gamepak_backup[eeprom_address + (eeprom_counter / 8)] >>
       (7 - (eeprom_counter % 8))) & 0x01;
      eeprom_counter++;
      if(eeprom_counter == 64)
      {
        eeprom_counter = 0;
        eeprom_mode = EEPROM_BASE_MODE;
      }
      break;

    case EEPROM_READ_HEADER_MODE:
      value = 0;
      eeprom_counter++;
      if(eeprom_counter == 4)
      {
        eeprom_mode = EEPROM_READ_MODE;
        eeprom_counter = 0;
      }
      break;

    default:
      value = 0;
      break;
  }

  return value;
}


#define read_memory(type)                                                     \
  switch(address >> 24)                                                       \
  {                                                                           \
    case 0x00:                                                                \
      /* BIOS */                                                              \
      if(reg[REG_PC] >= 0x4000)                                               \
        value = readaddress##type(&bios_read_protect, address & 0x03);        \
      else                                                                    \
        value = readaddress##type(bios_rom, address & 0x3FFF);                \
      break;                                                                  \
                                                                              \
    case 0x02:                                                                \
      /* external work RAM */                                                 \
      value = readaddress##type(ewram, (address & 0x3FFFF));                  \
      break;                                                                  \
                                                                              \
    case 0x03:                                                                \
      /* internal work RAM */                                                 \
      value = readaddress##type(iwram, (address & 0x7FFF) + 0x8000);          \
      break;                                                                  \
                                                                              \
    case 0x04:                                                                \
      /* I/O registers */                                                     \
      value = readaddress##type(io_registers, address & 0x3FF);               \
      break;                                                                  \
                                                                              \
    case 0x05:                                                                \
      /* palette RAM */                                                       \
      value = readaddress##type(palette_ram, address & 0x3FF);                \
      break;                                                                  \
                                                                              \
    case 0x06:                                                                \
      /* VRAM */                                                              \
      address &= 0x1FFFF;                                                     \
      if(address >= 0x18000)                                                  \
        address -= 0x8000;                                                    \
                                                                              \
      value = readaddress##type(vram, address);                               \
      break;                                                                  \
                                                                              \
    case 0x07:                                                                \
      /* OAM RAM */                                                           \
      value = readaddress##type(oam_ram, address & 0x3FF);                    \
      break;                                                                  \
                                                                              \
    case 0x08:                                                                \
    case 0x09:                                                                \
    case 0x0A:                                                                \
    case 0x0B:                                                                \
    case 0x0C:                                                                \
      /* gamepak ROM */                                                       \
      if((address & 0x1FFFFFF) >= gamepak_size)                               \
        value = 0;                                                            \
      else                                                                    \
      {                                                                       \
        read_memory_gamepak(type);                                            \
      }                                                                       \
      break;                                                                  \
                                                                              \
    case 0x0D:                                                                \
      if((address & 0x1FFFFFF) < gamepak_size)                                \
      {                                                                       \
        read_memory_gamepak(type);                                            \
      }                                                                       \
      else                                                                    \
        value = read_eeprom();                                                \
                                                                              \
      break;                                                                  \
                                                                              \
    case 0x0E:                                                                \
    case 0x0F:                                                                \
      read_backup##type();                                                    \
      break;                                                                  \
                                                                              \
    default:                                                                  \
      read_open##type();                                                      \
      break;                                                                  \
  }                                                                           \

static cpu_alert_type trigger_dma(u32 dma_number, u32 value)
{
  if(value & 0x8000)
  {
    if(dma[dma_number].start_type == DMA_INACTIVE)
    {
      dma_start_type start_type = (dma_start_type)((value >> 12) & 0x03);
      u32 dest_address = readaddress32(io_registers, (dma_number * 12) + 0xB4) &
       0xFFFFFFF;

      dma[dma_number].dma_channel = dma_number;
      dma[dma_number].source_address =
       readaddress32(io_registers, (dma_number * 12) + 0xB0) & 0xFFFFFFF;
      dma[dma_number].dest_address = dest_address;
      dma[dma_number].source_direction = (dma_increment_type)((value >>  7) & 3);
      dma[dma_number].repeat_type = (dma_repeat_type)((value >> 9) & 0x01);
      dma[dma_number].start_type = start_type;
      dma[dma_number].irq = (dma_irq_type)((value >> 14) & 0x1);

      /* If it is sound FIFO DMA make sure the settings are a certain way */
      if((dma_number >= 1) && (dma_number <= 2) &&
       (start_type == DMA_START_SPECIAL))
      {
        dma[dma_number].length_type = DMA_32BIT;
        dma[dma_number].length = 4;
        dma[dma_number].dest_direction = DMA_FIXED;
        if(dest_address == 0x40000A4)
          dma[dma_number].direct_sound_channel = DMA_DIRECT_SOUND_B;
        else
          dma[dma_number].direct_sound_channel = DMA_DIRECT_SOUND_A;
      }
      else
      {
        u32 length = read_ioreg(REG_DMA0CNT_L + (dma_number * 6));

        if((dma_number == 3) && ((dest_address >> 24) == 0x0D) &&
         ((length & 0x1F) == 17))
          eeprom_size = EEPROM_8_KBYTE;

        if(dma_number < 3)
          length &= 0x3FFF;

        if(length == 0)
        {
          if(dma_number == 3)
            length = 0x10000;
          else
            length = 0x04000;
        }

        dma[dma_number].length = length;
        dma[dma_number].length_type = (dma_length_type)((value >> 10) & 0x01);
        dma[dma_number].dest_direction = (dma_increment_type)((value >> 5) & 3);
      }

      write_ioreg(REG_DMA0CNT_H + (dma_number * 6), value);
      if(start_type == DMA_START_IMMEDIATELY)
        return dma_transfer(dma + dma_number);
    }
  }
  else
  {
    dma[dma_number].start_type = DMA_INACTIVE;
    dma[dma_number].direct_sound_channel = DMA_NO_DIRECT_SOUND;
    write_ioreg(REG_DMA0CNT_H + (dma_number * 6), value);
  }

  return CPU_ALERT_NONE;
}


#define access_register8_high(address)                                        \
  value = (value << 8) | (address8(io_registers, address))                    \

#define access_register8_low(address)                                         \
  value = ((address8(io_registers, address + 1)) << 8) | value                \

#define access_register16_high(address)                                       \
  value = (value << 16) | (readaddress16(io_registers, address))              \

#define access_register16_low(address)                                        \
  value = ((readaddress16(io_registers, address + 2)) << 16) | value          \

cpu_alert_type function_cc write_io_register8(u32 address, u32 value)
{
  value &= 0xff;
  switch(address)
  {
    case 0x00:
    {
      u32 dispcnt = read_ioreg(REG_DISPCNT);

      if((value & 0x07) != (dispcnt & 0x07))
        reg[OAM_UPDATED] = 1;

      address8(io_registers, 0x00) = value;
      break;
    }

    // DISPSTAT (lower byte)
    case 0x04:
      address8(io_registers, 0x04) =
       (address8(io_registers, 0x04) & 0x07) | (value & ~0x07);
      break;

    // VCOUNT
    case 0x06:
    case 0x07:
      break;

    // BG2 reference X
    case 0x28:
      access_register8_low(0x28);
      access_register16_low(0x28);
      affine_reference_x[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x28) = eswap32(value);
      break;

    case 0x29:
      access_register8_high(0x28);
      access_register16_low(0x28);
      affine_reference_x[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x28) = eswap32(value);
      break;

    case 0x2A:
      access_register8_low(0x2A);
      access_register16_high(0x28);
      affine_reference_x[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x28) = eswap32(value);
      break;

    case 0x2B:
      access_register8_high(0x2A);
      access_register16_high(0x28);
      affine_reference_x[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x28) = eswap32(value);
      break;

    // BG2 reference Y
    case 0x2C:
      access_register8_low(0x2C);
      access_register16_low(0x2C);
      affine_reference_y[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x2C) = eswap32(value);
      break;

    case 0x2D:
      access_register8_high(0x2C);
      access_register16_low(0x2C);
      affine_reference_y[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x2C) = eswap32(value);
      break;

    case 0x2E:
      access_register8_low(0x2E);
      access_register16_high(0x2C);
      affine_reference_y[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x2C) = eswap32(value);
      break;

    case 0x2F:
      access_register8_high(0x2E);
      access_register16_high(0x2C);
      affine_reference_y[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x2C) = eswap32(value);
      break;

    // BG3 reference X
    case 0x38:
      access_register8_low(0x38);
      access_register16_low(0x38);
      affine_reference_x[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x38) = eswap32(value);
      break;

    case 0x39:
      access_register8_high(0x38);
      access_register16_low(0x38);
      affine_reference_x[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x38) = eswap32(value);
      break;

    case 0x3A:
      access_register8_low(0x3A);
      access_register16_high(0x38);
      affine_reference_x[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x38) = eswap32(value);
      break;

    case 0x3B:
      access_register8_high(0x3A);
      access_register16_high(0x38);
      affine_reference_x[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x38) = eswap32(value);
      break;

    // BG3 reference Y
    case 0x3C:
      access_register8_low(0x3C);
      access_register16_low(0x3C);
      affine_reference_y[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x3C) = eswap32(value);
      break;

    case 0x3D:
      access_register8_high(0x3C);
      access_register16_low(0x3C);
      affine_reference_y[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x3C) = eswap32(value);
      break;

    case 0x3E:
      access_register8_low(0x3E);
      access_register16_high(0x3C);
      affine_reference_y[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x3C) = eswap32(value);
      break;

    case 0x3F:
      access_register8_high(0x3E);
      access_register16_high(0x3C);
      affine_reference_y[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x3C) = eswap32(value);
      break;

    // Sound 1 control sweep
    case 0x60:
      access_register8_low(0x60);
      gbc_sound_tone_control_sweep();
      break;

    case 0x61:
      access_register8_low(0x60);
      gbc_sound_tone_control_sweep();
      break;

    // Sound 1 control duty/length/envelope
    case 0x62:
      access_register8_low(0x62);
      gbc_sound_tone_control_low(0, REG_SOUND1CNT_H);
      break;

    case 0x63:
      access_register8_high(0x62);
      gbc_sound_tone_control_low(0, REG_SOUND1CNT_H);
      break;

    // Sound 1 control frequency
    case 0x64:
      access_register8_low(0x64);
      gbc_sound_tone_control_high(0, REG_SOUND1CNT_X);
      break;

    case 0x65:
      access_register8_high(0x64);
      gbc_sound_tone_control_high(0, REG_SOUND1CNT_X);
      break;

    // Sound 2 control duty/length/envelope
    case 0x68:
      access_register8_low(0x68);
      gbc_sound_tone_control_low(1, REG_SOUND2CNT_L);
      break;

    case 0x69:
      access_register8_high(0x68);
      gbc_sound_tone_control_low(1, REG_SOUND2CNT_L);
      break;

    // Sound 2 control frequency
    case 0x6C:
      access_register8_low(0x6C);
      gbc_sound_tone_control_high(1, REG_SOUND2CNT_H);
      break;

    case 0x6D:
      access_register8_high(0x6C);
      gbc_sound_tone_control_high(1, REG_SOUND2CNT_H);
      break;

    // Sound 3 control wave
    case 0x70:
      access_register8_low(0x70);
      gbc_sound_wave_control();
      break;

    case 0x71:
      access_register8_high(0x70);
      gbc_sound_wave_control();
      break;

    // Sound 3 control length/volume
    case 0x72:
      access_register8_low(0x72);
      gbc_sound_tone_control_low_wave();
      break;

    case 0x73:
      access_register8_high(0x72);
      gbc_sound_tone_control_low_wave();
      break;

    // Sound 3 control frequency
    case 0x74:
      access_register8_low(0x74);
      gbc_sound_tone_control_high_wave();
      break;

    case 0x75:
      access_register8_high(0x74);
      gbc_sound_tone_control_high_wave();
      break;

    // Sound 4 control length/envelope
    case 0x78:
      access_register8_low(0x78);
      gbc_sound_tone_control_low(3, REG_SOUND4CNT_L);
      break;

    case 0x79:
      access_register8_high(0x78);
      gbc_sound_tone_control_low(3, REG_SOUND4CNT_L);
      break;

    // Sound 4 control frequency
    case 0x7C:
      access_register8_low(0x7C);
      gbc_sound_noise_control();
      break;

    case 0x7D:
      access_register8_high(0x7C);
      gbc_sound_noise_control();
      break;

    // Sound control L
    case 0x80:
      access_register8_low(0x80);
      gbc_trigger_sound(value);
      break;

    case 0x81:
      access_register8_high(0x80);
      gbc_trigger_sound(value);
      break;

    // Sound control H
    case 0x82:
      access_register8_low(0x82);
      trigger_sound();
      break;

    case 0x83:
      access_register8_high(0x82);
      trigger_sound();
      break;

    // Sound control X
    case 0x84:
      sound_control_x(value);
      break;

    // Sound wave RAM
    case 0x90 ... 0x9F:
      gbc_sound_wave_update = 1;
      address8(io_registers, address) = value;
      break;

    // Sound FIFO A
    case 0xA0:
      sound_timer_queue8(0, value);
      break;

    // Sound FIFO B
    case 0xA4:
      sound_timer_queue8(1, value);
      break;

    // DMA control (trigger byte)
    case 0xBB:
      access_register8_low(0xBA);
      return trigger_dma(0, value);

    case 0xC7:
      access_register8_low(0xC6);
      return trigger_dma(1, value);

    case 0xD3:
      access_register8_low(0xD2);
      return trigger_dma(2, value);

    case 0xDF:
      access_register8_low(0xDE);
      return trigger_dma(3, value);

    // Timer counts
    case 0x100:
      access_register8_low(0x100);
      count_timer(0);
      break;

    case 0x101:
      access_register8_high(0x100);
      count_timer(0);
      break;

    case 0x104:
      access_register8_low(0x104);
      count_timer(1);
      break;

    case 0x105:
      access_register8_high(0x104);
      count_timer(1);
      break;

    case 0x108:
      access_register8_low(0x108);
      count_timer(2);
      break;

    case 0x109:
      access_register8_high(0x108);
      count_timer(2);
      break;

    case 0x10C:
      access_register8_low(0x10C);
      count_timer(3);
      break;

    case 0x10D:
      access_register8_high(0x10C);
      count_timer(3);
      break;

    // Timer control (trigger byte)
    case 0x103:
      access_register8_low(0x102);
      trigger_timer(0, value);
      break;

    case 0x107:
      access_register8_low(0x106);
      trigger_timer(1, value);
      break;

    case 0x10B:
      access_register8_low(0x10A);
      trigger_timer(2, value);
      break;

    case 0x10F:
      access_register8_low(0x10E);
      trigger_timer(3, value);
      break;

    // IF
    case 0x202:
      address8(io_registers, 0x202) &= ~value;
      break;

    case 0x203:
      address8(io_registers, 0x203) &= ~value;
      break;

    // Halt
    case 0x301:
      if((value & 0x01) == 0)
        reg[CPU_HALT_STATE] = CPU_HALT;
      else
        reg[CPU_HALT_STATE] = CPU_STOP;

      return CPU_ALERT_HALT;
      break;

    default:
      address8(io_registers, address) = value;
      break;
  }

  return CPU_ALERT_NONE;
}

cpu_alert_type function_cc write_io_register16(u32 address, u32 value)
{
  value &= 0xffff;
  switch(address)
  {
    case 0x00:
    {
      u32 dispcnt = read_ioreg(REG_DISPCNT);
      if((value & 0x07) != (dispcnt & 0x07))
        reg[OAM_UPDATED] = 1;

      write_ioreg(REG_DISPCNT, value);
      break;
    }

    // DISPSTAT
    case 0x04:
      write_ioreg(REG_DISPSTAT, (read_ioreg(REG_DISPSTAT) & 0x07) | (value & ~0x07));
      break;

    // VCOUNT
    case 0x06:
      break;

    // BG2 reference X
    case 0x28:
      access_register16_low(0x28);
      affine_reference_x[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x28) = eswap32(value);
      break;

    case 0x2A:
      access_register16_high(0x28);
      affine_reference_x[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x28) = eswap32(value);
      break;

    // BG2 reference Y
    case 0x2C:
      access_register16_low(0x2C);
      affine_reference_y[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x2C) = eswap32(value);
      break;

    case 0x2E:
      access_register16_high(0x2C);
      affine_reference_y[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x2C) = eswap32(value);
      break;

    // BG3 reference X

    case 0x38:
      access_register16_low(0x38);
      affine_reference_x[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x38) = eswap32(value);
      break;

    case 0x3A:
      access_register16_high(0x38);
      affine_reference_x[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x38) = eswap32(value);
      break;

    // BG3 reference Y
    case 0x3C:
      access_register16_low(0x3C);
      affine_reference_y[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x3C) = eswap32(value);
      break;

    case 0x3E:
      access_register16_high(0x3C);
      affine_reference_y[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x3C) = eswap32(value);
      break;

    // Sound 1 control sweep
    case 0x60:
      gbc_sound_tone_control_sweep();
      break;

    // Sound 1 control duty/length/envelope
    case 0x62:
      gbc_sound_tone_control_low(0, REG_SOUND1CNT_H);
      break;

    // Sound 1 control frequency
    case 0x64:
      gbc_sound_tone_control_high(0, REG_SOUND1CNT_X);
      break;

    // Sound 2 control duty/length/envelope
    case 0x68:
      gbc_sound_tone_control_low(1, REG_SOUND2CNT_L);
      break;

    // Sound 2 control frequency
    case 0x6C:
      gbc_sound_tone_control_high(1, REG_SOUND2CNT_H);
      break;

    // Sound 3 control wave
    case 0x70:
      gbc_sound_wave_control();
      break;

    // Sound 3 control length/volume
    case 0x72:
      gbc_sound_tone_control_low_wave();
      break;

    // Sound 3 control frequency
    case 0x74:
      gbc_sound_tone_control_high_wave();
      break;

    // Sound 4 control length/envelope
    case 0x78:
      gbc_sound_tone_control_low(3, REG_SOUND4CNT_L);
      break;

    // Sound 4 control frequency
    case 0x7C:
      gbc_sound_noise_control();
      break;

    // Sound control L
    case 0x80:
      gbc_trigger_sound(value);
      break;

    // Sound control H
    case 0x82:
      trigger_sound();
      break;

    // Sound control X
    case 0x84:
      sound_control_x(value);
      break;

    // Sound wave RAM
    case 0x90 ... 0x9E:
      gbc_sound_wave_update = 1;
      address16(io_registers, address) = eswap16(value);
      break;

    // Sound FIFO A
    case 0xA0:
      sound_timer_queue16(0, value);
      break;

    // Sound FIFO B
    case 0xA4:
      sound_timer_queue16(1, value);
      break;

    // DMA control
    case 0xBA:
      return trigger_dma(0, value);

    case 0xC6:
      return trigger_dma(1, value);

    case 0xD2:
      return trigger_dma(2, value);

    case 0xDE:
      return trigger_dma(3, value);

    // Timer counts
    case 0x100:
      count_timer(0);
      break;

    case 0x104:
      count_timer(1);
      break;

    case 0x108:
      count_timer(2);
      break;

    case 0x10C:
      count_timer(3);
      break;

    /* Timer control 0 */
    case 0x102:
      trigger_timer(0, value);
      break;

    /* Timer control 1 */
    case 0x106:
      trigger_timer(1, value);
      break;

    /* Timer control 2 */
    case 0x10A:
      trigger_timer(2, value);
      break;

    /* Timer control 3 */
    case 0x10E:
      trigger_timer(3, value);
      break;

    // P1
    case 0x130:
      break;

    // Interrupt flag
    case 0x202:
      write_ioreg(REG_IF, read_ioreg(REG_IF) & (~value));
      break;

    // WAITCNT
    case 0x204:
      break;

    // Halt
    case 0x300:
      if(((value >> 8) & 0x01) == 0)
        reg[CPU_HALT_STATE] = CPU_HALT;
      else
        reg[CPU_HALT_STATE] = CPU_STOP;

      return CPU_ALERT_HALT;

    default:
      address16(io_registers, address) = eswap16(value);
      break;
  }

  return CPU_ALERT_NONE;
}


cpu_alert_type function_cc write_io_register32(u32 address, u32 value)
{
  switch(address)
  {
    // BG2 reference X
    case 0x28:
      affine_reference_x[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x28) = eswap32(value);
      break;

    // BG2 reference Y
    case 0x2C:
      affine_reference_y[0] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x2C) = eswap32(value);
      break;

    // BG3 reference X
    case 0x38:
      affine_reference_x[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x38) = eswap32(value);
      break;

    // BG3 reference Y
    case 0x3C:
      affine_reference_y[1] = (s32)(value << 4) >> 4;
      address32(io_registers, 0x3C) = eswap32(value);
      break;

    // Sound FIFO A
    case 0xA0:
      sound_timer_queue32(0, value);
      break;

    // Sound FIFO B
    case 0xA4:
      sound_timer_queue32(1, value);
      break;

    default:
    {
      cpu_alert_type alert_low =
        write_io_register16(address, value & 0xFFFF);

      cpu_alert_type alert_high =
        write_io_register16(address + 2, value >> 16);

      if(alert_high)
        return alert_high;

      return alert_low;
    }
  }

  return CPU_ALERT_NONE;
}

#define write_palette8(address, value)                                        \

#define write_palette16(address, value)                                       \
{                                                                             \
  u32 palette_address = address;                                              \
  address16(palette_ram, palette_address) = eswap16(value);                   \
  convert_palette(value);                                                     \
  address16(palette_ram_converted, palette_address) = value;                  \
}                                                                             \

#define write_palette32(address, value)                                       \
{                                                                             \
  u32 palette_address = address;                                              \
  u32 value_high = value >> 16;                                               \
  u32 value_low = value & 0xFFFF;                                             \
  address32(palette_ram, palette_address) = eswap32(value);                   \
  convert_palette(value_high);                                                \
  address16(palette_ram_converted, palette_address + 2) = value_high;         \
  convert_palette(value_low);                                                 \
  address16(palette_ram_converted, palette_address) = value_low;              \
}                                                                             \


void function_cc write_backup(u32 address, u32 value)
{
  value &= 0xFF;

  if(backup_type == BACKUP_NONE)
    backup_type = BACKUP_SRAM;


  // gamepak SRAM or Flash ROM
  if((address == 0x5555) && (flash_mode != FLASH_WRITE_MODE))
  {
    if((flash_command_position == 0) && (value == 0xAA))
    {
      backup_type = BACKUP_FLASH;
      flash_command_position = 1;
    }

    if(flash_command_position == 2)
    {
      switch(value)
      {
        case 0x90:
          // Enter ID mode, this also tells the emulator that we're using
          // flash, not SRAM

          if(flash_mode == FLASH_BASE_MODE)
            flash_mode = FLASH_ID_MODE;

          break;

        case 0x80:
          // Enter erase mode
          if(flash_mode == FLASH_BASE_MODE)
            flash_mode = FLASH_ERASE_MODE;
          break;

        case 0xF0:
          // Terminate ID mode
          if(flash_mode == FLASH_ID_MODE)
            flash_mode = FLASH_BASE_MODE;
          break;

        case 0xA0:
          // Write mode
          if(flash_mode == FLASH_BASE_MODE)
            flash_mode = FLASH_WRITE_MODE;
          break;

        case 0xB0:
          // Bank switch
          // Here the chip is now officially 128KB.
          flash_size = FLASH_SIZE_128KB;
          if(flash_mode == FLASH_BASE_MODE)
            flash_mode = FLASH_BANKSWITCH_MODE;
          break;

        case 0x10:
          // Erase chip
          if(flash_mode == FLASH_ERASE_MODE)
          {
            if(flash_size == FLASH_SIZE_64KB)
              memset(gamepak_backup, 0xFF, 1024 * 64);
            else
              memset(gamepak_backup, 0xFF, 1024 * 128);
            flash_mode = FLASH_BASE_MODE;
          }
          break;

        default:
          break;
      }
      flash_command_position = 0;
    }
    if(backup_type == BACKUP_SRAM)
      gamepak_backup[0x5555] = value;
  }
  else

  if((address == 0x2AAA) && (value == 0x55) &&
   (flash_command_position == 1))
    flash_command_position = 2;
  else
  {
    if((flash_command_position == 2) &&
     (flash_mode == FLASH_ERASE_MODE) && (value == 0x30))
    {
      // Erase sector
      memset(flash_bank_ptr + (address & 0xF000), 0xFF, 1024 * 4);
      flash_mode = FLASH_BASE_MODE;
      flash_command_position = 0;
    }
    else

    if((flash_command_position == 0) &&
     (flash_mode == FLASH_BANKSWITCH_MODE) && (address == 0x0000) &&
     (flash_size == FLASH_SIZE_128KB))
    {
      flash_bank_ptr = gamepak_backup + ((value & 0x01) * (1024 * 64));
      flash_mode = FLASH_BASE_MODE;
    }
    else

    if((flash_command_position == 0) && (flash_mode == FLASH_WRITE_MODE))
    {
      // Write value to flash ROM
      flash_bank_ptr[address] = value;
      flash_mode = FLASH_BASE_MODE;
    }
    else

    if(backup_type == BACKUP_SRAM)
    {
      // Write value to SRAM
      // Hit 64KB territory?
      if(address >= 0x8000)
        sram_size = SRAM_SIZE_64KB;
      gamepak_backup[address] = value;
    }
  }
}

#define write_backup8()                                                       \
  write_backup(address & 0xFFFF, value)                                       \

#define write_backup16()                                                      \

#define write_backup32()                                                      \

#define write_vram8()                                                         \
  address &= ~0x01;                                                           \
  address16(vram, address) = eswap16((value << 8) | value)                    \

#define write_vram16()                                                        \
  address16(vram, address) = eswap16(value)                                   \

#define write_vram32()                                                        \
  address32(vram, address) = eswap32(value)                                   \

// RTC code derived from VBA's (due to lack of any real publically available
// documentation...)

typedef enum
{
  RTC_DISABLED,
  RTC_IDLE,
  RTC_COMMAND,
  RTC_OUTPUT_DATA,
  RTC_INPUT_DATA
} rtc_state_type;

typedef enum
{
  RTC_COMMAND_RESET            = 0x60,
  RTC_COMMAND_WRITE_STATUS     = 0x62,
  RTC_COMMAND_READ_STATUS      = 0x63,
  RTC_COMMAND_OUTPUT_TIME_FULL = 0x65,
  RTC_COMMAND_OUTPUT_TIME      = 0x67
} rtc_command_type;

typedef enum
{
  RTC_WRITE_TIME,
  RTC_WRITE_TIME_FULL,
  RTC_WRITE_STATUS
} rtc_write_mode_type;

rtc_state_type rtc_state = RTC_DISABLED;
rtc_write_mode_type rtc_write_mode;
u8 rtc_registers[3];
u32 rtc_command;
u32 rtc_data[12];
u32 rtc_status = 0x40;
u32 rtc_data_bytes;
s32 rtc_bit_count;

static u32 encode_bcd(u8 value)
{
  int l = 0;
  int h = 0;

  value = value % 100;
  l = value % 10;
  h = value / 10;

  return h * 16 + l;
}

// RTC writes need to reflect in the bytes [0xC4..0xC9] of the gamepak
#define write_rtc_register(index, _value)                                     \
  update_address = 0x80000C4 + (index * 2);                                   \
  rtc_registers[index] = _value;                                              \
  rtc_page_index = update_address >> 15;                                      \
  map = memory_map_read[rtc_page_index];                                      \
                                                                              \
  if(map) {                                                                   \
    address16(map, update_address & 0x7FFF) = eswap16(_value);                \
  }                                                                           \

void function_cc write_rtc(u32 address, u32 value)
{
  u32 rtc_page_index;
  u32 update_address;
  u8 *map;

  value &= 0xFFFF;

  switch(address)
  {
    // RTC command
    // Bit 0: SCHK, perform action
    // Bit 1: IO, input/output command data
    // Bit 2: CS, select input/output? If high make I/O write only
    case 0xC4:
      if(rtc_state == RTC_DISABLED)
        rtc_state = RTC_IDLE;
      if(!(rtc_registers[0] & 0x04))
        value = (rtc_registers[0] & 0x02) | (value & ~0x02);
      if(rtc_registers[2] & 0x01)
      {
        // To begin writing a command 1, 5 must be written to the command
        // registers.
        if((rtc_state == RTC_IDLE) && (rtc_registers[0] == 0x01) &&
         (value == 0x05))
        {
          // We're now ready to begin receiving a command.
          write_rtc_register(0, value);
          rtc_state = RTC_COMMAND;
          rtc_command = 0;
          rtc_bit_count = 7;
        }
        else
        {
          write_rtc_register(0, value);
          switch(rtc_state)
          {
            // Accumulate RTC command by receiving the next bit, and if we
            // have accumulated enough bits to form a complete command
            // execute it.
            case RTC_COMMAND:
              if(rtc_registers[0] & 0x01)
              {
                rtc_command |= ((value & 0x02) >> 1) << rtc_bit_count;
                rtc_bit_count--;
              }

              // Have we received a full RTC command? If so execute it.
              if(rtc_bit_count < 0)
              {
                switch(rtc_command)
                {
                  // Resets RTC
                  case RTC_COMMAND_RESET:
                    rtc_state = RTC_IDLE;
                    memset(rtc_registers, 0, sizeof(rtc_registers));
                    break;

                  // Sets status of RTC
                  case RTC_COMMAND_WRITE_STATUS:
                    rtc_state = RTC_INPUT_DATA;
                    rtc_data_bytes = 1;
                    rtc_write_mode = RTC_WRITE_STATUS;
                    break;

                  // Outputs current status of RTC
                  case RTC_COMMAND_READ_STATUS:
                    rtc_state = RTC_OUTPUT_DATA;
                    rtc_data_bytes = 1;
                    rtc_data[0] = rtc_status;
                    break;

                  // Actually outputs the time, all of it
                  case RTC_COMMAND_OUTPUT_TIME_FULL:
                  {
                    struct tm *current_time;
                    time_t current_time_flat;

                    time(&current_time_flat);
                    current_time = localtime(&current_time_flat);

                    rtc_state = RTC_OUTPUT_DATA;
                    rtc_data_bytes = 7;
                    rtc_data[0] = encode_bcd(current_time->tm_year);
                    rtc_data[1] = encode_bcd(current_time->tm_mon + 1);
                    rtc_data[2] = encode_bcd(current_time->tm_mday);
                    rtc_data[3] = encode_bcd(current_time->tm_wday);
                    rtc_data[4] = encode_bcd(current_time->tm_hour);
                    rtc_data[5] = encode_bcd(current_time->tm_min);
                    rtc_data[6] = encode_bcd(current_time->tm_sec);

                    break;
                  }

                  // Only outputs the current time of day.
                  case RTC_COMMAND_OUTPUT_TIME:
                  {
                    struct tm *current_time;
                    time_t current_time_flat;

                    time(&current_time_flat);
                    current_time = localtime(&current_time_flat);

                    rtc_state = RTC_OUTPUT_DATA;
                    rtc_data_bytes = 3;
                    rtc_data[0] = encode_bcd(current_time->tm_hour);
                    rtc_data[1] = encode_bcd(current_time->tm_min);
                    rtc_data[2] = encode_bcd(current_time->tm_sec);
                    break;
                  }
                }
                rtc_bit_count = 0;
              }
              break;

            // Receive parameters from the game as input to the RTC
            // for a given command. Read one bit at a time.
            case RTC_INPUT_DATA:
              // Bit 1 of parameter A must be high for input
              if(rtc_registers[1] & 0x02)
              {
                // Read next bit for input
                if(!(value & 0x01))
                {
                  rtc_data[rtc_bit_count >> 3] |=
                   ((value & 0x01) << (7 - (rtc_bit_count & 0x07)));
                }
                else
                {
                  rtc_bit_count++;

                  if(rtc_bit_count == (signed)(rtc_data_bytes * 8))
                  {
                    rtc_state = RTC_IDLE;
                    switch(rtc_write_mode)
                    {
                      case RTC_WRITE_STATUS:
                        rtc_status = rtc_data[0];
                        break;

                      default:
                        break;
                    }
                  }
                }
              }
              break;

            case RTC_OUTPUT_DATA:
              // Bit 1 of parameter A must be low for output
              if(!(rtc_registers[1] & 0x02))
              {
                // Write next bit to output, on bit 1 of parameter B
                if(!(value & 0x01))
                {
                  u8 current_output_byte = rtc_registers[2];

                  current_output_byte =
                   (current_output_byte & ~0x02) |
                   (((rtc_data[rtc_bit_count >> 3] >>
                   (rtc_bit_count & 0x07)) & 0x01) << 1);

                  write_rtc_register(0, current_output_byte);

                }
                else
                {
                  rtc_bit_count++;

                  if(rtc_bit_count == (signed)(rtc_data_bytes * 8))
                  {
                    rtc_state = RTC_IDLE;
                    memset(rtc_registers, 0, sizeof(rtc_registers));
                  }
                }
              }
              break;

            default:
              break;
          }
        }
      }
      else
      {
        write_rtc_register(2, value);
      }
      break;

    // Write parameter A
    case 0xC6:
      write_rtc_register(1, value);
      break;

    // Write parameter B
    case 0xC8:
      write_rtc_register(2, value);
      break;
  }
}

#define write_rtc8()                                                          \

#define write_rtc16()                                                         \
  write_rtc(address & 0xFF, value)                                            \

#define write_rtc32()                                                         \

#define write_memory(type)                                                    \
  switch(address >> 24)                                                       \
  {                                                                           \
    case 0x02:                                                                \
      /* external work RAM */                                                 \
      address##type(ewram, (address & 0x3FFFF)) = eswap##type(value);         \
      break;                                                                  \
                                                                              \
    case 0x03:                                                                \
      /* internal work RAM */                                                 \
      address##type(iwram, (address & 0x7FFF) + 0x8000) = eswap##type(value); \
      break;                                                                  \
                                                                              \
    case 0x04:                                                                \
      /* I/O registers */                                                     \
      return write_io_register##type(address & 0x3FF, value);                 \
                                                                              \
    case 0x05:                                                                \
      /* palette RAM */                                                       \
      write_palette##type(address & 0x3FF, value);                            \
      break;                                                                  \
                                                                              \
    case 0x06:                                                                \
      /* VRAM */                                                              \
      address &= 0x1FFFF;                                                     \
      if(address >= 0x18000)                                                  \
        address -= 0x8000;                                                    \
                                                                              \
      write_vram##type();                                                     \
      break;                                                                  \
                                                                              \
    case 0x07:                                                                \
      /* OAM RAM */                                                           \
      reg[OAM_UPDATED] = 1;                                                   \
      address##type(oam_ram, address & 0x3FF) = eswap##type(value);           \
      break;                                                                  \
                                                                              \
    case 0x08:                                                                \
      /* gamepak ROM or RTC */                                                \
      write_rtc##type();                                                      \
      break;                                                                  \
                                                                              \
    case 0x09:                                                                \
    case 0x0A:                                                                \
    case 0x0B:                                                                \
    case 0x0C:                                                                \
      /* gamepak ROM space */                                                 \
      break;                                                                  \
                                                                              \
    case 0x0D:                                                                \
      write_eeprom(address, value);                                           \
      break;                                                                  \
                                                                              \
    case 0x0E:                                                                \
      write_backup##type();                                                   \
      break;                                                                  \
  }                                                                           \

u8 function_cc read_memory8(u32 address)
{
  u8 value;
  read_memory(8);
  return value;
}

u32 read_memory8s(u32 address) {
  return (u32)((s8)read_memory8(address));
}

u16 function_cc read_memory16_signed(u32 address)
{
  u16 value;

  if(address & 0x01)
    return (s8)read_memory8(address);

  read_memory(16);

  return value;
}

u32 read_memory16s(u32 address) {
  return (u32)((s16)read_memory16_signed(address));
}

// unaligned reads are actually 32bit

u32 function_cc read_memory16(u32 address)
{
  u32 value;
  bool unaligned = (address & 0x01);
  address &= ~0x01;
  read_memory(16);
  if (unaligned) {
    ror(value, value, 8);
  }

  return value;
}


u32 function_cc read_memory32(u32 address)
{
  u32 value;
  u32 rotate = (address & 0x03) * 8;
  address &= ~0x03;
  read_memory(32);
  ror(value, value, rotate);
  return value;
}

cpu_alert_type function_cc write_memory8(u32 address, u8 value)
{
  write_memory(8);
  return CPU_ALERT_NONE;
}

cpu_alert_type function_cc write_memory16(u32 address, u16 value)
{
  write_memory(16);
  return CPU_ALERT_NONE;
}

cpu_alert_type function_cc write_memory32(u32 address, u32 value)
{
  write_memory(32);
  return CPU_ALERT_NONE;
}

char backup_filename[512];

u32 load_backup(char *name)
{
  FILE *fd = fopen(name, "rb");

  if(fd)
  {
    u32 backup_size = file_length(fd);

    fread(gamepak_backup, 1, backup_size, fd);
    fclose(fd);

    // The size might give away what kind of backup it is.
    switch(backup_size)
    {
      case 0x200:
        backup_type = BACKUP_EEPROM;
        eeprom_size = EEPROM_512_BYTE;
        break;

      case 0x2000:
        backup_type = BACKUP_EEPROM;
        eeprom_size = EEPROM_8_KBYTE;
        break;

      case 0x8000:
        backup_type = BACKUP_SRAM;
        sram_size = SRAM_SIZE_32KB;
        break;

      // Could be either flash or SRAM, go with flash
      case 0x10000:
        backup_type = BACKUP_FLASH;
        sram_size = (sram_size_type)FLASH_SIZE_64KB;
        break;

      case 0x20000:
        backup_type = BACKUP_FLASH;
        flash_size = FLASH_SIZE_128KB;
        break;
    }
    return 1;
  }
  else
  {
    backup_type = BACKUP_NONE;
    memset(gamepak_backup, 0xFF, 1024 * 128);
  }

  return 0;
}

u32 save_backup(char *name)
{
  if(backup_type != BACKUP_NONE)
  {
    FILE *fd = fopen(name, "wb");

    if(fd)
    {
      u32 backup_size = 0;

      switch(backup_type)
      {
        case BACKUP_SRAM:
          if(sram_size == SRAM_SIZE_32KB)
            backup_size = 0x8000;
          else
            backup_size = 0x10000;
          break;

        case BACKUP_FLASH:
          if(flash_size == FLASH_SIZE_64KB)
            backup_size = 0x10000;
          else
            backup_size = 0x20000;
          break;

        case BACKUP_EEPROM:
          if(eeprom_size == EEPROM_512_BYTE)
            backup_size = 0x200;
          else
            backup_size = 0x2000;
          break;

        default:
          break;
      }

      fwrite(gamepak_backup, 1, backup_size, fd);
      fclose(fd);
      return 1;
    }
  }

  return 0;
}

void update_backup(void)
{
  if (!use_libretro_save_method)
    save_backup(backup_filename);
}

#define CONFIG_FILENAME "game_config.txt"

static char *skip_spaces(char *line_ptr)
{
  while(*line_ptr == ' ')
    line_ptr++;

  return line_ptr;
}

static s32 parse_config_line(char *current_line, char *current_variable, char *current_value)
{
  char *line_ptr = current_line;
  char *line_ptr_new;

  if((current_line[0] == 0) || (current_line[0] == '#'))
    return -1;

  line_ptr_new = strchr(line_ptr, ' ');
  if(!line_ptr_new)
    return -1;

  *line_ptr_new = 0;
  strcpy(current_variable, line_ptr);
  line_ptr_new = skip_spaces(line_ptr_new + 1);

  if(*line_ptr_new != '=')
    return -1;

  line_ptr_new = skip_spaces(line_ptr_new + 1);
  strcpy(current_value, line_ptr_new);
  line_ptr_new = current_value + strlen(current_value) - 1;
  if(*line_ptr_new == '\n')
  {
    line_ptr_new--;
    *line_ptr_new = 0;
  }

  if(*line_ptr_new == '\r')
    *line_ptr_new = 0;

  return 0;
}

typedef struct
{
   char gamepak_title[13];
   char gamepak_code[5];
   char gamepak_maker[3];
   int flash_size;
   flash_device_id_type flash_device_id;
   int save_type;
   int rtc_enabled;
   int mirroring_enabled;
   int use_bios;
   u32 idle_loop_target_pc;
   u32 iwram_stack_optimize;
   u32 translation_gate_target_1;
   u32 translation_gate_target_2;
   u32 translation_gate_target_3;
} ini_t;

typedef struct
{
   char gamepak_title[13];
   char gamepak_code[5];
   char gamepak_maker[3];
} gamepak_info_t;

#include "gba_over.h"

static s32 load_game_config_over(gamepak_info_t *gpinfo)
{
  unsigned i = 0;

  for (i = 0; i < sizeof(gbaover)/sizeof(gbaover[0]); i++)
  {
     if (strcmp(gbaover[i].gamepak_code, gpinfo->gamepak_code))
        continue;

     if (strcmp(gbaover[i].gamepak_title, gpinfo->gamepak_title))
        continue;
     
     printf("gamepak title: %s\n", gbaover[i].gamepak_title);
     printf("gamepak code : %s\n", gbaover[i].gamepak_code);
     printf("gamepak maker: %s\n", gbaover[i].gamepak_maker);

     printf("INPUT gamepak title: %s\n", gpinfo->gamepak_title);
     printf("INPUT gamepak code : %s\n", gpinfo->gamepak_code);
     printf("INPUT gamepak maker: %s\n", gpinfo->gamepak_maker);

     if (gbaover[i].idle_loop_target_pc != 0)
        idle_loop_target_pc = gbaover[i].idle_loop_target_pc;

     iwram_stack_optimize = gbaover[i].iwram_stack_optimize;
     
     flash_device_id      = gbaover[i].flash_device_id;
     if (flash_device_id == FLASH_DEVICE_MACRONIX_128KB)
      flash_size = FLASH_SIZE_128KB;

     if (gbaover[i].translation_gate_target_1 != 0)
     {
        translation_gate_target_pc[translation_gate_targets] = gbaover[i].translation_gate_target_1;
        translation_gate_targets++;
     }

     if (gbaover[i].translation_gate_target_2 != 0)
     {
        translation_gate_target_pc[translation_gate_targets] = gbaover[i].translation_gate_target_2;
        translation_gate_targets++;
     }

     if (gbaover[i].translation_gate_target_3 != 0)
     {
        translation_gate_target_pc[translation_gate_targets] = gbaover[i].translation_gate_target_3;
        translation_gate_targets++;
     }

     printf("found entry in over ini file.\n");

     return 0;
  }

  return -1;
}

static s32 load_game_config(gamepak_info_t *gpinfo)
{
  char current_line[256];
  char current_variable[256];
  char current_value[256];
  char config_path[512];
  FILE *config_file;

  sprintf(config_path, "%s" PATH_SEPARATOR "%s", main_path, CONFIG_FILENAME);

  printf("config_path is : %s\n", config_path);

  config_file = fopen(config_path, "rb");

  if(config_file)
  {
    while(fgets(current_line, 256, config_file))
    {
      if(parse_config_line(current_line, current_variable, current_value)
       != -1)
      {
        if(strcmp(current_variable, "game_name") ||
         strcmp(current_value, gpinfo->gamepak_title))
          continue;

        if(!fgets(current_line, 256, config_file) ||
         (parse_config_line(current_line, current_variable,
           current_value) == -1) ||
         strcmp(current_variable, "game_code") ||
         strcmp(current_value, gpinfo->gamepak_code))
          continue;

        if(!fgets(current_line, 256, config_file) ||
         (parse_config_line(current_line, current_variable,
           current_value) == -1) ||
         strcmp(current_variable, "vender_code") ||
          strcmp(current_value, gpinfo->gamepak_maker))
          continue;

        while(fgets(current_line, 256, config_file))
        {
          if(parse_config_line(current_line, current_variable, current_value)
           != -1)
          {
            if(!strcmp(current_variable, "game_name"))
            {
              fclose(config_file);
              return 0;
            }

            if(!strcmp(current_variable, "idle_loop_eliminate_target"))
               idle_loop_target_pc = strtol(current_value, NULL, 16);

            if(!strcmp(current_variable, "translation_gate_target"))
            {
               if(translation_gate_targets < MAX_TRANSLATION_GATES)
               {
                  translation_gate_target_pc[translation_gate_targets] =
                     strtol(current_value, NULL, 16);
                  translation_gate_targets++;
               }
            }

            if(!strcmp(current_variable, "iwram_stack_optimize") &&
                  !strcmp(current_value, "no\0")) /* \0 for broken toolchain workaround */
               iwram_stack_optimize = 0;

            if(!strcmp(current_variable, "flash_rom_type") &&
              !strcmp(current_value, "128KB"))
              flash_device_id = FLASH_DEVICE_MACRONIX_128KB;
          }
        }

        fclose(config_file);
        return 0;
      }
    }

    fclose(config_file);
  }

  printf("game config missing\n");
  return -1;
}

// DMA memory regions can be one of the following:
// IWRAM - 32kb offset from the contiguous iwram region.
// EWRAM - also contiguous but with self modifying code check mirror.
// VRAM - 96kb offset from the contiguous vram region, should take care
// Palette RAM - Converts palette entries when written to.
// OAM RAM - Sets OAM modified flag to true.
// I/O registers - Uses the I/O register function.
// of mirroring properly.
// Segmented RAM/ROM - a region >= 32kb, the translated address has to
//  be reloaded if it wraps around the limit (cartride ROM)
// Ext - should be handled by the memory read/write function.

// The following map determines the region of each (assumes DMA access
// is not done out of bounds)

typedef enum
{
  DMA_REGION_IWRAM,
  DMA_REGION_EWRAM,
  DMA_REGION_VRAM,
  DMA_REGION_PALETTE_RAM,
  DMA_REGION_OAM_RAM,
  DMA_REGION_IO,
  DMA_REGION_GAMEPAK,
  DMA_REGION_EXT,
  DMA_REGION_BIOS,
  DMA_REGION_NULL
} dma_region_type;

dma_region_type dma_region_map[16] =
{
  DMA_REGION_BIOS,          // 0x00 - BIOS
  DMA_REGION_NULL,          // 0x01 - Nothing
  DMA_REGION_EWRAM,         // 0x02 - EWRAM
  DMA_REGION_IWRAM,         // 0x03 - IWRAM
  DMA_REGION_IO,            // 0x04 - I/O registers
  DMA_REGION_PALETTE_RAM,   // 0x05 - palette RAM
  DMA_REGION_VRAM,          // 0x06 - VRAM
  DMA_REGION_OAM_RAM,       // 0x07 - OAM RAM
  DMA_REGION_GAMEPAK,       // 0x08 - gamepak ROM
  DMA_REGION_GAMEPAK,       // 0x09 - gamepak ROM
  DMA_REGION_GAMEPAK,       // 0x0A - gamepak ROM
  DMA_REGION_GAMEPAK,       // 0x0B - gamepak ROM
  DMA_REGION_GAMEPAK,       // 0x0C - gamepak ROM
  DMA_REGION_EXT,           // 0x0D - EEPROM
  DMA_REGION_EXT,           // 0x0E - gamepak SRAM/flash ROM
  DMA_REGION_EXT            // 0x0F - gamepak SRAM/flash ROM
};

#define dma_print(src_op, dest_op, transfer_size, wb)                         \
  printf("dma from %x (%s) to %x (%s) for %x (%s) (%s) (%d) (pc %x)\n",       \
   src_ptr, #src_op, dest_ptr, #dest_op, length, #transfer_size, #wb,         \
   dma->irq, reg[15]);                                                        \

#define dma_smc_vars_src()                                                    \

#define dma_smc_vars_dest()                                                   \
  u32 smc_trigger = 0                                                         \

#define dma_vars_iwram(type)                                                  \
  dma_smc_vars_##type()                                                       \

#define dma_vars_ewram(type)                                                  \
  dma_smc_vars_##type()

#define dma_oam_ram_dest()                                                    \
  reg[OAM_UPDATED] = 1                                                        \

#define dma_vars_oam_ram(type)                                                \
  dma_oam_ram_##type()                                                        \

#define dma_vars_io(type)
#define dma_vars_vram(type)
#define dma_vars_palette_ram(type)
#define dma_vars_bios(type)
#define dma_vars_ext(type)

#define dma_oam_ram_src()

#define dma_segmented_load_src()                                              \
  memory_map_read[src_current_region]                                         \

#define dma_vars_gamepak(type)                                                \
  u32 type##_new_region;                                                      \
  u32 type##_current_region = type##_ptr >> 15;                               \
  u8 *type##_address_block = dma_segmented_load_##type();                     \
  if(type##_address_block == NULL)                                            \
  {                                                                           \
    if((type##_ptr & 0x1FFFFFF) >= gamepak_size)                              \
      break;                                                                  \
    type##_address_block = load_gamepak_page(type##_current_region & 0x3FF);  \
  }                                                                           \

#define dma_gamepak_check_region(type)                                        \
  type##_new_region = (type##_ptr >> 15);                                     \
  if(type##_new_region != type##_current_region)                              \
  {                                                                           \
    type##_current_region = type##_new_region;                                \
    type##_address_block = dma_segmented_load_##type();                       \
    if(type##_address_block == NULL)                                          \
    {                                                                         \
      type##_address_block =                                                  \
       load_gamepak_page(type##_current_region & 0x3FF);                      \
    }                                                                         \
  }                                                                           \

#define dma_read_iwram(type, transfer_size)                                   \
  read_value = readaddress##transfer_size(iwram + 0x8000, type##_ptr & 0x7FFF)\

#define dma_read_vram(type, transfer_size) {                                  \
  u32 rdaddr = type##_ptr & 0x1FFFF;                                          \
  if (rdaddr >= 0x18000) rdaddr -= 0x8000;                                    \
  read_value = readaddress##transfer_size(vram, rdaddr);                      \
}

#define dma_read_io(type, transfer_size)                                      \
  read_value = readaddress##transfer_size(io_registers, type##_ptr & 0x3FF)   \

#define dma_read_oam_ram(type, transfer_size)                                 \
  read_value = readaddress##transfer_size(oam_ram, type##_ptr & 0x3FF)        \

#define dma_read_palette_ram(type, transfer_size)                             \
  read_value = readaddress##transfer_size(palette_ram, type##_ptr & 0x3FF)    \

#define dma_read_ewram(type, transfer_size)                                   \
  read_value = readaddress##transfer_size(ewram, type##_ptr & 0x3FFFF)        \

#define dma_read_gamepak(type, transfer_size)                                 \
  dma_gamepak_check_region(type);                                             \
  read_value = readaddress##transfer_size(type##_address_block,               \
   type##_ptr & 0x7FFF)                                                       \

// DMAing from the BIOS is funny, just returns 0..

#define dma_read_bios(type, transfer_size)                                    \
  read_value = 0                                                              \

#define dma_read_ext(type, transfer_size)                                     \
  read_value = read_memory##transfer_size(type##_ptr)                         \

#define dma_write_iwram(type, transfer_size)                                  \
  address##transfer_size(iwram + 0x8000, type##_ptr & 0x7FFF) =               \
                                          eswap##transfer_size(read_value);   \
  smc_trigger |= address##transfer_size(iwram, type##_ptr & 0x7FFF)           \

#define dma_write_vram(type, transfer_size) {                                 \
  u32 wraddr = type##_ptr & 0x1FFFF;                                          \
  if (wraddr >= 0x18000) wraddr -= 0x8000;                                    \
  address##transfer_size(vram, wraddr) = eswap##transfer_size(read_value);    \
}

#define dma_write_io(type, transfer_size)                                     \
  write_io_register##transfer_size(type##_ptr & 0x3FF, read_value)            \

#define dma_write_oam_ram(type, transfer_size)                                \
  address##transfer_size(oam_ram, type##_ptr & 0x3FF) =                       \
                                  eswap##transfer_size(read_value)            \

#define dma_write_palette_ram(type, transfer_size)                            \
  write_palette##transfer_size(type##_ptr & 0x3FF, read_value)                \

#define dma_write_ext(type, transfer_size)                                    \
  write_memory##transfer_size(type##_ptr, read_value)                         \

#define dma_write_ewram(type, transfer_size)                                  \
  address##transfer_size(ewram, type##_ptr & 0x3FFFF) =                       \
                                  eswap##transfer_size(read_value);           \
  smc_trigger |= address##transfer_size(ewram,                                \
   (type##_ptr & 0x3FFFF) + 0x40000)                                          \

#define dma_epilogue_iwram()                                                  \
  if(smc_trigger)                                                             \
  {                                                                           \
    /* Special return code indicating to retranslate to the CPU code */       \
    return_value = CPU_ALERT_SMC;                                             \
  }                                                                           \

#define dma_epilogue_ewram()                                                  \
  if(smc_trigger)                                                             \
  {                                                                           \
    /* Special return code indicating to retranslate to the CPU code */       \
    return_value = CPU_ALERT_SMC;                                             \
  }                                                                           \

#define dma_epilogue_vram()                                                   \

#define dma_epilogue_io()                                                     \

#define dma_epilogue_oam_ram()                                                \

#define dma_epilogue_palette_ram()                                            \

#define dma_epilogue_GAMEPAK()                                                \

#define dma_epilogue_ext()                                                    \

#define print_line()                                                          \
  dma_print(src_op, dest_op, transfer_size, wb);                              \

#define dma_transfer_loop_region(src_region_type, dest_region_type, src_op,   \
 dest_op, transfer_size, wb)                                                  \
{                                                                             \
  dma_vars_##src_region_type(src);                                            \
  dma_vars_##dest_region_type(dest);                                          \
                                                                              \
  for(i = 0; i < length; i++)                                                 \
  {                                                                           \
    dma_read_##src_region_type(src, transfer_size);                           \
    dma_write_##dest_region_type(dest, transfer_size);                        \
    src_ptr += src_op;                                                        \
    dest_ptr += dest_op;                                                      \
  }                                                                           \
  dma->source_address = src_ptr;                                              \
  if (wb)                                                                     \
    dma->dest_address = dest_ptr;                                             \
  dma_epilogue_##dest_region_type();                                          \
  break;                                                                      \
}                                                                             \

#define dma_tf_loop_builder(transfer_size)                                    \
cpu_alert_type dma_tf_loop##transfer_size(                                    \
  u32 src_ptr, u32 dest_ptr, int src_strd, int dest_strd,                     \
  bool wb, u32 length, dma_transfer_type *dma)                                \
{                                                                             \
  u32 i;                                                                      \
  u32 read_value;                                                             \
  cpu_alert_type return_value = CPU_ALERT_NONE;                               \
  u32 src_region = src_ptr >> 24;                                             \
  u32 dest_region = dest_ptr >> 24;                                           \
  dma_region_type src_region_type = dma_region_map[src_region];               \
  dma_region_type dest_region_type = dma_region_map[dest_region];             \
                                                                              \
  switch(src_region_type | (dest_region_type << 4))                           \
  {                                                                           \
    case (DMA_REGION_BIOS | (DMA_REGION_IWRAM << 4)):                         \
      dma_transfer_loop_region(bios, iwram, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IWRAM | (DMA_REGION_IWRAM << 4)):                        \
      dma_transfer_loop_region(iwram, iwram, src_strd, dest_strd,             \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EWRAM | (DMA_REGION_IWRAM << 4)):                        \
      dma_transfer_loop_region(ewram, iwram, src_strd, dest_strd,             \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_VRAM | (DMA_REGION_IWRAM << 4)):                         \
      dma_transfer_loop_region(vram, iwram, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_PALETTE_RAM | (DMA_REGION_IWRAM << 4)):                  \
      dma_transfer_loop_region(palette_ram, iwram, src_strd, dest_strd,       \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_OAM_RAM | (DMA_REGION_IWRAM << 4)):                      \
      dma_transfer_loop_region(oam_ram, iwram, src_strd, dest_strd,           \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IO | (DMA_REGION_IWRAM << 4)):                           \
      dma_transfer_loop_region(io, iwram, src_strd, dest_strd,                \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_GAMEPAK | (DMA_REGION_IWRAM << 4)):                      \
      dma_transfer_loop_region(gamepak, iwram, src_strd, dest_strd,           \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EXT | (DMA_REGION_IWRAM << 4)):                          \
      dma_transfer_loop_region(ext, iwram, src_strd, dest_strd,               \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_BIOS | (DMA_REGION_EWRAM << 4)):                         \
      dma_transfer_loop_region(bios, ewram, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IWRAM | (DMA_REGION_EWRAM << 4)):                        \
      dma_transfer_loop_region(iwram, ewram, src_strd, dest_strd,             \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EWRAM | (DMA_REGION_EWRAM << 4)):                        \
      dma_transfer_loop_region(ewram, ewram, src_strd, dest_strd,             \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_VRAM | (DMA_REGION_EWRAM << 4)):                         \
      dma_transfer_loop_region(vram, ewram, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_PALETTE_RAM | (DMA_REGION_EWRAM << 4)):                  \
      dma_transfer_loop_region(palette_ram, ewram, src_strd, dest_strd,       \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_OAM_RAM | (DMA_REGION_EWRAM << 4)):                      \
      dma_transfer_loop_region(oam_ram, ewram, src_strd, dest_strd,           \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IO | (DMA_REGION_EWRAM << 4)):                           \
      dma_transfer_loop_region(io, ewram, src_strd, dest_strd,                \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_GAMEPAK | (DMA_REGION_EWRAM << 4)):                      \
      dma_transfer_loop_region(gamepak, ewram, src_strd, dest_strd,           \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EXT | (DMA_REGION_EWRAM << 4)):                          \
      dma_transfer_loop_region(ext, ewram, src_strd, dest_strd,               \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_BIOS | (DMA_REGION_VRAM << 4)):                          \
      dma_transfer_loop_region(bios, vram, src_strd, dest_strd,               \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IWRAM | (DMA_REGION_VRAM << 4)):                         \
      dma_transfer_loop_region(iwram, vram, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EWRAM | (DMA_REGION_VRAM << 4)):                         \
      dma_transfer_loop_region(ewram, vram, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_VRAM | (DMA_REGION_VRAM << 4)):                          \
      dma_transfer_loop_region(vram, vram, src_strd, dest_strd,               \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_PALETTE_RAM | (DMA_REGION_VRAM << 4)):                   \
      dma_transfer_loop_region(palette_ram, vram, src_strd, dest_strd,        \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_OAM_RAM | (DMA_REGION_VRAM << 4)):                       \
      dma_transfer_loop_region(oam_ram, vram, src_strd, dest_strd,            \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IO | (DMA_REGION_VRAM << 4)):                            \
      dma_transfer_loop_region(io, vram, src_strd, dest_strd,                 \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_GAMEPAK | (DMA_REGION_VRAM << 4)):                       \
      dma_transfer_loop_region(gamepak, vram, src_strd, dest_strd,            \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EXT | (DMA_REGION_VRAM << 4)):                           \
      dma_transfer_loop_region(ext, vram, src_strd, dest_strd,                \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_BIOS | (DMA_REGION_PALETTE_RAM << 4)):                   \
      dma_transfer_loop_region(bios, palette_ram, src_strd, dest_strd,        \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IWRAM | (DMA_REGION_PALETTE_RAM << 4)):                  \
      dma_transfer_loop_region(iwram, palette_ram, src_strd, dest_strd,       \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EWRAM | (DMA_REGION_PALETTE_RAM << 4)):                  \
      dma_transfer_loop_region(ewram, palette_ram, src_strd, dest_strd,       \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_VRAM | (DMA_REGION_PALETTE_RAM << 4)):                   \
      dma_transfer_loop_region(vram, palette_ram, src_strd, dest_strd,        \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_PALETTE_RAM | (DMA_REGION_PALETTE_RAM << 4)):            \
      dma_transfer_loop_region(palette_ram, palette_ram, src_strd, dest_strd, \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_OAM_RAM | (DMA_REGION_PALETTE_RAM << 4)):                \
      dma_transfer_loop_region(oam_ram, palette_ram, src_strd, dest_strd,     \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IO | (DMA_REGION_PALETTE_RAM << 4)):                     \
      dma_transfer_loop_region(io, palette_ram, src_strd, dest_strd,          \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_GAMEPAK | (DMA_REGION_PALETTE_RAM << 4)):                \
      dma_transfer_loop_region(gamepak, palette_ram, src_strd, dest_strd,     \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EXT | (DMA_REGION_PALETTE_RAM << 4)):                    \
      dma_transfer_loop_region(ext, palette_ram, src_strd, dest_strd,         \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_BIOS | (DMA_REGION_OAM_RAM << 4)):                       \
      dma_transfer_loop_region(bios, oam_ram, src_strd, dest_strd,            \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IWRAM | (DMA_REGION_OAM_RAM << 4)):                      \
      dma_transfer_loop_region(iwram, oam_ram, src_strd, dest_strd,           \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EWRAM | (DMA_REGION_OAM_RAM << 4)):                      \
      dma_transfer_loop_region(ewram, oam_ram, src_strd, dest_strd,           \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_VRAM | (DMA_REGION_OAM_RAM << 4)):                       \
      dma_transfer_loop_region(vram, oam_ram, src_strd, dest_strd,            \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_PALETTE_RAM | (DMA_REGION_OAM_RAM << 4)):                \
      dma_transfer_loop_region(palette_ram, oam_ram, src_strd, dest_strd,     \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_OAM_RAM | (DMA_REGION_OAM_RAM << 4)):                    \
      dma_transfer_loop_region(oam_ram, oam_ram, src_strd, dest_strd,         \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IO | (DMA_REGION_OAM_RAM << 4)):                         \
      dma_transfer_loop_region(io, oam_ram, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_GAMEPAK | (DMA_REGION_OAM_RAM << 4)):                    \
      dma_transfer_loop_region(gamepak, oam_ram, src_strd, dest_strd,         \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EXT | (DMA_REGION_OAM_RAM << 4)):                        \
      dma_transfer_loop_region(ext, oam_ram, src_strd, dest_strd,             \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_BIOS | (DMA_REGION_IO << 4)):                            \
      dma_transfer_loop_region(bios, io, src_strd, dest_strd,                 \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IWRAM | (DMA_REGION_IO << 4)):                           \
      dma_transfer_loop_region(iwram, io, src_strd, dest_strd,                \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EWRAM | (DMA_REGION_IO << 4)):                           \
      dma_transfer_loop_region(ewram, io, src_strd, dest_strd,                \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_VRAM | (DMA_REGION_IO << 4)):                            \
      dma_transfer_loop_region(vram, io, src_strd, dest_strd,                 \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_PALETTE_RAM | (DMA_REGION_IO << 4)):                     \
      dma_transfer_loop_region(palette_ram, io, src_strd, dest_strd,          \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_OAM_RAM | (DMA_REGION_IO << 4)):                         \
      dma_transfer_loop_region(oam_ram, io, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IO | (DMA_REGION_IO << 4)):                              \
      dma_transfer_loop_region(io, io, src_strd, dest_strd,                   \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_GAMEPAK | (DMA_REGION_IO << 4)):                         \
      dma_transfer_loop_region(gamepak, io, src_strd, dest_strd,              \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EXT | (DMA_REGION_IO << 4)):                             \
      dma_transfer_loop_region(ext, io, src_strd, dest_strd,                  \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_BIOS | (DMA_REGION_EXT << 4)):                           \
      dma_transfer_loop_region(bios, ext, src_strd, dest_strd,                \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IWRAM | (DMA_REGION_EXT << 4)):                          \
      dma_transfer_loop_region(iwram, ext, src_strd, dest_strd,               \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EWRAM | (DMA_REGION_EXT << 4)):                          \
      dma_transfer_loop_region(ewram, ext, src_strd, dest_strd,               \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_VRAM | (DMA_REGION_EXT << 4)):                           \
      dma_transfer_loop_region(vram, ext, src_strd, dest_strd,                \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_PALETTE_RAM | (DMA_REGION_EXT << 4)):                    \
      dma_transfer_loop_region(palette_ram, ext, src_strd, dest_strd,         \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_OAM_RAM | (DMA_REGION_EXT << 4)):                        \
      dma_transfer_loop_region(oam_ram, ext, src_strd, dest_strd,             \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_IO | (DMA_REGION_EXT << 4)):                             \
      dma_transfer_loop_region(io, ext, src_strd, dest_strd,                  \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_GAMEPAK | (DMA_REGION_EXT << 4)):                        \
      dma_transfer_loop_region(gamepak, ext, src_strd, dest_strd,             \
       transfer_size, wb);                                                    \
                                                                              \
    case (DMA_REGION_EXT | (DMA_REGION_EXT << 3)):                            \
      dma_transfer_loop_region(ext, ext, src_strd, dest_strd,                 \
       transfer_size, wb);                                                    \
  }                                                                           \
  return return_value;                                                        \
}                                                                             \

dma_tf_loop_builder(16);
dma_tf_loop_builder(32);

cpu_alert_type dma_transfer(dma_transfer_type *dma)
{
  u32 length = dma->length;
  u32 src_ptr = dma->source_address;
  uintptr_t dest_ptr = dma->dest_address;
  cpu_alert_type ret = CPU_ALERT_NONE;

  // Technically this should be done for source and destination, but
  // chances are this is only ever used (probably mistakingly!) for dest.
  // The only game I know of that requires this is Lucky Luke.
  if((dest_ptr >> 24) != ((dest_ptr + length - 1) >> 24))
  {
    u32 first_length = ((dest_ptr & 0xFF000000) + 0x1000000) - dest_ptr;
    u32 second_length = length - first_length;
    dma->length = first_length;

    dma_transfer(dma);

    dma->length = length;

    length = second_length;
    dest_ptr += first_length;
    src_ptr += first_length;
  }

  if(dma->length_type == DMA_16BIT)
  {
    src_ptr &= ~0x01;
    dest_ptr &= ~0x01;
    cycle_dma16_words += length;

    switch((dma->dest_direction << 2) | dma->source_direction)
    {
       case 0x00:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  2,  2, true, length, dma); break;
       case 0x01:
          ret = dma_tf_loop16(src_ptr, dest_ptr, -2,  2, true, length, dma); break;
       case 0x02:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  0,  2, true, length, dma); break;
       case 0x03:
          break;
       case 0x04:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  2, -2, true, length, dma); break;
       case 0x05:
          ret = dma_tf_loop16(src_ptr, dest_ptr, -2, -2, true, length, dma); break;
       case 0x06:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  0, -2, true, length, dma); break;
       case 0x07:
          break;
       case 0x08:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  2,  0, true, length, dma); break;
       case 0x09:
          ret = dma_tf_loop16(src_ptr, dest_ptr, -2,  0, true, length, dma); break;
       case 0x0A:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  0,  0, true, length, dma); break;
       case 0x0B:
          break;
       case 0x0C:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  2,  2, false, length, dma); break;
       case 0x0D:
          ret = dma_tf_loop16(src_ptr, dest_ptr, -2,  2, false, length, dma); break;
       case 0x0E:
          ret = dma_tf_loop16(src_ptr, dest_ptr,  0,  2, false, length, dma); break;
       case 0x0F:
          break;
    }
  }
  else
  {
    src_ptr &= ~0x03;
    dest_ptr &= ~0x03;
    cycle_dma32_words += length;

    switch((dma->dest_direction << 2) | dma->source_direction)
    {
       case 0x00:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  4,  4, true, length, dma); break;
       case 0x01:
          ret = dma_tf_loop32(src_ptr, dest_ptr, -4,  4, true, length, dma); break;
       case 0x02:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  0,  4, true, length, dma); break;
       case 0x03:
          break;
       case 0x04:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  4, -4, true, length, dma); break;
       case 0x05:
          ret = dma_tf_loop32(src_ptr, dest_ptr, -4, -4, true, length, dma); break;
       case 0x06:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  0, -4, true, length, dma); break;
       case 0x07:
          break;
       case 0x08:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  4,  0, true, length, dma); break;
       case 0x09:
          ret = dma_tf_loop32(src_ptr, dest_ptr, -4,  0, true, length, dma); break;
       case 0x0A:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  0,  0, true, length, dma); break;
       case 0x0B:
          break;
       case 0x0C:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  4,  4, false, length, dma); break;
       case 0x0D:
          ret = dma_tf_loop32(src_ptr, dest_ptr, -4,  4, false, length, dma); break;
       case 0x0E:
          ret = dma_tf_loop32(src_ptr, dest_ptr,  0,  4, false, length, dma); break;
       case 0x0F:
          break;
    }
  }

  if((dma->repeat_type == DMA_NO_REPEAT) ||
   (dma->start_type == DMA_START_IMMEDIATELY))
  {
    dma->start_type = DMA_INACTIVE;
    address16(io_registers, (dma->dma_channel * 12) + 0xBA) =
      readaddress16(io_registers, (dma->dma_channel * 12) + 0xBA) & (~0x8000);
  }

  if(dma->irq)
  {
    raise_interrupt(IRQ_DMA0 << dma->dma_channel);
    ret = CPU_ALERT_IRQ;
  }

  return ret;
}

// Be sure to do this after loading ROMs.

#define map_rom_entry(type, idx, ptr, mirror_blocks) {                        \
  unsigned mcount;                                                            \
  for(mcount = 0; mcount < 1024; mcount += (mirror_blocks)) {                 \
    memory_map_##type[(0x8000000 / (32 * 1024)) + (idx) + mcount] = (ptr);    \
    memory_map_##type[(0xA000000 / (32 * 1024)) + (idx) + mcount] = (ptr);    \
  }                                                                           \
  for(mcount = 0; mcount <  512; mcount += (mirror_blocks)) {                 \
    memory_map_##type[(0xC000000 / (32 * 1024)) + (idx) + mcount] = (ptr);    \
  }                                                                           \
}

#define map_region(type, start, end, mirror_blocks, region)                   \
  for(map_offset = (start) / 0x8000; map_offset <                             \
   ((end) / 0x8000); map_offset++)                                            \
  {                                                                           \
    memory_map_##type[map_offset] =                                           \
     ((u8 *)region) + ((map_offset % mirror_blocks) * 0x8000);                \
  }                                                                           \

#define map_null(type, start, end) {                                          \
  u32 map_offset;                                                             \
  for(map_offset = start / 0x8000; map_offset < (end / 0x8000);               \
   map_offset++)                                                              \
    memory_map_##type[map_offset] = NULL;                                     \
}

#define map_vram(type)                                                        \
  for(map_offset = 0x6000000 / 0x8000; map_offset < (0x7000000 / 0x8000);     \
   map_offset += 4)                                                           \
  {                                                                           \
    memory_map_##type[map_offset] = vram;                                     \
    memory_map_##type[map_offset + 1] = vram + 0x8000;                        \
    memory_map_##type[map_offset + 2] = vram + (0x8000 * 2);                  \
    memory_map_##type[map_offset + 3] = vram + (0x8000 * 2);                  \
  }                                                                           \


static u32 evict_gamepak_page(void)
{
  u32 ret;
  s16 phyrom;
  do {
    // Return the index to the last used entry
    u32 newhead = gamepak_blk_queue[gamepak_lru_head].next_lru;
    phyrom = gamepak_blk_queue[gamepak_lru_head].phy_rom;
    ret = gamepak_lru_head;

    // Second elem becomes head now
    gamepak_lru_head = newhead;

    // The evicted element goes at the end of the queue
    gamepak_blk_queue[gamepak_lru_tail].next_lru = ret;
    gamepak_lru_tail = ret;
    // If this page is marked as sticky, we keep going through the list
  } while (phyrom >= 0 && gamepak_sb_test(phyrom));

  // We unmap the ROM page if it was mapped, ensure we do not access it
  // without triggering a "page fault"
  if (phyrom >= 0) {
    map_rom_entry(read, phyrom, NULL, gamepak_size >> 15);
  }

  return ret;
}

u8 *load_gamepak_page(u32 physical_index)
{
  if(physical_index >= (gamepak_size >> 15))
    return &gamepak_buffers[0][0];

  u32 entry = evict_gamepak_page();
  u32 block_idx = entry / 32;
  u32 block_off = entry % 32;
  u8 *swap_location = &gamepak_buffers[block_idx][32 * 1024 * block_off];

  // Fill in the entry
  gamepak_blk_queue[entry].phy_rom = physical_index;

  fseek(gamepak_file_large, physical_index * (32 * 1024), SEEK_SET);
  fread(swap_location, 1, (32 * 1024), gamepak_file_large);

  // Map it to the read handlers now
  map_rom_entry(read, physical_index, swap_location, gamepak_size >> 15);

  // If RTC is active page the RTC register bytes so they can be read
  if ((rtc_state != RTC_DISABLED) && (physical_index == 0)) {
    address16(swap_location, 0xC4) = eswap16(rtc_registers[0]);
    address16(swap_location, 0xC6) = eswap16(rtc_registers[1]);
    address16(swap_location, 0xC8) = eswap16(rtc_registers[2]);
  }

  return swap_location;
}

void init_gamepak_buffer(void)
{
  unsigned i;
  // Try to allocate up to 32 blocks of 1MB each
  gamepak_buffer_count = 0;
  while (gamepak_buffer_count < ROM_BUFFER_SIZE)
  {
    void *ptr = malloc(1024*1024);
    if (!ptr)
      break;
    gamepak_buffers[gamepak_buffer_count++] = (u8*)ptr;
  }

  // Initialize the memory map structure
  for (i = 0; i < 1024; i++)
  {
    gamepak_blk_queue[i].next_lru = (u16)(i + 1);
    gamepak_blk_queue[i].phy_rom = -1;
  }

  gamepak_lru_head = 0;
  gamepak_lru_tail = 32 * gamepak_buffer_count - 1;
}

void init_memory(void)
{
  u32 map_offset = 0;

  // Fill memory map regions, areas marked as NULL must be checked directly
  map_region(read, 0x0000000, 0x1000000, 1, bios_rom);
  map_null(read, 0x1000000, 0x2000000);
  map_region(read, 0x2000000, 0x3000000, 8, ewram);
  map_region(read, 0x3000000, 0x4000000, 1, &iwram[0x8000]);
  map_region(read, 0x4000000, 0x5000000, 1, io_registers);
  map_null(read, 0x5000000, 0x6000000);
  map_null(read, 0x6000000, 0x7000000);
  map_vram(read);
  map_null(read, 0x7000000, 0x8000000);
  map_null(read, 0xE000000, 0x10000000);

  memset(io_registers, 0, sizeof(io_registers));
  memset(oam_ram, 0, sizeof(oam_ram));
  memset(palette_ram, 0, sizeof(palette_ram));
  memset(iwram, 0, sizeof(iwram));
  memset(ewram, 0, sizeof(ewram));
  memset(vram, 0, sizeof(vram));

  write_ioreg(REG_DISPCNT, 0x80);
  write_ioreg(REG_P1, 0x3FF);
  write_ioreg(REG_BG2PA, 0x100);
  write_ioreg(REG_BG2PD, 0x100);
  write_ioreg(REG_BG3PA, 0x100);
  write_ioreg(REG_BG3PD, 0x100);
  write_ioreg(REG_RCNT, 0x8000);

  backup_type = BACKUP_NONE;

  sram_size = SRAM_SIZE_32KB;
  //flash_size = FLASH_SIZE_64KB;

  flash_bank_ptr = gamepak_backup;
  flash_command_position = 0;
  eeprom_size = EEPROM_512_BYTE;
  eeprom_mode = EEPROM_BASE_MODE;
  eeprom_address = 0;
  eeprom_counter = 0;

  flash_mode = FLASH_BASE_MODE;

  rtc_state = RTC_DISABLED;
  memset(rtc_registers, 0, sizeof(rtc_registers));
  bios_read_protect = 0xe129f000;
}

void memory_term(void)
{
  if (gamepak_file_large)
  {
    fclose(gamepak_file_large);
    gamepak_file_large = NULL;
  }

  while (gamepak_buffer_count)
  {
    free(gamepak_buffers[--gamepak_buffer_count]);
  }
}

#define savestate_block(type)   \
  cpu_##type##_savestate();     \
  input_##type##_savestate();   \
  main_##type##_savestate();    \
  memory_##type##_savestate();  \
  sound_##type##_savestate();   \
  video_##type##_savestate()


const u8 *state_mem_read_ptr;
u8 *state_mem_write_ptr;

void gba_load_state(const void* src)
{
   u32 i;
   u32 current_color;

   state_mem_read_ptr = (u8*)src;
   savestate_block(read);

#ifdef HAVE_DYNAREC
   if (dynarec_enable)
      init_caches();
#endif

   reg[OAM_UPDATED] = 1;
   gbc_sound_update = 1;

   for(i = 0; i < 512; i++)
   {
      current_color = palette_ram[i];
      palette_ram_converted[i] =
       convert_palette(current_color);
   }

   // Oops, these contain raw pointers
   for(i = 0; i < 4; i++)
      gbc_sound_channel[i].sample_table_idx = 2;

   instruction_count = 0;

   reg[CHANGED_PC_STATUS] = 1;
}

void gba_save_state(void* dst)
{
  state_mem_write_ptr = (u8*)dst;
  savestate_block(write);
}


#define memory_savestate_builder(type)                         \
void memory_##type##_savestate(void)                           \
{                                                              \
  state_mem_##type##_variable(backup_type);                    \
  state_mem_##type##_variable(sram_size);                      \
  state_mem_##type##_variable(flash_mode);                     \
  state_mem_##type##_variable(flash_command_position);         \
  state_mem_##type##_pointer(flash_bank_ptr, gamepak_backup);  \
  state_mem_##type##_variable(flash_device_id);                \
  state_mem_##type##_variable(flash_manufacturer_id);          \
  state_mem_##type##_variable(flash_size);                     \
  state_mem_##type##_variable(eeprom_size);                    \
  state_mem_##type##_variable(eeprom_mode);                    \
  state_mem_##type##_variable(eeprom_address_length);          \
  state_mem_##type##_variable(eeprom_address);                 \
  state_mem_##type##_variable(eeprom_counter);                 \
  state_mem_##type##_variable(rtc_state);                      \
  state_mem_##type##_variable(rtc_write_mode);                 \
  state_mem_##type##_array(rtc_registers);                     \
  state_mem_##type##_variable(rtc_command);                    \
  state_mem_##type##_array(rtc_data);                          \
  state_mem_##type##_variable(rtc_status);                     \
  state_mem_##type##_variable(rtc_data_bytes);                 \
  state_mem_##type##_variable(rtc_bit_count);                  \
  state_mem_##type##_array(eeprom_buffer);                     \
  state_mem_##type##_array(dma);                               \
                                                               \
  state_mem_##type(iwram + 0x8000, 0x8000);                    \
  state_mem_##type(ewram, 0x40000);                            \
  state_mem_##type(vram, 0x18000);                             \
  state_mem_##type(oam_ram, 0x400);                            \
  state_mem_##type(palette_ram, 0x400);                        \
  state_mem_##type(io_registers, 0x400);                       \
                                                               \
  /* This should not happen anymore :P */                      \
  if((flash_bank_ptr < gamepak_backup) ||                      \
  (flash_bank_ptr > (&gamepak_backup[sizeof(gamepak_backup)])))\
    flash_bank_ptr = gamepak_backup;                           \
}

memory_savestate_builder(read)
memory_savestate_builder(write)


static s32 load_gamepak_raw(const char *name)
{
  unsigned i, j;
  gamepak_file_large = fopen(name, "rb");
  if(gamepak_file_large)
  {
    // Round size to 32KB pages
    gamepak_size = file_length(gamepak_file_large);
    gamepak_size = (gamepak_size + 0x7FFF) & ~0x7FFF;

    // Load stuff in 1MB chunks
    u32 buf_blocks = (gamepak_size + 1024*1024-1) / (1024*1024);
    u32 rom_blocks = gamepak_size >> 15;
    u32 ldblks = buf_blocks < gamepak_buffer_count ?
                    buf_blocks : gamepak_buffer_count;

    // Unmap the ROM space since we will re-map it now
    map_null(read, 0x8000000, 0xD000000);

    // Proceed to read the whole ROM or as much as possible.
    for (i = 0; i < ldblks; i++)
    {
      // Load 1MB chunk and map it
      fread(gamepak_buffers[i], 1, 1024*1024, gamepak_file_large);
      for (j = 0; j < 32 && i*32 + j < rom_blocks; j++)
      {
        u32 phyn = i*32 + j;
        u8* blkptr = &gamepak_buffers[i][32 * 1024 * j];
        u32 entry = evict_gamepak_page();
        gamepak_blk_queue[entry].phy_rom = phyn;
        // Map it to the read handlers now
        map_rom_entry(read, phyn, blkptr, rom_blocks);
      }
    } 

    return 0;
  }

  return -1;
}

u32 load_gamepak(const struct retro_game_info* info, const char *name)
{
   char *p;
   char gamepak_filename[512];
   gamepak_info_t gpinfo;

   if (load_gamepak_raw(name))
      return -1;

   strncpy(gamepak_filename, name, sizeof(gamepak_filename));
   gamepak_filename[sizeof(gamepak_filename) - 1] = 0;

   p = strrchr(gamepak_filename, PATH_SEPARATOR_CHAR);
   if (p)
      p++;
   else
      p = gamepak_filename;

   snprintf(backup_filename, sizeof(backup_filename), "%s%c%s", save_path, PATH_SEPARATOR_CHAR, p);
   p = strrchr(backup_filename, '.');
   if (p)
      strcpy(p, ".sav");

   if (!use_libretro_save_method)
     load_backup(backup_filename);

   // Buffer 0 always has the first 1MB chunk of the ROM
   memset(&gpinfo, 0, sizeof(gpinfo));
   memcpy(gpinfo.gamepak_title, &gamepak_buffers[0][0xA0], 12);
   memcpy(gpinfo.gamepak_code,  &gamepak_buffers[0][0xAC],  4);
   memcpy(gpinfo.gamepak_maker, &gamepak_buffers[0][0xB0],  2);

   idle_loop_target_pc = 0xFFFFFFFF;
   iwram_stack_optimize = 1;
   translation_gate_targets = 0;
   flash_device_id = FLASH_DEVICE_MACRONIX_64KB;
   flash_size = FLASH_SIZE_64KB;

   if ((load_game_config_over(&gpinfo)) < 0)
      load_game_config(&gpinfo);

   return 0;
}

s32 load_bios(char *name)
{
  FILE *fd = fopen(name, "rb");

  if(!fd)
    return -1;

  fread(bios_rom, 1, 0x4000, fd);
  fclose(fd);
  return 0;
}