/////////////////////////////////////////////////////////////////////////////
//
// iop - PS2 IOP emulation; can also do PS1 via compatibility mode
//
/////////////////////////////////////////////////////////////////////////////

#ifndef EMU_COMPILE
#error "Hi I forgot to set EMU_COMPILE"
#endif

#include "iop.h"

#include "psx.h"
#include "ioptimer.h"
#include "r3000.h"
#include "spu.h"
#include "bios.h"

//#include <windows.h>

/////////////////////////////////////////////////////////////////////////////
//
// Simulate DMA delay for harsh compat mode
//
#define DMA_SPU_CYCLES_PER_HALFWORD (8)

/////////////////////////////////////////////////////////////////////////////
//
// Static information
//
sint32 EMU_CALL iop_init(void) {
  return 0;
}

////////////////////////////////////////////////////////////////////////////////
/*
** State information
*/

//
// names from INTRMAN.H
//
#define IOP_INT_VBLANK  (1<<0)
#define IOP_INT_GM      (1<<1)
#define IOP_INT_CDROM   (1<<2)
#define IOP_INT_DMA     (1<<3)
#define IOP_INT_RTC0    (1<<4)
#define IOP_INT_RTC1    (1<<5)
#define IOP_INT_RTC2    (1<<6)
#define IOP_INT_SIO0    (1<<7)
#define IOP_INT_SIO1    (1<<8)
#define IOP_INT_SPU     (1<<9)
#define IOP_INT_PIO     (1<<10)
#define IOP_INT_EVBLANK (1<<11)
#define IOP_INT_DVD     (1<<12)
#define IOP_INT_PCMCIA  (1<<13)
#define IOP_INT_RTC3    (1<<14)
#define IOP_INT_RTC4    (1<<15)
#define IOP_INT_RTC5    (1<<16)
#define IOP_INT_SIO2    (1<<17)
#define IOP_INT_HTR0    (1<<18)
#define IOP_INT_HTR1    (1<<19)
#define IOP_INT_HTR2    (1<<20)
#define IOP_INT_HTR3    (1<<21)
#define IOP_INT_USB     (1<<22)
#define IOP_INT_EXTR    (1<<23)
#define IOP_INT_FWRE    (1<<24)
#define IOP_INT_FDMA    (1<<25)

struct IOP_INTR {
  uint32 en;
  uint32 signaled;
  uint8 is_masked;
};

struct IOP_DMA_CHAN {
  uint32 MADR;
  uint32 BCR;
  uint32 CHCR;
  uint64 cycles_until_interrupt;
};

struct IOP_DMA {
  struct IOP_DMA_CHAN chan[7];
  uint32 DPCR;
  uint32 DICR;
};

struct IOP_EVENT {
  uint64 time;
  uint32 type;
  char  *fmt; // Always points into constant static data. safe.
  uint32 arg[4];
};

#define IOP_MAX_EVENTS (16)

struct IOP_STATE {
  struct IOP_STATE *myself; // Pointer used to check location invariance

  uint8  version;
  uint32 offset_to_map_load;
  uint32 offset_to_map_store;
  uint32 offset_to_ioptimer;
  uint32 offset_to_r3000;
  uint32 offset_to_spu;
  uint32 ram[0x200000/4];
  uint32 scratch[0x400/4];
  uint64 odometer;
//  struct IOP_INTR      intr[2];
  struct IOP_INTR      intr;
  struct IOP_DMA       dma[2];
  sint16 *sound_buffer; // TEMPORARY pointer. safe.
  uint32  sound_buffer_samples_free;
  uint32  sound_cycles_pending;
  uint32  sound_cycles_until_interrupt;
  struct IOP_EVENT     event[IOP_MAX_EVENTS];
  uint32              event_write_index;
  uint32              event_count;
  uint32              event_mask;
  uint8 *audit_map; // Externally-registered pointer. safe.
  uint32 audit_bytes_used;
  uint8 compat_level;
  uint8 quitflag;
  uint8 fatalflag;
  uint32 cycles_per_sample;
  void *psx_state; // TEMPORARY pointer. safe.
};

#define IOPSTATE      ((struct IOP_STATE*)(state))
#define MAPLOAD       ((void*)(((char*)(state))+(IOPSTATE->offset_to_map_load)))
#define MAPSTORE      ((void*)(((char*)(state))+(IOPSTATE->offset_to_map_store)))
#define IOPTIMERSTATE ((void*)(((char*)(state))+(IOPSTATE->offset_to_ioptimer)))
#define R3000STATE    ((void*)(((char*)(state))+(IOPSTATE->offset_to_r3000)))
#define SPUSTATE      ((void*)(((char*)(state))+(IOPSTATE->offset_to_spu)))

extern const uint32 iop_map_load_entries;
extern const uint32 iop_map_store_entries;

uint32 EMU_CALL iop_get_state_size(int version) {
  uint32 size = 0;
  if(version != 2) { version = 1; }
  size += sizeof(struct IOP_STATE);
  size += sizeof(struct R3000_MEMORY_MAP) * iop_map_load_entries;
  size += sizeof(struct R3000_MEMORY_MAP) * iop_map_store_entries;
  size += ioptimer_get_state_size();
  size += r3000_get_state_size();
  size += spu_get_state_size(version);
  return size;
}

#define PSXRAM_BYTE_NATIVE ((uint8*)(IOPSTATE->ram))
#define BIOS_BYTE_NATIVE ((uint8*)(bios_rom_native))
#define SCRATCH_BYTE_NATIVE ((uint8*)(IOPSTATE->scratch))

static void EMU_CALL recompute_memory_maps(struct IOP_STATE *state);
static void EMU_CALL iop_advance(void *state, uint32 elapse);

void EMU_CALL iop_clear_state(void *state, int version) {
  uint32 offset;
  if(version != 2) { version = 1; }
  /* Clear local struct */
  memset(state, 0, sizeof(struct IOP_STATE));
  /* Set version */
  IOPSTATE->version = version;
  /* Set up offsets */
  offset = sizeof(struct IOP_STATE);
  IOPSTATE->offset_to_map_load  = offset; offset += sizeof(struct R3000_MEMORY_MAP) * iop_map_load_entries;
  IOPSTATE->offset_to_map_store = offset; offset += sizeof(struct R3000_MEMORY_MAP) * iop_map_store_entries;
  IOPSTATE->offset_to_ioptimer  = offset; offset += ioptimer_get_state_size();
  IOPSTATE->offset_to_r3000     = offset; offset += r3000_get_state_size();
  IOPSTATE->offset_to_spu       = offset; offset += spu_get_state_size(version);
  //
  // Set other variables
  //
  IOPSTATE->cycles_per_sample = 768;
  //
  // Take care of substructures: memory map, R3000 state, SPU state
  //
  recompute_memory_maps(IOPSTATE);

  ioptimer_clear_state(IOPTIMERSTATE);
  // default to NTSC / 60Hz
  iop_set_refresh(IOPSTATE, 60);

  r3000_clear_state(R3000STATE);
  r3000_set_prid(R3000STATE, (version == 1) ? 0x02 : 0x10);

  r3000_set_advance_callback(R3000STATE, iop_advance, IOPSTATE);
  r3000_set_memory_maps(R3000STATE, MAPLOAD, MAPSTORE);

  spu_clear_state(SPUSTATE, version);

  // Done
}

/////////////////////////////////////////////////////////////////////////////
//
// Set the screen refresh rate in Hz (50 or 60 for PAL or NTSC)
// Only 50 or 60 are valid; other values will be ignored
//
void EMU_CALL iop_set_refresh(void *state, uint32 refresh) {
  if(refresh == 50 || refresh == 60) {
    ioptimer_set_rates(IOPTIMERSTATE,
      (IOPSTATE->version == 1) ? 33868800 : 36864000,
      (IOPSTATE->version == 1) ? 429 : 858,
      (refresh == 60) ? 262 : 312,
      (refresh == 60) ? 224 : 240,
      refresh
    );
  }
}

////////////////////////////////////////////////////////////////////////////////
//
// Location invariance handlers
//

//
// Recompute all internally-kept pointers
//
static void EMU_CALL location_recompute(struct IOP_STATE *state) {
  recompute_memory_maps(state);
  r3000_set_advance_callback(R3000STATE, iop_advance, IOPSTATE);
  r3000_set_memory_maps(R3000STATE, MAPLOAD, MAPSTORE);
  state->myself = state;
}

//
// Check to see if this structure has moved, and if so, recompute
//
// This is currently ONLY done on iop_execute
//
static EMU_INLINE void EMU_CALL location_check(struct IOP_STATE *state) {
  if(state->myself == state) return;
  location_recompute(state);
}

////////////////////////////////////////////////////////////////////////////////
//
// Obtain substates
//
// These require no location checks as the substates are obtained via
// relative offsets
//
void* EMU_CALL iop_get_r3000_state(void *state) { return R3000STATE; }
void* EMU_CALL iop_get_spu_state  (void *state) { return SPUSTATE  ; }

////////////////////////////////////////////////////////////////////////////////
/*
** Register a map for auditing purposes
** (must contain 1 byte for every RAM byte)
**
** Pass NULL to disable auditing.
*/
void EMU_CALL iop_register_map_for_auditing(void *state, uint8 *map) {
  IOPSTATE->audit_map = map;
  IOPSTATE->audit_bytes_used = 0;
  recompute_memory_maps(IOPSTATE);
}

/*
** Auditing functions
*/
static EMU_INLINE void EMU_CALL audit(struct IOP_STATE *state, uint32 address, uint32 length, uint8 t) {
  for(; length; length--, address++) {
    if(!(state->audit_map[address & 0x1FFFFF])) {
      if(t == IOP_AUDIT_READ) { state->audit_bytes_used++; }
      state->audit_map[address & 0x1FFFFF] = t;
    }
  }
}

uint32 EMU_CALL iop_get_bytes_used_in_audit(void *state) {
  // No location check required
  return IOPSTATE->audit_bytes_used;
}

/*
** R3000 memory map callbacks for auditing
*/
static uint32 EMU_CALL audit_lw(void *state, uint32 a, uint32 mask) {

//PSXTRACE3("audit_lw(%08X,%08X,%08X)\n",state,a,mask);

  a &= 0x1FFFFC;
  if((mask & 0x000000FF) && (!(IOPSTATE->audit_map[a+0]))) { IOPSTATE->audit_map[a+0] = IOP_AUDIT_READ; IOPSTATE->audit_bytes_used++; }
  if((mask & 0x0000FF00) && (!(IOPSTATE->audit_map[a+1]))) { IOPSTATE->audit_map[a+1] = IOP_AUDIT_READ; IOPSTATE->audit_bytes_used++; }
  if((mask & 0x00FF0000) && (!(IOPSTATE->audit_map[a+2]))) { IOPSTATE->audit_map[a+2] = IOP_AUDIT_READ; IOPSTATE->audit_bytes_used++; }
  if((mask & 0xFF000000) && (!(IOPSTATE->audit_map[a+3]))) { IOPSTATE->audit_map[a+3] = IOP_AUDIT_READ; IOPSTATE->audit_bytes_used++; }
  return (*((uint32*)((PSXRAM_BYTE_NATIVE)+a))) & mask;
}

static void EMU_CALL audit_sw(void *state, uint32 a, uint32 d, uint32 mask) {

//PSXTRACE3("audit_sw(state,%08X,%08X,%08X)\n",a,d,mask);

  a &= 0x1FFFFC;
  if((mask & 0x000000FF) && (!(IOPSTATE->audit_map[a+0]))) IOPSTATE->audit_map[a+0] = IOP_AUDIT_WRITE;
  if((mask & 0x0000FF00) && (!(IOPSTATE->audit_map[a+1]))) IOPSTATE->audit_map[a+1] = IOP_AUDIT_WRITE;
  if((mask & 0x00FF0000) && (!(IOPSTATE->audit_map[a+2]))) IOPSTATE->audit_map[a+2] = IOP_AUDIT_WRITE;
  if((mask & 0xFF000000) && (!(IOPSTATE->audit_map[a+3]))) IOPSTATE->audit_map[a+3] = IOP_AUDIT_WRITE;
  (*((uint32*)((PSXRAM_BYTE_NATIVE)+a))) &= (~mask);
  (*((uint32*)((PSXRAM_BYTE_NATIVE)+a))) |= (d & mask);
}

////////////////////////////////////////////////////////////////////////////////
//
// Event handling
//
// None of this needs location checking.
//
static void EMU_CALL event(
  struct IOP_STATE *state,
  uint32 type,
  char *fmt,
  uint32 arg0,
  uint32 arg1,
  uint32 arg2,
  uint32 arg3
) {
  if(state->event_mask & (1 << type)) {
    struct IOP_EVENT *ev = state->event + state->event_write_index;
    state->event_write_index++;
    if(state->event_write_index >= IOP_MAX_EVENTS) { state->event_write_index = 0; }
    if(state->event_count < IOP_MAX_EVENTS) { state->event_count++; }
    /*
    ** Copy event info
    */
    ev->time = state->odometer;
    ev->type = type;
    ev->fmt  = fmt;
    ev->arg[0] = arg0;
    ev->arg[1] = arg1;
    ev->arg[2] = arg2;
    ev->arg[3] = arg3;
  }
}

void EMU_CALL iop_clear_events(void *state) {
  IOPSTATE->event_count = 0;
}

uint32 EMU_CALL iop_get_event_count(void *state) {
  return IOPSTATE->event_count;
}

void EMU_CALL iop_get_event(void *state, uint64 *time, uint32 *type, char **fmt, uint32 *arg) {
  struct IOP_EVENT *ev;
  if(!IOPSTATE->event_count) return;
  ev = IOPSTATE->event + (((IOPSTATE->event_write_index + IOP_MAX_EVENTS) - IOPSTATE->event_count) % IOP_MAX_EVENTS);
  if(time) *time = ev->time;
  if(type) *type = ev->type;
  if(fmt) *fmt = ev->fmt;
  if(arg) {
    arg[0] = ev->arg[0];
    arg[1] = ev->arg[1];
    arg[2] = ev->arg[2];
    arg[3] = ev->arg[3];
  }
}

void EMU_CALL iop_dismiss_event(void *state) {
  if(IOPSTATE->event_count) { IOPSTATE->event_count--; }
}

/////////////////////////////////////////////////////////////////////////////
//
// misc register load
//
static uint32 EMU_CALL misc_lw(struct IOP_STATE *state, uint32 a, uint32 mask) {
  uint32 d = 0;
  switch(a & 0x1FFFFFFC) {
  case 0x1F801450:
    if(state->version == 1) { d = 0x0000008; }
    else                    { d = 0x0000000; }
    break;
  }
  event(state, IOP_EVENT_REG_LOAD, "Misc. load (%08X,%08X)=%08X", a, mask, d, 0);
  return d;
}

////////////////////////////////////////////////////////////////////////////////
/*
** Interrupt controller
*/

/*
** Update the CPU-side interrupt pending flag
*/
static void EMU_CALL intr_update_ip(struct IOP_STATE *state) {
  uint32 ip = 0;
  if(
    (!(state->intr.is_masked)) &&
    ((state->intr.signaled & state->intr.en) != 0)
  ) { ip |= 0x4; }
  r3000_setinterrupt(R3000STATE, ip);
}

/*
** Signal an interrupt (hardware-side interrupt bits are used)
*/
static void EMU_CALL intr_signal(struct IOP_STATE *state, uint32 i) {

  event(state, IOP_EVENT_INTR_SIGNAL, "Interrupt %X signaled", i, 0, 0, 0);

  if(!(state->intr.signaled & i)) {
    state->intr.signaled |= i;
    intr_update_ip(state);
  }
}

/*
** INTR register load
*/
static uint32 EMU_CALL intr_lw(struct IOP_STATE *state, uint32 a, uint32 mask) {
  uint32 d = 0;

  switch(a & 0x7C) {
  case 0x70: d = state->intr.signaled; break;
  case 0x74: d = state->intr.en; break;
  case 0x78:
    d = (state->intr.is_masked) ? 0 : 1;
    state->intr.is_masked = 1;
    intr_update_ip(state);
    break;
  }

//end:
  d &= mask;
  event(state, IOP_EVENT_REG_LOAD, "INTR load (%08X,%08X)=%08X", a, mask, d, 0);
  return d;
}

/*
** INTR register store
*/
static void EMU_CALL intr_sw(struct IOP_STATE *state, uint32 a, uint32 d, uint32 mask) {

  event(state, IOP_EVENT_REG_STORE, "INTR store (%08X,%08X,%08X)", a, d, mask, 0);

  switch(a & 0x7C) {
  case 0x70:
    // Acknowledge
    state->intr.signaled &= (d | (~mask));
    intr_update_ip(state);
    break;
  case 0x74:
    // Change enable bits
    state->intr.en = (state->intr.en & (~mask)) | (d & mask);
    intr_update_ip(state);
    break;
  case 0x78:
    // Mask/unmask
    state->intr.is_masked = (d ^ 1) & 1;
    intr_update_ip(state);
    break;
  }

}

////////////////////////////////////////////////////////////////////////////////
/*
** DMA
*/

/*
** Signal completion on a DMA channel
*/
static void EMU_CALL dma_signal_completion(struct IOP_STATE *state, uint32 chan) {
  uint32 core = chan / 7;
  chan %= 7;

  /* Reset transfer busy bit */
  state->dma[core].chan[chan].CHCR &= (~0x01000000);

  /* TODO: see how the core affects the int numbers */

  if(state->dma[core].DICR & (0x00010000 << (chan))) {
    state->dma[core].DICR |= (0x01000000 << (chan));
    intr_signal(state, IOP_INT_DMA);
  }
}

/*
** Initiate a DMA transfer
*/
static void EMU_CALL dma_transfer(struct IOP_STATE *state, uint32 core, uint32 chan) {
  uint32 realchan      = 7 * core + chan;
  uint32 mem_address   = (state->dma[core].chan[chan].MADR      );
  uint32 blocksize     = (state->dma[core].chan[chan].BCR  >>  0) & 0xFFFF;
  uint32 blockcount    = (state->dma[core].chan[chan].BCR  >> 16) & 0xFFFF;
  int    is_writing    = (state->dma[core].chan[chan].CHCR >>  0) & 1;
//  int    is_continuous = (state->dma[core].chan[chan].CHCR >>  9) & 1;
//  int    is_linked     = (state->dma[core].chan[chan].CHCR >> 10) & 1;
  uint32 len = blocksize * 4 * blockcount;
//  uint32 i;
  uint64 cycles_delay = 0;

  event(state, IOP_EVENT_DMA_TRANSFER, 
    is_writing ? 
    "DMA ch.%d write (%08X, %08X)" :
    "DMA ch.%d read  (%08X, %08X)",
    realchan, mem_address, len, 0
  );

  mem_address &= 0x1FFFFC;
  if(!len) return;

  /*
  ** Remember to audit!
  ** TODO: audit linking: SPU with IOP etc.
  */
  if(state->audit_map) {
    audit(state, mem_address, len, is_writing ? IOP_AUDIT_READ : IOP_AUDIT_WRITE);
  }

  switch(realchan) {
  case 4: // SPU CORE0
    spu_dma(SPUSTATE, 0, PSXRAM_BYTE_NATIVE, mem_address & 0x1FFFFC, 0x1FFFFC, len, is_writing);
    cycles_delay = DMA_SPU_CYCLES_PER_HALFWORD * (len / 2);
    break;
  case 7: // SPU CORE1
    spu_dma(SPUSTATE, 1, PSXRAM_BYTE_NATIVE, mem_address & 0x1FFFFC, 0x1FFFFC, len, is_writing);
    cycles_delay = DMA_SPU_CYCLES_PER_HALFWORD * (len / 2);
    break;
  case 10: // SIF
    break;
  }

  /*
  ** Behavior here depends on compat level
  */
  switch(state->compat_level) {
  case IOP_COMPAT_FRIENDLY:
    /* Interrupt immediately */
    dma_signal_completion(state, realchan);
    break;
  case IOP_COMPAT_HARSH:
    /* Schedule interrupt for later */
    if(!cycles_delay) { cycles_delay = 1; }
    state->dma[core].chan[chan].cycles_until_interrupt = cycles_delay;
    break;
  default:
    /* Default behavior: friendly, interrupt immediately */
    dma_signal_completion(state, realchan);
    break;
  }

}

/*
** DMA register load
*/
static uint32 EMU_CALL dma_lw(struct IOP_STATE *state, uint32 core, uint32 a, uint32 mask) {
  uint32 d = 0;
  struct IOP_DMA *dma;
  uint8 chan;

  dma = &(state->dma[core]);
  chan = (a >> 4) & 7;
  if(chan == 7) {
    switch(a & 0xC) {
    case 0x0: d = dma->DPCR; break;
    case 0x4: d = dma->DICR; break;
    case 0x8: d = 0; break; // sometimes this is blocked until it's 0
    }
  }

//end:
  d &= mask;
  event(state, IOP_EVENT_REG_LOAD, "DMA%d load (%08X,%08X)=%08X", core, a, mask, d);
  return d;
}

static uint32 EMU_CALL dma0_lw(struct IOP_STATE *state, uint32 a, uint32 mask) { return dma_lw(state, 0, a, mask); }
static uint32 EMU_CALL dma1_lw(struct IOP_STATE *state, uint32 a, uint32 mask) { return dma_lw(state, 1, a, mask); }

/*
** DMA register store
*/
static void EMU_CALL dma_sw(struct IOP_STATE *state, uint32 core, uint32 a, uint32 d, uint32 mask) {
  struct IOP_DMA *dma;
  uint8 chan;

  event(state, IOP_EVENT_REG_STORE, "DMA%d store (%08X,%08X,%08X)", core, a, d, mask);

  dma = &(state->dma[core]);
  chan = (a >> 4) & 7;
  if(chan == 7) {
    switch(a & 0xC) {
    case 0x0: dma->DPCR = (dma->DPCR & (~mask)) | (d & mask); break;
    case 0x4:
      if(mask & 0xFF000000) { dma->DICR &= ((~d) & 0xFF000000); }
      if(mask & 0x00FF0000) { dma->DICR |= (( d) & 0x00FF0000); }
      break;
    case 0x8: // sometimes 0 is stored here
      break;
    }
  } else {
    switch(a & 0xC) {
    case 0x0: dma->chan[chan].MADR = (dma->chan[chan].MADR & (~mask)) | (d & mask); break;
    case 0x4: dma->chan[chan].BCR  = (dma->chan[chan].BCR  & (~mask)) | (d & mask); break;
    case 0x8:
      dma->chan[chan].CHCR = (dma->chan[chan].CHCR & (~mask)) | (d & mask);
      /* Transfer being initiated */
      if(d & mask & 0x01000000) dma_transfer(state, core, chan);
      break;
    }
  }

}

static void EMU_CALL dma0_sw(struct IOP_STATE *state, uint32 a, uint32 d, uint32 mask) { dma_sw(state, 0, a, d, mask); }
static void EMU_CALL dma1_sw(struct IOP_STATE *state, uint32 a, uint32 d, uint32 mask) { dma_sw(state, 1, a, d, mask); }

////////////////////////////////////////////////////////////////////////////////
/*
** Timers
*/

static uint32 EMU_CALL timer_lw(struct IOP_STATE *state, uint32 a, uint32 mask) {
  uint32 d = 0;
  event(state, IOP_EVENT_REG_LOAD, "Timer load (%08X,%08X)=%08X", a, mask, d, 0);
  d = ioptimer_lw(IOPTIMERSTATE, a, mask);
  return d & mask;
}

static void EMU_CALL timer_sw(struct IOP_STATE *state, uint32 a, uint32 d, uint32 mask) {
  event(state, IOP_EVENT_REG_STORE, "Timer store (%08X,%08X,%08X)", a, d, mask, 0);
  ioptimer_sw(IOPTIMERSTATE, a, d, mask);
}

////////////////////////////////////////////////////////////////////////////////
/*
** SPU: Forward read/write/advance calls to the SPU code
*/

static void EMU_CALL flush_sound(struct IOP_STATE *state) {
  uint32 samples_needed = state->sound_cycles_pending / (state->cycles_per_sample);
  if(samples_needed > state->sound_buffer_samples_free) {
    samples_needed = state->sound_buffer_samples_free;
  }
  if(!samples_needed) return;

  spu_render(SPUSTATE, state->sound_buffer, samples_needed);

  if(state->sound_buffer) state->sound_buffer += 2 * samples_needed;
  state->sound_buffer_samples_free -= samples_needed;
  state->sound_cycles_pending -= (state->cycles_per_sample) * samples_needed;
}

/*
** Register loads/stores
*/

static uint32 EMU_CALL iop_spu_lw(void *state, uint32 a, uint32 mask) {
  uint32 d = 0;
  flush_sound(IOPSTATE);
  if(mask & 0x0000FFFF) d |= ((((uint32)(spu_lh(SPUSTATE, (a&(~3))+0))) & 0xFFFF) <<  0);
  if(mask & 0xFFFF0000) d |= ((((uint32)(spu_lh(SPUSTATE, (a&(~3))+2))) & 0xFFFF) << 16);
//end:
  d &= mask;
  event(state, IOP_EVENT_REG_LOAD, "SPU load (%08X,%08X)=%08X", a, mask, d, 0);
  return d;
}

static void EMU_CALL iop_spu_sw(void *state, uint32 a, uint32 d, uint32 mask) {
  event(state, IOP_EVENT_REG_STORE, "SPU store (%08X,%08X,%08X)", a, d, mask, 0);
  flush_sound(IOPSTATE);
  if(mask & 0x0000FFFF) spu_sh(SPUSTATE, (a&(~3))+0, d >>  0);
  if(mask & 0xFFFF0000) spu_sh(SPUSTATE, (a&(~3))+2, d >> 16);
}

static uint32 EMU_CALL iop_spu2_lw(void *state, uint32 a, uint32 mask) {
  uint32 d = 0;
  if(IOPSTATE->version == 2) {
    flush_sound(IOPSTATE);
    if(mask & 0x0000FFFF) d |= ((((uint32)(spu_lh(SPUSTATE, (a&(~3))+0))) & 0xFFFF) <<  0);
    if(mask & 0xFFFF0000) d |= ((((uint32)(spu_lh(SPUSTATE, (a&(~3))+2))) & 0xFFFF) << 16);
  }
//end:
  d &= mask;
  event(state, IOP_EVENT_REG_LOAD, "SPU2 load (%08X,%08X)=%08X", a, mask, d, 0);
  return d;
}

static void EMU_CALL iop_spu2_sw(void *state, uint32 a, uint32 d, uint32 mask) {
  event(state, IOP_EVENT_REG_STORE, "SPU2 store (%08X,%08X,%08X)", a, d, mask, 0);
  if(IOPSTATE->version == 2) {
    flush_sound(IOPSTATE);
    if(mask & 0x0000FFFF) spu_sh(SPUSTATE, (a&(~3))+0, d >>  0);
    if(mask & 0xFFFF0000) spu_sh(SPUSTATE, (a&(~3))+2, d >> 16);
  }
}

/////////////////////////////////////////////////////////////////////////////
// 
// SIF register load
//
static uint32 EMU_CALL sif_lw(struct IOP_STATE *state, uint32 a, uint32 mask) {
  uint32 d = 0;
  switch(a & 0x7C) {
  case 0x20: d = 0x00010000; break;
  case 0x60: d = 0x1D000060; break;
  }
  d &= mask;
  event(state, IOP_EVENT_REG_LOAD, "SIF load (%08X,%08X)=%08X", a, mask, d, 0);
  return d;
}

//
// SIF register store
//
static void EMU_CALL sif_sw(struct IOP_STATE *state, uint32 a, uint32 d, uint32 mask) {

  event(state, IOP_EVENT_REG_STORE, "SIF store (%08X,%08X,%08X)", a, d, mask, 0);

  switch(a & 0x7C) {
  case 0x30: // seems to be a command of some kind
    //dma_signal_completion(state, 10);
    break;
  case 0x60:
    break;
  }

}

////////////////////////////////////////////////////////////////////////////////
/*
** hefile emucall
*/

static void EMU_CALL iop_emucall_sw(void *state, uint32 a, uint32 d, uint32 mask) {
  sint32 type;
  sint32 emufd;
  sint32 ofs;
  sint32 arg1;
  sint32 arg2;
  sint32 r;
  if(mask != 0xFFFFFFFF) return;
  if(d & 3) return;
  type  = *((sint32*)((PSXRAM_BYTE_NATIVE)+((d+(4*( 0 )))&0x1FFFFC)));
  emufd = *((sint32*)((PSXRAM_BYTE_NATIVE)+((d+(4*( 1 )))&0x1FFFFC)));
  ofs   = *((sint32*)((PSXRAM_BYTE_NATIVE)+((d+(4*( 2 )))&0x1FFFFC)));
  arg1  = *((sint32*)((PSXRAM_BYTE_NATIVE)+((d+(4*( 3 )))&0x1FFFFC)));
  arg2  = *((sint32*)((PSXRAM_BYTE_NATIVE)+((d+(4*( 4 )))&0x1FFFFC)));

  //
  // Log an event
  //
  switch(type) {
  case 0:  event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual console output(0x%X, 0x%X)", ofs, arg1, 0, 0); break;
  case 1:  event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual quit", 0, 0, 0, 0); break;
  case 3:  event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual open(%d, 0x%X, 0x%X, 0x%X)", emufd, ofs, arg1, arg2); break;
  case 4:  event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual close(%d)", emufd, 0, 0, 0); break;
  case 5:  event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual read(%d, 0x%X, 0x%X)", emufd, ofs, arg1, 0); break;
  case 6:  event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual write(%d, 0x%X, 0x%X)", emufd, ofs, arg1, 0); break;
  case 7:  event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual lseek(%d, 0x%X, 0x%X)", emufd, arg1, arg2, 0); break;
  default: event(IOPSTATE, IOP_EVENT_VIRTUAL_IO, "Virtual unknown event(0x%X, 0x%X, 0x%X, 0x%X)", emufd, ofs, arg1, arg2); break;
  }

  switch(type) {
  case 1: // HEFILE_PSX_QUIT
//MessageBox(NULL,"qfset",NULL,MB_OK);
    IOPSTATE->quitflag = 1;
    r3000_break(R3000STATE);
    r = 0;
    break;
  default:
    r = psx_emucall(
      IOPSTATE->psx_state,
      PSXRAM_BYTE_NATIVE,
      0x200000,
      type, emufd, ofs, arg1, arg2
    );
    //
    // Special case: fatal error if return value is -5
    //
    if(r == -5) {
      IOPSTATE->fatalflag = 1;
      r3000_break(R3000STATE);
    }
    break;
  }
  *((sint32*)((PSXRAM_BYTE_NATIVE)+((d+(4*( 0 )))&0x1FFFFC))) = r;
}

////////////////////////////////////////////////////////////////////////////////
/*
** Invalid-address catchers
*/

static uint32 EMU_CALL catcher_lw(void *state, uint32 a, uint32 mask) {
  uint32 d = 0;
  event(state, IOP_EVENT_REG_LOAD, "Catcher load (%08X,%08X)", a, mask, d, 0);
  return d;
}

static void EMU_CALL catcher_sw(void *state, uint32 a, uint32 d, uint32 mask) {
  event(state, IOP_EVENT_REG_STORE, "Catcher store (%08X,%08X,%08X)", a, d, mask, 0);
}

////////////////////////////////////////////////////////////////////////////////
/*
** Advance hardware activity by the given cycle count
*/
static void EMU_CALL iop_advance(void *state, uint32 elapse) {
  sint32 core, i; //, r;
  uint32 intr;
  if(!elapse) return;
  //
  // Check timers
  //
  intr = ioptimer_advance(IOPTIMERSTATE, elapse);
  if(intr) intr_signal(IOPSTATE, intr);
  /*
  ** Check DMA counters
  */
  for(core = 0; core < 2; core++) {
    for(i = 0; i < 7; i++) {
      uint64 u = IOPSTATE->dma[core].chan[i].cycles_until_interrupt;
      if(!u) continue;
      if(u > elapse) {
        u -= elapse;
      } else {
        u = 0;
        dma_signal_completion(IOPSTATE, core * 7 + i);
      }
      IOPSTATE->dma[core].chan[i].cycles_until_interrupt = u;
    }
  }
  /*
  ** Check SPU IRQ
  */
  if(elapse >= IOPSTATE->sound_cycles_until_interrupt) intr_signal(IOPSTATE, 0x200);
  /*
  ** Update pending sound cycles
  */
  IOPSTATE->sound_cycles_pending += elapse;
  /*
  ** Update odometer
  */
  IOPSTATE->odometer += ((uint64)elapse);
}

////////////////////////////////////////////////////////////////////////////////
/*
** Determine how many cycles until the next interrupt
**
** This is then used as an upper bound for how many cycles can be executed
** before checking for futher interrupts
*/
static uint32 EMU_CALL cycles_until_next_interrupt(struct IOP_STATE *state, uint32 min) {
  uint32 cyc;
  uint32 core, i; //, r;
  //
  // Timers
  //
  cyc = ioptimer_cycles_until_interrupt(IOPTIMERSTATE);
  if(cyc < min) min = cyc;
  //
  // DMA
  //
  for(core = 0; core < 2; core++) {
    for(i = 0; i < 7; i++) {
      uint64 u = state->dma[core].chan[i].cycles_until_interrupt;
      if(!u) continue;
      if(u < ((uint64)min)) { min = (uint32)u; }
    }
  }
  //
  // SPU
  //
  state->sound_cycles_until_interrupt = cyc = spu_cycles_until_interrupt(SPUSTATE, (min + 767) / 768);
  if(cyc < min) min = cyc;

  if(min < 1) min = 1;
  return min;
}

////////////////////////////////////////////////////////////////////////////////
/*
** Static memory map structures
*/

#define STATEOFS(thetype,thefield) ((void*)(&(((struct thetype*)0)->thefield)))

//
// Note that the first two entries here MUST both be state offsets.
//
static const struct R3000_MEMORY_MAP iop_map_load[] = {
  { 0x00000000, 0x007FFFFF, { 0x001FFFFF, R3000_MAP_TYPE_POINTER , STATEOFS(IOP_STATE, ram    ) } },
  { 0x1F800000, 0x1F800FFF, { 0x000003FF, R3000_MAP_TYPE_POINTER , STATEOFS(IOP_STATE, scratch) } },
  { 0x1FC00000, 0x1FFFFFFF, { 0x00000000, R3000_MAP_TYPE_POINTER , NULL                         } },

  { 0x1D000000, 0x1D00007F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, sif_lw                       } },
  { 0x1F801000, 0x1F80107F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, intr_lw                      } },
  { 0x1F801080, 0x1F8010FF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, dma0_lw                      } },
  { 0x1F801100, 0x1F80112F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, timer_lw                     } },
  { 0x1F801400, 0x1F80147F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, misc_lw                      } },
  { 0x1F801480, 0x1F8014AF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, timer_lw                     } },
  { 0x1F801500, 0x1F80157F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, dma1_lw                      } },
  { 0x1F801C00, 0x1F801DFF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, iop_spu_lw                   } },
  { 0x1F900000, 0x1F9007FF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, iop_spu2_lw                  } },

  { 0x00000000, 0xFFFFFFFF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, catcher_lw                   } }
};

static const struct R3000_MEMORY_MAP iop_map_store[] = {
  { 0x00000000, 0x007FFFFF, { 0x001FFFFF, R3000_MAP_TYPE_POINTER , STATEOFS(IOP_STATE, ram    ) } },
  { 0x1F800000, 0x1F800FFF, { 0x000003FF, R3000_MAP_TYPE_POINTER , STATEOFS(IOP_STATE, scratch) } },

  { 0x1D000000, 0x1D00007F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, sif_sw                       } },
  { 0x1F801000, 0x1F80107F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, intr_sw                      } },
  { 0x1F801080, 0x1F8010FF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, dma0_sw                      } },
  { 0x1F801100, 0x1F80112F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, timer_sw                     } },
//{ 0x1F801400, 0x1F80147F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, intr1_sw                     } },
  { 0x1F801480, 0x1F8014AF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, timer_sw                     } },
  { 0x1F801500, 0x1F80157F, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, dma1_sw                      } },
  { 0x1F801C00, 0x1F801DFF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, iop_spu_sw                   } },
  { 0x1F900000, 0x1F9007FF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, iop_spu2_sw                  } },

  { 0x1FC17120, 0x1FC17123, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, iop_emucall_sw               } },

  { 0x00000000, 0xFFFFFFFF, { 0x1FFFFFFF, R3000_MAP_TYPE_CALLBACK, catcher_sw                   } }
};

#define IOP_ARRAY_ENTRIES(x) (sizeof(x)/sizeof((x)[0]))

const uint32 iop_map_load_entries  = IOP_ARRAY_ENTRIES(iop_map_load );
const uint32 iop_map_store_entries = IOP_ARRAY_ENTRIES(iop_map_store);

////////////////////////////////////////////////////////////////////////////////
//
// Memory map recomputation
//
// Necessary on structure location change or audit change
//

static void perform_state_offset(struct R3000_MEMORY_MAP *map, struct IOP_STATE *state) {
  //
  // It's safe to typecast this "pointer" to a uint32 since, due to
  // the STATEOFS hack, it'll never be bigger than 4GB
  //
  uint32 o = (uint32)(map->type.p);
  map->type.p = ((uint8*)state) + o;
}

//
// Recompute the memory maps.
// PERFORMS NO REGISTRATION with the actual R3000 state.
//
static void recompute_memory_maps(struct IOP_STATE *state) {
  sint32 i;
  struct R3000_MEMORY_MAP *mapload  = MAPLOAD;
  struct R3000_MEMORY_MAP *mapstore = MAPSTORE;
  //
  // First, just copy from the static tables
  //
  memcpy(mapload , iop_map_load , sizeof(iop_map_load ));
  memcpy(mapstore, iop_map_store, sizeof(iop_map_store));
  //
  // Now perform state offsets on first _two_ entries in each map
  //
  for(i = 0; i < 2; i++) {
    perform_state_offset(mapload  + i, state);
    perform_state_offset(mapstore + i, state);
  }
  //
  // And importantly, set the third load entry to point to the BIOS
  //
  mapload[2].type.mask = bios_get_imagesize() - 1;
  mapload[2].type.n    = R3000_MAP_TYPE_POINTER;
  mapload[2].type.p    = bios_get_image_native();

  //
  // Finally, if we're auditing, we'll want to change the _first_ entry in
  // each map to an audit callback
  //
  if(state->audit_map) {
    mapload [0].type.n = R3000_MAP_TYPE_CALLBACK;
    mapload [0].type.p = audit_lw;
    mapstore[0].type.n = R3000_MAP_TYPE_CALLBACK;
    mapstore[0].type.p = audit_sw;
  }
}

////////////////////////////////////////////////////////////////////////////////
//
// Executes the given number of cycles or the given number of samples
// (whichever is less)
//
// Sets *sound_samples to the number of samples actually generated,
// which may be ZERO or LESS than the number requested, but never more.
//
// Return value:
// >= 0   The number of cycles actually executed, which may be ZERO, MORE,
//        or LESS than the number requested
// -1     Halted successfully (only applicable to PS2 environment)
// <= -2  Unrecoverable error
//
sint32 EMU_CALL iop_execute(
  void   *state,
  void   *psx_state,
  sint32  cycles,
  sint16 *sound_buf,
  uint32 *sound_samples,
  uint32  event_mask
) {
  sint32 r = 0;
  sint64 cyc_upperbound;
  uint64 old_odometer;
  uint64 target_odometer;

  location_check(IOPSTATE);

  IOPSTATE->psx_state = psx_state;

  old_odometer = IOPSTATE->odometer;

  IOPSTATE->event_mask = event_mask;
  IOPSTATE->sound_buffer              =  sound_buf;
  IOPSTATE->sound_buffer_samples_free = *sound_samples;
  //
  // If the quit flag was set, do nothing
  //
  if(IOPSTATE->quitflag) {
//MessageBox(NULL,"qfset_on_execute",NULL,MB_OK);
    return -1;
  }
  //
  // If the fatal flag was set, return -2
  //
  if(IOPSTATE->fatalflag) { return -2; }
  //
  // If we have a bogus cycle count, return error
  //
  if(cycles < 0) { return -2; }

  /*
  ** Begin by flushing any pending sound data into the newly available
  ** buffer, if necessary
  */
  flush_sound(IOPSTATE);
  /*
  ** Compute an upper bound for the number of cycles that can be generated
  ** while still fitting in the buffer
  */
  cyc_upperbound = 
    ((sint64)(IOPSTATE->cycles_per_sample)) *
    ((sint64)(IOPSTATE->sound_buffer_samples_free));
  if(cyc_upperbound > (IOPSTATE->sound_cycles_pending)) {
    cyc_upperbound -= IOPSTATE->sound_cycles_pending;
  } else {
    cyc_upperbound = 0;
  }
  /*
  ** Bound cyc_upperbound by the number of cycles requested
  */
  if(cycles > 0x70000000) cycles = 0x70000000;
  if(cyc_upperbound > cycles) cyc_upperbound = cycles;
  /*
  ** Now cyc_upperbound is the number of cycles to execute.
  ** Compute the target odometer
  */
  target_odometer = (IOPSTATE->odometer)+((uint64)(cyc_upperbound));
  /*
  ** Execution loop
  */
  for(;;) {
    uint32 diff, ci;
    if(IOPSTATE->odometer >= target_odometer) break;

    diff = (uint32)((target_odometer) - (IOPSTATE->odometer));
    ci = cycles_until_next_interrupt(IOPSTATE, diff);
    if(diff > ci) diff = ci;
    r = r3000_execute(R3000STATE, diff);
    //
    // On a clean quit, break with -1
    //
    if(IOPSTATE->quitflag) {
      r = -1;
      break;
    }
    //
    // Create an error if the fatalflag got set
    //
    if(IOPSTATE->fatalflag) {
      IOPSTATE->fatalflag = 0;
      r = -5;
    }
    //
    // On an unrecoverable CPU error, break with -2
    //
    if(r < 0) {
      r = -2;
      break;
    }
  }
  /*
  ** End with a final sound flush
  */
  flush_sound(IOPSTATE);
  /*
  ** Determine how many sound samples we generated
  */
  (*sound_samples) -= (IOPSTATE->sound_buffer_samples_free);
  //
  // If there was an error, return it
  //
  if(r < 0) {
//char s[100];
//sprintf(s,"blah blah %d", r);
//MessageBox(NULL,s,NULL,MB_OK);
    return r;
  }
  //
  // Otherwise return the number of cycles we executed
  //
  r = (sint32)(IOPSTATE->odometer - old_odometer);
  return r;
}

////////////////////////////////////////////////////////////////////////////////
//
// For debugger use
//
// Location checks not needed
//
uint32 EMU_CALL iop_getword(void *state, uint32 a) { a &= 0x1FFFFFFC;
         if(a < 0x00800000) { return (*((uint32*)(PSXRAM_BYTE_NATIVE+(a&0x1FFFFC))));
  } else if(a >=0x1FC00000) { return (*((uint32*)(bios_get_image_native()+(a&(bios_get_imagesize()-1)))));
  } else {                    return 0;
  }
}

void EMU_CALL iop_setword(void *state, uint32 a, uint32 d) { a &= 0x1FFFFFFC;
  if(a < 0x00800000) {
    (*((uint32*)(PSXRAM_BYTE_NATIVE+(a&0x1FFFFC)))) = d;
  }
}

////////////////////////////////////////////////////////////////////////////////
//
// Timing may be nonuniform
//
// Location checks not needed
//
uint64 EMU_CALL iop_get_odometer(void *state) {
  return IOPSTATE->odometer;
}

////////////////////////////////////////////////////////////////////////////////
//
// Compatibility level
//
// Location checks not needed
//
void EMU_CALL iop_set_compat(void *state, uint8 compat) {
  IOPSTATE->compat_level = compat;
}

////////////////////////////////////////////////////////////////////////////////
//
// Useful for playing with the tempo.
// 768 is normal.
// Higher is faster; lower is slower.
//
// Location checks not needed
//
void EMU_CALL iop_set_cycles_per_sample(void *state, uint32 cycles_per_sample) {
  if(!cycles_per_sample) cycles_per_sample = 1;
  IOPSTATE->cycles_per_sample = cycles_per_sample;
}

////////////////////////////////////////////////////////////////////////////////
//
// Upload a section to RAM
//
// Location checks not needed
//
void EMU_CALL iop_upload_to_ram(void *state, uint32 address, const void *src, uint32 len) {
  while(len) {
    uint32 advance;
    address &= 0x1FFFFF;
    advance = 0x200000 - address;
    if(advance > len) { advance = len; }
#ifdef EMU_BIG_ENDIAN
    { uint32 i;
      for(i = 0; i < advance; i++) {
        (PSXRAM_BYTE_NATIVE)[(address+i)^(EMU_ENDIAN_XOR(3))] = ((char*)src)[i];
      }
    }
#else
    memcpy((PSXRAM_BYTE_NATIVE) + address, src, advance);
#endif
    src = (((const char*)src) + advance);
    len -= advance;
    address += advance;
  }

}

////////////////////////////////////////////////////////////////////////////////
