/*  Copyright 2005 Guillaume Duhamel
    Copyright 2005-2006 Theo Berkau

    This file is part of Yabause.

    Yabause 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.

    Yabause 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 Yabause; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/
/*
        Copyright 2019 devMiyax(smiyaxdev@gmail.com)

This file is part of YabaSanshiro.

        YabaSanshiro 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.

YabaSanshiro 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 YabaSanshiro; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
*/

/*! \file memory.c
    \brief Memory access functions.
*/

#ifdef PSP  // see FIXME in T1MemoryInit()
# include <stdint.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <ctype.h>

#if defined(_WIN32) || defined(_WINDOWS)
#include <io.h>
#else
#include <unistd.h>
#endif

#include "memory.h"
#include "coffelf.h"
#include "cs0.h"
#include "cs1.h"
#include "cs2.h"
#include "debug.h"
#include "error.h"
#include "sh2core.h"
#include "scsp.h"
#include "scu.h"
#include "smpc.h"
#include "vdp1.h"
#include "vdp2.h"
#include "yabause.h"
#include "yui.h"
#include "movie.h"

#include "retroachievements/yabause_ra_integration_cwrapper.h"

//#ifdef HAVE_LIBGL
//#define USE_OPENGL
//#endif

#ifdef USE_OPENGL
#include "ygl.h"
#endif

#include "vidsoft.h"
#include "vidogl.h"

#if CACHE_ENABLE
#else
u8 FASTCALL MappedMemoryReadByteNocache(u32 addr, u32 * cycle){ return MappedMemoryReadByte(addr, NULL); }
u16 FASTCALL MappedMemoryReadWordNocache(u32 addr, u32 * cycle){ return MappedMemoryReadWord(addr, NULL); }
u32 FASTCALL MappedMemoryReadLongNocache(u32 addr, u32 * cycle){ return MappedMemoryReadLong(addr, NULL); }
void FASTCALL MappedMemoryWriteByteNocache(u32 addr, u8 val, u32 * cycle){ MappedMemoryWriteByte(addr,val, NULL);  }
void FASTCALL MappedMemoryWriteWordNocache(u32 addr, u16 val, u32 * cycle){ MappedMemoryWriteWord(addr, val, NULL); }
void FASTCALL MappedMemoryWriteLongNocache(u32 addr, u32 val, u32 * cycle){ MappedMemoryWriteLong(addr, val, NULL); }
#endif


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

writebytefunc WriteByteList[0x1000];
writewordfunc WriteWordList[0x1000];
writelongfunc WriteLongList[0x1000];

readbytefunc ReadByteList[0x1000];
readwordfunc ReadWordList[0x1000];
readlongfunc ReadLongList[0x1000];

u8 *HighWram;
u8 *LowWram;
u8 *BiosRom;
u8 *BupRam;

extern int tweak_backup_file_size;
extern u32 tweak_backup_file_addr;

/* This flag is set to 1 on every write to backup RAM.  Ports can freely
 * check or clear this flag to determine when backup RAM has been written,
 * e.g. for implementing autosave of backup RAM. */
u8 BupRamWritten;

#if defined(NX)

void * YabMemMap(char * filename, u32 size ) {
   return malloc(size);
}

void YabFreeMap(void * p) {
  free(p);
} 

#elif defined(__GNUC__) && !defined(_WINDOWS)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

static u32 mmapsize = 0;

void * YabMemMap(char * filename, u32 size ) {

  struct stat sb;
  off_t len;
  char *p;
  int fd;

  fd = open(filename, O_RDWR);
  if (fd ==-1) {
    LOG("YabMemMap: open failed");
    return NULL;
   }

  if (fstat(fd, &sb) ==-1) {
    LOG("YabMemMap: fstat failed");
    close(fd);
    return NULL;
  }

  if (sb.st_size > 10 * 1024 * 1024) {
    LOG("YabMemMap: file too large (%lld bytes), refusing to mmap", (long long)sb.st_size);
    close(fd);
    return NULL;
  }

  p = mmap(0, sb.st_size, PROT_READ| PROT_WRITE, MAP_SHARED, fd, 0);
  if (p == MAP_FAILED) {
    LOG("YabMemMap: mmap failed");
    return NULL;
  }

  if (close(fd) == -1) {
    LOG("YabMemMap: close failed");
    return NULL;
  }

  mmapsize = sb.st_size;
  return p;
}

void YabFreeMap(void * p) {
  munmap(p, mmapsize);
}

int YabFlushMemMap(void* mappedPtr)
{
  if (mappedPtr == NULL || mmapsize == 0) {
    return -1;  // 何もすることがない
  }

  // MS_SYNC: 呼び出しが戻るときには、少なくともカーネルバッファまでは書き出されている
  if (msync(mappedPtr, mmapsize, MS_SYNC) != 0) {
    //LOG("YabFlushMemMap: msync failed: %s", strerror(errno));
    return -1;
  }

  return 0;
}


#elif defined(_WINDOWS)

#include <windows.h>
HANDLE hFMWrite = INVALID_HANDLE_VALUE;
HANDLE hFile = INVALID_HANDLE_VALUE;
void * YabMemMap(char * filename, u32 size ) {

  struct stat sb;
  off_t len;
  char *p;
  int fd;

  hFile = CreateFileA(
    filename, 
    GENERIC_READ|GENERIC_WRITE, 
    FILE_SHARE_READ,
    0, 
    OPEN_EXISTING, 
    FILE_ATTRIBUTE_NORMAL,
    0);
  if (INVALID_HANDLE_VALUE == hFile) {
    DWORD errorMessageID = GetLastError();
    LPSTR messageBuffer = NULL;
    size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
      NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
    LOG(messageBuffer);
    return NULL;
  }

  hFMWrite = CreateFileMapping(
    hFile,
    NULL,
    PAGE_READWRITE,
    0,
    size,
    "BACKUP");
  if (hFMWrite == INVALID_HANDLE_VALUE)
    return NULL;

  return MapViewOfFile(hFMWrite, FILE_MAP_ALL_ACCESS, 0, 0, size);
}

void YabFreeMap(void * p) {
  UnmapViewOfFile(p);
  CloseHandle(hFMWrite);
  CloseHandle(hFile);
  hFMWrite = NULL;
}

int YabFlushMemMap(void* mappedPtr){
  boolean ok1 = TRUE;
  boolean ok2 = TRUE;

  // ① マップしたビューの内容をファイルに反映
  if (mappedPtr != NULL) {
    // 第2引数 0 → このビューの dirty page 全部をフラッシュ
    ok1 = FlushViewOfFile(mappedPtr, 0);
    if (!ok1) {
      LOG("FlushViewOfFile failed");
    }
  }

  // ② ファイルシステム側のバッファを物理ディスクに反映
  if (hFile != INVALID_HANDLE_VALUE) {
    ok2 = FlushFileBuffers(hFile);
    if (!ok2) {
      LOG("FlushFileBuffers failed");
    }
  }

  if(ok1 && ok2) return 0;
  return -1;

}


#else
void * YabMemMap(char * filename) {
  return NULL;
}
#endif


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

u8 * T1MemoryInit(u32 size)
{
#ifdef PSP  // FIXME: could be ported to all arches, but requires stdint.h
            //        for uintptr_t
   u8 * base;
   u8 * mem;

   if ((base = calloc((size * sizeof(u8)) + sizeof(u8 *) + 64, 1)) == NULL)
      return NULL;

   mem = base + sizeof(u8 *);
   mem = mem + (64 - ((uintptr_t) mem & 63));
   *(u8 **)(mem - sizeof(u8 *)) = base; // Save base pointer below memory block

   return mem;
#else
   return calloc(size, sizeof(u8));
#endif
}

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

void T1MemoryDeInit(u8 * mem)
{
#ifdef PSP
   if (mem)
      free(*(u8 **)(mem - sizeof(u8 *)));
#else
   free(mem);
#endif
}

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

T3Memory * T3MemoryInit(u32 size)
{
   T3Memory * mem;

   if ((mem = (T3Memory *) calloc(1, sizeof(T3Memory))) == NULL)
      return NULL;

   if ((mem->base_mem = (u8 *) calloc(size, sizeof(u8))) == NULL)
   {
      free(mem);
      return NULL;
   }

   mem->mem = mem->base_mem + size;

   return mem;
}

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

void T3MemoryDeInit(T3Memory * mem)
{
   free(mem->base_mem);
   free(mem);
}

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

Dummy * DummyInit(UNUSED u32 s)
{
   return NULL;
}

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

void DummyDeInit(UNUSED Dummy * d)
{
}

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

static u8 FASTCALL UnhandledMemoryReadByte(USED_IF_DEBUG u32 addr)
{
   LOG("Unhandled byte read %08X\n", (unsigned int)addr);
   return 0;
}

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

static u16 FASTCALL UnhandledMemoryReadWord(USED_IF_DEBUG u32 addr)
{
   LOG("Unhandled word read %08X\n", (unsigned int)addr);
   return 0;
}

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

static u32 FASTCALL UnhandledMemoryReadLong(USED_IF_DEBUG u32 addr)
{
   LOG("Unhandled long read %08X\n", (unsigned int)addr);
   return 0;
}

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

static void FASTCALL UnhandledMemoryWriteByte(USED_IF_DEBUG u32 addr, UNUSED u8 val)
{
   LOG("Unhandled byte write %08X\n", (unsigned int)addr);
}

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

static void FASTCALL UnhandledMemoryWriteWord(USED_IF_DEBUG u32 addr, UNUSED u16 val)
{
   LOG("Unhandled word write %08X\n", (unsigned int)addr);
}

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

static void FASTCALL UnhandledMemoryWriteLong(USED_IF_DEBUG u32 addr, UNUSED u32 val)
{
   LOG("Unhandled long write %08X\n", (unsigned int)addr);
}

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

static u8 FASTCALL HighWramMemoryReadByte(u32 addr)
{
   return T2ReadByte(HighWram, addr & 0xFFFFF);
}

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

static u16 FASTCALL HighWramMemoryReadWord(u32 addr)
{
   return T2ReadWord(HighWram, addr & 0xFFFFF);
}

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

static u32 FASTCALL HighWramMemoryReadLong(u32 addr)
{
   return T2ReadLong(HighWram, addr & 0xFFFFF);
}

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

static void FASTCALL HighWramMemoryWriteByte(u32 addr, u8 val)
{
   T2WriteByte(HighWram, addr & 0xFFFFF, val);
}

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

static void FASTCALL HighWramMemoryWriteWord(u32 addr, u16 val)
{
   T2WriteWord(HighWram, addr & 0xFFFFF, val);
}

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

static void FASTCALL HighWramMemoryWriteLong(u32 addr, u32 val)
{
   T2WriteLong(HighWram, addr & 0xFFFFF, val);
}

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

static u8 FASTCALL LowWramMemoryReadByte(u32 addr)
{
   return T2ReadByte(LowWram, addr & 0xFFFFF);
}

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

static u16 FASTCALL LowWramMemoryReadWord(u32 addr)
{
   return T2ReadWord(LowWram, addr & 0xFFFFF);
}

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

static u32 FASTCALL LowWramMemoryReadLong(u32 addr)
{
   return T2ReadLong(LowWram, addr & 0xFFFFF);
}

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

static void FASTCALL LowWramMemoryWriteByte(u32 addr, u8 val)
{
   T2WriteByte(LowWram, addr & 0xFFFFF, val);
}

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

static void FASTCALL LowWramMemoryWriteWord(u32 addr, u16 val)
{
   T2WriteWord(LowWram, addr & 0xFFFFF, val);
}

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

static void FASTCALL LowWramMemoryWriteLong(u32 addr, u32 val)
{
   T2WriteLong(LowWram, addr & 0xFFFFF, val);
}

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

static u8 FASTCALL BiosRomMemoryReadByte(u32 addr)
{
   return T2ReadByte(BiosRom, addr & 0x7FFFF);
}

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

static u16 FASTCALL BiosRomMemoryReadWord(u32 addr)
{
   return T2ReadWord(BiosRom, addr & 0x7FFFF);
}

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

static u32 FASTCALL BiosRomMemoryReadLong(u32 addr)
{
   return T2ReadLong(BiosRom, addr & 0x7FFFF);
}

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

static void FASTCALL BiosRomMemoryWriteByte(UNUSED u32 addr, UNUSED u8 val)
{
   // read-only
}

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

static void FASTCALL BiosRomMemoryWriteWord(UNUSED u32 addr, UNUSED u16 val)
{
   // read-only
}

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

static void FASTCALL BiosRomMemoryWriteLong(UNUSED u32 addr, UNUSED u32 val)
{
   // read-only
}

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

static u8 FASTCALL BupRamMemoryReadByte(u32 addr)
{
#if 0
  // maped memory is better
  if (BupRam == NULL) {
    if ((addr&0x0FFFFFFF) >= tweak_backup_file_addr) {
      addr = (addr & 0x0FFFFFFF) - tweak_backup_file_addr;
      if (addr >= tweak_backup_file_size) {
        return 0;
      }
    }
    else {
      addr = (addr & 0xFFFF);
    }
    fseek(pbackup, addr, 0);
    return fgetc(pbackup);
  }
#endif
  if (yabsys.extend_backup && addr >= tweak_backup_file_addr) {

     //if( yabsys.emulatebios == 1 ) {
        addr = (addr&0x0FFFFFFF) - tweak_backup_file_addr;
        if (addr >= tweak_backup_file_size) {
           return 0;
        }
     //}else{
    //    addr = (addr&0x0000FFFF );
    //    if (addr >= tweak_backup_file_size) {
    //       return 0;
    //    }
     //}
  }
  else {
    addr = addr & 0x0000FFFF;
  }
  //printf("BupRamMemoryReadByte %08X\n",addr);
  return T1ReadByte(BupRam, addr);
}

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

static u16 FASTCALL BupRamMemoryReadWord(USED_IF_DEBUG u32 addr)
{
   LOG("bup\t: BackupRam read word - %08X\n", addr);
   return 0;
}

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

static u32 FASTCALL BupRamMemoryReadLong(USED_IF_DEBUG u32 addr)
{
   LOG("bup\t: BackupRam read long - %08X\n", addr);
   return 0;
}

//////////////////////////////////////////////////////////////////////////////
static void FASTCALL BupRamMemoryWriteByte(u32 addr, u8 val)
{
#if 0
  // maped memory is better
  if (BupRam == NULL) {
    if ((addr & 0x0FFFFFFF) >= tweak_backup_file_addr) {
      addr = ((addr & 0x0FFFFFFF) - tweak_backup_file_addr)|0x01;
    }else {
      addr = (addr & 0xFFFF) | 0x1;
    }
    fseek(pbackup, addr, 0);
    fputc(val, pbackup);
    fflush(pbackup);
  }
  else {
    T1WriteByte(BupRam, (addr & 0xFFFF) | 0x1, val);
    BupRamWritten = 1;  
  }
#endif
  if (yabsys.extend_backup && addr >= tweak_backup_file_addr ) {
     //if( yabsys.emulatebios == 1 ) {
        addr = (addr&0x0FFFFFFF) - tweak_backup_file_addr;
        if (addr >= tweak_backup_file_size) {
           return;
        }
     //}else{
     //   addr = (addr&0x0000FFFF );
     //   if (addr >= tweak_backup_file_size) {
     //      return;
     //   }
     //}
  }
  else {
    addr = addr & 0x0000FFFF;
  }
  //printf("BupRamMemoryWriteByte %08X\n",addr);
  T1WriteByte(BupRam, addr|0x1, val);
}

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

static void FASTCALL BupRamMemoryWriteWord(USED_IF_DEBUG u32 addr, UNUSED u16 val)
{
   LOG("bup\t: BackupRam write word - %08X\n", addr);
}

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

static void FASTCALL BupRamMemoryWriteLong(USED_IF_DEBUG u32 addr, UNUSED u32 val)
{
   LOG("bup\t: BackupRam write long - %08X\n", addr);
}

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

static void FillMemoryArea(unsigned short start, unsigned short end,
                           readbytefunc r8func, readwordfunc r16func,
                           readlongfunc r32func, writebytefunc w8func,
                           writewordfunc w16func, writelongfunc w32func)
{
   int i;

   for (i=start; i < (end+1); i++)
   {
      ReadByteList[i] = r8func;
      ReadWordList[i] = r16func;
      ReadLongList[i] = r32func;
      WriteByteList[i] = w8func;
      WriteWordList[i] = w16func;
      WriteLongList[i] = w32func;
   }
}

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

void MappedMemoryInit()
{
   // Initialize everyting to unhandled to begin with
   FillMemoryArea(0x000, 0xFFF, &UnhandledMemoryReadByte,
                                &UnhandledMemoryReadWord,
                                &UnhandledMemoryReadLong,
                                &UnhandledMemoryWriteByte,
                                &UnhandledMemoryWriteWord,
                                &UnhandledMemoryWriteLong);

   // Fill the rest
   FillMemoryArea(0x000, 0x00F, &BiosRomMemoryReadByte,
                                &BiosRomMemoryReadWord,
                                &BiosRomMemoryReadLong,
                                &BiosRomMemoryWriteByte,
                                &BiosRomMemoryWriteWord,
                                &BiosRomMemoryWriteLong);
   FillMemoryArea(0x010, 0x017, &SmpcReadByte,
                                &SmpcReadWord,
                                &SmpcReadLong,
                                &SmpcWriteByte,
                                &SmpcWriteWord,
                                &SmpcWriteLong);
   FillMemoryArea(0x018, 0x01F, &BupRamMemoryReadByte,
                                &BupRamMemoryReadWord,
                                &BupRamMemoryReadLong,
                                &BupRamMemoryWriteByte,
                                &BupRamMemoryWriteWord,
                                &BupRamMemoryWriteLong);
   FillMemoryArea(0x020, 0x02F, &LowWramMemoryReadByte,
                                &LowWramMemoryReadWord,
                                &LowWramMemoryReadLong,
                                &LowWramMemoryWriteByte,
                                &LowWramMemoryWriteWord,
                                &LowWramMemoryWriteLong);
   FillMemoryArea(0x100, 0x17F, &UnhandledMemoryReadByte,
                                &UnhandledMemoryReadWord,
                                &UnhandledMemoryReadLong,
                                &UnhandledMemoryWriteByte,
                                &SSH2InputCaptureWriteWord,
                                &UnhandledMemoryWriteLong);
   FillMemoryArea(0x180, 0x1FF, &UnhandledMemoryReadByte,
                                &UnhandledMemoryReadWord,
                                &UnhandledMemoryReadLong,
                                &UnhandledMemoryWriteByte,
                                &MSH2InputCaptureWriteWord,
                                &UnhandledMemoryWriteLong);
   FillMemoryArea(0x200, 0x3FF, CartridgeArea->Cs0ReadByte,
                                CartridgeArea->Cs0ReadWord,
                                CartridgeArea->Cs0ReadLong,
                                CartridgeArea->Cs0WriteByte,
                                CartridgeArea->Cs0WriteWord,
                                CartridgeArea->Cs0WriteLong);
   FillMemoryArea(0x400, 0x4FF, &Cs1ReadByte,
                                &Cs1ReadWord,
                                &Cs1ReadLong,
                                &Cs1WriteByte,
                                &Cs1WriteWord,
                                &Cs1WriteLong);
   FillMemoryArea(0x580, 0x58F, &Cs2ReadByte,
                                &Cs2ReadWord,
                                &Cs2ReadLong,
                                &Cs2WriteByte,
                                &Cs2WriteWord,
                                &Cs2WriteLong);
   FillMemoryArea(0x5A0, 0x5AF, &SoundRamReadByte,
                                &SoundRamReadWord,
                                &SoundRamReadLong,
                                &SoundRamWriteByte,
                                &SoundRamWriteWord,
                                &SoundRamWriteLong);
   FillMemoryArea(0x5B0, 0x5BF, &scsp_r_b,
                                &scsp_r_w,
                                &scsp_r_d,
                                &scsp_w_b,
                                &scsp_w_w,
                                &scsp_w_d);
   FillMemoryArea(0x5C0, 0x5C7, &Vdp1RamReadByte,
                                &Vdp1RamReadWord,
                                &Vdp1RamReadLong,
                                &Vdp1RamWriteByte,
                                &Vdp1RamWriteWord,
                                &Vdp1RamWriteLong);
   FillMemoryArea(0x5C8, 0x5CF, &Vdp1FrameBufferReadByte,
                                &Vdp1FrameBufferReadWord,
                                &Vdp1FrameBufferReadLong,
                                &Vdp1FrameBufferWriteByte,
                                &Vdp1FrameBufferWriteWord,
                                &Vdp1FrameBufferWriteLong);
   FillMemoryArea(0x5D0, 0x5D7, &Vdp1ReadByte,
                                &Vdp1ReadWord,
                                &Vdp1ReadLong,
                                &Vdp1WriteByte,
                                &Vdp1WriteWord,
                                &Vdp1WriteLong);
   FillMemoryArea(0x5E0, 0x5EF, &Vdp2RamReadByte,
                                &Vdp2RamReadWord,
                                &Vdp2RamReadLong,
                                &Vdp2RamWriteByte,
                                &Vdp2RamWriteWord,
                                &Vdp2RamWriteLong);
   FillMemoryArea(0x5F0, 0x5F7, &Vdp2ColorRamReadByte,
                                &Vdp2ColorRamReadWord,
                                &Vdp2ColorRamReadLong,
                                &Vdp2ColorRamWriteByte,
                                &Vdp2ColorRamWriteWord,
                                &Vdp2ColorRamWriteLong);
   FillMemoryArea(0x5F8, 0x5FB, &Vdp2ReadByte,
                                &Vdp2ReadWord,
                                &Vdp2ReadLong,
                                &Vdp2WriteByte,
                                &Vdp2WriteWord,
                                &Vdp2WriteLong);
   FillMemoryArea(0x5FE, 0x5FE, &ScuReadByte,
                                &ScuReadWord,
                                &ScuReadLong,
                                &ScuWriteByte,
                                &ScuWriteWord,
                                &ScuWriteLong);
   FillMemoryArea(0x600, 0x610, &HighWramMemoryReadByte,
                                &HighWramMemoryReadWord,
                                &HighWramMemoryReadLong,
                                &HighWramMemoryWriteByte,
                                &HighWramMemoryWriteWord,
                                &HighWramMemoryWriteLong);
   FillMemoryArea( ((tweak_backup_file_addr >> 16) & 0xFFF) , 0x7ff, &BupRamMemoryReadByte,
     &BupRamMemoryReadWord,
     &BupRamMemoryReadLong,
     &BupRamMemoryWriteByte,
     &BupRamMemoryWriteWord,
     &BupRamMemoryWriteLong);
}

#if 0
#define GET_MEM_CYCLE_W *cycle = 0;
#define GET_MEM_CYCLE_R *cycle = 0;
#else
INLINE int getVramCycle(u32 addr) {
  if (yabsys.LineCount >= yabsys.VBlankLineCount) { 
    return 16;
  }
  if ((addr & 0x000F0000) < 0x00040000) {
    return Vdp2External.cpu_cycle_a;
  }
  else {
    return Vdp2External.cpu_cycle_b;
  }
  return 16;
}

const int clock_shift = 1;


u32 getMemClock(u32 addr) {
  
  addr = addr & 0xDFFFFFFF;

  // CPU bus 1
  if (addr >= 0x06000000 && addr < 0x06100000) {
    return (16 >> clock_shift);
  }
  else if (addr >= 0x000000 && addr < 0x00300000) {
    return 22 >> clock_shift;
  }

  // A bus 
  else if (addr >= 0x02000000 && addr < 0x05800000) {
    return 80 >> clock_shift;
  }

  // B bus
  else if (addr >= 0x05A00000 && addr < 0x05E00000) {
    return 80 >> clock_shift;
  }
  else if (addr >= 0x05e00000 && addr < 0x05E80000) {
    if (yabsys.LineCount >= yabsys.VBlankLineCount) {
      return 80 >> clock_shift;
    }
    if ((addr & 0x000F0000) < 0x00040000) {
      return Vdp2External.cpu_cycle_a >> clock_shift;
    }
    else {
      return Vdp2External.cpu_cycle_b >> clock_shift;
    }
    return 80 >> clock_shift;
  }
  else if (addr >= 0x05f00000 && addr < 0x060000000) {
    return 16 >> clock_shift;
  }
  return 0;
}

// Write cost model (cycle-cost accuracy Phase 1).
//
// Historically WorkRAM writes through the emulated cache charged zero
// memory cycles on both CPU cores. The reference model (mednafen) charges
// a posted-write issue cost on SDRAM (+2, ss/sh7095.inc:2206) and the full
// bus cost on CS0 regions. This function returns the write cost for the
// regions covered so far; regions not listed keep their legacy behavior.
//
// Default ON; set YABA_SH2_WRITE_COST_MODEL=old to A/B the legacy
// (uncharged) behavior. Note: Techno Motor's song load can sporadically
// take ~2000 frames longer -- confirmed to be a pre-existing host-async
// race, not caused by this model (tools/sh2timing/README.md).
static int write_cost_model_new = -1;

int sh2_write_cost_new(void)
{
  if (write_cost_model_new < 0)
  {
    const char *e = getenv("YABA_SH2_WRITE_COST_MODEL");
    write_cost_model_new = !(e != NULL && strcmp(e, "old") == 0);
  }
  return write_cost_model_new;
}

// Per-region write costs. Defaults follow mednafen (SDRAM posted issue +2;
// WorkRAM-L same bucket as reads). Overridable for diagnosis/tuning via
// YABA_SH2_WRITE_COST_H / YABA_SH2_WRITE_COST_L (integers).
static int write_cost_wramh = -1;
static int write_cost_wraml = -1;

static int env_cost(const char *name, int defval)
{
  const char *e = getenv(name);
  return e != NULL ? atoi(e) : defval;
}

// Read cost model (cycle-cost accuracy Phase 1).
//
// The legacy table (getMemClock) charges one flat cost per access
// regardless of size and lumps BIOS/WorkRAM-L/backup into one bucket.
// The reference model (mednafen) charges per bus transaction: CS0 is a
// 16-bit bus (32-bit access = 2 transactions; WorkRAM-L +7, BIOS +8,
// backup +8 each, ss/ss.cpp:192-276) and WorkRAM-H is 32-bit SDRAM
// (+7 per read regardless of size, ss/sh7095.inc:2282).
// Regions not listed keep the legacy table.
// Set YABA_SH2_READ_COST_MODEL=old to A/B the legacy behavior.
static int read_cost_model_new = -1;

int sh2_read_cost_new(void)
{
  if (read_cost_model_new < 0)
  {
    const char *e = getenv("YABA_SH2_READ_COST_MODEL");
    read_cost_model_new = !(e != NULL && strcmp(e, "old") == 0);
  }
  return read_cost_model_new;
}

// IF/MA contention approximation (cycle-cost accuracy Phase 2).
//
// On real SH2 (and in mednafen's pipeline model, ss/sh7095.inc:2604,3156)
// a data access occupies the MA stage and every other instruction fetch
// has to share that slot, so back-to-back cached loads cost ~1.5 cycles
// on average while isolated loads cost 1. We approximate this with an
// alternating +1 charged only when the previous data access was recent.
// Set YABA_SH2_MA_MODEL=old to disable.
static int ma_model_new = -1;

int sh2_ma_model_new(void)
{
  if (ma_model_new < 0)
  {
    const char *e = getenv("YABA_SH2_MA_MODEL");
    ma_model_new = !(e != NULL && strcmp(e, "old") == 0);
  }
  return ma_model_new;
}

// Slave bus-access penalty (cycle-cost accuracy Phase 3).
//
// The two SH2s share the external bus and the slave is deprioritized.
// mednafen models this only in full cache-emulation mode (SlavePenalty
// templates, ss/sh7095.inc:2326-2370): reads +2..3 around the access,
// writes lose their posted-write overlap. Measured effective costs on
// the timing disc (tools/sh2timing, mednafen -ss.dbg_cem full vs data):
// uncached reads +4.7/access regardless of bus width (S16 vs S17 pin it
// to per-access, not per-transaction), writes +5.5, line refills +6.25.
// We charge flat +5 per slave bus access and +6 per slave line refill.
//
// Opt-in (YABA_SH2_SLAVE_BUS_MODEL=new), default OFF: mednafen only
// models this penalty in full cache-emulation mode, i.e. it is absent
// from its default per-game configuration, and enabling it on top of
// the SCU-DMA and DMAC models pushes Bio Hazard's CD streaming buffer
// into the (badly modeled) buffer-full path where playback never
// resumes -> title freeze. It also shifts DOA's load phases. Penalties
// tunable via YABA_SH2_SLAVE_RW_PENALTY / YABA_SH2_SLAVE_REFILL_PENALTY.
static int slave_bus_model_new = -1;
static int slave_rw_penalty = -1;
static int slave_refill_penalty_v = -1;

int sh2_slave_bus_model_new(void)
{
  if (slave_bus_model_new < 0)
  {
    const char *e = getenv("YABA_SH2_SLAVE_BUS_MODEL");
    slave_bus_model_new = (e != NULL && strcmp(e, "new") == 0);
    slave_rw_penalty = env_cost("YABA_SH2_SLAVE_RW_PENALTY", 5);
    slave_refill_penalty_v = env_cost("YABA_SH2_SLAVE_REFILL_PENALTY", 6);
  }
  return slave_bus_model_new;
}

u32 sh2_slave_bus_penalty(void)
{
  if (!sh2_slave_bus_model_new() || CurrentSH2 == NULL || !CurrentSH2->isslave)
    return 0;
  return (u32)slave_rw_penalty;
}

u32 sh2_slave_refill_penalty(void)
{
  if (!sh2_slave_bus_model_new() || CurrentSH2 == NULL || !CurrentSH2->isslave)
    return 0;
  return (u32)slave_refill_penalty_v;
}

// On-chip DMAC bus contention (cycle-cost accuracy Phase 3c).
//
// While a DMAC channel is running the CPU and the DMAC share the external
// bus. mednafen charges every CPU external bus access +19 while a channel
// runs (sh7095.inc DMA_PenaltyKludge) and leaves cache-resident code
// untouched. The legacy yabause approximation instead charges the CPU
// half of the accumulated DMA bus time unconditionally (penerly>>1),
// which slows down pure-ALU loops that never touch the bus (timing disc
// S20: 36.4 cyc/iter vs mednafen's 20.0).
//
// New model (default ON; YABA_SH2_DMAC_MODEL=old reverts): per-access
// charge only. +12 per CPU bus access (tuned on S21: 101.3 cyc/iter
// measured) while a channel of the executing CPU is active; the penerly
// charge and the DMAC transfer-speed hacks are disabled (see sh2core.c
// DMAProc; CD-block-sourced transfers keep the legacy speed there).
// Override the charge via YABA_SH2_DMAC_RW_PENALTY.
static int dmac_model_new_v = -1;
static int dmac_rw_penalty_v = -1;
static int dmac_transfer_active_v = 0;

int sh2_dmac_model_new(void)
{
  if (dmac_model_new_v < 0)
  {
    // Back to opt-in (2026-07-12): combined with the Phase 7 CD-block
    // timing model, the DMAC model gives Bio Hazard's title a ~20%%
    // freeze rate when START skips the intro (interaction race, 2/7
    // frozen with both on; 0/15+ with the DMAC model off). Enable with
    // YABA_SH2_DMAC_MODEL=new for timing-accuracy work (S20-S22).
    const char *e = getenv("YABA_SH2_DMAC_MODEL");
    dmac_model_new_v = (e != NULL && strcmp(e, "new") == 0);
    dmac_rw_penalty_v = env_cost("YABA_SH2_DMAC_RW_PENALTY", 12);
  }
  return dmac_model_new_v;
}

// Diagnostic knob (Dragon Force black-screen investigation): with the
// new DMAC model, YABA_SH2_DMAC_UCOST=legacy keeps the legacy transfer
// speed (MAX(read,write) unit cost + the 1.5x budget) while retaining
// the rest of the new model. Isolates "accurate transfer speed" as the
// trigger for timing-sensitive games.
static int dmac_ucost_legacy_v = -1;

int sh2_dmac_ucost_legacy(void)
{
  if (dmac_ucost_legacy_v < 0)
  {
    const char *e = getenv("YABA_SH2_DMAC_UCOST");
    dmac_ucost_legacy_v = (e != NULL && strcmp(e, "legacy") == 0);
  }
  return dmac_ucost_legacy_v;
}

// Suppress the contention charge for the DMAC's own transfer accesses
// (they go through the same MappedMemory*Nocache choke points).
void sh2_dmac_transfer_begin(void) { dmac_transfer_active_v = 1; }
void sh2_dmac_transfer_end(void)   { dmac_transfer_active_v = 0; }

u32 sh2_dmac_bus_penalty(void)
{
  SH2_struct *sh = CurrentSH2;
  if (!sh2_dmac_model_new() || sh == NULL || dmac_transfer_active_v)
    return 0;
  if ((sh->onchip.DMAOR & 7) != 1)
    return 0;
  if (((sh->onchip.CHCR0 & 3) == 1 && sh->onchip.TCR0 != 0) ||
      ((sh->onchip.CHCR1 & 3) == 1 && sh->onchip.TCR1 != 0))
    return (u32)dmac_rw_penalty_v;
  return 0;
}

u32 getMemReadClock(u32 addr, int size)
{
  u32 a;
  if (!sh2_read_cost_new())
    return getMemClock(addr);
  a = addr & 0xDFFFFFFF;
  if (a >= 0x06000000 && a < 0x06100000) {
    return 7;                              // SDRAM, 32-bit bus: any size
  }
  if (a < 0x00100000) {
    return size == 4 ? 16 : 8;             // BIOS ROM, 16-bit bus
  }
  if (a >= 0x00180000 && a < 0x00200000) {
    return size == 4 ? 16 : 8;             // Backup RAM, 16-bit bus
  }
  if (a >= 0x00200000 && a < 0x00300000) {
    return size == 4 ? 14 : 7;             // WorkRAM-L, 16-bit bus
  }
  if (a >= 0x05A00000 && a < 0x05C00000) {
    // B-bus SCSP: reads are always 32-bit, split into two 16-bit
    // transactions of +24 each (ss/scu.inc:975,1229-1237)
    return 48;
  }
  if (a >= 0x05C00000 && a < 0x05D80000) {
    // B-bus VDP1 VRAM/FB/registers: two 16-bit transactions of +14
    // each, any access size (ss/scu.inc:876)
    return 28;
  }
  if (a >= 0x05F00000 && a < 0x05FC0000) {
    // B-bus VDP2 CRAM/registers: +20 per 16-bit transaction, always
    // 32-bit (ss/scu.inc:927). VDP2 VRAM (0x05E00000) keeps the legacy
    // display-slot model in getMemClock (same 40 outside display).
    return 40;
  }
  if (a >= 0x05800000 && a < 0x05900000) {
    // A-bus CS2 (CD block): +8 per 16-bit half, reads always 32-bit
    // (ss/scu.inc:1088-1104). Legacy charged 0 here, making HIRQ
    // polling loops nearly free.
    return 16;
  }
  // A-bus CS0/CS1 reads stay on the legacy table: flat 40 matches the
  // BIOS-default SCU ASR0 timing ((5+15 wait) x two 16-bit halves,
  // ss/scu.inc:1030; timing disc S29). Carts that reprogram ASR0 for
  // faster access are not modeled.
  return getMemClock(addr);                // other regions: legacy table
}

u32 getMemWriteClock(u32 addr, int size)
{
  if (write_cost_wramh < 0)
  {
    write_cost_wramh = env_cost("YABA_SH2_WRITE_COST_H", 2);
    write_cost_wraml = env_cost("YABA_SH2_WRITE_COST_L", 22 >> clock_shift);
  }
  addr = addr & 0xDFFFFFFF;
  if (addr >= 0x06000000 && addr < 0x06100000) {
    return write_cost_wramh;     // SDRAM posted write: issue cost only
  }
  if (addr >= 0x00200000 && addr < 0x00300000) {
    return write_cost_wraml;     // WorkRAM-L
  }
  // B-bus writes are posted with a per-region finish time (mednafen
  // scu.inc BBusRW_DB: CPU-visible +2, finish time += 17/9/3 per 16-bit
  // half, += 13/1/1 for the second half of a 32-bit access). Sequential
  // writes stall on the finish time; the constants below reproduce the
  // sustained loop cost measured on the timing disc (S24/S26/S28).
  if (addr >= 0x05A00000 && addr < 0x05C00000) {
    return size == 4 ? 30 : 17;  // SCSP RAM / registers
  }
  if (addr >= 0x05C00000 && addr < 0x05D80000) {
    return size == 4 ? 10 : 9;   // VDP1 VRAM / FB / registers
  }
  if (addr >= 0x05E00000 && addr < 0x05FC0000) {
    return size == 4 ? 4 : 3;    // VDP2 VRAM / CRAM / registers
  }
  // A-bus writes are synchronous (no posted-write finish time). CS0/CS1
  // cost follows the BIOS-default SCU ASR0 ((5+wait+ext) per 16-bit
  // half, ss/scu.inc:1030; timing disc S30); CS2 (CD block) is a flat
  // 8 per half (ss/scu.inc:1101).
  if (addr >= 0x02000000 && addr < 0x05000000) {
    return size == 4 ? 38 : 19;  // A-bus CS0/CS1 (cartridge)
  }
  if (addr >= 0x05800000 && addr < 0x05900000) {
    return size == 4 ? 16 : 8;   // A-bus CS2 (CD block)
  }
  return 0;                      // other regions: legacy (uncharged here)
}

#endif

#if 0
inline u32 getMemCycle(u32 addr) {
  switch (addr & 0xFFF00000) {
  case 0x26000000: // High
    return 7;
    break;
  case 0x20200000: // Low
    return 7; 
    break;
  case 0x25A00000: // SOUND RAM
  case 0x25B00000: // SOUND REG
    return 30;
    break;
  case 0x25C00000: // VDP1 RAM
    return 7;
  case 0x25D00000: // VDP1 REG
    return 7;
    break;
  case 0x25E00000: // VDP2 RAM
    if (yabsys.LineCount >= yabsys.VBlankLineCount) {
      return 7;
    }
    else {
      return 80;
    }
    break;
  case 0x25F00000: // VDP2 REG
    return 2;
    break;
  case 0x06000000: // High
  case 0x00200000: // Low
    return 0;
    break;
  default:
    return 0;
    break;
  }
  return 1;
}
#endif

//////////////////////////////////////////////////////////////////////////////
#if CACHE_ENABLE
u8 FASTCALL MappedMemoryReadByte(u32 addr, u32 * cycle){
	return cache_memory_read_b(&CurrentSH2->onchip.cache, addr, cycle);
}
u8 FASTCALL MappedMemoryReadByteNocache(u32 addr, u32 * cycle)
#else
u8 FASTCALL MappedMemoryReadByte(u32 addr, u32 * cycle)
#endif
{
  if (cycle != NULL) { 
    *cycle = getMemReadClock(addr, 1) + sh2_slave_bus_penalty() + sh2_dmac_bus_penalty();
  }

   switch (addr >> 29)
   {
      case 0x0:
      case 0x1:
      case 0x4:
      {
         // Cache/Non-Cached
         u8 rtn = ReadByteList[(addr >> 16) & 0xFFF](addr);
         //if( (addr&0xF0000000) == 0x20000000 ){
         // LOG("[%s] %zu-byte read address=0x%08x value=0x%x\n", CurrentSH2->isslave ? "SH2-S" : "SH2-M", 1, addr, rtn);
         //}
         return rtn;

      }

      case 0x2:
      case 0x5:
      {
        // Purge Area
        return 0xFF;
      }


      //case 0x4:
      case 0x6:
         // Data Array
         return DataArrayReadByte(addr);
      case 0x7:
      {
         if (addr >= 0xFFFFFE00)
         {
            // Onchip modules
            addr &= 0x1FF;
            return OnchipReadByte(addr);
         }
         else if (addr >= 0xFFFF8000 && addr < 0xFFFFC000)
         {
            // ???
         }
         else
         {
            // Garbage data
         }
         break;
      }
      default:
      {
         return UnhandledMemoryReadByte(addr);
      }
   }

   return 0;
}


//////////////////////////////////////////////////////////////////////////////
#if CACHE_ENABLE
u16 MappedMemoryReadInst(u32 addr, u32 * cycle) {
  return cache_memory_read_w(&CurrentSH2->onchip.cache, addr, cycle, 1);
}
u16 FASTCALL MappedMemoryReadWord(u32 addr, u32 * cycle){
  u16 rtn = cache_memory_read_w(&CurrentSH2->onchip.cache, addr, cycle, 0);
#if 0
  if (addr == 0x04) {
    //LOG("[%s] 2-byte read address=0x%08x value=0x%x\n", CurrentSH2->isslave ? "SH2-S" : "SH2-M", addr, rtn);
    LOG("[%s] %d 2-byte read of 0x%08x from 0x%08x PC=%08X frame=%d:%d", CurrentSH2->isslave ? "SH2-S" : "SH2-M", CurrentSH2->cycles, rtn, addr, CurrentSH2->regs.PC, yabsys.frame_count, yabsys.LineCount);
    if (CurrentSH2->bp.BreakpointCallBack) {
      CurrentSH2->bp.inbreakpoint = 1;
      SH2DumpHistory(CurrentSH2);
      CurrentSH2->bp.BreakpointCallBack(CurrentSH2, 0, CurrentSH2->bp.BreakpointUserData);
      CurrentSH2->bp.inbreakpoint = 0;
    }
  }
#endif
  return rtn;
}
u16 FASTCALL MappedMemoryReadWordNocache(u32 addr, u32 * cycle)
#else
u16 MappedMemoryReadInst(u32 addr, u32 * cycle) {
  return MappedMemoryReadWord(addr,cycle);
}
u16 FASTCALL MappedMemoryReadWord(u32 addr, u32 * cycle)
#endif
{
  if (cycle != NULL) {
    *cycle = getMemReadClock(addr, 2) + sh2_slave_bus_penalty() + sh2_dmac_bus_penalty();
  }

   switch (addr >> 29)
   {
      case 0x0:
      case 0x1:
      case 0x4:
      {
         // Cache/Non-Cached
         u16 rtn = ReadWordList[(addr >> 16) & 0xFFF](addr);
         //if( (addr&0xF0000000) == 0x20000000 ){
         //  LOG("[%s] %zu-byte read address=0x%08x value=0x%x\n", CurrentSH2->isslave ? "SH2-S" : "SH2-M", 2, addr, rtn);
         //}
         return rtn;
      }

      case 0x2:
      case 0x5:
      {
        // Purge Area
        return 0xFFFF;
      }


      //case 0x4:
      case 0x6:
         // Data Array
         return DataArrayReadWord(addr);
      case 0x7:
      {
         if (addr >= 0xFFFFFE00)
         {
            // Onchip modules
            addr &= 0x1FF;
            return OnchipReadWord(addr);
         }
         else if (addr >= 0xFFFF8000 && addr < 0xFFFFC000)
         {
            // ???
         }
         else
         {
            // Garbage data
         }
         break;
      }
      default:
      {
         return UnhandledMemoryReadWord(addr);
      }
   }

   return 0;
}

//////////////////////////////////////////////////////////////////////////////
#if CACHE_ENABLE
u32 FASTCALL MappedMemoryReadLong(u32 addr, u32 * cycle){
  u32 rtn = cache_memory_read_l(&CurrentSH2->onchip.cache, addr, cycle);
#if 0
  if(CurrentSH2->regs.PC == 0x06060292 && rtn == 0x00) {
     LOG("[%s] 4-byte read address=0x%08x value=0x%x\n", CurrentSH2->isslave ? "SH2-S" : "SH2-M", addr,rtn);
     if (CurrentSH2->bp.BreakpointCallBack) {
       CurrentSH2->bp.inbreakpoint = 1;
       SH2DumpHistory(CurrentSH2);
       CurrentSH2->bp.BreakpointCallBack(CurrentSH2, 0, CurrentSH2->bp.BreakpointUserData);
       CurrentSH2->bp.inbreakpoint = 0;
     }
  }
#endif
  return rtn;
}
u32 FASTCALL MappedMemoryReadLongNocache(u32 addr, u32 * cycle)
#else
u32 FASTCALL MappedMemoryReadLong(u32 addr, u32 * cycle)
#endif
{

  if (cycle != NULL) {
    *cycle = getMemReadClock(addr, 4) + sh2_slave_bus_penalty() + sh2_dmac_bus_penalty();
  }

   switch (addr >> 29)
   {
      case 0x0:
      case 0x1:
      case 0x4:
      {
         // Cache/Non-Cached
         u32 rtn = ReadLongList[(addr >> 16) & 0xFFF](addr);
         //if( (addr&0xF0000000) == 0x20000000 ){
         //   LOG("[%s] %zu-byte read address=0x%08x value=0x%x\n", CurrentSH2->isslave ? "SH2-S" : "SH2-M", 4, addr, rtn);
         //}
         return rtn;
      }

      case 0x2:
      case 0x5:
      {
        // Purge Area
        return 0xFFFFFFFF;
      }

      case 0x3:
      {
         // Address Array
         return AddressArrayReadLong(addr);
      }
      //case 0x4:
      case 0x6:
         // Data Array
         return DataArrayReadLong(addr);
      case 0x7:
      {
         if (addr >= 0xFFFFFE00)
         {
            // Onchip modules
            addr &= 0x1FF;
            return OnchipReadLong(addr);
         }
         else if (addr >= 0xFFFF8000 && addr < 0xFFFFC000)
         {
            // ???
         }
         else
         {
            // Garbage data
         }
         break;
      }
      default:
      {
         return UnhandledMemoryReadLong(addr);
      }
   }
   return 0;
}

//////////////////////////////////////////////////////////////////////////////
#if CACHE_ENABLE
void FASTCALL MappedMemoryWriteByte(u32 addr, u8 val, u32 * cycle){
	cache_memory_write_b(&CurrentSH2->onchip.cache,addr,val,cycle);
}
// Temporary write-watch for the timing-model investigation
// (YABA_DMAC_DEBUG=1): reports writers of the Bio Hazard boot-wait flag
// long at 0x060FFCBC (the game polls bit2 of byte 0x060FFCBD).
static int ww_dbg_v = -1;
static int ww_dbg_count = 0;

static void ww_check(u32 addr, u32 val, int size)
{
  if (ww_dbg_v < 0)
    ww_dbg_v = (getenv("YABA_DMAC_DEBUG") != NULL);
  if (!ww_dbg_v || ww_dbg_count > 300)
    return;
  if (((addr & 0x0FFFFFFF) & ~3u) == 0x060FFCBC)
  {
    ww_dbg_count++;
    printf("[WW] %c sz%d %08X <= %08X pc=%08X\n",
           (CurrentSH2 != NULL && CurrentSH2->isslave) ? 'S' : 'M', size,
           addr, val, CurrentSH2 != NULL ? SH2Core->GetPC(CurrentSH2) : 0);
    fflush(stdout);
  }
}

void FASTCALL MappedMemoryWriteByteNocache(u32 addr, u8 val, u32 * cycle)
#else
void FASTCALL MappedMemoryWriteByte(u32 addr, u8 val, u32 * cycle)
#endif
{
  ww_check(addr, val, 1);
#if 0
  if ((addr & 0x0FFFFFF0) == 0x060EA410) {
    LOG("[%s] %d Write 1-byte write of 0x%08x to 0x%08x PC=%08X frame=%d:%d", CurrentSH2->isslave ? "SH2-S" : "SH2-M", CurrentSH2->cycles, val, addr, CurrentSH2->regs.PC, yabsys.frame_count, yabsys.LineCount);
    //if (slogp != NULL){
    //   fprintf(slogp, "%08X: CPU write 0x%08X = %d(0x%08X)\n", CurrentSH2->regs.PC, addr, val,val );
    //}
  }
#endif


  if (cycle != NULL) {
    *cycle = getMemClock(addr);
  }

  switch (addr >> 29)
   {
      case 0x0:
      case 0x1:
      case 0x4:
      {
         // Cache/Non-Cached
         WriteByteList[(addr >> 16) & 0xFFF](addr, val);
         return;
      }

      case 0x2:
      case 0x5:
      {
         // Purge Area
         return;
      }

      //case 0x4:
      case 0x6:
         // Data Array
         DataArrayWriteByte(addr, val);
         return;
      case 0x7:
      {
         if (addr >= 0xFFFFFE00)
         {
            // Onchip modules
            addr &= 0x1FF;
            OnchipWriteByte(addr, val);
            return; 
         }
         else if (addr >= 0xFFFF8000 && addr < 0xFFFFC000)
         {
            // ???
         }
         else
         {
            // Garbage data
         }
         return;
      }
      default:
      {
         UnhandledMemoryWriteByte(addr, val);
         return;
      }
   }
}

//////////////////////////////////////////////////////////////////////////////
#if CACHE_ENABLE
void FASTCALL MappedMemoryWriteWord(u32 addr, u16 val, u32 * cycle){
	cache_memory_write_w(&CurrentSH2->onchip.cache, addr, val, cycle);
}
void FASTCALL MappedMemoryWriteWordNocache(u32 addr, u16 val, u32 * cycle)
#else
void FASTCALL MappedMemoryWriteWord(u32 addr, u16 val, u32 * cycle )
#endif
{
  ww_check(addr, val, 2);
  if (cycle != NULL) {
    *cycle = getMemClock(addr);
  }

   switch (addr >> 29)
   {
      case 0x0:
      case 0x1:
      case 0x4:
      {
         // Cache/Non-Cached
         WriteWordList[(addr >> 16) & 0xFFF](addr, val);
         return;
      }

      case 0x2:
      case 0x5:
      {
        // Purge Area
        return;
      }

      //case 0x4:
      case 0x6:
         // Data Array
         DataArrayWriteWord(addr, val);
         return;
      case 0x7:
      {
         if (addr >= 0xFFFFFE00)
         {
            // Onchip modules
            addr &= 0x1FF;
            OnchipWriteWord(addr, val);
            return;
         }
         else if (addr >= 0xFFFF8000 && addr < 0xFFFFC000)
         {
            // ???
         }
         else
         {
            // Garbage data
         }
         return;
      }
      default:
      {
         UnhandledMemoryWriteWord(addr, val);
         return;
      }
   }
}

#if 0  
extern FILE * slogp;
#endif

//////////////////////////////////////////////////////////////////////////////
#if CACHE_ENABLE
void FASTCALL MappedMemoryWriteLong(u32 addr, u32 val , u32 * cycle ){

#if 0
  if ((addr & 0x0FFFFFFF) == 0x060F084C && val == 0x00 ) {
    LOG("BBB [%s] %d Write 4-byte write of 0x%08x to 0x%08x PC=%08X frame=%d:%d", CurrentSH2->isslave ? "SH2-S" : "SH2-M", CurrentSH2->cycles, val, addr, CurrentSH2->regs.PC, yabsys.frame_count, yabsys.LineCount);
  }
  if ((addr & 0x0FFFFFFF) == 0x06001F40) {
    LOG("[%s] %d Write 4-byte write of 0x%08x to 0x%08x PC=%08X frame=%d:%d", CurrentSH2->isslave ? "SH2-S" : "SH2-M", CurrentSH2->cycles, val, addr, CurrentSH2->regs.PC, yabsys.frame_count, yabsys.LineCount);
    if (val == 0x0BEFFFFE) {
      if (CurrentSH2->bp.BreakpointCallBack) {
        CurrentSH2->bp.inbreakpoint = 1;
        SH2DumpHistory(CurrentSH2);
        CurrentSH2->bp.BreakpointCallBack(CurrentSH2, 0, CurrentSH2->bp.BreakpointUserData);
        CurrentSH2->bp.inbreakpoint = 0;
      }
    }
  }
#endif
	cache_memory_write_l(&CurrentSH2->onchip.cache, addr, val, cycle);
}
void FASTCALL MappedMemoryWriteLongNocache(u32 addr, u32 val , u32 * cycle)
#else
void FASTCALL MappedMemoryWriteLong(u32 addr, u32 val, u32 * cycle )
#endif
{
  ww_check(addr, val, 4);

   if (cycle != NULL) {
     *cycle = getMemClock(addr);
   }

   switch (addr >> 29)
   {
      case 0x0:
      case 0x1:
      case 0x4:
      {
         // Cache/Non-Cached
         WriteLongList[(addr >> 16) & 0xFFF](addr, val);
         return;
      }

      case 0x2:
      case 0x5:
      {
        // Purge Area
        return;
      }

      case 0x3:
      {
         if (cycle != NULL) { *cycle = 2; }
         
         // Address Array
         AddressArrayWriteLong(addr, val);
         return;
      }
      //case 0x4:
      case 0x6:
         // Data Array
         DataArrayWriteLong(addr, val);
         return;
      case 0x7:
      {
         if (addr >= 0xFFFFFE00)
         {
            // Onchip modules
            addr &= 0x1FF;
            OnchipWriteLong(addr, val);
            return;
         }
         else if (addr >= 0xFFFF8000 && addr < 0xFFFFC000)
         {
            // ???
         }
         else
         {
            // Garbage data
         }
         return;
      }
      default:
      {
         UnhandledMemoryWriteLong(addr, val);
         return;
      }
   }
}

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

int MappedMemoryLoad(const char *filename, u32 addr )
{
   FILE *fp;
   long filesize;
   u8 *buffer;
   u32 i;
   size_t num_read = 0;

   if (!filename)
      return -1;

   if ((fp = fopen(filename, "rb")) == NULL)
      return -1;

   // Calculate file size
   fseek(fp, 0, SEEK_END);
   filesize = ftell(fp);

   if (filesize <= 0)
   {
      YabSetError(YAB_ERR_FILEREAD, filename);
      fclose(fp);
      return -1;//error
   }

   fseek(fp, 0, SEEK_SET);

   if ((buffer = (u8 *)malloc(filesize)) == NULL)
   {
      fclose(fp);
      return -2;
   }

   num_read = fread((void *)buffer, 1, filesize, fp);
   fclose(fp);

   for (i = 0; i < filesize; i++)
      MappedMemoryWriteByte(addr+i, buffer[i], NULL);

   free(buffer);

   return 0;
}

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

int MappedMemorySave(const char *filename, u32 addr, u32 size)
{
   FILE *fp;
   u8 *buffer;
   u32 i;

   if (!filename)
      return -1;

   if ((fp = fopen(filename, "wb")) == NULL)
      return -1;

   if ((buffer = (u8 *)malloc(size)) == NULL)
   {
      fclose(fp);
      return -2;
   }

   for (i = 0; i < size; i++)
      buffer[i] = MappedMemoryReadByte(addr+i, NULL);

   fwrite((void *)buffer, 1, size, fp);
   fclose(fp);
   free(buffer);

   return 0;
}

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

int MappedMemoryLoadExec(const char *filename, u32 pc)
{
   char *p;
   size_t i;

   if ((p = strrchr(filename, '.')))
   {
      p = strdup(p);
      for (i = 0; i < strlen(p); i++)
         p[i] = toupper(p[i]);
      if (strcmp(p, ".COF") == 0 || strcmp(p, ".COFF") == 0)
      {
         int rtn = MappedMemoryLoadCoff(filename);
         free(p);
         return rtn;
      }
      else if(strcmp(p, ".ELF") == 0)
      {
         int rtn = MappedMemoryLoadElf(filename);
         free(p);
         return rtn;
      }

      free(p);
   }

   YabauseResetNoLoad();

   // Setup the vector table area, etc.
   YabauseSpeedySetup();

   MappedMemoryLoad(filename, pc);
   SH2GetRegisters(MSH2, &MSH2->regs);
   MSH2->regs.PC = pc;
   SH2SetRegisters(MSH2, &MSH2->regs);

   return 0;
}

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

int LoadBios(const char *filename)
{
   return T123Load(BiosRom, 0x80000, 2, filename);
}

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

int LoadBackupRam(const char *filename)
{
   return T123Load(BupRam, 0x10000, 1, filename);
}

static u8 header[32] = {
  0xFF, 'B', 0xFF, 'a', 0xFF, 'c', 0xFF, 'k',
  0xFF, 'U', 0xFF, 'p', 0xFF, 'R', 0xFF, 'a',
  0xFF, 'm', 0xFF, ' ', 0xFF, 'F', 0xFF, 'o',
  0xFF, 'r', 0xFF, 'm', 0xFF, 'a', 0xFF, 't'
};

int CheckBackupFile(FILE *fp) {
  int i, i2;
  u32 i3;

  // Fill in header
  for (i2 = 0; i2 < 4; i2++) {
    for (i = 0; i < 32; i++) {
      u8 val = fgetc(fp);
      if ( val != header[i]) {
        return -1;
      }
    }
  }
  return 0;
}

int ExtendBackupFile(FILE *fp, u32 size ) {

  fseek(fp, 0, SEEK_END);
  u32 acsize = ftell(fp);
  if (acsize < size) {
    // Clear the rest
    u32 i;
    for ( i = (acsize&0xFFFFFFFE) ; i < size; i += 2)
    {
      fputc(0xFF, fp);
      fputc(0x00, fp);
    }
    fflush(fp);
  }
  fseek(fp, 0, SEEK_SET);

  return 0;
}


//////////////////////////////////////////////////////////////////////////////
void FormatBackupRamFile(FILE *fp, u32 size) {

  int i, i2;
  u32 i3;

  fseek(fp, 0, SEEK_SET);

  // Fill in header
  for (i2 = 0; i2 < 4; i2++)
    for (i = 0; i < 32; i++)
      fputc(header[i],fp);

  // Clear the rest
  for (i3 = 0x80; i3 < size; i3 += 2)
  {
    fputc(0xFF,fp);
    fputc(0x00,fp);
  }
  fflush(fp);

  // Truncate any excess data beyond the expected size
#if defined(_WIN32) || defined(_WINDOWS)
  _chsize(_fileno(fp), size);
#else
  ftruncate(fileno(fp), size);
#endif
}

void FormatBackupRam(void *mem, u32 size)
{
   int i, i2;
   u32 i3;

   // Fill in header
   for(i2 = 0; i2 < 4; i2++)
      for(i = 0; i < 32; i++)
         T1WriteByte(mem, (i2 * 32) + i, header[i]);

   // Clear the rest
   for(i3 = 0x80; i3 < size; i3+=2)
   {
      T1WriteByte(mem, i3, 0xFF);
      T1WriteByte(mem, i3+1, 0x00);
   }
}

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

int YabSaveStateBuffer(void ** buffer, size_t * size)
{
   FILE * fp;
   int status;
   size_t num_read = 0;

   if (buffer != NULL) *buffer = NULL;
   *size = 0;

   fp = tmpfile();

   ScspLockThread();
   status = YabSaveStateStream(fp);
   ScspUnLockThread();

   if (status != 0)
   {
      fclose(fp);
      return status;
   }

   fseek(fp, 0, SEEK_END);
   *size = ftell(fp);
   fseek(fp, 0, SEEK_SET);

   if (buffer != NULL)
   {
      *buffer = malloc(*size);
      num_read = fread(*buffer, 1, *size, fp);
   }

   fclose(fp);
   return 0;
}

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

int YabSaveState(const char *filename)
{
   FILE *fp;
   int status = 0;

   //use a second set of savestates for movies
   filename = MakeMovieStateName(filename);
   if (!filename)
      return -1;

   if ((fp = fopen_utf8(filename, "wb")) == NULL)
      return -1;
   ScspLockThread();
   status = YabSaveStateStream(fp);
   ScspUnLockThread();
   fclose(fp);

   return status;
}


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

// FIXME: Here's a (possibly incomplete) list of data that should be added
// to the next version of the save state file:
//    yabsys.DecilineStop (new format)
//    yabsys.SH2CycleFrac (new field)
//    yabsys.DecilineUSed (new field)
//    yabsys.UsecFrac (new field)
//    [scsp2.c] It would be nice to redo the format entirely because so
//              many fields have changed format/size from the old scsp.c
//    [scsp2.c] scsp_clock, scsp_clock_frac, ScspState.sample_timer (timing)
//    [scsp2.c] cdda_buf, cdda_next_in, cdda_next_out (CDDA buffer)
//    [sh2core.c] frc.div changed to frc.shift
//    [sh2core.c] wdt probably needs to be written as well

int YabSaveStateStream(FILE *fp)
{
   u32 i;
   int offset;
   IOCheck_struct check;
   u8 *buf;
   int totalsize;
   int outputwidth;
   int outputheight;
   int movieposition;
   int temp;
   u32 temp32;

   check.done = 0;
   check.size = 0;

   // Write signature
   fprintf(fp, "YSS");

   // Write endianness byte
#ifdef WORDS_BIGENDIAN
   fputc(0x00, fp);
#else
   fputc(0x01, fp);
#endif

   // Write version - version 3 adds RetroAchievements support
   i = 3;
   ywrite(&check, (void *)&i, sizeof(i), 1, fp);

   // Skip the next 4 bytes for now
   i = 0;
   ywrite(&check, (void *)&i, sizeof(i), 1, fp);

   //write frame number
   ywrite(&check, (void *)&framecounter, 4, 1, fp);

   //this will be updated with the movie position later
   ywrite(&check, (void *)&framecounter, 4, 1, fp);

   // Go through each area and write each state
   i += CartSaveState(fp);
   i += Cs2SaveState(fp);
   i += SH2SaveState(MSH2, fp);
   i += SH2SaveState(SSH2, fp);
   i += SoundSaveState(fp);
   i += ScuSaveState(fp);
   i += SmpcSaveState(fp);
   i += Vdp1SaveState(fp);
   i += Vdp2SaveState(fp);

   // Save RetroAchievements state
   {
      size_t ra_size = YabauseRA_GetProgressSize();
      // Always write RACH header for version 3+ to maintain consistent format
      int ra_offset = StateWriteHeader(fp, "RACH", 1);
      
      // Write size first (0 if no data)
      ywrite(&check, (void *)&ra_size, sizeof(ra_size), 1, fp);
      
      // Only write buffer if there's data
      if (ra_size > 0) {
         // Allocate buffer and serialize progress
         uint8_t* ra_buffer = (uint8_t*)malloc(ra_size);
         if (ra_buffer) {
            if (YabauseRA_SerializeProgress(ra_buffer, ra_size)) {
               ywrite(&check, (void *)ra_buffer, ra_size, 1, fp);
            }
            free(ra_buffer);
         }
      }
      
      i += StateFinishHeader(fp, ra_offset);
   }

   offset = StateWriteHeader(fp, "OTHR", 1);

   // Other data
   ywrite(&check, (void *)BupRam, 0x10000, 1, fp); // do we really want to save this?
   ywrite(&check, (void *)HighWram, 0x100000, 1, fp);
   ywrite(&check, (void *)LowWram, 0x100000, 1, fp);

   ywrite(&check, (void *)&yabsys.DecilineCount, sizeof(int), 1, fp);
   ywrite(&check, (void *)&yabsys.LineCount, sizeof(int), 1, fp);
   ywrite(&check, (void *)&yabsys.VBlankLineCount, sizeof(int), 1, fp);
   ywrite(&check, (void *)&yabsys.MaxLineCount, sizeof(int), 1, fp);
   temp = yabsys.DecilineStop >> YABSYS_TIMING_BITS;
   ywrite(&check, (void *)&temp, sizeof(int), 1, fp);
   temp = (yabsys.CurSH2FreqType == CLKTYPE_26MHZ) ? 268 : 286;
   ywrite(&check, (void *)&temp, sizeof(int), 1, fp);
   temp32 = (yabsys.UsecFrac * temp / 10) >> YABSYS_TIMING_BITS;
   ywrite(&check, (void *)&temp32, sizeof(u32), 1, fp);
   ywrite(&check, (void *)&yabsys.CurSH2FreqType, sizeof(int), 1, fp);
   ywrite(&check, (void *)&yabsys.IsPal, sizeof(int), 1, fp);

   VIDCore->GetGlSize(&outputwidth, &outputheight);

   totalsize=outputwidth * outputheight * sizeof(u32);

   if ((buf = (u8 *)malloc(totalsize)) == NULL)
   {
      return -2;
   }

   //YuiSwapBuffers();
   #ifdef USE_OPENGL
   glPixelZoom(1,1);
   glReadBuffer(GL_BACK);
   glReadPixels(0, 0, outputwidth, outputheight, GL_RGBA, GL_UNSIGNED_BYTE, buf);
   #else
   //memcpy(buf, dispbuffer, totalsize);
   #endif
   //YuiSwapBuffers();

   ywrite(&check, (void *)&outputwidth, sizeof(outputwidth), 1, fp);
   ywrite(&check, (void *)&outputheight, sizeof(outputheight), 1, fp);

   ywrite(&check, (void *)buf, totalsize, 1, fp);

   movieposition=ftell(fp);
   //write the movie to the end of the savestate
   SaveMovieInState(fp, check);

   i += StateFinishHeader(fp, offset);

   // Go back and update size
   fseek(fp, 8, SEEK_SET);
   ywrite(&check, (void *)&i, sizeof(i), 1, fp);
   fseek(fp, 16, SEEK_SET);
   ywrite(&check, (void *)&movieposition, sizeof(movieposition), 1, fp);

   free(buf);

   OSDPushMessage(OSDMSG_STATUS, 150, "STATE SAVED");
   return 0;
}

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

int YabLoadStateBuffer(const void * buffer, size_t size)
{
   FILE * fp;
   int status;

   fp = tmpfile();
   fwrite(buffer, 1, size, fp);

   fseek(fp, 0, SEEK_SET);

   ScspLockThread();
   status = YabLoadStateStream(fp);
   ScspUnLockThread();

   fclose(fp);

   return status;
}

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

int YabLoadState(const char *filename)
{
   FILE *fp;
   int status;

   filename = MakeMovieStateName(filename);
   if (!filename)
      return -1;

   if ((fp = fopen_utf8(filename, "rb")) == NULL)
      return -1;

   ScspLockThread();
   status = YabLoadStateStream(fp);
   ScspUnLockThread();

   fclose(fp);

   return status;
}

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

int YabLoadStateStream(FILE *fp)
{
   char id[3];
   u8 endian;
   int headerversion, version, size, chunksize, headersize;
   IOCheck_struct check;
   u8* buf;
   int totalsize;
   int outputwidth;
   int outputheight;
   int curroutputwidth;
   int curroutputheight;
   int movieposition;
   int temp;
   u32 temp32;
	int test_endian;

  yabsys.frame_count = 0;

   headersize = 0xC;
   check.done = 0;
   check.size = 0;


   // Read signature
   yread(&check, (void *)id, 1, 3, fp);

   if (strncmp(id, "YSS", 3) != 0)
   {
      return -2;
   }

   // Read header
   yread(&check, (void *)&endian, 1, 1, fp);
   yread(&check, (void *)&headerversion, 4, 1, fp);
   yread(&check, (void *)&size, 4, 1, fp);
   switch(headerversion)
   {
      case 1:
         /* This is the "original" version of the format */
         break;
      case 2:
         /* version 2 adds video recording */
         yread(&check, (void *)&framecounter, 4, 1, fp);
		 movieposition=ftell(fp);
		 yread(&check, (void *)&movieposition, 4, 1, fp);
         headersize = 0x14;
         break;
      case 3:
         /* version 3 adds RetroAchievements support (same header format as version 2) */
         yread(&check, (void *)&framecounter, 4, 1, fp);
		 movieposition=ftell(fp);
		 yread(&check, (void *)&movieposition, 4, 1, fp);
         headersize = 0x14;
         break;
      default:
         /* we're trying to open a save state using a future version
          * of the YSS format, that won't work, sorry :) */
         return -3;
         break;
   }

#ifdef WORDS_BIGENDIAN
   test_endian = endian == 1;
#else
   test_endian = endian == 0;
#endif
   if (test_endian)
   {
      // should setup reading so it's byte-swapped
      YabSetError(YAB_ERR_OTHER, (void *)"Load State byteswapping not supported");
      return -3;
   }

   // Make sure size variable matches actual size minus header
   fseek(fp, 0, SEEK_END);

   if (size != (ftell(fp) - headersize))
   {
      return -2;
   }
   fseek(fp, headersize, SEEK_SET);

   // Verify version here
 
      
   if (StateCheckRetrieveHeader(fp, "CART", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   CartLoadState(fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "CS2 ", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   Cs2LoadState(fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "MSH2", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   SH2LoadState(MSH2, fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "SSH2", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   SH2LoadState(SSH2, fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "SCSP", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   SoundLoadState(fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "SCU ", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   ScuLoadState(fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "SMPC", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   SmpcLoadState(fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "VDP1", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   Vdp1LoadState(fp, version, chunksize);

   if (StateCheckRetrieveHeader(fp, "VDP2", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   Vdp2LoadState(fp, version, chunksize);

   // Load RetroAchievements state (only available in version 3+)
   if (headerversion >= 3) {
      long pos_before_rach = ftell(fp);  // Save position before trying to read RACH
      
      if (StateCheckRetrieveHeader(fp, "RACH", &version, &chunksize) == 0)
      {
         size_t ra_size;
         yread(&check, (void *)&ra_size, sizeof(ra_size), 1, fp);
         
         if (ra_size > 0 && ra_size <= chunksize - sizeof(ra_size)) {
            uint8_t* ra_buffer = (uint8_t*)malloc(ra_size);
            if (ra_buffer) {
               yread(&check, (void *)ra_buffer, ra_size, 1, fp);
               YabauseRA_DeserializeProgress(ra_buffer, ra_size);
               free(ra_buffer);
            } else {
               // Skip the data if we can't allocate memory
               fseek(fp, ra_size, SEEK_CUR);
            }
         } else {
            // Skip remaining chunk data if size is invalid
            fseek(fp, chunksize - sizeof(ra_size), SEEK_CUR);
         }
      } else {
         // No RACH header found - rewind to try OTHR instead (for compatibility with old saves)
         fseek(fp, pos_before_rach, SEEK_SET);
         // Reset RetroAchievements progress
         YabauseRA_DeserializeProgress(NULL, 0);
      }
   } else {
      // Older save state versions don't have RetroAchievements data - reset progress
      YabauseRA_DeserializeProgress(NULL, 0);
   }

   if (StateCheckRetrieveHeader(fp, "OTHR", &version, &chunksize) != 0)
   {
      // Revert back to old state here
      return -3;
   }
   // Other data
   //yread(&check, (void *)BupRam, 0x10000, 1, fp);
   fseek(fp, 0x10000, SEEK_CUR ); // skip this data
   yread(&check, (void *)HighWram, 0x100000, 1, fp);
   yread(&check, (void *)LowWram, 0x100000, 1, fp);

   yread(&check, (void *)&yabsys.DecilineCount, sizeof(int), 1, fp);
   yread(&check, (void *)&yabsys.LineCount, sizeof(int), 1, fp);
   yread(&check, (void *)&yabsys.VBlankLineCount, sizeof(int), 1, fp);
   yread(&check, (void *)&yabsys.MaxLineCount, sizeof(int), 1, fp);
   yread(&check, (void *)&temp, sizeof(int), 1, fp);
   yread(&check, (void *)&temp, sizeof(int), 1, fp);
   yread(&check, (void *)&temp32, sizeof(u32), 1, fp);
   yread(&check, (void *)&yabsys.CurSH2FreqType, sizeof(int), 1, fp);
   yread(&check, (void *)&yabsys.IsPal, sizeof(int), 1, fp);
   YabauseChangeTiming(yabsys.CurSH2FreqType);
   yabsys.UsecFrac = (temp32 << YABSYS_TIMING_BITS) * temp / 10;

   if (headerversion > 1) {

   yread(&check, (void *)&outputwidth, sizeof(outputwidth), 1, fp);
   yread(&check, (void *)&outputheight, sizeof(outputheight), 1, fp);

   totalsize=outputwidth * outputheight * sizeof(u32);

   if ((buf = (u8 *)malloc(totalsize)) == NULL)
   {
      return -2;
   }

   yread(&check, (void *)buf, totalsize, 1, fp);

   //YuiSwapBuffers();

   #ifdef USE_OPENGL
   if(VIDCore->id == VIDCORE_SOFT)
     glRasterPos2i(0, outputheight);
   if(VIDCore->id == VIDCORE_OGL)
	 glRasterPos2i(0, outputheight/2);
   #endif

   VIDCore->GetGlSize(&curroutputwidth, &curroutputheight);
   #ifdef USE_OPENGL
   glPixelZoom((float)curroutputwidth / (float)outputwidth, ((float)curroutputheight / (float)outputheight));
   glDrawPixels(outputwidth, outputheight, GL_RGBA, GL_UNSIGNED_BYTE, buf);
   #endif
   //YuiSwapBuffers();
   free(buf);

   fseek(fp, movieposition, SEEK_SET);
   MovieReadState(fp);
   }

   ScspUnMuteAudio(SCSP_MUTE_SYSTEM);

   OSDPushMessage(OSDMSG_STATUS, 150, "STATE LOADED");

   return 0;
}

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

int YabSaveStateSlot(const char *dirpath, u8 slot)
{
  int rtn;
   char filename[512];

   if (cdip == NULL)
      return -1;

#ifdef WIN32
   sprintf(filename, "%s\\%s_%03d.yss", dirpath, cdip->itemnum, slot);
#else
   sprintf(filename, "%s/%s_%03d.yss", dirpath, cdip->itemnum, slot);
#endif
   rtn = YabSaveState(filename);
   return rtn;
}

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

int YabLoadStateSlot(const char *dirpath, u8 slot)
{
   int rtn;
   char filename[512];

   if (cdip == NULL)
      return -1;

#ifdef WIN32
   sprintf(filename, "%s\\%s_%03d.yss", dirpath, cdip->itemnum, slot);
#else
   sprintf(filename, "%s/%s_%03d.yss", dirpath, cdip->itemnum, slot);
#endif
   rtn = YabLoadState(filename);
   return rtn;
}

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

static int MappedMemoryAddMatch(u32 addr, u32 val, int searchtype, result_struct *result, u32 *numresults)
{
   result[numresults[0]].addr = addr;
   result[numresults[0]].val = val;
   numresults[0]++;
   return 0;
}

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

static INLINE int SearchIncrementAndCheckBounds(result_struct *prevresults,
                                                u32 *maxresults,
                                                u32 numresults, u32 *i,
                                                u32 inc, u32 *newaddr,
                                                u32 endaddr)
{
   if (prevresults)
   {
      if (i[0] >= maxresults[0])
      {
         maxresults[0] = numresults;
         return 1;
      }
      newaddr[0] = prevresults[i[0]].addr;
      i[0]++;
   }
   else
   {
      newaddr[0] = inc;

      if (newaddr[0] > endaddr || numresults >= maxresults[0])
      {
         maxresults[0] = numresults;
         return 1;
      }
   }

   return 0;
}

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

static int SearchString(u32 startaddr, u32 endaddr, int searchtype,
                        const char *searchstr, result_struct *results,
                        u32 *maxresults)
{
   u8 *buf=NULL;
   u32 *buf32=NULL;
   u32 buflen=0;
   u32 counter;
   u32 addr;
   u32 numresults=0;

   buflen=(u32)strlen(searchstr);

   if ((buf32=(u32 *)malloc(buflen*sizeof(u32))) == NULL)
      return 0;

   buf = (u8 *)buf32;

   // Copy string to buffer
   switch (searchtype & 0x70)
   {
      case SEARCHSTRING:
         strcpy((char *)buf, searchstr);
         break;
      case SEARCHREL8BIT:
      case SEARCHREL16BIT:
      {
         char *text;
         char *searchtext=strdup(searchstr);

         // Calculate buffer length and read values into table
         buflen = 0;
         for (text=strtok((char *)searchtext, " ,"); text != NULL; text=strtok(NULL, " ,"))
         {            
            buf32[buflen] = strtoul(text, NULL, 0);
            buflen++;
         }
         free(searchtext);

         break;
      }
   }    

   addr = startaddr;
   counter = 0;

   for (;;)
   {
      // Fetch byte/word/etc.
      switch (searchtype & 0x70)
      {
         case SEARCHSTRING:
         {
            u8 val = MappedMemoryReadByte(addr, NULL);
            addr++;

            if (val == buf[counter])
            {
               counter++;
               if (counter == buflen)
                  MappedMemoryAddMatch(addr-buflen, val, searchtype, results, &numresults);
            }
            else
               counter = 0;
            break;
         }
         case SEARCHREL8BIT:
         {
            int diff;
            u32 j;
            u8 val2;
            u8 val = MappedMemoryReadByte(addr, NULL);

            for (j = 1; j < buflen; j++)
            {
               // grab the next value
               val2 = MappedMemoryReadByte(addr+j, NULL);

               // figure out the diff
               diff = (int)val2 - (int)val;

               // see if there's a match             
               if (((int)buf32[j] - (int)buf32[j-1]) != diff)
                  break;

               if (j == (buflen - 1))
                  MappedMemoryAddMatch(addr, val, searchtype, results, &numresults);

               val = val2;
            }

            addr++;

            break;
         }
         case SEARCHREL16BIT:
         {
            int diff;
            u32 j;
            u16 val2;
            u16 val = MappedMemoryReadWord(addr, NULL);

            for (j = 1; j < buflen; j++)
            {
               // grab the next value
               val2 = MappedMemoryReadWord(addr+(j*2), NULL);

               // figure out the diff
               diff = (int)val2 - (int)val;

               // see if there's a match             
               if (((int)buf32[j] - (int)buf32[j-1]) != diff)
                  break;

               if (j == (buflen - 1))
                  MappedMemoryAddMatch(addr, val, searchtype, results, &numresults);

               val = val2;
            }

            addr+=2;
            break;
         }
      }    

      if (addr > endaddr || numresults >= maxresults[0])
         break;
   }

   free(buf);
   maxresults[0] = numresults;
   return 1;
}

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

result_struct *MappedMemorySearch(u32 startaddr, u32 endaddr, int searchtype,
                                  const char *searchstr,
                                  result_struct *prevresults, u32 *maxresults)
{
   u32 i=0;
   result_struct *results;
   u32 numresults=0;
   unsigned long searchval = 0;
   int issigned=0;
   u32 addr;

   if ((results = (result_struct *)malloc(sizeof(result_struct) * maxresults[0])) == NULL)
      return NULL;

   switch (searchtype & 0x70)
   {
      case SEARCHSTRING:
      case SEARCHREL8BIT:
      case SEARCHREL16BIT:
      {
         // String/8-bit relative/16-bit relative search(not supported, yet)
         if (SearchString(startaddr, endaddr,  searchtype, searchstr,
                          results, maxresults) == 0)
         {
            maxresults[0] = 0;
            free(results);
            return NULL;
         }

         return results;
      }
      case SEARCHHEX:         
         sscanf(searchstr, "%08lx", &searchval);
         break;
      case SEARCHUNSIGNED:
         searchval = (unsigned long)strtoul(searchstr, NULL, 10);
         issigned = 0;
         break;
      case SEARCHSIGNED:
         searchval = (unsigned long)strtol(searchstr, NULL, 10);
         issigned = 1;
         break;
   }   

   if (prevresults)
   {
      addr = prevresults[i].addr;
      i++;
   }
   else
      addr = startaddr;

   // Regular value search
   for (;;)
   {
       u32 val=0;
       u32 newaddr;

       // Fetch byte/word/etc.
       switch (searchtype & 0x3)
       {
          case SEARCHBYTE:
             val = MappedMemoryReadByte(addr, NULL);
             // sign extend if neccessary
             if (issigned)
                val = (s8)val;

             if (SearchIncrementAndCheckBounds(prevresults, maxresults, numresults, &i, addr+1, &newaddr, endaddr))
                return results;
             break;
          case SEARCHWORD:
             val = MappedMemoryReadWord(addr, NULL);
             // sign extend if neccessary
             if (issigned)
                val = (s16)val;

             if (SearchIncrementAndCheckBounds(prevresults, maxresults, numresults, &i, addr+2, &newaddr, endaddr))
                return results;
             break;
          case SEARCHLONG:
             val = MappedMemoryReadLong(addr, NULL);

             if (SearchIncrementAndCheckBounds(prevresults, maxresults, numresults, &i, addr+4, &newaddr, endaddr))
                return results;
             break;
          default:
             maxresults[0] = 0; 
             if (results)
                free(results);
             return NULL;
       }

       // Do a comparison
       switch (searchtype & 0xC)
       {
          case SEARCHEXACT:
             if (val == searchval)
                MappedMemoryAddMatch(addr, val, searchtype, results, &numresults);
             break;
          case SEARCHLESSTHAN:
             if ((!issigned && val < searchval) || (issigned && (signed)val < (signed)searchval))
                MappedMemoryAddMatch(addr, val, searchtype, results, &numresults);
             break;
          case SEARCHGREATERTHAN:
             if ((!issigned && val > searchval) || (issigned && (signed)val > (signed)searchval))
                MappedMemoryAddMatch(addr, val, searchtype, results, &numresults);
             break;
          default:
             maxresults[0] = 0;
             if (results)
                free(results);
             return NULL;
       }

       addr = newaddr;
   }

   maxresults[0] = numresults;
   return results;
}
